From 2902436fb15c76755286ed2df444bc6f9d93af13 Mon Sep 17 00:00:00 2001 From: Stefan Johansson Date: Tue, 11 Nov 2025 13:00:22 +0000 Subject: [PATCH 001/418] 8371019: G1: Support heap expansion during startup Reviewed-by: eosterlund, tschatzl --- src/hotspot/share/gc/g1/g1CollectedHeap.cpp | 13 ++++++++----- src/hotspot/share/gc/g1/g1Policy.cpp | 17 +++++++++++++++-- src/hotspot/share/gc/g1/g1Policy.hpp | 1 + 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp index 8a5c6d4579e..9a5e390f6f1 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp @@ -185,10 +185,13 @@ G1HeapRegion* G1CollectedHeap::new_region(size_t word_size, G1HeapRegion* res = _hrm.allocate_free_region(type, node_index); if (res == nullptr && do_expand) { - // Currently, only attempts to allocate GC alloc regions set - // do_expand to true. So, we should only reach here during a - // safepoint. - assert(SafepointSynchronize::is_at_safepoint(), "invariant"); + // There are two situations where do_expand is set to true: + // - for mutator regions during initialization + // - for GC alloc regions during a safepoint + // Make sure we only reach here before initialization is complete + // or during a safepoint. + assert(!is_init_completed() || + SafepointSynchronize::is_at_safepoint() , "invariant"); log_debug(gc, ergo, heap)("Attempt heap expansion (region allocation request failed). Allocation request: %zuB", word_size * HeapWordSize); @@ -3101,7 +3104,7 @@ G1HeapRegion* G1CollectedHeap::new_mutator_alloc_region(size_t word_size, if (should_allocate) { G1HeapRegion* new_alloc_region = new_region(word_size, G1HeapRegionType::Eden, - false /* do_expand */, + policy()->should_expand_on_mutator_allocation() /* do_expand */, node_index); if (new_alloc_region != nullptr) { new_alloc_region->set_eden(); diff --git a/src/hotspot/share/gc/g1/g1Policy.cpp b/src/hotspot/share/gc/g1/g1Policy.cpp index fe754020e0c..ddcdfb0c3a4 100644 --- a/src/hotspot/share/gc/g1/g1Policy.cpp +++ b/src/hotspot/share/gc/g1/g1Policy.cpp @@ -1187,8 +1187,21 @@ double G1Policy::predict_region_code_root_scan_time(G1HeapRegion* hr, bool for_y } bool G1Policy::should_allocate_mutator_region() const { - uint young_list_length = _g1h->young_regions_count(); - return young_list_length < young_list_target_length(); + if (_g1h->young_regions_count() < young_list_target_length()) { + return true; + } + + if (should_expand_on_mutator_allocation()) { + return true; + } + + return false; +} + +bool G1Policy::should_expand_on_mutator_allocation() const { + // We can't do a GC during init so allow additional mutator + // allocations until we can GC. + return !is_init_completed(); } bool G1Policy::use_adaptive_young_list_length() const { diff --git a/src/hotspot/share/gc/g1/g1Policy.hpp b/src/hotspot/share/gc/g1/g1Policy.hpp index eb4a84b519d..93724657235 100644 --- a/src/hotspot/share/gc/g1/g1Policy.hpp +++ b/src/hotspot/share/gc/g1/g1Policy.hpp @@ -367,6 +367,7 @@ public: uint young_list_target_length() const { return AtomicAccess::load(&_young_list_target_length); } bool should_allocate_mutator_region() const; + bool should_expand_on_mutator_allocation() const; bool use_adaptive_young_list_length() const; From cbd77fc9f3e6c8f1e996b30afe208c6a074cce3a Mon Sep 17 00:00:00 2001 From: Martin Doerr Date: Tue, 11 Nov 2025 14:26:58 +0000 Subject: [PATCH 002/418] 8370244: [PPC64] Several vector tests fail on Power8 Reviewed-by: dbriemann, rrich --- src/hotspot/cpu/ppc/vm_version_ppc.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/hotspot/cpu/ppc/vm_version_ppc.cpp b/src/hotspot/cpu/ppc/vm_version_ppc.cpp index 8b1de754650..75feb389298 100644 --- a/src/hotspot/cpu/ppc/vm_version_ppc.cpp +++ b/src/hotspot/cpu/ppc/vm_version_ppc.cpp @@ -111,6 +111,10 @@ void VM_Version::initialize() { } MaxVectorSize = SuperwordUseVSX ? 16 : 8; + if (!SuperwordUseVSX && FLAG_IS_DEFAULT(EnableVectorSupport)) { + // VectorSupport intrinsics currently have issues with MaxVectorSize < 16 (JDK-8370803). + FLAG_SET_ERGO(EnableVectorSupport, false); + } if (FLAG_IS_DEFAULT(AlignVector)) { FLAG_SET_ERGO(AlignVector, false); } From 405d5f7a6892426d69409c3975d0c808304b8438 Mon Sep 17 00:00:00 2001 From: Hamlin Li Date: Tue, 11 Nov 2025 14:56:20 +0000 Subject: [PATCH 003/418] 8371297: C2: assert triggered in BoolTest::BoolTest Reviewed-by: dlong, luhenry, epeter --- src/hotspot/share/opto/vtransform.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/hotspot/share/opto/vtransform.cpp b/src/hotspot/share/opto/vtransform.cpp index 9fd6ad1089c..c617f8415e4 100644 --- a/src/hotspot/share/opto/vtransform.cpp +++ b/src/hotspot/share/opto/vtransform.cpp @@ -1586,8 +1586,9 @@ void VTransformReinterpretVectorNode::print_spec() const { void VTransformBoolVectorNode::print_spec() const { VTransformVectorNode::print_spec(); - const BoolTest bt(_test._mask); - tty->print(" test="); + BoolTest::mask m = BoolTest::mask(_test._mask & ~BoolTest::unsigned_compare); + const BoolTest bt(m); + tty->print(" test=%s", m == _test._mask ? "" : "unsigned "); bt.dump_on(tty); } #endif From bbeb6bf0ac8952feaf8afc9c9b25a9a372c2c798 Mon Sep 17 00:00:00 2001 From: Ashutosh Mehra Date: Tue, 11 Nov 2025 15:07:10 +0000 Subject: [PATCH 004/418] 8371493: Simplify search for AdapterHandlerEntry Reviewed-by: kvn, adinn --- src/hotspot/share/code/codeBlob.cpp | 3 +- src/hotspot/share/runtime/sharedRuntime.cpp | 34 ++------------------- src/hotspot/share/runtime/sharedRuntime.hpp | 1 - 3 files changed, 4 insertions(+), 34 deletions(-) diff --git a/src/hotspot/share/code/codeBlob.cpp b/src/hotspot/share/code/codeBlob.cpp index 18cdb6161fc..a0a34ec23fa 100644 --- a/src/hotspot/share/code/codeBlob.cpp +++ b/src/hotspot/share/code/codeBlob.cpp @@ -870,9 +870,10 @@ void CodeBlob::dump_for_addr(address addr, outputStream* st, bool verbose) const return; } // - if (AdapterHandlerLibrary::contains(this)) { + if (is_adapter_blob()) { st->print_cr(INTPTR_FORMAT " is at code_begin+%d in an AdapterHandler", p2i(addr), (int)(addr - code_begin())); AdapterHandlerLibrary::print_handler_on(st, this); + return; } // the stubroutines are generated into a buffer blob StubCodeDesc* d = StubCodeDesc::desc_for(addr); diff --git a/src/hotspot/share/runtime/sharedRuntime.cpp b/src/hotspot/share/runtime/sharedRuntime.cpp index 835bbc0ae2f..79c7c0b32b4 100644 --- a/src/hotspot/share/runtime/sharedRuntime.cpp +++ b/src/hotspot/share/runtime/sharedRuntime.cpp @@ -3396,42 +3396,12 @@ uint32_t AdapterHandlerLibrary::id(AdapterHandlerEntry* handler) { return handler->id(); } -bool AdapterHandlerLibrary::contains(const CodeBlob* b) { - bool found = false; -#if INCLUDE_CDS - if (AOTCodeCache::is_using_adapter()) { - auto findblob_archived_table = [&] (AdapterHandlerEntry* handler) { - if (b == CodeCache::find_blob(handler->get_i2c_entry())) { - found = true; - return false; // abort iteration - } else { - return true; // keep looking - } - }; - _aot_adapter_handler_table.iterate(findblob_archived_table); - } -#endif // INCLUDE_CDS - if (!found) { - auto findblob_runtime_table = [&] (AdapterFingerPrint* key, AdapterHandlerEntry* handler) { - if (b == CodeCache::find_blob(handler->get_i2c_entry())) { - found = true; - return false; // abort iteration - } else { - return true; // keep looking - } - }; - assert_locked_or_safepoint(AdapterHandlerLibrary_lock); - _adapter_handler_table->iterate(findblob_runtime_table); - } - return found; -} - void AdapterHandlerLibrary::print_handler_on(outputStream* st, const CodeBlob* b) { bool found = false; #if INCLUDE_CDS if (AOTCodeCache::is_using_adapter()) { auto findblob_archived_table = [&] (AdapterHandlerEntry* handler) { - if (b == CodeCache::find_blob(handler->get_i2c_entry())) { + if (b == handler->adapter_blob()) { found = true; st->print("Adapter for signature: "); handler->print_adapter_on(st); @@ -3445,7 +3415,7 @@ void AdapterHandlerLibrary::print_handler_on(outputStream* st, const CodeBlob* b #endif // INCLUDE_CDS if (!found) { auto findblob_runtime_table = [&] (AdapterFingerPrint* key, AdapterHandlerEntry* handler) { - if (b == CodeCache::find_blob(handler->get_i2c_entry())) { + if (b == handler->adapter_blob()) { found = true; st->print("Adapter for signature: "); handler->print_adapter_on(st); diff --git a/src/hotspot/share/runtime/sharedRuntime.hpp b/src/hotspot/share/runtime/sharedRuntime.hpp index f4ff51504f0..a696ce5a71b 100644 --- a/src/hotspot/share/runtime/sharedRuntime.hpp +++ b/src/hotspot/share/runtime/sharedRuntime.hpp @@ -866,7 +866,6 @@ class AdapterHandlerLibrary: public AllStatic { static void print_handler(const CodeBlob* b) { print_handler_on(tty, b); } static void print_handler_on(outputStream* st, const CodeBlob* b); - static bool contains(const CodeBlob* b); static const char* name(AdapterHandlerEntry* handler); static uint32_t id(AdapterHandlerEntry* handler); #ifndef PRODUCT From f5eacbeb5fc58c1bd844d709fe92621ce3689d78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Maillard?= Date: Tue, 11 Nov 2025 16:33:15 +0000 Subject: [PATCH 005/418] 8371534: C2: Missed Ideal optimization opportunity with AndL and URShiftL Reviewed-by: thartmann, mhaessig --- src/hotspot/share/opto/phaseX.cpp | 5 ++- .../compiler/c2/TestMaskAndRShiftReorder.java | 37 ++++++++++++++++--- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/src/hotspot/share/opto/phaseX.cpp b/src/hotspot/share/opto/phaseX.cpp index 5fcb2a62682..8f192cf069c 100644 --- a/src/hotspot/share/opto/phaseX.cpp +++ b/src/hotspot/share/opto/phaseX.cpp @@ -2550,11 +2550,12 @@ void PhaseIterGVN::add_users_of_use_to_worklist(Node* n, Node* use, Unique_Node_ } } } - // If changed AndI/AndL inputs, check RShift users for "(x & mask) >> shift" optimization opportunity + // If changed AndI/AndL inputs, check RShift/URShift users for "(x & mask) >> shift" optimization opportunity if (use_op == Op_AndI || use_op == Op_AndL) { for (DUIterator_Fast i2max, i2 = use->fast_outs(i2max); i2 < i2max; i2++) { Node* u = use->fast_out(i2); - if (u->Opcode() == Op_RShiftI || u->Opcode() == Op_RShiftL) { + if (u->Opcode() == Op_RShiftI || u->Opcode() == Op_RShiftL || + u->Opcode() == Op_URShiftI || u->Opcode() == Op_URShiftL) { worklist.push(u); } } diff --git a/test/hotspot/jtreg/compiler/c2/TestMaskAndRShiftReorder.java b/test/hotspot/jtreg/compiler/c2/TestMaskAndRShiftReorder.java index bbb80e2c120..3ebb8e5bf68 100644 --- a/test/hotspot/jtreg/compiler/c2/TestMaskAndRShiftReorder.java +++ b/test/hotspot/jtreg/compiler/c2/TestMaskAndRShiftReorder.java @@ -23,12 +23,12 @@ /* * @test - * @bug 8361700 + * @bug 8361700 8371534 * @summary An expression of the form "(x & mask) >> shift", where the mask * is a constant, should be transformed to "(x >> shift) & (mask >> shift)" * VerifyIterativeGVN checks that this optimization was applied * @run main/othervm -Xcomp -XX:+IgnoreUnrecognizedVMOptions - * -XX:CompileCommand=compileonly,compiler.c2.TestMaskAndRShiftReorder::test + * -XX:CompileCommand=compileonly,compiler.c2.TestMaskAndRShiftReorder::test* * -XX:VerifyIterativeGVN=1110 compiler.c2.TestMaskAndRShiftReorder * @run main compiler.c2.TestMaskAndRShiftReorder * @@ -41,13 +41,15 @@ public class TestMaskAndRShiftReorder { public static void main(String[] strArr) { - test(); + // No known reproducer with URShiftI so far + testRShiftI(); + testRShiftL(); + testURShiftL(); } - static long test() { + static void testRShiftI() { int x = 10; int y = -17; - int iArr[] = new int[10]; for (int i = 1; i < 7; i++) { for (int j = 1; j < 2; j++) { x <<= lFld; @@ -55,6 +57,29 @@ public class TestMaskAndRShiftReorder { y &= x; y >>= 1; } - return iArr.length; + } + + static void testRShiftL() { + long x = 10; + long y = -17L; + for (int i = 1; i < 7; i++) { + for (int j = 1; j < 2; j++) { + x <<= lFld; + } + y &= x; + y >>= 1; + } + } + + static void testURShiftL() { + long x = 10; + long y = -17L; + for (int i = 1; i < 7; i++) { + for (int j = 1; j < 2; j++) { + x <<= lFld; + } + y &= x; + y >>>= 1; + } } } From c6a8027b94bbcbde5f7dcabd0bff48b93bbb5a7f Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Tue, 11 Nov 2025 17:11:44 +0000 Subject: [PATCH 006/418] 8370154: Update @jls and @jvms taglets to point to local specs dir Reviewed-by: liach --- make/Docs.gmk | 3 +- .../src/classes/build/tools/taglet/JSpec.java | 61 +++++++++++++------ .../classes/build/tools/taglet/ToolGuide.java | 8 ++- 3 files changed, 49 insertions(+), 23 deletions(-) diff --git a/make/Docs.gmk b/make/Docs.gmk index 8b20572cb03..b105e1d8683 100644 --- a/make/Docs.gmk +++ b/make/Docs.gmk @@ -291,8 +291,7 @@ define SetupApiDocsGenerationBody $1_INDIRECT_EXPORTS := $$(call FindTransitiveIndirectDepsForModules, $$($1_MODULES)) $1_ALL_MODULES := $$(sort $$($1_MODULES) $$($1_INDIRECT_EXPORTS)) - $1_JAVA_ARGS := -Dextlink.spec.version=$$(VERSION_SPECIFICATION) \ - -Djspec.version=$$(VERSION_SPECIFICATION) + $1_JAVA_ARGS := -Dextlink.spec.version=$$(VERSION_SPECIFICATION) ifeq ($$(ENABLE_FULL_DOCS), true) $1_SEALED_GRAPHS_DIR := $$(SUPPORT_OUTPUTDIR)/docs/$1-sealed-graphs diff --git a/make/jdk/src/classes/build/tools/taglet/JSpec.java b/make/jdk/src/classes/build/tools/taglet/JSpec.java index 34cc2bd6b50..ca45104e086 100644 --- a/make/jdk/src/classes/build/tools/taglet/JSpec.java +++ b/make/jdk/src/classes/build/tools/taglet/JSpec.java @@ -33,6 +33,8 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; import javax.lang.model.element.Element; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; import com.sun.source.doctree.DocTree; import com.sun.source.doctree.LiteralTree; @@ -44,7 +46,7 @@ import jdk.javadoc.doclet.Taglet; import static com.sun.source.doctree.DocTree.Kind.*; /** - * A base class for block tags to insert a link to an external copy of JLS or JVMS. + * A base class for block tags to insert a link to a local copy of JLS or JVMS. * The tags can be used as follows: * *
@@ -57,30 +59,23 @@ import static com.sun.source.doctree.DocTree.Kind.*;
  * @jls 3.4 Line Terminators
  * 
* - * will produce the following HTML for a docs build configured for Java SE 12. + * will produce the following HTML, depending on the file containing + * the tag. * *
{@code
  * 
See Java Language Specification: - *
3.4 Line terminators + *
3.4 Line terminators * }
* - * The version of the spec must be set in the jspec.version system property. + * Copies of JLS and JVMS are expected to have been placed in the {@code specs} + * folder. These documents are not included in open-source repositories. */ public class JSpec implements Taglet { - static final String SPEC_VERSION; - - static { - SPEC_VERSION = System.getProperty("jspec.version"); - if (SPEC_VERSION == null) { - throw new RuntimeException("jspec.version property not set"); - } - } public static class JLS extends JSpec { public JLS() { super("jls", "Java Language Specification", - "https://docs.oracle.com/javase/specs/jls/se" + SPEC_VERSION + "/html", "jls"); } } @@ -89,20 +84,17 @@ public class JSpec implements Taglet { public JVMS() { super("jvms", "Java Virtual Machine Specification", - "https://docs.oracle.com/javase/specs/jvms/se" + SPEC_VERSION + "/html", "jvms"); } } private String tagName; private String specTitle; - private String baseURL; private String idPrefix; - JSpec(String tagName, String specTitle, String baseURL, String idPrefix) { + JSpec(String tagName, String specTitle, String idPrefix) { this.tagName = tagName; this.specTitle = specTitle; - this.baseURL = baseURL; this.idPrefix = idPrefix; } @@ -169,8 +161,8 @@ public class JSpec implements Taglet { String chapter = m.group("chapter"); String section = m.group("section"); - String url = String.format("%1$s/%2$s-%3$s.html#%2$s-%3$s%4$s", - baseURL, idPrefix, chapter, section); + String url = String.format("%1$s/../specs/%2$s/%2$s-%3$s.html#%2$s-%3$s%4$s", + docRoot(elem), idPrefix, chapter, section); sb.append(" Date: Tue, 11 Nov 2025 21:07:34 +0000 Subject: [PATCH 007/418] 8358735: GenShen: block_start() may be incorrect after class unloading Co-authored-by: Y. Srinivas Ramakrishna Reviewed-by: wkemper --- .../share/gc/shenandoah/shenandoahHeap.hpp | 1 + .../gc/shenandoah/shenandoahMarkBitMap.cpp | 20 + .../gc/shenandoah/shenandoahMarkBitMap.hpp | 24 +- .../shenandoahMarkBitMap.inline.hpp | 90 +++ .../shenandoah/shenandoahMarkingContext.hpp | 4 + .../shenandoahMarkingContext.inline.hpp | 4 + .../shenandoah/shenandoahScanRemembered.cpp | 141 ++++- .../shenandoah/shenandoahScanRemembered.hpp | 33 +- .../shenandoahScanRemembered.inline.hpp | 34 +- .../shenandoah/test_shenandoahMarkBitMap.cpp | 571 ++++++++++++++++++ .../test_shenandoahOldHeuristic.cpp | 2 +- .../gc/shenandoah/compiler/TestClone.java | 1 + 12 files changed, 883 insertions(+), 42 deletions(-) create mode 100644 test/hotspot/gtest/gc/shenandoah/test_shenandoahMarkBitMap.cpp diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp index e5961efd36e..cd388ee7cf3 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp @@ -270,6 +270,7 @@ private: public: inline HeapWord* base() const { return _heap_region.start(); } + inline HeapWord* end() const { return _heap_region.end(); } inline size_t num_regions() const { return _num_regions; } inline bool is_heap_region_special() { return _heap_region_special; } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahMarkBitMap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahMarkBitMap.cpp index 34e6af41b42..5318f38d8ef 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahMarkBitMap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahMarkBitMap.cpp @@ -57,6 +57,26 @@ bool ShenandoahMarkBitMap::is_bitmap_clear_range(const HeapWord* start, const He return (result == end); } +HeapWord* ShenandoahMarkBitMap::get_prev_marked_addr(const HeapWord* limit, + const HeapWord* addr) const { +#ifdef ASSERT + ShenandoahHeap* heap = ShenandoahHeap::heap(); + ShenandoahHeapRegion* r = heap->heap_region_containing(addr); + ShenandoahMarkingContext* ctx = heap->marking_context(); + HeapWord* tams = ctx->top_at_mark_start(r); + assert(limit != nullptr, "limit must not be null"); + assert(limit >= r->bottom(), "limit must be more than bottom"); + assert(addr <= tams, "addr must be less than TAMS"); +#endif + + // Round addr down to a possible object boundary to be safe. + size_t const addr_offset = address_to_index(align_down(addr, HeapWordSize << LogMinObjAlignment)); + size_t const limit_offset = address_to_index(limit); + size_t const last_offset = get_prev_one_offset(limit_offset, addr_offset); + + // cast required to remove const-ness of the value pointed to. We won't modify that object, but my caller might. + return (last_offset > addr_offset)? (HeapWord*) addr + 1: index_to_address(last_offset); +} HeapWord* ShenandoahMarkBitMap::get_next_marked_addr(const HeapWord* addr, const HeapWord* limit) const { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahMarkBitMap.hpp b/src/hotspot/share/gc/shenandoah/shenandoahMarkBitMap.hpp index c094ec434f5..73bf3ecbeea 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahMarkBitMap.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahMarkBitMap.hpp @@ -119,9 +119,21 @@ private: template inline idx_t get_next_bit_impl(idx_t l_index, idx_t r_index) const; - inline idx_t get_next_one_offset (idx_t l_index, idx_t r_index) const; + // Helper for get_prev_{zero,one}_bit variants. + // - flip designates whether searching for 1s or 0s. Must be one of + // find_{zeros,ones}_flip. + // - aligned_left is true if l_index is a priori on a bm_word_t boundary. + template + inline idx_t get_prev_bit_impl(idx_t l_index, idx_t r_index) const; - void clear_large_range (idx_t beg, idx_t end); + // Search for the first marked address in the range [l_index, r_index), or r_index if none found. + inline idx_t get_next_one_offset(idx_t l_index, idx_t r_index) const; + + // Search for last one in the range [l_index, r_index). Return r_index if not found. + inline idx_t get_prev_one_offset(idx_t l_index, idx_t r_index) const; + + // Clear the strong and weak mark bits for all index positions >= l_index and < r_index. + void clear_large_range(idx_t beg, idx_t end); // Verify bit is less than size(). void verify_index(idx_t bit) const NOT_DEBUG_RETURN; @@ -162,12 +174,14 @@ public: bool is_bitmap_clear_range(const HeapWord* start, const HeapWord* end) const; - // Return the address corresponding to the next marked bit at or after - // "addr", and before "limit", if "limit" is non-null. If there is no - // such bit, returns "limit" if that is non-null, or else "endWord()". + // Return the first marked address in the range [addr, limit), or limit if none found. HeapWord* get_next_marked_addr(const HeapWord* addr, const HeapWord* limit) const; + // Return the last marked address in the range [limit, addr], or addr+1 if none found. + HeapWord* get_prev_marked_addr(const HeapWord* limit, + const HeapWord* addr) const; + bm_word_t inverted_bit_mask_for_range(idx_t beg, idx_t end) const; void clear_range_within_word (idx_t beg, idx_t end); void clear_range (idx_t beg, idx_t end); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahMarkBitMap.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahMarkBitMap.inline.hpp index 3bea8d73959..ae56db810bb 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahMarkBitMap.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahMarkBitMap.inline.hpp @@ -29,6 +29,7 @@ #include "gc/shenandoah/shenandoahMarkBitMap.hpp" #include "runtime/atomicAccess.hpp" +#include "utilities/count_leading_zeros.hpp" #include "utilities/count_trailing_zeros.hpp" inline size_t ShenandoahMarkBitMap::address_to_index(const HeapWord* addr) const { @@ -169,10 +170,99 @@ inline ShenandoahMarkBitMap::idx_t ShenandoahMarkBitMap::get_next_bit_impl(idx_t return r_index; } +template +inline ShenandoahMarkBitMap::idx_t ShenandoahMarkBitMap::get_prev_bit_impl(idx_t l_index, idx_t r_index) const { + STATIC_ASSERT(flip == find_ones_flip || flip == find_zeros_flip); + verify_range(l_index, r_index); + assert(!aligned_left || is_aligned(l_index, BitsPerWord), "l_index not aligned"); + + // The first word often contains an interesting bit, either due to + // density or because of features of the calling algorithm. So it's + // important to examine that first word with a minimum of fuss, + // minimizing setup time for later words that will be wasted if the + // first word is indeed interesting. + + // The benefit from aligned_left being true is relatively small. + // It saves an operation in the setup for the word search loop. + // It also eliminates the range check on the final result. + // However, callers often have a comparison with l_index, and + // inlining often allows the two comparisons to be combined; it is + // important when !aligned_left that return paths either return + // l_index or a value dominating a comparison with l_index. + // aligned_left is still helpful when the caller doesn't have a + // range check because features of the calling algorithm guarantee + // an interesting bit will be present. + + if (l_index < r_index) { + // Get the word containing r_index, and shift out the high-order bits (representing objects that come after r_index) + idx_t index = to_words_align_down(r_index); + assert(BitsPerWord - 2 >= bit_in_word(r_index), "sanity"); + size_t shift = BitsPerWord - 2 - bit_in_word(r_index); + bm_word_t cword = (map(index) ^ flip) << shift; + // After this shift, the highest order bits correspond to r_index. + + // We give special handling if either of the two most significant bits (Weak or Strong) is set. With 64-bit + // words, the mask of interest is 0xc000_0000_0000_0000. Symbolically, this constant is represented by: + const bm_word_t first_object_mask = ((bm_word_t) 0x3) << (BitsPerWord - 2); + if ((cword & first_object_mask) != 0) { + // The first object is similarly often interesting. When it matters + // (density or features of the calling algorithm make it likely + // the first bit is set), going straight to the next clause compares + // poorly with doing this check first; count_leading_zeros can be + // relatively expensive, plus there is the additional range check. + // But when the first bit isn't set, the cost of having tested for + // it is relatively small compared to the rest of the search. + return r_index; + } else if (cword != 0) { + // Note that there are 2 bits corresponding to every index value (Weak and Strong), and every odd index value + // corresponds to the same object as index-1 + // Flipped and shifted first word is non-zero. If leading_zeros is 0 or 1, we return r_index (above). + // if leading zeros is 2 or 3, we return (r_index - 1) or (r_index - 2), and so forth + idx_t result = r_index + 1 - count_leading_zeros(cword); + if (aligned_left || (result >= l_index)) return result; + else { + // Sentinel value means no object found within specified range. + return r_index + 2; + } + } else { + // Flipped and shifted first word is zero. Word search through + // aligned up r_index for a non-zero flipped word. + idx_t limit = aligned_left + ? to_words_align_down(l_index) // Minuscule savings when aligned. + : to_words_align_up(l_index); + // Unsigned index is always >= unsigned limit if limit equals zero, so test for strictly greater than before decrement. + while (index-- > limit) { + cword = map(index) ^ flip; + if (cword != 0) { + // cword hods bits: + // 0x03 for the object corresponding to index (and index+1) (count_leading_zeros is 62 or 63) + // 0x0c for the object corresponding to index + 2 (and index+3) (count_leading_zeros is 60 or 61) + // and so on. + idx_t result = bit_index(index + 1) - (count_leading_zeros(cword) + 1); + if (aligned_left || (result >= l_index)) return result; + else { + // Sentinel value means no object found within specified range. + return r_index + 2; + } + } + } + // No bits in range; return r_index+2. + return r_index + 2; + } + } + else { + return r_index + 2; + } +} + inline ShenandoahMarkBitMap::idx_t ShenandoahMarkBitMap::get_next_one_offset(idx_t l_offset, idx_t r_offset) const { return get_next_bit_impl(l_offset, r_offset); } +inline ShenandoahMarkBitMap::idx_t ShenandoahMarkBitMap::get_prev_one_offset(idx_t l_offset, idx_t r_offset) const { + return get_prev_bit_impl(l_offset, r_offset); +} + // Returns a bit mask for a range of bits [beg, end) within a single word. Each // bit in the mask is 0 if the bit is in the range, 1 if not in the range. The // returned mask can be used directly to clear the range, or inverted to set the diff --git a/src/hotspot/share/gc/shenandoah/shenandoahMarkingContext.hpp b/src/hotspot/share/gc/shenandoah/shenandoahMarkingContext.hpp index 8a52042e513..d8e0c74ea4e 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahMarkingContext.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahMarkingContext.hpp @@ -67,8 +67,12 @@ public: inline bool is_marked_or_old(oop obj) const; inline bool is_marked_strong_or_old(oop obj) const; + // Return address of the first marked address in the range [addr,limit), or limit if no marked object found inline HeapWord* get_next_marked_addr(const HeapWord* addr, const HeapWord* limit) const; + // Return address of the last marked object in range [limit, start], returning start+1 if no marked object found + inline HeapWord* get_prev_marked_addr(const HeapWord* limit, const HeapWord* start) const; + inline bool allocated_after_mark_start(const oop obj) const; inline bool allocated_after_mark_start(const HeapWord* addr) const; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahMarkingContext.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahMarkingContext.inline.hpp index bff4afc9ce9..637dbf47c3f 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahMarkingContext.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahMarkingContext.inline.hpp @@ -72,6 +72,10 @@ inline HeapWord* ShenandoahMarkingContext::get_next_marked_addr(const HeapWord* return _mark_bit_map.get_next_marked_addr(start, limit); } +inline HeapWord* ShenandoahMarkingContext::get_prev_marked_addr(const HeapWord* limit, const HeapWord* start) const { + return _mark_bit_map.get_prev_marked_addr(limit, start); +} + inline bool ShenandoahMarkingContext::allocated_after_mark_start(oop obj) const { const HeapWord* addr = cast_from_oop(obj); return allocated_after_mark_start(addr); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.cpp b/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.cpp index af6cd6d39ab..3a99023eca4 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.cpp @@ -137,9 +137,8 @@ void ShenandoahDirectCardMarkRememberedSet::mark_write_table_as_clean() { // No lock required because arguments align with card boundaries. void ShenandoahCardCluster::reset_object_range(HeapWord* from, HeapWord* to) { - assert(((((unsigned long long) from) & (CardTable::card_size() - 1)) == 0) && - ((((unsigned long long) to) & (CardTable::card_size() - 1)) == 0), - "reset_object_range bounds must align with card boundaries"); + assert(CardTable::is_card_aligned(from) && CardTable::is_card_aligned(to), + "Must align with card boundaries"); size_t card_at_start = _rs->card_index_for_addr(from); size_t num_cards = (to - from) / CardTable::card_size_in_words(); @@ -234,31 +233,86 @@ size_t ShenandoahCardCluster::get_last_start(size_t card_index) const { return _object_starts[card_index].offsets.last; } -// Given a card_index, return the starting address of the first block in the heap -// that straddles into this card. If this card is co-initial with an object, then -// this would return the first address of the range that this card covers, which is -// where the card's first object also begins. -HeapWord* ShenandoahCardCluster::block_start(const size_t card_index) const { +HeapWord* ShenandoahCardCluster::first_object_start(const size_t card_index, const ShenandoahMarkingContext* const ctx, + HeapWord* tams, HeapWord* end_range_of_interest) const { HeapWord* left = _rs->addr_for_card_index(card_index); + assert(left < end_range_of_interest, "No meaningful work to do"); + ShenandoahHeapRegion* region = ShenandoahHeap::heap()->heap_region_containing(left); #ifdef ASSERT assert(ShenandoahHeap::heap()->mode()->is_generational(), "Do not use in non-generational mode"); - ShenandoahHeapRegion* region = ShenandoahHeap::heap()->heap_region_containing(left); assert(region->is_old(), "Do not use for young regions"); // For HumongousRegion:s it's more efficient to jump directly to the // start region. assert(!region->is_humongous(), "Use region->humongous_start_region() instead"); #endif + + HeapWord* right = MIN2(region->top(), end_range_of_interest); + HeapWord* end_of_search_next = MIN2(right, tams); + size_t last_relevant_card_index; + if (end_range_of_interest == _end_of_heap) { + last_relevant_card_index = _rs->card_index_for_addr(end_range_of_interest - 1); + } else { + last_relevant_card_index = _rs->card_index_for_addr(end_range_of_interest); + if (_rs->addr_for_card_index(last_relevant_card_index) == end_range_of_interest) { + last_relevant_card_index--; + } + } + assert(card_index <= last_relevant_card_index, "sanity: card_index: %zu, last_relevant: %zu, left: " PTR_FORMAT + ", end_of_range: " PTR_FORMAT, card_index, last_relevant_card_index, p2i(left), p2i(end_range_of_interest)); + + // if marking context is valid and we are below tams, we use the marking bit map to find the first marked object that + // intersects with this card. If no such object exists, we return the first marked object that follows the start + // of this card's memory range if such an object is found at or before last_relevant_card_index. If there are no + // marked objects in this range, we return nullptr. + if ((ctx != nullptr) && (left < tams)) { + if (ctx->is_marked(left)) { + oop obj = cast_to_oop(left); + assert(oopDesc::is_oop(obj), "Should be an object"); + return left; + } + // get the previous marked object, if any + if (region->bottom() < left) { + // In the case that this region was most recently marked as young, the fact that this region has been promoted in place + // denotes that final mark (Young) has completed. In the case that this region was most recently marked as old, the + // fact that (ctx != nullptr) denotes that old marking has completed. Otherwise, ctx would equal null. + HeapWord* prev = ctx->get_prev_marked_addr(region->bottom(), left - 1); + if (prev < left) { + oop obj = cast_to_oop(prev); + assert(oopDesc::is_oop(obj), "Should be an object"); + HeapWord* obj_end = prev + obj->size(); + if (obj_end > left) { + return prev; + } + } + } + // Either prev >= left (no previous object found), or the previous object that was found ends before my card range begins. + // In eiher case, find the next marked object if any on this or a following card + assert(!ctx->is_marked(left), "Was dealt with above"); + assert(right > left, "We don't expect to be examining cards above the smaller of TAMS or top"); + HeapWord* next = ctx->get_next_marked_addr(left, end_of_search_next); + // If end_of_search_next < right, we may return tams here, which is "marked" by default + if (next < right) { + oop obj = cast_to_oop(next); + assert(oopDesc::is_oop(obj), "Should be an object"); + return next; + } else { + return nullptr; + } + } + + assert((ctx == nullptr) || (left >= tams), "Should have returned above"); + + // The following code assumes that all data in region at or above left holds parsable objects + assert((left >= tams) || ShenandoahGenerationalHeap::heap()->old_generation()->is_parsable(), + "The code that follows expects a parsable heap"); if (starts_object(card_index) && get_first_start(card_index) == 0) { - // This card contains a co-initial object; a fortiori, it covers - // also the case of a card being the first in a region. + // This card contains a co-initial object; a fortiori, it covers also the case of a card being the first in a region. assert(oopDesc::is_oop(cast_to_oop(left)), "Should be an object"); return left; } - HeapWord* p = nullptr; - oop obj = cast_to_oop(p); ssize_t cur_index = (ssize_t)card_index; assert(cur_index >= 0, "Overflow"); assert(cur_index > 0, "Should have returned above"); @@ -268,37 +322,80 @@ HeapWord* ShenandoahCardCluster::block_start(const size_t card_index) const { } // cur_index should start an object: we should not have walked // past the left end of the region. - assert(cur_index >= 0 && (cur_index <= (ssize_t)card_index), "Error"); + assert(cur_index >= 0 && (cur_index <= (ssize_t) card_index), "Error"); assert(region->bottom() <= _rs->addr_for_card_index(cur_index), "Fell off the bottom of containing region"); assert(starts_object(cur_index), "Error"); size_t offset = get_last_start(cur_index); // can avoid call via card size arithmetic below instead - p = _rs->addr_for_card_index(cur_index) + offset; + HeapWord* p = _rs->addr_for_card_index(cur_index) + offset; + if ((ctx != nullptr) && (p < tams)) { + if (ctx->is_marked(p)) { + oop obj = cast_to_oop(p); + assert(oopDesc::is_oop(obj), "Should be an object"); + assert(Klass::is_valid(obj->klass()), "Not a valid klass ptr"); + assert(p + obj->size() > left, "This object should span start of card"); + assert(p < right, "Result must precede right"); + return p; + } else { + // Object that spans start of card is dead, so should not be scanned + assert((ctx == nullptr) || (left + get_first_start(card_index) >= tams), "Should have handled this case above"); + if (starts_object(card_index)) { + assert(left + get_first_start(card_index) < right, "Result must precede right"); + return left + get_first_start(card_index); + } else { + // Spanning object is dead and this card does not start an object, so the start object is in some card that follows + size_t following_card_index = card_index; + do { + following_card_index++; + if (following_card_index > last_relevant_card_index) { + return nullptr; + } + } while (!starts_object(following_card_index)); + assert(_rs->addr_for_card_index(following_card_index) + get_first_start(following_card_index), + "Result must precede right"); + return _rs->addr_for_card_index(following_card_index) + get_first_start(following_card_index); + } + } + } + // Recall that we already dealt with the co-initial object case above assert(p < left, "obj should start before left"); // While it is safe to ask an object its size in the loop that // follows, the (ifdef'd out) loop should never be needed. - // 1. we ask this question only for regions in the old generation + // 1. we ask this question only for regions in the old generation, and those + // that are not humongous regions // 2. there is no direct allocation ever by mutators in old generation - // regions. Only GC will ever allocate in old regions, and then - // too only during promotion/evacuation phases. Thus there is no danger + // regions walked by this code. Only GC will ever allocate in old regions, + // and then too only during promotion/evacuation phases. Thus there is no danger // of races between reading from and writing to the object start array, // or of asking partially initialized objects their size (in the loop below). + // Furthermore, humongous regions (and their dirty cards) are never processed + // by this code. // 3. only GC asks this question during phases when it is not concurrently // evacuating/promoting, viz. during concurrent root scanning (before // the evacuation phase) and during concurrent update refs (after the // evacuation phase) of young collections. This is never called - // during old or global collections. + // during global collections during marking or update refs.. // 4. Every allocation under TAMS updates the object start array. - NOT_PRODUCT(obj = cast_to_oop(p);) + oop obj = cast_to_oop(p); assert(oopDesc::is_oop(obj), "Should be an object"); +#ifdef ASSERT +#define WALK_FORWARD_IN_BLOCK_START true +#else #define WALK_FORWARD_IN_BLOCK_START false +#endif // ASSERT while (WALK_FORWARD_IN_BLOCK_START && p + obj->size() < left) { p += obj->size(); + obj = cast_to_oop(p); + assert(oopDesc::is_oop(obj), "Should be an object"); + assert(Klass::is_valid(obj->klass()), "Not a valid klass ptr"); + // Check assumptions in previous block comment if this assert fires + guarantee(false, "Should never need forward walk in block start"); } -#undef WALK_FORWARD_IN_BLOCK_START // false - assert(p + obj->size() > left, "obj should end after left"); +#undef WALK_FORWARD_IN_BLOCK_START + assert(p <= left, "p should start at or before left end of card"); + assert(p + obj->size() > left, "obj should end after left end of card"); return p; } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.hpp b/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.hpp index d04a768530f..9a0d28d2cb7 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.hpp @@ -353,6 +353,7 @@ class ShenandoahCardCluster: public CHeapObj { private: ShenandoahDirectCardMarkRememberedSet* _rs; + const HeapWord* _end_of_heap; public: static const size_t CardsPerCluster = 64; @@ -406,6 +407,7 @@ public: ShenandoahCardCluster(ShenandoahDirectCardMarkRememberedSet* rs) { _rs = rs; + _end_of_heap = ShenandoahHeap::heap()->end(); _object_starts = NEW_C_HEAP_ARRAY(crossing_info, rs->total_cards() + 1, mtGC); // the +1 is to account for card table guarding entry for (size_t i = 0; i < rs->total_cards(); i++) { _object_starts[i].short_word = 0; @@ -652,12 +654,31 @@ public: size_t get_last_start(size_t card_index) const; - // Given a card_index, return the starting address of the first block in the heap - // that straddles into the card. If the card is co-initial with an object, then - // this would return the starting address of the heap that this card covers. - // Expects to be called for a card affiliated with the old generation in - // generational mode. - HeapWord* block_start(size_t card_index) const; + // Given a card_index, return the starting address of the first live object in the heap + // that intersects with or follows this card. This must be a valid, parsable object, and must + // be the first such object that intersects with this card. The object may start before, + // at, or after the start of the card identified by card_index, and may end in or after the card. + // + // The tams argument represents top for the enclosing region at the start of the most recently + // initiated concurrent old marking effort. If ctx is non-null, we use the marking context to identify + // marked objects below tams. Above tams, we know that every object is marked and that the memory is + // parsable (so we can add an object's size to its address to find the next object). If ctx is null, + // we use crossing maps to find where object's start, and use object sizes to walk individual objects. + // The region must be parsable if ctx is null. + // + // The end_range_of_interest pointer argument represents an upper bound on how far we look in the forward direction + // for the first object in the heap that intersects or follows this card. If there are no live objects found at + // an address less than end_range_of_interest returns nullptr. + // + // Expects to be called for a card in a region affiliated with the old generation of the + // generational heap, otherwise behavior is undefined. + // + // If not null, ctx holds the complete marking context of the old generation. If null, + // we expect that the marking context isn't available and the crossing maps are valid. + // Note that crossing maps may be invalid following class unloading and before dead + // or unloaded objects have been coalesced and filled. Coalesce and fill updates the crossing maps. + HeapWord* first_object_start(size_t card_index, const ShenandoahMarkingContext* const ctx, + HeapWord* tams, HeapWord* end_range_of_interest) const; }; // ShenandoahScanRemembered is a concrete class representing the diff --git a/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.inline.hpp index 919cc4f6fd7..e394daa68c0 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.inline.hpp @@ -50,7 +50,7 @@ // degenerated execution, leading to dangling references. template void ShenandoahScanRemembered::process_clusters(size_t first_cluster, size_t count, HeapWord* end_of_range, - ClosureType* cl, bool use_write_table, uint worker_id) { + ClosureType* cl, bool use_write_table, uint worker_id) { assert(ShenandoahHeap::heap()->old_generation()->is_parsable(), "Old generation regions must be parsable for remembered set scan"); // If old-gen evacuation is active, then MarkingContext for old-gen heap regions is valid. We use the MarkingContext @@ -102,7 +102,7 @@ void ShenandoahScanRemembered::process_clusters(size_t first_cluster, size_t cou // tams and ctx below are for old generation marking. As such, young gen roots must // consider everything above tams, since it doesn't represent a TAMS for young gen's // SATB marking. - const HeapWord* tams = (ctx == nullptr ? region->bottom() : ctx->top_at_mark_start(region)); + HeapWord* const tams = (ctx == nullptr ? region->bottom() : ctx->top_at_mark_start(region)); NOT_PRODUCT(ShenandoahCardStats stats(whole_cards, card_stats(worker_id));) @@ -162,19 +162,37 @@ void ShenandoahScanRemembered::process_clusters(size_t first_cluster, size_t cou // [left, right) is a maximal right-open interval of dirty cards HeapWord* left = _rs->addr_for_card_index(dirty_l); // inclusive HeapWord* right = _rs->addr_for_card_index(dirty_r + 1); // exclusive + if (end_addr <= left) { + // The range of addresses to be scanned is empty + continue; + } // Clip right to end_addr established above (still exclusive) right = MIN2(right, end_addr); assert(right <= region->top() && end_addr <= region->top(), "Busted bounds"); const MemRegion mr(left, right); - // NOTE: We'll not call block_start() repeatedly - // on a very large object if its head card is dirty. If not, - // (i.e. the head card is clean) we'll call it each time we - // process a new dirty range on the object. This is always - // the case for large object arrays, which are typically more + // NOTE: We'll not call first_object_start() repeatedly + // on a very large object, i.e. one spanning multiple cards, + // if its head card is dirty. If not, (i.e. its head card is clean) + // we'll call it each time we process a new dirty range on the object. + // This is always the case for large object arrays, which are typically more // common. - HeapWord* p = _scc->block_start(dirty_l); + assert(ctx != nullptr || heap->old_generation()->is_parsable(), "Error"); + HeapWord* p = _scc->first_object_start(dirty_l, ctx, tams, right); + assert((p == nullptr) || (p < right), "No first object found is denoted by nullptr, p: " + PTR_FORMAT ", right: " PTR_FORMAT ", end_addr: " PTR_FORMAT ", next card addr: " PTR_FORMAT, + p2i(p), p2i(right), p2i(end_addr), p2i(_rs->addr_for_card_index(dirty_r + 1))); + if (p == nullptr) { + // There are no live objects to be scanned in this dirty range. cur_index identifies first card in this + // uninteresting dirty range. At top of next loop iteration, we will either end the looop + // (because cur_index < start_card_index) or we will begin the search for a range of clean cards. + continue; + } + oop obj = cast_to_oop(p); + assert(oopDesc::is_oop(obj), "Not an object at " PTR_FORMAT ", left: " PTR_FORMAT ", right: " PTR_FORMAT, + p2i(p), p2i(left), p2i(right)); + assert(ctx==nullptr || ctx->is_marked(obj), "Error"); // PREFIX: The object that straddles into this range of dirty cards // from the left may be subject to special treatment unless diff --git a/test/hotspot/gtest/gc/shenandoah/test_shenandoahMarkBitMap.cpp b/test/hotspot/gtest/gc/shenandoah/test_shenandoahMarkBitMap.cpp new file mode 100644 index 00000000000..3dbb7c62122 --- /dev/null +++ b/test/hotspot/gtest/gc/shenandoah/test_shenandoahMarkBitMap.cpp @@ -0,0 +1,571 @@ +/* + * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +#include "gc/shenandoah/shenandoahHeap.inline.hpp" +#include "gc/shenandoah/shenandoahMarkBitMap.hpp" +#include "gc/shenandoah/shenandoahMarkBitMap.inline.hpp" + +BEGIN_ALLOW_FORBIDDEN_FUNCTIONS +#include +END_ALLOW_FORBIDDEN_FUNCTIONS + +#include "memory/memRegion.hpp" +#include "unittest.hpp" + +#include "utilities/ostream.hpp" +#include "utilities/vmassert_reinstall.hpp" +#include "utilities/vmassert_uninstall.hpp" + +// These tests will all be skipped (unless Shenandoah becomes the default +// collector). To execute these tests, you must enable Shenandoah, which +// is done with: +// +// % make exploded-test TEST="gtest:ShenandoahOld*" CONF=release TEST_OPTS="JAVA_OPTIONS=-XX:+UseShenandoahGC -XX:+UnlockExperimentalVMOptions -XX:ShenandoahGCMode=generational" +// +// Please note that these 'unit' tests are really integration tests and rely +// on the JVM being initialized. These tests manipulate the state of the +// collector in ways that are not compatible with a normal collection run. +// If these tests take longer than the minimum time between gc intervals - +// or, more likely, if you have them paused in a debugger longer than this +// interval - you can expect trouble. These tests will also not run in a build +// with asserts enabled because they use APIs that expect to run on a safepoint. + +#ifdef ASSERT +#define SKIP_IF_NOT_SHENANDOAH() \ + std::cout << "skipped (debug build)\n"; \ + return; +#else +#define SKIP_IF_NOT_SHENANDOAH() \ + if (!UseShenandoahGC) { \ + std::cout << "skipped\n"; \ + return; \ + } +#endif + +static bool _success; +static size_t _assertion_failures; + +#define MarkBitMapAssertEqual(a, b) EXPECT_EQ((a), (b)); if ((a) != (b)) { _assertion_failures++; } +#define MarkBitMapAssertTrue(a) EXPECT_TRUE((a)); if ((a) == 0) { _assertion_failures++; } + + +class ShenandoahMarkBitMapTest: public ::testing::Test { +protected: + + static void verify_bitmap_is_empty(HeapWord *start, size_t words_in_heap, ShenandoahMarkBitMap* mbm) { + MarkBitMapAssertTrue(mbm->is_bitmap_clear_range(start, start + words_in_heap)); + while (words_in_heap-- > 0) { + MarkBitMapAssertTrue(!mbm->is_marked(start)); + MarkBitMapAssertTrue(!mbm->is_marked_weak(start)); + MarkBitMapAssertTrue(!mbm->is_marked_strong(start)); + start++; + } + } + + static void verify_bitmap_is_weakly_marked(ShenandoahMarkBitMap* mbm, + HeapWord* weakly_marked_addresses[], size_t weakly_marked_objects) { + for (size_t i = 0; i < weakly_marked_objects; i++) { + HeapWord* obj_addr = weakly_marked_addresses[i]; + MarkBitMapAssertTrue(mbm->is_marked(obj_addr)); + MarkBitMapAssertTrue(mbm->is_marked_weak(obj_addr)); + } + } + + static void verify_bitmap_is_strongly_marked(ShenandoahMarkBitMap* mbm, + HeapWord* strongly_marked_addresses[], size_t strongly_marked_objects) { + for (size_t i = 0; i < strongly_marked_objects; i++) { + HeapWord* obj_addr = strongly_marked_addresses[i]; + MarkBitMapAssertTrue(mbm->is_marked(obj_addr)); + MarkBitMapAssertTrue(mbm->is_marked_strong(obj_addr)); + } + } + + static void verify_bitmap_all(ShenandoahMarkBitMap* mbm, HeapWord* all_marked_addresses[], + bool is_weakly_marked_object[], bool is_strongly_marked_object[], size_t all_marked_objects, + HeapWord* heap_memory, HeapWord* end_of_heap_memory) { + HeapWord* last_marked_addr = &heap_memory[-1]; + for (size_t i = 0; i < all_marked_objects; i++) { + HeapWord* obj_addr = all_marked_addresses[i]; + if (is_strongly_marked_object[i]) { + MarkBitMapAssertTrue(mbm->is_marked(obj_addr)); + MarkBitMapAssertTrue(mbm->is_marked_strong(obj_addr)); + } + if (is_weakly_marked_object[i]) { + MarkBitMapAssertTrue(mbm->is_marked(obj_addr)); + MarkBitMapAssertTrue(mbm->is_marked_weak(obj_addr)); + } + while (++last_marked_addr < obj_addr) { + MarkBitMapAssertTrue(!mbm->is_marked(last_marked_addr)); + MarkBitMapAssertTrue(!mbm->is_marked_strong(last_marked_addr)); + MarkBitMapAssertTrue(!mbm->is_marked_weak(last_marked_addr)); + } + last_marked_addr = obj_addr; + } + while (++last_marked_addr < end_of_heap_memory) { + MarkBitMapAssertTrue(!mbm->is_marked(last_marked_addr)); + MarkBitMapAssertTrue(!mbm->is_marked_strong(last_marked_addr)); + MarkBitMapAssertTrue(!mbm->is_marked_weak(last_marked_addr)); + } + + HeapWord* next_marked = (HeapWord*) &heap_memory[0] - 1; + for (size_t i = 0; i < all_marked_objects; i++) { + next_marked = mbm->get_next_marked_addr(next_marked + 1, end_of_heap_memory); + MarkBitMapAssertTrue(mbm->is_marked(next_marked)); + MarkBitMapAssertEqual(next_marked, all_marked_addresses[i]); + if (is_strongly_marked_object[i]) { + MarkBitMapAssertTrue(mbm->is_marked_strong(next_marked)); + } + if (is_weakly_marked_object[i]) { + MarkBitMapAssertTrue(mbm->is_marked_weak(next_marked)); + } + } + // We expect no more marked addresses to be found. Should return limit. + HeapWord* sentinel = mbm->get_next_marked_addr(next_marked + 1, end_of_heap_memory); + MarkBitMapAssertEqual(sentinel, end_of_heap_memory); + + HeapWord* prev_marked = end_of_heap_memory + 1; + for (int i = (int) all_marked_objects - 1; i >= 0; i--) { + prev_marked = mbm->get_prev_marked_addr(&heap_memory[0], prev_marked - 1); + MarkBitMapAssertEqual(prev_marked, all_marked_addresses[i]); + MarkBitMapAssertTrue(mbm->is_marked(prev_marked)); + if (is_strongly_marked_object[i]) { + MarkBitMapAssertTrue(mbm->is_marked_strong(prev_marked)); + } + if (is_weakly_marked_object[i]) { + MarkBitMapAssertTrue(mbm->is_marked_weak(prev_marked)); + } + } + // We expect no more marked addresses to be found. should return prev_marked. + sentinel = mbm->get_prev_marked_addr(&heap_memory[0], prev_marked - 1); + MarkBitMapAssertEqual(sentinel, prev_marked); + } + +public: + + static bool run_test() { + ShenandoahHeap* heap = ShenandoahHeap::heap(); + size_t heap_size = heap->max_capacity(); + size_t heap_size_words = heap_size / HeapWordSize; + HeapWord* my_heap_memory = heap->base(); + HeapWord* end_of_my_heap = my_heap_memory + heap_size_words; + MemRegion heap_descriptor(my_heap_memory, heap_size_words); + + _success = false; + _assertion_failures = 0; + + size_t bitmap_page_size = UseLargePages ? os::large_page_size() : os::vm_page_size(); + size_t bitmap_size_orig = ShenandoahMarkBitMap::compute_size(heap_size); + size_t bitmap_size = align_up(bitmap_size_orig, bitmap_page_size); + size_t bitmap_word_size = (bitmap_size + HeapWordSize - 1) / HeapWordSize; + + HeapWord* my_bitmap_memory = NEW_C_HEAP_ARRAY(HeapWord, bitmap_word_size, mtGC); + + MarkBitMapAssertTrue(my_bitmap_memory != nullptr); + if (my_bitmap_memory == nullptr) { + std::cout <<"Cannot run test because failed to allocate bitmap memory\n" << std::flush; + return false; + } + MemRegion bitmap_descriptor(my_bitmap_memory, bitmap_size / HeapWordSize); + ShenandoahMarkBitMap mbm(heap_descriptor, bitmap_descriptor); + + mbm.clear_range_large(heap_descriptor); + verify_bitmap_is_empty((HeapWord*) my_heap_memory, heap_size_words, &mbm); + + HeapWord* weakly_marked_addresses[] = { + (HeapWord*) &my_heap_memory[13], + (HeapWord*) &my_heap_memory[14], + (HeapWord*) &my_heap_memory[15], + (HeapWord*) &my_heap_memory[16], + (HeapWord*) &my_heap_memory[176], + (HeapWord*) &my_heap_memory[240], + (HeapWord*) &my_heap_memory[480], + (HeapWord*) &my_heap_memory[1360], + (HeapWord*) &my_heap_memory[1488], + (HeapWord*) &my_heap_memory[2416], + (HeapWord*) &my_heap_memory[5968], + (HeapWord*) &my_heap_memory[8191], + (HeapWord*) &my_heap_memory[8192], + (HeapWord*) &my_heap_memory[8193] + }; + size_t weakly_marked_objects = sizeof(weakly_marked_addresses) / sizeof(HeapWord*); + for (size_t i = 0; i < weakly_marked_objects; i++) { + mbm.mark_weak(weakly_marked_addresses[i]); + } + HeapWord* next_marked = (HeapWord*) &my_heap_memory[0] - 1; + for (size_t i = 0; i < weakly_marked_objects; i++) { + next_marked = mbm.get_next_marked_addr(next_marked + 1, end_of_my_heap); + MarkBitMapAssertEqual(next_marked, weakly_marked_addresses[i]); + MarkBitMapAssertTrue(mbm.is_marked(next_marked)); + MarkBitMapAssertTrue(mbm.is_marked_weak(next_marked)); + MarkBitMapAssertTrue(!mbm.is_marked_strong(next_marked)); + } + // We expect no more marked addresses to be found. Should return limit. + HeapWord* sentinel = mbm.get_next_marked_addr(next_marked + 1, end_of_my_heap); + HeapWord* heap_limit = end_of_my_heap; + MarkBitMapAssertEqual(sentinel, heap_limit); + HeapWord* prev_marked = end_of_my_heap + 1;; + for (int i = (int) weakly_marked_objects - 1; i >= 0; i--) { + // to be renamed get_prev_marked_addr() + prev_marked = mbm.get_prev_marked_addr(&my_heap_memory[0], prev_marked - 1); + MarkBitMapAssertEqual(prev_marked, weakly_marked_addresses[i]); + MarkBitMapAssertTrue(mbm.is_marked(prev_marked)); + MarkBitMapAssertTrue(mbm.is_marked_weak(prev_marked)); + MarkBitMapAssertTrue(!mbm.is_marked_strong(prev_marked)); + } + // We expect no more marked addresses to be found. should return prev_marked. + sentinel = mbm.get_prev_marked_addr(&my_heap_memory[0], prev_marked - 1); + // MarkBitMapAssertEqual(sentinel, prev_marked); + MarkBitMapAssertEqual(sentinel, prev_marked); + verify_bitmap_is_weakly_marked(&mbm, weakly_marked_addresses, weakly_marked_objects); + + HeapWord* strongly_marked_addresses[] = { + (HeapWord*) &my_heap_memory[8], + (HeapWord*) &my_heap_memory[24], + (HeapWord*) &my_heap_memory[32], + (HeapWord*) &my_heap_memory[56], + (HeapWord*) &my_heap_memory[64], + (HeapWord*) &my_heap_memory[168], + (HeapWord*) &my_heap_memory[232], + (HeapWord*) &my_heap_memory[248], + (HeapWord*) &my_heap_memory[256], + (HeapWord*) &my_heap_memory[257], + (HeapWord*) &my_heap_memory[258], + (HeapWord*) &my_heap_memory[259], + (HeapWord*) &my_heap_memory[488], + (HeapWord*) &my_heap_memory[1352], + (HeapWord*) &my_heap_memory[1496], + (HeapWord*) &my_heap_memory[2432], + (HeapWord*) &my_heap_memory[5960] + }; + size_t strongly_marked_objects = sizeof(strongly_marked_addresses) / sizeof(HeapWord*); + for (size_t i = 0; i < strongly_marked_objects; i++) { + bool upgraded = false; + mbm.mark_strong(strongly_marked_addresses[i], upgraded); + MarkBitMapAssertTrue(!upgraded); + } + verify_bitmap_is_strongly_marked(&mbm, strongly_marked_addresses, strongly_marked_objects); + HeapWord* upgraded_weakly_marked_addresses[] = { + (HeapWord*) &my_heap_memory[240], + (HeapWord*) &my_heap_memory[1360], + }; + size_t upgraded_weakly_marked_objects = sizeof(upgraded_weakly_marked_addresses) / sizeof(HeapWord *); + for (size_t i = 0; i < upgraded_weakly_marked_objects; i++) { + bool upgraded = false; + mbm.mark_strong(upgraded_weakly_marked_addresses[i], upgraded); + MarkBitMapAssertTrue(upgraded); + } + verify_bitmap_is_strongly_marked(&mbm, upgraded_weakly_marked_addresses, upgraded_weakly_marked_objects); + + HeapWord* all_marked_addresses[] = { + (HeapWord*) &my_heap_memory[8], /* strongly marked */ + (HeapWord*) &my_heap_memory[13], /* weakly marked */ + (HeapWord*) &my_heap_memory[14], /* weakly marked */ + (HeapWord*) &my_heap_memory[15], /* weakly marked */ + (HeapWord*) &my_heap_memory[16], /* weakly marked */ + (HeapWord*) &my_heap_memory[24], /* strongly marked */ + (HeapWord*) &my_heap_memory[32], /* strongly marked */ + (HeapWord*) &my_heap_memory[56], /* strongly marked */ + (HeapWord*) &my_heap_memory[64], /* strongly marked */ + (HeapWord*) &my_heap_memory[168], /* strongly marked */ + (HeapWord*) &my_heap_memory[176], /* weakly marked */ + (HeapWord*) &my_heap_memory[232], /* strongly marked */ + (HeapWord*) &my_heap_memory[240], /* weakly marked upgraded to strongly marked */ + (HeapWord*) &my_heap_memory[248], /* strongly marked */ + (HeapWord*) &my_heap_memory[256], /* strongly marked */ + (HeapWord*) &my_heap_memory[257], /* strongly marked */ + (HeapWord*) &my_heap_memory[258], /* strongly marked */ + (HeapWord*) &my_heap_memory[259], /* strongly marked */ + (HeapWord*) &my_heap_memory[480], /* weakly marked */ + (HeapWord*) &my_heap_memory[488], /* strongly marked */ + (HeapWord*) &my_heap_memory[1352], /* strongly marked */ + (HeapWord*) &my_heap_memory[1360], /* weakly marked upgraded to strongly marked */ + (HeapWord*) &my_heap_memory[1488], /* weakly marked */ + (HeapWord*) &my_heap_memory[1496], /* strongly marked */ + (HeapWord*) &my_heap_memory[2416], /* weakly marked */ + (HeapWord*) &my_heap_memory[2432], /* strongly marked */ + (HeapWord*) &my_heap_memory[5960], /* strongly marked */ + (HeapWord*) &my_heap_memory[5968], /* weakly marked */ + (HeapWord*) &my_heap_memory[8191], /* weakly marked */ + (HeapWord*) &my_heap_memory[8192], /* weakly marked */ + (HeapWord*) &my_heap_memory[8193] /* weakly marked */ + }; + size_t all_marked_objects = sizeof(all_marked_addresses) / sizeof(HeapWord*); + bool is_weakly_marked_object[] = { + false, + true, + true, + true, + true, + false, + false, + false, + false, + false, + true, + false, + true, + false, + false, + false, + false, + false, + true, + false, + false, + true, + true, + false, + true, + false, + false, + true, + true, + true, + true + }; + bool is_strongly_marked_object[] = { + true, + false, + false, + false, + false, + true, + true, + true, + true, + true, + false, + true, + true, + true, + true, + true, + true, + true, + false, + true, + true, + true, + false, + true, + false, + true, + true, + false, + false, + false, + false + }; + verify_bitmap_all(&mbm, all_marked_addresses, is_weakly_marked_object, is_strongly_marked_object, all_marked_objects, + my_heap_memory, end_of_my_heap); + + MemRegion first_clear_region(&my_heap_memory[168], &my_heap_memory[256]); + mbm.clear_range_large(first_clear_region); + // Five objects are no longer marked + HeapWord* all_marked_addresses_after_first_clear[] = { + (HeapWord*) &my_heap_memory[8], /* strongly marked */ + (HeapWord*) &my_heap_memory[13], /* weakly marked */ + (HeapWord*) &my_heap_memory[14], /* weakly marked */ + (HeapWord*) &my_heap_memory[15], /* weakly marked */ + (HeapWord*) &my_heap_memory[16], /* weakly marked */ + (HeapWord*) &my_heap_memory[24], /* strongly marked */ + (HeapWord*) &my_heap_memory[32], /* strongly marked */ + (HeapWord*) &my_heap_memory[56], /* strongly marked */ + (HeapWord*) &my_heap_memory[64], /* strongly marked */ + (HeapWord*) &my_heap_memory[256], /* strongly marked */ + (HeapWord*) &my_heap_memory[257], /* strongly marked */ + (HeapWord*) &my_heap_memory[258], /* strongly marked */ + (HeapWord*) &my_heap_memory[259], /* strongly marked */ + (HeapWord*) &my_heap_memory[480], /* weakly marked */ + (HeapWord*) &my_heap_memory[488], /* strongly marked */ + (HeapWord*) &my_heap_memory[1352], /* strongly marked */ + (HeapWord*) &my_heap_memory[1360], /* weakly marked upgraded to strongly marked */ + (HeapWord*) &my_heap_memory[1488], /* weakly marked */ + (HeapWord*) &my_heap_memory[1496], /* strongly marked */ + (HeapWord*) &my_heap_memory[2416], /* weakly marked */ + (HeapWord*) &my_heap_memory[2432], /* strongly marked */ + (HeapWord*) &my_heap_memory[5960], /* strongly marked */ + (HeapWord*) &my_heap_memory[5968], /* weakly marked */ + (HeapWord*) &my_heap_memory[8191], /* weakly marked */ + (HeapWord*) &my_heap_memory[8192], /* weakly marked */ + (HeapWord*) &my_heap_memory[8193] /* weakly marked */ + }; + size_t all_marked_objects_after_first_clear = sizeof(all_marked_addresses_after_first_clear) / sizeof(HeapWord*); + bool is_weakly_marked_object_after_first_clear[] = { + false, + true, + true, + true, + true, + false, + false, + false, + false, + false, + false, + false, + false, + true, + false, + false, + true, + true, + false, + true, + false, + false, + true, + true, + true, + true + }; + bool is_strongly_marked_object_after_first_clear[] = { + true, + false, + false, + false, + false, + true, + true, + true, + true, + true, + true, + true, + true, + false, + true, + true, + true, + false, + true, + false, + true, + true, + false, + false, + false, + false + }; + verify_bitmap_all(&mbm, all_marked_addresses_after_first_clear, + is_weakly_marked_object_after_first_clear, is_strongly_marked_object_after_first_clear, + all_marked_objects_after_first_clear, my_heap_memory, end_of_my_heap); + + MemRegion second_clear_region(&my_heap_memory[1360], &my_heap_memory[2416]); + mbm.clear_range_large(second_clear_region); + // Five objects are no longer marked + HeapWord* all_marked_addresses_after_2nd_clear[] = { + (HeapWord*) &my_heap_memory[8], /* strongly marked */ + (HeapWord*) &my_heap_memory[13], /* weakly marked */ + (HeapWord*) &my_heap_memory[14], /* weakly marked */ + (HeapWord*) &my_heap_memory[15], /* weakly marked */ + (HeapWord*) &my_heap_memory[16], /* weakly marked */ + (HeapWord*) &my_heap_memory[24], /* strongly marked */ + (HeapWord*) &my_heap_memory[32], /* strongly marked */ + (HeapWord*) &my_heap_memory[56], /* strongly marked */ + (HeapWord*) &my_heap_memory[64], /* strongly marked */ + (HeapWord*) &my_heap_memory[256], /* strongly marked */ + (HeapWord*) &my_heap_memory[257], /* strongly marked */ + (HeapWord*) &my_heap_memory[258], /* strongly marked */ + (HeapWord*) &my_heap_memory[259], /* strongly marked */ + (HeapWord*) &my_heap_memory[480], /* weakly marked */ + (HeapWord*) &my_heap_memory[488], /* strongly marked */ + (HeapWord*) &my_heap_memory[1352], /* strongly marked */ + (HeapWord*) &my_heap_memory[2416], /* weakly marked */ + (HeapWord*) &my_heap_memory[2432], /* strongly marked */ + (HeapWord*) &my_heap_memory[5960], /* strongly marked */ + (HeapWord*) &my_heap_memory[5968], /* weakly marked */ + (HeapWord*) &my_heap_memory[8191], /* weakly marked */ + (HeapWord*) &my_heap_memory[8192], /* weakly marked */ + (HeapWord*) &my_heap_memory[8193] /* weakly marked */ + }; + size_t all_marked_objects_after_2nd_clear = sizeof(all_marked_addresses_after_2nd_clear) / sizeof(HeapWord*); + bool is_weakly_marked_object_after_2nd_clear[] = { + false, + true, + true, + true, + true, + false, + false, + false, + false, + false, + false, + false, + false, + true, + false, + false, + true, + false, + false, + true, + true, + true, + true + }; + bool is_strongly_marked_object_after_2nd_clear[] = { + true, + false, + false, + false, + false, + true, + true, + true, + true, + true, + true, + true, + true, + false, + true, + true, + false, + true, + true, + false, + false, + false, + false + }; + verify_bitmap_all(&mbm, all_marked_addresses_after_2nd_clear, + is_weakly_marked_object_after_2nd_clear, is_strongly_marked_object_after_2nd_clear, + all_marked_objects_after_2nd_clear, my_heap_memory, end_of_my_heap); + + FREE_C_HEAP_ARRAY(HeapWord, my_bitmap_memory); + _success = true; + return true; + } +}; + +TEST_VM_F(ShenandoahMarkBitMapTest, minimum_test) { + SKIP_IF_NOT_SHENANDOAH(); + + bool result = ShenandoahMarkBitMapTest::run_test(); + ASSERT_EQ(result, true); + ASSERT_EQ(_success, true); + ASSERT_EQ(_assertion_failures, (size_t) 0); +} diff --git a/test/hotspot/gtest/gc/shenandoah/test_shenandoahOldHeuristic.cpp b/test/hotspot/gtest/gc/shenandoah/test_shenandoahOldHeuristic.cpp index b661bd6bc57..b184b19ce6c 100644 --- a/test/hotspot/gtest/gc/shenandoah/test_shenandoahOldHeuristic.cpp +++ b/test/hotspot/gtest/gc/shenandoah/test_shenandoahOldHeuristic.cpp @@ -49,7 +49,7 @@ #else #define SKIP_IF_NOT_SHENANDOAH() \ if (!UseShenandoahGC) { \ - tty->print_cr("skipped"); \ + std::cout << "skipped\n"; \ return; \ } #endif diff --git a/test/hotspot/jtreg/gc/shenandoah/compiler/TestClone.java b/test/hotspot/jtreg/gc/shenandoah/compiler/TestClone.java index 2e98c72ee17..0775e5baadd 100644 --- a/test/hotspot/jtreg/gc/shenandoah/compiler/TestClone.java +++ b/test/hotspot/jtreg/gc/shenandoah/compiler/TestClone.java @@ -357,6 +357,7 @@ * -XX:TieredStopAtLevel=4 * TestClone */ + public class TestClone { public static void main(String[] args) throws Exception { From 15dcbf0bc80b3c1ab09e44b9447c639780cce65e Mon Sep 17 00:00:00 2001 From: Jayathirth D V Date: Wed, 12 Nov 2025 04:44:37 +0000 Subject: [PATCH 008/418] 8363950: Incorrect jtreg header in TestLayoutVsICU.java Reviewed-by: azvegint --- .../awt/font/TextLayout/TestLayoutVsICU.java | 889 -------- .../TextLayout/TestLayoutVsICU_jdkbase.xml | 1827 ----------------- 2 files changed, 2716 deletions(-) delete mode 100644 test/jdk/java/awt/font/TextLayout/TestLayoutVsICU.java delete mode 100644 test/jdk/java/awt/font/TextLayout/TestLayoutVsICU_jdkbase.xml diff --git a/test/jdk/java/awt/font/TextLayout/TestLayoutVsICU.java b/test/jdk/java/awt/font/TextLayout/TestLayoutVsICU.java deleted file mode 100644 index a12fd802fd5..00000000000 --- a/test/jdk/java/awt/font/TextLayout/TestLayoutVsICU.java +++ /dev/null @@ -1,889 +0,0 @@ -/* - * Copyright (c) 2015, 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. - */ - -/* - * Copyright (C) 2013-2014 IBM Corporation and Others. All Rights Reserved. - */ - -import java.awt.Color; -import java.awt.Composite; -import java.awt.Font; -import java.awt.FontFormatException; -import java.awt.FontMetrics; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.GraphicsConfiguration; -import java.awt.Image; -import java.awt.Paint; -import java.awt.Rectangle; -import java.awt.RenderingHints; -import java.awt.RenderingHints.Key; -import java.awt.Shape; -import java.awt.Stroke; -import java.awt.font.FontRenderContext; -import java.awt.font.GlyphVector; -import java.awt.font.TextLayout; -import java.awt.geom.AffineTransform; -import java.awt.image.BufferedImage; -import java.awt.image.BufferedImageOp; -import java.awt.image.ImageObserver; -import java.awt.image.RenderedImage; -import java.awt.image.renderable.RenderableImage; -import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.text.AttributedCharacterIterator; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.MissingResourceException; -import java.util.TreeMap; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; - -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.NamedNodeMap; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.xml.sax.SAXException; - -/** - * This test runs against a test XML file. It opens the fonts and attempts - * to shape and layout glyphs. - * Note that the test is highly environment dependent- you must have - * the same versions of the same fonts available or the test will fail. - * - * It is similar to letest which is part of ICU. - * For reference, here are some reference items: - * ICU's test file: - * http://source.icu-project.org/repos/icu/icu/trunk/source/test/testdata/letest.xml - * ICU's readme for the similar test: - * http://source.icu-project.org/repos/icu/icu/trunk/source/test/letest/readme.html - * - * @bug 8054203 - * @test - * @summary manual test of layout engine behavior. Takes an XML control file. - * @compile TestLayoutVsICU.java - * @author srl - * @run main/manual - */ -public class TestLayoutVsICU { - - public static boolean OPT_DRAW = false; - public static boolean OPT_VERBOSE = false; - public static boolean OPT_FAILMISSING = false; - public static boolean OPT_NOTHROW= false; // if true - don't stop on failure - - public static int docs = 0; // # docs processed - public static int skipped = 0; // cases skipped due to bad font - public static int total = 0; // cases processed - public static int bad = 0; // cases with errs - - public static final String XML_LAYOUT_TESTS = "layout-tests"; // top level - public static final String XML_TEST_CASE = "test-case"; - public static final String XML_TEST_FONT = "test-font"; - public static final String XML_TEST_TEXT = "test-text"; - public static final String XML_RESULT_GLYPHS = "result-glyphs"; - public static final String XML_ID = "id"; - public static final String XML_SCRIPT = "script"; - public static final String XML_NAME = "name"; - public static final String XML_VERSION = "version"; - public static final String XML_CHECKSUM = "checksum"; - public static final String XML_RESULT_INDICES = "result-indices"; - public static final String XML_RESULT_POSITIONS = "result-positions"; - - /** - * @param args - * @throws IOException - * @throws SAXException - * @throws ParserConfigurationException - */ - public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException { - System.out.println("Java " + System.getProperty("java.version") + " from " + System.getProperty("java.vendor")); - TestLayoutVsICU tlvi = null; - for(String arg : args) { - if(arg.equals("-d")) { - OPT_DRAW = true; - } else if(arg.equals("-n")) { - OPT_NOTHROW = true; - } else if(arg.equals("-v")) { - OPT_VERBOSE = true; - } else if(arg.equals("-f")) { - OPT_FAILMISSING = true; - } else { - if(tlvi == null) { - tlvi = new TestLayoutVsICU(); - } - try { - tlvi.show(arg); - } finally { - if(OPT_VERBOSE) { - System.out.println("# done with " + arg); - } - } - } - } - - if(tlvi == null) { - throw new IllegalArgumentException("No XML input. Usage: " + TestLayoutVsICU.class.getSimpleName() + " [-d][-v][-f] letest.xml ..."); - } else { - System.out.println("\n\nRESULTS:\n"); - System.out.println(skipped+"\tskipped due to missing font"); - System.out.println(total+"\ttested of which:"); - System.out.println(bad+"\twere bad"); - - if(bad>0) { - throw new InternalError("One or more failure(s)"); - } - } - } - - String id; - - private void show(String arg) throws ParserConfigurationException, SAXException, IOException { - id = ""; - File xmlFile = new File(arg); - if(!xmlFile.exists()) { - throw new FileNotFoundException("Can't open input XML file " + arg); - } - DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); - DocumentBuilder db = dbf.newDocumentBuilder(); - if(OPT_VERBOSE) { - System.out.println("# Parsing " + xmlFile.getAbsolutePath()); - } - Document doc = db.parse(xmlFile); - Element e = doc.getDocumentElement(); - if(!XML_LAYOUT_TESTS.equals(e.getNodeName())) { - throw new IllegalArgumentException("Document " + xmlFile.getAbsolutePath() + " does not have as its base"); - } - - NodeList testCases = e.getElementsByTagName(XML_TEST_CASE); - for(int caseNo=0;caseNo testCaseAttrs = attrs(testCase); - id = testCaseAttrs.get(XML_ID); - final String script = testCaseAttrs.get(XML_SCRIPT); - String testText = null; - Integer[] expectGlyphs = null; - Integer[] expectIndices = null; - Map fontAttrs = null; - if(OPT_VERBOSE) { - System.out.println("#"+caseNo+" id="+id + ", script="+script); - } - NodeList children = testCase.getChildNodes(); - for(int sub=0;sub glyphs = new ArrayList(); - Graphics2D myg2 = new Graphics2D(){ - - @Override - public void draw(Shape s) { - // TODO Auto-generated method stub - - } - - @Override - public boolean drawImage(Image img, AffineTransform xform, - ImageObserver obs) { - // TODO Auto-generated method stub - return false; - } - - @Override - public void drawImage(BufferedImage img, - BufferedImageOp op, int x, int y) { - // TODO Auto-generated method stub - - } - - @Override - public void drawRenderedImage(RenderedImage img, - AffineTransform xform) { - // TODO Auto-generated method stub - - } - - @Override - public void drawRenderableImage(RenderableImage img, - AffineTransform xform) { - // TODO Auto-generated method stub - - } - - @Override - public void drawString(String str, int x, int y) { - // TODO Auto-generated method stub - - } - - @Override - public void drawString(String str, float x, float y) { - // TODO Auto-generated method stub - - } - - @Override - public void drawString( - AttributedCharacterIterator iterator, int x, int y) { - // TODO Auto-generated method stub - - } - - @Override - public void drawString( - AttributedCharacterIterator iterator, float x, - float y) { - // TODO Auto-generated method stub - - } - - @Override - public void drawGlyphVector(GlyphVector g, float x, float y) { - if(x!=0.0 || y!=0.0) { - throw new InternalError("x,y should be 0 but got " + x+","+y); - } - //System.err.println("dGV : " + g.toString() + " @ "+x+","+y); - glyphs.add(g); - } - - @Override - public void fill(Shape s) { - // TODO Auto-generated method stub - - } - - @Override - public boolean hit(Rectangle rect, Shape s, boolean onStroke) { - // TODO Auto-generated method stub - return false; - } - - @Override - public GraphicsConfiguration getDeviceConfiguration() { - // TODO Auto-generated method stub - return null; - } - - @Override - public void setComposite(Composite comp) { - // TODO Auto-generated method stub - - } - - @Override - public void setPaint(Paint paint) { - // TODO Auto-generated method stub - - } - - @Override - public void setStroke(Stroke s) { - // TODO Auto-generated method stub - - } - - @Override - public void setRenderingHint(Key hintKey, Object hintValue) { - // TODO Auto-generated method stub - - } - - @Override - public Object getRenderingHint(Key hintKey) { - // TODO Auto-generated method stub - return null; - } - - @Override - public void setRenderingHints(Map hints) { - // TODO Auto-generated method stub - - } - - @Override - public void addRenderingHints(Map hints) { - // TODO Auto-generated method stub - - } - - @Override - public RenderingHints getRenderingHints() { - // TODO Auto-generated method stub - return null; - } - - @Override - public void translate(int x, int y) { - // TODO Auto-generated method stub - - } - - @Override - public void translate(double tx, double ty) { - // TODO Auto-generated method stub - - } - - @Override - public void rotate(double theta) { - // TODO Auto-generated method stub - - } - - @Override - public void rotate(double theta, double x, double y) { - // TODO Auto-generated method stub - - } - - @Override - public void scale(double sx, double sy) { - // TODO Auto-generated method stub - - } - - @Override - public void shear(double shx, double shy) { - // TODO Auto-generated method stub - - } - - @Override - public void transform(AffineTransform Tx) { - // TODO Auto-generated method stub - - } - - @Override - public void setTransform(AffineTransform Tx) { - // TODO Auto-generated method stub - - } - - @Override - public AffineTransform getTransform() { - // TODO Auto-generated method stub - return null; - } - - @Override - public Paint getPaint() { - // TODO Auto-generated method stub - return null; - } - - @Override - public Composite getComposite() { - // TODO Auto-generated method stub - return null; - } - - @Override - public void setBackground(Color color) { - // TODO Auto-generated method stub - - } - - @Override - public Color getBackground() { - // TODO Auto-generated method stub - return null; - } - - @Override - public Stroke getStroke() { - // TODO Auto-generated method stub - return null; - } - - @Override - public void clip(Shape s) { - // TODO Auto-generated method stub - - } - - @Override - public FontRenderContext getFontRenderContext() { - // TODO Auto-generated method stub - return null; - } - - @Override - public Graphics create() { - // TODO Auto-generated method stub - return null; - } - - @Override - public Color getColor() { - // TODO Auto-generated method stub - return null; - } - - @Override - public void setColor(Color c) { - // TODO Auto-generated method stub - - } - - @Override - public void setPaintMode() { - // TODO Auto-generated method stub - - } - - @Override - public void setXORMode(Color c1) { - // TODO Auto-generated method stub - - } - - @Override - public Font getFont() { - // TODO Auto-generated method stub - return null; - } - - @Override - public void setFont(Font font) { - // TODO Auto-generated method stub - - } - - @Override - public FontMetrics getFontMetrics(Font f) { - // TODO Auto-generated method stub - return null; - } - - @Override - public Rectangle getClipBounds() { - // TODO Auto-generated method stub - return null; - } - - @Override - public void clipRect(int x, int y, int width, int height) { - // TODO Auto-generated method stub - - } - - @Override - public void setClip(int x, int y, int width, int height) { - // TODO Auto-generated method stub - - } - - @Override - public Shape getClip() { - // TODO Auto-generated method stub - return null; - } - - @Override - public void setClip(Shape clip) { - // TODO Auto-generated method stub - - } - - @Override - public void copyArea(int x, int y, int width, int height, - int dx, int dy) { - // TODO Auto-generated method stub - - } - - @Override - public void drawLine(int x1, int y1, int x2, int y2) { - // TODO Auto-generated method stub - - } - - @Override - public void fillRect(int x, int y, int width, int height) { - // TODO Auto-generated method stub - - } - - @Override - public void clearRect(int x, int y, int width, int height) { - // TODO Auto-generated method stub - - } - - @Override - public void drawRoundRect(int x, int y, int width, - int height, int arcWidth, int arcHeight) { - // TODO Auto-generated method stub - - } - - @Override - public void fillRoundRect(int x, int y, int width, - int height, int arcWidth, int arcHeight) { - // TODO Auto-generated method stub - - } - - @Override - public void drawOval(int x, int y, int width, int height) { - // TODO Auto-generated method stub - - } - - @Override - public void fillOval(int x, int y, int width, int height) { - // TODO Auto-generated method stub - - } - - @Override - public void drawArc(int x, int y, int width, int height, - int startAngle, int arcAngle) { - // TODO Auto-generated method stub - - } - - @Override - public void fillArc(int x, int y, int width, int height, - int startAngle, int arcAngle) { - // TODO Auto-generated method stub - - } - - @Override - public void drawPolyline(int[] xPoints, int[] yPoints, - int nPoints) { - // TODO Auto-generated method stub - - } - - @Override - public void drawPolygon(int[] xPoints, int[] yPoints, - int nPoints) { - // TODO Auto-generated method stub - - } - - @Override - public void fillPolygon(int[] xPoints, int[] yPoints, - int nPoints) { - // TODO Auto-generated method stub - - } - - @Override - public boolean drawImage(Image img, int x, int y, - ImageObserver observer) { - // TODO Auto-generated method stub - return false; - } - - @Override - public boolean drawImage(Image img, int x, int y, - int width, int height, ImageObserver observer) { - // TODO Auto-generated method stub - return false; - } - - @Override - public boolean drawImage(Image img, int x, int y, - Color bgcolor, ImageObserver observer) { - // TODO Auto-generated method stub - return false; - } - - @Override - public boolean drawImage(Image img, int x, int y, - int width, int height, Color bgcolor, - ImageObserver observer) { - // TODO Auto-generated method stub - return false; - } - - @Override - public boolean drawImage(Image img, int dx1, int dy1, - int dx2, int dy2, int sx1, int sy1, int sx2, - int sy2, ImageObserver observer) { - // TODO Auto-generated method stub - return false; - } - - @Override - public boolean drawImage(Image img, int dx1, int dy1, - int dx2, int dy2, int sx1, int sy1, int sx2, - int sy2, Color bgcolor, ImageObserver observer) { - // TODO Auto-generated method stub - return false; - } - - @Override - public void dispose() { - // TODO Auto-generated method stub - - } - - }; - tl.draw(myg2, 0, 0); - if(glyphs.size() != 1) { - err("drew " + glyphs.size() + " times - expected 1"); - total++; - bad++; - continue; - } - boolean isBad = false; - GlyphVector gv = glyphs.get(0); - - // GLYPHS - int gotGlyphs[] = gv.getGlyphCodes(0, gv.getNumGlyphs(), new int[gv.getNumGlyphs()]); - - int count = Math.min(gotGlyphs.length, expectGlyphs.length); // go up to this count - - for(int i=0;i> OK: " + gotGlyphs.length + " glyphs"); - } - } - - - if(isBad) { - bad++; - System.out.println("* FAIL: " + id + " /\t" + fontName); - } else { - System.out.println("* OK : " + id + " /\t" + fontName); - } - total++; - } - } - - - private boolean verifyFont(File f, Map fontAttrs) { - InputStream fis = null; - String fontName = fontAttrs.get(XML_NAME); - int count=0; - try { - fis = new BufferedInputStream(new FileInputStream(f)); - - int i = 0; - int r; - try { - while((r=fis.read())!=-1) { - i+=(int)r; - count++; - } - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - return false; - } - if(OPT_VERBOSE) { - System.out.println("for " + f.getAbsolutePath() + " chks = 0x" + Integer.toHexString(i) + " size=" + count); - } - String theirStr = fontAttrs.get("rchecksum"); - - String ourStr = Integer.toHexString(i).toLowerCase(); - - if(theirStr!=null) { - if(theirStr.startsWith("0x")) { - theirStr = theirStr.substring(2).toLowerCase(); - } else { - theirStr = theirStr.toLowerCase(); - } - long theirs = Integer.parseInt(theirStr, 16); - if(theirs != i) { - err("WARNING: rchecksum for " + fontName + " was " + i + " (0x"+ourStr+") "+ " but file said " + theirs +" (0x"+theirStr+") - perhaps a different font?"); - return false; - } else { - if(OPT_VERBOSE) { - System.out.println(" rchecksum for " + fontName + " OK"); - } - return true; - } - } else { - //if(OPT_VERBOSE) { - System.err.println("WARNING: rchecksum for " + fontName + " was " + i + " (0x"+ourStr+") "+ " but rchecksum was MISSING. Old ICU data?"); - //} - } - } catch (FileNotFoundException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - return false; - } finally { - try { - fis.close(); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - return true; - } - - - private Integer[] parseHexArray(String hex) { - List ret = new ArrayList(); - String items[] = hex.split("[\\s,]"); - for(String i : items) { - if(i.isEmpty()) continue; - if(i.startsWith("0x")) { - i = i.substring(2); - } - ret.add(Integer.parseInt(i, 16)); - } - return ret.toArray(new Integer[0]); - } - - - private void err(String string) { - if(OPT_NOTHROW) { - System.out.println(id+" ERROR: " + string +" (continuing due to -n)"); - } else { - throw new InternalError(id+ ": " + string); - } - } - - - private Font getFont(String fontName, Map fontAttrs) { - Font f; - if(false) - try { - f = Font.getFont(fontName); - if(f!=null) { - if(OPT_VERBOSE) { - System.out.println("Loaded default path to " + fontName); - } - return f; - } - } catch(Throwable t) { - if(OPT_VERBOSE) { - t.printStackTrace(); - System.out.println("problem loading font " + fontName + " - " + t.toString()); - } - } - - File homeDir = new File(System.getProperty("user.home")); - File fontDir = new File(homeDir, "fonts"); - File fontFile = new File(fontDir, fontName); - //System.out.println("## trying " + fontFile.getAbsolutePath()); - if(fontFile.canRead()) { - try { - if(!verifyFont(fontFile,fontAttrs)) { - System.out.println("Warning: failed to verify " + fontName); - } - f = Font.createFont(Font.TRUETYPE_FONT, fontFile); - if(f!=null & OPT_VERBOSE) { - System.out.println("> loaded from " + fontFile.getAbsolutePath() + " - " + f.toString()); - } - return f; - } catch (FontFormatException e) { - if(OPT_VERBOSE) { - e.printStackTrace(); - System.out.println("problem loading font " + fontName + " - " + e.toString()); - } - } catch (IOException e) { - if(OPT_VERBOSE) { - e.printStackTrace(); - System.out.println("problem loading font " + fontName + " - " + e.toString()); - } - } - } - return null; - } - - - private static Map attrs(Node testCase) { - Map rv = new TreeMap(); - NamedNodeMap nnm = testCase.getAttributes(); - for(int i=0;i - - - - - - - - - - श्रीमद् भगवद्गीता अध्याय अर्जुन विषाद योग धृतराष्ट्र उवाचृ धर्मक्षेत्रे कुरुक्षेत्रे समवेता युयुत्सवः मामकाः पाण्डवाश्चैव किमकुर्वत संजव - - - 0x0000009E, 0x0000009A, 0x00000051, 0x00000222, 0x00000098, 0x00000091, 0x00000051, 0x00000003, - 0x00000097, 0x00000082, 0x0000009D, 0x000001A5, 0x0000FFFF, 0x0000FFFF, 0x00000222, 0x0000008F, - 0x00000221, 0x00000003, 0x0000005C, 0x000000DA, 0x0000FFFF, 0x00000099, 0x00000221, 0x00000099, - 0x00000003, 0x0000005C, 0x00000087, 0x000001D5, 0x0000005B, 0x0000FFFF, 0x00000093, 0x00000003, - 0x000001D2, 0x0000009D, 0x0000009F, 0x00000221, 0x00000091, 0x00000003, 0x00000099, 0x0000022A, - 0x00000082, 0x00000003, 0x00000092, 0x000001D9, 0x0000008F, 0x0000009A, 0x00000221, 0x000001B4, - 0x0000FFFF, 0x0000FFFF, 0x0000009A, 0x00000051, 0x00000003, 0x00000060, 0x0000009D, 0x00000221, - 0x00000085, 0x000001D9, 0x00000003, 0x00000092, 0x00000098, 0x0000005B, 0x0000FFFF, 0x000000A2, - 0x0000FFFF, 0x0000FFFF, 0x0000022F, 0x0000008F, 0x0000009A, 0x00000051, 0x0000022F, 0x00000003, - 0x00000080, 0x000001D5, 0x0000009A, 0x000001FD, 0x000000A2, 0x0000FFFF, 0x0000FFFF, 0x0000022F, - 0x0000008F, 0x0000009A, 0x00000051, 0x0000022F, 0x00000003, 0x000000A0, 0x00000098, 0x0000009D, - 0x0000022F, 0x0000008F, 0x00000221, 0x00000003, 0x00000099, 0x000001D5, 0x00000099, 0x000001D5, - 0x000000D7, 0x0000FFFF, 0x000000A0, 0x0000009D, 0x0000022C, 0x00000003, 0x00000098, 0x00000221, - 0x00000098, 0x00000080, 0x00000221, 0x0000022C, 0x00000003, 0x00000094, 0x00000221, 0x000000D6, - 0x0000FFFF, 0x0000008C, 0x0000009D, 0x00000221, 0x000001B1, 0x0000FFFF, 0x0000FFFF, 0x00000230, - 0x0000009D, 0x00000003, 0x000001D1, 0x00000080, 0x00000098, 0x00000080, 0x000001D5, 0x0000009D, - 0x0000005B, 0x0000FFFF, 0x0000008F, 0x00000003, 0x000000A0, 0x00000232, 0x00000087, 0x0000009D - - - - 0x00000000, 0x00000002, 0x00000001, 0x00000003, 0x00000004, 0x00000005, 0x00000006, 0x00000007, - 0x00000008, 0x00000009, 0x0000000A, 0x0000000B, 0x0000000C, 0x0000000D, 0x0000000E, 0x0000000F, - 0x00000010, 0x00000011, 0x00000012, 0x00000013, 0x00000014, 0x00000015, 0x00000016, 0x00000017, - 0x00000018, 0x00000019, 0x0000001C, 0x0000001D, 0x0000001A, 0x0000001B, 0x0000001E, 0x0000001F, - 0x00000021, 0x00000020, 0x00000022, 0x00000023, 0x00000024, 0x00000025, 0x00000026, 0x00000027, - 0x00000028, 0x00000029, 0x0000002A, 0x0000002B, 0x0000002C, 0x0000002D, 0x0000002E, 0x0000002F, - 0x00000030, 0x00000031, 0x00000033, 0x00000032, 0x00000034, 0x00000035, 0x00000036, 0x00000037, - 0x00000038, 0x00000039, 0x0000003A, 0x0000003B, 0x0000003E, 0x0000003C, 0x0000003D, 0x0000003F, - 0x00000040, 0x00000041, 0x00000042, 0x00000043, 0x00000045, 0x00000044, 0x00000046, 0x00000047, - 0x00000048, 0x00000049, 0x0000004A, 0x0000004B, 0x0000004C, 0x0000004D, 0x0000004E, 0x0000004F, - 0x00000050, 0x00000052, 0x00000051, 0x00000053, 0x00000054, 0x00000055, 0x00000056, 0x00000057, - 0x00000058, 0x00000059, 0x0000005A, 0x0000005B, 0x0000005C, 0x0000005D, 0x0000005E, 0x0000005F, - 0x00000060, 0x00000061, 0x00000062, 0x00000063, 0x00000064, 0x00000065, 0x00000066, 0x00000067, - 0x00000068, 0x00000069, 0x0000006A, 0x0000006B, 0x0000006C, 0x0000006D, 0x0000006E, 0x0000006F, - 0x00000070, 0x00000071, 0x00000072, 0x00000073, 0x00000074, 0x00000075, 0x00000076, 0x00000077, - 0x00000078, 0x00000079, 0x0000007B, 0x0000007A, 0x0000007C, 0x0000007D, 0x0000007E, 0x00000081, - 0x0000007F, 0x00000080, 0x00000082, 0x00000083, 0x00000084, 0x00000085, 0x00000086, 0x00000087 - - - - 0.000000, 0.000000, 9.468750, 0.000000, 19.130859, -0.451172, 15.984375, 0.000000, - 19.640625, 0.000000, 29.109375, 0.000000, 40.177734, -0.451172, 37.078125, 0.000000, - 43.078125, 0.000000, 52.546875, 0.000000, 62.015625, 0.000000, 69.984375, 0.000000, - 77.953125, 0.000000, 77.953125, 0.000000, 77.953125, 0.000000, 81.609375, 0.000000, - 89.578125, 0.000000, 93.234375, 0.000000, 99.234375, 0.000000, 109.171875, 0.000000, - 116.437500, 0.000000, 116.437500, 0.000000, 125.906250, 0.000000, 129.562500, 0.000000, - 139.031250, 0.000000, 145.031250, 0.000000, 154.968750, 0.000000, 164.718750, -0.011719, - 164.718750, 0.263672, 164.437500, 0.000000, 164.437500, 0.000000, 173.906250, 0.000000, - 179.906250, 0.000000, 184.265625, 0.000000, 192.234375, 0.000000, 200.203125, 0.000000, - 203.859375, 0.000000, 211.828125, 0.000000, 217.828125, 0.000000, 227.296875, 0.000000, - 231.375000, 0.000000, 240.843750, 0.000000, 246.843750, 0.000000, 256.740234, -0.011719, - 256.312500, 0.000000, 264.281250, 0.000000, 270.796875, 0.000000, 274.453125, 0.000000, - 282.796875, 0.000000, 282.796875, 0.000000, 282.796875, 0.000000, 292.458984, -0.451172, - 289.312500, 0.000000, 295.312500, 0.000000, 303.281250, 0.000000, 311.250000, 0.000000, - 314.906250, 0.000000, 324.890625, -0.011719, 324.375000, 0.000000, 330.375000, 0.000000, - 339.843750, 0.000000, 349.675781, 0.263672, 349.312500, 0.000000, 349.312500, 0.000000, - 360.187500, 0.000000, 360.187500, 0.000000, 359.384766, 0.275391, 360.187500, 0.000000, - 368.156250, 0.000000, 377.818359, -0.451172, 372.996094, 0.263672, 374.671875, 0.000000, - 380.671875, 0.000000, 388.371094, -0.011719, 391.546875, 0.000000, 398.062500, 0.000000, - 399.421875, 0.000000, 410.296875, 0.000000, 410.296875, 0.000000, 409.494141, 0.275391, - 410.296875, 0.000000, 418.265625, 0.000000, 427.927734, -0.451172, 423.105469, 0.263672, - 424.781250, 0.000000, 430.781250, 0.000000, 440.250000, 0.000000, 449.718750, 0.000000, - 456.832031, 0.263672, 457.687500, 0.000000, 465.656250, 0.000000, 469.312500, 0.000000, - 475.312500, 0.000000, 484.921875, -0.011719, 484.781250, 0.000000, 494.390625, -0.011719, - 494.250000, 0.000000, 500.179688, 0.000000, 500.179688, 0.000000, 509.648438, 0.000000, - 517.617188, 0.000000, 521.976562, 0.000000, 527.976562, 0.000000, 537.445312, 0.000000, - 541.101562, 0.000000, 550.570312, 0.000000, 561.445312, 0.000000, 565.101562, 0.000000, - 569.460938, 0.000000, 575.460938, 0.000000, 583.429688, 0.000000, 587.085938, 0.000000, - 594.351562, 0.000000, 594.351562, 0.000000, 602.320312, 0.000000, 610.289062, 0.000000, - 613.945312, 0.000000, 624.820312, 0.000000, 624.820312, 0.000000, 624.691406, 0.263672, - 624.820312, 0.000000, 632.789062, 0.000000, 638.789062, 0.000000, 643.148438, 0.000000, - 654.023438, 0.000000, 663.492188, 0.000000, 671.191406, -0.011719, 674.367188, 0.000000, - 682.628906, 0.263672, 682.335938, 0.000000, 682.335938, 0.000000, 690.304688, 0.000000, - 696.304688, 0.000000, 705.140625, 0.439453, 705.773438, 0.000000, 715.242188, 0.000000, - 723.210938, 0.000000 - - - - - - - أساسًا، تتعامل الحواسيب فقط مع الأرقام، وتقوم بتخزين الأحرف والمحارف الأخرى بعد أن تُعطي رقما معينا لكل واحد منها. وقبل اختراع "يونِكود"، كان هناك مئات الأنظمة للتشفير وتخصيص هذه الأرقام للمحارف، ولم يوجد نظام تشفير واحد يحتوي على جميع المحارف الضرورية - - - 0x0000CE28, 0x0000CE87, 0x0000CE41, 0x0000CE81, 0x0000CE42, 0x0000CE54, 0x0000CE73, 0x0000CE21, - 0x00000003, 0x0000CE65, 0x0000CE41, 0x0000CE22, 0x0000CE38, 0x0000CE78, 0x0000CE73, 0x0000CE21, - 0x00000003, 0x0000CE5E, 0x0000CE88, 0x0000CE78, 0x0000CE33, 0x00000003, 0x0000CE84, 0x0000CE74, - 0x0000CE5F, 0x00000003, 0x0000CE85, 0x0000CE82, 0x0000CE2C, 0x0000CE38, 0x0000CE87, 0x00000003, - 0x0000CE3E, 0x0000CE37, 0x0000CE21, 0x0000CE81, 0x00000003, 0x0000CE42, 0x0000CE88, 0x0000CE68, - 0x0000CE4C, 0x0000CE2B, 0x00000003, 0x0000CE75, 0x0000CE22, 0x0000CE5C, 0x0000CE7B, 0x00000003, - 0x0000CE3E, 0x0000CE33, 0x0000CE82, 0x0000CE87, 0x00000003, 0x0000CE76, 0x0000CE73, 0x0000CE81, - 0x00000003, 0x00000588, 0x0000CE65, 0x0000CE41, 0x0000CE22, 0x0000CE38, 0x0000CE78, 0x0000CE74, - 0x0000CE73, 0x00000003, 0x0000CE75, 0x0000CE22, 0x0000CE6B, 0x0000CE41, 0x0000FFFE, 0x0000CE8B, - 0x0000CE21, 0x00000003, 0x0000CE7D, 0x0000CE40, 0x0000CE7F, 0x00000003, 0x0000CE4E, 0x0000CE88, - 0x0000CE50, 0x0000CE3C, 0x0000CE2B, 0x0000CE81, 0x00000003, 0x0000CE42, 0x0000CE88, 0x0000CE68, - 0x0000CE4C, 0x0000CE2C, 0x0000CE74, 0x0000CE73, 0x00000003, 0x0000CE28, 0x0000CE78, 0x0000CE5C, - 0x0000CE7B, 0x0000FFFE, 0x0000CE8B, 0x0000CE21, 0x00000003, 0x0000CE29, 0x0000CE22, 0x0000CE20, - 0x0000CE77, 0x00000003, 0x0000CE6D, 0x0000CE22, 0x0000CE7C, 0x0000CE7F, 0x00000003, 0x0000CE79, - 0x0000CE22, 0x0000CE6F, 0x00000003, 0x00000588, 0x00000005, 0x0000CE3D, 0x0000CE82, 0x0000CE70, - 0x000005B5, 0x0000CE7B, 0x0000CE82, 0x0000CE87, 0x00000005, 0x00000003, 0x0000CE5D, 0x0000CE21, - 0x0000CE42, 0x0000CE2C, 0x0000CE3B, 0x0000CE21, 0x00000003, 0x0000CE72, 0x0000CE26, 0x0000CE6B, - 0x0000CE81, 0x00000003, 0x00000011, 0x0000CE22, 0x0000CE80, 0x0000CE7C, 0x0000CE77, 0x00000003, - 0x0000CE3E, 0x0000CE37, 0x0000CE21, 0x0000CE81, 0x00000003, 0x0000CE72, 0x0000CE70, 0x0000CE73, - 0x00000003, 0x0000CE22, 0x0000CE7C, 0x0000CE88, 0x0000CE60, 0x0000CE77, 0x00000003, 0x0000CE22, - 0x0000CE78, 0x0000CE6B, 0x0000CE41, 0x00000003, 0x0000CE86, 0x0000CE58, 0x0000CE60, 0x000005B4, - 0x0000CE2B, 0x00000003, 0x0000CE79, 0x0000CE17, 0x00000003, 0x0000CE3E, 0x0000CE60, 0x0000CE25, - 0x00000003, 0x0000CE83, 0x0000CE42, 0x0000CE3B, 0x0000FFFE, 0x0000CE8B, 0x0000CE21, 0x00000003, - 0x0000CE65, 0x0000CE41, 0x0000CE22, 0x0000CE38, 0x0000CE78, 0x0000CE73, 0x0000CE21, 0x0000CE81, - 0x00000003, 0x0000CE65, 0x0000CE42, 0x0000CE37, 0x0000FFFE, 0x0000CE8B, 0x0000CE21, 0x00000003, - 0x0000CE7A, 0x0000CE87, 0x0000CE44, 0x0000CE3C, 0x0000CE2C, 0x0000CE25, 0x00000003, 0x0000CE75, - 0x0000CE82, 0x0000CE6C, 0x0000CE2B, 0x0000CE81, 0x00000003, 0x00000588, 0x0000CE75, 0x0000CE22, - 0x0000CE6B, 0x0000CE41, 0x0000FFFE, 0x0000CE8B, 0x0000CE21, 0x00000003, 0x0000CE5E, 0x0000CE77, - 0x00000003, 0x0000CE56, 0x0000CE6C, 0x0000CE67, 0x00000003, 0x0000CE24, 0x0000CE88, 0x0000CE47, - 0x0000CE21, 0x0000CE82, 0x0000CE38, 0x0000CE73, 0x0000CE21, 0x00000003, 0x0000CE72, 0x0000CE77, - 0x0000CE22, 0x0000CE60, 0x0000CE2C, 0x0000CE2B, 0x00000003, 0x00000588, 0x0000CE22, 0x000005B0, - 0x0000CE47, 0x0000CE22, 0x0000CE47, 0x0000CE17 - - - - 0x000000FB, 0x000000FA, 0x000000F9, 0x000000F8, 0x000000F7, 0x000000F6, 0x000000F5, 0x000000F4, - 0x000000F3, 0x000000F2, 0x000000F1, 0x000000F0, 0x000000EF, 0x000000EE, 0x000000ED, 0x000000EC, - 0x000000EB, 0x000000EA, 0x000000E9, 0x000000E8, 0x000000E7, 0x000000E6, 0x000000E5, 0x000000E4, - 0x000000E3, 0x000000E2, 0x000000E1, 0x000000E0, 0x000000DF, 0x000000DE, 0x000000DD, 0x000000DC, - 0x000000DB, 0x000000DA, 0x000000D9, 0x000000D8, 0x000000D7, 0x000000D6, 0x000000D5, 0x000000D4, - 0x000000D3, 0x000000D2, 0x000000D1, 0x000000D0, 0x000000CF, 0x000000CE, 0x000000CD, 0x000000CC, - 0x000000CB, 0x000000CA, 0x000000C9, 0x000000C8, 0x000000C7, 0x000000C6, 0x000000C5, 0x000000C4, - 0x000000C3, 0x000000C2, 0x000000C1, 0x000000C0, 0x000000BF, 0x000000BE, 0x000000BD, 0x000000BC, - 0x000000BB, 0x000000BA, 0x000000B9, 0x000000B8, 0x000000B7, 0x000000B6, 0x000000B5, 0x000000B4, - 0x000000B3, 0x000000B2, 0x000000B1, 0x000000B0, 0x000000AF, 0x000000AE, 0x000000AD, 0x000000AC, - 0x000000AB, 0x000000AA, 0x000000A9, 0x000000A8, 0x000000A7, 0x000000A6, 0x000000A5, 0x000000A4, - 0x000000A3, 0x000000A2, 0x000000A1, 0x000000A0, 0x0000009F, 0x0000009E, 0x0000009D, 0x0000009C, - 0x0000009B, 0x0000009A, 0x00000099, 0x00000098, 0x00000097, 0x00000096, 0x00000095, 0x00000094, - 0x00000093, 0x00000092, 0x00000091, 0x00000090, 0x0000008F, 0x0000008E, 0x0000008D, 0x0000008C, - 0x0000008B, 0x0000008A, 0x00000089, 0x00000088, 0x00000087, 0x00000086, 0x00000085, 0x00000084, - 0x00000083, 0x00000082, 0x00000081, 0x00000080, 0x0000007F, 0x0000007E, 0x0000007D, 0x0000007C, - 0x0000007B, 0x0000007A, 0x00000079, 0x00000078, 0x00000077, 0x00000076, 0x00000075, 0x00000074, - 0x00000073, 0x00000072, 0x00000071, 0x00000070, 0x0000006F, 0x0000006E, 0x0000006D, 0x0000006C, - 0x0000006B, 0x0000006A, 0x00000069, 0x00000068, 0x00000067, 0x00000066, 0x00000065, 0x00000064, - 0x00000063, 0x00000062, 0x00000061, 0x00000060, 0x0000005F, 0x0000005E, 0x0000005D, 0x0000005C, - 0x0000005B, 0x0000005A, 0x00000059, 0x00000058, 0x00000057, 0x00000056, 0x00000055, 0x00000054, - 0x00000053, 0x00000052, 0x00000051, 0x00000050, 0x0000004F, 0x0000004E, 0x0000004D, 0x0000004C, - 0x0000004B, 0x0000004A, 0x00000049, 0x00000048, 0x00000047, 0x00000046, 0x00000045, 0x00000044, - 0x00000043, 0x00000042, 0x00000041, 0x00000040, 0x0000003F, 0x0000003E, 0x0000003D, 0x0000003C, - 0x0000003B, 0x0000003A, 0x00000039, 0x00000038, 0x00000037, 0x00000036, 0x00000035, 0x00000034, - 0x00000033, 0x00000032, 0x00000031, 0x00000030, 0x0000002F, 0x0000002E, 0x0000002D, 0x0000002C, - 0x0000002B, 0x0000002A, 0x00000029, 0x00000028, 0x00000027, 0x00000026, 0x00000025, 0x00000024, - 0x00000023, 0x00000022, 0x00000021, 0x00000020, 0x0000001F, 0x0000001E, 0x0000001D, 0x0000001C, - 0x0000001B, 0x0000001A, 0x00000019, 0x00000018, 0x00000017, 0x00000016, 0x00000015, 0x00000014, - 0x00000013, 0x00000012, 0x00000011, 0x00000010, 0x0000000F, 0x0000000E, 0x0000000D, 0x0000000C, - 0x0000000B, 0x0000000A, 0x00000009, 0x00000008, 0x00000007, 0x00000006, 0x00000005, 0x00000004, - 0x00000003, 0x00000002, 0x00000001, 0x00000000 - - - - 0.000000, 0.000000, 4.007812, 0.000000, 8.226562, 0.000000, 12.679688, 0.000000, - 18.679688, 0.000000, 23.132812, 0.000000, 31.289062, 0.000000, 34.312500, 0.000000, - 36.375000, 0.000000, 41.062500, 0.000000, 50.296875, 0.000000, 54.750000, 0.000000, - 56.859375, 0.000000, 62.367188, 0.000000, 66.632812, 0.000000, 69.656250, 0.000000, - 71.718750, 0.000000, 76.406250, 0.000000, 81.421875, 0.000000, 85.664062, 0.000000, - 89.929688, 0.000000, 95.742188, 0.000000, 100.429688, 0.000000, 108.796875, 0.000000, - 112.171875, 0.000000, 115.734375, 0.000000, 120.421875, 0.000000, 128.765625, 0.000000, - 134.765625, 0.000000, 139.007812, 0.000000, 144.515625, 0.000000, 148.734375, 0.000000, - 153.421875, 0.000000, 157.359375, 0.000000, 163.171875, 0.000000, 165.234375, 0.000000, - 171.234375, 0.000000, 175.921875, 0.000000, 180.375000, 0.000000, 184.617188, 0.000000, - 188.085938, 0.000000, 195.117188, 0.000000, 199.312500, 0.000000, 204.000000, 0.000000, - 208.007812, 0.000000, 210.117188, 0.000000, 217.054688, 0.000000, 220.429688, 0.000000, - 225.117188, 0.000000, 229.054688, 0.000000, 234.867188, 0.000000, 240.867188, 0.000000, - 245.085938, 0.000000, 249.773438, 0.000000, 253.781250, 0.000000, 256.804688, 0.000000, - 262.804688, 0.000000, 267.492188, 0.000000, 271.007812, 0.000000, 280.242188, 0.000000, - 284.695312, 0.000000, 286.804688, 0.000000, 292.312500, 0.000000, 296.578125, 0.000000, - 299.953125, 0.000000, 302.976562, 0.000000, 307.664062, 0.000000, 311.671875, 0.000000, - 313.781250, 0.000000, 317.882812, 0.000000, 322.335938, 0.000000, 322.335938, 0.000000, - 328.500000, 0.000000, 330.562500, 0.000000, 335.250000, 0.000000, 339.140625, 0.000000, - 343.078125, 0.000000, 348.984375, 0.000000, 353.671875, 0.000000, 366.445312, 0.000000, - 370.687500, 0.000000, 378.843750, 0.000000, 384.351562, 0.000000, 388.546875, 0.000000, - 394.546875, 0.000000, 399.234375, 0.000000, 403.687500, 0.000000, 407.929688, 0.000000, - 411.398438, 0.000000, 418.429688, 0.000000, 422.671875, 0.000000, 426.046875, 0.000000, - 429.070312, 0.000000, 433.757812, 0.000000, 437.765625, 0.000000, 442.031250, 0.000000, - 448.968750, 0.000000, 452.343750, 0.000000, 452.343750, 0.000000, 458.507812, 0.000000, - 460.570312, 0.000000, 465.257812, 0.000000, 474.492188, 0.000000, 476.601562, 0.000000, - 480.843750, 0.000000, 485.109375, 0.000000, 489.796875, 0.000000, 497.437500, 0.000000, - 499.546875, 0.000000, 503.765625, 0.000000, 509.671875, 0.000000, 514.359375, 0.000000, - 521.671875, 0.000000, 523.781250, 0.000000, 529.453125, 0.000000, 534.140625, 0.000000, - 537.656250, 0.000000, 543.046875, 0.000000, 546.585938, 0.000000, 552.585938, 0.000000, - 560.367188, 0.000000, 560.367188, 0.000000, 563.742188, 0.000000, 569.742188, 0.000000, - 573.960938, 0.000000, 579.351562, 0.000000, 584.039062, 0.000000, 589.851562, 0.000000, - 591.914062, 0.000000, 596.367188, 0.000000, 600.609375, 0.000000, 606.421875, 0.000000, - 608.484375, 0.000000, 613.171875, 0.000000, 619.570312, 0.000000, 623.812500, 0.000000, - 627.914062, 0.000000, 633.914062, 0.000000, 638.601562, 0.000000, 641.929688, 0.000000, - 644.039062, 0.000000, 647.789062, 0.000000, 652.007812, 0.000000, 656.273438, 0.000000, - 660.960938, 0.000000, 664.898438, 0.000000, 670.710938, 0.000000, 672.773438, 0.000000, - 678.773438, 0.000000, 683.460938, 0.000000, 689.859375, 0.000000, 697.640625, 0.000000, - 700.664062, 0.000000, 705.351562, 0.000000, 707.460938, 0.000000, 711.679688, 0.000000, - 715.921875, 0.000000, 719.390625, 0.000000, 723.656250, 0.000000, 728.343750, 0.000000, - 730.453125, 0.000000, 734.718750, 0.000000, 738.820312, 0.000000, 743.273438, 0.000000, - 747.960938, 0.000000, 756.328125, 0.000000, 763.265625, 0.000000, 766.734375, 0.000000, - 766.734375, 0.000000, 770.929688, 0.000000, 775.617188, 0.000000, 782.929688, 0.000000, - 785.273438, 0.000000, 789.960938, 0.000000, 793.898438, 0.000000, 797.367188, 0.000000, - 800.812500, 0.000000, 805.500000, 0.000000, 813.843750, 0.000000, 818.296875, 0.000000, - 824.109375, 0.000000, 824.109375, 0.000000, 830.273438, 0.000000, 832.335938, 0.000000, - 837.023438, 0.000000, 846.257812, 0.000000, 850.710938, 0.000000, 852.820312, 0.000000, - 858.328125, 0.000000, 862.593750, 0.000000, 865.617188, 0.000000, 867.679688, 0.000000, - 873.679688, 0.000000, 878.367188, 0.000000, 887.601562, 0.000000, 892.054688, 0.000000, - 897.867188, 0.000000, 897.867188, 0.000000, 904.031250, 0.000000, 906.093750, 0.000000, - 910.781250, 0.000000, 918.257812, 0.000000, 922.476562, 0.000000, 926.929688, 0.000000, - 932.437500, 0.000000, 936.679688, 0.000000, 940.125000, 0.000000, 944.812500, 0.000000, - 948.820312, 0.000000, 954.820312, 0.000000, 958.289062, 0.000000, 962.484375, 0.000000, - 968.484375, 0.000000, 973.171875, 0.000000, 976.687500, 0.000000, 980.695312, 0.000000, - 982.804688, 0.000000, 986.906250, 0.000000, 991.359375, 0.000000, 991.359375, 0.000000, - 997.523438, 0.000000, 999.585938, 0.000000, 1004.273438, 0.000000, 1009.289062, 0.000000, - 1013.554688, 0.000000, 1018.242188, 0.000000, 1026.187500, 0.000000, 1029.656250, 0.000000, - 1033.757812, 0.000000, 1038.445312, 0.000000, 1047.796875, 0.000000, 1052.039062, 0.000000, - 1058.859375, 0.000000, 1060.921875, 0.000000, 1066.921875, 0.000000, 1072.429688, 0.000000, - 1075.453125, 0.000000, 1077.515625, 0.000000, 1082.203125, 0.000000, 1088.601562, 0.000000, - 1092.867188, 0.000000, 1094.976562, 0.000000, 1098.445312, 0.000000, 1102.687500, 0.000000, - 1106.882812, 0.000000, 1111.570312, 0.000000, 1115.085938, 0.000000, 1117.195312, 0.000000, - 1117.195312, 0.000000, 1124.015625, 0.000000, 1126.125000, 0.000000, 1132.945312, 0.000000, - 1135.289062, 0.000000 - - - - - - - أساسًا، تتعامل الحواسيب فقط مع الأرقام، وتقوم بتخزين الأحرف والمحارف الأخرى بعد أن تُعطي رقما معينا لكل واحد منها. وقبل اختراع "يونِكود"، كان هناك مئات الأنظمة للتشفير وتخصيص هذه الأرقام للمحارف، ولم يوجد نظام تشفير واحد يحتوي على جميع المحارف الضرورية - - - 0x00000872, 0x000008D1, 0x000003F9, 0x0000040B, 0x0000088C, 0x0000089E, 0x000008BD, 0x000003EF, - 0x00000003, 0x00000404, 0x000003F9, 0x0000086C, 0x00000882, 0x000008C2, 0x000008BD, 0x000003EF, - 0x00000003, 0x000008A8, 0x000008D2, 0x000008C2, 0x0000087D, 0x00000003, 0x000008CE, 0x000008BE, - 0x000008A9, 0x00000003, 0x0000040D, 0x000008CC, 0x00000876, 0x00000882, 0x000008D1, 0x00000003, - 0x00000888, 0x00000881, 0x000003EF, 0x0000040B, 0x00000003, 0x0000088C, 0x000008D2, 0x000008B2, - 0x00000896, 0x00000875, 0x00000003, 0x00000408, 0x0000086C, 0x000008A6, 0x000008C5, 0x00000003, - 0x00000888, 0x0000087D, 0x000008CC, 0x000008D1, 0x00000003, 0x000008C0, 0x000008BD, 0x0000040B, - 0x00000003, 0x000003E6, 0x00000404, 0x000003F9, 0x0000086C, 0x00000882, 0x000008C2, 0x000008BE, - 0x000008BD, 0x00000003, 0x00000408, 0x0000086C, 0x000008B5, 0x000003F9, 0x0000FFFF, 0x000008D5, - 0x000003EF, 0x00000003, 0x0000040A, 0x0000088A, 0x000008C9, 0x00000003, 0x00000898, 0x000008D2, - 0x0000089A, 0x00000886, 0x00000875, 0x0000040B, 0x00000003, 0x0000088C, 0x000008D2, 0x000008B2, - 0x00000896, 0x00000876, 0x000008BE, 0x000008BD, 0x00000003, 0x00000872, 0x000008C2, 0x000008A6, - 0x000008C5, 0x0000FFFF, 0x000008D5, 0x000003EF, 0x00000003, 0x000003F2, 0x0000086C, 0x0000086A, - 0x000008C1, 0x00000003, 0x00000406, 0x0000086C, 0x000008C6, 0x000008C9, 0x00000003, 0x00000409, - 0x0000086C, 0x000008B9, 0x00000003, 0x000003E6, 0x00000005, 0x000003F7, 0x000008CC, 0x000008BA, - 0x00000413, 0x000008C5, 0x000008CC, 0x000008D1, 0x00000005, 0x00000003, 0x00000401, 0x000003EF, - 0x0000088C, 0x00000876, 0x00000885, 0x000003EF, 0x00000003, 0x000008BC, 0x00000870, 0x000008B5, - 0x0000040B, 0x00000003, 0x00000011, 0x0000086C, 0x000008CA, 0x000008C6, 0x000008C1, 0x00000003, - 0x00000888, 0x00000881, 0x000003EF, 0x0000040B, 0x00000003, 0x000008BC, 0x000008BA, 0x000008BD, - 0x00000003, 0x0000086C, 0x000008C6, 0x000008D2, 0x000008AA, 0x000008C1, 0x00000003, 0x0000086C, - 0x000008C2, 0x000008B5, 0x000003F9, 0x00000003, 0x000008D0, 0x000008A2, 0x000008AA, 0x00000412, - 0x00000875, 0x00000003, 0x00000409, 0x000003EB, 0x00000003, 0x00000888, 0x000008AA, 0x0000086F, - 0x00000003, 0x0000040C, 0x0000088C, 0x00000885, 0x0000FFFF, 0x000008D5, 0x000003EF, 0x00000003, - 0x00000404, 0x000003F9, 0x0000086C, 0x00000882, 0x000008C2, 0x000008BD, 0x000003EF, 0x0000040B, - 0x00000003, 0x00000404, 0x0000088C, 0x00000881, 0x0000FFFF, 0x000008D5, 0x000003EF, 0x00000003, - 0x000008C4, 0x000008D1, 0x0000088E, 0x00000886, 0x00000876, 0x0000086F, 0x00000003, 0x00000408, - 0x000008CC, 0x000008B6, 0x00000875, 0x0000040B, 0x00000003, 0x000003E6, 0x00000408, 0x0000086C, - 0x000008B5, 0x000003F9, 0x0000FFFF, 0x000008D5, 0x000003EF, 0x00000003, 0x000008A8, 0x000008C1, - 0x00000003, 0x000008A0, 0x000008B6, 0x000008B1, 0x00000003, 0x0000086E, 0x000008D2, 0x00000891, - 0x000003EF, 0x000008CC, 0x00000882, 0x000008BD, 0x000003EF, 0x00000003, 0x000008BC, 0x000008C1, - 0x0000086C, 0x000008AA, 0x00000876, 0x00000875, 0x00000003, 0x000003E6, 0x0000086C, 0x0000040E, - 0x00000891, 0x0000086C, 0x00000891, 0x000003EB - - - - 0x000000FB, 0x000000FA, 0x000000F9, 0x000000F8, 0x000000F7, 0x000000F6, 0x000000F5, 0x000000F4, - 0x000000F3, 0x000000F2, 0x000000F1, 0x000000F0, 0x000000EF, 0x000000EE, 0x000000ED, 0x000000EC, - 0x000000EB, 0x000000EA, 0x000000E9, 0x000000E8, 0x000000E7, 0x000000E6, 0x000000E5, 0x000000E4, - 0x000000E3, 0x000000E2, 0x000000E1, 0x000000E0, 0x000000DF, 0x000000DE, 0x000000DD, 0x000000DC, - 0x000000DB, 0x000000DA, 0x000000D9, 0x000000D8, 0x000000D7, 0x000000D6, 0x000000D5, 0x000000D4, - 0x000000D3, 0x000000D2, 0x000000D1, 0x000000D0, 0x000000CF, 0x000000CE, 0x000000CD, 0x000000CC, - 0x000000CB, 0x000000CA, 0x000000C9, 0x000000C8, 0x000000C7, 0x000000C6, 0x000000C5, 0x000000C4, - 0x000000C3, 0x000000C2, 0x000000C1, 0x000000C0, 0x000000BF, 0x000000BE, 0x000000BD, 0x000000BC, - 0x000000BB, 0x000000BA, 0x000000B9, 0x000000B8, 0x000000B7, 0x000000B6, 0x000000B5, 0x000000B4, - 0x000000B3, 0x000000B2, 0x000000B1, 0x000000B0, 0x000000AF, 0x000000AE, 0x000000AD, 0x000000AC, - 0x000000AB, 0x000000AA, 0x000000A9, 0x000000A8, 0x000000A7, 0x000000A6, 0x000000A5, 0x000000A4, - 0x000000A3, 0x000000A2, 0x000000A1, 0x000000A0, 0x0000009F, 0x0000009E, 0x0000009D, 0x0000009C, - 0x0000009B, 0x0000009A, 0x00000099, 0x00000098, 0x00000097, 0x00000096, 0x00000095, 0x00000094, - 0x00000093, 0x00000092, 0x00000091, 0x00000090, 0x0000008F, 0x0000008E, 0x0000008D, 0x0000008C, - 0x0000008B, 0x0000008A, 0x00000089, 0x00000088, 0x00000087, 0x00000086, 0x00000085, 0x00000084, - 0x00000083, 0x00000082, 0x00000081, 0x00000080, 0x0000007F, 0x0000007E, 0x0000007D, 0x0000007C, - 0x0000007B, 0x0000007A, 0x00000079, 0x00000078, 0x00000077, 0x00000076, 0x00000075, 0x00000074, - 0x00000073, 0x00000072, 0x00000071, 0x00000070, 0x0000006F, 0x0000006E, 0x0000006D, 0x0000006C, - 0x0000006B, 0x0000006A, 0x00000069, 0x00000068, 0x00000067, 0x00000066, 0x00000065, 0x00000064, - 0x00000063, 0x00000062, 0x00000061, 0x00000060, 0x0000005F, 0x0000005E, 0x0000005D, 0x0000005C, - 0x0000005B, 0x0000005A, 0x00000059, 0x00000058, 0x00000057, 0x00000056, 0x00000055, 0x00000054, - 0x00000053, 0x00000052, 0x00000051, 0x00000050, 0x0000004F, 0x0000004E, 0x0000004D, 0x0000004C, - 0x0000004B, 0x0000004A, 0x00000049, 0x00000048, 0x00000047, 0x00000046, 0x00000045, 0x00000044, - 0x00000043, 0x00000042, 0x00000041, 0x00000040, 0x0000003F, 0x0000003E, 0x0000003D, 0x0000003C, - 0x0000003B, 0x0000003A, 0x00000039, 0x00000038, 0x00000037, 0x00000036, 0x00000035, 0x00000034, - 0x00000033, 0x00000032, 0x00000031, 0x00000030, 0x0000002F, 0x0000002E, 0x0000002D, 0x0000002C, - 0x0000002B, 0x0000002A, 0x00000029, 0x00000028, 0x00000027, 0x00000026, 0x00000025, 0x00000024, - 0x00000023, 0x00000022, 0x00000021, 0x00000020, 0x0000001F, 0x0000001E, 0x0000001D, 0x0000001C, - 0x0000001B, 0x0000001A, 0x00000019, 0x00000018, 0x00000017, 0x00000016, 0x00000015, 0x00000014, - 0x00000013, 0x00000012, 0x00000011, 0x00000010, 0x0000000F, 0x0000000E, 0x0000000D, 0x0000000C, - 0x0000000B, 0x0000000A, 0x00000009, 0x00000008, 0x00000007, 0x00000006, 0x00000005, 0x00000004, - 0x00000003, 0x00000002, 0x00000001, 0x00000000 - - - - 0.000000, 0.000000, 6.316406, 0.000000, 10.382812, 0.000000, 15.492188, 0.000000, - 21.035156, 0.000000, 27.058594, 0.000000, 39.527344, 0.000000, 43.792969, 0.000000, - 47.408203, 0.000000, 51.205078, 0.000000, 66.216797, 0.000000, 71.326172, 0.000000, - 74.695312, 0.000000, 83.367188, 0.000000, 90.826172, 0.000000, 95.091797, 0.000000, - 98.707031, 0.000000, 102.503906, 0.000000, 109.962891, 0.000000, 114.949219, 0.000000, - 122.408203, 0.000000, 130.687500, 0.000000, 134.484375, 0.000000, 145.787109, 0.000000, - 150.773438, 0.000000, 156.884766, 0.000000, 160.681641, 0.000000, 172.277344, 0.000000, - 177.919922, 0.000000, 182.906250, 0.000000, 191.578125, 0.000000, 195.644531, 0.000000, - 199.441406, 0.000000, 206.507812, 0.000000, 214.787109, 0.000000, 218.402344, 0.000000, - 223.945312, 0.000000, 227.742188, 0.000000, 233.765625, 0.000000, 238.751953, 0.000000, - 245.185547, 0.000000, 257.982422, 0.000000, 262.048828, 0.000000, 265.845703, 0.000000, - 272.654297, 0.000000, 276.023438, 0.000000, 285.240234, 0.000000, 289.306641, 0.000000, - 293.103516, 0.000000, 300.169922, 0.000000, 308.449219, 0.000000, 314.091797, 0.000000, - 318.158203, 0.000000, 321.955078, 0.000000, 329.572266, 0.000000, 333.837891, 0.000000, - 339.380859, 0.000000, 343.177734, 0.000000, 346.974609, 0.000000, 361.986328, 0.000000, - 367.095703, 0.000000, 370.464844, 0.000000, 379.136719, 0.000000, 386.595703, 0.000000, - 391.582031, 0.000000, 395.847656, 0.000000, 399.644531, 0.000000, 406.453125, 0.000000, - 409.822266, 0.000000, 415.523438, 0.000000, 420.632812, 0.000000, 420.632812, 0.000000, - 427.441406, 0.000000, 431.056641, 0.000000, 434.853516, 0.000000, 441.357422, 0.000000, - 448.423828, 0.000000, 455.912109, 0.000000, 459.708984, 0.000000, 479.255859, 0.000000, - 484.242188, 0.000000, 496.710938, 0.000000, 505.382812, 0.000000, 509.449219, 0.000000, - 514.992188, 0.000000, 518.789062, 0.000000, 524.812500, 0.000000, 529.798828, 0.000000, - 536.232422, 0.000000, 549.029297, 0.000000, 554.015625, 0.000000, 559.001953, 0.000000, - 563.267578, 0.000000, 567.064453, 0.000000, 573.380859, 0.000000, 580.839844, 0.000000, - 590.056641, 0.000000, 594.123047, 0.000000, 594.123047, 0.000000, 600.931641, 0.000000, - 604.546875, 0.000000, 608.343750, 0.000000, 620.636719, 0.000000, 624.005859, 0.000000, - 628.992188, 0.000000, 635.830078, 0.000000, 639.626953, 0.000000, 653.361328, 0.000000, - 656.730469, 0.000000, 661.716797, 0.000000, 669.205078, 0.000000, 673.001953, 0.000000, - 683.777344, 0.000000, 687.146484, 0.000000, 692.660156, 0.000000, 696.457031, 0.000000, - 700.253906, 0.000000, 704.736328, 0.000000, 711.105469, 0.000000, 716.748047, 0.000000, - 722.994141, 0.000000, 722.994141, 0.000000, 727.060547, 0.000000, 732.703125, 0.000000, - 736.769531, 0.000000, 741.251953, 0.000000, 745.048828, 0.000000, 752.507812, 0.000000, - 756.123047, 0.000000, 762.146484, 0.000000, 767.132812, 0.000000, 775.412109, 0.000000, - 779.027344, 0.000000, 782.824219, 0.000000, 794.203125, 0.000000, 799.189453, 0.000000, - 804.890625, 0.000000, 810.433594, 0.000000, 814.230469, 0.000000, 818.027344, 0.000000, - 821.396484, 0.000000, 828.128906, 0.000000, 833.115234, 0.000000, 839.953125, 0.000000, - 843.750000, 0.000000, 850.816406, 0.000000, 859.095703, 0.000000, 862.710938, 0.000000, - 868.253906, 0.000000, 872.050781, 0.000000, 883.429688, 0.000000, 889.675781, 0.000000, - 893.941406, 0.000000, 897.738281, 0.000000, 901.107422, 0.000000, 906.093750, 0.000000, - 911.080078, 0.000000, 917.800781, 0.000000, 924.638672, 0.000000, 928.435547, 0.000000, - 931.804688, 0.000000, 939.263672, 0.000000, 944.964844, 0.000000, 950.074219, 0.000000, - 953.871094, 0.000000, 965.173828, 0.000000, 974.390625, 0.000000, 981.111328, 0.000000, - 981.111328, 0.000000, 985.177734, 0.000000, 988.974609, 0.000000, 999.750000, 0.000000, - 1003.365234, 0.000000, 1007.162109, 0.000000, 1014.228516, 0.000000, 1020.949219, 0.000000, - 1025.015625, 0.000000, 1028.812500, 0.000000, 1040.408203, 0.000000, 1046.431641, 0.000000, - 1054.710938, 0.000000, 1054.710938, 0.000000, 1061.519531, 0.000000, 1065.134766, 0.000000, - 1068.931641, 0.000000, 1083.943359, 0.000000, 1089.052734, 0.000000, 1092.421875, 0.000000, - 1101.093750, 0.000000, 1108.552734, 0.000000, 1112.818359, 0.000000, 1116.433594, 0.000000, - 1121.976562, 0.000000, 1125.773438, 0.000000, 1140.785156, 0.000000, 1146.808594, 0.000000, - 1155.087891, 0.000000, 1155.087891, 0.000000, 1161.896484, 0.000000, 1165.511719, 0.000000, - 1169.308594, 0.000000, 1180.541016, 0.000000, 1184.607422, 0.000000, 1190.630859, 0.000000, - 1199.302734, 0.000000, 1204.289062, 0.000000, 1208.355469, 0.000000, 1212.152344, 0.000000, - 1218.960938, 0.000000, 1224.603516, 0.000000, 1231.037109, 0.000000, 1235.103516, 0.000000, - 1240.646484, 0.000000, 1244.443359, 0.000000, 1248.240234, 0.000000, 1255.048828, 0.000000, - 1258.417969, 0.000000, 1264.119141, 0.000000, 1269.228516, 0.000000, 1269.228516, 0.000000, - 1276.037109, 0.000000, 1279.652344, 0.000000, 1283.449219, 0.000000, 1290.908203, 0.000000, - 1297.746094, 0.000000, 1301.542969, 0.000000, 1311.427734, 0.000000, 1317.861328, 0.000000, - 1323.562500, 0.000000, 1327.359375, 0.000000, 1341.492188, 0.000000, 1346.478516, 0.000000, - 1357.904297, 0.000000, 1361.519531, 0.000000, 1367.162109, 0.000000, 1375.833984, 0.000000, - 1380.099609, 0.000000, 1383.714844, 0.000000, 1387.511719, 0.000000, 1398.890625, 0.000000, - 1405.728516, 0.000000, 1409.097656, 0.000000, 1415.818359, 0.000000, 1420.804688, 0.000000, - 1424.871094, 0.000000, 1428.667969, 0.000000, 1432.464844, 0.000000, 1435.833984, 0.000000, - 1435.833984, 0.000000, 1447.259766, 0.000000, 1450.628906, 0.000000, 1462.054688, 0.000000, - 1465.669922, 0.000000 - - - - - - - บทที่๑พายุไซโคลนโดโรธีอาศัยอยู่ท่ามกลางทุ่งใหญ่ในแคนซัสกับลุงเฮนรีชาวไร่และป้าเอ็มภรรยาชาวไร่บ้านของพวกเขาหลังเล็กเพราะไม้สร้างบ้านต้องขนมาด้วยเกวียนเป็นระยะทางหลายไมล์ - - - 0x000000F3, 0x000000F0, 0x000000F0, 0x0000010E, 0x0000011D, 0x00000126, 0x000000F7, 0x0000010B, - 0x000000FB, 0x00000111, 0x00000119, 0x000000E4, 0x00000117, 0x000000DD, 0x000000FE, 0x000000F2, - 0x00000117, 0x000000ED, 0x00000117, 0x000000FC, 0x000000F1, 0x0000010E, 0x00000106, 0x0000010B, - 0x00000101, 0x0000010A, 0x000000FB, 0x00000106, 0x000000FB, 0x00000112, 0x0000013B, 0x000000F0, - 0x0000013B, 0x0000010B, 0x000000FA, 0x000000DA, 0x000000FE, 0x0000010B, 0x000000E0, 0x000000F0, - 0x00000111, 0x0000013B, 0x000000E0, 0x00000118, 0x00000104, 0x000000E6, 0x0000013B, 0x00000118, - 0x000000F2, 0x00000116, 0x000000DD, 0x000000F2, 0x000000E4, 0x0000010A, 0x00000103, 0x000000DA, - 0x0000010A, 0x000000F3, 0x000000FE, 0x00000111, 0x000000E0, 0x00000115, 0x00000107, 0x000000F2, - 0x000000FC, 0x0000010E, 0x000000E3, 0x0000010B, 0x00000100, 0x00000119, 0x000000FC, 0x0000013B, - 0x00000116, 0x000000FE, 0x00000109, 0x000000F4, 0x00000137, 0x0000010B, 0x00000115, 0x00000106, - 0x0000011C, 0x000000FA, 0x000000F9, 0x000000FC, 0x000000FC, 0x000000FB, 0x0000010B, 0x000000E3, - 0x0000010B, 0x00000100, 0x00000119, 0x000000FC, 0x0000013B, 0x000000F3, 0x0000013C, 0x0000010B, - 0x000000F2, 0x000000DB, 0x00000106, 0x000000E0, 0x000000F7, 0x00000100, 0x000000DA, 0x00000115, - 0x000000DB, 0x0000010B, 0x00000104, 0x000000FE, 0x0000010A, 0x000000E0, 0x00000115, 0x000000FE, - 0x0000011C, 0x000000DA, 0x00000115, 0x000000F7, 0x000000FC, 0x0000010B, 0x00000109, 0x00000119, - 0x000000FA, 0x0000013C, 0x00000103, 0x000000FC, 0x0000013C, 0x0000010B, 0x000000E0, 0x000000F3, - 0x0000013C, 0x0000010B, 0x000000F2, 0x000000EE, 0x0000013C, 0x00000106, 0x000000E0, 0x000000DB, - 0x000000F2, 0x000000FA, 0x0000010B, 0x000000ED, 0x0000013C, 0x00000100, 0x000000FB, 0x00000115, - 0x000000DA, 0x00000100, 0x0000010E, 0x000000FB, 0x000000F2, 0x00000115, 0x000000F4, 0x00000143, - 0x000000F2, 0x000000FC, 0x00000109, 0x000000FB, 0x00000109, 0x000000F0, 0x0000010B, 0x000000E0, - 0x00000104, 0x000000FE, 0x0000010B, 0x000000FB, 0x00000119, 0x000000FA, 0x000000FE, 0x0000013F - - - - 0x00000000, 0x00000001, 0x00000002, 0x00000003, 0x00000004, 0x00000005, 0x00000006, 0x00000007, - 0x00000008, 0x00000009, 0x0000000A, 0x0000000B, 0x0000000C, 0x0000000D, 0x0000000E, 0x0000000F, - 0x00000010, 0x00000011, 0x00000012, 0x00000013, 0x00000014, 0x00000015, 0x00000016, 0x00000017, - 0x00000018, 0x00000019, 0x0000001A, 0x0000001B, 0x0000001C, 0x0000001D, 0x0000001E, 0x0000001F, - 0x00000020, 0x00000021, 0x00000022, 0x00000023, 0x00000024, 0x00000025, 0x00000026, 0x00000027, - 0x00000028, 0x00000029, 0x0000002A, 0x0000002B, 0x0000002C, 0x0000002D, 0x0000002E, 0x0000002F, - 0x00000030, 0x00000031, 0x00000032, 0x00000033, 0x00000034, 0x00000035, 0x00000036, 0x00000037, - 0x00000038, 0x00000039, 0x0000003A, 0x0000003B, 0x0000003C, 0x0000003D, 0x0000003E, 0x0000003F, - 0x00000040, 0x00000041, 0x00000042, 0x00000043, 0x00000044, 0x00000045, 0x00000046, 0x00000047, - 0x00000048, 0x00000049, 0x0000004A, 0x0000004B, 0x0000004C, 0x0000004D, 0x0000004E, 0x0000004F, - 0x00000050, 0x00000051, 0x00000052, 0x00000053, 0x00000054, 0x00000055, 0x00000056, 0x00000057, - 0x00000058, 0x00000059, 0x0000005A, 0x0000005B, 0x0000005C, 0x0000005D, 0x0000005E, 0x0000005F, - 0x00000060, 0x00000061, 0x00000062, 0x00000063, 0x00000064, 0x00000065, 0x00000066, 0x00000067, - 0x00000068, 0x00000069, 0x0000006A, 0x0000006B, 0x0000006C, 0x0000006D, 0x0000006E, 0x0000006F, - 0x00000070, 0x00000071, 0x00000072, 0x00000073, 0x00000074, 0x00000075, 0x00000076, 0x00000077, - 0x00000078, 0x00000079, 0x0000007A, 0x0000007B, 0x0000007C, 0x0000007D, 0x0000007E, 0x0000007F, - 0x00000080, 0x00000081, 0x00000082, 0x00000083, 0x00000084, 0x00000085, 0x00000086, 0x00000087, - 0x00000088, 0x00000089, 0x0000008A, 0x0000008B, 0x0000008C, 0x0000008D, 0x0000008E, 0x0000008F, - 0x00000090, 0x00000091, 0x00000092, 0x00000093, 0x00000094, 0x00000095, 0x00000096, 0x00000097, - 0x00000098, 0x00000099, 0x0000009A, 0x0000009B, 0x0000009C, 0x0000009D, 0x0000009E, 0x0000009F, - 0x000000A0, 0x000000A1, 0x000000A2, 0x000000A3, 0x000000A4, 0x000000A5, 0x000000A6, 0x000000A7 - - - - 0.000000, 0.000000, 5.399414, 0.000000, 10.798828, 0.000000, 16.198242, 0.000000, - 16.198242, 0.000000, 16.198242, 0.000000, 21.046875, 0.000000, 26.616211, 0.000000, - 30.035156, 0.000000, 34.151367, 0.000000, 34.151367, 0.000000, 38.279297, 0.000000, - 43.558594, 0.000000, 47.663086, 0.000000, 52.438477, 0.000000, 57.178711, 0.000000, - 62.698242, 0.000000, 66.802734, 0.000000, 71.601562, 0.000000, 75.706055, 0.000000, - 79.810547, 0.000000, 84.369141, 0.000000, 84.369141, 0.000000, 89.097656, 0.000000, - 92.516602, 0.000000, 97.195312, 0.000000, 97.195312, 0.000000, 101.311523, 0.000000, - 106.040039, 0.000000, 110.156250, 0.000000, 110.156250, 0.000000, 110.156250, 0.000000, - 115.555664, 0.000000, 115.555664, 0.000000, 118.974609, 0.000000, 124.013672, 0.000000, - 128.765625, 0.000000, 133.505859, 0.000000, 136.924805, 0.000000, 140.704102, 0.000000, - 146.103516, 0.000000, 146.103516, 0.000000, 146.103516, 0.000000, 149.882812, 0.000000, - 153.553711, 0.000000, 159.158203, 0.000000, 165.421875, 0.000000, 165.421875, 0.000000, - 169.092773, 0.000000, 174.612305, 0.000000, 179.135742, 0.000000, 183.911133, 0.000000, - 189.430664, 0.000000, 194.709961, 0.000000, 194.709961, 0.000000, 199.989258, 0.000000, - 204.741211, 0.000000, 204.741211, 0.000000, 210.140625, 0.000000, 214.880859, 0.000000, - 214.880859, 0.000000, 218.660156, 0.000000, 220.675781, 0.000000, 225.128906, 0.000000, - 230.648438, 0.000000, 234.752930, 0.000000, 234.752930, 0.000000, 239.613281, 0.000000, - 243.032227, 0.000000, 247.280273, 0.000000, 251.408203, 0.000000, 255.512695, 0.000000, - 255.512695, 0.000000, 260.036133, 0.000000, 264.776367, 0.000000, 269.071289, 0.000000, - 274.470703, 0.000000, 274.470703, 0.000000, 277.889648, 0.000000, 279.905273, 0.000000, - 284.633789, 0.000000, 284.633789, 0.000000, 289.672852, 0.000000, 294.641602, 0.000000, - 298.746094, 0.000000, 302.850586, 0.000000, 306.966797, 0.000000, 310.385742, 0.000000, - 315.246094, 0.000000, 318.665039, 0.000000, 322.913086, 0.000000, 327.041016, 0.000000, - 331.145508, 0.000000, 331.145508, 0.000000, 336.544922, 0.000000, 336.544922, 0.000000, - 339.963867, 0.000000, 345.483398, 0.000000, 350.258789, 0.000000, 354.987305, 0.000000, - 358.766602, 0.000000, 364.335938, 0.000000, 368.583984, 0.000000, 373.335938, 0.000000, - 375.351562, 0.000000, 380.126953, 0.000000, 383.545898, 0.000000, 389.150391, 0.000000, - 393.890625, 0.000000, 393.890625, 0.000000, 397.669922, 0.000000, 399.685547, 0.000000, - 404.425781, 0.000000, 404.425781, 0.000000, 409.177734, 0.000000, 411.193359, 0.000000, - 416.762695, 0.000000, 420.867188, 0.000000, 424.286133, 0.000000, 428.581055, 0.000000, - 432.708984, 0.000000, 437.748047, 0.000000, 437.748047, 0.000000, 443.027344, 0.000000, - 447.131836, 0.000000, 447.131836, 0.000000, 450.550781, 0.000000, 454.330078, 0.000000, - 459.729492, 0.000000, 459.729492, 0.000000, 463.148438, 0.000000, 468.667969, 0.000000, - 473.478516, 0.000000, 473.478516, 0.000000, 478.207031, 0.000000, 481.986328, 0.000000, - 486.761719, 0.000000, 492.281250, 0.000000, 497.320312, 0.000000, 500.739258, 0.000000, - 505.538086, 0.000000, 505.538086, 0.000000, 509.786133, 0.000000, 513.902344, 0.000000, - 515.917969, 0.000000, 520.669922, 0.000000, 524.917969, 0.000000, 524.917969, 0.000000, - 529.034180, 0.000000, 534.553711, 0.000000, 536.569336, 0.000000, 541.968750, 0.000000, - 541.968750, 0.000000, 547.488281, 0.000000, 551.592773, 0.000000, 555.887695, 0.000000, - 560.003906, 0.000000, 564.298828, 0.000000, 569.698242, 0.000000, 573.117188, 0.000000, - 576.896484, 0.000000, 582.500977, 0.000000, 587.241211, 0.000000, 590.660156, 0.000000, - 594.776367, 0.000000, 598.904297, 0.000000, 603.943359, 0.000000, 608.683594, 0.000000, - 608.683594, 0.000000 - - - - - - - أساسًا، تتعامل الحواسيب فقط مع الأرقام، وتقوم بتخزين الأحرف والمحارف الأخرى بعد أن تُعطي رقما معينا لكل واحد منها. وقبل اختراع "يونِكود"، كان هناك مئات الأنظمة للتشفير وتخصيص هذه الأرقام للمحارف، ولم يوجد نظام تشفير واحد يحتوي على جميع المحارف الضرورية - - - 0x00000872, 0x000008D1, 0x000003F9, 0x0000040B, 0x0000088C, 0x0000089E, 0x000008BD, 0x000003EF, - 0x00000003, 0x00000404, 0x000003F9, 0x0000086C, 0x00000882, 0x000008C2, 0x000008BD, 0x000003EF, - 0x00000003, 0x000008A8, 0x000008D2, 0x000008C2, 0x0000087D, 0x00000003, 0x000008CE, 0x000008BE, - 0x000008A9, 0x00000003, 0x0000040D, 0x000008CC, 0x00000876, 0x00000882, 0x000008D1, 0x00000003, - 0x00000888, 0x00000881, 0x000003EF, 0x0000040B, 0x00000003, 0x0000088C, 0x000008D2, 0x000008B2, - 0x00000896, 0x00000875, 0x00000003, 0x00000408, 0x0000086C, 0x000008A6, 0x000008C5, 0x00000003, - 0x00000888, 0x0000087D, 0x000008CC, 0x000008D1, 0x00000003, 0x000008C0, 0x000008BD, 0x0000040B, - 0x00000003, 0x000003E6, 0x00000404, 0x000003F9, 0x0000086C, 0x00000882, 0x000008C2, 0x000008BE, - 0x000008BD, 0x00000003, 0x00000408, 0x0000086C, 0x000008B5, 0x000003F9, 0x0000FFFF, 0x000008D5, - 0x000003EF, 0x00000003, 0x0000040A, 0x0000088A, 0x000008C9, 0x00000003, 0x00000898, 0x000008D2, - 0x0000089A, 0x00000886, 0x00000875, 0x0000040B, 0x00000003, 0x0000088C, 0x000008D2, 0x000008B2, - 0x00000896, 0x00000876, 0x000008BE, 0x000008BD, 0x00000003, 0x00000872, 0x000008C2, 0x000008A6, - 0x000008C5, 0x0000FFFF, 0x000008D5, 0x000003EF, 0x00000003, 0x000003F2, 0x0000086C, 0x0000086A, - 0x000008C1, 0x00000003, 0x00000406, 0x0000086C, 0x000008C6, 0x000008C9, 0x00000003, 0x00000409, - 0x0000086C, 0x000008B9, 0x00000003, 0x000003E6, 0x00000005, 0x000003F7, 0x000008CC, 0x000008BA, - 0x00000413, 0x000008C5, 0x000008CC, 0x000008D1, 0x00000005, 0x00000003, 0x00000401, 0x000003EF, - 0x0000088C, 0x00000876, 0x00000885, 0x000003EF, 0x00000003, 0x000008BC, 0x00000870, 0x000008B5, - 0x0000040B, 0x00000003, 0x00000011, 0x0000086C, 0x000008CA, 0x000008C6, 0x000008C1, 0x00000003, - 0x00000888, 0x00000881, 0x000003EF, 0x0000040B, 0x00000003, 0x000008BC, 0x000008BA, 0x000008BD, - 0x00000003, 0x0000086C, 0x000008C6, 0x000008D2, 0x000008AA, 0x000008C1, 0x00000003, 0x0000086C, - 0x000008C2, 0x000008B5, 0x000003F9, 0x00000003, 0x000008D0, 0x000008A2, 0x000008AA, 0x00000412, - 0x00000875, 0x00000003, 0x00000409, 0x000003EB, 0x00000003, 0x00000888, 0x000008AA, 0x0000086F, - 0x00000003, 0x0000040C, 0x0000088C, 0x00000885, 0x0000FFFF, 0x000008D5, 0x000003EF, 0x00000003, - 0x00000404, 0x000003F9, 0x0000086C, 0x00000882, 0x000008C2, 0x000008BD, 0x000003EF, 0x0000040B, - 0x00000003, 0x00000404, 0x0000088C, 0x00000881, 0x0000FFFF, 0x000008D5, 0x000003EF, 0x00000003, - 0x000008C4, 0x000008D1, 0x0000088E, 0x00000886, 0x00000876, 0x0000086F, 0x00000003, 0x00000408, - 0x000008CC, 0x000008B6, 0x00000875, 0x0000040B, 0x00000003, 0x000003E6, 0x00000408, 0x0000086C, - 0x000008B5, 0x000003F9, 0x0000FFFF, 0x000008D5, 0x000003EF, 0x00000003, 0x000008A8, 0x000008C1, - 0x00000003, 0x000008A0, 0x000008B6, 0x000008B1, 0x00000003, 0x0000086E, 0x000008D2, 0x00000891, - 0x000003EF, 0x000008CC, 0x00000882, 0x000008BD, 0x000003EF, 0x00000003, 0x000008BC, 0x000008C1, - 0x0000086C, 0x000008AA, 0x00000876, 0x00000875, 0x00000003, 0x000003E6, 0x0000086C, 0x0000040E, - 0x00000891, 0x0000086C, 0x00000891, 0x000003EB - - - - 0x000000FB, 0x000000FA, 0x000000F9, 0x000000F8, 0x000000F7, 0x000000F6, 0x000000F5, 0x000000F4, - 0x000000F3, 0x000000F2, 0x000000F1, 0x000000F0, 0x000000EF, 0x000000EE, 0x000000ED, 0x000000EC, - 0x000000EB, 0x000000EA, 0x000000E9, 0x000000E8, 0x000000E7, 0x000000E6, 0x000000E5, 0x000000E4, - 0x000000E3, 0x000000E2, 0x000000E1, 0x000000E0, 0x000000DF, 0x000000DE, 0x000000DD, 0x000000DC, - 0x000000DB, 0x000000DA, 0x000000D9, 0x000000D8, 0x000000D7, 0x000000D6, 0x000000D5, 0x000000D4, - 0x000000D3, 0x000000D2, 0x000000D1, 0x000000D0, 0x000000CF, 0x000000CE, 0x000000CD, 0x000000CC, - 0x000000CB, 0x000000CA, 0x000000C9, 0x000000C8, 0x000000C7, 0x000000C6, 0x000000C5, 0x000000C4, - 0x000000C3, 0x000000C2, 0x000000C1, 0x000000C0, 0x000000BF, 0x000000BE, 0x000000BD, 0x000000BC, - 0x000000BB, 0x000000BA, 0x000000B9, 0x000000B8, 0x000000B7, 0x000000B6, 0x000000B5, 0x000000B4, - 0x000000B3, 0x000000B2, 0x000000B1, 0x000000B0, 0x000000AF, 0x000000AE, 0x000000AD, 0x000000AC, - 0x000000AB, 0x000000AA, 0x000000A9, 0x000000A8, 0x000000A7, 0x000000A6, 0x000000A5, 0x000000A4, - 0x000000A3, 0x000000A2, 0x000000A1, 0x000000A0, 0x0000009F, 0x0000009E, 0x0000009D, 0x0000009C, - 0x0000009B, 0x0000009A, 0x00000099, 0x00000098, 0x00000097, 0x00000096, 0x00000095, 0x00000094, - 0x00000093, 0x00000092, 0x00000091, 0x00000090, 0x0000008F, 0x0000008E, 0x0000008D, 0x0000008C, - 0x0000008B, 0x0000008A, 0x00000089, 0x00000088, 0x00000087, 0x00000086, 0x00000085, 0x00000084, - 0x00000083, 0x00000082, 0x00000081, 0x00000080, 0x0000007F, 0x0000007E, 0x0000007D, 0x0000007C, - 0x0000007B, 0x0000007A, 0x00000079, 0x00000078, 0x00000077, 0x00000076, 0x00000075, 0x00000074, - 0x00000073, 0x00000072, 0x00000071, 0x00000070, 0x0000006F, 0x0000006E, 0x0000006D, 0x0000006C, - 0x0000006B, 0x0000006A, 0x00000069, 0x00000068, 0x00000067, 0x00000066, 0x00000065, 0x00000064, - 0x00000063, 0x00000062, 0x00000061, 0x00000060, 0x0000005F, 0x0000005E, 0x0000005D, 0x0000005C, - 0x0000005B, 0x0000005A, 0x00000059, 0x00000058, 0x00000057, 0x00000056, 0x00000055, 0x00000054, - 0x00000053, 0x00000052, 0x00000051, 0x00000050, 0x0000004F, 0x0000004E, 0x0000004D, 0x0000004C, - 0x0000004B, 0x0000004A, 0x00000049, 0x00000048, 0x00000047, 0x00000046, 0x00000045, 0x00000044, - 0x00000043, 0x00000042, 0x00000041, 0x00000040, 0x0000003F, 0x0000003E, 0x0000003D, 0x0000003C, - 0x0000003B, 0x0000003A, 0x00000039, 0x00000038, 0x00000037, 0x00000036, 0x00000035, 0x00000034, - 0x00000033, 0x00000032, 0x00000031, 0x00000030, 0x0000002F, 0x0000002E, 0x0000002D, 0x0000002C, - 0x0000002B, 0x0000002A, 0x00000029, 0x00000028, 0x00000027, 0x00000026, 0x00000025, 0x00000024, - 0x00000023, 0x00000022, 0x00000021, 0x00000020, 0x0000001F, 0x0000001E, 0x0000001D, 0x0000001C, - 0x0000001B, 0x0000001A, 0x00000019, 0x00000018, 0x00000017, 0x00000016, 0x00000015, 0x00000014, - 0x00000013, 0x00000012, 0x00000011, 0x00000010, 0x0000000F, 0x0000000E, 0x0000000D, 0x0000000C, - 0x0000000B, 0x0000000A, 0x00000009, 0x00000008, 0x00000007, 0x00000006, 0x00000005, 0x00000004, - 0x00000003, 0x00000002, 0x00000001, 0x00000000 - - - - 0.000000, 0.000000, 6.316406, 0.000000, 10.382812, 0.000000, 15.492188, 0.000000, - 21.035156, 0.000000, 27.058594, 0.000000, 39.527344, 0.000000, 43.792969, 0.000000, - 47.408203, 0.000000, 51.205078, 0.000000, 66.216797, 0.000000, 71.326172, 0.000000, - 74.695312, 0.000000, 83.367188, 0.000000, 90.826172, 0.000000, 95.091797, 0.000000, - 98.707031, 0.000000, 102.503906, 0.000000, 109.962891, 0.000000, 114.949219, 0.000000, - 122.408203, 0.000000, 130.687500, 0.000000, 134.484375, 0.000000, 145.787109, 0.000000, - 150.773438, 0.000000, 156.884766, 0.000000, 160.681641, 0.000000, 172.277344, 0.000000, - 177.919922, 0.000000, 182.906250, 0.000000, 191.578125, 0.000000, 195.644531, 0.000000, - 199.441406, 0.000000, 206.507812, 0.000000, 214.787109, 0.000000, 218.402344, 0.000000, - 223.945312, 0.000000, 227.742188, 0.000000, 233.765625, 0.000000, 238.751953, 0.000000, - 245.185547, 0.000000, 257.982422, 0.000000, 262.048828, 0.000000, 265.845703, 0.000000, - 272.654297, 0.000000, 276.023438, 0.000000, 285.240234, 0.000000, 289.306641, 0.000000, - 293.103516, 0.000000, 300.169922, 0.000000, 308.449219, 0.000000, 314.091797, 0.000000, - 318.158203, 0.000000, 321.955078, 0.000000, 329.572266, 0.000000, 333.837891, 0.000000, - 339.380859, 0.000000, 343.177734, 0.000000, 346.974609, 0.000000, 361.986328, 0.000000, - 367.095703, 0.000000, 370.464844, 0.000000, 379.136719, 0.000000, 386.595703, 0.000000, - 391.582031, 0.000000, 395.847656, 0.000000, 399.644531, 0.000000, 406.453125, 0.000000, - 409.822266, 0.000000, 415.523438, 0.000000, 420.632812, 0.000000, 420.632812, 0.000000, - 427.441406, 0.000000, 431.056641, 0.000000, 434.853516, 0.000000, 441.357422, 0.000000, - 448.423828, 0.000000, 455.912109, 0.000000, 459.708984, 0.000000, 479.255859, 0.000000, - 484.242188, 0.000000, 496.710938, 0.000000, 505.382812, 0.000000, 509.449219, 0.000000, - 514.992188, 0.000000, 518.789062, 0.000000, 524.812500, 0.000000, 529.798828, 0.000000, - 536.232422, 0.000000, 549.029297, 0.000000, 554.015625, 0.000000, 559.001953, 0.000000, - 563.267578, 0.000000, 567.064453, 0.000000, 573.380859, 0.000000, 580.839844, 0.000000, - 590.056641, 0.000000, 594.123047, 0.000000, 594.123047, 0.000000, 600.931641, 0.000000, - 604.546875, 0.000000, 608.343750, 0.000000, 620.636719, 0.000000, 624.005859, 0.000000, - 628.992188, 0.000000, 635.830078, 0.000000, 639.626953, 0.000000, 653.361328, 0.000000, - 656.730469, 0.000000, 661.716797, 0.000000, 669.205078, 0.000000, 673.001953, 0.000000, - 683.777344, 0.000000, 687.146484, 0.000000, 692.660156, 0.000000, 696.457031, 0.000000, - 700.253906, 0.000000, 704.736328, 0.000000, 711.105469, 0.000000, 716.748047, 0.000000, - 722.994141, 0.000000, 722.994141, 0.000000, 727.060547, 0.000000, 732.703125, 0.000000, - 736.769531, 0.000000, 741.251953, 0.000000, 745.048828, 0.000000, 752.507812, 0.000000, - 756.123047, 0.000000, 762.146484, 0.000000, 767.132812, 0.000000, 775.412109, 0.000000, - 779.027344, 0.000000, 782.824219, 0.000000, 794.203125, 0.000000, 799.189453, 0.000000, - 804.890625, 0.000000, 810.433594, 0.000000, 814.230469, 0.000000, 818.027344, 0.000000, - 821.396484, 0.000000, 828.128906, 0.000000, 833.115234, 0.000000, 839.953125, 0.000000, - 843.750000, 0.000000, 850.816406, 0.000000, 859.095703, 0.000000, 862.710938, 0.000000, - 868.253906, 0.000000, 872.050781, 0.000000, 883.429688, 0.000000, 889.675781, 0.000000, - 893.941406, 0.000000, 897.738281, 0.000000, 901.107422, 0.000000, 906.093750, 0.000000, - 911.080078, 0.000000, 917.800781, 0.000000, 924.638672, 0.000000, 928.435547, 0.000000, - 931.804688, 0.000000, 939.263672, 0.000000, 944.964844, 0.000000, 950.074219, 0.000000, - 953.871094, 0.000000, 965.173828, 0.000000, 974.390625, 0.000000, 981.111328, 0.000000, - 981.111328, 0.000000, 985.177734, 0.000000, 988.974609, 0.000000, 999.750000, 0.000000, - 1003.365234, 0.000000, 1007.162109, 0.000000, 1014.228516, 0.000000, 1020.949219, 0.000000, - 1025.015625, 0.000000, 1028.812500, 0.000000, 1040.408203, 0.000000, 1046.431641, 0.000000, - 1054.710938, 0.000000, 1054.710938, 0.000000, 1061.519531, 0.000000, 1065.134766, 0.000000, - 1068.931641, 0.000000, 1083.943359, 0.000000, 1089.052734, 0.000000, 1092.421875, 0.000000, - 1101.093750, 0.000000, 1108.552734, 0.000000, 1112.818359, 0.000000, 1116.433594, 0.000000, - 1121.976562, 0.000000, 1125.773438, 0.000000, 1140.785156, 0.000000, 1146.808594, 0.000000, - 1155.087891, 0.000000, 1155.087891, 0.000000, 1161.896484, 0.000000, 1165.511719, 0.000000, - 1169.308594, 0.000000, 1180.541016, 0.000000, 1184.607422, 0.000000, 1190.630859, 0.000000, - 1199.302734, 0.000000, 1204.289062, 0.000000, 1208.355469, 0.000000, 1212.152344, 0.000000, - 1218.960938, 0.000000, 1224.603516, 0.000000, 1231.037109, 0.000000, 1235.103516, 0.000000, - 1240.646484, 0.000000, 1244.443359, 0.000000, 1248.240234, 0.000000, 1255.048828, 0.000000, - 1258.417969, 0.000000, 1264.119141, 0.000000, 1269.228516, 0.000000, 1269.228516, 0.000000, - 1276.037109, 0.000000, 1279.652344, 0.000000, 1283.449219, 0.000000, 1290.908203, 0.000000, - 1297.746094, 0.000000, 1301.542969, 0.000000, 1311.427734, 0.000000, 1317.861328, 0.000000, - 1323.562500, 0.000000, 1327.359375, 0.000000, 1341.492188, 0.000000, 1346.478516, 0.000000, - 1357.904297, 0.000000, 1361.519531, 0.000000, 1367.162109, 0.000000, 1375.833984, 0.000000, - 1380.099609, 0.000000, 1383.714844, 0.000000, 1387.511719, 0.000000, 1398.890625, 0.000000, - 1405.728516, 0.000000, 1409.097656, 0.000000, 1415.818359, 0.000000, 1420.804688, 0.000000, - 1424.871094, 0.000000, 1428.667969, 0.000000, 1432.464844, 0.000000, 1435.833984, 0.000000, - 1435.833984, 0.000000, 1447.259766, 0.000000, 1450.628906, 0.000000, 1462.054688, 0.000000, - 1465.669922, 0.000000 - - - - - - - ुं ं॑ - - - 0x0000029C, 0x000001D5, 0x00000232, 0x00000003, 0x0000029C, 0x00000232, 0x00000233 - - - - 0x00000000, 0x00000000, 0x00000001, 0x00000002, 0x00000003, 0x00000003, 0x00000004 - - - - 0.000000, 0.000000, 7.541016, 0.000000, 7.541016, 0.000000, 7.541016, 0.000000, - 13.541016, 0.000000, 21.082031, 0.000000, 19.953125, -6.052734, 21.082031, 0.000000 - - - - - - - कँ कं कः क॑ क॒ कँ॑ कं॒ कँंः क॒॑ - - - 0x00000080, 0x00000231, 0x00000003, 0x00000080, 0x00000232, 0x00000003, 0x00000080, 0x0000022C, - 0x00000003, 0x00000080, 0x00000233, 0x00000003, 0x00000080, 0x000001DF, 0x00000003, 0x00000080, - 0x00000231, 0x00000233, 0x00000003, 0x00000080, 0x000001DF, 0x00000232, 0x00000003, 0x00000080, - 0x00000231, 0x0000029C, 0x00000232, 0x0000029C, 0x0000022C, 0x00000003, 0x00000080, 0x00000233, - 0x0000029C, 0x000001DF - - - - 0x00000000, 0x00000001, 0x00000002, 0x00000003, 0x00000004, 0x00000005, 0x00000006, 0x00000007, - 0x00000008, 0x00000009, 0x0000000A, 0x0000000B, 0x0000000C, 0x0000000D, 0x0000000E, 0x0000000F, - 0x00000010, 0x00000011, 0x00000012, 0x00000013, 0x00000015, 0x00000014, 0x00000016, 0x00000017, - 0x00000018, 0x00000019, 0x00000019, 0x0000001A, 0x0000001A, 0x0000001B, 0x0000001C, 0x0000001D, - 0x0000001E, 0x0000001E - - - - 0.000000, 0.000000, 10.001953, -0.087891, 10.875000, 0.000000, 16.875000, 0.000000, - 23.783203, 0.439453, 27.750000, 0.000000, 33.750000, 0.000000, 44.625000, 0.000000, - 48.984375, 0.000000, 54.984375, 0.000000, 63.546875, -1.669922, 65.859375, 0.000000, - 71.859375, 0.000000, 80.332031, 0.492188, 82.734375, 0.000000, 88.734375, 0.000000, - 98.736328, -0.087891, 97.431641, -6.228516, 99.609375, 0.000000, 105.609375, 0.000000, - 114.082031, 0.492188, 112.517578, 0.439453, 116.484375, 0.000000, 122.484375, 0.000000, - 132.486328, -0.087891, 133.359375, 0.000000, 140.900391, 0.000000, 140.900391, 0.000000, - 148.441406, 0.000000, 152.800781, 0.000000, 158.800781, 0.000000, 167.363281, -1.669922, - 169.675781, 0.000000, 177.216797, 0.000000, 177.216797, 0.000000 - - - - - - - रू क़् क्ष क्कि क्रि ट्रि हिन्दी र्क्रिं क्षत्रज्ञत्रक्ष श्र थ्र श्र कके र्कें केूकूेकेृ र्कू क़ क क् क्ष क्ष् क्ष्क ज़ ज ज् ज्ञ ज्ञ् ज्ञ्क र्क र्क्क ड्र क्क क़्क क़्क क़् क्ष्क क्ष् त्र्क द्द कि हि रू रु र्के र्कं क् कु के द्द्द क़्ष क्ष र्क्षे द्दत्र्क ज्ञ क्त्व ज्ञ्क र्कँ र्किँ र्केँ र्क्रिँ हिंदी ह्मिह्यिखि ङ्क ङ्म ङ्क्त ङ्ख ङ्ग ङ्घ ङ्क्ष ङ्क्ष्व ङ्क्ष्य र्क्त्वि र्र्र्र कै के कु कू कृ कॅ कॆ हु हू हॆ है हे - - - 0x0000009A, 0x000001FE, 0x00000003, 0x000000A4, 0x00000051, 0x00000003, 0x000000A2, 0x0000FFFF, - 0x0000FFFF, 0x00000003, 0x000001D4, 0x000000C8, 0x0000FFFF, 0x00000080, 0x00000003, 0x000001D1, - 0x00000080, 0x0000009A, 0x00000051, 0x00000003, 0x000001D1, 0x0000008A, 0x0000009A, 0x00000051, - 0x00000003, 0x000001D1, 0x000000A1, 0x000000DB, 0x0000FFFF, 0x00000091, 0x00000223, 0x00000003, - 0x000001D1, 0x00000080, 0x000000E2, 0x0000FFFF, 0x0000009A, 0x00000051, 0x00000232, 0x00000003, - 0x000000A2, 0x0000FFFF, 0x0000FFFF, 0x0000008F, 0x000000E2, 0x0000FFFF, 0x000000A3, 0x0000FFFF, - 0x0000FFFF, 0x0000008F, 0x000000E2, 0x0000FFFF, 0x000000A2, 0x0000FFFF, 0x0000FFFF, 0x00000003, - 0x0000009E, 0x0000009A, 0x00000051, 0x00000003, 0x00000090, 0x0000009A, 0x00000051, 0x00000003, - 0x0000009E, 0x0000009A, 0x00000051, 0x00000003, 0x00000080, 0x00000080, 0x0000022F, 0x00000003, - 0x00000080, 0x0000022F, 0x0000024D, 0x0000FFFF, 0x0000FFFF, 0x00000003, 0x00000080, 0x0000022F, - 0x0000029C, 0x000001D7, 0x00000080, 0x000001D7, 0x0000029C, 0x0000022F, 0x00000080, 0x0000022F, - 0x0000029C, 0x000001D9, 0x00000003, 0x00000080, 0x000001D7, 0x0000005B, 0x0000FFFF, 0x00000003, - 0x000000A4, 0x00000003, 0x00000080, 0x00000003, 0x00000080, 0x00000051, 0x00000003, 0x000000A2, - 0x0000FFFF, 0x0000FFFF, 0x00000003, 0x000000A2, 0x0000FFFF, 0x0000FFFF, 0x00000051, 0x00000003, - 0x000000EA, 0x0000FFFF, 0x0000FFFF, 0x0000FFFF, 0x00000080, 0x00000003, 0x00000003, 0x000000AB, - 0x0000FFFF, 0x00000003, 0x00000087, 0x00000003, 0x00000087, 0x00000051, 0x00000003, 0x000000A3, - 0x0000FFFF, 0x0000FFFF, 0x00000003, 0x000000A3, 0x0000FFFF, 0x0000FFFF, 0x00000051, 0x00000003, - 0x000000EB, 0x0000FFFF, 0x0000FFFF, 0x0000FFFF, 0x00000080, 0x00000003, 0x00000080, 0x0000005B, - 0x0000FFFF, 0x00000003, 0x000000C8, 0x0000FFFF, 0x00000080, 0x0000005B, 0x0000FFFF, 0x00000003, - 0x0000008C, 0x0000009A, 0x00000051, 0x00000003, 0x000000C8, 0x0000FFFF, 0x00000080, 0x00000003, - 0x000000EC, 0x0000FFFF, 0x00000080, 0x00000003, 0x000000EC, 0x0000FFFF, 0x00000080, 0x00000003, - 0x000000A4, 0x00000051, 0x00000003, 0x000000EA, 0x0000FFFF, 0x0000FFFF, 0x0000FFFF, 0x00000080, - 0x00000003, 0x000000A2, 0x0000FFFF, 0x0000FFFF, 0x00000051, 0x00000003, 0x000000D7, 0x0000FFFF, - 0x000000E2, 0x0000FFFF, 0x00000080, 0x00000003, 0x000001A7, 0x0000FFFF, 0x0000FFFF, 0x00000003, - 0x000001D1, 0x00000080, 0x00000003, 0x000001D1, 0x000000A1, 0x00000003, 0x0000009A, 0x000001FE, - 0x00000003, 0x0000009A, 0x000001FD, 0x00000003, 0x00000080, 0x0000022F, 0x0000005A, 0x0000FFFF, - 0x00000003, 0x00000080, 0x0000024D, 0x0000FFFF, 0x0000FFFF, 0x00000003, 0x00000080, 0x00000051, - 0x00000003, 0x00000080, 0x000001D5, 0x00000003, 0x00000080, 0x0000022F, 0x00000003, 0x000000D9, - 0x0000FFFF, 0x000001A7, 0x0000FFFF, 0x0000FFFF, 0x00000003, 0x000000EC, 0x0000FFFF, 0x0000009F, - 0x00000003, 0x000000A2, 0x0000FFFF, 0x0000FFFF, 0x00000003, 0x000000A2, 0x0000FFFF, 0x0000FFFF, - 0x0000022F, 0x0000005A, 0x0000FFFF, 0x00000003, 0x000001A7, 0x0000FFFF, 0x0000FFFF, 0x000000D7, - 0x0000FFFF, 0x000000E2, 0x0000FFFF, 0x00000080, 0x00000003, 0x000000A3, 0x0000FFFF, 0x0000FFFF, - 0x00000003, 0x000000C8, 0x0000FFFF, 0x000000D7, 0x0000FFFF, 0x0000009D, 0x00000003, 0x000000EB, - 0x0000FFFF, 0x0000FFFF, 0x0000FFFF, 0x00000080, 0x00000003, 0x00000080, 0x0000024C, 0x0000FFFF, - 0x0000FFFF, 0x00000003, 0x000001D1, 0x00000080, 0x0000024C, 0x0000FFFF, 0x0000FFFF, 0x00000003, - 0x00000080, 0x0000022F, 0x0000024C, 0x0000FFFF, 0x0000FFFF, 0x00000003, 0x000001D1, 0x00000080, - 0x000000E2, 0x0000FFFF, 0x0000009A, 0x00000051, 0x00000231, 0x00000003, 0x000001D1, 0x000000A1, - 0x00000232, 0x00000091, 0x00000223, 0x00000003, 0x000001D3, 0x000001BA, 0x0000FFFF, 0x0000FFFF, - 0x000001D3, 0x000001BB, 0x0000FFFF, 0x0000FFFF, 0x000001D4, 0x00000081, 0x00000003, 0x000000CC, - 0x0000FFFF, 0x00000080, 0x00000003, 0x000001A2, 0x0000FFFF, 0x0000FFFF, 0x00000003, 0x000000CC, - 0x0000FFFF, 0x000001A0, 0x0000FFFF, 0x0000FFFF, 0x00000003, 0x000000CC, 0x0000FFFF, 0x00000081, - 0x00000003, 0x000000CC, 0x0000FFFF, 0x00000082, 0x00000003, 0x000000CC, 0x0000FFFF, 0x00000083, - 0x00000003, 0x000000CC, 0x0000FFFF, 0x000000A2, 0x0000FFFF, 0x0000FFFF, 0x00000003, 0x000000CC, - 0x0000FFFF, 0x000000EA, 0x0000FFFF, 0x0000FFFF, 0x0000FFFF, 0x0000009D, 0x00000003, 0x000000CC, - 0x0000FFFF, 0x000000EA, 0x0000FFFF, 0x0000FFFF, 0x0000FFFF, 0x00000099, 0x00000003, 0x000001D4, - 0x000000C8, 0x0000FFFF, 0x000000D7, 0x0000FFFF, 0x0000009D, 0x0000005B, 0x0000FFFF, 0x00000003, - 0x0000009A, 0x00000051, 0x0000009A, 0x000000E2, 0x0000FFFF, 0x0000009A, 0x00000051, 0x00000003, - 0x00000080, 0x00000230, 0x00000003, 0x00000080, 0x0000022F, 0x00000003, 0x00000080, 0x000001D5, - 0x00000003, 0x00000080, 0x000001D7, 0x00000003, 0x00000080, 0x000001D9, 0x00000003, 0x00000080, - 0x0000022D, 0x00000003, 0x00000080, 0x0000022E, 0x00000003, 0x000000A1, 0x000001D5, 0x00000003, - 0x000000A1, 0x000001D7, 0x00000003, 0x000000A1, 0x0000022E, 0x00000003, 0x000000A1, 0x00000230, - 0x00000003, 0x000000A1, 0x0000022F - - - - 0x00000000, 0x00000001, 0x00000002, 0x00000003, 0x00000004, 0x00000005, 0x00000006, 0x00000007, - 0x00000008, 0x00000009, 0x0000000D, 0x0000000A, 0x0000000B, 0x0000000C, 0x0000000E, 0x00000012, - 0x0000000F, 0x00000011, 0x00000010, 0x00000013, 0x00000017, 0x00000014, 0x00000016, 0x00000015, - 0x00000018, 0x0000001A, 0x00000019, 0x0000001B, 0x0000001C, 0x0000001D, 0x0000001E, 0x0000001F, - 0x00000025, 0x00000022, 0x00000024, 0x00000023, 0x00000020, 0x00000021, 0x00000026, 0x00000027, - 0x00000028, 0x00000029, 0x0000002A, 0x0000002B, 0x0000002D, 0x0000002C, 0x0000002E, 0x0000002F, - 0x00000030, 0x00000031, 0x00000033, 0x00000032, 0x00000034, 0x00000035, 0x00000036, 0x00000037, - 0x00000038, 0x0000003A, 0x00000039, 0x0000003B, 0x0000003C, 0x0000003E, 0x0000003D, 0x0000003F, - 0x00000040, 0x00000042, 0x00000041, 0x00000043, 0x00000044, 0x00000045, 0x00000046, 0x00000047, - 0x0000004A, 0x0000004B, 0x00000048, 0x00000049, 0x0000004C, 0x0000004D, 0x0000004E, 0x0000004F, - 0x00000050, 0x00000050, 0x00000051, 0x00000052, 0x00000053, 0x00000053, 0x00000054, 0x00000055, - 0x00000056, 0x00000056, 0x00000057, 0x0000005A, 0x0000005B, 0x00000058, 0x00000059, 0x0000005C, - 0x0000005D, 0x0000005E, 0x0000005F, 0x00000060, 0x00000061, 0x00000062, 0x00000063, 0x00000064, - 0x00000065, 0x00000066, 0x00000067, 0x00000068, 0x00000069, 0x0000006A, 0x0000006B, 0x0000006C, - 0x0000006D, 0x0000006E, 0x0000006F, 0x00000070, 0x00000071, 0x00000072, 0x00000073, 0x00000074, - 0x00000075, 0x00000076, 0x00000077, 0x00000078, 0x00000079, 0x0000007A, 0x0000007B, 0x0000007C, - 0x0000007D, 0x0000007E, 0x0000007F, 0x00000080, 0x00000081, 0x00000082, 0x00000083, 0x00000084, - 0x00000085, 0x00000086, 0x00000087, 0x00000088, 0x00000089, 0x0000008A, 0x0000008D, 0x0000008B, - 0x0000008C, 0x0000008E, 0x00000091, 0x00000092, 0x00000093, 0x0000008F, 0x00000090, 0x00000094, - 0x00000095, 0x00000097, 0x00000096, 0x00000098, 0x00000099, 0x0000009A, 0x0000009B, 0x0000009C, - 0x0000009D, 0x0000009E, 0x0000009F, 0x000000A0, 0x000000A1, 0x000000A2, 0x000000A3, 0x000000A4, - 0x000000A5, 0x000000A6, 0x000000A7, 0x000000A8, 0x000000A9, 0x000000AA, 0x000000AB, 0x000000AC, - 0x000000AD, 0x000000AE, 0x000000AF, 0x000000B0, 0x000000B1, 0x000000B2, 0x000000B3, 0x000000B4, - 0x000000B5, 0x000000B6, 0x000000B7, 0x000000B8, 0x000000B9, 0x000000BA, 0x000000BB, 0x000000BC, - 0x000000BE, 0x000000BD, 0x000000BF, 0x000000C1, 0x000000C0, 0x000000C2, 0x000000C3, 0x000000C4, - 0x000000C5, 0x000000C6, 0x000000C7, 0x000000C8, 0x000000CB, 0x000000CC, 0x000000C9, 0x000000CA, - 0x000000CD, 0x000000D0, 0x000000CE, 0x000000CF, 0x000000D1, 0x000000D2, 0x000000D3, 0x000000D4, - 0x000000D5, 0x000000D6, 0x000000D7, 0x000000D8, 0x000000D9, 0x000000DA, 0x000000DB, 0x000000DC, - 0x000000DD, 0x000000DE, 0x000000DF, 0x000000E0, 0x000000E1, 0x000000E2, 0x000000E3, 0x000000E4, - 0x000000E5, 0x000000E6, 0x000000E7, 0x000000E8, 0x000000E9, 0x000000EC, 0x000000ED, 0x000000EE, - 0x000000EF, 0x000000EA, 0x000000EB, 0x000000F0, 0x000000F1, 0x000000F2, 0x000000F3, 0x000000F4, - 0x000000F5, 0x000000F6, 0x000000F7, 0x000000F8, 0x000000F9, 0x000000FA, 0x000000FB, 0x000000FC, - 0x000000FD, 0x000000FE, 0x000000FF, 0x00000100, 0x00000101, 0x00000102, 0x00000103, 0x00000104, - 0x00000105, 0x00000106, 0x00000107, 0x00000108, 0x00000109, 0x0000010C, 0x0000010A, 0x0000010B, - 0x0000010D, 0x0000010E, 0x00000112, 0x00000111, 0x0000010F, 0x00000110, 0x00000113, 0x00000114, - 0x00000117, 0x00000118, 0x00000115, 0x00000116, 0x00000119, 0x0000011A, 0x00000120, 0x0000011D, - 0x0000011F, 0x0000011E, 0x0000011B, 0x0000011C, 0x00000121, 0x00000122, 0x00000124, 0x00000123, - 0x00000125, 0x00000126, 0x00000127, 0x00000128, 0x0000012C, 0x00000129, 0x0000012A, 0x0000012B, - 0x00000130, 0x0000012D, 0x0000012E, 0x0000012F, 0x00000132, 0x00000131, 0x00000133, 0x00000134, - 0x00000135, 0x00000136, 0x00000137, 0x00000138, 0x00000139, 0x0000013A, 0x0000013B, 0x0000013C, - 0x0000013D, 0x0000013E, 0x0000013F, 0x00000140, 0x00000141, 0x00000142, 0x00000143, 0x00000144, - 0x00000145, 0x00000146, 0x00000147, 0x00000148, 0x00000149, 0x0000014A, 0x0000014B, 0x0000014C, - 0x0000014D, 0x0000014E, 0x0000014F, 0x00000150, 0x00000151, 0x00000152, 0x00000153, 0x00000154, - 0x00000155, 0x00000156, 0x00000157, 0x00000158, 0x00000159, 0x0000015A, 0x0000015B, 0x0000015C, - 0x0000015D, 0x0000015E, 0x0000015F, 0x00000160, 0x00000161, 0x00000162, 0x00000163, 0x0000016B, - 0x00000166, 0x00000167, 0x00000168, 0x00000169, 0x0000016A, 0x00000164, 0x00000165, 0x0000016C, - 0x0000016F, 0x00000170, 0x00000171, 0x00000173, 0x00000172, 0x0000016D, 0x0000016E, 0x00000174, - 0x00000175, 0x00000176, 0x00000177, 0x00000178, 0x00000179, 0x0000017A, 0x0000017B, 0x0000017C, - 0x0000017D, 0x0000017E, 0x0000017F, 0x00000180, 0x00000181, 0x00000182, 0x00000183, 0x00000184, - 0x00000185, 0x00000186, 0x00000187, 0x00000188, 0x00000189, 0x0000018A, 0x0000018B, 0x0000018C, - 0x0000018D, 0x0000018E, 0x0000018F, 0x00000190, 0x00000191, 0x00000192, 0x00000193, 0x00000194, - 0x00000195, 0x00000196, 0x00000197 - - - - 0.000000, 0.000000, 6.515625, 0.000000, 8.121094, 0.000000, 14.121094, 0.000000, - 24.861328, -0.451172, 24.996094, 0.000000, 30.996094, 0.000000, 41.871094, 0.000000, - 41.871094, 0.000000, 41.871094, 0.000000, 47.871094, 0.000000, 52.230469, 0.000000, - 60.949219, 0.000000, 60.949219, 0.000000, 71.824219, 0.000000, 77.824219, 0.000000, - 82.183594, 0.000000, 93.058594, 0.000000, 102.720703, -0.451172, 99.574219, 0.000000, - 105.574219, 0.000000, 109.933594, 0.000000, 117.902344, 0.000000, 127.564453, -0.451172, - 124.417969, 0.000000, 130.417969, 0.000000, 134.777344, 0.000000, 142.746094, 0.000000, - 150.011719, 0.000000, 150.011719, 0.000000, 157.980469, 0.000000, 161.636719, 0.000000, - 167.636719, 0.000000, 171.996094, 0.000000, 182.871094, 0.000000, 186.621094, 0.000000, - 186.621094, 0.000000, 196.283203, -0.451172, 191.613281, 0.439453, 193.136719, 0.000000, - 199.136719, 0.000000, 210.011719, 0.000000, 210.011719, 0.000000, 210.011719, 0.000000, - 217.980469, 0.000000, 221.730469, 0.000000, 221.730469, 0.000000, 232.605469, 0.000000, - 232.605469, 0.000000, 232.605469, 0.000000, 240.574219, 0.000000, 244.324219, 0.000000, - 244.324219, 0.000000, 255.199219, 0.000000, 255.199219, 0.000000, 255.199219, 0.000000, - 261.199219, 0.000000, 270.667969, 0.000000, 280.330078, -0.451172, 277.183594, 0.000000, - 283.183594, 0.000000, 292.652344, 0.000000, 302.314453, -0.451172, 299.167969, 0.000000, - 305.167969, 0.000000, 314.636719, 0.000000, 324.298828, -0.451172, 321.152344, 0.000000, - 327.152344, 0.000000, 338.027344, 0.000000, 344.783203, 0.263672, 348.902344, 0.000000, - 354.902344, 0.000000, 361.658203, 0.263672, 362.279297, 0.263672, 365.777344, 0.000000, - 365.777344, 0.000000, 365.777344, 0.000000, 371.777344, 0.000000, 378.533203, 0.263672, - 382.652344, 0.000000, 390.193359, 0.000000, 390.193359, 0.000000, 397.916016, -0.011719, - 401.068359, 0.000000, 408.609375, 0.000000, 408.609375, 0.000000, 415.365234, 0.263672, - 419.484375, 0.000000, 427.025391, 0.000000, 427.025391, 0.000000, 433.025391, 0.000000, - 440.748047, -0.011719, 440.929688, 0.263672, 443.900391, 0.000000, 443.900391, 0.000000, - 449.900391, 0.000000, 460.775391, 0.000000, 466.775391, 0.000000, 477.650391, 0.000000, - 483.650391, 0.000000, 494.390625, -0.451172, 494.525391, 0.000000, 500.525391, 0.000000, - 511.400391, 0.000000, 511.400391, 0.000000, 511.400391, 0.000000, 517.400391, 0.000000, - 528.275391, 0.000000, 528.275391, 0.000000, 531.457031, -0.451172, 528.275391, 0.000000, - 534.275391, 0.000000, 542.994141, 0.000000, 542.994141, 0.000000, 542.994141, 0.000000, - 542.994141, 0.000000, 553.869141, 0.000000, 559.869141, 0.000000, 565.869141, 0.000000, - 575.337891, 0.000000, 575.337891, 0.000000, 581.337891, 0.000000, 590.806641, 0.000000, - 596.806641, 0.000000, 609.597656, -0.451172, 606.275391, 0.000000, 612.275391, 0.000000, - 623.150391, 0.000000, 623.150391, 0.000000, 623.150391, 0.000000, 629.150391, 0.000000, - 640.025391, 0.000000, 640.025391, 0.000000, 643.207031, -0.451172, 640.025391, 0.000000, - 646.025391, 0.000000, 653.994141, 0.000000, 653.994141, 0.000000, 653.994141, 0.000000, - 653.994141, 0.000000, 664.869141, 0.000000, 670.869141, 0.000000, 678.773438, 0.263672, - 681.744141, 0.000000, 681.744141, 0.000000, 687.744141, 0.000000, 696.462891, 0.000000, - 696.462891, 0.000000, 704.367188, 0.263672, 707.337891, 0.000000, 707.337891, 0.000000, - 713.337891, 0.000000, 721.306641, 0.000000, 730.968750, -0.451172, 727.822266, 0.000000, - 733.822266, 0.000000, 742.541016, 0.000000, 742.541016, 0.000000, 753.416016, 0.000000, - 759.416016, 0.000000, 768.134766, 0.000000, 768.134766, 0.000000, 779.009766, 0.000000, - 785.009766, 0.000000, 793.728516, 0.000000, 793.728516, 0.000000, 804.603516, 0.000000, - 810.603516, 0.000000, 821.343750, -0.451172, 821.478516, 0.000000, 827.478516, 0.000000, - 836.197266, 0.000000, 836.197266, 0.000000, 836.197266, 0.000000, 836.197266, 0.000000, - 847.072266, 0.000000, 853.072266, 0.000000, 863.947266, 0.000000, 863.947266, 0.000000, - 867.128906, -0.451172, 863.947266, 0.000000, 869.947266, 0.000000, 875.876953, 0.000000, - 875.876953, 0.000000, 879.626953, 0.000000, 879.626953, 0.000000, 890.501953, 0.000000, - 896.501953, 0.000000, 903.064453, 0.000000, 903.064453, 0.000000, 903.064453, 0.000000, - 909.064453, 0.000000, 913.423828, 0.000000, 924.298828, 0.000000, 930.298828, 0.000000, - 934.658203, 0.000000, 942.626953, 0.000000, 948.626953, 0.000000, 955.142578, 0.000000, - 956.748047, 0.000000, 962.748047, 0.000000, 969.263672, 0.000000, 970.623047, 0.000000, - 976.623047, 0.000000, 983.378906, 0.263672, 983.853516, 0.164062, 987.498047, 0.000000, - 987.498047, 0.000000, 993.498047, 0.000000, 1000.875000, 0.263672, 1004.373047, 0.000000, - 1004.373047, 0.000000, 1004.373047, 0.000000, 1010.373047, 0.000000, 1021.113281, -0.451172, - 1021.248047, 0.000000, 1027.248047, 0.000000, 1034.947266, -0.011719, 1038.123047, 0.000000, - 1044.123047, 0.000000, 1050.878906, 0.263672, 1054.998047, 0.000000, 1060.998047, 0.000000, - 1068.966797, 0.000000, 1068.966797, 0.000000, 1075.529297, 0.000000, 1075.529297, 0.000000, - 1075.529297, 0.000000, 1081.529297, 0.000000, 1090.248047, 0.000000, 1090.248047, 0.000000, - 1098.216797, 0.000000, 1104.216797, 0.000000, 1115.091797, 0.000000, 1115.091797, 0.000000, - 1115.091797, 0.000000, 1121.091797, 0.000000, 1131.966797, 0.000000, 1131.966797, 0.000000, - 1131.164062, 0.275391, 1131.638672, 0.175781, 1131.966797, 0.000000, 1131.966797, 0.000000, - 1137.966797, 0.000000, 1144.529297, 0.000000, 1144.529297, 0.000000, 1144.529297, 0.000000, - 1150.458984, 0.000000, 1150.458984, 0.000000, 1154.208984, 0.000000, 1154.208984, 0.000000, - 1165.083984, 0.000000, 1171.083984, 0.000000, 1181.958984, 0.000000, 1181.958984, 0.000000, - 1181.958984, 0.000000, 1187.958984, 0.000000, 1196.677734, 0.000000, 1196.677734, 0.000000, - 1202.607422, 0.000000, 1202.607422, 0.000000, 1210.576172, 0.000000, 1216.576172, 0.000000, - 1224.544922, 0.000000, 1224.544922, 0.000000, 1224.544922, 0.000000, 1224.544922, 0.000000, - 1235.419922, 0.000000, 1241.419922, 0.000000, 1249.236328, 0.263672, 1252.294922, 0.000000, - 1252.294922, 0.000000, 1252.294922, 0.000000, 1258.294922, 0.000000, 1262.654297, 0.000000, - 1270.470703, 0.263672, 1273.529297, 0.000000, 1273.529297, 0.000000, 1273.529297, 0.000000, - 1279.529297, 0.000000, 1286.285156, 0.263672, 1287.345703, 0.263672, 1290.404297, 0.000000, - 1290.404297, 0.000000, 1290.404297, 0.000000, 1296.404297, 0.000000, 1300.763672, 0.000000, - 1311.638672, 0.000000, 1315.388672, 0.000000, 1315.388672, 0.000000, 1325.050781, -0.451172, - 1323.474609, -0.087891, 1321.904297, 0.000000, 1327.904297, 0.000000, 1332.263672, 0.000000, - 1338.767578, 0.439453, 1340.232422, 0.000000, 1348.201172, 0.000000, 1351.857422, 0.000000, - 1357.857422, 0.000000, 1362.216797, 0.000000, 1372.388672, 0.000000, 1372.388672, 0.000000, - 1372.388672, 0.000000, 1376.748047, 0.000000, 1386.919922, 0.000000, 1386.919922, 0.000000, - 1386.919922, 0.000000, 1391.279297, 0.000000, 1402.154297, 0.000000, 1408.154297, 0.000000, - 1416.123047, 0.000000, 1416.123047, 0.000000, 1426.998047, 0.000000, 1432.998047, 0.000000, - 1446.076172, 0.000000, 1446.076172, 0.000000, 1446.076172, 0.000000, 1452.076172, 0.000000, - 1460.044922, 0.000000, 1460.044922, 0.000000, 1471.294922, 0.000000, 1471.294922, 0.000000, - 1471.294922, 0.000000, 1477.294922, 0.000000, 1485.263672, 0.000000, 1485.263672, 0.000000, - 1496.138672, 0.000000, 1502.138672, 0.000000, 1510.107422, 0.000000, 1510.107422, 0.000000, - 1519.576172, 0.000000, 1525.576172, 0.000000, 1533.544922, 0.000000, 1533.544922, 0.000000, - 1543.013672, 0.000000, 1549.013672, 0.000000, 1556.982422, 0.000000, 1556.982422, 0.000000, - 1567.857422, 0.000000, 1567.857422, 0.000000, 1567.857422, 0.000000, 1573.857422, 0.000000, - 1581.826172, 0.000000, 1581.826172, 0.000000, 1590.544922, 0.000000, 1590.544922, 0.000000, - 1590.544922, 0.000000, 1590.544922, 0.000000, 1598.513672, 0.000000, 1604.513672, 0.000000, - 1612.482422, 0.000000, 1612.482422, 0.000000, 1621.201172, 0.000000, 1621.201172, 0.000000, - 1621.201172, 0.000000, 1621.201172, 0.000000, 1630.669922, 0.000000, 1636.669922, 0.000000, - 1641.029297, 0.000000, 1649.748047, 0.000000, 1649.748047, 0.000000, 1655.677734, 0.000000, - 1655.677734, 0.000000, 1663.939453, 0.263672, 1663.646484, 0.000000, 1663.646484, 0.000000, - 1669.646484, 0.000000, 1679.308594, -0.451172, 1676.162109, 0.000000, 1682.677734, 0.000000, - 1686.427734, 0.000000, 1686.427734, 0.000000, 1696.089844, -0.451172, 1692.943359, 0.000000, - 1698.943359, 0.000000, 1706.314453, 0.263672, 1709.818359, 0.000000, 1715.818359, 0.000000, - 1722.574219, 0.263672, 1726.693359, 0.000000, 1732.693359, 0.000000, 1740.392578, -0.011719, - 1743.568359, 0.000000, 1749.568359, 0.000000, 1757.291016, -0.011719, 1760.443359, 0.000000, - 1766.443359, 0.000000, 1774.376953, -0.011719, 1777.318359, 0.000000, 1783.318359, 0.000000, - 1791.738281, -0.439453, 1794.193359, 0.000000, 1800.193359, 0.000000, 1808.121094, 0.263672, - 1811.068359, 0.000000, 1817.068359, 0.000000, 1823.085938, -0.011719, 1825.037109, 0.000000, - 1831.037109, 0.000000, 1837.078125, -0.011719, 1839.005859, 0.000000, 1845.005859, 0.000000, - 1852.529297, 0.263672, 1852.974609, 0.000000, 1858.974609, 0.000000, 1865.941406, 0.263672, - 1866.943359, 0.000000, 1872.943359, 0.000000, 1879.294922, 0.263672, 1880.912109, 0.000000 - - - - - - - 中华人民共和国 台湾 中華人民共和國 臺灣 - - - 0x000020BC, 0x000025DD, 0x00002149, 0x00003EA0, 0x00002400, 0x0000271B, 0x0000298C, 0x00000003, - 0x0000267F, 0x0000410D, 0x00000003, 0x000020BC, 0x0000567E, 0x00002149, 0x00003EA0, 0x00002400, - 0x0000271B, 0x0000299A, 0x00000003, 0x00005489, 0x000042F2 - - - - 0x00000000, 0x00000001, 0x00000002, 0x00000003, 0x00000004, 0x00000005, 0x00000006, 0x00000007, - 0x00000008, 0x00000009, 0x0000000A, 0x0000000B, 0x0000000C, 0x0000000D, 0x0000000E, 0x0000000F, - 0x00000010, 0x00000011, 0x00000012, 0x00000013, 0x00000014 - - - - 0.000000, 0.000000, 12.000000, 0.000000, 24.000000, 0.000000, 36.000000, 0.000000, - 48.000000, 0.000000, 60.000000, 0.000000, 72.000000, 0.000000, 84.000000, 0.000000, - 87.333984, 0.000000, 99.333984, 0.000000, 111.333984, 0.000000, 114.667969, 0.000000, - 126.667969, 0.000000, 138.667969, 0.000000, 150.667969, 0.000000, 162.667969, 0.000000, - 174.667969, 0.000000, 186.667969, 0.000000, 198.667969, 0.000000, 202.001953, 0.000000, - 214.001953, 0.000000, 226.001953, 0.000000 - - - - - - - - - - - - शङ़ु - - - 0x00000002, 0x00000001, 0x00000006, 0x0000FFFF - - - - 0x00000000, 0x00000001, 0x00000002, 0x00000003 - - - - 0.000000, 0.000000, 7.572000, 0.000000, 15.108000, 0.000000, 15.108000, 0.000000, - 15.108000, 0.000000 - - - - - - - - - क्ष र्क क्‍ष र्‍क - - - 0x000000A2, 0x0000FFFF, 0x0000FFFF, 0x00000003, 0x00000080, 0x0000005B, 0x0000FFFF, 0x00000003, - 0x00000080, 0x00000051, 0x00000001, 0x0000009F, 0x00000003, 0x0000009A, 0x00000051, 0x00000001, - 0x00000080 - - - - 0x00000000, 0x00000001, 0x00000002, 0x00000003, 0x00000006, 0x00000004, 0x00000005, 0x00000007, - 0x00000008, 0x00000009, 0x0000000A, 0x0000000B, 0x0000000C, 0x0000000D, 0x0000000E, 0x0000000F, - 0x00000010 - - - - 0.000000, 0.000000, 10.875000, 0.000000, 10.875000, 0.000000, 10.875000, 0.000000, - 16.875000, 0.000000, 24.779297, 0.263672, 27.750000, 0.000000, 27.750000, 0.000000, - 33.750000, 0.000000, 44.490234, -0.451172, 44.625000, 0.000000, 44.625000, 0.000000, - 52.593750, 0.000000, 58.593750, 0.000000, 68.255859, -0.451172, 65.109375, 0.000000, - 65.109375, 0.000000, 75.984375, 0.000000 - - - - - - - 마만만 - - - 0x00000000, 0x0000FFFF, 0x00000000, 0x0000FFFF, 0x0000FFFF, 0x00000000, 0x0000FFFF - - - - 0x00000000, 0x00000001, 0x00000002, 0x00000003, 0x00000004, 0x00000005, 0x00000006 - - - - 0.000000, 0.000000, 9.000000, 0.000000, 9.000000, 0.000000, 18.000000, 0.000000, - 18.000000, 0.000000, 18.000000, 0.000000, 27.000000, 0.000000, 27.000000, 0.000000 - - - - - - - מָשְׁכֵנִיאַחֲרֶיךָנָּרוּצָההֱבִיאַנִיהַמֶּלֶךְחֲדָרָיונָגִילָהוְנִשְׂמְחָהבָּךְנַזְכִּירָהדֹדֶיךָמִיַּיִןמֵישָׁרִיםאֲהֵבוּךָ - - - 0x0000FFFF, 0x00000055, 0x0000FFFF, 0x0000004B, 0x0000001D, 0x00000097, 0x00000021, 0x00000094, - 0x0000001B, 0x0000002D, 0x00000027, 0x00000096, 0x0000003A, 0x0000009A, 0x0000FFFF, 0x00000066, - 0x00000027, 0x00000097, 0x0000002F, 0x00000030, 0x00000096, 0x00000027, 0x00000099, 0x0000FFFF, - 0x00000051, 0x00000096, 0x0000002F, 0x0000FFFF, 0x00000055, 0x00000027, 0x00000098, 0x0000001F, - 0x0000009C, 0x0000001F, 0x00000021, 0x0000009A, 0x0000003A, 0x00000027, 0x00000096, 0x0000FFFF, - 0x00000056, 0x00000092, 0x00000024, 0x00000099, 0x00000031, 0x0000FFFF, 0x00000054, 0x0000009A, - 0x0000FFFF, 0x00000043, 0x00000021, 0x0000009A, 0x00000025, 0x00000092, 0x0000002F, 0x00000092, - 0x0000FFFF, 0x00000067, 0x00000096, 0x00000031, 0x00000092, 0x00000023, 0x00000021, 0x0000009A, - 0x0000002B, 0x00000027, 0x00000096, 0x0000001E, 0x0000009A, 0x00000031, 0x00000023, 0x00000027, - 0x0000009A, 0x0000003A, 0x0000009A, 0x0000001F, 0x00000094, 0x00000025, 0x0000FFFF, 0x00000054, - 0x00000098, 0x0000002B, 0x00000098, 0x0000FFFF, 0x0000005D, 0x00000099, 0x00000021, 0x00000027, - 0x00000096, 0x00000031, 0x00000099, 0x0000001B, 0x00000027, 0x00000096, 0x0000001D, 0x00000093, - 0x00000021, 0x00000021, 0x0000009A, 0x00000038, 0x0000FFFF, 0x0000004B, 0x0000003A, 0x0000009A, - 0x0000FFFF, 0x0000005E, 0x0000FFFF, 0x00000055, 0x00000027, 0x00000098, 0x0000003A, 0x00000094, - 0x00000025, 0x00000099, 0x0000001B, 0x00000027, 0x00000096, 0x00000031, 0x00000097, 0x00000029, - 0x00000092, 0x0000FFFF, 0x00000066, 0x0000009A, 0x0000002F - - - - 0x0000007C, 0x0000007B, 0x0000007A, 0x00000079, 0x00000078, 0x00000077, 0x00000076, 0x00000075, - 0x00000074, 0x00000073, 0x00000072, 0x00000071, 0x00000070, 0x0000006F, 0x0000006E, 0x0000006D, - 0x0000006C, 0x0000006B, 0x0000006A, 0x00000069, 0x00000068, 0x00000067, 0x00000066, 0x00000065, - 0x00000064, 0x00000063, 0x00000062, 0x00000061, 0x00000060, 0x0000005F, 0x0000005E, 0x0000005D, - 0x0000005C, 0x0000005B, 0x0000005A, 0x00000059, 0x00000058, 0x00000057, 0x00000056, 0x00000055, - 0x00000054, 0x00000053, 0x00000052, 0x00000051, 0x00000050, 0x0000004F, 0x0000004E, 0x0000004D, - 0x0000004C, 0x0000004B, 0x0000004A, 0x00000049, 0x00000048, 0x00000047, 0x00000046, 0x00000045, - 0x00000044, 0x00000043, 0x00000042, 0x00000041, 0x00000040, 0x0000003F, 0x0000003E, 0x0000003D, - 0x0000003C, 0x0000003B, 0x0000003A, 0x00000039, 0x00000038, 0x00000037, 0x00000036, 0x00000035, - 0x00000034, 0x00000033, 0x00000032, 0x00000031, 0x00000030, 0x0000002F, 0x0000002E, 0x0000002D, - 0x0000002C, 0x0000002B, 0x0000002A, 0x00000029, 0x00000028, 0x00000027, 0x00000026, 0x00000025, - 0x00000024, 0x00000023, 0x00000022, 0x00000021, 0x00000020, 0x0000001F, 0x0000001E, 0x0000001D, - 0x0000001C, 0x0000001B, 0x0000001A, 0x00000019, 0x00000018, 0x00000017, 0x00000016, 0x00000015, - 0x00000014, 0x00000013, 0x00000012, 0x00000011, 0x00000010, 0x0000000F, 0x0000000E, 0x0000000D, - 0x0000000C, 0x0000000B, 0x0000000A, 0x00000009, 0x00000008, 0x00000007, 0x00000006, 0x00000005, - 0x00000004, 0x00000003, 0x00000002, 0x00000001, 0x00000000 - - - - 0.000000, 0.000000, 0.000000, 0.000000, 5.806641, 0.000000, 5.806641, 0.000000, - 9.087891, 0.000000, 18.679688, 0.000000, 15.251953, 0.000000, 25.365234, 0.000000, - 21.878906, 0.000000, 28.787109, 0.000000, 35.191406, 0.000000, 42.966797, 0.000000, - 38.279297, 0.000000, 47.982422, 0.000000, 44.085938, 0.000000, 44.085938, 0.000000, - 52.347656, 0.000000, 58.453125, 0.000000, 55.113281, 0.000000, 62.021484, 0.000000, - 66.292969, 0.000000, 64.886719, 0.000000, 69.292969, 0.000000, 67.886719, 0.000000, - 67.886719, 0.000000, 74.050781, 0.000000, 70.710938, 0.000000, 77.619141, 0.000000, - 77.619141, 0.000000, 83.425781, 0.000000, 90.527344, 0.000000, 86.513672, 0.000000, - 91.804688, 0.000000, 92.390625, 0.000000, 98.267578, 0.000000, 109.347656, 0.000000, - 104.894531, 0.000000, 110.701172, 0.000000, 116.250000, 0.000000, 113.701172, 0.000000, - 113.554688, 0.000000, 121.242188, 0.000000, 119.484375, 0.000000, 124.552734, 0.000000, - 122.677734, 0.000000, 126.679688, 0.000000, 126.679688, 0.000000, 135.210938, 0.000000, - 132.486328, 0.000000, 132.251953, 0.000000, 138.416016, 0.000000, 148.734375, 0.000000, - 145.042969, 0.000000, 155.308594, 0.000000, 151.968750, 0.000000, 162.773438, 0.000000, - 158.876953, 0.000000, 158.876953, 0.000000, 168.398438, 0.000000, 166.523438, 0.000000, - 172.546875, 0.000000, 170.525391, 0.000000, 173.689453, 0.000000, 182.718750, 0.000000, - 180.199219, 0.000000, 185.583984, 0.000000, 190.107422, 0.000000, 187.998047, 0.000000, - 194.132812, 0.000000, 192.257812, 0.000000, 196.259766, 0.000000, 199.423828, 0.000000, - 206.964844, 0.000000, 202.511719, 0.000000, 212.332031, 0.000000, 208.318359, 0.000000, - 217.886719, 0.000000, 214.195312, 0.000000, 221.121094, 0.000000, 221.121094, 0.000000, - 229.447266, 0.000000, 226.927734, 0.000000, 235.652344, 0.000000, 232.312500, 0.000000, - 232.312500, 0.000000, 242.648438, 0.000000, 239.220703, 0.000000, 245.847656, 0.000000, - 250.195312, 0.000000, 248.320312, 0.000000, 255.808594, 0.000000, 252.322266, 0.000000, - 259.230469, 0.000000, 265.042969, 0.000000, 262.083984, 0.000000, 271.675781, 0.000000, - 268.248047, 0.000000, 274.875000, 0.000000, 284.197266, 0.000000, 281.501953, 0.000000, - 287.250000, 0.000000, 287.250000, 0.000000, 290.531250, 0.000000, 298.212891, 0.000000, - 296.337891, 0.000000, 296.337891, 0.000000, 300.339844, 0.000000, 300.339844, 0.000000, - 306.146484, 0.000000, 313.687500, 0.000000, 309.234375, 0.000000, 318.732422, 0.000000, - 315.041016, 0.000000, 325.453125, 0.000000, 321.966797, 0.000000, 328.875000, 0.000000, - 333.222656, 0.000000, 331.347656, 0.000000, 338.044922, 0.000000, 335.349609, 0.000000, - 345.175781, 0.000000, 341.279297, 0.000000, 341.279297, 0.000000, 352.558594, 0.000000, - 349.218750, 0.000000, 356.126953, 0.000000 - - - - - - - Ţhiş iş a ţeşţ. - - - 0x00000107, 0x00000049, 0x0000004A, 0x00000104, 0x00000001, 0x0000004A, 0x00000104, 0x00000001, - 0x00000042, 0x00000001, 0x00000108, 0x00000046, 0x00000104, 0x00000108, 0x0000000F - - - - 0x00000000, 0x00000001, 0x00000002, 0x00000003, 0x00000004, 0x00000005, 0x00000006, 0x00000007, - 0x00000008, 0x00000009, 0x0000000A, 0x0000000B, 0x0000000C, 0x0000000D, 0x0000000E - - - - 0.000000, 0.000000, 7.356000, 0.000000, 14.340000, 0.000000, 17.832001, 0.000000, - 22.920000, 0.000000, 25.920000, 0.000000, 29.412001, 0.000000, 34.500000, 0.000000, - 37.500000, 0.000000, 43.500000, 0.000000, 46.500000, 0.000000, 50.411999, 0.000000, - 56.160000, 0.000000, 61.248001, 0.000000, 65.160004, 0.000000, 68.160004, 0.000000 - - - - - - - - - فتح بینچ خلیج شیخ پہنچ - - - 0x0000003B, 0x00000344, 0x000001D5, 0x00000318, 0x00000349, 0x0000007C, 0x00000003, 0x0000003D, - 0x00000348, 0x000001D5, 0x00000346, 0x000000B5, 0x00000003, 0x0000003A, 0x00000348, 0x000001D5, - 0x000002E3, 0x00000344, 0x00000087, 0x00000003, 0x0000003B, 0x00000344, 0x000001D5, 0x00000348, - 0x000001E5, 0x00000347, 0x0000006E, 0x00000003, 0x0000003C, 0x00000345, 0x000001D5, 0x00000344, - 0x0000011D - - - - 0x00000015, 0x00000014, 0x00000014, 0x00000013, 0x00000012, 0x00000012, 0x00000011, 0x00000010, - 0x0000000F, 0x0000000F, 0x0000000E, 0x0000000E, 0x0000000D, 0x0000000C, 0x0000000B, 0x0000000B, - 0x0000000A, 0x00000009, 0x00000009, 0x00000008, 0x00000007, 0x00000006, 0x00000006, 0x00000005, - 0x00000005, 0x00000004, 0x00000004, 0x00000003, 0x00000002, 0x00000001, 0x00000001, 0x00000000, - 0x00000000 - - - - 0.000000, 0.000000, 3.205078, -11.097656, 1.406250, 0.000000, 4.558594, -1.376953, - 9.421875, -5.273438, 7.505859, -6.843750, 11.537109, 0.000000, 12.726562, 0.000000, - 20.455078, -4.798828, 15.357422, 0.000000, 19.289062, -13.072266, 18.509766, -1.376953, - 22.552734, 0.000000, 23.742188, 0.000000, 30.246094, -4.798828, 25.148438, 0.000000, - 28.300781, -1.376953, 32.917969, -13.792969, 30.158203, -7.792969, 35.208984, 0.000000, - 36.398438, 0.000000, 39.603516, -11.097656, 37.804688, 0.000000, 45.632812, 3.181641, - 40.957031, -1.376953, 44.853516, -6.046875, 42.457031, -5.572266, 46.066406, 0.000000, - 47.255859, 0.000000, 49.376953, -11.396484, 48.662109, 0.000000, 52.769531, -14.332031, - 51.814453, -1.376953, 56.789062, 0.000000 - - - - - - - ണു് - - - 0x00000023, 0x0000003C, 0x00000045 - - - - 0x00000000, 0x00000001, 0x00000002 - - - - 0.000000, 0.000000, 15.117188, 0.000000, 18.503906, 0.000000, 18.503906, 0.000000 - - - - - - - 中華人民共和國 臺灣 - - - 0x00000292, 0x000024E8, 0x000002D1, 0x00001582, 0x000004A1, 0x00000650, 0x000007E2, 0x00000021, - 0x00002395, 0x00001896 - - - - 0x00000000, 0x00000001, 0x00000002, 0x00000003, 0x00000004, 0x00000005, 0x00000006, 0x00000007, - 0x00000008, 0x00000009 - - - - 0.000000, 0.000000, 12.000000, 0.000000, 24.000000, 0.000000, 36.000000, 0.000000, - 48.000000, 0.000000, 60.000000, 0.000000, 72.000000, 0.000000, 84.000000, 0.000000, - 90.000000, 0.000000, 102.000000, 0.000000, 114.000000, 0.000000 - - - - - - - ప్రకాష్ - - - 0x00000057, 0x0000023B, 0x0000FFFF, 0x00000125, 0x00000066, 0x00000241, 0x0000FFFF - - - - 0x00000000, 0x00000002, 0x00000001, 0x00000003, 0x00000004, 0x00000005, 0x00000006 - - - - 0.000000, 0.000000, 8.285156, 0.000000, 14.894531, 0.000000, 14.894531, 0.000000, - 21.503906, 0.000000, 25.136719, 0.000000, 33.421875, 0.000000, 33.421875, 0.000000 - - - - - - - บทที่๑พายุไซโคลนโดโรธีอาศัยอยู่ท่ามกลางทุ่งใหญ่ในแคนซัสกับลุงเฮนรีชาวไร่และป้าเอ็มภรรยาชาวไร่บ้านของพวกเขาหลังเล็กเพราะไม้สร้างบ้านต้องขนมาด้วยเกวียนเป็นระยะทางหลายไมล์ - - - 0x0000009D, 0x0000009A, 0x0000009A, 0x000000B8, 0x000000C9, 0x000000D2, 0x000000A1, 0x000000B5, - 0x000000A5, 0x000000BB, 0x000000C5, 0x0000008E, 0x000000C3, 0x00000087, 0x000000A8, 0x0000009C, - 0x000000C3, 0x00000097, 0x000000C3, 0x000000A6, 0x0000009B, 0x000000B8, 0x000000B0, 0x000000B5, - 0x000000AB, 0x000000B4, 0x000000A5, 0x000000B0, 0x000000A5, 0x000000BC, 0x0000006E, 0x0000009A, - 0x0000006E, 0x000000B5, 0x000000A4, 0x00000084, 0x000000A8, 0x000000B5, 0x0000008A, 0x0000009A, - 0x000000BB, 0x0000006E, 0x0000008A, 0x000000C4, 0x000000AE, 0x00000090, 0x0000006E, 0x000000C4, - 0x0000009C, 0x000000C2, 0x00000087, 0x0000009C, 0x0000008E, 0x000000B4, 0x000000AD, 0x00000084, - 0x000000B4, 0x0000009D, 0x000000A8, 0x000000BB, 0x0000008A, 0x000000C1, 0x000000B1, 0x0000009C, - 0x000000A6, 0x000000B8, 0x0000008D, 0x000000B5, 0x000000AA, 0x000000C5, 0x000000A6, 0x0000006E, - 0x000000C2, 0x000000A8, 0x000000B3, 0x0000009E, 0x0000006F, 0x000000B5, 0x000000C1, 0x000000B0, - 0x000000C8, 0x000000A4, 0x000000A3, 0x000000A6, 0x000000A6, 0x000000A5, 0x000000B5, 0x0000008D, - 0x000000B5, 0x000000AA, 0x000000C5, 0x000000A6, 0x0000006E, 0x0000009D, 0x0000006F, 0x000000B5, - 0x0000009C, 0x00000085, 0x000000B0, 0x0000008A, 0x000000A1, 0x000000AA, 0x00000084, 0x000000C1, - 0x00000085, 0x000000B5, 0x000000AE, 0x000000A8, 0x000000B4, 0x0000008A, 0x000000C1, 0x000000A8, - 0x000000C8, 0x00000084, 0x000000C1, 0x000000A1, 0x000000A6, 0x000000B5, 0x000000B3, 0x000000C5, - 0x000000A4, 0x0000006F, 0x000000AD, 0x000000A6, 0x0000006F, 0x000000B5, 0x0000008A, 0x0000009D, - 0x0000006F, 0x000000B5, 0x0000009C, 0x00000098, 0x0000006F, 0x000000B0, 0x0000008A, 0x00000085, - 0x0000009C, 0x000000A4, 0x000000B5, 0x00000097, 0x0000006F, 0x000000AA, 0x000000A5, 0x000000C1, - 0x00000084, 0x000000AA, 0x000000B8, 0x000000A5, 0x0000009C, 0x000000C1, 0x0000009E, 0x000000C8, - 0x0000009C, 0x000000A6, 0x000000B3, 0x000000A5, 0x000000B3, 0x0000009A, 0x000000B5, 0x0000008A, - 0x000000AE, 0x000000A8, 0x000000B5, 0x000000A5, 0x000000C5, 0x000000A4, 0x000000A8, 0x00000072 - - - - 0x00000000, 0x00000001, 0x00000002, 0x00000003, 0x00000004, 0x00000005, 0x00000006, 0x00000007, - 0x00000008, 0x00000009, 0x0000000A, 0x0000000B, 0x0000000C, 0x0000000D, 0x0000000E, 0x0000000F, - 0x00000010, 0x00000011, 0x00000012, 0x00000013, 0x00000014, 0x00000015, 0x00000016, 0x00000017, - 0x00000018, 0x00000019, 0x0000001A, 0x0000001B, 0x0000001C, 0x0000001D, 0x0000001E, 0x0000001F, - 0x00000020, 0x00000021, 0x00000022, 0x00000023, 0x00000024, 0x00000025, 0x00000026, 0x00000027, - 0x00000028, 0x00000029, 0x0000002A, 0x0000002B, 0x0000002C, 0x0000002D, 0x0000002E, 0x0000002F, - 0x00000030, 0x00000031, 0x00000032, 0x00000033, 0x00000034, 0x00000035, 0x00000036, 0x00000037, - 0x00000038, 0x00000039, 0x0000003A, 0x0000003B, 0x0000003C, 0x0000003D, 0x0000003E, 0x0000003F, - 0x00000040, 0x00000041, 0x00000042, 0x00000043, 0x00000044, 0x00000045, 0x00000046, 0x00000047, - 0x00000048, 0x00000049, 0x0000004A, 0x0000004B, 0x0000004C, 0x0000004D, 0x0000004E, 0x0000004F, - 0x00000050, 0x00000051, 0x00000052, 0x00000053, 0x00000054, 0x00000055, 0x00000056, 0x00000057, - 0x00000058, 0x00000059, 0x0000005A, 0x0000005B, 0x0000005C, 0x0000005D, 0x0000005E, 0x0000005F, - 0x00000060, 0x00000061, 0x00000062, 0x00000063, 0x00000064, 0x00000065, 0x00000066, 0x00000067, - 0x00000068, 0x00000069, 0x0000006A, 0x0000006B, 0x0000006C, 0x0000006D, 0x0000006E, 0x0000006F, - 0x00000070, 0x00000071, 0x00000072, 0x00000073, 0x00000074, 0x00000075, 0x00000076, 0x00000077, - 0x00000078, 0x00000079, 0x0000007A, 0x0000007B, 0x0000007C, 0x0000007D, 0x0000007E, 0x0000007F, - 0x00000080, 0x00000081, 0x00000082, 0x00000083, 0x00000084, 0x00000085, 0x00000086, 0x00000087, - 0x00000088, 0x00000089, 0x0000008A, 0x0000008B, 0x0000008C, 0x0000008D, 0x0000008E, 0x0000008F, - 0x00000090, 0x00000091, 0x00000092, 0x00000093, 0x00000094, 0x00000095, 0x00000096, 0x00000097, - 0x00000098, 0x00000099, 0x0000009A, 0x0000009B, 0x0000009C, 0x0000009D, 0x0000009E, 0x0000009F, - 0x000000A0, 0x000000A1, 0x000000A2, 0x000000A3, 0x000000A4, 0x000000A5, 0x000000A6, 0x000000A7 - - - - 0.000000, 0.000000, 5.399414, 0.000000, 10.798828, 0.000000, 15.072266, 0.000000, - 15.072266, 0.000000, 16.198242, 0.000000, 21.046875, 0.000000, 26.616211, 0.000000, - 30.035156, 0.000000, 31.312500, 0.000000, 34.151367, 0.000000, 38.279297, 0.000000, - 43.558594, 0.000000, 47.663086, 0.000000, 52.438477, 0.000000, 57.178711, 0.000000, - 62.698242, 0.000000, 66.802734, 0.000000, 71.601562, 0.000000, 75.706055, 0.000000, - 79.810547, 0.000000, 84.029297, 0.000000, 84.369141, 0.000000, 89.097656, 0.000000, - 92.516602, 0.000000, 97.614258, 0.000000, 97.195312, 0.000000, 101.311523, 0.000000, - 106.040039, 0.000000, 107.375977, 0.000000, 108.326172, -0.084961, 110.156250, 0.000000, - 113.497070, -0.084961, 115.555664, 0.000000, 118.974609, 0.000000, 124.013672, 0.000000, - 128.765625, 0.000000, 133.505859, 0.000000, 136.924805, 0.000000, 140.704102, 0.000000, - 143.036133, 0.000000, 144.044922, -0.084961, 146.103516, 0.000000, 149.882812, 0.000000, - 153.553711, 0.000000, 159.158203, 0.000000, 163.377930, -0.084961, 165.421875, 0.000000, - 169.092773, 0.000000, 174.612305, 0.000000, 179.135742, 0.000000, 183.911133, 0.000000, - 189.430664, 0.000000, 194.879883, 0.000000, 194.709961, 0.000000, 199.989258, 0.000000, - 204.092773, -0.084961, 204.741211, 0.000000, 210.140625, 0.000000, 212.828125, 0.000000, - 214.880859, 0.000000, 218.660156, 0.000000, 220.675781, 0.000000, 225.128906, 0.000000, - 230.648438, 0.000000, 234.105469, 0.000000, 234.752930, 0.000000, 239.613281, 0.000000, - 243.032227, 0.000000, 247.280273, 0.000000, 251.408203, 0.000000, 253.932617, -0.084961, - 255.512695, 0.000000, 260.036133, 0.000000, 264.776367, 0.000000, 269.071289, 0.000000, - 272.704102, 0.000000, 274.470703, 0.000000, 277.889648, 0.000000, 279.905273, 0.000000, - 284.768555, 0.000000, 284.633789, 0.000000, 289.672852, 0.000000, 294.641602, 0.000000, - 298.746094, 0.000000, 302.850586, 0.000000, 306.966797, 0.000000, 310.385742, 0.000000, - 315.246094, 0.000000, 318.665039, 0.000000, 322.913086, 0.000000, 327.041016, 0.000000, - 329.565430, -0.084961, 331.145508, 0.000000, 335.911133, 0.000000, 336.544922, 0.000000, - 339.963867, 0.000000, 345.483398, 0.000000, 350.258789, 0.000000, 354.987305, 0.000000, - 358.766602, 0.000000, 364.335938, 0.000000, 368.583984, 0.000000, 373.335938, 0.000000, - 375.351562, 0.000000, 380.126953, 0.000000, 383.545898, 0.000000, 389.150391, 0.000000, - 394.306641, 0.000000, 393.890625, 0.000000, 397.669922, 0.000000, 399.685547, 0.000000, - 404.548828, 0.000000, 404.425781, 0.000000, 409.177734, 0.000000, 411.193359, 0.000000, - 416.762695, 0.000000, 420.867188, 0.000000, 424.286133, 0.000000, 428.581055, 0.000000, - 432.708984, 0.000000, 438.123047, 0.000000, 437.748047, 0.000000, 443.027344, 0.000000, - 446.976562, 0.000000, 447.131836, 0.000000, 450.550781, 0.000000, 454.330078, 0.000000, - 459.095703, 0.000000, 459.729492, 0.000000, 463.148438, 0.000000, 468.667969, 0.000000, - 473.847656, 0.000000, 473.478516, 0.000000, 478.207031, 0.000000, 481.986328, 0.000000, - 486.761719, 0.000000, 492.281250, 0.000000, 497.320312, 0.000000, 500.739258, 0.000000, - 505.860352, 0.000000, 505.538086, 0.000000, 509.786133, 0.000000, 513.902344, 0.000000, - 515.917969, 0.000000, 520.669922, 0.000000, 523.947266, 0.000000, 524.917969, 0.000000, - 529.034180, 0.000000, 534.553711, 0.000000, 536.569336, 0.000000, 540.846680, 0.000000, - 541.968750, 0.000000, 547.488281, 0.000000, 551.592773, 0.000000, 555.887695, 0.000000, - 560.003906, 0.000000, 564.298828, 0.000000, 569.698242, 0.000000, 573.117188, 0.000000, - 576.896484, 0.000000, 582.500977, 0.000000, 587.241211, 0.000000, 590.660156, 0.000000, - 594.776367, 0.000000, 598.904297, 0.000000, 603.943359, 0.000000, 608.894531, 0.000000, - 608.683594, 0.000000 - - - - - - - - - ‭ﻲﺑﺮﻌﻟﺎﺑ - - - 0x0000FFFF, 0x00000206, 0x000001A5, 0x000001C2, 0x000001E0, 0x000001F3, 0x000001A2, 0x000001A5 - - - - 0x00000000, 0x00000001, 0x00000002, 0x00000003, 0x00000004, 0x00000005, 0x00000006, 0x00000007 - - - - 0.000000, 0.000000, 0.000000, 0.000000, 5.759766, 0.000000, 7.980469, 0.000000, - 11.748047, 0.000000, 15.298828, 0.000000, 17.302734, 0.000000, 19.763672, 0.000000, - 21.984375, 0.000000 - - - - - - - ﻲﺑﺮﻌﻟﺎﺑ - - - 0x000001A5, 0x000001A2, 0x000001F3, 0x000001E0, 0x000001C2, 0x000001A5, 0x00000206 - - - - 0x00000006, 0x00000005, 0x00000004, 0x00000003, 0x00000002, 0x00000001, 0x00000000 - - - - 0.000000, 0.000000, 2.220703, 0.000000, 4.681641, 0.000000, 6.685547, 0.000000, - 10.236328, 0.000000, 14.003906, 0.000000, 16.224609, 0.000000, 21.984375, 0.000000 - - - - - - - ḤḤ - - - 0x000009A3, 0x000009A3 - - - - 0x00000000, 0x00000001 - - - - 0.000000, 0.000000, 8.666016, 0.000000, 17.332031, 0.000000 - - - - - - - र्य र्‌य - - - 0x00000099, 0x0000005B, 0x0000FFFF, 0x00000003, 0x0000009A, 0x00000051, 0x00000001, 0x00000099 - - - - 0x00000002, 0x00000000, 0x00000001, 0x00000003, 0x00000004, 0x00000005, 0x00000006, 0x00000007 - - - - 0.000000, 0.000000, 9.726562, 0.263672, 9.468750, 0.000000, 9.468750, 0.000000, - 15.468750, 0.000000, 25.130859, -0.451172, 21.984375, 0.000000, 21.984375, 0.000000, - 31.453125, 0.000000 - - - - - - - - - - - Li kien kien, li kieku kieku. - - - 0x0000000D, 0x00000024, 0x00000001, 0x00000026, 0x00000024, 0x00000020, 0x00000029, 0x00000001, - 0x00000026, 0x00000024, 0x00000020, 0x00000029, 0x000001D0, 0x00000001, 0x00000027, 0x00000024, - 0x00000001, 0x00000026, 0x00000024, 0x00000020, 0x00000026, 0x00000030, 0x00000001, 0x00000026, - 0x00000024, 0x00000020, 0x00000026, 0x00000030, 0x000001CF - - - - 0x00000000, 0x00000001, 0x00000002, 0x00000003, 0x00000004, 0x00000005, 0x00000006, 0x00000007, - 0x00000008, 0x00000009, 0x0000000A, 0x0000000B, 0x0000000C, 0x0000000D, 0x0000000E, 0x0000000F, - 0x00000010, 0x00000011, 0x00000012, 0x00000013, 0x00000014, 0x00000015, 0x00000016, 0x00000017, - 0x00000018, 0x00000019, 0x0000001A, 0x0000001B, 0x0000001C - - - - 0.000000, 0.000000, 7.200000, 0.000000, 14.400000, 0.000000, 21.599998, 0.000000, - 28.799999, 0.000000, 36.000000, 0.000000, 43.200001, 0.000000, 50.400002, 0.000000, - 57.600002, 0.000000, 64.800003, 0.000000, 72.000000, 0.000000, 79.199997, 0.000000, - 86.399994, 0.000000, 93.599991, 0.000000, 100.799988, 0.000000, 107.999985, 0.000000, - 115.199982, 0.000000, 122.399979, 0.000000, 129.599976, 0.000000, 136.799973, 0.000000, - 143.999969, 0.000000, 151.199966, 0.000000, 158.399963, 0.000000, 165.599960, 0.000000, - 172.799957, 0.000000, 179.999954, 0.000000, 187.199951, 0.000000, 194.399948, 0.000000, - 201.599945, 0.000000, 208.799942, 0.000000 - - - - - - - Il-Mistoqsija oħt l-għerf. - - - 0x0000000A, 0x00000027, 0x00000210, 0x0000000E, 0x00000024, 0x0000002E, 0x0000002F, 0x0000002A, - 0x0000002C, 0x0000002E, 0x00000024, 0x00000025, 0x0000001C, 0x00000001, 0x0000002A, 0x0000011F, - 0x0000002F, 0x00000001, 0x00000027, 0x00000210, 0x00000022, 0x0000011F, 0x00000020, 0x0000002D, - 0x00000021, 0x000001FB - - - - 0x00000000, 0x00000001, 0x00000002, 0x00000003, 0x00000004, 0x00000005, 0x00000006, 0x00000007, - 0x00000008, 0x00000009, 0x0000000A, 0x0000000B, 0x0000000C, 0x0000000D, 0x0000000E, 0x0000000F, - 0x00000010, 0x00000011, 0x00000012, 0x00000013, 0x00000014, 0x00000015, 0x00000016, 0x00000017, - 0x00000018, 0x00000019 - - - - 0.000000, 0.000000, 3.096000, 0.000000, 6.156000, 0.000000, 9.888000, 0.000000, - 18.552000, 0.000000, 21.504000, 0.000000, 26.532000, 0.000000, 30.467999, 0.000000, - 36.972000, 0.000000, 43.571999, 0.000000, 48.599998, 0.000000, 51.551998, 0.000000, - 54.515999, 0.000000, 60.660000, 0.000000, 63.084000, 0.000000, 69.587997, 0.000000, - 76.115997, 0.000000, 80.171997, 0.000000, 82.596001, 0.000000, 85.655998, 0.000000, - 89.388000, 0.000000, 95.435997, 0.000000, 101.963997, 0.000000, 107.916000, 0.000000, - 112.080002, 0.000000, 114.984001, 0.000000, 117.972000, 0.000000 - - - - - - - ༄༅།། ཏིན་ཏིན་གྱི་དཔའ་རྩལ - - - 0x00000145, 0x0000FFFF, 0x00000151, 0x00000151, 0x00000003, 0x0000046C, 0x00000BFD, 0x0000059A, - 0x0000014E, 0x0000046C, 0x00000BFD, 0x0000059A, 0x0000014E, 0x000002CA, 0x0000FFFF, 0x00000BFD, - 0x0000014E, 0x0000050E, 0x00000611, 0x00000848, 0x0000014E, 0x0000093C, 0x0000FFFF, 0x0000098B - - - - 0x00000000, 0x00000001, 0x00000002, 0x00000003, 0x00000004, 0x00000005, 0x00000006, 0x00000007, - 0x00000008, 0x00000009, 0x0000000A, 0x0000000B, 0x0000000C, 0x0000000D, 0x0000000E, 0x0000000F, - 0x00000010, 0x00000011, 0x00000012, 0x00000013, 0x00000014, 0x00000015, 0x00000016, 0x00000017 - - - - 0.000000, 0.000000, 14.906250, 0.000000, 14.906250, 0.000000, 17.648438, 0.000000, - 20.390625, 0.000000, 23.906250, 0.000000, 29.109375, 0.000000, 29.109375, 0.000000, - 34.171875, 0.000000, 35.929688, 0.000000, 41.132812, 0.000000, 41.132812, 0.000000, - 46.195312, 0.000000, 47.953125, 0.000000, 54.773438, 0.000000, 54.773438, 0.000000, - 54.773438, 0.000000, 56.531250, 0.000000, 61.875000, 0.000000, 67.570312, 0.000000, - 73.195312, 0.000000, 74.953125, 0.000000, 80.437500, 0.000000, 80.437500, 0.000000, - 87.328125, 0.000000 - - - - - - - ᄊᆞᆷ ᄒᆞᆫ글 ᄀᆞᇹ ᄫᆞᆼ - - - 0x000044FF, 0x00004707, 0x00004859, 0x00000005, 0x0000462B, 0x00004707, 0x00004785, 0x000019B2, - 0x00000005, 0x00004361, 0x00004707, 0x0000498D, 0x00000005, 0x000044C3, 0x00004707, 0x00004911 - - - - 0x00000000, 0x00000001, 0x00000002, 0x00000003, 0x00000004, 0x00000005, 0x00000006, 0x00000007, - 0x00000008, 0x00000009, 0x0000000A, 0x0000000B, 0x0000000C, 0x0000000D, 0x0000000E, 0x0000000F - - - - 0.000000, 0.000000, 12.000000, 0.000000, 12.000000, 0.000000, 12.000000, 0.000000, - 14.700000, 0.000000, 26.700001, 0.000000, 26.700001, 0.000000, 26.700001, 0.000000, - 38.700001, 0.000000, 41.400002, 0.000000, 53.400002, 0.000000, 53.400002, 0.000000, - 53.400002, 0.000000, 56.100002, 0.000000, 68.100006, 0.000000, 68.100006, 0.000000, - 68.100006, 0.000000 - - - - - - - के े - - - 0x00000901, 0x00000931, 0x00000003, 0x00000956, 0x00000931 - - - - 0x00000000, 0x00000001, 0x00000002, 0x00000003, 0x00000003 - - - - 0.000000, 0.000000, 5.935547, 0.000000, 8.548828, 0.000000, 12.345703, 0.000000, - 17.085938, 0.000000, 18.345703, 0.000000 - - - - - - - - अँग्रेज़ी - - - 0x000008F1, 0x000008EE, 0x000009CB, 0x0000FFFF, 0x0000FFFF, 0x00000931, 0x00000940, 0x0000FFFF, - 0x0000092A - - - - 0x00000000, 0x00000001, 0x00000002, 0x00000004, 0x00000003, 0x00000005, 0x00000006, 0x00000007, - 0x00000008 - - - - 0.000000, 0.000000, 9.076172, 0.000000, 9.076172, 0.000000, 16.025391, 0.000000, - 16.025391, 0.000000, 16.025391, 0.000000, 16.025391, 0.000000, 23.976562, 0.000000, - 23.976562, 0.000000, 27.304688, 0.000000 - - - - From 6df78c4585fc5a71ceafa6f4b1dc0fe68db2657c Mon Sep 17 00:00:00 2001 From: Emanuel Peter Date: Wed, 12 Nov 2025 07:10:29 +0000 Subject: [PATCH 009/418] 8371065: C2 SuperWord: VTransformLoopPhiNode::apply setting type leads to assert/wrong result Co-authored-by: Roland Westrelin Reviewed-by: qamai, chagedorn --- .../share/opto/superwordVTransformBuilder.cpp | 2 +- src/hotspot/share/opto/vtransform.cpp | 91 ++++++--- src/hotspot/share/opto/vtransform.hpp | 34 +++- .../superword/TestLoopPhiApplyBadType.java | 189 ++++++++++++++++++ 4 files changed, 283 insertions(+), 33 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/loopopts/superword/TestLoopPhiApplyBadType.java diff --git a/src/hotspot/share/opto/superwordVTransformBuilder.cpp b/src/hotspot/share/opto/superwordVTransformBuilder.cpp index 45c919ccffa..832e91603d9 100644 --- a/src/hotspot/share/opto/superwordVTransformBuilder.cpp +++ b/src/hotspot/share/opto/superwordVTransformBuilder.cpp @@ -64,7 +64,7 @@ void SuperWordVTransformBuilder::build_scalar_vtnodes_for_non_packed_nodes() { const VPointer& mem_p = _vloop_analyzer.vpointers().vpointer(mem); vtn = new (_vtransform.arena()) VTransformMemopScalarNode(_vtransform, mem, mem_p); } else if (n->is_Phi()) { - vtn = new (_vtransform.arena()) VTransformLoopPhiNode(_vtransform, n->as_Phi()); + vtn = new (_vtransform.arena()) VTransformPhiScalarNode(_vtransform, n->as_Phi()); } else if (n->is_CountedLoop()) { vtn = new (_vtransform.arena()) VTransformCountedLoopNode(_vtransform, n->as_CountedLoop()); } else if (n->is_CFG()) { diff --git a/src/hotspot/share/opto/vtransform.cpp b/src/hotspot/share/opto/vtransform.cpp index c617f8415e4..e775eb60cab 100644 --- a/src/hotspot/share/opto/vtransform.cpp +++ b/src/hotspot/share/opto/vtransform.cpp @@ -62,7 +62,7 @@ void VTransformGraph::optimize(VTransform& vtransform) { // 1. Memory phi uses are not modeled, so they appear to have no use here, but must be kept alive. // 2. Similarly, some stores may not have their memory uses modeled, but need to be kept alive. // 3. Outer node with strong inputs: is a use after the loop that we must keep alive. - !(vtn->isa_LoopPhi() != nullptr || + !(vtn->isa_PhiScalar() != nullptr || vtn->is_load_or_store_in_loop() || (vtn->isa_Outer() != nullptr && vtn->has_strong_in_edge()))) { vtn->mark_dead(); @@ -123,8 +123,10 @@ bool VTransformGraph::schedule() { // Skip dead nodes if (!use->is_alive()) { continue; } - // Skip LoopPhi backedge. - if ((use->isa_LoopPhi() != nullptr || use->isa_CountedLoop() != nullptr) && use->in_req(2) == vtn) { continue; } + // Skip backedges. + if ((use->is_loop_head_phi() || use->isa_CountedLoop() != nullptr) && use->in_req(2) == vtn) { + continue; + } if (post_visited.test(use->_idx)) { continue; } if (pre_visited.test(use->_idx)) { @@ -205,7 +207,7 @@ void VTransformGraph::mark_vtnodes_in_loop(VectorSet& in_loop) const { for (int i = 0; i < _schedule.length(); i++) { VTransformNode* vtn = _schedule.at(i); // Is vtn a loop-phi? - if (vtn->isa_LoopPhi() != nullptr || + if (vtn->is_loop_head_phi() || vtn->is_load_or_store_in_loop()) { is_not_before_loop.set(vtn->_idx); continue; @@ -232,8 +234,7 @@ void VTransformGraph::mark_vtnodes_in_loop(VectorSet& in_loop) const { for (uint i = 0; i < vtn->out_strong_edges(); i++) { VTransformNode* use = vtn->out_strong_edge(i); // Or is vtn a backedge or one of its transitive defs? - if (in_loop.test(use->_idx) || - use->isa_LoopPhi() != nullptr) { + if (in_loop.test(use->_idx) || use->is_loop_head_phi()) { in_loop.set(vtn->_idx); break; } @@ -957,7 +958,7 @@ VTransformApplyResult VTransformDataScalarNode::apply(VTransformApplyState& appl return VTransformApplyResult::make_scalar(_node); } -VTransformApplyResult VTransformLoopPhiNode::apply(VTransformApplyState& apply_state) const { +VTransformApplyResult VTransformPhiScalarNode::apply(VTransformApplyState& apply_state) const { PhaseIdealLoop* phase = apply_state.phase(); Node* in0 = apply_state.transformed_node(in_req(0)); Node* in1 = apply_state.transformed_node(in_req(1)); @@ -965,19 +966,14 @@ VTransformApplyResult VTransformLoopPhiNode::apply(VTransformApplyState& apply_s phase->igvn().replace_input_of(_node, 1, in1); // Note: the backedge is hooked up later. - // The Phi's inputs may have been modified, and the types changes, - // e.g. from scalar to vector. - const Type* t = in1->bottom_type(); - _node->as_Type()->set_type(t); - phase->igvn().set_type(_node, t); - return VTransformApplyResult::make_scalar(_node); } // Cleanup backedges. In the schedule, the backedges come after their phis. Hence, // we only have the transformed backedges after the phis are already transformed. // We hook the backedges into the phis now, during cleanup. -void VTransformLoopPhiNode::apply_backedge(VTransformApplyState& apply_state) const { +void VTransformPhiScalarNode::apply_backedge(VTransformApplyState& apply_state) const { + assert(_node == apply_state.transformed_node(this), "sanity"); PhaseIdealLoop* phase = apply_state.phase(); if (_node->is_memory_phi()) { // Memory phi/backedge @@ -1219,7 +1215,7 @@ bool VTransformReductionVectorNode::requires_strict_order() const { // are performed inside the loop. bool VTransformReductionVectorNode::optimize_move_non_strict_order_reductions_out_of_loop_preconditions(VTransform& vtransform) { // We have a phi with a single use. - VTransformLoopPhiNode* phi = in_req(1)->isa_LoopPhi(); + VTransformPhiScalarNode* phi = in_req(1)->isa_PhiScalar(); if (phi == nullptr) { return false; } @@ -1284,7 +1280,7 @@ bool VTransformReductionVectorNode::optimize_move_non_strict_order_reductions_ou // All uses must be outside loop body, except for the phi. for (uint i = 0; i < current_red->out_strong_edges(); i++) { VTransformNode* use = current_red->out_strong_edge(i); - if (use->isa_LoopPhi() == nullptr && + if (use->isa_PhiScalar() == nullptr && use->isa_Outer() == nullptr) { // Should not be allowed by SuperWord::mark_reductions assert(false, "reduction has use inside loop"); @@ -1339,16 +1335,28 @@ bool VTransformReductionVectorNode::optimize_move_non_strict_order_reductions_ou VTransformNode* vtn_identity_vector = new (vtransform.arena()) VTransformReplicateNode(vtransform, vlen, bt); vtn_identity_vector->init_req(1, vtn_identity); - // Turn the scalar phi into a vector phi. - VTransformLoopPhiNode* phi = in_req(1)->isa_LoopPhi(); - VTransformNode* init = phi->in_req(1); - phi->set_req(1, vtn_identity_vector); + // Look at old scalar phi. + VTransformPhiScalarNode* phi_scalar = in_req(1)->isa_PhiScalar(); + PhiNode* old_phi = phi_scalar->node(); + VTransformNode* init = phi_scalar->in_req(1); + + TRACE_OPTIMIZE( + tty->print(" phi_scalar "); + phi_scalar->print(); + ) + + // Create new vector phi + const VTransformVectorNodeProperties properties = VTransformVectorNodeProperties::make_for_phi_vector(old_phi, vlen, bt); + VTransformPhiVectorNode* phi_vector = new (vtransform.arena()) VTransformPhiVectorNode(vtransform, 3, properties); + phi_vector->init_req(0, phi_scalar->in_req(0)); + phi_vector->init_req(1, vtn_identity_vector); + // Note: backedge comes later // Traverse down the chain of reductions, and replace them with vector_accumulators. VTransformReductionVectorNode* first_red = this; - VTransformReductionVectorNode* last_red = phi->in_req(2)->isa_ReductionVector(); + VTransformReductionVectorNode* last_red = phi_scalar->in_req(2)->isa_ReductionVector(); VTransformReductionVectorNode* current_red = first_red; - VTransformNode* current_vector_accumulator = phi; + VTransformNode* current_vector_accumulator = phi_vector; while (true) { VTransformNode* vector_input = current_red->in_req(2); VTransformVectorNode* vector_accumulator = new (vtransform.arena()) VTransformElementWiseVectorNode(vtransform, 3, current_red->properties(), vopc); @@ -1366,15 +1374,15 @@ bool VTransformReductionVectorNode::optimize_move_non_strict_order_reductions_ou } // Feed vector accumulator into the backedge. - phi->set_req(2, current_vector_accumulator); + phi_vector->set_req(2, current_vector_accumulator); // Create post-loop reduction. last_red keeps all uses outside the loop. last_red->set_req(1, init); last_red->set_req(2, current_vector_accumulator); TRACE_OPTIMIZE( - tty->print(" phi "); - phi->print(); + tty->print(" phi_scalar "); + phi_scalar->print(); tty->print(" after loop "); last_red->print(); ) @@ -1398,6 +1406,37 @@ VTransformApplyResult VTransformReductionVectorNode::apply(VTransformApplyState& return VTransformApplyResult::make_vector(vn, vn->vect_type()); } +VTransformApplyResult VTransformPhiVectorNode::apply(VTransformApplyState& apply_state) const { + PhaseIdealLoop* phase = apply_state.phase(); + Node* in0 = apply_state.transformed_node(in_req(0)); + Node* in1 = apply_state.transformed_node(in_req(1)); + + // We create a new phi node, because the type is different to the scalar phi. + PhiNode* old_phi = approximate_origin()->as_Phi(); + PhiNode* new_phi = old_phi->clone()->as_Phi(); + + phase->igvn().replace_input_of(new_phi, 0, in0); + phase->igvn().replace_input_of(new_phi, 1, in1); + // Note: the backedge is hooked up later. + + // Give the new phi node the correct vector type. + const TypeVect* vt = TypeVect::make(element_basic_type(), vector_length()); + new_phi->as_Type()->set_type(vt); + phase->igvn().set_type(new_phi, vt); + + return VTransformApplyResult::make_vector(new_phi, vt); +} + +// Cleanup backedges. In the schedule, the backedges come after their phis. Hence, +// we only have the transformed backedges after the phis are already transformed. +// We hook the backedges into the phis now, during cleanup. +void VTransformPhiVectorNode::apply_backedge(VTransformApplyState& apply_state) const { + PhaseIdealLoop* phase = apply_state.phase(); + PhiNode* new_phi = apply_state.transformed_node(this)->as_Phi(); + Node* in2 = apply_state.transformed_node(in_req(2)); + phase->igvn().replace_input_of(new_phi, 2, in2); +} + float VTransformLoadVectorNode::cost(const VLoopAnalyzer& vloop_analyzer) const { uint vlen = vector_length(); BasicType bt = element_basic_type(); @@ -1535,7 +1574,7 @@ void VTransformDataScalarNode::print_spec() const { tty->print("node[%d %s]", _node->_idx, _node->Name()); } -void VTransformLoopPhiNode::print_spec() const { +void VTransformPhiScalarNode::print_spec() const { tty->print("node[%d %s]", _node->_idx, _node->Name()); } diff --git a/src/hotspot/share/opto/vtransform.hpp b/src/hotspot/share/opto/vtransform.hpp index a30f0ff098f..a3e4977494e 100644 --- a/src/hotspot/share/opto/vtransform.hpp +++ b/src/hotspot/share/opto/vtransform.hpp @@ -79,7 +79,7 @@ class VTransform; class VTransformNode; class VTransformMemopScalarNode; class VTransformDataScalarNode; -class VTransformLoopPhiNode; +class VTransformPhiScalarNode; class VTransformCFGNode; class VTransformCountedLoopNode; class VTransformOuterNode; @@ -88,6 +88,7 @@ class VTransformElementWiseVectorNode; class VTransformCmpVectorNode; class VTransformBoolVectorNode; class VTransformReductionVectorNode; +class VTransformPhiVectorNode; class VTransformMemVectorNode; class VTransformLoadVectorNode; class VTransformStoreVectorNode; @@ -539,7 +540,7 @@ public: } virtual VTransformMemopScalarNode* isa_MemopScalar() { return nullptr; } - virtual VTransformLoopPhiNode* isa_LoopPhi() { return nullptr; } + virtual VTransformPhiScalarNode* isa_PhiScalar() { return nullptr; } virtual VTransformCountedLoopNode* isa_CountedLoop() { return nullptr; } virtual VTransformOuterNode* isa_Outer() { return nullptr; } virtual VTransformVectorNode* isa_Vector() { return nullptr; } @@ -547,6 +548,7 @@ public: virtual VTransformCmpVectorNode* isa_CmpVector() { return nullptr; } virtual VTransformBoolVectorNode* isa_BoolVector() { return nullptr; } virtual VTransformReductionVectorNode* isa_ReductionVector() { return nullptr; } + virtual VTransformPhiVectorNode* isa_PhiVector() { return nullptr; } virtual VTransformMemVectorNode* isa_MemVector() { return nullptr; } virtual VTransformLoadVectorNode* isa_LoadVector() { return nullptr; } virtual VTransformStoreVectorNode* isa_StoreVector() { return nullptr; } @@ -554,6 +556,7 @@ public: virtual bool is_load_in_loop() const { return false; } virtual bool is_load_or_store_in_loop() const { return false; } virtual const VPointer& vpointer() const { ShouldNotReachHere(); } + virtual bool is_loop_head_phi() const { return false; } virtual bool optimize(const VLoopAnalyzer& vloop_analyzer, VTransform& vtransform) { return false; } @@ -613,21 +616,24 @@ public: }; // Identity transform for loop head phi nodes. -class VTransformLoopPhiNode : public VTransformNode { +class VTransformPhiScalarNode : public VTransformNode { private: PhiNode* _node; public: - VTransformLoopPhiNode(VTransform& vtransform, PhiNode* n) : + VTransformPhiScalarNode(VTransform& vtransform, PhiNode* n) : VTransformNode(vtransform, n->req()), _node(n) { assert(_node->in(0)->is_Loop(), "phi ctrl must be Loop: %s", _node->in(0)->Name()); } - virtual VTransformLoopPhiNode* isa_LoopPhi() override { return this; } + PhiNode* node() const { return _node; } + + virtual VTransformPhiScalarNode* isa_PhiScalar() override { return this; } + virtual bool is_loop_head_phi() const override { return in_req(0)->isa_CountedLoop() != nullptr; } virtual float cost(const VLoopAnalyzer& vloop_analyzer) const override { return 0; } virtual VTransformApplyResult apply(VTransformApplyState& apply_state) const override; virtual void apply_backedge(VTransformApplyState& apply_state) const override; - NOT_PRODUCT(virtual const char* name() const override { return "LoopPhi"; };) + NOT_PRODUCT(virtual const char* name() const override { return "PhiScalar"; };) NOT_PRODUCT(virtual void print_spec() const override;) }; @@ -754,6 +760,10 @@ public: return VTransformVectorNodeProperties(first, opc, vlen, bt); } + static VTransformVectorNodeProperties make_for_phi_vector(PhiNode* phi, int vlen, BasicType bt) { + return VTransformVectorNodeProperties(phi, phi->Opcode(), vlen, bt); + } + Node* approximate_origin() const { return _approximate_origin; } int scalar_opcode() const { return _scalar_opcode; } uint vector_length() const { return _vector_length; } @@ -870,6 +880,18 @@ private: bool optimize_move_non_strict_order_reductions_out_of_loop(const VLoopAnalyzer& vloop_analyzer, VTransform& vtransform); }; +class VTransformPhiVectorNode : public VTransformVectorNode { +public: + VTransformPhiVectorNode(VTransform& vtransform, uint req, const VTransformVectorNodeProperties properties) : + VTransformVectorNode(vtransform, req, properties) {} + virtual VTransformPhiVectorNode* isa_PhiVector() override { return this; } + virtual bool is_loop_head_phi() const override { return in_req(0)->isa_CountedLoop() != nullptr; } + virtual float cost(const VLoopAnalyzer& vloop_analyzer) const override { return 0; } + virtual VTransformApplyResult apply(VTransformApplyState& apply_state) const override; + virtual void apply_backedge(VTransformApplyState& apply_state) const override; + NOT_PRODUCT(virtual const char* name() const override { return "PhiVector"; };) +}; + class VTransformMemVectorNode : public VTransformVectorNode { private: const VPointer _vpointer; // with size of the vector diff --git a/test/hotspot/jtreg/compiler/loopopts/superword/TestLoopPhiApplyBadType.java b/test/hotspot/jtreg/compiler/loopopts/superword/TestLoopPhiApplyBadType.java new file mode 100644 index 00000000000..17bc3894c28 --- /dev/null +++ b/test/hotspot/jtreg/compiler/loopopts/superword/TestLoopPhiApplyBadType.java @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test id=all-flags-1 + * @bug 8371065 + * @summary A bug in VTransformLoopPhiNode::apply led us to copy the type of phi->in(1) + * which we did not expect to ever be a constant, but always the full type range. + * Setting the phi type to that value meant that the phi would wrongly constant + * fold, and lead to wrong results. + * @run main/othervm + * -XX:+IgnoreUnrecognizedVMOptions + * -XX:CompileCommand=compileonly,*TestLoopPhiApplyBadType::test* + * -XX:CompileCommand=dontinline,*TestLoopPhiApplyBadType::notInlined + * -XX:-TieredCompilation + * -XX:-UseOnStackReplacement + * -XX:-BackgroundCompilation + * -XX:PartialPeelNewPhiDelta=1 + * -Xbatch + * -XX:+UnlockDiagnosticVMOptions + * -XX:+StressIGVN + * -XX:StressSeed=212574406 + * compiler.loopopts.superword.TestLoopPhiApplyBadType + */ + +/* + * @test id=fewer-flags-1 + * @bug 8371065 + * @run main/othervm + * -XX:+IgnoreUnrecognizedVMOptions + * -XX:CompileCommand=compileonly,*TestLoopPhiApplyBadType::test* + * -XX:CompileCommand=dontinline,*TestLoopPhiApplyBadType::notInlined + * -XX:-TieredCompilation + * -XX:-UseOnStackReplacement + * -XX:-BackgroundCompilation + * -XX:PartialPeelNewPhiDelta=1 + * -Xbatch + * -XX:+UnlockDiagnosticVMOptions + * -XX:+StressIGVN + * compiler.loopopts.superword.TestLoopPhiApplyBadType + */ + +/* + * @test id=all-flags-2 + * @bug 8371065 8371472 + * @run main/othervm + * -XX:+IgnoreUnrecognizedVMOptions + * -XX:CompileCommand=compileonly,*TestLoopPhiApplyBadType::test* + * -XX:CompileCommand=dontinline,*TestLoopPhiApplyBadType::notInlined + * -XX:-TieredCompilation + * -Xbatch + * -XX:+UnlockDiagnosticVMOptions + * -XX:+StressIGVN + * -XX:StressSeed=3497198372 + * compiler.loopopts.superword.TestLoopPhiApplyBadType + */ + +/* + * @test id=fewer-flags-2 + * @bug 8371065 8371472 + * @run main/othervm + * -XX:+IgnoreUnrecognizedVMOptions + * -XX:CompileCommand=compileonly,*TestLoopPhiApplyBadType::test* + * -XX:CompileCommand=dontinline,*TestLoopPhiApplyBadType::notInlined + * -XX:-TieredCompilation + * -Xbatch + * -XX:+UnlockDiagnosticVMOptions + * -XX:+StressIGVN + * compiler.loopopts.superword.TestLoopPhiApplyBadType + */ + +/* + * @test id=minimal-flags + * @bug 8371065 + * @run main/othervm + * -XX:+IgnoreUnrecognizedVMOptions + * -XX:CompileCommand=compileonly,*TestLoopPhiApplyBadType::test* + * -XX:CompileCommand=dontinline,*TestLoopPhiApplyBadType::notInlined + * compiler.loopopts.superword.TestLoopPhiApplyBadType + */ + +/* + * @test id=no-flags + * @bug 8371065 + * @run main compiler.loopopts.superword.TestLoopPhiApplyBadType + */ + +package compiler.loopopts.superword; + +public class TestLoopPhiApplyBadType { + public static void main(String[] args) { + for (int i = 0; i < 20_000; i++) { + int[] array = test1(); + int j; + boolean abort = false; + for (j = 0; j < array.length-2; j++) { + if (array[j] != (j | 1)) { + System.out.println("For " + j + " " + array[j]); + abort = true; + } + } + for (; j < array.length; j++) { + if (array[j] != j) { + System.out.println("For " + j + " " + array[j]); + abort = true; + } + } + if (abort) { + throw new RuntimeException("Failure"); + } + } + + int gold2 = test2(); + for (int i = 0; i < 10; i++) { + int res = test2(); + if (gold2 != res) { + throw new RuntimeException("Wrong value: " + res + " vs " + gold2); + } + } + } + + private static int[] test1() { + int limit = 2; + for (; limit < 4; limit *= 2); + int k = limit / 4; + + int[] array = new int[1000]; + int[] flags = new int[1000]; + int[] flags2 = new int[1000]; + int j; + for (j = 0; j < 10; j += k) { + + } + notInlined(array); + for (int i = 0; ; i++) { + synchronized (new Object()) {} + int ii = Integer.min(Integer.max(i, 0), array.length-1); + int v = array[ii]; + if (flags2[ii] * (j-10) != 0) { + throw new RuntimeException("never taken" + v); + } + if (i * k >= array.length - 2) { + break; + } + if (flags[ii]*(j-10) != 0) { + throw new RuntimeException("never taken"); + } + array[ii] = v | 1; + } + return array; + } + + static int test2() { + int arr[] = new int[400]; + int x = 34; + for (int i = 1; i < 50; i++) { + for (int k = 3; 201 > k; ++k) { + x += Math.min(k, arr[k - 1]); + } + } + return x; + } + + private static void notInlined(int[] array) { + for (int i = 0; i < array.length; i++) { + array[i] = i; + } + } +} From 76a0732ba5c0f3159ed0ebc5fcb2dfb7117b38cd Mon Sep 17 00:00:00 2001 From: Jan Lahoda Date: Wed, 12 Nov 2025 07:14:45 +0000 Subject: [PATCH 010/418] 8366691: JShell should support a more convenient completion Reviewed-by: asotona --- .../jshell/tool/ConsoleIOContext.java | 57 ++- .../shellsupport/doc/JavadocHelper.java | 93 ++++- .../jdk/jshell/SourceCodeAnalysis.java | 159 +++++++ .../jdk/jshell/SourceCodeAnalysisImpl.java | 395 +++++++++++------- .../jdk/jshell/CompletionAPITest.java | 303 ++++++++++++++ .../jdk/jshell/CompletionSuggestionTest.java | 15 +- test/langtools/jdk/jshell/KullaTesting.java | 13 +- .../jdk/jshell/ToolTabSnippetTest.java | 8 +- 8 files changed, 881 insertions(+), 162 deletions(-) create mode 100644 test/langtools/jdk/jshell/CompletionAPITest.java diff --git a/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ConsoleIOContext.java b/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ConsoleIOContext.java index 1662f81710a..57d427c1774 100644 --- a/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ConsoleIOContext.java +++ b/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ConsoleIOContext.java @@ -345,18 +345,21 @@ class ConsoleIOContext extends IOContext { ConsoleIOContextTestSupport.willComputeCompletion(); int[] anchor = new int[] {-1}; List suggestions; - List doc; + List doc; boolean command = prefix.isEmpty() && text.startsWith("/"); if (command) { suggestions = repl.commandCompletionSuggestions(text, cursor, anchor); - doc = repl.commandDocumentation(text, cursor, true); + doc = repl.commandDocumentation(text, cursor, true) + .stream() + .map(AttributedString::new) + .toList(); } else { int prefixLength = prefix.length(); suggestions = repl.analysis.completionSuggestions(prefix + text, cursor + prefixLength, anchor); anchor[0] -= prefixLength; doc = repl.analysis.documentation(prefix + text, cursor + prefix.length(), false) .stream() - .map(Documentation::signature) + .map(this::renderSignature) .toList(); } long smartCount = suggestions.stream().filter(Suggestion::matchesType).count(); @@ -502,6 +505,41 @@ class ConsoleIOContext extends IOContext { } } + private AttributedString renderSignature(Documentation doc) { + int activeParamIndex = doc.activeParameterIndex(); + String signature = doc.signature(); + + if (activeParamIndex == (-1)) { + return new AttributedString(signature); + } + + int lparen = signature.indexOf('('); + int rparen = signature.indexOf(')', lparen); + + if (lparen == (-1) || rparen == (-1)) { + return new AttributedString(signature); + } + + AttributedStringBuilder result = new AttributedStringBuilder(); + + result.append(signature.substring(0, lparen + 1), AttributedStyle.DEFAULT); + + String[] params = signature.substring(lparen + 1, rparen).split(", *"); + String sep = ""; + + for (int i = 0; i < params.length; i++) { + result.append(sep); + result.append(params[i], i == activeParamIndex ? AttributedStyle.BOLD + : AttributedStyle.DEFAULT); + + sep = ", "; + } + + result.append(signature.substring(rparen), AttributedStyle.DEFAULT); + + return result.toAttributedString(); + } + private CompletionTask.Result doPrintFullDocumentation(List todo, List doc, boolean command) { if (doc != null && !doc.isEmpty()) { Terminal term = in.getTerminal(); @@ -722,9 +760,9 @@ class ConsoleIOContext extends IOContext { private final class CommandSynopsisTask implements CompletionTask { - private final List synopsis; + private final List synopsis; - public CommandSynopsisTask(List synposis) { + public CommandSynopsisTask(List synposis) { this.synopsis = synposis; } @@ -738,6 +776,7 @@ class ConsoleIOContext extends IOContext { // try { in.getTerminal().writer().println(); in.getTerminal().writer().println(synopsis.stream() + .map(doc -> doc.toAnsi(in.getTerminal())) .map(l -> l.replaceAll("\n", LINE_SEPARATOR)) .collect(Collectors.joining(LINE_SEPARATORS2))); // } catch (IOException ex) { @@ -771,9 +810,9 @@ class ConsoleIOContext extends IOContext { private final class ExpressionSignaturesTask implements CompletionTask { - private final List doc; + private final List doc; - public ExpressionSignaturesTask(List doc) { + public ExpressionSignaturesTask(List doc) { this.doc = doc; } @@ -786,7 +825,9 @@ class ConsoleIOContext extends IOContext { public Result perform(String text, int cursor) throws IOException { in.getTerminal().writer().println(); in.getTerminal().writer().println(repl.getResourceString("jshell.console.completion.current.signatures")); - in.getTerminal().writer().println(doc.stream().collect(Collectors.joining(LINE_SEPARATOR))); + in.getTerminal().writer().println(doc.stream() + .map(doc -> doc.toAnsi(in.getTerminal())) + .collect(Collectors.joining(LINE_SEPARATOR))); return Result.FINISH; } diff --git a/src/jdk.jshell/share/classes/jdk/internal/shellsupport/doc/JavadocHelper.java b/src/jdk.jshell/share/classes/jdk/internal/shellsupport/doc/JavadocHelper.java index e69f32097b2..099aba26968 100644 --- a/src/jdk.jshell/share/classes/jdk/internal/shellsupport/doc/JavadocHelper.java +++ b/src/jdk.jshell/share/classes/jdk/internal/shellsupport/doc/JavadocHelper.java @@ -118,7 +118,7 @@ public abstract class JavadocHelper implements AutoCloseable { StandardJavaFileManager fm = compiler.getStandardFileManager(null, null, null); try { fm.setLocationFromPaths(StandardLocation.SOURCE_PATH, sourceLocations); - return new OnDemandJavadocHelper(mainTask, fm); + return new OnDemandJavadocHelper(mainTask, fm, sourceLocations); } catch (IOException ex) { try { fm.close(); @@ -135,6 +135,21 @@ public abstract class JavadocHelper implements AutoCloseable { } @Override public void close() throws IOException {} + + @Override + public String getResolvedDocComment(StoredElement forElement) throws IOException { + return null; + } + + @Override + public StoredElement getHandle(Element forElement) { + return null; + } + + @Override + public Collection getSourceLocations() { + return List.of(); + } }; } } @@ -147,6 +162,7 @@ public abstract class JavadocHelper implements AutoCloseable { * @throws IOException if something goes wrong in the search */ public abstract String getResolvedDocComment(Element forElement) throws IOException; + public abstract String getResolvedDocComment(StoredElement forElement) throws IOException; /**Returns an element representing the same given program element, but the returned element will * be resolved from source, if it can be found. Returns the original element if the source for @@ -158,6 +174,9 @@ public abstract class JavadocHelper implements AutoCloseable { */ public abstract Element getSourceElement(Element forElement) throws IOException; + public abstract StoredElement getHandle(Element forElement); + public abstract Collection getSourceLocations(); + /**Closes the helper. * * @throws IOException if something foes wrong during the close @@ -165,16 +184,20 @@ public abstract class JavadocHelper implements AutoCloseable { @Override public abstract void close() throws IOException; + public record StoredElement(String module, String binaryName, String handle) {} + private static final class OnDemandJavadocHelper extends JavadocHelper { private final JavacTask mainTask; private final JavaFileManager baseFileManager; private final StandardJavaFileManager fm; private final Map> signature2Source = new HashMap<>(); + private final Collection sourceLocations; - private OnDemandJavadocHelper(JavacTask mainTask, StandardJavaFileManager fm) { + private OnDemandJavadocHelper(JavacTask mainTask, StandardJavaFileManager fm, Collection sourceLocations) { this.mainTask = mainTask; this.baseFileManager = ((JavacTaskImpl) mainTask).getContext().get(JavaFileManager.class); this.fm = fm; + this.sourceLocations = sourceLocations; } @Override @@ -187,6 +210,16 @@ public abstract class JavadocHelper implements AutoCloseable { return getResolvedDocComment(sourceElement.fst, sourceElement.snd); } + @Override + public String getResolvedDocComment(StoredElement forElement) throws IOException { + Pair sourceElement = getSourceElement(forElement); + + if (sourceElement == null) + return null; + + return getResolvedDocComment(sourceElement.fst, sourceElement.snd); + } + @Override public Element getSourceElement(Element forElement) throws IOException { Pair sourceElement = getSourceElement(mainTask, forElement); @@ -202,7 +235,30 @@ public abstract class JavadocHelper implements AutoCloseable { return result; } - private String getResolvedDocComment(JavacTask task, TreePath el) throws IOException { + @Override + public StoredElement getHandle(Element forElement) { + TypeElement type = topLevelType(forElement); + + if (type == null) + return null; + + Elements elements = mainTask.getElements(); + ModuleElement module = elements.getModuleOf(type); + String moduleName = module == null || module.isUnnamed() + ? null + : module.getQualifiedName().toString(); + String binaryName = elements.getBinaryName(type).toString(); + String handle = elementSignature(forElement); + + return new StoredElement(moduleName, binaryName, handle); + } + + @Override + public Collection getSourceLocations() { + return sourceLocations; + } + + private String getResolvedDocComment(JavacTask task, TreePath el) throws IOException { DocTrees trees = DocTrees.instance(task); Element element = trees.getElement(el); String docComment = trees.getDocComment(el); @@ -634,7 +690,7 @@ public abstract class JavadocHelper implements AutoCloseable { .filter(supMethod -> task.getElements().overrides(method, supMethod, type)); } - /* Find types from which methods in type may inherit javadoc, in the proper order.*/ + /* Find types from which methods in binaryName may inherit javadoc, in the proper order.*/ private Stream superTypeForInheritDoc(JavacTask task, Element type) { TypeElement clazz = (TypeElement) type; Stream result = interfaces(clazz); @@ -701,6 +757,35 @@ public abstract class JavadocHelper implements AutoCloseable { return exc != null ? exc.toString() : null; } + private Pair getSourceElement(StoredElement el) throws IOException { + if (el == null) { + return null; + } + + String handle = el.handle(); + Pair cached = signature2Source.get(handle); + + if (cached != null) { + return cached.fst != null ? cached : null; + } + + Pair source = findSource(el.module(), el.binaryName()); + + if (source == null) + return null; + + fillElementCache(source.fst, source.snd); + + cached = signature2Source.get(handle); + + if (cached != null) { + return cached; + } else { + signature2Source.put(handle, Pair.of(null, null)); + return null; + } + } + private Pair getSourceElement(JavacTask origin, Element el) throws IOException { String handle = elementSignature(el); Pair cached = signature2Source.get(handle); diff --git a/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysis.java b/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysis.java index 10da664cd03..91bdde888fa 100644 --- a/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysis.java +++ b/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysis.java @@ -28,6 +28,12 @@ package jdk.jshell; import java.util.Collection; import java.util.List; import java.util.Set; +import java.util.function.Supplier; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; /** * Provides analysis utilities for source code input. @@ -64,6 +70,18 @@ public abstract class SourceCodeAnalysis { */ public abstract List completionSuggestions(String input, int cursor, int[] anchor); + /** + * Compute possible follow-ups for the given input. + * Uses information from the current {@code JShell} state, including + * type information, to filter the suggestions. + * @param input the user input, so far + * @param cursor the current position of the cursors in the given {@code input} text + * @param convertor convert the given {@linkplain ElementSuggestion} to a custom completion suggestions. + * @return list of candidate continuations of the given input. + * @since 26 + */ + public abstract List completionSuggestions(String input, int cursor, ElementSuggestionConvertor convertor); + /** * Compute documentation for the given user's input. Multiple {@code Documentation} objects may * be returned when multiple elements match the user's input (like for overloaded methods). @@ -315,6 +333,135 @@ public abstract class SourceCodeAnalysis { boolean matchesType(); } + /** + * A description of an {@linkplain Element} that is a possible continuation of + * a given snippet. + * + * @apiNote Instances of this interface and instances of the returned {@linkplain Elements} + * should only be used and held during the execution of the + * {@link #completionSuggestions(java.lang.String, int, jdk.jshell.SourceCodeAnalysis.ElementSuggestionConvertor) } + * method. Their use outside of the context of the method is not supported and + * the effect is undefined. + * + * @since 26 + */ + public sealed interface ElementSuggestion permits SourceCodeAnalysisImpl.ElementSuggestionImpl { + /** + * {@return a possible continuation {@linkplain Element}, or {@code null} + * if this item does not represent an {@linkplain Element}.} + */ + Element element(); + /** + * {@return a possible continuation keyword, or {@code null} + * if this item does not represent a keyword.} + */ + String keyword(); + /** + * {@return {@code true} if this {@linkplain Element}'s type fits into + * the context.} + * + * Typically used when the type of the element fits the expected type. + */ + boolean matchesType(); + /** + * {@return the offset in the original snippet at which point this {@linkplain Element} + * should be inserted.} + */ + int anchor(); + /** + * {@return a {@linkplain Supplier} for the javadoc documentation for this Element.} + * + * @apiNote The instance returned from this method is safe to hold for extended + * periods of time, and can be called outside of the context of the + * {@link #completionSuggestions(java.lang.String, int, jdk.jshell.SourceCodeAnalysis.ElementSuggestionConvertor) } method. + */ + Supplier documentation(); + } + + /** + * Permit access to completion state. + * + * @since 26 + */ + public sealed interface CompletionState permits SourceCodeAnalysisImpl.CompletionStateImpl { + /** + * {@return true if the given element is available using the simple name at + * the place of the cursor.} + * + * @param el {@linkplain Element} to check + */ + public boolean availableUsingSimpleName(Element el); + /** + * {@return flags describing the overall completion context.} + */ + public Set completionContext(); + /** + * {@return if the context is a qualified expression + * (i.e. {@link CompletionContext#QUALIFIED} is set), + * the type of the selector expression; {@code null} otherwise.} + */ + public TypeMirror selectorType(); + /** + * {@return an implementation of some utility methods for + * operating on elements} + */ + Elements elementUtils(); + /** + * {@return an implementation of some utility methods for + * operating on types} + */ + Types typeUtils(); + } + + /** + * Various flags describing the context in which the completion happens. + * + * @since 26 + */ + public enum CompletionContext { + /** + * The context is inside annotation attributes. + */ + ANNOTATION_ATTRIBUTE, + /** + * Parentheses should not be filled for methods and constructor + * in the current context. + * + * Typically used in the import or method reference contexts. + */ + NO_PAREN, + /** + * Interpret {@link ElementKind#ANNOTATION_TYPE}s as annotation uses. Typically means + * they should be prefixed with {@code @}. + */ + TYPES_AS_ANNOTATIONS, + /** + * The context is in a qualified expression (like member access). Simple + * names only should be used. + */ + QUALIFIED, + ; + } + + /** + * A convertor from a list of {@linkplain ElementSuggestion} to a list + * of custom target completion items. + * + * @param a custom target completion type. + * @since 26 + */ + public interface ElementSuggestionConvertor { + /** + * Convert a list of {@linkplain ElementSuggestion} to a list + * of custom completion items. + * + * @param state the state of the completion + * @param suggestions the input suggestions + * @return the converted suggestions + */ + public List convert(CompletionState state, List suggestions); + } + /** * A documentation for a candidate for continuation of the given user's input. */ @@ -333,6 +480,18 @@ public abstract class SourceCodeAnalysis { * @return the javadoc, or null if not found or not requested */ String javadoc(); + + /** + * If this {@code Documentation} is created for a method invocation, + * return the current parameter index. + * + * @implNote the default implementation returns {@code -1} + * @return the active parameter index, or {@code -1} if not available + * @since 26 + */ + default int activeParameterIndex() { + return -1; + } } /** diff --git a/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysisImpl.java b/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysisImpl.java index 19bad110a5d..d5b44b569a4 100644 --- a/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysisImpl.java +++ b/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysisImpl.java @@ -114,11 +114,13 @@ import java.util.Map; import java.util.NoSuchElementException; import java.util.Optional; import java.util.Set; +import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -152,8 +154,13 @@ import static jdk.jshell.SourceCodeAnalysis.Completeness.DEFINITELY_INCOMPLETE; import static jdk.jshell.TreeDissector.printType; import static java.util.stream.Collectors.joining; +import static javax.lang.model.element.ElementKind.CONSTRUCTOR; +import static javax.lang.model.element.ElementKind.MODULE; +import static javax.lang.model.element.ElementKind.PACKAGE; import javax.lang.model.type.IntersectionType; +import javax.lang.model.util.Elements; +import jdk.internal.shellsupport.doc.JavadocHelper.StoredElement; /** * The concrete implementation of SourceCodeAnalysis. @@ -278,18 +285,76 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis { @Override public List completionSuggestions(String code, int cursor, int[] anchor) { - suspendIndexing(); + ElementSuggestionConvertor convertor = (state, suggestions) -> { + Set haveParams = suggestions.stream() + .map(s -> s.element()) + .filter(el -> el != null) + .filter(IS_CONSTRUCTOR.or(IS_METHOD)) + .filter(c -> !((ExecutableElement)c).getParameters().isEmpty()) + .map(this::simpleContinuationName) + .collect(toSet()); + List result = new ArrayList<>(); + + for (ElementSuggestion s : suggestions) { + Element el = s.element(); + if (el != null) { + String continuation = continuationName(state, el); + + switch (el.getKind()) { + case CONSTRUCTOR, METHOD -> { + if (state.completionContext().contains(CompletionContext.ANNOTATION_ATTRIBUTE)) { + continuation += " = "; + } else if (!state.completionContext().contains(CompletionContext.NO_PAREN)) { + // add trailing open or matched parenthesis, as approriate: + continuation += haveParams.contains(continuation) ? "(" : "()"; + } + } + case ANNOTATION_TYPE -> { + if (state.completionContext().contains(CompletionContext.TYPES_AS_ANNOTATIONS)) { + boolean hasAnyAttributes = + ElementFilter.methodsIn(el.getEnclosedElements()) + .stream() + .anyMatch(attribute -> attribute.getParameters().isEmpty()); + String paren = hasAnyAttributes ? "(" : ""; + continuation = "@" + continuation + paren; + } + } + case PACKAGE -> + // add trailing dot to package names + continuation += "."; + } + + result.add(new SuggestionImpl(continuation, s.matchesType())); + } else if (s.keyword() != null) { + result.add(new SuggestionImpl(s.keyword(), s.matchesType())); + } + + anchor[0] = s.anchor(); + } + + Collections.sort(result, Comparator.comparing(Suggestion::continuation)); + + return result; + }; try { - return completionSuggestionsImpl(code, cursor, anchor); + return completionSuggestions(code, cursor, convertor); } catch (Throwable exc) { proc.debug(exc, "Exception thrown in SourceCodeAnalysisImpl.completionSuggestions"); return Collections.emptyList(); + } + } + + @Override + public List completionSuggestions(String code, int cursor, ElementSuggestionConvertor convertor) { + suspendIndexing(); + try { + return completionSuggestionsImpl(code, cursor, convertor); } finally { resumeIndexing(); } } - private List completionSuggestionsImpl(String code, int cursor, int[] anchor) { + private List completionSuggestionsImpl(String code, int cursor, ElementSuggestionConvertor suggestionConvertor) { code = code.substring(0, cursor); Matcher m = JAVA_IDENTIFIER.matcher(code); String identifier = ""; @@ -302,31 +367,27 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis { } OuterWrap codeWrap = wrapCodeForCompletion(code, cursor, true); - String[] requiredPrefix = new String[] {identifier}; - return computeSuggestions(codeWrap, code, cursor, requiredPrefix, anchor).stream() - .filter(s -> filteringText(s).startsWith(requiredPrefix[0]) && !s.continuation().equals(REPL_DOESNOTMATTER_CLASS_NAME)) - .sorted(Comparator.comparing(Suggestion::continuation)) - .toList(); + return computeSuggestions(codeWrap, code, cursor, identifier, suggestionConvertor); } - private static String filteringText(Suggestion suggestion) { - return suggestion instanceof SuggestionImpl impl - ? impl.filteringText - : suggestion.continuation(); - } + private static List COMPLETION_EXTRA_PARAMETERS = List.of("-parameters"); - private List computeSuggestions(OuterWrap code, String inputCode, int cursor, String[] requiredPrefix, int[] anchor) { - return proc.taskFactory.analyze(code, at -> { + private List computeSuggestions(OuterWrap code, String inputCode, int cursor, String prefix, ElementSuggestionConvertor suggestionConvertor) { + return proc.taskFactory.analyze(code, COMPLETION_EXTRA_PARAMETERS, at -> { + try (JavadocHelper javadoc = JavadocHelper.create(at.task, findSources())) { SourcePositions sp = at.trees().getSourcePositions(); CompilationUnitTree topLevel = at.firstCuTree(); - List result = new ArrayList<>(); TreePath tp = pathFor(topLevel, sp, code, cursor); if (tp != null) { + List result = new ArrayList<>(); Scope scope = at.trees().getScope(tp); + Collection scopeContent = scopeContent(at, scope, IDENTITY); + Set completionContext = EnumSet.noneOf(CompletionContext.class); Predicate accessibility = createAccessibilityFilter(at, tp); Predicate smartTypeFilter; Predicate smartFilter; Iterable targetTypes = findTargetType(at, tp); + TypeMirror selectorType = null; if (targetTypes != null) { if (tp.getLeaf().getKind() == Kind.MEMBER_REFERENCE) { Types types = at.getTypes(); @@ -386,24 +447,24 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis { } switch (tp.getLeaf().getKind()) { case MEMBER_REFERENCE, MEMBER_SELECT: { + completionContext.add(CompletionContext.QUALIFIED); + javax.lang.model.element.Name identifier; ExpressionTree expression; - Function paren; if (tp.getLeaf().getKind() == Kind.MEMBER_SELECT) { MemberSelectTree mst = (MemberSelectTree)tp.getLeaf(); identifier = mst.getIdentifier(); expression = mst.getExpression(); - paren = DEFAULT_PAREN; } else { MemberReferenceTree mst = (MemberReferenceTree)tp.getLeaf(); identifier = mst.getName(); expression = mst.getQualifierExpression(); - paren = NO_PAREN; + completionContext.add(CompletionContext.NO_PAREN); } if (identifier.contentEquals("*")) break; TreePath exprPath = new TreePath(tp, expression); - TypeMirror site = at.trees().getTypeMirror(exprPath); + selectorType = at.trees().getTypeMirror(exprPath); boolean staticOnly = isStaticContext(at, exprPath); ImportTree it = findImport(tp); @@ -413,17 +474,14 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis { ? ((MemberSelectTree) it.getQualifiedIdentifier()).getExpression().toString() + "." : ""; - addModuleElements(at, qualifiedPrefix, result); + addModuleElements(at, javadoc, selectStart, qualifiedPrefix + prefix, result); - requiredPrefix[0] = qualifiedPrefix + requiredPrefix[0]; - anchor[0] = selectStart; - - return result; + break; } boolean isImport = it != null; - List members = membersOf(at, site, staticOnly && !isImport && tp.getLeaf().getKind() == Kind.MEMBER_SELECT); + List members = membersOf(at, selectorType, staticOnly && !isImport && tp.getLeaf().getKind() == Kind.MEMBER_SELECT); Predicate filter = accessibility; if (isNewClass(tp)) { // new xxx.| @@ -434,7 +492,7 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis { } return true; }); - addElements(membersOf(at, members), constructorFilter, smartFilter, result); + addElements(javadoc, membersOf(at, members), constructorFilter, smartFilter, cursor, prefix, result); filter = filter.and(IS_PACKAGE); } else if (isThrowsClause(tp)) { @@ -442,7 +500,7 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis { filter = filter.and(IS_PACKAGE.or(IS_CLASS).or(IS_INTERFACE)); smartFilter = IS_PACKAGE.negate().and(smartTypeFilter); } else if (isImport) { - paren = NO_PAREN; + completionContext.add(CompletionContext.NO_PAREN); if (!it.isStatic()) { filter = filter.and(IS_PACKAGE.or(IS_CLASS).or(IS_INTERFACE)); } @@ -452,7 +510,7 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis { filter = filter.and(staticOnly ? STATIC_ONLY : INSTANCE_ONLY); - addElements(members, filter, smartFilter, paren, result); + addElements(javadoc, members, filter, smartFilter, cursor, prefix, result); break; } case IDENTIFIER: @@ -466,35 +524,43 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis { if (enclosingExpression != null) { // expr.new IDENT| TypeMirror site = at.trees().getTypeMirror(new TreePath(tp, enclosingExpression)); filter = filter.and(el -> el.getEnclosingElement().getKind() == ElementKind.CLASS && !el.getEnclosingElement().getModifiers().contains(Modifier.STATIC)); - addElements(membersOf(at, membersOf(at, site, false)), filter, smartFilter, result); + addElements(javadoc, membersOf(at, membersOf(at, site, false)), filter, smartFilter, cursor, prefix, result); } else { - addScopeElements(at, scope, listEnclosed, filter, smartFilter, result); + addScopeElements(at, javadoc, scope, listEnclosed, filter, smartFilter, cursor, prefix, result); } break; } if (isThrowsClause(tp)) { Predicate accept = accessibility.and(STATIC_ONLY) .and(IS_PACKAGE.or(IS_CLASS).or(IS_INTERFACE)); - addScopeElements(at, scope, IDENTITY, accept, IS_PACKAGE.negate().and(smartTypeFilter), result); + addElements(javadoc, scopeContent, accept, IS_PACKAGE.negate().and(smartTypeFilter), cursor, prefix, result); break; } if (isAnnotation(tp)) { + completionContext.add(CompletionContext.TYPES_AS_ANNOTATIONS); + if (getAnnotationAttributeNameOrNull(tp.getParentPath(), true) != null) { //nested annotation - result = completionSuggestionsImpl(inputCode, cursor - 1, anchor); - requiredPrefix[0] = "@" + requiredPrefix[0]; - return result; + return completionSuggestionsImpl(inputCode, cursor - 1, (state, items) -> { + CompletionState newState = new CompletionStateImpl(((CompletionStateImpl) state).scopeContent, completionContext, state.selectorType(), state.elementUtils(), state.typeUtils()); + return suggestionConvertor.convert(newState, + items.stream() + .filter(s -> s.element().getKind() == ElementKind.ANNOTATION_TYPE) + .filter(s -> s.element().getSimpleName().toString().startsWith(prefix)) + .map(s -> new ElementSuggestionImpl(s.element(), s.keyword(), s.matchesType(), s.anchor(), s.documentation())) + .toList()); + }); } Predicate accept = accessibility.and(STATIC_ONLY) .and(IS_PACKAGE.or(IS_CLASS).or(IS_INTERFACE)); - addScopeElements(at, scope, IDENTITY, accept, IS_PACKAGE.negate().and(smartTypeFilter), result); + addElements(javadoc, scopeContent, accept, IS_PACKAGE.negate().and(smartTypeFilter), cursor - 1, prefix, result); break; } ImportTree it = findImport(tp); if (it != null) { if (it.isModule()) { - addModuleElements(at, "", result); + addModuleElements(at, javadoc, cursor, prefix, result); } else { // the context of the identifier is an import, look for // package names that start with the identifier. @@ -503,20 +569,20 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis { // JShell to change to use the default package, and that // change is done, then this should use some variation // of membersOf(at, at.getElements().getPackageElement("").asType(), false) - addElements(listPackages(at, ""), + addElements(javadoc, listPackages(at, ""), it.isStatic() ? STATIC_ONLY.and(accessibility) : accessibility, - smartFilter, result); + smartFilter, cursor, prefix, result); - result.add(new SuggestionImpl("module ", false)); + result.add(new ElementSuggestionImpl(null, "module ", false, cursor, () -> null)); //TODO: better javadoc? } } break; case CLASS: { Predicate accept = accessibility.and(IS_TYPE); - addScopeElements(at, scope, IDENTITY, accept, smartFilter, result); - addElements(primitivesOrVoid(at), TRUE, smartFilter, result); + addElements(javadoc, scopeContent, accept, smartFilter, cursor, prefix, result); + addElements(javadoc, primitivesOrVoid(at), TRUE, smartFilter, cursor, prefix, result); break; } case BLOCK: @@ -553,6 +619,8 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis { accept = accept.and(IS_TYPE); } } else if (tp.getParentPath().getLeaf().getKind() == Kind.ANNOTATION) { + completionContext.add(CompletionContext.ANNOTATION_ATTRIBUTE); + AnnotationTree annotation = (AnnotationTree) tp.getParentPath().getLeaf(); Element annotationType = at.trees().getElement(tp.getParentPath()); Set present = annotation.getArguments() @@ -563,15 +631,18 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis { .filter(var -> var.getKind() == Kind.IDENTIFIER) .map(var -> ((IdentifierTree) var).getName().toString()) .collect(Collectors.toSet()); - addElements(ElementFilter.methodsIn(annotationType.getEnclosedElements()), el -> !present.contains(el.getSimpleName().toString()), TRUE, _ -> " = ", result); + addElements(javadoc, ElementFilter.methodsIn(annotationType.getEnclosedElements()), el -> !present.contains(el.getSimpleName().toString()), TRUE, cursor, prefix, /*_ -> " = ", */result); break; } else if (getAnnotationAttributeNameOrNull(tp, true) instanceof String attributeName) { + completionContext.add(CompletionContext.ANNOTATION_ATTRIBUTE); + Element annotationType = tp.getParentPath().getParentPath().getLeaf().getKind() == Kind.ANNOTATION ? at.trees().getElement(tp.getParentPath().getParentPath()) : at.trees().getElement(tp.getParentPath().getParentPath().getParentPath()); if (sp.getEndPosition(topLevel, tp.getParentPath().getLeaf()) == (-1)) { //synthetic 'value': - addElements(ElementFilter.methodsIn(annotationType.getEnclosedElements()), TRUE, TRUE, _ -> " = ", result); + //TODO: filter out existing: + addElements(javadoc, ElementFilter.methodsIn(annotationType.getEnclosedElements()), TRUE, TRUE, cursor, prefix, result); boolean hasValue = findAnnotationAttributeIfAny(annotationType, "value").isPresent(); if (!hasValue) { break; @@ -588,29 +659,18 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis { if (relevantAttributeType.getKind() == TypeKind.DECLARED && at.getTypes().asElement(relevantAttributeType) instanceof Element attributeTypeEl) { if (attributeTypeEl.getKind() == ElementKind.ANNOTATION_TYPE) { - boolean hasAnyAttributes = - ElementFilter.methodsIn(attributeTypeEl.getEnclosedElements()) - .stream() - .anyMatch(attribute -> attribute.getParameters().isEmpty()); - String paren = hasAnyAttributes ? "(" : ""; - String name = scopeContent(at, scope, IDENTITY).contains(attributeTypeEl) - ? attributeTypeEl.getSimpleName().toString() //simple name ought to be enough: - : ((TypeElement) attributeTypeEl).getQualifiedName().toString(); - result.add(new SuggestionImpl("@" + name + paren, true)); + completionContext.add(CompletionContext.TYPES_AS_ANNOTATIONS); + + addElements(javadoc, List.of(attributeTypeEl), TRUE, TRUE, cursor, prefix, result); break; } else if (attributeTypeEl.getKind() == ElementKind.ENUM) { - String typeName = scopeContent(at, scope, IDENTITY).contains(attributeTypeEl) - ? attributeTypeEl.getSimpleName().toString() //simple name ought to be enough: - : ((TypeElement) attributeTypeEl).getQualifiedName().toString(); - result.add(new SuggestionImpl(typeName, true)); - result.addAll(ElementFilter.fieldsIn(attributeTypeEl.getEnclosedElements()) - .stream() - .filter(e -> e.getKind() == ElementKind.ENUM_CONSTANT) - .map(c -> new SuggestionImpl(scopeContent(at, scope, IDENTITY).contains(c) - ? c.getSimpleName().toString() - : typeName + "." + c.getSimpleName(), c.getSimpleName().toString(), - true)) - .toList()); + List elements = new ArrayList<>(); + elements.add(attributeTypeEl); + ElementFilter.fieldsIn(attributeTypeEl.getEnclosedElements()) + .stream() + .filter(e -> e.getKind() == ElementKind.ENUM_CONSTANT) + .forEach(elements::add); + addElements(javadoc, elements, TRUE, TRUE, cursor, prefix, result); break; } } @@ -625,7 +685,7 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis { insertPrimitiveTypes = false; } - addScopeElements(at, scope, IDENTITY, accept, smartFilter, result); + addElements(javadoc, scopeContent, accept, smartFilter, cursor, prefix, result); if (insertPrimitiveTypes) { Tree parent = tp.getParentPath().getLeaf(); @@ -637,22 +697,32 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis { case TYPE_PARAMETER, CLASS, INTERFACE, ENUM, RECORD -> FALSE; default -> TRUE; }; - addElements(primitivesOrVoid(at), accept, smartFilter, result); + addElements(javadoc, primitivesOrVoid(at), accept, smartFilter, cursor, prefix, result); } boolean hasBooleanSmartType = targetTypes != null && StreamSupport.stream(targetTypes.spliterator(), false) .anyMatch(tm -> tm.getKind() == TypeKind.BOOLEAN); if (hasBooleanSmartType) { - result.add(new SuggestionImpl("true", true)); - result.add(new SuggestionImpl("false", true)); + for (String booleanKeyword : new String[] {"false", "true"}) { + if (booleanKeyword.startsWith(prefix)) { + result.add(new ElementSuggestionImpl(null, booleanKeyword, true, cursor, () -> null)); + } + } } break; } } + + CompletionState completionState = new CompletionStateImpl(scopeContent, completionContext, selectorType, at.getElements(), at.getTypes()); + + return suggestionConvertor.convert(completionState, result); } - anchor[0] = cursor; - return result; + } catch (IOException ex) { + //TODO: + ex.printStackTrace(); + } + return Collections.emptyList(); }); } @@ -1162,59 +1232,74 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis { IS_PACKAGE.test(encl); }; private final Function> IDENTITY = Collections::singletonList; - private final Function DEFAULT_PAREN = hasParams -> hasParams ? "(" : "()"; - private final Function NO_PAREN = hasParams -> ""; - - private void addElements(Iterable elements, Predicate accept, Predicate smart, List result) { - addElements(elements, accept, smart, DEFAULT_PAREN, result); - } - private void addElements(Iterable elements, Predicate accept, Predicate smart, Function paren, List result) { - Set hasParams = Util.stream(elements) - .filter(accept) - .filter(IS_CONSTRUCTOR.or(IS_METHOD)) - .filter(c -> !((ExecutableElement)c).getParameters().isEmpty()) - .map(this::simpleName) - .collect(toSet()); + private void addElements(JavadocHelper javadoc, Iterable elements, Predicate accept, Predicate smart, int anchor, String prefix, List result) { for (Element c : elements) { - if (!accept.test(c)) + if (!accept.test(c) || !simpleContinuationName(c).startsWith(prefix)) continue; if (c.getKind() == ElementKind.METHOD && c.getSimpleName().contentEquals(Util.DOIT_METHOD_NAME) && ((ExecutableElement) c).getParameters().isEmpty()) { continue; } - String simpleName = simpleName(c); - switch (c.getKind()) { - case CONSTRUCTOR: - case METHOD: - // add trailing open or matched parenthesis, as approriate - simpleName += paren.apply(hasParams.contains(simpleName)); - break; - case PACKAGE: - // add trailing dot to package names - simpleName += "."; - break; - } - result.add(new SuggestionImpl(simpleName, smart.test(c))); + StoredElement stored = javadoc.getHandle(c); + Collection sourceLocations = javadoc.getSourceLocations(); + result.add(new ElementSuggestionImpl(c, null, smart.test(c), anchor, () -> { + return proc.taskFactory.analyze(proc.outerMap.wrapInTrialClass(Wrap.methodWrap(";")), task -> { + try (JavadocHelper nestedJavadoc = JavadocHelper.create(task.task, sourceLocations)) { + return nestedJavadoc.getResolvedDocComment(stored); + } catch (IOException ex) { + ex.printStackTrace(); + } + return null; + }); + })); } } - private void addModuleElements(AnalyzeTask at, + private void addModuleElements(AnalyzeTask at, JavadocHelper javadoc, int anchor, String prefix, - List result) { + List result) { for (ModuleElement me : at.getElements().getAllModuleElements()) { if (!me.getQualifiedName().toString().startsWith(prefix)) { continue; } - result.add(new SuggestionImpl(me.getQualifiedName().toString(), - false)); + result.add(new ElementSuggestionImpl(me, null, false, anchor, () -> null)); //TODO: better javadoc! } } - private String simpleName(Element el) { - return el.getKind() == ElementKind.CONSTRUCTOR ? el.getEnclosingElement().getSimpleName().toString() - : el.getSimpleName().toString(); + private String simpleContinuationName(Element el) { + return switch (el.getKind()) { + case CONSTRUCTOR -> el.getEnclosingElement().getSimpleName().toString(); + case MODULE -> ((ModuleElement) el).getQualifiedName().toString(); + default -> el.getSimpleName().toString(); + }; + } + + private String continuationName(CompletionState state, Element el) { + if (state.completionContext().contains(CompletionContext.QUALIFIED)) { + return simpleContinuationName(el); + } else if (state.availableUsingSimpleName(el)) { + return el.getSimpleName().toString(); + } else { + return (switch (el.getKind()) { + case PACKAGE -> ((PackageElement) el).getQualifiedName(); + case ANNOTATION_TYPE, CLASS, ENUM, INTERFACE, RECORD -> + primitiveLikeClass(el) + ? el.getSimpleName() + : continuationName(state, el.getEnclosingElement()) + "." + el.getSimpleName(); + case ENUM_CONSTANT, FIELD, METHOD -> + el.getModifiers().contains(Modifier.STATIC) + ? continuationName(state, el.getEnclosingElement()) + "." + el.getSimpleName() + : el.getSimpleName(); + default -> simpleContinuationName(el); + }).toString(); + } + } + + private boolean primitiveLikeClass(Element el) { + return el.asType().getKind().isPrimitive() || + el.asType().getKind() == TypeKind.VOID; } private List membersOf(AnalyzeTask at, TypeMirror site, boolean shouldGenerateDotClassItem) { @@ -1625,8 +1710,8 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis { }; } - private void addScopeElements(AnalyzeTask at, Scope scope, Function> elementConvertor, Predicate filter, Predicate smartFilter, List result) { - addElements(scopeContent(at, scope, elementConvertor), filter, smartFilter, result); + private void addScopeElements(AnalyzeTask at, JavadocHelper javadoc, Scope scope, Function> elementConvertor, Predicate filter, Predicate smartFilter, int anchor, String prefix, List result) { + addElements(javadoc, scopeContent(at, scope, elementConvertor), filter, smartFilter, anchor, prefix, result); } private Iterable> methodCandidates(AnalyzeTask at, TreePath invocation) { @@ -1759,6 +1844,7 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis { Stream elements; Iterable> candidates; List arguments; + int parameterIndex = -1; if (tp.getLeaf().getKind() == Kind.METHOD_INVOCATION || tp.getLeaf().getKind() == Kind.NEW_CLASS) { if (tp.getLeaf().getKind() == Kind.METHOD_INVOCATION) { @@ -1783,6 +1869,10 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis { } elements = Util.stream(candidates).map(method -> method.fst); + + if (prevPath != null) { + parameterIndex = arguments.indexOf(prevPath.getLeaf()); + } } else if (tp.getLeaf().getKind() == Kind.IDENTIFIER || tp.getLeaf().getKind() == Kind.MEMBER_SELECT) { Element el = at.trees().getElement(tp); @@ -1820,7 +1910,8 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis { List result = Collections.emptyList(); try (JavadocHelper helper = JavadocHelper.create(at.task, findSources())) { - result = elements.map(el -> constructDocumentation(at, helper, el, computeJavadoc)) + int parameterIndexFin = parameterIndex; + result = elements.map(el -> constructDocumentation(at, helper, el, parameterIndexFin, computeJavadoc)) .filter(Objects::nonNull) .toList(); } catch (IOException ex) { @@ -1831,7 +1922,7 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis { }); } - private Documentation constructDocumentation(AnalyzeTask at, JavadocHelper helper, Element el, boolean computeJavadoc) { + private Documentation constructDocumentation(AnalyzeTask at, JavadocHelper helper, Element el, int parameterIndex, boolean computeJavadoc) { String javadoc = null; try { if (hasSyntheticParameterNames(el)) { @@ -1844,7 +1935,7 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis { proc.debug(ex, "SourceCodeAnalysisImpl.element2String(..., " + el + ")"); } String signature = Util.expunge(elementHeader(at, el, !hasSyntheticParameterNames(el), true)); - return new DocumentationImpl(signature, javadoc); + return new DocumentationImpl(signature, javadoc, parameterIndex); } public void close() { @@ -1857,27 +1948,7 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis { } } - private static final class DocumentationImpl implements Documentation { - - private final String signature; - private final String javadoc; - - public DocumentationImpl(String signature, String javadoc) { - this.signature = signature; - this.javadoc = javadoc; - } - - @Override - public String signature() { - return signature; - } - - @Override - public String javadoc() { - return javadoc; - } - - } + private record DocumentationImpl(String signature, String javadoc, int activeParameterIndex) implements Documentation {} private boolean isEmptyArgumentsContext(List arguments) { if (arguments.size() == 1) { @@ -2520,7 +2591,6 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis { private static class SuggestionImpl implements Suggestion { private final String continuation; - private final String filteringText; private final boolean matchesType; /** @@ -2530,19 +2600,7 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis { * @param matchesType does the candidate match the target type */ public SuggestionImpl(String continuation, boolean matchesType) { - this(continuation, continuation, matchesType); - } - - /** - * Create a {@code Suggestion} instance. - * - * @param continuation a candidate continuation of the user's input - * @param filteringText a text that should be used for filtering - * @param matchesType does the candidate match the target type - */ - public SuggestionImpl(String continuation, String filteringText, boolean matchesType) { this.continuation = continuation; - this.filteringText = filteringText; this.matchesType = matchesType; } @@ -2620,4 +2678,53 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis { } } + record ElementSuggestionImpl(Element element, String keyword, boolean matchesType, int anchor, Supplier documentation) implements ElementSuggestion { + } + + final static class CompletionStateImpl implements CompletionState { + + private final Collection scopeContent; + private final Set completionContext; + private final TypeMirror selectorType; + private final Elements elementUtils; + private final Types typeUtils; + + public CompletionStateImpl(Collection scopeContent, + Set completionContext, + TypeMirror selectorType, + Elements elementUtils, + Types typeUtils) { + this.scopeContent = scopeContent; + this.completionContext = completionContext; + this.selectorType = selectorType; + this.elementUtils = elementUtils; + this.typeUtils = typeUtils; + } + + @Override + public boolean availableUsingSimpleName(Element el) { + return scopeContent.contains(el); + } + + @Override + public Set completionContext() { + return completionContext; + } + + @Override + public TypeMirror selectorType() { + return selectorType; + } + + @Override + public Elements elementUtils() { + return elementUtils; + } + + @Override + public Types typeUtils() { + return typeUtils; + } + + } } diff --git a/test/langtools/jdk/jshell/CompletionAPITest.java b/test/langtools/jdk/jshell/CompletionAPITest.java new file mode 100644 index 00000000000..ad5dea90a95 --- /dev/null +++ b/test/langtools/jdk/jshell/CompletionAPITest.java @@ -0,0 +1,303 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8366691 + * @summary Test JShell Completion API + * @library /tools/lib + * @modules jdk.compiler/com.sun.tools.javac.api + * jdk.compiler/com.sun.tools.javac.main + * jdk.jdeps/com.sun.tools.javap + * jdk.jshell/jdk.jshell:open + * @build toolbox.ToolBox toolbox.JarTask toolbox.JavacTask + * @build KullaTesting TestingInputStream Compiler + * @run junit CompletionAPITest + */ + +import java.io.IOException; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.lang.reflect.Field; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiConsumer; +import java.util.function.Supplier; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; +import java.util.stream.Collectors; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.QualifiedNameable; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import jdk.jshell.SourceCodeAnalysis.CompletionContext; +import jdk.jshell.SourceCodeAnalysis.CompletionState; +import jdk.jshell.SourceCodeAnalysis.ElementSuggestion; +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; + +public class CompletionAPITest extends KullaTesting { + + private static final long TIMEOUT = 2_000; + + @Test + public void testAPI() { + waitIndexingFinished(); + assertEval("String str = \"\";"); + List actual; + actual = completionSuggestions("str.", (state, suggestions) -> { + assertEquals(EnumSet.of(CompletionContext.QUALIFIED), state.completionContext()); + }); + assertTrue(actual.contains("java.lang.String.length()"), String.valueOf(actual)); + actual = completionSuggestions("java.lang.", (state, suggestions) -> { + assertEquals(EnumSet.of(CompletionContext.QUALIFIED), state.completionContext()); + }); + assertTrue(actual.contains("java.lang.String"), String.valueOf(actual)); + actual = completionSuggestions("java.", (state, suggestions) -> { + assertEquals(EnumSet.of(CompletionContext.QUALIFIED), state.completionContext()); + }); + assertTrue(actual.contains("java.lang"), String.valueOf(actual)); + assertEval("@interface Ann2 { }"); + assertEval("@interface Ann1 { Ann2 value(); }"); + actual = completionSuggestions("@Ann", (state, suggestions) -> { + assertEquals(EnumSet.of(CompletionContext.TYPES_AS_ANNOTATIONS), state.completionContext()); + }); + assertTrue(actual.containsAll(Set.of("Ann1", "Ann2")), String.valueOf(actual)); + actual = completionSuggestions("@Ann1(", (state, suggestions) -> { + assertEquals(EnumSet.of(CompletionContext.ANNOTATION_ATTRIBUTE, + CompletionContext.TYPES_AS_ANNOTATIONS), + state.completionContext()); + }); + assertTrue(actual.contains("Ann2"), String.valueOf(actual)); + actual = completionSuggestions("import static java.lang.String.", (state, suggestions) -> { + assertEquals(EnumSet.of(CompletionContext.QUALIFIED, CompletionContext.NO_PAREN), state.completionContext()); + }); + assertTrue(actual.contains("java.lang.String.valueOf(int arg0)"), String.valueOf(actual)); + actual = completionSuggestions("java.util.function.IntFunction f = String::", (state, suggestions) -> { + assertEquals(EnumSet.of(CompletionContext.QUALIFIED, CompletionContext.NO_PAREN), state.completionContext()); + }); + assertTrue(actual.contains("java.lang.String.valueOf(int arg0)"), String.valueOf(actual)); + actual = completionSuggestions("str.^len", (state, suggestions) -> { + assertEquals(EnumSet.of(CompletionContext.QUALIFIED), state.completionContext()); + }); + assertTrue(actual.contains("java.lang.String.length()"), String.valueOf(actual)); + actual = completionSuggestions("^@Depr", (state, suggestions) -> { + assertEquals(EnumSet.of(CompletionContext.TYPES_AS_ANNOTATIONS), state.completionContext()); + }); + assertTrue(actual.contains("java.lang.Deprecated"), String.valueOf(actual)); + assertEval("import java.util.*;"); + actual = completionSuggestions("^ArrayL", (state, suggestions) -> { + TypeElement arrayList = + suggestions.stream() + .filter(el -> el.element() != null) + .map(el -> el.element()) + .filter(el -> el.getKind() == ElementKind.CLASS) + .map(el -> (TypeElement) el) + .filter(el -> el.getQualifiedName().contentEquals("java.util.ArrayList")) + .findAny() + .orElseThrow(); + assertTrue(state.availableUsingSimpleName(arrayList)); + assertEquals(EnumSet.noneOf(CompletionContext.class), state.completionContext()); + }); + assertTrue(actual.contains("java.util.ArrayList"), String.valueOf(actual)); + completionSuggestions("(new java.util.ArrayList()).", (state, suggestions) -> { + List elsWithTypes = + suggestions.stream() + .filter(el -> el.element() != null) + .map(el -> el.element()) + .filter(el -> el.getKind() == ElementKind.METHOD) + .map(el -> el.getSimpleName() + state.typeUtils() + .asMemberOf((DeclaredType) state.selectorType(), el) + .toString()) + .toList(); + assertTrue(elsWithTypes.contains("add(java.lang.String)boolean")); + }); + } + + @Test + public void testDocumentation() { + waitIndexingFinished(); + + Path classes = prepareZip(); + getState().addToClasspath(classes.toString()); + + AtomicReference> documentation = new AtomicReference<>(); + AtomicReference> clazz = new AtomicReference<>(); + completionSuggestions("jshelltest.JShellTest", (state, suggestions) -> { + ElementSuggestion test = + suggestions.stream() + .filter(el -> el.element() != null) + .filter(el -> el.element().getKind() == ElementKind.CLASS) + .filter(el -> ((TypeElement) el.element()).getQualifiedName().contentEquals("jshelltest.JShellTest")) + .findAny() + .orElseThrow(); + documentation.set(test.documentation()); + clazz.set(new WeakReference<>(test.element())); + }); + + //throw away the JavacTaskPool, so that the cached javac instances are dropped: + getState().addToClasspath("undefined"); + + long start = System.currentTimeMillis(); + + while (clazz.get().get() != null && (System.currentTimeMillis() - start) < TIMEOUT) { + System.gc(); + } + + assertNull(clazz.get().get()); + assertEquals("JShellTest 0 ", documentation.get().get()); + } + + @Test + public void testSignature() { + waitIndexingFinished(); + + assertEval("void test(int i) {}"); + assertEval("void test(int i, int j) {}"); + assertSignature("test(|", true, "void test(int i):0", "void test(int i, int j):0"); + assertSignature("test(0, |", true, "void test(int i, int j):1"); + } + + private List completionSuggestions(String input, + BiConsumer> validator) { + int expectedAnchor = input.indexOf('^'); + + if (expectedAnchor != (-1)) { + input = input.substring(0, expectedAnchor) + input.substring(expectedAnchor + 1); + } + + AtomicInteger mergedAnchor = new AtomicInteger(-1); + + List result = getAnalysis().completionSuggestions(input, input.length(), (state, suggestions) -> { + validator.accept(state, suggestions); + + if (expectedAnchor != (-1)) { + for (ElementSuggestion sugg : suggestions) { + if (mergedAnchor.get() == (-1)) { + mergedAnchor.set(sugg.anchor()); + } else { + assertEquals(mergedAnchor.get(), sugg.anchor()); + } + } + } + + return suggestions.stream() + .map(this::convertElement) + .toList(); + }); + + if (expectedAnchor != (-1)) { + assertEquals(expectedAnchor, mergedAnchor.get()); + } + + return result; + } + + private String convertElement(ElementSuggestion suggestion) { + if (suggestion.keyword() != null) { + return suggestion.keyword(); + } + + Element el = suggestion.element(); + + if (el.getKind().isClass() || el.getKind().isInterface() || el.getKind() == ElementKind.PACKAGE) { + String qualifiedName = ((QualifiedNameable) el).getQualifiedName().toString(); + if (qualifiedName.startsWith("REPL.$JShell$")) { + String[] parts = qualifiedName.split("\\.", 3); + + return parts[2]; + } else { + return qualifiedName; + } + } else if (el.getKind().isField()) { + return ((QualifiedNameable) el.getEnclosingElement()).getQualifiedName().toString() + + "." + + el.getSimpleName(); + } else if (el.getKind() == ElementKind.CONSTRUCTOR || el.getKind() == ElementKind.METHOD) { + String name = el.getKind() == ElementKind.CONSTRUCTOR ? "" : "." + el.getSimpleName(); + ExecutableElement method = (ExecutableElement) el; + + return ((QualifiedNameable) el.getEnclosingElement()).getQualifiedName().toString() + + name + + method.getParameters() + .stream() + .map(var -> var.asType().toString() + " " + var.getSimpleName()) + .collect(Collectors.joining(", ", "(", ")")); + } else { + return el.getSimpleName().toString(); + } + } + + private Path prepareZip() { + String clazz = + "package jshelltest;\n" + + "/**JShellTest 0" + + " */\n" + + "public class JShellTest {\n" + + "}\n"; + + Path srcZip = Paths.get("src.zip"); + + try (JarOutputStream out = new JarOutputStream(Files.newOutputStream(srcZip))) { + out.putNextEntry(new JarEntry("jshelltest/JShellTest.java")); + out.write(clazz.getBytes()); + } catch (IOException ex) { + throw new IllegalStateException(ex); + } + + compiler.compile(clazz); + + try { + Field availableSources = Class.forName("jdk.jshell.SourceCodeAnalysisImpl").getDeclaredField("availableSourcesOverride"); + availableSources.setAccessible(true); + availableSources.set(null, Arrays.asList(srcZip)); + } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException | ClassNotFoundException ex) { + throw new IllegalStateException(ex); + } + + return compiler.getClassDir(); + } + //where: + private final Compiler compiler = new Compiler(); + + static { + try { + //disable reading of paramater names, to improve stability: + Class analysisClass = Class.forName("jdk.jshell.SourceCodeAnalysisImpl"); + Field params = analysisClass.getDeclaredField("COMPLETION_EXTRA_PARAMETERS"); + params.setAccessible(true); + params.set(null, List.of()); + } catch (ReflectiveOperationException ex) { + throw new IllegalStateException(ex); + } + } +} diff --git a/test/langtools/jdk/jshell/CompletionSuggestionTest.java b/test/langtools/jdk/jshell/CompletionSuggestionTest.java index 7907fa4d027..7d739efddc6 100644 --- a/test/langtools/jdk/jshell/CompletionSuggestionTest.java +++ b/test/langtools/jdk/jshell/CompletionSuggestionTest.java @@ -44,6 +44,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.Set; import java.util.HashSet; +import java.util.List; import java.util.function.BiFunction; import java.util.function.Function; import java.util.jar.JarEntry; @@ -901,7 +902,7 @@ public class CompletionSuggestionTest extends KullaTesting { @Test public void testAnnotation() { - assertCompletion("@Deprec|", "Deprecated"); + assertCompletion("@Deprec|", "@Deprecated("); assertCompletion("@Deprecated(|", "forRemoval = ", "since = "); assertCompletion("@Deprecated(forRemoval = |", true, "false", "true"); assertCompletion("@Deprecated(forRemoval = true, |", "since = "); @@ -951,4 +952,16 @@ public class CompletionSuggestionTest extends KullaTesting { assertCompletion("class S { public int length() { return 0; } } new S().len|", true, "length()"); assertSignature("void f() { } f(|", "void f()"); } + + static { + try { + //disable reading of paramater names, to improve stability: + Class analysisClass = Class.forName("jdk.jshell.SourceCodeAnalysisImpl"); + Field params = analysisClass.getDeclaredField("COMPLETION_EXTRA_PARAMETERS"); + params.setAccessible(true); + params.set(null, List.of()); + } catch (ReflectiveOperationException ex) { + throw new IllegalStateException(ex); + } + } } diff --git a/test/langtools/jdk/jshell/KullaTesting.java b/test/langtools/jdk/jshell/KullaTesting.java index fd9348507c4..8b62ce69a4c 100644 --- a/test/langtools/jdk/jshell/KullaTesting.java +++ b/test/langtools/jdk/jshell/KullaTesting.java @@ -44,6 +44,7 @@ import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -969,11 +970,21 @@ public class KullaTesting { } public void assertSignature(String code, String... expected) { + assertSignature(code, false, expected); + } + + public void assertSignature(String code, boolean includeActive, String... expected) { int cursor = code.indexOf('|'); code = code.replace("|", ""); assertTrue(cursor > -1, "'|' expected, but not found in: " + code); List documentation = getAnalysis().documentation(code, cursor, false); - Set docSet = documentation.stream().map(doc -> doc.signature()).collect(Collectors.toSet()); + Function convert; + if (includeActive) { + convert = doc -> doc.signature() + ":" + doc.activeParameterIndex(); + } else { + convert = doc -> doc.signature(); + } + Set docSet = documentation.stream().map(convert).collect(Collectors.toSet()); Set expectedSet = Stream.of(expected).collect(Collectors.toSet()); assertEquals(expectedSet, docSet, "Input: " + code); } diff --git a/test/langtools/jdk/jshell/ToolTabSnippetTest.java b/test/langtools/jdk/jshell/ToolTabSnippetTest.java index 968935b0814..112208c40b3 100644 --- a/test/langtools/jdk/jshell/ToolTabSnippetTest.java +++ b/test/langtools/jdk/jshell/ToolTabSnippetTest.java @@ -103,8 +103,8 @@ public class ToolTabSnippetTest extends UITesting { inputSink.write("(" + TAB); waitOutput(out, "\\(\n" + resource("jshell.console.completion.current.signatures") + "\n" + - "JShellTest\\(String str\\)\n" + - "JShellTest\\(String str, int i\\)\n" + + "JShellTest\\(\\u001B\\[1mString str\\u001B\\[0m\\)\n" + + "JShellTest\\(\\u001B\\[1mString str\\u001B\\[0m, int i\\)\n" + "\n" + resource("jshell.console.see.documentation") + REDRAW_PROMPT + "new JShellTest\\("); @@ -138,8 +138,8 @@ public class ToolTabSnippetTest extends UITesting { "str \n" + "\n" + resource("jshell.console.completion.current.signatures") + "\n" + - "JShellTest\\(String str\\)\n" + - "JShellTest\\(String str, int i\\)\n" + + "JShellTest\\(\\u001B\\[1mString str\\u001B\\[0m\\)\n" + + "JShellTest\\(\\u001B\\[1mString str\\u001B\\[0m, int i\\)\n" + "\n" + resource("jshell.console.see.documentation") + REDRAW_PROMPT + "new JShellTest\\("); From 400a83da893f5fc285a175b63a266de21e93683c Mon Sep 17 00:00:00 2001 From: Ivan Walulya Date: Wed, 12 Nov 2025 08:48:07 +0000 Subject: [PATCH 011/418] 8371625: G1: G1HeapRegion::print_on misalignment Reviewed-by: ayang, tschatzl --- src/hotspot/share/gc/g1/g1HeapRegion.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1HeapRegion.cpp b/src/hotspot/share/gc/g1/g1HeapRegion.cpp index ca4359dcc24..63d64503316 100644 --- a/src/hotspot/share/gc/g1/g1HeapRegion.cpp +++ b/src/hotspot/share/gc/g1/g1HeapRegion.cpp @@ -428,7 +428,7 @@ bool G1HeapRegion::verify_code_roots(VerifyOption vo) const { void G1HeapRegion::print() const { print_on(tty); } void G1HeapRegion::print_on(outputStream* st) const { - st->print("|%4u", this->_hrm_index); + st->print("|%5u", this->_hrm_index); st->print("|" PTR_FORMAT ", " PTR_FORMAT ", " PTR_FORMAT, p2i(bottom()), p2i(top()), p2i(end())); st->print("|%3d%%", (int) ((double) used() * 100 / capacity())); @@ -442,7 +442,7 @@ void G1HeapRegion::print_on(outputStream* st) const { st->print("| "); } G1ConcurrentMark* cm = G1CollectedHeap::heap()->concurrent_mark(); - st->print("|TAMS " PTR_FORMAT "| PB " PTR_FORMAT "| %s ", + st->print("|TAMS " PTR_FORMAT "| PB " PTR_FORMAT "| %-9s ", p2i(cm->top_at_mark_start(this)), p2i(parsable_bottom_acquire()), rem_set()->get_state_str()); if (UseNUMA) { G1NUMA* numa = G1NUMA::numa(); From 1f1f7bb44842fac966bd8f16cc6cfeee0ea972f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Jeli=C5=84ski?= Date: Wed, 12 Nov 2025 12:32:05 +0000 Subject: [PATCH 012/418] 8370024: HttpClient: QUIC congestion controller doesn't implement pacing Reviewed-by: dfuchs --- .../net/http/quic/PacketSpaceManager.java | 22 +- .../http/quic/QuicCongestionController.java | 47 ++- .../net/http/quic/QuicConnectionImpl.java | 2 +- .../jdk/internal/net/http/quic/QuicPacer.java | 191 ++++++++++++ .../quic/QuicRenoCongestionController.java | 116 +++++++- .../java/net/httpclient/quic/PacerTest.java | 273 ++++++++++++++++++ .../quic/PacketSpaceManagerTest.java | 38 ++- 7 files changed, 673 insertions(+), 16 deletions(-) create mode 100644 src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicPacer.java create mode 100644 test/jdk/java/net/httpclient/quic/PacerTest.java diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/quic/PacketSpaceManager.java b/src/java.net.http/share/classes/jdk/internal/net/http/quic/PacketSpaceManager.java index 494af85447e..90031decdb4 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/quic/PacketSpaceManager.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/quic/PacketSpaceManager.java @@ -97,6 +97,7 @@ public sealed class PacketSpaceManager implements PacketSpace private final QuicCongestionController congestionController; private volatile boolean blockedByCC; + private volatile boolean blockedByPacer; // packet threshold for loss detection; RFC 9002 suggests 3 private static final long kPacketThreshold = 3; // Multiplier for persistent congestion; RFC 9002 suggests 3 @@ -386,6 +387,7 @@ public sealed class PacketSpaceManager implements PacketSpace // Handle is called from within the executor var nextDeadline = this.nextDeadline; Deadline now = now(); + congestionController.updatePacer(now); do { transmitNow = false; var closed = !isOpenForTransmission(); @@ -404,6 +406,7 @@ public sealed class PacketSpaceManager implements PacketSpace boolean needBackoff = isPTO(now); int packetsSent = 0; boolean cwndAvailable; + long startTime = System.nanoTime(); while ((cwndAvailable = congestionController.canSendPacket()) || (needBackoff && packetsSent < 2)) { // if PTO, try to send 2 packets if (!isOpenForTransmission()) { @@ -442,6 +445,7 @@ public sealed class PacketSpaceManager implements PacketSpace + qkue.getMessage()); } if (!sentNew) { + congestionController.appLimited(); break; } else { if (needBackoff && packetsSent == 0 && Log.quicRetransmit()) { @@ -451,12 +455,19 @@ public sealed class PacketSpaceManager implements PacketSpace } packetsSent++; } - blockedByCC = !cwndAvailable; + if (packetsSent != 0 && Log.quicCC()) { + Log.logQuic("%s OUT: sent: %s packets in %s ns, cwnd limited: %s, pacer limited: %s".formatted( + packetEmitter.logTag(), packetsSent, System.nanoTime() - startTime, + congestionController.isCwndLimited(), congestionController.isPacerLimited())); + } + blockedByCC = !cwndAvailable && congestionController.isCwndLimited(); + blockedByPacer = !cwndAvailable && congestionController.isPacerLimited(); if (!cwndAvailable && isOpenForTransmission()) { if (debug.on()) debug.log("handle: blocked by CC"); // CC might be available already if (congestionController.canSendPacket()) { if (debug.on()) debug.log("handle: unblocked immediately"); + blockedByCC = blockedByPacer = false; transmitNow = true; } } @@ -1389,6 +1400,15 @@ public sealed class PacketSpaceManager implements PacketSpace Deadline ackDeadline = (ack == null || ack.sent() != null) ? Deadline.MAX // if the ack frame has already been sent, getNextAck() returns null : ack.deadline(); + if (blockedByPacer) { + Deadline pacerDeadline = congestionController.pacerDeadline(); + if (verbose && Log.quicTimer()) { + Log.logQuic(String.format("%s: [%s] pacer deadline: %s, ackDeadline: %s, deadline in %s", + packetEmitter.logTag(), packetNumberSpace, pacerDeadline, ackDeadline, + Utils.debugDeadline(now(), min(ackDeadline, pacerDeadline)))); + } + return min(ackDeadline, pacerDeadline); + } Deadline lossDeadline = getLossTimer(); // TODO: consider removing the debug traces in this method when integrating // if both loss deadline and PTO timer are set, loss deadline is always earlier diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicCongestionController.java b/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicCongestionController.java index 4bfad2c5560..94dafaf16eb 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicCongestionController.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicCongestionController.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -72,4 +72,49 @@ public interface QuicCongestionController { */ void packetDiscarded(Collection discardedPackets); + /** + * {@return the current size of the congestion window in bytes} + */ + long congestionWindow(); + + /** + * {@return the initial window size in bytes} + */ + long initialWindow(); + + /** + * {@return maximum datagram size} + */ + long maxDatagramSize(); + + /** + * {@return true if the connection is in slow start phase} + */ + boolean isSlowStart(); + + /** + * Update the pacer with the current time + * @param now the current time + */ + void updatePacer(Deadline now); + + /** + * {@return true if sending is blocked by pacer} + */ + boolean isPacerLimited(); + + /** + * {@return true if sending is blocked by congestion window} + */ + boolean isCwndLimited(); + + /** + * {@return deadline when pacer will unblock sending} + */ + Deadline pacerDeadline(); + + /** + * Notify the congestion controller that sending is app-limited + */ + void appLimited(); } diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicConnectionImpl.java b/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicConnectionImpl.java index 9a280c3a8a5..7ebe09e008e 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicConnectionImpl.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicConnectionImpl.java @@ -334,7 +334,7 @@ public class QuicConnectionImpl extends QuicConnection implements QuicPacketRece this.connectionId = this.endpoint.idFactory().newConnectionId(); this.logTag = logTagFormat.formatted(labelId); this.dbgTag = dbgTag(quicInstance, logTag); - this.congestionController = new QuicRenoCongestionController(dbgTag); + this.congestionController = new QuicRenoCongestionController(dbgTag, rttEstimator); this.originalVersion = this.quicVersion = firstFlightVersion == null ? QuicVersion.firstFlightVersion(quicInstance.getAvailableVersions()) : firstFlightVersion; diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicPacer.java b/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicPacer.java new file mode 100644 index 00000000000..0ba7d78038b --- /dev/null +++ b/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicPacer.java @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2025, 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. 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.internal.net.http.quic; + +import jdk.internal.net.http.common.Deadline; +import jdk.internal.net.http.common.Log; +import jdk.internal.net.http.common.Utils; +import jdk.internal.util.OperatingSystem; + +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Implementation of pacing. + * + * When the connection is sending at a rate lower than permitted + * by the congestion controller, pacer is responsible for spreading out + * the outgoing packets across the entire RTT. + * + * Technically the pacer provides two functions: + * - computes the number of packets that can be sent now + * - computes the time when another packet can be sent + * + * When a new flow starts, or when the flow is not pacer-limited, + * the pacer limits the window to: + * max(INITIAL_WINDOW, pacingRate / timerFreq) + * timerFreq is the best timer resolution we can get from the selector. + * pacingRate is N * congestionWindow / smoothedRTT + * where N = 2 when in slow start, N = 1.25 otherwise. + * + * After that, the window refills at pacingRate, up to two timer periods or 4 packets, + * whichever is higher. + * + * The time when another packet can be sent is computed + * as the time when the window will allow at least 2 packets. + * + * All methods are externally synchronized in congestion controller. + * + * Ideas taken from: + * https://www.rfc-editor.org/rfc/rfc9002.html#name-pacing + * https://www.ietf.org/archive/id/draft-welzl-iccrg-pacing-03.html + */ +public class QuicPacer { + + // usually 64 Hz on Windows, 1000 on Linux + private static final long DEFAULT_TIMER_FREQ_HZ = OperatingSystem.isWindows() ? 64 : 1000; + private static final long TIMER_FREQ_HZ = Math.clamp( + Utils.getLongProperty("jdk.httpclient.quic.timerFrequency", DEFAULT_TIMER_FREQ_HZ), + 1, 1000); + + private final QuicRttEstimator rttEstimator; + private final QuicCongestionController congestionController; + + private boolean appLimited; + private long quota; + private Deadline lastUpdate; + + /** + * Create a QUIC pacer for the given RTT estimator and congestion controller + * + * @param rttEstimator the RTT estimator + * @param congestionController the congestion controller + */ + public QuicPacer(QuicRttEstimator rttEstimator, + QuicCongestionController congestionController) { + this.rttEstimator = rttEstimator; + this.congestionController = congestionController; + this.appLimited = true; + } + + /** + * called to indicate that the flow is app-limited. + * Alters the behavior of the following updateQuota call. + */ + public void appLimited() { + appLimited = true; + } + + /** + * {@return true if pacer quota not hit yet, false otherwise} + */ + public boolean canSend() { + return quota >= congestionController.maxDatagramSize(); + } + + /** + * Update quota based on time since the last call to this method + * and whether appLimited() was called or not. + * + * @param now current time + */ + public void updateQuota(Deadline now) { + if (lastUpdate != null && !now.isAfter(lastUpdate)) { + // might happen when transmission tasks from different packet spaces + // race to update quota. Keep the most recent update only. + return; + } + long rttMicros = rttEstimator.state().smoothedRttMicros(); + long cwnd = congestionController.congestionWindow(); + if (rttMicros * TIMER_FREQ_HZ < TimeUnit.SECONDS.toMicros(2)) { + // RTT less than two timer periods; don't pace + quota = 2 * cwnd; + lastUpdate = now; + return; + } + long pacingRate = cwnd * (congestionController.isSlowStart() ? 2_000_000 : 1_250_000) / rttMicros; // bytes per second + long initialWindow = congestionController.initialWindow(); + long onePeriodWindow = pacingRate / TIMER_FREQ_HZ; + long maxQuota; + if (appLimited) { + maxQuota = Math.max(initialWindow, onePeriodWindow); + } else { + maxQuota = Math.max(2 * onePeriodWindow, 4 * congestionController.maxDatagramSize()); + } + if (lastUpdate == null) { + quota = Math.max(initialWindow, maxQuota); + } else { + long nanosSinceUpdate = Deadline.between(lastUpdate, now).toNanos(); + if (nanosSinceUpdate >= TimeUnit.MICROSECONDS.toNanos(rttMicros)) { + // don't bother computing the increment, it might overflow and will be capped to maxQuota anyway + quota = maxQuota; + if (Log.quicCC()) { + Log.logQuic("pacer cwnd: %s, rtt %s us, duration %s ns, quota: %s".formatted( + cwnd, rttMicros, nanosSinceUpdate, quota)); + } + } else { + long quotaIncrement = pacingRate * nanosSinceUpdate / 1_000_000_000; + quota += quotaIncrement; + quota = Math.min(quota, maxQuota); + if (Log.quicCC()) { + Log.logQuic("pacer cwnd: %s, rtt %s us, duration %s ns, increment %s, quota %s".formatted( + cwnd, rttMicros, nanosSinceUpdate, quotaIncrement, quota)); + } + } + } + lastUpdate = now; + appLimited = false; + } + + /** + * {@return the deadline when quota will increase to two packets} + */ + public Deadline twoPacketDeadline() { + long datagramSize = congestionController.maxDatagramSize(); + long quotaNeeded = datagramSize * 2 - quota; + if (quotaNeeded <= 0) { + assert canSend(); + return lastUpdate; + } + // Window increases at a rate of rtt / cwnd / N + long rttMicros = rttEstimator.state().smoothedRttMicros(); + long cwnd = congestionController.congestionWindow(); + return lastUpdate.plus(rttMicros + * (congestionController.isSlowStart() ? 500 : 800) /* 1000/N */ + * quotaNeeded / cwnd, ChronoUnit.NANOS); + } + + /** + * called to indicate that a packet was sent + * + * @param packetBytes packet size in bytes + */ + public void packetSent(int packetBytes) { + quota -= packetBytes; + } +} diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicRenoCongestionController.java b/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicRenoCongestionController.java index fde253740d1..ff51aafc131 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicRenoCongestionController.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicRenoCongestionController.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, 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,9 +64,12 @@ class QuicRenoCongestionController implements QuicCongestionController { private Deadline congestionRecoveryStartTime; private long ssThresh = Long.MAX_VALUE; - public QuicRenoCongestionController(String dbgTag) { + private final QuicPacer pacer; + + public QuicRenoCongestionController(String dbgTag, QuicRttEstimator rttEstimator) { this.dbgTag = dbgTag; this.timeSource = TimeSource.source(); + this.pacer = new QuicPacer(rttEstimator, this); } private boolean inCongestionRecovery(Deadline sentTime) { @@ -83,7 +86,7 @@ class QuicRenoCongestionController implements QuicCongestionController { congestionWindow = Math.max(minimumWindow, ssThresh); maxBytesInFlight = 0; if (Log.quicCC()) { - Log.logQuic(dbgTag+ " Congestion: ssThresh: " + ssThresh + + Log.logQuic(dbgTag + " Congestion: ssThresh: " + ssThresh + ", in flight: " + bytesInFlight + ", cwnd:" + congestionWindow); } @@ -103,8 +106,10 @@ class QuicRenoCongestionController implements QuicCongestionController { if (bytesInFlight >= MAX_BYTES_IN_FLIGHT) { return false; } - var canSend = congestionWindow - bytesInFlight >= maxDatagramSize; - return canSend; + if (isCwndLimited() || isPacerLimited()) { + return false; + } + return true; } finally { lock.unlock(); } @@ -132,6 +137,7 @@ class QuicRenoCongestionController implements QuicCongestionController { if (bytesInFlight > maxBytesInFlight) { maxBytesInFlight = bytesInFlight; } + pacer.packetSent(packetBytes); } finally { lock.unlock(); } @@ -147,8 +153,8 @@ class QuicRenoCongestionController implements QuicCongestionController { // Here we limit cwnd growth based on the maximum bytes in flight // observed since the last congestion event if (inCongestionRecovery(sentTime)) { - if (Log.quicCC()) { - Log.logQuic(dbgTag+ " Acked, in recovery: bytes: " + packetBytes + + if (Log.quicCC() && Log.trace()) { + Log.logQuic(dbgTag + " Acked, in recovery: bytes: " + packetBytes + ", in flight: " + bytesInFlight); } return; @@ -165,9 +171,9 @@ class QuicRenoCongestionController implements QuicCongestionController { congestionWindow += Math.max((long) maxDatagramSize * packetBytes / congestionWindow, 1L); } } - if (Log.quicCC()) { + if (Log.quicCC() && Log.trace()) { if (isAppLimited) { - Log.logQuic(dbgTag+ " Acked, not blocked: bytes: " + packetBytes + + Log.logQuic(dbgTag + " Acked, not blocked: bytes: " + packetBytes + ", in flight: " + bytesInFlight); } else { Log.logQuic(dbgTag + " Acked, increased: bytes: " + packetBytes + @@ -194,7 +200,7 @@ class QuicRenoCongestionController implements QuicCongestionController { congestionWindow = minimumWindow; congestionRecoveryStartTime = null; if (Log.quicCC()) { - Log.logQuic(dbgTag+ " Persistent congestion: ssThresh: " + ssThresh + + Log.logQuic(dbgTag + " Persistent congestion: ssThresh: " + ssThresh + ", in flight: " + bytesInFlight + ", cwnd:" + congestionWindow); } @@ -217,4 +223,94 @@ class QuicRenoCongestionController implements QuicCongestionController { lock.unlock(); } } + + @Override + public long congestionWindow() { + lock.lock(); + try { + return congestionWindow; + } finally { + lock.unlock(); + } + } + + @Override + public long initialWindow() { + lock.lock(); + try { + return Math.max(14720, 2 * maxDatagramSize); + } finally { + lock.unlock(); + } + } + + @Override + public long maxDatagramSize() { + lock.lock(); + try { + return maxDatagramSize; + } finally { + lock.unlock(); + } + } + + @Override + public boolean isSlowStart() { + lock.lock(); + try { + return congestionWindow < ssThresh; + } finally { + lock.unlock(); + } + } + + @Override + public void updatePacer(Deadline now) { + lock.lock(); + try { + pacer.updateQuota(now); + } finally { + lock.unlock(); + } + } + + @Override + public boolean isPacerLimited() { + lock.lock(); + try { + return !pacer.canSend(); + } finally { + lock.unlock(); + } + } + + @Override + public boolean isCwndLimited() { + lock.lock(); + try { + return congestionWindow - bytesInFlight < maxDatagramSize; + } finally { + lock.unlock(); + } + } + + @Override + public Deadline pacerDeadline() { + lock.lock(); + try { + return pacer.twoPacketDeadline(); + } finally { + lock.unlock(); + } + } + + @Override + public void appLimited() { + lock.lock(); + try { + pacer.appLimited(); + } finally { + lock.unlock(); + } + } } diff --git a/test/jdk/java/net/httpclient/quic/PacerTest.java b/test/jdk/java/net/httpclient/quic/PacerTest.java new file mode 100644 index 00000000000..d54f4125a46 --- /dev/null +++ b/test/jdk/java/net/httpclient/quic/PacerTest.java @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2025, 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.internal.net.http.common.Deadline; +import jdk.internal.net.http.quic.QuicCongestionController; +import jdk.internal.net.http.quic.QuicPacer; +import jdk.internal.net.http.quic.QuicRttEstimator; +import jdk.internal.net.http.quic.packets.QuicPacket; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.time.temporal.ChronoUnit; +import java.util.Collection; +import java.util.List; +import java.util.stream.Stream; + +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +/* + * @test + * @run testng/othervm -Djdk.httpclient.quic.timerFrequency=1000 PacerTest + */ +public class PacerTest { + + private static class TestCongestionController implements QuicCongestionController { + private long cwnd; + private long iw; + private long maxDatagramSize; + private boolean isSlowStart; + + private TestCongestionController(long cwnd, long iw, long maxDatagramSize, boolean isSlowStart) { + this.cwnd = cwnd; + this.iw = iw; + this.maxDatagramSize = maxDatagramSize; + this.isSlowStart = isSlowStart; + } + + @Override + public boolean canSendPacket() { + throw new AssertionError("Should not come here"); + } + + @Override + public void updateMaxDatagramSize(int newSize) { + throw new AssertionError("Should not come here"); + } + + @Override + public void packetSent(int packetBytes) { + throw new AssertionError("Should not come here"); + } + + @Override + public void packetAcked(int packetBytes, Deadline sentTime) { + throw new AssertionError("Should not come here"); + } + + @Override + public void packetLost(Collection lostPackets, Deadline sentTime, boolean persistent) { + throw new AssertionError("Should not come here"); + } + + @Override + public void packetDiscarded(Collection discardedPackets) { + throw new AssertionError("Should not come here"); + } + + @Override + public long congestionWindow() { + return cwnd; + } + + @Override + public long initialWindow() { + return iw; + } + + @Override + public long maxDatagramSize() { + return maxDatagramSize; + } + + @Override + public boolean isSlowStart() { + return isSlowStart; + } + + @Override + public void updatePacer(Deadline now) { + throw new AssertionError("Should not come here"); + } + + @Override + public boolean isPacerLimited() { + throw new AssertionError("Should not come here"); + } + + @Override + public boolean isCwndLimited() { + throw new AssertionError("Should not come here"); + } + + @Override + public Deadline pacerDeadline() { + throw new AssertionError("Should not come here"); + } + + @Override + public void appLimited() { + throw new AssertionError("Should not come here"); + } + } + + public record TestCase(int maxDatagramSize, int packetsInIW, int packetsInCwnd, int millisInRtt, + int initialPermit, int periodicPermit, boolean slowStart) { + } + + @DataProvider + public Object[][] pacerFirstFlight() { + return List.of( + // Should permit initial window before blocking + new TestCase(1200, 10, 32, 16, 10, 4, true), + // Should permit 2*cwnd/rtt packets before blocking + new TestCase(1200, 10, 128, 16, 16, 16, true), + // Should permit 1.25*cwnd/rtt packets before blocking + new TestCase(1200, 10, 256, 16, 20, 20, false) + ).stream().map(Stream::of) + .map(Stream::toArray) + .toArray(Object[][]::new); + } + + @Test(dataProvider = "pacerFirstFlight") + public void testBasicPacing(TestCase test) { + int maxDatagramSize = test.maxDatagramSize; + int packetsInIW = test.packetsInIW; + int packetsInCwnd = test.packetsInCwnd; + int millisInRtt = test.millisInRtt; + int permit = test.initialPermit; + QuicCongestionController cc = new TestCongestionController(packetsInCwnd * maxDatagramSize, + maxDatagramSize * packetsInIW, maxDatagramSize, test.slowStart); + QuicRttEstimator rtt = new QuicRttEstimator(); + rtt.consumeRttSample(1000 * millisInRtt, 0, Deadline.MIN); + QuicPacer pacer = new QuicPacer(rtt, cc); + pacer.updateQuota(Deadline.MIN); + for (int i = 0; i < permit; i++) { + assertTrue(pacer.canSend(), "Pacer blocked after " + i + " packets"); + pacer.packetSent(maxDatagramSize); + } + assertFalse(pacer.canSend(), "Pacer didn't block"); + Deadline next = pacer.twoPacketDeadline(); + pacer.updateQuota(next); + for (int i = 0; i < 2; i++) { + assertTrue(pacer.canSend(), "Two packet deadline: pacer blocked after " + i + " packets"); + pacer.packetSent(maxDatagramSize); + } + assertFalse(pacer.canSend(), "Two packet deadline: pacer didn't block"); + next = next.plus(1, ChronoUnit.MILLIS); + pacer.updateQuota(next); + for (int i = 0; i < test.periodicPermit; i++) { + assertTrue(pacer.canSend(), "One millisecond: pacer blocked after " + i + " packets"); + pacer.packetSent(maxDatagramSize); + } + assertFalse(pacer.canSend(), "One millisecond: pacer didn't block"); + next = next.plus(3, ChronoUnit.MILLIS); + pacer.updateQuota(next); + // Quota capped at two millisecond equivalent + for (int i = 0; i < 2 * test.periodicPermit; i++) { + assertTrue(pacer.canSend(), "Three milliseconds: pacer blocked after " + i + " packets"); + pacer.packetSent(maxDatagramSize); + } + assertFalse(pacer.canSend(), "Three milliseconds: pacer didn't block"); + next = next.plus(3, ChronoUnit.MILLIS); + pacer.appLimited(); + pacer.updateQuota(next); + // App-limited: quota capped at initialPermit + for (int i = 0; i < test.initialPermit; i++) { + assertTrue(pacer.canSend(), "App limited: pacer blocked after " + i + " packets"); + pacer.packetSent(maxDatagramSize); + } + assertFalse(pacer.canSend(), "App limited: pacer didn't block"); + } + + @Test + public void testPacingShortRtt() { + int maxDatagramSize = 1200; + int packetsInIW = 10; + int packetsInCwnd = 32; + QuicCongestionController cc = new TestCongestionController(packetsInCwnd * maxDatagramSize, + maxDatagramSize * packetsInIW, maxDatagramSize, true); + QuicRttEstimator rtt = new QuicRttEstimator(); + rtt.consumeRttSample(1000, 0, Deadline.MIN); + QuicPacer pacer = new QuicPacer(rtt, cc); + pacer.updateQuota(Deadline.MIN); + for (int i = 0; i < 2 * packetsInCwnd; i++) { + assertTrue(pacer.canSend(), "Pacer blocked after " + i + " packets"); + pacer.packetSent(maxDatagramSize); + } + assertFalse(pacer.canSend(), "Pacer didn't block"); + // when RTT is short, permit cwnd on every update + Deadline next = pacer.twoPacketDeadline(); + pacer.updateQuota(next); + for (int i = 0; i < 2 * packetsInCwnd; i++) { + assertTrue(pacer.canSend(), "Two packet deadline: pacer blocked after " + i + " packets"); + pacer.packetSent(maxDatagramSize); + } + assertFalse(pacer.canSend(), "Two packet deadline: pacer didn't block"); + } + + @Test + public void testPacingSmallCwnd() { + int maxDatagramSize = 1200; + int packetsInIW = 10; + int packetsInCwnd = 2; + int millisInRtt = 16; + QuicCongestionController cc = new TestCongestionController(packetsInCwnd * maxDatagramSize, + maxDatagramSize * packetsInIW, maxDatagramSize, true); + QuicRttEstimator rtt = new QuicRttEstimator(); + rtt.consumeRttSample(1000 * millisInRtt, 0, Deadline.MIN); + QuicPacer pacer = new QuicPacer(rtt, cc); + // first quota update is capped to IW + pacer.updateQuota(Deadline.MIN); + // update quota again. This time it's capped to 4 packets + pacer.updateQuota(Deadline.MIN.plusNanos(1)); + for (int i = 0; i < 4; i++) { + assertTrue(pacer.canSend(), "Pacer blocked after " + i + " packets"); + pacer.packetSent(maxDatagramSize); + } + assertFalse(pacer.canSend(), "Pacer didn't block"); + Deadline next = pacer.twoPacketDeadline(); + pacer.updateQuota(next); + for (int i = 0; i < 2; i++) { + assertTrue(pacer.canSend(), "Two packet deadline: pacer blocked after " + i + " packets"); + pacer.packetSent(maxDatagramSize); + } + assertFalse(pacer.canSend(), "Two packet deadline: pacer didn't block"); + // pacing rate is 1 packet per 4 milliseconds + next = next.plus(4, ChronoUnit.MILLIS); + pacer.updateQuota(next); + assertTrue(pacer.canSend(), "Pacer blocked after 4 millis"); + pacer.packetSent(maxDatagramSize); + assertFalse(pacer.canSend(), "Pacer permitted 2 packets after 4 millis"); + + next = next.plus(2, ChronoUnit.MILLIS); + pacer.updateQuota(next); + assertFalse(pacer.canSend(), "Pacer permitted a packet after 2 millis"); + next = next.plus(2, ChronoUnit.MILLIS); + pacer.updateQuota(next); + assertTrue(pacer.canSend(), "Pacer blocked after 2x2 millis"); + pacer.packetSent(maxDatagramSize); + assertFalse(pacer.canSend(), "Pacer permitted 2 packets after 2x2 millis"); + } +} diff --git a/test/jdk/java/net/httpclient/quic/PacketSpaceManagerTest.java b/test/jdk/java/net/httpclient/quic/PacketSpaceManagerTest.java index 7cd2adfe7ab..40497ec13da 100644 --- a/test/jdk/java/net/httpclient/quic/PacketSpaceManagerTest.java +++ b/test/jdk/java/net/httpclient/quic/PacketSpaceManagerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2025, 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 @@ -45,10 +45,12 @@ import jdk.internal.net.http.common.Deadline; import jdk.internal.net.http.common.Logger; import jdk.internal.net.http.common.TestLoggerUtil; import jdk.internal.net.http.common.TimeLine; +import jdk.internal.net.http.quic.CodingContext; import jdk.internal.net.http.quic.PacketEmitter; import jdk.internal.net.http.quic.PacketSpaceManager; import jdk.internal.net.http.quic.PeerConnectionId; import jdk.internal.net.http.quic.QuicCongestionController; +import jdk.internal.net.http.quic.QuicConnectionId; import jdk.internal.net.http.quic.QuicRttEstimator; import jdk.internal.net.http.quic.QuicTimerQueue; import jdk.internal.net.http.quic.frames.AckFrame; @@ -61,8 +63,6 @@ import jdk.internal.net.http.quic.packets.InitialPacket; import jdk.internal.net.http.quic.packets.PacketSpace; import jdk.internal.net.http.quic.packets.QuicPacketDecoder; import jdk.internal.net.http.quic.packets.QuicPacketEncoder; -import jdk.internal.net.http.quic.CodingContext; -import jdk.internal.net.http.quic.QuicConnectionId; import jdk.internal.net.http.quic.packets.QuicPacket; import jdk.internal.net.http.quic.packets.QuicPacket.PacketNumberSpace; import jdk.internal.net.http.quic.packets.QuicPacket.PacketType; @@ -608,6 +608,38 @@ public class PacketSpaceManagerTest { public void packetLost(Collection lostPackets, Deadline sentTime, boolean persistent) { } @Override public void packetDiscarded(Collection discardedPackets) { } + @Override + public long congestionWindow() { + return Integer.MAX_VALUE; + } + @Override + public long initialWindow() { + return Integer.MAX_VALUE; + } + @Override + public long maxDatagramSize() { + return 1200; + } + @Override + public boolean isSlowStart() { + return false; + } + @Override + public void updatePacer(Deadline now) { } + @Override + public boolean isPacerLimited() { + return false; + } + @Override + public boolean isCwndLimited() { + return false; + } + @Override + public Deadline pacerDeadline() { + return Deadline.MIN; + } + @Override + public void appLimited() { } }; manager = new PacketSpaceManager(space, this, timeSource, rttEstimator, congestionController, new DummyQuicTLSEngine(), From e5a272a59058e36136acd6aef635f87136fbb027 Mon Sep 17 00:00:00 2001 From: Vicente Romero Date: Wed, 12 Nov 2025 14:30:08 +0000 Subject: [PATCH 013/418] 8369517: Compilation mismatch for equivalent lambda and method reference Reviewed-by: mcimadamore --- .../com/sun/tools/javac/code/Source.java | 1 + .../com/sun/tools/javac/comp/Attr.java | 7 ++- .../ResultTypeNotBeingCapturedTest.java | 49 +++++++++++++++++++ .../ResultTypeNotBeingCapturedTest.out | 3 ++ 4 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 test/langtools/tools/javac/lambda/methodReference/ResultTypeNotBeingCapturedTest.java create mode 100644 test/langtools/tools/javac/lambda/methodReference/ResultTypeNotBeingCapturedTest.out diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Source.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Source.java index 063d37be4e2..d0cb45ebc09 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Source.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Source.java @@ -277,6 +277,7 @@ public enum Source { JAVA_BASE_TRANSITIVE(JDK25, Fragments.FeatureJavaBaseTransitive, DiagKind.PLURAL), PRIVATE_MEMBERS_IN_PERMITS_CLAUSE(JDK19), ERASE_POLY_SIG_RETURN_TYPE(JDK24), + CAPTURE_MREF_RETURN_TYPE(JDK26), ; enum DiagKind { diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java index 1465ea652b1..4a8a8d4a324 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java @@ -124,6 +124,7 @@ public class Attr extends JCTree.Visitor { final ArgumentAttr argumentAttr; final MatchBindingsComputer matchBindingsComputer; final AttrRecover attrRecover; + final boolean captureMRefReturnType; public static Attr instance(Context context) { Attr instance = context.get(attrKey); @@ -175,6 +176,7 @@ public class Attr extends JCTree.Visitor { Feature.UNCONDITIONAL_PATTERN_IN_INSTANCEOF.allowedInSource(source); sourceName = source.name; useBeforeDeclarationWarning = options.isSet("useBeforeDeclarationWarning"); + captureMRefReturnType = Source.Feature.ERASE_POLY_SIG_RETURN_TYPE.allowedInSource(source); statInfo = new ResultInfo(KindSelector.NIL, Type.noType); varAssignmentInfo = new ResultInfo(KindSelector.ASG, Type.noType); @@ -3832,9 +3834,10 @@ public class Attr extends JCTree.Visitor { } if (!returnType.hasTag(VOID) && !resType.hasTag(VOID)) { + Type capturedResType = captureMRefReturnType ? types.capture(resType) : resType; if (resType.isErroneous() || - new FunctionalReturnContext(checkContext).compatible(resType, returnType, - checkContext.checkWarner(tree, resType, returnType))) { + new FunctionalReturnContext(checkContext).compatible(capturedResType, returnType, + checkContext.checkWarner(tree, capturedResType, returnType))) { incompatibleReturnType = null; } } diff --git a/test/langtools/tools/javac/lambda/methodReference/ResultTypeNotBeingCapturedTest.java b/test/langtools/tools/javac/lambda/methodReference/ResultTypeNotBeingCapturedTest.java new file mode 100644 index 00000000000..a87dd8b302d --- /dev/null +++ b/test/langtools/tools/javac/lambda/methodReference/ResultTypeNotBeingCapturedTest.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8369517 + * @summary Compilation mismatch for equivalent lambda and method reference + * @compile/fail/ref=ResultTypeNotBeingCapturedTest.out -XDrawDiagnostics ResultTypeNotBeingCapturedTest.java + */ + +import java.util.function.Supplier; + +class ResultTypeNotBeingCapturedTest { + interface X { + X self(); + } + + static X makeX() {return null;} + + static X create(Supplier supplier) {return null;} + + static X> methodRef() { + return create(ResultTypeNotBeingCapturedTest::makeX).self(); + } + + static X> lambda() { + return create(() -> makeX()).self(); + } +} diff --git a/test/langtools/tools/javac/lambda/methodReference/ResultTypeNotBeingCapturedTest.out b/test/langtools/tools/javac/lambda/methodReference/ResultTypeNotBeingCapturedTest.out new file mode 100644 index 00000000000..bcd4dbad07b --- /dev/null +++ b/test/langtools/tools/javac/lambda/methodReference/ResultTypeNotBeingCapturedTest.out @@ -0,0 +1,3 @@ +ResultTypeNotBeingCapturedTest.java:43:66: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: ResultTypeNotBeingCapturedTest.X>, ResultTypeNotBeingCapturedTest.X>) +ResultTypeNotBeingCapturedTest.java:47:42: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: ResultTypeNotBeingCapturedTest.X>, ResultTypeNotBeingCapturedTest.X>) +2 errors From 4042e821c6f582bf31201acb9f2d98d940383f1c Mon Sep 17 00:00:00 2001 From: Daniel Gredler Date: Wed, 12 Nov 2025 14:42:30 +0000 Subject: [PATCH 014/418] 8371066: Remove unused class TextSourceLabel and associated class hierarchy Reviewed-by: prr, psadhukhan --- .../share/classes/java/awt/font/TextLine.java | 2 +- .../share/classes/sun/font/Decoration.java | 3 +- .../classes/sun/font/ExtendedTextLabel.java | 146 ------------- .../sun/font/ExtendedTextSourceLabel.java | 193 +++++++++++++++--- .../share/classes/sun/font/TextLabel.java | 124 ----------- .../classes/sun/font/TextLabelFactory.java | 40 +--- .../classes/sun/font/TextSourceLabel.java | 173 ---------------- 7 files changed, 169 insertions(+), 512 deletions(-) delete mode 100644 src/java.desktop/share/classes/sun/font/ExtendedTextLabel.java delete mode 100644 src/java.desktop/share/classes/sun/font/TextLabel.java delete mode 100644 src/java.desktop/share/classes/sun/font/TextSourceLabel.java diff --git a/src/java.desktop/share/classes/java/awt/font/TextLine.java b/src/java.desktop/share/classes/java/awt/font/TextLine.java index 3464c1626c6..6db1bc72360 100644 --- a/src/java.desktop/share/classes/java/awt/font/TextLine.java +++ b/src/java.desktop/share/classes/java/awt/font/TextLine.java @@ -1020,7 +1020,7 @@ final class TextLine { } TextLineComponent nextComponent = - factory.createExtended(font, cm, decorator, startPos, startPos + lmCount); + factory.createTextLabel(font, cm, decorator, startPos, startPos + lmCount); ++numComponents; if (numComponents > components.length) { diff --git a/src/java.desktop/share/classes/sun/font/Decoration.java b/src/java.desktop/share/classes/sun/font/Decoration.java index 26f5295d8d3..f131914134f 100644 --- a/src/java.desktop/share/classes/sun/font/Decoration.java +++ b/src/java.desktop/share/classes/sun/font/Decoration.java @@ -54,7 +54,8 @@ import static sun.font.EAttribute.*; /** * This class handles underlining, strikethrough, and foreground and * background styles on text. Clients simply acquire instances - * of this class and hand them off to ExtendedTextLabels or GraphicComponents. + * of this class and hand them off to ExtendedTextSourceLabels or + * GraphicComponents. */ public class Decoration { diff --git a/src/java.desktop/share/classes/sun/font/ExtendedTextLabel.java b/src/java.desktop/share/classes/sun/font/ExtendedTextLabel.java deleted file mode 100644 index 2962d8ffc37..00000000000 --- a/src/java.desktop/share/classes/sun/font/ExtendedTextLabel.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (c) 1998, 2003, 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. 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. - */ -/* - * - * (C) Copyright IBM Corp. 1998-2003- All Rights Reserved. - */ - -package sun.font; - -import java.awt.Font; - -import java.awt.font.GlyphJustificationInfo; -import java.awt.font.LineMetrics; - -import java.awt.geom.Point2D; -import java.awt.geom.Rectangle2D; - -/** - * An extension of TextLabel that maintains information - * about characters. - */ - -public abstract class ExtendedTextLabel extends TextLabel - implements TextLineComponent{ - /** - * Return the number of characters represented by this label. - */ - public abstract int getNumCharacters(); - - /** - * Return the line metrics for all text in this label. - */ - public abstract CoreMetrics getCoreMetrics(); - - /** - * Return the x location of the character at the given logical index. - */ - public abstract float getCharX(int logicalIndex); - - /** - * Return the y location of the character at the given logical index. - */ - public abstract float getCharY(int logicalIndex); - - /** - * Return the advance of the character at the given logical index. - */ - public abstract float getCharAdvance(int logicalIndex); - - /** - * Return the visual bounds of the character at the given logical index. - * This bounds encloses all the pixels of the character when the label is rendered - * at x, y. - */ - public abstract Rectangle2D getCharVisualBounds(int logicalIndex, float x, float y); - - /** - * Return the visual index of the character at the given logical index. - */ - public abstract int logicalToVisual(int logicalIndex); - - /** - * Return the logical index of the character at the given visual index. - */ - public abstract int visualToLogical(int visualIndex); - - /** - * Return the logical index of the character, starting with the character at - * logicalStart, whose accumulated advance exceeds width. If the advances of - * all characters do not exceed width, return getNumCharacters. If width is - * less than zero, return logicalStart - 1. - */ - public abstract int getLineBreakIndex(int logicalStart, float width); - - /** - * Return the accumulated advances of all characters between logicalStart and - * logicalLimit. - */ - public abstract float getAdvanceBetween(int logicalStart, int logicalLimit); - - /** - * Return whether a caret can exist on the leading edge of the - * character at offset. If the character is part of a ligature - * (for example) a caret may not be appropriate at offset. - */ - public abstract boolean caretAtOffsetIsValid(int offset); - - /** - * A convenience overload of getCharVisualBounds that defaults the label origin - * to 0, 0. - */ - public Rectangle2D getCharVisualBounds(int logicalIndex) { - return getCharVisualBounds(logicalIndex, 0, 0); - } - - public abstract TextLineComponent getSubset(int start, int limit, int dir); - - /** - * Return the number of justification records this uses. - */ - public abstract int getNumJustificationInfos(); - - /** - * Return GlyphJustificationInfo objects for the characters between - * charStart and charLimit, starting at offset infoStart. Infos - * will be in visual order. All positions between infoStart and - * getNumJustificationInfos will be set. If a position corresponds - * to a character outside the provided range, it is set to null. - */ - public abstract void getJustificationInfos(GlyphJustificationInfo[] infos, int infoStart, int charStart, int charLimit); - - /** - * Apply deltas to the data in this component, starting at offset - * deltaStart, and return the new component. There are two floats - * for each justification info, for a total of 2 * getNumJustificationInfos. - * The first delta is the left adjustment, the second is the right - * adjustment. - *

- * If flags[0] is true on entry, rejustification is allowed. If - * the new component requires rejustification (ligatures were - * formed or split), flags[0] will be set on exit. - */ - public abstract TextLineComponent applyJustificationDeltas(float[] deltas, int deltaStart, boolean[] flags); -} diff --git a/src/java.desktop/share/classes/sun/font/ExtendedTextSourceLabel.java b/src/java.desktop/share/classes/sun/font/ExtendedTextSourceLabel.java index 784035f4fe2..85c631d6e3e 100644 --- a/src/java.desktop/share/classes/sun/font/ExtendedTextSourceLabel.java +++ b/src/java.desktop/share/classes/sun/font/ExtendedTextSourceLabel.java @@ -46,32 +46,35 @@ import java.awt.geom.Rectangle2D; import java.util.Map; -/** - * Default implementation of ExtendedTextLabel. - */ - // {jbr} I made this class package-private to keep the // Decoration.Label API package-private. -/* public */ -class ExtendedTextSourceLabel extends ExtendedTextLabel implements Decoration.Label { +/** + * A label. + * Visual bounds is a rect that encompasses the entire rendered area. + * Logical bounds is a rect that defines how to position this next + * to other objects. + * Align bounds is a rect that defines how to align this to margins. + * it generally allows some overhang that logical bounds would prevent. + */ +class ExtendedTextSourceLabel implements TextLineComponent, Decoration.Label { - TextSource source; - private Decoration decorator; + private final TextSource source; + private final Decoration decorator; // caches private Font font; private AffineTransform baseTX; private CoreMetrics cm; - Rectangle2D lb; - Rectangle2D ab; - Rectangle2D vb; - Rectangle2D ib; - StandardGlyphVector gv; - float[] charinfo; + private Rectangle2D lb; + private Rectangle2D ab; + private Rectangle2D vb; + private Rectangle2D ib; + private StandardGlyphVector gv; + private float[] charinfo; - float advTracking; + private float advTracking; /** * Create from a TextSource. @@ -116,13 +119,11 @@ class ExtendedTextSourceLabel extends ExtendedTextLabel implements Decoration.La advTracking = font.getSize() * AttributeValues.getTracking(atts); } - - // TextLabel API - - public Rectangle2D getLogicalBounds() { - return getLogicalBounds(0, 0); - } - + /** + * Return a rectangle that corresponds to the logical bounds of the text + * when this label is rendered at x, y. + * This rectangle is used when positioning text next to other text. + */ public Rectangle2D getLogicalBounds(float x, float y) { if (lb == null) { lb = createLogicalBounds(); @@ -133,13 +134,16 @@ class ExtendedTextSourceLabel extends ExtendedTextLabel implements Decoration.La (float)lb.getHeight()); } - public float getAdvance() { - if (lb == null) { - lb = createLogicalBounds(); - } - return (float)lb.getWidth(); + public float getAdvance() { + if (lb == null) { + lb = createLogicalBounds(); } + return (float)lb.getWidth(); + } + /** + * Return a rectangle that surrounds the text outline when this label is rendered at x, y. + */ public Rectangle2D getVisualBounds(float x, float y) { if (vb == null) { vb = decorator.getVisualBounds(this); @@ -150,6 +154,12 @@ class ExtendedTextSourceLabel extends ExtendedTextLabel implements Decoration.La (float)vb.getHeight()); } + /** + * Return a rectangle that corresponds to the alignment bounds of the text + * when this label is rendered at x, y. This rectangle is used when positioning text next + * to a margin. It differs from the logical bounds in that it does not include leading or + * trailing whitespace. + */ public Rectangle2D getAlignBounds(float x, float y) { if (ab == null) { ab = createAlignBounds(); @@ -161,6 +171,10 @@ class ExtendedTextSourceLabel extends ExtendedTextLabel implements Decoration.La } + /** + * Return a rectangle that corresponds to the logical bounds of the text, adjusted + * to angle the leading and trailing edges by the italic angle. + */ public Rectangle2D getItalicBounds(float x, float y) { if (ib == null) { ib = createItalicBounds(); @@ -189,6 +203,9 @@ class ExtendedTextSourceLabel extends ExtendedTextLabel implements Decoration.La return getGV().getOutline(x, y); } + /** + * Return an outline of the characters in the label when rendered at x, y. + */ public Shape getOutline(float x, float y) { return decorator.getOutline(this, x, y); } @@ -197,10 +214,63 @@ class ExtendedTextSourceLabel extends ExtendedTextLabel implements Decoration.La g.drawGlyphVector(getGV(), x, y); } + /** + * Render the label at x, y in the graphics. + */ public void draw(Graphics2D g, float x, float y) { decorator.drawTextAndDecorations(this, g, x, y); } + /** + * A convenience method that returns the visual bounds when rendered at 0, 0. + */ + public Rectangle2D getVisualBounds() { + return getVisualBounds(0f, 0f); + } + + /** + * A convenience method that returns the logical bounds when rendered at 0, 0. + */ + public Rectangle2D getLogicalBounds() { + return getLogicalBounds(0f, 0f); + } + + /** + * A convenience method that returns the align bounds when rendered at 0, 0. + */ + public Rectangle2D getAlignBounds() { + return getAlignBounds(0f, 0f); + } + + /** + * A convenience method that returns the italic bounds when rendered at 0, 0. + */ + public Rectangle2D getItalicBounds() { + return getItalicBounds(0f, 0f); + } + + /** + * A convenience method that returns the outline when rendered at 0, 0. + */ + public Shape getOutline() { + return getOutline(0f, 0f); + } + + /** + * A convenience method that renders the label at 0, 0. + */ + public void draw(Graphics2D g) { + draw(g, 0f, 0f); + } + + /** + * A convenience overload of getCharVisualBounds that defaults the label origin + * to 0, 0. + */ + public Rectangle2D getCharVisualBounds(int logicalIndex) { + return getCharVisualBounds(logicalIndex, 0, 0); + } + /** * The logical bounds extends from the origin of the glyphvector to the * position at which a following glyphvector's origin should be placed. @@ -336,8 +406,6 @@ class ExtendedTextSourceLabel extends ExtendedTextLabel implements Decoration.La return gv; } - // ExtendedTextLabel API - private static final int posx = 0, posy = 1, advx = 2, @@ -348,14 +416,23 @@ class ExtendedTextSourceLabel extends ExtendedTextLabel implements Decoration.La vish = 7; private static final int numvals = 8; + /** + * Return the number of characters represented by this label. + */ public int getNumCharacters() { return source.getLength(); } + /** + * Return the line metrics for all text in this label. + */ public CoreMetrics getCoreMetrics() { return cm; } + /** + * Return the x location of the character at the given logical index. + */ public float getCharX(int index) { validate(index); float[] charinfo = getCharinfo(); @@ -367,6 +444,9 @@ class ExtendedTextSourceLabel extends ExtendedTextLabel implements Decoration.La } } + /** + * Return the y location of the character at the given logical index. + */ public float getCharY(int index) { validate(index); float[] charinfo = getCharinfo(); @@ -378,6 +458,9 @@ class ExtendedTextSourceLabel extends ExtendedTextLabel implements Decoration.La } } + /** + * Return the advance of the character at the given logical index. + */ public float getCharAdvance(int index) { validate(index); float[] charinfo = getCharinfo(); @@ -403,6 +486,11 @@ class ExtendedTextSourceLabel extends ExtendedTextLabel implements Decoration.La charinfo[index + vish]); } + /** + * Return the visual bounds of the character at the given logical index. + * This bounds encloses all the pixels of the character when the label is rendered + * at x, y. + */ public Rectangle2D getCharVisualBounds(int index, float x, float y) { Rectangle2D bounds = decorator.getCharVisualBounds(this, index); @@ -470,16 +558,28 @@ class ExtendedTextSourceLabel extends ExtendedTextLabel implements Decoration.La } */ + /** + * Return the visual index of the character at the given logical index. + */ public int logicalToVisual(int logicalIndex) { validate(logicalIndex); return l2v(logicalIndex); } + /** + * Return the logical index of the character at the given visual index. + */ public int visualToLogical(int visualIndex) { validate(visualIndex); return v2l(visualIndex); } + /** + * Return the logical index of the character, starting with the character at + * logicalStart, whose accumulated advance exceeds width. If the advances of + * all characters do not exceed width, return getNumCharacters. If width is + * less than zero, return logicalStart - 1. + */ public int getLineBreakIndex(int start, float width) { final float epsilon = 0.005f; @@ -504,6 +604,10 @@ class ExtendedTextSourceLabel extends ExtendedTextLabel implements Decoration.La return start; } + /** + * Return the accumulated advances of all characters between logicalStart and + * logicalLimit. + */ public float getAdvanceBetween(int start, int limit) { float a = 0f; @@ -523,6 +627,11 @@ class ExtendedTextSourceLabel extends ExtendedTextLabel implements Decoration.La return a; } + /** + * Return whether a caret can exist on the leading edge of the + * character at offset. If the character is part of a ligature + * (for example) a caret may not be appropriate at offset. + */ public boolean caretAtOffsetIsValid(int offset) { // REMIND: improve this implementation @@ -912,15 +1021,20 @@ class ExtendedTextSourceLabel extends ExtendedTextLabel implements Decoration.La return sb.toString(); } - //public static ExtendedTextLabel create(TextSource source) { - // return new ExtendedTextSourceLabel(source); - //} - + /** + * Return the number of justification records this uses. + */ public int getNumJustificationInfos() { return getGV().getNumGlyphs(); } - + /** + * Return GlyphJustificationInfo objects for the characters between + * charStart and charLimit, starting at offset infoStart. Infos + * will be in visual order. All positions between infoStart and + * getNumJustificationInfos will be set. If a position corresponds + * to a character outside the provided range, it is set to null. + */ public void getJustificationInfos(GlyphJustificationInfo[] infos, int infoStart, int charStart, int charLimit) { // This simple implementation only uses spaces for justification. // Since regular characters aren't justified, we don't need to deal with @@ -992,6 +1106,17 @@ class ExtendedTextSourceLabel extends ExtendedTextLabel implements Decoration.La } } + /** + * Apply deltas to the data in this component, starting at offset + * deltaStart, and return the new component. There are two floats + * for each justification info, for a total of 2 * getNumJustificationInfos. + * The first delta is the left adjustment, the second is the right + * adjustment. + *

+ * If flags[0] is true on entry, rejustification is allowed. If + * the new component requires rejustification (ligatures were + * formed or split), flags[0] will be set on exit. + */ public TextLineComponent applyJustificationDeltas(float[] deltas, int deltaStart, boolean[] flags) { // when we justify, we need to adjust the charinfo since spaces diff --git a/src/java.desktop/share/classes/sun/font/TextLabel.java b/src/java.desktop/share/classes/sun/font/TextLabel.java deleted file mode 100644 index 738ff0c294d..00000000000 --- a/src/java.desktop/share/classes/sun/font/TextLabel.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (c) 1998, 2003, 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. 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. - */ -/* - * - * (C) Copyright IBM Corp. 1998-2003 All Rights Reserved - */ - -package sun.font; - -import java.awt.Graphics2D; -import java.awt.Shape; - -import java.awt.geom.Rectangle2D; - -/** - * A label. - * Visual bounds is a rect that encompasses the entire rendered area. - * Logical bounds is a rect that defines how to position this next - * to other objects. - * Align bounds is a rect that defines how to align this to margins. - * it generally allows some overhang that logical bounds would prevent. - */ -public abstract class TextLabel { - - /** - * Return a rectangle that surrounds the text outline when this label is rendered at x, y. - */ - public abstract Rectangle2D getVisualBounds(float x, float y); - - /** - * Return a rectangle that corresponds to the logical bounds of the text - * when this label is rendered at x, y. - * This rectangle is used when positioning text next to other text. - */ - public abstract Rectangle2D getLogicalBounds(float x, float y); - - /** - * Return a rectangle that corresponds to the alignment bounds of the text - * when this label is rendered at x, y. This rectangle is used when positioning text next - * to a margin. It differs from the logical bounds in that it does not include leading or - * trailing whitespace. - */ - public abstract Rectangle2D getAlignBounds(float x, float y); - - /** - * Return a rectangle that corresponds to the logical bounds of the text, adjusted - * to angle the leading and trailing edges by the italic angle. - */ - public abstract Rectangle2D getItalicBounds(float x, float y); - - /** - * Return an outline of the characters in the label when rendered at x, y. - */ - public abstract Shape getOutline(float x, float y); - - /** - * Render the label at x, y in the graphics. - */ - public abstract void draw(Graphics2D g, float x, float y); - - /** - * A convenience method that returns the visual bounds when rendered at 0, 0. - */ - public Rectangle2D getVisualBounds() { - return getVisualBounds(0f, 0f); - } - - /** - * A convenience method that returns the logical bounds when rendered at 0, 0. - */ - public Rectangle2D getLogicalBounds() { - return getLogicalBounds(0f, 0f); - } - - /** - * A convenience method that returns the align bounds when rendered at 0, 0. - */ - public Rectangle2D getAlignBounds() { - return getAlignBounds(0f, 0f); - } - - /** - * A convenience method that returns the italic bounds when rendered at 0, 0. - */ - public Rectangle2D getItalicBounds() { - return getItalicBounds(0f, 0f); - } - - /** - * A convenience method that returns the outline when rendered at 0, 0. - */ - public Shape getOutline() { - return getOutline(0f, 0f); - } - - /** - * A convenience method that renders the label at 0, 0. - */ - public void draw(Graphics2D g) { - draw(g, 0f, 0f); - } -} diff --git a/src/java.desktop/share/classes/sun/font/TextLabelFactory.java b/src/java.desktop/share/classes/sun/font/TextLabelFactory.java index 7a41bd2e7f5..996986617de 100644 --- a/src/java.desktop/share/classes/sun/font/TextLabelFactory.java +++ b/src/java.desktop/share/classes/sun/font/TextLabelFactory.java @@ -42,8 +42,7 @@ import java.text.Bidi; * @see Font * @see FontRenderContext * @see java.awt.font.GlyphVector - * @see TextLabel - * @see ExtendedTextLabel + * @see ExtendedTextSourceLabel * @see Bidi * @see java.awt.font.TextLayout */ @@ -100,7 +99,7 @@ public final class TextLabelFactory { } /** - * Create an extended glyph array for the text between start and limit. + * Create a glyph array for the text between start and limit. * * @param font the font to use to generate glyphs and character positions. * @param start the start of the subrange for which to create the glyph array @@ -113,11 +112,11 @@ public final class TextLabelFactory { * at start. Clients should ensure that all text between start and limit * has the same bidi level for the current line. */ - public ExtendedTextLabel createExtended(Font font, - CoreMetrics lm, - Decoration decorator, - int start, - int limit) { + public ExtendedTextSourceLabel createTextLabel(Font font, + CoreMetrics lm, + Decoration decorator, + int start, + int limit) { if (start > limit || start < lineStart || limit > lineLimit) { throw new IllegalArgumentException("bad start: " + start + " or limit: " + limit); @@ -132,29 +131,4 @@ public final class TextLabelFactory { TextSource source = new StandardTextSource(text, start, limit - start, lineStart, lineLimit - lineStart, level, layoutFlags, font, frc, lm); return new ExtendedTextSourceLabel(source, decorator); } - - /** - * Create a simple glyph array for the text between start and limit. - * - * @param font the font to use to generate glyphs and character positions. - * @param start the start of the subrange for which to create the glyph array - * @param limit the limit of the subrange for which to create glyph array - */ - public TextLabel createSimple(Font font, - CoreMetrics lm, - int start, - int limit) { - - if (start > limit || start < lineStart || limit > lineLimit) { - throw new IllegalArgumentException("bad start: " + start + " or limit: " + limit); - } - - int level = lineBidi == null ? 0 : lineBidi.getLevelAt(start - lineStart); - int linedir = (lineBidi == null || lineBidi.baseIsLeftToRight()) ? 0 : 1; - int layoutFlags = flags & ~0x9; // remove bidi, line direction flags - if ((level & 0x1) != 0) layoutFlags |= 1; // rtl - if ((linedir & 0x1) != 0) layoutFlags |= 8; // line rtl - TextSource source = new StandardTextSource(text, start, limit - start, lineStart, lineLimit - lineStart, level, layoutFlags, font, frc, lm); - return new TextSourceLabel(source); - } } diff --git a/src/java.desktop/share/classes/sun/font/TextSourceLabel.java b/src/java.desktop/share/classes/sun/font/TextSourceLabel.java deleted file mode 100644 index 8514be6ac7a..00000000000 --- a/src/java.desktop/share/classes/sun/font/TextSourceLabel.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright (c) 1998, 2017, 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. 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. - */ -/* - * - * (C) Copyright IBM Corp. 1998, 1999 - All Rights Reserved - */ - -package sun.font; - -import java.awt.Font; -import java.awt.Graphics2D; -import java.awt.Rectangle; -import java.awt.Shape; -import java.awt.font.FontRenderContext; -import java.awt.font.GlyphVector; -import java.awt.geom.AffineTransform; -import java.awt.geom.Rectangle2D; - -/** - * Implementation of TextLabel based on String. - */ - -public class TextSourceLabel extends TextLabel { - TextSource source; - - // caches - Rectangle2D lb; - Rectangle2D ab; - Rectangle2D vb; - Rectangle2D ib; - GlyphVector gv; - - public TextSourceLabel(TextSource source) { - this(source, null, null, null); - } - - public TextSourceLabel(TextSource source, Rectangle2D lb, Rectangle2D ab, GlyphVector gv) { - this.source = source; - - this.lb = lb; - this.ab = ab; - this.gv = gv; - } - - public TextSource getSource() { - return source; - } - - public final Rectangle2D getLogicalBounds(float x, float y) { - if (lb == null) { - lb = createLogicalBounds(); - } - return new Rectangle2D.Float((float)(lb.getX() + x), - (float)(lb.getY() + y), - (float)lb.getWidth(), - (float)lb.getHeight()); - } - - public final Rectangle2D getVisualBounds(float x, float y) { - if (vb == null) { - vb = createVisualBounds(); - - } - return new Rectangle2D.Float((float)(vb.getX() + x), - (float)(vb.getY() + y), - (float)vb.getWidth(), - (float)vb.getHeight()); - } - - public final Rectangle2D getAlignBounds(float x, float y) { - if (ab == null) { - ab = createAlignBounds(); - } - return new Rectangle2D.Float((float)(ab.getX() + x), - (float)(ab.getY() + y), - (float)ab.getWidth(), - (float)ab.getHeight()); - } - - public Rectangle2D getItalicBounds(float x, float y) { - if (ib == null) { - ib = createItalicBounds(); - } - return new Rectangle2D.Float((float)(ib.getX() + x), - (float)(ib.getY() + y), - (float)ib.getWidth(), - (float)ib.getHeight()); - - } - - public Rectangle getPixelBounds(FontRenderContext frc, float x, float y) { - return getGV().getPixelBounds(frc, x, y); // no cache - } - - public AffineTransform getBaselineTransform() { - Font font = source.getFont(); - if (font.hasLayoutAttributes()) { - return AttributeValues.getBaselineTransform(font.getAttributes()); - } - return null; - } - - public Shape getOutline(float x, float y) { - return getGV().getOutline(x, y); - } - - public void draw(Graphics2D g, float x, float y) { - g.drawGlyphVector(getGV(), x, y); - } - - protected Rectangle2D createLogicalBounds() { - return getGV().getLogicalBounds(); - } - - protected Rectangle2D createVisualBounds() { - return getGV().getVisualBounds(); - } - - protected Rectangle2D createItalicBounds() { - // !!! fix - return getGV().getLogicalBounds(); - } - - protected Rectangle2D createAlignBounds() { - return createLogicalBounds(); - } - - private GlyphVector getGV() { - if (gv == null) { - gv = createGV(); - } - - return gv; - } - - protected GlyphVector createGV() { - Font font = source.getFont(); - FontRenderContext frc = source.getFRC(); - int flags = source.getLayoutFlags(); - char[] context = source.getChars(); - int start = source.getStart(); - int length = source.getLength(); - - GlyphLayout gl = GlyphLayout.get(null); // !!! no custom layout engines - StandardGlyphVector gv = gl.layout(font, frc, context, start, length, - flags, null); // ??? use textsource - GlyphLayout.done(gl); - - return gv; - } -} From 56a27d11971d935e8b28ac9d701cf9890014a949 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Lund=C3=A9n?= Date: Wed, 12 Nov 2025 14:45:22 +0000 Subject: [PATCH 015/418] 8341039: compiler/cha/TypeProfileFinalMethod.java fails with assertEquals expected: 0 but was: 2 Reviewed-by: rcastanedalo, dfenacci --- test/hotspot/jtreg/ProblemList-Xcomp.txt | 2 -- .../compiler/cha/TypeProfileFinalMethod.java | 33 ++++++++++--------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/test/hotspot/jtreg/ProblemList-Xcomp.txt b/test/hotspot/jtreg/ProblemList-Xcomp.txt index a564124b6e4..758bb32e968 100644 --- a/test/hotspot/jtreg/ProblemList-Xcomp.txt +++ b/test/hotspot/jtreg/ProblemList-Xcomp.txt @@ -45,6 +45,4 @@ vmTestbase/nsk/jvmti/scenarios/capability/CM03/cm03t001/TestDescription.java 829 vmTestbase/nsk/stress/thread/thread006.java 8321476 linux-all -compiler/cha/TypeProfileFinalMethod.java 8341039 generic-all - gc/arguments/TestNewSizeFlags.java 8299116 macosx-aarch64 diff --git a/test/hotspot/jtreg/compiler/cha/TypeProfileFinalMethod.java b/test/hotspot/jtreg/compiler/cha/TypeProfileFinalMethod.java index a81ba53af52..8958a195653 100644 --- a/test/hotspot/jtreg/compiler/cha/TypeProfileFinalMethod.java +++ b/test/hotspot/jtreg/compiler/cha/TypeProfileFinalMethod.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2024, Alibaba Group Holding Limited. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -25,7 +25,7 @@ /* * @test * @summary test c1 to record type profile with CHA optimization - * @requires vm.flavor == "server" & (vm.opt.TieredStopAtLevel == null | vm.opt.TieredStopAtLevel == 4) + * @requires vm.flavor == "server" & vm.flagless * @library /test/lib * @build jdk.test.whitebox.WhiteBox * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox @@ -48,6 +48,7 @@ public class TypeProfileFinalMethod { "-Xbatch", "-XX:-UseOnStackReplacement", "-XX:+UnlockDiagnosticVMOptions", "-XX:+WhiteBoxAPI", "-XX:Tier3InvocationThreshold=200", "-XX:Tier4InvocationThreshold=5000", + "-XX:CompileCommand=CompileOnly," + Launcher.class.getName() + "::test*", Launcher.class.getName()); OutputAnalyzer output = ProcessTools.executeProcess(pb); System.out.println("debug output"); @@ -61,7 +62,7 @@ public class TypeProfileFinalMethod { while (matcher.find()) { matchCnt++; } - Asserts.assertEquals(matchCnt, 2); // inline Child1::m() twice + Asserts.assertEquals(2, matchCnt); // inline Child1::m() twice } static class Launcher { @@ -86,23 +87,23 @@ public class TypeProfileFinalMethod { static void addCompilerDirectives() { WhiteBox WB = WhiteBox.getWhiteBox(); - // do not inline getInstance() for test1() and test2() + // Directive for test1 String directive = "[{ match: [\"" + Launcher.class.getName() + "::test1\"]," + - "inline:[\"-" + Launcher.class.getName()+"::getInstance()\"] }]"; + // Do not inline getInstance + "inline:[\"-" + Launcher.class.getName()+"::getInstance\"] }]"; WB.addCompilerDirective(directive); + // Directive for test2 directive = "[{ match: [\"" + Launcher.class.getName() + "::test2\"]," + - "inline:[\"-" + Launcher.class.getName()+"::getInstance()\"] }]"; - WB.addCompilerDirective(directive); - - // do not inline test1() for test2() in c1 compilation - directive = "[{ match: [\"" + Launcher.class.getName() + "::test2\"]," + - "c1: { inline:[\"-" + Launcher.class.getName()+"::test1()\"] } }]"; - WB.addCompilerDirective(directive); - - // print inline tree for checking - directive = "[{ match: [\"" + Launcher.class.getName() + "::test2\"]," + - "c2: { PrintInlining: true } }]"; + // Do not inline getInstance + "inline:[\"-" + Launcher.class.getName()+"::getInstance\"]," + + // Do not inline test1 in C1 compilation + "c1: { inline:[\"-" + Launcher.class.getName()+"::test1\"] }," + + // Make sure to inline test1 in C2 compilation + "c2: { inline:[\"+" + Launcher.class.getName()+"::test1\"]," + + // Print the inline tree for checking + " PrintInlining:true }" + + "}]"; WB.addCompilerDirective(directive); } From e5c72937af50433029b8d4b6b30a5318c31a9da4 Mon Sep 17 00:00:00 2001 From: David Beaumont Date: Wed, 12 Nov 2025 15:41:40 +0000 Subject: [PATCH 016/418] 8371645: BasicImageReader getEntryNames() is stateful and cannot be called more than once Reviewed-by: alanb, rriggs --- .../jdk/internal/jimage/BasicImageReader.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/jimage/BasicImageReader.java b/src/java.base/share/classes/jdk/internal/jimage/BasicImageReader.java index c06c62488db..6807928b2c2 100644 --- a/src/java.base/share/classes/jdk/internal/jimage/BasicImageReader.java +++ b/src/java.base/share/classes/jdk/internal/jimage/BasicImageReader.java @@ -317,13 +317,12 @@ public class BasicImageReader implements AutoCloseable { } public String[] getEntryNames() { - int[] attributeOffsets = new int[offsets.capacity()]; - offsets.get(attributeOffsets); - return IntStream.of(attributeOffsets) - .filter(o -> o != 0) - .mapToObj(o -> ImageLocation.readFrom(this, o).getFullName()) - .sorted() - .toArray(String[]::new); + return IntStream.range(0, offsets.capacity()) + .map(offsets::get) + .filter(o -> o != 0) + .mapToObj(o -> ImageLocation.readFrom(this, o).getFullName()) + .sorted() + .toArray(String[]::new); } ImageLocation getLocation(int offset) { From 78db38f14044d434eabb61ff8293d62eff3c497c Mon Sep 17 00:00:00 2001 From: Harshitha Onkar Date: Wed, 12 Nov 2025 17:56:19 +0000 Subject: [PATCH 017/418] 8371365: Update javax/swing/JFileChooser/bug4759934.java to use Util.findComponent() Reviewed-by: aivanov, dnguyen, azvegint --- .../jdk/javax/swing/JFileChooser/bug4759934.java | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/test/jdk/javax/swing/JFileChooser/bug4759934.java b/test/jdk/javax/swing/JFileChooser/bug4759934.java index 08ccdebfb2b..c7f340ccdd0 100644 --- a/test/jdk/javax/swing/JFileChooser/bug4759934.java +++ b/test/jdk/javax/swing/JFileChooser/bug4759934.java @@ -31,10 +31,11 @@ * @run main bug4759934 */ +import java.awt.Component; +import java.awt.Container; import java.awt.Dialog; import java.awt.Point; import java.awt.Robot; -import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import javax.swing.JButton; import javax.swing.JDialog; @@ -72,8 +73,10 @@ public class bug4759934 { robot.mouseRelease(MouseEvent.BUTTON1_DOWN_MASK); robot.delay(500); - robot.keyPress(KeyEvent.VK_ESCAPE); - robot.keyRelease(KeyEvent.VK_ESCAPE); + SwingUtilities.invokeAndWait(() -> { + JButton cancelBtn = findCancelButton(jfc); + cancelBtn.doClick(); + }); robot.delay(500); SwingUtilities.invokeAndWait(() -> { @@ -121,4 +124,11 @@ public class bug4759934 { dlg.setLocation(fr.getX() + fr.getWidth() + 10, fr.getY()); dlg.setVisible(true); } + + private static JButton findCancelButton(final Container container) { + Component result = Util.findComponent(container, + c -> c instanceof JButton button + && "Cancel".equals(button.getText())); + return (JButton) result; + } } From 705bd6fbdc0e78625d05dbfa8af547c50b076e69 Mon Sep 17 00:00:00 2001 From: Leonid Mesnik Date: Wed, 12 Nov 2025 18:54:55 +0000 Subject: [PATCH 018/418] 8367902: Allocation after Universe::before_exit() in the VM shutdown sequence Reviewed-by: ayang, stefank, iwalulya, aboldtch, sspitsyn --- src/hotspot/share/runtime/java.cpp | 22 ++--- .../TestAllocatingInVMDeath.java | 81 +++++++++++++++++++ .../libTestAllocatingInVMDeath.cpp | 59 ++++++++++++++ 3 files changed, 152 insertions(+), 10 deletions(-) create mode 100644 test/hotspot/jtreg/serviceability/jvmti/events/VMDeath/AllocatingInVMDeath/TestAllocatingInVMDeath.java create mode 100644 test/hotspot/jtreg/serviceability/jvmti/events/VMDeath/AllocatingInVMDeath/libTestAllocatingInVMDeath.cpp diff --git a/src/hotspot/share/runtime/java.cpp b/src/hotspot/share/runtime/java.cpp index 6fda21ac86c..fb4abdac2ef 100644 --- a/src/hotspot/share/runtime/java.cpp +++ b/src/hotspot/share/runtime/java.cpp @@ -476,7 +476,18 @@ void before_exit(JavaThread* thread, bool halt) { NativeHeapTrimmer::cleanup(); - // Run before exit and then stop concurrent GC threads + if (JvmtiExport::should_post_thread_life()) { + JvmtiExport::post_thread_end(thread); + } + + // Always call even when there are not JVMTI environments yet, since environments + // may be attached late and JVMTI must track phases of VM execution. + JvmtiExport::post_vm_death(); + JvmtiAgentList::unload_agents(); + + // No user code can be executed in the current thread after this point. + + // Run before exit and then stop concurrent GC threads. Universe::before_exit(); if (PrintBytecodeHistogram) { @@ -492,15 +503,6 @@ void before_exit(JavaThread* thread, bool halt) { } #endif - if (JvmtiExport::should_post_thread_life()) { - JvmtiExport::post_thread_end(thread); - } - - // Always call even when there are not JVMTI environments yet, since environments - // may be attached late and JVMTI must track phases of VM execution - JvmtiExport::post_vm_death(); - JvmtiAgentList::unload_agents(); - // Terminate the signal thread // Note: we don't wait until it actually dies. os::terminate_signal_thread(); diff --git a/test/hotspot/jtreg/serviceability/jvmti/events/VMDeath/AllocatingInVMDeath/TestAllocatingInVMDeath.java b/test/hotspot/jtreg/serviceability/jvmti/events/VMDeath/AllocatingInVMDeath/TestAllocatingInVMDeath.java new file mode 100644 index 00000000000..47a04b305d2 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/events/VMDeath/AllocatingInVMDeath/TestAllocatingInVMDeath.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary Test verifies that VM still can execute java code, allocate + * memory, and call GC in the VMDeath event callback. + * + * @bug 8367902 + * @requires vm.jvmti + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/native TestAllocatingInVMDeath + */ +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +import jdk.test.lib.Asserts; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; +import jdk.test.whitebox.WhiteBox; + +public class TestAllocatingInVMDeath { + public static String UPCALL_MARKER = "Hello from upCall. "; + + public static void main(String[] args) throws Exception { + ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder( + "-agentlib:TestAllocatingInVMDeath", + "--enable-native-access=ALL-UNNAMED", + "-Xbootclasspath/a:.", + "-XX:+UnlockDiagnosticVMOptions", + "-XX:+WhiteBoxAPI", + "DoWork"); + + OutputAnalyzer oa = new OutputAnalyzer(pb.start()); + String output = oa.getOutput(); + System.err.println("DoWork output:"); + System.err.println(output); + Asserts.assertTrue(oa.getExitValue() == 0); + Asserts.assertTrue(output.contains(UPCALL_MARKER)); + } +} + +class DoWork { + static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox(); + + // This method is called from VMDeath event callback. + static void upCall() { + // Do some work including memory allocation, so it can't be optimized by compiler. + LocalDateTime now = LocalDateTime.now(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"); + String result = TestAllocatingInVMDeath.UPCALL_MARKER + now.format(formatter); + WHITE_BOX.fullGC(); + System.out.println(result); + } + + public static void main(String argv[]) throws Exception { + System.out.println("Hello from DoWork main()."); + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/events/VMDeath/AllocatingInVMDeath/libTestAllocatingInVMDeath.cpp b/test/hotspot/jtreg/serviceability/jvmti/events/VMDeath/AllocatingInVMDeath/libTestAllocatingInVMDeath.cpp new file mode 100644 index 00000000000..5f46d69afa1 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/events/VMDeath/AllocatingInVMDeath/libTestAllocatingInVMDeath.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "jvmti.h" +#include "jni.h" +#include "jvmti_common.hpp" + +static void JNICALL +cbVMDeath(jvmtiEnv* jvmti, JNIEnv* jni) { + jclass clz = jni->FindClass("DoWork"); + if (clz == nullptr) { + fatal(jni, "Can't find DoWork class."); + return; + } + jmethodID mid = jni->GetStaticMethodID(clz, "upCall", "()V"); + if (mid == nullptr) { + fatal(jni, "Can't find upCall method."); + } + jni->CallStaticObjectMethod(clz, mid); +} + +JNIEXPORT jint JNICALL +Agent_OnLoad(JavaVM *vm, char *options, void *reserved) { + jvmtiEnv *jvmti = nullptr; + jint res = vm->GetEnv((void **) &jvmti, JVMTI_VERSION_21); + if (res != JNI_OK) { + return JNI_ERR; + } + jvmtiError err = JVMTI_ERROR_NONE; + + jvmtiEventCallbacks callbacks; + (void) memset(&callbacks, 0, sizeof (callbacks)); + callbacks.VMDeath = &cbVMDeath; + err = jvmti->SetEventCallbacks(&callbacks, (int) sizeof (jvmtiEventCallbacks)); + check_jvmti_error(err, "SetEventCallbacks"); + err = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_DEATH, nullptr); + check_jvmti_error(err, "SetEventNotificationMode"); + return JNI_OK; +} From d2571ea76ae5a9ccb7053bfec24bf3aedd366084 Mon Sep 17 00:00:00 2001 From: Naoto Sato Date: Thu, 13 Nov 2025 00:31:10 +0000 Subject: [PATCH 019/418] 8371339: Illegal pattern char 'B' with locale.providers as HOST on macOS for Taiwanese Reviewed-by: jlu, rriggs --- .../provider/HostLocaleProviderAdapterImpl.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/java.base/macosx/classes/sun/util/locale/provider/HostLocaleProviderAdapterImpl.java b/src/java.base/macosx/classes/sun/util/locale/provider/HostLocaleProviderAdapterImpl.java index e587ee54097..e24a986782d 100644 --- a/src/java.base/macosx/classes/sun/util/locale/provider/HostLocaleProviderAdapterImpl.java +++ b/src/java.base/macosx/classes/sun/util/locale/provider/HostLocaleProviderAdapterImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2025, 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 @@ -874,6 +874,17 @@ public class HostLocaleProviderAdapterImpl { sb.append('\''); break; + case 'B': + // 'B' character (day period) is not supported by SimpleDateFormat, + // this is a workaround in which 'B' character + // appearing in macos' pattern string (based on LDML) is replaced + // with 'a' character and hence resolved with am/pm strings. + // This workaround is based on the fallback mechanism + // specified in LDML spec for 'B' character, when a locale + // does not have data for day period ('B') + appendN('a', count, sb); + break; + default: appendN(cldrLetter, count, sb); break; From bc66d3e65d208edc69e8ae334d23b38f2b78a440 Mon Sep 17 00:00:00 2001 From: Prasanta Sadhukhan Date: Thu, 13 Nov 2025 01:19:37 +0000 Subject: [PATCH 020/418] 8370467: BorderFactory.createBevelBorder and createSoftBevelBorder throws NPE for null highlight and shadow Reviewed-by: aivanov, tr, honkar --- .../javax/swing/border/BevelBorder.java | 6 +- .../swing/border/TestBevelBorderParam.java | 64 +++++++++++++++++++ 2 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 test/jdk/javax/swing/border/TestBevelBorderParam.java diff --git a/src/java.desktop/share/classes/javax/swing/border/BevelBorder.java b/src/java.desktop/share/classes/javax/swing/border/BevelBorder.java index 13c9c85ab29..dbcab475072 100644 --- a/src/java.desktop/share/classes/javax/swing/border/BevelBorder.java +++ b/src/java.desktop/share/classes/javax/swing/border/BevelBorder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2025, 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 @@ -95,7 +95,9 @@ public class BevelBorder extends AbstractBorder * @param shadow the color to use for the bevel shadow */ public BevelBorder(int bevelType, Color highlight, Color shadow) { - this(bevelType, highlight.brighter(), highlight, shadow, shadow.brighter()); + this(bevelType, + (highlight != null) ? highlight.brighter() : null, highlight, + shadow, (shadow != null) ? shadow.brighter() : null); } /** diff --git a/test/jdk/javax/swing/border/TestBevelBorderParam.java b/test/jdk/javax/swing/border/TestBevelBorderParam.java new file mode 100644 index 00000000000..ad1d779afd1 --- /dev/null +++ b/test/jdk/javax/swing/border/TestBevelBorderParam.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8370467 + * @summary Verifies createBevelBorder and createSoftBevelBorder does not throw NPE + * @run main TestBevelBorderParam + */ + +import javax.swing.BorderFactory; +import javax.swing.border.BevelBorder; + +public class TestBevelBorderParam { + + public static void main(String[] args) throws Exception { + StringBuilder str = new StringBuilder(); + + try { + BorderFactory.createBevelBorder(BevelBorder.RAISED, null, null); + } catch (NullPointerException ex) { + str.append("\n"); + str.append("BorderFactory.createBevelBorder throws NPE for null highlight and shadow"); + } + + try { + BorderFactory.createSoftBevelBorder(BevelBorder.RAISED, null, null); + } catch (NullPointerException e) { + str.append("\n"); + str.append("BorderFactory.createSoftBevelBorder throws NPE for null highlight and shadow"); + } + + try { + new BevelBorder(BevelBorder.RAISED, null, null); + } catch (NullPointerException ex) { + str.append("\n"); + str.append("BevelBorder constructor throws NPE for null highlight and shadow"); + } + + if (str.length() != 0) { + throw new RuntimeException(str.toString()); + } + } +} From 676e6fd8d5152f4e0d14ae59ddd7aa0a7127ea58 Mon Sep 17 00:00:00 2001 From: Xiaohong Gong Date: Thu, 13 Nov 2025 01:33:21 +0000 Subject: [PATCH 021/418] 8367292: VectorAPI: Optimize VectorMask.fromLong/toLong() for SVE Reviewed-by: epeter, psandoz, haosun, sviswanathan --- src/hotspot/cpu/aarch64/aarch64_vector.ad | 67 ++++-- src/hotspot/cpu/aarch64/aarch64_vector_ad.m4 | 67 ++++-- .../cpu/aarch64/c2_MacroAssembler_aarch64.cpp | 218 +++++++++--------- .../cpu/aarch64/c2_MacroAssembler_aarch64.hpp | 20 +- src/hotspot/cpu/arm/arm.ad | 4 + src/hotspot/cpu/ppc/ppc.ad | 4 + src/hotspot/cpu/riscv/riscv_v.ad | 5 + src/hotspot/cpu/s390/s390.ad | 4 + src/hotspot/cpu/x86/x86.ad | 5 + src/hotspot/share/opto/matcher.hpp | 6 + src/hotspot/share/opto/vectorIntrinsics.cpp | 6 +- src/hotspot/share/opto/vectornode.cpp | 16 +- .../compiler/lib/ir_framework/IRNode.java | 10 + .../ir_framework/test/IREncodingPrinter.java | 1 + .../vectorapi/VectorMaskFromLongTest.java | 147 ++++++------ .../vectorapi/VectorMaskToLongTest.java | 146 ++++++++++-- 16 files changed, 470 insertions(+), 256 deletions(-) diff --git a/src/hotspot/cpu/aarch64/aarch64_vector.ad b/src/hotspot/cpu/aarch64/aarch64_vector.ad index 9809d096233..842784d1a29 100644 --- a/src/hotspot/cpu/aarch64/aarch64_vector.ad +++ b/src/hotspot/cpu/aarch64/aarch64_vector.ad @@ -393,6 +393,32 @@ source %{ return false; } + bool Matcher::mask_op_prefers_predicate(int opcode, const TypeVect* vt) { + // Only SVE supports the predicate feature. + if (UseSVE == 0) { + // On architectures that do not support predicate, masks are stored in + // general vector registers (TypeVect) with sizes ranging from TypeVectA + // to TypeVectX based on the vector size in bytes. + assert(vt->isa_vectmask() == nullptr, "mask type is not matched"); + return false; + } + + assert(vt->isa_vectmask() != nullptr, "expected TypeVectMask on SVE"); + switch (opcode) { + case Op_VectorMaskToLong: + case Op_VectorLongToMask: + // These operations lack native SVE predicate instructions and are + // implemented using general vector instructions instead. Use vector + // registers rather than predicate registers to save the mask for + // better performance. + return false; + default: + // By default, the mask operations are implemented with predicate + // instructions with a predicate input/output. + return true; + } + } + // Assert that the given node is not a variable shift. bool assert_not_var_shift(const Node* n) { assert(!n->as_ShiftV()->is_var_shift(), "illegal variable shift"); @@ -6249,31 +6275,44 @@ instruct vmask_tolong_neon(iRegLNoSp dst, vReg src) %{ ins_pipe(pipe_slow); %} -instruct vmask_tolong_sve(iRegLNoSp dst, pReg src, vReg tmp1, vReg tmp2) %{ - predicate(UseSVE > 0); +instruct vmask_tolong_sve(iRegLNoSp dst, vReg src, vReg tmp) %{ + predicate(UseSVE > 0 && !VM_Version::supports_svebitperm()); + match(Set dst (VectorMaskToLong src)); + effect(TEMP tmp); + format %{ "vmask_tolong_sve $dst, $src\t# KILL $tmp" %} + ins_encode %{ + // Input "src" is a vector of boolean represented as + // bytes with 0x00/0x01 as element values. + __ sve_vmask_tolong($dst$$Register, $src$$FloatRegister, + $tmp$$FloatRegister, Matcher::vector_length(this, $src)); + %} + ins_pipe(pipe_slow); +%} + +instruct vmask_tolong_sve2(iRegLNoSp dst, vReg src, vReg tmp1, vReg tmp2) %{ + predicate(VM_Version::supports_svebitperm()); match(Set dst (VectorMaskToLong src)); effect(TEMP tmp1, TEMP tmp2); - format %{ "vmask_tolong_sve $dst, $src\t# KILL $tmp1, $tmp2" %} + format %{ "vmask_tolong_sve2 $dst, $src\t# KILL $tmp1, $tmp2" %} ins_encode %{ - __ sve_vmask_tolong($dst$$Register, $src$$PRegister, - Matcher::vector_element_basic_type(this, $src), - Matcher::vector_length(this, $src), - $tmp1$$FloatRegister, $tmp2$$FloatRegister); + // Input "src" is a vector of boolean represented as + // bytes with 0x00/0x01 as element values. + __ sve2_vmask_tolong($dst$$Register, $src$$FloatRegister, + $tmp1$$FloatRegister, $tmp2$$FloatRegister, + Matcher::vector_length(this, $src)); %} ins_pipe(pipe_slow); %} // fromlong -instruct vmask_fromlong(pReg dst, iRegL src, vReg tmp1, vReg tmp2) %{ +instruct vmask_fromlong(vReg dst, iRegL src, vReg tmp) %{ match(Set dst (VectorLongToMask src)); - effect(TEMP tmp1, TEMP tmp2); - format %{ "vmask_fromlong $dst, $src\t# vector (sve2). KILL $tmp1, $tmp2" %} + effect(TEMP_DEF dst, TEMP tmp); + format %{ "vmask_fromlong $dst, $src\t# vector (sve2). KILL $tmp" %} ins_encode %{ - __ sve_vmask_fromlong($dst$$PRegister, $src$$Register, - Matcher::vector_element_basic_type(this), - Matcher::vector_length(this), - $tmp1$$FloatRegister, $tmp2$$FloatRegister); + __ sve_vmask_fromlong($dst$$FloatRegister, $src$$Register, + $tmp$$FloatRegister, Matcher::vector_length(this)); %} ins_pipe(pipe_slow); %} diff --git a/src/hotspot/cpu/aarch64/aarch64_vector_ad.m4 b/src/hotspot/cpu/aarch64/aarch64_vector_ad.m4 index a9f42e1bc08..dff82ce95ac 100644 --- a/src/hotspot/cpu/aarch64/aarch64_vector_ad.m4 +++ b/src/hotspot/cpu/aarch64/aarch64_vector_ad.m4 @@ -383,6 +383,32 @@ source %{ return false; } + bool Matcher::mask_op_prefers_predicate(int opcode, const TypeVect* vt) { + // Only SVE supports the predicate feature. + if (UseSVE == 0) { + // On architectures that do not support predicate, masks are stored in + // general vector registers (TypeVect) with sizes ranging from TypeVectA + // to TypeVectX based on the vector size in bytes. + assert(vt->isa_vectmask() == nullptr, "mask type is not matched"); + return false; + } + + assert(vt->isa_vectmask() != nullptr, "expected TypeVectMask on SVE"); + switch (opcode) { + case Op_VectorMaskToLong: + case Op_VectorLongToMask: + // These operations lack native SVE predicate instructions and are + // implemented using general vector instructions instead. Use vector + // registers rather than predicate registers to save the mask for + // better performance. + return false; + default: + // By default, the mask operations are implemented with predicate + // instructions with a predicate input/output. + return true; + } + } + // Assert that the given node is not a variable shift. bool assert_not_var_shift(const Node* n) { assert(!n->as_ShiftV()->is_var_shift(), "illegal variable shift"); @@ -4303,31 +4329,44 @@ instruct vmask_tolong_neon(iRegLNoSp dst, vReg src) %{ ins_pipe(pipe_slow); %} -instruct vmask_tolong_sve(iRegLNoSp dst, pReg src, vReg tmp1, vReg tmp2) %{ - predicate(UseSVE > 0); +instruct vmask_tolong_sve(iRegLNoSp dst, vReg src, vReg tmp) %{ + predicate(UseSVE > 0 && !VM_Version::supports_svebitperm()); + match(Set dst (VectorMaskToLong src)); + effect(TEMP tmp); + format %{ "vmask_tolong_sve $dst, $src\t# KILL $tmp" %} + ins_encode %{ + // Input "src" is a vector of boolean represented as + // bytes with 0x00/0x01 as element values. + __ sve_vmask_tolong($dst$$Register, $src$$FloatRegister, + $tmp$$FloatRegister, Matcher::vector_length(this, $src)); + %} + ins_pipe(pipe_slow); +%} + +instruct vmask_tolong_sve2(iRegLNoSp dst, vReg src, vReg tmp1, vReg tmp2) %{ + predicate(VM_Version::supports_svebitperm()); match(Set dst (VectorMaskToLong src)); effect(TEMP tmp1, TEMP tmp2); - format %{ "vmask_tolong_sve $dst, $src\t# KILL $tmp1, $tmp2" %} + format %{ "vmask_tolong_sve2 $dst, $src\t# KILL $tmp1, $tmp2" %} ins_encode %{ - __ sve_vmask_tolong($dst$$Register, $src$$PRegister, - Matcher::vector_element_basic_type(this, $src), - Matcher::vector_length(this, $src), - $tmp1$$FloatRegister, $tmp2$$FloatRegister); + // Input "src" is a vector of boolean represented as + // bytes with 0x00/0x01 as element values. + __ sve2_vmask_tolong($dst$$Register, $src$$FloatRegister, + $tmp1$$FloatRegister, $tmp2$$FloatRegister, + Matcher::vector_length(this, $src)); %} ins_pipe(pipe_slow); %} // fromlong -instruct vmask_fromlong(pReg dst, iRegL src, vReg tmp1, vReg tmp2) %{ +instruct vmask_fromlong(vReg dst, iRegL src, vReg tmp) %{ match(Set dst (VectorLongToMask src)); - effect(TEMP tmp1, TEMP tmp2); - format %{ "vmask_fromlong $dst, $src\t# vector (sve2). KILL $tmp1, $tmp2" %} + effect(TEMP_DEF dst, TEMP tmp); + format %{ "vmask_fromlong $dst, $src\t# vector (sve2). KILL $tmp" %} ins_encode %{ - __ sve_vmask_fromlong($dst$$PRegister, $src$$Register, - Matcher::vector_element_basic_type(this), - Matcher::vector_length(this), - $tmp1$$FloatRegister, $tmp2$$FloatRegister); + __ sve_vmask_fromlong($dst$$FloatRegister, $src$$Register, + $tmp$$FloatRegister, Matcher::vector_length(this)); %} ins_pipe(pipe_slow); %} diff --git a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp index ebb4a897906..5e57044dcba 100644 --- a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp @@ -1399,137 +1399,125 @@ void C2_MacroAssembler::bytemask_compress(Register dst) { andr(dst, dst, 0xff); // dst = 0x8D } -// Pack the lowest-numbered bit of each mask element in src into a long value -// in dst, at most the first 64 lane elements. -// Clobbers: rscratch1, if UseSVE=1 or the hardware doesn't support FEAT_BITPERM. -void C2_MacroAssembler::sve_vmask_tolong(Register dst, PRegister src, BasicType bt, int lane_cnt, - FloatRegister vtmp1, FloatRegister vtmp2) { +// Pack the value of each mask element in "src" into a long value in "dst", at most +// the first 64 lane elements. The input "src" is a vector of boolean represented as +// bytes with 0x00/0x01 as element values. Each lane value from "src" is packed into +// one bit in "dst". +// +// Example: src = 0x0001010000010001 0100000001010001, lane_cnt = 16 +// Expected: dst = 0x658D +// +// Clobbers: rscratch1 +void C2_MacroAssembler::sve_vmask_tolong(Register dst, FloatRegister src, + FloatRegister vtmp, int lane_cnt) { assert(lane_cnt <= 64 && is_power_of_2(lane_cnt), "Unsupported lane count"); assert_different_registers(dst, rscratch1); - assert_different_registers(vtmp1, vtmp2); + assert_different_registers(src, vtmp); + assert(UseSVE > 0, "must be"); - Assembler::SIMD_RegVariant size = elemType_to_regVariant(bt); - // Example: src = 0b01100101 10001101, bt = T_BYTE, lane_cnt = 16 - // Expected: dst = 0x658D + // Compress the lowest 8 bytes. + fmovd(dst, src); + bytemask_compress(dst); + if (lane_cnt <= 8) return; - // Convert the mask into vector with sequential bytes. - // vtmp1 = 0x00010100 0x00010001 0x01000000 0x01010001 - sve_cpy(vtmp1, size, src, 1, false); - if (bt != T_BYTE) { - sve_vector_narrow(vtmp1, B, vtmp1, size, vtmp2); - } - - if (UseSVE > 1 && VM_Version::supports_svebitperm()) { - // Given a vector with the value 0x00 or 0x01 in each byte, the basic idea - // is to compress each significant bit of the byte in a cross-lane way. Due - // to the lack of a cross-lane bit-compress instruction, we use BEXT - // (bit-compress in each lane) with the biggest lane size (T = D) then - // concatenate the results. - - // The second source input of BEXT, initialized with 0x01 in each byte. - // vtmp2 = 0x01010101 0x01010101 0x01010101 0x01010101 - sve_dup(vtmp2, B, 1); - - // BEXT vtmp1.D, vtmp1.D, vtmp2.D - // vtmp1 = 0x0001010000010001 | 0x0100000001010001 - // vtmp2 = 0x0101010101010101 | 0x0101010101010101 - // --------------------------------------- - // vtmp1 = 0x0000000000000065 | 0x000000000000008D - sve_bext(vtmp1, D, vtmp1, vtmp2); - - // Concatenate the lowest significant 8 bits in each 8 bytes, and extract the - // result to dst. - // vtmp1 = 0x0000000000000000 | 0x000000000000658D - // dst = 0x658D - if (lane_cnt <= 8) { - // No need to concatenate. - umov(dst, vtmp1, B, 0); - } else if (lane_cnt <= 16) { - ins(vtmp1, B, vtmp1, 1, 8); - umov(dst, vtmp1, H, 0); - } else { - // As the lane count is 64 at most, the final expected value must be in - // the lowest 64 bits after narrowing vtmp1 from D to B. - sve_vector_narrow(vtmp1, B, vtmp1, D, vtmp2); - umov(dst, vtmp1, D, 0); - } - } else if (UseSVE > 0) { - // Compress the lowest 8 bytes. - fmovd(dst, vtmp1); - bytemask_compress(dst); - if (lane_cnt <= 8) return; - - // Repeat on higher bytes and join the results. - // Compress 8 bytes in each iteration. - for (int idx = 1; idx < (lane_cnt / 8); idx++) { - sve_extract_integral(rscratch1, T_LONG, vtmp1, idx, vtmp2); - bytemask_compress(rscratch1); - orr(dst, dst, rscratch1, Assembler::LSL, idx << 3); - } - } else { - assert(false, "unsupported"); - ShouldNotReachHere(); + // Repeat on higher bytes and join the results. + // Compress 8 bytes in each iteration. + for (int idx = 1; idx < (lane_cnt / 8); idx++) { + sve_extract_integral(rscratch1, T_LONG, src, idx, vtmp); + bytemask_compress(rscratch1); + orr(dst, dst, rscratch1, Assembler::LSL, idx << 3); } } -// Unpack the mask, a long value in src, into predicate register dst based on the -// corresponding data type. Note that dst can support at most 64 lanes. -// Below example gives the expected dst predicate register in different types, with -// a valid src(0x658D) on a 1024-bit vector size machine. -// BYTE: dst = 0x00 00 00 00 00 00 00 00 00 00 00 00 00 00 65 8D -// SHORT: dst = 0x00 00 00 00 00 00 00 00 00 00 00 00 14 11 40 51 -// INT: dst = 0x00 00 00 00 00 00 00 00 01 10 01 01 10 00 11 01 -// LONG: dst = 0x00 01 01 00 00 01 00 01 01 00 00 00 01 01 00 01 -// -// The number of significant bits of src must be equal to lane_cnt. E.g., 0xFF658D which -// has 24 significant bits would be an invalid input if dst predicate register refers to -// a LONG type 1024-bit vector, which has at most 16 lanes. -void C2_MacroAssembler::sve_vmask_fromlong(PRegister dst, Register src, BasicType bt, int lane_cnt, - FloatRegister vtmp1, FloatRegister vtmp2) { - assert(UseSVE == 2 && VM_Version::supports_svebitperm() && - lane_cnt <= 64 && is_power_of_2(lane_cnt), "unsupported"); - Assembler::SIMD_RegVariant size = elemType_to_regVariant(bt); - // Example: src = 0x658D, bt = T_BYTE, size = B, lane_cnt = 16 - // Expected: dst = 0b01101001 10001101 +// The function is same as above "sve_vmask_tolong", but it uses SVE2's BEXT +// instruction which requires the FEAT_BITPERM feature. +void C2_MacroAssembler::sve2_vmask_tolong(Register dst, FloatRegister src, + FloatRegister vtmp1, FloatRegister vtmp2, + int lane_cnt) { + assert(lane_cnt <= 64 && is_power_of_2(lane_cnt), "Unsupported lane count"); + assert_different_registers(src, vtmp1, vtmp2); + assert(UseSVE > 1 && VM_Version::supports_svebitperm(), "must be"); - // Put long value from general purpose register into the first lane of vector. - // vtmp1 = 0x0000000000000000 | 0x000000000000658D - sve_dup(vtmp1, B, 0); - mov(vtmp1, D, 0, src); + // Given a vector with the value 0x00 or 0x01 in each byte, the basic idea + // is to compress each significant bit of the byte in a cross-lane way. Due + // to the lack of a cross-lane bit-compress instruction, we use BEXT + // (bit-compress in each lane) with the biggest lane size (T = D) then + // concatenate the results. - // As sve_cmp generates mask value with the minimum unit in byte, we should - // transform the value in the first lane which is mask in bit now to the - // mask in byte, which can be done by SVE2's BDEP instruction. - - // The first source input of BDEP instruction. Deposite each byte in every 8 bytes. - // vtmp1 = 0x0000000000000065 | 0x000000000000008D - if (lane_cnt <= 8) { - // Nothing. As only one byte exsits. - } else if (lane_cnt <= 16) { - ins(vtmp1, B, vtmp1, 8, 1); - mov(vtmp1, B, 1, zr); - } else { - sve_vector_extend(vtmp1, D, vtmp1, B); - } - - // The second source input of BDEP instruction, initialized with 0x01 for each byte. + // The second source input of BEXT, initialized with 0x01 in each byte. // vtmp2 = 0x01010101 0x01010101 0x01010101 0x01010101 sve_dup(vtmp2, B, 1); - // BDEP vtmp1.D, vtmp1.D, vtmp2.D - // vtmp1 = 0x0000000000000065 | 0x000000000000008D + // BEXT vtmp1.D, src.D, vtmp2.D + // src = 0x0001010000010001 | 0x0100000001010001 // vtmp2 = 0x0101010101010101 | 0x0101010101010101 // --------------------------------------- - // vtmp1 = 0x0001010000010001 | 0x0100000001010001 - sve_bdep(vtmp1, D, vtmp1, vtmp2); + // vtmp1 = 0x0000000000000065 | 0x000000000000008D + sve_bext(vtmp1, D, src, vtmp2); - if (bt != T_BYTE) { - sve_vector_extend(vtmp1, size, vtmp1, B); + // Concatenate the lowest significant 8 bits in each 8 bytes, and extract the + // result to dst. + // vtmp1 = 0x0000000000000000 | 0x000000000000658D + // dst = 0x658D + if (lane_cnt <= 8) { + // No need to concatenate. + umov(dst, vtmp1, B, 0); + } else if (lane_cnt <= 16) { + ins(vtmp1, B, vtmp1, 1, 8); + umov(dst, vtmp1, H, 0); + } else { + // As the lane count is 64 at most, the final expected value must be in + // the lowest 64 bits after narrowing vtmp1 from D to B. + sve_vector_narrow(vtmp1, B, vtmp1, D, vtmp2); + umov(dst, vtmp1, D, 0); } - // Generate mask according to the given vector, in which the elements have been - // extended to expected type. - // dst = 0b01101001 10001101 - sve_cmp(Assembler::NE, dst, size, ptrue, vtmp1, 0); +} + +// Unpack the mask, a long value in "src", into a vector register of boolean +// represented as bytes with 0x00/0x01 as element values in "dst". Each bit in +// "src" is unpacked into one byte lane in "dst". Note that "dst" can support at +// most 64 lanes. +// +// Below example gives the expected dst vector register, with a valid src(0x658D) +// on a 128-bit vector size machine. +// dst = 0x00 01 01 00 00 01 00 01 01 00 00 00 01 01 00 01 +void C2_MacroAssembler::sve_vmask_fromlong(FloatRegister dst, Register src, + FloatRegister vtmp, int lane_cnt) { + assert_different_registers(dst, vtmp); + assert(UseSVE == 2 && VM_Version::supports_svebitperm() && + lane_cnt <= 64 && is_power_of_2(lane_cnt), "unsupported"); + + // Example: src = 0x658D, lane_cnt = 16 + // Expected: dst = 0x00 01 01 00 00 01 00 01 01 00 00 00 01 01 00 01 + + // Put long value from general purpose register into the first lane of vector. + // vtmp = 0x0000000000000000 | 0x000000000000658D + sve_dup(vtmp, B, 0); + mov(vtmp, D, 0, src); + + // Transform the value in the first lane which is mask in bit now to the mask in + // byte, which can be done by SVE2's BDEP instruction. + + // The first source input of BDEP instruction. Deposite each byte in every 8 bytes. + // vtmp = 0x0000000000000065 | 0x000000000000008D + if (lane_cnt <= 8) { + // Nothing. As only one byte exsits. + } else if (lane_cnt <= 16) { + ins(vtmp, B, vtmp, 8, 1); + } else { + sve_vector_extend(vtmp, D, vtmp, B); + } + + // The second source input of BDEP instruction, initialized with 0x01 for each byte. + // dst = 0x01010101 0x01010101 0x01010101 0x01010101 + sve_dup(dst, B, 1); + + // BDEP dst.D, vtmp.D, dst.D + // vtmp = 0x0000000000000065 | 0x000000000000008D + // dst = 0x0101010101010101 | 0x0101010101010101 + // --------------------------------------- + // dst = 0x0001010000010001 | 0x0100000001010001 + sve_bdep(dst, D, vtmp, dst); } // Clobbers: rflags diff --git a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.hpp b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.hpp index ccd091938a3..412f0f37e9e 100644 --- a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.hpp @@ -85,15 +85,19 @@ // the higher garbage bits. void bytemask_compress(Register dst); - // Pack the lowest-numbered bit of each mask element in src into a long value - // in dst, at most the first 64 lane elements. - void sve_vmask_tolong(Register dst, PRegister src, BasicType bt, int lane_cnt, - FloatRegister vtmp1, FloatRegister vtmp2); + // Pack the value of each mask element in "src" into a long value in "dst", at most the + // first 64 lane elements. The input "src" is a vector of boolean represented as bytes + // with 0x00/0x01 as element values. Each lane value from "src" is packed into one bit in + // "dst". + void sve_vmask_tolong(Register dst, FloatRegister src, FloatRegister vtmp, int lane_cnt); - // Unpack the mask, a long value in src, into predicate register dst based on the - // corresponding data type. Note that dst can support at most 64 lanes. - void sve_vmask_fromlong(PRegister dst, Register src, BasicType bt, int lane_cnt, - FloatRegister vtmp1, FloatRegister vtmp2); + void sve2_vmask_tolong(Register dst, FloatRegister src, FloatRegister vtmp1, + FloatRegister vtmp2, int lane_cnt); + + // Unpack the mask, a long value in "src", into vector register "dst" with boolean type. + // Each bit in "src" is unpacked into one byte lane in "dst". Note that "dst" can support + // at most 64 lanes. + void sve_vmask_fromlong(FloatRegister dst, Register src, FloatRegister vtmp, int lane_cnt); // SIMD&FP comparison void neon_compare(FloatRegister dst, BasicType bt, FloatRegister src1, diff --git a/src/hotspot/cpu/arm/arm.ad b/src/hotspot/cpu/arm/arm.ad index 31a442be624..92c0df68deb 100644 --- a/src/hotspot/cpu/arm/arm.ad +++ b/src/hotspot/cpu/arm/arm.ad @@ -1003,6 +1003,10 @@ bool Matcher::vector_rearrange_requires_load_shuffle(BasicType elem_bt, int vlen return false; } +bool Matcher::mask_op_prefers_predicate(int opcode, const TypeVect* vt) { + return false; +} + const RegMask* Matcher::predicate_reg_mask(void) { return nullptr; } diff --git a/src/hotspot/cpu/ppc/ppc.ad b/src/hotspot/cpu/ppc/ppc.ad index 36326e5fdb7..7fcd096d2ad 100644 --- a/src/hotspot/cpu/ppc/ppc.ad +++ b/src/hotspot/cpu/ppc/ppc.ad @@ -2292,6 +2292,10 @@ bool Matcher::vector_rearrange_requires_load_shuffle(BasicType elem_bt, int vlen return false; } +bool Matcher::mask_op_prefers_predicate(int opcode, const TypeVect* vt) { + return false; +} + const RegMask* Matcher::predicate_reg_mask(void) { return nullptr; } diff --git a/src/hotspot/cpu/riscv/riscv_v.ad b/src/hotspot/cpu/riscv/riscv_v.ad index fe323474d60..d162280106a 100644 --- a/src/hotspot/cpu/riscv/riscv_v.ad +++ b/src/hotspot/cpu/riscv/riscv_v.ad @@ -164,6 +164,11 @@ source %{ bool Matcher::vector_rearrange_requires_load_shuffle(BasicType elem_bt, int vlen) { return false; } + + bool Matcher::mask_op_prefers_predicate(int opcode, const TypeVect* vt) { + // Prefer predicate if the mask type is "TypeVectMask". + return vt->isa_vectmask() != nullptr; + } %} // All VEC instructions diff --git a/src/hotspot/cpu/s390/s390.ad b/src/hotspot/cpu/s390/s390.ad index 2b2ce713491..cab3965ecfa 100644 --- a/src/hotspot/cpu/s390/s390.ad +++ b/src/hotspot/cpu/s390/s390.ad @@ -1809,6 +1809,10 @@ bool Matcher::vector_rearrange_requires_load_shuffle(BasicType elem_bt, int vlen return false; } +bool Matcher::mask_op_prefers_predicate(int opcode, const TypeVect* vt) { + return false; +} + const RegMask* Matcher::predicate_reg_mask(void) { return nullptr; } diff --git a/src/hotspot/cpu/x86/x86.ad b/src/hotspot/cpu/x86/x86.ad index 9a0bbdc27a0..a9748617e1f 100644 --- a/src/hotspot/cpu/x86/x86.ad +++ b/src/hotspot/cpu/x86/x86.ad @@ -3736,6 +3736,11 @@ bool Matcher::vector_rearrange_requires_load_shuffle(BasicType elem_bt, int vlen } } +bool Matcher::mask_op_prefers_predicate(int opcode, const TypeVect* vt) { + // Prefer predicate if the mask type is "TypeVectMask". + return vt->isa_vectmask() != nullptr; +} + MachOper* Matcher::pd_specialize_generic_vector_operand(MachOper* generic_opnd, uint ideal_reg, bool is_temp) { assert(Matcher::is_generic_vector(generic_opnd), "not generic"); bool legacy = (generic_opnd->opcode() == LEGVEC); diff --git a/src/hotspot/share/opto/matcher.hpp b/src/hotspot/share/opto/matcher.hpp index e4396b423ac..01f11b1fdc9 100644 --- a/src/hotspot/share/opto/matcher.hpp +++ b/src/hotspot/share/opto/matcher.hpp @@ -333,6 +333,12 @@ public: static bool vector_rearrange_requires_load_shuffle(BasicType elem_bt, int vlen); + // Identify if a vector mask operation prefers the input/output mask to be + // saved with a predicate type or not. + // - Return true if it prefers a predicate type (i.e. TypeVectMask). + // - Return false if it prefers a general vector type (i.e. TypeVectA to TypeVectZ). + static bool mask_op_prefers_predicate(int opcode, const TypeVect* vt); + static const RegMask* predicate_reg_mask(void); // Vector width in bytes diff --git a/src/hotspot/share/opto/vectorIntrinsics.cpp b/src/hotspot/share/opto/vectorIntrinsics.cpp index 85d9790c0eb..b48b5f2cd05 100644 --- a/src/hotspot/share/opto/vectorIntrinsics.cpp +++ b/src/hotspot/share/opto/vectorIntrinsics.cpp @@ -622,7 +622,7 @@ bool LibraryCallKit::inline_vector_mask_operation() { return false; } - if (mask_vec->bottom_type()->isa_vectmask() == nullptr) { + if (!Matcher::mask_op_prefers_predicate(mopc, mask_vec->bottom_type()->is_vect())) { mask_vec = gvn().transform(VectorStoreMaskNode::make(gvn(), mask_vec, elem_bt, num_elem)); } const Type* maskoper_ty = mopc == Op_VectorMaskToLong ? (const Type*)TypeLong::LONG : (const Type*)TypeInt::INT; @@ -708,7 +708,7 @@ bool LibraryCallKit::inline_vector_frombits_coerced() { if (opc == Op_VectorLongToMask) { const TypeVect* vt = TypeVect::makemask(elem_bt, num_elem); - if (vt->isa_vectmask()) { + if (Matcher::mask_op_prefers_predicate(opc, vt)) { broadcast = gvn().transform(new VectorLongToMaskNode(elem, vt)); } else { const TypeVect* mvt = TypeVect::make(T_BOOLEAN, num_elem); @@ -2545,7 +2545,7 @@ bool LibraryCallKit::inline_vector_extract() { return false; } // VectorMaskToLongNode requires the input is either a mask or a vector with BOOLEAN type. - if (opd->bottom_type()->isa_vectmask() == nullptr) { + if (!Matcher::mask_op_prefers_predicate(Op_VectorMaskToLong, opd->bottom_type()->is_vect())) { opd = gvn().transform(VectorStoreMaskNode::make(gvn(), opd, elem_bt, num_elem)); } // ((toLong() >>> pos) & 1L diff --git a/src/hotspot/share/opto/vectornode.cpp b/src/hotspot/share/opto/vectornode.cpp index 6ae8bbe8aa0..a49f3d24fd4 100644 --- a/src/hotspot/share/opto/vectornode.cpp +++ b/src/hotspot/share/opto/vectornode.cpp @@ -1403,7 +1403,7 @@ Node* ReductionNode::Ideal(PhaseGVN* phase, bool can_reshape) { } // Convert fromLong to maskAll if the input sets or unsets all lanes. -Node* convertFromLongToMaskAll(PhaseGVN* phase, const TypeLong* bits_type, bool is_mask, const TypeVect* vt) { +static Node* convertFromLongToMaskAll(PhaseGVN* phase, const TypeLong* bits_type, const TypeVect* vt) { uint vlen = vt->length(); BasicType bt = vt->element_basic_type(); // The "maskAll" API uses the corresponding integer types for floating-point data. @@ -1418,7 +1418,7 @@ Node* convertFromLongToMaskAll(PhaseGVN* phase, const TypeLong* bits_type, bool } else { con = phase->intcon(con_value); } - Node* res = VectorNode::scalar2vector(con, vlen, maskall_bt, is_mask); + Node* res = VectorNode::scalar2vector(con, vlen, maskall_bt, vt->isa_vectmask() != nullptr); // Convert back to the original floating-point data type. if (is_floating_point_type(bt)) { res = new VectorMaskCastNode(phase->transform(res), vt); @@ -1432,7 +1432,7 @@ Node* VectorLoadMaskNode::Ideal(PhaseGVN* phase, bool can_reshape) { // VectorLoadMask(VectorLongToMask(-1/0)) => Replicate(-1/0) if (in(1)->Opcode() == Op_VectorLongToMask) { const TypeVect* vt = bottom_type()->is_vect(); - Node* res = convertFromLongToMaskAll(phase, in(1)->in(1)->bottom_type()->isa_long(), false, vt); + Node* res = convertFromLongToMaskAll(phase, in(1)->in(1)->bottom_type()->isa_long(), vt); if (res != nullptr) { return res; } @@ -1900,10 +1900,12 @@ Node* VectorMaskCastNode::Identity(PhaseGVN* phase) { // l is -1 or 0. Node* VectorMaskToLongNode::Ideal_MaskAll(PhaseGVN* phase) { Node* in1 = in(1); - // VectorMaskToLong follows a VectorStoreMask if predicate is not supported. + // VectorMaskToLong follows a VectorStoreMask if it doesn't require the mask + // saved with a predicate type. if (in1->Opcode() == Op_VectorStoreMask) { - assert(!in1->in(1)->bottom_type()->isa_vectmask(), "sanity"); - in1 = in1->in(1); + Node* mask = in1->in(1); + assert(!Matcher::mask_op_prefers_predicate(Opcode(), mask->bottom_type()->is_vect()), "sanity"); + in1 = mask; } if (VectorNode::is_all_ones_vector(in1)) { int vlen = in1->bottom_type()->is_vect()->length(); @@ -1960,7 +1962,7 @@ Node* VectorLongToMaskNode::Ideal(PhaseGVN* phase, bool can_reshape) { // VectorLongToMask(-1/0) => MaskAll(-1/0) const TypeLong* bits_type = in(1)->bottom_type()->isa_long(); if (bits_type && is_mask) { - Node* res = convertFromLongToMaskAll(phase, bits_type, true, dst_type); + Node* res = convertFromLongToMaskAll(phase, bits_type, dst_type); if (res != nullptr) { return res; } diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java index 80429ad868a..25ebcc94844 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java @@ -2060,6 +2060,16 @@ public class IRNode { beforeMatchingNameRegex(STORE_VECTOR_SCATTER_MASKED, "StoreVectorScatterMasked"); } + public static final String VECTOR_LOAD_MASK = PREFIX + "VECTOR_LOAD_MASK" + POSTFIX; + static { + beforeMatchingNameRegex(VECTOR_LOAD_MASK, "VectorLoadMask"); + } + + public static final String VECTOR_STORE_MASK = PREFIX + "VECTOR_STORE_MASK" + POSTFIX; + static { + beforeMatchingNameRegex(VECTOR_STORE_MASK, "VectorStoreMask"); + } + public static final String SUB = PREFIX + "SUB" + POSTFIX; static { beforeMatchingNameRegex(SUB, "Sub(I|L|F|D|HF)"); diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/test/IREncodingPrinter.java b/test/hotspot/jtreg/compiler/lib/ir_framework/test/IREncodingPrinter.java index daa2b9765f8..a24cfbd3e37 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/test/IREncodingPrinter.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/test/IREncodingPrinter.java @@ -114,6 +114,7 @@ public class IREncodingPrinter { "asimd", "sve", "sve2", + "svebitperm", "fphp", "asimdhp", // RISCV64 diff --git a/test/hotspot/jtreg/compiler/vectorapi/VectorMaskFromLongTest.java b/test/hotspot/jtreg/compiler/vectorapi/VectorMaskFromLongTest.java index eaa6211efc5..c4feb97ebf3 100644 --- a/test/hotspot/jtreg/compiler/vectorapi/VectorMaskFromLongTest.java +++ b/test/hotspot/jtreg/compiler/vectorapi/VectorMaskFromLongTest.java @@ -22,14 +22,14 @@ */ /* -* @test -* @bug 8356760 8367391 -* @library /test/lib / -* @summary Optimize VectorMask.fromLong for all-true/all-false cases -* @modules jdk.incubator.vector -* -* @run driver compiler.vectorapi.VectorMaskFromLongTest -*/ + * @test + * @bug 8356760 8367391 8367292 + * @library /test/lib / + * @summary IR test for VectorMask.fromLong() + * @modules jdk.incubator.vector + * + * @run driver compiler.vectorapi.VectorMaskFromLongTest + */ package compiler.vectorapi; @@ -47,11 +47,6 @@ public class VectorMaskFromLongTest { static boolean[] mr = new boolean[B_SPECIES.length()]; - @ForceInline - public static void maskFromLongKernel(VectorSpecies species, long inputLong) { - VectorMask.fromLong(species, inputLong).intoArray(mr, 0); - } - @DontInline public static void verifyMaskFromLong(VectorSpecies species, long inputLong) { for (int i = 0; i < species.length(); i++) { @@ -63,9 +58,11 @@ public class VectorMaskFromLongTest { } } + // Tests for "VectorLongToMask(-1/0) => MaskAll(-1/0)" + @ForceInline - public static void testMaskFromLong(VectorSpecies species, long inputLong ) { - maskFromLongKernel(species, inputLong); + public static void fromLongMaskAllKernel(VectorSpecies species, long inputLong ) { + VectorMask.fromLong(species, inputLong).intoArray(mr, 0); verifyMaskFromLong(species, inputLong); } @@ -73,16 +70,16 @@ public class VectorMaskFromLongTest { public static void testMaskFromLongMaskAll(VectorSpecies species) { int vlen = species.length(); long inputLong = 0L; - testMaskFromLong(species, inputLong); + fromLongMaskAllKernel(species, inputLong); inputLong = vlen >= 64 ? 0L : (0x1L << vlen); - testMaskFromLong(species, inputLong); + fromLongMaskAllKernel(species, inputLong); inputLong = -1L; - testMaskFromLong(species, inputLong); + fromLongMaskAllKernel(species, inputLong); inputLong = (-1L >>> (64 - vlen)); - testMaskFromLong(species, inputLong); + fromLongMaskAllKernel(species, inputLong); } @Test @@ -169,102 +166,104 @@ public class VectorMaskFromLongTest { testMaskFromLongMaskAll(D_SPECIES); } - // Tests for general input long values + // Tests for general input long values. The purpose is to test the IRs + // for API VectorMask.fromLong(). To avoid any IR being optimized out by + // compiler, we insert a VectorMask.not() after fromLong(). + + @ForceInline + public static void fromLongGeneralKernel(VectorSpecies species, long inputLong) { + VectorMask.fromLong(species, inputLong).not().intoArray(mr, 0); + verifyMaskFromLong(species, inputLong ^ -1L); + } + + @ForceInline + public static void testMaskFromLongGeneral(VectorSpecies species) { + fromLongGeneralKernel(species, (-1L >>> (64 - species.length())) - 1); + fromLongGeneralKernel(species, (-1L >>> (64 - species.length())) >>> 1); + } @Test - @IR(counts = { IRNode.MASK_ALL, "= 0", + @IR(counts = { IRNode.VECTOR_LOAD_MASK, "= 0", IRNode.VECTOR_LONG_TO_MASK, "= 2" }, - applyIfCPUFeatureOr = { "sve2", "true", "avx512", "true", "rvv", "true" }) - @IR(counts = { IRNode.REPLICATE_B, "= 0", - IRNode.VECTOR_LONG_TO_MASK, "= 0" }, - applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) - @IR(counts = { IRNode.REPLICATE_B, "= 0", + applyIfCPUFeatureOr = { "avx512", "true", "rvv", "true" }) + @IR(counts = { IRNode.VECTOR_LOAD_MASK, "= 2", IRNode.VECTOR_LONG_TO_MASK, "= 2" }, applyIfCPUFeatureAnd = { "avx2", "true", "avx512", "false" }) + @IR(counts = { IRNode.VECTOR_LOAD_MASK, "= 2", + IRNode.VECTOR_LONG_TO_MASK, "= 2" }, + applyIfCPUFeature = { "svebitperm", "true" }) public static void testMaskFromLongByte() { - // Test cases where some but not all bits are set. - testMaskFromLong(B_SPECIES, (-1L >>> (64 - B_SPECIES.length())) - 1); - testMaskFromLong(B_SPECIES, (-1L >>> (64 - B_SPECIES.length())) >>> 1); + testMaskFromLongGeneral(B_SPECIES); } @Test - @IR(counts = { IRNode.MASK_ALL, "= 0", + @IR(counts = { IRNode.VECTOR_LOAD_MASK, "= 0", IRNode.VECTOR_LONG_TO_MASK, "= 2" }, - applyIfCPUFeatureOr = { "sve2", "true", "avx512", "true", "rvv", "true" }) - @IR(counts = { IRNode.REPLICATE_S, "= 0", - IRNode.VECTOR_LONG_TO_MASK, "= 0" }, - applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) - @IR(counts = { IRNode.REPLICATE_S, "= 0", + applyIfCPUFeatureOr = { "avx512", "true", "rvv", "true" }) + @IR(counts = { IRNode.VECTOR_LOAD_MASK, "= 2", IRNode.VECTOR_LONG_TO_MASK, "= 2" }, applyIfCPUFeatureAnd = { "avx2", "true", "avx512", "false" }) + @IR(counts = { IRNode.VECTOR_LOAD_MASK, "= 2", + IRNode.VECTOR_LONG_TO_MASK, "= 2" }, + applyIfCPUFeature = { "svebitperm", "true" }) public static void testMaskFromLongShort() { - // Test cases where some but not all bits are set. - testMaskFromLong(S_SPECIES, (-1L >>> (64 - S_SPECIES.length())) - 1); - testMaskFromLong(S_SPECIES, (-1L >>> (64 - S_SPECIES.length())) >>> 1); + testMaskFromLongGeneral(S_SPECIES); } @Test - @IR(counts = { IRNode.MASK_ALL, "= 0", + @IR(counts = { IRNode.VECTOR_LOAD_MASK, "= 0", IRNode.VECTOR_LONG_TO_MASK, "= 2" }, - applyIfCPUFeatureOr = { "sve2", "true", "avx512", "true", "rvv", "true" }) - @IR(counts = { IRNode.REPLICATE_I, "= 0", - IRNode.VECTOR_LONG_TO_MASK, "= 0" }, - applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) - @IR(counts = { IRNode.REPLICATE_I, "= 0", + applyIfCPUFeatureOr = { "avx512", "true", "rvv", "true" }) + @IR(counts = { IRNode.VECTOR_LOAD_MASK, "= 2", IRNode.VECTOR_LONG_TO_MASK, "= 2" }, applyIfCPUFeatureAnd = { "avx2", "true", "avx512", "false" }) + @IR(counts = { IRNode.VECTOR_LOAD_MASK, "= 2", + IRNode.VECTOR_LONG_TO_MASK, "= 2" }, + applyIfCPUFeature = { "svebitperm", "true" }) public static void testMaskFromLongInt() { - // Test cases where some but not all bits are set. - testMaskFromLong(I_SPECIES, (-1L >>> (64 - I_SPECIES.length())) - 1); - testMaskFromLong(I_SPECIES, (-1L >>> (64 - I_SPECIES.length())) >>> 1); + testMaskFromLongGeneral(I_SPECIES); } @Test - @IR(counts = { IRNode.MASK_ALL, "= 0", + @IR(counts = { IRNode.VECTOR_LOAD_MASK, "= 0", IRNode.VECTOR_LONG_TO_MASK, "= 2" }, - applyIfCPUFeatureOr = { "sve2", "true", "avx512", "true", "rvv", "true" }) - @IR(counts = { IRNode.REPLICATE_L, "= 0", - IRNode.VECTOR_LONG_TO_MASK, "= 0" }, - applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) - @IR(counts = { IRNode.REPLICATE_L, "= 0", + applyIfCPUFeatureOr = { "avx512", "true", "rvv", "true" }) + @IR(counts = { IRNode.VECTOR_LOAD_MASK, "= 2", IRNode.VECTOR_LONG_TO_MASK, "= 2" }, applyIfCPUFeatureAnd = { "avx2", "true", "avx512", "false" }) + @IR(counts = { IRNode.VECTOR_LOAD_MASK, "= 2", + IRNode.VECTOR_LONG_TO_MASK, "= 2" }, + applyIfCPUFeature = { "svebitperm", "true" }) public static void testMaskFromLongLong() { - // Test cases where some but not all bits are set. - testMaskFromLong(L_SPECIES, (-1L >>> (64 - L_SPECIES.length())) - 1); - testMaskFromLong(L_SPECIES, (-1L >>> (64 - L_SPECIES.length())) >>> 1); + testMaskFromLongGeneral(L_SPECIES); } @Test - @IR(counts = { IRNode.MASK_ALL, "= 0", + @IR(counts = { IRNode.VECTOR_LOAD_MASK, "= 0", IRNode.VECTOR_LONG_TO_MASK, "= 2" }, - applyIfCPUFeatureOr = { "sve2", "true", "avx512", "true", "rvv", "true" }) - @IR(counts = { IRNode.REPLICATE_I, "= 0", - IRNode.VECTOR_LONG_TO_MASK, "= 0" }, - applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) - @IR(counts = { IRNode.REPLICATE_I, "= 0", + applyIfCPUFeatureOr = { "avx512", "true", "rvv", "true" }) + @IR(counts = { IRNode.VECTOR_LOAD_MASK, "= 2", IRNode.VECTOR_LONG_TO_MASK, "= 2" }, applyIfCPUFeatureAnd = { "avx2", "true", "avx512", "false" }) + @IR(counts = { IRNode.VECTOR_LOAD_MASK, "= 2", + IRNode.VECTOR_LONG_TO_MASK, "= 2" }, + applyIfCPUFeature = { "svebitperm", "true" }) public static void testMaskFromLongFloat() { - // Test cases where some but not all bits are set. - testMaskFromLong(F_SPECIES, (-1L >>> (64 - F_SPECIES.length())) - 1); - testMaskFromLong(F_SPECIES, (-1L >>> (64 - F_SPECIES.length())) >>> 1); + testMaskFromLongGeneral(F_SPECIES); } @Test - @IR(counts = { IRNode.MASK_ALL, "= 0", + @IR(counts = { IRNode.VECTOR_LOAD_MASK, "= 0", IRNode.VECTOR_LONG_TO_MASK, "= 2" }, - applyIfCPUFeatureOr = { "sve2", "true", "avx512", "true", "rvv", "true" }) - @IR(counts = { IRNode.REPLICATE_L, "= 0", - IRNode.VECTOR_LONG_TO_MASK, "= 0" }, - applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) - @IR(counts = { IRNode.REPLICATE_L, "= 0", + applyIfCPUFeatureOr = { "avx512", "true", "rvv", "true" }) + @IR(counts = { IRNode.VECTOR_LOAD_MASK, "= 2", IRNode.VECTOR_LONG_TO_MASK, "= 2" }, applyIfCPUFeatureAnd = { "avx2", "true", "avx512", "false" }) + @IR(counts = { IRNode.VECTOR_LOAD_MASK, "= 2", + IRNode.VECTOR_LONG_TO_MASK, "= 2" }, + applyIfCPUFeature = { "svebitperm", "true" }) public static void testMaskFromLongDouble() { - // Test cases where some but not all bits are set. - testMaskFromLong(D_SPECIES, (-1L >>> (64 - D_SPECIES.length())) - 1); - testMaskFromLong(D_SPECIES, (-1L >>> (64 - D_SPECIES.length())) >>> 1); + testMaskFromLongGeneral(D_SPECIES); } public static void main(String[] args) { diff --git a/test/hotspot/jtreg/compiler/vectorapi/VectorMaskToLongTest.java b/test/hotspot/jtreg/compiler/vectorapi/VectorMaskToLongTest.java index 3201d593efe..35a5aca966a 100644 --- a/test/hotspot/jtreg/compiler/vectorapi/VectorMaskToLongTest.java +++ b/test/hotspot/jtreg/compiler/vectorapi/VectorMaskToLongTest.java @@ -22,18 +22,19 @@ */ /* -* @test -* @bug 8356760 -* @library /test/lib / -* @summary Optimize VectorMask.fromLong for all-true/all-false cases -* @modules jdk.incubator.vector -* -* @run driver compiler.vectorapi.VectorMaskToLongTest -*/ + * @test + * @bug 8356760 8367292 + * @library /test/lib / + * @summary IR test for VectorMask.toLong() + * @modules jdk.incubator.vector + * + * @run driver compiler.vectorapi.VectorMaskToLongTest + */ package compiler.vectorapi; import compiler.lib.ir_framework.*; +import java.util.Arrays; import jdk.incubator.vector.*; import jdk.test.lib.Asserts; @@ -45,12 +46,21 @@ public class VectorMaskToLongTest { static final VectorSpecies L_SPECIES = LongVector.SPECIES_MAX; static final VectorSpecies D_SPECIES = DoubleVector.SPECIES_MAX; + private static boolean[] m; + + static { + m = new boolean[B_SPECIES.length()]; + Arrays.fill(m, true); + } + @DontInline public static void verifyMaskToLong(VectorSpecies species, long inputLong, long got) { long expected = inputLong & (-1L >>> (64 - species.length())); Asserts.assertEquals(expected, got, "for input long " + inputLong); } + // Tests for "VectorMaskToLong(MaskAll(0/-1)) => ((0/-1) & (-1ULL >> (64 - vlen)))" + @ForceInline public static void testMaskAllToLong(VectorSpecies species) { int vlen = species.length(); @@ -173,12 +183,12 @@ public class VectorMaskToLongTest { @Test @IR(counts = { IRNode.VECTOR_LONG_TO_MASK, "= 0", IRNode.VECTOR_MASK_TO_LONG, "= 0" }, - applyIfCPUFeatureOr = { "sve2", "true", "avx2", "true", "rvv", "true" }) + applyIfCPUFeatureOr = { "svebitperm", "true", "avx2", "true", "rvv", "true" }) @IR(counts = { IRNode.VECTOR_LONG_TO_MASK, "= 0", IRNode.VECTOR_MASK_TO_LONG, "= 1" }, - applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) + applyIfCPUFeatureAnd = { "asimd", "true", "svebitperm", "false" }) public static void testFromLongToLongByte() { - // Test the case where some but not all bits are set. + // Test the case where some but not all bits are set. long inputLong = (-1L >>> (64 - B_SPECIES.length()))-1; long got = VectorMask.fromLong(B_SPECIES, inputLong).toLong(); verifyMaskToLong(B_SPECIES, inputLong, got); @@ -187,10 +197,10 @@ public class VectorMaskToLongTest { @Test @IR(counts = { IRNode.VECTOR_LONG_TO_MASK, "= 0", IRNode.VECTOR_MASK_TO_LONG, "= 0" }, - applyIfCPUFeatureOr = { "sve2", "true", "avx2", "true", "rvv", "true" }) + applyIfCPUFeatureOr = { "svebitperm", "true", "avx2", "true", "rvv", "true" }) @IR(counts = { IRNode.VECTOR_LONG_TO_MASK, "= 0", IRNode.VECTOR_MASK_TO_LONG, "= 1" }, - applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) + applyIfCPUFeatureAnd = { "asimd", "true", "svebitperm", "false" }) public static void testFromLongToLongShort() { // Test the case where some but not all bits are set. long inputLong = (-1L >>> (64 - S_SPECIES.length()))-1; @@ -201,10 +211,10 @@ public class VectorMaskToLongTest { @Test @IR(counts = { IRNode.VECTOR_LONG_TO_MASK, "= 0", IRNode.VECTOR_MASK_TO_LONG, "= 0" }, - applyIfCPUFeatureOr = { "sve2", "true", "avx2", "true", "rvv", "true" }) + applyIfCPUFeatureOr = { "svebitperm", "true", "avx2", "true", "rvv", "true" }) @IR(counts = { IRNode.VECTOR_LONG_TO_MASK, "= 0", IRNode.VECTOR_MASK_TO_LONG, "= 1" }, - applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) + applyIfCPUFeatureAnd = { "asimd", "true", "svebitperm", "false" }) public static void testFromLongToLongInt() { // Test the case where some but not all bits are set. long inputLong = (-1L >>> (64 - I_SPECIES.length()))-1; @@ -215,10 +225,10 @@ public class VectorMaskToLongTest { @Test @IR(counts = { IRNode.VECTOR_LONG_TO_MASK, "= 0", IRNode.VECTOR_MASK_TO_LONG, "= 0" }, - applyIfCPUFeatureOr = { "sve2", "true", "avx2", "true", "rvv", "true" }) + applyIfCPUFeatureOr = { "svebitperm", "true", "avx2", "true", "rvv", "true" }) @IR(counts = { IRNode.VECTOR_LONG_TO_MASK, "= 0", IRNode.VECTOR_MASK_TO_LONG, "= 1" }, - applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) + applyIfCPUFeatureAnd = { "asimd", "true", "svebitperm", "false" }) public static void testFromLongToLongLong() { // Test the case where some but not all bits are set. long inputLong = (-1L >>> (64 - L_SPECIES.length()))-1; @@ -229,10 +239,10 @@ public class VectorMaskToLongTest { @Test @IR(counts = { IRNode.VECTOR_LONG_TO_MASK, "= 1", IRNode.VECTOR_MASK_TO_LONG, "= 1" }, - applyIfCPUFeatureOr = { "sve2", "true", "avx2", "true", "rvv", "true" }) + applyIfCPUFeatureOr = { "svebitperm", "true", "avx2", "true", "rvv", "true" }) @IR(counts = { IRNode.VECTOR_LONG_TO_MASK, "= 0", IRNode.VECTOR_MASK_TO_LONG, "= 1" }, - applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) + applyIfCPUFeatureAnd = { "asimd", "true", "svebitperm", "false" }) public static void testFromLongToLongFloat() { // Test the case where some but not all bits are set. long inputLong = (-1L >>> (64 - F_SPECIES.length()))-1; @@ -243,10 +253,10 @@ public class VectorMaskToLongTest { @Test @IR(counts = { IRNode.VECTOR_LONG_TO_MASK, "= 1", IRNode.VECTOR_MASK_TO_LONG, "= 1" }, - applyIfCPUFeatureOr = { "sve2", "true", "avx2", "true", "rvv", "true" }) + applyIfCPUFeatureOr = { "svebitperm", "true", "avx2", "true", "rvv", "true" }) @IR(counts = { IRNode.VECTOR_LONG_TO_MASK, "= 0", IRNode.VECTOR_MASK_TO_LONG, "= 1" }, - applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) + applyIfCPUFeatureAnd = { "asimd", "true", "svebitperm", "false" }) public static void testFromLongToLongDouble() { // Test the case where some but not all bits are set. long inputLong = (-1L >>> (64 - D_SPECIES.length()))-1; @@ -254,6 +264,100 @@ public class VectorMaskToLongTest { verifyMaskToLong(D_SPECIES, inputLong, got); } + // General cases for VectorMask.toLong(). The main purpose is to test the IRs + // for API VectorMask.toLong(). To avoid the IRs being optimized out by compiler, + // we insert a VectorMask.not() before toLong(). + + @ForceInline + public static void testToLongGeneral(VectorSpecies species) { + long got = VectorMask.fromArray(species, m, 0).not().toLong(); + verifyMaskToLong(species, 0, got); + } + + @Test + @IR(counts = { IRNode.VECTOR_STORE_MASK, "= 0", + IRNode.VECTOR_MASK_TO_LONG, "= 1" }, + applyIfCPUFeatureOr = { "avx512", "true", "rvv", "true" }) + @IR(counts = { IRNode.VECTOR_STORE_MASK, "= 1", + IRNode.VECTOR_MASK_TO_LONG, "= 1" }, + applyIfCPUFeatureAnd = { "avx2", "true", "avx512", "false" }) + @IR(counts = { IRNode.VECTOR_STORE_MASK, "= 1", + IRNode.VECTOR_MASK_TO_LONG, "= 1" }, + applyIfCPUFeature = { "asimd", "true" }) + public static void testToLongByte() { + testToLongGeneral(B_SPECIES); + } + + @Test + @IR(counts = { IRNode.VECTOR_STORE_MASK, "= 0", + IRNode.VECTOR_MASK_TO_LONG, "= 1" }, + applyIfCPUFeatureOr = { "avx512", "true", "rvv", "true" }) + @IR(counts = { IRNode.VECTOR_STORE_MASK, "= 1", + IRNode.VECTOR_MASK_TO_LONG, "= 1" }, + applyIfCPUFeatureAnd = { "avx2", "true", "avx512", "false" }) + @IR(counts = { IRNode.VECTOR_STORE_MASK, "= 1", + IRNode.VECTOR_MASK_TO_LONG, "= 1" }, + applyIfCPUFeature = { "asimd", "true" }) + public static void testToLongShort() { + testToLongGeneral(S_SPECIES); + } + + @Test + @IR(counts = { IRNode.VECTOR_STORE_MASK, "= 0", + IRNode.VECTOR_MASK_TO_LONG, "= 1" }, + applyIfCPUFeatureOr = { "avx512", "true", "rvv", "true" }) + @IR(counts = { IRNode.VECTOR_STORE_MASK, "= 1", + IRNode.VECTOR_MASK_TO_LONG, "= 1" }, + applyIfCPUFeatureAnd = { "avx2", "true", "avx512", "false" }) + @IR(counts = { IRNode.VECTOR_STORE_MASK, "= 1", + IRNode.VECTOR_MASK_TO_LONG, "= 1" }, + applyIfCPUFeature = { "asimd", "true" }) + public static void testToLongInt() { + testToLongGeneral(I_SPECIES); + } + + @Test + @IR(counts = { IRNode.VECTOR_STORE_MASK, "= 0", + IRNode.VECTOR_MASK_TO_LONG, "= 1" }, + applyIfCPUFeatureOr = { "avx512", "true", "rvv", "true" }) + @IR(counts = { IRNode.VECTOR_STORE_MASK, "= 1", + IRNode.VECTOR_MASK_TO_LONG, "= 1" }, + applyIfCPUFeatureAnd = { "avx2", "true", "avx512", "false" }) + @IR(counts = { IRNode.VECTOR_STORE_MASK, "= 1", + IRNode.VECTOR_MASK_TO_LONG, "= 1" }, + applyIfCPUFeature = { "asimd", "true" }) + public static void testToLongLong() { + testToLongGeneral(L_SPECIES); + } + + @Test + @IR(counts = { IRNode.VECTOR_STORE_MASK, "= 0", + IRNode.VECTOR_MASK_TO_LONG, "= 1" }, + applyIfCPUFeatureOr = { "avx512", "true", "rvv", "true" }) + @IR(counts = { IRNode.VECTOR_STORE_MASK, "= 1", + IRNode.VECTOR_MASK_TO_LONG, "= 1" }, + applyIfCPUFeature = { "asimd", "true" }) + @IR(counts = { IRNode.VECTOR_STORE_MASK, "= 1", + IRNode.VECTOR_MASK_TO_LONG, "= 1" }, + applyIfCPUFeatureAnd = { "avx2", "true", "avx512", "false" }) + public static void testToLongFloat() { + testToLongGeneral(F_SPECIES); + } + + @Test + @IR(counts = { IRNode.VECTOR_STORE_MASK, "= 0", + IRNode.VECTOR_MASK_TO_LONG, "= 1" }, + applyIfCPUFeatureOr = { "avx512", "true", "rvv", "true" }) + @IR(counts = { IRNode.VECTOR_STORE_MASK, "= 1", + IRNode.VECTOR_MASK_TO_LONG, "= 1" }, + applyIfCPUFeatureAnd = { "avx2", "true", "avx512", "false" }) + @IR(counts = { IRNode.VECTOR_STORE_MASK, "= 1", + IRNode.VECTOR_MASK_TO_LONG, "= 1" }, + applyIfCPUFeature = { "asimd", "true" }) + public static void testToLongDouble() { + testToLongGeneral(D_SPECIES); + } + public static void main(String[] args) { TestFramework testFramework = new TestFramework(); testFramework.setDefaultWarmup(10000) From b6ba1ac9aa800e01e2235c2b8737ad4670b0a655 Mon Sep 17 00:00:00 2001 From: Yasumasa Suenaga Date: Thu, 13 Nov 2025 04:29:22 +0000 Subject: [PATCH 022/418] 8371093: Assert "section header string table should be loaded" failed on debug VM Reviewed-by: phubner, jsjolen --- src/hotspot/share/utilities/decoder_elf.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/hotspot/share/utilities/decoder_elf.cpp b/src/hotspot/share/utilities/decoder_elf.cpp index 962b572db64..127748b32ed 100644 --- a/src/hotspot/share/utilities/decoder_elf.cpp +++ b/src/hotspot/share/utilities/decoder_elf.cpp @@ -114,6 +114,11 @@ ElfFile* ElfDecoder::get_elf_file(const char* filepath) { file = new (std::nothrow)ElfFile(filepath); if (file != nullptr) { + _decoder_status = file->get_status(); + if (has_error()) { + delete file; + return nullptr; + } if (_opened_elf_files != nullptr) { file->set_next(_opened_elf_files); } From 5f42c7708588db28f9c18bf63462001e99b35ec7 Mon Sep 17 00:00:00 2001 From: Chen Liang Date: Thu, 13 Nov 2025 04:33:00 +0000 Subject: [PATCH 023/418] 8370839: Tests to verify peculiar Proxy dispatching behaviors Reviewed-by: jvernee --- .../lang/reflect/Proxy/BridgeMethodsTest.java | 86 ++++++++ .../Proxy/NonPublicMethodTypeTest.java | 99 ++++++--- .../Proxy/ProtectedObjectMethodsTest.java | 190 ++++++++++++++++++ 3 files changed, 349 insertions(+), 26 deletions(-) create mode 100644 test/jdk/java/lang/reflect/Proxy/BridgeMethodsTest.java create mode 100644 test/jdk/java/lang/reflect/Proxy/ProtectedObjectMethodsTest.java diff --git a/test/jdk/java/lang/reflect/Proxy/BridgeMethodsTest.java b/test/jdk/java/lang/reflect/Proxy/BridgeMethodsTest.java new file mode 100644 index 00000000000..ee3e42a943c --- /dev/null +++ b/test/jdk/java/lang/reflect/Proxy/BridgeMethodsTest.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2025, 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 java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.lang.reflect.UndeclaredThrowableException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.function.Consumer; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/* + * @test + * @bug 8370839 + * @summary Behavior of bridge methods in interfaces + * @run junit BridgeMethodsTest + */ +public class BridgeMethodsTest { + + interface StringCallable extends Callable { + @Override + String call(); // throws no exception + } + + @Test + void testExceptionTypes() throws Throwable { + class MyException extends Exception {} + // This proxy has two distinct methods, even though the + // Java language would treat the first one as overridden: + // Object call() throws Exception; - from Callable + // String call(); - from StringCallable + var instance = Proxy.newProxyInstance(StringCallable.class.getClassLoader(), + new Class[] { StringCallable.class }, (_, _, _) -> { throw new MyException(); }); + // The exception can't be thrown through StringCallable.call which has no throws + var undeclared = assertThrows(UndeclaredThrowableException.class, () -> ((StringCallable) instance).call()); + assertInstanceOf(MyException.class, undeclared.getCause()); + // But it can be thrown through Callable.call which permits Exception + assertThrows(MyException.class, () -> ((Callable) instance).call()); + } + + interface SpecificConsumer extends Consumer { + @Override + void accept(String s); + } + + @Test + @SuppressWarnings("unchecked") + void testMethodObjects() throws Throwable { + List methods = new ArrayList<>(); + // This proxy has two distinct methods, even though the + // Java language would treat the first one as overridden: + // void accept(Object); - from Consumer + // void accept(String); - from SpecificConsumer + var instance = Proxy.newProxyInstance(SpecificConsumer.class.getClassLoader(), + new Class[] { SpecificConsumer.class }, (_, m, _) -> methods.add(m)); + ((Consumer) instance).accept(null); + ((SpecificConsumer) instance).accept(null); + assertEquals(2, methods.size()); + // invocation handler gets different method due to covariant parameter types + assertNotEquals(methods.getFirst(), methods.getLast()); + } +} diff --git a/test/jdk/java/lang/reflect/Proxy/NonPublicMethodTypeTest.java b/test/jdk/java/lang/reflect/Proxy/NonPublicMethodTypeTest.java index 8919dc2f0b2..eb455495bf2 100644 --- a/test/jdk/java/lang/reflect/Proxy/NonPublicMethodTypeTest.java +++ b/test/jdk/java/lang/reflect/Proxy/NonPublicMethodTypeTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, 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,39 +21,86 @@ * questions. */ -/* - * @test - * @bug 8333854 - * @summary Test invoking a method in a proxy interface with package-private - * classes or interfaces in its method type - * @run junit NonPublicMethodTypeTest - */ +import java.lang.reflect.Proxy; +import java.util.concurrent.atomic.AtomicReference; import org.junit.jupiter.api.Test; -import java.lang.reflect.Proxy; +import static org.junit.jupiter.api.Assertions.*; -import static org.junit.jupiter.api.Assertions.assertNotSame; +/* + * @test + * @bug 8333854 8370839 + * @summary Behavior of methods whose signature has package-private + * class or interfaces but the proxy interface is public + * @run junit NonPublicMethodTypeTest + */ +public class NonPublicMethodTypeTest { + // Java language and JVM allow using fields and methods with inaccessible + // classes or interfaces in its signature, as long as the field or method + // is accessible and its declaring class or interface is accessible. + // Such inaccessible classes and interfaces are treated as if an arbitrary + // subtype of their accessible types, or an arbitrary supertype of their + // accessible subtypes. + // java.lang.invoke is stricter - MethodType constant pool entry resolution + // for such signatures fail, so they can't be used for MethodHandle or indy. + enum Internal { INSTANCE } -public final class NonPublicMethodTypeTest { - interface NonPublicWorker { - void work(); - } - - public interface PublicWorkable { - void accept(NonPublicWorker worker); + public interface InternalParameter { + void call(Internal parameter); } @Test - public void test() { - PublicWorkable proxy = (PublicWorkable) Proxy.newProxyInstance( - NonPublicMethodTypeTest.class.getClassLoader(), - new Class[] {PublicWorkable.class}, - (_, _, _) -> null); - assertNotSame(NonPublicWorker.class.getPackage(), - proxy.getClass().getPackage(), + void testNonPublicParameter() throws Throwable { + // Creation should be successful + // 8333854 - BSM usage fails for looking up such methods + InternalParameter instance = (InternalParameter) Proxy.newProxyInstance( + InternalParameter.class.getClassLoader(), + new Class[] { InternalParameter.class }, + (_, _, _) -> null); + assertNotSame(Internal.class.getPackage(), + instance.getClass().getPackage(), "Proxy class should not be able to access method parameter " + - "NonPublic type's package"); - proxy.accept(() -> {}); // Call should not fail + "Internal class's package"); + // Calls should be always successful + instance.call(null); + instance.call(Internal.INSTANCE); + } + + public interface InternalReturn { + Internal call(); + } + + @Test + void testNonPublicReturn() throws Throwable { + AtomicReference returnValue = new AtomicReference<>(); + // Creation should be successful + // A lot of annotation interfaces are implemented by such proxy classes, + // due to presence of package-private annotation interface or enum-typed + // elements in public annotation interfaces. + InternalReturn instance = (InternalReturn) Proxy.newProxyInstance( + InternalReturn.class.getClassLoader(), + new Class[] { InternalReturn.class }, + (_, _, _) -> returnValue.get()); + assertNotSame(Internal.class.getPackage(), + instance.getClass().getPackage(), + "Proxy class should not be able to access method parameter " + + "Internal class's package"); + + // The generated call() implementation is as follows: + // aload0, getfield Proxy.h, aload 0, getstatic (Method), aconst_null, + // invokevirtual InvocationHandler::invoke(Object, Method, Object[])Object, + // checkcast Internal.class, areturn + // In this bytecode, checkcast Internal.class will fail with a + // IllegalAccessError as a result of resolution of Internal.class + // if and only if the incoming reference is non-null. + + // checkcast does not perform access check for null + returnValue.set(null); + instance.call(); + // checkcast fails - proxy class cannot access the return type + // See JDK-8349716 + returnValue.set(Internal.INSTANCE); + assertThrows(IllegalAccessError.class, instance::call); } } diff --git a/test/jdk/java/lang/reflect/Proxy/ProtectedObjectMethodsTest.java b/test/jdk/java/lang/reflect/Proxy/ProtectedObjectMethodsTest.java new file mode 100644 index 00000000000..ec01c58b8f0 --- /dev/null +++ b/test/jdk/java/lang/reflect/Proxy/ProtectedObjectMethodsTest.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2025, 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 java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Proxy; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/* + * @test + * @bug 8370839 + * @summary Behavior of protected methods in java.lang.Object + * @modules java.base/java.lang:+open + * @run junit ProtectedObjectMethodsTest + */ +public class ProtectedObjectMethodsTest { + + static final MethodHandle OBJECT_CLONE; + static final MethodHandle OBJECT_FINALIZE; + + static { + try { + var lookup = MethodHandles.privateLookupIn(Object.class, MethodHandles.lookup()); + OBJECT_CLONE = lookup.findVirtual(Object.class, "clone", MethodType.methodType(Object.class)); + OBJECT_FINALIZE = lookup.findVirtual(Object.class, "finalize", MethodType.methodType(void.class)); + } catch (ReflectiveOperationException ex) { + throw new ExceptionInInitializerError(ex); + } + } + + interface FakeClone { + // This method is not related to Object::clone in the JVM, but most + // implementations in the Java Language override Object::clone as + // covariant overrides + FakeClone clone(); + } + + interface TrueClone { + // This method is identical to Object::clone in the JVM, that calls + // to Object::clone can always call an implementation of this method + // if it exists + Object clone(); + } + + interface PrimitiveClone { + // This method is not related to Object::clone in the JVM, but it can't + // be implemented in the Java Language unless using a lambda expression, + // due to the Java language covariant override restrictions. + int clone(); + } + + // Does not duplicate with Object::clone so it is not proxied + @Test + void testDistinctClone() throws Throwable { + { + // This proxy declares FakeClone clone(); which cannot be + // invoked through Object::clone. + var fake = (FakeClone) Proxy.newProxyInstance(FakeClone.class.getClassLoader(), new Class[] { FakeClone.class }, (p, _, _) -> p); + assertSame(fake, fake.clone()); + // Verify the default Object::clone behavior (this is not Cloneable) + assertThrows(CloneNotSupportedException.class, () -> { + var _ = (Object) OBJECT_CLONE.invoke((Object) fake); + }); + } + + { + // This proxy declares FakeClone clone(); which cannot be + // invoked through Object::clone. + var fake = (FakeClone) Proxy.newProxyInstance(FakeClone.class.getClassLoader(), new Class[] { FakeClone.class, Cloneable.class }, (p, _, _) -> p); + assertSame(fake, fake.clone()); + // Verify the default Object::clone behavior (this is Cloneable) + var fakeClone = (Object) OBJECT_CLONE.invoke((Object) fake); + assertNotSame(fake, fakeClone); + assertSame(fake.getClass(), fakeClone.getClass()); + assertSame(fakeClone, ((FakeClone) fakeClone).clone()); + } + + { + // This proxy declares int clone(); which cannot be + // invoked through Object::clone. + var instance = (PrimitiveClone) Proxy.newProxyInstance(PrimitiveClone.class.getClassLoader(), new Class[] { PrimitiveClone.class }, (_, _, _) -> 42); + assertEquals(42, instance.clone()); + // Verify the default Object::clone behavior (this is not Cloneable) + assertThrows(CloneNotSupportedException.class, () -> { + var _ = (Object) OBJECT_CLONE.invoke((Object) instance); + }); + } + + { + // This proxy declares int clone(); which cannot be + // invoked through Object::clone + var instance = (PrimitiveClone) Proxy.newProxyInstance(PrimitiveClone.class.getClassLoader(), new Class[] { PrimitiveClone.class, Cloneable.class }, (_, _, _) -> 76); + assertEquals(76, instance.clone()); + // Verify the default Object::clone behavior (this is Cloneable) + var clone = (Object) OBJECT_CLONE.invoke((Object) instance); + assertNotSame(instance, clone); + assertSame(instance.getClass(), clone.getClass()); + assertEquals(76, ((PrimitiveClone) clone).clone()); + } + } + + // Duplicates with Object::clone so it is proxied + @Test + void testDuplicateClone() throws Throwable { + { + // This proxy declares Object clone();, which + // accidentally overrides Object::clone + var instance = (TrueClone) Proxy.newProxyInstance(TrueClone.class.getClassLoader(), new Class[] { TrueClone.class }, (p, _, _) -> p); + assertSame(instance, instance.clone()); + // Verify Object::clone is overridden + assertSame(instance, (Object) OBJECT_CLONE.invoke((Object) instance)); + } + + { + // This proxy declares Object clone(); and FakeClone clone();. + // They are considered duplicate methods and dispatched equivalently. + // Object clone() accidentally overrides Object::clone, so now + // FakeClone clone() can be called through Object::clone + var instance = Proxy.newProxyInstance(TrueClone.class.getClassLoader(), new Class[] { TrueClone.class, FakeClone.class }, (p, _, _) -> p); + assertSame(instance, ((FakeClone) instance).clone()); + assertSame(instance, ((TrueClone) instance).clone()); + // Verify Object::clone is overridden + assertSame(instance, (Object) OBJECT_CLONE.invoke((Object) instance)); + } + } + + interface FalseFinalize { + // This method is not related to Object::finalize in the JVM, but it can't + // be implemented in the Java Language unless using a lambda expression, + // due to the Java language covariant override restrictions. + int finalize(); + } + + interface TrueFinalize { + // This method is identical to Object::finalize in the JVM, that calls + // to Object::finalize can always call an implementation of this method + // if it exists + void finalize(); + } + + @Test + void testDistinctFinalize() throws Throwable { + AtomicInteger invokeCount = new AtomicInteger(); + // This proxy declares int finalize(), which cannot be + // invoked through Object::finalize. + var instance = Proxy.newProxyInstance(FalseFinalize.class.getClassLoader(), new Class[] { FalseFinalize.class }, (_, _, _) -> invokeCount.incrementAndGet()); + // Verify the default Object::finalize behavior + OBJECT_FINALIZE.invoke(instance); + assertEquals(0, invokeCount.get()); + assertEquals(1, ((FalseFinalize) instance).finalize()); + } + + @Test + void testDuplicateFinalize() throws Throwable { + AtomicInteger invokeCount = new AtomicInteger(); + // This proxy declares void finalize(), which can be + // invoked through Object::finalize. + var instance = Proxy.newProxyInstance(TrueFinalize.class.getClassLoader(), new Class[] { TrueFinalize.class }, (_, _, _) -> invokeCount.incrementAndGet()); + // Verify the overridden Object::finalize behavior + OBJECT_FINALIZE.invoke(instance); + assertEquals(1, invokeCount.get()); + ((TrueFinalize) instance).finalize(); + assertEquals(2, invokeCount.get()); + } +} From d91480b9b0f85aca8d9dba615ae5a27f26ce5fee Mon Sep 17 00:00:00 2001 From: Axel Boldt-Christmas Date: Thu, 13 Nov 2025 06:17:16 +0000 Subject: [PATCH 024/418] 8371675: ZGC: Remove leftover X VMOp symbols Reviewed-by: jsikstro, stefank, tschatzl --- src/hotspot/share/runtime/vmOperation.hpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/hotspot/share/runtime/vmOperation.hpp b/src/hotspot/share/runtime/vmOperation.hpp index ada5014beee..66bf5dc2e27 100644 --- a/src/hotspot/share/runtime/vmOperation.hpp +++ b/src/hotspot/share/runtime/vmOperation.hpp @@ -69,10 +69,6 @@ template(ZRelocateStartYoung) \ template(ZRendezvousGCThreads) \ template(ZVerifyOld) \ - template(XMarkStart) \ - template(XMarkEnd) \ - template(XRelocateStart) \ - template(XVerify) \ template(HandshakeAllThreads) \ template(PopulateDumpSharedSpace) \ template(JNIFunctionTableCopier) \ From 42aecc4070e952ed6308ebefaf716e35fed2f929 Mon Sep 17 00:00:00 2001 From: Axel Boldt-Christmas Date: Thu, 13 Nov 2025 06:17:35 +0000 Subject: [PATCH 025/418] 8371680: JVMTI: Remove unused VMOp type JvmtiPostObjectFree Reviewed-by: stefank, lmesnik --- src/hotspot/share/runtime/vmOperation.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/hotspot/share/runtime/vmOperation.hpp b/src/hotspot/share/runtime/vmOperation.hpp index 66bf5dc2e27..5781b133cdb 100644 --- a/src/hotspot/share/runtime/vmOperation.hpp +++ b/src/hotspot/share/runtime/vmOperation.hpp @@ -110,7 +110,6 @@ template(GTestExecuteAtSafepoint) \ template(GTestStopSafepoint) \ template(JFROldObject) \ - template(JvmtiPostObjectFree) \ template(RendezvousGCThreads) \ template(JFRInitializeCPUTimeSampler) \ template(JFRTerminateCPUTimeSampler) \ From 279f39f14a6329d0147613edc3836b7d6d043186 Mon Sep 17 00:00:00 2001 From: Axel Boldt-Christmas Date: Thu, 13 Nov 2025 06:17:52 +0000 Subject: [PATCH 026/418] 8371681: Remove unused VMOp type CollectForCodeCacheAllocation Reviewed-by: stefank, ayang, tschatzl --- src/hotspot/share/runtime/vmOperation.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/hotspot/share/runtime/vmOperation.hpp b/src/hotspot/share/runtime/vmOperation.hpp index 5781b133cdb..5140d0401fb 100644 --- a/src/hotspot/share/runtime/vmOperation.hpp +++ b/src/hotspot/share/runtime/vmOperation.hpp @@ -48,7 +48,6 @@ template(Verify) \ template(HeapDumper) \ template(CollectForMetadataAllocation) \ - template(CollectForCodeCacheAllocation) \ template(GC_HeapInspection) \ template(SerialCollectForAllocation) \ template(SerialGCCollect) \ From 436b3357e9791f6acb2673e2ac96d33c6a2782e6 Mon Sep 17 00:00:00 2001 From: Shawn M Emery Date: Thu, 13 Nov 2025 08:10:12 +0000 Subject: [PATCH 027/418] 8371450: AES performance improvements for key schedule generation Reviewed-by: valeriep, jnimeh --- .../com/sun/crypto/provider/AES_Crypt.java | 56 ++++++++++--------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/src/java.base/share/classes/com/sun/crypto/provider/AES_Crypt.java b/src/java.base/share/classes/com/sun/crypto/provider/AES_Crypt.java index 6e3e6144aff..19dceae01af 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/AES_Crypt.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/AES_Crypt.java @@ -941,8 +941,9 @@ final class AES_Crypt extends SymmetricCipher { * Generate the cipher's round keys as outlined in section 5.2 of the spec. * * @param key [in] the symmetric key byte array. + * @param rounds [in] the number of rounds for generating the round keys. * - * @return w the cipher round keys. + * @return the cipher round keys. */ private static int[] genRoundKeys(byte[] key, int rounds) { int wLen = WB * (rounds + 1); @@ -970,53 +971,58 @@ final class AES_Crypt extends SymmetricCipher { /** * Generate the inverse cipher round keys. * - * @return w1 the inverse cipher round keys. + * @param w [in] the targeted word for substitution. + * @param rounds [in] the number of rounds for generating the round keys. + * + * @return the inverse cipher round keys. */ private static int[] genInvRoundKeys(int[] w, int rounds) { - int kLen = w.length;; - int[] temp = new int[WB]; - int[] dw = new int[kLen]; + int[] dw = new int[w.length]; // Intrinsics requires the inverse key expansion to be reverse order // except for the first and last round key as the first two round keys // are without a mix column transform. for (int i = 1; i < rounds; i++) { - System.arraycopy(w, i * WB, temp, 0, WB); - temp[0] = TMI0[temp[0] >>> 24] ^ TMI1[(temp[0] >> 16) & 0xFF] - ^ TMI2[(temp[0] >> 8) & 0xFF] ^ TMI3[temp[0] & 0xFF]; - temp[1] = TMI0[temp[1] >>> 24] ^ TMI1[(temp[1] >> 16) & 0xFF] - ^ TMI2[(temp[1] >> 8) & 0xFF] ^ TMI3[temp[1] & 0xFF]; - temp[2] = TMI0[temp[2] >>> 24] ^ TMI1[(temp[2] >> 16) & 0xFF] - ^ TMI2[(temp[2] >> 8) & 0xFF] ^ TMI3[temp[2] & 0xFF]; - temp[3] = TMI0[temp[3] >>> 24] ^ TMI1[(temp[3] >> 16) & 0xFF] - ^ TMI2[(temp[3] >> 8) & 0xFF] ^ TMI3[temp[3] & 0xFF]; - System.arraycopy(temp, 0, dw, kLen - (i * WB), WB); + int widx = i * WB; + int idx = w.length - widx; + + dw[idx] = TMI0[w[widx] >>> 24] ^ TMI1[(w[widx] >> 16) & 0xFF] + ^ TMI2[(w[widx] >> 8) & 0xFF] ^ TMI3[w[widx] & 0xFF]; + dw[idx + 1] = TMI0[w[widx + 1] >>> 24] + ^ TMI1[(w[widx + 1] >> 16) & 0xFF] + ^ TMI2[(w[widx + 1] >> 8) & 0xFF] + ^ TMI3[w[widx + 1] & 0xFF]; + dw[idx + 2] = TMI0[w[widx + 2] >>> 24] + ^ TMI1[(w[widx + 2] >> 16) & 0xFF] + ^ TMI2[(w[widx + 2] >> 8) & 0xFF] + ^ TMI3[w[widx + 2] & 0xFF]; + dw[idx + 3] = TMI0[w[widx + 3] >>> 24] + ^ TMI1[(w[widx + 3] >> 16) & 0xFF] + ^ TMI2[(w[widx + 3] >> 8) & 0xFF] + ^ TMI3[w[widx + 3] & 0xFF]; } - System.arraycopy(w, kLen - WB, dw, WB, WB); + System.arraycopy(w, w.length - WB, dw, WB, WB); System.arraycopy(w, 0, dw, 0, WB); - Arrays.fill(temp, 0); return dw; } /** - * Subtitute the word as a step of key expansion. + * Substitute the word as a step of key expansion. * - * @param state [in] the targeted word for substituion. - * @param sub [in] the substitute table for cipher and inverse cipher. + * @param word [in] the targeted word for substitution. * * @return the substituted word. */ private static int subWord(int word) { - byte b0 = (byte) (word >>> 24); - byte b1 = (byte) ((word >> 16) & 0xFF); - byte b2 = (byte) ((word >> 8) & 0xFF); - byte b3 = (byte) (word & 0xFF); + byte b0 = (byte) (word >> 24); + byte b1 = (byte) (word >> 16); + byte b2 = (byte) (word >> 8); return ((SBOX[(b0 & 0xF0) >> 4][b0 & 0x0F] & 0xFF) << 24) | ((SBOX[(b1 & 0xF0) >> 4][b1 & 0x0F] & 0xFF) << 16) | ((SBOX[(b2 & 0xF0) >> 4][b2 & 0x0F] & 0xFF) << 8) - | (SBOX[(b3 & 0xF0) >> 4][b3 & 0x0F] & 0xFF); + | (SBOX[(word & 0xF0) >> 4][word & 0x0F] & 0xFF); } /** From 795ec5c1e90309bc008acb28cfe0ce039dabcb8f Mon Sep 17 00:00:00 2001 From: Kim Barrett Date: Thu, 13 Nov 2025 08:33:15 +0000 Subject: [PATCH 028/418] 8370333: hotspot-unit-tests.md specifies wrong directory structure for tests Reviewed-by: stefank, ayang --- doc/hotspot-unit-tests.html | 12 ++++++------ doc/hotspot-unit-tests.md | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/hotspot-unit-tests.html b/doc/hotspot-unit-tests.html index b3700abb3ac..5cc424322f9 100644 --- a/doc/hotspot-unit-tests.html +++ b/doc/hotspot-unit-tests.html @@ -305,11 +305,11 @@ recognize your tests.

the product.

  • All unit tests for a class from foo/bar/baz.cpp -should be placed foo/bar/test_baz.cpp in -hotspot/test/native/ directory. Having all tests for a -class in one file is a common practice for unit tests, it helps to see -all existing tests at once, share functions and/or resources without -losing encapsulation.

  • +should be placed foo/bar/test_baz.cpp in the +test/hotspot/gtest/ directory. Having all tests for a class +in one file is a common practice for unit tests, it helps to see all +existing tests at once, share functions and/or resources without losing +encapsulation.

  • For tests which test more than one class, directory hierarchy should be the same as product hierarchy, and file name should reflect the name of the tested subsystem/functionality. For example, if a @@ -319,7 +319,7 @@ placed in gc/g1 directory.

  • Please note that framework prepends directory name to a test group name. For example, if TEST(foo, check_this) and TEST(bar, check_that) are defined in -hotspot/test/native/gc/shared/test_foo.cpp file, they will +test/hotspot/gtest/gc/shared/test_foo.cpp file, they will be reported as gc/shared/foo::check_this and gc/shared/bar::check_that.

    Test names

    diff --git a/doc/hotspot-unit-tests.md b/doc/hotspot-unit-tests.md index 69a95307109..f59e6084910 100644 --- a/doc/hotspot-unit-tests.md +++ b/doc/hotspot-unit-tests.md @@ -241,7 +241,7 @@ recognize your tests. Test file location should reflect a location of the tested part of the product. * All unit tests for a class from `foo/bar/baz.cpp` should be placed -`foo/bar/test_baz.cpp` in `hotspot/test/native/` directory. Having all +`foo/bar/test_baz.cpp` in the `test/hotspot/gtest/` directory. Having all tests for a class in one file is a common practice for unit tests, it helps to see all existing tests at once, share functions and/or resources without losing encapsulation. @@ -254,7 +254,7 @@ sub-system under tests belongs to `gc/g1`, tests should be placed in Please note that framework prepends directory name to a test group name. For example, if `TEST(foo, check_this)` and `TEST(bar, check_that)` -are defined in `hotspot/test/native/gc/shared/test_foo.cpp` file, they +are defined in `test/hotspot/gtest/gc/shared/test_foo.cpp` file, they will be reported as `gc/shared/foo::check_this` and `gc/shared/bar::check_that`. From 10220ed06ea452083693406113107484fce40275 Mon Sep 17 00:00:00 2001 From: Kim Barrett Date: Thu, 13 Nov 2025 08:43:59 +0000 Subject: [PATCH 029/418] 8367013: Add Atomic to package/replace idiom of volatile var plus AtomicAccess:: operations Reviewed-by: stefank, aboldtch, jsjolen --- .../shared/stringdedup/stringDedupTable.cpp | 34 +- .../shared/stringdedup/stringDedupTable.hpp | 9 +- src/hotspot/share/runtime/atomic.hpp | 546 +++++++++++++++ .../share/utilities/globalDefinitions.hpp | 8 + .../utilities/singleWriterSynchronizer.cpp | 22 +- .../utilities/singleWriterSynchronizer.hpp | 16 +- test/hotspot/gtest/runtime/test_atomic.cpp | 640 ++++++++++++++++++ 7 files changed, 1236 insertions(+), 39 deletions(-) create mode 100644 src/hotspot/share/runtime/atomic.hpp create mode 100644 test/hotspot/gtest/runtime/test_atomic.cpp diff --git a/src/hotspot/share/gc/shared/stringdedup/stringDedupTable.cpp b/src/hotspot/share/gc/shared/stringdedup/stringDedupTable.cpp index cfa276c0de0..6682993766d 100644 --- a/src/hotspot/share/gc/shared/stringdedup/stringDedupTable.cpp +++ b/src/hotspot/share/gc/shared/stringdedup/stringDedupTable.cpp @@ -245,20 +245,20 @@ void StringDedup::Table::num_dead_callback(size_t num_dead) { // Lock while modifying dead count and state. MonitorLocker ml(StringDedup_lock, Mutex::_no_safepoint_check_flag); - switch (AtomicAccess::load(&_dead_state)) { + switch (_dead_state.load_relaxed()) { case DeadState::good: - AtomicAccess::store(&_dead_count, num_dead); + _dead_count.store_relaxed(num_dead); break; case DeadState::wait1: // Set count first, so dedup thread gets this or a later value if it // sees the good state. - AtomicAccess::store(&_dead_count, num_dead); - AtomicAccess::release_store(&_dead_state, DeadState::good); + _dead_count.store_relaxed(num_dead); + _dead_state.release_store(DeadState::good); break; case DeadState::wait2: - AtomicAccess::release_store(&_dead_state, DeadState::wait1); + _dead_state.release_store(DeadState::wait1); break; case DeadState::cleaning: @@ -423,8 +423,10 @@ size_t StringDedup::Table::_number_of_entries = 0; size_t StringDedup::Table::_grow_threshold; StringDedup::Table::CleanupState* StringDedup::Table::_cleanup_state = nullptr; bool StringDedup::Table::_need_bucket_shrinking = false; -volatile size_t StringDedup::Table::_dead_count = 0; -volatile StringDedup::Table::DeadState StringDedup::Table::_dead_state = DeadState::good; +Atomic StringDedup::Table::_dead_count{}; + +Atomic +StringDedup::Table::_dead_state{DeadState::good}; void StringDedup::Table::initialize_storage() { assert(_table_storage == nullptr, "storage already created"); @@ -477,19 +479,19 @@ void StringDedup::Table::add(TableValue tv, uint hash_code) { } bool StringDedup::Table::is_dead_count_good_acquire() { - return AtomicAccess::load_acquire(&_dead_state) == DeadState::good; + return _dead_state.load_acquire() == DeadState::good; } // Should be consistent with cleanup_start_if_needed. bool StringDedup::Table::is_grow_needed() { return is_dead_count_good_acquire() && - ((_number_of_entries - AtomicAccess::load(&_dead_count)) > _grow_threshold); + ((_number_of_entries - _dead_count.load_relaxed()) > _grow_threshold); } // Should be consistent with cleanup_start_if_needed. bool StringDedup::Table::is_dead_entry_removal_needed() { return is_dead_count_good_acquire() && - Config::should_cleanup_table(_number_of_entries, AtomicAccess::load(&_dead_count)); + Config::should_cleanup_table(_number_of_entries, _dead_count.load_relaxed()); } StringDedup::Table::TableValue @@ -651,7 +653,7 @@ bool StringDedup::Table::cleanup_start_if_needed(bool grow_only, bool force) { // If dead count is good then we can read it once and use it below // without needing any locking. The recorded count could increase // after the read, but that's okay. - size_t dead_count = AtomicAccess::load(&_dead_count); + size_t dead_count = _dead_count.load_relaxed(); // This assertion depends on dead state tracking. Otherwise, concurrent // reference processing could detect some, but a cleanup operation could // remove them before they are reported. @@ -675,8 +677,8 @@ bool StringDedup::Table::cleanup_start_if_needed(bool grow_only, bool force) { void StringDedup::Table::set_dead_state_cleaning() { MutexLocker ml(StringDedup_lock, Mutex::_no_safepoint_check_flag); - AtomicAccess::store(&_dead_count, size_t(0)); - AtomicAccess::store(&_dead_state, DeadState::cleaning); + _dead_count.store_relaxed(0); + _dead_state.store_relaxed(DeadState::cleaning); } bool StringDedup::Table::start_resizer(bool grow_only, size_t number_of_entries) { @@ -710,7 +712,7 @@ void StringDedup::Table::cleanup_end() { delete _cleanup_state; _cleanup_state = nullptr; MutexLocker ml(StringDedup_lock, Mutex::_no_safepoint_check_flag); - AtomicAccess::store(&_dead_state, DeadState::wait2); + _dead_state.store_relaxed(DeadState::wait2); } void StringDedup::Table::verify() { @@ -732,8 +734,8 @@ void StringDedup::Table::log_statistics() { int dead_state; { MutexLocker ml(StringDedup_lock, Mutex::_no_safepoint_check_flag); - dead_count = _dead_count; - dead_state = static_cast(_dead_state); + dead_count = _dead_count.load_relaxed(); + dead_state = static_cast(_dead_state.load_relaxed()); } log_debug(stringdedup)("Table: %zu values in %zu buckets, %zu dead (%d)", _number_of_entries, _number_of_buckets, diff --git a/src/hotspot/share/gc/shared/stringdedup/stringDedupTable.hpp b/src/hotspot/share/gc/shared/stringdedup/stringDedupTable.hpp index a163319e84c..4c57339f2d5 100644 --- a/src/hotspot/share/gc/shared/stringdedup/stringDedupTable.hpp +++ b/src/hotspot/share/gc/shared/stringdedup/stringDedupTable.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -30,6 +30,7 @@ #include "memory/allStatic.hpp" #include "oops/typeArrayOop.hpp" #include "oops/weakHandle.hpp" +#include "runtime/atomic.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/macros.hpp" @@ -86,9 +87,9 @@ private: static CleanupState* _cleanup_state; static bool _need_bucket_shrinking; // These are always written while holding StringDedup_lock, but may be - // read by the dedup thread without holding the lock lock. - static volatile size_t _dead_count; - static volatile DeadState _dead_state; + // read by the dedup thread without holding the lock. + static Atomic _dead_count; + static Atomic _dead_state; static uint compute_hash(typeArrayOop obj); static size_t hash_to_index(uint hash_code); diff --git a/src/hotspot/share/runtime/atomic.hpp b/src/hotspot/share/runtime/atomic.hpp new file mode 100644 index 00000000000..5b4d7d8659f --- /dev/null +++ b/src/hotspot/share/runtime/atomic.hpp @@ -0,0 +1,546 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_RUNTIME_ATOMIC_HPP +#define SHARE_RUNTIME_ATOMIC_HPP + +#include "cppstdlib/type_traits.hpp" +#include "metaprogramming/enableIf.hpp" +#include "metaprogramming/primitiveConversions.hpp" +#include "runtime/atomicAccess.hpp" +#include "utilities/globalDefinitions.hpp" + +// Atomic is used to declare a variable of type T with atomic access. +// +// The following value types T are supported: +// +// (1) Integers with sizeof the same as sizeof int32_t or int64_t. These are +// referred to as atomic integers below. +// +// (2) Integers with sizeof 1, including bool. These are referred to as atomic +// bytes below. +// +// (3) Pointers. These are referred to as atomic pointers below. +// +// (4) Types with a PrimitiveValues::Translate definition. These are referred +// to as atomic translated types below. The atomic value for the associated +// decayed type is referred to as the atomic decayed type. +// +// The interface provided by an Atomic depends on the value type. +// +// If T is the value type, v is an Atomic, x and y are instances of T, i is +// an integer, and o is an atomic_memory_order, then: +// +// (1) All Atomic types provide +// +// nested types: +// ValueType -> T +// +// special functions: +// explicit constructor(T) +// noncopyable +// destructor +// +// static member functions: +// value_offset_in_bytes() -> int // constexpr +// value_size_in_bytes() -> int // constexpr +// These provide the compiler and the like with direct access to the +// value field. They shouldn't be used directly to bypass normal access. +// +// member functions: +// v.load_relaxed() -> T +// v.load_acquire() -> T +// v.store_relaxed(x) -> void +// v.release_store(x) -> void +// v.release_store_fence(x) -> void +// v.compare_exchange(x, y [, o]) -> T +// +// (2) All atomic types are default constructible. +// +// Default construction of an atomic integer or atomic byte initializes the +// value to zero. Default construction of an atomic pointer initializes the +// value to null. +// +// If the value type of an atomic translated type is default constructible, +// then default construction of the atomic translated type will initialize the +// value to a default constructed object of the value type. Otherwise, the +// value will be initialized as if by translating the value that would be +// provided by default constructing an atomic type for the value type's +// decayed type. + +// (3) Atomic pointers and atomic integers additionally provide +// +// member functions: +// v.exchange(x [, o]) -> T +// v.add_then_fetch(i [, o]) -> T +// v.sub_then_fetch(i [, o]) -> T +// v.fetch_then_add(i [, o]) -> T +// v.fetch_then_sub(i [, o]) -> T +// +// sizeof(i) must not exceed sizeof(T). For atomic integers, both T and the +// type of i must be signed, or both must be unsigned. Atomic pointers perform +// element arithmetic. +// +// (4) An atomic translated type additionally provides the exchange +// function if its associated atomic decayed type provides that function. +// +// (5) Atomic integers additionally provide +// +// member functions: +// v.and_then_fetch(x [, o]) -> T +// v.or_then_fetch(x [, o]) -> T +// v.xor_then_fetch(x [, o]) -> T +// v.fetch_then_and(x [, o]) -> T +// v.fetch_then_or(x [, o]) -> T +// v.fetch_then_xor(x [, o]) -> T +// +// (6) Atomic pointers additionally provide +// +// nested types: +// ElementType -> std::remove_pointer_t +// +// Some of the function names provided by (some variants of) Atomic differ +// from the corresponding functions provided by the AtomicAccess class. In +// some cases this is done for regularity; there are some inconsistencies in +// the AtomicAccess names. Some of the naming choices are also to make them +// stand out a little more when used in surrounding non-atomic code. Without +// the "AtomicAccess::" qualifier, some of those names are easily overlooked. +// +// Atomic bytes don't provide exchange(). This is because that operation +// hasn't been implemented for 1 byte values. That could be changed if needed. +// +// Atomic for 2 byte integers is not supported. This is because atomic +// operations of that size have not been implemented. There haven't been +// required use-cases. Many platforms don't provide hardware support. +// +// Atomic translated types don't provide the full interface of the associated +// atomic decayed type. They could do so, perhaps under the control of an +// associated type trait. +// +// Atomic is not intended to be anything approaching a drop-in replacement +// for std::atomic. Rather, it's wrapping up a long-established HotSpot +// idiom in a tidier and more rigorous package. Some of the differences from +// std::atomic include +// +// * Atomic supports a much more limited set of value types. +// +// * All supported Atomic types are "lock free", so the standard mechanisms +// for testing for that are not provided. (There might have been some types on +// some platforms that used a lock long-ago, but that's no longer the case.) +// +// * Rather than load and store operations with a memory order parameter, +// Atomic provides load_relaxed(), load_acquire(), release_store(), +// store_relaxed(), and release_store_fence() operations. +// +// * Atomic doesn't provide operator overloads that perform various +// operations with sequentially consistent ordering semantics. The rationale +// for not providing these is similar to that for having different (often +// longer) names for some operations than the corresponding AtomicAccess +// functions. + +// Implementation support for Atomic. +class AtomicImpl { + enum class Category { + Integer, + Byte, + Pointer, + Translated + }; + +#if defined(__GNUC__) && !defined(__clang__) + // Workaround for gcc bug. Make category() public, else we get this error + // error: 'static constexpr AtomicImpl::Category AtomicImpl::category() + // [with T = unsigned int]' is private within this context + // The only reference is the default template parameter value in the Atomic + // class a couple lines below, in this same class! + // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=122098 +public: +#endif + // Selection of Atomic category, based on T. + template + static constexpr Category category(); +private: + + // Helper base classes, providing various parts of the APIs. + template class CommonCore; + template class SupportsExchange; + template class SupportsArithmetic; + + // Support conditional exchange() for atomic translated types. + template class HasExchange; + template class DecayedHasExchange; + template::value> + class TranslatedExchange; + +public: + template()> + class Atomic; +}; + +// The Atomic type. +template +using Atomic = AtomicImpl::Atomic; + +template +constexpr auto AtomicImpl::category() -> Category { + static_assert(std::is_same_v>, + "Value type must not be cv-qualified"); + if constexpr (std::is_integral_v) { + if constexpr ((sizeof(T) == sizeof(int32_t)) || (sizeof(T) == sizeof(int64_t))) { + return Category::Integer; + } else if constexpr (sizeof(T) == 1) { + return Category::Byte; + } else { + static_assert(DependentAlwaysFalse, "Invalid atomic integer type"); + } + } else if constexpr (std::is_pointer_v) { + return Category::Pointer; + } else if constexpr (PrimitiveConversions::Translate::value) { + return Category::Translated; + } else { + static_assert(DependentAlwaysFalse, "Invalid atomic value type"); + } +} + +// Atomic implementation classes. + +template +class AtomicImpl::CommonCore { + T volatile _value; + +protected: + explicit CommonCore(T value) : _value(value) {} + ~CommonCore() = default; + + T volatile* value_ptr() { return &_value; } + T const volatile* value_ptr() const { return &_value; } + + // Support for value_offset_in_bytes. + template + static constexpr int value_offset_in_bytes_impl() { + return offsetof(Derived, _value); + } + +public: + NONCOPYABLE(CommonCore); + + static constexpr int value_size_in_bytes() { + return sizeof(_value); + } + + // Common core Atomic operations. + + T load_relaxed() const { + return AtomicAccess::load(value_ptr()); + } + + T load_acquire() const { + return AtomicAccess::load_acquire(value_ptr()); + } + + void store_relaxed(T value) { + AtomicAccess::store(value_ptr(), value); + } + + void release_store(T value) { + AtomicAccess::release_store(value_ptr(), value); + } + + void release_store_fence(T value) { + AtomicAccess::release_store_fence(value_ptr(), value); + } + + T compare_exchange(T compare_value, T new_value, + atomic_memory_order order = memory_order_conservative) { + return AtomicAccess::cmpxchg(value_ptr(), compare_value, new_value, order); + } +}; + +template +class AtomicImpl::SupportsExchange : public CommonCore { +protected: + explicit SupportsExchange(T value) : CommonCore(value) {} + ~SupportsExchange() = default; + +public: + T exchange(T new_value, + atomic_memory_order order = memory_order_conservative) { + return AtomicAccess::xchg(this->value_ptr(), new_value, order); + } +}; + +template +class AtomicImpl::SupportsArithmetic : public SupportsExchange { + // Guarding the AtomicAccess calls with constexpr checking of Offset produces + // better compile-time error messages. + template + static constexpr bool check_offset_type() { + static_assert(std::is_integral_v, "offset must be integral"); + static_assert(sizeof(Offset) <= sizeof(T), "offset size exceeds value size"); + if constexpr (!std::is_integral_v) { + static_assert(std::is_pointer_v, "must be"); + } else if constexpr (std::is_signed_v) { + static_assert(std::is_signed_v, + "value is signed but offset is unsigned"); + } else { + static_assert(std::is_unsigned_v, + "value is unsigned but offset is signed"); + } + return true; + } + +protected: + explicit SupportsArithmetic(T value) : SupportsExchange(value) {} + ~SupportsArithmetic() = default; + +public: + template + T add_then_fetch(Offset add_value, + atomic_memory_order order = memory_order_conservative) { + if constexpr (check_offset_type()) { + return AtomicAccess::add(this->value_ptr(), add_value, order); + } + } + + template + T fetch_then_add(Offset add_value, + atomic_memory_order order = memory_order_conservative) { + if constexpr (check_offset_type()) { + return AtomicAccess::fetch_then_add(this->value_ptr(), add_value, order); + } + } + + template + T sub_then_fetch(Offset sub_value, + atomic_memory_order order = memory_order_conservative) { + if constexpr (check_offset_type()) { + return AtomicAccess::sub(this->value_ptr(), sub_value, order); + } + } + + template + T fetch_then_sub(Offset sub_value, + atomic_memory_order order = memory_order_conservative) { + if constexpr (check_offset_type()) { + // AtomicAccess doesn't currently provide fetch_then_sub. + return sub_then_fetch(sub_value, order) + sub_value; + } + } +}; + +template +class AtomicImpl::Atomic + : public SupportsArithmetic +{ +public: + explicit Atomic(T value = 0) : SupportsArithmetic(value) {} + + NONCOPYABLE(Atomic); + + using ValueType = T; + + static constexpr int value_offset_in_bytes() { + return CommonCore::template value_offset_in_bytes_impl(); + } + + T fetch_then_and(T bits, atomic_memory_order order = memory_order_conservative) { + return AtomicAccess::fetch_then_and(this->value_ptr(), bits, order); + } + + T fetch_then_or(T bits, atomic_memory_order order = memory_order_conservative) { + return AtomicAccess::fetch_then_or(this->value_ptr(), bits, order); + } + + T fetch_then_xor(T bits, atomic_memory_order order = memory_order_conservative) { + return AtomicAccess::fetch_then_xor(this->value_ptr(), bits, order); + } + + T and_then_fetch(T bits, atomic_memory_order order = memory_order_conservative) { + return AtomicAccess::and_then_fetch(this->value_ptr(), bits, order); + } + + T or_then_fetch(T bits, atomic_memory_order order = memory_order_conservative) { + return AtomicAccess::or_then_fetch(this->value_ptr(), bits, order); + } + + T xor_then_fetch(T bits, atomic_memory_order order = memory_order_conservative) { + return AtomicAccess::xor_then_fetch(this->value_ptr(), bits, order); + } +}; + +template +class AtomicImpl::Atomic + : public CommonCore +{ +public: + explicit Atomic(T value = 0) : CommonCore(value) {} + + NONCOPYABLE(Atomic); + + using ValueType = T; + + static constexpr int value_offset_in_bytes() { + return CommonCore::template value_offset_in_bytes_impl(); + } +}; + +template +class AtomicImpl::Atomic + : public SupportsArithmetic +{ +public: + explicit Atomic(T value = nullptr) : SupportsArithmetic(value) {} + + NONCOPYABLE(Atomic); + + using ValueType = T; + using ElementType = std::remove_pointer_t; + + static constexpr int value_offset_in_bytes() { + return CommonCore::template value_offset_in_bytes_impl(); + } +}; + +// Atomic translated type + +// Test whether Atomic has exchange(). +template +class AtomicImpl::HasExchange { + template static void* test(decltype(&Check::exchange)); + template static int test(...); + using test_type = decltype(test>(nullptr)); +public: + static constexpr bool value = std::is_pointer_v; +}; + +// Test whether the atomic decayed type associated with T has exchange(). +template +class AtomicImpl::DecayedHasExchange { + using Translator = PrimitiveConversions::Translate; + using Decayed = typename Translator::Decayed; + + // "Unit test" HasExchange<>. + static_assert(HasExchange::value); + static_assert(HasExchange::value); + static_assert(!HasExchange::value); + +public: + static constexpr bool value = HasExchange::value; +}; + +// Base class for atomic translated type if atomic decayed type doesn't have +// exchange(). +template +class AtomicImpl::TranslatedExchange {}; + +// Base class for atomic translated type if atomic decayed type does have +// exchange(). +template +class AtomicImpl::TranslatedExchange { +public: + T exchange(T new_value, + atomic_memory_order order = memory_order_conservative) { + return static_cast(this)->exchange_impl(new_value, order); + } +}; + +template +class AtomicImpl::Atomic + : public TranslatedExchange, T> +{ + // Give TranslatedExchange<> access to exchange_impl() if needed. + friend class TranslatedExchange, T>; + + using Translator = PrimitiveConversions::Translate; + using Decayed = typename Translator::Decayed; + + Atomic _value; + + static Decayed decay(T x) { return Translator::decay(x); } + static T recover(Decayed x) { return Translator::recover(x); } + + // Support for default construction via the default construction of _value. + struct UseDecayedCtor {}; + explicit Atomic(UseDecayedCtor) : _value() {} + using DefaultCtorSelect = + std::conditional_t, T, UseDecayedCtor>; + +public: + using ValueType = T; + + // If T is default constructible, construct from a default constructed T. + // Otherwise, default construct the underlying Atomic. + Atomic() : Atomic(DefaultCtorSelect()) {} + + explicit Atomic(T value) : _value(decay(value)) {} + + NONCOPYABLE(Atomic); + + static constexpr int value_offset_in_bytes() { + return (offsetof(Atomic, _value) + + Atomic::value_offset_in_bytes()); + } + + static constexpr int value_size_in_bytes() { + return Atomic::value_size_in_bytes(); + } + + T load_relaxed() const { + return recover(_value.load_relaxed()); + } + + T load_acquire() const { + return recover(_value.load_acquire()); + } + + void store_relaxed(T value) { + _value.store_relaxed(decay(value)); + } + + void release_store(T value) { + _value.release_store(decay(value)); + } + + void release_store_fence(T value) { + _value.release_store_fence(decay(value)); + } + + T compare_exchange(T compare_value, T new_value, + atomic_memory_order order = memory_order_conservative) { + return recover(_value.compare_exchange(decay(compare_value), + decay(new_value), + order)); + } + +private: + // Implementation of exchange() if needed. + // Exclude when not needed, to prevent reference to non-existent function + // of atomic decayed type if someone explicitly instantiates Atomic. + template::value)> + T exchange_impl(T new_value, atomic_memory_order order) { + return recover(_value.exchange(decay(new_value), order)); + } +}; + +#endif // SHARE_RUNTIME_ATOMIC_HPP diff --git a/src/hotspot/share/utilities/globalDefinitions.hpp b/src/hotspot/share/utilities/globalDefinitions.hpp index 12a69043013..1910759b434 100644 --- a/src/hotspot/share/utilities/globalDefinitions.hpp +++ b/src/hotspot/share/utilities/globalDefinitions.hpp @@ -1374,6 +1374,14 @@ template int primitive_compare(const K& k0, const K& k1) { template std::add_rvalue_reference_t declval() noexcept; +// This provides a workaround for static_assert(false) in discarded or +// otherwise uninstantiated places. Instead use +// static_assert(DependentAlwaysFalse, "...") +// See http://wg21.link/p2593r1. Some, but not all, compiler versions we're +// using have implemented that change as a DR: +// https://cplusplus.github.io/CWG/issues/2518.html +template inline constexpr bool DependentAlwaysFalse = false; + // Quickly test to make sure IEEE-754 subnormal numbers are correctly // handled. bool IEEE_subnormal_handling_OK(); diff --git a/src/hotspot/share/utilities/singleWriterSynchronizer.cpp b/src/hotspot/share/utilities/singleWriterSynchronizer.cpp index 5e4c9777468..932cd5c9093 100644 --- a/src/hotspot/share/utilities/singleWriterSynchronizer.cpp +++ b/src/hotspot/share/utilities/singleWriterSynchronizer.cpp @@ -43,17 +43,17 @@ SingleWriterSynchronizer::SingleWriterSynchronizer() : // synchronization have exited that critical section. void SingleWriterSynchronizer::synchronize() { // Side-effect in assert balanced by debug-only dec at end. - assert(AtomicAccess::add(&_writers, 1u) == 1u, "multiple writers"); + assert(_writers.fetch_then_add(1u) == 0u, "multiple writers"); // We don't know anything about the muxing between this invocation // and invocations in other threads. We must start with the latest // _enter polarity, else we could clobber the wrong _exit value on // the first iteration. So fence to ensure everything here follows // whatever muxing was used. OrderAccess::fence(); - uint value = _enter; + uint value = _enter.load_relaxed(); // (1) Determine the old and new exit counters, based on the // polarity (bit0 value) of the on-entry enter counter. - volatile uint* new_ptr = &_exit[(value + 1) & 1]; + Atomic& new_exit = _exit[(value + 1) & 1]; // (2) Change the in-use exit counter to the new counter, by adding // 1 to the enter counter (flipping the polarity), meanwhile // "simultaneously" initializing the new exit counter to that enter @@ -62,29 +62,29 @@ void SingleWriterSynchronizer::synchronize() { uint old; do { old = value; - *new_ptr = ++value; - value = AtomicAccess::cmpxchg(&_enter, old, value); + new_exit.store_relaxed(++value); + value = _enter.compare_exchange(old, value); } while (old != value); // Critical sections entered before we changed the polarity will use // the old exit counter. Critical sections entered after the change // will use the new exit counter. - volatile uint* old_ptr = &_exit[old & 1]; - assert(old_ptr != new_ptr, "invariant"); + Atomic& old_exit = _exit[old & 1]; + assert(&new_exit != &old_exit, "invariant"); // (3) Inform threads in in-progress critical sections that there is // a pending synchronize waiting. The thread that completes the // request (_exit value == old) will signal the _wakeup semaphore to // allow us to proceed. - _waiting_for = old; + _waiting_for.store_relaxed(old); // Write of _waiting_for must precede read of _exit and associated // conditional semaphore wait. If they were re-ordered then a // critical section exit could miss the wakeup request, failing to // signal us while we're waiting. OrderAccess::fence(); // (4) Wait for all the critical sections started before the change - // to complete, e.g. for the value of old_ptr to catch up with old. + // to complete, e.g. for the value of old_exit to catch up with old. // Loop because there could be pending wakeups unrelated to this // synchronize request. - while (old != AtomicAccess::load_acquire(old_ptr)) { + while (old != old_exit.load_acquire()) { _wakeup.wait(); } // (5) Drain any pending wakeups. A critical section exit may have @@ -95,5 +95,5 @@ void SingleWriterSynchronizer::synchronize() { // lead to semaphore overflow. This doesn't guarantee no unrelated // wakeups for the next wait, but prevents unbounded accumulation. while (_wakeup.trywait()) {} - DEBUG_ONLY(AtomicAccess::dec(&_writers);) + assert(_writers.sub_then_fetch(1u) == 0u, "invariant"); } diff --git a/src/hotspot/share/utilities/singleWriterSynchronizer.hpp b/src/hotspot/share/utilities/singleWriterSynchronizer.hpp index 737d5c6d4ac..450c7e89233 100644 --- a/src/hotspot/share/utilities/singleWriterSynchronizer.hpp +++ b/src/hotspot/share/utilities/singleWriterSynchronizer.hpp @@ -26,7 +26,7 @@ #define SHARE_UTILITIES_SINGLEWRITERSYNCHRONIZER_HPP #include "memory/allocation.hpp" -#include "runtime/atomicAccess.hpp" +#include "runtime/atomic.hpp" #include "runtime/semaphore.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/macros.hpp" @@ -48,12 +48,12 @@ // the single writer at a time restriction. Use this only in // situations where GlobalCounter won't work for some reason. class SingleWriterSynchronizer { - volatile uint _enter; - volatile uint _exit[2]; - volatile uint _waiting_for; + Atomic _enter; + Atomic _exit[2]; + Atomic _waiting_for; Semaphore _wakeup; - DEBUG_ONLY(volatile uint _writers;) + DEBUG_ONLY(Atomic _writers;) NONCOPYABLE(SingleWriterSynchronizer); @@ -87,15 +87,15 @@ public: }; inline uint SingleWriterSynchronizer::enter() { - return AtomicAccess::add(&_enter, 2u); + return _enter.add_then_fetch(2u); } inline void SingleWriterSynchronizer::exit(uint enter_value) { - uint exit_value = AtomicAccess::add(&_exit[enter_value & 1], 2u); + uint exit_value = _exit[enter_value & 1].add_then_fetch(2u); // If this exit completes a synchronize request, wakeup possibly // waiting synchronizer. Read of _waiting_for must follow the _exit // update. - if (exit_value == _waiting_for) { + if (exit_value == _waiting_for.load_relaxed()) { _wakeup.signal(); } } diff --git a/test/hotspot/gtest/runtime/test_atomic.cpp b/test/hotspot/gtest/runtime/test_atomic.cpp new file mode 100644 index 00000000000..dc492e523d1 --- /dev/null +++ b/test/hotspot/gtest/runtime/test_atomic.cpp @@ -0,0 +1,640 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "cppstdlib/type_traits.hpp" +#include "metaprogramming/primitiveConversions.hpp" +#include "runtime/atomic.hpp" + +#include "unittest.hpp" + +// These tests of Atomic only verify functionality. They don't verify +// atomicity. + +template +struct AtomicIntegerArithmeticTestSupport { + Atomic _test_value; + + static constexpr T _old_value = static_cast(UCONST64(0x2000000020000)); + static constexpr T _change_value = static_cast(UCONST64( 0x100000001)); + + AtomicIntegerArithmeticTestSupport() : _test_value(0) {} + + void fetch_then_add() { + _test_value.store_relaxed(_old_value); + T expected = _old_value + _change_value; + T result = _test_value.fetch_then_add(_change_value); + EXPECT_EQ(_old_value, result); + EXPECT_EQ(expected, _test_value.load_relaxed()); + } + + void fetch_then_sub() { + _test_value.store_relaxed(_old_value); + T expected = _old_value - _change_value; + T result = _test_value.fetch_then_sub(_change_value); + EXPECT_EQ(_old_value, result); + EXPECT_EQ(expected, _test_value.load_relaxed()); + } + + void add_then_fetch() { + _test_value.store_relaxed(_old_value); + T expected = _old_value + _change_value; + T result = _test_value.add_then_fetch(_change_value); + EXPECT_EQ(expected, result); + EXPECT_EQ(expected, _test_value.load_relaxed()); + } + + void sub_then_fetch() { + _test_value.store_relaxed(_old_value); + T expected = _old_value - _change_value; + T result = _test_value.sub_then_fetch(_change_value); + EXPECT_EQ(expected, result); + EXPECT_EQ(expected, _test_value.load_relaxed()); + } + +#define TEST_ARITHMETIC(name) { SCOPED_TRACE(XSTR(name)); name(); } + + void operator()() { + TEST_ARITHMETIC(fetch_then_add) + TEST_ARITHMETIC(fetch_then_sub) + TEST_ARITHMETIC(add_then_fetch) + TEST_ARITHMETIC(sub_then_fetch) + } + +#undef TEST_ARITHMETIC +}; + +TEST_VM(AtomicIntegerTest, arith_int32) { + AtomicIntegerArithmeticTestSupport()(); +} + +TEST_VM(AtomicIntegerTest, arith_uint32) { + AtomicIntegerArithmeticTestSupport()(); +} + +TEST_VM(AtomicIntegerTest, arith_int64) { + AtomicIntegerArithmeticTestSupport()(); +} + +TEST_VM(AtomicIntegerTest, arith_uint64) { + AtomicIntegerArithmeticTestSupport()(); +} + +template +struct AtomicIntegerXchgTestSupport { + Atomic _test_value; + + AtomicIntegerXchgTestSupport() : _test_value{} {} + + void test() { + T zero = 0; + T five = 5; + _test_value.store_relaxed(zero); + T res = _test_value.exchange(five); + EXPECT_EQ(zero, res); + EXPECT_EQ(five, _test_value.load_relaxed()); + } +}; + +TEST_VM(AtomicIntegerTest, xchg_int32) { + using Support = AtomicIntegerXchgTestSupport; + Support().test(); +} + +TEST_VM(AtomicIntegerTest, xchg_int64) { + using Support = AtomicIntegerXchgTestSupport; + Support().test(); +} + +template +struct AtomicIntegerCmpxchgTestSupport { + Atomic _test_value; + + AtomicIntegerCmpxchgTestSupport() : _test_value{} {} + + void test() { + T zero = 0; + T five = 5; + T ten = 10; + _test_value.store_relaxed(zero); + T res = _test_value.compare_exchange(five, ten); + EXPECT_EQ(zero, res); + EXPECT_EQ(zero, _test_value.load_relaxed()); + res = _test_value.compare_exchange(zero, ten); + EXPECT_EQ(zero, res); + EXPECT_EQ(ten, _test_value.load_relaxed()); + } +}; + +TEST_VM(AtomicIntegerTest, cmpxchg_int32) { + using Support = AtomicIntegerCmpxchgTestSupport; + Support().test(); +} + +TEST_VM(AtomicIntegerTest, cmpxchg_int64) { + // Check if 64-bit atomics are available on the machine. + if (!VM_Version::supports_cx8()) return; + + using Support = AtomicIntegerCmpxchgTestSupport; + Support().test(); +} + +struct AtomicCmpxchg1ByteStressSupport { + char _default_val; + int _base; + Atomic _array[7+32+7]; + + AtomicCmpxchg1ByteStressSupport() : _default_val(0x7a), _base(7) {} + + void validate(char val, char val2, int index) { + for (int i = 0; i < 7; i++) { + EXPECT_EQ(_array[i].load_relaxed(), _default_val); + } + for (int i = 7; i < (7+32); i++) { + if (i == index) { + EXPECT_EQ(_array[i].load_relaxed(), val2); + } else { + EXPECT_EQ(_array[i].load_relaxed(), val); + } + } + for (int i = 0; i < 7; i++) { + EXPECT_EQ(_array[i].load_relaxed(), _default_val); + } + } + + void test_index(int index) { + char one = 1; + _array[index].compare_exchange(_default_val, one); + validate(_default_val, one, index); + + _array[index].compare_exchange(one, _default_val); + validate(_default_val, _default_val, index); + } + + void test() { + for (size_t i = 0; i < ARRAY_SIZE(_array); ++i) { + _array[i].store_relaxed(_default_val); + } + for (int i = _base; i < (_base+32); i++) { + test_index(i); + } + } +}; + +TEST_VM(AtomicCmpxchg1Byte, stress) { + AtomicCmpxchg1ByteStressSupport support; + support.test(); +} + +template +struct AtomicEnumTestSupport { + Atomic _test_value; + + AtomicEnumTestSupport() : _test_value{} {} + + void test_store_load(T value) { + EXPECT_NE(value, _test_value.load_relaxed()); + _test_value.store_relaxed(value); + EXPECT_EQ(value, _test_value.load_relaxed()); + } + + void test_cmpxchg(T value1, T value2) { + EXPECT_NE(value1, _test_value.load_relaxed()); + _test_value.store_relaxed(value1); + EXPECT_EQ(value1, _test_value.compare_exchange(value2, value2)); + EXPECT_EQ(value1, _test_value.load_relaxed()); + EXPECT_EQ(value1, _test_value.compare_exchange(value1, value2)); + EXPECT_EQ(value2, _test_value.load_relaxed()); + } + + void test_xchg(T value1, T value2) { + EXPECT_NE(value1, _test_value.load_relaxed()); + _test_value.store_relaxed(value1); + EXPECT_EQ(value1, _test_value.exchange(value2)); + EXPECT_EQ(value2, _test_value.load_relaxed()); + } +}; + +namespace AtomicEnumTestUnscoped { // Scope the enumerators. + enum TestEnum { A, B, C }; +} + +TEST_VM(AtomicEnumTest, unscoped_enum) { + using namespace AtomicEnumTestUnscoped; + using Support = AtomicEnumTestSupport; + + Support().test_store_load(B); + Support().test_cmpxchg(B, C); + Support().test_xchg(B, C); +} + +enum class AtomicEnumTestScoped { A, B, C }; + +TEST_VM(AtomicEnumTest, scoped_enum) { + const AtomicEnumTestScoped B = AtomicEnumTestScoped::B; + const AtomicEnumTestScoped C = AtomicEnumTestScoped::C; + using Support = AtomicEnumTestSupport; + + Support().test_store_load(B); + Support().test_cmpxchg(B, C); + Support().test_xchg(B, C); +} + +template +struct AtomicBitopsTestSupport { + Atomic _test_value; + + // At least one byte differs between _old_value and _old_value op _change_value. + static constexpr T _old_value = static_cast(UCONST64(0x7f5300007f530044)); + static constexpr T _change_value = static_cast(UCONST64(0x3800530038005322)); + + AtomicBitopsTestSupport() : _test_value(0) {} + + void fetch_then_and() { + _test_value.store_relaxed(_old_value); + T expected = _old_value & _change_value; + EXPECT_NE(_old_value, expected); + T result = _test_value.fetch_then_and(_change_value); + EXPECT_EQ(_old_value, result); + EXPECT_EQ(expected, _test_value.load_relaxed()); + } + + void fetch_then_or() { + _test_value.store_relaxed(_old_value); + T expected = _old_value | _change_value; + EXPECT_NE(_old_value, expected); + T result = _test_value.fetch_then_or(_change_value); + EXPECT_EQ(_old_value, result); + EXPECT_EQ(expected, _test_value.load_relaxed()); + } + + void fetch_then_xor() { + _test_value.store_relaxed(_old_value); + T expected = _old_value ^ _change_value; + EXPECT_NE(_old_value, expected); + T result = _test_value.fetch_then_xor(_change_value); + EXPECT_EQ(_old_value, result); + EXPECT_EQ(expected, _test_value.load_relaxed()); + } + + void and_then_fetch() { + _test_value.store_relaxed(_old_value); + T expected = _old_value & _change_value; + EXPECT_NE(_old_value, expected); + T result = _test_value.and_then_fetch(_change_value); + EXPECT_EQ(expected, result); + EXPECT_EQ(expected, _test_value.load_relaxed()); + } + + void or_then_fetch() { + _test_value.store_relaxed(_old_value); + T expected = _old_value | _change_value; + EXPECT_NE(_old_value, expected); + T result = _test_value.or_then_fetch(_change_value); + EXPECT_EQ(expected, result); + EXPECT_EQ(expected, _test_value.load_relaxed()); + } + + void xor_then_fetch() { + _test_value.store_relaxed(_old_value); + T expected = _old_value ^ _change_value; + EXPECT_NE(_old_value, expected); + T result = _test_value.xor_then_fetch(_change_value); + EXPECT_EQ(expected, result); + EXPECT_EQ(expected, _test_value.load_relaxed()); + } + +#define TEST_BITOP(name) { SCOPED_TRACE(XSTR(name)); name(); } + + void operator()() { + TEST_BITOP(fetch_then_and) + TEST_BITOP(fetch_then_or) + TEST_BITOP(fetch_then_xor) + TEST_BITOP(and_then_fetch) + TEST_BITOP(or_then_fetch) + TEST_BITOP(xor_then_fetch) + } + +#undef TEST_BITOP +}; + +TEST_VM(AtomicBitopsTest, int32) { + AtomicBitopsTestSupport()(); +} + +TEST_VM(AtomicBitopsTest, uint32) { + AtomicBitopsTestSupport()(); +} + +TEST_VM(AtomicBitopsTest, int64) { + AtomicBitopsTestSupport()(); +} + +TEST_VM(AtomicBitopsTest, uint64) { + AtomicBitopsTestSupport()(); +} + +template +struct AtomicPointerTestSupport { + static T _test_values[10]; + static T* _initial_ptr; + + Atomic _test_value; + + AtomicPointerTestSupport() : _test_value(nullptr) {} + + void fetch_then_add() { + _test_value.store_relaxed(_initial_ptr); + T* expected = _initial_ptr + 2; + T* result = _test_value.fetch_then_add(2); + EXPECT_EQ(_initial_ptr, result); + EXPECT_EQ(expected, _test_value.load_relaxed()); + } + + void fetch_then_sub() { + _test_value.store_relaxed(_initial_ptr); + T* expected = _initial_ptr - 2; + T* result = _test_value.fetch_then_sub(2); + EXPECT_EQ(_initial_ptr, result); + EXPECT_EQ(expected, _test_value.load_relaxed()); + } + + void add_then_fetch() { + _test_value.store_relaxed(_initial_ptr); + T* expected = _initial_ptr + 2; + T* result = _test_value.add_then_fetch(2); + EXPECT_EQ(expected, result); + EXPECT_EQ(expected, _test_value.load_relaxed()); + } + + void sub_then_fetch() { + _test_value.store_relaxed(_initial_ptr); + T* expected = _initial_ptr - 2; + T* result = _test_value.sub_then_fetch(2); + EXPECT_EQ(expected, result); + EXPECT_EQ(expected, _test_value.load_relaxed()); + } + + void exchange() { + _test_value.store_relaxed(_initial_ptr); + T* replace = _initial_ptr + 3; + T* result = _test_value.exchange(replace); + EXPECT_EQ(_initial_ptr, result); + EXPECT_EQ(replace, _test_value.load_relaxed()); + } + + void compare_exchange() { + _test_value.store_relaxed(_initial_ptr); + T* not_initial_ptr = _initial_ptr - 1; + T* replace = _initial_ptr + 3; + + T* result = _test_value.compare_exchange(not_initial_ptr, replace); + EXPECT_EQ(_initial_ptr, result); + EXPECT_EQ(_initial_ptr, _test_value.load_relaxed()); + + result = _test_value.compare_exchange(_initial_ptr, replace); + EXPECT_EQ(_initial_ptr, result); + EXPECT_EQ(replace, _test_value.load_relaxed()); + } + +#define TEST_OP(name) { SCOPED_TRACE(XSTR(name)); name(); } + + void operator()() { + TEST_OP(fetch_then_add) + TEST_OP(fetch_then_sub) + TEST_OP(add_then_fetch) + TEST_OP(sub_then_fetch) + TEST_OP(exchange) + TEST_OP(compare_exchange) + } + +#undef TEST_OP +}; + +template +T AtomicPointerTestSupport::_test_values[10] = {}; + +template +T* AtomicPointerTestSupport::_initial_ptr = &_test_values[5]; + +TEST_VM(AtomicPointerTest, ptr_to_char) { + AtomicPointerTestSupport()(); +} + +TEST_VM(AtomicPointerTest, ptr_to_int32) { + AtomicPointerTestSupport()(); +} + +TEST_VM(AtomicPointerTest, ptr_to_int64) { + AtomicPointerTestSupport()(); +} + +// Test translation, including chaining. + +struct TranslatedAtomicTestObject1 { + int _value; + + // NOT default constructible. + + explicit TranslatedAtomicTestObject1(int value) + : _value(value) {} +}; + +template<> +struct PrimitiveConversions::Translate + : public std::true_type +{ + using Value = TranslatedAtomicTestObject1; + using Decayed = int; + + static Decayed decay(Value x) { return x._value; } + static Value recover(Decayed x) { return Value(x); } +}; + +struct TranslatedAtomicTestObject2 { + TranslatedAtomicTestObject1 _value; + + static constexpr int DefaultObject1Value = 3; + + TranslatedAtomicTestObject2() + : TranslatedAtomicTestObject2(TranslatedAtomicTestObject1(DefaultObject1Value)) + {} + + explicit TranslatedAtomicTestObject2(TranslatedAtomicTestObject1 value) + : _value(value) {} +}; + +template<> +struct PrimitiveConversions::Translate + : public std::true_type +{ + using Value = TranslatedAtomicTestObject2; + using Decayed = TranslatedAtomicTestObject1; + + static Decayed decay(Value x) { return x._value; } + static Value recover(Decayed x) { return Value(x); } +}; + +struct TranslatedAtomicByteObject { + uint8_t _value; + + // NOT default constructible. + + explicit TranslatedAtomicByteObject(uint8_t value = 0) : _value(value) {} +}; + +template<> +struct PrimitiveConversions::Translate + : public std::true_type +{ + using Value = TranslatedAtomicByteObject; + using Decayed = uint8_t; + + static Decayed decay(Value x) { return x._value; } + static Value recover(Decayed x) { return Value(x); } +}; + +// Test whether Atomic has exchange(). +// Note: This is intentionally a different implementation from what is used +// by the atomic translated type to decide whether to provide exchange(). +// The intent is to make related testing non-tautological. +// The two implementations must agree; it's a bug if they don't. +template +class AtomicTypeHasExchange { + template, + typename = decltype(declval().exchange(declval()))> + static char* test(int); + + template static char test(...); + + using test_type = decltype(test(0)); + +public: + static constexpr bool value = std::is_pointer_v; +}; + +// Unit tests for AtomicTypeHasExchange. +static_assert(AtomicTypeHasExchange::value); +static_assert(AtomicTypeHasExchange::value); +static_assert(AtomicTypeHasExchange::value); +static_assert(AtomicTypeHasExchange::value); +static_assert(!AtomicTypeHasExchange::value); + +// Verify translated byte type *doesn't* have exchange. +static_assert(!AtomicTypeHasExchange::value); + +// Verify that explicit instantiation doesn't attempt to reference the +// non-existent exchange of the atomic decayed type. +template class AtomicImpl::Atomic; + +template +static void test_atomic_translated_type() { + // This works even if T is not default constructible. + Atomic _test_value{}; + + using Translated = PrimitiveConversions::Translate; + + EXPECT_EQ(0, Translated::decay(_test_value.load_relaxed())); + _test_value.store_relaxed(Translated::recover(5)); + EXPECT_EQ(5, Translated::decay(_test_value.load_relaxed())); + EXPECT_EQ(5, Translated::decay(_test_value.compare_exchange(Translated::recover(5), + Translated::recover(10)))); + EXPECT_EQ(10, Translated::decay(_test_value.load_relaxed())); + + if constexpr (AtomicTypeHasExchange::value) { + EXPECT_EQ(10, Translated::decay(_test_value.exchange(Translated::recover(20)))); + EXPECT_EQ(20, Translated::decay(_test_value.load_relaxed())); + } +} + +TEST_VM(AtomicTranslatedTypeTest, int_test) { + test_atomic_translated_type(); +} + +TEST_VM(AtomicTranslatedTypeTest, byte_test) { + test_atomic_translated_type(); +} + +TEST_VM(AtomicTranslatedTypeTest, chain) { + Atomic _test_value{}; + + using Translated1 = PrimitiveConversions::Translate; + using Translated2 = PrimitiveConversions::Translate; + + auto resolve = [&](TranslatedAtomicTestObject2 x) { + return Translated1::decay(Translated2::decay(x)); + }; + + auto construct = [&](int x) { + return Translated2::recover(Translated1::recover(x)); + }; + + EXPECT_EQ(TranslatedAtomicTestObject2::DefaultObject1Value, + resolve(_test_value.load_relaxed())); + _test_value.store_relaxed(construct(5)); + EXPECT_EQ(5, resolve(_test_value.load_relaxed())); + EXPECT_EQ(5, resolve(_test_value.compare_exchange(construct(5), construct(10)))); + EXPECT_EQ(10, resolve(_test_value.load_relaxed())); + EXPECT_EQ(10, resolve(_test_value.exchange(construct(20)))); + EXPECT_EQ(20, resolve(_test_value.load_relaxed())); +}; + +template +static void test_value_access() { + using AT = Atomic; + // In addition to verifying values are as expected, also verify the + // operations are constexpr. + static_assert(sizeof(T) == AT::value_size_in_bytes(), "value size differs"); + static_assert(0 == AT::value_offset_in_bytes(), "unexpected offset"); + // Also verify no unexpected increase in size for Atomic wrapper. + static_assert(sizeof(T) == sizeof(AT), "unexpected size difference"); +}; + +TEST_VM(AtomicValueAccessTest, access_char) { + test_value_access(); +} + +TEST_VM(AtomicValueAccessTest, access_bool) { + test_value_access(); +} + +TEST_VM(AtomicValueAccessTest, access_int32) { + test_value_access(); +} + +TEST_VM(AtomicValueAccessTest, access_int64) { + test_value_access(); +} + +TEST_VM(AtomicValueAccessTest, access_ptr) { + test_value_access(); +} + +TEST_VM(AtomicValueAccessTest, access_trans1) { + test_value_access(); +} + +TEST_VM(AtomicValueAccessTest, access_trans2) { + test_value_access(); +} From 9d6a61fda6f43577ee8f19483e5b47100ff8eec0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Maillard?= Date: Thu, 13 Nov 2025 09:24:51 +0000 Subject: [PATCH 030/418] 8371558: C2: Missing optimization opportunity in AbsNode::Ideal Reviewed-by: thartmann, rcastanedalo, chagedorn --- src/hotspot/share/opto/phaseX.cpp | 10 +++ .../c2/TestMissingOptAbsZeroMinusX.java | 85 +++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 test/hotspot/jtreg/compiler/c2/TestMissingOptAbsZeroMinusX.java diff --git a/src/hotspot/share/opto/phaseX.cpp b/src/hotspot/share/opto/phaseX.cpp index 8f192cf069c..1fe911aa7ac 100644 --- a/src/hotspot/share/opto/phaseX.cpp +++ b/src/hotspot/share/opto/phaseX.cpp @@ -2596,6 +2596,16 @@ void PhaseIterGVN::add_users_of_use_to_worklist(Node* n, Node* use, Unique_Node_ } } } + // Check for "abs(0-x)" into "abs(x)" conversion + if (use->is_Sub()) { + for (DUIterator_Fast i2max, i2 = use->fast_outs(i2max); i2 < i2max; i2++) { + Node* u = use->fast_out(i2); + if (u->Opcode() == Op_AbsD || u->Opcode() == Op_AbsF || + u->Opcode() == Op_AbsL || u->Opcode() == Op_AbsI) { + worklist.push(u); + } + } + } auto enqueue_init_mem_projs = [&](ProjNode* proj) { add_users_to_worklist0(proj, worklist); }; diff --git a/test/hotspot/jtreg/compiler/c2/TestMissingOptAbsZeroMinusX.java b/test/hotspot/jtreg/compiler/c2/TestMissingOptAbsZeroMinusX.java new file mode 100644 index 00000000000..1422aef7b78 --- /dev/null +++ b/test/hotspot/jtreg/compiler/c2/TestMissingOptAbsZeroMinusX.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8371558 + * @summary An expression of the form "abs(0-x)" should be transformed to "abs(x)". + * This test ensures that updates to the Sub node’s inputs propagate as + * expected and that the optimization is not missed. + * @run main/othervm -XX:+IgnoreUnrecognizedVMOptions -XX:-TieredCompilation -Xcomp + * -XX:CompileCommand=compileonly,compiler.c2.TestMissingOptAbsZeroMinusX::test* + * -XX:VerifyIterativeGVN=1110 compiler.c2.TestMissingOptAbsZeroMinusX + * @run main compiler.c2.TestMissingOptAbsZeroMinusX + * + */ + +package compiler.c2; + +public class TestMissingOptAbsZeroMinusX { + static int a; + static boolean b; + + public static void main(String[] strArr) { + // no known reproducer for AbsL + testAbsI(); + testAbsF(); + testAbsD(); + } + + static void testAbsI() { + int d = 4; + for (int i = 8; i < 133; i += 3) { + d -= a; + b = (d != Math.abs(d)); + for (long f = 3; f < 127; f++) { + for (int e = 1; e < 3; e++) {} + } + d = 0; + } + } + + static void testAbsF() { + float d = 12.3f; + for (int i = 8; i < 133; i += 3) { + d -= a; + b = (d != Math.abs(d)); + for (long f = 3; f < 127; f++) { + for (int e = 1; e < 3; e++) {} + } + d = 0.0f; + } + } + + static void testAbsD() { + double d = 12.3; + for (int i = 8; i < 133; i += 3) { + d -= a; + b = (d != Math.abs(d)); + for (long f = 3; f < 127; f++) { + for (int e = 1; e < 3; e++) {} + } + d = 0.0; + } + } +} \ No newline at end of file From 48c59faf58a4d7b7ec9d6824a5cbc9a55888ce72 Mon Sep 17 00:00:00 2001 From: Daniel Fuchs Date: Thu, 13 Nov 2025 10:46:00 +0000 Subject: [PATCH 031/418] 8371722: java/net/httpclient/BufferSizePropertyClampTest.java should use Locale.ROOT Reviewed-by: djelinski, jpai, vyazici --- .../httpclient/BufferSizePropertyClampTest.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/test/jdk/java/net/httpclient/BufferSizePropertyClampTest.java b/test/jdk/java/net/httpclient/BufferSizePropertyClampTest.java index caef0a58a6d..d9695dce3cb 100644 --- a/test/jdk/java/net/httpclient/BufferSizePropertyClampTest.java +++ b/test/jdk/java/net/httpclient/BufferSizePropertyClampTest.java @@ -29,6 +29,7 @@ import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.logging.Handler; import java.util.logging.LogRecord; import java.util.logging.Logger; @@ -68,13 +69,20 @@ class BufferSizePropertyClampTest { private static final List CLIENT_LOGGER_MESSAGES = Collections.synchronizedList(new ArrayList<>()); + private static final String EXPECTED_MSG = + "ERROR: Property value for jdk.httpclient.bufsize={0} not in [1..16,384]: using default=16,384"; + + private static String format(String pattern, Object... args) { + return new MessageFormat(pattern, Locale.ROOT).format(args); + } + @BeforeAll static void registerLoggerHandler() { CLIENT_LOGGER.addHandler(new Handler() { @Override public void publish(LogRecord record) { - var message = MessageFormat.format(record.getMessage(), record.getParameters()); + var message = format(record.getMessage(), record.getParameters()); CLIENT_LOGGER_MESSAGES.add(message); } @@ -97,10 +105,8 @@ class BufferSizePropertyClampTest { assertEquals( 1, CLIENT_LOGGER_MESSAGES.size(), "Unexpected number of logger messages: " + CLIENT_LOGGER_MESSAGES); - var expectedMessage = "ERROR: Property value for jdk.httpclient.bufsize=" + - System.getProperty("jdk.httpclient.bufsize") + - " not in [1..16384]: using default=16384"; - assertEquals(expectedMessage, CLIENT_LOGGER_MESSAGES.getFirst().replaceAll(",", "")); + var expectedMessage = format(EXPECTED_MSG, Integer.getInteger("jdk.httpclient.bufsize")); + assertEquals(expectedMessage, CLIENT_LOGGER_MESSAGES.getFirst()); } } From 6b6fdf1d9222eb03cd013cbe792fa77fd78c1acb Mon Sep 17 00:00:00 2001 From: Ramesh Bhagavatam Gangadhar Date: Thu, 13 Nov 2025 12:57:16 +0000 Subject: [PATCH 032/418] 8357874: UNLIMTED_CRYPTO typo in class description of JceSecurity.java.template Reviewed-by: wetmore --- .../share/classes/javax/crypto/JceSecurity.java.template | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/classes/javax/crypto/JceSecurity.java.template b/src/java.base/share/classes/javax/crypto/JceSecurity.java.template index 01282394b57..a6010945660 100644 --- a/src/java.base/share/classes/javax/crypto/JceSecurity.java.template +++ b/src/java.base/share/classes/javax/crypto/JceSecurity.java.template @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2025, 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 @@ -31,7 +31,7 @@ * * In the current jdk builds, this file is first preprocessed to replace * @@JCE_DEFAULT_POLICY@ [sic] with "limited" or "unlimited" which is - * determined by the $(UNLIMTED_CRYPTO) make variable. This variable is + * determined by the $(UNLIMITED_CRYPTO) make variable. This variable is * set by top-level configure script, using either * --disable-unlimited-crypto or --enable-unlimited-crypto [default]. * From bbc0f9ef30c467c8da8b873813bde50a7e9ff697 Mon Sep 17 00:00:00 2001 From: Thomas Schatzl Date: Thu, 13 Nov 2025 13:53:09 +0000 Subject: [PATCH 033/418] 8371788: Fix documentation for CollectedHeap::collect(GCCause) Reviewed-by: ayang, iwalulya --- src/hotspot/share/gc/g1/g1CollectedHeap.hpp | 3 --- src/hotspot/share/gc/parallel/parallelScavengeHeap.hpp | 1 - src/hotspot/share/gc/serial/serialHeap.hpp | 3 --- src/hotspot/share/gc/shared/collectedHeap.hpp | 4 +--- 4 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp index e160cb9e428..7a0edfacd0f 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp @@ -1040,9 +1040,6 @@ public: // old GC alloc region. bool is_old_gc_alloc_region(G1HeapRegion* hr); - // Perform a collection of the heap; intended for use in implementing - // "System.gc". This probably implies as full a collection as the - // "CollectedHeap" supports. void collect(GCCause::Cause cause) override; // Try to perform a collection of the heap with the given cause to allocate allocation_word_size diff --git a/src/hotspot/share/gc/parallel/parallelScavengeHeap.hpp b/src/hotspot/share/gc/parallel/parallelScavengeHeap.hpp index de1e3ce851c..f9161afc28f 100644 --- a/src/hotspot/share/gc/parallel/parallelScavengeHeap.hpp +++ b/src/hotspot/share/gc/parallel/parallelScavengeHeap.hpp @@ -206,7 +206,6 @@ public: HeapWord* satisfy_failed_allocation(size_t size, bool is_tlab); - // Support for System.gc() void collect(GCCause::Cause cause) override; void collect_at_safepoint(bool full); diff --git a/src/hotspot/share/gc/serial/serialHeap.hpp b/src/hotspot/share/gc/serial/serialHeap.hpp index 432cd73f616..ee016173c2a 100644 --- a/src/hotspot/share/gc/serial/serialHeap.hpp +++ b/src/hotspot/share/gc/serial/serialHeap.hpp @@ -139,9 +139,6 @@ public: // Callback from VM_SerialGCCollect. void collect_at_safepoint(bool full); - // Perform a full collection of the heap; intended for use in implementing - // "System.gc". This implies as full a collection as the CollectedHeap - // supports. Caller does not hold the Heap_lock on entry. void collect(GCCause::Cause cause) override; // Returns "TRUE" iff "p" points into the committed areas of the heap. diff --git a/src/hotspot/share/gc/shared/collectedHeap.hpp b/src/hotspot/share/gc/shared/collectedHeap.hpp index 659106f9c19..6be0057480d 100644 --- a/src/hotspot/share/gc/shared/collectedHeap.hpp +++ b/src/hotspot/share/gc/shared/collectedHeap.hpp @@ -353,9 +353,7 @@ protected: // collection or expansion activity. virtual size_t unsafe_max_tlab_alloc() const = 0; - // Perform a collection of the heap; intended for use in implementing - // "System.gc". This probably implies as full a collection as the - // "CollectedHeap" supports. + // Perform a collection of the heap of a type depending on the given cause. virtual void collect(GCCause::Cause cause) = 0; // Perform a full collection From 7d78818ae609461ab830c32c222f15f1cab0d2d4 Mon Sep 17 00:00:00 2001 From: Thomas Schatzl Date: Thu, 13 Nov 2025 13:55:25 +0000 Subject: [PATCH 034/418] 8274178: G1: Occupancy value in IHOP logging and JFR event is inaccurate 8371635: G1: Young gen allocations should never be considered when comparing against IHOP threshold Reviewed-by: ayang, iwalulya --- src/hotspot/share/gc/g1/g1CollectedHeap.cpp | 17 +++++++++ src/hotspot/share/gc/g1/g1CollectedHeap.hpp | 11 ++++-- .../share/gc/g1/g1HeapSizingPolicy.cpp | 36 ++++++++---------- src/hotspot/share/gc/g1/g1IHOPControl.cpp | 37 +++++++++++-------- src/hotspot/share/gc/g1/g1IHOPControl.hpp | 16 +++++--- src/hotspot/share/gc/g1/g1Policy.cpp | 34 +++++------------ src/hotspot/share/gc/g1/g1Policy.hpp | 6 +-- src/hotspot/share/gc/g1/g1Trace.cpp | 8 ++-- src/hotspot/share/gc/g1/g1Trace.hpp | 4 +- 9 files changed, 91 insertions(+), 78 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp index 9a5e390f6f1..f04658a1415 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp @@ -353,6 +353,14 @@ size_t G1CollectedHeap::humongous_obj_size_in_regions(size_t word_size) { return align_up(word_size, G1HeapRegion::GrainWords) / G1HeapRegion::GrainWords; } +size_t G1CollectedHeap::allocation_used_bytes(size_t allocation_word_size) { + if (is_humongous(allocation_word_size)) { + return humongous_obj_size_in_regions(allocation_word_size) * G1HeapRegion::GrainBytes; + } else { + return allocation_word_size * HeapWordSize; + } +} + // If could fit into free regions w/o expansion, try. // Otherwise, if can expand, do so. // Otherwise, if using ex regions might help, try with ex given back. @@ -2955,6 +2963,15 @@ void G1CollectedHeap::abandon_collection_set() { collection_set()->abandon(); } +size_t G1CollectedHeap::non_young_occupancy_after_allocation(size_t allocation_word_size) { + // For simplicity, just count whole regions. + const size_t cur_occupancy = (old_regions_count() + humongous_regions_count()) * G1HeapRegion::GrainBytes; + // Humongous allocations will always be assigned to non-young heap, so consider + // that allocation in the result as well. Otherwise the allocation will always + // be in young gen, so there is no need to account it here. + return cur_occupancy + (is_humongous(allocation_word_size) ? allocation_used_bytes(allocation_word_size) : 0); +} + bool G1CollectedHeap::is_old_gc_alloc_region(G1HeapRegion* hr) { return _allocator->is_retained_old_region(hr); } diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp index 7a0edfacd0f..5dccf41e909 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp @@ -1032,9 +1032,10 @@ public: inline void old_set_add(G1HeapRegion* hr); inline void old_set_remove(G1HeapRegion* hr); - size_t non_young_capacity_bytes() { - return (old_regions_count() + humongous_regions_count()) * G1HeapRegion::GrainBytes; - } + // Returns how much memory there is assigned to non-young heap that can not be + // allocated into any more without garbage collection after a hypothetical + // allocation of allocation_word_size. + size_t non_young_occupancy_after_allocation(size_t allocation_word_size); // Determine whether the given region is one that we are using as an // old GC alloc region. @@ -1226,6 +1227,10 @@ public: // requires. static size_t humongous_obj_size_in_regions(size_t word_size); + // Returns how much space in bytes an allocation of word_size will use up in the + // heap. + static size_t allocation_used_bytes(size_t word_size); + // Print the maximum heap capacity. size_t max_capacity() const override; size_t min_capacity() const; diff --git a/src/hotspot/share/gc/g1/g1HeapSizingPolicy.cpp b/src/hotspot/share/gc/g1/g1HeapSizingPolicy.cpp index 03a1444f8e6..4dd0a509bcd 100644 --- a/src/hotspot/share/gc/g1/g1HeapSizingPolicy.cpp +++ b/src/hotspot/share/gc/g1/g1HeapSizingPolicy.cpp @@ -366,28 +366,24 @@ static size_t target_heap_capacity(size_t used_bytes, uintx free_ratio) { } size_t G1HeapSizingPolicy::full_collection_resize_amount(bool& expand, size_t allocation_word_size) { - // If the full collection was triggered by an allocation failure, we should account - // for the bytes required for this allocation under used_after_gc. This prevents - // unnecessary shrinking that would be followed by an expand call to satisfy the - // allocation. - size_t allocation_bytes = allocation_word_size * HeapWordSize; - if (_g1h->is_humongous(allocation_word_size)) { - // Humongous objects are allocated in entire regions, we must calculate - // required space in terms of full regions, not just the object size. - allocation_bytes = G1HeapRegion::align_up_to_region_byte_size(allocation_bytes); - } - + const size_t capacity_after_gc = _g1h->capacity(); // Capacity, free and used after the GC counted as full regions to // include the waste in the following calculations. - const size_t capacity_after_gc = _g1h->capacity(); - const size_t used_after_gc = capacity_after_gc + allocation_bytes - - _g1h->unused_committed_regions_in_bytes() - - // Discount space used by current Eden to establish a - // situation during Remark similar to at the end of full - // GC where eden is empty. During Remark there can be an - // arbitrary number of eden regions which would skew the - // results. - _g1h->eden_regions_count() * G1HeapRegion::GrainBytes; + const size_t current_used_after_gc = capacity_after_gc - + _g1h->unused_committed_regions_in_bytes() - + // Discount space used by current Eden to establish a + // situation during Remark similar to at the end of full + // GC where eden is empty. During Remark there can be an + // arbitrary number of eden regions which would skew the + // results. + _g1h->eden_regions_count() * G1HeapRegion::GrainBytes; + + // Add pending allocation; + const size_t used_after_gc = current_used_after_gc + + // If the full collection was triggered by an allocation failure, + // account that allocation too. Otherwise we could shrink and then + // expand immediately to satisfy the allocation. + _g1h->allocation_used_bytes(allocation_word_size); size_t minimum_desired_capacity = target_heap_capacity(used_after_gc, MinHeapFreeRatio); size_t maximum_desired_capacity = target_heap_capacity(used_after_gc, MaxHeapFreeRatio); diff --git a/src/hotspot/share/gc/g1/g1IHOPControl.cpp b/src/hotspot/share/gc/g1/g1IHOPControl.cpp index 5c05169c29d..34c8cd0366b 100644 --- a/src/hotspot/share/gc/g1/g1IHOPControl.cpp +++ b/src/hotspot/share/gc/g1/g1IHOPControl.cpp @@ -44,32 +44,37 @@ void G1IHOPControl::update_target_occupancy(size_t new_target_occupancy) { _target_occupancy = new_target_occupancy; } +void G1IHOPControl::report_statistics(G1NewTracer* new_tracer, size_t non_young_occupancy) { + print_log(non_young_occupancy); + send_trace_event(new_tracer, non_young_occupancy); +} + void G1IHOPControl::update_allocation_info(double allocation_time_s, size_t additional_buffer_size) { assert(allocation_time_s >= 0.0, "Allocation time must be positive but is %.3f", allocation_time_s); _last_allocation_time_s = allocation_time_s; } -void G1IHOPControl::print() { +void G1IHOPControl::print_log(size_t non_young_occupancy) { assert(_target_occupancy > 0, "Target occupancy still not updated yet."); size_t cur_conc_mark_start_threshold = get_conc_mark_start_threshold(); - log_debug(gc, ihop)("Basic information (value update), threshold: %zuB (%1.2f), target occupancy: %zuB, current occupancy: %zuB, " + log_debug(gc, ihop)("Basic information (value update), threshold: %zuB (%1.2f), target occupancy: %zuB, non-young occupancy: %zuB, " "recent allocation size: %zuB, recent allocation duration: %1.2fms, recent old gen allocation rate: %1.2fB/s, recent marking phase length: %1.2fms", cur_conc_mark_start_threshold, percent_of(cur_conc_mark_start_threshold, _target_occupancy), _target_occupancy, - G1CollectedHeap::heap()->used(), + non_young_occupancy, _old_gen_alloc_tracker->last_period_old_gen_bytes(), _last_allocation_time_s * 1000.0, _last_allocation_time_s > 0.0 ? _old_gen_alloc_tracker->last_period_old_gen_bytes() / _last_allocation_time_s : 0.0, last_marking_length_s() * 1000.0); } -void G1IHOPControl::send_trace_event(G1NewTracer* tracer) { +void G1IHOPControl::send_trace_event(G1NewTracer* tracer, size_t non_young_occupancy) { assert(_target_occupancy > 0, "Target occupancy still not updated yet."); tracer->report_basic_ihop_statistics(get_conc_mark_start_threshold(), _target_occupancy, - G1CollectedHeap::heap()->used(), + non_young_occupancy, _old_gen_alloc_tracker->last_period_old_gen_bytes(), _last_allocation_time_s, last_marking_length_s()); @@ -165,27 +170,27 @@ void G1AdaptiveIHOPControl::update_marking_length(double marking_length_s) { _marking_times_s.add(marking_length_s); } -void G1AdaptiveIHOPControl::print() { - G1IHOPControl::print(); - size_t actual_target = actual_target_threshold(); - log_debug(gc, ihop)("Adaptive IHOP information (value update), threshold: %zuB (%1.2f), internal target occupancy: %zuB, " - "occupancy: %zuB, additional buffer size: %zuB, predicted old gen allocation rate: %1.2fB/s, " +void G1AdaptiveIHOPControl::print_log(size_t non_young_occupancy) { + G1IHOPControl::print_log(non_young_occupancy); + size_t actual_threshold = actual_target_threshold(); + log_debug(gc, ihop)("Adaptive IHOP information (value update), threshold: %zuB (%1.2f), internal target threshold: %zuB, " + "non-young occupancy: %zuB, additional buffer size: %zuB, predicted old gen allocation rate: %1.2fB/s, " "predicted marking phase length: %1.2fms, prediction active: %s", get_conc_mark_start_threshold(), - percent_of(get_conc_mark_start_threshold(), actual_target), - actual_target, - G1CollectedHeap::heap()->used(), + percent_of(get_conc_mark_start_threshold(), actual_threshold), + actual_threshold, + non_young_occupancy, _last_unrestrained_young_size, predict(&_allocation_rate_s), predict(&_marking_times_s) * 1000.0, have_enough_data_for_prediction() ? "true" : "false"); } -void G1AdaptiveIHOPControl::send_trace_event(G1NewTracer* tracer) { - G1IHOPControl::send_trace_event(tracer); +void G1AdaptiveIHOPControl::send_trace_event(G1NewTracer* tracer, size_t non_young_occupancy) { + G1IHOPControl::send_trace_event(tracer, non_young_occupancy); tracer->report_adaptive_ihop_statistics(get_conc_mark_start_threshold(), actual_target_threshold(), - G1CollectedHeap::heap()->used(), + non_young_occupancy, _last_unrestrained_young_size, predict(&_allocation_rate_s), predict(&_marking_times_s), diff --git a/src/hotspot/share/gc/g1/g1IHOPControl.hpp b/src/hotspot/share/gc/g1/g1IHOPControl.hpp index 507fbb269d1..392a12a785a 100644 --- a/src/hotspot/share/gc/g1/g1IHOPControl.hpp +++ b/src/hotspot/share/gc/g1/g1IHOPControl.hpp @@ -55,7 +55,11 @@ class G1IHOPControl : public CHeapObj { // Most recent time from the end of the concurrent start to the start of the first // mixed gc. virtual double last_marking_length_s() const = 0; - public: + + virtual void print_log(size_t non_young_occupancy); + virtual void send_trace_event(G1NewTracer* tracer, size_t non_young_occupancy); + +public: virtual ~G1IHOPControl() { } // Get the current non-young occupancy at which concurrent marking should start. @@ -76,8 +80,7 @@ class G1IHOPControl : public CHeapObj { // the first mixed gc. virtual void update_marking_length(double marking_length_s) = 0; - virtual void print(); - virtual void send_trace_event(G1NewTracer* tracer); + void report_statistics(G1NewTracer* tracer, size_t non_young_occupancy); }; // The returned concurrent mark starting occupancy threshold is a fixed value @@ -139,6 +142,10 @@ class G1AdaptiveIHOPControl : public G1IHOPControl { double last_mutator_period_old_allocation_rate() const; protected: virtual double last_marking_length_s() const { return _marking_times_s.last(); } + + virtual void print_log(size_t non_young_occupancy); + virtual void send_trace_event(G1NewTracer* tracer, size_t non_young_occupancy); + public: G1AdaptiveIHOPControl(double ihop_percent, G1OldGenAllocationTracker const* old_gen_alloc_tracker, @@ -150,9 +157,6 @@ class G1AdaptiveIHOPControl : public G1IHOPControl { virtual void update_allocation_info(double allocation_time_s, size_t additional_buffer_size); virtual void update_marking_length(double marking_length_s); - - virtual void print(); - virtual void send_trace_event(G1NewTracer* tracer); }; #endif // SHARE_GC_G1_G1IHOPCONTROL_HPP diff --git a/src/hotspot/share/gc/g1/g1Policy.cpp b/src/hotspot/share/gc/g1/g1Policy.cpp index ddcdfb0c3a4..19573e11cd7 100644 --- a/src/hotspot/share/gc/g1/g1Policy.cpp +++ b/src/hotspot/share/gc/g1/g1Policy.cpp @@ -749,22 +749,14 @@ bool G1Policy::need_to_start_conc_mark(const char* source, size_t allocation_wor } size_t marking_initiating_used_threshold = _ihop_control->get_conc_mark_start_threshold(); - - size_t cur_used_bytes = _g1h->non_young_capacity_bytes(); - size_t allocation_byte_size = allocation_word_size * HeapWordSize; - // For humongous allocations, we need to consider that we actually use full regions - // for allocations. So compare the threshold to this size. - if (_g1h->is_humongous(allocation_word_size)) { - allocation_byte_size = G1HeapRegion::align_up_to_region_byte_size(allocation_byte_size); - } - size_t marking_request_bytes = cur_used_bytes + allocation_byte_size; + size_t non_young_occupancy = _g1h->non_young_occupancy_after_allocation(allocation_word_size); bool result = false; - if (marking_request_bytes > marking_initiating_used_threshold) { + if (non_young_occupancy > marking_initiating_used_threshold) { result = collector_state()->in_young_only_phase(); - log_debug(gc, ergo, ihop)("%s occupancy: %zuB allocation request: %zuB threshold: %zuB (%1.2f) source: %s", + log_debug(gc, ergo, ihop)("%s non-young occupancy: %zuB allocation request: %zuB threshold: %zuB (%1.2f) source: %s", result ? "Request concurrent cycle initiation (occupancy higher than threshold)" : "Do not request concurrent cycle initiation (still doing mixed collections)", - cur_used_bytes, allocation_byte_size, marking_initiating_used_threshold, (double) marking_initiating_used_threshold / _g1h->capacity() * 100, source); + non_young_occupancy, allocation_word_size * HeapWordSize, marking_initiating_used_threshold, (double) marking_initiating_used_threshold / _g1h->capacity() * 100, source); } return result; } @@ -995,10 +987,10 @@ void G1Policy::record_young_collection_end(bool concurrent_operation_is_full_mar update_young_length_bounds(); _old_gen_alloc_tracker.reset_after_gc(_g1h->humongous_regions_count() * G1HeapRegion::GrainBytes); - update_ihop_prediction(app_time_ms / 1000.0, - G1GCPauseTypeHelper::is_young_only_pause(this_pause)); - - _ihop_control->send_trace_event(_g1h->gc_tracer_stw()); + if (update_ihop_prediction(app_time_ms / 1000.0, + G1GCPauseTypeHelper::is_young_only_pause(this_pause))) { + _ihop_control->report_statistics(_g1h->gc_tracer_stw(), _g1h->non_young_occupancy_after_allocation(allocation_word_size)); + } } else { // Any garbage collection triggered as periodic collection resets the time-to-mixed // measurement. Periodic collection typically means that the application is "inactive", i.e. @@ -1045,7 +1037,7 @@ G1IHOPControl* G1Policy::create_ihop_control(const G1OldGenAllocationTracker* ol } } -void G1Policy::update_ihop_prediction(double mutator_time_s, +bool G1Policy::update_ihop_prediction(double mutator_time_s, bool this_gc_was_young_only) { // Always try to update IHOP prediction. Even evacuation failures give information // about e.g. whether to start IHOP earlier next time. @@ -1082,13 +1074,7 @@ void G1Policy::update_ihop_prediction(double mutator_time_s, report = true; } - if (report) { - report_ihop_statistics(); - } -} - -void G1Policy::report_ihop_statistics() { - _ihop_control->print(); + return report; } void G1Policy::record_young_gc_pause_end(bool evacuation_failed) { diff --git a/src/hotspot/share/gc/g1/g1Policy.hpp b/src/hotspot/share/gc/g1/g1Policy.hpp index 93724657235..72fdc6deb5b 100644 --- a/src/hotspot/share/gc/g1/g1Policy.hpp +++ b/src/hotspot/share/gc/g1/g1Policy.hpp @@ -60,10 +60,10 @@ class G1Policy: public CHeapObj { static G1IHOPControl* create_ihop_control(const G1OldGenAllocationTracker* old_gen_alloc_tracker, const G1Predictions* predictor); - // Update the IHOP control with necessary statistics. - void update_ihop_prediction(double mutator_time_s, + // Update the IHOP control with the necessary statistics. Returns true if there + // has been a significant update to the prediction. + bool update_ihop_prediction(double mutator_time_s, bool this_gc_was_young_only); - void report_ihop_statistics(); G1Predictions _predictor; G1Analytics* _analytics; diff --git a/src/hotspot/share/gc/g1/g1Trace.cpp b/src/hotspot/share/gc/g1/g1Trace.cpp index 6c9a87e4d98..ed6a91f41ed 100644 --- a/src/hotspot/share/gc/g1/g1Trace.cpp +++ b/src/hotspot/share/gc/g1/g1Trace.cpp @@ -98,13 +98,13 @@ void G1NewTracer::report_evacuation_statistics(const G1EvacSummary& young_summar void G1NewTracer::report_basic_ihop_statistics(size_t threshold, size_t target_ccupancy, - size_t current_occupancy, + size_t non_young_occupancy, size_t last_allocation_size, double last_allocation_duration, double last_marking_length) { send_basic_ihop_statistics(threshold, target_ccupancy, - current_occupancy, + non_young_occupancy, last_allocation_size, last_allocation_duration, last_marking_length); @@ -206,7 +206,7 @@ void G1NewTracer::send_old_evacuation_statistics(const G1EvacSummary& summary) c void G1NewTracer::send_basic_ihop_statistics(size_t threshold, size_t target_occupancy, - size_t current_occupancy, + size_t non_young_occupancy, size_t last_allocation_size, double last_allocation_duration, double last_marking_length) { @@ -216,7 +216,7 @@ void G1NewTracer::send_basic_ihop_statistics(size_t threshold, evt.set_threshold(threshold); evt.set_targetOccupancy(target_occupancy); evt.set_thresholdPercentage(target_occupancy > 0 ? ((double)threshold / target_occupancy) : 0.0); - evt.set_currentOccupancy(current_occupancy); + evt.set_currentOccupancy(non_young_occupancy); evt.set_recentMutatorAllocationSize(last_allocation_size); evt.set_recentMutatorDuration(last_allocation_duration * MILLIUNITS); evt.set_recentAllocationRate(last_allocation_duration != 0.0 ? last_allocation_size / last_allocation_duration : 0.0); diff --git a/src/hotspot/share/gc/g1/g1Trace.hpp b/src/hotspot/share/gc/g1/g1Trace.hpp index 025a1c3fe95..1415828f376 100644 --- a/src/hotspot/share/gc/g1/g1Trace.hpp +++ b/src/hotspot/share/gc/g1/g1Trace.hpp @@ -73,13 +73,13 @@ private: void send_basic_ihop_statistics(size_t threshold, size_t target_occupancy, - size_t current_occupancy, + size_t non_young_occupancy, size_t last_allocation_size, double last_allocation_duration, double last_marking_length); void send_adaptive_ihop_statistics(size_t threshold, size_t internal_target_occupancy, - size_t current_occupancy, + size_t non_young_occupancy, size_t additional_buffer_size, double predicted_allocation_rate, double predicted_marking_length, From 8102f436f5586253302cd8cef49bfe2b4af41693 Mon Sep 17 00:00:00 2001 From: Vicente Romero Date: Thu, 13 Nov 2025 15:28:08 +0000 Subject: [PATCH 035/418] 8371480: VerifyError after JDK-8369654 Reviewed-by: mcimadamore --- .../com/sun/tools/javac/code/Types.java | 2 +- .../classes/com/sun/tools/javac/jvm/Code.java | 19 +-- .../VerifierErrorWrongSuperTypeTest.java | 130 ++++++++++++++++++ 3 files changed, 142 insertions(+), 9 deletions(-) create mode 100644 test/langtools/tools/javac/switchexpr/VerifierErrorWrongSuperTypeTest.java diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java index ffd304c18a2..d59505555f2 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java @@ -3971,7 +3971,7 @@ public class Types { * Return the minimum types of a closure, suitable for computing * compoundMin or glb. */ - private List closureMin(List cl) { + public List closureMin(List cl) { ListBuffer classes = new ListBuffer<>(); ListBuffer interfaces = new ListBuffer<>(); Set toSkip = new HashSet<>(); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Code.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Code.java index 7899b8335b6..227143c3148 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Code.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Code.java @@ -1854,14 +1854,17 @@ public class Code { } else { t1 = types.skipTypeVars(t1, false); t2 = types.skipTypeVars(t2, false); - List intersection = types.intersect( - t1.hasTag(ARRAY) ? - List.of(syms.serializableType, syms.cloneableType, syms.objectType) : - types.erasedSupertypes(t1), - t2.hasTag(ARRAY) ? - List.of(syms.serializableType, syms.cloneableType, syms.objectType) : - types.erasedSupertypes(t2)); - return intersection.head; + List result = types.closureMin( + types.intersect( + t1.hasTag(ARRAY) ? + List.of(syms.serializableType, syms.cloneableType, syms.objectType) : + types.erasedSupertypes(t1), + t2.hasTag(ARRAY) ? + List.of(syms.serializableType, syms.cloneableType, syms.objectType) : + types.erasedSupertypes(t2) + ) + ); + return result.head; } } diff --git a/test/langtools/tools/javac/switchexpr/VerifierErrorWrongSuperTypeTest.java b/test/langtools/tools/javac/switchexpr/VerifierErrorWrongSuperTypeTest.java new file mode 100644 index 00000000000..115bf8980b0 --- /dev/null +++ b/test/langtools/tools/javac/switchexpr/VerifierErrorWrongSuperTypeTest.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary VerifyError after JDK-8369654, incorrect supertype + * @library /tools/lib + * @modules jdk.compiler/com.sun.tools.javac.api + * jdk.compiler/com.sun.tools.javac.main + * jdk.compiler/com.sun.tools.javac.util + * jdk.compiler/com.sun.tools.javac.code + * @build toolbox.ToolBox toolbox.JavacTask + * @run main VerifierErrorWrongSuperTypeTest + */ + +import java.util.*; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import toolbox.TestRunner; +import toolbox.ToolBox; +import toolbox.JavaTask; +import toolbox.JavacTask; +import toolbox.Task; +import toolbox.Task.OutputKind; + +public class VerifierErrorWrongSuperTypeTest extends TestRunner { + ToolBox tb; + + VerifierErrorWrongSuperTypeTest() { + super(System.err); + tb = new ToolBox(); + } + + protected void runTests() throws Exception { + runTests(m -> new Object[]{Paths.get(m.getName())}); + } + + Path[] findJavaFiles(Path... paths) throws IOException { + return tb.findJavaFiles(paths); + } + + public static void main(String... args) throws Exception { + VerifierErrorWrongSuperTypeTest t = new VerifierErrorWrongSuperTypeTest(); + t.runTests(); + } + + @Test + public void testCompatibilityAfterMakingSuperclassSealed(Path base) throws Exception { + Path src = base.resolve("src"); + Path pkg = src.resolve("p"); + Path v = pkg.resolve("V"); + tb.writeJavaFiles(v, + """ + package p; + public abstract class V {} + """ + ); + Path d = pkg.resolve("D"); + tb.writeJavaFiles(d, + """ + package p; + public abstract class D extends V implements Cloneable {} + """ + ); + Path a = pkg.resolve("A"); + tb.writeJavaFiles(a, + """ + package p; + public class A extends V implements Cloneable {} + """ + ); + Path t = src.resolve("T"); + tb.writeJavaFiles(t, + """ + import p.A; + import p.D; + import p.V; + class T { + public static void main(String[] args) { + new T().foo(false, null); + } + void foo(boolean b, D d) { + V u = b ? d : new A(); + g(u); + } + void g(V u) {} + } + """ + ); + Path out = base.resolve("out"); + Files.createDirectories(out); + new JavacTask(tb) + .outdir(out) + .files(findJavaFiles(src)) + .run(); + + try { + new JavaTask(tb) + .classpath(out.toString()) + .classArgs("T") + .run(); + } catch (Throwable error) { + throw new AssertionError("execution failed"); + } + } +} From bfc048aba6391d52c07d9a5146466b47d2f6fed8 Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Thu, 13 Nov 2025 16:26:17 +0000 Subject: [PATCH 036/418] 8371608: Jtreg test jdk/internal/vm/Continuation/Fuzz.java sometimes fails with (fast)debug binaries Reviewed-by: mdoerr, rrich --- test/jdk/jdk/internal/vm/Continuation/Fuzz.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/jdk/jdk/internal/vm/Continuation/Fuzz.java b/test/jdk/jdk/internal/vm/Continuation/Fuzz.java index 8d522cc83e1..49d86099dc7 100644 --- a/test/jdk/jdk/internal/vm/Continuation/Fuzz.java +++ b/test/jdk/jdk/internal/vm/Continuation/Fuzz.java @@ -96,6 +96,9 @@ public class Fuzz implements Runnable { if (Platform.isPPC()) { COMPILATION_TIMEOUT = COMPILATION_TIMEOUT * 2; } + if (Platform.isDebugBuild()) { + COMPILATION_TIMEOUT = COMPILATION_TIMEOUT * 2; + } warmup(); for (int compileLevel : new int[]{4}) { for (boolean compileRun : new boolean[]{true}) { From 2199b5fef4540ae8da77c5c4feafc8822a3d9d3d Mon Sep 17 00:00:00 2001 From: Rui Li Date: Thu, 13 Nov 2025 18:01:58 +0000 Subject: [PATCH 037/418] 8371381: [Shenandoah] Setting ergo flags should use FLAG_SET_ERGO Reviewed-by: xpeng, wkemper, ysr, cslucas --- .../share/gc/shenandoah/heuristics/shenandoahHeuristics.hpp | 6 +++--- .../share/gc/shenandoah/mode/shenandoahPassiveMode.cpp | 6 +++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.hpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.hpp index 6e3062d158f..3cd2cb1d171 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.hpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.hpp @@ -36,7 +36,7 @@ do { \ if (FLAG_IS_DEFAULT(name) && (name)) { \ log_info(gc)("Heuristics ergonomically sets -XX:-" #name); \ - FLAG_SET_DEFAULT(name, false); \ + FLAG_SET_ERGO(name, false); \ } \ } while (0) @@ -44,7 +44,7 @@ do { \ if (FLAG_IS_DEFAULT(name) && !(name)) { \ log_info(gc)("Heuristics ergonomically sets -XX:+" #name); \ - FLAG_SET_DEFAULT(name, true); \ + FLAG_SET_ERGO(name, true); \ } \ } while (0) @@ -52,7 +52,7 @@ do { \ if (FLAG_IS_DEFAULT(name)) { \ log_info(gc)("Heuristics ergonomically sets -XX:" #name "=" #value); \ - FLAG_SET_DEFAULT(name, value); \ + FLAG_SET_ERGO(name, value); \ } \ } while (0) diff --git a/src/hotspot/share/gc/shenandoah/mode/shenandoahPassiveMode.cpp b/src/hotspot/share/gc/shenandoah/mode/shenandoahPassiveMode.cpp index 4c0bc209d78..41b2703730b 100644 --- a/src/hotspot/share/gc/shenandoah/mode/shenandoahPassiveMode.cpp +++ b/src/hotspot/share/gc/shenandoah/mode/shenandoahPassiveMode.cpp @@ -29,6 +29,7 @@ #include "gc/shenandoah/shenandoahHeap.inline.hpp" #include "logging/log.hpp" #include "logging/logTag.hpp" +#include "runtime/globals_extension.hpp" #include "runtime/java.hpp" void ShenandoahPassiveMode::initialize_flags() const { @@ -38,7 +39,10 @@ void ShenandoahPassiveMode::initialize_flags() const { // No need for evacuation reserve with Full GC, only for Degenerated GC. if (!ShenandoahDegeneratedGC) { - SHENANDOAH_ERGO_OVERRIDE_DEFAULT(ShenandoahEvacReserve, 0); + if (FLAG_IS_DEFAULT(ShenandoahEvacReserve)) { + log_info(gc)("Heuristics sets -XX:ShenandoahEvacReserve=0"); + FLAG_SET_DEFAULT(ShenandoahEvacReserve, 0); + } } // Disable known barriers by default. From d09a8cb81b70a6c51ef5599bee04f1445a48e63f Mon Sep 17 00:00:00 2001 From: Sergey Bylokhov Date: Thu, 13 Nov 2025 18:39:49 +0000 Subject: [PATCH 038/418] 8371746: Some imports in Integer.java and Long.java became unused after JDK-8370503 Reviewed-by: liach, darcy, iris --- src/java.base/share/classes/java/lang/Integer.java | 2 -- src/java.base/share/classes/java/lang/Long.java | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/java.base/share/classes/java/lang/Integer.java b/src/java.base/share/classes/java/lang/Integer.java index 20d1edb6d5f..2742ec40abf 100644 --- a/src/java.base/share/classes/java/lang/Integer.java +++ b/src/java.base/share/classes/java/lang/Integer.java @@ -41,8 +41,6 @@ import java.util.Optional; import static java.lang.Character.digit; import static java.lang.String.COMPACT_STRINGS; -import static java.lang.String.LATIN1; -import static java.lang.String.UTF16; /** * The {@code Integer} class is the {@linkplain diff --git a/src/java.base/share/classes/java/lang/Long.java b/src/java.base/share/classes/java/lang/Long.java index b0477fdab6d..3077e7c0a38 100644 --- a/src/java.base/share/classes/java/lang/Long.java +++ b/src/java.base/share/classes/java/lang/Long.java @@ -41,8 +41,6 @@ import jdk.internal.vm.annotation.Stable; import static java.lang.Character.digit; import static java.lang.String.COMPACT_STRINGS; -import static java.lang.String.LATIN1; -import static java.lang.String.UTF16; /** * The {@code Long} class is the {@linkplain From db3a8386d482c161c45fae1689826bd53709f11f Mon Sep 17 00:00:00 2001 From: Brian Burkhalter Date: Thu, 13 Nov 2025 18:59:34 +0000 Subject: [PATCH 039/418] 8371436: (fs) java/nio/file/FileStore/Basic.java fails on macOS platform due to assertTrue(!store.equals(prev)); Reviewed-by: alanb --- .../macosx/native/libnio/fs/BsdNativeDispatcher.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/java.base/macosx/native/libnio/fs/BsdNativeDispatcher.c b/src/java.base/macosx/native/libnio/fs/BsdNativeDispatcher.c index 8776411be07..7a9e92d6fa9 100644 --- a/src/java.base/macosx/native/libnio/fs/BsdNativeDispatcher.c +++ b/src/java.base/macosx/native/libnio/fs/BsdNativeDispatcher.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2025, 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 @@ -50,6 +50,7 @@ static jfieldID entry_name; static jfieldID entry_dir; static jfieldID entry_fstype; static jfieldID entry_options; +static jfieldID entry_dev; struct fsstat_iter { struct statfs *buf; @@ -85,6 +86,8 @@ Java_sun_nio_fs_BsdNativeDispatcher_initIDs(JNIEnv* env, jclass this) CHECK_NULL(entry_fstype); entry_options = (*env)->GetFieldID(env, clazz, "opts", "[B"); CHECK_NULL(entry_options); + entry_dev = (*env)->GetFieldID(env, clazz, "dev", "J"); + CHECK_NULL(entry_dev); } JNIEXPORT jlong JNICALL @@ -151,6 +154,8 @@ Java_sun_nio_fs_BsdNativeDispatcher_fsstatEntry(JNIEnv* env, jclass this, char* dir; char* fstype; char* options; + int32_t fsid_val[2]; + long dev; if (iter == NULL || iter->pos >= iter->nentries) return -1; @@ -162,6 +167,8 @@ Java_sun_nio_fs_BsdNativeDispatcher_fsstatEntry(JNIEnv* env, jclass this, options="ro"; else options=""; + fsid_val[0] = iter->buf[iter->pos].f_fsid.val[0]; + fsid_val[1] = iter->buf[iter->pos].f_fsid.val[1]; iter->pos++; @@ -193,6 +200,9 @@ Java_sun_nio_fs_BsdNativeDispatcher_fsstatEntry(JNIEnv* env, jclass this, (*env)->SetByteArrayRegion(env, bytes, 0, len, (jbyte*)options); (*env)->SetObjectField(env, entry, entry_options, bytes); + dev = (((long)fsid_val[1]) << 32) | (long)fsid_val[0]; + (*env)->SetLongField(env, entry, entry_dev, long_to_jlong(dev)); + return 0; } From 6322aaba63b235cb6c73d23a932210af318404ec Mon Sep 17 00:00:00 2001 From: Roger Riggs Date: Thu, 13 Nov 2025 19:08:35 +0000 Subject: [PATCH 040/418] 8371821: Duplicate export of jdk.internal.util to java.net.http Reviewed-by: naoto, alanb --- src/java.base/share/classes/module-info.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/java.base/share/classes/module-info.java b/src/java.base/share/classes/module-info.java index 3ae84fdf198..70a79390828 100644 --- a/src/java.base/share/classes/module-info.java +++ b/src/java.base/share/classes/module-info.java @@ -262,7 +262,6 @@ module java.base { jdk.jfr; exports jdk.internal.util to java.desktop, - java.net.http, java.prefs, java.security.jgss, java.smartcardio, From 155d7df555fcebc318db89408ef0fffbd95414a0 Mon Sep 17 00:00:00 2001 From: Leonid Mesnik Date: Thu, 13 Nov 2025 23:54:07 +0000 Subject: [PATCH 041/418] 8371749: New test serviceability/jvmti/events/VMDeath/AllocatingInVMDeath/TestAllocatingInVMDeath.java fails with -Xcheck:jni Reviewed-by: sspitsyn, amenkov, cjplummer --- .../AllocatingInVMDeath/libTestAllocatingInVMDeath.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/hotspot/jtreg/serviceability/jvmti/events/VMDeath/AllocatingInVMDeath/libTestAllocatingInVMDeath.cpp b/test/hotspot/jtreg/serviceability/jvmti/events/VMDeath/AllocatingInVMDeath/libTestAllocatingInVMDeath.cpp index 5f46d69afa1..93d66bc38bc 100644 --- a/test/hotspot/jtreg/serviceability/jvmti/events/VMDeath/AllocatingInVMDeath/libTestAllocatingInVMDeath.cpp +++ b/test/hotspot/jtreg/serviceability/jvmti/events/VMDeath/AllocatingInVMDeath/libTestAllocatingInVMDeath.cpp @@ -37,6 +37,10 @@ cbVMDeath(jvmtiEnv* jvmti, JNIEnv* jni) { fatal(jni, "Can't find upCall method."); } jni->CallStaticObjectMethod(clz, mid); + if (jni->ExceptionOccurred()) { + jni->ExceptionDescribe(); + fatal(jni, "cbVMDeath: unexpected exception occurred in Java upcall method."); + } } JNIEXPORT jint JNICALL From 0d8b5188bb4315be3c63898a2ce4e68dd2bd4481 Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Fri, 14 Nov 2025 01:07:05 +0000 Subject: [PATCH 042/418] 8364560: The default value of --linux-menu-group option is invalid 8356574: Test --linux-menu-group option Reviewed-by: almatvee --- .../internal/LinuxPackageBuilder.java | 10 ++++---- .../internal/model/LinuxPackageMixin.java | 15 +++++++++++- .../resources/LinuxResources.properties | 1 - .../jdk/jpackage/test/LinuxHelper.java | 3 ++- .../jpackage/linux/ShortcutHintTest.java | 24 +++++++++++++++---- 5 files changed, 41 insertions(+), 12 deletions(-) diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageBuilder.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageBuilder.java index cc00d7816f5..4cfb8a26c8f 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageBuilder.java +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageBuilder.java @@ -84,9 +84,9 @@ final class LinuxPackageBuilder { private LinuxPackage create(Package pkg) throws ConfigException { return LinuxPackage.create(pkg, new LinuxPackageMixin.Stub( Optional.ofNullable(menuGroupName).orElseGet(DEFAULTS::menuGroupName), - Optional.ofNullable(category), + category(), Optional.ofNullable(additionalDependencies), - Optional.ofNullable(release), + release(), pkg.asStandardPackageType().map(LinuxPackageArch::getValue).orElseThrow())); } @@ -192,7 +192,7 @@ final class LinuxPackageBuilder { private final PackageBuilder pkgBuilder; - private static final Defaults DEFAULTS = new Defaults(I18N.getString( - "param.menu-group.default")); - + // Should be one of https://specifications.freedesktop.org/menu/latest/category-registry.html#main-category-registry + // The category is an ID, not a localizable string + private static final Defaults DEFAULTS = new Defaults("Utility"); } diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/model/LinuxPackageMixin.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/model/LinuxPackageMixin.java index 5bcf57194f6..056e3b89527 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/model/LinuxPackageMixin.java +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/model/LinuxPackageMixin.java @@ -34,6 +34,12 @@ public interface LinuxPackageMixin { /** * Gets the name of the start menu group where to create shortcuts for * application launchers of this package. + *

    + * It sets the value of the "Categories" property in .desktop files of the + * package. + *

    + * Should be one of the values from https://specifications.freedesktop.org/menu/latest/category-registry.html * * @return the name of the start menu group where to create shortcuts for * application launchers of this package @@ -44,6 +50,13 @@ public interface LinuxPackageMixin { /** * Gets the category of this package. + *

    + * For RPM packages this is the value of the optional "Group" property. + *

    + * For DEB packages this is the value of the mandatory "Section" property. + * The + * present list of recognized values. * * @return the category of this package */ @@ -62,7 +75,7 @@ public interface LinuxPackageMixin { * Gets the release of this package. Returns an empty {@link Optional} instance * if this package doesn't have a release. *

    - * For RPM packages, this is the value of a "Release" property in spec file. RPM + * For RPM packages, this is the value of the "Release" property in spec file. RPM * packages always have a release. *

    * For DEB packages, this is an optional {@code debian_revision} component of a diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources.properties b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources.properties index fbc83ba2e10..a732d02c7d1 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources.properties +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources.properties @@ -28,7 +28,6 @@ deb.bundler.name=DEB Bundle rpm.bundler.name=RPM Bundle param.license-type.default=Unknown -param.menu-group.default=Unknown resource.deb-control-file=DEB control file resource.deb-preinstall-script=DEB preinstall script diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LinuxHelper.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LinuxHelper.java index 25358e8ecdc..e795f7c9760 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LinuxHelper.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LinuxHelper.java @@ -564,7 +564,8 @@ public final class LinuxHelper { for (var e : List.of( Map.entry("Type", "Application"), Map.entry("Terminal", "false"), - Map.entry("Comment", launcherDescription) + Map.entry("Comment", launcherDescription), + Map.entry("Categories", Optional.ofNullable(cmd.getArgumentValue("--linux-menu-group")).orElse("Utility")) )) { String key = e.getKey(); TKit.assertEquals(e.getValue(), data.find(key).orElseThrow(), String.format( diff --git a/test/jdk/tools/jpackage/linux/ShortcutHintTest.java b/test/jdk/tools/jpackage/linux/ShortcutHintTest.java index 8d373cb2b86..2591d1d393a 100644 --- a/test/jdk/tools/jpackage/linux/ShortcutHintTest.java +++ b/test/jdk/tools/jpackage/linux/ShortcutHintTest.java @@ -26,13 +26,15 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import jdk.jpackage.test.AdditionalLauncher; +import jdk.jpackage.test.Annotations.Parameter; +import jdk.jpackage.test.Annotations.Test; import jdk.jpackage.test.FileAssociations; -import jdk.jpackage.test.PackageType; -import jdk.jpackage.test.PackageTest; -import jdk.jpackage.test.TKit; import jdk.jpackage.test.JPackageCommand; import jdk.jpackage.test.LinuxHelper; -import jdk.jpackage.test.Annotations.Test; +import jdk.jpackage.test.PackageTest; +import jdk.jpackage.test.PackageType; +import jdk.jpackage.test.RunnablePackageTest.Action; +import jdk.jpackage.test.TKit; /** * Test --linux-shortcut parameter. Output of the test should be @@ -179,4 +181,18 @@ public class ShortcutHintTest { .apply(Files.readAllLines(desktopFile)); }).run(); } + + /** + * Test "--linux-menu-group" option. + * + * @param menuGroup value of "--linux-menu-group" option + */ + @Test + // Values from https://specifications.freedesktop.org/menu/latest/category-registry.html#main-category-registry + @Parameter("Development") + public static void testMenuGroup(String menuGroup) { + createTest().addInitializer(JPackageCommand::setFakeRuntime).addInitializer(cmd -> { + cmd.addArgument("--linux-shortcut").setArgumentValue("--linux-menu-group", menuGroup); + }).run(Action.CREATE_AND_UNPACK); + } } From eaddefb475c6431821c2d62baf550ba2c5f357bf Mon Sep 17 00:00:00 2001 From: Fei Yang Date: Fri, 14 Nov 2025 01:10:11 +0000 Subject: [PATCH 043/418] 8371753: compiler/c2/cr7200264/TestIntVect.java fails IR verification Reviewed-by: chagedorn, fjiang --- .../compiler/c2/cr7200264/TestIntVect.java | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/test/hotspot/jtreg/compiler/c2/cr7200264/TestIntVect.java b/test/hotspot/jtreg/compiler/c2/cr7200264/TestIntVect.java index 76c33ec1b07..3df6f956b0f 100644 --- a/test/hotspot/jtreg/compiler/c2/cr7200264/TestIntVect.java +++ b/test/hotspot/jtreg/compiler/c2/cr7200264/TestIntVect.java @@ -413,7 +413,8 @@ public class TestIntVect { @Test @IR(counts = { IRNode.LOAD_VECTOR_I, "> 0", IRNode.ADD_REDUCTION_VI, "> 0", - IRNode.ADD_VI, "> 0" }) + IRNode.ADD_VI, "> 0" }, + applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true", "rvv", "true"}) // The reduction is moved outside the loop, and we use a // element-wise accumulator inside the loop. int test_sum(int[] a1) { @@ -426,7 +427,7 @@ public class TestIntVect { @Test @IR(counts = { IRNode.ADD_VI, "> 0" }, - applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true"}) + applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true", "rvv", "true"}) void test_addc(int[] a0, int[] a1) { for (int i = 0; i < a0.length; i+=1) { a0[i] = (int)(a1[i]+VALUE); @@ -435,7 +436,7 @@ public class TestIntVect { @Test @IR(counts = { IRNode.ADD_VI, "> 0" }, - applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true"}) + applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true", "rvv", "true"}) void test_addv(int[] a0, int[] a1, int b) { for (int i = 0; i < a0.length; i+=1) { a0[i] = (int)(a1[i]+b); @@ -444,7 +445,7 @@ public class TestIntVect { @Test @IR(counts = { IRNode.ADD_VI, "> 0" }, - applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true"}) + applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true", "rvv", "true"}) void test_adda(int[] a0, int[] a1, int[] a2) { for (int i = 0; i < a0.length; i+=1) { a0[i] = (int)(a1[i]+a2[i]); @@ -453,7 +454,7 @@ public class TestIntVect { @Test @IR(counts = { IRNode.ADD_VI, "> 0" }, - applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true"}) + applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true", "rvv", "true"}) void test_subc(int[] a0, int[] a1) { for (int i = 0; i < a0.length; i+=1) { a0[i] = (int)(a1[i]-VALUE); @@ -462,7 +463,7 @@ public class TestIntVect { @Test @IR(counts = { IRNode.SUB_VI, "> 0" }, - applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true"}) + applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true", "rvv", "true"}) void test_subv(int[] a0, int[] a1, int b) { for (int i = 0; i < a0.length; i+=1) { a0[i] = (int)(a1[i]-b); @@ -471,7 +472,7 @@ public class TestIntVect { @Test @IR(counts = { IRNode.SUB_VI, "> 0", }, - applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true"}) + applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true", "rvv", "true"}) void test_suba(int[] a0, int[] a1, int[] a2) { for (int i = 0; i < a0.length; i+=1) { a0[i] = (int)(a1[i]-a2[i]); @@ -498,7 +499,7 @@ public class TestIntVect { @Test @IR(counts = { IRNode.MUL_VI, "> 0" }, - applyIfCPUFeatureOr = {"sse4.1", "true", "asimd", "true"}) + applyIfCPUFeatureOr = {"sse4.1", "true", "asimd", "true", "rvv", "true"}) void test_mulv(int[] a0, int[] a1, int b) { for (int i = 0; i < a0.length; i+=1) { a0[i] = (int)(a1[i]*b); @@ -507,7 +508,7 @@ public class TestIntVect { @Test @IR(counts = { IRNode.MUL_VI, "> 0" }, - applyIfCPUFeatureOr = {"sse4.1", "true", "asimd", "true"}) + applyIfCPUFeatureOr = {"sse4.1", "true", "asimd", "true", "rvv", "true"}) void test_mula(int[] a0, int[] a1, int[] a2) { for (int i = 0; i < a0.length; i+=1) { a0[i] = (int)(a1[i]*a2[i]); @@ -580,7 +581,7 @@ public class TestIntVect { @Test @IR(counts = { IRNode.AND_VI, "> 0" }, - applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true"}) + applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true", "rvv", "true"}) void test_andc(int[] a0, int[] a1) { for (int i = 0; i < a0.length; i+=1) { a0[i] = (int)(a1[i]&BIT_MASK); @@ -589,7 +590,7 @@ public class TestIntVect { @Test @IR(counts = { IRNode.AND_VI, "> 0" }, - applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true"}) + applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true", "rvv", "true"}) void test_andv(int[] a0, int[] a1, int b) { for (int i = 0; i < a0.length; i+=1) { a0[i] = (int)(a1[i]&b); @@ -598,7 +599,7 @@ public class TestIntVect { @Test @IR(counts = { IRNode.AND_VI, "> 0" }, - applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true"}) + applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true", "rvv", "true"}) void test_anda(int[] a0, int[] a1, int[] a2) { for (int i = 0; i < a0.length; i+=1) { a0[i] = (int)(a1[i]&a2[i]); @@ -607,7 +608,7 @@ public class TestIntVect { @Test @IR(counts = { IRNode.OR_VI, "> 0" }, - applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true"}) + applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true", "rvv", "true"}) void test_orc(int[] a0, int[] a1) { for (int i = 0; i < a0.length; i+=1) { a0[i] = (int)(a1[i]|BIT_MASK); @@ -616,7 +617,7 @@ public class TestIntVect { @Test @IR(counts = { IRNode.OR_VI, "> 0" }, - applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true"}) + applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true", "rvv", "true"}) void test_orv(int[] a0, int[] a1, int b) { for (int i = 0; i < a0.length; i+=1) { a0[i] = (int)(a1[i]|b); @@ -625,7 +626,7 @@ public class TestIntVect { @Test @IR(counts = { IRNode.OR_VI, "> 0" }, - applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true"}) + applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true", "rvv", "true"}) void test_ora(int[] a0, int[] a1, int[] a2) { for (int i = 0; i < a0.length; i+=1) { a0[i] = (int)(a1[i]|a2[i]); @@ -634,7 +635,7 @@ public class TestIntVect { @Test @IR(counts = { IRNode.XOR_VI, "> 0" }, - applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true"}) + applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true", "rvv", "true"}) void test_xorc(int[] a0, int[] a1) { for (int i = 0; i < a0.length; i+=1) { a0[i] = (int)(a1[i]^BIT_MASK); @@ -643,7 +644,7 @@ public class TestIntVect { @Test @IR(counts = { IRNode.XOR_VI, "> 0" }, - applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true"}) + applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true", "rvv", "true"}) void test_xorv(int[] a0, int[] a1, int b) { for (int i = 0; i < a0.length; i+=1) { a0[i] = (int)(a1[i]^b); @@ -652,7 +653,7 @@ public class TestIntVect { @Test @IR(counts = { IRNode.XOR_VI, "> 0" }, - applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true"}) + applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true", "rvv", "true"}) void test_xora(int[] a0, int[] a1, int[] a2) { for (int i = 0; i < a0.length; i+=1) { a0[i] = (int)(a1[i]^a2[i]); From 7733632f90a17ec848c4c9259c1aa58fded8c15a Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Fri, 14 Nov 2025 02:08:45 +0000 Subject: [PATCH 044/418] 8369206: jpackage should not set R/O permission on app launchers Reviewed-by: almatvee --- .../internal/ExecutableRebrander.java | 69 +++++++++---------- 1 file changed, 32 insertions(+), 37 deletions(-) diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/ExecutableRebrander.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/ExecutableRebrander.java index 913a44dbe18..05b87e6f449 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/ExecutableRebrander.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/ExecutableRebrander.java @@ -120,46 +120,41 @@ final class ExecutableRebrander { List actions) throws IOException { Objects.requireNonNull(actions); actions.forEach(Objects::requireNonNull); + + String tempDirectory = env.buildRoot().toAbsolutePath().toString(); + if (WindowsDefender.isThereAPotentialWindowsDefenderIssue(tempDirectory)) { + Log.verbose(I18N.format("message.potential.windows.defender.issue", tempDirectory)); + } + + var shortTargetPath = ShortPathUtils.toShortPath(target); + long resourceLock = lockResource(shortTargetPath.orElse(target).toString()); + if (resourceLock == 0) { + throw I18N.buildException().message("error.lock-resource", shortTargetPath.orElse(target)).create(RuntimeException::new); + } + + final boolean resourceUnlockedSuccess; try { - String tempDirectory = env.buildRoot().toAbsolutePath().toString(); - if (WindowsDefender.isThereAPotentialWindowsDefenderIssue(tempDirectory)) { - Log.verbose(I18N.format("message.potential.windows.defender.issue", tempDirectory)); - } - - target.toFile().setWritable(true, true); - - var shortTargetPath = ShortPathUtils.toShortPath(target); - long resourceLock = lockResource(shortTargetPath.orElse(target).toString()); - if (resourceLock == 0) { - throw I18N.buildException().message("error.lock-resource", shortTargetPath.orElse(target)).create(RuntimeException::new); - } - - final boolean resourceUnlockedSuccess; - try { - for (var action : actions) { - action.editResource(resourceLock); - } - } finally { - if (resourceLock == 0) { - resourceUnlockedSuccess = true; - } else { - resourceUnlockedSuccess = unlockResource(resourceLock); - if (shortTargetPath.isPresent()) { - // Windows will rename the excuatble in the unlock operation. - // Should restore executable's name. - var tmpPath = target.getParent().resolve( - target.getFileName().toString() + ".restore"); - Files.move(shortTargetPath.get(), tmpPath); - Files.move(tmpPath, target); - } - } - } - - if (!resourceUnlockedSuccess) { - throw I18N.buildException().message("error.unlock-resource", target).create(RuntimeException::new); + for (var action : actions) { + action.editResource(resourceLock); } } finally { - target.toFile().setReadOnly(); + if (resourceLock == 0) { + resourceUnlockedSuccess = true; + } else { + resourceUnlockedSuccess = unlockResource(resourceLock); + if (shortTargetPath.isPresent()) { + // Windows will rename the executable in the unlock operation. + // Should restore executable's name. + var tmpPath = target.getParent().resolve( + target.getFileName().toString() + ".restore"); + Files.move(shortTargetPath.get(), tmpPath); + Files.move(tmpPath, target); + } + } + } + + if (!resourceUnlockedSuccess) { + throw I18N.buildException().message("error.unlock-resource", target).create(RuntimeException::new); } } From 1baf5164d6a9077e0c440b7b78be6424a052f8a9 Mon Sep 17 00:00:00 2001 From: Daniel Skantz Date: Fri, 14 Nov 2025 07:09:05 +0000 Subject: [PATCH 045/418] 8371628: C2: add a test case for the arraycopy changes in JDK-8297933 Reviewed-by: rcastanedalo, shade --- .../compiler/arraycopy/TestACSameSrcDst.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/test/hotspot/jtreg/compiler/arraycopy/TestACSameSrcDst.java b/test/hotspot/jtreg/compiler/arraycopy/TestACSameSrcDst.java index f85bcf27d74..93b9fead584 100644 --- a/test/hotspot/jtreg/compiler/arraycopy/TestACSameSrcDst.java +++ b/test/hotspot/jtreg/compiler/arraycopy/TestACSameSrcDst.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2017, Red Hat, Inc. All rights reserved. + * Copyright (c) 2025, 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 +24,7 @@ /** * @test - * @bug 8179678 + * @bug 8179678 8297933 8371628 * @summary ArrayCopy with same src and dst can cause incorrect execution or compiler crash * * @run main/othervm -XX:CompileCommand=compileonly,TestACSameSrcDst::test* TestACSameSrcDst @@ -76,6 +77,17 @@ public class TestACSameSrcDst { return 0; } + static void test6(int x) { + int[] src = new int[10]; + int l = 0; + while (l < 1) { l++; } // Delay folding. + // The bug relies on idealizations of ArrayCopy source and destination offsets but this would be limited if carried out before IGVN. + + System.arraycopy(src, x + 1, src, x + 1, l); + // source and destination offsets are a shared AddNode that would go away during LShiftL idealization + // -- causing a crash if not for a hook node retaining its liveness. + } + public static void main(String[] args) { int[] array = new int[15]; for (int i = 0; i < 20000; i++) { @@ -101,6 +113,7 @@ public class TestACSameSrcDst { if (res != 0) { throw new RuntimeException("bad result: " + res + " != " + 0); } + test6(0); } } } From 0829c6acde496833300efb38b4b900bf94b99dc0 Mon Sep 17 00:00:00 2001 From: Anton Seoane Ampudia Date: Fri, 14 Nov 2025 07:25:44 +0000 Subject: [PATCH 046/418] 8356761: IGV: dump escape analysis information Reviewed-by: rcastanedalo, chagedorn --- src/hotspot/share/opto/escape.cpp | 41 +++++++++++++-- src/hotspot/share/opto/escape.hpp | 6 ++- src/hotspot/share/opto/idealGraphPrinter.cpp | 38 ++++++++++++++ src/hotspot/share/opto/idealGraphPrinter.hpp | 3 ++ src/hotspot/share/opto/phasetype.hpp | 17 +++++++ .../filters/colorEscapeAnalysis.filter | 51 +++++++++++++++++++ .../showConnectionGraphNodesOnly.filter | 6 +++ .../filters/showConnectionInfo.filter | 16 ++++++ .../sun/hotspot/igv/servercompiler/layer.xml | 14 ++++- .../lib/ir_framework/CompilePhase.java | 17 +++++++ 10 files changed, 204 insertions(+), 5 deletions(-) create mode 100644 src/utils/IdealGraphVisualizer/ServerCompiler/src/main/resources/com/sun/hotspot/igv/servercompiler/filters/colorEscapeAnalysis.filter create mode 100644 src/utils/IdealGraphVisualizer/ServerCompiler/src/main/resources/com/sun/hotspot/igv/servercompiler/filters/showConnectionGraphNodesOnly.filter create mode 100644 src/utils/IdealGraphVisualizer/ServerCompiler/src/main/resources/com/sun/hotspot/igv/servercompiler/filters/showConnectionInfo.filter diff --git a/src/hotspot/share/opto/escape.cpp b/src/hotspot/share/opto/escape.cpp index 61aa009361f..fb3b5dba42c 100644 --- a/src/hotspot/share/opto/escape.cpp +++ b/src/hotspot/share/opto/escape.cpp @@ -114,11 +114,13 @@ void ConnectionGraph::do_analysis(Compile *C, PhaseIterGVN *igvn) { invocation = C->congraph()->_invocation + 1; } ConnectionGraph* congraph = new(C->comp_arena()) ConnectionGraph(C, igvn, invocation); + NOT_PRODUCT(if (C->should_print_igv(/* Any level */ 1)) C->igv_printer()->set_congraph(congraph);) // Perform escape analysis if (congraph->compute_escape()) { // There are non escaping objects. C->set_congraph(congraph); } + NOT_PRODUCT(if (C->should_print_igv(/* Any level */ 1)) C->igv_printer()->set_congraph(nullptr);) // Cleanup. if (oop_null->outcnt() == 0) { igvn->hash_delete(oop_null); @@ -126,6 +128,8 @@ void ConnectionGraph::do_analysis(Compile *C, PhaseIterGVN *igvn) { if (noop_null->outcnt() == 0) { igvn->hash_delete(noop_null); } + + C->print_method(PHASE_AFTER_EA, 2); } bool ConnectionGraph::compute_escape() { @@ -281,6 +285,8 @@ bool ConnectionGraph::compute_escape() { return false; } + _compile->print_method(PHASE_EA_AFTER_INITIAL_CONGRAPH, 4); + // 2. Finish Graph construction by propagating references to all // java objects through graph. if (!complete_connection_graph(ptnodes_worklist, non_escaped_allocs_worklist, @@ -291,6 +297,8 @@ bool ConnectionGraph::compute_escape() { return false; } + _compile->print_method(PHASE_EA_AFTER_COMPLETE_CONGRAPH, 4); + // 3. Adjust scalar_replaceable state of nonescaping objects and push // scalar replaceable allocations on alloc_worklist for processing // in split_unique_types(). @@ -312,6 +320,7 @@ bool ConnectionGraph::compute_escape() { found_nsr_alloc = true; } } + _compile->print_method(PHASE_EA_ADJUST_SCALAR_REPLACEABLE_ITER, 6, n); } // Propagate NSR (Not Scalar Replaceable) state. @@ -350,6 +359,7 @@ bool ConnectionGraph::compute_escape() { _collecting = false; + _compile->print_method(PHASE_EA_AFTER_PROPAGATE_NSR, 4); } // TracePhase t3("connectionGraph") // 4. Optimize ideal graph based on EA information. @@ -387,6 +397,8 @@ bool ConnectionGraph::compute_escape() { } #endif + _compile->print_method(PHASE_EA_AFTER_GRAPH_OPTIMIZATION, 4); + // 5. Separate memory graph for scalar replaceable allcations. bool has_scalar_replaceable_candidates = (alloc_worklist.length() > 0); if (has_scalar_replaceable_candidates && EliminateAllocations) { @@ -398,7 +410,6 @@ bool ConnectionGraph::compute_escape() { NOT_PRODUCT(escape_state_statistics(java_objects_worklist);) return false; } - C->print_method(PHASE_AFTER_EA, 2); #ifdef ASSERT } else if (Verbose && (PrintEscapeAnalysis || PrintEliminateAllocations)) { @@ -413,6 +424,8 @@ bool ConnectionGraph::compute_escape() { #endif } + _compile->print_method(PHASE_EA_AFTER_SPLIT_UNIQUE_TYPES, 4); + // 6. Reduce allocation merges used as debug information. This is done after // split_unique_types because the methods used to create SafePointScalarObject // need to traverse the memory graph to find values for object fields. We also @@ -454,6 +467,8 @@ bool ConnectionGraph::compute_escape() { } } + _compile->print_method(PHASE_EA_AFTER_REDUCE_PHI_ON_SAFEPOINTS, 4); + NOT_PRODUCT(escape_state_statistics(java_objects_worklist);) return has_non_escaping_obj; } @@ -1302,11 +1317,14 @@ void ConnectionGraph::reduce_phi(PhiNode* ophi, GrowableArray &alloc_work } } + _compile->print_method(PHASE_EA_BEFORE_PHI_REDUCTION, 5, ophi); + // CastPPs need to be processed before Cmps because during the process of // splitting CastPPs we make reference to the inputs of the Cmp that is used // by the If controlling the CastPP. for (uint i = 0; i < castpps.size(); i++) { reduce_phi_on_castpp_field_load(castpps.at(i), alloc_worklist); + _compile->print_method(PHASE_EA_AFTER_PHI_CASTPP_REDUCTION, 6, castpps.at(i)); } for (uint i = 0; i < others.size(); i++) { @@ -1314,8 +1332,10 @@ void ConnectionGraph::reduce_phi(PhiNode* ophi, GrowableArray &alloc_work if (use->is_AddP()) { reduce_phi_on_field_access(use, alloc_worklist); + _compile->print_method(PHASE_EA_AFTER_PHI_ADDP_REDUCTION, 6, use); } else if(use->is_Cmp()) { reduce_phi_on_cmp(use); + _compile->print_method(PHASE_EA_AFTER_PHI_CMP_REDUCTION, 6, use); } } @@ -2417,6 +2437,7 @@ bool ConnectionGraph::complete_connection_graph( timeout = true; break; } + _compile->print_method(PHASE_EA_COMPLETE_CONNECTION_GRAPH_ITER, 5); } if ((iterations < GRAPH_BUILD_ITER_LIMIT) && !timeout) { time.start(); @@ -2490,7 +2511,8 @@ bool ConnectionGraph::complete_connection_graph( // Propagate GlobalEscape and ArgEscape escape states to all nodes // and check that we still have non-escaping java objects. bool ConnectionGraph::find_non_escaped_objects(GrowableArray& ptnodes_worklist, - GrowableArray& non_escaped_allocs_worklist) { + GrowableArray& non_escaped_allocs_worklist, + bool print_method) { GrowableArray escape_worklist; // First, put all nodes with GlobalEscape and ArgEscape states on worklist. int ptnodes_length = ptnodes_worklist.length(); @@ -2550,6 +2572,9 @@ bool ConnectionGraph::find_non_escaped_objects(GrowableArray& ptn escape_worklist.push(e); } } + if (print_method) { + _compile->print_method(PHASE_EA_CONNECTION_GRAPH_PROPAGATE_ITER, 6, e->ideal_node()); + } } } // Remove escaped objects from non_escaped list. @@ -3137,6 +3162,7 @@ void ConnectionGraph::find_scalar_replaceable_allocs(GrowableArrayprint_method(PHASE_EA_PROPAGATE_NSR_ITER, 5, jobj->ideal_node()); } } } @@ -3159,7 +3185,7 @@ void ConnectionGraph::verify_connection_graph( assert(new_edges == 0, "graph was not complete"); // Verify that escape state is final. int length = non_escaped_allocs_worklist.length(); - find_non_escaped_objects(ptnodes_worklist, non_escaped_allocs_worklist); + find_non_escaped_objects(ptnodes_worklist, non_escaped_allocs_worklist, /*print_method=*/ false); assert((non_escaped_length == non_escaped_allocs_worklist.length()) && (non_escaped_length == length) && (_worklist.length() == 0), "escape state was not final"); @@ -4720,6 +4746,8 @@ void ConnectionGraph::split_unique_types(GrowableArray &alloc_worklist, // New alias types were created in split_AddP(). uint new_index_end = (uint) _compile->num_alias_types(); + _compile->print_method(PHASE_EA_AFTER_SPLIT_UNIQUE_TYPES_1, 5); + // Phase 2: Process MemNode's from memnode_worklist. compute new address type and // compute new values for Memory inputs (the Memory inputs are not // actually updated until phase 4.) @@ -4920,6 +4948,8 @@ void ConnectionGraph::split_unique_types(GrowableArray &alloc_worklist, record_for_optimizer(nmm); } + _compile->print_method(PHASE_EA_AFTER_SPLIT_UNIQUE_TYPES_3, 5); + // Phase 4: Update the inputs of non-instance memory Phis and // the Memory input of memnodes // First update the inputs of any non-instance Phi's from @@ -4988,6 +5018,7 @@ void ConnectionGraph::split_unique_types(GrowableArray &alloc_worklist, assert(old_cnt == old_mem->outcnt(), "old mem could be lost"); } #endif + _compile->print_method(PHASE_EA_AFTER_SPLIT_UNIQUE_TYPES_4, 5); } #ifndef PRODUCT @@ -5010,6 +5041,10 @@ static const char *esc_names[] = { "GlobalEscape" }; +const char* PointsToNode::esc_name() const { + return esc_names[(int)escape_state()]; +} + void PointsToNode::dump_header(bool print_state, outputStream* out) const { NodeType nt = node_type(); out->print("%s(%d) ", node_type_names[(int) nt], _pidx); diff --git a/src/hotspot/share/opto/escape.hpp b/src/hotspot/share/opto/escape.hpp index 77d14525383..eea6403acdd 100644 --- a/src/hotspot/share/opto/escape.hpp +++ b/src/hotspot/share/opto/escape.hpp @@ -26,6 +26,7 @@ #define SHARE_OPTO_ESCAPE_HPP #include "opto/addnode.hpp" +#include "opto/idealGraphPrinter.hpp" #include "opto/node.hpp" #include "utilities/growableArray.hpp" @@ -235,6 +236,7 @@ public: NodeType node_type() const { return (NodeType)_type;} void dump(bool print_state=true, outputStream* out=tty, bool newline=true) const; void dump_header(bool print_state=true, outputStream* out=tty) const; + const char* esc_name() const; #endif }; @@ -321,6 +323,7 @@ public: class ConnectionGraph: public ArenaObj { friend class PointsToNode; // to access _compile friend class FieldNode; + friend class IdealGraphPrinter; private: GrowableArray _nodes; // Map from ideal nodes to // ConnectionGraph nodes. @@ -467,7 +470,8 @@ private: // Propagate GlobalEscape and ArgEscape escape states to all nodes // and check that we still have non-escaping java objects. bool find_non_escaped_objects(GrowableArray& ptnodes_worklist, - GrowableArray& non_escaped_worklist); + GrowableArray& non_escaped_worklist, + bool print_method = true); // Adjust scalar_replaceable state after Connection Graph is built. void adjust_scalar_replaceable_state(JavaObjectNode* jobj, Unique_Node_List &reducible_merges); diff --git a/src/hotspot/share/opto/idealGraphPrinter.cpp b/src/hotspot/share/opto/idealGraphPrinter.cpp index 2873c1ef9d7..6a738878a1b 100644 --- a/src/hotspot/share/opto/idealGraphPrinter.cpp +++ b/src/hotspot/share/opto/idealGraphPrinter.cpp @@ -24,6 +24,7 @@ #include "memory/resourceArea.hpp" #include "opto/chaitin.hpp" +#include "opto/escape.hpp" #include "opto/idealGraphPrinter.hpp" #include "opto/machnode.hpp" #include "opto/parse.hpp" @@ -161,6 +162,7 @@ void IdealGraphPrinter::init(const char* file_name, bool use_multiple_files, boo _current_method = nullptr; _network_stream = nullptr; _append = append; + _congraph = nullptr; _parse = nullptr; if (file_name != nullptr) { @@ -637,6 +639,29 @@ void IdealGraphPrinter::visit_node(Node* n, bool edges) { print_prop("is_block_start", "true"); } + // Dump escape analysis state for relevant nodes. + if (node->is_Allocate()) { + AllocateNode* alloc = node->as_Allocate(); + if (alloc->_is_scalar_replaceable) { + print_prop("is_scalar_replaceable", "true"); + } + if (alloc->_is_non_escaping) { + print_prop("is_non_escaping", "true"); + } + if (alloc->does_not_escape_thread()) { + print_prop("does_not_escape_thread", "true"); + } + } + if (node->is_SafePoint() && node->as_SafePoint()->has_ea_local_in_scope()) { + print_prop("has_ea_local_in_scope", "true"); + } + if (node->is_CallJava() && node->as_CallJava()->arg_escape()) { + print_prop("arg_escape", "true"); + } + if (node->is_Initialize() && node->as_Initialize()->does_not_escape()) { + print_prop("does_not_escape", "true"); + } + const char *short_name = "short_name"; if (strcmp(node->Name(), "Parm") == 0 && node->as_Proj()->_con >= TypeFunc::Parms) { int index = node->as_Proj()->_con - TypeFunc::Parms; @@ -731,6 +756,19 @@ void IdealGraphPrinter::visit_node(Node* n, bool edges) { print_prop("lrg", lrg_id); } + if (_congraph != nullptr && node->_idx < _congraph->nodes_size()) { + PointsToNode* ptn = _congraph->ptnode_adr(node->_idx); + if (ptn != nullptr) { + stringStream node_head; + ptn->dump_header(false, &node_head); + print_prop("ea_node", node_head.freeze()); + print_prop("escape_state", ptn->esc_name()); + if (ptn->scalar_replaceable()) { + print_prop("scalar_replaceable", "true"); + } + } + } + if (node->is_MachSafePoint()) { const OopMap* oopmap = node->as_MachSafePoint()->oop_map(); if (oopmap != nullptr) { diff --git a/src/hotspot/share/opto/idealGraphPrinter.hpp b/src/hotspot/share/opto/idealGraphPrinter.hpp index df1c6b254d5..2159779ddfa 100644 --- a/src/hotspot/share/opto/idealGraphPrinter.hpp +++ b/src/hotspot/share/opto/idealGraphPrinter.hpp @@ -42,6 +42,7 @@ class Node; class InlineTree; class ciMethod; class JVMState; +class ConnectionGraph; class Parse; class IdealGraphPrinter : public CHeapObj { @@ -116,6 +117,7 @@ class IdealGraphPrinter : public CHeapObj { Compile *C; double _max_freq; bool _append; + ConnectionGraph* _congraph; const Parse* _parse; // Walk the native stack and print relevant C2 frames as IGV properties (if @@ -165,6 +167,7 @@ class IdealGraphPrinter : public CHeapObj { void end_method(); void print_graph(const char* name, const frame* fr = nullptr); void print(const char* name, Node* root, GrowableArray& hidden_nodes, const frame* fr = nullptr); + void set_congraph(ConnectionGraph* congraph) { _congraph = congraph; } void set_compile(Compile* compile) {C = compile; } void update_compiled_method(ciMethod* current_method); }; diff --git a/src/hotspot/share/opto/phasetype.hpp b/src/hotspot/share/opto/phasetype.hpp index f24938b51c1..f388dc6cdc6 100644 --- a/src/hotspot/share/opto/phasetype.hpp +++ b/src/hotspot/share/opto/phasetype.hpp @@ -50,6 +50,23 @@ flags(ITER_GVN_AFTER_VECTOR, "Iter GVN after Vector Box Elimination") \ flags(BEFORE_LOOP_OPTS, "Before Loop Optimizations") \ flags(PHASEIDEAL_BEFORE_EA, "PhaseIdealLoop before EA") \ + flags(EA_AFTER_INITIAL_CONGRAPH, "EA: 1. Intial Connection Graph") \ + flags(EA_CONNECTION_GRAPH_PROPAGATE_ITER, "EA: 2. Connection Graph Propagate Iter") \ + flags(EA_COMPLETE_CONNECTION_GRAPH_ITER, "EA: 2. Complete Connection Graph Iter") \ + flags(EA_AFTER_COMPLETE_CONGRAPH, "EA: 2. Complete Connection Graph") \ + flags(EA_ADJUST_SCALAR_REPLACEABLE_ITER, "EA: 3. Adjust scalar_replaceable State Iter") \ + flags(EA_PROPAGATE_NSR_ITER, "EA: 3. Propagate NSR Iter") \ + flags(EA_AFTER_PROPAGATE_NSR, "EA: 3. Propagate NSR") \ + flags(EA_AFTER_GRAPH_OPTIMIZATION, "EA: 4. After Graph Optimization") \ + flags(EA_AFTER_SPLIT_UNIQUE_TYPES_1, "EA: 5. After split_unique_types Phase 1") \ + flags(EA_AFTER_SPLIT_UNIQUE_TYPES_3, "EA: 5. After split_unique_types Phase 3") \ + flags(EA_AFTER_SPLIT_UNIQUE_TYPES_4, "EA: 5. After split_unique_types Phase 4") \ + flags(EA_AFTER_SPLIT_UNIQUE_TYPES, "EA: 5. After split_unique_types") \ + flags(EA_AFTER_REDUCE_PHI_ON_SAFEPOINTS, "EA: 6. After reduce_phi_on_safepoints") \ + flags(EA_BEFORE_PHI_REDUCTION, "EA: 5. Before Phi Reduction") \ + flags(EA_AFTER_PHI_CASTPP_REDUCTION, "EA: 5. Phi -> CastPP Reduction") \ + flags(EA_AFTER_PHI_ADDP_REDUCTION, "EA: 5. Phi -> AddP Reduction") \ + flags(EA_AFTER_PHI_CMP_REDUCTION, "EA: 5. Phi -> Cmp Reduction") \ flags(AFTER_EA, "After Escape Analysis") \ flags(ITER_GVN_AFTER_EA, "Iter GVN after EA") \ flags(BEFORE_BEAUTIFY_LOOPS, "Before Beautify Loops") \ diff --git a/src/utils/IdealGraphVisualizer/ServerCompiler/src/main/resources/com/sun/hotspot/igv/servercompiler/filters/colorEscapeAnalysis.filter b/src/utils/IdealGraphVisualizer/ServerCompiler/src/main/resources/com/sun/hotspot/igv/servercompiler/filters/colorEscapeAnalysis.filter new file mode 100644 index 00000000000..2ef14f65110 --- /dev/null +++ b/src/utils/IdealGraphVisualizer/ServerCompiler/src/main/resources/com/sun/hotspot/igv/servercompiler/filters/colorEscapeAnalysis.filter @@ -0,0 +1,51 @@ +// Color those nodes present in the escape analysis connection graph +// to indicate the result of scape analysis. +// This filter is relevant between the first EA phase and "After Macro +// Expansion". + +var bestColor = java.awt.Color.decode("#6aa84f"); // Green. +var betterColor = java.awt.Color.decode("#f1c232"); // Yellow. +var worseColor = java.awt.Color.decode("#e69138"); // Orange. +var worstColor = java.awt.Color.decode("#cc0000"); // Red. + +// Apply first colors based on persistent node attributes + +// Object does not escape compilation unit and is scalar replaceable. +colorize(and([hasProperty("is_non_escaping"), + hasProperty("is_scalar_replaceable")]), + bestColor); + +// Object does not escape compilation unit but is not scalar replaceable, +// due to of scalar replacement limitations. We can at least elide locks. +colorize(and([hasProperty("is_non_escaping"), + not(hasProperty("is_scalar_replaceable"))]), + betterColor); + +// Object may escape compilation unit but does not escape thread. +// We can at least elide locks. +colorize(and([hasProperty("does_not_escape_thread"), + not(hasProperty("is_non_escaping"))]), + worseColor); + +// Object may escape compilation unit and thread. Nothing to do. +colorize(and([matches("name", "Allocate"), + not(hasProperty("is_non_escaping")), + not(hasProperty("does_not_escape_thread"))]), + worstColor); + +// Apply colors again based on connection graph-derived attributes + +colorize(and([matches("escape_state", "NoEscape"), + hasProperty("scalar_replaceable")]), + bestColor); + +colorize(and([matches("escape_state", "NoEscape"), + not(hasProperty("scalar_replaceable"))]), + betterColor); + +colorize(and([matches("escape_state", "ArgEscape"), + not(hasProperty("scalar_replaceable"))]), + worseColor); + +colorize(matches("escape_state", "GlobalEscape"), + worstColor); diff --git a/src/utils/IdealGraphVisualizer/ServerCompiler/src/main/resources/com/sun/hotspot/igv/servercompiler/filters/showConnectionGraphNodesOnly.filter b/src/utils/IdealGraphVisualizer/ServerCompiler/src/main/resources/com/sun/hotspot/igv/servercompiler/filters/showConnectionGraphNodesOnly.filter new file mode 100644 index 00000000000..b0986db221a --- /dev/null +++ b/src/utils/IdealGraphVisualizer/ServerCompiler/src/main/resources/com/sun/hotspot/igv/servercompiler/filters/showConnectionGraphNodesOnly.filter @@ -0,0 +1,6 @@ +// This filter shows only the nodes that are present in the escape analysis +// connection graph. This can be used to approximate the connection graph inside +// IGV. +// This filter is only relevant during the escape analysis phases. + +remove(not(hasProperty("ea_node"))); diff --git a/src/utils/IdealGraphVisualizer/ServerCompiler/src/main/resources/com/sun/hotspot/igv/servercompiler/filters/showConnectionInfo.filter b/src/utils/IdealGraphVisualizer/ServerCompiler/src/main/resources/com/sun/hotspot/igv/servercompiler/filters/showConnectionInfo.filter new file mode 100644 index 00000000000..ca3509687f5 --- /dev/null +++ b/src/utils/IdealGraphVisualizer/ServerCompiler/src/main/resources/com/sun/hotspot/igv/servercompiler/filters/showConnectionInfo.filter @@ -0,0 +1,16 @@ +// This filter appends escape analysis connection graph node information to the +// (possibly already existing) extra-label line. +// This is only carried out for those nodes that are relevant to escape +// analysis (and therefore represented in the connection graph). + +// Merge a possibly existing extra label with the escape analysis node type into a +// new, single extra label. +function mergeAndAppendTypeInfo(extra_label, ea_node) { + new_extra_label = extra_label == null ? "" : (extra_label + " "); + return new_extra_label + ea_node; +} + +editProperty(hasProperty("ea_node"), + ["extra_label", "ea_node"], + "extra_label", + function(propertyValues) {return mergeAndAppendTypeInfo(propertyValues[0], propertyValues[1]);}); diff --git a/src/utils/IdealGraphVisualizer/ServerCompiler/src/main/resources/com/sun/hotspot/igv/servercompiler/layer.xml b/src/utils/IdealGraphVisualizer/ServerCompiler/src/main/resources/com/sun/hotspot/igv/servercompiler/layer.xml index db4682f1cab..38dfc911a44 100644 --- a/src/utils/IdealGraphVisualizer/ServerCompiler/src/main/resources/com/sun/hotspot/igv/servercompiler/layer.xml +++ b/src/utils/IdealGraphVisualizer/ServerCompiler/src/main/resources/com/sun/hotspot/igv/servercompiler/layer.xml @@ -85,9 +85,21 @@ + + + + + + + + + + + + - + diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/CompilePhase.java b/test/hotspot/jtreg/compiler/lib/ir_framework/CompilePhase.java index 06b2afa8a67..a536808d269 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/CompilePhase.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/CompilePhase.java @@ -60,6 +60,23 @@ public enum CompilePhase { ITER_GVN_AFTER_VECTOR( "Iter GVN after Vector Box Elimination"), BEFORE_LOOP_OPTS( "Before Loop Optimizations"), PHASEIDEAL_BEFORE_EA( "PhaseIdealLoop before EA"), + EA_AFTER_INITIAL_CONGRAPH( "EA: 1. Intial Connection Graph"), + EA_CONNECTION_GRAPH_PROPAGATE_ITER("EA: 2. Connection Graph Propagate Iter"), + EA_COMPLETE_CONNECTION_GRAPH_ITER( "EA: 2. Complete Connection Graph Iter"), + EA_AFTER_COMPLETE_CONGRAPH( "EA: 2. Complete Connection Graph"), + EA_ADJUST_SCALAR_REPLACEABLE_ITER( "EA: 3. Adjust scalar_replaceable State Iter"), + EA_PROPAGATE_NSR_ITER( "EA: 3. Propagate NSR Iter"), + EA_AFTER_PROPAGATE_NSR( "EA: 3. Propagate NSR"), + EA_AFTER_GRAPH_OPTIMIZATION( "EA: 4. After Graph Optimization"), + EA_AFTER_SPLIT_UNIQUE_TYPES_1( "EA: 5. After split_unique_types Phase 1"), + EA_AFTER_SPLIT_UNIQUE_TYPES_3( "EA: 5. After split_unique_types Phase 3"), + EA_AFTER_SPLIT_UNIQUE_TYPES_4( "EA: 5. After split_unique_types Phase 4"), + EA_AFTER_SPLIT_UNIQUE_TYPES( "EA: 5. After split_unique_types"), + EA_AFTER_REDUCE_PHI_ON_SAFEPOINTS( "EA: 6. After reduce_phi_on_safepoints"), + EA_BEFORE_PHI_REDUCTION( "EA: 5. Before Phi Reduction"), + EA_AFTER_PHI_CASTPP_REDUCTION( "EA: 5. Phi -> CastPP Reduction"), + EA_AFTER_PHI_ADDP_REDUCTION( "EA: 5. Phi -> AddP Reduction"), + EA_AFTER_PHI_CMP_REDUCTION( "EA: 5. Phi -> Cmp Reduction"), AFTER_EA( "After Escape Analysis"), ITER_GVN_AFTER_EA( "Iter GVN after EA"), BEFORE_BEAUTIFY_LOOPS( "Before Beautify Loops"), From f4305923fb6203089fd13cf3387c81e127ae5fe2 Mon Sep 17 00:00:00 2001 From: Anton Seoane Ampudia Date: Fri, 14 Nov 2025 07:26:03 +0000 Subject: [PATCH 047/418] 8369002: Extract the loop->is_member(get_loop(get_ctrl(node))) pattern in a new function Reviewed-by: bmaillard, rcastanedalo --- src/hotspot/share/opto/loopTransform.cpp | 12 +++++----- src/hotspot/share/opto/loopnode.cpp | 10 ++++----- src/hotspot/share/opto/loopnode.hpp | 12 +++++++--- src/hotspot/share/opto/loopopts.cpp | 28 ++++++++++-------------- src/hotspot/share/opto/predicates.cpp | 2 +- src/hotspot/share/opto/superword.cpp | 3 +-- 6 files changed, 33 insertions(+), 34 deletions(-) diff --git a/src/hotspot/share/opto/loopTransform.cpp b/src/hotspot/share/opto/loopTransform.cpp index 9a21c7f5dda..31d1cbe0443 100644 --- a/src/hotspot/share/opto/loopTransform.cpp +++ b/src/hotspot/share/opto/loopTransform.cpp @@ -107,7 +107,7 @@ void IdealLoopTree::compute_trip_count(PhaseIdealLoop* phase, BasicType loop_bt) cl->set_nonexact_trip_count(); // Loop's test should be part of loop. - if (!phase->is_member(this, phase->get_ctrl(cl->loopexit()->in(CountedLoopEndNode::TestValue)))) + if (!phase->ctrl_is_member(this, cl->loopexit()->in(CountedLoopEndNode::TestValue))) return; // Infinite loop #ifdef ASSERT @@ -611,7 +611,7 @@ void PhaseIdealLoop::peeled_dom_test_elim(IdealLoopTree* loop, Node_List& old_ne if (test_cond != nullptr && // Test? !test_cond->is_Con() && // And not already obvious? // And condition is not a member of this loop? - !loop->is_member(get_loop(get_ctrl(test_cond)))) { + !ctrl_is_member(loop, test_cond)) { // Walk loop body looking for instances of this test for (uint i = 0; i < loop->_body.size(); i++) { Node* n = loop->_body.at(i); @@ -1682,7 +1682,7 @@ Node* PhaseIdealLoop::find_last_store_in_outer_loop(Node* store, const IdealLoop for (DUIterator_Fast imax, l = last->fast_outs(imax); l < imax; l++) { Node* use = last->fast_out(l); if (use->is_Store() && use->in(MemNode::Memory) == last) { - if (is_member(outer_loop, get_ctrl(use))) { + if (ctrl_is_member(outer_loop, use)) { assert(unique_next == last, "memory node should only have one usage in the loop body"); unique_next = use; } @@ -1795,7 +1795,7 @@ Node *PhaseIdealLoop::insert_post_loop(IdealLoopTree* loop, Node_List& old_new, // as this is when we would normally expect a Phi as input. If the memory input // is in the loop body as well, then we can safely assume it is still correct as the entire // body was cloned as a unit - if (!is_member(outer_loop, get_ctrl(store->in(MemNode::Memory)))) { + if (!ctrl_is_member(outer_loop, store->in(MemNode::Memory))) { Node* mem_out = find_last_store_in_outer_loop(store, outer_loop); Node* store_new = old_new[store->_idx]; store_new->set_req(MemNode::Memory, mem_out); @@ -3285,7 +3285,7 @@ bool IdealLoopTree::empty_loop_candidate(PhaseIdealLoop* phase) const { if (!cl->is_valid_counted_loop(T_INT)) { return false; // Malformed loop } - if (!phase->is_member(this, phase->get_ctrl(cl->loopexit()->in(CountedLoopEndNode::TestValue)))) { + if (!phase->ctrl_is_member(this, cl->loopexit()->in(CountedLoopEndNode::TestValue))) { return false; // Infinite loop } return true; @@ -3376,7 +3376,7 @@ bool IdealLoopTree::empty_loop_with_extra_nodes_candidate(PhaseIdealLoop* phase) return false; } - if (phase->is_member(this, phase->get_ctrl(cl->limit()))) { + if (phase->ctrl_is_member(this, cl->limit())) { return false; } return true; diff --git a/src/hotspot/share/opto/loopnode.cpp b/src/hotspot/share/opto/loopnode.cpp index 3b398b053a3..dfff7ef96a5 100644 --- a/src/hotspot/share/opto/loopnode.cpp +++ b/src/hotspot/share/opto/loopnode.cpp @@ -463,16 +463,16 @@ Node* PhaseIdealLoop::loop_exit_test(Node* back_control, IdealLoopTree* loop, No // need 'loop()' test to tell if limit is loop invariant // --------- - if (!is_member(loop, get_ctrl(incr))) { // Swapped trip counter and limit? + if (!ctrl_is_member(loop, incr)) { // Swapped trip counter and limit? Node* tmp = incr; // Then reverse order into the CmpI incr = limit; limit = tmp; bt = BoolTest(bt).commute(); // And commute the exit test } - if (is_member(loop, get_ctrl(limit))) { // Limit must be loop-invariant + if (ctrl_is_member(loop, limit)) { // Limit must be loop-invariant return nullptr; } - if (!is_member(loop, get_ctrl(incr))) { // Trip counter must be loop-variant + if (!ctrl_is_member(loop, incr)) { // Trip counter must be loop-variant return nullptr; } return cmp; @@ -485,7 +485,7 @@ Node* PhaseIdealLoop::loop_iv_incr(Node* incr, Node* x, IdealLoopTree* loop, Nod } phi_incr = incr; incr = phi_incr->in(LoopNode::LoopBackControl); // Assume incr is on backedge of Phi - if (!is_member(loop, get_ctrl(incr))) { // Trip counter must be loop-variant + if (!ctrl_is_member(loop, incr)) { // Trip counter must be loop-variant return nullptr; } } @@ -1795,7 +1795,7 @@ bool PhaseIdealLoop::convert_to_long_loop(Node* cmp, Node* phi, IdealLoopTree* l if (in == nullptr) { continue; } - if (loop->is_member(get_loop(get_ctrl(in)))) { + if (ctrl_is_member(loop, in)) { iv_nodes.push(in); } } diff --git a/src/hotspot/share/opto/loopnode.hpp b/src/hotspot/share/opto/loopnode.hpp index 7ea66ea5486..1e34331f213 100644 --- a/src/hotspot/share/opto/loopnode.hpp +++ b/src/hotspot/share/opto/loopnode.hpp @@ -1376,7 +1376,7 @@ public: Node* exact_limit( IdealLoopTree *loop ); // Return a post-walked LoopNode - IdealLoopTree *get_loop( Node *n ) const { + IdealLoopTree* get_loop(const Node* n) const { // Dead nodes have no loop, so return the top level loop instead if (!has_node(n)) return _ltree_root; assert(!has_ctrl(n), ""); @@ -1386,8 +1386,14 @@ public: IdealLoopTree* ltree_root() const { return _ltree_root; } // Is 'n' a (nested) member of 'loop'? - int is_member( const IdealLoopTree *loop, Node *n ) const { - return loop->is_member(get_loop(n)); } + bool is_member(const IdealLoopTree* loop, const Node* n) const { + return loop->is_member(get_loop(n)); + } + + // is the control for 'n' a (nested) member of 'loop'? + bool ctrl_is_member(const IdealLoopTree* loop, const Node* n) { + return is_member(loop, get_ctrl(n)); + } // This is the basic building block of the loop optimizations. It clones an // entire loop body. It makes an old_new loop body mapping; with this diff --git a/src/hotspot/share/opto/loopopts.cpp b/src/hotspot/share/opto/loopopts.cpp index 50b1ae0de8d..3ef6a085b1c 100644 --- a/src/hotspot/share/opto/loopopts.cpp +++ b/src/hotspot/share/opto/loopopts.cpp @@ -637,11 +637,11 @@ Node* PhaseIdealLoop::remix_address_expressions(Node* n) { if (n->in(3)->Opcode() == Op_AddX) { Node* V = n->in(3)->in(1); Node* I = n->in(3)->in(2); - if (is_member(n_loop,get_ctrl(V))) { + if (ctrl_is_member(n_loop, V)) { } else { Node *tmp = V; V = I; I = tmp; } - if (!is_member(n_loop,get_ctrl(I))) { + if (!ctrl_is_member(n_loop, I)) { Node* add1 = new AddPNode(n->in(1), n->in(2), I); // Stuff new AddP in the loop preheader register_new_node(add1, n_loop->_head->as_Loop()->skip_strip_mined(1)->in(LoopNode::EntryControl)); @@ -937,8 +937,6 @@ Node* PhaseIdealLoop::try_move_store_before_loop(Node* n, Node *n_ctrl) { Node* address = n->in(MemNode::Address); Node* value = n->in(MemNode::ValueIn); Node* mem = n->in(MemNode::Memory); - IdealLoopTree* address_loop = get_loop(get_ctrl(address)); - IdealLoopTree* value_loop = get_loop(get_ctrl(value)); // - address and value must be loop invariant // - memory must be a memory Phi for the loop @@ -957,8 +955,8 @@ Node* PhaseIdealLoop::try_move_store_before_loop(Node* n, Node *n_ctrl) { // memory Phi but sometimes is a bottom memory Phi that takes the // store as input). - if (!n_loop->is_member(address_loop) && - !n_loop->is_member(value_loop) && + if (!ctrl_is_member(n_loop, address) && + !ctrl_is_member(n_loop, value) && mem->is_Phi() && mem->in(0) == n_loop->_head && mem->outcnt() == 1 && mem->in(LoopNode::LoopBackControl) == n) { @@ -1021,17 +1019,15 @@ void PhaseIdealLoop::try_move_store_after_loop(Node* n) { if (n_loop != _ltree_root && !n_loop->_irreducible) { Node* address = n->in(MemNode::Address); Node* value = n->in(MemNode::ValueIn); - IdealLoopTree* address_loop = get_loop(get_ctrl(address)); // address must be loop invariant - if (!n_loop->is_member(address_loop)) { + if (!ctrl_is_member(n_loop, address)) { // Store must be last on this memory slice in the loop and // nothing in the loop must observe it Node* phi = nullptr; for (DUIterator_Fast imax, i = n->fast_outs(imax); i < imax; i++) { Node* u = n->fast_out(i); if (has_ctrl(u)) { // control use? - IdealLoopTree *u_loop = get_loop(get_ctrl(u)); - if (!n_loop->is_member(u_loop)) { + if (!ctrl_is_member(n_loop, u)) { continue; } if (u->is_Phi() && u->in(0) == n_loop->_head) { @@ -1838,7 +1834,7 @@ void PhaseIdealLoop::try_sink_out_of_loop(Node* n) { Node* cast = nullptr; for (uint k = 0; k < x->req(); k++) { Node* in = x->in(k); - if (in != nullptr && n_loop->is_member(get_loop(get_ctrl(in)))) { + if (in != nullptr && ctrl_is_member(n_loop, in)) { const Type* in_t = _igvn.type(in); cast = ConstraintCastNode::make_cast_for_type(x_ctrl, in, in_t, ConstraintCastNode::UnconditionalDependency, nullptr); @@ -2366,11 +2362,9 @@ static void collect_nodes_in_outer_loop_not_reachable_from_sfpt(Node* n, const I Node* u = n->fast_out(j); assert(check_old_new || old_new[u->_idx] == nullptr, "shouldn't have been cloned"); if (!u->is_CFG() && (!check_old_new || old_new[u->_idx] == nullptr)) { - Node* c = phase->get_ctrl(u); - IdealLoopTree* u_loop = phase->get_loop(c); - assert(!loop->is_member(u_loop) || !loop->_body.contains(u), "can be in outer loop or out of both loops only"); - if (!loop->is_member(u_loop)) { - if (outer_loop->is_member(u_loop)) { + assert(!phase->ctrl_is_member(loop, u) || !loop->_body.contains(u), "can be in outer loop or out of both loops only"); + if (!phase->ctrl_is_member(loop, u)) { + if (phase->ctrl_is_member(outer_loop, u)) { wq.push(u); } else { // nodes pinned with control in the outer loop but not referenced from the safepoint must be moved out of @@ -2849,7 +2843,7 @@ int PhaseIdealLoop::stride_of_possible_iv(Node* iff) { return 0; } // Must have an invariant operand - if (is_member(get_loop(iff), get_ctrl(cmp->in(2)))) { + if (ctrl_is_member(get_loop(iff), cmp->in(2))) { return 0; } Node* add2 = nullptr; diff --git a/src/hotspot/share/opto/predicates.cpp b/src/hotspot/share/opto/predicates.cpp index da9f704ee8d..208bd6583c5 100644 --- a/src/hotspot/share/opto/predicates.cpp +++ b/src/hotspot/share/opto/predicates.cpp @@ -1001,7 +1001,7 @@ InitializedAssertionPredicate CreateAssertionPredicatesVisitor::initialize_from_ } bool NodeInSingleLoopBody::check_node_in_loop_body(Node* node) const { - return _phase->is_member(_ilt, _phase->get_ctrl(node)); + return _phase->ctrl_is_member(_ilt, node); } // Clone the provided Template Assertion Predicate and set '_init' as new input for the OpaqueLoopInitNode. diff --git a/src/hotspot/share/opto/superword.cpp b/src/hotspot/share/opto/superword.cpp index 7a2e6bc7fbd..35b46d22732 100644 --- a/src/hotspot/share/opto/superword.cpp +++ b/src/hotspot/share/opto/superword.cpp @@ -73,8 +73,7 @@ public: void set_ignored(Node* n) { // Only consider nodes in the loop. - Node* ctrl = _vloop.phase()->get_ctrl(n); - if (_vloop.lpt()->is_member(_vloop.phase()->get_loop(ctrl))) { + if (_vloop.phase()->ctrl_is_member(_vloop.lpt(), n)) { // Find the index in the loop. for (uint j = 0; j < _body.size(); j++) { if (n == _body.at(j)) { From 81e0c87f28934cb0d66ad2500352b2728f44a1b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20H=C3=BCbner?= Date: Fri, 14 Nov 2025 08:29:57 +0000 Subject: [PATCH 048/418] 8371320: runtime/ErrorHandling/PrintVMInfoAtExitTest.java fails with unexpected amount for Java Heap reserved memory Reviewed-by: azafari, jsikstro --- .../ErrorHandling/PrintVMInfoAtExitTest.java | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/test/hotspot/jtreg/runtime/ErrorHandling/PrintVMInfoAtExitTest.java b/test/hotspot/jtreg/runtime/ErrorHandling/PrintVMInfoAtExitTest.java index 5e535bab626..80c209a9ec4 100644 --- a/test/hotspot/jtreg/runtime/ErrorHandling/PrintVMInfoAtExitTest.java +++ b/test/hotspot/jtreg/runtime/ErrorHandling/PrintVMInfoAtExitTest.java @@ -27,17 +27,13 @@ * @test * @summary Test PrintVMInfoAtExit * @library /test/lib - * @build jdk.test.whitebox.WhiteBox - * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox - * @modules java.base/jdk.internal.misc * @requires vm.flagless * @requires vm.bits == "64" - * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI PrintVMInfoAtExitTest + * @run driver PrintVMInfoAtExitTest */ import jdk.test.lib.process.OutputAnalyzer; import jdk.test.lib.process.ProcessTools; -import jdk.test.whitebox.WhiteBox; public class PrintVMInfoAtExitTest { @@ -52,18 +48,20 @@ public class PrintVMInfoAtExitTest { "-XX:CompressedClassSpaceSize=256m", "-version"); + // How many kb of committed memory we expect in the NMT summary. + int committed_kb = 65536; OutputAnalyzer output_detail = new OutputAnalyzer(pb.start()); output_detail.shouldContain("# JRE version:"); output_detail.shouldContain("-- S U M M A R Y --"); output_detail.shouldContain("Command Line: -Xmx64M -Xms64M -XX:-CreateCoredumpOnCrash -XX:+UnlockDiagnosticVMOptions -XX:+PrintVMInfoAtExit -XX:NativeMemoryTracking=summary -XX:CompressedClassSpaceSize=256m"); output_detail.shouldContain("Native Memory Tracking:"); - WhiteBox wb = WhiteBox.getWhiteBox(); - if (wb.isAsanEnabled()) { - // the reserved value can be influenced by asan - output_detail.shouldContain("Java Heap (reserved="); - output_detail.shouldContain(", committed=65536KB)"); - } else { - output_detail.shouldContain("Java Heap (reserved=65536KB, committed=65536KB)"); + // Make sure the heap summary is present. + output_detail.shouldMatch("Java Heap \\(reserved=[0-9]+KB, committed=" + committed_kb + "KB\\)"); + // Check reserved >= committed. + String reserved_kb_string = output_detail.firstMatch("Java Heap \\(reserved=([0-9]+)KB, committed=" + committed_kb + "KB\\)", 1); + int reserved_kb = Integer.parseInt(reserved_kb_string); + if (reserved_kb < committed_kb) { + throw new RuntimeException("committed more memory than reserved"); } } } From 9eaa364a5221cba960467ffbaea14ea790809c6a Mon Sep 17 00:00:00 2001 From: Afshin Zafari Date: Fri, 14 Nov 2025 09:03:11 +0000 Subject: [PATCH 049/418] 8361487: [ubsan] test_committed_virtualmemory.cpp check_covered_pages shows overflow Reviewed-by: jsjolen, phubner --- .../gtest/runtime/test_committed_virtualmemory.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/test/hotspot/gtest/runtime/test_committed_virtualmemory.cpp b/test/hotspot/gtest/runtime/test_committed_virtualmemory.cpp index 4d3fc1ff82e..5b78a66a3ae 100644 --- a/test/hotspot/gtest/runtime/test_committed_virtualmemory.cpp +++ b/test/hotspot/gtest/runtime/test_committed_virtualmemory.cpp @@ -83,14 +83,21 @@ public: ASSERT_TRUE(found_stack_top); } + static const int PAGE_CONTAINED_IN_RANGE_TAG = -1; + static bool is_page_in_committed_region(int a) { return (a == PAGE_CONTAINED_IN_RANGE_TAG); } + static void set_page_as_contained_in_committed_region(int &a) { a = PAGE_CONTAINED_IN_RANGE_TAG; } + static void check_covered_pages(address addr, size_t size, address base, size_t touch_pages, int* page_num) { const size_t page_sz = os::vm_page_size(); size_t index; for (index = 0; index < touch_pages; index ++) { + if (is_page_in_committed_region(page_num[index])) { // Already tagged? + continue; + } address page_addr = base + page_num[index] * page_sz; // The range covers this page, marks the page if (page_addr >= addr && page_addr < addr + size) { - page_num[index] = -1; + set_page_as_contained_in_committed_region(page_num[index]); } } } @@ -135,7 +142,7 @@ public: if (precise_tracking_supported) { // All touched pages should be committed for (size_t index = 0; index < touch_pages; index ++) { - ASSERT_EQ(page_num[index], -1); + ASSERT_TRUE(is_page_in_committed_region(page_num[index])); } } From 8a7af77e991511e144914abc129a9d4d40c0b76b Mon Sep 17 00:00:00 2001 From: Daniel Fuchs Date: Fri, 14 Nov 2025 10:10:03 +0000 Subject: [PATCH 050/418] 8371366: java/net/httpclient/whitebox/RawChannelTestDriver.java fails intermittently in jtreg timeout Reviewed-by: djelinski, vyazici --- .../whitebox/RawChannelTestDriver.java | 9 +- .../jdk/internal/net/http/RawChannelTest.java | 82 +++++++++++++++---- 2 files changed, 75 insertions(+), 16 deletions(-) diff --git a/test/jdk/java/net/httpclient/whitebox/RawChannelTestDriver.java b/test/jdk/java/net/httpclient/whitebox/RawChannelTestDriver.java index ac577069b70..ab44fba5ecd 100644 --- a/test/jdk/java/net/httpclient/whitebox/RawChannelTestDriver.java +++ b/test/jdk/java/net/httpclient/whitebox/RawChannelTestDriver.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,6 +25,11 @@ * @test * @bug 8151299 8164704 * @modules java.net.http/jdk.internal.net.http - * @run testng java.net.http/jdk.internal.net.http.RawChannelTest + * @run testng/othervm java.net.http/jdk.internal.net.http.RawChannelTest */ +// use +// @run testng/othervm -Dseed=6434511950803022575 +// java.net.http/jdk.internal.net.http.RawChannelTest +// to reproduce a failure with a particular seed (e.g. 6434511950803022575) +// if this test is observed failing with that seed //-Djdk.internal.httpclient.websocket.debug=true diff --git a/test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/RawChannelTest.java b/test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/RawChannelTest.java index 9b5764735b2..f6bcdcb4d33 100644 --- a/test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/RawChannelTest.java +++ b/test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/RawChannelTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,6 +23,7 @@ package jdk.internal.net.http; +import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -43,8 +44,9 @@ import java.util.concurrent.atomic.AtomicLong; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; +import java.util.concurrent.atomic.AtomicReference; + import jdk.internal.net.http.websocket.RawChannel; -import jdk.internal.net.http.websocket.WebSocketRequest; import org.testng.annotations.Test; import static java.net.http.HttpResponse.BodyHandlers.discarding; import static java.util.concurrent.TimeUnit.SECONDS; @@ -57,6 +59,20 @@ import static org.testng.Assert.assertEquals; */ public class RawChannelTest { + // can't use jdk.test.lib when injected in java.net.httpclient + // Seed can be specified on the @run line with -Dseed= + private static class RandomFactory { + private static long getSeed() { + long seed = Long.getLong("seed", new Random().nextLong()); + System.out.println("Seed from RandomFactory = "+seed+"L"); + return seed; + } + public static Random getRandom() { + return new Random(getSeed()); + } + } + + private static final Random RANDOM = RandomFactory.getRandom(); private final AtomicLong clientWritten = new AtomicLong(); private final AtomicLong serverWritten = new AtomicLong(); private final AtomicLong clientRead = new AtomicLong(); @@ -90,7 +106,8 @@ public class RawChannelTest { server.setReuseAddress(false); server.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); int port = server.getLocalPort(); - new TestServer(server).start(); + TestServer testServer = new TestServer(server); + testServer.start(); final RawChannel chan = channelOf(port); print("RawChannel is %s", String.valueOf(chan)); @@ -129,6 +146,7 @@ public class RawChannelTest { } catch (IOException e) { outputCompleted.completeExceptionally(e); e.printStackTrace(); + closeChannel(chan); } return; } @@ -145,6 +163,9 @@ public class RawChannelTest { chan.registerEvent(this); writeStall.countDown(); // signal send buffer is full } catch (IOException e) { + print("OP_WRITE failed: " + e); + outputCompleted.completeExceptionally(e); + closeChannel(chan); throw new UncheckedIOException(e); } } @@ -168,6 +189,7 @@ public class RawChannelTest { read = chan.read(); } catch (IOException e) { inputCompleted.completeExceptionally(e); + closeChannel(chan); e.printStackTrace(); } if (read == null) { @@ -179,7 +201,10 @@ public class RawChannelTest { try { chan.registerEvent(this); } catch (IOException e) { - e.printStackTrace(); + print("OP_READ failed to register event: " + e); + inputCompleted.completeExceptionally(e); + closeChannel(chan); + throw new UncheckedIOException(e); } readStall.countDown(); break; @@ -191,21 +216,33 @@ public class RawChannelTest { print("OP_READ read %s bytes (%s total)", total, clientRead.get()); } }); + CompletableFuture.allOf(outputCompleted,inputCompleted) .whenComplete((r,t) -> { - try { - print("closing channel"); - chan.close(); - } catch (IOException x) { - x.printStackTrace(); - } + closeChannel(chan); }); exit.await(); // All done, we need to compare results: assertEquals(clientRead.get(), serverWritten.get()); assertEquals(serverRead.get(), clientWritten.get()); + Throwable serverError = testServer.failed.get(); + if (serverError != null) { + throw new AssertionError("TestServer failed: " + + serverError, serverError); + } } } + private static void closeChannel(RawChannel chan) { + print("closing channel"); + try { + chan.close(); + } catch (IOException x) { + print("Failed to close channel: " + x); + x.printStackTrace(); + } + } + + private static RawChannel channelOf(int port) throws Exception { URI uri = URI.create("http://localhost:" + port + "/"); print("raw channel to %s", uri.toString()); @@ -237,11 +274,24 @@ public class RawChannelTest { private class TestServer extends Thread { // Powered by Slowpokes private final ServerSocket server; + private final AtomicReference failed = new AtomicReference<>(); TestServer(ServerSocket server) throws IOException { this.server = server; } + private void fail(Closeable s, String actor, Throwable t) { + failed.compareAndSet(null, t); + print("Server %s got exception: %s", actor, t); + t.printStackTrace(); + try { + s.close(); + } catch (Exception x) { + print("Server %s failed to close socket: %s", actor, t); + } + + } + @Override public void run() { try (Socket s = server.accept()) { @@ -252,21 +302,23 @@ public class RawChannelTest { Thread reader = new Thread(() -> { try { + print("Server reader started"); long n = readSlowly(is); print("Server read %s bytes", n); s.shutdownInput(); } catch (Exception e) { - e.printStackTrace(); + fail(s, "reader", e); } }); Thread writer = new Thread(() -> { try { + print("Server writer started"); long n = writeSlowly(os); print("Server written %s bytes", n); s.shutdownOutput(); } catch (Exception e) { - e.printStackTrace(); + fail(s, "writer", e); } }); @@ -276,7 +328,7 @@ public class RawChannelTest { reader.join(); writer.join(); } catch (Exception e) { - e.printStackTrace(); + fail(server,"acceptor", e); } finally { exit.countDown(); } @@ -365,6 +417,8 @@ public class RawChannelTest { } private static byte[] byteArrayOfSize(int bound) { - return new byte[new Random().nextInt(1 + bound)]; + // bound must be > 1; No need to check it, + // nextInt will throw IllegalArgumentException if needed + return new byte[RANDOM.nextInt(1, bound + 1)]; } } From 00f2c38e373f5ae58ad6593cc7b9d53b9596eb17 Mon Sep 17 00:00:00 2001 From: Dhamoder Nalla Date: Fri, 14 Nov 2025 10:54:39 +0000 Subject: [PATCH 051/418] 8371161: [AArch64] Enable CPU feature UseSHA3Intrinsics for the Qualcomm processor family Reviewed-by: aph, haosun --- src/hotspot/cpu/aarch64/vm_version_aarch64.cpp | 4 ++-- src/hotspot/cpu/aarch64/vm_version_aarch64.hpp | 2 +- .../os_cpu/windows_aarch64/vm_version_windows_aarch64.cpp | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/hotspot/cpu/aarch64/vm_version_aarch64.cpp b/src/hotspot/cpu/aarch64/vm_version_aarch64.cpp index a04e9defa4b..659c231464a 100644 --- a/src/hotspot/cpu/aarch64/vm_version_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/vm_version_aarch64.cpp @@ -378,8 +378,8 @@ void VM_Version::initialize() { if (UseSHA && VM_Version::supports_sha3()) { // Auto-enable UseSHA3Intrinsics on hardware with performance benefit. // Note that the evaluation of UseSHA3Intrinsics shows better performance - // on Apple silicon but worse performance on Neoverse V1 and N2. - if (_cpu == CPU_APPLE) { // Apple silicon + // on Apple and Qualcomm silicon but worse performance on Neoverse V1 and N2. + if (_cpu == CPU_APPLE || _cpu == CPU_QUALCOMM) { // Apple or Qualcomm silicon if (FLAG_IS_DEFAULT(UseSHA3Intrinsics)) { FLAG_SET_DEFAULT(UseSHA3Intrinsics, true); } diff --git a/src/hotspot/cpu/aarch64/vm_version_aarch64.hpp b/src/hotspot/cpu/aarch64/vm_version_aarch64.hpp index 3f7ba683efc..17087d243d3 100644 --- a/src/hotspot/cpu/aarch64/vm_version_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/vm_version_aarch64.hpp @@ -106,7 +106,7 @@ public: CPU_MOTOROLA = 'M', CPU_NVIDIA = 'N', CPU_AMCC = 'P', - CPU_QUALCOM = 'Q', + CPU_QUALCOMM = 'Q', CPU_MARVELL = 'V', CPU_INTEL = 'i', CPU_APPLE = 'a', diff --git a/src/hotspot/os_cpu/windows_aarch64/vm_version_windows_aarch64.cpp b/src/hotspot/os_cpu/windows_aarch64/vm_version_windows_aarch64.cpp index de9bf76fdb0..a20feadcba4 100644 --- a/src/hotspot/os_cpu/windows_aarch64/vm_version_windows_aarch64.cpp +++ b/src/hotspot/os_cpu/windows_aarch64/vm_version_windows_aarch64.cpp @@ -84,6 +84,8 @@ void VM_Version::get_os_cpu_info() { _cpu = CPU_AMCC; } else if (buf && strstr(buf, "Cavium Inc.") != nullptr) { _cpu = CPU_CAVIUM; + } else if (buf && strstr(buf, "Qualcomm Technologies Inc") != nullptr) { + _cpu = CPU_QUALCOMM; } else { log_info(os)("VM_Version: unknown CPU model"); } From ff851de852673740542d922d1ee15a6c92b80473 Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Fri, 14 Nov 2025 12:06:13 +0000 Subject: [PATCH 052/418] 8371709: Add CTW to hotspot_compiler testing Reviewed-by: thartmann, epeter --- test/hotspot/jtreg/TEST.groups | 1 + 1 file changed, 1 insertion(+) diff --git a/test/hotspot/jtreg/TEST.groups b/test/hotspot/jtreg/TEST.groups index d4f1470aea5..f07d276f1f5 100644 --- a/test/hotspot/jtreg/TEST.groups +++ b/test/hotspot/jtreg/TEST.groups @@ -36,6 +36,7 @@ hotspot_all_no_apps = \ # Component test groups hotspot_compiler = \ + applications/ctw/modules \ compiler hotspot_compiler_resourcehogs = \ From 4cc655a2f445bb32ce555b80ac28610b26c51f4c Mon Sep 17 00:00:00 2001 From: Thomas Schatzl Date: Fri, 14 Nov 2025 12:49:46 +0000 Subject: [PATCH 053/418] 8371791: G1: Improve accuracy of G1CollectedHeap::non_young_occupancy_after_allocation() Reviewed-by: ayang, iwalulya --- src/hotspot/share/gc/g1/g1Allocator.cpp | 8 ++++++++ src/hotspot/share/gc/g1/g1Allocator.hpp | 3 +++ src/hotspot/share/gc/g1/g1CollectedHeap.cpp | 4 ++-- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1Allocator.cpp b/src/hotspot/share/gc/g1/g1Allocator.cpp index 713bafd4782..78710084ee3 100644 --- a/src/hotspot/share/gc/g1/g1Allocator.cpp +++ b/src/hotspot/share/gc/g1/g1Allocator.cpp @@ -123,6 +123,14 @@ void G1Allocator::reuse_retained_old_region(G1EvacInfo* evacuation_info, } } +size_t G1Allocator::free_bytes_in_retained_old_region() const { + if (_retained_old_gc_alloc_region == nullptr) { + return 0; + } else { + return _retained_old_gc_alloc_region->free(); + } +} + void G1Allocator::init_gc_alloc_regions(G1EvacInfo* evacuation_info) { assert_at_safepoint_on_vm_thread(); diff --git a/src/hotspot/share/gc/g1/g1Allocator.hpp b/src/hotspot/share/gc/g1/g1Allocator.hpp index 19b19c06e92..9a7e62f5cc6 100644 --- a/src/hotspot/share/gc/g1/g1Allocator.hpp +++ b/src/hotspot/share/gc/g1/g1Allocator.hpp @@ -103,7 +103,10 @@ public: void init_gc_alloc_regions(G1EvacInfo* evacuation_info); void release_gc_alloc_regions(G1EvacInfo* evacuation_info); void abandon_gc_alloc_regions(); + bool is_retained_old_region(G1HeapRegion* hr); + // Return the amount of free bytes in the current retained old region. + size_t free_bytes_in_retained_old_region() const; // Node index of current thread. inline uint current_node_index() const; diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp index f04658a1415..d18f61ff507 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp @@ -2964,8 +2964,8 @@ void G1CollectedHeap::abandon_collection_set() { } size_t G1CollectedHeap::non_young_occupancy_after_allocation(size_t allocation_word_size) { - // For simplicity, just count whole regions. - const size_t cur_occupancy = (old_regions_count() + humongous_regions_count()) * G1HeapRegion::GrainBytes; + const size_t cur_occupancy = (old_regions_count() + humongous_regions_count()) * G1HeapRegion::GrainBytes - + _allocator->free_bytes_in_retained_old_region(); // Humongous allocations will always be assigned to non-young heap, so consider // that allocation in the result as well. Otherwise the allocation will always // be in young gen, so there is no need to account it here. From 5d65c23cd99b72527dcfab9eb6da9510e7dc6330 Mon Sep 17 00:00:00 2001 From: Severin Gehwolf Date: Fri, 14 Nov 2025 13:13:09 +0000 Subject: [PATCH 054/418] 8370492: [Linux] Update cpu shares to cpu.weight mapping function Reviewed-by: cnorrbin, ayang, syan --- .../os/linux/cgroupV2Subsystem_linux.cpp | 31 ++++++++--- .../platform/cgroupv2/CgroupV2Subsystem.java | 31 ++++++++--- .../jtreg/containers/docker/TestMisc.java | 52 ++++++++++++++++++- .../platform/docker/MetricsCpuTester.java | 10 ++-- 4 files changed, 107 insertions(+), 17 deletions(-) diff --git a/src/hotspot/os/linux/cgroupV2Subsystem_linux.cpp b/src/hotspot/os/linux/cgroupV2Subsystem_linux.cpp index 38258a1f049..41fc8db7e81 100644 --- a/src/hotspot/os/linux/cgroupV2Subsystem_linux.cpp +++ b/src/hotspot/os/linux/cgroupV2Subsystem_linux.cpp @@ -26,6 +26,8 @@ #include "cgroupUtil_linux.hpp" #include "cgroupV2Subsystem_linux.hpp" +#include + // Constructor CgroupV2Controller::CgroupV2Controller(char* mount_path, char *cgroup_path, @@ -61,22 +63,39 @@ int CgroupV2CpuController::cpu_shares() { log_debug(os, container)("CPU Shares is: %d", -1); return -1; } + // cg v2 values must be in range [1-10000] + assert(shares_int >= 1 && shares_int <= 10000, "invariant"); // CPU shares (OCI) value needs to get translated into // a proper Cgroups v2 value. See: - // https://github.com/containers/crun/blob/master/crun.1.md#cpu-controller + // https://github.com/containers/crun/blob/1.24/crun.1.md#cpu-controller // // Use the inverse of (x == OCI value, y == cgroupsv2 value): - // ((262142 * y - 1)/9999) + 2 = x + // y = 10^(log2(x)^2/612 + 125/612 * log2(x) - 7.0/34.0) // - int x = 262142 * shares_int - 1; - double frac = x/9999.0; - x = ((int)frac) + 2; + // By re-arranging it to the standard quadratic form: + // log2(x)^2 + 125 * log2(x) - (126 + 612 * log_10(y)) = 0 + // + // Therefore, log2(x) = (-125 + sqrt( 125^2 - 4 * (-(126 + 612 * log_10(y)))))/2 + // + // As a result we have the inverse (we can discount substraction of the + // square root value since those values result in very small numbers and the + // cpu shares values - OCI - are in range [2,262144]): + // + // x = 2^((-125 + sqrt(16129 + 2448* log10(y)))/2) + // + double log_multiplicand = log10(shares_int); + double discriminant = 16129 + 2448 * log_multiplicand; + double square_root = sqrt(discriminant); + double exponent = (-125 + square_root)/2; + double scaled_val = pow(2, exponent); + int x = (int) scaled_val; log_trace(os, container)("Scaled CPU shares value is: %d", x); // Since the scaled value is not precise, return the closest // multiple of PER_CPU_SHARES for a more conservative mapping if ( x <= PER_CPU_SHARES ) { - // will always map to 1 CPU + // Don't do the multiples of PER_CPU_SHARES mapping since we + // have a value <= PER_CPU_SHARES log_debug(os, container)("CPU Shares is: %d", x); return x; } diff --git a/src/java.base/linux/classes/jdk/internal/platform/cgroupv2/CgroupV2Subsystem.java b/src/java.base/linux/classes/jdk/internal/platform/cgroupv2/CgroupV2Subsystem.java index aa618766b38..3e3f637cd4c 100644 --- a/src/java.base/linux/classes/jdk/internal/platform/cgroupv2/CgroupV2Subsystem.java +++ b/src/java.base/linux/classes/jdk/internal/platform/cgroupv2/CgroupV2Subsystem.java @@ -156,22 +156,39 @@ public class CgroupV2Subsystem implements CgroupSubsystem { @Override public long getCpuShares() { long sharesRaw = getLongVal("cpu.weight"); - if (sharesRaw == 100 || sharesRaw <= 0) { + // cg v2 value must be in range [1,10000] + if (sharesRaw == 100 || sharesRaw <= 0 || sharesRaw > 10000) { return CgroupSubsystem.LONG_RETVAL_UNLIMITED; } int shares = (int)sharesRaw; // CPU shares (OCI) value needs to get translated into // a proper Cgroups v2 value. See: - // https://github.com/containers/crun/blob/master/crun.1.md#cpu-controller + // https://github.com/containers/crun/blob/1.24/crun.1.md#cpu-controller // // Use the inverse of (x == OCI value, y == cgroupsv2 value): - // ((262142 * y - 1)/9999) + 2 = x + // y = 10^(log2(x)^2/612 + 125/612 * log2(x) - 7.0/34.0) // - int x = 262142 * shares - 1; - double frac = x/9999.0; - x = ((int)frac) + 2; + // By re-arranging it to the standard quadratic form: + // log2(x)^2 + 125 * log2(x) - (126 + 612 * log_10(y)) = 0 + // + // Therefore, log2(x) = (-125 + sqrt( 125^2 - 4 * (-(126 + 612 * log_10(y)))))/2 + // + // As a result we have the inverse (we can discount substraction of the + // square root value since those values result in very small numbers and the + // cpu shares values - OCI - are in range [2-262144]) + // + // x = 2^((-125 + sqrt(16129 + 2448* log10(y)))/2) + // + double logMultiplicand = Math.log10(shares); + double discriminant = 16129 + 2448 * logMultiplicand; + double squareRoot = Math.sqrt(discriminant); + double exponent = (-125 + squareRoot)/2; + double scaledValue = Math.pow(2, exponent); + + int x = (int)scaledValue; if ( x <= PER_CPU_SHARES ) { - return PER_CPU_SHARES; // mimic cgroups v1 + // Return the back-mapped value. + return x; } int f = x/PER_CPU_SHARES; int lower_multiple = f * PER_CPU_SHARES; diff --git a/test/hotspot/jtreg/containers/docker/TestMisc.java b/test/hotspot/jtreg/containers/docker/TestMisc.java index a1998cef344..400119dac9d 100644 --- a/test/hotspot/jtreg/containers/docker/TestMisc.java +++ b/test/hotspot/jtreg/containers/docker/TestMisc.java @@ -29,20 +29,24 @@ * @requires !vm.asan * @library /test/lib * @modules java.base/jdk.internal.misc + * java.base/jdk.internal.platform * java.management * jdk.jartool/sun.tools.jar * @build CheckContainerized jdk.test.whitebox.WhiteBox PrintContainerInfo * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar whitebox.jar jdk.test.whitebox.WhiteBox * @run driver TestMisc */ +import jdk.internal.platform.Metrics; import jdk.test.lib.containers.docker.Common; import jdk.test.lib.containers.docker.DockerTestUtils; import jdk.test.lib.containers.docker.DockerRunOptions; import jdk.test.lib.process.OutputAnalyzer; import jdk.test.lib.process.ProcessTools; +import jtreg.SkippedException; public class TestMisc { + private static final Metrics metrics = Metrics.systemMetrics(); private static final String imageName = Common.imageName("misc"); public static void main(String[] args) throws Exception { @@ -58,6 +62,7 @@ public class TestMisc { testIsContainerized(); testPrintContainerInfo(); testPrintContainerInfoActiveProcessorCount(); + testPrintContainerInfoCPUShares(); } finally { DockerTestUtils.removeDockerImage(imageName); } @@ -94,8 +99,53 @@ public class TestMisc { checkContainerInfo(Common.run(opts)); } + // Test the mapping function on cgroups v2. Should also pass on cgroups v1 as it's + // a direct mapping there. + private static void testPrintContainerInfoCPUShares() throws Exception { + // Test won't work on cgv1 rootless podman since resource limits don't + // work there. + if ("cgroupv1".equals(metrics.getProvider()) && + DockerTestUtils.isPodman() && + DockerTestUtils.isRootless()) { + throw new SkippedException("Resource limits required for testPrintContainerInfoCPUShares(). " + + "This is cgv1 with podman in rootless mode. Test skipped."); + } + // Anything less than 1024 should return the back-mapped cpu-shares value without + // rounding to next multiple of 1024 (on cg v2). Only ensure that we get + // 'cpu_shares: ' over 'cpu_shares: no shares'. + printContainerInfo(512, 1024, false); + // Don't use 1024 exactly so as to avoid mapping to the unlimited/uset case. + // Use a value > 100 post-mapping so as to hit the non-default branch: 1052 => 103 + printContainerInfo(1052, 1024, true); + // need at least 2 CPU cores for this test to work + if (Runtime.getRuntime().availableProcessors() >= 2) { + printContainerInfo(2048, 2048, true); + } + } + + private static void printContainerInfo(int cpuShares, int expected, boolean numberMatch) throws Exception { + Common.logNewTestCase("Test print_container_info() - cpu shares - given: " + cpuShares + ", expected: " + expected); + + DockerRunOptions opts = Common.newOpts(imageName, "PrintContainerInfo"); + Common.addWhiteBoxOpts(opts); + opts.addDockerOpts("--cpu-shares", Integer.valueOf(cpuShares).toString()); + + OutputAnalyzer out = Common.run(opts); + String str = out.getOutput(); + boolean isCgroupV2 = str.contains("cgroupv2"); + // cg v1 maps cpu shares values verbatim. Only cg v2 uses the + // mapping function. + if (numberMatch) { + int valueExpected = isCgroupV2 ? expected : cpuShares; + out.shouldContain("cpu_shares: " + valueExpected); + } else { + // must not print "no shares" + out.shouldNotContain("cpu_shares: no shares"); + } + } + private static void testPrintContainerInfoActiveProcessorCount() throws Exception { - Common.logNewTestCase("Test print_container_info()"); + Common.logNewTestCase("Test print_container_info() - ActiveProcessorCount"); DockerRunOptions opts = Common.newOpts(imageName, "PrintContainerInfo").addJavaOpts("-XX:ActiveProcessorCount=2"); Common.addWhiteBoxOpts(opts); diff --git a/test/jdk/jdk/internal/platform/docker/MetricsCpuTester.java b/test/jdk/jdk/internal/platform/docker/MetricsCpuTester.java index ff5d52d95a6..62549e7b518 100644 --- a/test/jdk/jdk/internal/platform/docker/MetricsCpuTester.java +++ b/test/jdk/jdk/internal/platform/docker/MetricsCpuTester.java @@ -145,9 +145,13 @@ public class MetricsCpuTester { private static void testCpuShares(long shares) { Metrics metrics = Metrics.systemMetrics(); if ("cgroupv2".equals(metrics.getProvider()) && shares < 1024) { - // Adjust input shares for < 1024 cpu shares as the - // impl. rounds up to the next multiple of 1024 - shares = 1024; + // Don't assert for shares values less than 1024 as we don't + // have a 1-to-1 mapping from the cgroup v2 value to the OCI + // value. + System.out.println("Debug: cgv2 - Got CPU shares of: " + + metrics.getCpuShares() + " - Skipping assert."); + System.out.println("TEST PASSED!!!"); + return; } long newShares = metrics.getCpuShares(); if (newShares != shares) { From 36daa2650d504b3cdc43c774601a6e5f9e9b2403 Mon Sep 17 00:00:00 2001 From: Coleen Phillimore Date: Fri, 14 Nov 2025 14:12:27 +0000 Subject: [PATCH 055/418] 8371860: Make non-public methods in java_lang_Class private Reviewed-by: kbarrett, fparain --- src/hotspot/share/classfile/javaClasses.cpp | 11 ----------- src/hotspot/share/classfile/javaClasses.hpp | 15 +++++++-------- 2 files changed, 7 insertions(+), 19 deletions(-) diff --git a/src/hotspot/share/classfile/javaClasses.cpp b/src/hotspot/share/classfile/javaClasses.cpp index e41af702601..ede3582b32e 100644 --- a/src/hotspot/share/classfile/javaClasses.cpp +++ b/src/hotspot/share/classfile/javaClasses.cpp @@ -1336,11 +1336,6 @@ void java_lang_Class::set_class_data(oop java_class, oop class_data) { java_class->obj_field_put(_classData_offset, class_data); } -void java_lang_Class::set_reflection_data(oop java_class, oop reflection_data) { - assert(_reflectionData_offset != 0, "must be set"); - java_class->obj_field_put(_reflectionData_offset, reflection_data); -} - void java_lang_Class::set_class_loader(oop java_class, oop loader) { assert(_class_loader_offset != 0, "offsets should have been initialized"); java_class->obj_field_put(_class_loader_offset, loader); @@ -1483,7 +1478,6 @@ Klass* java_lang_Class::array_klass_acquire(oop java_class) { return k; } - void java_lang_Class::release_set_array_klass(oop java_class, Klass* klass) { assert(klass->is_klass() && klass->is_array_klass(), "should be array klass"); java_class->release_metadata_field_put(_array_klass_offset, klass); @@ -1589,11 +1583,6 @@ void java_lang_Class::set_modifiers(oop the_class_mirror, u2 value) { the_class_mirror->char_field_put(_modifiers_offset, value); } -int java_lang_Class::raw_access_flags(oop the_class_mirror) { - assert(_raw_access_flags_offset != 0, "offsets should have been initialized"); - return the_class_mirror->char_field(_raw_access_flags_offset); -} - void java_lang_Class::set_raw_access_flags(oop the_class_mirror, u2 value) { assert(_raw_access_flags_offset != 0, "offsets should have been initialized"); the_class_mirror->char_field_put(_raw_access_flags_offset, value); diff --git a/src/hotspot/share/classfile/javaClasses.hpp b/src/hotspot/share/classfile/javaClasses.hpp index 28f8c0a3b8c..699dd39b887 100644 --- a/src/hotspot/share/classfile/javaClasses.hpp +++ b/src/hotspot/share/classfile/javaClasses.hpp @@ -273,6 +273,12 @@ class java_lang_Class : AllStatic { static void initialize_mirror_fields(InstanceKlass* ik, Handle mirror, Handle protection_domain, Handle classData, TRAPS); static void set_mirror_module_field(JavaThread* current, Klass* K, Handle mirror, Handle module); + + static void set_modifiers(oop java_class, u2 value); + static void set_raw_access_flags(oop java_class, u2 value); + static void set_is_primitive(oop java_class); + static void release_set_array_klass(oop java_class, Klass* klass); + public: static void allocate_fixup_lists(); static void compute_offsets(); @@ -307,12 +313,10 @@ class java_lang_Class : AllStatic { static bool is_instance(oop obj); static bool is_primitive(oop java_class); - static void set_is_primitive(oop java_class); static BasicType primitive_type(oop java_class); static oop primitive_mirror(BasicType t); - // JVM_NewArray support static Klass* array_klass_acquire(oop java_class); - static void release_set_array_klass(oop java_class, Klass* klass); + // compiler support for class operations static int klass_offset() { CHECK_INIT(_klass_offset); } static int array_klass_offset() { CHECK_INIT(_array_klass_offset); } @@ -331,7 +335,6 @@ class java_lang_Class : AllStatic { static objArrayOop signers(oop java_class); static oop class_data(oop java_class); static void set_class_data(oop java_class, oop classData); - static void set_reflection_data(oop java_class, oop reflection_data); static int reflection_data_offset() { return _reflectionData_offset; } static oop class_loader(oop java_class); @@ -344,10 +347,6 @@ class java_lang_Class : AllStatic { static void set_source_file(oop java_class, oop source_file); static int modifiers(oop java_class); - static void set_modifiers(oop java_class, u2 value); - - static int raw_access_flags(oop java_class); - static void set_raw_access_flags(oop java_class, u2 value); static size_t oop_size(oop java_class); static void set_oop_size(HeapWord* java_class, size_t size); From 466cb383144edf0baa202dc5a2cac37e7572e2db Mon Sep 17 00:00:00 2001 From: Thomas Stuefe Date: Fri, 14 Nov 2025 14:53:19 +0000 Subject: [PATCH 056/418] 8371885: Mark UseCompressedClassPointers as obsolete for JDK 27 Reviewed-by: mdoerr, coleenp --- src/hotspot/share/runtime/arguments.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp index 79027cf113f..3748c35f00d 100644 --- a/src/hotspot/share/runtime/arguments.cpp +++ b/src/hotspot/share/runtime/arguments.cpp @@ -530,7 +530,7 @@ static SpecialFlag const special_jvm_flags[] = { { "UseSharedSpaces", JDK_Version::jdk(18), JDK_Version::jdk(19), JDK_Version::undefined() }, { "LockingMode", JDK_Version::jdk(24), JDK_Version::jdk(26), JDK_Version::jdk(27) }, #ifdef _LP64 - { "UseCompressedClassPointers", JDK_Version::jdk(25), JDK_Version::jdk(26), JDK_Version::undefined() }, + { "UseCompressedClassPointers", JDK_Version::jdk(25), JDK_Version::jdk(27), JDK_Version::undefined() }, #endif { "ParallelRefProcEnabled", JDK_Version::jdk(26), JDK_Version::jdk(27), JDK_Version::jdk(28) }, { "ParallelRefProcBalancingEnabled", JDK_Version::jdk(26), JDK_Version::jdk(27), JDK_Version::jdk(28) }, From 10f262a6ad9a6e89cd79409c5e1a3f7efda76928 Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Fri, 14 Nov 2025 15:31:28 +0000 Subject: [PATCH 057/418] 8371804: C2: Tighten up LoadNode::Value comments after JDK-8346184 Reviewed-by: kvn, vlivanov --- src/hotspot/share/opto/memnode.cpp | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/hotspot/share/opto/memnode.cpp b/src/hotspot/share/opto/memnode.cpp index f42a6ea9489..61300ab4fcb 100644 --- a/src/hotspot/share/opto/memnode.cpp +++ b/src/hotspot/share/opto/memnode.cpp @@ -2012,11 +2012,7 @@ const Type* LoadNode::Value(PhaseGVN* phase) const { assert(off != Type::OffsetTop, "case covered by TypePtr::empty"); Compile* C = phase->C; - // If we are loading from a freshly-allocated object, produce a zero, - // if the load is provably beyond the header of the object. - // (Also allow a variable load from a fresh array to produce zero.) - const TypeOopPtr* tinst = tp->isa_oopptr(); - bool is_instance = (tinst != nullptr) && tinst->is_known_instance_field(); + // If load can see a previous constant store, use that. Node* value = can_see_stored_value(mem, phase); if (value != nullptr && value->is_Con()) { assert(value->bottom_type()->higher_equal(_type), "sanity"); @@ -2227,13 +2223,16 @@ const Type* LoadNode::Value(PhaseGVN* phase) const { } } - bool is_vect = (_type->isa_vect() != nullptr); - if (is_instance && !is_vect) { - // If we have an instance type and our memory input is the - // programs's initial memory state, there is no matching store, - // so just return a zero of the appropriate type - - // except if it is vectorized - then we have no zero constant. - Node *mem = in(MemNode::Memory); + // If we are loading from a freshly-allocated object/array, produce a zero. + // Things to check: + // 1. Load is beyond the header: headers are not guaranteed to be zero + // 2. Load is not vectorized: vectors have no zero constant + // 3. Load has no matching store, i.e. the input is the initial memory state + const TypeOopPtr* tinst = tp->isa_oopptr(); + bool is_not_header = (tinst != nullptr) && tinst->is_known_instance_field(); + bool is_not_vect = (_type->isa_vect() == nullptr); + if (is_not_header && is_not_vect) { + Node* mem = in(MemNode::Memory); if (mem->is_Parm() && mem->in(0)->is_Start()) { assert(mem->as_Parm()->_con == TypeFunc::Memory, "must be memory Parm"); return Type::get_zero_type(_type->basic_type()); From 6e7eaf40d1b660cbec0a226911c9dc88f94756aa Mon Sep 17 00:00:00 2001 From: David Beaumont Date: Fri, 14 Nov 2025 18:24:04 +0000 Subject: [PATCH 058/418] 8371591: VerifyJimage test incorrectly skips all tests when comparing directory structure Reviewed-by: rriggs --- test/jdk/tools/jimage/VerifyJimage.java | 425 +++++++++++++++--------- 1 file changed, 259 insertions(+), 166 deletions(-) diff --git a/test/jdk/tools/jimage/VerifyJimage.java b/test/jdk/tools/jimage/VerifyJimage.java index 6da0e719950..08f567cbecb 100644 --- a/test/jdk/tools/jimage/VerifyJimage.java +++ b/test/jdk/tools/jimage/VerifyJimage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2025, 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,17 +21,19 @@ * questions. */ -import java.io.File; +import jdk.internal.jimage.BasicImageReader; +import jtreg.SkippedException; + import java.io.IOException; import java.io.UncheckedIOException; -import java.nio.file.DirectoryStream; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.ArrayList; import java.util.Arrays; import java.util.Deque; +import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentLinkedDeque; @@ -41,205 +43,296 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import java.util.stream.Stream; +import java.util.stream.StreamSupport; -import jdk.internal.jimage.BasicImageReader; -import jdk.internal.jimage.ImageLocation; +import static java.util.stream.Collectors.joining; /* - * @test - * @summary Verify jimage + * @test id=load + * @summary Load all classes defined in JRT file system. + * @library /test/lib * @modules java.base/jdk.internal.jimage * @run main/othervm --add-modules ALL-SYSTEM VerifyJimage */ -/** - * This test runs in two modes: - * (1) No argument: it verifies the jimage by loading all classes in the runtime - * (2) path of exploded modules: it compares bytes of each file in the exploded - * module with the entry in jimage - * - * FIXME: exception thrown when findLocation from jimage by multiple threads - * -Djdk.test.threads= to specify the number of threads. +/* + * @test id=compare + * @summary Compare an exploded directory of module classes with the system jimage. + * @library /test/lib + * @modules java.base/jdk.internal.jimage + * @run main/othervm --add-modules ALL-SYSTEM -Djdk.test.threads=10 VerifyJimage ../../jdk/modules */ -public class VerifyJimage { +public abstract class VerifyJimage implements Runnable { private static final String MODULE_INFO = "module-info.class"; - private static final Deque failed = new ConcurrentLinkedDeque<>(); public static void main(String... args) throws Exception { - - String home = System.getProperty("java.home"); - Path bootimagePath = Paths.get(home, "lib", "modules"); + // Best practice is to read "test.jdk" in preference to "java.home". + String testJdk = System.getProperty("test.jdk", System.getProperty("java.home")); + Path jdkRoot = Path.of(testJdk); + Path bootimagePath = jdkRoot.resolve("lib", "modules"); if (Files.notExists(bootimagePath)) { - System.out.println("Test skipped, not an images build"); - return; + throw new SkippedException("No boot image: " + bootimagePath); } - long start = System.nanoTime(); - int numThreads = Integer.getInteger("jdk.test.threads", 1); - JImageReader reader = newJImageReader(); - VerifyJimage verify = new VerifyJimage(reader, numThreads); + FileSystem jrtFs = FileSystems.getFileSystem(URI.create("jrt:/")); + Path modulesRoot = jrtFs.getPath("/").resolve("modules"); + List modules; + try (Stream moduleDirs = Files.list(modulesRoot)) { + modules = moduleDirs.map(Path::getFileName).map(Object::toString).toList(); + } + VerifyJimage verifier; if (args.length == 0) { - // load classes from jimage - verify.loadClasses(); + verifier = new ClassLoadingVerifier(modules, modulesRoot); } else { - Path dir = Paths.get(args[0]); - if (Files.notExists(dir) || !Files.isDirectory(dir)) { - throw new RuntimeException("Invalid argument: " + dir); + Path pathArg = Path.of(args[0].replace("/", FileSystems.getDefault().getSeparator())); + // The path argument may be relative. + Path rootDir = jdkRoot.resolve(pathArg); + if (!Files.isDirectory(rootDir)) { + throw new SkippedException("No modules directory found: " + rootDir); } - verify.compareExplodedModules(dir); + int maxThreads = Integer.getInteger("jdk.test.threads", 1); + verifier = new DirectoryContentVerifier(modules, rootDir, maxThreads, bootimagePath); } - verify.waitForCompletion(); + verifier.verify(); + } + + final List modules; + // Count of items which have passed verification. + final AtomicInteger verifiedCount = new AtomicInteger(0); + // Error messages for verification failures. + final Deque failed = new ConcurrentLinkedDeque<>(); + + private VerifyJimage(List modules) { + this.modules = modules; + } + + void verify() { + long start = System.nanoTime(); + run(); long end = System.nanoTime(); - int entries = reader.entries(); - System.out.format("%d entries %d files verified: %d ms %d errors%n", - entries, verify.count.get(), - TimeUnit.NANOSECONDS.toMillis(end - start), failed.size()); - for (String f : failed) { - System.err.println(f); - } + + System.out.format("Verified %d entries: %d ms, %d errors%n", + verifiedCount.get(), + TimeUnit.NANOSECONDS.toMillis(end - start), + failed.size()); if (!failed.isEmpty()) { + failed.forEach(System.err::println); throw new AssertionError("Test failed"); } } - private final AtomicInteger count = new AtomicInteger(0); - private final JImageReader reader; - private final ExecutorService pool; + private static final class DirectoryContentVerifier extends VerifyJimage { + private final Path rootDir; + private final ExecutorService pool; + private final Path jimagePath; - VerifyJimage(JImageReader reader, int numThreads) { - this.reader = reader; - this.pool = Executors.newFixedThreadPool(numThreads); - } + DirectoryContentVerifier(List modules, Path rootDir, int maxThreads, Path jimagePath) { + super(modules); + this.rootDir = rootDir; + this.pool = Executors.newFixedThreadPool(maxThreads); + this.jimagePath = jimagePath; + } - private void waitForCompletion() throws InterruptedException { - pool.shutdown(); - pool.awaitTermination(20, TimeUnit.SECONDS); - } - - private void compareExplodedModules(Path dir) throws IOException { - System.out.println("comparing jimage with " + dir); - - try (DirectoryStream stream = Files.newDirectoryStream(dir)) { - for (Path mdir : stream) { - if (Files.isDirectory(mdir)) { - pool.execute(new Runnable() { - @Override - public void run() { - try { - Files.find(mdir, Integer.MAX_VALUE, (Path p, BasicFileAttributes attr) - -> !Files.isDirectory(p) && - !mdir.relativize(p).toString().startsWith("_") && - !p.getFileName().toString().equals("MANIFEST.MF")) - .forEach(p -> compare(mdir, p, reader)); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - }); + @Override + public void run() { + System.out.println("Comparing jimage with: " + rootDir); + try (BasicImageReader jimage = BasicImageReader.open(jimagePath)) { + for (String modName : modules) { + Path modDir = rootDir.resolve(modName); + if (!Files.isDirectory(modDir)) { + failed.add("Missing module directory: " + modDir); + } else { + pool.execute(new ModuleResourceComparator(rootDir, modName, jimage)); + } } + pool.shutdown(); + if (!pool.awaitTermination(20, TimeUnit.SECONDS)) { + failed.add("Directory verification timed out"); + } + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } catch (InterruptedException e) { + failed.add("Directory verification was interrupted"); + Thread.currentThread().interrupt(); + } + } + + /** + * Verifies the contents of the current runtime jimage file by comparing + * entries with the on-disk resources in a given directory. + */ + private class ModuleResourceComparator implements Runnable { + private final Path rootDir; + private final String moduleName; + private final BasicImageReader jimage; + private final String moduleInfoName; + // Entries we expect to find in the jimage module. + private final Set moduleEntries; + private final Set handledEntries = new HashSet<>(); + + public ModuleResourceComparator(Path rootDir, String moduleName, BasicImageReader jimage) { + this.rootDir = rootDir; + this.moduleName = moduleName; + this.jimage = jimage; + String moduleEntryPrefix = "/" + moduleName + "/"; + this.moduleInfoName = moduleEntryPrefix + MODULE_INFO; + this.moduleEntries = + Arrays.stream(jimage.getEntryNames()) + .filter(n -> n.startsWith(moduleEntryPrefix)) + .filter(n -> !isJimageOnly(n)) + .collect(Collectors.toSet()); + } + + @Override + public void run() { + try (Stream files = Files.walk(rootDir.resolve(moduleName))) { + files.filter(this::shouldVerify).forEach(this::compareEntry); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + moduleEntries.stream() + .filter(n -> !handledEntries.contains(n)) + .sorted() + .forEach(n -> failed.add("Untested jimage entry: " + n)); + } + + void compareEntry(Path path) { + String entryName = getEntryName(path); + if (!moduleEntries.contains(entryName)) { + // Corresponds to an on-disk file which is not expected to + // be present in the jimage. This is normal and is skipped. + return; + } + // Mark valid entries as "handled" to track if we've seen them + // (even if we don't test their content). + if (!handledEntries.add(entryName)) { + failed.add("Duplicate entry name: " + entryName); + return; + } + if (isExpectedToDiffer(entryName)) { + return; + } + try { + int mismatch = Arrays.mismatch( + Files.readAllBytes(path), + jimage.getResource(entryName)); + if (mismatch == -1) { + verifiedCount.incrementAndGet(); + } else { + failed.add("Content diff (byte offset " + mismatch + "): " + entryName); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + /** + * Predicate for files which correspond to entries in the jimage. + * + *

    This should be a narrow test with minimal chance of + * false-negative matching, primarily focusing on excluding build + * artifacts. + */ + boolean shouldVerify(Path path) { + // Use the entry name because we know it uses the '/' separator. + String entryName = getEntryName(path); + return Files.isRegularFile(path) + && !entryName.contains("/_the.") + && !entryName.contains("/_element_lists."); + } + + /** + * Predicate for the limited subset of entries which are expected to + * exist in the file system, but are not expected to have the same + * content as the associated jimage entry. This is to handle files + * which are modified/patched by jlink plugins. + * + *

    This should be a narrow test with minimal chance of + * false-positive matching. + */ + private boolean isExpectedToDiffer(String entryName) { + return entryName.equals(moduleInfoName) + || (entryName.startsWith("/java.base/java/lang/invoke/") && entryName.endsWith("$Holder.class")) + || entryName.equals("/java.base/jdk/internal/module/SystemModulesMap.class"); + } + + /** + * Predicate for the limited subset of entries which are not expected + * to exist in the file system, such as those created synthetically + * by jlink plugins. + * + *

    This should be a narrow test with minimal chance of + * false-positive matching. + */ + private boolean isJimageOnly(String entryName) { + return entryName.startsWith("/java.base/jdk/internal/module/SystemModules$") + || entryName.startsWith("/java.base/java/lang/invoke/BoundMethodHandle$Species_"); + } + + private String getEntryName(Path path) { + return StreamSupport.stream(rootDir.relativize(path).spliterator(), false) + .map(Object::toString).collect(joining("/", "/", "")); } } } - private final List BOOT_RESOURCES = Arrays.asList( - "java.base/META-INF/services/java.nio.file.spi.FileSystemProvider" - ); - private final List EXT_RESOURCES = Arrays.asList( - "jdk.zipfs/META-INF/services/java.nio.file.spi.FileSystemProvider" - ); - private final List APP_RESOURCES = Arrays.asList( - "jdk.hotspot.agent/META-INF/services/com.sun.jdi.connect.Connector", - "jdk.jdi/META-INF/services/com.sun.jdi.connect.Connector" - ); + /** + * Verifies the contents of the current runtime jimage file by attempting to + * load every available class based on the content of the JRT file system. + */ + static final class ClassLoadingVerifier extends VerifyJimage { + private static final String CLASS_SUFFIX = ".class"; - private void compare(Path mdir, Path p, JImageReader reader) { - String entry = p.getFileName().toString().equals(MODULE_INFO) - ? mdir.getFileName().toString() + "/" + MODULE_INFO - : mdir.relativize(p).toString().replace(File.separatorChar, '/'); + private final Path modulesRoot; - count.incrementAndGet(); - String file = mdir.getFileName().toString() + "/" + entry; - if (APP_RESOURCES.contains(file)) { - // skip until the service config file is merged - System.out.println("Skipped " + file); - return; + ClassLoadingVerifier(List modules, Path modulesRoot) { + super(modules); + this.modulesRoot = modulesRoot; } - if (reader.findLocation(entry) != null) { - reader.compare(entry, p); - } - } - - private void loadClasses() { - ClassLoader loader = ClassLoader.getSystemClassLoader(); - Stream.of(reader.getEntryNames()) - .filter(this::accept) - .map(this::toClassName) - .forEach(cn -> { - count.incrementAndGet(); - try { - System.out.println("Loading " + cn); - Class.forName(cn, false, loader); - } catch (VerifyError ve) { - System.err.println("VerifyError for " + cn); - failed.add(reader.imageName() + ": " + cn + " not verified: " + ve.getMessage()); - } catch (ClassNotFoundException e) { - failed.add(reader.imageName() + ": " + cn + " not found"); - } - }); - } - - private String toClassName(String entry) { - int index = entry.indexOf('/', 1); - return entry.substring(index + 1, entry.length()) - .replaceAll("\\.class$", "").replace('/', '.'); - } - - // All JVMCI packages other than jdk.vm.ci.services are dynamically - // exported to jdk.graal.compiler - private static Set EXCLUDED_MODULES = Set.of("jdk.graal.compiler"); - - private boolean accept(String entry) { - int index = entry.indexOf('/', 1); - String mn = index > 1 ? entry.substring(1, index) : ""; - if (mn.isEmpty() || EXCLUDED_MODULES.contains(mn)) { - return false; - } - return entry.endsWith(".class") && !entry.endsWith(MODULE_INFO); - } - - private static JImageReader newJImageReader() throws IOException { - String home = System.getProperty("java.home"); - Path jimage = Paths.get(home, "lib", "modules"); - System.out.println("opened " + jimage); - return new JImageReader(jimage); - } - - static class JImageReader extends BasicImageReader { - final Path jimage; - JImageReader(Path p) throws IOException { - super(p); - this.jimage = p; + @Override + public void run() { + ClassLoader loader = ClassLoader.getSystemClassLoader(); + for (String modName : modules) { + Path modDir = modulesRoot.resolve(modName); + try (Stream files = Files.walk(modDir)) { + files.map(modDir::relativize) + .filter(ClassLoadingVerifier::isClassFile) + .map(ClassLoadingVerifier::toClassName) + .forEach(cn -> loadClass(cn, loader)); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } } - String imageName() { - return jimage.getFileName().toString(); - } - - int entries() { - return getHeader().getTableLength(); - } - - void compare(String entry, Path p) { + private void loadClass(String cn, ClassLoader loader) { try { - byte[] bytes = Files.readAllBytes(p); - byte[] imagebytes = getResource(entry); - if (!Arrays.equals(bytes, imagebytes)) { - failed.add(imageName() + ": bytes differs than " + p.toString()); - } - } catch (IOException e) { - throw new UncheckedIOException(e); + Class.forName(cn, false, loader); + verifiedCount.incrementAndGet(); + } catch (VerifyError ve) { + System.err.println("VerifyError for " + cn); + failed.add("Class: " + cn + " not verified: " + ve.getMessage()); + } catch (ClassNotFoundException e) { + failed.add("Class: " + cn + " not found"); } } + + /** + * Maps a module-relative JRT path of a class file to its corresponding + * fully-qualified class name. + */ + private static String toClassName(Path path) { + // JRT uses '/' as the separator, and relative paths don't start with '/'. + String s = path.toString(); + return s.substring(0, s.length() - CLASS_SUFFIX.length()).replace('/', '.'); + } + + /** Whether a module-relative JRT file system path is a class file. */ + private static boolean isClassFile(Path path) { + String classFileName = path.getFileName().toString(); + return classFileName.endsWith(CLASS_SUFFIX) + && !classFileName.equals(MODULE_INFO); + } } } From 58b601ac4250a455e3f25f8505ead8c130eba642 Mon Sep 17 00:00:00 2001 From: Ioi Lam Date: Fri, 14 Nov 2025 18:41:50 +0000 Subject: [PATCH 059/418] 8371874: AOTLinkedClassBulkLoader::preload_classes() should not allocate heap objects Reviewed-by: shade, ayang --- src/hotspot/share/classfile/javaClasses.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/hotspot/share/classfile/javaClasses.cpp b/src/hotspot/share/classfile/javaClasses.cpp index ede3582b32e..ee80dbbc45c 100644 --- a/src/hotspot/share/classfile/javaClasses.cpp +++ b/src/hotspot/share/classfile/javaClasses.cpp @@ -1241,10 +1241,7 @@ bool java_lang_Class::restore_archived_mirror(Klass *k, if (!k->is_array_klass()) { // - local static final fields with initial values were initialized at dump time - - // create the init_lock - typeArrayOop r = oopFactory::new_typeArray(T_INT, 0, CHECK_(false)); - set_init_lock(mirror(), r); + assert(init_lock(mirror()) != nullptr, "allocated during AOT assembly"); if (protection_domain.not_null()) { set_protection_domain(mirror(), protection_domain()); From 3924a28a2281bbdb13fe9f1e0b5347d57197f8dc Mon Sep 17 00:00:00 2001 From: Alex Menkov Date: Fri, 14 Nov 2025 19:39:26 +0000 Subject: [PATCH 060/418] 8371083: FollowReferences reports non-class objects as JVMTI_HEAP_REFERENCE_SYSTEM_CLASS Reviewed-by: lmesnik, sspitsyn --- src/hotspot/share/prims/jvmtiTagMap.cpp | 39 +++++- .../KindSystemClass/KindSystemClass.java | 62 ++++++++++ .../KindSystemClass/libKindSystemClass.cpp | 111 ++++++++++++++++++ 3 files changed, 209 insertions(+), 3 deletions(-) create mode 100644 test/hotspot/jtreg/serviceability/jvmti/FollowReferences/KindSystemClass/KindSystemClass.java create mode 100644 test/hotspot/jtreg/serviceability/jvmti/FollowReferences/KindSystemClass/libKindSystemClass.cpp diff --git a/src/hotspot/share/prims/jvmtiTagMap.cpp b/src/hotspot/share/prims/jvmtiTagMap.cpp index a69c7cb7142..c923f91f69d 100644 --- a/src/hotspot/share/prims/jvmtiTagMap.cpp +++ b/src/hotspot/share/prims/jvmtiTagMap.cpp @@ -2190,6 +2190,39 @@ class SimpleRootsClosure : public OopClosure { virtual void do_oop(narrowOop* obj_p) { ShouldNotReachHere(); } }; +// A supporting closure used to process ClassLoaderData roots. +class CLDRootsClosure: public OopClosure { +private: + bool _continue; +public: + CLDRootsClosure(): _continue(true) {} + + inline bool stopped() { + return !_continue; + } + + void do_oop(oop* obj_p) { + if (stopped()) { + return; + } + + oop o = NativeAccess::oop_load(obj_p); + // ignore null + if (o == nullptr) { + return; + } + + jvmtiHeapReferenceKind kind = JVMTI_HEAP_REFERENCE_OTHER; + if (o->klass() == vmClasses::Class_klass()) { + kind = JVMTI_HEAP_REFERENCE_SYSTEM_CLASS; + } + + // invoke the callback + _continue = CallbackInvoker::report_simple_root(kind, o); + } + virtual void do_oop(narrowOop* obj_p) { ShouldNotReachHere(); } +}; + // A supporting closure used to process JNI locals class JNILocalRootsClosure : public OopClosure { private: @@ -2776,10 +2809,10 @@ inline bool VM_HeapWalkOperation::collect_simple_roots() { } // Preloaded classes and loader from the system dictionary - blk.set_kind(JVMTI_HEAP_REFERENCE_SYSTEM_CLASS); - CLDToOopClosure cld_closure(&blk, ClassLoaderData::_claim_none); + CLDRootsClosure cld_roots_closure; + CLDToOopClosure cld_closure(&cld_roots_closure, ClassLoaderData::_claim_none); ClassLoaderDataGraph::always_strong_cld_do(&cld_closure); - if (blk.stopped()) { + if (cld_roots_closure.stopped()) { return false; } diff --git a/test/hotspot/jtreg/serviceability/jvmti/FollowReferences/KindSystemClass/KindSystemClass.java b/test/hotspot/jtreg/serviceability/jvmti/FollowReferences/KindSystemClass/KindSystemClass.java new file mode 100644 index 00000000000..d02364d2b8f --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/FollowReferences/KindSystemClass/KindSystemClass.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8371083 + * @summary Verify FollowReferences does not report non-classes roots as JVMTI_HEAP_REFERENCE_SYSTEM_CLASS + * @requires vm.jvmti + * @run main/othervm/native -agentlib:KindSystemClass + * KindSystemClass + */ + +public class KindSystemClass { + + static native int tagSysClasses(); + static native Object[] getObjectsWithTags(); + + public static void main(String[] args) throws Exception { + System.loadLibrary("KindSystemClass"); + + int tagged = tagSysClasses(); + System.out.println("Tagged " + tagged + " classes"); + + Object[] objs = getObjectsWithTags(); + System.out.println("Tagged objects (total " + objs.length + "):"); + int nonClassesCnt = 0; + for (int i = 0; i < objs.length; i++) { + Object obj = objs[i]; + String s; + if (obj instanceof Class cls) { + s = "OK: " + cls; + } else { + nonClassesCnt++; + s = "ERROR, not a class: " + obj; + } + System.out.println("[" + i + "] " + s); + } + if (nonClassesCnt != 0) { + throw new RuntimeException("Found " + nonClassesCnt + " non-classes"); + } + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/FollowReferences/KindSystemClass/libKindSystemClass.cpp b/test/hotspot/jtreg/serviceability/jvmti/FollowReferences/KindSystemClass/libKindSystemClass.cpp new file mode 100644 index 00000000000..1be111fd40e --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/FollowReferences/KindSystemClass/libKindSystemClass.cpp @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include +#include "jvmti_common.hpp" + +static jvmtiEnv *jvmti = nullptr; +static int class_counter = 0; +static int other_counter = 0; + +static jint JNICALL +heap_reference_callback(jvmtiHeapReferenceKind reference_kind, + const jvmtiHeapReferenceInfo* reference_info, + jlong class_tag, + jlong referrer_class_tag, + jlong size, + jlong* tag_ptr, + jlong* referrer_tag_ptr, + jint length, + void* user_data) { + switch (reference_kind) { + case JVMTI_HEAP_REFERENCE_SYSTEM_CLASS: + *tag_ptr = ++class_counter; + break; + case JVMTI_HEAP_REFERENCE_OTHER: + ++other_counter; + break; + default: + break; + } + return JVMTI_VISIT_OBJECTS; +} + +extern "C" JNIEXPORT jint JNICALL +Java_KindSystemClass_tagSysClasses(JNIEnv* jni, jclass clazz) { + jvmtiHeapCallbacks callbacks = {}; + callbacks.heap_reference_callback = heap_reference_callback; + + jvmtiError err = jvmti->FollowReferences(0 /* filter nothing */, + nullptr /* no class filter */, + nullptr /* no initial object, follow roots */, + &callbacks, + nullptr); + check_jvmti_error(err, "FollowReferences failed"); + + LOG("JVMTI_HEAP_REFERENCE_SYSTEM_CLASS: %d, JVMTI_HEAP_REFERENCE_OTHER: %d\n", class_counter, other_counter); + + return class_counter; +} + +extern "C" JNIEXPORT jobjectArray JNICALL +Java_KindSystemClass_getObjectsWithTags(JNIEnv* jni, jclass clazz) { + // request tagged objects with tags 1..class_counter + jlong* tags = nullptr; + jvmtiError err = jvmti->Allocate(class_counter * sizeof(jlong), (unsigned char**)&tags); + check_jvmti_error(err, "Allocate failed"); + + for (int i = 0; i < class_counter; i++) { + tags[i] = i + 1; + } + + jint count = 0; + jobject* objects = nullptr; + + err = jvmti->GetObjectsWithTags(class_counter, tags, + &count, &objects, nullptr); + check_jvmti_error(err, "GetObjectsWithTags failed"); + + jclass object_klass = jni->FindClass("java/lang/Object"); + jobjectArray array = jni->NewObjectArray(count, object_klass, nullptr); + + for (jint i = 0; i < count; i++) { + jni->SetObjectArrayElement(array, i, objects[i]); + } + + deallocate(jvmti, jni, objects); + + return array; +} + +extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved) { + if (vm->GetEnv(reinterpret_cast(&jvmti), JVMTI_VERSION) != JNI_OK || !jvmti) { + LOG("Could not initialize JVMTI\n"); + abort(); + } + jvmtiCapabilities capabilities; + memset(&capabilities, 0, sizeof(capabilities)); + capabilities.can_tag_objects = 1; + check_jvmti_error(jvmti->AddCapabilities(&capabilities), "adding capabilities"); + return JVMTI_ERROR_NONE; +} From 91b97a49d48ee8528b34486172293fd3a68ae3c7 Mon Sep 17 00:00:00 2001 From: Kim Barrett Date: Fri, 14 Nov 2025 20:32:12 +0000 Subject: [PATCH 061/418] 8371922: Remove unused NonblockingQueue class Reviewed-by: coleenp --- .../share/utilities/nonblockingQueue.hpp | 136 --------- .../utilities/nonblockingQueue.inline.hpp | 248 --------------- .../gtest/utilities/test_nonblockingQueue.cpp | 283 ------------------ 3 files changed, 667 deletions(-) delete mode 100644 src/hotspot/share/utilities/nonblockingQueue.hpp delete mode 100644 src/hotspot/share/utilities/nonblockingQueue.inline.hpp delete mode 100644 test/hotspot/gtest/utilities/test_nonblockingQueue.cpp diff --git a/src/hotspot/share/utilities/nonblockingQueue.hpp b/src/hotspot/share/utilities/nonblockingQueue.hpp deleted file mode 100644 index 1b7e4b8bac4..00000000000 --- a/src/hotspot/share/utilities/nonblockingQueue.hpp +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - * - */ - -#ifndef SHARE_UTILITIES_NONBLOCKINGQUEUE_HPP -#define SHARE_UTILITIES_NONBLOCKINGQUEUE_HPP - -#include "memory/padded.hpp" -#include "utilities/globalDefinitions.hpp" -#include "utilities/pair.hpp" - -// The NonblockingQueue template provides a non-blocking FIFO. -// It has inner padding of one cache line between its two internal pointers. -// -// The queue is internally represented by a linked list of elements, with -// the link to the next element provided by a member of each element. -// Access to this member is provided by the next_ptr function. -// -// The queue has a special pseudo-element that marks the end of the list. -// Each queue has its own unique special element. A pointer to this element -// can be recognized using the is_end() function. Such a pointer must never -// be dereferenced. This end marker is the value of the next member of the -// last element in the queue, and possibly other elements while modifying -// the queue. -// -// A queue may temporarily appear to be empty even though elements have been -// added and not removed. For example, after running the following program, -// the value of r may be null. -// -// thread1: q.push(a); r = q.pop(); -// thread2: q.push(b); -// -// This can occur if the push of b started before the push of a, but didn't -// complete until after the pop. -// -// \tparam T is the class of the elements in the queue. -// -// \tparam next_ptr is a function pointer. Applying this function to -// an object of type T must return a pointer to the list entry member -// of the object associated with the NonblockingQueue type. -template -class NonblockingQueue { - T* volatile _head; - // Padding of one cache line to avoid false sharing. - DEFINE_PAD_MINUS_SIZE(1, DEFAULT_PADDING_SIZE, sizeof(T*)); - T* volatile _tail; - - NONCOPYABLE(NonblockingQueue); - - // Return the entry following node in the list used by the - // specialized NonblockingQueue class. - static inline T* next(const T& node); - - // Set the entry following node to new_next in the list used by the - // specialized NonblockingQueue class. Not thread-safe, as it cannot - // concurrently run with push or try_pop operations that modify this - // node. - static inline void set_next(T& node, T* new_next); - - // A unique pseudo-object pointer associated with this specific queue. - // The resulting pointer must not be dereferenced. - inline T* end_marker() const; - -public: - inline NonblockingQueue(); - inline ~NonblockingQueue() NOT_DEBUG(= default); - - // Return true if the queue is empty. - // Not thread-safe. There must be no concurrent modification while the - // queue is being tested. - inline bool empty() const; - - // Return the number of objects in the queue. - // Not thread-safe. There must be no concurrent modification while the - // length is being determined. - inline size_t length() const; - - // Thread-safe add the object to the end of the queue. - // Subject to ABA behavior; callers must ensure usage is safe. - inline void push(T& node) { append(node, node); } - - // Thread-safe add the objects from first to last to the end of the queue. - // Subject to ABA behavior; callers must ensure usage is safe. - inline void append(T& first, T& last); - - // Thread-safe attempt to remove and return the first object in the queue. - // Returns true if successful. If successful then *node_ptr is the former - // first object, or null if the queue was empty. If unsuccessful, because - // of contention with a concurrent modification, then returns false with - // the value of *node_ptr unspecified. Subject to ABA behavior; callers - // must ensure usage is safe. - inline bool try_pop(T** node_ptr); - - // Thread-safe remove and return the first object in the queue, or null - // if the queue was empty. This just iterates on try_pop() until it - // succeeds, returning the (possibly null) element obtained from that. - // Subject to ABA behavior; callers must ensure usage is safe. - inline T* pop(); - - // Take all the objects from the queue, leaving the queue empty. - // Not thread-safe. There must be no concurrent operations. - // Returns a pair of pointers to the current queue. - inline Pair take_all(); - - // Iteration support is provided by first() and is_end(). The queue must - // not be modified while iterating over its elements. - - // Return the first object in the queue, or an end marker (a pointer p for - // which is_end(p) is true) if the queue is empty. - inline T* first() const; - - // Test whether entry is an end marker for this queue. - inline bool is_end(const T* entry) const; -}; - -#endif // SHARE_UTILITIES_NONBLOCKINGQUEUE_HPP diff --git a/src/hotspot/share/utilities/nonblockingQueue.inline.hpp b/src/hotspot/share/utilities/nonblockingQueue.inline.hpp deleted file mode 100644 index d805eedb7a4..00000000000 --- a/src/hotspot/share/utilities/nonblockingQueue.inline.hpp +++ /dev/null @@ -1,248 +0,0 @@ -/* - * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - * - */ - -#ifndef SHARE_UTILITIES_NONBLOCKINGQUEUE_INLINE_HPP -#define SHARE_UTILITIES_NONBLOCKINGQUEUE_INLINE_HPP - -#include "utilities/nonblockingQueue.hpp" - -#include "runtime/atomicAccess.hpp" - -template -T* NonblockingQueue::next(const T& node) { - return AtomicAccess::load(next_ptr(const_cast(node))); -} - -template -void NonblockingQueue::set_next(T& node, T* new_next) { - AtomicAccess::store(next_ptr(node), new_next); -} - -template -NonblockingQueue::NonblockingQueue() : _head(nullptr), _tail(nullptr) {} - -#ifdef ASSERT -template -NonblockingQueue::~NonblockingQueue() { - assert(_head == nullptr, "precondition"); - assert(_tail == nullptr, "precondition"); -} -#endif - -// The end_marker must be uniquely associated with the specific queue, in -// case queue elements can make their way through multiple queues. A -// pointer to the queue itself (after casting) satisfies that requirement. -template -T* NonblockingQueue::end_marker() const { - return const_cast(reinterpret_cast(this)); -} - -template -T* NonblockingQueue::first() const { - T* head = AtomicAccess::load(&_head); - return head == nullptr ? end_marker() : head; -} - -template -bool NonblockingQueue::is_end(const T* entry) const { - return entry == end_marker(); -} - -template -bool NonblockingQueue::empty() const { - return AtomicAccess::load(&_head) == nullptr; -} - -template -size_t NonblockingQueue::length() const { - size_t result = 0; - for (T* cur = first(); !is_end(cur); cur = next(*cur)) { - ++result; - } - return result; -} - -// An append operation atomically exchanges the new tail with the queue tail. -// It then sets the "next" value of the old tail to the head of the list being -// appended. If the old tail is null then the queue was empty, then the -// head of the list being appended is instead stored in the queue head. -// -// This means there is a period between the exchange and the old tail update -// where the queue sequence is split into two parts, the list from the queue -// head to the old tail, and the list being appended. If there are concurrent -// push/append operations, each may introduce another such segment. But they -// all eventually get resolved by their respective updates of their old tail's -// "next" value. This also means that try_pop operation must handle an object -// differently depending on its "next" value. -// -// A push operation is just a degenerate append, where the object being pushed -// is both the head and the tail of the list being appended. -template -void NonblockingQueue::append(T& first, T& last) { - assert(next(last) == nullptr, "precondition"); - // Make last the new end of the queue. Any further push/appends will - // extend after last. We will try to extend from the previous end of - // queue. - set_next(last, end_marker()); - T* old_tail = AtomicAccess::xchg(&_tail, &last); - if (old_tail == nullptr) { - // If old_tail is null then the queue was empty, and _head must also be - // null. The correctness of this assertion depends on try_pop clearing - // first _head then _tail when taking the last entry. - assert(AtomicAccess::load(&_head) == nullptr, "invariant"); - // Fall through to common update of _head. - } else if (is_end(AtomicAccess::cmpxchg(next_ptr(*old_tail), end_marker(), &first))) { - // Successfully extended the queue list from old_tail to first. No - // other push/append could have competed with us, because we claimed - // old_tail for extension. We won any races with try_pop by changing - // away from end-marker. So we're done. - // - // Note that ABA is possible here. A concurrent try_pop could take - // old_tail before our update of old_tail's next_ptr, old_tail gets - // recycled and re-added to the end of this queue, and then we - // successfully cmpxchg, making the list in _tail circular. Callers - // must ensure this can't happen. - return; - } else { - // A concurrent try_pop has claimed old_tail, so it is no longer in the - // list. The queue was logically empty. _head is either null or - // old_tail, depending on how far try_pop operations have progressed. - DEBUG_ONLY(T* old_head = AtomicAccess::load(&_head);) - assert((old_head == nullptr) || (old_head == old_tail), "invariant"); - // Fall through to common update of _head. - } - // The queue was empty, and first should become the new _head. The queue - // will appear to be empty to any further try_pops until done. - AtomicAccess::store(&_head, &first); -} - -template -bool NonblockingQueue::try_pop(T** node_ptr) { - // We only need memory_order_consume. Upgrade it to "load_acquire" - // as the memory_order_consume API is not ready for use yet. - T* old_head = AtomicAccess::load_acquire(&_head); - if (old_head == nullptr) { - *node_ptr = nullptr; - return true; // Queue is empty. - } - - T* next_node = AtomicAccess::load_acquire(next_ptr(*old_head)); - if (!is_end(next_node)) { - // [Clause 1] - // There are several cases for next_node. - // (1) next_node is the extension of the queue's list. - // (2) next_node is null, because a competing try_pop took old_head. - // (3) next_node is the extension of some unrelated list, because a - // competing try_pop took old_head and put it in some other list. - // - // Attempt to advance the list, replacing old_head with next_node in - // _head. The success or failure of that attempt, along with the value - // of next_node, are used to partially determine which case we're in and - // how to proceed. In particular, advancement will fail for case (3). - if (old_head != AtomicAccess::cmpxchg(&_head, old_head, next_node)) { - // [Clause 1a] - // The cmpxchg to advance the list failed; a concurrent try_pop won - // the race and claimed old_head. This can happen for any of the - // next_node cases. - return false; - } else if (next_node == nullptr) { - // [Clause 1b] - // The cmpxchg to advance the list succeeded, but a concurrent try_pop - // has already claimed old_head (see [Clause 2] - old_head was the last - // entry in the list) by nulling old_head's next field. The advance set - // _head to null, "helping" the competing try_pop. _head will remain - // nullptr until a subsequent push/append. This is a lost race, and we - // report it as such for consistency, though we could report the queue - // was empty. We don't attempt to further help [Clause 2] by also - // trying to set _tail to nullptr, as that would just ensure that one or - // the other cmpxchg is a wasted failure. - return false; - } else { - // [Clause 1c] - // Successfully advanced the list and claimed old_head. next_node was - // in the extension of the queue's list. Return old_head after - // unlinking it from next_node. - set_next(*old_head, nullptr); - *node_ptr = old_head; - return true; - } - - } else if (is_end(AtomicAccess::cmpxchg(next_ptr(*old_head), next_node, (T*)nullptr))) { - // [Clause 2] - // Old_head was the last entry and we've claimed it by setting its next - // value to null. However, this leaves the queue in disarray. Fix up - // the queue, possibly in conjunction with other concurrent operations. - // Any further try_pops will consider the queue empty until a - // push/append completes by installing a new head. - - // The order of the two cmpxchgs doesn't matter algorithmically, but - // dealing with _head first gives a stronger invariant in append, and is - // also consistent with [Clause 1b]. - - // Attempt to change the queue head from old_head to null. Failure of - // the cmpxchg indicates a concurrent operation updated _head first. That - // could be either a push/append or a try_pop in [Clause 1b]. - AtomicAccess::cmpxchg(&_head, old_head, (T*)nullptr); - - // Attempt to change the queue tail from old_head to null. Failure of - // the cmpxchg indicates that a concurrent push/append updated _tail first. - // That operation will eventually recognize the old tail (our old_head) is - // no longer in the list and update _head from the list being appended. - AtomicAccess::cmpxchg(&_tail, old_head, (T*)nullptr); - - // The queue has been restored to order, and we can return old_head. - *node_ptr = old_head; - return true; - - } else { - // [Clause 3] - // Old_head was the last entry in the list, but either a concurrent - // try_pop claimed it first or a concurrent push/append extended the - // list from it. Either way, we lost the race to claim it. - return false; - } -} - -template -T* NonblockingQueue::pop() { - T* result = nullptr; - // Typically try_pop() will succeed without retrying many times, thus we - // omit SpinPause in the loop body. SpinPause or yield may be worthwhile - // in rare, highly contended cases, and client code could implement such - // with try_pop(). - while (!try_pop(&result)) {} - return result; -} - -template -Pair NonblockingQueue::take_all() { - T* tail = AtomicAccess::load(&_tail); - if (tail != nullptr) set_next(*tail, nullptr); // Clear end marker. - Pair result(AtomicAccess::load(&_head), tail); - AtomicAccess::store(&_head, (T*)nullptr); - AtomicAccess::store(&_tail, (T*)nullptr); - return result; -} - -#endif // SHARE_UTILITIES_NONBLOCKINGQUEUE_INLINE_HPP diff --git a/test/hotspot/gtest/utilities/test_nonblockingQueue.cpp b/test/hotspot/gtest/utilities/test_nonblockingQueue.cpp deleted file mode 100644 index ae299730f6e..00000000000 --- a/test/hotspot/gtest/utilities/test_nonblockingQueue.cpp +++ /dev/null @@ -1,283 +0,0 @@ -/* - * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -#include "memory/allocation.inline.hpp" -#include "runtime/atomicAccess.hpp" -#include "utilities/globalDefinitions.hpp" -#include "utilities/nonblockingQueue.inline.hpp" -#include "utilities/pair.hpp" -#include "threadHelper.inline.hpp" -#include "unittest.hpp" - -#include - -class NonblockingQueueTestElement { - typedef NonblockingQueueTestElement Element; - - Element* volatile _entry; - Element* volatile _entry1; - size_t _id; - - static Element* volatile* entry_ptr(Element& e) { return &e._entry; } - static Element* volatile* entry1_ptr(Element& e) { return &e._entry1; } - -public: - using TestQueue = NonblockingQueue; - using TestQueue1 = NonblockingQueue; - - NonblockingQueueTestElement(size_t id = 0) : _entry(), _entry1(), _id(id) {} - size_t id() const { return _id; } - void set_id(size_t value) { _id = value; } - Element* next() { return _entry; } - Element* next1() { return _entry1; } -}; - -typedef NonblockingQueueTestElement Element; -typedef Element::TestQueue TestQueue; -typedef Element::TestQueue1 TestQueue1; - -static void initialize(Element* elements, size_t size, TestQueue* queue) { - for (size_t i = 0; i < size; ++i) { - elements[i].set_id(i); - } - ASSERT_TRUE(queue->empty()); - ASSERT_EQ(0u, queue->length()); - ASSERT_TRUE(queue->is_end(queue->first())); - ASSERT_TRUE(queue->pop() == nullptr); - - for (size_t id = 0; id < size; ++id) { - ASSERT_EQ(id, queue->length()); - Element* e = &elements[id]; - ASSERT_EQ(id, e->id()); - queue->push(*e); - ASSERT_FALSE(queue->empty()); - // first() is always the oldest element. - ASSERT_EQ(&elements[0], queue->first()); - } -} - -class NonblockingQueueTestBasics : public ::testing::Test { -public: - NonblockingQueueTestBasics(); - - static const size_t nelements = 10; - Element elements[nelements]; - TestQueue queue; -}; - -const size_t NonblockingQueueTestBasics::nelements; - -NonblockingQueueTestBasics::NonblockingQueueTestBasics() : queue() { - initialize(elements, nelements, &queue); -} - -TEST_F(NonblockingQueueTestBasics, pop) { - for (size_t i = 0; i < nelements; ++i) { - ASSERT_FALSE(queue.empty()); - ASSERT_EQ(nelements - i, queue.length()); - Element* e = queue.pop(); - ASSERT_TRUE(e != nullptr); - ASSERT_EQ(&elements[i], e); - ASSERT_EQ(i, e->id()); - } - ASSERT_TRUE(queue.empty()); - ASSERT_EQ(0u, queue.length()); - ASSERT_TRUE(queue.pop() == nullptr); -} - -TEST_F(NonblockingQueueTestBasics, append) { - TestQueue other_queue; - ASSERT_TRUE(other_queue.empty()); - ASSERT_EQ(0u, other_queue.length()); - ASSERT_TRUE(other_queue.is_end(other_queue.first())); - ASSERT_TRUE(other_queue.pop() == nullptr); - - Pair pair = queue.take_all(); - other_queue.append(*pair.first, *pair.second); - ASSERT_EQ(nelements, other_queue.length()); - ASSERT_TRUE(queue.empty()); - ASSERT_EQ(0u, queue.length()); - ASSERT_TRUE(queue.is_end(queue.first())); - ASSERT_TRUE(queue.pop() == nullptr); - - for (size_t i = 0; i < nelements; ++i) { - ASSERT_EQ(nelements - i, other_queue.length()); - Element* e = other_queue.pop(); - ASSERT_TRUE(e != nullptr); - ASSERT_EQ(&elements[i], e); - ASSERT_EQ(i, e->id()); - } - ASSERT_EQ(0u, other_queue.length()); - ASSERT_TRUE(other_queue.pop() == nullptr); -} - -TEST_F(NonblockingQueueTestBasics, two_queues) { - TestQueue1 queue1; - ASSERT_TRUE(queue1.pop() == nullptr); - - for (size_t id = 0; id < nelements; ++id) { - queue1.push(elements[id]); - } - ASSERT_EQ(nelements, queue1.length()); - Element* e0 = queue.first(); - Element* e1 = queue1.first(); - ASSERT_TRUE(e0 != nullptr); - ASSERT_TRUE(e1 != nullptr); - ASSERT_FALSE(queue.is_end(e0)); - ASSERT_FALSE(queue1.is_end(e1)); - while (!queue.is_end(e0) && !queue1.is_end(e1)) { - ASSERT_EQ(e0, e1); - e0 = e0->next(); - e1 = e1->next1(); - } - ASSERT_TRUE(queue.is_end(e0)); - ASSERT_TRUE(queue1.is_end(e1)); - - for (size_t i = 0; i < nelements; ++i) { - ASSERT_EQ(nelements - i, queue.length()); - ASSERT_EQ(nelements - i, queue1.length()); - - Element* e = queue.pop(); - ASSERT_TRUE(e != nullptr); - ASSERT_EQ(&elements[i], e); - ASSERT_EQ(i, e->id()); - - Element* e1 = queue1.pop(); - ASSERT_TRUE(e1 != nullptr); - ASSERT_EQ(&elements[i], e1); - ASSERT_EQ(i, e1->id()); - - ASSERT_EQ(e, e1); - } - ASSERT_EQ(0u, queue.length()); - ASSERT_EQ(0u, queue1.length()); - ASSERT_TRUE(queue.pop() == nullptr); - ASSERT_TRUE(queue1.pop() == nullptr); -} - -class NonblockingQueueTestThread : public JavaTestThread { - uint _id; - TestQueue* _from; - TestQueue* _to; - volatile size_t* _processed; - size_t _process_limit; - size_t _local_processed; - volatile bool _ready; - -public: - NonblockingQueueTestThread(Semaphore* post, - uint id, - TestQueue* from, - TestQueue* to, - volatile size_t* processed, - size_t process_limit) : - JavaTestThread(post), - _id(id), - _from(from), - _to(to), - _processed(processed), - _process_limit(process_limit), - _local_processed(0), - _ready(false) - {} - - virtual void main_run() { - AtomicAccess::release_store_fence(&_ready, true); - while (true) { - Element* e = _from->pop(); - if (e != nullptr) { - _to->push(*e); - AtomicAccess::inc(_processed); - ++_local_processed; - } else if (AtomicAccess::load_acquire(_processed) == _process_limit) { - tty->print_cr("thread %u processed %zu", _id, _local_processed); - return; - } - } - } - - bool ready() const { return AtomicAccess::load_acquire(&_ready); } -}; - -TEST_VM(NonblockingQueueTest, stress) { - Semaphore post; - TestQueue initial_queue; - TestQueue start_queue; - TestQueue middle_queue; - TestQueue final_queue; - volatile size_t stage1_processed = 0; - volatile size_t stage2_processed = 0; - - const size_t nelements = 10000; - Element* elements = NEW_C_HEAP_ARRAY(Element, nelements, mtOther); - for (size_t id = 0; id < nelements; ++id) { - ::new (&elements[id]) Element(id); - initial_queue.push(elements[id]); - } - ASSERT_EQ(nelements, initial_queue.length()); - - // - stage1 threads pop from start_queue and push to middle_queue. - // - stage2 threads pop from middle_queue and push to final_queue. - // - all threads in a stage count the number of elements processed in - // their corresponding stageN_processed counter. - - const uint stage1_threads = 2; - const uint stage2_threads = 2; - const uint nthreads = stage1_threads + stage2_threads; - NonblockingQueueTestThread* threads[nthreads] = {}; - - for (uint i = 0; i < ARRAY_SIZE(threads); ++i) { - TestQueue* from = &start_queue; - TestQueue* to = &middle_queue; - volatile size_t* processed = &stage1_processed; - if (i >= stage1_threads) { - from = &middle_queue; - to = &final_queue; - processed = &stage2_processed; - } - threads[i] = - new NonblockingQueueTestThread(&post, i, from, to, processed, nelements); - threads[i]->doit(); - while (!threads[i]->ready()) {} // Wait until ready to start test. - } - - // Transfer elements to start_queue to start test. - Pair pair = initial_queue.take_all(); - start_queue.append(*pair.first, *pair.second); - - // Wait for all threads to complete. - for (uint i = 0; i < nthreads; ++i) { - post.wait(); - } - - // Verify expected state. - ASSERT_EQ(nelements, stage1_processed); - ASSERT_EQ(nelements, stage2_processed); - ASSERT_EQ(0u, initial_queue.length()); - ASSERT_EQ(0u, start_queue.length()); - ASSERT_EQ(0u, middle_queue.length()); - ASSERT_EQ(nelements, final_queue.length()); - while (final_queue.pop() != nullptr) {} - - FREE_C_HEAP_ARRAY(Element, elements); -} From cc05530b813564a40c233eaaa80b906795c6d752 Mon Sep 17 00:00:00 2001 From: Roger Riggs Date: Fri, 14 Nov 2025 20:57:20 +0000 Subject: [PATCH 062/418] 8371732: [redo] Change java.time month/day field types to 'byte' Reviewed-by: darcy, alanb --- .../share/classes/java/time/LocalDate.java | 28 +++++++++++++++---- .../share/classes/java/time/MonthDay.java | 26 +++++++++++++---- .../share/classes/java/time/YearMonth.java | 24 +++++++++++++--- .../classes/java/time/chrono/HijrahDate.java | 14 +++++----- .../time/test/java/time/TestLocalDate.java | 18 +++++++++++- .../time/test/java/time/TestMonthDay.java | 18 +++++++++++- .../time/test/java/time/TestYearMonth.java | 20 ++++++++++++- 7 files changed, 124 insertions(+), 24 deletions(-) diff --git a/src/java.base/share/classes/java/time/LocalDate.java b/src/java.base/share/classes/java/time/LocalDate.java index 016bdab5394..2105621d135 100644 --- a/src/java.base/share/classes/java/time/LocalDate.java +++ b/src/java.base/share/classes/java/time/LocalDate.java @@ -79,6 +79,8 @@ import java.io.DataOutput; import java.io.IOException; import java.io.InvalidObjectException; import java.io.ObjectInputStream; +import java.io.ObjectStreamField; +import java.io.Serial; import java.io.Serializable; import java.time.chrono.ChronoLocalDate; import java.time.chrono.IsoEra; @@ -142,6 +144,22 @@ import jdk.internal.util.DateTimeHelper; public final class LocalDate implements Temporal, TemporalAdjuster, ChronoLocalDate, Serializable { + /** + * For backward compatibility of the serialized {@code LocalDate.class} object, + * explicitly declare the types of the serialized fields as defined in Java SE 8. + * Instances of {@code LocalDate} are serialized using the dedicated + * serialized form by {@code writeReplace}. + * @serialField year int The year. + * @serialField month short The month-of-year. + * @serialField day short The day-of-month. + */ + @Serial + private static final ObjectStreamField[] serialPersistentFields = { + new ObjectStreamField("year", int.class), + new ObjectStreamField("month", short.class), + new ObjectStreamField("day", short.class) + }; + /** * The minimum supported {@code LocalDate}, '-999999999-01-01'. * This could be used by an application as a "far past" date. @@ -178,15 +196,15 @@ public final class LocalDate /** * @serial The year. */ - private final int year; + private final transient int year; /** * @serial The month-of-year. */ - private final short month; + private final transient byte month; /** * @serial The day-of-month. */ - private final short day; + private final transient byte day; //----------------------------------------------------------------------- /** @@ -490,8 +508,8 @@ public final class LocalDate */ private LocalDate(int year, int month, int dayOfMonth) { this.year = year; - this.month = (short) month; - this.day = (short) dayOfMonth; + this.month = (byte) month; + this.day = (byte) dayOfMonth; } //----------------------------------------------------------------------- diff --git a/src/java.base/share/classes/java/time/MonthDay.java b/src/java.base/share/classes/java/time/MonthDay.java index 1de4fa84d3e..a25c9beec95 100644 --- a/src/java.base/share/classes/java/time/MonthDay.java +++ b/src/java.base/share/classes/java/time/MonthDay.java @@ -69,6 +69,8 @@ import java.io.DataOutput; import java.io.IOException; import java.io.InvalidObjectException; import java.io.ObjectInputStream; +import java.io.ObjectStreamField; +import java.io.Serial; import java.io.Serializable; import java.time.chrono.Chronology; import java.time.chrono.IsoChronology; @@ -133,6 +135,20 @@ public final class MonthDay */ @java.io.Serial private static final long serialVersionUID = -939150713474957432L; + + /** + * For backward compatibility of the serialized {@code MonthDay.class} object, + * explicitly declare the types of the serialized fields as defined in Java SE 8. + * Instances of {@code MonthDay} are serialized using the dedicated + * serialized form by {@code writeReplace}. + * @serialField month int The month-of-year. + * @serialField day int The day-of-month. + */ + @Serial + private static final ObjectStreamField[] serialPersistentFields = { + new ObjectStreamField("month", int.class), + new ObjectStreamField("day", int.class) + }; /** * Parser. */ @@ -144,13 +160,13 @@ public final class MonthDay .toFormatter(); /** - * @serial The month-of-year, not null. + * @serial The month-of-year. */ - private final int month; + private final transient byte month; /** * @serial The day-of-month. */ - private final int day; + private final transient byte day; //----------------------------------------------------------------------- /** @@ -319,8 +335,8 @@ public final class MonthDay * @param dayOfMonth the day-of-month to represent, validated from 1 to 29-31 */ private MonthDay(int month, int dayOfMonth) { - this.month = month; - this.day = dayOfMonth; + this.month = (byte) month; + this.day = (byte) dayOfMonth; } //----------------------------------------------------------------------- diff --git a/src/java.base/share/classes/java/time/YearMonth.java b/src/java.base/share/classes/java/time/YearMonth.java index 8ad1172811f..b3d1aff7bc2 100644 --- a/src/java.base/share/classes/java/time/YearMonth.java +++ b/src/java.base/share/classes/java/time/YearMonth.java @@ -78,6 +78,8 @@ import java.io.DataOutput; import java.io.IOException; import java.io.InvalidObjectException; import java.io.ObjectInputStream; +import java.io.ObjectStreamField; +import java.io.Serial; import java.io.Serializable; import java.time.chrono.Chronology; import java.time.chrono.IsoChronology; @@ -137,6 +139,20 @@ public final class YearMonth */ @java.io.Serial private static final long serialVersionUID = 4183400860270640070L; + + /** + * For backward compatibility of the serialized {@code YearMonth.class} object, + * explicitly declare the types of the serialized fields as defined in Java SE 8. + * Instances of {@code YearMonth} are serialized using the dedicated + * serialized form by {@code writeReplace}. + * @serialField year int The year. + * @serialField month int The month-of-year. + */ + @java.io.Serial + private static final ObjectStreamField[] serialPersistentFields = { + new ObjectStreamField("year", int.class), + new ObjectStreamField("month", int.class), + }; /** * Parser. */ @@ -149,11 +165,11 @@ public final class YearMonth /** * @serial The year. */ - private final int year; + private final transient int year; /** - * @serial The month-of-year, not null. + * @serial The month-of-year.. */ - private final int month; + private final transient byte month; //----------------------------------------------------------------------- /** @@ -306,7 +322,7 @@ public final class YearMonth */ private YearMonth(int year, int month) { this.year = year; - this.month = month; + this.month = (byte) month; } /** diff --git a/src/java.base/share/classes/java/time/chrono/HijrahDate.java b/src/java.base/share/classes/java/time/chrono/HijrahDate.java index 114a47e4797..2d3e4f93e69 100644 --- a/src/java.base/share/classes/java/time/chrono/HijrahDate.java +++ b/src/java.base/share/classes/java/time/chrono/HijrahDate.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2025, 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 @@ -137,11 +137,11 @@ public final class HijrahDate /** * The month-of-year. */ - private final transient int monthOfYear; + private final transient byte monthOfYear; /** * The day-of-month. */ - private final transient int dayOfMonth; + private final transient byte dayOfMonth; //------------------------------------------------------------------------- /** @@ -273,8 +273,8 @@ public final class HijrahDate this.chrono = chrono; this.prolepticYear = prolepticYear; - this.monthOfYear = monthOfYear; - this.dayOfMonth = dayOfMonth; + this.monthOfYear = (byte) monthOfYear; + this.dayOfMonth = (byte) dayOfMonth; } /** @@ -287,8 +287,8 @@ public final class HijrahDate this.chrono = chrono; this.prolepticYear = dateInfo[0]; - this.monthOfYear = dateInfo[1]; - this.dayOfMonth = dateInfo[2]; + this.monthOfYear = (byte) dateInfo[1]; + this.dayOfMonth = (byte) dateInfo[2]; } //----------------------------------------------------------------------- diff --git a/test/jdk/java/time/test/java/time/TestLocalDate.java b/test/jdk/java/time/test/java/time/TestLocalDate.java index 4b732b0619b..ef7a10ac687 100644 --- a/test/jdk/java/time/test/java/time/TestLocalDate.java +++ b/test/jdk/java/time/test/java/time/TestLocalDate.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -65,6 +65,8 @@ import static org.testng.Assert.assertSame; import static org.testng.Assert.assertTrue; import static org.testng.Assert.fail; +import java.io.ObjectStreamClass; +import java.io.ObjectStreamField; import java.time.DateTimeException; import java.time.LocalDate; import java.time.Month; @@ -515,4 +517,18 @@ public class TestLocalDate extends AbstractTest { .minus(quarterYears, ChronoUnit.MONTHS); assertEquals(t0, t1); } + + // Verify serialized fields types are backward compatible + @Test + public void verifySerialFields() { + var osc = ObjectStreamClass.lookup(LocalDate.class); + for (ObjectStreamField f : osc.getFields()) { + switch (f.getName()) { + case "year" -> assertEquals(f.getType(), int.class, f.getName()); + case "month", + "day" -> assertEquals(f.getType(), short.class); + default -> fail("unknown field in LocalDate: " + f.getName()); + } + } + } } diff --git a/test/jdk/java/time/test/java/time/TestMonthDay.java b/test/jdk/java/time/test/java/time/TestMonthDay.java index b03878be1a5..311a40030c1 100644 --- a/test/jdk/java/time/test/java/time/TestMonthDay.java +++ b/test/jdk/java/time/test/java/time/TestMonthDay.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2025, 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 @@ -62,7 +62,10 @@ package test.java.time; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertSame; import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; +import java.io.ObjectStreamClass; +import java.io.ObjectStreamField; import java.time.LocalDate; import java.time.Month; import java.time.MonthDay; @@ -144,4 +147,17 @@ public class TestMonthDay extends AbstractTest { } } + + // Verify serialized fields types are backward compatible + @Test + public void verifySerialFields() { + var osc = ObjectStreamClass.lookup(MonthDay.class); + for (ObjectStreamField f : osc.getFields()) { + switch (f.getName()) { + case "month", + "day" -> assertEquals(f.getType(), int.class, f.getName()); + default -> fail("unknown field in MonthDay: " + f.getName()); + } + } + } } diff --git a/test/jdk/java/time/test/java/time/TestYearMonth.java b/test/jdk/java/time/test/java/time/TestYearMonth.java index f02ed96a5d7..0552994b8c2 100644 --- a/test/jdk/java/time/test/java/time/TestYearMonth.java +++ b/test/jdk/java/time/test/java/time/TestYearMonth.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2025, 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 @@ -59,10 +59,16 @@ */ package test.java.time; +import java.io.ObjectStreamClass; +import java.io.ObjectStreamField; +import java.time.LocalDate; import java.time.YearMonth; import org.testng.annotations.Test; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.fail; + /** * Test YearMonth. */ @@ -75,4 +81,16 @@ public class TestYearMonth extends AbstractTest { assertImmutable(YearMonth.class); } + // Verify serialized fields types are backward compatible + @Test + public void verifySerialFields() { + var osc = ObjectStreamClass.lookup(YearMonth.class); + for (ObjectStreamField f : osc.getFields()) { + switch (f.getName()) { + case "year" -> assertEquals(f.getType(), int.class, f.getName()); + case "month" -> assertEquals(f.getType(), int.class, f.getName()); + default -> fail("unknown field in YearMonth: " + f.getName()); + } + } + } } From ad3dfaf1fc483bb2bfd5c26d76c43b8f69454cbd Mon Sep 17 00:00:00 2001 From: Anthony Scarpino Date: Fri, 14 Nov 2025 21:08:36 +0000 Subject: [PATCH 063/418] 8360564: Implement JEP 524: PEM Encodings of Cryptographic Objects (Second Preview) Reviewed-by: weijun, mullan --- .../classes/java/security/DEREncodable.java | 4 +- .../share/classes/java/security/PEM.java | 147 ++++++ .../classes/java/security/PEMDecoder.java | 361 ++++++++------- .../classes/java/security/PEMEncoder.java | 367 +++++++-------- .../classes/java/security/PEMRecord.java | 120 ----- .../javax/crypto/EncryptedPrivateKeyInfo.java | 435 +++++++++++------- .../jdk/internal/javac/PreviewFeature.java | 3 +- .../classes/sun/security/ec/ECKeyFactory.java | 15 +- .../sun/security/ec/ECPrivateKeyImpl.java | 93 ++-- .../sun/security/ec/XDHKeyFactory.java | 22 +- .../sun/security/ec/ed/EdDSAKeyFactory.java | 15 +- .../classes/sun/security/pkcs/PKCS8Key.java | 49 +- .../sun/security/provider/X509Factory.java | 4 +- .../classes/sun/security/util/KeyUtil.java | 20 +- .../share/classes/sun/security/util/Pem.java | 149 +++++- test/jdk/java/security/PEM/PEMData.java | 36 +- .../jdk/java/security/PEM/PEMDecoderTest.java | 112 +++-- .../jdk/java/security/PEM/PEMEncoderTest.java | 83 +++- .../{EncryptKey.java => Encrypt.java} | 42 +- .../EncryptedPrivateKeyInfo/GetKey.java | 3 +- .../EncryptedPrivateKeyInfo/GetKeyPair.java | 120 +++++ .../net/ssl/interop/ClientHelloInterOp.java | 1 - 22 files changed, 1385 insertions(+), 816 deletions(-) create mode 100644 src/java.base/share/classes/java/security/PEM.java delete mode 100644 src/java.base/share/classes/java/security/PEMRecord.java rename test/jdk/javax/crypto/EncryptedPrivateKeyInfo/{EncryptKey.java => Encrypt.java} (76%) create mode 100644 test/jdk/javax/crypto/EncryptedPrivateKeyInfo/GetKeyPair.java diff --git a/src/java.base/share/classes/java/security/DEREncodable.java b/src/java.base/share/classes/java/security/DEREncodable.java index 63c6a73ee52..1401336037c 100644 --- a/src/java.base/share/classes/java/security/DEREncodable.java +++ b/src/java.base/share/classes/java/security/DEREncodable.java @@ -47,7 +47,7 @@ import java.security.spec.X509EncodedKeySpec; * @see EncryptedPrivateKeyInfo * @see X509Certificate * @see X509CRL - * @see PEMRecord + * @see PEM * * @since 25 */ @@ -55,5 +55,5 @@ import java.security.spec.X509EncodedKeySpec; @PreviewFeature(feature = PreviewFeature.Feature.PEM_API) public sealed interface DEREncodable permits AsymmetricKey, KeyPair, PKCS8EncodedKeySpec, X509EncodedKeySpec, EncryptedPrivateKeyInfo, - X509Certificate, X509CRL, PEMRecord { + X509Certificate, X509CRL, PEM { } diff --git a/src/java.base/share/classes/java/security/PEM.java b/src/java.base/share/classes/java/security/PEM.java new file mode 100644 index 00000000000..2068fb707dc --- /dev/null +++ b/src/java.base/share/classes/java/security/PEM.java @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2025, 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. 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 java.security; + +import jdk.internal.javac.PreviewFeature; + +import sun.security.util.Pem; + +import java.io.InputStream; +import java.util.Base64; +import java.util.Objects; + +/** + * {@code PEM} is a {@link DEREncodable} that represents Privacy-Enhanced + * Mail (PEM) data by its type and Base64-encoded content. + * + *

    The {@link PEMDecoder#decode(String)} and + * {@link PEMDecoder#decode(InputStream)} methods return a {@code PEM} object + * when the data type cannot be represented by a cryptographic object. + * If you need access to the leading data of a PEM text, or want to + * handle the text content directly, use the decoding methods + * {@link PEMDecoder#decode(String, Class)} or + * {@link PEMDecoder#decode(InputStream, Class)} with {@code PEM.class} as an + * argument type. + * + *

    A {@code PEM} object can be encoded back to its textual format by calling + * {@link #toString()} or by using the encode methods in {@link PEMEncoder}. + * + *

    When constructing a {@code PEM} instance, both {@code type} and + * {@code content} must not be {@code null}. + * + *

    No validation is performed during instantiation to ensure that + * {@code type} conforms to RFC 7468 or other legacy formats, that + * {@code content} is valid Base64 data, or that {@code content} matches the + * {@code type}. + + *

    Common {@code type} values include, but are not limited to: + * CERTIFICATE, CERTIFICATE REQUEST, ATTRIBUTE CERTIFICATE, X509 CRL, PKCS7, + * CMS, PRIVATE KEY, ENCRYPTED PRIVATE KEY, and PUBLIC KEY. + * + *

    {@code leadingData} is {@code null} if there is no data preceding the PEM + * header during decoding. {@code leadingData} can be useful for reading + * metadata that accompanies the PEM data. Because the value may represent a large + * amount of data, it is not defensively copied by the constructor, and the + * {@link #leadingData()} method does not return a clone. Modification of the + * passed-in or returned array changes the value stored in this record. + * + * @param type the type identifier from the PEM header, without PEM syntax + * labels; for example, for a public key, {@code type} would be + * "PUBLIC KEY" + * @param content the Base64-encoded data, excluding the PEM header and footer + * @param leadingData any non-PEM data that precedes the PEM header during + * decoding. This value may be {@code null}. + * + * @spec https://www.rfc-editor.org/info/rfc7468 + * RFC 7468: Textual Encodings of PKIX, PKCS, and CMS Structures + * + * @see PEMDecoder + * @see PEMEncoder + * + * @since 26 + */ +@PreviewFeature(feature = PreviewFeature.Feature.PEM_API) +public record PEM(String type, String content, byte[] leadingData) + implements DEREncodable { + + /** + * Creates a {@code PEM} instance with the specified parameters. + * + * @param type the PEM type identifier + * @param content the Base64-encoded data, excluding the PEM header and footer + * @param leadingData any non-PEM data read during the decoding process + * before the PEM header. This value may be {@code null}. + * @throws IllegalArgumentException if {@code type} is incorrectly formatted + * @throws NullPointerException if {@code type} or {@code content} is {@code null} + */ + public PEM { + Objects.requireNonNull(type, "\"type\" cannot be null."); + Objects.requireNonNull(content, "\"content\" cannot be null."); + + // With no validity checking on `type`, the constructor accept anything + // including lowercase. The onus is on the caller. + if (type.startsWith("-") || type.startsWith("BEGIN ") || + type.startsWith("END ")) { + throw new IllegalArgumentException("PEM syntax labels found. " + + "Only the PEM type identifier is allowed."); + } + } + + /** + * Creates a {@code PEM} instance with the specified type and content. This + * constructor sets {@code leadingData} to {@code null}. + * + * @param type the PEM type identifier + * @param content the Base64-encoded data, excluding the PEM header and footer + * @throws IllegalArgumentException if {@code type} is incorrectly formatted + * @throws NullPointerException if {@code type} or {@code content} is {@code null} + */ + public PEM(String type, String content) { + this(type, content, null); + } + + /** + * Returns the PEM formatted string containing the {@code type} and + * Base64-encoded {@code content}. {@code leadingData} is not included. + * + * @return the PEM text representation + */ + @Override + final public String toString() { + return Pem.pemEncoded(this); + } + + /** + * Returns a Base64-decoded byte array of {@code content}, using + * {@link Base64#getMimeDecoder()}. + * + * @return a decoded byte array + * @throws IllegalArgumentException if decoding fails + */ + final public byte[] decode() { + return Base64.getMimeDecoder().decode(content); + } +} diff --git a/src/java.base/share/classes/java/security/PEMDecoder.java b/src/java.base/share/classes/java/security/PEMDecoder.java index 0b959bf2440..7a4b7753876 100644 --- a/src/java.base/share/classes/java/security/PEMDecoder.java +++ b/src/java.base/share/classes/java/security/PEMDecoder.java @@ -27,6 +27,7 @@ package java.security; import jdk.internal.javac.PreviewFeature; +import jdk.internal.ref.CleanerFactory; import sun.security.pkcs.PKCS8Key; import sun.security.rsa.RSAPrivateCrtKeyImpl; import sun.security.util.KeyUtil; @@ -35,6 +36,7 @@ import sun.security.util.Pem; import javax.crypto.EncryptedPrivateKeyInfo; import javax.crypto.spec.PBEKeySpec; import java.io.*; +import java.lang.ref.Reference; import java.nio.charset.StandardCharsets; import java.security.cert.*; import java.security.spec.*; @@ -43,96 +45,105 @@ import java.util.Objects; /** * {@code PEMDecoder} implements a decoder for Privacy-Enhanced Mail (PEM) data. - * PEM is a textual encoding used to store and transfer security + * PEM is a textual encoding used to store and transfer cryptographic * objects, such as asymmetric keys, certificates, and certificate revocation - * lists (CRLs). It is defined in RFC 1421 and RFC 7468. PEM consists of a - * Base64-formatted binary encoding enclosed by a type-identifying header + * lists (CRLs). It is defined in RFC 1421 and RFC 7468. PEM consists of a + * Base64-encoded binary encoding enclosed by a type-identifying header * and footer. * - *

    The {@linkplain #decode(String)} and {@linkplain #decode(InputStream)} - * methods return an instance of a class that matches the data - * type and implements {@link DEREncodable}. - * - *

    The following lists the supported PEM types and the {@code DEREncodable} - * types that each are decoded as: + *

    The {@link #decode(String)} and {@link #decode(InputStream)} methods + * return an instance of a class that matches the PEM type and implements + * {@link DEREncodable}, as follows: *

      - *
    • CERTIFICATE : {@code X509Certificate}
    • - *
    • X509 CRL : {@code X509CRL}
    • - *
    • PUBLIC KEY : {@code PublicKey}
    • - *
    • PUBLIC KEY : {@code X509EncodedKeySpec} (Only supported when passed as - * a {@code Class} parameter)
    • - *
    • PRIVATE KEY : {@code PrivateKey}
    • - *
    • PRIVATE KEY : {@code PKCS8EncodedKeySpec} (Only supported when passed - * as a {@code Class} parameter)
    • - *
    • PRIVATE KEY : {@code KeyPair} (if the encoding also contains a - * public key)
    • - *
    • ENCRYPTED PRIVATE KEY : {@code EncryptedPrivateKeyInfo}
    • - *
    • ENCRYPTED PRIVATE KEY : {@code PrivateKey} (if configured with - * Decryption)
    • - *
    • Other types : {@code PEMRecord}
    • + *
    • CERTIFICATE : {@link X509Certificate}
    • + *
    • X509 CRL : {@link X509CRL}
    • + *
    • PUBLIC KEY : {@link PublicKey}
    • + *
    • PRIVATE KEY : {@link PrivateKey} or {@link KeyPair} + * (if the encoding contains a public key)
    • + *
    • ENCRYPTED PRIVATE KEY : {@link EncryptedPrivateKeyInfo}
    • + *
    • Other types : {@link PEM}
    • + *
    + * When used with a {@code PEMDecoder} instance configured for decryption: + *
      + *
    • ENCRYPTED PRIVATE KEY : {@link PrivateKey} or {@link KeyPair} + * (if the encoding contains a public key)
    • *
    * - *

    The {@code PublicKey} and {@code PrivateKey} types, an algorithm specific - * subclass is returned if the underlying algorithm is supported. For example an - * ECPublicKey and ECPrivateKey for Elliptic Curve keys. + *

    For {@code PublicKey} and {@code PrivateKey} types, an algorithm-specific + * subclass is returned if the algorithm is supported. For example, an + * {@code ECPublicKey} or an {@code ECPrivateKey} for Elliptic Curve keys. * *

    If the PEM type does not have a corresponding class, * {@code decode(String)} and {@code decode(InputStream)} will return a - * {@link PEMRecord}. + * {@code PEM} object. * - *

    The {@linkplain #decode(String, Class)} and - * {@linkplain #decode(InputStream, Class)} methods take a class parameter - * which determines the type of {@code DEREncodable} that is returned. These - * methods are useful when extracting or changing the return class. - * For example, if the PEM contains both public and private keys, the - * class parameter can specify which to return. Use - * {@code PrivateKey.class} to return only the private key. - * If the class parameter is set to {@code X509EncodedKeySpec.class}, the - * public key will be returned in that format. Any type of PEM data can be - * decoded into a {@code PEMRecord} by specifying {@code PEMRecord.class}. - * If the class parameter doesn't match the PEM content, a - * {@linkplain ClassCastException} will be thrown. + *

    The {@link #decode(String, Class)} and {@link #decode(InputStream, Class)} + * methods take a class parameter that specifies the type of {@code DEREncodable} + * to return. These methods are useful for avoiding casts when the PEM type is + * known, or when extracting a specific type if there is more than one option. + * For example, if the PEM contains both a public and private key, specifying + * {@code PrivateKey.class} returns only the private key. + * If the class parameter specifies {@code X509EncodedKeySpec.class}, the + * public key encoding is returned as an instance of {@code X509EncodedKeySpec} + * class. Any type of PEM data can be decoded into a {@code PEM} object by + * specifying {@code PEM.class}. If the class parameter does not match the PEM + * content, a {@code ClassCastException} is thrown. + * + *

    In addition to the types listed above, these methods support the + * following PEM types and {@code DEREncodable} classes when specified as + * parameters: + *

      + *
    • PUBLIC KEY : {@link X509EncodedKeySpec}
    • + *
    • PRIVATE KEY : {@link PKCS8EncodedKeySpec}
    • + *
    • PRIVATE KEY : {@link PublicKey} (if the encoding contains a public key)
    • + *
    • PRIVATE KEY : {@link X509EncodedKeySpec} (if the encoding contains a public key)
    • + *
    + * When used with a {@code PEMDecoder} instance configured for decryption: + *
      + *
    • ENCRYPTED PRIVATE KEY : {@link PKCS8EncodedKeySpec}
    • + *
    • ENCRYPTED PRIVATE KEY : {@link PublicKey} (if the encoding contains a public key)
    • + *
    • ENCRYPTED PRIVATE KEY : {@link X509EncodedKeySpec} (if the encoding contains a public key)
    • + *
    * *

    A new {@code PEMDecoder} instance is created when configured - * with {@linkplain #withFactory(Provider)} and/or - * {@linkplain #withDecryption(char[])}. {@linkplain #withFactory(Provider)} - * configures the decoder to use only {@linkplain KeyFactory} and - * {@linkplain CertificateFactory} instances from the given {@code Provider}. - * {@linkplain #withDecryption(char[])} configures the decoder to decrypt all - * encrypted private key PEM data using the given password. - * Configuring an instance for decryption does not prevent decoding with - * unencrypted PEM. Any encrypted PEM that fails decryption - * will throw a {@link RuntimeException}. When an encrypted private key PEM is - * used with a decoder not configured for decryption, an - * {@link EncryptedPrivateKeyInfo} object is returned. + * with {@link #withFactory(Provider)} or {@link #withDecryption(char[])}. + * The {@link #withFactory(Provider)} method uses the specified provider + * to produce cryptographic objects from {@link KeyFactory} and + * {@link CertificateFactory}. The {@link #withDecryption(char[])} method configures the + * decoder to decrypt and decode encrypted private key PEM data using the given + * password. If decryption fails, an {@link IllegalArgumentException} is thrown. + * If an encrypted private key PEM is processed by a decoder not configured + * for decryption, an {@link EncryptedPrivateKeyInfo} object is returned. + * A {@code PEMDecoder} configured for decryption will decode unencrypted PEM. * - *

    This class is immutable and thread-safe. + *

    This class is immutable and thread-safe. * - *

    Here is an example of decoding a {@code PrivateKey} object: + *

    Example: decode a private key: * {@snippet lang = java: * PEMDecoder pd = PEMDecoder.of(); * PrivateKey priKey = pd.decode(priKeyPEM, PrivateKey.class); * } * - *

    Here is an example of a {@code PEMDecoder} configured with decryption - * and a factory provider: + *

    Example: configure decryption and a factory provider: * {@snippet lang = java: * PEMDecoder pd = PEMDecoder.of().withDecryption(password). - * withFactory(provider); - * byte[] pemData = pd.decode(privKey); + * withFactory(provider); + * DEREncodable pemData = pd.decode(privKeyPEM); * } * - * @implNote An implementation may support other PEM types and - * {@code DEREncodable} objects. This implementation additionally supports - * the following PEM types: {@code X509 CERTIFICATE}, - * {@code X.509 CERTIFICATE}, {@code CRL}, and {@code RSA PRIVATE KEY}. + * @implNote This implementation decodes RSA PRIVATE KEY as {@code PrivateKey}, + * X509 CERTIFICATE and X.509 CERTIFICATE as {@code X509Certificate}, + * and CRL as {@code X509CRL}. Other implementations may recognize + * additional PEM types. * * @see PEMEncoder - * @see PEMRecord + * @see PEM * @see EncryptedPrivateKeyInfo * * @spec https://www.rfc-editor.org/info/rfc1421 * RFC 1421: Privacy Enhancement for Internet Electronic Mail + * @spec https://www.rfc-editor.org/info/rfc5958 + * RFC 5958: Asymmetric Key Packages * @spec https://www.rfc-editor.org/info/rfc7468 * RFC 7468: Textual Encodings of PKIX, PKCS, and CMS Structures * @@ -142,7 +153,7 @@ import java.util.Objects; @PreviewFeature(feature = PreviewFeature.Feature.PEM_API) public final class PEMDecoder { private final Provider factory; - private final PBEKeySpec password; + private final PBEKeySpec keySpec; // Singleton instance for PEMDecoder private final static PEMDecoder PEM_DECODER = new PEMDecoder(null, null); @@ -154,8 +165,12 @@ public final class PEMDecoder { * decryption */ private PEMDecoder(Provider withFactory, PBEKeySpec withPassword) { - password = withPassword; + keySpec = withPassword; factory = withFactory; + if (withPassword != null) { + final var k = this.keySpec; + CleanerFactory.cleaner().register(this, k::clearPassword); + } } /** @@ -172,7 +187,7 @@ public final class PEMDecoder { * header and footer and proceed with decoding the base64 for the * appropriate type. */ - private DEREncodable decode(PEMRecord pem) { + private DEREncodable decode(PEM pem) { Base64.Decoder decoder = Base64.getMimeDecoder(); try { @@ -185,41 +200,57 @@ public final class PEMDecoder { generatePublic(spec); } case Pem.PRIVATE_KEY -> { - PKCS8Key p8key = new PKCS8Key(decoder.decode(pem.content())); - String algo = p8key.getAlgorithm(); - KeyFactory kf = getKeyFactory(algo); - DEREncodable d = kf.generatePrivate( - new PKCS8EncodedKeySpec(p8key.getEncoded(), algo)); + DEREncodable d; + PKCS8Key p8key = null; + PKCS8EncodedKeySpec p8spec = null; + byte[] encoding = decoder.decode(pem.content()); - // Look for a public key inside the pkcs8 encoding. - if (p8key.getPubKeyEncoded() != null) { - // Check if this is a OneAsymmetricKey encoding - X509EncodedKeySpec spec = new X509EncodedKeySpec( - p8key.getPubKeyEncoded(), algo); - yield new KeyPair(getKeyFactory(algo). - generatePublic(spec), (PrivateKey) d); + try { + p8key = new PKCS8Key(encoding); + String algo = p8key.getAlgorithm(); + KeyFactory kf = getKeyFactory(algo); + p8spec = new PKCS8EncodedKeySpec(encoding, algo); + d = kf.generatePrivate(p8spec); - } else if (d instanceof PKCS8Key p8 && - p8.getPubKeyEncoded() != null) { - // If the KeyFactory decoded an algorithm-specific - // encodings, look for the public key again. This - // happens with EC and SEC1-v2 encoding - X509EncodedKeySpec spec = new X509EncodedKeySpec( - p8.getPubKeyEncoded(), algo); - yield new KeyPair(getKeyFactory(algo). - generatePublic(spec), p8); - } else { - // No public key, return the private key. - yield d; + // Look for a public key inside the pkcs8 encoding. + if (p8key.getPubKeyEncoded() != null) { + // Check if this is a OneAsymmetricKey encoding + X509EncodedKeySpec spec = new X509EncodedKeySpec( + p8key.getPubKeyEncoded(), algo); + yield new KeyPair(getKeyFactory(algo). + generatePublic(spec), (PrivateKey) d); + + } else if (d instanceof PKCS8Key p8 && + p8.getPubKeyEncoded() != null) { + // If the KeyFactory decoded an algorithm-specific + // encodings, look for the public key again. + X509EncodedKeySpec spec = new X509EncodedKeySpec( + p8.getPubKeyEncoded(), algo); + yield new KeyPair(getKeyFactory(algo). + generatePublic(spec), (PrivateKey) d); + } else { + // No public key, return the private key. + yield d; + } + } finally { + KeyUtil.clear(encoding, p8spec, p8key); } } case Pem.ENCRYPTED_PRIVATE_KEY -> { - if (password == null) { - yield new EncryptedPrivateKeyInfo(decoder.decode( - pem.content())); + byte[] p8 = null; + byte[] encoding = null; + try { + encoding = decoder.decode(pem.content()); + var ekpi = new EncryptedPrivateKeyInfo(encoding); + if (keySpec == null) { + yield ekpi; + } + p8 = Pem.decryptEncoding(ekpi, keySpec); + yield Pem.toDEREncodable(p8, true, factory); + } finally { + Reference.reachabilityFence(this); + KeyUtil.clear(encoding, p8); } - yield new EncryptedPrivateKeyInfo(decoder.decode(pem.content())). - getKey(password.getPassword()); } case Pem.CERTIFICATE, Pem.X509_CERTIFICATE, Pem.X_509_CERTIFICATE -> { @@ -246,28 +277,26 @@ public final class PEMDecoder { } /** - * Decodes and returns a {@link DEREncodable} from the given {@code String}. + * Decodes and returns a {@code DEREncodable} from the given {@code String}. * *

    This method reads the {@code String} until PEM data is found * or the end of the {@code String} is reached. If no PEM data is found, * an {@code IllegalArgumentException} is thrown. * - *

    This method returns a Java API cryptographic object, - * such as a {@code PrivateKey}, if the PEM type is supported. - * Any non-PEM data preceding the PEM header is ignored by the decoder. - * Otherwise, a {@link PEMRecord} will be returned containing - * the type identifier and Base64-encoded data. - * Any non-PEM data preceding the PEM header will be stored in - * {@code leadingData}. + *

    A {@code DEREncodable} will be returned that best represents the + * decoded data. If the PEM type is not supported, a {@code PEM} object is + * returned containing the type identifier, Base64-encoded data, and any + * leading data preceding the PEM header. For {@code DEREncodable} types + * other than {@code PEM}, leading data is ignored and not returned as part + * of the {@code DEREncodable} object. * *

    Input consumed by this method is read in as * {@link java.nio.charset.StandardCharsets#UTF_8 UTF-8}. * - * @param str a String containing PEM data + * @param str a {@code String} containing PEM data * @return a {@code DEREncodable} - * @throws IllegalArgumentException on error in decoding or no PEM data - * found - * @throws NullPointerException when {@code str} is null + * @throws IllegalArgumentException on error in decoding or no PEM data found + * @throws NullPointerException when {@code str} is {@code null} */ public DEREncodable decode(String str) { Objects.requireNonNull(str); @@ -281,67 +310,65 @@ public final class PEMDecoder { } /** - * Decodes and returns a {@link DEREncodable} from the given + * Decodes and returns a {@code DEREncodable} from the given * {@code InputStream}. * *

    This method reads from the {@code InputStream} until the end of - * the PEM footer or the end of the stream. If an I/O error occurs, + * a PEM footer or the end of the stream. If an I/O error occurs, * the read position in the stream may become inconsistent. * It is recommended to perform no further decoding operations * on the {@code InputStream}. * - *

    This method returns a Java API cryptographic object, - * such as a {@code PrivateKey}, if the PEM type is supported. - * Any non-PEM data preceding the PEM header is ignored by the decoder. - * Otherwise, a {@link PEMRecord} will be returned containing - * the type identifier and Base64-encoded data. - * Any non-PEM data preceding the PEM header will be stored in - * {@code leadingData}. + *

    A {@code DEREncodable} will be returned that best represents the + * decoded data. If the PEM type is not supported, a {@code PEM} object is + * returned containing the type identifier, Base64-encoded data, and any + * leading data preceding the PEM header. For {@code DEREncodable} types + * other than {@code PEM}, leading data is ignored and not returned as part + * of the {@code DEREncodable} object. * - *

    If no PEM data is found, an {@code IllegalArgumentException} is - * thrown. + *

    If no PEM data is found, an {@code EOFException} is thrown. * - * @param is InputStream containing PEM data + * @param is {@code InputStream} containing PEM data * @return a {@code DEREncodable} * @throws IOException on IO or PEM syntax error where the - * {@code InputStream} did not complete decoding. - * @throws EOFException at the end of the {@code InputStream} + * {@code InputStream} did not complete decoding + * @throws EOFException no PEM data found or unexpectedly reached the + * end of the {@code InputStream} * @throws IllegalArgumentException on error in decoding - * @throws NullPointerException when {@code is} is null + * @throws NullPointerException when {@code is} is {@code null} */ public DEREncodable decode(InputStream is) throws IOException { Objects.requireNonNull(is); - PEMRecord pem = Pem.readPEM(is); + PEM pem = Pem.readPEM(is); return decode(pem); } /** * Decodes and returns a {@code DEREncodable} of the specified class from - * the given PEM string. {@code tClass} must extend {@link DEREncodable} - * and be an appropriate class for the PEM type. + * the given PEM string. {@code tClass} must be an appropriate class for + * the PEM type. * *

    This method reads the {@code String} until PEM data is found * or the end of the {@code String} is reached. If no PEM data is found, * an {@code IllegalArgumentException} is thrown. * - *

    If the class parameter is {@code PEMRecord.class}, - * a {@linkplain PEMRecord} is returned containing the - * type identifier and Base64 encoding. Any non-PEM data preceding - * the PEM header will be stored in {@code leadingData}. Other - * class parameters will not return preceding non-PEM data. + *

    If the class parameter is {@code PEM.class}, a {@code PEM} object is + * returned containing the type identifier, Base64-encoded data, and any + * leading data preceding the PEM header. For {@code DEREncodable} types + * other than {@code PEM}, leading data is ignored and not returned as part + * of the {@code DEREncodable} object. * *

    Input consumed by this method is read in as * {@link java.nio.charset.StandardCharsets#UTF_8 UTF-8}. * - * @param Class type parameter that extends {@code DEREncodable} - * @param str the String containing PEM data - * @param tClass the returned object class that implements - * {@code DEREncodable} + * @param class type parameter that extends {@code DEREncodable} + * @param str the {@code String} containing PEM data + * @param tClass the returned object class that extends or implements + * {@code DEREncodable} * @return a {@code DEREncodable} specified by {@code tClass} - * @throws IllegalArgumentException on error in decoding or no PEM data - * found - * @throws ClassCastException if {@code tClass} is invalid for the PEM type - * @throws NullPointerException when any input values are null + * @throws IllegalArgumentException on error in decoding or no PEM data found + * @throws ClassCastException if {@code tClass} does not represent the PEM type + * @throws NullPointerException when any input values are {@code null} */ public S decode(String str, Class tClass) { Objects.requireNonNull(str); @@ -355,47 +382,47 @@ public final class PEMDecoder { } /** - * Decodes and returns the specified class for the given - * {@link InputStream}. The class must extend {@link DEREncodable} and be - * an appropriate class for the PEM type. + * Decodes and returns a {@code DEREncodable} of the specified class for the + * given {@code InputStream}. {@code tClass} must be an appropriate class + * for the PEM type. * *

    This method reads from the {@code InputStream} until the end of - * the PEM footer or the end of the stream. If an I/O error occurs, + * a PEM footer or the end of the stream. If an I/O error occurs, * the read position in the stream may become inconsistent. * It is recommended to perform no further decoding operations * on the {@code InputStream}. * - *

    If the class parameter is {@code PEMRecord.class}, - * a {@linkplain PEMRecord} is returned containing the - * type identifier and Base64 encoding. Any non-PEM data preceding - * the PEM header will be stored in {@code leadingData}. Other - * class parameters will not return preceding non-PEM data. + *

    If the class parameter is {@code PEM.class}, a {@code PEM} object is + * returned containing the type identifier, Base64-encoded data, and any + * leading data preceding the PEM header. For {@code DEREncodable} types + * other than {@code PEM}, leading data is ignored and not returned as part + * of the {@code DEREncodable} object. * - *

    If no PEM data is found, an {@code IllegalArgumentException} is - * thrown. + *

    If no PEM data is found, an {@code EOFException} is thrown. * - * @param Class type parameter that extends {@code DEREncodable}. - * @param is an InputStream containing PEM data - * @param tClass the returned object class that implements - * {@code DEREncodable}. + * @param class type parameter that extends {@code DEREncodable} + * @param is an {@code InputStream} containing PEM data + * @param tClass the returned object class that extends or implements + * {@code DEREncodable} * @return a {@code DEREncodable} typecast to {@code tClass} * @throws IOException on IO or PEM syntax error where the - * {@code InputStream} did not complete decoding. - * @throws EOFException at the end of the {@code InputStream} + * {@code InputStream} did not complete decoding + * @throws EOFException no PEM data found or unexpectedly reached the + * end of the {@code InputStream} * @throws IllegalArgumentException on error in decoding - * @throws ClassCastException if {@code tClass} is invalid for the PEM type - * @throws NullPointerException when any input values are null + * @throws ClassCastException if {@code tClass} does not represent the PEM type + * @throws NullPointerException when any input values are {@code null} * - * @see #decode(InputStream) + * @see #decode(InputStream) * @see #decode(String, Class) */ public S decode(InputStream is, Class tClass) throws IOException { Objects.requireNonNull(is); Objects.requireNonNull(tClass); - PEMRecord pem = Pem.readPEM(is); + PEM pem = Pem.readPEM(is); - if (tClass.isAssignableFrom(PEMRecord.class)) { + if (tClass.isAssignableFrom(PEM.class)) { return tClass.cast(pem); } DEREncodable so = decode(pem); @@ -478,28 +505,28 @@ public final class PEMDecoder { /** * Returns a copy of this {@code PEMDecoder} instance that uses - * {@link KeyFactory} and {@link CertificateFactory} implementations - * from the specified {@link Provider} to produce cryptographic objects. + * {@code KeyFactory} and {@code CertificateFactory} implementations + * from the specified {@code Provider} to produce cryptographic objects. * Any errors using the {@code Provider} will occur during decoding. * * @param provider the factory provider - * @return a new PEMEncoder instance configured to the {@code Provider}. - * @throws NullPointerException if {@code provider} is null + * @return a new {@code PEMDecoder} instance configured with the {@code Provider} + * @throws NullPointerException if {@code provider} is {@code null} */ public PEMDecoder withFactory(Provider provider) { Objects.requireNonNull(provider); - return new PEMDecoder(provider, password); + return new PEMDecoder(provider, keySpec); } /** * Returns a copy of this {@code PEMDecoder} that decodes and decrypts * encrypted private keys using the specified password. - * Non-encrypted PEM can still be decoded from this instance. + * Non-encrypted PEM can also be decoded from this instance. * - * @param password the password to decrypt encrypted PEM data. This array + * @param password the password to decrypt the encrypted PEM data. This array * is cloned and stored in the new instance. - * @return a new PEMEncoder instance configured for decryption - * @throws NullPointerException if {@code password} is null + * @return a new {@code PEMDecoder} instance configured for decryption + * @throws NullPointerException if {@code password} is {@code null} */ public PEMDecoder withDecryption(char[] password) { Objects.requireNonNull(password); diff --git a/src/java.base/share/classes/java/security/PEMEncoder.java b/src/java.base/share/classes/java/security/PEMEncoder.java index 95fcffe967b..62a0942caf7 100644 --- a/src/java.base/share/classes/java/security/PEMEncoder.java +++ b/src/java.base/share/classes/java/security/PEMEncoder.java @@ -27,10 +27,8 @@ package java.security; import jdk.internal.javac.PreviewFeature; import sun.security.pkcs.PKCS8Key; -import sun.security.util.DerOutputStream; -import sun.security.util.DerValue; +import sun.security.util.KeyUtil; import sun.security.util.Pem; -import sun.security.x509.AlgorithmId; import javax.crypto.*; import javax.crypto.spec.PBEKeySpec; @@ -41,83 +39,84 @@ import java.security.spec.AlgorithmParameterSpec; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.Objects; -import java.util.concurrent.locks.ReentrantLock; /** * {@code PEMEncoder} implements an encoder for Privacy-Enhanced Mail (PEM) - * data. PEM is a textual encoding used to store and transfer security + * data. PEM is a textual encoding used to store and transfer cryptographic * objects, such as asymmetric keys, certificates, and certificate revocation - * lists (CRL). It is defined in RFC 1421 and RFC 7468. PEM consists of a - * Base64-formatted binary encoding enclosed by a type-identifying header + * lists (CRLs). It is defined in RFC 1421 and RFC 7468. PEM consists of a + * Base64-encoded binary encoding enclosed by a type-identifying header * and footer. * - *

    Encoding may be performed on Java API cryptographic objects that + *

    Encoding can be performed on cryptographic objects that * implement {@link DEREncodable}. The {@link #encode(DEREncodable)} - * and {@link #encodeToString(DEREncodable)} methods encode a DEREncodable - * into PEM and return the data in a byte array or String. + * and {@link #encodeToString(DEREncodable)} methods encode a {@code DEREncodable} + * into PEM and return the data in a byte array or {@code String}. * *

    Private keys can be encrypted and encoded by configuring a - * {@code PEMEncoder} with the {@linkplain #withEncryption(char[])} method, + * {@code PEMEncoder} with the {@link #withEncryption(char[])} method, * which takes a password and returns a new {@code PEMEncoder} instance * configured to encrypt the key with that password. Alternatively, a - * private key encrypted as an {@code EncryptedKeyInfo} object can be encoded + * private key encrypted as an {@link EncryptedPrivateKeyInfo} object can be encoded * directly to PEM by passing it to the {@code encode} or * {@code encodeToString} methods. * - *

    PKCS #8 2.0 defines the ASN.1 OneAsymmetricKey structure, which may + *

    PKCS #8 v2.0 defines the ASN.1 OneAsymmetricKey structure, which may * contain both private and public keys. - * {@link KeyPair} objects passed to the {@code encode} or + * {@code KeyPair} objects passed to the {@code encode} or * {@code encodeToString} methods are encoded as a * OneAsymmetricKey structure using the "PRIVATE KEY" type. * - *

    When encoding a {@link PEMRecord}, the API surrounds the - * {@linkplain PEMRecord#content()} with the PEM header and footer - * from {@linkplain PEMRecord#type()}. {@linkplain PEMRecord#leadingData()} is - * not included in the encoding. {@code PEMRecord} will not perform - * validity checks on the data. - * - *

    The following lists the supported {@code DEREncodable} classes and - * the PEM types that each are encoded as: + *

    When encoding a {@link PEM} object, the API surrounds + * {@link PEM#content()} with a PEM header and footer based on + * {@link PEM#type()}. The value returned by {@link PEM#leadingData()} is not + * included in the output. * + *

    The following lists the supported {@code DEREncodable} classes and + * the PEM types they encode as: *

      - *
    • {@code X509Certificate} : CERTIFICATE
    • - *
    • {@code X509CRL} : X509 CRL
    • - *
    • {@code PublicKey}: PUBLIC KEY
    • - *
    • {@code PrivateKey} : PRIVATE KEY
    • - *
    • {@code PrivateKey} (if configured with encryption): - * ENCRYPTED PRIVATE KEY
    • - *
    • {@code EncryptedPrivateKeyInfo} : ENCRYPTED PRIVATE KEY
    • - *
    • {@code KeyPair} : PRIVATE KEY
    • - *
    • {@code X509EncodedKeySpec} : PUBLIC KEY
    • - *
    • {@code PKCS8EncodedKeySpec} : PRIVATE KEY
    • - *
    • {@code PEMRecord} : {@code PEMRecord.type()}
    • - *
    + *
  • {@link X509Certificate} : CERTIFICATE
  • + *
  • {@link X509CRL} : X509 CRL
  • + *
  • {@link PublicKey} : PUBLIC KEY
  • + *
  • {@link PrivateKey} : PRIVATE KEY
  • + *
  • {@link EncryptedPrivateKeyInfo} : ENCRYPTED PRIVATE KEY
  • + *
  • {@link KeyPair} : PRIVATE KEY
  • + *
  • {@link X509EncodedKeySpec} : PUBLIC KEY
  • + *
  • {@link PKCS8EncodedKeySpec} : PRIVATE KEY
  • + *
  • {@link PEM} : {@code PEM.type()}
  • + *
+ *

When used with a {@code PEMEncoder} instance configured for encryption: + *

    + *
  • {@link PrivateKey} : ENCRYPTED PRIVATE KEY
  • + *
  • {@link KeyPair} : ENCRYPTED PRIVATE KEY
  • + *
  • {@link PKCS8EncodedKeySpec} : ENCRYPTED PRIVATE KEY
  • + *
* *

This class is immutable and thread-safe. * - *

Here is an example of encoding a {@code PrivateKey} object: + *

Example: encode a private key: * {@snippet lang = java: * PEMEncoder pe = PEMEncoder.of(); * byte[] pemData = pe.encode(privKey); * } * - *

Here is an example that encrypts and encodes a private key using the - * specified password: + *

Example: encrypt and encode a private key using a password: * {@snippet lang = java: * PEMEncoder pe = PEMEncoder.of().withEncryption(password); * byte[] pemData = pe.encode(privKey); * } * - * @implNote An implementation may support other PEM types and - * {@code DEREncodable} objects. + * @implNote Implementations may support additional PEM types. * * * @see PEMDecoder - * @see PEMRecord + * @see PEM * @see EncryptedPrivateKeyInfo * * @spec https://www.rfc-editor.org/info/rfc1421 * RFC 1421: Privacy Enhancement for Internet Electronic Mail + * @spec https://www.rfc-editor.org/info/rfc5958 + * RFC 5958: Asymmetric Key Packages * @spec https://www.rfc-editor.org/info/rfc7468 * RFC 7468: Textual Encodings of PKIX, PKCS, and CMS Structures * @@ -128,24 +127,26 @@ public final class PEMEncoder { // Singleton instance of PEMEncoder private static final PEMEncoder PEM_ENCODER = new PEMEncoder(null); - - // Stores the password for an encrypted encoder that isn't setup yet. - private PBEKeySpec keySpec; - // Stores the key after the encoder is ready to encrypt. The prevents - // repeated SecretKeyFactory calls if the encoder is used on multiple keys. - private SecretKey key; - // Makes SecretKeyFactory generation thread-safe. - private final ReentrantLock lock; + // PBE key for encryption + private final Key key; /** - * Instantiate a {@code PEMEncoder} for Encrypted Private Keys. - * - * @param pbe contains the password spec used for encryption. + * Create an encrypted {@code PEMEncoder} instance. */ - private PEMEncoder(PBEKeySpec pbe) { - keySpec = pbe; - key = null; - lock = new ReentrantLock(); + private PEMEncoder(PBEKeySpec keySpec) { + if (keySpec != null) { + try { + key = SecretKeyFactory.getInstance(Pem.DEFAULT_ALGO). + generateSecret(keySpec); + } catch (GeneralSecurityException e) { + throw new IllegalArgumentException("Operation failed: " + + "unable to generate key or locate a valid algorithm. " + + "Check the jdk.epkcs8.defaultAlgorithm security " + + "property for a valid configuration.", e); + } + } else { + key = null; + } } /** @@ -158,70 +159,90 @@ public final class PEMEncoder { } /** - * Encodes the specified {@code DEREncodable} and returns a PEM encoded + * Encodes the specified {@code DEREncodable} and returns a PEM-encoded * string. * * @param de the {@code DEREncodable} to be encoded - * @return a {@code String} containing the PEM encoded data - * @throws IllegalArgumentException if the {@code DEREncodable} cannot be - * encoded + * @return a {@code String} containing the PEM-encoded data + * @throws IllegalArgumentException if the {@code DEREncodable} cannot be encoded * @throws NullPointerException if {@code de} is {@code null} * @see #withEncryption(char[]) */ public String encodeToString(DEREncodable de) { Objects.requireNonNull(de); return switch (de) { - case PublicKey pu -> buildKey(null, pu.getEncoded()); - case PrivateKey pr -> buildKey(pr.getEncoded(), null); - case KeyPair kp -> { - if (kp.getPublic() == null) { - throw new IllegalArgumentException("KeyPair does not " + - "contain PublicKey."); - } - if (kp.getPrivate() == null) { - throw new IllegalArgumentException("KeyPair does not " + - "contain PrivateKey."); - } - yield buildKey(kp.getPrivate().getEncoded(), - kp.getPublic().getEncoded()); - } - case X509EncodedKeySpec x -> - buildKey(null, x.getEncoded()); - case PKCS8EncodedKeySpec p -> - buildKey(p.getEncoded(), null); - case EncryptedPrivateKeyInfo epki -> { + case PublicKey pu -> buildKey(pu.getEncoded(), null); + case PrivateKey pr -> { + byte[] encoding = pr.getEncoded(); try { - yield Pem.pemEncoded(Pem.ENCRYPTED_PRIVATE_KEY, - epki.getEncoded()); + yield buildKey(null, encoding); + } finally { + KeyUtil.clear(encoding); + } + } + case KeyPair kp -> { + byte[] encoding = null; + try { + if (kp.getPublic() == null) { + throw new IllegalArgumentException("KeyPair does not " + + "contain PublicKey."); + } + if (kp.getPrivate() == null) { + throw new IllegalArgumentException("KeyPair does not " + + "contain PrivateKey."); + } + encoding = kp.getPrivate().getEncoded(); + if (encoding == null || encoding.length == 0) { + throw new IllegalArgumentException("PrivateKey is " + + "null or has no encoding."); + } + yield buildKey(kp.getPublic().getEncoded(), encoding); + } finally { + KeyUtil.clear(encoding); + } + } + case X509EncodedKeySpec x -> buildKey(x.getEncoded(), null); + case PKCS8EncodedKeySpec p -> buildKey(null, p.getEncoded()); + case EncryptedPrivateKeyInfo epki -> { + byte[] encoding = null; + if (key != null) { + throw new IllegalArgumentException( + "EncryptedPrivateKeyInfo cannot be encrypted"); + } + try { + encoding = epki.getEncoded(); + yield Pem.pemEncoded(Pem.ENCRYPTED_PRIVATE_KEY, encoding); } catch (IOException e) { throw new IllegalArgumentException(e); + } finally { + KeyUtil.clear(encoding); } } case X509Certificate c -> { + if (key != null) { + throw new IllegalArgumentException("Certificates " + + "cannot be encrypted"); + } try { - if (isEncrypted()) { - throw new IllegalArgumentException("Certificates " + - "cannot be encrypted"); - } yield Pem.pemEncoded(Pem.CERTIFICATE, c.getEncoded()); } catch (CertificateEncodingException e) { throw new IllegalArgumentException(e); } } case X509CRL crl -> { + if (key != null) { + throw new IllegalArgumentException("CRLs cannot be " + + "encrypted"); + } try { - if (isEncrypted()) { - throw new IllegalArgumentException("CRLs cannot be " + - "encrypted"); - } yield Pem.pemEncoded(Pem.X509_CRL, crl.getEncoded()); } catch (CRLException e) { throw new IllegalArgumentException(e); } } - case PEMRecord rec -> { - if (isEncrypted()) { - throw new IllegalArgumentException("PEMRecord cannot be " + + case PEM rec -> { + if (key != null) { + throw new IllegalArgumentException("PEM cannot be " + "encrypted"); } yield Pem.pemEncoded(rec); @@ -233,13 +254,12 @@ public final class PEMEncoder { } /** - * Encodes the specified {@code DEREncodable} and returns the PEM encoding - * in a byte array. + * Encodes the specified {@code DEREncodable} and returns a PEM-encoded + * byte array. * * @param de the {@code DEREncodable} to be encoded - * @return a PEM encoded byte array - * @throws IllegalArgumentException if the {@code DEREncodable} cannot be - * encoded + * @return a PEM-encoded byte array + * @throws IllegalArgumentException if the {@code DEREncodable} cannot be encoded * @throws NullPointerException if {@code de} is {@code null} * @see #withEncryption(char[]) */ @@ -248,136 +268,95 @@ public final class PEMEncoder { } /** - * Returns a new {@code PEMEncoder} instance configured for encryption - * with the default algorithm and a given password. + * Returns a copy of this PEMEncoder that encrypts and encodes + * using the specified password and default encryption algorithm. * - *

Only {@link PrivateKey} objects can be encrypted with this newly - * configured instance. Encoding other {@link DEREncodable} objects will + *

Only {@code PrivateKey}, {@code KeyPair}, and + * {@code PKCS8EncodedKeySpec} objects can be encoded with this newly + * configured instance. Encoding other {@code DEREncodable} objects will * throw an {@code IllegalArgumentException}. * - * @implNote - * The default password-based encryption algorithm is defined - * by the {@code jdk.epkcs8.defaultAlgorithm} security property and - * uses the default encryption parameters of the provider that is selected. - * For greater flexibility with encryption options and parameters, use - * {@link EncryptedPrivateKeyInfo#encryptKey(PrivateKey, Key, + * @implNote The {@code jdk.epkcs8.defaultAlgorithm} security property + * defines the default encryption algorithm. The {@code AlgorithmParameterSpec} + * defaults are determined by the provider. To use non-default encryption + * parameters, or to encrypt with a different encryption provider, use + * {@link EncryptedPrivateKeyInfo#encrypt(DEREncodable, Key, * String, AlgorithmParameterSpec, Provider, SecureRandom)} and use the * returned object with {@link #encode(DEREncodable)}. * * @param password the encryption password. The array is cloned and - * stored in the new instance. + * stored in the new instance. * @return a new {@code PEMEncoder} instance configured for encryption - * @throws NullPointerException when password is {@code null} + * @throws NullPointerException if password is {@code null} + * @throws IllegalArgumentException if generating the encryption key fails */ public PEMEncoder withEncryption(char[] password) { - // PBEKeySpec clones the password Objects.requireNonNull(password, "password cannot be null."); - return new PEMEncoder(new PBEKeySpec(password)); + PBEKeySpec keySpec = new PBEKeySpec(password); + try { + return new PEMEncoder(keySpec); + } finally { + keySpec.clearPassword(); + } } /** * Build PEM encoding. + * + * privateKeyEncoding will be zeroed when the method returns */ - private String buildKey(byte[] privateBytes, byte[] publicBytes) { - DerOutputStream out = new DerOutputStream(); - Cipher cipher; - - if (privateBytes == null && publicBytes == null) { + private String buildKey(byte[] publicEncoding, byte[] privateEncoding) { + if (publicEncoding == null && privateEncoding == null) { throw new IllegalArgumentException("No encoded data given by the " + "DEREncodable."); } - // If `keySpec` is non-null, then `key` hasn't been established. - // Setting a `key` prevents repeated key generation operations. - // withEncryption() is a configuration method and cannot throw an - // exception; therefore generation is delayed. - if (keySpec != null) { - // For thread safety - lock.lock(); - if (key == null) { - try { - key = SecretKeyFactory.getInstance(Pem.DEFAULT_ALGO). - generateSecret(keySpec); - keySpec.clearPassword(); - keySpec = null; - } catch (GeneralSecurityException e) { - throw new IllegalArgumentException("Security property " + - "\"jdk.epkcs8.defaultAlgorithm\" may not specify a " + - "valid algorithm. Operation cannot be performed.", e); - } finally { - lock.unlock(); - } - } else { - lock.unlock(); - } + if (publicEncoding != null && publicEncoding.length == 0) { + throw new IllegalArgumentException("Public key has no " + + "encoding"); } - // If `key` is non-null, this is an encoder ready to encrypt. - if (key != null) { - if (privateBytes == null || publicBytes != null) { - throw new IllegalArgumentException("Can only encrypt a " + - "PrivateKey."); - } + if (privateEncoding != null && privateEncoding.length == 0) { + throw new IllegalArgumentException("Private key has no " + + "encoding"); + } - try { - cipher = Cipher.getInstance(Pem.DEFAULT_ALGO); - cipher.init(Cipher.ENCRYPT_MODE, key); - } catch (GeneralSecurityException e) { - throw new IllegalArgumentException("Security property " + - "\"jdk.epkcs8.defaultAlgorithm\" may not specify a " + - "valid algorithm. Operation cannot be performed.", e); - } - - try { - new AlgorithmId(Pem.getPBEID(Pem.DEFAULT_ALGO), - cipher.getParameters()).encode(out); - out.putOctetString(cipher.doFinal(privateBytes)); - return Pem.pemEncoded(Pem.ENCRYPTED_PRIVATE_KEY, - DerValue.wrap(DerValue.tag_Sequence, out).toByteArray()); - } catch (GeneralSecurityException e) { - throw new IllegalArgumentException(e); - } + if (key != null && privateEncoding == null) { + throw new IllegalArgumentException("This DEREncodable cannot " + + "be encrypted."); } // X509 only - if (publicBytes != null && privateBytes == null) { - if (publicBytes.length == 0) { - throw new IllegalArgumentException("No public key encoding " + - "given by the DEREncodable."); - } - - return Pem.pemEncoded(Pem.PUBLIC_KEY, publicBytes); + if (publicEncoding != null && privateEncoding == null) { + return Pem.pemEncoded(Pem.PUBLIC_KEY, publicEncoding); } - // PKCS8 only - if (publicBytes == null && privateBytes != null) { - if (privateBytes.length == 0) { + byte[] encoding = null; + PKCS8EncodedKeySpec p8KeySpec = null; + try { + if (publicEncoding == null) { + encoding = privateEncoding; + } else { + encoding = PKCS8Key.getEncoded(publicEncoding, + privateEncoding); + } + if (key != null) { + p8KeySpec = new PKCS8EncodedKeySpec(encoding); + encoding = EncryptedPrivateKeyInfo.encrypt(p8KeySpec, key, + Pem.DEFAULT_ALGO, null, null, null). + getEncoded(); + } + if (encoding.length == 0) { throw new IllegalArgumentException("No private key encoding " + "given by the DEREncodable."); } - - return Pem.pemEncoded(Pem.PRIVATE_KEY, privateBytes); - } - - // OneAsymmetricKey - if (privateBytes.length == 0) { - throw new IllegalArgumentException("No private key encoding " + - "given by the DEREncodable."); - } - - if (publicBytes.length == 0) { - throw new IllegalArgumentException("No public key encoding " + - "given by the DEREncodable."); - } - try { - return Pem.pemEncoded(Pem.PRIVATE_KEY, - PKCS8Key.getEncoded(publicBytes, privateBytes)); + return Pem.pemEncoded( + (key == null ? Pem.PRIVATE_KEY : Pem.ENCRYPTED_PRIVATE_KEY), + encoding); } catch (IOException e) { - throw new IllegalArgumentException(e); + throw new IllegalArgumentException("Error while encoding", e); + } finally { + KeyUtil.clear(encoding, p8KeySpec); } } - - private boolean isEncrypted() { - return (key != null || keySpec != null); - } } diff --git a/src/java.base/share/classes/java/security/PEMRecord.java b/src/java.base/share/classes/java/security/PEMRecord.java deleted file mode 100644 index 2ce567f9fde..00000000000 --- a/src/java.base/share/classes/java/security/PEMRecord.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (c) 2025, 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. 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 java.security; - -import jdk.internal.javac.PreviewFeature; - -import sun.security.util.Pem; - -import java.util.Objects; - -/** - * {@code PEMRecord} is a {@link DEREncodable} that represents Privacy-Enhanced - * Mail (PEM) data by its type and Base64 form. {@link PEMDecoder} and - * {@link PEMEncoder} use {@code PEMRecord} when representing the data as a - * cryptographic object is not desired or the type has no - * {@code DEREncodable}. - * - *

{@code type} and {@code content} may not be {@code null}. - * {@code leadingData} may be null if no non-PEM data preceded PEM header - * during decoding. {@code leadingData} may be useful for reading metadata - * that accompanies PEM data. - * - *

No validation is performed during instantiation to ensure that - * {@code type} conforms to {@code RFC 7468}, that {@code content} is valid - * Base64, or that {@code content} matches the {@code type}. - * {@code leadingData} is not defensively copied and does not return a - * clone when {@linkplain #leadingData()} is called. - * - * @param type the type identifier in the PEM header without PEM syntax labels. - * For a public key, {@code type} would be "PUBLIC KEY". - * @param content the Base64-encoded data, excluding the PEM header and footer - * @param leadingData any non-PEM data preceding the PEM header when decoding. - * - * @spec https://www.rfc-editor.org/info/rfc7468 - * RFC 7468: Textual Encodings of PKIX, PKCS, and CMS Structures - * - * @see PEMDecoder - * @see PEMEncoder - * - * @since 25 - */ -@PreviewFeature(feature = PreviewFeature.Feature.PEM_API) -public record PEMRecord(String type, String content, byte[] leadingData) - implements DEREncodable { - - /** - * Creates a {@code PEMRecord} instance with the given parameters. - * - * @param type the type identifier - * @param content the Base64-encoded data, excluding the PEM header and - * footer - * @param leadingData any non-PEM data read during the decoding process - * before the PEM header. This value maybe {@code null}. - * @throws IllegalArgumentException if {@code type} is incorrectly - * formatted. - * @throws NullPointerException if {@code type} and/or {@code content} are - * {@code null}. - */ - public PEMRecord { - Objects.requireNonNull(type, "\"type\" cannot be null."); - Objects.requireNonNull(content, "\"content\" cannot be null."); - - // With no validity checking on `type`, the constructor accept anything - // including lowercase. The onus is on the caller. - if (type.startsWith("-") || type.startsWith("BEGIN ") || - type.startsWith("END ")) { - throw new IllegalArgumentException("PEM syntax labels found. " + - "Only the PEM type identifier is allowed"); - } - - } - - /** - * Creates a {@code PEMRecord} instance with a given {@code type} and - * {@code content} data in String form. {@code leadingData} is set to null. - * - * @param type the PEM type identifier - * @param content the Base64-encoded data, excluding the PEM header and - * footer - * @throws IllegalArgumentException if {@code type} is incorrectly - * formatted. - * @throws NullPointerException if {@code type} and/or {@code content} are - * {@code null}. - */ - public PEMRecord(String type, String content) { - this(type, content, null); - } - - /** - * Returns the type and Base64 encoding in PEM format. {@code leadingData} - * is not returned by this method. - */ - @Override - public String toString() { - return Pem.pemEncoded(this); - } -} diff --git a/src/java.base/share/classes/javax/crypto/EncryptedPrivateKeyInfo.java b/src/java.base/share/classes/javax/crypto/EncryptedPrivateKeyInfo.java index 90316d7437e..15933183e5f 100644 --- a/src/java.base/share/classes/javax/crypto/EncryptedPrivateKeyInfo.java +++ b/src/java.base/share/classes/javax/crypto/EncryptedPrivateKeyInfo.java @@ -276,8 +276,7 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable { * @param cipher the initialized {@code Cipher} object which will be * used for decrypting the encrypted data. * @return the PKCS8EncodedKeySpec object. - * @exception NullPointerException if {@code cipher} - * is {@code null}. + * @exception NullPointerException if {@code cipher} is {@code null}. * @exception InvalidKeySpecException if the given cipher is * inappropriate for the encrypted data or the encrypted * data is corrupted and cannot be decrypted. @@ -296,10 +295,9 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable { } } - private PKCS8EncodedKeySpec getKeySpecImpl(Key decryptKey, - Provider provider) throws NoSuchAlgorithmException, - InvalidKeyException { - byte[] encoded; + // Return the decrypted encryptedData in this instance. + private byte[] decryptData(Key decryptKey, Provider provider) + throws NoSuchAlgorithmException, InvalidKeyException { Cipher c; try { if (provider == null) { @@ -308,162 +306,157 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable { } else { c = Cipher.getInstance(getAlgName(), provider); } + } catch (NoSuchPaddingException e) { + throw new NoSuchAlgorithmException(e); + } + try { c.init(Cipher.DECRYPT_MODE, decryptKey, getAlgParameters()); - encoded = c.doFinal(encryptedData); - return pkcs8EncodingToSpec(encoded); + return c.doFinal(encryptedData); + } catch (GeneralSecurityException e) { + throw new InvalidKeyException(e); + } + } + + // Wrap the decrypted encryptedData in a P8EKS for getKeySpec methods. + private PKCS8EncodedKeySpec getKeySpecImpl(Key decryptKey, + Provider provider) throws NoSuchAlgorithmException, + InvalidKeyException { + byte[] encoding = null; + try { + encoding = decryptData(decryptKey, provider); + return pkcs8EncodingToSpec(encoding); } catch (NoSuchAlgorithmException nsae) { // rethrow throw nsae; } catch (GeneralSecurityException | IOException ex) { throw new InvalidKeyException( - "Cannot retrieve the PKCS8EncodedKeySpec", ex); + "Cannot retrieve the PKCS8EncodedKeySpec", ex); + } finally { + KeyUtil.clear(encoding); } } /** - * Creates and encrypts an {@code EncryptedPrivateKeyInfo} from a given - * {@code PrivateKey}. A valid password-based encryption (PBE) algorithm + * Creates an {@code EncryptedPrivateKeyInfo} by encrypting the specified + * {@code DEREncodable}. A valid password-based encryption (PBE) algorithm * and password must be specified. * - *

The PBE algorithm string format details can be found in the + *

The format of the PBE algorithm string is described in the * - * Cipher section of the Java Security Standard Algorithm Names + * Cipher Algorithms section of the Java Security Standard Algorithm Names * Specification. * - * @param key the {@code PrivateKey} to be encrypted - * @param password the password used in the PBE encryption. This array - * will be cloned before being used. - * @param algorithm the PBE encryption algorithm. The default algorithm - * will be used if {@code null}. However, {@code null} is - * not allowed when {@code params} is non-null. - * @param params the {@code AlgorithmParameterSpec} to be used with - * encryption. The provider default will be used if - * {@code null}. - * @param provider the {@code Provider} will be used for PBE - * {@link SecretKeyFactory} generation and {@link Cipher} - * encryption operations. The default provider list will be - * used if {@code null}. + * @param de the {@code DEREncodable} to encrypt. Supported types include + * {@code PrivateKey}, {@code KeyPair}, and {@code PKCS8EncodedKeySpec}. + * @param password the password used for PBE encryption. This array is cloned + * before use. + * @param algorithm the PBE encryption algorithm + * @param params the {@code AlgorithmParameterSpec} used for encryption. If + * {@code null}, the provider’s default parameters are applied. + * @param provider the {@code Provider} for {@code SecretKeyFactory} and + * {@code Cipher} operations. If {@code null}, provider + * defaults are used. * @return an {@code EncryptedPrivateKeyInfo} - * @throws IllegalArgumentException on initialization errors based on the - * arguments passed to the method - * @throws RuntimeException on an encryption error - * @throws NullPointerException if the key or password are {@code null}. If - * {@code params} is non-null when {@code algorithm} is {@code null}. - * - * @implNote The {@code jdk.epkcs8.defaultAlgorithm} Security Property - * defines the default encryption algorithm and the - * {@code AlgorithmParameterSpec} are the provider's algorithm defaults. + * @throws NullPointerException if {@code de}, {@code password}, or + * {@code algorithm} is {@code null} + * @throws IllegalArgumentException if {@code de} is an unsupported + * {@code DEREncodable}, if an error occurs while generating the + * PBE key, if {@code algorithm} or {@code params} are + * not supported by any provider, or if an error occurs during + * encryption. * * @since 25 */ @PreviewFeature(feature = PreviewFeature.Feature.PEM_API) - public static EncryptedPrivateKeyInfo encryptKey(PrivateKey key, + public static EncryptedPrivateKeyInfo encrypt(DEREncodable de, char[] password, String algorithm, AlgorithmParameterSpec params, Provider provider) { - SecretKey skey; - Objects.requireNonNull(key, "key cannot be null"); - Objects.requireNonNull(password, "password cannot be null."); - PBEKeySpec keySpec = new PBEKeySpec(password); - if (algorithm == null) { - if (params != null) { - throw new NullPointerException("algorithm must be specified" + - " if params is non-null."); - } - algorithm = Pem.DEFAULT_ALGO; - } - + Objects.requireNonNull(de, "a key must be specified."); + Objects.requireNonNull(password, "a password must be specified."); + Objects.requireNonNull(algorithm, "an algorithm must be specified."); + char[] passwd = password.clone(); + byte[] encoding = getEncoding(de); try { - SecretKeyFactory factory; - if (provider == null) { - factory = SecretKeyFactory.getInstance(algorithm); - } else { - factory = SecretKeyFactory.getInstance(algorithm, provider); - } - skey = factory.generateSecret(keySpec); - } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { - throw new IllegalArgumentException(e); + return encryptImpl(encoding, algorithm, + generateSecretKey(passwd, algorithm, provider), params, + provider, null); + } finally { + KeyUtil.clear(passwd, encoding); } - return encryptKeyImpl(key, algorithm, skey, params, provider, null); } - /** - * Creates and encrypts an {@code EncryptedPrivateKeyInfo} from a given - * {@code PrivateKey} and password. Default algorithm and parameters are - * used. + * Creates an {@code EncryptedPrivateKeyInfo} by encrypting the specified + * {@code DEREncodable}. A valid password must be specified. A default + * password-based encryption (PBE) algorithm and provider are used. * - * @param key the {@code PrivateKey} to be encrypted - * @param password the password used in the PBE encryption. This array - * will be cloned before being used. + * @param de the {@code DEREncodable} to encrypt. Supported types include + * {@code PrivateKey}, {@code KeyPair}, and {@code PKCS8EncodedKeySpec}. + * @param password the password used for PBE encryption. This array is cloned + * before use. * @return an {@code EncryptedPrivateKeyInfo} - * @throws IllegalArgumentException on initialization errors based on the - * arguments passed to the method - * @throws RuntimeException on an encryption error - * @throws NullPointerException when the {@code key} or {@code password} - * is {@code null} + * @throws NullPointerException if {@code de} or {@code password} is {@code null} + * @throws IllegalArgumentException if {@code de} is an unsupported + * {@code DEREncodable}, if an error occurs while generating the + * PBE key, or if the default algorithm is misconfigured * - * @implNote The {@code jdk.epkcs8.defaultAlgorithm} Security Property - * defines the default encryption algorithm and the - * {@code AlgorithmParameterSpec} are the provider's algorithm defaults. + * @implNote The {@code jdk.epkcs8.defaultAlgorithm} security property + * defines the default encryption algorithm. The {@code AlgorithmParameterSpec} + * defaults are determined by the provider. * * @since 25 */ @PreviewFeature(feature = PreviewFeature.Feature.PEM_API) - public static EncryptedPrivateKeyInfo encryptKey(PrivateKey key, + public static EncryptedPrivateKeyInfo encrypt(DEREncodable de, char[] password) { - return encryptKey(key, password, Pem.DEFAULT_ALGO, null, null); + return encrypt(de, password, Pem.DEFAULT_ALGO, null, + null); } /** - * Creates and encrypts an {@code EncryptedPrivateKeyInfo} from the given - * {@link PrivateKey} using the {@code encKey} and given parameters. + * Creates an {@code EncryptedPrivateKeyInfo} by encrypting the specified + * {@code DEREncodable}. A valid encryption algorithm and {@code Key} must + * be specified. * - * @param key the {@code PrivateKey} to be encrypted - * @param encKey the password-based encryption (PBE) {@code Key} used to - * encrypt {@code key}. - * @param algorithm the PBE encryption algorithm. The default algorithm is - * will be used if {@code null}; however, {@code null} is - * not allowed when {@code params} is non-null. - * @param params the {@code AlgorithmParameterSpec} to be used with - * encryption. The provider list default will be used if - * {@code null}. - * @param random the {@code SecureRandom} instance used during - * encryption. The default will be used if {@code null}. - * @param provider the {@code Provider} is used for {@link Cipher} - * encryption operation. The default provider list will be - * used if {@code null}. + *

The format of the algorithm string is described in the + * + * Cipher Algorithms section of the Java Security Standard Algorithm Names + * Specification. + * + * @param de the {@code DEREncodable} to encrypt. Supported types include + * {@code PrivateKey}, {@code KeyPair}, and {@code PKCS8EncodedKeySpec}. + * @param encryptKey the key used to encrypt the encoding + * @param algorithm the encryption algorithm, such as a password-based + * encryption (PBE) algorithm + * @param params the {@code AlgorithmParameterSpec} used for encryption. If + * {@code null}, the provider’s default parameters are applied. + * @param random the {@code SecureRandom} instance used during encryption. + * If {@code null}, the default is used. + * @param provider the {@code Provider} for {@code Cipher} operations. + * If {@code null}, the default provider list is used. * @return an {@code EncryptedPrivateKeyInfo} - * @throws IllegalArgumentException on initialization errors based on the - * arguments passed to the method - * @throws RuntimeException on an encryption error - * @throws NullPointerException if the {@code key} or {@code encKey} are - * {@code null}. If {@code params} is non-null, {@code algorithm} cannot be - * {@code null}. - * - * @implNote The {@code jdk.epkcs8.defaultAlgorithm} Security Property - * defines the default encryption algorithm and the - * {@code AlgorithmParameterSpec} are the provider's algorithm defaults. + * @throws NullPointerException if {@code de}, {@code encryptKey}, or + * {@code algorithm} is {@code null} + * @throws IllegalArgumentException if {@code de} is an unsupported + * {@code DEREncodable}, if {@code encryptKey} is invalid, if + * {@code algorithm} or {@code params} are not supported by any + * provider, or if an error occurs during encryption * * @since 25 */ @PreviewFeature(feature = PreviewFeature.Feature.PEM_API) - public static EncryptedPrivateKeyInfo encryptKey(PrivateKey key, Key encKey, - String algorithm, AlgorithmParameterSpec params, Provider provider, - SecureRandom random) { + public static EncryptedPrivateKeyInfo encrypt(DEREncodable de, + Key encryptKey, String algorithm, AlgorithmParameterSpec params, + Provider provider, SecureRandom random) { - Objects.requireNonNull(key); - Objects.requireNonNull(encKey); - if (algorithm == null) { - if (params != null) { - throw new NullPointerException("algorithm must be specified " + - "if params is non-null."); - } - algorithm = Pem.DEFAULT_ALGO; - } - return encryptKeyImpl(key, algorithm, encKey, params, provider, random); + Objects.requireNonNull(de, "a key must be specified."); + Objects.requireNonNull(encryptKey, "an encryption key must be specified."); + Objects.requireNonNull(algorithm, "an algorithm must be specified."); + return encryptImpl(getEncoding(de), algorithm, encryptKey, + params, provider, random); } - private static EncryptedPrivateKeyInfo encryptKeyImpl(PrivateKey key, + private static EncryptedPrivateKeyInfo encryptImpl(byte[] encoded, String algorithm, Key encryptKey, AlgorithmParameterSpec params, Provider provider, SecureRandom random) { AlgorithmId algId; @@ -481,17 +474,26 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable { c = Cipher.getInstance(algorithm, provider); } c.init(Cipher.ENCRYPT_MODE, encryptKey, params, random); - encryptedData = c.doFinal(key.getEncoded()); - algId = new AlgorithmId(Pem.getPBEID(algorithm), c.getParameters()); + encryptedData = c.doFinal(encoded); + try { + // Use shared PEM method for very likely case the algorithm is PBE. + algId = new AlgorithmId(Pem.getPBEID(algorithm), c.getParameters()); + } catch (IllegalArgumentException e) { + // For the unlikely case a non-PBE cipher is used, get the OID. + algId = new AlgorithmId(AlgorithmId.get(algorithm).getOID(), + c.getParameters()); + } out = new DerOutputStream(); algId.encode(out); out.putOctetString(encryptedData); } catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException | - NoSuchPaddingException e) { + IllegalStateException | NoSuchPaddingException | + IllegalBlockSizeException | InvalidKeyException e) { throw new IllegalArgumentException(e); - } catch (IllegalBlockSizeException | BadPaddingException | - InvalidKeyException e) { - throw new RuntimeException(e); + } catch (BadPaddingException e) { + throw new AssertionError(e); + } finally { + KeyUtil.clear(encoded); } return new EncryptedPrivateKeyInfo( DerValue.wrap(DerValue.tag_Sequence, out).toByteArray(), @@ -499,63 +501,129 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable { } /** - * Extract the enclosed {@code PrivateKey} object from the encrypted data - * and return it. + * Extracts and returns the enclosed {@code PrivateKey} using the + * specified password. * - * @param password the password used in the PBE encryption. This array - * will be cloned before being used. - * @return a {@code PrivateKey} - * @throws GeneralSecurityException if an error occurs parsing or - * decrypting the encrypted data, or producing the key object. - * @throws NullPointerException if {@code password} is null + * @param password the password used for PBE decryption. The array is cloned + * before use. + * @return the decrypted {@code PrivateKey} + * @throws NullPointerException if {@code password} is {@code null} + * @throws NoSuchAlgorithmException if the decryption algorithm is unsupported + * @throws InvalidKeyException if an error occurs during parsing, + * decryption, or key generation * * @since 25 */ @PreviewFeature(feature = PreviewFeature.Feature.PEM_API) - public PrivateKey getKey(char[] password) throws GeneralSecurityException { - SecretKeyFactory skf; - PKCS8EncodedKeySpec p8KeySpec; - Objects.requireNonNull(password, "password cannot be null"); + public PrivateKey getKey(char[] password) + throws NoSuchAlgorithmException, InvalidKeyException { + Objects.requireNonNull(password, "a password must be specified."); PBEKeySpec keySpec = new PBEKeySpec(password); - skf = SecretKeyFactory.getInstance(getAlgName()); - p8KeySpec = getKeySpec(skf.generateSecret(keySpec)); - - return PKCS8Key.parseKey(p8KeySpec.getEncoded()); + try { + return PKCS8Key.parseKey(Pem.decryptEncoding(this, keySpec), null); + } finally { + keySpec.clearPassword(); + } } /** - * Extract the enclosed {@code PrivateKey} object from the encrypted data - * and return it. + * Extracts and returns the enclosed {@code PrivateKey} using the specified + * decryption key and provider. * - * @param decryptKey the decryption key and cannot be {@code null} - * @param provider the {@code Provider} used for Cipher decryption and - * {@code PrivateKey} generation. A {@code null} value will - * use the default provider configuration. - * @return a {@code PrivateKey} - * @throws GeneralSecurityException if an error occurs parsing or - * decrypting the encrypted data, or producing the key object. - * @throws NullPointerException if {@code decryptKey} is null + * @param decryptKey the decryption key. Must not be {@code null}. + * @param provider the {@code Provider} for {@code Cipher} decryption + * and {@code PrivateKey} generation. If {@code null}, the + * default provider configuration is used. + * @return the decrypted {@code PrivateKey} + * @throws NullPointerException if {@code decryptKey} is {@code null} + * @throws NoSuchAlgorithmException if the decryption algorithm is unsupported + * @throws InvalidKeyException if an error occurs during parsing, + * decryption, or key generation * * @since 25 */ @PreviewFeature(feature = PreviewFeature.Feature.PEM_API) public PrivateKey getKey(Key decryptKey, Provider provider) - throws GeneralSecurityException { - Objects.requireNonNull(decryptKey,"decryptKey cannot be null."); - PKCS8EncodedKeySpec p = getKeySpecImpl(decryptKey, provider); + throws NoSuchAlgorithmException, InvalidKeyException { + Objects.requireNonNull(decryptKey,"a decryptKey must be specified."); + byte[] encoding = null; try { - if (provider == null) { - return KeyFactory.getInstance( - KeyUtil.getAlgorithm(p.getEncoded())). - generatePrivate(p); - } - return KeyFactory.getInstance(KeyUtil.getAlgorithm(p.getEncoded()), - provider).generatePrivate(p); - } catch (IOException e) { - throw new GeneralSecurityException(e); + encoding = decryptData(decryptKey, provider); + return PKCS8Key.parseKey(encoding, provider); + } finally { + KeyUtil.clear(encoding); } } + /** + * Extracts and returns the enclosed {@code KeyPair} using the specified + * password. If the encoded data does not contain both a public and private + * key, an {@code InvalidKeyException} is thrown. + * + * @param password the password used for PBE decryption. The array is cloned + * before use. + * @return a decrypted {@code KeyPair} + * @throws NullPointerException if {@code password} is {@code null} + * @throws NoSuchAlgorithmException if the decryption algorithm is unsupported + * @throws InvalidKeyException if the encoded data lacks a public key, or if + * an error occurs during parsing, decryption, or key generation + * + * @since 26 + */ + @PreviewFeature(feature = PreviewFeature.Feature.PEM_API) + public KeyPair getKeyPair(char[] password) + throws NoSuchAlgorithmException, InvalidKeyException { + Objects.requireNonNull(password, "a password must be specified."); + + PBEKeySpec keySpec = new PBEKeySpec(password); + DEREncodable d; + try { + d = Pem.toDEREncodable(Pem.decryptEncoding(this, keySpec), true, null); + } finally { + keySpec.clearPassword(); + } + return switch (d) { + case KeyPair kp -> kp; + case PrivateKey ignored -> throw new InvalidKeyException( + "This encoding does not contain a public key."); + default -> throw new InvalidKeyException( + "Invalid class returned " + d.getClass().getName()); + }; + } + + /** + * Extracts and returns the enclosed {@code KeyPair} using the specified + * decryption key and provider. If the encoded data does not contain both a + * public and private key, an {@code InvalidKeyException} is thrown. + * + * @param decryptKey the decryption key. Must not be {@code null}. + * @param provider the {@code Provider} for {@code Cipher} decryption + * and key generation. If {@code null}, the default provider + * configuration is used. + * @return a decrypted {@code KeyPair} + * @throws NullPointerException if {@code decryptKey} is {@code null} + * @throws NoSuchAlgorithmException if the decryption algorithm is unsupported + * @throws InvalidKeyException if the encoded data lacks a public key, or if + * an error occurs during parsing, decryption, or key generation + * + * @since 26 + */ + @PreviewFeature(feature = PreviewFeature.Feature.PEM_API) + public KeyPair getKeyPair(Key decryptKey, Provider provider) + throws NoSuchAlgorithmException, InvalidKeyException { + Objects.requireNonNull(decryptKey,"a decryptKey must be specified."); + + DEREncodable d = Pem.toDEREncodable( + decryptData(decryptKey, provider),true, provider); + return switch (d) { + case KeyPair kp -> kp; + case PrivateKey ignored -> throw new InvalidKeyException( + "This encoding does not contain a public key."); + default -> throw new InvalidKeyException( + "Invalid class returned " + d.getClass().getName()); + }; + } + /** * Extract the enclosed PKCS8EncodedKeySpec object from the * encrypted data and return it. @@ -585,7 +653,7 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable { * @param decryptKey key used for decrypting the encrypted data. * @param providerName the name of provider whose cipher * implementation will be used. - * @return the PKCS8EncodedKeySpec object. + * @return the PKCS8EncodedKeySpec object * @exception NullPointerException if {@code decryptKey} * or {@code providerName} is {@code null}. * @exception NoSuchProviderException if no provider @@ -670,17 +738,48 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable { return this.encoded.clone(); } - private static void checkTag(DerValue val, byte tag, String valName) - throws IOException { - if (val.getTag() != tag) { - throw new IOException("invalid key encoding - wrong tag for " + - valName); - } - } - + // Read the encodedKey and return a P8EKS with the algorithm specified private static PKCS8EncodedKeySpec pkcs8EncodingToSpec(byte[] encodedKey) throws IOException { return new PKCS8EncodedKeySpec(encodedKey, KeyUtil.getAlgorithm(encodedKey)); } + + // Return the PKCS#8 encoding from a DEREncodable + private static byte[] getEncoding(DEREncodable d) { + return switch (d) { + case PrivateKey p -> p.getEncoded(); + case PKCS8EncodedKeySpec p8 -> p8.getEncoded(); + case KeyPair kp -> { + try { + yield PKCS8Key.getEncoded(kp.getPublic().getEncoded(), + kp.getPrivate().getEncoded()); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } + default -> throw new IllegalArgumentException( + d.getClass().getName() + " not supported by this method"); + }; + } + + // Generate a SecretKey from the password. + private static SecretKey generateSecretKey(char[] password, String algorithm, + Provider provider) { + PBEKeySpec keySpec = new PBEKeySpec(password); + + try { + SecretKeyFactory factory; + if (provider == null) { + factory = SecretKeyFactory.getInstance(algorithm); + } else { + factory = SecretKeyFactory.getInstance(algorithm, provider); + } + return factory.generateSecret(keySpec); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + throw new IllegalArgumentException(e); + } finally { + keySpec.clearPassword(); + } + } } diff --git a/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java b/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java index 6b8cda6aa1a..1316b8b946f 100644 --- a/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java +++ b/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java @@ -87,7 +87,8 @@ public @interface PreviewFeature { STRUCTURED_CONCURRENCY, @JEP(number = 502, title = "Stable Values", status = "Preview") STABLE_VALUES, - @JEP(number=470, title="PEM Encodings of Cryptographic Objects", status="Preview") + @JEP(number=524, title="PEM Encodings of Cryptographic Objects", + status="Second Preview") PEM_API, LANGUAGE_MODEL, /** diff --git a/src/java.base/share/classes/sun/security/ec/ECKeyFactory.java b/src/java.base/share/classes/sun/security/ec/ECKeyFactory.java index 2530425fbd4..a753a105e6e 100644 --- a/src/java.base/share/classes/sun/security/ec/ECKeyFactory.java +++ b/src/java.base/share/classes/sun/security/ec/ECKeyFactory.java @@ -26,6 +26,7 @@ package sun.security.ec; import sun.security.pkcs.PKCS8Key; +import sun.security.util.KeyUtil; import java.security.*; import java.security.interfaces.*; @@ -213,11 +214,17 @@ public final class ECKeyFactory extends KeyFactorySpi { case ECPublicKeySpec e -> new ECPublicKeyImpl(e.getW(), e.getParams()); case PKCS8EncodedKeySpec p8 -> { - PKCS8Key p8key = new ECPrivateKeyImpl(p8.getEncoded()); - if (!p8key.hasPublicKey()) { - throw new InvalidKeySpecException("No public key found."); + byte[] encoded = p8.getEncoded(); + PKCS8Key p8key = null; + try { + p8key = new ECPrivateKeyImpl(encoded); + if (!p8key.hasPublicKey()) { + throw new InvalidKeySpecException("No public key found."); + } + yield new ECPublicKeyImpl(p8key.getPubKeyEncoded()); + } finally { + KeyUtil.clear(encoded, p8key); } - yield new ECPublicKeyImpl(p8key.getPubKeyEncoded()); } case null -> throw new InvalidKeySpecException( "keySpec must not be null"); diff --git a/src/java.base/share/classes/sun/security/ec/ECPrivateKeyImpl.java b/src/java.base/share/classes/sun/security/ec/ECPrivateKeyImpl.java index 65c4f093f27..1b69a5b22b4 100644 --- a/src/java.base/share/classes/sun/security/ec/ECPrivateKeyImpl.java +++ b/src/java.base/share/classes/sun/security/ec/ECPrivateKeyImpl.java @@ -75,6 +75,7 @@ public final class ECPrivateKeyImpl extends PKCS8Key implements ECPrivateKey { @SuppressWarnings("serial") // Type of field is not Serializable private ECParameterSpec params; private byte[] domainParams; //Currently unsupported + private final byte SEC1v2 = 1; /** * Construct a key from its encoding. Called by the ECKeyFactory. @@ -92,34 +93,6 @@ public final class ECPrivateKeyImpl extends PKCS8Key implements ECPrivateKey { throws InvalidKeyException { this.s = s; this.params = params; - makeEncoding(s); - - } - - ECPrivateKeyImpl(byte[] s, ECParameterSpec params) - throws InvalidKeyException { - this.arrayS = s.clone(); - this.params = params; - makeEncoding(s); - } - - private void makeEncoding(byte[] s) throws InvalidKeyException { - algid = new AlgorithmId - (AlgorithmId.EC_oid, ECParameters.getAlgorithmParameters(params)); - DerOutputStream out = new DerOutputStream(); - out.putInteger(1); // version 1 - byte[] privBytes = s.clone(); - ArrayUtil.reverse(privBytes); - out.putOctetString(privBytes); - Arrays.fill(privBytes, (byte) 0); - DerValue val = DerValue.wrap(DerValue.tag_Sequence, out); - privKeyMaterial = val.toByteArray(); - val.clear(); - } - - private void makeEncoding(BigInteger s) throws InvalidKeyException { - algid = new AlgorithmId(AlgorithmId.EC_oid, - ECParameters.getAlgorithmParameters(params)); byte[] sArr = s.toByteArray(); // convert to fixed-length array int numOctets = (params.getOrder().bitLength() + 7) / 8; @@ -129,11 +102,33 @@ public final class ECPrivateKeyImpl extends PKCS8Key implements ECPrivateKey { int length = Math.min(sArr.length, sOctets.length); System.arraycopy(sArr, inPos, sOctets, outPos, length); Arrays.fill(sArr, (byte) 0); + try { + makeEncoding(sOctets); + } finally { + Arrays.fill(sOctets, (byte) 0); + } + } + ECPrivateKeyImpl(byte[] s, ECParameterSpec params) + throws InvalidKeyException { + this.arrayS = s.clone(); + this.params = params; + byte[] privBytes = arrayS.clone(); + ArrayUtil.reverse(privBytes); + try { + makeEncoding(privBytes); + } finally { + Arrays.fill(privBytes, (byte) 0); + } + + } + + private void makeEncoding(byte[] privBytes) throws InvalidKeyException { + algid = new AlgorithmId(AlgorithmId.EC_oid, + ECParameters.getAlgorithmParameters(params)); DerOutputStream out = new DerOutputStream(); out.putInteger(1); // version 1 - out.putOctetString(sOctets); - Arrays.fill(sOctets, (byte) 0); + out.putOctetString(privBytes); DerValue val = DerValue.wrap(DerValue.tag_Sequence, out); privKeyMaterial = val.toByteArray(); val.clear(); @@ -181,7 +176,7 @@ public final class ECPrivateKeyImpl extends PKCS8Key implements ECPrivateKey { } DerInputStream data = derValue.data; int version = data.getInteger(); - if (version != V2) { + if (version != SEC1v2) { throw new IOException("Version must be 1"); } byte[] privData = data.getOctetString(); @@ -253,4 +248,40 @@ public final class ECPrivateKeyImpl extends PKCS8Key implements ECPrivateKey { throw new InvalidObjectException( "ECPrivateKeyImpl keys are not directly deserializable"); } + + // Parse the SEC1v2 encoding to extract public key, if available. + public static BitArray parsePublicBits(byte[] privateBytes) { + DerValue seq = null; + try { + seq = new DerValue(privateBytes); + if (seq.tag == DerValue.tag_Sequence) { + int version = seq.data.getInteger(); + if (version == 1) { // EC + seq.data.getDerValue(); // read pass the private key + if (seq.data.available() != 0) { + DerValue derValue = seq.data.getDerValue(); + // check for optional [0] EC domain parameters + if (derValue.isContextSpecific((byte) 0)) { + if (seq.data.available() == 0) { + return null; + } + derValue = seq.data.getDerValue(); + } + // [1] public key + if (derValue.isContextSpecific((byte) 1)) { + derValue = derValue.data.getDerValue(); + return derValue.getUnalignedBitString(); + } + } + } + } + } catch (IOException e) { + throw new IllegalArgumentException(e); + } finally { + if (seq != null) { + seq.clear(); + } + } + return null; + } } diff --git a/src/java.base/share/classes/sun/security/ec/XDHKeyFactory.java b/src/java.base/share/classes/sun/security/ec/XDHKeyFactory.java index c8fb6c0c11b..8f060705323 100644 --- a/src/java.base/share/classes/sun/security/ec/XDHKeyFactory.java +++ b/src/java.base/share/classes/sun/security/ec/XDHKeyFactory.java @@ -26,6 +26,7 @@ package sun.security.ec; import sun.security.pkcs.PKCS8Key; +import sun.security.util.KeyUtil; import java.security.*; import java.security.interfaces.XECKey; @@ -159,15 +160,20 @@ public class XDHKeyFactory extends KeyFactorySpi { yield new XDHPublicKeyImpl(params, publicKeySpec.getU()); } case PKCS8EncodedKeySpec p8 -> { - PKCS8Key p8key = new XDHPrivateKeyImpl(p8.getEncoded()); - if (!p8key.hasPublicKey()) { - throw new InvalidKeySpecException("No public key found."); + byte[] encoded = p8.getEncoded(); + PKCS8Key p8key = new XDHPrivateKeyImpl(encoded); + try { + if (!p8key.hasPublicKey()) { + throw new InvalidKeySpecException("No public key found."); + } + XDHPublicKeyImpl result = + new XDHPublicKeyImpl(p8key.getPubKeyEncoded()); + checkLockedParams(InvalidKeySpecException::new, + result.getParams()); + yield result; + } finally { + KeyUtil.clear(encoded, p8key); } - XDHPublicKeyImpl result = - new XDHPublicKeyImpl(p8key.getPubKeyEncoded()); - checkLockedParams(InvalidKeySpecException::new, - result.getParams()); - yield result; } case null -> throw new InvalidKeySpecException( "keySpec must not be null"); diff --git a/src/java.base/share/classes/sun/security/ec/ed/EdDSAKeyFactory.java b/src/java.base/share/classes/sun/security/ec/ed/EdDSAKeyFactory.java index 71ec14ba06f..d59e44d81db 100644 --- a/src/java.base/share/classes/sun/security/ec/ed/EdDSAKeyFactory.java +++ b/src/java.base/share/classes/sun/security/ec/ed/EdDSAKeyFactory.java @@ -26,6 +26,7 @@ package sun.security.ec.ed; import sun.security.pkcs.PKCS8Key; +import sun.security.util.KeyUtil; import java.security.*; import java.security.interfaces.*; @@ -152,11 +153,17 @@ public class EdDSAKeyFactory extends KeyFactorySpi { yield new EdDSAPublicKeyImpl(params, publicKeySpec.getPoint()); } case PKCS8EncodedKeySpec p8 -> { - PKCS8Key p8key = new EdDSAPrivateKeyImpl(p8.getEncoded()); - if (!p8key.hasPublicKey()) { - throw new InvalidKeySpecException("No public key found."); + byte[] encoded = p8.getEncoded(); + PKCS8Key p8key = null; + try { + p8key = new EdDSAPrivateKeyImpl(encoded); + if (!p8key.hasPublicKey()) { + throw new InvalidKeySpecException("No public key found."); + } + yield new EdDSAPublicKeyImpl(p8key.getPubKeyEncoded()); + } finally { + KeyUtil.clear(encoded, p8key); } - yield new EdDSAPublicKeyImpl(p8key.getPubKeyEncoded()); } case null -> throw new InvalidKeySpecException( "keySpec must not be null"); diff --git a/src/java.base/share/classes/sun/security/pkcs/PKCS8Key.java b/src/java.base/share/classes/sun/security/pkcs/PKCS8Key.java index b7cc5e7057f..dea87bd0a32 100644 --- a/src/java.base/share/classes/sun/security/pkcs/PKCS8Key.java +++ b/src/java.base/share/classes/sun/security/pkcs/PKCS8Key.java @@ -26,6 +26,7 @@ package sun.security.pkcs; import jdk.internal.access.SharedSecrets; +import sun.security.ec.ECPrivateKeyImpl; import sun.security.util.*; import sun.security.x509.AlgorithmId; import sun.security.x509.X509Key; @@ -104,11 +105,29 @@ public class PKCS8Key implements PrivateKey, InternalPrivateKey { } } - private PKCS8Key(byte[] privEncoding, byte[] pubEncoding) + /** + * Constructor that takes both public and private encodings. + * + * If the private key includes a public key encoding (like an EC key in + * SEC1v2 format), and a specified public key matches it, the existing + * encoding is reused rather than recreated. + */ + public PKCS8Key(byte[] publicEncoding, byte[] privateEncoding) throws InvalidKeyException { - this(privEncoding); - pubKeyEncoded = pubEncoding; - version = V2; + this(privateEncoding); + if (publicEncoding != null) { + if (pubKeyEncoded != null) { + if (!Arrays.equals(pubKeyEncoded, publicEncoding)) { + Arrays.fill(privKeyMaterial, (byte) 0x0); + throw new InvalidKeyException("PrivateKey " + + "encoding has a public key that does not match " + + "the given PublicKey"); + } + } else { + pubKeyEncoded = publicEncoding; + version = V2; + } + } } public int getVersion() { @@ -137,6 +156,14 @@ public class PKCS8Key implements PrivateKey, InternalPrivateKey { // Store key material for subclasses to parse privKeyMaterial = val.data.getOctetString(); + // Special check and parsing for ECDSA's SEC1v2 format + if (algid.getOID().equals(AlgorithmId.EC_oid)) { + var bits = ECPrivateKeyImpl.parsePublicBits(privKeyMaterial); + if (bits != null) { + pubKeyEncoded = new X509Key(algid, bits).getEncoded(); + } + } + // PKCS8 v1 typically ends here if (val.data.available() == 0) { return; @@ -271,19 +298,24 @@ public class PKCS8Key implements PrivateKey, InternalPrivateKey { * With a given encoded Public and Private key, generate and return a * PKCS8v2 DER-encoded byte[]. * - * @param pubKeyEncoded DER-encoded PublicKey + * @param pubKeyEncoded DER-encoded PublicKey, this may be null. * @param privKeyEncoded DER-encoded PrivateKey * @return DER-encoded byte array * @throws IOException thrown on encoding failure */ public static byte[] getEncoded(byte[] pubKeyEncoded, byte[] privKeyEncoded) throws IOException { + PKCS8Key pkcs8Key; try { - return new PKCS8Key(privKeyEncoded, pubKeyEncoded). - generateEncoding(); + pkcs8Key = new PKCS8Key(pubKeyEncoded, privKeyEncoded); } catch (InvalidKeyException e) { throw new IOException(e); } + try { + return pkcs8Key.generateEncoding().clone(); + } finally { + pkcs8Key.clear(); + } } /** @@ -295,7 +327,7 @@ public class PKCS8Key implements PrivateKey, InternalPrivateKey { private synchronized byte[] getEncodedInternal() { if (encodedKey == null) { try { - encodedKey = generateEncoding(); + generateEncoding(); } catch (IOException e) { return null; } @@ -326,7 +358,6 @@ public class PKCS8Key implements PrivateKey, InternalPrivateKey { throw new IOException(e); } - // X509Key x = X509Key.parse(pubKeyEncoded); DerOutputStream pubOut = new DerOutputStream(); pubOut.putUnalignedBitString(x.getKey()); out.writeImplicit( diff --git a/src/java.base/share/classes/sun/security/provider/X509Factory.java b/src/java.base/share/classes/sun/security/provider/X509Factory.java index 1a8ace55fc8..f732c7c0455 100644 --- a/src/java.base/share/classes/sun/security/provider/X509Factory.java +++ b/src/java.base/share/classes/sun/security/provider/X509Factory.java @@ -27,7 +27,7 @@ package sun.security.provider; import java.io.*; -import java.security.PEMRecord; +import java.security.PEM; import java.security.cert.*; import java.util.*; @@ -559,7 +559,7 @@ public class X509Factory extends CertificateFactorySpi { return bout.toByteArray(); } else { try { - PEMRecord rec; + PEM rec; try { rec = Pem.readPEM(is, (c == '-' ? true : false)); } catch (EOFException e) { diff --git a/src/java.base/share/classes/sun/security/util/KeyUtil.java b/src/java.base/share/classes/sun/security/util/KeyUtil.java index ccc72ea6ea2..942a91d61b8 100644 --- a/src/java.base/share/classes/sun/security/util/KeyUtil.java +++ b/src/java.base/share/classes/sun/security/util/KeyUtil.java @@ -36,12 +36,14 @@ import javax.crypto.interfaces.DHKey; import javax.crypto.interfaces.DHPublicKey; import javax.crypto.spec.DHParameterSpec; import javax.crypto.spec.DHPublicKeySpec; +import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; import javax.security.auth.DestroyFailedException; import jdk.internal.access.SharedSecrets; import com.sun.crypto.provider.PBKDF2KeyImpl; import sun.security.jca.JCAUtil; +import sun.security.pkcs.PKCS8Key; import sun.security.x509.AlgorithmId; /** @@ -548,6 +550,22 @@ public final class KeyUtil { throw new IOException("No algorithm detected"); } - + // Generic method for zeroing arrays and objects + public static void clear(Object... list) { + for (Object o: list) { + switch (o) { + case byte[] b -> Arrays.fill(b, (byte)0); + case char[] c -> Arrays.fill(c, (char)0); + case PKCS8Key p8 -> p8.clear(); + case PKCS8EncodedKeySpec p8 -> + SharedSecrets.getJavaSecuritySpecAccess().clearEncodedKeySpec(p8); + case PBEKeySpec pbe -> pbe.clearPassword(); + case null -> {} + default -> + throw new IllegalArgumentException( + o.getClass().getName() + " not defined in KeyUtil.clear()"); + } + } + } } diff --git a/src/java.base/share/classes/sun/security/util/Pem.java b/src/java.base/share/classes/sun/security/util/Pem.java index 492017eca29..a9b2908bcc9 100644 --- a/src/java.base/share/classes/sun/security/util/Pem.java +++ b/src/java.base/share/classes/sun/security/util/Pem.java @@ -25,13 +25,18 @@ package sun.security.util; +import sun.security.pkcs.PKCS8Key; import sun.security.x509.AlgorithmId; +import javax.crypto.EncryptedPrivateKeyInfo; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; import java.io.*; import java.nio.charset.StandardCharsets; -import java.security.NoSuchAlgorithmException; -import java.security.PEMRecord; -import java.security.Security; +import java.security.*; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; import java.util.Arrays; import java.util.Base64; import java.util.HexFormat; @@ -145,6 +150,9 @@ public class Pem { * Read the PEM text and return it in it's three components: header, * base64, and footer. * + * The header begins processing when "-----B" is read. At that point + * exceptions will be thrown for syntax errors. + * * The method will leave the stream after reading the end of line of the * footer or end of file * @param is an InputStream @@ -159,15 +167,16 @@ public class Pem { * but the read position in the stream is at the end of the block, so * future reads can be successful. */ - public static PEMRecord readPEM(InputStream is, boolean shortHeader) + public static PEM readPEM(InputStream is, boolean shortHeader) throws IOException { Objects.requireNonNull(is); int hyphen = (shortHeader ? 1 : 0); int eol = 0; - ByteArrayOutputStream os = new ByteArrayOutputStream(6); - // Find starting hyphens + + // Find 5 hyphens followed by a 'B' to start processing the header. + boolean headerStarted = false; do { int d = is.read(); switch (d) { @@ -178,13 +187,20 @@ public class Pem { } throw new EOFException("No PEM data found"); } + case 'B' -> { + if (hyphen == 5) { + headerStarted = true; + } else { + hyphen = 0; + } + } default -> hyphen = 0; } os.write(d); - } while (hyphen != 5); + } while (!headerStarted); StringBuilder sb = new StringBuilder(64); - sb.append("-----"); + sb.append("-----B"); hyphen = 0; int c; @@ -307,14 +323,14 @@ public class Pem { // If there was data before finding the 5 dashes of the PEM header, // backup 5 characters and save that data. byte[] preData = null; - if (os.size() > 5) { - preData = Arrays.copyOf(os.toByteArray(), os.size() - 5); + if (os.size() > 6) { + preData = Arrays.copyOf(os.toByteArray(), os.size() - 6); } - return new PEMRecord(typeConverter(headerType), data, preData); + return new PEM(typeConverter(headerType), data, preData); } - public static PEMRecord readPEM(InputStream is) throws IOException { + public static PEM readPEM(InputStream is) throws IOException { return readPEM(is, false); } @@ -342,8 +358,115 @@ public class Pem { * is not used with this method. * @return PEM in a string */ - public static String pemEncoded(PEMRecord pem) { + public static String pemEncoded(PEM pem) { String p = pem.content().replaceAll("(.{64})", "$1\r\n"); return pemEncoded(pem.type(), p); } + + /* + * Get PKCS8 encoding from an encrypted private key encoding. + */ + public static byte[] decryptEncoding(byte[] encoded, char[] password) + throws GeneralSecurityException { + EncryptedPrivateKeyInfo ekpi; + + Objects.requireNonNull(password, "password cannot be null"); + PBEKeySpec keySpec = new PBEKeySpec(password); + try { + ekpi = new EncryptedPrivateKeyInfo(encoded); + return decryptEncoding(ekpi, keySpec); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } finally { + keySpec.clearPassword(); + } + } + + public static byte[] decryptEncoding(EncryptedPrivateKeyInfo ekpi, PBEKeySpec keySpec) + throws NoSuchAlgorithmException, InvalidKeyException { + + PKCS8EncodedKeySpec p8KeySpec = null; + try { + SecretKeyFactory skf = SecretKeyFactory.getInstance(ekpi.getAlgName()); + p8KeySpec = ekpi.getKeySpec(skf.generateSecret(keySpec)); + return p8KeySpec.getEncoded(); + } catch (InvalidKeySpecException e) { + throw new InvalidKeyException(e); + } finally { + KeyUtil.clear(p8KeySpec); + } + } + + + /** + * With a given PKCS8 encoding, construct a PrivateKey or KeyPair. A + * KeyPair is returned if requested and the encoding has a public key; + * otherwise, a PrivateKey is returned. + * + * @param encoded PKCS8 encoding + * @param pair set to true for returning a KeyPair, if possible. Otherwise, + * return a PrivateKey + * @param provider KeyFactory provider + */ + public static DEREncodable toDEREncodable(byte[] encoded, boolean pair, + Provider provider) throws InvalidKeyException { + + PrivateKey privKey; + PublicKey pubKey = null; + PKCS8EncodedKeySpec p8KeySpec; + PKCS8Key p8key = new PKCS8Key(encoded); + KeyFactory kf; + + try { + p8KeySpec = new PKCS8EncodedKeySpec(encoded); + } catch (NullPointerException e) { + p8key.clear(); + throw new InvalidKeyException("No encoding found", e); + } + + try { + if (provider == null) { + kf = KeyFactory.getInstance(p8key.getAlgorithm()); + } else { + kf = KeyFactory.getInstance(p8key.getAlgorithm(), provider); + } + } catch (NoSuchAlgorithmException e) { + KeyUtil.clear(p8KeySpec, p8key); + throw new InvalidKeyException("Unable to find the algorithm: " + + p8key.getAlgorithm(), e); + } + + try { + privKey = kf.generatePrivate(p8KeySpec); + + // Only want the PrivateKey? then return it. + if (!pair) { + return privKey; + } + + if (p8key.hasPublicKey()) { + // PKCS8Key.decode() has extracted the public key already + pubKey = kf.generatePublic( + new X509EncodedKeySpec(p8key.getPubKeyEncoded())); + } else { + // In case decode() could not read the public key, the + // KeyFactory can try. Failure is ok as there may not + // be a public key in the encoding. + try { + pubKey = kf.generatePublic(p8KeySpec); + } catch (InvalidKeySpecException e) { + // ignore + } + } + } catch (InvalidKeySpecException e) { + throw new InvalidKeyException(e); + } finally { + KeyUtil.clear(p8KeySpec, p8key); + } + if (pair && pubKey != null) { + return new KeyPair(pubKey, privKey); + } + return privKey; + } + } diff --git a/test/jdk/java/security/PEM/PEMData.java b/test/jdk/java/security/PEM/PEMData.java index 2c8c60fcccc..1c03baa7e7d 100644 --- a/test/jdk/java/security/PEM/PEMData.java +++ b/test/jdk/java/security/PEM/PEMData.java @@ -26,7 +26,7 @@ import javax.crypto.EncryptedPrivateKeyInfo; import java.security.DEREncodable; import java.security.KeyPair; -import java.security.PEMRecord; +import java.security.PEM; import java.security.cert.X509CRL; import java.security.cert.X509Certificate; import java.security.interfaces.*; @@ -48,6 +48,17 @@ class PEMData { -----END PRIVATE KEY----- """, KeyPair.class, "SunEC"); + // EC 256 with a domain parameter & public key + public static final Entry ecsecp256dom0 = new Entry("ecsecp256dom0", + """ + -----BEGIN PRIVATE KEY----- + MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgkW3Jx561NlEgBnut + KwDdi3cNwu7YYD/QtJ+9+AEBdoqgCgYIKoZIzj0DAQehRANCAASL+REY4vvAI9M3 + gonaml5K3lRgHq5w+OO4oO0VNduC44gUN1nrk7/wdNSpL+xXNEX52Dsff+2RD/fo + p224ANvB + -----END PRIVATE KEY----- + """, KeyPair.class, "SunEC"); + public static final Entry rsapriv = new Entry("rsapriv", """ -----BEGIN PRIVATE KEY----- @@ -149,7 +160,7 @@ class PEMData { -----END PRIVATE KEY----- """, RSAPrivateKey.class, "SunRsaSign"); - public static final Entry ec25519priv = new Entry("ed25519priv", + public static final Entry ed25519priv = new Entry("ed25519priv", """ -----BEGIN PRIVATE KEY----- MC4CAQAwBQYDK2VwBCIEIFFZsmD+OKk67Cigc84/2fWtlKsvXWLSoMJ0MHh4jI4I @@ -189,6 +200,7 @@ class PEMData { -----END PUBLIC KEY----- """, RSAPublicKey.class, "SunRsaSign"); + // This is the public key contained in ecsecp256 public static final Entry ecsecp256pub = new Entry("ecsecp256pub", """ -----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEi/kRGOL7wCPTN4KJ2ppeSt5UYB6u @@ -286,6 +298,19 @@ class PEMData { -----END RSA PRIVATE KEY----- """, RSAPrivateKey.class, "SunRsaSign"); + static final Entry ecsecp256ekpi = new Entry("ecsecp256ekpi", + """ + -----BEGIN ENCRYPTED PRIVATE KEY----- + MIH0MF8GCSqGSIb3DQEFDTBSMDEGCSqGSIb3DQEFDDAkBBDhqUj1Oadj1GZXUMXT + b3QEAgIIADAMBggqhkiG9w0CCQUAMB0GCWCGSAFlAwQBAgQQitxCfcZcMtoNu+X+ + PQk+/wSBkFL1NddKkUL2tRv6pNf1TR7eI7qJReGRgJexU/6pDN+UQS5e5qSySa7E + k1m2pUHgZlySUblXZj9nOzCsNFfq/jxlL15ZpAviAM2fRINnNEJcvoB+qZTS5cRb + Xs3wC7wymHW3EdIZ9sxfSHq9t7j9SnC1jGHjno0v1rKcdIvJtYloxsRYjsG/Sxhz + uNYnx8AMuQ== + -----END ENCRYPTED PRIVATE KEY----- + """, EncryptedPrivateKeyInfo.class, "SunEC", "fish".toCharArray()); + + static final Entry ed25519ep8 = new Entry("ed25519ep8", """ -----BEGIN ENCRYPTED PRIVATE KEY----- @@ -450,7 +475,7 @@ class PEMData { MQYMBGZpc2gwCgYIKoZIzj0EAwIDRwAwRAIgUBTdrMDE4BqruYRh1rRyKQBf48WR kIX8R4dBK9h1VRcCIEBR2Mzvku/huTbWTwKVlXBZeEmwIlxKwpRepPtViXcW -----END CERTIFICATE REQUEST----- - """, PEMRecord.class, "SunEC"); + """, PEM.class, "SunEC"); public static final String preData = "TEXT BLAH TEXT BLAH" + System.lineSeparator(); @@ -471,7 +496,7 @@ class PEMData { MQYMBGZpc2gwCgYIKoZIzj0EAwIDRwAwRAIgUBTdrMDE4BqruYRh1rRyKQBf48WR kIX8R4dBK9h1VRcCIEBR2Mzvku/huTbWTwKVlXBZeEmwIlxKwpRepPtViXcW -----END CERTIFICATE REQUEST----- - """ + postData, PEMRecord.class, "SunEC"); + """ + postData, PEM.class, "SunEC"); final static Pattern CR = Pattern.compile("\r"); final static Pattern LF = Pattern.compile("\n"); @@ -564,8 +589,9 @@ class PEMData { privList.add(rsapsspriv); privList.add(rsaprivbc); privList.add(ecsecp256); + privList.add(ecsecp256dom0); privList.add(ecsecp384); - privList.add(ec25519priv); + privList.add(ed25519priv); privList.add(ed25519ekpi); // The non-EKPI version needs decryption privList.add(rsaOpenSSL); oasList.add(oasrfc8410); diff --git a/test/jdk/java/security/PEM/PEMDecoderTest.java b/test/jdk/java/security/PEM/PEMDecoderTest.java index 8e3ae76994d..3ae088329b4 100644 --- a/test/jdk/java/security/PEM/PEMDecoderTest.java +++ b/test/jdk/java/security/PEM/PEMDecoderTest.java @@ -53,8 +53,11 @@ import sun.security.util.Pem; public class PEMDecoderTest { static HexFormat hex = HexFormat.of(); + static final PEMDecoder d = PEMDecoder.of(); public static void main(String[] args) throws Exception { + PEMDecoder decr; + System.out.println("Decoder test:"); PEMData.entryList.forEach(entry -> test(entry, false)); System.out.println("Decoder test withFactory:"); @@ -89,12 +92,12 @@ public class PEMDecoderTest { System.out.println("Decoder test ecsecp256:"); testFailure(PEMData.ecsecp256pub.makeNoCRLF("pubecpem-no")); System.out.println("Decoder test RSAcert with decryption Decoder:"); - PEMDecoder d = PEMDecoder.of().withDecryption("123".toCharArray()); - d.decode(PEMData.rsaCert.pem()); + decr = d.withDecryption("123".toCharArray()); + decr.decode(PEMData.rsaCert.pem()); System.out.println("Decoder test ecsecp256 private key with decryption Decoder:"); - ((KeyPair) d.decode(PEMData.ecsecp256.pem())).getPrivate(); + ((KeyPair) decr.decode(PEMData.ecsecp256.pem())).getPrivate(); System.out.println("Decoder test ecsecp256 to P8EKS:"); - d.decode(PEMData.ecsecp256.pem(), PKCS8EncodedKeySpec.class); + decr.decode(PEMData.ecsecp256.pem(), PKCS8EncodedKeySpec.class); System.out.println("Checking if decode() returns the same encoding:"); PEMData.privList.forEach(PEMDecoderTest::testDERCheck); @@ -111,11 +114,11 @@ public class PEMDecoderTest { System.out.println("Checking if ecCSR:"); test(PEMData.ecCSR); System.out.println("Checking if ecCSR with preData:"); - DEREncodable result = PEMDecoder.of().decode(PEMData.ecCSRWithData.pem(), PEMRecord.class); - if (result instanceof PEMRecord rec) { + DEREncodable result = d.decode(PEMData.ecCSRWithData.pem(), PEM.class); + if (result instanceof PEM rec) { if (PEMData.preData.compareTo(new String(rec.leadingData())) != 0) { - System.err.println("expected: " + PEMData.preData); - System.err.println("received: " + new String(rec.leadingData())); + System.err.println("expected: \"" + PEMData.preData + "\""); + System.err.println("received: \"" + new String(rec.leadingData()) + "\""); throw new AssertionError("ecCSRWithData preData wrong"); } if (rec.content().lastIndexOf("F") > rec.content().length() - 5) { @@ -128,35 +131,34 @@ public class PEMDecoderTest { } System.out.println("Decoding RSA pub using class PEMRecord:"); - result = PEMDecoder.of().decode(PEMData.rsapub.pem(), PEMRecord.class); - if (!(result instanceof PEMRecord)) { + result = d.decode(PEMData.rsapub.pem(), PEM.class); + if (!(result instanceof PEM)) { throw new AssertionError("pubecpem didn't return a PEMRecord"); } - if (((PEMRecord) result).type().compareTo(Pem.PUBLIC_KEY) != 0) { + if (((PEM) result).type().compareTo(Pem.PUBLIC_KEY) != 0) { throw new AssertionError("pubecpem PEMRecord didn't decode as a Public Key"); } testInputStream(); testPEMRecord(PEMData.rsapub); testPEMRecord(PEMData.ecCert); - testPEMRecord(PEMData.ec25519priv); + testPEMRecord(PEMData.ed25519priv); testPEMRecord(PEMData.ecCSR); testPEMRecord(PEMData.ecCSRWithData); testPEMRecordDecode(PEMData.rsapub); testPEMRecordDecode(PEMData.ecCert); - testPEMRecordDecode(PEMData.ec25519priv); + testPEMRecordDecode(PEMData.ed25519priv); testPEMRecordDecode(PEMData.ecCSR); testPEMRecordDecode(PEMData.ecCSRWithData); - d = PEMDecoder.of(); System.out.println("Check leadingData is null with back-to-back PEMs: "); - String s = new PEMRecord("ONE", "1212").toString() - + new PEMRecord("TWO", "3434").toString(); - var ins = new ByteArrayInputStream(s.getBytes(StandardCharsets.UTF_8)); - if (d.decode(ins, PEMRecord.class).leadingData() != null) { + String s = new PEM("ONE", "1212").toString() + + new PEM("TWO", "3434").toString(); + ByteArrayInputStream bis = new ByteArrayInputStream(s.getBytes(StandardCharsets.UTF_8)); + if (d.decode(bis, PEM.class).leadingData() != null) { throw new AssertionError("leading data not null on first pem"); } - if (d.decode(ins, PEMRecord.class).leadingData() != null) { + if (d.decode(bis, PEM.class).leadingData() != null) { throw new AssertionError("leading data not null on second pem"); } System.out.println("PASS"); @@ -173,8 +175,8 @@ public class PEMDecoderTest { } // PBE - System.out.println("EncryptedPrivateKeyInfo.encryptKey with PBE: "); - ekpi = EncryptedPrivateKeyInfo.encryptKey(privateKey, + System.out.println("EncryptedPrivateKeyInfo.encrypt with PBE: "); + ekpi = EncryptedPrivateKeyInfo.encrypt(privateKey, "password".toCharArray(),"PBEWithMD5AndDES", null, null); try { ekpi.getKey("password".toCharArray()); @@ -184,8 +186,8 @@ public class PEMDecoderTest { } // PBES2 - System.out.println("EncryptedPrivateKeyInfo.encryptKey with default: "); - ekpi = EncryptedPrivateKeyInfo.encryptKey(privateKey + System.out.println("EncryptedPrivateKeyInfo.encrypt with default: "); + ekpi = EncryptedPrivateKeyInfo.encrypt(privateKey , "password".toCharArray()); try { ekpi.getKey("password".toCharArray()); @@ -197,6 +199,13 @@ public class PEMDecoderTest { System.out.println("Decoder test testCoefZero:"); testCoefZero(PEMData.rsaCrtCoefZeroPriv); + + // leadingData can contain dashes + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + bos.write("--------\n".getBytes(StandardCharsets.ISO_8859_1)); + bos.write(PEMData.ecsecp256ekpi.pem().getBytes(StandardCharsets.ISO_8859_1)); + bis = new ByteArrayInputStream(bos.toByteArray()); + result = d.decode(bis, PEM.class); } static void testInputStream() throws IOException { @@ -211,10 +220,10 @@ public class PEMDecoderTest { ByteArrayInputStream is = new ByteArrayInputStream(ba.toByteArray()); System.out.println("Decoding 2 RSA pub with pre & post data:"); - PEMRecord obj; + PEM obj; int keys = 0; while (keys++ < 2) { - obj = PEMDecoder.of().decode(is, PEMRecord.class); + obj = d.decode(is, PEM.class); if (!PEMData.preData.equalsIgnoreCase( new String(obj.leadingData()))) { System.out.println("expected: \"" + PEMData.preData + "\""); @@ -225,7 +234,7 @@ public class PEMDecoderTest { System.out.println(" Read public key."); } try { - PEMDecoder.of().decode(is, PEMRecord.class); + d.decode(is, PEM.class); throw new AssertionError("3rd entry returned a PEMRecord"); } catch (EOFException e) { System.out.println("Success: No 3rd entry found. EOFE thrown."); @@ -234,7 +243,7 @@ public class PEMDecoderTest { // End of stream try { System.out.println("Failed: There should be no PEMRecord: " + - PEMDecoder.of().decode(is, PEMRecord.class)); + d.decode(is, PEM.class)); } catch (EOFException e) { System.out.println("Success"); return; @@ -250,22 +259,22 @@ public class PEMDecoderTest { static void testCertTypeConverter(PEMData.Entry entry) throws CertificateEncodingException { String certPem = entry.pem().replace("CERTIFICATE", "X509 CERTIFICATE"); Asserts.assertEqualsByteArray(entry.der(), - PEMDecoder.of().decode(certPem, X509Certificate.class).getEncoded()); + d.decode(certPem, X509Certificate.class).getEncoded()); certPem = entry.pem().replace("CERTIFICATE", "X.509 CERTIFICATE"); Asserts.assertEqualsByteArray(entry.der(), - PEMDecoder.of().decode(certPem, X509Certificate.class).getEncoded()); + d.decode(certPem, X509Certificate.class).getEncoded()); } // test that when the crtCoeff is zero, the key is decoded but only the modulus and private // exponent are used resulting in a different der static void testCoefZero(PEMData.Entry entry) { - RSAPrivateKey decoded = PEMDecoder.of().decode(entry.pem(), RSAPrivateKey.class); + RSAPrivateKey decoded = d.decode(entry.pem(), RSAPrivateKey.class); Asserts.assertNotEqualsByteArray(decoded.getEncoded(), entry.der()); } static void testPEMRecord(PEMData.Entry entry) { - PEMRecord r = PEMDecoder.of().decode(entry.pem(), PEMRecord.class); + PEM r = d.decode(entry.pem(), PEM.class); String expected = entry.pem().split("-----")[2].replace(System.lineSeparator(), ""); try { PEMData.checkResults(expected, r.content()); @@ -285,7 +294,7 @@ public class PEMDecoderTest { case Pem.X509_CRL -> entry.clazz().isAssignableFrom(X509CRL.class); case "CERTIFICATE REQUEST" -> - entry.clazz().isAssignableFrom(PEMRecord.class); + entry.clazz().isAssignableFrom(PEM.class); default -> false; }; @@ -300,8 +309,8 @@ public class PEMDecoderTest { static void testPEMRecordDecode(PEMData.Entry entry) { - PEMRecord r = PEMDecoder.of().decode(entry.pem(), PEMRecord.class); - DEREncodable de = PEMDecoder.of().decode(r.toString()); + PEM r = d.decode(entry.pem(), PEM.class); + DEREncodable de = d.decode(r.toString()); boolean result = switch(r.type()) { case Pem.PRIVATE_KEY -> @@ -311,7 +320,7 @@ public class PEMDecoderTest { case Pem.CERTIFICATE, Pem.X509_CERTIFICATE -> (de instanceof X509Certificate); case Pem.X509_CRL -> (de instanceof X509CRL); - case "CERTIFICATE REQUEST" -> (de instanceof PEMRecord); + case "CERTIFICATE REQUEST" -> (de instanceof PEM); default -> false; }; @@ -332,7 +341,7 @@ public class PEMDecoderTest { static void testFailure(PEMData.Entry entry, Class c) { try { - test(entry.pem(), c, PEMDecoder.of()); + test(entry.pem(), c, d); if (entry.pem().indexOf('\r') != -1) { System.out.println("Found a CR."); } @@ -351,9 +360,11 @@ public class PEMDecoderTest { } static DEREncodable testEncrypted(PEMData.Entry entry) { - PEMDecoder decoder = PEMDecoder.of(); + PEMDecoder decoder; if (!Objects.equals(entry.clazz(), EncryptedPrivateKeyInfo.class)) { - decoder = decoder.withDecryption(entry.password()); + decoder = d.withDecryption(entry.password()); + } else { + decoder = d; } try { @@ -381,9 +392,9 @@ public class PEMDecoderTest { PEMDecoder pemDecoder; if (withFactory) { Provider provider = Security.getProvider(entry.provider()); - pemDecoder = PEMDecoder.of().withFactory(provider); + pemDecoder = d.withFactory(provider); } else { - pemDecoder = PEMDecoder.of(); + pemDecoder = d; } DEREncodable r = test(entry.pem(), entry.clazz(), pemDecoder); System.out.println("PASS (" + entry.name() + ")"); @@ -446,9 +457,8 @@ public class PEMDecoderTest { // result is the same static void testTwoKeys() throws IOException { PublicKey p1, p2; - PEMDecoder pd = PEMDecoder.of(); - p1 = pd.decode(PEMData.rsapub.pem(), RSAPublicKey.class); - p2 = pd.decode(PEMData.rsapub.pem(), RSAPublicKey.class); + p1 = d.decode(PEMData.rsapub.pem(), RSAPublicKey.class); + p2 = d.decode(PEMData.rsapub.pem(), RSAPublicKey.class); if (!Arrays.equals(p1.getEncoded(), p2.getEncoded())) { System.err.println("These two should have matched:"); System.err.println(hex.parseHex(new String(p1.getEncoded()))); @@ -460,7 +470,7 @@ public class PEMDecoderTest { private static void testPKCS8Key(PEMData.Entry entry) { try { - PKCS8Key key = PEMDecoder.of().decode(entry.pem(), PKCS8Key.class); + PKCS8Key key = d.decode(entry.pem(), PKCS8Key.class); PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(key.getEncoded()); @@ -472,7 +482,7 @@ public class PEMDecoderTest { } static void testClass(PEMData.Entry entry, Class clazz) throws IOException { - var pk = PEMDecoder.of().decode(entry.pem(), clazz); + var pk = d.decode(entry.pem(), clazz); } static void testClass(PEMData.Entry entry, Class clazz, boolean pass) @@ -506,7 +516,7 @@ public class PEMDecoderTest { return; } - PKCS8EncodedKeySpec p8 = PEMDecoder.of().decode(entry.pem(), + PKCS8EncodedKeySpec p8 = d.decode(entry.pem(), PKCS8EncodedKeySpec.class); int result = Arrays.compare(entry.der(), p8.getEncoded()); if (result != 0) { @@ -531,8 +541,8 @@ public class PEMDecoderTest { byte[] data = "12345678".getBytes(); PrivateKey privateKey; - DEREncodable d = PEMDecoder.of().decode(entry.pem()); - switch (d) { + DEREncodable der = d.decode(entry.pem()); + switch (der) { case PrivateKey p -> privateKey = p; case KeyPair kp -> privateKey = kp.getPrivate(); case EncryptedPrivateKeyInfo e -> { @@ -563,7 +573,7 @@ public class PEMDecoderTest { }; try { - if (d instanceof PrivateKey) { + if (der instanceof PrivateKey) { s = Signature.getInstance(algorithm); if (spec != null) { s.setParameter(spec); @@ -572,12 +582,12 @@ public class PEMDecoderTest { s.update(data); s.sign(); System.out.println("PASS (Sign): " + entry.name()); - } else if (d instanceof KeyPair) { + } else if (der instanceof KeyPair) { s = Signature.getInstance(algorithm); s.initSign(privateKey); s.update(data); byte[] sig = s.sign(); - s.initVerify(((KeyPair)d).getPublic()); + s.initVerify(((KeyPair)der).getPublic()); s.verify(sig); System.out.println("PASS (Sign/Verify): " + entry.name()); } else { diff --git a/test/jdk/java/security/PEM/PEMEncoderTest.java b/test/jdk/java/security/PEM/PEMEncoderTest.java index 3d1948ba2fe..4b60758c89f 100644 --- a/test/jdk/java/security/PEM/PEMEncoderTest.java +++ b/test/jdk/java/security/PEM/PEMEncoderTest.java @@ -62,6 +62,10 @@ public class PEMEncoderTest { public static void main(String[] args) throws Exception { pkcs8DefaultAlgExpect = args[0]; PEMEncoder encoder = PEMEncoder.of(); + PEMDecoder decoder = PEMDecoder.of(); + EncryptedPrivateKeyInfo ekpi; + KeyPair kp; + PEM pem; // These entries are removed var newEntryList = new ArrayList<>(PEMData.entryList); @@ -70,14 +74,13 @@ public class PEMEncoderTest { newEntryList.remove(PEMData.getEntry("ecsecp384")); keymap = generateObjKeyMap(newEntryList); System.out.println("Same instance re-encode test:"); - keymap.keySet().stream().forEach(key -> test(key, encoder)); + keymap.keySet().forEach(key -> test(key, encoder)); System.out.println("New instance re-encode test:"); - keymap.keySet().stream().forEach(key -> test(key, PEMEncoder.of())); + keymap.keySet().forEach(key -> test(key, PEMEncoder.of())); System.out.println("Same instance re-encode testToString:"); - keymap.keySet().stream().forEach(key -> testToString(key, encoder)); + keymap.keySet().forEach(key -> testToString(key, encoder)); System.out.println("New instance re-encode testToString:"); - keymap.keySet().stream().forEach(key -> testToString(key, - PEMEncoder.of())); + keymap.keySet().forEach(key -> testToString(key, PEMEncoder.of())); System.out.println("Same instance Encoder testEncodedKeySpec:"); testEncodedKeySpec(encoder); System.out.println("New instance Encoder testEncodedKeySpec:"); @@ -86,14 +89,14 @@ public class PEMEncoderTest { testEmptyAndNullKey(encoder); keymap = generateObjKeyMap(PEMData.encryptedList); System.out.println("Same instance Encoder match test:"); - keymap.keySet().stream().forEach(key -> testEncryptedMatch(key, encoder)); + keymap.keySet().forEach(key -> testEncryptedMatch(key, encoder)); System.out.println("Same instance Encoder new withEnc test:"); - keymap.keySet().stream().forEach(key -> testEncrypted(key, encoder)); + keymap.keySet().forEach(key -> testEncrypted(key, encoder)); System.out.println("New instance Encoder and withEnc test:"); - keymap.keySet().stream().forEach(key -> testEncrypted(key, PEMEncoder.of())); + keymap.keySet().forEach(key -> testEncrypted(key, PEMEncoder.of())); System.out.println("Same instance encrypted Encoder test:"); PEMEncoder encEncoder = encoder.withEncryption("fish".toCharArray()); - keymap.keySet().stream().forEach(key -> testSameEncryptor(key, encEncoder)); + keymap.keySet().forEach(key -> testSameEncryptor(key, encEncoder)); try { encoder.withEncryption(null); } catch (Exception e) { @@ -102,17 +105,51 @@ public class PEMEncoderTest { } } - PEMDecoder d = PEMDecoder.of(); - PEMRecord pemRecord = - d.decode(PEMData.ed25519ep8.pem(), PEMRecord.class); - PEMData.checkResults(PEMData.ed25519ep8, pemRecord.toString()); + pem = decoder.decode(PEMData.ed25519ep8.pem(), PEM.class); + PEMData.checkResults(PEMData.ed25519ep8, pem.toString()); - // test PemRecord is encapsulated with PEM header and footer on encoding + // test PEM is encapsulated with PEM header and footer on encoding String[] pemLines = PEMData.ed25519ep8.pem().split("\n"); String[] pemNoHeaderFooter = Arrays.copyOfRange(pemLines, 1, pemLines.length - 1); - PEMRecord pemR = new PEMRecord("ENCRYPTED PRIVATE KEY", String.join("\n", + pem = new PEM("ENCRYPTED PRIVATE KEY", String.join("\n", pemNoHeaderFooter)); - PEMData.checkResults(PEMData.ed25519ep8.pem(), encoder.encodeToString(pemR)); + PEMData.checkResults(PEMData.ed25519ep8.pem(), encoder.encodeToString(pem)); + + // Verify the same private key bytes are returned with an ECDSA private + // key PEM and an encrypted PEM. + kp = decoder.decode(PEMData.ecsecp256.pem(), KeyPair.class); + var origPriv = kp.getPrivate(); + String s = encoder.withEncryption(PEMData.ecsecp256ekpi.password()).encodeToString(kp); + kp = decoder.withDecryption(PEMData.ecsecp256ekpi.password()).decode(s, KeyPair.class); + var newPriv = kp.getPrivate(); + if (!Arrays.equals(origPriv.getEncoded(), newPriv.getEncoded())) { + throw new AssertionError("compare fails"); + } + + // Encoded non-encrypted Keypair + kp = KeyPairGenerator.getInstance("XDH").generateKeyPair(); + s = encoder.encodeToString(kp); + decoder.decode(s, KeyPair.class); + + // EmptyKey for the PrivateKey in a KeyPair. Uses keypair from above. + try { + encoder.encode(new KeyPair(kp.getPublic(), new EmptyKey())); + throw new AssertionError("encoder accepted a empty private key encoding"); + } catch (IllegalArgumentException _) {} + + // NullKey for the PrivateKey in a KeyPair. Uses keypair from above. + try { + encoder.encode(new KeyPair(kp.getPublic(), new NullKey())); + throw new AssertionError("encoder accepted a empty private key encoding"); + } catch (IllegalArgumentException _) {} + + ekpi = decoder.decode(PEMData.ecsecp256ekpi.pem(), + EncryptedPrivateKeyInfo.class); + try { + encoder.withEncryption("blah".toCharArray()).encode(ekpi); + throw new AssertionError("encoder tried to encrypt " + + "an EncryptedPrivateKeyInfo."); + } catch (IllegalArgumentException _) {} } static Map generateObjKeyMap(List list) { @@ -215,7 +252,7 @@ public class PEMEncoderTest { EncryptedPrivateKeyInfo ekpi = PEMDecoder.of().decode(entry.pem(), EncryptedPrivateKeyInfo.class); if (entry.password() != null) { - EncryptedPrivateKeyInfo.encryptKey(pkey, entry.password(), + EncryptedPrivateKeyInfo.encrypt(pkey, entry.password(), Pem.DEFAULT_ALGO, ekpi.getAlgParameters(). getParameterSpec(PBEParameterSpec.class), null); @@ -267,4 +304,16 @@ public class PEMEncoderTest { @Override public byte[] getEncoded() { return new byte[0]; } } + + private static class NullKey implements PrivateKey { + @Override + public String getAlgorithm() { return "Test"; } + + @Override + public String getFormat() { return "Test"; } + + @Override + public byte[] getEncoded() { return null; } + } + } diff --git a/test/jdk/javax/crypto/EncryptedPrivateKeyInfo/EncryptKey.java b/test/jdk/javax/crypto/EncryptedPrivateKeyInfo/Encrypt.java similarity index 76% rename from test/jdk/javax/crypto/EncryptedPrivateKeyInfo/EncryptKey.java rename to test/jdk/javax/crypto/EncryptedPrivateKeyInfo/Encrypt.java index 3fe8cfcfbfa..abeed7c3395 100644 --- a/test/jdk/javax/crypto/EncryptedPrivateKeyInfo/EncryptKey.java +++ b/test/jdk/javax/crypto/EncryptedPrivateKeyInfo/Encrypt.java @@ -48,7 +48,7 @@ import java.util.Arrays; import static jdk.test.lib.Asserts.assertEquals; -public class EncryptKey { +public class Encrypt { private static final String encEdECKey = """ @@ -74,7 +74,7 @@ public class EncryptKey { AlgorithmParameters ap = ekpi.getAlgParameters(); // Test encryptKey(PrivateKey, char[], String, ... ) - var e = EncryptedPrivateKeyInfo.encryptKey(priKey, password, + var e = EncryptedPrivateKeyInfo.encrypt(priKey, password, ekpi.getAlgName(), ap.getParameterSpec(PBEParameterSpec.class), null); if (!Arrays.equals(ekpi.getEncryptedData(), e.getEncryptedData())) { @@ -83,45 +83,53 @@ public class EncryptKey { } // Test encryptKey(PrivateKey, char[], String, ...) with provider - e = EncryptedPrivateKeyInfo.encryptKey(priKey, password, ekpi.getAlgName(), - ap.getParameterSpec(PBEParameterSpec.class), p); + e = EncryptedPrivateKeyInfo.encrypt(priKey, password, ekpi.getAlgName(), + ap.getParameterSpec(PBEParameterSpec.class), p); if (!Arrays.equals(ekpi.getEncryptedData(), e.getEncryptedData())) { throw new AssertionError("encryptKey() didn't match" + - " with expected."); + " with expected."); } // Test encryptKey(PrivateKey, char[], String, ...) with provider and null algorithm - e = EncryptedPrivateKeyInfo.encryptKey(priKey, password, null, null, - p); + e = EncryptedPrivateKeyInfo.encrypt(priKey, password, Pem.DEFAULT_ALGO, null, p); assertEquals(e.getAlgName(), Pem.DEFAULT_ALGO); // Test encryptKey(PrivateKey, Key, String, ...) - e = EncryptedPrivateKeyInfo.encryptKey(priKey, key, ekpi.getAlgName(), - ap.getParameterSpec(PBEParameterSpec.class),null, null); + e = EncryptedPrivateKeyInfo.encrypt(priKey, key, ekpi.getAlgName(), + ap.getParameterSpec(PBEParameterSpec.class), null, null); if (!Arrays.equals(ekpi.getEncryptedData(), e.getEncryptedData())) { throw new AssertionError("encryptKey() didn't match" + " with expected."); } // Test encryptKey(PrivateKey, Key, String, ...) with provider and null random - e = EncryptedPrivateKeyInfo.encryptKey(priKey, key, ekpi.getAlgName(), - ap.getParameterSpec(PBEParameterSpec.class), p, null); + e = EncryptedPrivateKeyInfo.encrypt(priKey, key, ekpi.getAlgName(), + ap.getParameterSpec(PBEParameterSpec.class), p, null); if (!Arrays.equals(ekpi.getEncryptedData(), e.getEncryptedData())) { throw new AssertionError("encryptKey() didn't match" + - " with expected."); + " with expected."); } // Test encryptKey(PrivateKey, Key, String, ...) with provider and SecureRandom - e = EncryptedPrivateKeyInfo.encryptKey(priKey, key, ekpi.getAlgName(), - ap.getParameterSpec(PBEParameterSpec.class), p, new SecureRandom()); + e = EncryptedPrivateKeyInfo.encrypt(priKey, key, ekpi.getAlgName(), + ap.getParameterSpec(PBEParameterSpec.class), p, new SecureRandom()); if (!Arrays.equals(ekpi.getEncryptedData(), e.getEncryptedData())) { throw new AssertionError("encryptKey() didn't match" + - " with expected."); + " with expected."); } // Test encryptKey(PrivateKey, Key, String, ...) with provider and null algorithm - e = EncryptedPrivateKeyInfo.encryptKey(priKey, key, null, null, - p, new SecureRandom()); + e = EncryptedPrivateKeyInfo.encrypt(priKey, key, Pem.DEFAULT_ALGO, null, + p, new SecureRandom()); assertEquals(e.getAlgName(), Pem.DEFAULT_ALGO); + + + SecretKey key2 = new SecretKeySpec("1234567890123456".getBytes(), "AES"); + + // Test encryptKey(PrivateKey, Key, String, ...) with provider and SecureRandom + e = EncryptedPrivateKeyInfo.encrypt(priKey, key2, "AES_128/GCM/NoPadding", + null, p, new SecureRandom()); + PrivateKey key3 = e.getKey(key2, null); + assertEquals(key3, priKey, "AES encryption failed"); } } diff --git a/test/jdk/javax/crypto/EncryptedPrivateKeyInfo/GetKey.java b/test/jdk/javax/crypto/EncryptedPrivateKeyInfo/GetKey.java index b1917ffa84d..36e6b02faba 100644 --- a/test/jdk/javax/crypto/EncryptedPrivateKeyInfo/GetKey.java +++ b/test/jdk/javax/crypto/EncryptedPrivateKeyInfo/GetKey.java @@ -71,7 +71,8 @@ public class GetKey { passwdText.getBytes(), "PBE"); public static void main(String[] args) throws Exception { - Provider p = Security.getProvider(System.getProperty("test.provider.name", "SunJCE")); + Provider p = Security.getProvider( + System.getProperty("test.provider.name", "SunJCE")); EncryptedPrivateKeyInfo ekpi = PEMDecoder.of().decode(encEdECKey, EncryptedPrivateKeyInfo.class); diff --git a/test/jdk/javax/crypto/EncryptedPrivateKeyInfo/GetKeyPair.java b/test/jdk/javax/crypto/EncryptedPrivateKeyInfo/GetKeyPair.java new file mode 100644 index 00000000000..d35197e1971 --- /dev/null +++ b/test/jdk/javax/crypto/EncryptedPrivateKeyInfo/GetKeyPair.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2025, 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. 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. + */ + +/* + * @test + * @bug 8360563 + * @library /test/lib + * @summary Testing getKeyPair using ML-KEM + * @enablePreview + * @modules java.base/sun.security.util + */ + +import jdk.test.lib.Asserts; +import sun.security.util.DerValue; +import javax.crypto.EncryptedPrivateKeyInfo; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.security.*; + +/* + * This generates an ML-KEM key pair and makes it into PEM data. By using + * PEM, it constructs a OneAsymmetricKey structure that combines + * the public key into the private key encoding. Decode the PEM data into + * a KeyPair and an EKPI for verification. + * + * The original private key does not have the public key encapsulated, so it + * cannot be used for verification. + * + * Verify the decoded PEM KeyPair and EKPI.getKeyPair() return matching public + * and private keys encodings; as well as, verify the original public key + * matches. + */ + +public class GetKeyPair { + private static final String passwdText = "fish"; + private static final char[] password = passwdText.toCharArray(); + private static final SecretKey key = new SecretKeySpec( + passwdText.getBytes(), "PBE"); + static byte[] keyOrigPub, keyOrigPriv; + + public static void main(String[] args) throws Exception { + Provider p = Security.getProvider( + System.getProperty("test.provider.name", "SunJCE")); + + KeyPairGenerator kpg = KeyPairGenerator.getInstance("ML-KEM"); + KeyPair kpOrig = kpg.generateKeyPair(); + keyOrigPub = kpOrig.getPublic().getEncoded(); + keyOrigPriv = getPrivateKey(kpOrig.getPrivate()); + + // Encode the KeyPair into PEM, constructing an OneAsymmetricKey encoding + String pem = PEMEncoder.of().withEncryption(password). + encodeToString(kpOrig); + // Decode to a KeyPair from the generated PEM for verification. + KeyPair mlkemKP = PEMDecoder.of().withDecryption(password). + decode(pem, KeyPair.class); + + // Check decoded public key pair with original. + Asserts.assertEqualsByteArray(mlkemKP.getPublic().getEncoded(), + keyOrigPub, "Initial PublicKey compare didn't match."); + byte[] priv = getPrivateKey(mlkemKP.getPrivate()); + Asserts.assertEqualsByteArray(priv, keyOrigPriv, + "Initial PrivateKey compare didn't match"); + + // Decode to a EncryptedPrivateKeyInfo. + EncryptedPrivateKeyInfo ekpi = PEMDecoder.of().decode(pem, + EncryptedPrivateKeyInfo.class); + + // Test getKeyPair(password) + System.out.print("Testing getKeyPair(char[]): "); + arrayCheck(ekpi.getKeyPair(password)); + + // Test getKeyPair(key, provider) provider null + System.out.print("Testing getKeyPair(key, null): "); + arrayCheck(ekpi.getKeyPair(key, null)); + + // Test getKeyPair(key, provider) provider SunJCE + System.out.print("Testing getKeyPair(key, SunJCE): "); + arrayCheck(ekpi.getKeyPair(key, p)); + } + + static void arrayCheck(KeyPair kp) throws Exception { + Asserts.assertEqualsByteArray(getPrivateKey(kp.getPrivate()), keyOrigPriv, + "PrivateKey didn't match with expected."); + Asserts.assertEqualsByteArray(kp.getPublic().getEncoded(), keyOrigPub, + "PublicKey didn't match with expected."); + System.out.println("PASS"); + } + + static byte[] getPrivateKey(PrivateKey p) throws Exception{ + var val = new DerValue(p.getEncoded()); + // Get version + val.data.getInteger(); + // Get AlgorithmID + val.data.getDerValue(); + // Return PrivateKey + return val.data.getOctetString(); + } +} diff --git a/test/jdk/javax/net/ssl/interop/ClientHelloInterOp.java b/test/jdk/javax/net/ssl/interop/ClientHelloInterOp.java index 808d137223e..80e50d01160 100644 --- a/test/jdk/javax/net/ssl/interop/ClientHelloInterOp.java +++ b/test/jdk/javax/net/ssl/interop/ClientHelloInterOp.java @@ -31,7 +31,6 @@ import javax.net.ssl.TrustManagerFactory; import java.nio.ByteBuffer; import java.security.KeyStore; import java.security.PEMDecoder; -import java.security.PEMRecord; import java.security.PrivateKey; import java.security.cert.Certificate; import java.security.cert.X509Certificate; From 7aff8e15ba59b1e23d2e62c200d52a26da1a2030 Mon Sep 17 00:00:00 2001 From: Chen Liang Date: Fri, 14 Nov 2025 22:55:28 +0000 Subject: [PATCH 064/418] 8371319: java.lang.reflect.Method#equals doesn't short-circuit with same instances Reviewed-by: jvernee --- .../classes/java/lang/reflect/Executable.java | 4 + .../reflect/ExecutableCompareBenchmark.java | 84 +++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 test/micro/org/openjdk/bench/java/lang/reflect/ExecutableCompareBenchmark.java diff --git a/src/java.base/share/classes/java/lang/reflect/Executable.java b/src/java.base/share/classes/java/lang/reflect/Executable.java index 2f6f6cca89f..a22d0fa8076 100644 --- a/src/java.base/share/classes/java/lang/reflect/Executable.java +++ b/src/java.base/share/classes/java/lang/reflect/Executable.java @@ -70,6 +70,10 @@ public abstract sealed class Executable extends AccessibleObject abstract ConstructorRepository getGenericInfo(); boolean equalParamTypes(Class[] params1, Class[] params2) { + // The parameter arrays are trusted and the same for a root and all leaf + // copies. Thus, == on arrays is more useful than == on Executable. + if (params1 == params2) + return true; /* Avoid unnecessary cloning */ if (params1.length == params2.length) { for (int i = 0; i < params1.length; i++) { diff --git a/test/micro/org/openjdk/bench/java/lang/reflect/ExecutableCompareBenchmark.java b/test/micro/org/openjdk/bench/java/lang/reflect/ExecutableCompareBenchmark.java new file mode 100644 index 00000000000..1413938dbd5 --- /dev/null +++ b/test/micro/org/openjdk/bench/java/lang/reflect/ExecutableCompareBenchmark.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2025, 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 org.openjdk.bench.java.lang.reflect; + +import java.lang.reflect.Method; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +/** + * Benchmark measuring the speed of Method and Constructor comparison. + */ +@BenchmarkMode(Mode.AverageTime) +@State(Scope.Benchmark) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@Fork(1) +@Warmup(iterations = 3, time = 2, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS) +public class ExecutableCompareBenchmark { + Method appendRange; + Method appendRangeDup; + Method appendFull; + + public ExecutableCompareBenchmark() { + try { + appendRange = Appendable.class.getMethod("append", CharSequence.class, int.class, int.class); + appendRangeDup = Appendable.class.getMethod("append", CharSequence.class, int.class, int.class); + if (appendRange == appendRangeDup || !appendRange.equals(appendRangeDup)) { + throw new IllegalStateException(); + } + appendFull = Appendable.class.getMethod("append", CharSequence.class); + } catch (ReflectiveOperationException ex) { + throw new RuntimeException(ex); + } + } + + // Comparing identical Method objects. + @Benchmark + public boolean sameMethodObject() { + return appendRange.equals(appendRange); + } + + // Comparing equal Method objects with potentially different access + // suppression status. + @Benchmark + public boolean equalMethods() { + return appendRange.equals(appendRangeDup); + } + + // Comparing Method objects with only different parameter types; + // The declaring class, name, and the return type are the same. + @Benchmark + public boolean distinctParams() { + return appendRange.equals(appendFull); + } +} \ No newline at end of file From bc928c814b5ea70505e362a643e18664e119bce3 Mon Sep 17 00:00:00 2001 From: Archie Cobbs Date: Fri, 14 Nov 2025 23:53:31 +0000 Subject: [PATCH 065/418] 5038439: Warning message for literal shift amounts outside the canonical domain Reviewed-by: darcy, jlahoda --- .../propertiesparser/parser/MessageType.java | 1 + .../com/sun/tools/javac/comp/Attr.java | 2 + .../com/sun/tools/javac/comp/Check.java | 31 +++++++ .../tools/javac/resources/compiler.properties | 5 ++ .../tools/javac/resources/javac.properties | 2 +- .../share/classes/module-info.java | 5 +- src/jdk.compiler/share/man/javac.md | 2 +- .../diags/examples/BitShiftOutOfRange.java | 31 +++++++ .../tools/javac/lint/ShiftOutOfRange.java | 83 +++++++++++++++++++ .../tools/javac/lint/ShiftOutOfRange.out | 29 +++++++ 10 files changed, 187 insertions(+), 4 deletions(-) create mode 100644 test/langtools/tools/javac/diags/examples/BitShiftOutOfRange.java create mode 100644 test/langtools/tools/javac/lint/ShiftOutOfRange.java create mode 100644 test/langtools/tools/javac/lint/ShiftOutOfRange.out diff --git a/make/langtools/tools/propertiesparser/parser/MessageType.java b/make/langtools/tools/propertiesparser/parser/MessageType.java index a4ea0ddc3c0..4b7064e3872 100644 --- a/make/langtools/tools/propertiesparser/parser/MessageType.java +++ b/make/langtools/tools/propertiesparser/parser/MessageType.java @@ -84,6 +84,7 @@ public interface MessageType { FILE_OBJECT("file object", "JavaFileObject", "javax.tools"), PATH("path", "Path", "java.nio.file"), NAME("name", "Name", "com.sun.tools.javac.util"), + LONG("long", "long", null), NUMBER("number", "int", null), OPTION_NAME("option name", "Option", "com.sun.tools.javac.main"), PROFILE("profile", "Profile", "com.sun.tools.javac.jvm"), diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java index 4a8a8d4a324..a45f7466f9e 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java @@ -4001,6 +4001,7 @@ public class Attr extends JCTree.Visitor { operator.type.getReturnType(), owntype); chk.checkLossOfPrecision(tree.rhs.pos(), operand, owntype); + chk.checkOutOfRangeShift(tree.rhs.pos(), operator, operand); } result = check(tree, owntype, KindSelector.VAL, resultInfo); } @@ -4091,6 +4092,7 @@ public class Attr extends JCTree.Visitor { } chk.checkDivZero(tree.rhs.pos(), operator, right); + chk.checkOutOfRangeShift(tree.rhs.pos(), operator, right); } result = check(tree, owntype, KindSelector.VAL, resultInfo); } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java index da7db138604..9098568f42a 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java @@ -4041,6 +4041,37 @@ public class Check { } } + /** + * Check for bit shifts using an out-of-range bit count. + * @param pos Position for error reporting. + * @param operator The operator for the expression + * @param operand The right hand operand for the expression + */ + void checkOutOfRangeShift(final DiagnosticPosition pos, Symbol operator, Type operand) { + if (operand.constValue() instanceof Number shiftAmount) { + Type targetType; + int maximumShift; + switch (((OperatorSymbol)operator).opcode) { + case ByteCodes.ishl, ByteCodes.ishr, ByteCodes.iushr, ByteCodes.ishll, ByteCodes.ishrl, ByteCodes.iushrl -> { + targetType = syms.intType; + maximumShift = 0x1f; + } + case ByteCodes.lshl, ByteCodes.lshr, ByteCodes.lushr, ByteCodes.lshll, ByteCodes.lshrl, ByteCodes.lushrl -> { + targetType = syms.longType; + maximumShift = 0x3f; + } + default -> { + return; + } + } + long specifiedShift = shiftAmount.longValue(); + if (specifiedShift > maximumShift || specifiedShift < -maximumShift) { + int actualShift = (int)specifiedShift & (maximumShift - 1); + log.warning(pos, LintWarnings.BitShiftOutOfRange(targetType, specifiedShift, actualShift)); + } + } + } + /** * Check for possible loss of precission * @param pos Position for error reporting. diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties index 6318476fca4..95a7546e2b3 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties @@ -2464,6 +2464,11 @@ compiler.err.no.zipfs.for.archive=\ compiler.warn.div.zero=\ division by zero +# 0: type, 1: long, 2: number +# lint: lossy-conversions +compiler.warn.bit.shift.out.of.range=\ + shifting {0} by {1} bits is equivalent to shifting by {2} bit(s) + # lint: empty compiler.warn.empty.if=\ empty statement after if diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties index 6d4276c794b..d5ee9469d22 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties @@ -228,7 +228,7 @@ javac.opt.Xlint.desc.incubating=\ Warn about use of incubating modules. javac.opt.Xlint.desc.lossy-conversions=\ - Warn about possible lossy conversions in compound assignment. + Warn about possible lossy conversions in compound assignment and bit shift operations. javac.opt.Xlint.desc.module=\ Warn about module system related issues. diff --git a/src/jdk.compiler/share/classes/module-info.java b/src/jdk.compiler/share/classes/module-info.java index 46ada3a12e7..4aa60a3dffe 100644 --- a/src/jdk.compiler/share/classes/module-info.java +++ b/src/jdk.compiler/share/classes/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2025, 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 @@ -165,7 +165,8 @@ import javax.tools.StandardLocation; * the next * {@code finally} {@code finally} clauses that do not terminate normally * {@code identity} use of a value-based class where an identity class is expected - * {@code lossy-conversions} possible lossy conversions in compound assignment + * {@code lossy-conversions} possible lossy conversions in compound assignments or bit shifts + * (more than \u00B131 bits for integers or \u00B163 bits for longs) * {@code missing-explicit-ctor} missing explicit constructors in public and protected classes * in exported packages * {@code module} module system related issues diff --git a/src/jdk.compiler/share/man/javac.md b/src/jdk.compiler/share/man/javac.md index 1822931bf40..c749ca4da10 100644 --- a/src/jdk.compiler/share/man/javac.md +++ b/src/jdk.compiler/share/man/javac.md @@ -611,7 +611,7 @@ file system locations may be directories, JAR files or JMOD files. - `incubating`: Warns about the use of incubating modules. - `lossy-conversions`: Warns about possible lossy conversions - in compound assignment. + in compound assignment and bit shift operations. - `missing-explicit-ctor`: Warns about missing explicit constructors in public and protected classes in exported packages. diff --git a/test/langtools/tools/javac/diags/examples/BitShiftOutOfRange.java b/test/langtools/tools/javac/diags/examples/BitShiftOutOfRange.java new file mode 100644 index 00000000000..2855c6f3023 --- /dev/null +++ b/test/langtools/tools/javac/diags/examples/BitShiftOutOfRange.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2025, 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. + */ + +// key: compiler.warn.bit.shift.out.of.range +// options: -Xlint:lossy-conversions + +class BitShiftOutOfRange { + int m(int a) { + return a << 32; + } +} diff --git a/test/langtools/tools/javac/lint/ShiftOutOfRange.java b/test/langtools/tools/javac/lint/ShiftOutOfRange.java new file mode 100644 index 00000000000..e066c4469b9 --- /dev/null +++ b/test/langtools/tools/javac/lint/ShiftOutOfRange.java @@ -0,0 +1,83 @@ +/* + * @test /nodynamiccopyright/ + * @bug 5038439 + * @summary Verify warnings about bit shifts using out-of-range shift amounts + * @compile/ref=ShiftOutOfRange.out -XDrawDiagnostics -Xlint:lossy-conversions ShiftOutOfRange.java + */ + +public class ShiftOutOfRange { + + public void shiftInt() { + int a = 123; + + // These should generate warnings + a = a << (byte)-32; + a = a >> (short)-32; + a = a >>> -32; + a <<= -32L; + a >>= (byte)-32; + a >>>= (short)-32; + + // These should not generate warnings + a = a << (byte)-31; + a = a >> (short)-23; + a = a >>> -17; + a <<= -13L; + a >>= (byte)-1; + a >>>= (short)0; + a = a << (byte)0; + a = a >> (char)7; + a = a >>> (short)13; + a <<= 17; + a >>= (long)23; + a >>>= (byte)31; + a <<= hashCode(); + a >>= hashCode(); + a >>>= hashCode(); + + // These should generate warnings + a = a << (byte)32; + a = a >> (char)32; + a = a >>> (short)32; + a <<= 32; + a >>= (long)32; + a >>>= (byte)32; + } + + public void shiftLong() { + long a = 123; + + // These should generate warnings + a = a << (byte)-64; + a = a >> (short)-64; + a = a >>> -64; + a <<= -64L; + a >>= (byte)-64L; + a >>>= (short)-64; + + // These should not generate warnings + a = a << (byte)-63; + a = a >> (short)-47; + a = a >>> -34; + a <<= -25L; + a >>= (byte)-15; + a >>>= (short)0; + a = a << (byte)0; + a = a >> (char)15; + a = a >>> (short)25; + a <<= 34; + a >>= (long)47; + a >>>= (byte)63; + a <<= hashCode(); + a >>= hashCode(); + a >>>= hashCode(); + + // These should generate warnings + a = a << (byte)64; + a = a >> (char)64; + a = a >>> (short)64; + a <<= 64; + a >>= (long)64; + a >>>= (byte)64; + } +} diff --git a/test/langtools/tools/javac/lint/ShiftOutOfRange.out b/test/langtools/tools/javac/lint/ShiftOutOfRange.out new file mode 100644 index 00000000000..7aee0b014af --- /dev/null +++ b/test/langtools/tools/javac/lint/ShiftOutOfRange.out @@ -0,0 +1,29 @@ +ShiftOutOfRange.java:14:18: compiler.warn.bit.shift.out.of.range: int, -32, 0 +ShiftOutOfRange.java:15:18: compiler.warn.bit.shift.out.of.range: int, -32, 0 +ShiftOutOfRange.java:16:19: compiler.warn.bit.shift.out.of.range: int, -32, 0 +ShiftOutOfRange.java:17:15: compiler.warn.possible.loss.of.precision: long, int +ShiftOutOfRange.java:17:15: compiler.warn.bit.shift.out.of.range: int, -32, 0 +ShiftOutOfRange.java:18:15: compiler.warn.bit.shift.out.of.range: int, -32, 0 +ShiftOutOfRange.java:19:16: compiler.warn.bit.shift.out.of.range: int, -32, 0 +ShiftOutOfRange.java:25:15: compiler.warn.possible.loss.of.precision: long, int +ShiftOutOfRange.java:32:15: compiler.warn.possible.loss.of.precision: long, int +ShiftOutOfRange.java:39:18: compiler.warn.bit.shift.out.of.range: int, 32, 0 +ShiftOutOfRange.java:40:18: compiler.warn.bit.shift.out.of.range: int, 32, 0 +ShiftOutOfRange.java:41:19: compiler.warn.bit.shift.out.of.range: int, 32, 0 +ShiftOutOfRange.java:42:15: compiler.warn.bit.shift.out.of.range: int, 32, 0 +ShiftOutOfRange.java:43:15: compiler.warn.possible.loss.of.precision: long, int +ShiftOutOfRange.java:43:15: compiler.warn.bit.shift.out.of.range: int, 32, 0 +ShiftOutOfRange.java:44:16: compiler.warn.bit.shift.out.of.range: int, 32, 0 +ShiftOutOfRange.java:51:18: compiler.warn.bit.shift.out.of.range: long, -64, 0 +ShiftOutOfRange.java:52:18: compiler.warn.bit.shift.out.of.range: long, -64, 0 +ShiftOutOfRange.java:53:19: compiler.warn.bit.shift.out.of.range: long, -64, 0 +ShiftOutOfRange.java:54:15: compiler.warn.bit.shift.out.of.range: long, -64, 0 +ShiftOutOfRange.java:55:15: compiler.warn.bit.shift.out.of.range: long, -64, 0 +ShiftOutOfRange.java:56:16: compiler.warn.bit.shift.out.of.range: long, -64, 0 +ShiftOutOfRange.java:76:18: compiler.warn.bit.shift.out.of.range: long, 64, 0 +ShiftOutOfRange.java:77:18: compiler.warn.bit.shift.out.of.range: long, 64, 0 +ShiftOutOfRange.java:78:19: compiler.warn.bit.shift.out.of.range: long, 64, 0 +ShiftOutOfRange.java:79:15: compiler.warn.bit.shift.out.of.range: long, 64, 0 +ShiftOutOfRange.java:80:15: compiler.warn.bit.shift.out.of.range: long, 64, 0 +ShiftOutOfRange.java:81:16: compiler.warn.bit.shift.out.of.range: long, 64, 0 +28 warnings From 7c169c9814a694126f524e8941b1035e6695900c Mon Sep 17 00:00:00 2001 From: Harshitha Onkar Date: Fri, 14 Nov 2025 23:53:44 +0000 Subject: [PATCH 066/418] 8365426: [macos26] Graphics2D tests fail on new macOS 26 Reviewed-by: kizune, dnguyen --- test/jdk/java/awt/Graphics2D/CopyAreaOOB.java | 67 ++++++++++++------- 1 file changed, 42 insertions(+), 25 deletions(-) diff --git a/test/jdk/java/awt/Graphics2D/CopyAreaOOB.java b/test/jdk/java/awt/Graphics2D/CopyAreaOOB.java index 27a1a3a868a..9ee44037f14 100644 --- a/test/jdk/java/awt/Graphics2D/CopyAreaOOB.java +++ b/test/jdk/java/awt/Graphics2D/CopyAreaOOB.java @@ -27,6 +27,8 @@ * @bug 6430601 8198613 * @summary Verifies that copyArea() works properly when the * destination parameters are outside the destination bounds. + * @library /test/lib + * @build jdk.test.lib.Platform * @run main/othervm CopyAreaOOB */ @@ -49,23 +51,29 @@ import java.awt.image.RenderedImage; import java.io.File; import java.io.IOException; import java.util.List; - import javax.imageio.ImageIO; +import jdk.test.lib.Platform; public class CopyAreaOOB extends Canvas { + private static final int PRIMARY_TOLERANCE = 2; + private static int TOLERANCE = 30; + private static final boolean DEBUG = false; private static Frame frame; private static Robot robot; private static BufferedImage captureImg; - private static StringBuffer errorLog = new StringBuffer(); + private static final StringBuffer errorLog = new StringBuffer(); private static final Point OFF_FRAME_LOC = new Point(50, 50); private static final int SIZE = 400; public static void main(String[] args) throws Exception { try { - robot = new Robot(); + if (!Platform.isOSX()) { + TOLERANCE = PRIMARY_TOLERANCE; + } + robot = new Robot(); // added to move mouse pointer away from test UI // so that it is not captured in the screenshot robot.mouseMove(OFF_FRAME_LOC.x, OFF_FRAME_LOC.y); @@ -77,7 +85,7 @@ public class CopyAreaOOB extends Canvas { if (!errorLog.isEmpty()) { saveImages(); - throw new RuntimeException("Test failed: \n" + errorLog.toString()); + throw new RuntimeException("Test failed for the following region(s)" + errorLog); } } finally { if (frame != null) { @@ -101,13 +109,13 @@ public class CopyAreaOOB extends Canvas { int h = getHeight(); Graphics2D g2d = (Graphics2D)g; - g2d.setColor(Color.black); + g2d.setColor(Color.BLUE); g2d.fillRect(0, 0, w, h); - g2d.setColor(Color.green); + g2d.setColor(Color.GREEN); g2d.fillRect(0, 0, w, 10); - g2d.setColor(Color.red); + g2d.setColor(Color.RED); g2d.fillRect(0, 10, 50, h - 10); // copy the region such that part of it goes below the bottom of the @@ -123,12 +131,12 @@ public class CopyAreaOOB extends Canvas { captureImg = robot.createScreenCapture(rect); // Test pixels - testRegion("green", 0, 0, 400, 10, 0xff00ff00); - testRegion("original-red", 0, 10, 50, 400, 0xffff0000); - testRegion("background", 50, 10, 60, 400, 0xff000000); - testRegion("in-between", 60, 10, 110, 20, 0xff000000); - testRegion("copied-red", 60, 20, 110, 400, 0xffff0000); - testRegion("background", 110, 10, 400, 400, 0xff000000); + testRegion("green", 0, 0, 400, 10, Color.GREEN); + testRegion("original-red", 0, 10, 50, 400, Color.RED); + testRegion("background", 50, 10, 60, 400, Color.BLUE); + testRegion("in-between", 60, 10, 110, 20, Color.BLUE); + testRegion("copied-red", 60, 20, 110, 400, Color.RED); + testRegion("background", 110, 10, 400, 400, Color.BLUE); } public Dimension getPreferredSize() { @@ -137,24 +145,23 @@ public class CopyAreaOOB extends Canvas { private static void testRegion(String region, int x1, int y1, int x2, int y2, - int expected) { - System.out.print("Test region: " + region); + Color expected) { for (int y = y1; y < y2; y++) { for (int x = x1; x < x2; x++) { - int actual = captureImg.getRGB(x, y); - if (actual != expected) { - System.out.print(" Status: FAILED\n"); - errorLog.append("Test failed for " + region - + " region at x: " + x + " y: " + y - + " (expected: " - + Integer.toHexString(expected) - + " actual: " - + Integer.toHexString(actual) + ")\n"); + Color actual = new Color(captureImg.getRGB(x, y)); + if (DEBUG) { + System.out.println("Actual color: " + actual); + } + if (!compareColor(expected, actual)) { + errorLog.append("\nTest region: " + region + " Status: FAILED!!!\n"); + errorLog.append("Test failed at x : " + x + " y : " + y + "\n" + + "Expected Color: " + expected + "\n" + + "Actual Color: " + actual + "\n"); return; } } } - System.out.print(" Status: PASSED\n"); + System.out.println("Test region: " + region + " Status: PASSED"); } private static void saveImages() { @@ -173,4 +180,14 @@ public class CopyAreaOOB extends Canvas { System.err.println("Can't write image " + e); } } + + private static boolean compareColor(Color expected, Color actual) { + return Math.abs(expected.getRed() - actual.getRed()) + < (expected.equals(Color.RED) ? PRIMARY_TOLERANCE : TOLERANCE) + && Math.abs(expected.getGreen() - actual.getGreen()) + < (expected.equals(Color.GREEN) ? PRIMARY_TOLERANCE : TOLERANCE) + && Math.abs(expected.getBlue() - actual.getBlue()) + < (expected.equals(Color.BLUE) ? PRIMARY_TOLERANCE : TOLERANCE); + } } + From f971ee5ea07e3e1c0efe447a416e7242f5e46a16 Mon Sep 17 00:00:00 2001 From: Serguei Spitsyn Date: Sat, 15 Nov 2025 01:50:47 +0000 Subject: [PATCH 067/418] 8349192: jvmti/scenarios/contention/TC05/tc05t001 fails: ERROR: tc05t001.cpp, 281: (waitedThreadCpuTime - waitThreadCpuTime) < (EXPECTED_ACCURACY * 1000000) Reviewed-by: cjplummer, lmesnik --- .../nsk/jvmti/scenarios/contention/TC05/tc05t001/tc05t001.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/contention/TC05/tc05t001/tc05t001.cpp b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/contention/TC05/tc05t001/tc05t001.cpp index 9680d01847a..df67828d28b 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/contention/TC05/tc05t001/tc05t001.cpp +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/contention/TC05/tc05t001/tc05t001.cpp @@ -42,7 +42,7 @@ static const jlong EXPECTED_TIMEOUT_ACCURACY_NS = 300000; #if (defined(WIN32) || defined(_WIN32)) static const jlong EXPECTED_ACCURACY = 16; // 16ms is longest clock update interval #else -static const jlong EXPECTED_ACCURACY = 10; // high frequency clock updates expected +static const jlong EXPECTED_ACCURACY = 32; // high frequency clock updates expected #endif /* scaffold objects */ From 6042c9a6f0c25c141a74d72ad462189da7f9e625 Mon Sep 17 00:00:00 2001 From: Anthony Scarpino Date: Sat, 15 Nov 2025 02:46:30 +0000 Subject: [PATCH 068/418] 8371934: EncryptedPrivateKeyInfo methods need @since updates Reviewed-by: jnimeh --- .../share/classes/javax/crypto/EncryptedPrivateKeyInfo.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/java.base/share/classes/javax/crypto/EncryptedPrivateKeyInfo.java b/src/java.base/share/classes/javax/crypto/EncryptedPrivateKeyInfo.java index 15933183e5f..dc3c4c14b27 100644 --- a/src/java.base/share/classes/javax/crypto/EncryptedPrivateKeyInfo.java +++ b/src/java.base/share/classes/javax/crypto/EncryptedPrivateKeyInfo.java @@ -365,7 +365,7 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable { * not supported by any provider, or if an error occurs during * encryption. * - * @since 25 + * @since 26 */ @PreviewFeature(feature = PreviewFeature.Feature.PEM_API) public static EncryptedPrivateKeyInfo encrypt(DEREncodable de, @@ -404,7 +404,7 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable { * defines the default encryption algorithm. The {@code AlgorithmParameterSpec} * defaults are determined by the provider. * - * @since 25 + * @since 26 */ @PreviewFeature(feature = PreviewFeature.Feature.PEM_API) public static EncryptedPrivateKeyInfo encrypt(DEREncodable de, @@ -442,7 +442,7 @@ public non-sealed class EncryptedPrivateKeyInfo implements DEREncodable { * {@code algorithm} or {@code params} are not supported by any * provider, or if an error occurs during encryption * - * @since 25 + * @since 26 */ @PreviewFeature(feature = PreviewFeature.Feature.PEM_API) public static EncryptedPrivateKeyInfo encrypt(DEREncodable de, From f6c90fe8f9986b02797ba1f967c71a592a892266 Mon Sep 17 00:00:00 2001 From: Alan Bateman Date: Sat, 15 Nov 2025 08:15:50 +0000 Subject: [PATCH 069/418] 8371226: Thread class description needs section on Thread Interruption Reviewed-by: prappo, vklang, liach, rriggs --- .../java/lang/InterruptedException.java | 29 +++---- .../share/classes/java/lang/Thread.java | 77 ++++++++++++++++--- 2 files changed, 79 insertions(+), 27 deletions(-) diff --git a/src/java.base/share/classes/java/lang/InterruptedException.java b/src/java.base/share/classes/java/lang/InterruptedException.java index ef13e5f94e3..e8cf3d28bc5 100644 --- a/src/java.base/share/classes/java/lang/InterruptedException.java +++ b/src/java.base/share/classes/java/lang/InterruptedException.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1995, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1995, 2025, 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,24 +26,19 @@ package java.lang; /** - * Thrown when a thread is waiting, sleeping, or otherwise occupied, - * and the thread is interrupted, either before or during the activity. - * Occasionally a method may wish to test whether the current - * thread has been interrupted, and if so, to immediately throw - * this exception. The following code can be used to achieve - * this effect: - * {@snippet lang=java : - * if (Thread.interrupted()) // Clears interrupted status! - * throw new InterruptedException(); - * } + * Thrown when a thread executing a blocking method is {@linkplain Thread#interrupt() + * interrupted}. {@link Thread#sleep(long) Thread.sleep}, {@link Object#wait() + * Object.wait} and many other blocking methods throw this exception if interrupted. + * + *

Blocking methods that throw {@code InterruptedException} clear the thread's + * interrupted status before throwing the exception. Code that catches {@code + * InterruptedException} should rethrow the exception, or restore the current thread's + * interrupted status, with {@link Thread#currentThread() + * Thread.currentThread()}.{@link Thread#interrupt() interrupt()}, before continuing + * normally or handling it by throwing another type of exception. * * @author Frank Yellin - * @see java.lang.Object#wait() - * @see java.lang.Object#wait(long) - * @see java.lang.Object#wait(long, int) - * @see java.lang.Thread#sleep(long) - * @see java.lang.Thread#interrupt() - * @see java.lang.Thread#interrupted() + * @see Thread##thread-interruption Thread Interruption * @since 1.0 */ public class InterruptedException extends Exception { diff --git a/src/java.base/share/classes/java/lang/Thread.java b/src/java.base/share/classes/java/lang/Thread.java index ace29f30a56..798ded2b941 100644 --- a/src/java.base/share/classes/java/lang/Thread.java +++ b/src/java.base/share/classes/java/lang/Thread.java @@ -63,8 +63,9 @@ import static java.util.concurrent.TimeUnit.NANOSECONDS; *

A thread terminates if either its {@code run} method completes normally, * or if its {@code run} method completes abruptly and the appropriate {@linkplain * Thread.UncaughtExceptionHandler uncaught exception handler} completes normally or - * abruptly. With no code left to run, the thread has completed execution. The - * {@link #join() join} method can be used to wait for a thread to terminate. + * abruptly. With no code left to run, the thread has completed execution. The {@link + * #isAlive isAlive} method can be used to test if a started thread has terminated. + * The {@link #join() join} method can be used to wait for a thread to terminate. * *

Threads have a unique {@linkplain #threadId() identifier} and a {@linkplain * #getName() name}. The identifier is generated when a {@code Thread} is created @@ -79,7 +80,7 @@ import static java.util.concurrent.TimeUnit.NANOSECONDS; * {@code Thread} supports a special inheritable thread local for the thread * {@linkplain #getContextClassLoader() context-class-loader}. * - *

Platform threads

+ *

Platform Threads

*

{@code Thread} supports the creation of platform threads that are * typically mapped 1:1 to kernel threads scheduled by the operating system. * Platform threads will usually have a large stack and other resources that are @@ -99,7 +100,7 @@ import static java.util.concurrent.TimeUnit.NANOSECONDS; * #getPriority() thread priority} and are members of a {@linkplain ThreadGroup * thread group}. * - *

Virtual threads

+ *

Virtual Threads

*

{@code Thread} also supports the creation of virtual threads. * Virtual threads are typically user-mode threads scheduled by the Java * runtime rather than the operating system. Virtual threads will typically require @@ -124,7 +125,7 @@ import static java.util.concurrent.TimeUnit.NANOSECONDS; * Virtual threads have a fixed {@linkplain #getPriority() thread priority} * that cannot be changed. * - *

Creating and starting threads

+ *

Creating And Starting Threads

* *

{@code Thread} defines public constructors for creating platform threads and * the {@link #start() start} method to schedule threads to execute. {@code Thread} @@ -153,7 +154,7 @@ import static java.util.concurrent.TimeUnit.NANOSECONDS; * ThreadFactory factory = Thread.ofVirtual().factory(); * } * - *

Inheritance when creating threads

+ *

Inheritance When Creating Threads

* A {@code Thread} created with one of the public constructors inherits the daemon * status and thread priority from the parent thread at the time that the child {@code * Thread} is created. The {@linkplain ThreadGroup thread group} is also inherited when @@ -171,7 +172,45 @@ import static java.util.concurrent.TimeUnit.NANOSECONDS; * {@link Builder#inheritInheritableThreadLocals(boolean) inheritInheritableThreadLocals} * method can be used to select if the initial values are inherited. * - *

Unless otherwise specified, passing a {@code null} argument to a constructor + *

Thread Interruption

+ * A {@code Thread} has an interrupted status which serves as a "request" for + * code executing in the thread to "stop or cancel its current activity". The interrupted + * status is set by invoking the target thread's {@link #interrupt()} method. Many methods + * that cause a thread to block or wait are interruptible, meaning they detect + * that the thread's interrupted status is set and cause execution to return early from + * the method, usually by throwing an exception. + * + *

If a thread executing {@link #sleep(long) Thread.sleep} or {@link Object#wait() + * Object.wait} is interrupted then it causes the method to throw {@link InterruptedException}. + * Methods that throw {@code InterruptedException} do so after first clearing the + * interrupted status. Code that catches {@code InterruptedException} should rethrow the + * exception, or restore the current thread's interrupted status, with + * {@link #currentThread() Thread.currentThread()}.{@link #interrupt()}, before + * continuing normally or handling it by throwing another type of exception. Code that + * throws another type of exception with the {@code InterruptedException} as {@linkplain + * Throwable#getCause() cause}, or the {@code InterruptedException} as a {@linkplain + * Throwable#addSuppressed(Throwable) suppressed exception}, should also restore the + * interrupted status before throwing the exception. + * + *

If a thread executing a blocking I/O operation on an {@link + * java.nio.channels.InterruptibleChannel} is interrupted then it causes the channel to be + * closed, and the blocking I/O operation to throw {@link java.nio.channels.ClosedByInterruptException} + * with the thread's interrupted status set. If a thread blocked in a {@linkplain + * java.nio.channels.Selector selection operation} is interrupted then it causes the + * selection operation to return early, with the thread's interrupted status set. + * + *

Code that doesn't invoke any interruptible methods can still respond to interrupt + * by polling the current thread's interrupted status with + * {@link Thread#currentThread() Thread.currentThread()}.{@link #isInterrupted() + * isInterrupted()}. + * + *

In addition to the {@link #interrupt()} and {@link #isInterrupted()} methods, + * {@code Thread} also defines the static {@link #interrupted() Thread.interrupted()} + * method to test the current thread's interrupted status and clear it. It should be rare + * to need to use this method. + * + *

Null Handling

+ * Unless otherwise specified, passing a {@code null} argument to a constructor * or method in this class will cause a {@link NullPointerException} to be thrown. * * @implNote @@ -190,8 +229,9 @@ import static java.util.concurrent.TimeUnit.NANOSECONDS; * * {@systemProperty jdk.virtualThreadScheduler.parallelism} * - * The number of platform threads available for scheduling virtual - * threads. It defaults to the number of available processors. + * The scheduler's target parallelism. This is the number of platform threads + * available for scheduling virtual threads. It defaults to the number of available + * processors. * * * @@ -202,6 +242,8 @@ import static java.util.concurrent.TimeUnit.NANOSECONDS; * * * + *

The virtual thread scheduler can be monitored and managed with the + * {@code jdk.management.VirtualThreadSchedulerMXBean} management interface. * * @since 1.0 */ @@ -1558,6 +1600,9 @@ public class Thread implements Runnable { * @implNote In the JDK Reference Implementation, interruption of a thread * that is not alive still records that the interrupt request was made and * will report it via {@link #interrupted()} and {@link #isInterrupted()}. + * + * @see ##thread-interruption Thread Interruption + * @see #isInterrupted() */ public void interrupt() { // Setting the interrupted status must be done before reading nioBlocker. @@ -1587,8 +1632,19 @@ public class Thread implements Runnable { * interrupted again, after the first call had cleared its interrupted * status and before the second call had examined it). * + * @apiNote It should be rare to use this method directly. It is intended + * for cases that detect {@linkplain ##thread-interruption thread interruption} + * and clear the interrupted status before throwing {@link InterruptedException}. + * It may also be useful for cases that implement an uninterruptible + * method that makes use of an interruptible method such as + * {@link LockSupport#park()}. The {@code interrupted()} method can be used + * to test if interrupted and clear the interrupted status to allow the code + * retry the interruptible method. The uninterruptible method + * should restore the interrupted status before it completes. + * * @return {@code true} if the current thread has been interrupted; * {@code false} otherwise. + * @see ##thread-interruption Thread Interruption * @see #isInterrupted() */ public static boolean interrupted() { @@ -1601,7 +1657,8 @@ public class Thread implements Runnable { * * @return {@code true} if this thread has been interrupted; * {@code false} otherwise. - * @see #interrupted() + * @see ##thread-interruption Thread Interruption + * @see #interrupt() */ public boolean isInterrupted() { return interrupted; From f510b4a3bafa3f0d2c9ebf0b33d48f57f3bdef95 Mon Sep 17 00:00:00 2001 From: Quan Anh Mai Date: Sat, 15 Nov 2025 12:59:04 +0000 Subject: [PATCH 070/418] 8355574: Fatal error in abort_verify_int_in_range due to Invalid CastII Reviewed-by: vlivanov, roland --- src/hotspot/share/opto/arraycopynode.cpp | 24 ++++----- src/hotspot/share/opto/arraycopynode.hpp | 2 +- src/hotspot/share/opto/macroArrayCopy.cpp | 49 ++++++++----------- test/hotspot/jtreg/ProblemList.txt | 2 - .../arraycopy/TestArrayCopyConjoint.java | 7 ++- 5 files changed, 39 insertions(+), 45 deletions(-) diff --git a/src/hotspot/share/opto/arraycopynode.cpp b/src/hotspot/share/opto/arraycopynode.cpp index c02aefc7943..4ee6107fe54 100644 --- a/src/hotspot/share/opto/arraycopynode.cpp +++ b/src/hotspot/share/opto/arraycopynode.cpp @@ -28,8 +28,6 @@ #include "gc/shared/gc_globals.hpp" #include "opto/arraycopynode.hpp" #include "opto/graphKit.hpp" -#include "runtime/sharedRuntime.hpp" -#include "utilities/macros.hpp" #include "utilities/powerOfTwo.hpp" const TypeFunc* ArrayCopyNode::_arraycopy_type_Type = nullptr; @@ -779,15 +777,17 @@ bool ArrayCopyNode::modifies(intptr_t offset_lo, intptr_t offset_hi, PhaseValues return false; } -// As an optimization, choose optimum vector size for copy length known at compile time. -int ArrayCopyNode::get_partial_inline_vector_lane_count(BasicType type, int const_len) { - int lane_count = ArrayOperationPartialInlineSize/type2aelembytes(type); - if (const_len > 0) { - int size_in_bytes = const_len * type2aelembytes(type); - if (size_in_bytes <= 16) - lane_count = 16/type2aelembytes(type); - else if (size_in_bytes > 16 && size_in_bytes <= 32) - lane_count = 32/type2aelembytes(type); +// As an optimization, choose the optimal vector size for bounded copy length +int ArrayCopyNode::get_partial_inline_vector_lane_count(BasicType type, jlong max_len) { + assert(max_len > 0, JLONG_FORMAT, max_len); + // We only care whether max_size_in_bytes is not larger than 32, we also want to avoid + // multiplication overflow, so clamp max_len to [0, 64] + int max_size_in_bytes = MIN2(max_len, 64) * type2aelembytes(type); + if (ArrayOperationPartialInlineSize > 16 && max_size_in_bytes <= 16) { + return 16 / type2aelembytes(type); + } else if (ArrayOperationPartialInlineSize > 32 && max_size_in_bytes <= 32) { + return 32 / type2aelembytes(type); + } else { + return ArrayOperationPartialInlineSize / type2aelembytes(type); } - return lane_count; } diff --git a/src/hotspot/share/opto/arraycopynode.hpp b/src/hotspot/share/opto/arraycopynode.hpp index 13e739fc2c7..83c085fd5db 100644 --- a/src/hotspot/share/opto/arraycopynode.hpp +++ b/src/hotspot/share/opto/arraycopynode.hpp @@ -191,7 +191,7 @@ public: static bool may_modify(const TypeOopPtr* t_oop, MemBarNode* mb, PhaseValues* phase, ArrayCopyNode*& ac); - static int get_partial_inline_vector_lane_count(BasicType type, int const_len); + static int get_partial_inline_vector_lane_count(BasicType type, jlong max_len); bool modifies(intptr_t offset_lo, intptr_t offset_hi, PhaseValues* phase, bool must_modify) const; diff --git a/src/hotspot/share/opto/macroArrayCopy.cpp b/src/hotspot/share/opto/macroArrayCopy.cpp index 10de940c0c2..0ba8ed40c37 100644 --- a/src/hotspot/share/opto/macroArrayCopy.cpp +++ b/src/hotspot/share/opto/macroArrayCopy.cpp @@ -204,53 +204,46 @@ void PhaseMacroExpand::generate_limit_guard(Node** ctrl, Node* offset, Node* sub void PhaseMacroExpand::generate_partial_inlining_block(Node** ctrl, MergeMemNode** mem, const TypePtr* adr_type, RegionNode** exit_block, Node** result_memory, Node* length, Node* src_start, Node* dst_start, BasicType type) { - const TypePtr *src_adr_type = _igvn.type(src_start)->isa_ptr(); - Node* inline_block = nullptr; - Node* stub_block = nullptr; + int inline_limit = ArrayOperationPartialInlineSize / type2aelembytes(type); - int const_len = -1; - const TypeInt* lty = nullptr; - uint shift = exact_log2(type2aelembytes(type)); - if (length->Opcode() == Op_ConvI2L) { - lty = _igvn.type(length->in(1))->isa_int(); - } else { - lty = _igvn.type(length)->isa_int(); - } - if (lty && lty->is_con()) { - const_len = lty->get_con() << shift; + const TypeLong* length_type = _igvn.type(length)->isa_long(); + if (length_type == nullptr) { + assert(_igvn.type(length) == Type::TOP, ""); + return; } - // Return if copy length is greater than partial inline size limit or - // target does not supports masked load/stores. - int lane_count = ArrayCopyNode::get_partial_inline_vector_lane_count(type, const_len); - if ( const_len > ArrayOperationPartialInlineSize || - !Matcher::match_rule_supported_vector(Op_LoadVectorMasked, lane_count, type) || + const TypeLong* inline_range = TypeLong::make(0, inline_limit, Type::WidenMin); + if (length_type->join(inline_range) == Type::TOP) { + // The ranges do not intersect, the inline check will surely fail + return; + } + + // Return if the target does not supports masked load/stores. + int lane_count = ArrayCopyNode::get_partial_inline_vector_lane_count(type, length_type->_hi); + if (!Matcher::match_rule_supported_vector(Op_LoadVectorMasked, lane_count, type) || !Matcher::match_rule_supported_vector(Op_StoreVectorMasked, lane_count, type) || !Matcher::match_rule_supported_vector(Op_VectorMaskGen, lane_count, type)) { return; } - int inline_limit = ArrayOperationPartialInlineSize / type2aelembytes(type); - Node* casted_length = new CastLLNode(*ctrl, length, TypeLong::make(0, inline_limit, Type::WidenMin)); - transform_later(casted_length); - Node* copy_bytes = new LShiftXNode(length, intcon(shift)); - transform_later(copy_bytes); - - Node* cmp_le = new CmpULNode(copy_bytes, longcon(ArrayOperationPartialInlineSize)); + Node* cmp_le = new CmpULNode(length, longcon(inline_limit)); transform_later(cmp_le); Node* bol_le = new BoolNode(cmp_le, BoolTest::le); transform_later(bol_le); - inline_block = generate_guard(ctrl, bol_le, nullptr, PROB_FAIR); - stub_block = *ctrl; + Node* inline_block = generate_guard(ctrl, bol_le, nullptr, PROB_FAIR); + Node* stub_block = *ctrl; + Node* casted_length = new CastLLNode(inline_block, length, inline_range, ConstraintCastNode::RegularDependency); + transform_later(casted_length); Node* mask_gen = VectorMaskGenNode::make(casted_length, type); transform_later(mask_gen); - unsigned vec_size = lane_count * type2aelembytes(type); + unsigned vec_size = lane_count * type2aelembytes(type); if (C->max_vector_size() < vec_size) { C->set_max_vector_size(vec_size); } + const TypePtr* src_adr_type = _igvn.type(src_start)->isa_ptr(); const TypeVect * vt = TypeVect::make(type, lane_count); Node* mm = (*mem)->memory_at(C->get_alias_index(src_adr_type)); Node* masked_load = new LoadVectorMaskedNode(inline_block, mm, src_start, diff --git a/test/hotspot/jtreg/ProblemList.txt b/test/hotspot/jtreg/ProblemList.txt index bc163ae8197..a45fdc09323 100644 --- a/test/hotspot/jtreg/ProblemList.txt +++ b/test/hotspot/jtreg/ProblemList.txt @@ -75,8 +75,6 @@ compiler/codecache/CodeCacheFullCountTest.java 8332954 generic-all compiler/interpreter/Test6833129.java 8335266 generic-i586 -compiler/c2/TestVerifyConstraintCasts.java 8355574 generic-all - compiler/c2/aarch64/TestStaticCallStub.java 8359963 linux-aarch64,macosx-aarch64 serviceability/jvmti/NMethodRelocation/NMethodRelocationTest.java 8369150 generic-all diff --git a/test/hotspot/jtreg/compiler/arraycopy/TestArrayCopyConjoint.java b/test/hotspot/jtreg/compiler/arraycopy/TestArrayCopyConjoint.java index 8d98628c074..a6ba75df625 100644 --- a/test/hotspot/jtreg/compiler/arraycopy/TestArrayCopyConjoint.java +++ b/test/hotspot/jtreg/compiler/arraycopy/TestArrayCopyConjoint.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2025, 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,7 @@ import java.util.Random; /** * @test - * @bug 8251871 8285301 + * @bug 8251871 8285301 8355574 * @summary Optimize arrayCopy using AVX-512 masked instructions. * * @run main/othervm/timeout=600 -XX:-TieredCompilation -Xbatch -XX:+IgnoreUnrecognizedVMOptions @@ -38,6 +38,9 @@ import java.util.Random; * @run main/othervm/timeout=600 -XX:-TieredCompilation -Xbatch -XX:+IgnoreUnrecognizedVMOptions * -XX:UseAVX=3 -XX:+UnlockDiagnosticVMOptions -XX:ArrayOperationPartialInlineSize=32 -XX:+UnlockDiagnosticVMOptions -XX:MaxVectorSize=32 -XX:+UnlockDiagnosticVMOption * compiler.arraycopy.TestArrayCopyConjoint + * @run main/othervm/timeout=600 -XX:-TieredCompilation -Xbatch -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions -XX:+StressGCM -XX:VerifyConstraintCasts=2 + * -XX:UseAVX=3 -XX:ArrayOperationPartialInlineSize=32 -XX:MaxVectorSize=32 + * compiler.arraycopy.TestArrayCopyConjoint * @run main/othervm/timeout=600 -XX:-TieredCompilation -Xbatch -XX:+IgnoreUnrecognizedVMOptions * -XX:UseAVX=3 -XX:+UnlockDiagnosticVMOptions -XX:ArrayOperationPartialInlineSize=32 -XX:+UnlockDiagnosticVMOptions -XX:MaxVectorSize=64 * compiler.arraycopy.TestArrayCopyConjoint From 7d35a283cf2497565d230e3d5426f563f7e5870d Mon Sep 17 00:00:00 2001 From: Tobias Hartmann Date: Sun, 16 Nov 2025 10:31:23 +0000 Subject: [PATCH 071/418] 8371958: [BACKOUT] 8371709: Add CTW to hotspot_compiler testing Reviewed-by: ayang --- test/hotspot/jtreg/TEST.groups | 1 - 1 file changed, 1 deletion(-) diff --git a/test/hotspot/jtreg/TEST.groups b/test/hotspot/jtreg/TEST.groups index f07d276f1f5..d4f1470aea5 100644 --- a/test/hotspot/jtreg/TEST.groups +++ b/test/hotspot/jtreg/TEST.groups @@ -36,7 +36,6 @@ hotspot_all_no_apps = \ # Component test groups hotspot_compiler = \ - applications/ctw/modules \ compiler hotspot_compiler_resourcehogs = \ From 7738131835d08f47dd7c535b12bb7ea7b0ff0b90 Mon Sep 17 00:00:00 2001 From: David Briemann Date: Mon, 17 Nov 2025 06:49:12 +0000 Subject: [PATCH 072/418] 8371642: TestNumberOfContinuousZeros.java fails on PPC64 Reviewed-by: mdoerr, epeter --- .../compiler/vectorization/TestNumberOfContinuousZeros.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/hotspot/jtreg/compiler/vectorization/TestNumberOfContinuousZeros.java b/test/hotspot/jtreg/compiler/vectorization/TestNumberOfContinuousZeros.java index e9773e8ac07..d50a96af749 100644 --- a/test/hotspot/jtreg/compiler/vectorization/TestNumberOfContinuousZeros.java +++ b/test/hotspot/jtreg/compiler/vectorization/TestNumberOfContinuousZeros.java @@ -180,7 +180,8 @@ public class TestNumberOfContinuousZeros { } @Test - @IR(counts = {IRNode.COUNT_LEADING_ZEROS_VL, "> 0"}) + @IR(applyIfPlatformOr = {"x64", "true", "aarch64", "true", "riscv64", "true"}, + counts = {IRNode.COUNT_LEADING_ZEROS_VL, "> 0"}) @Arguments(setup = "setupSpecialLongArray") public Object[] testSpecialLongLeadingZeros(long[] longs) { int[] res = new int[LEN]; @@ -245,7 +246,8 @@ public class TestNumberOfContinuousZeros { } @Test - @IR(counts = {IRNode.COUNT_LEADING_ZEROS_VL, "> 0"}) + @IR(applyIfPlatformOr = {"x64", "true", "aarch64", "true", "riscv64", "true"}, + counts = {IRNode.COUNT_LEADING_ZEROS_VL, "> 0"}) @Arguments(setup = "setupSpecialLongArray") public Object[] testLongLeadingZerosVector(long[] longs) { long[] res = new long[LEN]; From ce1adf63ea1146fba4cf36c10dc5f1d33aa88000 Mon Sep 17 00:00:00 2001 From: Axel Boldt-Christmas Date: Mon, 17 Nov 2025 07:33:33 +0000 Subject: [PATCH 073/418] 8371672: G1: G1YoungGenSizer handling of NewRatio, NewSize and MaxNewSize 8370494: G1: NewSize not bounded by InitialHeapSize or MaxHeapSize Reviewed-by: tschatzl, iwalulya --- src/hotspot/share/gc/g1/g1YoungGenSizer.cpp | 96 +++++++++++++++------ 1 file changed, 72 insertions(+), 24 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1YoungGenSizer.cpp b/src/hotspot/share/gc/g1/g1YoungGenSizer.cpp index a8209eb19ba..ffa573c68cc 100644 --- a/src/hotspot/share/gc/g1/g1YoungGenSizer.cpp +++ b/src/hotspot/share/gc/g1/g1YoungGenSizer.cpp @@ -25,47 +25,95 @@ #include "gc/g1/g1Arguments.hpp" #include "gc/g1/g1HeapRegion.hpp" #include "gc/g1/g1YoungGenSizer.hpp" +#include "gc/shared/gc_globals.hpp" #include "logging/log.hpp" #include "runtime/globals_extension.hpp" G1YoungGenSizer::G1YoungGenSizer() : _sizer_kind(SizerDefaults), _use_adaptive_sizing(true), _min_desired_young_length(0), _max_desired_young_length(0) { + precond(!FLAG_IS_ERGO(NewRatio)); + precond(!FLAG_IS_ERGO(NewSize)); + precond(!FLAG_IS_ERGO(MaxNewSize)); + + // Figure out compatible young gen sizing policies. + // This will either use all default, NewRatio or a combination of NewSize and + // MaxNewSize. If both ratio and size is user specified NewRatio will be ignored. + + const bool user_specified_NewRatio = !FLAG_IS_DEFAULT(NewRatio); + const bool user_specified_NewSize = !FLAG_IS_DEFAULT(NewSize); + const bool user_specified_MaxNewSize = !FLAG_IS_DEFAULT(MaxNewSize); + + // MaxNewSize is updated every time the heap is resized (and when initialized), + // as such the value of MaxNewSize is only modified if it is also used by the + // young generation sizing. (If MaxNewSize is user specified). + + if (!user_specified_NewRatio && !user_specified_NewSize && !user_specified_MaxNewSize) { + // Using Defaults. + return; + } + + if (user_specified_NewRatio && !user_specified_NewSize && !user_specified_MaxNewSize) { + // Using NewRatio. + _sizer_kind = SizerNewRatio; + _use_adaptive_sizing = false; + return; + } + if (FLAG_IS_CMDLINE(NewRatio)) { - if (FLAG_IS_CMDLINE(NewSize) || FLAG_IS_CMDLINE(MaxNewSize)) { - log_warning(gc, ergo)("-XX:NewSize and -XX:MaxNewSize override -XX:NewRatio"); - } else { - _sizer_kind = SizerNewRatio; - _use_adaptive_sizing = false; - return; + // NewRatio ignored at this point, issue warning if NewRatio was specified + // on the command line. + log_warning(gc, ergo)("-XX:NewSize and -XX:MaxNewSize overrides -XX:NewRatio"); + } + + assert(!FLAG_IS_DEFAULT(InitialHeapSize), "Initial heap size must be selected"); + if (user_specified_NewSize && NewSize > InitialHeapSize) { + // If user specifed NewSize is larger than the InitialHeapSize truncate the value. + if (FLAG_IS_CMDLINE(NewSize)) { + log_warning(gc, ergo)("NewSize (%zuk) is greater than the initial heap size (%zuk). " + "A new NewSize of %zuk will be used.", + NewSize/K, InitialHeapSize/K, InitialHeapSize/K); } + FLAG_SET_ERGO(NewSize, InitialHeapSize); + } + + assert(!FLAG_IS_DEFAULT(MaxHeapSize), "Max heap size must be selected"); + if (user_specified_MaxNewSize && MaxNewSize > MaxHeapSize) { + // If user specifed MaxNewSize is larger than the MaxHeapSize truncate the value. + if (FLAG_IS_CMDLINE(MaxNewSize)) { + log_warning(gc, ergo)("MaxNewSize (%zuk) greater than the entire heap (%zuk). " + "A new MaxNewSize of %zuk will be used.", + MaxNewSize/K, MaxHeapSize/K, MaxHeapSize/K); + } + FLAG_SET_ERGO(MaxNewSize, MaxHeapSize); } if (NewSize > MaxNewSize) { + // Either NewSize, MaxNewSize or both have been specified and are incompatible. + // In either case set MaxNewSize to the value of NewSize. if (FLAG_IS_CMDLINE(MaxNewSize)) { - log_warning(gc, ergo)("NewSize (%zuk) is greater than the MaxNewSize (%zuk). " - "A new max generation size of %zuk will be used.", + log_warning(gc, ergo)("NewSize (%zuk) is greater than MaxNewSize (%zuk). " + "A new MaxNewSize of %zuk will be used.", NewSize/K, MaxNewSize/K, NewSize/K); } FLAG_SET_ERGO(MaxNewSize, NewSize); } - if (FLAG_IS_CMDLINE(NewSize)) { - _min_desired_young_length = MAX2((uint) (NewSize / G1HeapRegion::GrainBytes), - 1U); - if (FLAG_IS_CMDLINE(MaxNewSize)) { - _max_desired_young_length = - MAX2((uint) (MaxNewSize / G1HeapRegion::GrainBytes), - 1U); - _sizer_kind = SizerMaxAndNewSize; - _use_adaptive_sizing = _min_desired_young_length != _max_desired_young_length; - } else { - _sizer_kind = SizerNewSizeOnly; - } - } else if (FLAG_IS_CMDLINE(MaxNewSize)) { - _max_desired_young_length = - MAX2((uint) (MaxNewSize / G1HeapRegion::GrainBytes), - 1U); + if (user_specified_NewSize) { + _min_desired_young_length = MAX2((uint)(NewSize / G1HeapRegion::GrainBytes), 1U); + } + + if (user_specified_MaxNewSize) { + _max_desired_young_length = MAX2((uint)(MaxNewSize / G1HeapRegion::GrainBytes), 1U); + } + + if (user_specified_NewSize && user_specified_MaxNewSize) { + _sizer_kind = SizerMaxAndNewSize; + _use_adaptive_sizing = _min_desired_young_length != _max_desired_young_length; + } else if (user_specified_NewSize) { + _sizer_kind = SizerNewSizeOnly; + } else { + postcond(user_specified_MaxNewSize); _sizer_kind = SizerMaxNewSizeOnly; } } From 8690d263d9dd0fd06ed41d9529fd8cc84e1c08c8 Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Mon, 17 Nov 2025 07:53:32 +0000 Subject: [PATCH 074/418] 8268613: jar --validate should check inital entries of a JAR file Reviewed-by: lancea, jvernee --- .../classes/sun/tools/jar/Validator.java | 13 +++ .../sun/tools/jar/resources/jar.properties | 4 + test/jdk/tools/jar/ValidatorTest.java | 94 +++++++++++++++++++ 3 files changed, 111 insertions(+) diff --git a/src/jdk.jartool/share/classes/sun/tools/jar/Validator.java b/src/jdk.jartool/share/classes/sun/tools/jar/Validator.java index 1852b2cad9d..be694eeb1bc 100644 --- a/src/jdk.jartool/share/classes/sun/tools/jar/Validator.java +++ b/src/jdk.jartool/share/classes/sun/tools/jar/Validator.java @@ -260,6 +260,19 @@ final class Validator { isValid = false; warn(getMsg("warn.validator.order.mismatch")); } + // Check location of an optional manifest entry + if ("META-INF/MANIFEST.MF".equals(entryName)) { + int index = entryInfo.cen().order(); + if (index > 1) { // Expect base manifest at index 0 or 1 + String position = Integer.toString(index); + errorAndInvalid(formatMsg("error.validator.manifest.wrong.position", position)); + } else if (index == 1) { // Ensure "META-INF/" preceeds manifest + String firstName = entries.sequencedKeySet().getFirst(); + if (!"META-INF/".equals(firstName)) { + errorAndInvalid(formatMsg("error.validator.metainf.wrong.position", firstName)); + } + } + } } /** diff --git a/src/jdk.jartool/share/classes/sun/tools/jar/resources/jar.properties b/src/jdk.jartool/share/classes/sun/tools/jar/resources/jar.properties index 0e3555f181e..c80377e20c1 100644 --- a/src/jdk.jartool/share/classes/sun/tools/jar/resources/jar.properties +++ b/src/jdk.jartool/share/classes/sun/tools/jar/resources/jar.properties @@ -134,6 +134,10 @@ error.validator.info.version.notequal=\ {0}: module-info.class in a versioned directory contains different "version" error.validator.info.manclass.notequal=\ {0}: module-info.class in a versioned directory contains different "main-class" +error.validator.metainf.wrong.position=\ + expected entry META-INF/ to be at position 0, but found: {0} +error.validator.manifest.wrong.position=\ + expected entry META-INF/MANIFEST.MF to be at position 0 or 1, but found it at position: {0} warn.validator.identical.entry=\ Warning: entry {0} contains a class that\n\ is identical to an entry already in the jar diff --git a/test/jdk/tools/jar/ValidatorTest.java b/test/jdk/tools/jar/ValidatorTest.java index c08e6a8a984..c2dfeae8dec 100644 --- a/test/jdk/tools/jar/ValidatorTest.java +++ b/test/jdk/tools/jar/ValidatorTest.java @@ -38,10 +38,13 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance.Lifecycle; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertLinesMatch; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import java.io.IOException; +import java.io.FileInputStream; import java.io.PrintStream; import java.io.UncheckedIOException; import java.nio.charset.StandardCharsets; @@ -50,6 +53,7 @@ import java.nio.file.Path; import java.util.Arrays; import java.util.List; import java.util.jar.JarFile; +import java.util.jar.JarInputStream; import java.util.spi.ToolProvider; import java.util.stream.Stream; import java.util.zip.ZipEntry; @@ -306,6 +310,96 @@ class ValidatorTest { } } + /** + * Validates that base manifest-related entries are at expected LOC positions. + *

+ * Copied from JarInputStream.java: + *

+     * This implementation assumes the META-INF/MANIFEST.MF entry
+     * should be either the first or the second entry (when preceded
+     * by the dir META-INF/). It skips the META-INF/ and then
+     * "consumes" the MANIFEST.MF to initialize the Manifest object.
+     * 
+ * This test does not do a similar CEN check in the event that the LOC and CEN + * entries do not match. Those mismatch cases are already checked by other tests. + */ + @Test + public void testWrongManifestPositions() throws IOException { + testWrongManifestPosition( + Path.of("wrong-entry-position-A.jar"), + """ + expected entry META-INF/ to be at position 0, but found: PLACEHOLDER + """, + EntryWriter.ofText("PLACEHOLDER", "0"), + EntryWriter.ofText(META_INF + "MANIFEST.MF", "Manifest-Version: 1.0")); + testWrongManifestPosition( + Path.of("wrong-entry-position-B.jar"), + """ + expected entry META-INF/MANIFEST.MF to be at position 0 or 1, but found it at position: 2 + """, + EntryWriter.ofDirectory(META_INF), + EntryWriter.ofText("PLACEHOLDER", "1"), + EntryWriter.ofText(META_INF + "MANIFEST.MF", "Manifest-Version: 1.0")); + testWrongManifestPosition( + Path.of("wrong-entry-position-C.jar"), + """ + expected entry META-INF/MANIFEST.MF to be at position 0 or 1, but found it at position: 4 + """, + EntryWriter.ofDirectory(META_INF), + EntryWriter.ofText("PLACEHOLDER1", "1"), + EntryWriter.ofText("PLACEHOLDER2", "2"), + EntryWriter.ofText("PLACEHOLDER3", "3"), + EntryWriter.ofText(META_INF + "MANIFEST.MF", "Manifest-Version: 1.0")); + } + + private void testWrongManifestPosition( + Path path, String expectedErrorMessage, EntryWriter... entries) throws IOException { + createZipFile(path, entries); + // first check JAR file with streaming API + try (var jis = new JarInputStream(new FileInputStream(path.toFile()))) { + var manifest = jis.getManifest(); + assertNull(manifest, "Manifest not null?!"); + } + // now validate with tool CLI + try { + jar("--validate --file " + path); + fail("Expecting non-zero exit code validating: " + path); + } catch (IOException e) { + var err = e.getMessage(); + System.out.println(err); + assertLinesMatch(expectedErrorMessage.lines(), err.lines()); + } + } + + record EntryWriter(ZipEntry entry, Writer writer) { + @FunctionalInterface + interface Writer { + void write(ZipOutputStream stream) throws IOException; + } + static EntryWriter ofDirectory(String name) { + return new EntryWriter(new ZipEntry(name), _ -> {}); + } + static EntryWriter ofText(String name, String text) { + return new EntryWriter(new ZipEntry(name), + stream -> stream.write(text.getBytes(StandardCharsets.UTF_8))); + } + } + + private static void createZipFile(Path path, EntryWriter... entries) throws IOException { + System.out.printf("%n%n*****Creating Zip file with %d entries*****%n".formatted(entries.length)); + var out = new ByteArrayOutputStream(1024); + try (var zos = new ZipOutputStream(out)) { + for (var entry : entries) { + System.out.printf(" %s%n".formatted(entry.entry().getName())); + zos.putNextEntry(entry.entry()); + entry.writer().write(zos); + zos.closeEntry(); + } + zos.flush(); + } + Files.write(path, out.toByteArray()); + } + // return stderr output private String jar(String cmdline) throws IOException { System.out.println("jar " + cmdline); From d032b28d9d042a36f5163b079151643bb49294e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joel=20Sikstr=C3=B6m?= Date: Mon, 17 Nov 2025 08:50:50 +0000 Subject: [PATCH 075/418] 8371894: Minor style fixes in AOT/CDS code Reviewed-by: stefank, kvn, iklam --- src/hotspot/share/cds/cdsConfig.cpp | 2 +- src/hotspot/share/cds/filemap.hpp | 2 +- src/hotspot/share/cds/heapShared.cpp | 5 ++- src/hotspot/share/memory/universe.cpp | 44 +++++++++++++-------------- 4 files changed, 26 insertions(+), 27 deletions(-) diff --git a/src/hotspot/share/cds/cdsConfig.cpp b/src/hotspot/share/cds/cdsConfig.cpp index 7976f690b8b..86533e212d8 100644 --- a/src/hotspot/share/cds/cdsConfig.cpp +++ b/src/hotspot/share/cds/cdsConfig.cpp @@ -526,7 +526,7 @@ void CDSConfig::check_aotmode_record() { bool has_output = !FLAG_IS_DEFAULT(AOTCacheOutput); if (!has_output && !has_config) { - vm_exit_during_initialization("At least one of AOTCacheOutput and AOTConfiguration must be specified when using -XX:AOTMode=record"); + vm_exit_during_initialization("At least one of AOTCacheOutput and AOTConfiguration must be specified when using -XX:AOTMode=record"); } if (has_output) { diff --git a/src/hotspot/share/cds/filemap.hpp b/src/hotspot/share/cds/filemap.hpp index b97b46a7c26..2a761843e47 100644 --- a/src/hotspot/share/cds/filemap.hpp +++ b/src/hotspot/share/cds/filemap.hpp @@ -290,7 +290,7 @@ public: void log_paths(const char* msg, int start_idx, int end_idx); - FileMapInfo(const char* full_apth, bool is_static); + FileMapInfo(const char* full_path, bool is_static); ~FileMapInfo(); static void free_current_info(); diff --git a/src/hotspot/share/cds/heapShared.cpp b/src/hotspot/share/cds/heapShared.cpp index 357b317ee49..f2382289c7d 100644 --- a/src/hotspot/share/cds/heapShared.cpp +++ b/src/hotspot/share/cds/heapShared.cpp @@ -631,9 +631,8 @@ void HeapShared::init_scratch_objects_for_basic_type_mirrors(TRAPS) { } // Given java_mirror that represents a (primitive or reference) type T, -// return the "scratch" version that represents the same type T. -// Note that if java_mirror will be returned if it's already a -// scratch mirror. +// return the "scratch" version that represents the same type T. Note +// that java_mirror will be returned if the mirror is already a scratch mirror. // // See java_lang_Class::create_scratch_mirror() for more info. oop HeapShared::scratch_java_mirror(oop java_mirror) { diff --git a/src/hotspot/share/memory/universe.cpp b/src/hotspot/share/memory/universe.cpp index db0435044ca..d389fe81806 100644 --- a/src/hotspot/share/memory/universe.cpp +++ b/src/hotspot/share/memory/universe.cpp @@ -557,32 +557,32 @@ void Universe::genesis(TRAPS) { void Universe::initialize_basic_type_mirrors(TRAPS) { #if INCLUDE_CDS_JAVA_HEAP - if (CDSConfig::is_using_archive() && - HeapShared::is_archived_heap_in_use() && - _basic_type_mirrors[T_INT].resolve() != nullptr) { - // check that all basic type mirrors are mapped also - for (int i = T_BOOLEAN; i < T_VOID+1; i++) { - if (!is_reference_type((BasicType)i)) { - oop m = _basic_type_mirrors[i].resolve(); - assert(m != nullptr, "archived mirrors should not be null"); - } + if (CDSConfig::is_using_archive() && + HeapShared::is_archived_heap_in_use() && + _basic_type_mirrors[T_INT].resolve() != nullptr) { + // check that all basic type mirrors are mapped also + for (int i = T_BOOLEAN; i < T_VOID+1; i++) { + if (!is_reference_type((BasicType)i)) { + oop m = _basic_type_mirrors[i].resolve(); + assert(m != nullptr, "archived mirrors should not be null"); } - } else - // _basic_type_mirrors[T_INT], etc, are null if not using an archived heap + } + } else + // _basic_type_mirrors[T_INT], etc, are null if not using an archived heap #endif - { - for (int i = T_BOOLEAN; i < T_VOID+1; i++) { - BasicType bt = (BasicType)i; - if (!is_reference_type(bt)) { - oop m = java_lang_Class::create_basic_type_mirror(type2name(bt), bt, CHECK); - _basic_type_mirrors[i] = OopHandle(vm_global(), m); - } - CDS_JAVA_HEAP_ONLY(_archived_basic_type_mirror_indices[i] = -1); + { + for (int i = T_BOOLEAN; i < T_VOID+1; i++) { + BasicType bt = (BasicType)i; + if (!is_reference_type(bt)) { + oop m = java_lang_Class::create_basic_type_mirror(type2name(bt), bt, CHECK); + _basic_type_mirrors[i] = OopHandle(vm_global(), m); } + CDS_JAVA_HEAP_ONLY(_archived_basic_type_mirror_indices[i] = -1); } - if (CDSConfig::is_dumping_heap()) { - HeapShared::init_scratch_objects_for_basic_type_mirrors(CHECK); - } + } + if (CDSConfig::is_dumping_heap()) { + HeapShared::init_scratch_objects_for_basic_type_mirrors(CHECK); + } } void Universe::fixup_mirrors(TRAPS) { From 970533d41d3d1b4ebe12868c85579d37b3b23655 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20Sj=C3=B6len?= Date: Mon, 17 Nov 2025 09:06:32 +0000 Subject: [PATCH 076/418] 8371779: Replace MemTagBitmap with ResourceBitMap Reviewed-by: azafari, phubner --- src/hotspot/share/nmt/memMapPrinter.cpp | 19 +++++---- src/hotspot/share/nmt/memTagBitmap.hpp | 56 ------------------------- 2 files changed, 10 insertions(+), 65 deletions(-) delete mode 100644 src/hotspot/share/nmt/memTagBitmap.hpp diff --git a/src/hotspot/share/nmt/memMapPrinter.cpp b/src/hotspot/share/nmt/memMapPrinter.cpp index 7e82de23dd8..9a2fe166d3d 100644 --- a/src/hotspot/share/nmt/memMapPrinter.cpp +++ b/src/hotspot/share/nmt/memMapPrinter.cpp @@ -32,7 +32,6 @@ #include "memory/universe.hpp" #include "nmt/memMapPrinter.hpp" #include "nmt/memTag.hpp" -#include "nmt/memTagBitmap.hpp" #include "nmt/memTracker.hpp" #include "nmt/virtualMemoryTracker.hpp" #include "runtime/nonJavaThread.hpp" @@ -40,6 +39,8 @@ #include "runtime/thread.hpp" #include "runtime/threadSMR.hpp" #include "runtime/vmThread.hpp" +#include "utilities/bitMap.hpp" +#include "utilities/bitMap.inline.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/ostream.hpp" #include "utilities/permitForbiddenFunctions.hpp" @@ -128,8 +129,8 @@ public: } // Given a vma [from, to), find all regions that intersect with this vma and - // return their collective flags. - MemTagBitmap lookup(const void* from, const void* to) const { + // fill out their collective flags into bm. + void lookup(const void* from, const void* to, ResourceBitMap& bm) const { assert(from <= to, "Sanity"); // We optimize for sequential lookups. Since this class is used when a list // of OS mappings is scanned (VirtualQuery, /proc/pid/maps), and these lists @@ -138,16 +139,14 @@ public: // the range is to the right of the given section, we need to re-start the search _last = 0; } - MemTagBitmap bm; for(uintx i = _last; i < _count; i++) { if (range_intersects(from, to, _ranges[i].from, _ranges[i].to)) { - bm.set_tag(_mem_tags[i]); + bm.set_bit((BitMap::idx_t)_mem_tags[i]); } else if (to <= _ranges[i].from) { _last = i; break; } } - return bm; } bool do_allocation_site(const ReservedMemoryRegion* rgn) override { @@ -247,11 +246,13 @@ bool MappingPrintSession::print_nmt_info_for_region(const void* vma_from, const // print NMT information, if available if (MemTracker::enabled()) { // Correlate vma region (from, to) with NMT region(s) we collected previously. - const MemTagBitmap flags = _nmt_info.lookup(vma_from, vma_to); - if (flags.has_any()) { + ResourceMark rm; + ResourceBitMap flags(mt_number_of_tags); + _nmt_info.lookup(vma_from, vma_to, flags); + if (!flags.is_empty()) { for (int i = 0; i < mt_number_of_tags; i++) { const MemTag mem_tag = (MemTag)i; - if (flags.has_tag(mem_tag)) { + if (flags.at((BitMap::idx_t)mem_tag)) { if (num_printed > 0) { _out->put(','); } diff --git a/src/hotspot/share/nmt/memTagBitmap.hpp b/src/hotspot/share/nmt/memTagBitmap.hpp deleted file mode 100644 index f65dce60fa6..00000000000 --- a/src/hotspot/share/nmt/memTagBitmap.hpp +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2023, 2024, Red Hat, Inc. 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 - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - * - */ - -#ifndef SHARE_NMT_MEMTAGBITMAP_HPP -#define SHARE_NMT_MEMTAGBITMAP_HPP - -#include "nmt/memTag.hpp" -#include "utilities/debug.hpp" -#include "utilities/globalDefinitions.hpp" - -class MemTagBitmap { - uint32_t _v; - STATIC_ASSERT(sizeof(_v) * BitsPerByte >= mt_number_of_tags); - -public: - MemTagBitmap(uint32_t v = 0) : _v(v) {} - MemTagBitmap(const MemTagBitmap& o) : _v(o._v) {} - - uint32_t raw_value() const { return _v; } - - void set_tag(MemTag mem_tag) { - const int bitno = (int)mem_tag; - _v |= nth_bit(bitno); - } - - bool has_tag(MemTag mem_tag) const { - const int bitno = (int)mem_tag; - return _v & nth_bit(bitno); - } - - bool has_any() const { return _v > 0; } -}; - -#endif // SHARE_NMT_MEMTAGBITMAP_HPP From 69e30244c0c359e7108acd36d903fa22970822b9 Mon Sep 17 00:00:00 2001 From: Andrey Turbanov Date: Mon, 17 Nov 2025 09:08:21 +0000 Subject: [PATCH 077/418] 8349157: Unnecessary Hashtable usage in XKeysym.javaKeycode2KeysymHash Reviewed-by: aivanov, serb --- .../unix/classes/sun/awt/X11/XKeysym.java | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/java.desktop/unix/classes/sun/awt/X11/XKeysym.java b/src/java.desktop/unix/classes/sun/awt/X11/XKeysym.java index 2970305ce26..eab0898b47a 100644 --- a/src/java.desktop/unix/classes/sun/awt/X11/XKeysym.java +++ b/src/java.desktop/unix/classes/sun/awt/X11/XKeysym.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, 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 @@ -37,7 +37,9 @@ */ package sun.awt.X11; + import java.util.Hashtable; +import java.util.Map; import jdk.internal.misc.Unsafe; import sun.util.logging.PlatformLogger; @@ -68,7 +70,12 @@ public final class XKeysym { // for robot only it seems to me. After that, we can remove lookup table // from XWindow.c altogether. // Another use for reverse lookup: query keyboard state, for some keys. - static Hashtable javaKeycode2KeysymHash = new Hashtable(); + private static final Map javaKeycode2KeysymHash = Map.of( + java.awt.event.KeyEvent.VK_CAPS_LOCK, XKeySymConstants.XK_Caps_Lock, + java.awt.event.KeyEvent.VK_NUM_LOCK, XKeySymConstants.XK_Num_Lock, + java.awt.event.KeyEvent.VK_SCROLL_LOCK, XKeySymConstants.XK_Scroll_Lock, + java.awt.event.KeyEvent.VK_KANA_LOCK, XKeySymConstants.XK_Kana_Lock + ); static long keysym_lowercase = unsafe.allocateMemory(Native.getLongSize()); static long keysym_uppercase = unsafe.allocateMemory(Native.getLongSize()); static Keysym2JavaKeycode kanaLock = new Keysym2JavaKeycode(java.awt.event.KeyEvent.VK_KANA_LOCK, @@ -289,9 +296,8 @@ public final class XKeysym { Keysym2JavaKeycode jkc = getJavaKeycode( keysym ); return jkc == null ? java.awt.event.KeyEvent.VK_UNDEFINED : jkc.getJavaKeycode(); } - static long javaKeycode2Keysym( int jkey ) { - Long ks = javaKeycode2KeysymHash.get( jkey ); - return (ks == null ? 0 : ks.longValue()); + static long javaKeycode2Keysym(int jkey) { + return javaKeycode2KeysymHash.getOrDefault(jkey, 0L); } /** Return keysym derived from a keycode and modifiers. @@ -1716,14 +1722,6 @@ public final class XKeysym { keysym2JavaKeycodeHash.put( Long.valueOf(XKeySymConstants.hpXK_mute_asciitilde), new Keysym2JavaKeycode(java.awt.event.KeyEvent.VK_DEAD_TILDE, java.awt.event.KeyEvent.KEY_LOCATION_STANDARD)); keysym2JavaKeycodeHash.put( Long.valueOf(XConstants.NoSymbol), new Keysym2JavaKeycode(java.awt.event.KeyEvent.VK_UNDEFINED, java.awt.event.KeyEvent.KEY_LOCATION_UNKNOWN)); - - /* Reverse search of keysym by keycode. */ - - /* Add keyboard locking codes. */ - javaKeycode2KeysymHash.put( java.awt.event.KeyEvent.VK_CAPS_LOCK, XKeySymConstants.XK_Caps_Lock); - javaKeycode2KeysymHash.put( java.awt.event.KeyEvent.VK_NUM_LOCK, XKeySymConstants.XK_Num_Lock); - javaKeycode2KeysymHash.put( java.awt.event.KeyEvent.VK_SCROLL_LOCK, XKeySymConstants.XK_Scroll_Lock); - javaKeycode2KeysymHash.put( java.awt.event.KeyEvent.VK_KANA_LOCK, XKeySymConstants.XK_Kana_Lock); } } From 09b25cd0a24a4eaddce49917d958adc667ab5465 Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Mon, 17 Nov 2025 09:38:17 +0000 Subject: [PATCH 078/418] 8371465: Parallel: Revise asserts around heap expansion Reviewed-by: aboldtch, tschatzl --- .../share/gc/parallel/mutableSpace.cpp | 13 -------- .../share/gc/parallel/mutableSpace.hpp | 5 --- .../gc/parallel/parallelScavengeHeap.cpp | 9 +++++ src/hotspot/share/gc/parallel/psOldGen.cpp | 33 +++++++++++++++---- 4 files changed, 36 insertions(+), 24 deletions(-) diff --git a/src/hotspot/share/gc/parallel/mutableSpace.cpp b/src/hotspot/share/gc/parallel/mutableSpace.cpp index 6d30c5a8d1f..fc42fc1eab2 100644 --- a/src/hotspot/share/gc/parallel/mutableSpace.cpp +++ b/src/hotspot/share/gc/parallel/mutableSpace.cpp @@ -180,19 +180,6 @@ bool MutableSpace::cas_deallocate(HeapWord *obj, size_t size) { return AtomicAccess::cmpxchg(top_addr(), expected_top, obj) == expected_top; } -// Only used by oldgen allocation. -bool MutableSpace::needs_expand(size_t word_size) const { - // This method can be invoked either outside of safepoint by java threads or - // in safepoint by gc workers. Such accesses are synchronized by holding one - // of the following locks. - assert(Heap_lock->is_locked() || PSOldGenExpand_lock->is_locked(), "precondition"); - - // Holding the lock means end is stable. So while top may be advancing - // via concurrent allocations, there is no need to order the reads of top - // and end here, unlike in cas_allocate. - return pointer_delta(end(), top()) < word_size; -} - void MutableSpace::oop_iterate(OopIterateClosure* cl) { HeapWord* obj_addr = bottom(); HeapWord* t = top(); diff --git a/src/hotspot/share/gc/parallel/mutableSpace.hpp b/src/hotspot/share/gc/parallel/mutableSpace.hpp index 37fa7e3710e..9d3894e2489 100644 --- a/src/hotspot/share/gc/parallel/mutableSpace.hpp +++ b/src/hotspot/share/gc/parallel/mutableSpace.hpp @@ -127,11 +127,6 @@ public: virtual HeapWord* cas_allocate(size_t word_size); // Optional deallocation. Used in NUMA-allocator. bool cas_deallocate(HeapWord *obj, size_t size); - // Return true if this space needs to be expanded in order to satisfy an - // allocation request of the indicated size. Concurrent allocations and - // resizes may change the result of a later call. Used by oldgen allocator. - // precondition: holding PSOldGenExpand_lock if not VM thread - bool needs_expand(size_t word_size) const; // Iteration. void oop_iterate(OopIterateClosure* cl); diff --git a/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp b/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp index f5ed40c40e5..ebaea3ecba4 100644 --- a/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp +++ b/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp @@ -403,7 +403,16 @@ bool ParallelScavengeHeap::check_gc_overhead_limit() { } HeapWord* ParallelScavengeHeap::expand_heap_and_allocate(size_t size, bool is_tlab) { +#ifdef ASSERT assert(Heap_lock->is_locked(), "precondition"); + if (is_init_completed()) { + assert(SafepointSynchronize::is_at_safepoint(), "precondition"); + assert(Thread::current()->is_VM_thread(), "precondition"); + } else { + assert(Thread::current()->is_Java_thread(), "precondition"); + assert(Heap_lock->owned_by_self(), "precondition"); + } +#endif HeapWord* result = young_gen()->expand_and_allocate(size); diff --git a/src/hotspot/share/gc/parallel/psOldGen.cpp b/src/hotspot/share/gc/parallel/psOldGen.cpp index 4e614c53447..974cd6aca59 100644 --- a/src/hotspot/share/gc/parallel/psOldGen.cpp +++ b/src/hotspot/share/gc/parallel/psOldGen.cpp @@ -33,6 +33,7 @@ #include "gc/shared/spaceDecorator.hpp" #include "logging/log.hpp" #include "oops/oop.inline.hpp" +#include "runtime/init.hpp" #include "runtime/java.hpp" #include "utilities/align.hpp" @@ -118,13 +119,22 @@ void PSOldGen::initialize_performance_counters() { } HeapWord* PSOldGen::expand_and_allocate(size_t word_size) { +#ifdef ASSERT assert(Heap_lock->is_locked(), "precondition"); + if (is_init_completed()) { + assert(SafepointSynchronize::is_at_safepoint(), "precondition"); + assert(Thread::current()->is_VM_thread(), "precondition"); + } else { + assert(Thread::current()->is_Java_thread(), "precondition"); + assert(Heap_lock->owned_by_self(), "precondition"); + } +#endif - if (object_space()->needs_expand(word_size)) { + if (pointer_delta(object_space()->end(), object_space()->top()) < word_size) { expand(word_size*HeapWordSize); } - // Reuse the CAS API even though this is VM thread in safepoint. This method + // Reuse the CAS API even though this is in a critical section. This method // is not invoked repeatedly, so the CAS overhead should be negligible. return cas_allocate_noexpand(word_size); } @@ -168,7 +178,7 @@ bool PSOldGen::expand_for_allocate(size_t word_size) { // true until we expand, since we have the lock. Other threads may take // the space we need before we can allocate it, regardless of whether we // expand. That's okay, we'll just try expanding again. - if (object_space()->needs_expand(word_size)) { + if (pointer_delta(object_space()->end(), object_space()->top()) < word_size) { result = expand(word_size*HeapWordSize); } } @@ -192,10 +202,21 @@ void PSOldGen::try_expand_till_size(size_t target_capacity_bytes) { bool PSOldGen::expand(size_t bytes) { #ifdef ASSERT - if (!Thread::current()->is_VM_thread()) { - assert_lock_strong(PSOldGenExpand_lock); + // During startup (is_init_completed() == false), expansion can occur for + // 1. java-threads invoking heap-allocation (using Heap_lock) + // 2. CDS construction by a single thread (using PSOldGenExpand_lock but not needed) + // + // After startup (is_init_completed() == true), expansion can occur for + // 1. GC workers for promoting to old-gen (using PSOldGenExpand_lock) + // 2. VM thread to satisfy the pending allocation + // Both cases are inside safepoint pause, but are never overlapping. + // + if (is_init_completed()) { + assert(SafepointSynchronize::is_at_safepoint(), "precondition"); + assert(Thread::current()->is_VM_thread() || PSOldGenExpand_lock->owned_by_self(), "precondition"); + } else { + assert(Heap_lock->owned_by_self() || PSOldGenExpand_lock->owned_by_self(), "precondition"); } - assert_locked_or_safepoint(Heap_lock); assert(bytes > 0, "precondition"); #endif const size_t remaining_bytes = virtual_space()->uncommitted_size(); From 812add27abdc70bc52ca105bc9430494a6491ecd Mon Sep 17 00:00:00 2001 From: Jonas Norlinder Date: Mon, 17 Nov 2025 10:42:02 +0000 Subject: [PATCH 079/418] 8368527: JMX: Add an MXBeans method to query GC CPU time Reviewed-by: phh, kevinw --- src/hotspot/share/include/jmm.h | 4 +- src/hotspot/share/services/cpuTimeUsage.cpp | 1 + src/hotspot/share/services/management.cpp | 19 +++ .../java/lang/management/MemoryMXBean.java | 40 ++++++- .../classes/sun/management/MemoryImpl.java | 4 + .../classes/sun/management/VMManagement.java | 1 + .../sun/management/VMManagementImpl.java | 1 + .../native/libmanagement/VMManagementImpl.c | 7 ++ .../share/server/ServerMemoryMXBean.java | 4 + ...StressGetTotalGcCpuTimeDuringShutdown.java | 112 ++++++++++++++++++ .../MemoryMXBean/TestGetTotalGcCpuTime.java | 112 ++++++++++++++++++ .../management/mxbean/MXBeanInteropTest1.java | 2 + 12 files changed, 304 insertions(+), 3 deletions(-) create mode 100644 test/jdk/java/lang/management/MemoryMXBean/StressGetTotalGcCpuTimeDuringShutdown.java create mode 100644 test/jdk/java/lang/management/MemoryMXBean/TestGetTotalGcCpuTime.java diff --git a/src/hotspot/share/include/jmm.h b/src/hotspot/share/include/jmm.h index ba7ed3bbca5..ee1462fe5a8 100644 --- a/src/hotspot/share/include/jmm.h +++ b/src/hotspot/share/include/jmm.h @@ -53,7 +53,8 @@ enum { JMM_VERSION_2 = 0x20020000, // JDK 10 JMM_VERSION_3 = 0x20030000, // JDK 14 JMM_VERSION_4 = 0x20040000, // JDK 21 - JMM_VERSION = JMM_VERSION_4 + JMM_VERSION_5 = 0x20050000, // JDK 26 + JMM_VERSION = JMM_VERSION_5 }; typedef struct { @@ -81,6 +82,7 @@ typedef enum { JMM_GC_TIME_MS = 9, /* Total accumulated time spent in collection */ JMM_GC_COUNT = 10, /* Total number of collections */ JMM_JVM_UPTIME_MS = 11, /* The JVM uptime in milliseconds */ + JMM_TOTAL_GC_CPU_TIME = 12, /* Total accumulated GC CPU time */ JMM_INTERNAL_ATTRIBUTE_INDEX = 100, JMM_CLASS_LOADED_BYTES = 101, /* Number of bytes loaded instance classes */ diff --git a/src/hotspot/share/services/cpuTimeUsage.cpp b/src/hotspot/share/services/cpuTimeUsage.cpp index 27b5e90fbaf..0c7ecfdb655 100644 --- a/src/hotspot/share/services/cpuTimeUsage.cpp +++ b/src/hotspot/share/services/cpuTimeUsage.cpp @@ -36,6 +36,7 @@ volatile bool CPUTimeUsage::Error::_has_error = false; static inline jlong thread_cpu_time_or_zero(Thread* thread) { + assert(!Universe::is_shutting_down(), "Should not query during shutdown"); jlong cpu_time = os::thread_cpu_time(thread); if (cpu_time == -1) { CPUTimeUsage::Error::mark_error(); diff --git a/src/hotspot/share/services/management.cpp b/src/hotspot/share/services/management.cpp index cfe13d0c8f1..cc26e2e1352 100644 --- a/src/hotspot/share/services/management.cpp +++ b/src/hotspot/share/services/management.cpp @@ -54,6 +54,7 @@ #include "runtime/threadSMR.hpp" #include "runtime/vmOperations.hpp" #include "services/classLoadingService.hpp" +#include "services/cpuTimeUsage.hpp" #include "services/diagnosticCommand.hpp" #include "services/diagnosticFramework.hpp" #include "services/finalizerService.hpp" @@ -889,6 +890,21 @@ static jint get_num_flags() { return count; } +static jlong get_gc_cpu_time() { + if (!os::is_thread_cpu_time_supported()) { + return -1; + } + + { + MutexLocker hl(Heap_lock); + if (Universe::heap()->is_shutting_down()) { + return -1; + } + + return CPUTimeUsage::GC::total(); + } +} + static jlong get_long_attribute(jmmLongAttribute att) { switch (att) { case JMM_CLASS_LOADED_COUNT: @@ -915,6 +931,9 @@ static jlong get_long_attribute(jmmLongAttribute att) { case JMM_JVM_UPTIME_MS: return Management::ticks_to_ms(os::elapsed_counter()); + case JMM_TOTAL_GC_CPU_TIME: + return get_gc_cpu_time(); + case JMM_COMPILE_TOTAL_TIME_MS: return Management::ticks_to_ms(CompileBroker::total_compilation_ticks()); diff --git a/src/java.management/share/classes/java/lang/management/MemoryMXBean.java b/src/java.management/share/classes/java/lang/management/MemoryMXBean.java index 2c982e1ce9c..1211d8d1c4d 100644 --- a/src/java.management/share/classes/java/lang/management/MemoryMXBean.java +++ b/src/java.management/share/classes/java/lang/management/MemoryMXBean.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2025, 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 @@ -267,6 +267,43 @@ public interface MemoryMXBean extends PlatformManagedObject { */ public MemoryUsage getNonHeapMemoryUsage(); + /** + * Returns the approximate accumulated time, in nanoseconds, + * spent in garbage collection (GC). + * + *

The time spent in spent in GC is the CPU time used by + * all GC activity, including any overhead, which means the + * result may be non-zero even if no GC has occurred. + * + * This method returns {@code -1} if the platform does + * not support this operation or the information is not + * available. + * + * @apiNote + * May be used in conjunction with {@link jdk.management/com.sun.management.OperatingSystemMXBean#getProcessCpuTime()} + * for calculating the GC's usage of CPU time as a whole. + * + * @implNote The specifics on what constitutes the time spent + * in GC are highly implementation dependent. In the HotSpot + * Virtual Machine, this time includes relevant + * implementation-specific details such as driver threads, + * workers, VM Operations and string deduplication (if + * enabled). Driver threads may be created by a GC to + * orchestrate its work. The return value can be -1 if called + * when measurement is not possible, such as during shutdown. + * + * @implSpec The default implementation returns {@code -1}. + * + * @return the total accumulated CPU time for GC in + * nanoseconds, or {@code -1}. + * + * @since 26 + */ + @SuppressWarnings("doclint:reference") + default public long getTotalGcCpuTime() { + return -1; + } + /** * Tests if verbose output for the memory system is enabled. * @@ -302,5 +339,4 @@ public interface MemoryMXBean extends PlatformManagedObject { * @see java.lang.System#gc() */ public void gc(); - } diff --git a/src/java.management/share/classes/sun/management/MemoryImpl.java b/src/java.management/share/classes/sun/management/MemoryImpl.java index e1f68ce3711..3e27ba2e5bb 100644 --- a/src/java.management/share/classes/sun/management/MemoryImpl.java +++ b/src/java.management/share/classes/sun/management/MemoryImpl.java @@ -67,6 +67,10 @@ class MemoryImpl extends NotificationEmitterSupport Runtime.getRuntime().gc(); } + public long getTotalGcCpuTime() { + return jvm.getTotalGcCpuTime(); + } + // Need to make a VM call to get coherent value public MemoryUsage getHeapMemoryUsage() { return getMemoryUsage0(true); diff --git a/src/java.management/share/classes/sun/management/VMManagement.java b/src/java.management/share/classes/sun/management/VMManagement.java index f4445f0225a..3e227225ccd 100644 --- a/src/java.management/share/classes/sun/management/VMManagement.java +++ b/src/java.management/share/classes/sun/management/VMManagement.java @@ -55,6 +55,7 @@ public interface VMManagement { public boolean getVerboseClass(); // Memory Subsystem + public long getTotalGcCpuTime(); public boolean getVerboseGC(); // Runtime Subsystem diff --git a/src/java.management/share/classes/sun/management/VMManagementImpl.java b/src/java.management/share/classes/sun/management/VMManagementImpl.java index 041f09547d2..b8c7a2921d4 100644 --- a/src/java.management/share/classes/sun/management/VMManagementImpl.java +++ b/src/java.management/share/classes/sun/management/VMManagementImpl.java @@ -128,6 +128,7 @@ class VMManagementImpl implements VMManagement { public native boolean getVerboseClass(); // Memory Subsystem + public native long getTotalGcCpuTime(); public native boolean getVerboseGC(); // Runtime Subsystem diff --git a/src/java.management/share/native/libmanagement/VMManagementImpl.c b/src/java.management/share/native/libmanagement/VMManagementImpl.c index f1a566676dc..d5141c71ee7 100644 --- a/src/java.management/share/native/libmanagement/VMManagementImpl.c +++ b/src/java.management/share/native/libmanagement/VMManagementImpl.c @@ -121,6 +121,13 @@ Java_sun_management_VMManagementImpl_getUnloadedClassCount return count; } +JNIEXPORT jlong JNICALL +Java_sun_management_VMManagementImpl_getTotalGcCpuTime + (JNIEnv *env, jobject dummy) +{ + return jmm_interface->GetLongAttribute(env, NULL, JMM_TOTAL_GC_CPU_TIME); +} + JNIEXPORT jboolean JNICALL Java_sun_management_VMManagementImpl_getVerboseGC (JNIEnv *env, jobject dummy) diff --git a/test/hotspot/jtreg/vmTestbase/nsk/monitoring/share/server/ServerMemoryMXBean.java b/test/hotspot/jtreg/vmTestbase/nsk/monitoring/share/server/ServerMemoryMXBean.java index 0c536fcd123..31050b6b8bb 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/monitoring/share/server/ServerMemoryMXBean.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/monitoring/share/server/ServerMemoryMXBean.java @@ -58,6 +58,10 @@ public class ServerMemoryMXBean extends ServerMXBean implements MemoryMXBean { return getIntAttribute(OBJECT_PENDING_FINALIZATION_COUNT); } + public long getTotalGcCpuTime() { + throw new UnsupportedOperationException("This method is not supported"); + } + public boolean isVerbose() { return getBooleanAttribute(VERBOSE); } diff --git a/test/jdk/java/lang/management/MemoryMXBean/StressGetTotalGcCpuTimeDuringShutdown.java b/test/jdk/java/lang/management/MemoryMXBean/StressGetTotalGcCpuTimeDuringShutdown.java new file mode 100644 index 00000000000..21a8b18ffd7 --- /dev/null +++ b/test/jdk/java/lang/management/MemoryMXBean/StressGetTotalGcCpuTimeDuringShutdown.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test id=Epsilon + * @requires vm.gc.Epsilon + * @bug 8368527 + * @summary Stress MemoryMXBean.getTotalGcCpuTime during shutdown + * @library /test/lib + * @run main/othervm -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC StressGetTotalGcCpuTimeDuringShutdown + */ + +/* + * @test id=Serial + * @requires vm.gc.Serial + * @bug 8368527 + * @summary Stress MemoryMXBean.getTotalGcCpuTime during shutdown + * @library /test/lib + * @run main/othervm -XX:+UseSerialGC StressGetTotalGcCpuTimeDuringShutdown + */ + +/* + * @test id=Parallel + * @requires vm.gc.Parallel + * @bug 8368527 + * @summary Stress MemoryMXBean.getTotalGcCpuTime during shutdown + * @library /test/lib + * @run main/othervm -XX:+UseParallelGC StressGetTotalGcCpuTimeDuringShutdown + */ + +/* + * @test id=G1 + * @requires vm.gc.G1 + * @bug 8368527 + * @summary Stress MemoryMXBean.getTotalGcCpuTime during shutdown + * @library /test/lib + * @run main/othervm -XX:+UseG1GC StressGetTotalGcCpuTimeDuringShutdown + */ + +/* + * @test id=ZGC + * @requires vm.gc.Z + * @bug 8368527 + * @summary Stress MemoryMXBean.getTotalGcCpuTime during shutdown + * @library /test/lib + * @run main/othervm -XX:+UseZGC StressGetTotalGcCpuTimeDuringShutdown + */ + +/* + * @test id=Shenandoah + * @requires vm.gc.Shenandoah + * @bug 8368527 + * @summary Stress MemoryMXBean.getTotalGcCpuTime during shutdown + * @library /test/lib + * @run main/othervm -XX:+UseShenandoahGC StressGetTotalGcCpuTimeDuringShutdown + */ + +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryMXBean; +import java.lang.management.ThreadMXBean; + +public class StressGetTotalGcCpuTimeDuringShutdown { + static final ThreadMXBean mxThreadBean = ManagementFactory.getThreadMXBean(); + static final MemoryMXBean mxMemoryBean = ManagementFactory.getMemoryMXBean(); + + public static void main(String[] args) throws Exception { + try { + if (!mxThreadBean.isThreadCpuTimeEnabled()) { + return; + } + } catch (UnsupportedOperationException e) { + if (mxMemoryBean.getTotalGcCpuTime() != -1) { + throw new RuntimeException("GC CPU time should be -1"); + } + return; + } + + final int numberOfThreads = Runtime.getRuntime().availableProcessors() * 8; + for (int i = 0; i < numberOfThreads; i++) { + Thread t = new Thread(() -> { + while (true) { + long gcCpuTimeFromThread = mxMemoryBean.getTotalGcCpuTime(); + if (gcCpuTimeFromThread < -1) { + throw new RuntimeException("GC CPU time should never be less than -1 but was " + gcCpuTimeFromThread); + } + } + }); + t.setDaemon(true); + t.start(); + } + } +} diff --git a/test/jdk/java/lang/management/MemoryMXBean/TestGetTotalGcCpuTime.java b/test/jdk/java/lang/management/MemoryMXBean/TestGetTotalGcCpuTime.java new file mode 100644 index 00000000000..d8a760027a7 --- /dev/null +++ b/test/jdk/java/lang/management/MemoryMXBean/TestGetTotalGcCpuTime.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test id=Epsilon + * @requires vm.gc.Epsilon + * @bug 8368527 + * @summary Stress MemoryMXBean.getTotalGcCpuTime during shutdown + * @library /test/lib + * @run main/othervm -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC TestGetTotalGcCpuTime + */ + +/* + * @test id=Serial + * @requires vm.gc.Serial + * @bug 8368527 + * @summary Stress MemoryMXBean.getTotalGcCpuTime during shutdown + * @library /test/lib + * @run main/othervm -XX:+UseSerialGC TestGetTotalGcCpuTime + */ + +/* + * @test id=Parallel + * @requires vm.gc.Parallel + * @bug 8368527 + * @summary Stress MemoryMXBean.getTotalGcCpuTime during shutdown + * @library /test/lib + * @run main/othervm -XX:+UseParallelGC TestGetTotalGcCpuTime + */ + +/* + * @test id=G1 + * @requires vm.gc.G1 + * @bug 8368527 + * @summary Stress MemoryMXBean.getTotalGcCpuTime during shutdown + * @library /test/lib + * @run main/othervm -XX:+UseG1GC TestGetTotalGcCpuTime + */ + +/* + * @test id=ZGC + * @requires vm.gc.Z + * @bug 8368527 + * @summary Stress MemoryMXBean.getTotalGcCpuTime during shutdown + * @library /test/lib + * @run main/othervm -XX:+UseZGC TestGetTotalGcCpuTime + */ + +/* + * @test id=Shenandoah + * @requires vm.gc.Shenandoah + * @bug 8368527 + * @summary Stress MemoryMXBean.getTotalGcCpuTime during shutdown + * @library /test/lib + * @run main/othervm -XX:+UseShenandoahGC TestGetTotalGcCpuTime + */ + +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryMXBean; +import java.lang.management.ThreadMXBean; + +public class TestGetTotalGcCpuTime { + static final ThreadMXBean mxThreadBean = ManagementFactory.getThreadMXBean(); + static final MemoryMXBean mxMemoryBean = ManagementFactory.getMemoryMXBean(); + static final boolean usingEpsilonGC = ManagementFactory.getRuntimeMXBean().getInputArguments().stream().anyMatch(p -> p.contains("-XX:+UseEpsilonGC")); + + public static void main(String[] args) throws Exception { + try { + if (!mxThreadBean.isThreadCpuTimeEnabled()) { + return; + } + } catch (UnsupportedOperationException e) { + if (mxMemoryBean.getTotalGcCpuTime() != -1) { + throw new RuntimeException("GC CPU time should be -1"); + } + return; + } + + System.gc(); + long gcCpuTimeFromThread = mxMemoryBean.getTotalGcCpuTime(); + + if (usingEpsilonGC) { + if (gcCpuTimeFromThread != 0) { + throw new RuntimeException("Epsilon GC can't have any GC CPU time by definition"); + } + } else { + if (gcCpuTimeFromThread <= 0) { + throw new RuntimeException("Some GC CPU time must have been reported"); + } + } + } +} diff --git a/test/jdk/javax/management/mxbean/MXBeanInteropTest1.java b/test/jdk/javax/management/mxbean/MXBeanInteropTest1.java index 500fef1c2f3..011bdb144fb 100644 --- a/test/jdk/javax/management/mxbean/MXBeanInteropTest1.java +++ b/test/jdk/javax/management/mxbean/MXBeanInteropTest1.java @@ -222,6 +222,8 @@ public class MXBeanInteropTest1 { + memory.getNonHeapMemoryUsage()); System.out.println("getObjectPendingFinalizationCount\t\t" + memory.getObjectPendingFinalizationCount()); + System.out.println("getTotalGcCpuTime\t\t" + + memory.getTotalGcCpuTime()); System.out.println("isVerbose\t\t" + memory.isVerbose()); From d19e072f97681cfc50a8c7b96a25589070436a10 Mon Sep 17 00:00:00 2001 From: Daniel Fuchs Date: Mon, 17 Nov 2025 11:01:32 +0000 Subject: [PATCH 080/418] 8371916: Questionable volatile decrement in AckFrameSpliterator Reviewed-by: vyazici, jpai, djelinski --- .../net/http/quic/frames/AckFrame.java | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/quic/frames/AckFrame.java b/src/java.net.http/share/classes/jdk/internal/net/http/quic/frames/AckFrame.java index 7983d1be4f0..09d05f91066 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/quic/frames/AckFrame.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/quic/frames/AckFrame.java @@ -86,7 +86,7 @@ public final class AckFrame extends QuicFrame { } var ackRanges = new ArrayList(ackRangeCount + 1); AckRange first = AckRange.of(0, firstAckRange); - ackRanges.add(0, first); + ackRanges.addFirst(first); for (int i=1; i <= ackRangeCount; i++) { long gap = decodeVLField(buffer, "gap"); long len = decodeVLField(buffer, "range length"); @@ -139,10 +139,10 @@ public final class AckFrame extends QuicFrame { super(ACK); this.largestAcknowledged = requireVLRange(largestAcknowledged, "largestAcknowledged"); this.ackDelay = requireVLRange(ackDelay, "ackDelay"); - if (ackRanges.size() < 1) { + if (ackRanges.isEmpty()) { throw new IllegalArgumentException("insufficient ackRanges"); } - if (ackRanges.get(0).gap() != 0) { + if (ackRanges.getFirst().gap() != 0) { throw new IllegalArgumentException("first range must have zero gap"); } this.ackRanges = List.copyOf(ackRanges); @@ -178,7 +178,7 @@ public final class AckFrame extends QuicFrame { encodeVLField(buffer, largestAcknowledged, "largestAcknowledged"); encodeVLField(buffer, ackDelay, "ackDelay"); encodeVLField(buffer, ackRangeCount, "ackRangeCount"); - encodeVLField(buffer, ackRanges.get(0).range(), "firstAckRange"); + encodeVLField(buffer, ackRanges.getFirst().range(), "firstAckRange"); for (int i=1; i <= ackRangeCount; i++) { AckRange ar = ackRanges.get(i); encodeVLField(buffer, ar.gap(), "gap"); @@ -198,7 +198,7 @@ public final class AckFrame extends QuicFrame { + getVLFieldLengthFor(largestAcknowledged) + getVLFieldLengthFor(ackDelay) + getVLFieldLengthFor(ackRangeCount) - + getVLFieldLengthFor(ackRanges.get(0).range()) + + getVLFieldLengthFor(ackRanges.getFirst().range()) + ackRanges.stream().skip(1).mapToInt(AckRange::size).sum(); if (countsPresent) { size = size + getVLFieldLengthFor(ect0Count) @@ -236,7 +236,7 @@ public final class AckFrame extends QuicFrame { /** * {@return a new {@code AckFrame} identical to this one, but * with the given {@code ackDelay}}; - * @param ackDelay + * @param ackDelay the delay sending the Ack Frame */ public AckFrame withAckDelay(long ackDelay) { if (ackDelay == this.ackDelay) return this; @@ -361,9 +361,9 @@ public final class AckFrame extends QuicFrame { return null; } private final Iterator ackRangeIterator; - private volatile long largest; - private volatile long smallest; - private volatile long pn; // the current packet number + private long largest; + private long smallest; + private long pn; // the current packet number // The stream returns packet number in decreasing order // (largest packet number is returned first) @@ -377,7 +377,7 @@ public final class AckFrame extends QuicFrame { @Override public boolean tryAdvance(LongConsumer action) { // First call will see pn == 0 and smallest >= 2, - // which guarantees we will not enter the if below + // which guarantees we will not enter the `if` below // before pn has been initialized from the // first ackRange value if (pn >= smallest) { @@ -687,7 +687,7 @@ public final class AckFrame extends QuicFrame { return this; } else if (packetNumber == smallest - 1) { // Otherwise, if the packet number to be acknowledged is - // just below the smallest packet ackowledged by the current + // just below the smallest packet acknowledged by the current // range, there are again two cases, depending on // whether the next ackRange has a gap that can be reduced, // or not @@ -855,7 +855,7 @@ public final class AckFrame extends QuicFrame { private AckFrameBuilder acknowledgeLargerPacket(long largerThanLargest) { var packetNumber = largerThanLargest; // the new packet is larger than the largest acknowledged - var firstAckRange = ackRanges.get(0); + var firstAckRange = ackRanges.getFirst(); if (largestAcknowledged == packetNumber -1) { // if packetNumber is largestAcknowledged + 1, we can simply // extend the first ack range by 1 @@ -864,7 +864,7 @@ public final class AckFrame extends QuicFrame { } else { // otherwise - we have a gap - we need to acknowledge the new packetNumber, // and then add the gap that separate it from the previous largestAcknowledged... - ackRanges.add(0, AckRange.INITIAL); // acknowledge packetNumber only + ackRanges.addFirst(AckRange.INITIAL); // acknowledge packetNumber only long gap = packetNumber - largestAcknowledged -2; var secondAckRange = AckRange.of(gap, firstAckRange.range); ackRanges.set(1, secondAckRange); // add the gap From df35412db1d7e883148590e24d968cfe2f5c6bbc Mon Sep 17 00:00:00 2001 From: Zihao Lin Date: Mon, 17 Nov 2025 11:49:01 +0000 Subject: [PATCH 081/418] 8368961: Remove redundant checks in ciField.cpp Reviewed-by: bmaillard, aseoane, thartmann --- src/hotspot/share/ci/ciField.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/hotspot/share/ci/ciField.cpp b/src/hotspot/share/ci/ciField.cpp index d47c4c508d7..8f33c17d0e6 100644 --- a/src/hotspot/share/ci/ciField.cpp +++ b/src/hotspot/share/ci/ciField.cpp @@ -216,9 +216,6 @@ ciField::ciField(fieldDescriptor *fd) : static bool trust_final_non_static_fields(ciInstanceKlass* holder) { if (holder == nullptr) return false; - if (holder->name() == ciSymbols::java_lang_System()) - // Never trust strangely unstable finals: System.out, etc. - return false; // Even if general trusting is disabled, trust system-built closures in these packages. if (holder->is_in_package("java/lang/invoke") || holder->is_in_package("sun/invoke") || holder->is_in_package("java/lang/reflect") || holder->is_in_package("jdk/internal/reflect") || @@ -230,15 +227,9 @@ static bool trust_final_non_static_fields(ciInstanceKlass* holder) { // can't be serialized, so there is no hacking of finals going on with them. if (holder->is_hidden()) return true; - // Trust final fields in all boxed classes - if (holder->is_box_klass()) - return true; // Trust final fields in records if (holder->is_record()) return true; - // Trust final fields in String - if (holder->name() == ciSymbols::java_lang_String()) - return true; // Trust Atomic*FieldUpdaters: they are very important for performance, and make up one // more reason not to use Unsafe, if their final fields are trusted. See more in JDK-8140483. if (holder->name() == ciSymbols::java_util_concurrent_atomic_AtomicIntegerFieldUpdater_Impl() || From cebb03ef24fad8705156f12cecd2da6351cd1ef6 Mon Sep 17 00:00:00 2001 From: Matthew Donovan Date: Mon, 17 Nov 2025 12:13:39 +0000 Subject: [PATCH 082/418] 8371349: Update NSS library to 3.117 Reviewed-by: weijun, myankelevich, hchao --- test/jdk/sun/security/pkcs11/PKCS11Test.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jdk/sun/security/pkcs11/PKCS11Test.java b/test/jdk/sun/security/pkcs11/PKCS11Test.java index 98343d9b7e6..0e8d2d61d53 100644 --- a/test/jdk/sun/security/pkcs11/PKCS11Test.java +++ b/test/jdk/sun/security/pkcs11/PKCS11Test.java @@ -80,7 +80,7 @@ public abstract class PKCS11Test { // Version of the NSS artifact. This coincides with the version of // the NSS version - private static final String NSS_BUNDLE_VERSION = "3.111"; + private static final String NSS_BUNDLE_VERSION = "3.117"; private static final String NSSLIB = "jpg.tests.jdk.nsslib"; static Version nss_version = null; From 8301d9917ec9fed40e3af77998f88165e0837daf Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Mon, 17 Nov 2025 12:28:39 +0000 Subject: [PATCH 083/418] 8371825: G1: Use more precise filler API in fill_range_with_dead_objects Reviewed-by: shade, tschatzl --- src/hotspot/share/gc/g1/g1HeapRegion.cpp | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1HeapRegion.cpp b/src/hotspot/share/gc/g1/g1HeapRegion.cpp index 63d64503316..b1eeb333d8d 100644 --- a/src/hotspot/share/gc/g1/g1HeapRegion.cpp +++ b/src/hotspot/share/gc/g1/g1HeapRegion.cpp @@ -787,23 +787,13 @@ void G1HeapRegion::fill_range_with_dead_objects(HeapWord* start, HeapWord* end) // possible that there is a pinned object that is not any more referenced by // Java code (only by native). // - // In this case we must not zap contents of such an array but we can overwrite - // the header; since only pinned typearrays are allowed, this fits nicely with - // putting filler arrays into the dead range as the object header sizes match and - // no user data is overwritten. + // In this case we should not zap, because that would overwrite + // user-observable data. Memory corresponding to obj-header is safe to + // change, since it's not directly user-observable. // // In particular String Deduplication might change the reference to the character // array of the j.l.String after native code obtained a raw reference to it (via // GetStringCritical()). - CollectedHeap::fill_with_objects(start, range_size, !has_pinned_objects()); - HeapWord* current = start; - do { - // Update the BOT if the a threshold is crossed. - size_t obj_size = cast_to_oop(current)->size(); - update_bot_for_block(current, current + obj_size); - - // Advance to the next object. - current += obj_size; - guarantee(current <= end, "Should never go past end"); - } while (current != end); + CollectedHeap::fill_with_object(start, range_size, !has_pinned_objects()); + update_bot_for_block(start, start + range_size); } From 960987e8c1428ce1d89ee13e007e06206fe6885c Mon Sep 17 00:00:00 2001 From: Kevin Walls Date: Mon, 17 Nov 2025 13:40:36 +0000 Subject: [PATCH 084/418] 8371991: Build failure in docs for MemoryMXBean Reviewed-by: alanb --- .../share/classes/java/lang/management/MemoryMXBean.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/java.management/share/classes/java/lang/management/MemoryMXBean.java b/src/java.management/share/classes/java/lang/management/MemoryMXBean.java index 1211d8d1c4d..247868d3d99 100644 --- a/src/java.management/share/classes/java/lang/management/MemoryMXBean.java +++ b/src/java.management/share/classes/java/lang/management/MemoryMXBean.java @@ -280,7 +280,7 @@ public interface MemoryMXBean extends PlatformManagedObject { * available. * * @apiNote - * May be used in conjunction with {@link jdk.management/com.sun.management.OperatingSystemMXBean#getProcessCpuTime()} + * May be used in conjunction with {@code jdk.management/com.sun.management.OperatingSystemMXBean#getProcessCpuTime()} * for calculating the GC's usage of CPU time as a whole. * * @implNote The specifics on what constitutes the time spent @@ -299,7 +299,6 @@ public interface MemoryMXBean extends PlatformManagedObject { * * @since 26 */ - @SuppressWarnings("doclint:reference") default public long getTotalGcCpuTime() { return -1; } From 44087ea5d697deb3a7dd0e3c82f898dd9df1bfa3 Mon Sep 17 00:00:00 2001 From: SendaoYan Date: Mon, 17 Nov 2025 13:48:52 +0000 Subject: [PATCH 085/418] 8371682: Suppress javac warning from ThreadPoolExecutorSubclassTest.java Reviewed-by: jpai --- .../java/util/concurrent/tck/ExecutorsTest.java | 1 - .../java/util/concurrent/tck/JSR166TestCase.java | 1 - .../tck/ThreadPoolExecutorSubclassTest.java | 15 --------------- 3 files changed, 17 deletions(-) diff --git a/test/jdk/java/util/concurrent/tck/ExecutorsTest.java b/test/jdk/java/util/concurrent/tck/ExecutorsTest.java index b4b4324ae5c..91dfd3cce1c 100644 --- a/test/jdk/java/util/concurrent/tck/ExecutorsTest.java +++ b/test/jdk/java/util/concurrent/tck/ExecutorsTest.java @@ -329,7 +329,6 @@ public class ExecutorsTest extends JSR166TestCase { * ThreadPoolExecutor using defaultThreadFactory has * specified group, priority, daemon status, and name */ - @SuppressWarnings("removal") public void testDefaultThreadFactory() throws Exception { final ThreadGroup egroup = Thread.currentThread().getThreadGroup(); final CountDownLatch done = new CountDownLatch(1); diff --git a/test/jdk/java/util/concurrent/tck/JSR166TestCase.java b/test/jdk/java/util/concurrent/tck/JSR166TestCase.java index 80e6c98bfa7..f1f32bee310 100644 --- a/test/jdk/java/util/concurrent/tck/JSR166TestCase.java +++ b/test/jdk/java/util/concurrent/tck/JSR166TestCase.java @@ -1169,7 +1169,6 @@ public class JSR166TestCase extends TestCase { * A debugging tool to print stack traces of most threads, as jstack does. * Uninteresting threads are filtered out. */ - @SuppressWarnings("removal") static void dumpTestThreads() { System.err.println("------ stacktrace dump start ------"); for (ThreadInfo info : THREAD_MXBEAN.dumpAllThreads(true, true)) diff --git a/test/jdk/java/util/concurrent/tck/ThreadPoolExecutorSubclassTest.java b/test/jdk/java/util/concurrent/tck/ThreadPoolExecutorSubclassTest.java index dec00360f60..5f117fd5e85 100644 --- a/test/jdk/java/util/concurrent/tck/ThreadPoolExecutorSubclassTest.java +++ b/test/jdk/java/util/concurrent/tck/ThreadPoolExecutorSubclassTest.java @@ -1997,20 +1997,5 @@ public class ThreadPoolExecutorSubclassTest extends JSR166TestCase { } } } - @SuppressWarnings("deprecation") - public void testFinalizeMethodCallsSuperFinalize() { - new CustomTPE(1, 1, - LONG_DELAY_MS, MILLISECONDS, - new LinkedBlockingQueue()) { - - /** - * A finalize method without "throws Throwable", that - * calls super.finalize(). - */ - protected void finalize() { - super.finalize(); - } - }.shutdown(); - } } From 6385c663dc6ce892c23bc9208e1ffe24fa78ccd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20H=C3=BCbner?= Date: Mon, 17 Nov 2025 14:06:41 +0000 Subject: [PATCH 086/418] 8371607: Remove GCSharedStringsDuringDumpWb.java after JDK-8362561 Reviewed-by: ayang, jsikstro --- .../javaldr/GCSharedStringsDuringDumpWb.java | 45 ------------------- 1 file changed, 45 deletions(-) delete mode 100644 test/hotspot/jtreg/runtime/cds/appcds/javaldr/GCSharedStringsDuringDumpWb.java diff --git a/test/hotspot/jtreg/runtime/cds/appcds/javaldr/GCSharedStringsDuringDumpWb.java b/test/hotspot/jtreg/runtime/cds/appcds/javaldr/GCSharedStringsDuringDumpWb.java deleted file mode 100644 index f00fd3fa4a5..00000000000 --- a/test/hotspot/jtreg/runtime/cds/appcds/javaldr/GCSharedStringsDuringDumpWb.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2017, 2022, 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.whitebox.WhiteBox; - -public class GCSharedStringsDuringDumpWb { - public static void main(String[] args) throws Exception { - WhiteBox wb = WhiteBox.getWhiteBox(); - String s = "shared_test_string_unique_14325"; - s = s.intern(); - CheckString(wb, s); - for (int i=0; i<100000; i++) { - s = "generated_string " + i; - s = s.intern(); - CheckString(wb, s); - } - } - - public static void CheckString(WhiteBox wb, String s) { - if (wb.areSharedStringsMapped() && !wb.isSharedInternedString(s)) { - throw new RuntimeException("String is not shared."); - } - } -} From 52ffe8a09637701cf93d3425b69089ced5ad4dcb Mon Sep 17 00:00:00 2001 From: Mark Powers Date: Mon, 17 Nov 2025 14:59:30 +0000 Subject: [PATCH 087/418] 8371156: PBKDF2 default values should not be DER encoded Reviewed-by: weijun --- .../classes/sun/security/util/PBKDF2Parameters.java | 6 ++++-- test/jdk/sun/security/pkcs12/ImportPassKeyAlg.java | 12 ++++++++---- test/jdk/sun/security/pkcs12/PBMAC1Test.java | 10 ++++++++++ 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/java.base/share/classes/sun/security/util/PBKDF2Parameters.java b/src/java.base/share/classes/sun/security/util/PBKDF2Parameters.java index 07d4c70fecb..ba6636d7922 100644 --- a/src/java.base/share/classes/sun/security/util/PBKDF2Parameters.java +++ b/src/java.base/share/classes/sun/security/util/PBKDF2Parameters.java @@ -27,7 +27,6 @@ package sun.security.util; import java.io.IOException; -import sun.security.util.KnownOIDs; import sun.security.x509.AlgorithmId; /** @@ -164,7 +163,10 @@ public final class PBKDF2Parameters { tmp0.putInteger(keyLength); // prf AlgorithmIdentifier {{PBKDF2-PRFs}} - tmp0.write(new AlgorithmId(prf)); + // HmacSHA1 is the default and must not be encoded. + if (!prf.equals(ObjectIdentifier.of(KnownOIDs.HmacSHA1))) { + tmp0.write(new AlgorithmId(prf)); + } // id-PBKDF2 OBJECT IDENTIFIER ::= {pkcs-5 12} out.putOID(ObjectIdentifier.of(KnownOIDs.PBKDF2)); diff --git a/test/jdk/sun/security/pkcs12/ImportPassKeyAlg.java b/test/jdk/sun/security/pkcs12/ImportPassKeyAlg.java index ba98c3ce2de..bf767a6344d 100644 --- a/test/jdk/sun/security/pkcs12/ImportPassKeyAlg.java +++ b/test/jdk/sun/security/pkcs12/ImportPassKeyAlg.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, 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 8286069 + * @bug 8286069 8371156 * @summary keytool prints out wrong key algorithm for -importpass command * @library /test/lib * @modules java.base/sun.security.util @@ -92,7 +92,7 @@ public class ImportPassKeyAlg { // 0034:0004 [1011] INTEGER 10000 // 0038:0003 [1012] INTEGER 16 // 003B:000E [1013] SEQUENCE - // 003D:000A [10130] OID 1.2.840.113549.2.7 (HmacSHA1) + // 003D:000A [10130] OID 1.2.840.113549.2.9 (HmacSHA256) // 0047:0002 [10131] NULL // 0049:001F [11] SEQUENCE // 004B:000B [110] OID 2.16.840.1.101.3.4.1.2 (AES_128/CBC/NoPadding) @@ -100,7 +100,11 @@ public class ImportPassKeyAlg { var data = Files.readAllBytes(Path.of(name)); DerUtils.checkAlg(data, "110c010c01010c00", oids[0]); if (oids[0] == KnownOIDs.PBES2) { - DerUtils.checkAlg(data, "110c010c01010c010130", oids[1]); + if (oids[1] == KnownOIDs.HmacSHA1) { + DerUtils.shouldNotExist(data, "110c010c01010c010130"); + } else { + DerUtils.checkAlg(data, "110c010c01010c010130", oids[1]); + } DerUtils.checkAlg(data, "110c010c01010c0110", oids[2]); } } diff --git a/test/jdk/sun/security/pkcs12/PBMAC1Test.java b/test/jdk/sun/security/pkcs12/PBMAC1Test.java index acb0f73a2d2..4e89322bc0a 100644 --- a/test/jdk/sun/security/pkcs12/PBMAC1Test.java +++ b/test/jdk/sun/security/pkcs12/PBMAC1Test.java @@ -116,6 +116,16 @@ public class PBMAC1Test { var reason = Asserts.assertThrows(NoSuchAlgorithmException.class, () -> emptyP12()).getMessage(); Asserts.assertTrue(reason.contains("Algorithm hmacsha456 not available"), reason); + + // Verify that DEFAULT HmacSHA1 prf does not get encoded. + System.setProperty("keystore.pkcs12.macAlgorithm", "PBEWITHHMACSHA1"); + der = emptyP12(); + DerUtils.checkAlg(der, "2000", KnownOIDs.PBMAC1); + DerUtils.checkAlg(der, "200100", KnownOIDs.PBKDF2); + DerUtils.shouldNotExist(der, "20010130"); + DerUtils.checkAlg(der, "200110", KnownOIDs.HmacSHA1); + DerUtils.checkInt(der, "2001011", 10000); + DerUtils.checkInt(der, "2001012", 20); } static void migrate() throws Exception { From 9ec773ad27773f5813c79ae33ac1d2393c2e0cc8 Mon Sep 17 00:00:00 2001 From: Brian Burkhalter Date: Mon, 17 Nov 2025 16:48:40 +0000 Subject: [PATCH 088/418] 8371689: (fs) CopyMoveHelper.copyToForeignTarget use of sourcePosixView is confusing Reviewed-by: alanb --- .../classes/java/nio/file/CopyMoveHelper.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/java.base/share/classes/java/nio/file/CopyMoveHelper.java b/src/java.base/share/classes/java/nio/file/CopyMoveHelper.java index 357e200e930..4e5bd7ebba1 100644 --- a/src/java.base/share/classes/java/nio/file/CopyMoveHelper.java +++ b/src/java.base/share/classes/java/nio/file/CopyMoveHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, 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 @@ -109,21 +109,21 @@ class CopyMoveHelper { LinkOption[] linkOptions = (opts.followLinks) ? new LinkOption[0] : new LinkOption[] { LinkOption.NOFOLLOW_LINKS }; - // retrieve source posix view, null if unsupported - final PosixFileAttributeView sourcePosixView = - Files.getFileAttributeView(source, PosixFileAttributeView.class); + // determine whether the source supports posix attributes + boolean sourceSupportsPosixAttributes = Files.getFileAttributeView + (source, PosixFileAttributeView.class) != null; // attributes of source file BasicFileAttributes sourceAttrs = null; - if (sourcePosixView != null) { + if (sourceSupportsPosixAttributes) sourceAttrs = Files.readAttributes(source, PosixFileAttributes.class, linkOptions); - } - if (sourceAttrs == null) + else sourceAttrs = Files.readAttributes(source, BasicFileAttributes.class, linkOptions); + assert sourceAttrs != null; if (sourceAttrs.isSymbolicLink()) @@ -151,7 +151,7 @@ class CopyMoveHelper { // copy basic and, if supported, POSIX attributes to target if (opts.copyAttributes) { BasicFileAttributeView targetView = null; - if (sourcePosixView != null) { + if (sourceSupportsPosixAttributes) { targetView = Files.getFileAttributeView(target, PosixFileAttributeView.class); } From 6c09529cd637a34c1ffc42a5feb71e8646be4237 Mon Sep 17 00:00:00 2001 From: Kim Barrett Date: Mon, 17 Nov 2025 20:53:10 +0000 Subject: [PATCH 089/418] 8369188: Update link-time check for HotSpot uses of allocation and deallocation functions Reviewed-by: jwaters, erikj --- make/hotspot/lib/CompileJvm.gmk | 38 ++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/make/hotspot/lib/CompileJvm.gmk b/make/hotspot/lib/CompileJvm.gmk index fd574b9e42d..a8b90c92e4d 100644 --- a/make/hotspot/lib/CompileJvm.gmk +++ b/make/hotspot/lib/CompileJvm.gmk @@ -337,6 +337,30 @@ TARGETS += $(BUILD_LIBJVM) # for the associated class. If the class doesn't provide a more specific # declaration (either directly or by inheriting from a class that provides # one) then the global definition will be used, triggering this check. +# + +# The HotSpot wrapper for declares as deprecated all the allocation and +# deallocation functions that use the global allocator. But that blocking +# isn't a bullet-proof. Some of these functions are implicitly available in +# every translation unit, without the need to include . So even with that +# wrapper we still need this link-time check. The implicitly declared +# functions and their mangled names are - from C++17 6.7.4: +# +# void* operator new(size_t) // _Znwm +# void* operator new(size_t, align_val_t) // _ZnwmSt11align_val_t +# +# void operator delete(void*) noexcept // _ZdlPv +# void operator delete(void*, size_t) noexcept // _ZdlPvm +# void operator delete(void*, align_val_t) noexcept // _ZdlPvSt11align_val_t +# void operator delete(void*, size_t, align_val_t) noexcept // _ZdlPvmSt11align_val_t +# +# void* operator new[](size_t) // _Znam +# void* operator new[](size_t, align_val_t) // _ZnamSt11align_val_t +# +# void operator delete[](void*) noexcept // _ZdaPv +# void operator delete[](void*, size_t) noexcept // _ZdaPvm +# void operator delete[](void*, align_val_t) noexcept // _ZdaPvSt11align_val_t +# void operator delete[](void*, size_t, align_val_t) noexcept // _ZdaPvmSt11align_val_t ifneq ($(GENERATE_COMPILE_COMMANDS_ONLY), true) ifneq ($(filter $(TOOLCHAIN_TYPE), gcc clang), ) @@ -347,10 +371,18 @@ ifneq ($(GENERATE_COMPILE_COMMANDS_ONLY), true) # so use mangled names when looking for symbols. # Save the demangling for when something is actually found. MANGLED_SYMS := \ - _ZdaPv \ - _ZdlPv \ - _Znam \ _Znwm \ + _ZnwmSt11align_val_t \ + _ZdlPv \ + _ZdlPvm \ + _ZdlPvSt11align_val_t \ + _ZdlPvmSt11align_val_t \ + _Znam \ + _ZnamSt11align_val_t \ + _ZdaPv \ + _ZdaPvm \ + _ZdaPvSt11align_val_t \ + _ZdaPvmSt11align_val_t \ # UNDEF_PATTERN := ' U ' From e5f63326100384d2c2be8c916423e1f120b595d3 Mon Sep 17 00:00:00 2001 From: Leonid Mesnik Date: Mon, 17 Nov 2025 21:00:22 +0000 Subject: [PATCH 090/418] 8371650: Add CMakeLists.txt and compile_commands.json into .gitignore Reviewed-by: erikj --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index 852b692f99b..0743489f8ec 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,8 @@ NashornProfile.txt *.rej *.orig test/benchmarks/**/target +/src/hotspot/CMakeLists.txt +/src/hotspot/compile_commands.json +/src/hotspot/cmake-build-debug/ +/src/hotspot/.cache/ +/src/hotspot/.idea/ From e067038796e2798132e07aa47b695f3c21b87e79 Mon Sep 17 00:00:00 2001 From: Damon Nguyen Date: Mon, 17 Nov 2025 21:21:03 +0000 Subject: [PATCH 091/418] 8150564: Migrate useful ExtendedRobot methods into awt.Robot Reviewed-by: kizune, prr, liach --- .../share/classes/java/awt/Robot.java | 227 ++++++++++++++++++ 1 file changed, 227 insertions(+) diff --git a/src/java.desktop/share/classes/java/awt/Robot.java b/src/java.desktop/share/classes/java/awt/Robot.java index e91cc582c4c..d887cad4f77 100644 --- a/src/java.desktop/share/classes/java/awt/Robot.java +++ b/src/java.desktop/share/classes/java/awt/Robot.java @@ -126,6 +126,20 @@ public class Robot { private DirectColorModel screenCapCM = null; + /** + * Default delay in milliseconds for mouse + * {@link #glide(int, int, int, int) glide}, + * {@link #type(int) type}, and + * {@link #click(int) click}. + */ + public static final int DEFAULT_DELAY = 20; + + /** + * Default pixel step-length for mouse + * {@link #glide(int, int, int, int) glide}. + */ + public static final int DEFAULT_STEP_LENGTH = 2; + /** * Constructs a Robot object in the coordinate system of the primary screen. * @@ -773,4 +787,217 @@ public class Robot { String params = "autoDelay = "+getAutoDelay()+", "+"autoWaitForIdle = "+isAutoWaitForIdle(); return getClass().getName() + "[ " + params + " ]"; } + + /** + * A convenience method that simulates clicking a mouse button by calling {@code mousePress}, + * {@code mouseRelease} and {@code waitForIdle}. Invokes {@code waitForIdle} with a default + * delay of {@value #DEFAULT_DELAY} milliseconds after {@code mousePress} and {@code mouseRelease} + * calls. For specifics on valid inputs see {@link java.awt.Robot#mousePress(int)}. + * + * @param buttons The button mask; a combination of one or more mouse button masks. + * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for + * extra mouse button and support for extended mouse buttons is + * {@linkplain Toolkit#areExtraMouseButtonsEnabled() disabled} by Java + * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for + * extra mouse button that does not exist on the mouse and support for extended + * mouse buttons is {@linkplain Toolkit#areExtraMouseButtonsEnabled() enabled} + * by Java + * @throws IllegalThreadStateException if called on the AWT event dispatching thread + * @see #DEFAULT_DELAY + * @see #mousePress(int) + * @see #mouseRelease(int) + * @see InputEvent#getMaskForButton(int) + * @see Toolkit#areExtraMouseButtonsEnabled() + * @see java.awt.event.MouseEvent + * @since 26 + */ + public void click(int buttons) { + try { + mousePress(buttons); + waitForIdle(DEFAULT_DELAY); + } finally { + mouseRelease(buttons); + waitForIdle(DEFAULT_DELAY); + } + } + + /** + * A convenience method that clicks mouse button 1. + * + * @throws IllegalThreadStateException if called on the AWT event dispatching thread + * @see #click(int) + * @since 26 + */ + public void click() { + click(InputEvent.BUTTON1_DOWN_MASK); + } + + /** + * A convenience method that calls {@code waitForIdle} then waits an additional specified + * {@code delayValue} time in milliseconds. + * + * @param delayValue Additional delay length in milliseconds to wait until thread + * sync been completed + * @throws IllegalThreadStateException if called on the AWT event + * dispatching thread + * @throws IllegalArgumentException if {@code delayValue} is not between {@code 0} + * and {@code 60,000} milliseconds inclusive + * @since 26 + */ + public void waitForIdle(int delayValue) { + waitForIdle(); + delay(delayValue); + } + + /** + * A convenience method that moves the mouse in multiple + * steps from its current location to the destination coordinates. + * + * @implSpec Invokes {@link #mouseMove(int, int) mouseMove} with a step-length + * of {@value #DEFAULT_STEP_LENGTH} and a step-delay of {@value #DEFAULT_DELAY}. + * + * @param x Destination point x coordinate + * @param y Destination point y coordinate + * + * @throws IllegalThreadStateException if called on the AWT event dispatching + * thread and {@code isAutoWaitForIdle} would return true + * @see #DEFAULT_STEP_LENGTH + * @see #DEFAULT_DELAY + * @see #glide(int, int, int, int, int, int) + * @since 26 + */ + public void glide(int x, int y) { + Point p = MouseInfo.getPointerInfo().getLocation(); + glide(p.x, p.y, x, y); + } + + /** + * A convenience method that moves the mouse in multiple steps + * from source coordinates to the destination coordinates. + * + * @implSpec Invokes {@link #mouseMove(int, int) mouseMove} with a step-length + * of {@value #DEFAULT_STEP_LENGTH} and a step-delay of {@value #DEFAULT_DELAY}. + * + * @param srcX Source point x coordinate + * @param srcY Source point y coordinate + * @param dstX Destination point x coordinate + * @param dstY Destination point y coordinate + * + * @throws IllegalThreadStateException if called on the AWT event dispatching + * thread and {@code isAutoWaitForIdle} would return true + * @see #DEFAULT_STEP_LENGTH + * @see #DEFAULT_DELAY + * @see #glide(int, int, int, int, int, int) + * @since 26 + */ + public void glide(int srcX, int srcY, int dstX, int dstY) { + glide(srcX, srcY, dstX, dstY, DEFAULT_STEP_LENGTH, DEFAULT_DELAY); + } + + /** + * A convenience method that moves the mouse in multiple + * steps from source point to the destination point with a + * given {@code stepLength} and {@code stepDelay}. + * + * @param srcX Source point x coordinate + * @param srcY Source point y coordinate + * @param destX Destination point x coordinate + * @param destY Destination point y coordinate + * @param stepLength Preferred length of one step in pixels + * @param stepDelay Delay between steps in milliseconds + * + * @throws IllegalArgumentException if {@code stepLength} is not greater than zero + * @throws IllegalArgumentException if {@code stepDelay} is not between {@code 0} + * and {@code 60,000} milliseconds inclusive + * @throws IllegalThreadStateException if called on the AWT event dispatching + * thread and {@code isAutoWaitForIdle} would return true + * @see #mouseMove(int, int) + * @see #delay(int) + * @since 26 + */ + public void glide(int srcX, int srcY, int destX, int destY, int stepLength, int stepDelay) { + if (stepLength <= 0) { + throw new IllegalArgumentException("Step length must be greater than zero"); + } + if (stepDelay <= 0 || stepDelay > 60000) { + throw new IllegalArgumentException("Step delay must be between 0 and 60,000 milliseconds"); + } + + int stepNum; + double tDx, tDy; + double dx, dy, ds; + double x, y; + + dx = (destX - srcX); + dy = (destY - srcY); + ds = Math.sqrt(dx*dx + dy*dy); + + tDx = dx / ds * stepLength; + tDy = dy / ds * stepLength; + + int stepsCount = (int) ds / stepLength; + + // Walk the mouse to the destination one step at a time + mouseMove(srcX, srcY); + + for (x = srcX, y = srcY, stepNum = 0; + stepNum < stepsCount; + stepNum++) { + x += tDx; + y += tDy; + mouseMove((int)x, (int)y); + delay(stepDelay); + } + + // Ensure the mouse moves to the right destination. + // The steps may have led the mouse to a slightly wrong place. + if (x != destX || y != destY) { + mouseMove(destX, destY); + } + } + + /** + * A convenience method that simulates typing a key by calling {@code keyPress} + * and {@code keyRelease}. Invokes {@code waitForIdle} with a delay of {@value #DEFAULT_DELAY} + * milliseconds after {@code keyPress} and {@code keyRelease} calls. + *

+ * Key codes that have more than one physical key associated with them + * (e.g. {@code KeyEvent.VK_SHIFT} could mean either the + * left or right shift key) will map to the left key. + * + * @param keycode Key to type (e.g. {@code KeyEvent.VK_A}) + * @throws IllegalArgumentException if {@code keycode} is not + * a valid key + * @throws IllegalThreadStateException if called on the AWT event dispatching thread + * @see #DEFAULT_DELAY + * @see #keyPress(int) + * @see #keyRelease(int) + * @see java.awt.event.KeyEvent + * @since 26 + */ + public synchronized void type(int keycode) { + keyPress(keycode); + waitForIdle(DEFAULT_DELAY); + keyRelease(keycode); + waitForIdle(DEFAULT_DELAY); + } + + /** + * A convenience method that simulates typing a char by calling {@code keyPress} + * and {@code keyRelease}. Gets the ExtendedKeyCode for the char and calls + * {@code type(int keycode)}. + * + * @param c Character to be typed (e.g. {@code 'a'}) + * @throws IllegalArgumentException if {@code keycode} is not + * a valid key + * @throws IllegalThreadStateException if called on the AWT event dispatching thread + * @see #type(int) + * @see #keyPress(int) + * @see #keyRelease(int) + * @see java.awt.event.KeyEvent + * @since 26 + */ + public synchronized void type(char c) { + type(KeyEvent.getExtendedKeyCodeForChar(c)); + } } From 696821670e11fee003906806f081038032ac4985 Mon Sep 17 00:00:00 2001 From: Ramkumar Sunderbabu Date: Tue, 18 Nov 2025 00:59:14 +0000 Subject: [PATCH 092/418] 8293484: AArch64: TestUseSHA512IntrinsicsOptionOnSupportedCPU.java fails on CPU with SHA512 feature support Reviewed-by: haosun, aph --- .../jtreg/compiler/intrinsics/sha/cli/DigestOptionsBase.java | 4 ++-- .../sha/cli/TestUseSHA512IntrinsicsOptionOnSupportedCPU.java | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/test/hotspot/jtreg/compiler/intrinsics/sha/cli/DigestOptionsBase.java b/test/hotspot/jtreg/compiler/intrinsics/sha/cli/DigestOptionsBase.java index 2d4d4353868..22b3bba854c 100644 --- a/test/hotspot/jtreg/compiler/intrinsics/sha/cli/DigestOptionsBase.java +++ b/test/hotspot/jtreg/compiler/intrinsics/sha/cli/DigestOptionsBase.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2025, 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 @@ -120,7 +120,7 @@ public class DigestOptionsBase extends CommandLineOptionTest { case DigestOptionsBase.USE_SHA256_INTRINSICS_OPTION: return IntrinsicPredicates.SHA256_INSTRUCTION_AVAILABLE; case DigestOptionsBase.USE_SHA512_INTRINSICS_OPTION: - return IntrinsicPredicates.SHA512_INSTRUCTION_AVAILABLE; + return IntrinsicPredicates.isSHA512IntrinsicAvailable(); case DigestOptionsBase.USE_SHA3_INTRINSICS_OPTION: return IntrinsicPredicates.SHA3_INSTRUCTION_AVAILABLE; default: diff --git a/test/hotspot/jtreg/compiler/intrinsics/sha/cli/TestUseSHA512IntrinsicsOptionOnSupportedCPU.java b/test/hotspot/jtreg/compiler/intrinsics/sha/cli/TestUseSHA512IntrinsicsOptionOnSupportedCPU.java index e349c22e383..7913a25d939 100644 --- a/test/hotspot/jtreg/compiler/intrinsics/sha/cli/TestUseSHA512IntrinsicsOptionOnSupportedCPU.java +++ b/test/hotspot/jtreg/compiler/intrinsics/sha/cli/TestUseSHA512IntrinsicsOptionOnSupportedCPU.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2025, 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 @@ * @test * @bug 8035968 * @summary Verify UseSHA512Intrinsics option processing on supported CPU. - * @requires os.arch!="x86" & os.arch!="i386" * @library /test/lib / * @requires vm.flagless * From 46b5e588ab18a68d164b1d97e71d769585c7c4b8 Mon Sep 17 00:00:00 2001 From: SendaoYan Date: Tue, 18 Nov 2025 03:18:36 +0000 Subject: [PATCH 093/418] 8371697: test/jdk/java/nio/file/FileStore/Basic.java fails after 8360887 on linux Reviewed-by: alanb --- test/jdk/java/nio/file/FileStore/Basic.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/test/jdk/java/nio/file/FileStore/Basic.java b/test/jdk/java/nio/file/FileStore/Basic.java index 0a7b3d5e2f6..052348c1f02 100644 --- a/test/jdk/java/nio/file/FileStore/Basic.java +++ b/test/jdk/java/nio/file/FileStore/Basic.java @@ -38,6 +38,7 @@ import java.io.IOException; import jdk.test.lib.Platform; import jdk.test.lib.util.FileUtils; +import static jdk.test.lib.Asserts.*; public class Basic { @@ -52,11 +53,6 @@ public class Basic { } } - static void assertTrue(boolean okay) { - if (!okay) - throw new RuntimeException("Assertion failed"); - } - static void checkWithin1GB(String space, long expected, long actual) { long diff = Math.abs(actual - expected); if (diff > G) { @@ -73,8 +69,11 @@ public class Basic { FileStore store = Files.getFileStore(file); boolean supported = store.supportsFileAttributeView(viewClass); assertTrue(store.supportsFileAttributeView(viewName) == supported); - boolean haveView = Files.getFileAttributeView(file, viewClass) != null; - assertTrue(haveView == supported); + // If the file attribute view is supported by the FileStore then + // Files.getFileAttributeView should return that view + if (supported) { + assertNotNull(Files.getFileAttributeView(file, viewClass)); + } } static void doTests(Path dir) throws IOException { From 695a4abd5f7e9edcea9f1a724a9ceb87340a8f25 Mon Sep 17 00:00:00 2001 From: Anjian Wen Date: Tue, 18 Nov 2025 03:37:11 +0000 Subject: [PATCH 094/418] 8371966: RISC-V: Incorrect pointer dereference in TemplateInterpreterGenerator::generate_native_entry Reviewed-by: fyang, fjiang --- src/hotspot/cpu/riscv/templateInterpreterGenerator_riscv.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/hotspot/cpu/riscv/templateInterpreterGenerator_riscv.cpp b/src/hotspot/cpu/riscv/templateInterpreterGenerator_riscv.cpp index 692335d8c08..f073909bf5d 100644 --- a/src/hotspot/cpu/riscv/templateInterpreterGenerator_riscv.cpp +++ b/src/hotspot/cpu/riscv/templateInterpreterGenerator_riscv.cpp @@ -1146,9 +1146,7 @@ address TemplateInterpreterGenerator::generate_native_entry(bool synchronized) { Label L; __ ld(x28, Address(xmethod, Method::native_function_offset())); ExternalAddress unsatisfied(SharedRuntime::native_method_throw_unsatisfied_link_error_entry()); - __ la(t, unsatisfied); - __ load_long_misaligned(t1, Address(t, 0), t0, 2); // 2 bytes aligned, but not 4 or 8 - + __ la(t1, unsatisfied); __ bne(x28, t1, L); __ call_VM(noreg, CAST_FROM_FN_PTR(address, From 16557739791ada59dc1991f65a0218434df01f9e Mon Sep 17 00:00:00 2001 From: Vishal Chand Date: Tue, 18 Nov 2025 06:49:03 +0000 Subject: [PATCH 095/418] 8371881: C2: Fix potential SEGV in VTransformReductionVectorNode tracing Reviewed-by: shade, epeter --- src/hotspot/share/opto/vtransform.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/hotspot/share/opto/vtransform.cpp b/src/hotspot/share/opto/vtransform.cpp index e775eb60cab..b437d2e6eac 100644 --- a/src/hotspot/share/opto/vtransform.cpp +++ b/src/hotspot/share/opto/vtransform.cpp @@ -1252,7 +1252,6 @@ bool VTransformReductionVectorNode::optimize_move_non_strict_order_reductions_ou // back to the phi. Check that all non strict order reductions only have a single // use, except for the last (last_red), which only has phi as a use in the loop, // and all other uses are outside the loop. - VTransformReductionVectorNode* first_red = this; VTransformReductionVectorNode* last_red = phi->in_req(2)->isa_ReductionVector(); VTransformReductionVectorNode* current_red = last_red; while (true) { @@ -1264,7 +1263,11 @@ bool VTransformReductionVectorNode::optimize_move_non_strict_order_reductions_ou tty->print(" Cannot move out of loop, other reduction node does not match:"); print(); tty->print(" other: "); - current_red->print(); + if (current_red != nullptr) { + current_red->print(); + } else { + tty->print("nullptr"); + } ) return false; // not compatible } From 8cdfec8d1cdc7e3137035cebe1cc189e36c0e319 Mon Sep 17 00:00:00 2001 From: Nityanand Rai <163765635+nityarai08@users.noreply.github.com> Date: Tue, 18 Nov 2025 06:49:28 +0000 Subject: [PATCH 096/418] 8371852: Shenandoah: Unused ShenandoahFreeSet::_allocated_since_gc_start field Reviewed-by: shade, fandreuzzi --- src/hotspot/share/gc/shenandoah/shenandoahFreeSet.hpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.hpp b/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.hpp index 5cc9eab7b4b..a9c5ebe49de 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.hpp @@ -106,9 +106,6 @@ private: size_t _used[UIntNumPartitions]; size_t _available[UIntNumPartitions]; - // Measured in bytes. - size_t _allocated_since_gc_start[UIntNumPartitions]; - // Some notes: // total_region_counts[p] is _capacity[p] / region_size_bytes // retired_regions[p] is total_region_counts[p] - _region_counts[p] From 26460b6f12ce0763b79acfd98fca260b509a82c5 Mon Sep 17 00:00:00 2001 From: Alan Bateman Date: Tue, 18 Nov 2025 08:06:18 +0000 Subject: [PATCH 097/418] 8353835: Implement JEP 500: Prepare to Make Final Mean Final Reviewed-by: liach, vlivanov, dholmes, vyazici --- make/test/JtregNativeJdk.gmk | 1 + src/hotspot/share/ci/ciField.cpp | 12 +- src/hotspot/share/prims/jni.cpp | 30 + src/hotspot/share/prims/jniCheck.cpp | 31 +- src/hotspot/share/runtime/arguments.cpp | 22 +- src/hotspot/share/runtime/fieldDescriptor.cpp | 10 + src/hotspot/share/runtime/fieldDescriptor.hpp | 4 +- .../share/classes/java/lang/Module.java | 161 ++++- .../share/classes/java/lang/ModuleLayer.java | 14 +- .../share/classes/java/lang/System.java | 21 +- .../java/lang/invoke/MethodHandles.java | 10 +- .../java/lang/reflect/AccessibleObject.java | 14 +- .../classes/java/lang/reflect/Field.java | 412 ++++++++++++- .../java/lang/reflect/ReflectAccess.java | 7 +- .../reflect/doc-files/MutationMethods.html | 69 +++ .../jdk/internal/access/JavaLangAccess.java | 36 +- .../access/JavaLangReflectAccess.java | 8 +- .../event/FinalFieldMutationEvent.java | 47 ++ .../jdk/internal/module/ModuleBootstrap.java | 96 ++- .../classes/jdk/internal/module/Modules.java | 41 +- .../classes/sun/launcher/LauncherHelper.java | 13 + .../launcher/resources/launcher.properties | 10 + src/java.base/share/man/java.md | 38 +- .../jfr/events/FinalFieldMutationEvent.java | 55 ++ .../classes/jdk/jfr/internal/JDKEvents.java | 1 + .../jdk/jfr/internal/MirrorEvents.java | 4 +- .../jdk/jfr/internal/PlatformEventType.java | 1 + src/jdk.jfr/share/conf/jfr/default.jfc | 5 + src/jdk.jfr/share/conf/jfr/profile.jfc | 5 + .../jni/mutateFinals/MutateFinals.java | 356 +++++++++++ .../jni/mutateFinals/MutateFinalsTest.java | 170 ++++++ .../jni/mutateFinals/libMutateFinals.c | 160 +++++ .../lang/invoke/MethodHandlesGeneralTest.java | 3 +- .../TestFieldLookupAccessibility.java | 28 +- .../lang/invoke/unreflect/UnreflectTest.java | 64 +- .../AccessibleObject/HiddenClassTest.java | 58 +- .../java/lang/reflect/Field/NegativeTest.java | 4 +- test/jdk/java/lang/reflect/Field/Set.java | 3 +- .../FinalFieldMutationEventTest.java | 203 +++++++ .../Field/mutateFinals/MutateFinalsTest.java | 358 +++++++++++ .../mutateFinals/cli/CommandLineTest.java | 294 +++++++++ .../cli/CommandLineTestHelper.java | 88 +++ .../mutateFinals/jar/ExecutableJarTest.java | 227 +++++++ .../jar/ExecutableJarTestHelper.java | 97 +++ .../Field/mutateFinals/jar/m/module-info.java | 29 + .../reflect/Field/mutateFinals/jar/m/p/C.java | 35 ++ .../mutateFinals/jni/JNIAttachMutator.java | 123 ++++ .../jni/JNIAttachMutatorTest.java | 124 ++++ .../mutateFinals/jni/libJNIAttachMutator.c | 192 ++++++ .../Field/mutateFinals/jni/m/module-info.java | 29 + .../Field/mutateFinals/jni/m/p/C1.java | 35 ++ .../Field/mutateFinals/jni/m/p/C2.java | 35 ++ .../Field/mutateFinals/jni/m/p/C3.java | 35 ++ .../reflect/Field/mutateFinals/jni/m/q/C.java | 35 ++ .../Field/mutateFinals/modules/Driver.java | 35 ++ .../mutateFinals/modules/m1/module-info.java | 27 + .../mutateFinals/modules/m1/p1/M1Mutator.java | 81 +++ .../mutateFinals/modules/m2/module-info.java | 27 + .../mutateFinals/modules/m2/p2/M2Mutator.java | 81 +++ .../mutateFinals/modules/m3/module-info.java | 27 + .../mutateFinals/modules/m3/p3/M3Mutator.java | 81 +++ .../modules/test/module-info.java | 33 ++ .../modules/test/test/TestMain.java | 556 ++++++++++++++++++ .../test/test/fieldholders/PrivateFields.java | 124 ++++ .../test/test/fieldholders/PublicFields.java | 124 ++++ .../test/test/internal/TestMutator.java | 81 +++ .../modules/test/test/spi/Mutator.java | 88 +++ .../Attributes/NullAndEmptyKeysAndValues.java | 4 +- .../util/logging/FileHandlerLongLimit.java | 4 +- .../metadata/TestLookForUntestedEvents.java | 5 + .../pkcs11/Cipher/CancelMultipart.java | 2 +- .../provider/SecureRandom/DRBGS11n.java | 6 +- .../util/ManifestDigester/FindSection.java | 4 +- .../jdk/jshell/CompletionSuggestionTest.java | 2 +- test/lib/jdk/test/lib/jfr/EventNames.java | 1 + .../bench/java/lang/reflect/FieldSet.java | 151 +++++ 76 files changed, 5311 insertions(+), 196 deletions(-) create mode 100644 src/java.base/share/classes/java/lang/reflect/doc-files/MutationMethods.html create mode 100644 src/java.base/share/classes/jdk/internal/event/FinalFieldMutationEvent.java create mode 100644 src/jdk.jfr/share/classes/jdk/jfr/events/FinalFieldMutationEvent.java create mode 100644 test/hotspot/jtreg/runtime/jni/mutateFinals/MutateFinals.java create mode 100644 test/hotspot/jtreg/runtime/jni/mutateFinals/MutateFinalsTest.java create mode 100644 test/hotspot/jtreg/runtime/jni/mutateFinals/libMutateFinals.c create mode 100644 test/jdk/java/lang/reflect/Field/mutateFinals/FinalFieldMutationEventTest.java create mode 100644 test/jdk/java/lang/reflect/Field/mutateFinals/MutateFinalsTest.java create mode 100644 test/jdk/java/lang/reflect/Field/mutateFinals/cli/CommandLineTest.java create mode 100644 test/jdk/java/lang/reflect/Field/mutateFinals/cli/CommandLineTestHelper.java create mode 100644 test/jdk/java/lang/reflect/Field/mutateFinals/jar/ExecutableJarTest.java create mode 100644 test/jdk/java/lang/reflect/Field/mutateFinals/jar/ExecutableJarTestHelper.java create mode 100644 test/jdk/java/lang/reflect/Field/mutateFinals/jar/m/module-info.java create mode 100644 test/jdk/java/lang/reflect/Field/mutateFinals/jar/m/p/C.java create mode 100644 test/jdk/java/lang/reflect/Field/mutateFinals/jni/JNIAttachMutator.java create mode 100644 test/jdk/java/lang/reflect/Field/mutateFinals/jni/JNIAttachMutatorTest.java create mode 100644 test/jdk/java/lang/reflect/Field/mutateFinals/jni/libJNIAttachMutator.c create mode 100644 test/jdk/java/lang/reflect/Field/mutateFinals/jni/m/module-info.java create mode 100644 test/jdk/java/lang/reflect/Field/mutateFinals/jni/m/p/C1.java create mode 100644 test/jdk/java/lang/reflect/Field/mutateFinals/jni/m/p/C2.java create mode 100644 test/jdk/java/lang/reflect/Field/mutateFinals/jni/m/p/C3.java create mode 100644 test/jdk/java/lang/reflect/Field/mutateFinals/jni/m/q/C.java create mode 100644 test/jdk/java/lang/reflect/Field/mutateFinals/modules/Driver.java create mode 100644 test/jdk/java/lang/reflect/Field/mutateFinals/modules/m1/module-info.java create mode 100644 test/jdk/java/lang/reflect/Field/mutateFinals/modules/m1/p1/M1Mutator.java create mode 100644 test/jdk/java/lang/reflect/Field/mutateFinals/modules/m2/module-info.java create mode 100644 test/jdk/java/lang/reflect/Field/mutateFinals/modules/m2/p2/M2Mutator.java create mode 100644 test/jdk/java/lang/reflect/Field/mutateFinals/modules/m3/module-info.java create mode 100644 test/jdk/java/lang/reflect/Field/mutateFinals/modules/m3/p3/M3Mutator.java create mode 100644 test/jdk/java/lang/reflect/Field/mutateFinals/modules/test/module-info.java create mode 100644 test/jdk/java/lang/reflect/Field/mutateFinals/modules/test/test/TestMain.java create mode 100644 test/jdk/java/lang/reflect/Field/mutateFinals/modules/test/test/fieldholders/PrivateFields.java create mode 100644 test/jdk/java/lang/reflect/Field/mutateFinals/modules/test/test/fieldholders/PublicFields.java create mode 100644 test/jdk/java/lang/reflect/Field/mutateFinals/modules/test/test/internal/TestMutator.java create mode 100644 test/jdk/java/lang/reflect/Field/mutateFinals/modules/test/test/spi/Mutator.java create mode 100644 test/micro/org/openjdk/bench/java/lang/reflect/FieldSet.java diff --git a/make/test/JtregNativeJdk.gmk b/make/test/JtregNativeJdk.gmk index a204467a77b..0482011f561 100644 --- a/make/test/JtregNativeJdk.gmk +++ b/make/test/JtregNativeJdk.gmk @@ -80,6 +80,7 @@ else BUILD_JDK_JTREG_LIBRARIES_LDFLAGS_libExplicitAttach := -pthread BUILD_JDK_JTREG_LIBRARIES_LDFLAGS_libImplicitAttach := -pthread + BUILD_JDK_JTREG_LIBRARIES_LDFLAGS_libJNIAttachMutator := -pthread BUILD_JDK_JTREG_EXCLUDE += exerevokeall.c ifeq ($(call isTargetOs, linux), true) BUILD_JDK_JTREG_EXECUTABLES_LIBS_exelauncher := -ldl diff --git a/src/hotspot/share/ci/ciField.cpp b/src/hotspot/share/ci/ciField.cpp index 8f33c17d0e6..19e05784f4d 100644 --- a/src/hotspot/share/ci/ciField.cpp +++ b/src/hotspot/share/ci/ciField.cpp @@ -258,17 +258,7 @@ void ciField::initialize_from(fieldDescriptor* fd) { // not be constant is when the field is a *special* static & final field // whose value may change. The three examples are java.lang.System.in, // java.lang.System.out, and java.lang.System.err. - assert(vmClasses::System_klass() != nullptr, "Check once per vm"); - if (k == vmClasses::System_klass()) { - // Check offsets for case 2: System.in, System.out, or System.err - if (_offset == java_lang_System::in_offset() || - _offset == java_lang_System::out_offset() || - _offset == java_lang_System::err_offset()) { - _is_constant = false; - return; - } - } - _is_constant = true; + _is_constant = !fd->is_mutable_static_final(); } else { // An instance field can be constant if it's a final static field or if // it's a final non-static field of a trusted class (classes in diff --git a/src/hotspot/share/prims/jni.cpp b/src/hotspot/share/prims/jni.cpp index 5af8edbb758..2297ce9b790 100644 --- a/src/hotspot/share/prims/jni.cpp +++ b/src/hotspot/share/prims/jni.cpp @@ -1867,6 +1867,32 @@ address jni_GetDoubleField_addr() { return (address)jni_GetDoubleField; } +static void log_debug_if_final_static_field(JavaThread* current, const char* func_name, InstanceKlass* ik, int offset) { + if (log_is_enabled(Debug, jni)) { + fieldDescriptor fd; + bool found = ik->find_field_from_offset(offset, true, &fd); + assert(found, "bad field offset"); + assert(fd.is_static(), "static/instance mismatch"); + if (fd.is_final() && !fd.is_mutable_static_final()) { + ResourceMark rm(current); + log_debug(jni)("%s mutated final static field %s.%s", func_name, ik->external_name(), fd.name()->as_C_string()); + } + } +} + +static void log_debug_if_final_instance_field(JavaThread* current, const char* func_name, InstanceKlass* ik, int offset) { + if (log_is_enabled(Debug, jni)) { + fieldDescriptor fd; + bool found = ik->find_field_from_offset(offset, false, &fd); + assert(found, "bad field offset"); + assert(!fd.is_static(), "static/instance mismatch"); + if (fd.is_final()) { + ResourceMark rm(current); + log_debug(jni)("%s mutated final instance field %s.%s", func_name, ik->external_name(), fd.name()->as_C_string()); + } + } +} + JNI_ENTRY_NO_PRESERVE(void, jni_SetObjectField(JNIEnv *env, jobject obj, jfieldID fieldID, jobject value)) HOTSPOT_JNI_SETOBJECTFIELD_ENTRY(env, obj, (uintptr_t) fieldID, value); oop o = JNIHandles::resolve_non_null(obj); @@ -1879,6 +1905,7 @@ JNI_ENTRY_NO_PRESERVE(void, jni_SetObjectField(JNIEnv *env, jobject obj, jfieldI o = JvmtiExport::jni_SetField_probe(thread, obj, o, k, fieldID, false, JVM_SIGNATURE_CLASS, (jvalue *)&field_value); } HeapAccess::oop_store_at(o, offset, JNIHandles::resolve(value)); + log_debug_if_final_instance_field(thread, "SetObjectField", InstanceKlass::cast(k), offset); HOTSPOT_JNI_SETOBJECTFIELD_RETURN(); JNI_END @@ -1901,6 +1928,7 @@ JNI_ENTRY_NO_PRESERVE(void, jni_Set##Result##Field(JNIEnv *env, jobject obj, jfi o = JvmtiExport::jni_SetField_probe(thread, obj, o, k, fieldID, false, SigType, (jvalue *)&field_value); \ } \ o->Fieldname##_field_put(offset, value); \ + log_debug_if_final_instance_field(thread, "SetField", InstanceKlass::cast(k), offset); \ ReturnProbe; \ JNI_END @@ -2072,6 +2100,7 @@ JNI_ENTRY(void, jni_SetStaticObjectField(JNIEnv *env, jclass clazz, jfieldID fie JvmtiExport::jni_SetField_probe(thread, nullptr, nullptr, id->holder(), fieldID, true, JVM_SIGNATURE_CLASS, (jvalue *)&field_value); } id->holder()->java_mirror()->obj_field_put(id->offset(), JNIHandles::resolve(value)); + log_debug_if_final_static_field(THREAD, "SetStaticObjectField", id->holder(), id->offset()); HOTSPOT_JNI_SETSTATICOBJECTFIELD_RETURN(); JNI_END @@ -2093,6 +2122,7 @@ JNI_ENTRY(void, jni_SetStatic##Result##Field(JNIEnv *env, jclass clazz, jfieldID JvmtiExport::jni_SetField_probe(thread, nullptr, nullptr, id->holder(), fieldID, true, SigType, (jvalue *)&field_value); \ } \ id->holder()->java_mirror()-> Fieldname##_field_put (id->offset(), value); \ + log_debug_if_final_static_field(THREAD, "SetStaticField", id->holder(), id->offset()); \ ReturnProbe;\ JNI_END diff --git a/src/hotspot/share/prims/jniCheck.cpp b/src/hotspot/share/prims/jniCheck.cpp index 43cc61d7363..5f4cf10ebf4 100644 --- a/src/hotspot/share/prims/jniCheck.cpp +++ b/src/hotspot/share/prims/jniCheck.cpp @@ -233,7 +233,7 @@ functionExit(JavaThread* thr) } static inline void -checkStaticFieldID(JavaThread* thr, jfieldID fid, jclass cls, int ftype) +checkStaticFieldID(JavaThread* thr, jfieldID fid, jclass cls, int ftype, bool setter) { fieldDescriptor fd; @@ -258,10 +258,18 @@ checkStaticFieldID(JavaThread* thr, jfieldID fid, jclass cls, int ftype) !(fd.field_type() == T_ARRAY && ftype == T_OBJECT)) { ReportJNIFatalError(thr, fatal_static_field_mismatch); } + + /* check if setting a final field */ + if (setter && fd.is_final() && !fd.is_mutable_static_final()) { + ResourceMark rm(thr); + stringStream ss; + ss.print("SetStaticField called to mutate final static field %s.%s", k_oop->external_name(), fd.name()->as_C_string()); + ReportJNIWarning(thr, ss.as_string()); + } } static inline void -checkInstanceFieldID(JavaThread* thr, jfieldID fid, jobject obj, int ftype) +checkInstanceFieldID(JavaThread* thr, jfieldID fid, jobject obj, int ftype, bool setter) { fieldDescriptor fd; @@ -287,14 +295,21 @@ checkInstanceFieldID(JavaThread* thr, jfieldID fid, jobject obj, int ftype) ReportJNIFatalError(thr, fatal_wrong_field); /* check for proper field type */ - if (!InstanceKlass::cast(k_oop)->find_field_from_offset(offset, - false, &fd)) + if (!InstanceKlass::cast(k_oop)->find_field_from_offset(offset, false, &fd)) ReportJNIFatalError(thr, fatal_instance_field_not_found); if ((fd.field_type() != ftype) && !(fd.field_type() == T_ARRAY && ftype == T_OBJECT)) { ReportJNIFatalError(thr, fatal_instance_field_mismatch); } + + /* check if setting a final field */ + if (setter && fd.is_final()) { + ResourceMark rm(thr); + stringStream ss; + ss.print("SetField called to mutate final instance field %s.%s", k_oop->external_name(), fd.name()->as_C_string()); + ReportJNIWarning(thr, ss.as_string()); + } } static inline void @@ -1204,7 +1219,7 @@ JNI_ENTRY_CHECKED(ReturnType, \ jfieldID fieldID)) \ functionEnter(thr); \ IN_VM( \ - checkInstanceFieldID(thr, fieldID, obj, FieldType); \ + checkInstanceFieldID(thr, fieldID, obj, FieldType, false); \ ) \ ReturnType result = UNCHECKED()->Get##Result##Field(env,obj,fieldID); \ functionExit(thr); \ @@ -1229,7 +1244,7 @@ JNI_ENTRY_CHECKED(void, \ ValueType val)) \ functionEnter(thr); \ IN_VM( \ - checkInstanceFieldID(thr, fieldID, obj, FieldType); \ + checkInstanceFieldID(thr, fieldID, obj, FieldType, true); \ ) \ UNCHECKED()->Set##Result##Field(env,obj,fieldID,val); \ functionExit(thr); \ @@ -1395,7 +1410,7 @@ JNI_ENTRY_CHECKED(ReturnType, \ functionEnter(thr); \ IN_VM( \ jniCheck::validate_class(thr, clazz, false); \ - checkStaticFieldID(thr, fieldID, clazz, FieldType); \ + checkStaticFieldID(thr, fieldID, clazz, FieldType, false); \ ) \ ReturnType result = UNCHECKED()->GetStatic##Result##Field(env, \ clazz, \ @@ -1423,7 +1438,7 @@ JNI_ENTRY_CHECKED(void, \ functionEnter(thr); \ IN_VM( \ jniCheck::validate_class(thr, clazz, false); \ - checkStaticFieldID(thr, fieldID, clazz, FieldType); \ + checkStaticFieldID(thr, fieldID, clazz, FieldType, true); \ ) \ UNCHECKED()->SetStatic##Result##Field(env,clazz,fieldID,value); \ functionExit(thr); \ diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp index 3748c35f00d..1ef2ee9de0d 100644 --- a/src/hotspot/share/runtime/arguments.cpp +++ b/src/hotspot/share/runtime/arguments.cpp @@ -317,6 +317,10 @@ bool needs_module_property_warning = false; #define ENABLE_NATIVE_ACCESS_LEN 20 #define ILLEGAL_NATIVE_ACCESS "illegal.native.access" #define ILLEGAL_NATIVE_ACCESS_LEN 21 +#define ENABLE_FINAL_FIELD_MUTATION "enable.final.field.mutation" +#define ENABLE_FINAL_FIELD_MUTATION_LEN 27 +#define ILLEGAL_FINAL_FIELD_MUTATION "illegal.final.field.mutation" +#define ILLEGAL_FINAL_FIELD_MUTATION_LEN 28 // Return TRUE if option matches 'property', or 'property=', or 'property.'. static bool matches_property_suffix(const char* option, const char* property, size_t len) { @@ -343,7 +347,9 @@ bool Arguments::internal_module_property_helper(const char* property, bool check if (matches_property_suffix(property_suffix, PATCH, PATCH_LEN) || matches_property_suffix(property_suffix, LIMITMODS, LIMITMODS_LEN) || matches_property_suffix(property_suffix, UPGRADE_PATH, UPGRADE_PATH_LEN) || - matches_property_suffix(property_suffix, ILLEGAL_NATIVE_ACCESS, ILLEGAL_NATIVE_ACCESS_LEN)) { + matches_property_suffix(property_suffix, ILLEGAL_NATIVE_ACCESS, ILLEGAL_NATIVE_ACCESS_LEN) || + matches_property_suffix(property_suffix, ENABLE_FINAL_FIELD_MUTATION, ENABLE_FINAL_FIELD_MUTATION_LEN) || + matches_property_suffix(property_suffix, ILLEGAL_FINAL_FIELD_MUTATION, ILLEGAL_FINAL_FIELD_MUTATION_LEN)) { return true; } @@ -1809,6 +1815,7 @@ static unsigned int addexports_count = 0; static unsigned int addopens_count = 0; static unsigned int patch_mod_count = 0; static unsigned int enable_native_access_count = 0; +static unsigned int enable_final_field_mutation = 0; static bool patch_mod_javabase = false; // Check the consistency of vm_init_args @@ -2273,6 +2280,19 @@ jint Arguments::parse_each_vm_init_arg(const JavaVMInitArgs* args, JVMFlagOrigin if (res != JNI_OK) { return res; } + } else if (match_option(option, "--enable-final-field-mutation=", &tail)) { + if (!create_numbered_module_property("jdk.module.enable.final.field.mutation", tail, enable_final_field_mutation++)) { + return JNI_ENOMEM; + } + } else if (match_option(option, "--illegal-final-field-mutation=", &tail)) { + if (strcmp(tail, "allow") == 0 || strcmp(tail, "warn") == 0 || strcmp(tail, "debug") == 0 || strcmp(tail, "deny") == 0) { + PropertyList_unique_add(&_system_properties, "jdk.module.illegal.final.field.mutation", tail, + AddProperty, WriteableProperty, InternalProperty); + } else { + jio_fprintf(defaultStream::error_stream(), + "Value specified to --illegal-final-field-mutation not recognized: '%s'\n", tail); + return JNI_ERR; + } } else if (match_option(option, "--sun-misc-unsafe-memory-access=", &tail)) { if (strcmp(tail, "allow") == 0 || strcmp(tail, "warn") == 0 || strcmp(tail, "debug") == 0 || strcmp(tail, "deny") == 0) { PropertyList_unique_add(&_system_properties, "sun.misc.unsafe.memory.access", tail, diff --git a/src/hotspot/share/runtime/fieldDescriptor.cpp b/src/hotspot/share/runtime/fieldDescriptor.cpp index c5c3bdbd4bc..491157d5bf7 100644 --- a/src/hotspot/share/runtime/fieldDescriptor.cpp +++ b/src/hotspot/share/runtime/fieldDescriptor.cpp @@ -46,6 +46,16 @@ bool fieldDescriptor::is_trusted_final() const { return is_final() && (is_static() || ik->is_hidden() || ik->is_record()); } +bool fieldDescriptor::is_mutable_static_final() const { + InstanceKlass* ik = field_holder(); + // write protected fields (JLS 17.5.4) + if (is_final() && is_static() && ik == vmClasses::System_klass() && + (offset() == java_lang_System::in_offset() || offset() == java_lang_System::out_offset() || offset() == java_lang_System::err_offset())) { + return true; + } + return false; +} + AnnotationArray* fieldDescriptor::annotations() const { InstanceKlass* ik = field_holder(); Array* md = ik->fields_annotations(); diff --git a/src/hotspot/share/runtime/fieldDescriptor.hpp b/src/hotspot/share/runtime/fieldDescriptor.hpp index aae789b1fb7..fa3d1b9d23c 100644 --- a/src/hotspot/share/runtime/fieldDescriptor.hpp +++ b/src/hotspot/share/runtime/fieldDescriptor.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2025, 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 @@ -98,6 +98,8 @@ class fieldDescriptor { bool is_trusted_final() const; + bool is_mutable_static_final() const; + inline void set_is_field_access_watched(const bool value); inline void set_is_field_modification_watched(const bool value); inline void set_has_initialized_final_update(const bool value); diff --git a/src/java.base/share/classes/java/lang/Module.java b/src/java.base/share/classes/java/lang/Module.java index 065e1ac4620..cd2b8095ee4 100644 --- a/src/java.base/share/classes/java/lang/Module.java +++ b/src/java.base/share/classes/java/lang/Module.java @@ -37,6 +37,7 @@ import java.lang.module.ModuleDescriptor.Version; import java.lang.module.ResolvedModule; import java.lang.reflect.AccessFlag; import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Field; import java.net.URI; import java.net.URL; import java.security.CodeSource; @@ -115,6 +116,10 @@ public final class Module implements AnnotatedElement { @Stable private boolean enableNativeAccess; + // true if this module is allowed to mutate final instance fields + @Stable + private boolean enableFinalMutation; + /** * Creates a new named Module. The resulting Module will be defined to the * VM but will not read any other modules, will not have any exports setup @@ -262,7 +267,6 @@ public final class Module implements AnnotatedElement { * in the outer Module class as that would create a circular initializer dependency. */ private static final class EnableNativeAccess { - private EnableNativeAccess() {} private static final Unsafe UNSAFE = Unsafe.getUnsafe(); @@ -331,12 +335,52 @@ public final class Module implements AnnotatedElement { } /** - * Update all unnamed modules to allow access to restricted methods. + * Enable code in all unnamed modules to access restricted methods. */ - static void implAddEnableNativeAccessToAllUnnamed() { + static void addEnableNativeAccessToAllUnnamed() { EnableNativeAccess.trySetEnableNativeAccess(ALL_UNNAMED_MODULE); } + /** + * This class exists to avoid using Unsafe during early initialization of Module. + */ + private static final class EnableFinalMutation { + private static final Unsafe UNSAFE = Unsafe.getUnsafe(); + private static final long ENABLE_FINAL_MUTATION_OFFSET = + UNSAFE.objectFieldOffset(Module.class, "enableFinalMutation"); + + private static boolean isEnableFinalMutation(Module module) { + return UNSAFE.getBooleanVolatile(module, ENABLE_FINAL_MUTATION_OFFSET); + } + + private static boolean tryEnableFinalMutation(Module module) { + return UNSAFE.compareAndSetBoolean(module, ENABLE_FINAL_MUTATION_OFFSET, false, true); + } + } + + /** + * Enable code in all unnamed modules to mutate final instance fields. + */ + static void addEnableFinalMutationToAllUnnamed() { + EnableFinalMutation.tryEnableFinalMutation(ALL_UNNAMED_MODULE); + } + + /** + * Enable code in this named module to mutate final instance fields. + */ + boolean tryEnableFinalMutation() { + Module m = isNamed() ? this : ALL_UNNAMED_MODULE; + return EnableFinalMutation.tryEnableFinalMutation(m); + } + + /** + * Return true if code in this module is allowed to mutate final instance fields. + */ + boolean isFinalMutationEnabled() { + Module m = isNamed() ? this : ALL_UNNAMED_MODULE; + return EnableFinalMutation.isEnableFinalMutation(m); + } + // -- // special Module to mean "all unnamed modules" @@ -718,8 +762,50 @@ public final class Module implements AnnotatedElement { } /** - * Returns {@code true} if this module exports or opens a package to - * the given module via its module declaration or CLI options. + * Returns {@code true} if this module statically exports a package to the given module. + * If the package is exported to the given module via {@code addExports} then this method + * returns {@code false}. + */ + boolean isStaticallyExported(String pn, Module other) { + return isStaticallyExportedOrOpened(pn, other, false); + } + + /** + * Returns {@code true} if this module statically opens a package to the given module. + * If the package is opened to the given module via {@code addOpens} then this method + * returns {@code false}. + */ + boolean isStaticallyOpened(String pn, Module other) { + return isStaticallyExportedOrOpened(pn, other, true); + } + + /** + * Returns {@code true} if this module exports or opens a package to the + * given module via its module declaration or CLI options. + */ + private boolean isStaticallyExportedOrOpened(String pn, Module other, boolean open) { + // all packages in unnamed modules are exported and open + if (!isNamed()) + return true; + + // all packages are exported/open to self + if (other == this && descriptor.packages().contains(pn)) + return true; + + // all packages in open and automatic modules are exported/open + if (descriptor.isOpen() || descriptor.isAutomatic()) + return descriptor.packages().contains(pn); + + // exported/opened via module descriptor + if (isExplicitlyExportedOrOpened(pn, other, open)) + return true; + + return false; + } + + /** + * Returns {@code true} if this module exports or opens a package to the + * given module via its module declaration or CLI options. */ private boolean isExplicitlyExportedOrOpened(String pn, Module other, boolean open) { // test if package is open to everyone or @@ -818,11 +904,16 @@ public final class Module implements AnnotatedElement { return isReflectivelyExportedOrOpened(pn, other, true); } - /** * If the caller's module is this module then update this module to export * the given package to the given module. * + *

Exporting a package with this method does not allow the given module to + * {@linkplain Field#set(Object, Object) reflectively set} or {@linkplain + * java.lang.invoke.MethodHandles.Lookup#unreflectSetter(Field) obtain a method + * handle with write access} to a public final field declared in a public class + * in the package. + * *

This method has no effect if the package is already exported (or * open) to the given module.

* @@ -860,21 +951,27 @@ public final class Module implements AnnotatedElement { if (caller != this) { throw new IllegalCallerException(caller + " != " + this); } - implAddExportsOrOpens(pn, other, /*open*/false, /*syncVM*/true); + implAddExports(pn, other); } return this; } /** - * If this module has opened a package to at least the caller - * module then update this module to open the package to the given module. - * Opening a package with this method allows all types in the package, + * If this module has opened the given package to at least the caller + * module, then update this module to also open the package to the given module. + * + *

Opening a package with this method allows all types in the package, * and all their members, not just public types and their public members, - * to be reflected on by the given module when using APIs that support - * private access or a way to bypass or suppress default Java language + * to be reflected on by the given module when using APIs that either support + * private access or provide a way to bypass or suppress Java language * access control checks. * + *

Opening a package with this method does not allow the given module to + * {@linkplain Field#set(Object, Object) reflectively set} or {@linkplain + * java.lang.invoke.MethodHandles.Lookup#unreflectSetter(Field) obtain a method + * handle with write access} to a final field declared in a class in the package. + * *

This method has no effect if the package is already open * to the given module.

* @@ -913,7 +1010,7 @@ public final class Module implements AnnotatedElement { Module caller = getCallerModule(Reflection.getCallerClass()); if (caller != this && (caller == null || !isOpen(pn, caller))) throw new IllegalCallerException(pn + " is not open to " + caller); - implAddExportsOrOpens(pn, other, /*open*/true, /*syncVM*/true); + implAddOpens(pn, other); } return this; @@ -923,28 +1020,29 @@ public final class Module implements AnnotatedElement { /** * Updates this module to export a package unconditionally. * - * @apiNote This method is for JDK tests only. + * @apiNote Used by Proxy and other dynamic modules. */ void implAddExports(String pn) { - implAddExportsOrOpens(pn, Module.EVERYONE_MODULE, false, true); + implAddExportsOrOpens(pn, Module.EVERYONE_MODULE, false, true, true); } /** * Updates this module to export a package to another module. * - * @apiNote Used by Instrumentation::redefineModule and --add-exports + * @apiNote Used by addExports, Instrumentation::redefineModule, and --add-exports */ void implAddExports(String pn, Module other) { - implAddExportsOrOpens(pn, other, false, true); + implAddExportsOrOpens(pn, other, false, VM.isBooted(), true); } /** * Updates this module to export a package to all unnamed modules. * - * @apiNote Used by the --add-exports command line option. + * @apiNote Used by the --add-exports command line option and the launcher when + * an executable JAR file has the "Add-Exports" attribute in its main manifest. */ void implAddExportsToAllUnnamed(String pn) { - implAddExportsOrOpens(pn, Module.ALL_UNNAMED_MODULE, false, true); + implAddExportsOrOpens(pn, Module.ALL_UNNAMED_MODULE, false, false, true); } /** @@ -954,7 +1052,7 @@ public final class Module implements AnnotatedElement { * @apiNote This method is for VM white-box testing. */ void implAddExportsNoSync(String pn) { - implAddExportsOrOpens(pn.replace('/', '.'), Module.EVERYONE_MODULE, false, false); + implAddExportsOrOpens(pn.replace('/', '.'), Module.EVERYONE_MODULE, false, true, false); } /** @@ -964,7 +1062,7 @@ public final class Module implements AnnotatedElement { * @apiNote This method is for VM white-box testing. */ void implAddExportsNoSync(String pn, Module other) { - implAddExportsOrOpens(pn.replace('/', '.'), other, false, false); + implAddExportsOrOpens(pn.replace('/', '.'), other, false, true, false); } /** @@ -973,35 +1071,40 @@ public final class Module implements AnnotatedElement { * @apiNote This method is for JDK tests only. */ void implAddOpens(String pn) { - implAddExportsOrOpens(pn, Module.EVERYONE_MODULE, true, true); + implAddExportsOrOpens(pn, Module.EVERYONE_MODULE, true, true, true); } /** * Updates this module to open a package to another module. * - * @apiNote Used by Instrumentation::redefineModule and --add-opens + * @apiNote Used by addOpens, Instrumentation::redefineModule, and --add-opens */ void implAddOpens(String pn, Module other) { - implAddExportsOrOpens(pn, other, true, true); + implAddExportsOrOpens(pn, other, true, VM.isBooted(), true); } /** * Updates this module to open a package to all unnamed modules. * - * @apiNote Used by the --add-opens command line option. + * @apiNote Used by the --add-opens command line option and the launcher when + * an executable JAR file has the "Add-Opens" attribute in its main manifest. */ void implAddOpensToAllUnnamed(String pn) { - implAddExportsOrOpens(pn, Module.ALL_UNNAMED_MODULE, true, true); + implAddExportsOrOpens(pn, Module.ALL_UNNAMED_MODULE, true, false, true); } /** * Updates a module to export or open a module to another module. - * - * If {@code syncVM} is {@code true} then the VM is notified. + * @param pn package name + * @param other the module to export/open the package to + * @param open true to open, false to export + * @param reflectively true if exported/opened reflectively + * @param syncVM true to update the VM */ private void implAddExportsOrOpens(String pn, Module other, boolean open, + boolean reflectively, boolean syncVM) { Objects.requireNonNull(other); Objects.requireNonNull(pn); @@ -1031,7 +1134,7 @@ public final class Module implements AnnotatedElement { } } - if (VM.isBooted()) { + if (reflectively) { // add package name to ReflectionData.exports if absent Map map = ReflectionData.exports .computeIfAbsent(this, other, diff --git a/src/java.base/share/classes/java/lang/ModuleLayer.java b/src/java.base/share/classes/java/lang/ModuleLayer.java index 5dfd93796d2..9d922f787a6 100644 --- a/src/java.base/share/classes/java/lang/ModuleLayer.java +++ b/src/java.base/share/classes/java/lang/ModuleLayer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2025, 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,6 +28,7 @@ package java.lang; import java.lang.module.Configuration; import java.lang.module.ModuleDescriptor; import java.lang.module.ResolvedModule; +import java.lang.reflect.Field; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; @@ -252,6 +253,12 @@ public final class ModuleLayer { * module {@code target}. This method is a no-op if {@code source} * already exports the package to at least {@code target}. * + *

Exporting a package with this method does not allow the target module to + * {@linkplain Field#set(Object, Object) reflectively set} or {@linkplain + * java.lang.invoke.MethodHandles.Lookup#unreflectSetter(Field) obtain a method + * handle with write access} to a public final field declared in a public class + * in the package. + * * @param source * The source module * @param pn @@ -278,6 +285,11 @@ public final class ModuleLayer { * module {@code target}. This method is a no-op if {@code source} * already opens the package to at least {@code target}. * + *

Opening a package with this method does not allow the target module + * to {@linkplain Field#set(Object, Object) reflectively set} or {@linkplain + * java.lang.invoke.MethodHandles.Lookup#unreflectSetter(Field) obtain a method + * handle with write access} to a final field declared in a class in the package. + * * @param source * The source module * @param pn diff --git a/src/java.base/share/classes/java/lang/System.java b/src/java.base/share/classes/java/lang/System.java index c88cf4ac797..5c2b47afe3d 100644 --- a/src/java.base/share/classes/java/lang/System.java +++ b/src/java.base/share/classes/java/lang/System.java @@ -2096,18 +2096,33 @@ public final class System { public boolean isReflectivelyOpened(Module m, String pn, Module other) { return m.isReflectivelyOpened(pn, other); } - public Module addEnableNativeAccess(Module m) { - return m.implAddEnableNativeAccess(); + public void addEnableNativeAccess(Module m) { + m.implAddEnableNativeAccess(); } public boolean addEnableNativeAccess(ModuleLayer layer, String name) { return layer.addEnableNativeAccess(name); } public void addEnableNativeAccessToAllUnnamed() { - Module.implAddEnableNativeAccessToAllUnnamed(); + Module.addEnableNativeAccessToAllUnnamed(); } public void ensureNativeAccess(Module m, Class owner, String methodName, Class currentClass, boolean jni) { m.ensureNativeAccess(owner, methodName, currentClass, jni); } + public boolean isStaticallyExported(Module m, String pn, Module other) { + return m.isStaticallyExported(pn, other); + } + public boolean isStaticallyOpened(Module m, String pn, Module other) { + return m.isStaticallyOpened(pn, other); + } + public boolean isFinalMutationEnabled(Module m) { + return m.isFinalMutationEnabled(); + } + public boolean tryEnableFinalMutation(Module m) { + return m.tryEnableFinalMutation(); + } + public void addEnableFinalMutationToAllUnnamed() { + Module.addEnableFinalMutationToAllUnnamed(); + } public ServicesCatalog getServicesCatalog(ModuleLayer layer) { return layer.getServicesCatalog(); } diff --git a/src/java.base/share/classes/java/lang/invoke/MethodHandles.java b/src/java.base/share/classes/java/lang/invoke/MethodHandles.java index 92a37587926..feb8aaaa1a9 100644 --- a/src/java.base/share/classes/java/lang/invoke/MethodHandles.java +++ b/src/java.base/share/classes/java/lang/invoke/MethodHandles.java @@ -3429,12 +3429,15 @@ return mh1; * or if the field is {@code final} and write access * is not enabled on the {@code Field} object * @throws NullPointerException if the argument is null + * @see Mutation methods */ public MethodHandle unreflectSetter(Field f) throws IllegalAccessException { return unreflectField(f, true); } private MethodHandle unreflectField(Field f, boolean isSetter) throws IllegalAccessException { + @SuppressWarnings("deprecation") + boolean isAccessible = f.isAccessible(); MemberName field = new MemberName(f, isSetter); if (isSetter && field.isFinal()) { if (field.isTrustedFinalField()) { @@ -3442,12 +3445,15 @@ return mh1; : "final field has no write access"; throw field.makeAccessException(msg, this); } + // check if write access to final field allowed + if (!field.isStatic() && isAccessible) { + SharedSecrets.getJavaLangReflectAccess().checkAllowedToUnreflectFinalSetter(lookupClass, f); + } } assert(isSetter ? MethodHandleNatives.refKindIsSetter(field.getReferenceKind()) : MethodHandleNatives.refKindIsGetter(field.getReferenceKind())); - @SuppressWarnings("deprecation") - Lookup lookup = f.isAccessible() ? IMPL_LOOKUP : this; + Lookup lookup = isAccessible ? IMPL_LOOKUP : this; return lookup.getDirectField(field.getReferenceKind(), f.getDeclaringClass(), field); } diff --git a/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java b/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java index a045f9c196a..1637d26b571 100644 --- a/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java +++ b/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2025, 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 @@ -160,18 +160,6 @@ public class AccessibleObject implements AnnotatedElement { * to the caller and the package containing the declaring class is not open * to the caller's module.

* - *

This method cannot be used to enable {@linkplain Field#set write} - * access to a non-modifiable final field. The following fields - * are non-modifiable: - *

    - *
  • static final fields declared in any class or interface
  • - *
  • final fields declared in a {@linkplain Class#isHidden() hidden class}
  • - *
  • final fields declared in a {@linkplain Class#isRecord() record}
  • - *
- *

The {@code accessible} flag when {@code true} suppresses Java language access - * control checks to only enable {@linkplain Field#get read} access to - * these non-modifiable final fields. - * * @param flag the new value for the {@code accessible} flag * @throws InaccessibleObjectException if access cannot be enabled * diff --git a/src/java.base/share/classes/java/lang/reflect/Field.java b/src/java.base/share/classes/java/lang/reflect/Field.java index e26d8b03ff8..663e3453343 100644 --- a/src/java.base/share/classes/java/lang/reflect/Field.java +++ b/src/java.base/share/classes/java/lang/reflect/Field.java @@ -25,7 +25,18 @@ package java.lang.reflect; +import java.lang.annotation.Annotation; +import java.net.URL; +import java.security.CodeSource; +import java.util.Map; +import java.util.Set; +import java.util.Objects; import jdk.internal.access.SharedSecrets; +import jdk.internal.event.FinalFieldMutationEvent; +import jdk.internal.loader.ClassLoaders; +import jdk.internal.misc.VM; +import jdk.internal.module.ModuleBootstrap; +import jdk.internal.module.Modules; import jdk.internal.reflect.CallerSensitive; import jdk.internal.reflect.FieldAccessor; import jdk.internal.reflect.Reflection; @@ -35,10 +46,6 @@ import sun.reflect.generics.repository.FieldRepository; import sun.reflect.generics.factory.CoreReflectionFactory; import sun.reflect.generics.factory.GenericsFactory; import sun.reflect.generics.scope.ClassScope; -import java.lang.annotation.Annotation; -import java.util.Map; -import java.util.Set; -import java.util.Objects; import sun.reflect.annotation.AnnotationParser; import sun.reflect.annotation.AnnotationSupport; import sun.reflect.annotation.TypeAnnotation; @@ -165,6 +172,27 @@ class Field extends AccessibleObject implements Member { } /** + * {@inheritDoc} + * + *

If this reflected object represents a non-final field, and this method is + * used to enable access, then both {@linkplain #get(Object) read} + * and {@linkplain #set(Object, Object) write} access to the field + * are enabled. + * + *

If this reflected object represents a non-modifiable final field + * then enabling access only enables read access. Any attempt to {@linkplain + * #set(Object, Object) set} the field value throws an {@code + * IllegalAccessException}. The following fields are non-modifiable: + *

    + *
  • static final fields declared in any class or interface
  • + *
  • final fields declared in a {@linkplain Class#isRecord() record}
  • + *
  • final fields declared in a {@linkplain Class#isHidden() hidden class}
  • + *
+ *

If this reflected object represents a non-static final field in a class that + * is not a record class or hidden class, then enabling access will enable read + * access. Whether write access is allowed or not is checked when attempting to + * {@linkplain #set(Object, Object) set} the field value. + * * @throws InaccessibleObjectException {@inheritDoc} */ @Override @@ -762,18 +790,59 @@ class Field extends AccessibleObject implements Member { * the underlying field is inaccessible, the method throws an * {@code IllegalAccessException}. * - *

If the underlying field is final, this {@code Field} object has - * write access if and only if the following conditions are met: + *

If the underlying field is final, this {@code Field} object has write + * access if and only if all of the following conditions are true, where {@code D} is + * the field's {@linkplain #getDeclaringClass() declaring class}: + * *

    - *
  • {@link #setAccessible(boolean) setAccessible(true)} has succeeded for - * this {@code Field} object;
  • - *
  • the field is non-static; and
  • - *
  • the field's declaring class is not a {@linkplain Class#isHidden() - * hidden class}; and
  • - *
  • the field's declaring class is not a {@linkplain Class#isRecord() - * record class}.
  • + *
  • {@link #setAccessible(boolean) setAccessible(true)} has succeeded for this + * {@code Field} object.
  • + *
  • final field mutation is enabled + * for the caller's module.
  • + *
  • At least one of the following conditions holds: + *
      + *
    1. {@code D} and the caller class are in the same module.
    2. + *
    3. The field is {@code public} and {@code D} is {@code public} in a package + * that the module containing {@code D} exports to at least the caller's module.
    4. + *
    5. {@code D} is in a package that is {@linkplain Module#isOpen(String, Module) + * open} to the caller's module.
    6. + *
    + *
  • + *
  • {@code D} is not a {@linkplain Class#isRecord() record class}.
  • + *
  • {@code D} is not a {@linkplain Class#isHidden() hidden class}.
  • + *
  • The field is non-static.
  • *
- * If any of the above checks is not met, this method throws an + * + *

If any of the above conditions is not met, this method throws an + * {@code IllegalAccessException}. + * + *

These conditions are more restrictive than the conditions specified by {@link + * #setAccessible(boolean)} to suppress access checks. In particular, updating a + * module to export or open a package cannot be used to allow write access + * to final fields with the {@code set} methods defined by {@code Field}. + * Condition (b) is not met if the module containing {@code D} has been updated with + * {@linkplain Module#addExports(String, Module) addExports} to export the package to + * the caller's module. Condition (c) is not met if the module containing {@code D} + * has been updated with {@linkplain Module#addOpens(String, Module) addOpens} to open + * the package to the caller's module. + * + *

This method may be called by + * JNI code with no caller class on the stack. In that case, and when the + * underlying field is final, this {@code Field} object has write access + * if and only if all of the following conditions are true, where {@code D} is the + * field's {@linkplain #getDeclaringClass() declaring class}: + * + *

    + *
  • {@code setAccessible(true)} has succeeded for this {@code Field} object.
  • + *
  • final field mutation is enabled for the unnamed module.
  • + *
  • The field is {@code public} and {@code D} is {@code public} in a package that + * is {@linkplain Module#isExported(String) exported} to all modules.
  • + *
  • {@code D} is not a {@linkplain Class#isRecord() record class}.
  • + *
  • {@code D} is not a {@linkplain Class#isHidden() hidden class}.
  • + *
  • The field is non-static.
  • + *
+ * + *

If any of the above conditions is not met, this method throws an * {@code IllegalAccessException}. * *

Setting a final field in this way @@ -818,6 +887,8 @@ class Field extends AccessibleObject implements Member { * and the field is an instance field. * @throws ExceptionInInitializerError if the initialization provoked * by this method fails. + * + * @see Mutation methods */ @CallerSensitive @ForceInline // to ensure Reflection.getCallerClass optimization @@ -828,8 +899,14 @@ class Field extends AccessibleObject implements Member { Class caller = Reflection.getCallerClass(); checkAccess(caller, obj); getFieldAccessor().set(obj, value); + return; + } + + FieldAccessor fa = getOverrideFieldAccessor(); + if (!Modifier.isFinal(modifiers)) { + fa.set(obj, value); } else { - getOverrideFieldAccessor().set(obj, value); + setFinal(Reflection.getCallerClass(), obj, () -> fa.set(obj, value)); } } @@ -867,8 +944,14 @@ class Field extends AccessibleObject implements Member { Class caller = Reflection.getCallerClass(); checkAccess(caller, obj); getFieldAccessor().setBoolean(obj, z); + return; + } + + FieldAccessor fa = getOverrideFieldAccessor(); + if (!Modifier.isFinal(modifiers)) { + fa.setBoolean(obj, z); } else { - getOverrideFieldAccessor().setBoolean(obj, z); + setFinal(Reflection.getCallerClass(), obj, () -> fa.setBoolean(obj, z)); } } @@ -906,8 +989,14 @@ class Field extends AccessibleObject implements Member { Class caller = Reflection.getCallerClass(); checkAccess(caller, obj); getFieldAccessor().setByte(obj, b); + return; + } + + FieldAccessor fa = getOverrideFieldAccessor(); + if (!Modifier.isFinal(modifiers)) { + fa.setByte(obj, b); } else { - getOverrideFieldAccessor().setByte(obj, b); + setFinal(Reflection.getCallerClass(), obj, () -> fa.setByte(obj, b)); } } @@ -945,8 +1034,14 @@ class Field extends AccessibleObject implements Member { Class caller = Reflection.getCallerClass(); checkAccess(caller, obj); getFieldAccessor().setChar(obj, c); + return; + } + + FieldAccessor fa = getOverrideFieldAccessor(); + if (!Modifier.isFinal(modifiers)) { + fa.setChar(obj, c); } else { - getOverrideFieldAccessor().setChar(obj, c); + setFinal(Reflection.getCallerClass(), obj, () -> fa.setChar(obj, c)); } } @@ -984,8 +1079,14 @@ class Field extends AccessibleObject implements Member { Class caller = Reflection.getCallerClass(); checkAccess(caller, obj); getFieldAccessor().setShort(obj, s); + return; + } + + FieldAccessor fa = getOverrideFieldAccessor(); + if (!Modifier.isFinal(modifiers)) { + fa.setShort(obj, s); } else { - getOverrideFieldAccessor().setShort(obj, s); + setFinal(Reflection.getCallerClass(), obj, () -> fa.setShort(obj, s)); } } @@ -1023,8 +1124,14 @@ class Field extends AccessibleObject implements Member { Class caller = Reflection.getCallerClass(); checkAccess(caller, obj); getFieldAccessor().setInt(obj, i); + return; + } + + FieldAccessor fa = getOverrideFieldAccessor(); + if (!Modifier.isFinal(modifiers)) { + fa.setInt(obj, i); } else { - getOverrideFieldAccessor().setInt(obj, i); + setFinal(Reflection.getCallerClass(), obj, () -> fa.setInt(obj, i)); } } @@ -1062,8 +1169,14 @@ class Field extends AccessibleObject implements Member { Class caller = Reflection.getCallerClass(); checkAccess(caller, obj); getFieldAccessor().setLong(obj, l); + return; + } + + FieldAccessor fa = getOverrideFieldAccessor(); + if (!Modifier.isFinal(modifiers)) { + fa.setLong(obj, l); } else { - getOverrideFieldAccessor().setLong(obj, l); + setFinal(Reflection.getCallerClass(), obj, () -> fa.setLong(obj, l)); } } @@ -1101,8 +1214,14 @@ class Field extends AccessibleObject implements Member { Class caller = Reflection.getCallerClass(); checkAccess(caller, obj); getFieldAccessor().setFloat(obj, f); + return; + } + + FieldAccessor fa = getOverrideFieldAccessor(); + if (!Modifier.isFinal(modifiers)) { + fa.setFloat(obj, f); } else { - getOverrideFieldAccessor().setFloat(obj, f); + setFinal(Reflection.getCallerClass(), obj, () -> fa.setFloat(obj, f)); } } @@ -1140,8 +1259,14 @@ class Field extends AccessibleObject implements Member { Class caller = Reflection.getCallerClass(); checkAccess(caller, obj); getFieldAccessor().setDouble(obj, d); + return; + } + + FieldAccessor fa = getOverrideFieldAccessor(); + if (!Modifier.isFinal(modifiers)) { + fa.setDouble(obj, d); } else { - getOverrideFieldAccessor().setDouble(obj, d); + setFinal(Reflection.getCallerClass(), obj, () -> fa.setDouble(obj, d)); } } @@ -1304,5 +1429,244 @@ class Field extends AccessibleObject implements Member { getDeclaringClass(), getGenericType(), TypeAnnotation.TypeAnnotationTarget.FIELD); -} + } + + /** + * A function that sets a field to a value. + */ + @FunctionalInterface + private interface FieldSetter { + void setFieldValue() throws IllegalAccessException; + } + + /** + * Attempts to set a final field. + */ + private void setFinal(Class caller, Object obj, FieldSetter setter) throws IllegalAccessException { + if (obj != null && isFinalInstanceInNormalClass()) { + preSetFinal(caller, false); + setter.setFieldValue(); + postSetFinal(caller, false); + } else { + // throws IllegalAccessException if static, or field in record or hidden class + setter.setFieldValue(); + } + } + + /** + * Return true if this field is a final instance field in a normal class (not a + * record class or hidden class), + */ + private boolean isFinalInstanceInNormalClass() { + return Modifier.isFinal(modifiers) + && !Modifier.isStatic(modifiers) + && !clazz.isRecord() + && !clazz.isHidden(); + } + + /** + * Check that the caller is allowed to unreflect for mutation a final instance field + * in a normal class. + * @throws IllegalAccessException if not allowed + */ + void checkAllowedToUnreflectFinalSetter(Class caller) throws IllegalAccessException { + Objects.requireNonNull(caller); + preSetFinal(caller, true); + postSetFinal(caller, true); + } + + /** + * Invoke before attempting to mutate, or unreflect for mutation, a final instance + * field in a normal class. + * @throws IllegalAccessException if not allowed + */ + private void preSetFinal(Class caller, boolean unreflect) throws IllegalAccessException { + assert isFinalInstanceInNormalClass(); + + if (caller != null) { + // check if declaring class in package that is open to caller, or public field + // and declaring class is public in package exported to caller + if (!isFinalDeeplyAccessible(caller)) { + throw new IllegalAccessException(notAccessibleToCallerMessage(caller, unreflect)); + } + } else { + // no java caller, only allowed if field is public in exported package + if (!Reflection.verifyPublicMemberAccess(clazz, modifiers)) { + throw new IllegalAccessException(notAccessibleToNoCallerMessage(unreflect)); + } + } + + // check if field mutation is enabled for caller module or illegal final field + // mutation is allowed + var mode = ModuleBootstrap.illegalFinalFieldMutation(); + if (mode == ModuleBootstrap.IllegalFinalFieldMutation.DENY + && !Modules.isFinalMutationEnabled(moduleToCheck(caller))) { + throw new IllegalAccessException(callerNotAllowedToMutateMessage(caller, unreflect)); + } + } + + /** + * Invoke after mutating a final instance field, or when unreflecting a final instance + * field for mutation, to print a warning and record a JFR event. + */ + private void postSetFinal(Class caller, boolean unreflect) { + assert isFinalInstanceInNormalClass(); + + var mode = ModuleBootstrap.illegalFinalFieldMutation(); + if (mode == ModuleBootstrap.IllegalFinalFieldMutation.WARN) { + // first mutation prints warning + Module moduleToCheck = moduleToCheck(caller); + if (Modules.tryEnableFinalMutation(moduleToCheck)) { + String warningMsg = finalFieldMutationWarning(caller, unreflect); + String targetModule = (caller != null && moduleToCheck.isNamed()) + ? moduleToCheck.getName() + : "ALL-UNNAMED"; + VM.initialErr().printf(""" + WARNING: %s + WARNING: Use --enable-final-field-mutation=%s to avoid a warning + WARNING: Mutating final fields will be blocked in a future release unless final field mutation is enabled + """, warningMsg, targetModule); + } + } else if (mode == ModuleBootstrap.IllegalFinalFieldMutation.DEBUG) { + // print warning and stack trace + var sb = new StringBuilder(finalFieldMutationWarning(caller, unreflect)); + StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE) + .forEach(sf -> { + sb.append(System.lineSeparator()).append("\tat " + sf); + }); + VM.initialErr().println(sb); + } + + // record JFR event + FinalFieldMutationEvent.offer(getDeclaringClass(), getName()); + } + + /** + * Returns true if this final field is "deeply accessible" to the caller. + * The field is deeply accessible if declaring class is in a package that is open + * to the caller's module, or the field is public in a public class that is exported + * to the caller's module. + * + * Updates to the module of the declaring class at runtime with {@code Module.addExports} + * or {@code Module.addOpens} have no impact on the result of this method. + */ + private boolean isFinalDeeplyAccessible(Class caller) { + assert isFinalInstanceInNormalClass(); + + // all fields in unnamed modules are deeply accessible + Module declaringModule = clazz.getModule(); + if (!declaringModule.isNamed()) return true; + + // all fields in the caller's module are deeply accessible + Module callerModule = caller.getModule(); + if (callerModule == declaringModule) return true; + + // public field, public class, package exported to caller's module + String pn = clazz.getPackageName(); + if (Modifier.isPublic(modifiers) + && Modifier.isPublic(clazz.getModifiers()) + && Modules.isStaticallyExported(declaringModule, pn, callerModule)) { + return true; + } + + // package open to caller's module + return Modules.isStaticallyOpened(declaringModule, pn, callerModule); + } + + /** + * Returns the Module to use for access checks with the given caller. + */ + private Module moduleToCheck(Class caller) { + if (caller != null) { + return caller.getModule(); + } else { + // no java caller, only allowed if field is public in exported package + return ClassLoaders.appClassLoader().getUnnamedModule(); + } + } + + /** + * Returns the warning message to print when this final field is mutated by + * the given possibly-null caller. + */ + private String finalFieldMutationWarning(Class caller, boolean unreflect) { + assert Modifier.isFinal(modifiers); + String source; + if (caller != null) { + source = caller + " in " + caller.getModule(); + CodeSource cs = caller.getProtectionDomain().getCodeSource(); + if (cs != null) { + URL url = cs.getLocation(); + if (url != null) { + source += " (" + url + ")"; + } + } + } else { + source = "JNI attached thread with no caller frame"; + } + return String.format("Final field %s in %s has been %s by %s", + name, + clazz, + (unreflect) ? "unreflected for mutation" : "mutated reflectively", + source); + } + + /** + * Returns the message for an IllegalAccessException when a final field cannot be + * mutated because the declaring class is in a package that is not "deeply accessible" + * to the caller. + */ + private String notAccessibleToCallerMessage(Class caller, boolean unreflect) { + String exportsOrOpens = Modifier.isPublic(modifiers) + && Modifier.isPublic(clazz.getModifiers()) ? "exports" : "opens"; + return String.format("%s, %s does not explicitly \"%s\" package %s to %s", + cannotSetFieldMessage(caller, unreflect), + clazz.getModule(), + exportsOrOpens, + clazz.getPackageName(), + caller.getModule()); + } + + /** + * Returns the exception message for the IllegalAccessException when this + * final field cannot be mutated because the caller module is not allowed + * to mutate final fields. + */ + private String callerNotAllowedToMutateMessage(Class caller, boolean unreflect) { + if (caller != null) { + return String.format("%s, %s is not allowed to mutate final fields", + cannotSetFieldMessage(caller, unreflect), + caller.getModule()); + } else { + return notAccessibleToNoCallerMessage(unreflect); + } + } + + /** + * Returns the message for an IllegalAccessException when a field is not + * accessible to a JNI attached thread. + */ + private String notAccessibleToNoCallerMessage(boolean unreflect) { + return cannotSetFieldMessage("JNI attached thread with no caller frame cannot", unreflect); + } + + /** + * Returns a message to indicate that the caller cannot set/unreflect this final field. + */ + private String cannotSetFieldMessage(Class caller, boolean unreflect) { + return cannotSetFieldMessage(caller + " (in " + caller.getModule() + ") cannot", unreflect); + } + + /** + * Returns a message to indicate that a field cannot be set/unreflected. + */ + private String cannotSetFieldMessage(String prefix, boolean unreflect) { + if (unreflect) { + return prefix + " unreflect final field " + clazz.getName() + "." + name + + " (in " + clazz.getModule() + ") for mutation"; + } else { + return prefix + " set final field " + clazz.getName() + "." + name + + " (in " + clazz.getModule() + ")"; + } + } } diff --git a/src/java.base/share/classes/java/lang/reflect/ReflectAccess.java b/src/java.base/share/classes/java/lang/reflect/ReflectAccess.java index 835ffef616e..ea4e5e06dcf 100644 --- a/src/java.base/share/classes/java/lang/reflect/ReflectAccess.java +++ b/src/java.base/share/classes/java/lang/reflect/ReflectAccess.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2025, 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 @@ -78,4 +78,9 @@ final class ReflectAccess implements JavaLangReflectAccess { { return ctor.newInstanceWithCaller(args, true, caller); } + + @Override + public void checkAllowedToUnreflectFinalSetter(Class caller, Field f) throws IllegalAccessException { + f.checkAllowedToUnreflectFinalSetter(caller); + } } diff --git a/src/java.base/share/classes/java/lang/reflect/doc-files/MutationMethods.html b/src/java.base/share/classes/java/lang/reflect/doc-files/MutationMethods.html new file mode 100644 index 00000000000..8a731040cd1 --- /dev/null +++ b/src/java.base/share/classes/java/lang/reflect/doc-files/MutationMethods.html @@ -0,0 +1,69 @@ + + + + + Mutation methods + + + +

Mutation methods

+ +

A number of methods in Java SE API provide write access to non-static +final fields. This means that Java code can alter the value of a final field +after the field has been initialized in a constructor. + +The methods that provide write access, known as mutation methods are: +

    +
  • {@link java.lang.reflect.Field#set(Object, Object)}
  • +
  • {@link java.lang.reflect.Field#setBoolean(Object, boolean)}
  • +
  • {@link java.lang.reflect.Field#setByte(Object, byte)}
  • +
  • {@link java.lang.reflect.Field#setChar(Object, char)}
  • +
  • {@link java.lang.reflect.Field#setInt(Object, int)}
  • +
  • {@link java.lang.reflect.Field#setLong(Object, long)}
  • +
  • {@link java.lang.reflect.Field#setFloat(Object, float)}
  • +
  • {@link java.lang.reflect.Field#setDouble(Object, double)}
  • +
  • {@link java.lang.invoke.MethodHandles.Lookup#unreflectSetter(java.lang.reflect.Field)}
  • +
+ +

The use of mutation methods to alter the values of final fields is +strongly inadvisable because it undermines the correctness of programs written in +expectation of final fields being immutable. + +

In the reference implementation, a module can be granted the capability to mutate +final instance fields of classes in packages that are open to the module using +the command line option --enable-final-field-mutation=M1,M2, ... Mn} where +M1, M2, ...Mn are module names (for the unnamed +module, the special value ALL-UNNAMED can be used). Mutation of final +instance fields of classes from modules not listed by that option is deemed +illegal. +The command line option --illegal-final-field-mutation controls how illegal +final mutation is handled. Valid values for this command line option are "warn", "allow", +"debug, and "deny". If this option is not specified then the default is "warn" so that +illegal final field mutation will result in a warning at runtime. + + + + 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 fa6e5b4aac3..86e5f317dc7 100644 --- a/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java +++ b/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java @@ -205,7 +205,7 @@ public interface JavaLangAccess { * Updates the readability so that module m1 reads m2. The new read edge * does not result in a strong reference to m2 (m2 can be GC'ed). * - * This method is the same as m1.addReads(m2) but without a permission check. + * This method is the same as m1.addReads(m2) but without a caller check. */ void addReads(Module m1, Module m2); @@ -259,11 +259,11 @@ public interface JavaLangAccess { /** * Updates module m to allow access to restricted methods. */ - Module addEnableNativeAccess(Module m); + void addEnableNativeAccess(Module m); /** - * Updates module named {@code name} in layer {@code layer} to allow access to restricted methods. - * Returns true iff the given module exists in the given layer. + * Updates module named {@code name} in layer {@code layer} to allow access to + * restricted methods. Returns true iff the given module exists in the given layer. */ boolean addEnableNativeAccess(ModuleLayer layer, String name); @@ -273,7 +273,8 @@ public interface JavaLangAccess { void addEnableNativeAccessToAllUnnamed(); /** - * Ensure that the given module has native access. If not, warn or throw exception depending on the configuration. + * Ensure that the given module has native access. If not, warn or throw exception + * depending on the configuration. * @param m the module in which native access occurred * @param owner the owner of the restricted method being called (or the JNI method being bound) * @param methodName the name of the restricted method being called (or the JNI method being bound) @@ -282,6 +283,31 @@ public interface JavaLangAccess { */ void ensureNativeAccess(Module m, Class owner, String methodName, Class currentClass, boolean jni); + /** + * Enable code in all unnamed modules to mutate final instance fields. + */ + void addEnableFinalMutationToAllUnnamed(); + + /** + * Enable code in a given module to mutate final instance fields. + */ + boolean tryEnableFinalMutation(Module m); + + /** + * Return true if code in a given module is allowed to mutate final instance fields. + */ + boolean isFinalMutationEnabled(Module m); + + /** + * Return true if a given module has statically exported the given package to a given other module. + */ + boolean isStaticallyExported(Module module, String pn, Module other); + + /** + * Return true if a given module has statically opened the given package to a given other module. + */ + boolean isStaticallyOpened(Module module, String pn, Module other); + /** * Returns the ServicesCatalog for the given Layer. */ diff --git a/src/java.base/share/classes/jdk/internal/access/JavaLangReflectAccess.java b/src/java.base/share/classes/jdk/internal/access/JavaLangReflectAccess.java index d0c415d2dc6..965bac67f45 100644 --- a/src/java.base/share/classes/jdk/internal/access/JavaLangReflectAccess.java +++ b/src/java.base/share/classes/jdk/internal/access/JavaLangReflectAccess.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -67,4 +67,10 @@ public interface JavaLangReflectAccess { /** Returns a new instance created by the given constructor with access check */ public T newInstance(Constructor ctor, Object[] args, Class caller) throws IllegalAccessException, InstantiationException, InvocationTargetException; + + /** + * Check that the caller is allowed to unreflect for mutation a final instance field + * in a class that is not a record or hidden class. + */ + void checkAllowedToUnreflectFinalSetter(Class caller, Field f) throws IllegalAccessException; } diff --git a/src/java.base/share/classes/jdk/internal/event/FinalFieldMutationEvent.java b/src/java.base/share/classes/jdk/internal/event/FinalFieldMutationEvent.java new file mode 100644 index 00000000000..4db29d43bd6 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/event/FinalFieldMutationEvent.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2025, 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. 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.internal.event; + +public class FinalFieldMutationEvent extends Event { + public Class declaringClass; + public String fieldName; + + /** + * Commit a FinalFieldMutationEvent if enabled. + */ + public static void offer(Class declaringClass, String fieldName) { + if (enabled()) { + var event = new FinalFieldMutationEvent(); + event.declaringClass = declaringClass; + event.fieldName = fieldName; + event.commit(); + } + } + + public static boolean enabled() { + // Generated by JFR + return false; + } +} diff --git a/src/java.base/share/classes/jdk/internal/module/ModuleBootstrap.java b/src/java.base/share/classes/jdk/internal/module/ModuleBootstrap.java index 45cddc9fb6f..4dfc740024e 100644 --- a/src/java.base/share/classes/jdk/internal/module/ModuleBootstrap.java +++ b/src/java.base/share/classes/jdk/internal/module/ModuleBootstrap.java @@ -452,9 +452,12 @@ public final class ModuleBootstrap { addExtraReads(bootLayer); addExtraExportsAndOpens(bootLayer); - // add enable native access + // enable native access to modules specified to --enable-native-access addEnableNativeAccess(bootLayer); + // allow final mutation by modules specified to --enable-final-field-mutation + addEnableFinalFieldMutation(bootLayer); + Counters.add("jdk.module.boot.7.adjustModulesTime"); // Step 8: CDS dump phase @@ -721,7 +724,6 @@ public final class ModuleBootstrap { * additional packages specified on the command-line. */ private static void addExtraExportsAndOpens(ModuleLayer bootLayer) { - // --add-exports String prefix = "jdk.module.addexports."; Map> extraExports = decode(prefix); @@ -729,14 +731,12 @@ public final class ModuleBootstrap { addExtraExportsOrOpens(bootLayer, extraExports, false); } - // --add-opens prefix = "jdk.module.addopens."; Map> extraOpens = decode(prefix); if (!extraOpens.isEmpty()) { addExtraExportsOrOpens(bootLayer, extraOpens, true); } - } private static void addExtraExportsOrOpens(ModuleLayer bootLayer, @@ -807,6 +807,7 @@ public final class ModuleBootstrap { private static final Set USER_NATIVE_ACCESS_MODULES; private static final Set JDK_NATIVE_ACCESS_MODULES; private static final IllegalNativeAccess ILLEGAL_NATIVE_ACCESS; + private static final IllegalFinalFieldMutation ILLEGAL_FINAL_FIELD_MUTATION; public enum IllegalNativeAccess { ALLOW, @@ -814,14 +815,26 @@ public final class ModuleBootstrap { DENY } + public enum IllegalFinalFieldMutation { + ALLOW, + WARN, + DEBUG, + DENY + } + + static { + ILLEGAL_NATIVE_ACCESS = decodeIllegalNativeAccess(); + USER_NATIVE_ACCESS_MODULES = decodeEnableNativeAccess(); + JDK_NATIVE_ACCESS_MODULES = ModuleLoaderMap.nativeAccessModules(); + ILLEGAL_FINAL_FIELD_MUTATION = decodeIllegalFinalFieldMutation(); + } + public static IllegalNativeAccess illegalNativeAccess() { return ILLEGAL_NATIVE_ACCESS; } - static { - ILLEGAL_NATIVE_ACCESS = addIllegalNativeAccess(); - USER_NATIVE_ACCESS_MODULES = decodeEnableNativeAccess(); - JDK_NATIVE_ACCESS_MODULES = ModuleLoaderMap.nativeAccessModules(); + public static IllegalFinalFieldMutation illegalFinalFieldMutation() { + return ILLEGAL_FINAL_FIELD_MUTATION; } /** @@ -881,7 +894,7 @@ public final class ModuleBootstrap { /** * Process the --illegal-native-access option (and its default). */ - private static IllegalNativeAccess addIllegalNativeAccess() { + private static IllegalNativeAccess decodeIllegalNativeAccess() { String value = getAndRemoveProperty("jdk.module.illegal.native.access"); // don't use a switch: bootstrapping issues! if (value == null) { @@ -899,6 +912,71 @@ public final class ModuleBootstrap { } } + /** + * Process the --illegal-final-field-mutation option. + */ + private static IllegalFinalFieldMutation decodeIllegalFinalFieldMutation() { + String value = getAndRemoveProperty("jdk.module.illegal.final.field.mutation"); + if (value == null) { + return IllegalFinalFieldMutation.WARN; // default + } else if (value.equals("allow")) { + return IllegalFinalFieldMutation.ALLOW; + } else if (value.equals("warn")) { + return IllegalFinalFieldMutation.WARN; + } else if (value.equals("debug")) { + return IllegalFinalFieldMutation.DEBUG; + } else if (value.equals("deny")) { + return IllegalFinalFieldMutation.DENY; + } else { + fail("Value specified to --illegal-final-field-mutation not recognized:" + + " '" + value + "'"); + return null; + } + } + + /** + * Process the modules specified to --enable-final-field-mutation and grant the + * capability to mutate finals to specified named modules or all unnamed modules. + */ + private static void addEnableFinalFieldMutation(ModuleLayer bootLayer) { + for (String name : decodeEnableFinalFieldMutation()) { + if (name.equals("ALL-UNNAMED")) { + JLA.addEnableFinalMutationToAllUnnamed(); + } else { + Module m = bootLayer.findModule(name).orElse(null); + if (m != null) { + JLA.tryEnableFinalMutation(m); + } else { + warnUnknownModule("--enable-final-field-mutation", name); + } + } + } + } + + /** + * Returns the set of module names specified by --enable-final-field-mutation options. + */ + private static Set decodeEnableFinalFieldMutation() { + String prefix = "jdk.module.enable.final.field.mutation."; + int index = 0; + // the system property is removed after decoding + String value = getAndRemoveProperty(prefix + index); + Set modules = new HashSet<>(); + if (value == null) { + return modules; + } + while (value != null) { + for (String s : value.split(",")) { + if (!s.isEmpty()) { + modules.add(s); + } + } + index++; + value = getAndRemoveProperty(prefix + index); + } + return modules; + } + /** * Decodes the values of --add-reads, -add-exports, --add-opens or * --patch-modules options that are encoded in system properties. diff --git a/src/java.base/share/classes/jdk/internal/module/Modules.java b/src/java.base/share/classes/jdk/internal/module/Modules.java index 3c3d148e196..760b3ba9a08 100644 --- a/src/java.base/share/classes/jdk/internal/module/Modules.java +++ b/src/java.base/share/classes/jdk/internal/module/Modules.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2025, 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 @@ -139,6 +139,45 @@ public class Modules { JLA.addEnableNativeAccessToAllUnnamed(); } + /** + * Enable code in all unnamed modules to mutate final instance fields. + */ + public static void addEnableFinalMutationToAllUnnamed() { + JLA.addEnableFinalMutationToAllUnnamed(); + } + + /** + * Enable code in a given module to mutate final instance fields. + */ + public static boolean tryEnableFinalMutation(Module m) { + return JLA.tryEnableFinalMutation(m); + } + + /** + * Return true if code in a given module is allowed to mutate final instance fields. + */ + public static boolean isFinalMutationEnabled(Module m) { + return JLA.isFinalMutationEnabled(m); + } + + /** + * Return true if a given module has statically exported the given package to a given + * other module. "statically exported" means the module declaration, --add-exports on + * the command line, or Add-Exports in the main manifest of an executable JAR. + */ + public static boolean isStaticallyExported(Module m, String pn, Module other) { + return JLA.isStaticallyExported(m, pn, other); + } + + /** + * Return true if a given module has statically opened the given package to a given + * other module. "statically open" means the module declaration, --add-opens on the + * command line, or Add-Opens in the main manifest of an executable JAR. + */ + public static boolean isStaticallyOpened(Module m, String pn, Module other) { + return JLA.isStaticallyOpened(m, pn, other); + } + /** * Updates module m to use a service. * Same as m2.addUses(service) but without a caller check. diff --git a/src/java.base/share/classes/sun/launcher/LauncherHelper.java b/src/java.base/share/classes/sun/launcher/LauncherHelper.java index 81a4fe32110..9badf2beaeb 100644 --- a/src/java.base/share/classes/sun/launcher/LauncherHelper.java +++ b/src/java.base/share/classes/sun/launcher/LauncherHelper.java @@ -103,6 +103,7 @@ public final class LauncherHelper { private static final String ADD_EXPORTS = "Add-Exports"; private static final String ADD_OPENS = "Add-Opens"; private static final String ENABLE_NATIVE_ACCESS = "Enable-Native-Access"; + private static final String ENABLE_FINAL_FIELD_MUTATION = "Enable-Final-Field-Mutation"; private static StringBuilder outBuf = new StringBuilder(); @@ -647,6 +648,8 @@ public final class LauncherHelper { if (opens != null) { addExportsOrOpens(opens, true); } + + // Enable-Native-Access String enableNativeAccess = mainAttrs.getValue(ENABLE_NATIVE_ACCESS); if (enableNativeAccess != null) { if (!enableNativeAccess.equals("ALL-UNNAMED")) { @@ -655,6 +658,16 @@ public final class LauncherHelper { Modules.addEnableNativeAccessToAllUnnamed(); } + // Enable-Final-Field-Mutation + String enableFinalFieldMutation = mainAttrs.getValue(ENABLE_FINAL_FIELD_MUTATION); + if (enableFinalFieldMutation != null) { + if (!enableFinalFieldMutation.equals("ALL-UNNAMED")) { + abort(null, "java.launcher.jar.error.illegal.effm.value", + enableFinalFieldMutation); + } + Modules.addEnableFinalMutationToAllUnnamed(); + } + /* * Hand off to FXHelper if it detects a JavaFX application * This must be done after ensuring a Main-Class entry diff --git a/src/java.base/share/classes/sun/launcher/resources/launcher.properties b/src/java.base/share/classes/sun/launcher/resources/launcher.properties index 83e08a3e11c..739ef8b8a29 100644 --- a/src/java.base/share/classes/sun/launcher/resources/launcher.properties +++ b/src/java.base/share/classes/sun/launcher/resources/launcher.properties @@ -70,6 +70,14 @@ java.launcher.opt.footer = \ \ by code in modules for which native access is not explicitly enabled.\n\ \ is one of "deny", "warn" or "allow". The default value is "warn".\n\ \ This option will be removed in a future release.\n\ +\ --enable-final-field-mutation [,...]\n\ +\ allow code in the specified modules to mutate final instance fields.\n\ +\ can also be ALL-UNNAMED to indicate code on the class path.\n\ +\ --illegal-final-field-mutation=\n\ +\ allow or deny final field mutation by code in modules for which final\n\ +\ field mutation is not explicitly enabled.\n\ +\ is one of "deny", "warn", "debug", or "allow". The default value is "warn".\n\ +\ This option will be removed in a future release.\n\ \ --list-modules\n\ \ list observable modules and exit\n\ \ -d \n\ @@ -291,6 +299,8 @@ java.launcher.jar.error5=\ Error: An unexpected error occurred while trying to close file {0} java.launcher.jar.error.illegal.ena.value=\ Error: illegal value \"{0}\" for Enable-Native-Access manifest attribute. Only 'ALL-UNNAMED' is allowed +java.launcher.jar.error.illegal.effm.value=\ + Error: illegal value \"{0}\" for Enable-Final-Field-Mutation manifest attribute. Only 'ALL-UNNAMED' is allowed java.launcher.init.error=initialization error java.launcher.javafx.error1=\ Error: The JavaFX launchApplication method has the wrong signature, it\n\ diff --git a/src/java.base/share/man/java.md b/src/java.base/share/man/java.md index 43719ff619a..462baa5a4a0 100644 --- a/src/java.base/share/man/java.md +++ b/src/java.base/share/man/java.md @@ -450,7 +450,7 @@ the JVM. > **Note:** This option will be removed in a future release. - `allow`: This mode allows illegal native access in all modules, - without any warings. + without any warnings. - `warn`: This mode is identical to `allow` except that a warning message is issued for the first illegal native access found in a module. @@ -465,6 +465,39 @@ the JVM. run it with `--illegal-native-access=deny` along with any necessary `--enable-native-access` options. +`--enable-final-field-mutation` *module*\[,*module*...\] +: Mutation of final fields is possible with the reflection API of the Java Platform. + However, it compromises safety and performance in all programs. + This option allows code in the specified modules to mutate final fields by reflection. + Attempts by code in any other module to mutate final fields by reflection are deemed _illegal_. + + *module* can be the name of a module on the module path, or `ALL-UNNAMED` to indicate + code on the class path. + +-`--illegal-final-field-mutation=`*parameter* +: This option specifies a mode for how _illegal_ final field mutation is handled: + + > **Note:** This option will be removed in a future release. + + - `allow`: This mode allows illegal final field mutation in all modules, + without any warnings. + + - `warn`: This mode is identical to `allow` except that a warning message is + issued for the first illegal final field mutation performaed in a module. + This mode is the default for the current JDK but will change in a future + release. + + - `debug`: This mode is identical to `allow` except that a warning message + and stack trace are printed for every illegal final field mutation. + + - `deny`: This mode disables final field mutation. That is, any illegal final + field mutation access causes an `IllegalAccessException`. This mode will + become the default in a future release. + + To verify that your application is ready for a future version of the JDK, + run it with `--illegal-final-field-mutation=deny` along with any necessary + `--enable-final-field-mutation` options. + `--finalization=`*value* : Controls whether the JVM performs finalization of objects. Valid values are "enabled" and "disabled". Finalization is enabled by default, so the @@ -701,7 +734,8 @@ the Java HotSpot Virtual Machine. - A class descriptor is in decorated format (`Lname;`) when it should not be. - A `NULL` parameter is allowed, but its use is questionable. - Calling other JNI functions in the scope of `Get/ReleasePrimitiveArrayCritical` - or `Get/ReleaseStringCritical` + or `Get/ReleaseStringCritical`. + - A JNI call was made to mutate a final field. Expect a performance degradation when this option is used. diff --git a/src/jdk.jfr/share/classes/jdk/jfr/events/FinalFieldMutationEvent.java b/src/jdk.jfr/share/classes/jdk/jfr/events/FinalFieldMutationEvent.java new file mode 100644 index 00000000000..8f90ad2c8fd --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/events/FinalFieldMutationEvent.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2025, 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. 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.jfr.events; + +import jdk.jfr.Category; +import jdk.jfr.Description; +import jdk.jfr.Label; +import jdk.jfr.Name; +import jdk.jfr.internal.Type; +import jdk.jfr.internal.MirrorEvent; +import jdk.jfr.internal.RemoveFields; + +@Category("Java Application") +@Label("Final Field Mutation") +@Name(Type.EVENT_NAME_PREFIX + "FinalFieldMutation") +@RemoveFields("duration") +@StackFilter({ + "java.lang.reflect.Field", + "java.lang.reflect.ReflectAccess", + "java.lang.invoke.MethodHandles$Lookup" +}) +public final class FinalFieldMutationEvent extends MirrorEvent { + + @Label("Declaring Class") + @Description("Declaring class with final field") + public Class declaringClass; + + @Label("Field Name") + @Description("Field name of final field") + public String fieldName; + +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/JDKEvents.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/JDKEvents.java index 503a7955e00..53d40c1e5e1 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/JDKEvents.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/JDKEvents.java @@ -76,6 +76,7 @@ public final class JDKEvents { jdk.internal.event.VirtualThreadSubmitFailedEvent.class, jdk.internal.event.X509CertificateEvent.class, jdk.internal.event.X509ValidationEvent.class, + jdk.internal.event.FinalFieldMutationEvent.class, DirectBufferStatisticsEvent.class, InitialSecurityPropertyEvent.class, MethodTraceEvent.class, diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/MirrorEvents.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/MirrorEvents.java index 48dc0d22cea..7cbd893ee1d 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/MirrorEvents.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/MirrorEvents.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -34,6 +34,7 @@ import jdk.jfr.events.ExceptionThrownEvent; import jdk.jfr.events.FileForceEvent; import jdk.jfr.events.FileReadEvent; import jdk.jfr.events.FileWriteEvent; +import jdk.jfr.events.FinalFieldMutationEvent; import jdk.jfr.events.ProcessStartEvent; import jdk.jfr.events.SecurityPropertyModificationEvent; import jdk.jfr.events.SecurityProviderServiceEvent; @@ -77,6 +78,7 @@ final class MirrorEvents { register("jdk.internal.event.ErrorThrownEvent", ErrorThrownEvent.class); register("jdk.internal.event.ExceptionStatisticsEvent", ExceptionStatisticsEvent.class); register("jdk.internal.event.ExceptionThrownEvent", ExceptionThrownEvent.class); + register("jdk.internal.event.FinalFieldMutationEvent", FinalFieldMutationEvent.class); }; private static void register(String eventClassName, Class mirrorClass) { diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java index 30bd96ba86a..eaba86e6327 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java @@ -125,6 +125,7 @@ public final class PlatformEventType extends Type { Type.EVENT_NAME_PREFIX + "FileWrite" -> 6; case Type.EVENT_NAME_PREFIX + "FileRead", Type.EVENT_NAME_PREFIX + "FileForce" -> 5; + case Type.EVENT_NAME_PREFIX + "FinalFieldMutation" -> 4; default -> 3; }; } diff --git a/src/jdk.jfr/share/conf/jfr/default.jfc b/src/jdk.jfr/share/conf/jfr/default.jfc index eb3b8626722..e4dc3315d2b 100644 --- a/src/jdk.jfr/share/conf/jfr/default.jfc +++ b/src/jdk.jfr/share/conf/jfr/default.jfc @@ -117,6 +117,11 @@ everyChunk + + true + true + + true true diff --git a/src/jdk.jfr/share/conf/jfr/profile.jfc b/src/jdk.jfr/share/conf/jfr/profile.jfc index 5ffdc8d9e4d..619d7d90a53 100644 --- a/src/jdk.jfr/share/conf/jfr/profile.jfc +++ b/src/jdk.jfr/share/conf/jfr/profile.jfc @@ -117,6 +117,11 @@ everyChunk + + true + true + + true true diff --git a/test/hotspot/jtreg/runtime/jni/mutateFinals/MutateFinals.java b/test/hotspot/jtreg/runtime/jni/mutateFinals/MutateFinals.java new file mode 100644 index 00000000000..ecf89f1a5da --- /dev/null +++ b/test/hotspot/jtreg/runtime/jni/mutateFinals/MutateFinals.java @@ -0,0 +1,356 @@ +/* + * Copyright (c) 2025, 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 java.lang.reflect.Method; +import java.util.Objects; + +/** + * Invoked by MutateFinalsTest, either directly or in a child VM, with the name of the + * test method in this class to execute. + */ + +public class MutateFinals { + + /** + * Usage: java MutateFinals + */ + public static void main(String[] args) throws Exception { + invoke(args[0]); + } + + /** + * Invokes the given method. + */ + static void invoke(String methodName) throws Exception { + Method m = MutateFinals.class.getDeclaredMethod(methodName); + m.invoke(null); + } + + /** + * JNI SetObjectField. + */ + private static void testJniSetObjectField() throws Exception { + class C { + final Object value; + C(Object value) { + this.value = value; + } + } + Object oldValue = new Object(); + Object newValue = new Object(); + var obj = new C(oldValue); + jniSetObjectField(obj, newValue); + assertTrue(obj.value == newValue); + } + + /** + * JNI SetBooleanField. + */ + private static void testJniSetBooleanField() throws Exception { + class C { + final boolean value; + C(boolean value) { + this.value = value; + } + } + var obj = new C(false); + jniSetBooleanField(obj, true); + assertTrue(obj.value); + } + + /** + * JNI SetByteField. + */ + private static void testJniSetByteField() throws Exception { + class C { + final byte value; + C(byte value) { + this.value = value; + } + } + byte oldValue = (byte) 1; + byte newValue = (byte) 2; + var obj = new C(oldValue); + jniSetByteField(obj, newValue); + assertEquals(newValue, obj.value); + } + + /** + * JNI SetCharField. + */ + private static void testJniSetCharField() throws Exception { + class C { + final char value; + C(char value) { + this.value = value; + } + } + char oldValue = 'A'; + char newValue = 'B'; + var obj = new C(oldValue); + jniSetCharField(obj, newValue); + assertEquals(newValue, obj.value); + } + + /** + * JNI SetShortField. + */ + private static void testJniSetShortField() throws Exception { + class C { + final short value; + C(short value) { + this.value = value; + } + } + short oldValue = (short) 1; + short newValue = (short) 2; + var obj = new C(oldValue); + jniSetShortField(obj, newValue); + assertEquals(newValue, obj.value); + } + + /** + * JNI SetIntField. + */ + private static void testJniSetIntField() throws Exception { + class C { + final int value; + C(int value) { + this.value = value; + } + } + int oldValue = 1; + int newValue = 2; + var obj = new C(oldValue); + jniSetIntField(obj, newValue); + assertEquals(newValue, obj.value); + } + + /** + * JNI SetLongField. + */ + private static void testJniSetLongField() throws Exception { + class C { + final long value; + C(long value) { + this.value = value; + } + } + long oldValue = 1L; + long newValue = 2L; + var obj = new C(oldValue); + jniSetLongField(obj, newValue); + assertEquals(newValue, obj.value); + } + + /** + * JNI SetFloatField. + */ + private static void testJniSetFloatField() throws Exception { + class C { + final float value; + C(float value) { + this.value = value; + } + } + float oldValue = 1.0f; + float newValue = 2.0f; + var obj = new C(oldValue); + jniSetFloatField(obj, newValue); + assertEquals(newValue, obj.value); + } + + /** + * JNI SetDoubleField. + */ + private static void testJniSetDoubleField() throws Exception { + class C { + final double value; + C(double value) { + this.value = value; + } + } + double oldValue = 1.0d; + double newValue = 2.0d; + var obj = new C(oldValue); + jniSetDoubleField(obj, newValue); + assertEquals(newValue, obj.value); + } + + /** + * JNI SetStaticObjectField. + */ + private static void testJniSetStaticObjectField() throws Exception { + class C { + static final Object value = new Object(); + } + Object newValue = new Object(); + jniSetStaticObjectField(C.class, newValue); + assertTrue(C.value == newValue); + } + + /** + * JNI SetStaticBooleanField. + */ + private static void testJniSetStaticBooleanField() throws Exception { + class C { + static final boolean value = false; + } + jniSetStaticBooleanField(C.class, true); + // use reflection as field treated as constant by compiler + boolean value = (boolean) C.class.getDeclaredField("value").get(null); + assertTrue(value); + } + + /** + * JNI SetStaticByteField. + */ + private static void testJniSetStaticByteField() throws Exception { + class C { + static final byte value = (byte) 1; + } + byte newValue = (byte) 2; + jniSetStaticByteField(C.class, newValue); + // use reflection as field treated as constant by compiler + byte value = (byte) C.class.getDeclaredField("value").get(null); + assertEquals(newValue, value); + } + + /** + * JNI SetStaticCharField. + */ + private static void testJniSetStaticCharField() throws Exception { + class C { + static final char value = 'A'; + } + char newValue = 'B'; + jniSetStaticCharField(C.class, newValue); + // use reflection as field treated as constant by compiler + char value = (char) C.class.getDeclaredField("value").get(null); + assertEquals(newValue, value); + } + + /** + * JNI SetStaticShortField. + */ + private static void testJniSetStaticShortField() throws Exception { + class C { + static final short value = (short) 1; + } + short newValue = (short) 2; + jniSetStaticShortField(C.class, newValue); + // use reflection as field treated as constant by compiler + short value = (short) C.class.getDeclaredField("value").get(null); + assertEquals(newValue, value); + } + + /** + * JNI SetStaticIntField. + */ + private static void testJniSetStaticIntField() throws Exception { + class C { + static final int value = 1; + } + int newValue = 2; + jniSetStaticIntField(C.class, newValue); + // use reflection as field treated as constant by compiler + int value = (int) C.class.getDeclaredField("value").get(null); + assertEquals(newValue, value); + } + + /** + * JNI SetStaticLongField. + */ + private static void testJniSetStaticLongField() throws Exception { + class C { + static final long value = 1L; + } + long newValue = 2L; + jniSetStaticLongField(C.class, newValue); + // use reflection as field treated as constant by compiler + long value = (long) C.class.getDeclaredField("value").get(null); + assertEquals(newValue, value); + } + + /** + * JNI SetStaticFloatField. + */ + private static void testJniSetStaticFloatField() throws Exception { + class C { + static final float value = 1.0f; + } + float newValue = 2.0f; + jniSetStaticFloatField(C.class, newValue); + // use reflection as field treated as constant by compiler + float value = (float) C.class.getDeclaredField("value").get(null); + assertEquals(newValue, value); + } + + /** + * JNI SetStaticDoubleField. + */ + private static void testJniSetStaticDoubleField() throws Exception { + class C { + static final double value = 1.0d; + } + double newValue = 2.0f; + jniSetStaticDoubleField(C.class, newValue); + // use reflection as field treated as constant by compiler + double value = (double) C.class.getDeclaredField("value").get(null); + assertEquals(newValue, value); + } + + private static native void jniSetObjectField(Object obj, Object value); + private static native void jniSetBooleanField(Object obj, boolean value); + private static native void jniSetByteField(Object obj, byte value); + private static native void jniSetCharField(Object obj, char value); + private static native void jniSetShortField(Object obj, short value); + private static native void jniSetIntField(Object obj, int value); + private static native void jniSetLongField(Object obj, long value); + private static native void jniSetFloatField(Object obj, float value); + private static native void jniSetDoubleField(Object obj, double value); + + private static native void jniSetStaticObjectField(Class clazz, Object value); + private static native void jniSetStaticBooleanField(Class clazz, boolean value); + private static native void jniSetStaticByteField(Class clazz, byte value); + private static native void jniSetStaticCharField(Class clazz, char value); + private static native void jniSetStaticShortField(Class clazz, short value); + private static native void jniSetStaticIntField(Class clazz, int value); + private static native void jniSetStaticLongField(Class clazz, long value); + private static native void jniSetStaticFloatField(Class clazz, float value); + private static native void jniSetStaticDoubleField(Class clazz, double value); + + static { + System.loadLibrary("MutateFinals"); + } + + private static void assertTrue(boolean e) { + if (!e) throw new RuntimeException("Not true as expected"); + } + + private static void assertEquals(Object expected, Object actual) { + if (!Objects.equals(expected, actual)) { + throw new RuntimeException("Actual: " + actual + ", expected: " + expected); + } + } +} diff --git a/test/hotspot/jtreg/runtime/jni/mutateFinals/MutateFinalsTest.java b/test/hotspot/jtreg/runtime/jni/mutateFinals/MutateFinalsTest.java new file mode 100644 index 00000000000..0ae4ae231b9 --- /dev/null +++ b/test/hotspot/jtreg/runtime/jni/mutateFinals/MutateFinalsTest.java @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8353835 + * @summary Test JNI SetXXXField methods to set final instance and final static fields + * @key randomness + * @modules java.management + * @library /test/lib + * @compile MutateFinals.java + * @run junit/native/timeout=300 MutateFinalsTest + */ + +import java.util.List; +import java.util.stream.Stream; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import jdk.test.lib.process.ProcessTools; +import jdk.test.lib.process.OutputAnalyzer; + +class MutateFinalsTest { + + static String javaLibraryPath; + + @BeforeAll + static void init() { + javaLibraryPath = System.getProperty("java.library.path"); + } + + /** + * The names of the test methods that use JNI to set final instance fields. + */ + static Stream mutateInstanceFieldMethods() { + return Stream.of( + "testJniSetObjectField", + "testJniSetBooleanField", + "testJniSetByteField", + "testJniSetCharField", + "testJniSetShortField", + "testJniSetIntField", + "testJniSetLongField", + "testJniSetFloatField", + "testJniSetDoubleField" + ); + } + + /** + * The names of the test methods that use JNI to set final static fields. + */ + static Stream mutateStaticFieldMethods() { + return Stream.of( + "testJniSetStaticObjectField", + "testJniSetStaticBooleanField", + "testJniSetStaticByteField", + "testJniSetStaticCharField", + "testJniSetStaticShortField", + "testJniSetStaticIntField", + "testJniSetStaticLongField", + "testJniSetStaticFloatField", + "testJniSetStaticDoubleField" + ); + } + + /** + * The names of all test methods that use JNI to set final fields. + */ + static Stream allMutationMethods() { + return Stream.concat(mutateInstanceFieldMethods(), mutateStaticFieldMethods()); + } + + /** + * Mutate a final field with JNI. + */ + @ParameterizedTest + @MethodSource("allMutationMethods") + void testMutateFinal(String methodName) throws Exception { + MutateFinals.invoke(methodName); + } + + /** + * Mutate a final instance field with JNI. The test launches a child VM with -Xcheck:jni + * and expects a warning in the output. + */ + @ParameterizedTest + @MethodSource("mutateInstanceFieldMethods") + void testMutateInstanceFinalWithXCheckJni(String methodName) throws Exception { + test(methodName, "-Xcheck:jni") + .shouldContain("WARNING in native method: SetField called to mutate final instance field") + .shouldHaveExitValue(0); + } + + /** + * Mutate final static fields with JNI. The test launches a child VM with -Xcheck:jni + * and expects a warning in the output. + */ + @ParameterizedTest + @MethodSource("mutateStaticFieldMethods") + void testMutateStaticFinalWithXCheckJni(String methodName) throws Exception { + test(methodName, "-Xcheck:jni") + .shouldContain("WARNING in native method: SetStaticField called to mutate final static field") + .shouldHaveExitValue(0); + } + + /** + * Mutate a final instance field with JNI. The test launches a child VM with -Xlog + * and expects a log message in the output. + */ + @ParameterizedTest + @MethodSource("mutateInstanceFieldMethods") + void testMutateInstanceFinalWithLogging(String methodName) throws Exception { + String type = methodName.contains("Object") ? "Object" : ""; + test(methodName, "-Xlog:jni=debug") + .shouldContain("[debug][jni] Set" + type + "Field mutated final instance field") + .shouldHaveExitValue(0); + } + + /** + * Mutate a final static field with JNI. The test launches a child VM with -Xlog + * and expects a log message in the output. + */ + @ParameterizedTest + @MethodSource("mutateStaticFieldMethods") + void testMutateStaticFinalWithLogging(String methodName) throws Exception { + String type = methodName.contains("Object") ? "Object" : ""; + test(methodName, "-Xlog:jni=debug") + .shouldContain("[debug][jni] SetStatic" + type + "Field mutated final static field") + .shouldHaveExitValue(0); + } + + /** + * Launches MutateFinals with the given method name as parameter, and the given VM options. + */ + private OutputAnalyzer test(String methodName, String... vmopts) throws Exception { + Stream s1 = Stream.of( + "-Djava.library.path=" + javaLibraryPath, + "--enable-native-access=ALL-UNNAMED"); + Stream s2 = Stream.of(vmopts); + Stream s3 = Stream.of("MutateFinals", methodName); + String[] opts = Stream.concat(Stream.concat(s1, s2), s3).toArray(String[]::new); + var outputAnalyzer = ProcessTools + .executeTestJava(opts) + .outputTo(System.err) + .errorTo(System.err); + return outputAnalyzer; + } +} diff --git a/test/hotspot/jtreg/runtime/jni/mutateFinals/libMutateFinals.c b/test/hotspot/jtreg/runtime/jni/mutateFinals/libMutateFinals.c new file mode 100644 index 00000000000..b903169484b --- /dev/null +++ b/test/hotspot/jtreg/runtime/jni/mutateFinals/libMutateFinals.c @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include +#include "jni.h" + +JNIEXPORT void JNICALL Java_MutateFinals_jniSetObjectField(JNIEnv *env, jclass ignore, jobject obj, jobject value) { + jclass clazz = (*env)->GetObjectClass(env, obj); + jfieldID fid = (*env)->GetFieldID(env, clazz, "value", "Ljava/lang/Object;"); + if (fid != NULL) { + (*env)->SetObjectField(env, obj, fid, value); + } +} + +JNIEXPORT void JNICALL Java_MutateFinals_jniSetBooleanField(JNIEnv *env, jclass ignore, jobject obj, jboolean value) { + jclass clazz = (*env)->GetObjectClass(env, obj); + jfieldID fid = (*env)->GetFieldID(env, clazz, "value", "Z"); + if (fid != NULL) { + (*env)->SetBooleanField(env, obj, fid, value); + } +} + +JNIEXPORT void JNICALL Java_MutateFinals_jniSetByteField(JNIEnv *env, jclass ignore, jobject obj, jbyte value) { + jclass clazz = (*env)->GetObjectClass(env, obj); + jfieldID fid = (*env)->GetFieldID(env, clazz, "value", "B"); + if (fid != NULL) { + (*env)->SetByteField(env, obj, fid, value); + } +} + +JNIEXPORT void JNICALL Java_MutateFinals_jniSetCharField(JNIEnv *env, jclass ignore, jobject obj, jchar value) { + jclass clazz = (*env)->GetObjectClass(env, obj); + jfieldID fid = (*env)->GetFieldID(env, clazz, "value", "C"); + if (fid != NULL) { + (*env)->SetCharField(env, obj, fid, value); + } +} + +JNIEXPORT void JNICALL Java_MutateFinals_jniSetShortField(JNIEnv *env, jclass ignore, jobject obj, jshort value) { + jclass clazz = (*env)->GetObjectClass(env, obj); + jfieldID fid = (*env)->GetFieldID(env, clazz, "value", "S"); + if (fid != NULL) { + (*env)->SetShortField(env, obj, fid, value); + } +} + +JNIEXPORT void JNICALL Java_MutateFinals_jniSetIntField(JNIEnv *env, jclass ignore, jobject obj, jint value) { + jclass clazz = (*env)->GetObjectClass(env, obj); + jfieldID fid = (*env)->GetFieldID(env, clazz, "value", "I"); + if (fid != NULL) { + (*env)->SetIntField(env, obj, fid, value); + } +} + +JNIEXPORT void JNICALL Java_MutateFinals_jniSetLongField(JNIEnv *env, jclass ignore, jobject obj, jlong value) { + jclass clazz = (*env)->GetObjectClass(env, obj); + jfieldID fid = (*env)->GetFieldID(env, clazz, "value", "J"); + if (fid != NULL) { + (*env)->SetLongField(env, obj, fid, value); + } +} + +JNIEXPORT void JNICALL Java_MutateFinals_jniSetFloatField(JNIEnv *env, jclass ignore, jobject obj, jfloat value) { + jclass clazz = (*env)->GetObjectClass(env, obj); + jfieldID fid = (*env)->GetFieldID(env, clazz, "value", "F"); + if (fid != NULL) { + (*env)->SetFloatField(env, obj, fid, value); + } +} + +JNIEXPORT void JNICALL Java_MutateFinals_jniSetDoubleField(JNIEnv *env, jclass ignore, jobject obj, jdouble value) { + jclass clazz = (*env)->GetObjectClass(env, obj); + jfieldID fid = (*env)->GetFieldID(env, clazz, "value", "D"); + if (fid != NULL) { + (*env)->SetDoubleField(env, obj, fid, value); + } +} + +JNIEXPORT void JNICALL Java_MutateFinals_jniSetStaticObjectField(JNIEnv *env, jclass ignore, jclass clazz, jobject value) { + jfieldID fid = (*env)->GetStaticFieldID(env, clazz, "value", "Ljava/lang/Object;"); + if (fid != NULL) { + (*env)->SetStaticObjectField(env, clazz, fid, value); + } +} + +JNIEXPORT void JNICALL Java_MutateFinals_jniSetStaticBooleanField(JNIEnv *env, jclass ignore, jclass clazz, jboolean value) { + jfieldID fid = (*env)->GetStaticFieldID(env, clazz, "value", "Z"); + if (fid != NULL) { + (*env)->SetStaticBooleanField(env, clazz, fid, value); + } +} + +JNIEXPORT void JNICALL Java_MutateFinals_jniSetStaticByteField(JNIEnv *env, jclass ignore, jclass clazz, jbyte value) { + jfieldID fid = (*env)->GetStaticFieldID(env, clazz, "value", "B"); + if (fid != NULL) { + (*env)->SetStaticByteField(env, clazz, fid, value); + } +} + +JNIEXPORT void JNICALL Java_MutateFinals_jniSetStaticCharField(JNIEnv *env, jclass ignore, jclass clazz, jchar value) { + jfieldID fid = (*env)->GetStaticFieldID(env, clazz, "value", "C"); + if (fid != NULL) { + (*env)->SetStaticCharField(env, clazz, fid, value); + } +} + +JNIEXPORT void JNICALL Java_MutateFinals_jniSetStaticShortField(JNIEnv *env, jclass ignore, jclass clazz, jshort value) { + jfieldID fid = (*env)->GetStaticFieldID(env, clazz, "value", "S"); + if (fid != NULL) { + (*env)->SetStaticShortField(env, clazz, fid, value); + } +} + +JNIEXPORT void JNICALL Java_MutateFinals_jniSetStaticIntField(JNIEnv *env, jclass ignore, jclass clazz, jint value) { + jfieldID fid = (*env)->GetStaticFieldID(env, clazz, "value", "I"); + if (fid != NULL) { + (*env)->SetStaticIntField(env, clazz, fid, value); + } +} + +JNIEXPORT void JNICALL Java_MutateFinals_jniSetStaticLongField(JNIEnv *env, jclass ignore, jclass clazz, jlong value) { + jfieldID fid = (*env)->GetStaticFieldID(env, clazz, "value", "J"); + if (fid != NULL) { + (*env)->SetStaticLongField(env, clazz, fid, value); + } +} + +JNIEXPORT void JNICALL Java_MutateFinals_jniSetStaticFloatField(JNIEnv *env, jclass ignore, jclass clazz, jfloat value) { + jfieldID fid = (*env)->GetStaticFieldID(env, clazz, "value", "F"); + if (fid != NULL) { + (*env)->SetStaticFloatField(env, clazz, fid, value); + } +} + +JNIEXPORT void JNICALL Java_MutateFinals_jniSetStaticDoubleField(JNIEnv *env, jclass ignore, jclass clazz, jdouble value) { + jfieldID fid = (*env)->GetStaticFieldID(env, clazz, "value", "D"); + if (fid != NULL) { + (*env)->SetStaticDoubleField(env, clazz, fid, value); + } +} diff --git a/test/jdk/java/lang/invoke/MethodHandlesGeneralTest.java b/test/jdk/java/lang/invoke/MethodHandlesGeneralTest.java index 7997e1266c2..b60c35fc30b 100644 --- a/test/jdk/java/lang/invoke/MethodHandlesGeneralTest.java +++ b/test/jdk/java/lang/invoke/MethodHandlesGeneralTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,6 +29,7 @@ * @run junit/othervm/timeout=2500 -XX:+IgnoreUnrecognizedVMOptions * -XX:-VerifyDependencies * -esa + * --enable-final-field-mutation=ALL-UNNAMED * test.java.lang.invoke.MethodHandlesGeneralTest */ diff --git a/test/jdk/java/lang/invoke/VarHandles/accessibility/TestFieldLookupAccessibility.java b/test/jdk/java/lang/invoke/VarHandles/accessibility/TestFieldLookupAccessibility.java index 9e2d01ec7c5..343b15a1caf 100644 --- a/test/jdk/java/lang/invoke/VarHandles/accessibility/TestFieldLookupAccessibility.java +++ b/test/jdk/java/lang/invoke/VarHandles/accessibility/TestFieldLookupAccessibility.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2025, 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,10 +27,12 @@ * @compile TestFieldLookupAccessibility.java * pkg/A.java pkg/B_extends_A.java pkg/C.java * pkg/subpkg/B_extends_A.java pkg/subpkg/C.java - * @run testng/othervm TestFieldLookupAccessibility + * @run testng/othervm --enable-final-field-mutation=ALL-UNNAMED -DwriteAccess=true TestFieldLookupAccessibility + * @run testng/othervm --illegal-final-field-mutation=deny -DwriteAccess=false TestFieldLookupAccessibility */ -import org.testng.Assert; +import static org.testng.Assert.*; +import org.testng.annotations.BeforeClass; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import pkg.B_extends_A; @@ -48,6 +50,14 @@ import java.util.stream.Collectors; import java.util.stream.Stream; public class TestFieldLookupAccessibility { + static boolean writeAccess; + + @BeforeClass + static void setup() { + String s = System.getProperty("writeAccess"); + assertNotNull(s); + writeAccess = Boolean.valueOf(s); + } // The set of possible field lookup mechanisms enum FieldLookup { @@ -118,7 +128,11 @@ public class TestFieldLookupAccessibility { } boolean isAccessible(Field f) { - return !(Modifier.isStatic(f.getModifiers()) && Modifier.isFinal(f.getModifiers())); + if (Modifier.isFinal(f.getModifiers())) { + return !Modifier.isStatic(f.getModifiers()) && writeAccess; + } else { + return true; + } } // Setting the accessibility bit of a Field grants access to non-static @@ -226,15 +240,15 @@ public class TestFieldLookupAccessibility { collect(Collectors.toSet()); if (!actualFieldNames.equals(expected)) { if (actualFieldNames.isEmpty()) { - Assert.assertEquals(actualFieldNames, expected, "No accessibility failures:"); + assertEquals(actualFieldNames, expected, "No accessibility failures:"); } else { - Assert.assertEquals(actualFieldNames, expected, "Accessibility failures differ:"); + assertEquals(actualFieldNames, expected, "Accessibility failures differ:"); } } else { if (!actual.values().stream().allMatch(IllegalAccessException.class::isInstance)) { - Assert.fail("Expecting an IllegalArgumentException for all failures " + actual); + fail("Expecting an IllegalArgumentException for all failures " + actual); } } } diff --git a/test/jdk/java/lang/invoke/unreflect/UnreflectTest.java b/test/jdk/java/lang/invoke/unreflect/UnreflectTest.java index 31dc851f4ea..b034d63a223 100644 --- a/test/jdk/java/lang/invoke/unreflect/UnreflectTest.java +++ b/test/jdk/java/lang/invoke/unreflect/UnreflectTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,9 +24,10 @@ /* * @test * @bug 8238358 8247444 - * @run testng/othervm UnreflectTest * @summary Test Lookup::unreflectSetter and Lookup::unreflectVarHandle on * trusted final fields (declared in hidden classes and records) + * @run junit/othervm --enable-final-field-mutation=ALL-UNNAMED -DwriteAccess=true UnreflectTest + * @run junit/othervm --illegal-final-field-mutation=deny -DwriteAccess=false UnreflectTest */ import java.io.IOException; @@ -38,25 +39,25 @@ import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; -import org.testng.annotations.Test; -import static org.testng.Assert.*; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.BeforeAll; +import static org.junit.jupiter.api.Assertions.*; -public class UnreflectTest { - static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); - static final Class hiddenClass = defineHiddenClass(); - private static Class defineHiddenClass() { +class UnreflectTest { + static Class hiddenClass; + static boolean writeAccess; + + @BeforeAll + static void setup() throws Exception { String classes = System.getProperty("test.classes"); - Path cf = Paths.get(classes, "Fields.class"); - try { - byte[] bytes = Files.readAllBytes(cf); - return MethodHandles.lookup().defineHiddenClass(bytes, true).lookupClass(); - } catch (IOException e) { - throw new UncheckedIOException(e); - } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } + Path cf = Path.of(classes, "Fields.class"); + byte[] bytes = Files.readAllBytes(cf); + hiddenClass = MethodHandles.lookup().defineHiddenClass(bytes, true).lookupClass(); + + String s = System.getProperty("writeAccess"); + assertNotNull(s); + writeAccess = Boolean.valueOf(s); } /* @@ -64,7 +65,7 @@ public class UnreflectTest { * can write the value of a non-static final field in a normal class */ @Test - public void testFieldsInNormalClass() throws Throwable { + void testFieldsInNormalClass() throws Throwable { // despite the name "HiddenClass", this class is loaded by the // class loader as non-hidden class Class c = Fields.class; @@ -72,7 +73,11 @@ public class UnreflectTest { assertFalse(c.isHidden()); readOnlyAccessibleObject(c, "STATIC_FINAL", null, true); readWriteAccessibleObject(c, "STATIC_NON_FINAL", null, false); - readWriteAccessibleObject(c, "FINAL", o, true); + if (writeAccess) { + readWriteAccessibleObject(c, "FINAL", o, true); + } else { + readOnlyAccessibleObject(c, "FINAL", o, true); + } readWriteAccessibleObject(c, "NON_FINAL", o, false); } @@ -81,7 +86,7 @@ public class UnreflectTest { * has NO write the value of a non-static final field in a hidden class */ @Test - public void testFieldsInHiddenClass() throws Throwable { + void testFieldsInHiddenClass() throws Throwable { assertTrue(hiddenClass.isHidden()); Object o = hiddenClass.newInstance(); readOnlyAccessibleObject(hiddenClass, "STATIC_FINAL", null, true); @@ -99,7 +104,8 @@ public class UnreflectTest { * Test Lookup::unreflectSetter and Lookup::unreflectVarHandle that * cannot write the value of a non-static final field in a record class */ - public void testFieldsInRecordClass() throws Throwable { + @Test + void testFieldsInRecordClass() throws Throwable { assertTrue(TestRecord.class.isRecord()); Object o = new TestRecord(1); readOnlyAccessibleObject(TestRecord.class, "STATIC_FINAL", null, true); @@ -121,16 +127,12 @@ public class UnreflectTest { assertTrue(f.trySetAccessible()); // Field object with read-only access - MethodHandle mh = LOOKUP.unreflectGetter(f); + MethodHandles.Lookup lookup = MethodHandles.lookup(); + MethodHandle mh = lookup.unreflectGetter(f); Object value = Modifier.isStatic(modifier) ? mh.invoke() : mh.invoke(o); assertTrue(value == f.get(o)); - try { - LOOKUP.unreflectSetter(f); - assertTrue(false, "should fail to unreflect a setter for " + name); - } catch (IllegalAccessException e) { - } - - VarHandle vh = LOOKUP.unreflectVarHandle(f); + assertThrows(IllegalAccessException.class, () -> lookup.unreflectSetter(f)); + VarHandle vh = lookup.unreflectVarHandle(f); if (isFinal) { assertFalse(vh.isAccessModeSupported(VarHandle.AccessMode.SET)); } else { @@ -163,7 +165,7 @@ public class UnreflectTest { throw e; } - VarHandle vh = LOOKUP.unreflectVarHandle(f); + VarHandle vh = MethodHandles.lookup().unreflectVarHandle(f); if (isFinal) { assertFalse(vh.isAccessModeSupported(VarHandle.AccessMode.SET)); } else { diff --git a/test/jdk/java/lang/reflect/AccessibleObject/HiddenClassTest.java b/test/jdk/java/lang/reflect/AccessibleObject/HiddenClassTest.java index 273523bee65..6fc03934666 100644 --- a/test/jdk/java/lang/reflect/AccessibleObject/HiddenClassTest.java +++ b/test/jdk/java/lang/reflect/AccessibleObject/HiddenClassTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2025, 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,9 +23,9 @@ /** * @test - * @build Fields HiddenClassTest - * @run testng/othervm HiddenClassTest * @summary Test java.lang.reflect.AccessibleObject with modules + * @run junit/othervm --enable-final-field-mutation=ALL-UNNAMED -DwriteAccess=true HiddenClassTest + * @run junit/othervm --illegal-final-field-mutation=deny -DwriteAccess=false HiddenClassTest */ import java.io.IOException; @@ -37,22 +37,24 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import org.testng.annotations.Test; -import static org.testng.Assert.*; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.BeforeAll; +import static org.junit.jupiter.api.Assertions.*; -public class HiddenClassTest { - static final Class hiddenClass = defineHiddenClass(); - private static Class defineHiddenClass() { +class HiddenClassTest { + static Class hiddenClass; + static boolean writeAccess; + + @BeforeAll + static void setup() throws Exception { String classes = System.getProperty("test.classes"); - Path cf = Paths.get(classes, "Fields.class"); - try { - byte[] bytes = Files.readAllBytes(cf); - return MethodHandles.lookup().defineHiddenClass(bytes, true).lookupClass(); - } catch (IOException e) { - throw new UncheckedIOException(e); - } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } + Path cf = Path.of(classes, "Fields.class"); + byte[] bytes = Files.readAllBytes(cf); + hiddenClass = MethodHandles.lookup().defineHiddenClass(bytes, true).lookupClass(); + + String s = System.getProperty("writeAccess"); + assertNotNull(s); + writeAccess = Boolean.valueOf(s); } /* @@ -60,7 +62,7 @@ public class HiddenClassTest { * in a normal class */ @Test - public void testFieldsInNormalClass() throws Throwable { + void testFieldsInNormalClass() throws Throwable { // despite the name "HiddenClass", this class is loaded by the // class loader as non-hidden class Class c = Fields.class; @@ -68,7 +70,11 @@ public class HiddenClassTest { assertFalse(c.isHidden()); readOnlyAccessibleObject(c, "STATIC_FINAL", null, true); readWriteAccessibleObject(c, "STATIC_NON_FINAL", null, false); - readWriteAccessibleObject(c, "FINAL", o, true); + if (writeAccess) { + readWriteAccessibleObject(c, "FINAL", o, true); + } else { + readOnlyAccessibleObject(c, "FINAL", o, true); + } readWriteAccessibleObject(c, "NON_FINAL", o, false); } @@ -77,7 +83,7 @@ public class HiddenClassTest { * in a hidden class */ @Test - public void testFieldsInHiddenClass() throws Throwable { + void testFieldsInHiddenClass() throws Throwable { assertTrue(hiddenClass.isHidden()); Object o = hiddenClass.newInstance(); readOnlyAccessibleObject(hiddenClass, "STATIC_FINAL", null, true); @@ -96,11 +102,7 @@ public class HiddenClassTest { } assertTrue(f.trySetAccessible()); assertTrue(f.get(o) != null); - try { - f.set(o, null); - assertTrue(false, "should fail to set " + name); - } catch (IllegalAccessException e) { - } + assertThrows(IllegalAccessException.class, () -> f.set(o, null)); } private static void readWriteAccessibleObject(Class c, String name, Object o, boolean isFinal) throws Exception { @@ -113,10 +115,6 @@ public class HiddenClassTest { } assertTrue(f.trySetAccessible()); assertTrue(f.get(o) != null); - try { - f.set(o, null); - } catch (IllegalAccessException e) { - throw e; - } + f.set(o, null); } } diff --git a/test/jdk/java/lang/reflect/Field/NegativeTest.java b/test/jdk/java/lang/reflect/Field/NegativeTest.java index 874686e55b1..fe1f2dd7480 100644 --- a/test/jdk/java/lang/reflect/Field/NegativeTest.java +++ b/test/jdk/java/lang/reflect/Field/NegativeTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,9 +24,9 @@ /** * @test * @bug 8277451 - * @run testng NegativeTest * @summary Test exception thrown due to bad receiver and bad value on * Field with and without setAccessible(true) + * @run testng/othervm --enable-final-field-mutation=ALL-UNNAMED NegativeTest */ import java.lang.reflect.Field; diff --git a/test/jdk/java/lang/reflect/Field/Set.java b/test/jdk/java/lang/reflect/Field/Set.java index 6232026cee3..b369ea07d45 100644 --- a/test/jdk/java/lang/reflect/Field/Set.java +++ b/test/jdk/java/lang/reflect/Field/Set.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2004, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2025, 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,6 +26,7 @@ * @bug 4250960 5044412 * @summary Should not be able to set final fields through reflection unless setAccessible(true) passes and is not static * @author David Bowen (modified by Doug Lea) + * @run main/othervm --enable-final-field-mutation=ALL-UNNAMED Set */ import java.lang.reflect.*; diff --git a/test/jdk/java/lang/reflect/Field/mutateFinals/FinalFieldMutationEventTest.java b/test/jdk/java/lang/reflect/Field/mutateFinals/FinalFieldMutationEventTest.java new file mode 100644 index 00000000000..ea1a28221fb --- /dev/null +++ b/test/jdk/java/lang/reflect/Field/mutateFinals/FinalFieldMutationEventTest.java @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8353835 + * @summary Basic test for JFR FinalFieldMutation event + * @requires vm.hasJFR + * @modules jdk.jfr/jdk.jfr.events + * @run junit FinalFieldMutationEventTest + * @run junit/othervm --illegal-final-field-mutation=allow FinalFieldMutationEventTest + * @run junit/othervm --illegal-final-field-mutation=warn FinalFieldMutationEventTest + * @run junit/othervm --illegal-final-field-mutation=debug FinalFieldMutationEventTest + * @run junit/othervm --illegal-final-field-mutation=deny FinalFieldMutationEventTest + */ + +import java.lang.annotation.Annotation; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.nio.file.Path; +import java.util.List; +import java.util.stream.Stream; +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordedClass; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordedMethod; +import jdk.jfr.consumer.RecordingFile; +import jdk.jfr.events.FinalFieldMutationEvent; +import jdk.jfr.events.StackFilter; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +class FinalFieldMutationEventTest { + private static final String EVENT_NAME = "jdk.FinalFieldMutation"; + + /** + * Test class with final field. + */ + private static class C { + final int value; + C(int value) { + this.value = value; + } + } + + /** + * Test jdk.FinalFieldMutation is recorded when mutating a final field. + */ + @Test + void testFieldSet() throws Exception { + Field field = C.class.getDeclaredField("value"); + field.setAccessible(true); + + try (Recording recording = new Recording()) { + recording.enable(EVENT_NAME).withStackTrace(); + + boolean mutated = false; + recording.start(); + try { + var obj = new C(100); + try { + field.setInt(obj, 200); + mutated = true; + } catch (IllegalAccessException e) { + // denied + } + } finally { + recording.stop(); + } + + // FinalFieldMutation event should be recorded if field mutated + List events = find(recording, EVENT_NAME); + System.err.println(events); + if (mutated) { + assertEquals(1, events.size(), "1 event expected"); + checkEvent(events.get(0), field, "FinalFieldMutationEventTest::testFieldSet"); + } else { + assertEquals(0, events.size(), "No events expected"); + } + } + } + + /** + * Test jdk.FinalFieldMutation is recorded when unreflecting a field field for mutation. + */ + @Test + void testUnreflectSetter() throws Exception { + Field field = C.class.getDeclaredField("value"); + field.setAccessible(true); + + try (Recording recording = new Recording()) { + recording.enable(EVENT_NAME).withStackTrace(); + + boolean unreflected = false; + recording.start(); + try { + MethodHandles.lookup().unreflectSetter(field); + unreflected = true; + } catch (IllegalAccessException e) { + // denied + } finally { + recording.stop(); + } + + // FinalFieldMutation event should be recorded if field unreflected for set + List events = find(recording, EVENT_NAME); + System.err.println(events); + if (unreflected) { + assertEquals(1, events.size(), "1 event expected"); + checkEvent(events.get(0), field, "FinalFieldMutationEventTest::testUnreflectSetter"); + } else { + assertEquals(0, events.size(), "No events expected"); + } + } + } + + /** + * Test that a FinalFieldMutationEvent event has the declaringClass and fieldName of + * the given Field, and the expected top frame. + */ + private void checkEvent(RecordedEvent e, Field f, String expectedTopFrame) { + RecordedClass clazz = e.getClass("declaringClass"); + assertNotNull(clazz); + assertEquals(f.getDeclaringClass().getName(), clazz.getName()); + assertEquals(f.getName(), e.getString("fieldName")); + + // check the top-frame of the stack trace + RecordedMethod m = e.getStackTrace().getFrames().getFirst().getMethod(); + assertEquals(expectedTopFrame, m.getType().getName() + "::" + m.getName()); + } + + /** + * Tests that FinalFieldMutationEvent's stack filter value names classes/methods that + * exist. This will help detect stale values when the implementation is refactored. + */ + @Test + void testFinalFieldMutationEventStackFilter() throws Exception { + String[] filters = FinalFieldMutationEvent.class.getAnnotation(StackFilter.class).value(); + for (String filter : filters) { + String[] classAndMethod = filter.split("::"); + String cn = classAndMethod[0]; + + // throws if class not found + Class clazz = Class.forName(cn); + + // if the filter has a method name then check a method of that name exists + if (classAndMethod.length > 1) { + String mn = classAndMethod[1]; + Method method = Stream.of(clazz.getDeclaredMethods()) + .filter(m -> m.getName().equals(mn)) + .findFirst() + .orElse(null); + assertNotNull(method, cn + "::" + mn + " not found"); + } + } + } + + /** + * Returns the list of events in the given recording with the given name. + */ + private List find(Recording recording, String name) throws Exception { + Path recordingFile = recordingFile(recording); + return RecordingFile.readAllEvents(recordingFile) + .stream() + .filter(e -> e.getEventType().getName().equals(name)) + .toList(); + } + + /** + * Return the file path to the recording file. + */ + private Path recordingFile(Recording recording) throws Exception { + Path recordingFile = recording.getDestination(); + if (recordingFile == null) { + ProcessHandle h = ProcessHandle.current(); + recordingFile = Path.of("recording-" + recording.getId() + "-pid" + h.pid() + ".jfr"); + recording.dump(recordingFile); + } + return recordingFile; + } +} diff --git a/test/jdk/java/lang/reflect/Field/mutateFinals/MutateFinalsTest.java b/test/jdk/java/lang/reflect/Field/mutateFinals/MutateFinalsTest.java new file mode 100644 index 00000000000..78a7849dfc0 --- /dev/null +++ b/test/jdk/java/lang/reflect/Field/mutateFinals/MutateFinalsTest.java @@ -0,0 +1,358 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8353835 + * @summary Test Field.set and Lookup.unreflectSetter on final instance fields + * @run junit/othervm -DwriteAccess=true MutateFinalsTest + * @run junit/othervm --enable-final-field-mutation=ALL-UNNAMED -DwriteAccess=true MutateFinalsTest + * @run junit/othervm --illegal-final-field-mutation=allow -DwriteAccess=true MutateFinalsTest + * @run junit/othervm --illegal-final-field-mutation=warn -DwriteAccess=true MutateFinalsTest + * @run junit/othervm --illegal-final-field-mutation=debug -DwriteAccess=true MutateFinalsTest + * @run junit/othervm --illegal-final-field-mutation=deny -DwriteAccess=false MutateFinalsTest + */ + +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Field; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +class MutateFinalsTest { + static boolean writeAccess; + + @BeforeAll + static void setup() throws Exception { + String s = System.getProperty("writeAccess"); + assertNotNull(s); + writeAccess = Boolean.valueOf(s); + } + + @Test + void testFieldSet() throws Exception { + class C { + final String value; + C(String value) { + this.value = value; + } + } + Field f = C.class.getDeclaredField("value"); + + String oldValue = "oldValue"; + String newValue = "newValue"; + var obj = new C(oldValue); + + f.setAccessible(false); + assertThrows(NullPointerException.class, () -> f.set(null, newValue)); + assertThrows(IllegalAccessException.class, () -> f.set(obj, newValue)); + assertTrue(obj.value == oldValue); + + f.setAccessible(true); + assertThrows(NullPointerException.class, () -> f.set(null, newValue)); + if (writeAccess) { + assertThrows(IllegalArgumentException.class, () -> f.set("not a C", newValue)); + assertThrows(IllegalArgumentException.class, () -> f.set(obj, 100)); // not a string + f.set(obj, newValue); + assertTrue(obj.value == newValue); + } else { + assertThrows(IllegalAccessException.class, () -> f.set(obj, newValue)); + assertTrue(obj.value == oldValue); + } + } + + @Test + void testFieldSetBoolean() throws Throwable { + class C { + final boolean value; + C(boolean value) { + this.value = value; + } + } + Field f = C.class.getDeclaredField("value"); + + boolean oldValue = false; + boolean newValue = true; + var obj = new C(oldValue); + + f.setAccessible(false); + assertThrows(NullPointerException.class, () -> f.setBoolean(null, newValue)); + assertThrows(IllegalAccessException.class, () -> f.setBoolean(obj, newValue)); + assertTrue(obj.value == oldValue); + + f.setAccessible(true); + assertThrows(NullPointerException.class, () -> f.setBoolean(null, newValue)); + if (writeAccess) { + assertThrows(IllegalArgumentException.class, () -> f.setBoolean("not a C", newValue)); + f.setBoolean(obj, newValue); + assertTrue(obj.value == newValue); + } else { + assertThrows(IllegalAccessException.class, () -> f.setBoolean(obj, newValue)); + assertTrue(obj.value == oldValue); + } + } + + @Test + void testFieldSetByte() throws Exception { + class C { + final byte value; + C(byte value) { + this.value = value; + } + } + Field f = C.class.getDeclaredField("value"); + + byte oldValue = (byte) 1; + byte newValue = (byte) 2; + var obj = new C(oldValue); + + f.setAccessible(false); + assertThrows(NullPointerException.class, () -> f.setByte(null, newValue)); + assertThrows(IllegalAccessException.class, () -> f.setByte(obj, newValue)); + assertTrue(obj.value == oldValue); + + f.setAccessible(true); + assertThrows(NullPointerException.class, () -> f.setByte(null, newValue)); + if (writeAccess) { + assertThrows(IllegalArgumentException.class, () -> f.setByte("not a C", newValue)); + f.setByte(obj, newValue); + assertTrue(obj.value == newValue); + } else { + assertThrows(IllegalAccessException.class, () -> f.setByte(obj, newValue)); + assertTrue(obj.value == oldValue); + } + } + + @Test + void testFieldSetChar() throws Exception { + class C { + final char value; + C(char value) { + this.value = value; + } + } + Field f = C.class.getDeclaredField("value"); + + char oldValue = 'A'; + char newValue = 'B'; + var obj = new C(oldValue); + + f.setAccessible(false); + assertThrows(NullPointerException.class, () -> f.setChar(null, newValue)); + assertThrows(IllegalAccessException.class, () -> f.setChar(obj, newValue)); + assertTrue(obj.value == oldValue); + + f.setAccessible(true); + assertThrows(NullPointerException.class, () -> f.setChar(null, newValue)); + if (writeAccess) { + assertThrows(IllegalArgumentException.class, () -> f.setChar("not a C", newValue)); + f.setChar(obj, newValue); + assertTrue(obj.value == newValue); + } else { + assertThrows(IllegalAccessException.class, () -> f.setChar(obj, newValue)); + assertTrue(obj.value == oldValue); + } + } + + @Test + void testFieldSetShort() throws Exception { + class C { + final short value; + C(short value) { + this.value = value; + } + } + Field f = C.class.getDeclaredField("value"); + + short oldValue = (short) 1; + short newValue = (short) 2; + var obj = new C(oldValue); + + f.setAccessible(false); + assertThrows(NullPointerException.class, () -> f.setShort(null, newValue)); + assertThrows(IllegalAccessException.class, () -> f.setShort(obj, newValue)); + assertTrue(obj.value == oldValue); + + f.setAccessible(true); + assertThrows(NullPointerException.class, () -> f.setShort(null, newValue)); + if (writeAccess) { + assertThrows(IllegalArgumentException.class, () -> f.setShort("not a C", newValue)); + f.setShort(obj, newValue); + assertTrue(obj.value == newValue); + } else { + assertThrows(IllegalAccessException.class, () -> f.setShort(obj, newValue)); + assertTrue(obj.value == oldValue); + } + } + + @Test + void testFieldSetInt() throws Exception { + class C { + final int value; + C(int value) { + this.value = value; + } + } + Field f = C.class.getDeclaredField("value"); + + int oldValue = 1; + int newValue = 2; + var obj = new C(oldValue); + + f.setAccessible(false); + assertThrows(NullPointerException.class, () -> f.setInt(null, newValue)); + assertThrows(IllegalAccessException.class, () -> f.setInt(obj, newValue)); + assertTrue(obj.value == oldValue); + + f.setAccessible(true); + assertThrows(NullPointerException.class, () -> f.setInt(null, newValue)); + if (writeAccess) { + assertThrows(IllegalArgumentException.class, () -> f.setInt("not a C", newValue)); + f.setInt(obj, newValue); + assertTrue(obj.value == newValue); + } else { + assertThrows(IllegalAccessException.class, () -> f.setInt(obj, newValue)); + assertTrue(obj.value == oldValue); + } + } + + @Test + void testFieldSetLong() throws Exception { + class C { + final long value; + C(long value) { + this.value = value; + } + } + Field f = C.class.getDeclaredField("value"); + + long oldValue = 1L; + long newValue = 2L; + var obj = new C(oldValue); + + f.setAccessible(false); + assertThrows(NullPointerException.class, () -> f.setLong(null, newValue)); + assertThrows(IllegalAccessException.class, () -> f.setLong(obj, newValue)); + assertTrue(obj.value == oldValue); + + f.setAccessible(true); + assertThrows(NullPointerException.class, () -> f.setLong(null, newValue)); + if (writeAccess) { + assertThrows(IllegalArgumentException.class, () -> f.setLong("not a C", newValue)); + f.setLong(obj, newValue); + assertTrue(obj.value == newValue); + } else { + assertThrows(IllegalAccessException.class, () -> f.setLong(obj, newValue)); + assertTrue(obj.value == oldValue); + } + } + + @Test + void testFieldSetFloat() throws Exception { + class C { + final float value; + C(float value) { + this.value = value; + } + } + Field f = C.class.getDeclaredField("value"); + + float oldValue = 1.0f; + float newValue = 2.0f; + var obj = new C(oldValue); + + f.setAccessible(false); + assertThrows(NullPointerException.class, () -> f.setFloat(null, newValue)); + assertThrows(IllegalAccessException.class, () -> f.setFloat(obj, newValue)); + assertTrue(obj.value == oldValue); + + f.setAccessible(true); + assertThrows(NullPointerException.class, () -> f.setFloat(null, newValue)); + if (writeAccess) { + assertThrows(IllegalArgumentException.class, () -> f.setFloat("not a C", newValue)); + f.setFloat(obj, newValue); + assertTrue(obj.value == newValue); + } else { + assertThrows(IllegalAccessException.class, () -> f.setFloat(obj, newValue)); + assertTrue(obj.value == oldValue); + } + } + + @Test + void testFieldSetDouble() throws Exception { + class C { + final double value; + C(double value) { + this.value = value; + } + } + Field f = C.class.getDeclaredField("value"); + + double oldValue = 1.0d; + double newValue = 2.0d; + var obj = new C(oldValue); + + f.setAccessible(false); + assertThrows(NullPointerException.class, () -> f.setDouble(null, newValue)); + assertThrows(IllegalAccessException.class, () -> f.setDouble(obj, newValue)); + assertTrue(obj.value == oldValue); + + f.setAccessible(true); + assertThrows(NullPointerException.class, () -> f.setDouble(null, newValue)); + if (writeAccess) { + assertThrows(IllegalArgumentException.class, () -> f.setDouble("not a C", newValue)); + f.setDouble(obj, newValue); + assertTrue(obj.value == newValue); + } else { + assertThrows(IllegalAccessException.class, () -> f.setDouble(obj, newValue)); + assertTrue(obj.value == oldValue); + } + } + + @Test + void testUnreflectSetter() throws Throwable { + class C { + final Object value; + C(Object value) { + this.value = value; + } + } + Field f = C.class.getDeclaredField("value"); + + Object oldValue = new Object(); + var obj = new C(oldValue); + + f.setAccessible(false); + assertThrows(IllegalAccessException.class, () -> MethodHandles.lookup().unreflectSetter(f)); + + f.setAccessible(true); + if (writeAccess) { + Object newValue = new Object(); + MethodHandles.lookup().unreflectSetter(f).invoke(obj, newValue); + assertTrue(obj.value == newValue); + } else { + assertThrows(IllegalAccessException.class, () -> MethodHandles.lookup().unreflectSetter(f)); + } + } +} diff --git a/test/jdk/java/lang/reflect/Field/mutateFinals/cli/CommandLineTest.java b/test/jdk/java/lang/reflect/Field/mutateFinals/cli/CommandLineTest.java new file mode 100644 index 00000000000..34f9bb2bd5b --- /dev/null +++ b/test/jdk/java/lang/reflect/Field/mutateFinals/cli/CommandLineTest.java @@ -0,0 +1,294 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8353835 + * @summary Test the command line option --enable-final-field-mutation + * @library /test/lib + * @build CommandLineTestHelper + * @run junit CommandLineTest + */ + +import java.util.regex.Pattern; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import static org.junit.jupiter.api.Assertions.*; + +import jdk.test.lib.process.ProcessTools; +import jdk.test.lib.process.OutputAnalyzer; + +class CommandLineTest { + + // helper class name + private static final String HELPER = "CommandLineTestHelper"; + + // warning output + private static final String WARNING_LINE1 = + "WARNING: Final field value in class " + HELPER; + private static final String WARNING_LINE3 = + "WARNING: Use --enable-final-field-mutation=ALL-UNNAMED to avoid a warning"; + private static final String WARNING_LINE4 = + "WARNING: Mutating final fields will be blocked in a future release unless final field mutation is enabled"; + + // warning line 2 depends on the method + private static final String WARNING_MUTATED = + " has been mutated reflectively by class " + HELPER + " in unnamed module"; + private static final String WARNING_UNREFLECTED = + " has been unreflected for mutation by class " + HELPER + " in unnamed module"; + + /** + * Test that a warning is printed by default. + */ + @Test + void testDefault() throws Exception { + test("testFieldSetInt") + .shouldContain(WARNING_LINE1) + .shouldContain(WARNING_MUTATED) + .shouldContain(WARNING_LINE3) + .shouldContain(WARNING_LINE4) + .shouldHaveExitValue(0); + + test("testUnreflectSetter") + .shouldContain(WARNING_LINE1) + .shouldContain(WARNING_UNREFLECTED) + .shouldContain(WARNING_LINE3) + .shouldContain(WARNING_LINE4) + .shouldHaveExitValue(0); + } + + /** + * Test allow mutation of finals. + */ + @Test + void testAllow() throws Exception { + test("testFieldSetInt", "--illegal-final-field-mutation=allow") + .shouldNotContain(WARNING_LINE1) + .shouldNotContain(WARNING_MUTATED) + .shouldHaveExitValue(0); + + test("testFieldSetInt", "--enable-final-field-mutation=ALL-UNNAMED") + .shouldNotContain(WARNING_LINE1) + .shouldNotContain(WARNING_MUTATED) + .shouldHaveExitValue(0); + + // allow ALL-UNNAMED, deny by default + test("testFieldSetInt", "--enable-final-field-mutation=ALL-UNNAMED", "--illegal-final-field-mutation=deny") + .shouldNotContain(WARNING_LINE1) + .shouldNotContain(WARNING_MUTATED) + .shouldHaveExitValue(0); + + test("testUnreflectSetter", "--illegal-final-field-mutation=allow") + .shouldNotContain(WARNING_LINE1) + .shouldNotContain(WARNING_UNREFLECTED) + .shouldHaveExitValue(0); + + test("testUnreflectSetter", "--enable-final-field-mutation=ALL-UNNAMED") + .shouldNotContain(WARNING_LINE1) + .shouldNotContain(WARNING_UNREFLECTED) + .shouldHaveExitValue(0); + + // allow ALL-UNNAMED, deny by default + test("testUnreflectSetter", "--enable-final-field-mutation=ALL-UNNAMED", "--illegal-final-field-mutation=deny") + .shouldNotContain(WARNING_LINE1) + .shouldNotContain(WARNING_UNREFLECTED) + .shouldHaveExitValue(0); + } + + /** + * Test warn on first mutation or unreflect of a final field. + */ + @Test + void testWarn() throws Exception { + test("testFieldSetInt", "--illegal-final-field-mutation=warn") + .shouldContain(WARNING_LINE1) + .shouldContain(WARNING_MUTATED) + .shouldContain(WARNING_LINE3) + .shouldContain(WARNING_LINE4) + .shouldHaveExitValue(0); + + test("testUnreflectSetter", "--illegal-final-field-mutation=warn") + .shouldContain(WARNING_LINE1) + .shouldContain(WARNING_UNREFLECTED) + .shouldContain(WARNING_LINE3) + .shouldContain(WARNING_LINE4) + .shouldHaveExitValue(0); + + // should be one warning only, for Field.set + var output = test("testFieldSetInt+testUnreflectSetter", "--illegal-final-field-mutation=warn") + .shouldContain(WARNING_MUTATED) + .shouldNotContain(WARNING_UNREFLECTED) + .shouldHaveExitValue(0) + .getOutput(); + assertEquals(1, countStrings(output, WARNING_LINE1)); + assertEquals(1, countStrings(output, WARNING_LINE3)); + assertEquals(1, countStrings(output, WARNING_LINE4)); + + // should be one warning only, for Lookup.unreflectSetter + output = test("testUnreflectSetter+testFieldSetInt", "--illegal-final-field-mutation=warn") + .shouldNotContain(WARNING_MUTATED) + .shouldContain(WARNING_UNREFLECTED) + .shouldHaveExitValue(0) + .getOutput(); + assertEquals(1, countStrings(output, WARNING_LINE1)); + assertEquals(1, countStrings(output, WARNING_LINE3)); + assertEquals(1, countStrings(output, WARNING_LINE4)); + } + + /** + * Test debug mode. + */ + @Test + void testDebug() throws Exception { + test("testFieldSetInt+testUnreflectSetter", "--illegal-final-field-mutation=debug") + .shouldContain("Final field value in class " + HELPER) + .shouldContain(WARNING_MUTATED) + .shouldContain("java.lang.reflect.Field.setInt") + .shouldContain(WARNING_UNREFLECTED) + .shouldContain("java.lang.invoke.MethodHandles$Lookup.unreflectSetter") + .shouldHaveExitValue(0); + + test("testUnreflectSetter+testFieldSetInt", "--illegal-final-field-mutation=debug") + .shouldContain("Final field value in class " + HELPER) + .shouldContain(WARNING_UNREFLECTED) + .shouldContain("java.lang.invoke.MethodHandles$Lookup.unreflectSetter") + .shouldContain(WARNING_MUTATED) + .shouldContain("java.lang.reflect.Field.setInt") + .shouldHaveExitValue(0); + } + + /** + * Test deny mutation of finals. + */ + @Test + void testDeny() throws Exception { + test("testFieldSetInt", "--illegal-final-field-mutation=deny") + .shouldNotContain(WARNING_LINE1) + .shouldNotContain(WARNING_MUTATED) + .shouldContain("java.lang.IllegalAccessException") + .shouldNotHaveExitValue(0); + + test("testUnreflectSetter", "--illegal-final-field-mutation=deny") + .shouldNotContain(WARNING_LINE1) + .shouldNotContain(WARNING_UNREFLECTED) + .shouldContain("java.lang.IllegalAccessException") + .shouldNotHaveExitValue(0); + } + + /** + * Test last usage of --illegal-final-field-mutation "wins". + */ + @Test + void testLastOneWins() throws Exception { + test("testFieldSetInt", "--illegal-final-field-mutation=allow", "--illegal-final-field-mutation=deny") + .shouldNotContain(WARNING_LINE1) + .shouldNotContain(WARNING_MUTATED) + .shouldContain("java.lang.IllegalAccessException") + .shouldNotHaveExitValue(0); + + test("testFieldSetInt", "--illegal-final-field-mutation=deny", "--illegal-final-field-mutation=warn") + .shouldContain(WARNING_LINE1) + .shouldContain(WARNING_MUTATED) + .shouldHaveExitValue(0); + } + + /** + * Test --illegal-final-field-mutation with bad values. + */ + @ParameterizedTest + @ValueSource(strings = { "", "bad" }) + void testInvalidValues(String value) throws Exception { + test("testFieldSetInt", "--illegal-final-field-mutation=" + value) + .shouldContain("Value specified to --illegal-final-field-mutation not recognized") + .shouldNotHaveExitValue(0); + } + + /** + * Test setting the internal system properties (that correspond to the command line + * options) on the commannd line. They should be ignored. + */ + @Test + void testSetPropertyOnCommandLine() throws Exception { + // --enable-final-field-mutation=ALL-UNNAMED + test("testFieldSetInt", "-Djdk.module.enable.final.field.mutation.0=ALL-UNNAMED") + .shouldContain(WARNING_LINE1) + .shouldContain(WARNING_MUTATED) + .shouldContain(WARNING_LINE3) + .shouldContain(WARNING_LINE4) + .shouldHaveExitValue(0); + + // --illegal-final-field-mutation=allow + test("testFieldSetInt", "-Djdk.module.illegal.final.field.mutation=allow") + .shouldContain(WARNING_LINE1) + .shouldContain(WARNING_MUTATED) + .shouldContain(WARNING_LINE3) + .shouldContain(WARNING_LINE4) + .shouldHaveExitValue(0); + } + + /** + * Test setting the internal system properties (that correspond to the command line + * options) at runtime. They should be ignored. + */ + @Test + void testSetPropertyAtRuntime() throws Exception { + // --enable-final-field-mutation=ALL-UNNAMED + test("setPropertyIllegalFinalFieldMutationAllow+testFieldSetInt") + .shouldContain(WARNING_LINE1) + .shouldContain(WARNING_MUTATED) + .shouldContain(WARNING_LINE3) + .shouldContain(WARNING_LINE4) + .shouldHaveExitValue(0); + + // --illegal-final-field-mutation=allow + test("setPropertyEnableFinalFieldMutationAllUnnamed+testFieldSetInt") + .shouldContain(WARNING_LINE1) + .shouldContain(WARNING_MUTATED) + .shouldContain(WARNING_LINE3) + .shouldContain(WARNING_LINE4) + .shouldHaveExitValue(0); + } + + /** + * Launch helper with the given arguments and VM options. + */ + private OutputAnalyzer test(String action, String... vmopts) throws Exception { + Stream s1 = Stream.of(vmopts); + Stream s2 = Stream.of(HELPER, action); + String[] opts = Stream.concat(s1, s2).toArray(String[]::new); + var outputAnalyzer = ProcessTools + .executeTestJava(opts) + .outputTo(System.err) + .errorTo(System.err); + return outputAnalyzer; + } + + /** + * Counts the number of substrings in the given input string. + */ + private int countStrings(String input, String substring) { + return input.split(Pattern.quote(substring)).length - 1; + } +} diff --git a/test/jdk/java/lang/reflect/Field/mutateFinals/cli/CommandLineTestHelper.java b/test/jdk/java/lang/reflect/Field/mutateFinals/cli/CommandLineTestHelper.java new file mode 100644 index 00000000000..cbda9987c5d --- /dev/null +++ b/test/jdk/java/lang/reflect/Field/mutateFinals/cli/CommandLineTestHelper.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2025, 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 java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.invoke.MethodHandles; + +public class CommandLineTestHelper { + + /** + * The argument is a list of names of no-arg static methods in this class to invoke. + * The names are separated with a '+'. + */ + public static void main(String[] args) throws Exception { + String[] methodNames = args.length > 0 ? args[0].split("\\+") : new String[0]; + for (String methodName : methodNames) { + Method m = CommandLineTestHelper.class.getDeclaredMethod(methodName); + m.invoke(null); + } + } + + static void testFieldSetInt() throws Exception { + class C { + final int value; + C(int value) { + this.value = value; + } + } + Field f = C.class.getDeclaredField("value"); + f.setAccessible(true); + var obj = new C(100); + f.setInt(obj, 200); + if (obj.value != 200) { + throw new RuntimeException("Unexpected value: " + obj.value); + } + } + + static void testUnreflectSetter() throws Throwable { + class C { + final int value; + C(int value) { + this.value = value; + } + } + Field f = C.class.getDeclaredField("value"); + f.setAccessible(true); + var obj = new C(100); + MethodHandles.lookup().unreflectSetter(f).invoke(obj, 200); + if (obj.value != 200) { + throw new RuntimeException("Unexpected value: " + obj.value); + } + } + + /** + * Set the internal system property that corresponds to the first usage of + * --enable-final-field-mutation. + */ + static void setPropertyEnableFinalFieldMutationAllUnnamed() { + System.setProperty("jdk.module.enable.final.field.mutation.0", "ALL-UNNAMED"); + } + + /** + * Set the internal system property that corresponds to --illegal-final-field-mutation. + */ + static void setPropertyIllegalFinalFieldMutationAllow() { + System.setProperty("jdk.module.illegal.final.field.mutation", "allow"); + } +} diff --git a/test/jdk/java/lang/reflect/Field/mutateFinals/jar/ExecutableJarTest.java b/test/jdk/java/lang/reflect/Field/mutateFinals/jar/ExecutableJarTest.java new file mode 100644 index 00000000000..a9bdae67cfd --- /dev/null +++ b/test/jdk/java/lang/reflect/Field/mutateFinals/jar/ExecutableJarTest.java @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8353835 + * @summary Test the executable JAR file attribute Enable-Final-Field-Mutation + * @library /test/lib + * @build m/* + * @build ExecutableJarTestHelper jdk.test.lib.util.JarUtils + * @run junit ExecutableJarTest + */ + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; +import java.util.stream.Stream; +import java.util.jar.Attributes; +import java.util.jar.Manifest; + +import jdk.test.lib.process.ProcessTools; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.util.JarUtils; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import static org.junit.jupiter.api.Assertions.*; + +class ExecutableJarTest { + + // helper class name + private static final String HELPER = "ExecutableJarTestHelper"; + + // warning output + private static final String WARNING_LINE1 = + "WARNING: Final field value in class " + HELPER; + + // warning line 2 depends on the method + private static final String WARNING_MUTATED = + " has been mutated reflectively by class " + HELPER + " in unnamed module"; + private static final String WARNING_UNREFLECTED = + " has been unreflected for mutation by class " + HELPER + " in unnamed module"; + + /** + * Test executable JAR with code that uses Field.set to mutate a final field. + * A warning should be printed. + */ + @Test + void testFieldSetExpectingWarning() throws Exception { + String jarFile = createExecutableJar(Map.of()); + testExecutableJar(jarFile, "testFieldSetInt") + .shouldContain(WARNING_LINE1) + .shouldContain(WARNING_MUTATED) + .shouldHaveExitValue(0); + } + + /** + * Test executable JAR with code that uses Lookup.unreflectSetter to get MH to a + * final field. A warning should be printed. + */ + @Test + void testUnreflectExpectingWarning() throws Exception { + String jarFile = createExecutableJar(Map.of()); + testExecutableJar(jarFile, "testUnreflectSetter") + .shouldContain(WARNING_LINE1) + .shouldContain(WARNING_UNREFLECTED) + .shouldHaveExitValue(0); + } + + /** + * Test executable JAR with Enable-Final-Field-Mutation attribute and code that uses + * Field.set to mutate a final field. No warning should be printed. + */ + @Test + void testFieldSetExpectingAllow() throws Exception { + String jarFile = createExecutableJar(Map.of("Enable-Final-Field-Mutation", "ALL-UNNAMED")); + testExecutableJar(jarFile, "testFieldSetInt") + .shouldNotContain(WARNING_LINE1) + .shouldNotContain(WARNING_MUTATED) + .shouldHaveExitValue(0); + } + + /** + * Test executable JAR with Enable-Final-Field-Mutation attribute and code that uses + * Lookup.unreflectSetter to get MH to a final field. No warning should be printed. + */ + @Test + void testUnreflectExpectingAllow() throws Exception { + String jarFile = createExecutableJar(Map.of("Enable-Final-Field-Mutation", "ALL-UNNAMED")); + testExecutableJar(jarFile, "testUnreflectSetter") + .shouldNotContain(WARNING_LINE1) + .shouldNotContain(WARNING_UNREFLECTED) + .shouldHaveExitValue(0); + } + + /** + * Test executable JAR with Enable-Final-Field-Mutation attribute and code that uses + * Field.set to mutate a final field of class in a named module. The package is opened + * with --add-open. + */ + @Test + void testFieldSetWithAddOpens1() throws Exception { + String jarFile = createExecutableJar(Map.of( + "Enable-Final-Field-Mutation", "ALL-UNNAMED")); + testExecutableJar(jarFile, "testFieldInNamedModule", + "--illegal-final-field-mutation=deny", + "--module-path", modulePath(), + "--add-modules", "m", + "--add-opens", "m/p=ALL-UNNAMED") + .shouldHaveExitValue(0); + } + + /** + * Test executable JAR with Enable-Final-Field-Mutation attribute and code that uses + * Field.set to mutate a final field of class in a named module. The package is opened + * with with the Add-Opens attribute. + */ + @Test + void testFieldSetWithAddOpens2() throws Exception { + String jarFile = createExecutableJar(Map.of( + "Enable-Final-Field-Mutation", "ALL-UNNAMED", + "Add-Opens", "m/p")); + testExecutableJar(jarFile, "testFieldInNamedModule", + "--illegal-final-field-mutation=deny", + "--module-path", modulePath(), + "--add-modules", "m") + .shouldHaveExitValue(0); + } + + /** + * Test executable JAR with Enable-Final-Field-Mutation with that a value that is not + * "ALL-UNNAMED". + */ + @ParameterizedTest + @ValueSource(strings = {"java.base", "BadValue", " ", ""}) + void testFinalFieldMutationBadValue(String value) throws Exception { + String jarFile = createExecutableJar(Map.of("Enable-Final-Field-Mutation", value)); + testExecutableJar(jarFile, "testFieldSetInt") + .shouldContain("Error: illegal value \"" + value + "\" for Enable-Final-Field-Mutation" + + " manifest attribute. Only ALL-UNNAMED is allowed") + .shouldNotHaveExitValue(0); + } + + /** + * Launch ExecutableJarTestHelper with the given arguments and VM options. + */ + private OutputAnalyzer test(String action, String... vmopts) throws Exception { + Stream s1 = Stream.of(vmopts); + Stream s2 = Stream.of("ExecutableJarTestHelper", action); + String[] opts = Stream.concat(s1, s2).toArray(String[]::new); + var outputAnalyzer = ProcessTools + .executeTestJava(opts) + .outputTo(System.err) + .errorTo(System.err); + return outputAnalyzer; + } + + /** + * Launch ExecutableJarTestHelper with the given arguments and VM options. + */ + private OutputAnalyzer testExecutableJar(String jarFile, + String action, + String... vmopts) throws Exception { + Stream s1 = Stream.of(vmopts); + Stream s2 = Stream.of("-jar", jarFile, action); + String[] opts = Stream.concat(s1, s2).toArray(String[]::new); + var outputAnalyzer = ProcessTools + .executeTestJava(opts) + .outputTo(System.err) + .errorTo(System.err); + return outputAnalyzer; + } + + /** + * Creates executable JAR named helper.jar with ExecutableJarTestHelper* classes. + */ + private String createExecutableJar(Map map) throws Exception { + Path jarFile = Path.of("helper.jar"); + var man = new Manifest(); + Attributes attrs = man.getMainAttributes(); + attrs.put(Attributes.Name.MANIFEST_VERSION, "1.0"); + attrs.put(Attributes.Name.MAIN_CLASS, "ExecutableJarTestHelper"); + map.entrySet().forEach(e -> { + var name = new Attributes.Name(e.getKey()); + attrs.put(name, e.getValue()); + }); + Path dir = Path.of(System.getProperty("test.classes")); + try (Stream stream = Files.list(dir)) { + Path[] files = Files.list(dir).filter(p -> { + String fn = p.getFileName().toString(); + return fn.startsWith("ExecutableJarTestHelper") && fn.endsWith(".class"); + }) + .toArray(Path[]::new); + JarUtils.createJarFile(jarFile, man, dir, files); + } + return jarFile.toString(); + } + + /** + * Return the module path for the modules used by this test. + */ + private String modulePath() { + return Path.of(System.getProperty("test.classes"), "modules").toString(); + } +} diff --git a/test/jdk/java/lang/reflect/Field/mutateFinals/jar/ExecutableJarTestHelper.java b/test/jdk/java/lang/reflect/Field/mutateFinals/jar/ExecutableJarTestHelper.java new file mode 100644 index 00000000000..e0f7c60f637 --- /dev/null +++ b/test/jdk/java/lang/reflect/Field/mutateFinals/jar/ExecutableJarTestHelper.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2025, 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 java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.invoke.MethodHandles; + +public class ExecutableJarTestHelper { + + /** + * The argument is a list of names of no-arg static methods in this class to invoke. + * The names are separated with a '+'. + */ + public static void main(String[] args) throws Exception { + String[] methodNames = args.length > 0 ? args[0].split("\\+") : new String[0]; + for (String methodName : methodNames) { + Method m = ExecutableJarTestHelper.class.getDeclaredMethod(methodName); + m.invoke(null); + } + } + + /** + * Uses Field.set to mutate a final field. + */ + static void testFieldSetInt() throws Exception { + class C { + final int value; + C(int value) { + this.value = value; + } + } + Field f = C.class.getDeclaredField("value"); + f.setAccessible(true); + var obj = new C(100); + f.setInt(obj, 200); + if (obj.value != 200) { + throw new RuntimeException("Unexpected value: " + obj.value); + } + } + + /** + * Uses Lookup.unreflectSetter to get a method handle to set a final field. + */ + static void testUnreflectSetter() throws Throwable { + class C { + final int value; + C(int value) { + this.value = value; + } + } + Field f = C.class.getDeclaredField("value"); + f.setAccessible(true); + var obj = new C(100); + MethodHandles.lookup().unreflectSetter(f).invoke(obj, 200); + if (obj.value != 200) { + throw new RuntimeException("Unexpected value: " + obj.value); + } + } + + /** + * Uses Field.set to mutate a final field of a class in a named module. + */ + static void testFieldInNamedModule() throws Exception { + Class c = Class.forName("p.C"); + if (!c.getModule().isNamed()) { + throw new RuntimeException(c + " is not in a named module"); + } + Object obj = c.getDeclaredConstructor(int.class).newInstance(100); + Field f = c.getDeclaredField("value"); + f.setAccessible(true); + f.setInt(obj, 200); + int newValue = f.getInt(obj); + if (newValue != 200) { + throw new RuntimeException("Unexpected value: " + newValue); + } + } +} diff --git a/test/jdk/java/lang/reflect/Field/mutateFinals/jar/m/module-info.java b/test/jdk/java/lang/reflect/Field/mutateFinals/jar/m/module-info.java new file mode 100644 index 00000000000..197a2101894 --- /dev/null +++ b/test/jdk/java/lang/reflect/Field/mutateFinals/jar/m/module-info.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2025, 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. + */ + +/** + * Module containing a class with a final field used by ExecutableJarTest. + */ +module m { + exports p; +} diff --git a/test/jdk/java/lang/reflect/Field/mutateFinals/jar/m/p/C.java b/test/jdk/java/lang/reflect/Field/mutateFinals/jar/m/p/C.java new file mode 100644 index 00000000000..e128330ab5e --- /dev/null +++ b/test/jdk/java/lang/reflect/Field/mutateFinals/jar/m/p/C.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2025, 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 p; + +/** + * A class with a final field used by ExecutableJarTest. + */ +public class C { + private final int value; + + public C(int value) { + this.value = value; + } +} diff --git a/test/jdk/java/lang/reflect/Field/mutateFinals/jni/JNIAttachMutator.java b/test/jdk/java/lang/reflect/Field/mutateFinals/jni/JNIAttachMutator.java new file mode 100644 index 00000000000..9af4370c652 --- /dev/null +++ b/test/jdk/java/lang/reflect/Field/mutateFinals/jni/JNIAttachMutator.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2025, 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 java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.util.concurrent.CountDownLatch; + +/** + * Launched by JNIAttachMutatorTest to test a JNI attached thread attempting to mutate + * a final field. + */ + +public class JNIAttachMutator { + private static final CountDownLatch finished = new CountDownLatch(1); + private static volatile Object obj; + private static volatile Throwable exc; + + // public class, public final field + public static class C1 { + public final int value; + C1(int value) { + this.value = value; + } + } + + // public class, non-public final field + public static class C2 { + final int value; + C2(int value) { + this.value = value; + } + } + + // non-public class, public final field + static class C3 { + public final int value; + C3(int value) { + this.value = value; + } + } + + /** + * Usage: java JNIAttachMutator + */ + public static void main(String[] args) throws Exception { + String cn = args[0]; + boolean expectIAE = Boolean.parseBoolean(args[1]); + + Class clazz = Class.forName(args[0]); + Constructor ctor = clazz.getDeclaredConstructor(int.class); + ctor.setAccessible(true); + obj = ctor.newInstance(100); + + // start native thread + startThread(); + + // wait for native thread to finish + finished.await(); + + if (expectIAE) { + if (exc == null) { + // IAE expected + throw new RuntimeException("IllegalAccessException not thrown"); + } else if (!(exc instanceof IllegalAccessException)) { + // unexpected exception + throw new RuntimeException(exc); + } + } else if (exc != null) { + // no exception expected + throw new RuntimeException(exc); + } + } + + /** + * Invoked by JNI attached thread to get object. + */ + static Object getObject() { + return obj; + } + + /** + * Invoked by JNI attached thread to get Field object with accessible enabled. + */ + static Field getField() throws NoSuchFieldException { + Field f = obj.getClass().getDeclaredField("value"); + f.setAccessible(true); + return f; + } + + /** + * Invoked by JNI attached thread when finished. + */ + static void finish(Throwable ex) { + exc = ex; + finished.countDown(); + } + + private static native void startThread(); + + static { + System.loadLibrary("JNIAttachMutator"); + } +} diff --git a/test/jdk/java/lang/reflect/Field/mutateFinals/jni/JNIAttachMutatorTest.java b/test/jdk/java/lang/reflect/Field/mutateFinals/jni/JNIAttachMutatorTest.java new file mode 100644 index 00000000000..19f5e21085f --- /dev/null +++ b/test/jdk/java/lang/reflect/Field/mutateFinals/jni/JNIAttachMutatorTest.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8353835 + * @summary Test native thread attaching to the VM with JNI AttachCurrentThread and directly + * invoking Field.set to set a final field + * @library /test/lib + * @build m/* + * @compile JNIAttachMutator.java + * @run junit JNIAttachMutatorTest + */ + +import java.nio.file.Path; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; +import jdk.test.lib.process.ProcessTools; +import jdk.test.lib.process.OutputAnalyzer; + +class JNIAttachMutatorTest { + private static String testClasses; + private static String modulesDir; + private static String javaLibraryPath; + + @BeforeAll + static void setup() { + testClasses = System.getProperty("test.classes"); + modulesDir = Path.of(testClasses, "modules").toString(); + javaLibraryPath = System.getProperty("java.library.path"); + } + + /** + * Final final mutation allowed. All final fields are public, in public classes, + * and in packages exported to all modules. + */ + @ParameterizedTest + @ValueSource(strings = { + "JNIAttachMutator$C1", // unnamed module + "p.C1", // named module + }) + void testAllowed(String cn) throws Exception { + test(cn, false); + } + + /** + * Final final mutation not allowed. + */ + @ParameterizedTest + @ValueSource(strings = { + // unnamed module + "JNIAttachMutator$C2", // public class, non-public final field + "JNIAttachMutator$C3", // non-public class, public final field + + // named module + "p.C2", // public class, non-public final field, exported package + "p.C3", // non-public class, public final field, exported package + "q.C" // public class, public final field, package not exported + }) + void testDenied(String cn) throws Exception { + test(cn, true); + } + + /** + * public final field, public class, package exported to some modules. + */ + @Test + void testQualifiedExports() throws Exception { + test("q.C", true, "--add-exports", "m/q=ALL-UNNAMED"); + } + + /** + * Launches JNIAttachMutator to test a JNI attached thread mutating a final field. + * @param className the class with the field final + * @param expectIAE if IllegalAccessException is expected + * @param extraOps additional VM options + */ + private void test(String className, boolean expectIAE, String... extraOps) throws Exception { + Stream s1 = Stream.of(extraOps); + Stream s2 = Stream.of( + "-cp", testClasses, + "-Djava.library.path=" + javaLibraryPath, + "--module-path", modulesDir, + "--add-modules", "m", + "--add-opens", "m/p=ALL-UNNAMED", // allow setAccessible + "--add-opens", "m/q=ALL-UNNAMED", + "--enable-native-access=ALL-UNNAMED", + "--enable-final-field-mutation=ALL-UNNAMED", + "--illegal-final-field-mutation=deny", + "JNIAttachMutator", + className, + expectIAE ? "true" : "false"); + String[] opts = Stream.concat(s1, s2).toArray(String[]::new); + OutputAnalyzer outputAnalyzer = ProcessTools + .executeTestJava(opts) + .outputTo(System.out) + .errorTo(System.out); + outputAnalyzer.shouldHaveExitValue(0); + } +} diff --git a/test/jdk/java/lang/reflect/Field/mutateFinals/jni/libJNIAttachMutator.c b/test/jdk/java/lang/reflect/Field/mutateFinals/jni/libJNIAttachMutator.c new file mode 100644 index 00000000000..077b9f5d625 --- /dev/null +++ b/test/jdk/java/lang/reflect/Field/mutateFinals/jni/libJNIAttachMutator.c @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +#include +#ifdef _WIN32 +#include +#else +#include +#endif +#include "jni.h" + +#define STACK_SIZE 0x100000 + +static JavaVM *vm; + +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void* reserved) { + vm = jvm; + return JNI_VERSION_1_8; +} + +/** + * Invokes JNIAttachMutator.getObject() + */ +jobject getObject(JNIEnv* env) { + jclass clazz = (*env)->FindClass(env, "JNIAttachMutator"); + if (clazz == NULL) { + fprintf(stderr, "FindClass failed\n"); + return NULL; + } + jmethodID mid = (*env)->GetStaticMethodID(env, clazz, "getObject", "()Ljava/lang/Object;"); + if (mid == NULL) { + fprintf(stderr, "GetMethodID for getObject failed\n"); + return NULL; + } + jobject obj = (*env)->CallStaticObjectMethod(env, clazz, mid); + if (obj == NULL) { + fprintf(stderr, "CallObjectMethod to getObject failed\n"); + return NULL; + } + return obj; +} + +/** + * Invokes JNIAttachMutator.getField() + */ +jobject getField(JNIEnv* env) { + jclass clazz = (*env)->FindClass(env, "JNIAttachMutator"); + if (clazz == NULL) { + fprintf(stderr, "FindClass failed\n"); + return NULL; + } + jmethodID mid = (*env)->GetStaticMethodID(env, clazz, "getField", "()Ljava/lang/reflect/Field;"); + if (mid == NULL) { + fprintf(stderr, "GetStaticMethodID for getField failed\n"); + return NULL; + } + jobject obj = (*env)->CallStaticObjectMethod(env, clazz, mid); + if (obj == NULL) { + fprintf(stderr, "CallObjectMethod to getField failed\n"); + return NULL; + } + return obj; +} + +/** + * Invokes Field.setInt + */ +jboolean setInt(JNIEnv* env, jobject obj, jobject fieldObj, jint newValue) { + jclass fieldClass = (*env)->GetObjectClass(env, fieldObj); + jmethodID mid = (*env)->GetMethodID(env, fieldClass, "setInt", "(Ljava/lang/Object;I)V"); + if (mid == NULL) { + fprintf(stderr, "GetMethodID for Field.setInt failed\n"); + return JNI_FALSE; + } + (*env)->CallObjectMethod(env, fieldObj, mid, obj, newValue); + return JNI_TRUE; +} + +/** + * Invokes JNIAttachMutator.finish + */ +void finish(JNIEnv* env, jthrowable ex) { + jclass clazz = (*env)->FindClass(env, "JNIAttachMutator"); + if (clazz == NULL) { + fprintf(stderr, "FindClass failed\n"); + return; + } + + // invoke finish + jmethodID mid = (*env)->GetStaticMethodID(env, clazz, "finish", "(Ljava/lang/Throwable;)V"); + if (mid == NULL) { + fprintf(stderr, "GetStaticMethodID failed\n"); + return; + } + (*env)->CallStaticVoidMethod(env, clazz, mid, ex); + if ((*env)->ExceptionOccurred(env)) { + fprintf(stderr, "CallStaticVoidMethod failed\n"); + } +} + +/** + * Attach the current thread with JNI AttachCurrentThread. + */ +void* thread_main(void* arg) { + JNIEnv *env; + jint res; + jthrowable ex; + + res = (*vm)->AttachCurrentThread(vm, (void **) &env, NULL); + if (res != JNI_OK) { + fprintf(stderr, "AttachCurrentThread failed: %d\n", res); + return NULL; + } + + // invoke JNIAttachMutator.getObject to get the object to test + jobject obj = getObject(env); + if (obj == NULL) { + goto done; + } + + // invoke JNIAttachMutator.getField to get the Field object with access enabled + jobject fieldObj = getField(env); + if (fieldObj == NULL) { + goto done; + } + + // invoke Field.setInt to attempt to set the value to 200 + if (!setInt(env, obj, fieldObj, 200)) { + goto done; + } + + done: + + ex = (*env)->ExceptionOccurred(env); + if (ex != NULL) { + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + } + finish(env, ex); + + res = (*vm)->DetachCurrentThread(vm); + if (res != JNI_OK) { + fprintf(stderr, "DetachCurrentThread failed: %d\n", res); + } + + return NULL; +} + +#ifdef _WIN32 +static DWORD WINAPI win32_thread_main(void* p) { + thread_main(p); + return 0; +} +#endif + +JNIEXPORT void JNICALL Java_JNIAttachMutator_startThread(JNIEnv *env, jclass clazz) { +#ifdef _WIN32 + HANDLE handle = CreateThread(NULL, STACK_SIZE, win32_thread_main, NULL, 0, NULL); + if (handle == NULL) { + fprintf(stderr, "CreateThread failed: %d\n", GetLastError()); + } +#else + pthread_t tid; + pthread_attr_t attr; + + pthread_attr_init(&attr); + pthread_attr_setstacksize(&attr, STACK_SIZE); + int res = pthread_create(&tid, &attr, thread_main, NULL); + if (res != 0) { + fprintf(stderr, "pthread_create failed: %d\n", res); + } +#endif +} diff --git a/test/jdk/java/lang/reflect/Field/mutateFinals/jni/m/module-info.java b/test/jdk/java/lang/reflect/Field/mutateFinals/jni/m/module-info.java new file mode 100644 index 00000000000..bb113e926df --- /dev/null +++ b/test/jdk/java/lang/reflect/Field/mutateFinals/jni/m/module-info.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2025, 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. + */ + +/** + * Module containing classes with final fields. + */ +module m { + exports p; +} diff --git a/test/jdk/java/lang/reflect/Field/mutateFinals/jni/m/p/C1.java b/test/jdk/java/lang/reflect/Field/mutateFinals/jni/m/p/C1.java new file mode 100644 index 00000000000..8d931f2eb47 --- /dev/null +++ b/test/jdk/java/lang/reflect/Field/mutateFinals/jni/m/p/C1.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2025, 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 p; + +/** + * public class, public final field. + */ +public class C1 { + public final int value; + + C1(int value) { + this.value = value; + } +} diff --git a/test/jdk/java/lang/reflect/Field/mutateFinals/jni/m/p/C2.java b/test/jdk/java/lang/reflect/Field/mutateFinals/jni/m/p/C2.java new file mode 100644 index 00000000000..186d8e82575 --- /dev/null +++ b/test/jdk/java/lang/reflect/Field/mutateFinals/jni/m/p/C2.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2025, 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 p; + +/** + * public class, non-public final field. + */ +public class C2 { + final int value; + + C2(int value) { + this.value = value; + } +} diff --git a/test/jdk/java/lang/reflect/Field/mutateFinals/jni/m/p/C3.java b/test/jdk/java/lang/reflect/Field/mutateFinals/jni/m/p/C3.java new file mode 100644 index 00000000000..56b90490abf --- /dev/null +++ b/test/jdk/java/lang/reflect/Field/mutateFinals/jni/m/p/C3.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2025, 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 p; + +/** + * non-public class, public final field. + */ +class C3 { + public final int value; + + C3(int value) { + this.value = value; + } +} diff --git a/test/jdk/java/lang/reflect/Field/mutateFinals/jni/m/q/C.java b/test/jdk/java/lang/reflect/Field/mutateFinals/jni/m/q/C.java new file mode 100644 index 00000000000..1411547347e --- /dev/null +++ b/test/jdk/java/lang/reflect/Field/mutateFinals/jni/m/q/C.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2025, 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 q; + +/** + * public class, public final field. + */ +public class C { + public final int value; + + public C(int value) { + this.value = value; + } +} diff --git a/test/jdk/java/lang/reflect/Field/mutateFinals/modules/Driver.java b/test/jdk/java/lang/reflect/Field/mutateFinals/modules/Driver.java new file mode 100644 index 00000000000..f0b87b09304 --- /dev/null +++ b/test/jdk/java/lang/reflect/Field/mutateFinals/modules/Driver.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8353835 + * @summary Test mutating final fields in module test from code in modules test, m1, m2 and m3 + * @build test/* m1/* m2/* m3/* + * @run junit/othervm --illegal-final-field-mutation=deny --enable-final-field-mutation=test,m1,m2,m3 + * test/test.TestMain + * @run junit/othervm --illegal-final-field-mutation=deny --enable-final-field-mutation=test,m1,m2,m3 + * --add-exports test/test.fieldholders=m3 test/test.TestMain + * @run junit/othervm --illegal-final-field-mutation=deny --enable-final-field-mutation=test,m1,m2,m3 + * --add-opens test/test.fieldholders=m2 test/test.TestMain + */ diff --git a/test/jdk/java/lang/reflect/Field/mutateFinals/modules/m1/module-info.java b/test/jdk/java/lang/reflect/Field/mutateFinals/modules/m1/module-info.java new file mode 100644 index 00000000000..c7b4fbdc424 --- /dev/null +++ b/test/jdk/java/lang/reflect/Field/mutateFinals/modules/m1/module-info.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2025, 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. + */ +@SuppressWarnings("module") +module m1 { + requires test; + provides test.spi.Mutator with p1.M1Mutator; +} diff --git a/test/jdk/java/lang/reflect/Field/mutateFinals/modules/m1/p1/M1Mutator.java b/test/jdk/java/lang/reflect/Field/mutateFinals/modules/m1/p1/M1Mutator.java new file mode 100644 index 00000000000..4fed0c97692 --- /dev/null +++ b/test/jdk/java/lang/reflect/Field/mutateFinals/modules/m1/p1/M1Mutator.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2025, 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 p1; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Field; + +public class M1Mutator implements test.spi.Mutator { + + @Override + public void set(Field f, Object obj, Object value) throws IllegalAccessException { + f.set(obj, value); + } + + @Override + public void setBoolean(Field f, Object obj, boolean value) throws IllegalAccessException { + f.setBoolean(obj, value); + } + + @Override + public void setByte(Field f, Object obj, byte value) throws IllegalAccessException { + f.setByte(obj, value); + } + + @Override + public void setChar(Field f, Object obj, char value) throws IllegalAccessException { + f.setChar(obj, value); + } + + @Override + public void setShort(Field f, Object obj, short value) throws IllegalAccessException { + f.setShort(obj, value); + } + + @Override + public void setInt(Field f, Object obj, int value) throws IllegalAccessException { + f.setInt(obj, value); + } + + @Override + public void setLong(Field f, Object obj, long value) throws IllegalAccessException { + f.setLong(obj, value); + } + + @Override + public void setFloat(Field f, Object obj, float value) throws IllegalAccessException { + f.setFloat(obj, value); + } + + @Override + public void setDouble(Field f, Object obj, double value) throws IllegalAccessException { + f.setDouble(obj, value); + } + + @Override + public MethodHandle unreflectSetter(Field f) throws IllegalAccessException { + return MethodHandles.lookup().unreflectSetter(f); + } + +} diff --git a/test/jdk/java/lang/reflect/Field/mutateFinals/modules/m2/module-info.java b/test/jdk/java/lang/reflect/Field/mutateFinals/modules/m2/module-info.java new file mode 100644 index 00000000000..3c48cb6dfc7 --- /dev/null +++ b/test/jdk/java/lang/reflect/Field/mutateFinals/modules/m2/module-info.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2025, 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. + */ +@SuppressWarnings("module") +module m2 { + requires test; + provides test.spi.Mutator with p2.M2Mutator; +} diff --git a/test/jdk/java/lang/reflect/Field/mutateFinals/modules/m2/p2/M2Mutator.java b/test/jdk/java/lang/reflect/Field/mutateFinals/modules/m2/p2/M2Mutator.java new file mode 100644 index 00000000000..a3f2252e20c --- /dev/null +++ b/test/jdk/java/lang/reflect/Field/mutateFinals/modules/m2/p2/M2Mutator.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2025, 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 p2; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Field; + +public class M2Mutator implements test.spi.Mutator { + + @Override + public void set(Field f, Object obj, Object value) throws IllegalAccessException { + f.set(obj, value); + } + + @Override + public void setBoolean(Field f, Object obj, boolean value) throws IllegalAccessException { + f.setBoolean(obj, value); + } + + @Override + public void setByte(Field f, Object obj, byte value) throws IllegalAccessException { + f.setByte(obj, value); + } + + @Override + public void setChar(Field f, Object obj, char value) throws IllegalAccessException { + f.setChar(obj, value); + } + + @Override + public void setShort(Field f, Object obj, short value) throws IllegalAccessException { + f.setShort(obj, value); + } + + @Override + public void setInt(Field f, Object obj, int value) throws IllegalAccessException { + f.setInt(obj, value); + } + + @Override + public void setLong(Field f, Object obj, long value) throws IllegalAccessException { + f.setLong(obj, value); + } + + @Override + public void setFloat(Field f, Object obj, float value) throws IllegalAccessException { + f.setFloat(obj, value); + } + + @Override + public void setDouble(Field f, Object obj, double value) throws IllegalAccessException { + f.setDouble(obj, value); + } + + @Override + public MethodHandle unreflectSetter(Field f) throws IllegalAccessException { + return MethodHandles.lookup().unreflectSetter(f); + } + +} diff --git a/test/jdk/java/lang/reflect/Field/mutateFinals/modules/m3/module-info.java b/test/jdk/java/lang/reflect/Field/mutateFinals/modules/m3/module-info.java new file mode 100644 index 00000000000..8e378882a42 --- /dev/null +++ b/test/jdk/java/lang/reflect/Field/mutateFinals/modules/m3/module-info.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2025, 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. + */ +@SuppressWarnings("module") +module m3 { + requires test; + provides test.spi.Mutator with p3.M3Mutator; +} diff --git a/test/jdk/java/lang/reflect/Field/mutateFinals/modules/m3/p3/M3Mutator.java b/test/jdk/java/lang/reflect/Field/mutateFinals/modules/m3/p3/M3Mutator.java new file mode 100644 index 00000000000..c432b8d8f07 --- /dev/null +++ b/test/jdk/java/lang/reflect/Field/mutateFinals/modules/m3/p3/M3Mutator.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2025, 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 p3; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Field; + +public class M3Mutator implements test.spi.Mutator { + + @Override + public void set(Field f, Object obj, Object value) throws IllegalAccessException { + f.set(obj, value); + } + + @Override + public void setBoolean(Field f, Object obj, boolean value) throws IllegalAccessException { + f.setBoolean(obj, value); + } + + @Override + public void setByte(Field f, Object obj, byte value) throws IllegalAccessException { + f.setByte(obj, value); + } + + @Override + public void setChar(Field f, Object obj, char value) throws IllegalAccessException { + f.setChar(obj, value); + } + + @Override + public void setShort(Field f, Object obj, short value) throws IllegalAccessException { + f.setShort(obj, value); + } + + @Override + public void setInt(Field f, Object obj, int value) throws IllegalAccessException { + f.setInt(obj, value); + } + + @Override + public void setLong(Field f, Object obj, long value) throws IllegalAccessException { + f.setLong(obj, value); + } + + @Override + public void setFloat(Field f, Object obj, float value) throws IllegalAccessException { + f.setFloat(obj, value); + } + + @Override + public void setDouble(Field f, Object obj, double value) throws IllegalAccessException { + f.setDouble(obj, value); + } + + @Override + public MethodHandle unreflectSetter(Field f) throws IllegalAccessException { + return MethodHandles.lookup().unreflectSetter(f); + } + +} diff --git a/test/jdk/java/lang/reflect/Field/mutateFinals/modules/test/module-info.java b/test/jdk/java/lang/reflect/Field/mutateFinals/modules/test/module-info.java new file mode 100644 index 00000000000..9e41df42f20 --- /dev/null +++ b/test/jdk/java/lang/reflect/Field/mutateFinals/modules/test/module-info.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2025, 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. + */ +module test { + requires org.junit.platform.console.standalone; + opens test to org.junit.platform.console.standalone; + + opens test.fieldholders to m1; + exports test.fieldholders to m2; + + exports test.spi; + uses test.spi.Mutator; + provides test.spi.Mutator with test.internal.TestMutator; +} diff --git a/test/jdk/java/lang/reflect/Field/mutateFinals/modules/test/test/TestMain.java b/test/jdk/java/lang/reflect/Field/mutateFinals/modules/test/test/TestMain.java new file mode 100644 index 00000000000..17ed2d78e0a --- /dev/null +++ b/test/jdk/java/lang/reflect/Field/mutateFinals/modules/test/test/TestMain.java @@ -0,0 +1,556 @@ +/* + * Copyright (c) 2025, 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 test; + +import java.lang.reflect.Field; +import java.util.ServiceLoader; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import test.spi.Mutator; +import test.fieldholders.PublicFields; +import test.fieldholders.PrivateFields; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import static org.junit.jupiter.api.Assertions.*; + +/** + * Test mutating final fields from different modules. + */ +class TestMain { + static Map> exportedToMutators; + static Map> openToMutators; + + // test module and the name of package with the fieldholder classes + static Module testModule; + static String fieldHoldersPackage; + + @BeforeAll + static void setup() throws Exception { + testModule = TestMain.class.getModule(); + fieldHoldersPackage = PublicFields.class.getPackageName(); + + List allMutators = ServiceLoader.load(Mutator.class) + .stream() + .map(ServiceLoader.Provider::get) + .toList(); + + // mutators that test.fieldholders is exported to + exportedToMutators = allMutators.stream() + .collect(Collectors.partitioningBy(m -> testModule.isExported(fieldHoldersPackage, + m.getClass().getModule()), + Collectors.toList())); + + // mutators that test.fieldholders is open to + openToMutators = allMutators.stream() + .collect(Collectors.partitioningBy(m -> testModule.isOpen(fieldHoldersPackage, + m.getClass().getModule()), + Collectors.toList())); + + + // exported to at least test, m1 and m2 + assertTrue(exportedToMutators.get(Boolean.TRUE).size() >= 3); + + // open to at least test and m1 + assertTrue(openToMutators.get(Boolean.TRUE).size() >= 2); + } + + /** + * Returns a stream of mutators that test.fieldholders is exported to. + */ + static Stream exportedToMutators() { + return exportedToMutators.get(Boolean.TRUE).stream(); + } + + /** + * Returns a stream of mutators that test.fieldholders is open to. + */ + static Stream openToMutators() { + return openToMutators.get(Boolean.TRUE).stream(); + } + + /** + * Returns a stream of mutators that test.fieldholders is not exported to. + */ + static Stream notExportedToMutators() { + List mutators = exportedToMutators.get(Boolean.FALSE); + if (mutators.isEmpty()) { + // can't return an empty stream at this time + return Stream.of(Mutator.throwing()); + } else { + return mutators.stream(); + } + } + + /** + * Returns a stream of mutators that test.fieldholders is not open to. + */ + static Stream notOpenToMutators() { + List mutators = openToMutators.get(Boolean.FALSE); + if (mutators.isEmpty()) { + // can't return an empty stream at this time + return Stream.of(Mutator.throwing()); + } else { + return mutators.stream(); + } + } + + // public field, public class in package exported to mutator + + @ParameterizedTest() + @MethodSource("exportedToMutators") + void testFieldSetExportedPackage(Mutator mutator) throws Exception { + Field f = PublicFields.objectField(); + var obj = new PublicFields(); + Object oldValue = obj.objectValue(); + Object newValue = new Object(); + + f.setAccessible(false); + assertThrows(IllegalAccessException.class, () -> mutator.set(f, obj, newValue)); + assertTrue(obj.objectValue() == oldValue); + + f.setAccessible(true); + mutator.set(f, obj, newValue); + assertTrue(obj.objectValue() == newValue); + } + + @ParameterizedTest() + @MethodSource("exportedToMutators") + void testFieldSetBooleanExportedPackage(Mutator mutator) throws Exception { + Field f = PublicFields.booleanField(); + var obj = new PublicFields(); + boolean oldValue = obj.booleanValue(); + boolean newValue = true; + + f.setAccessible(false); + assertThrows(IllegalAccessException.class, () -> mutator.setBoolean(f, obj, newValue)); + assertTrue(obj.booleanValue() == oldValue); + + f.setAccessible(true); + mutator.setBoolean(f, obj, newValue); + assertTrue(obj.booleanValue() == newValue); + } + + @ParameterizedTest() + @MethodSource("exportedToMutators") + void testFieldSetByteExportedPackage(Mutator mutator) throws Exception { + Field f = PublicFields.byteField(); + var obj = new PublicFields(); + byte oldValue = obj.byteValue(); + byte newValue = 10; + + f.setAccessible(false); + assertThrows(IllegalAccessException.class, () -> mutator.setByte(f, obj, newValue)); + assertTrue(obj.byteValue() == oldValue); + + f.setAccessible(true); + mutator.setByte(f, obj, newValue); + assertTrue(obj.byteValue() == newValue); + } + + @ParameterizedTest() + @MethodSource("exportedToMutators") + void testFieldSetCharExportedPackage(Mutator mutator) throws Exception { + Field f = PublicFields.charField(); + var obj = new PublicFields(); + char oldValue = obj.charValue(); + char newValue = 'Z'; + + f.setAccessible(false); + assertThrows(IllegalAccessException.class, () -> mutator.setChar(f, obj, newValue)); + assertTrue(obj.charValue() == oldValue); + + f.setAccessible(true); + mutator.setChar(f, obj, newValue); + assertTrue(obj.charValue() == newValue); + } + + @ParameterizedTest() + @MethodSource("exportedToMutators") + void testFieldSetShortExportedPackage(Mutator mutator) throws Exception { + Field f = PublicFields.shortField(); + var obj = new PublicFields(); + short oldValue = obj.shortValue(); + short newValue = 99; + + f.setAccessible(false); + assertThrows(IllegalAccessException.class, () -> mutator.setShort(f, obj, newValue)); + assertTrue(obj.shortValue() == oldValue); + + f.setAccessible(true); + mutator.setShort(f, obj, newValue); + assertTrue(obj.shortValue() == newValue); + } + + @ParameterizedTest() + @MethodSource("exportedToMutators") + void testFieldSetIntExportedPackage(Mutator mutator) throws Exception { + Field f = PublicFields.intField(); + var obj = new PublicFields(); + int oldValue = obj.intValue(); + int newValue = 999; + + f.setAccessible(false); + assertThrows(IllegalAccessException.class, () -> mutator.setInt(f, obj, newValue)); + assertTrue(obj.intValue() == oldValue); + + f.setAccessible(true); + mutator.setInt(f, obj, newValue); + assertTrue(obj.intValue() == newValue); + } + + @ParameterizedTest() + @MethodSource("exportedToMutators") + void testFieldSetLongExportedPackage(Mutator mutator) throws Exception { + Field f = PublicFields.longField(); + var obj = new PublicFields(); + long oldValue = obj.longValue(); + long newValue = 9999; + + f.setAccessible(false); + assertThrows(IllegalAccessException.class, () -> mutator.setLong(f, obj, newValue)); + assertTrue(obj.longValue() == oldValue); + + f.setAccessible(true); + mutator.setLong(f, obj, newValue); + assertTrue(obj.longValue() == newValue); + } + + @ParameterizedTest() + @MethodSource("exportedToMutators") + void testFieldSetFloatExportedPackage(Mutator mutator) throws Exception { + Field f = PublicFields.floatField(); + var obj = new PublicFields(); + float oldValue = obj.floatValue(); + float newValue = 9.9f; + + f.setAccessible(false); + assertThrows(IllegalAccessException.class, () -> mutator.setFloat(f, obj, newValue)); + assertTrue(obj.floatValue() == oldValue); + + f.setAccessible(true); + mutator.setFloat(f, obj, newValue); + assertTrue(obj.floatValue() == newValue); + } + + @ParameterizedTest() + @MethodSource("exportedToMutators") + void testFieldSetDoublExportedPackage(Mutator mutator) throws Exception { + Field f = PublicFields.doubleField(); + var obj = new PublicFields(); + double oldValue = obj.doubleValue(); + double newValue = 99.9d; + + f.setAccessible(false); + assertThrows(IllegalAccessException.class, () -> mutator.setDouble(f, obj, newValue)); + assertTrue(obj.doubleValue() == oldValue); + + f.setAccessible(true); + mutator.setDouble(f, obj, newValue); + assertTrue(obj.doubleValue() == newValue); + } + + @ParameterizedTest + @MethodSource("exportedToMutators") + void testUnreflectSetterExportedPackage(Mutator mutator) throws Throwable { + Field f = PublicFields.objectField(); + var obj = new PublicFields(); + Object oldValue = obj.objectValue(); + Object newValue = new Object(); + + f.setAccessible(false); + assertThrows(IllegalAccessException.class, () -> mutator.unreflectSetter(f)); + + f.setAccessible(true); + mutator.unreflectSetter(f).invokeExact(obj, newValue); + assertTrue(obj.objectValue() == newValue); + } + + // private field, class in package opened to mutator + + @ParameterizedTest() + @MethodSource("openToMutators") + void testFieldSetOpenPackage(Mutator mutator) throws Exception { + Field f = PrivateFields.objectField(); + var obj = new PrivateFields(); + Object oldValue = obj.objectValue(); + Object newValue = new Object(); + + f.setAccessible(false); + assertThrows(IllegalAccessException.class, () -> mutator.set(f, obj, newValue)); + assertTrue(obj.objectValue() == oldValue); + + f.setAccessible(true); + mutator.set(f, obj, newValue); + assertTrue(obj.objectValue() == newValue); + } + + @ParameterizedTest() + @MethodSource("openToMutators") + void testFieldSetBooleanOpenPackage(Mutator mutator) throws Exception { + Field f = PrivateFields.booleanField(); + var obj = new PrivateFields(); + boolean oldValue = obj.booleanValue(); + boolean newValue = true; + + f.setAccessible(false); + assertThrows(IllegalAccessException.class, () -> mutator.setBoolean(f, obj, newValue)); + assertTrue(obj.booleanValue() == oldValue); + + f.setAccessible(true); + mutator.setBoolean(f, obj, newValue); + assertTrue(obj.booleanValue() == newValue); + } + + @ParameterizedTest() + @MethodSource("openToMutators") + void testFieldSetByteOpenPackage(Mutator mutator) throws Exception { + Field f = PrivateFields.byteField(); + var obj = new PrivateFields(); + byte oldValue = obj.byteValue(); + byte newValue = 10; + + f.setAccessible(false); + assertThrows(IllegalAccessException.class, () -> mutator.setByte(f, obj, newValue)); + assertTrue(obj.byteValue() == oldValue); + + f.setAccessible(true); + mutator.setByte(f, obj, newValue); + assertTrue(obj.byteValue() == newValue); + } + + @ParameterizedTest() + @MethodSource("openToMutators") + void testFieldSetCharOpenPackage(Mutator mutator) throws Exception { + Field f = PrivateFields.charField(); + var obj = new PrivateFields(); + char oldValue = obj.charValue(); + char newValue = 'Z'; + + f.setAccessible(false); + assertThrows(IllegalAccessException.class, () -> mutator.setChar(f, obj, newValue)); + assertTrue(obj.charValue() == oldValue); + + f.setAccessible(true); + mutator.setChar(f, obj, newValue); + assertTrue(obj.charValue() == newValue); + } + + @ParameterizedTest() + @MethodSource("openToMutators") + void testFieldSetShortOpenPackage(Mutator mutator) throws Exception { + Field f = PrivateFields.shortField(); + var obj = new PrivateFields(); + short oldValue = obj.shortValue(); + short newValue = 'Z'; + + f.setAccessible(false); + assertThrows(IllegalAccessException.class, () -> mutator.setShort(f, obj, newValue)); + assertTrue(obj.shortValue() == oldValue); + + f.setAccessible(true); + mutator.setShort(f, obj, newValue); + assertTrue(obj.shortValue() == newValue); + } + + @ParameterizedTest() + @MethodSource("openToMutators") + void testFieldSetIntOpenPackage(Mutator mutator) throws Exception { + Field f = PrivateFields.intField(); + var obj = new PrivateFields(); + int oldValue = obj.intValue(); + int newValue = 99; + + f.setAccessible(false); + assertThrows(IllegalAccessException.class, () -> mutator.setInt(f, obj, newValue)); + assertTrue(obj.intValue() == oldValue); + + f.setAccessible(true); + mutator.setInt(f, obj, newValue); + assertTrue(obj.intValue() == newValue); + } + + @ParameterizedTest() + @MethodSource("openToMutators") + void testFieldSetLongOpenPackage(Mutator mutator) throws Exception { + Field f = PrivateFields.longField(); + var obj = new PrivateFields(); + long oldValue = obj.longValue(); + long newValue = 999; + + f.setAccessible(false); + assertThrows(IllegalAccessException.class, () -> mutator.setLong(f, obj, newValue)); + assertTrue(obj.longValue() == oldValue); + + f.setAccessible(true); + mutator.setLong(f, obj, newValue); + assertTrue(obj.longValue() == newValue); + } + + @ParameterizedTest() + @MethodSource("openToMutators") + void testFieldSetFloatOpenPackage(Mutator mutator) throws Exception { + Field f = PrivateFields.floatField(); + var obj = new PrivateFields(); + float oldValue = obj.floatValue(); + float newValue = 9.9f; + + f.setAccessible(false); + assertThrows(IllegalAccessException.class, () -> mutator.setFloat(f, obj, newValue)); + assertTrue(obj.floatValue() == oldValue); + + f.setAccessible(true); + mutator.setFloat(f, obj, newValue); + assertTrue(obj.floatValue() == newValue); + } + + @ParameterizedTest() + @MethodSource("openToMutators") + void testFieldSetDoubleOpenPackage(Mutator mutator) throws Exception { + Field f = PrivateFields.doubleField(); + var obj = new PrivateFields(); + double oldValue = obj.doubleValue(); + double newValue = 99.9d; + + f.setAccessible(false); + assertThrows(IllegalAccessException.class, () -> mutator.setDouble(f, obj, newValue)); + assertTrue(obj.doubleValue() == oldValue); + + f.setAccessible(true); + mutator.setDouble(f, obj, newValue); + assertTrue(obj.doubleValue() == newValue); + } + + @ParameterizedTest + @MethodSource("openToMutators") + void testUnreflectSetterOpenPackage(Mutator mutator) throws Throwable { + Field f = PrivateFields.objectField(); + var obj = new PrivateFields(); + Object oldValue = obj.objectValue(); + Object newValue = new Object(); + + f.setAccessible(false); + assertThrows(IllegalAccessException.class, () -> mutator.unreflectSetter(f)); + + f.setAccessible(true); + mutator.unreflectSetter(f).invokeExact(obj, newValue); + assertTrue(obj.objectValue() == newValue); + } + + // public field, public class in package not exported to mutator + + @ParameterizedTest + @MethodSource("notExportedToMutators") + void testFieldSetNotExportedPackage(Mutator mutator) throws Exception { + Field f = PublicFields.objectField(); + var obj = new PublicFields(); + Object oldValue = obj.objectValue(); + Object newValue = new Object(); + + f.setAccessible(true); + var e1 = assertThrows(IllegalAccessException.class, () -> mutator.set(f, obj, newValue)); + Module mutatorModule = mutator.getClass().getModule(); + if (mutatorModule != testModule) { + assertTrue(e1.getMessage().contains("module " + testModule.getName() + + " does not explicitly \"exports\" package " + + f.getDeclaringClass().getPackageName() + + " to module " + mutatorModule.getName())); + } + assertTrue(obj.objectValue() == oldValue); + + // export package to mutator module, should have no effect on set method + testModule.addExports(fieldHoldersPackage, mutator.getClass().getModule()); + var e2 = assertThrows(IllegalAccessException.class, () -> mutator.set(f, obj, newValue)); + assertEquals(e1.getMessage(), e2.getMessage()); + assertTrue(obj.objectValue() == oldValue); + + // open package to mutator module, should have no effect on set method + testModule.addOpens(fieldHoldersPackage, mutator.getClass().getModule()); + var e3 = assertThrows(IllegalAccessException.class, () -> mutator.set(f, obj, newValue)); + assertEquals(e1.getMessage(), e3.getMessage()); + assertTrue(obj.objectValue() == oldValue); + } + + @ParameterizedTest + @MethodSource("notExportedToMutators") + void testUnreflectSetterNotExportedPackage(Mutator mutator) throws Exception { + Field f = PublicFields.objectField(); + + f.setAccessible(true); + assertThrows(IllegalAccessException.class, () -> mutator.unreflectSetter(f)); + + // export package to mutator module, should have no effect on unreflectSetter method + testModule.addExports(fieldHoldersPackage, mutator.getClass().getModule()); + assertThrows(IllegalAccessException.class, () -> mutator.unreflectSetter(f)); + + // open package to mutator module, should have no effect on unreflectSetter method + testModule.addOpens(fieldHoldersPackage, mutator.getClass().getModule()); + assertThrows(IllegalAccessException.class, () -> mutator.unreflectSetter(f)); + } + + // private field, class in package not opened to mutator + + @ParameterizedTest + @MethodSource("notOpenToMutators") + void testFieldSetNotOpenPackage(Mutator mutator) throws Exception { + Field f = PrivateFields.objectField(); + var obj = new PrivateFields(); + Object oldValue = obj.objectValue(); + Object newValue = new Object(); + + f.setAccessible(true); + var e1 = assertThrows(IllegalAccessException.class, () -> mutator.set(f, obj, newValue)); + Module mutatorModule = mutator.getClass().getModule(); + if (mutatorModule != testModule) { + assertTrue(e1.getMessage().contains("module " + testModule.getName() + + " does not explicitly \"opens\" package " + + f.getDeclaringClass().getPackageName() + + " to module " + mutatorModule.getName())); + } + assertTrue(obj.objectValue() == oldValue); + + // open package to mutator module, should have no effect on set method + testModule.addOpens(fieldHoldersPackage, mutator.getClass().getModule()); + var e2 = assertThrows(IllegalAccessException.class, () -> mutator.set(f, obj, newValue)); + assertEquals(e2.getMessage(), e2.getMessage()); + assertTrue(obj.objectValue() == oldValue); + } + + @ParameterizedTest + @MethodSource("notOpenToMutators") + void testUnreflectSetterNotOpenPackage(Mutator mutator) throws Exception { + Field f = PrivateFields.class.getDeclaredField("obj"); + + f.setAccessible(true); + assertThrows(IllegalAccessException.class, () -> mutator.unreflectSetter(f)); + + // open package to mutator module, should have no effect on unreflectSetter method + testModule.addOpens(fieldHoldersPackage, mutator.getClass().getModule()); + assertThrows(IllegalAccessException.class, () -> mutator.unreflectSetter(f)); + } +} diff --git a/test/jdk/java/lang/reflect/Field/mutateFinals/modules/test/test/fieldholders/PrivateFields.java b/test/jdk/java/lang/reflect/Field/mutateFinals/modules/test/test/fieldholders/PrivateFields.java new file mode 100644 index 00000000000..2e7ed57a36f --- /dev/null +++ b/test/jdk/java/lang/reflect/Field/mutateFinals/modules/test/test/fieldholders/PrivateFields.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2025, 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 test.fieldholders; + +import java.lang.reflect.Field; + +/** + * public class with private final fields. + */ +public class PrivateFields { + private final Object obj; + private final boolean z; + private final byte b; + private final char c; + private final short s; + private final int i; + private final long l; + private final float f; + private final double d; + + public PrivateFields() { + obj = new Object(); + z = false; + b = 0; + c = 0; + s = 0; + i = 0; + l = 0; + f = 0.0f; + d = 0.0d; + } + + public static Field objectField() throws NoSuchFieldException { + return PrivateFields.class.getDeclaredField("obj"); + } + + public static Field booleanField() throws NoSuchFieldException { + return PrivateFields.class.getDeclaredField("z"); + } + + public static Field byteField() throws NoSuchFieldException { + return PrivateFields.class.getDeclaredField("b"); + } + + public static Field charField() throws NoSuchFieldException { + return PrivateFields.class.getDeclaredField("c"); + } + + public static Field shortField() throws NoSuchFieldException { + return PrivateFields.class.getDeclaredField("s"); + } + + public static Field intField() throws NoSuchFieldException { + return PrivateFields.class.getDeclaredField("i"); + } + + public static Field longField() throws NoSuchFieldException { + return PrivateFields.class.getDeclaredField("l"); + } + + public static Field floatField() throws NoSuchFieldException { + return PrivateFields.class.getDeclaredField("f"); + } + + public static Field doubleField() throws NoSuchFieldException { + return PrivateFields.class.getDeclaredField("d"); + } + + public Object objectValue() { + return obj; + } + + public boolean booleanValue() { + return z; + } + + public byte byteValue() { + return b; + } + + public char charValue() { + return c; + } + + public short shortValue() { + return s; + } + + public int intValue() { + return i; + } + + public long longValue() { + return l; + } + + public float floatValue() { + return f; + } + + public double doubleValue() { + return d; + } +} diff --git a/test/jdk/java/lang/reflect/Field/mutateFinals/modules/test/test/fieldholders/PublicFields.java b/test/jdk/java/lang/reflect/Field/mutateFinals/modules/test/test/fieldholders/PublicFields.java new file mode 100644 index 00000000000..5a830249753 --- /dev/null +++ b/test/jdk/java/lang/reflect/Field/mutateFinals/modules/test/test/fieldholders/PublicFields.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2025, 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 test.fieldholders; + +import java.lang.reflect.Field; + +/** + * public class with public final fields. + */ +public class PublicFields { + public final Object obj; + public final boolean z; + public final byte b; + public final char c; + public final short s; + public final int i; + public final long l; + public final float f; + public final double d; + + public PublicFields() { + obj = new Object(); + z = false; + b = 0; + c = 0; + s = 0; + i = 0; + l = 0; + f = 0.0f; + d = 0.0d; + } + + public static Field objectField() throws NoSuchFieldException { + return PublicFields.class.getDeclaredField("obj"); + } + + public static Field booleanField() throws NoSuchFieldException { + return PublicFields.class.getDeclaredField("z"); + } + + public static Field byteField() throws NoSuchFieldException { + return PublicFields.class.getDeclaredField("b"); + } + + public static Field charField() throws NoSuchFieldException { + return PublicFields.class.getDeclaredField("c"); + } + + public static Field shortField() throws NoSuchFieldException { + return PublicFields.class.getDeclaredField("s"); + } + + public static Field intField() throws NoSuchFieldException { + return PublicFields.class.getDeclaredField("i"); + } + + public static Field longField() throws NoSuchFieldException { + return PublicFields.class.getDeclaredField("l"); + } + + public static Field floatField() throws NoSuchFieldException { + return PublicFields.class.getDeclaredField("f"); + } + + public static Field doubleField() throws NoSuchFieldException { + return PublicFields.class.getDeclaredField("d"); + } + + public Object objectValue() { + return obj; + } + + public boolean booleanValue() { + return z; + } + + public byte byteValue() { + return b; + } + + public char charValue() { + return c; + } + + public short shortValue() { + return s; + } + + public int intValue() { + return i; + } + + public long longValue() { + return l; + } + + public float floatValue() { + return f; + } + + public double doubleValue() { + return d; + } +} diff --git a/test/jdk/java/lang/reflect/Field/mutateFinals/modules/test/test/internal/TestMutator.java b/test/jdk/java/lang/reflect/Field/mutateFinals/modules/test/test/internal/TestMutator.java new file mode 100644 index 00000000000..33f7056026b --- /dev/null +++ b/test/jdk/java/lang/reflect/Field/mutateFinals/modules/test/test/internal/TestMutator.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2025, 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 test.internal; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Field; + +public class TestMutator implements test.spi.Mutator { + + @Override + public void set(Field f, Object obj, Object value) throws IllegalAccessException { + f.set(obj, value); + } + + @Override + public void setBoolean(Field f, Object obj, boolean value) throws IllegalAccessException { + f.setBoolean(obj, value); + } + + @Override + public void setByte(Field f, Object obj, byte value) throws IllegalAccessException { + f.setByte(obj, value); + } + + @Override + public void setChar(Field f, Object obj, char value) throws IllegalAccessException { + f.setChar(obj, value); + } + + @Override + public void setShort(Field f, Object obj, short value) throws IllegalAccessException { + f.setShort(obj, value); + } + + @Override + public void setInt(Field f, Object obj, int value) throws IllegalAccessException { + f.setInt(obj, value); + } + + @Override + public void setLong(Field f, Object obj, long value) throws IllegalAccessException { + f.setLong(obj, value); + } + + @Override + public void setFloat(Field f, Object obj, float value) throws IllegalAccessException { + f.setFloat(obj, value); + } + + @Override + public void setDouble(Field f, Object obj, double value) throws IllegalAccessException { + f.setDouble(obj, value); + } + + @Override + public MethodHandle unreflectSetter(Field f) throws IllegalAccessException { + return MethodHandles.lookup().unreflectSetter(f); + } + +} diff --git a/test/jdk/java/lang/reflect/Field/mutateFinals/modules/test/test/spi/Mutator.java b/test/jdk/java/lang/reflect/Field/mutateFinals/modules/test/test/spi/Mutator.java new file mode 100644 index 00000000000..467a84e8a55 --- /dev/null +++ b/test/jdk/java/lang/reflect/Field/mutateFinals/modules/test/test/spi/Mutator.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2025, 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 test.spi; + +import java.lang.invoke.MethodHandle; +import java.lang.reflect.Field; + +public interface Mutator { + void set(Field f, Object obj, Object value) throws IllegalAccessException; + void setBoolean(Field f, Object obj, boolean value) throws IllegalAccessException; + void setByte(Field f, Object obj, byte value) throws IllegalAccessException; + void setChar(Field f, Object obj, char value) throws IllegalAccessException; + void setShort(Field f, Object obj, short value) throws IllegalAccessException; + void setInt(Field f, Object obj, int value) throws IllegalAccessException; + void setLong(Field f, Object obj, long value) throws IllegalAccessException; + void setFloat(Field f, Object obj, float value) throws IllegalAccessException; + void setDouble(Field f, Object obj, double value) throws IllegalAccessException; + MethodHandle unreflectSetter(Field f) throws IllegalAccessException; + + static Mutator throwing() { + return new Mutator() { + @Override + public void set(Field f, Object obj, Object value) throws IllegalAccessException { + throw new IllegalAccessException(); + } + @Override + public void setBoolean(Field f, Object obj, boolean value) throws IllegalAccessException { + throw new IllegalAccessException(); + } + @Override + public void setByte(Field f, Object obj, byte value) throws IllegalAccessException { + throw new IllegalAccessException(); + } + @Override + public void setChar(Field f, Object obj, char value) throws IllegalAccessException { + throw new IllegalAccessException(); + } + @Override + public void setShort(Field f, Object obj, short value) throws IllegalAccessException { + throw new IllegalAccessException(); + } + @Override + public void setInt(Field f, Object obj, int value) throws IllegalAccessException { + throw new IllegalAccessException(); + } + @Override + public void setLong(Field f, Object obj, long value) throws IllegalAccessException { + throw new IllegalAccessException(); + } + @Override + public void setFloat(Field f, Object obj, float value) throws IllegalAccessException { + throw new IllegalAccessException(); + } + @Override + public void setDouble(Field f, Object obj, double value) throws IllegalAccessException { + throw new IllegalAccessException(); + } + @Override + public MethodHandle unreflectSetter(Field f) throws IllegalAccessException { + throw new IllegalAccessException(); + } + @Override + public String toString() { + return ""; + } + }; + } +} diff --git a/test/jdk/java/util/jar/Attributes/NullAndEmptyKeysAndValues.java b/test/jdk/java/util/jar/Attributes/NullAndEmptyKeysAndValues.java index 396c0a27b6d..c62ddcced8a 100644 --- a/test/jdk/java/util/jar/Attributes/NullAndEmptyKeysAndValues.java +++ b/test/jdk/java/util/jar/Attributes/NullAndEmptyKeysAndValues.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -38,7 +38,7 @@ import static org.testng.Assert.*; * @test * @bug 8066619 * @modules java.base/java.util.jar:+open - * @run testng/othervm NullAndEmptyKeysAndValues + * @run testng/othervm --enable-final-field-mutation=ALL-UNNAMED NullAndEmptyKeysAndValues * @summary Tests manifests with {@code null} and empty string {@code ""} * values as section name, header name, or value in both main and named * attributes sections. diff --git a/test/jdk/java/util/logging/FileHandlerLongLimit.java b/test/jdk/java/util/logging/FileHandlerLongLimit.java index 43594ad3df4..69aa20e8bed 100644 --- a/test/jdk/java/util/logging/FileHandlerLongLimit.java +++ b/test/jdk/java/util/logging/FileHandlerLongLimit.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2025, 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 @@ import java.util.logging.LogRecord; * @bug 8059767 * @summary tests that FileHandler can accept a long limit. * @modules java.logging/java.util.logging:open - * @run main/othervm FileHandlerLongLimit + * @run main/othervm --enable-final-field-mutation=ALL-UNNAMED FileHandlerLongLimit * @author danielfuchs * @key randomness */ diff --git a/test/jdk/jdk/jfr/event/metadata/TestLookForUntestedEvents.java b/test/jdk/jdk/jfr/event/metadata/TestLookForUntestedEvents.java index d6e126a493f..155ece15b86 100644 --- a/test/jdk/jdk/jfr/event/metadata/TestLookForUntestedEvents.java +++ b/test/jdk/jdk/jfr/event/metadata/TestLookForUntestedEvents.java @@ -81,6 +81,10 @@ public class TestLookForUntestedEvents { private static final Set coveredVirtualThreadEvents = Set.of( "VirtualThreadPinned", "VirtualThreadSubmitFailed"); + // This event is tested in test/jdk/java/lang/reflect/Field/mutateFinals/FinalFieldMutationEventTest.java + private static final Set coveredFinalFieldMutationEvents = Set.of( + "FinalFieldMutationEvent"); + // This is a "known failure list" for this test. // NOTE: if the event is not covered, a bug should be open, and bug number // noted in the comments for this set. @@ -128,6 +132,7 @@ public class TestLookForUntestedEvents { eventsNotCoveredByTest.removeAll(hardToTestEvents); eventsNotCoveredByTest.removeAll(coveredGcEvents); eventsNotCoveredByTest.removeAll(coveredVirtualThreadEvents); + eventsNotCoveredByTest.removeAll(coveredFinalFieldMutationEvents); eventsNotCoveredByTest.removeAll(coveredContainerEvents); eventsNotCoveredByTest.removeAll(knownNotCoveredEvents); diff --git a/test/jdk/sun/security/pkcs11/Cipher/CancelMultipart.java b/test/jdk/sun/security/pkcs11/Cipher/CancelMultipart.java index 28f3699050c..afd9fb2cfca 100644 --- a/test/jdk/sun/security/pkcs11/Cipher/CancelMultipart.java +++ b/test/jdk/sun/security/pkcs11/Cipher/CancelMultipart.java @@ -26,7 +26,7 @@ * @bug 8258833 * @library /test/lib .. * @modules jdk.crypto.cryptoki/sun.security.pkcs11:open - * @run main/othervm CancelMultipart + * @run main/othervm --enable-final-field-mutation=ALL-UNNAMED CancelMultipart */ import java.lang.reflect.Field; diff --git a/test/jdk/sun/security/provider/SecureRandom/DRBGS11n.java b/test/jdk/sun/security/provider/SecureRandom/DRBGS11n.java index 410bf0cacb2..95b91805882 100644 --- a/test/jdk/sun/security/provider/SecureRandom/DRBGS11n.java +++ b/test/jdk/sun/security/provider/SecureRandom/DRBGS11n.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -34,8 +34,8 @@ import java.lang.reflect.Field; * @bug 8157308 * @modules java.base/sun.security.provider:+open * @summary Make AbstractDrbg non-Serializable - * @run main DRBGS11n mech - * @run main DRBGS11n capability + * @run main/othervm --enable-final-field-mutation=ALL-UNNAMED DRBGS11n mech + * @run main/othervm --enable-final-field-mutation=ALL-UNNAMED DRBGS11n capability */ public class DRBGS11n { diff --git a/test/jdk/sun/security/util/ManifestDigester/FindSection.java b/test/jdk/sun/security/util/ManifestDigester/FindSection.java index ed80c5bcbbd..f7517427239 100644 --- a/test/jdk/sun/security/util/ManifestDigester/FindSection.java +++ b/test/jdk/sun/security/util/ManifestDigester/FindSection.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2025, 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 @@ -44,7 +44,7 @@ import static org.testng.Assert.*; * @bug 8217375 * @modules java.base/sun.security.util:+open * @compile ../../tools/jarsigner/Utils.java - * @run testng/othervm FindSection + * @run testng/othervm --enable-final-field-mutation=ALL-UNNAMED FindSection * @summary Check {@link ManifestDigester#findSection}. */ public class FindSection { diff --git a/test/langtools/jdk/jshell/CompletionSuggestionTest.java b/test/langtools/jdk/jshell/CompletionSuggestionTest.java index 7d739efddc6..d31a32b63f8 100644 --- a/test/langtools/jdk/jshell/CompletionSuggestionTest.java +++ b/test/langtools/jdk/jshell/CompletionSuggestionTest.java @@ -32,7 +32,7 @@ * jdk.jshell/jdk.jshell:open * @build toolbox.ToolBox toolbox.JarTask toolbox.JavacTask * @build KullaTesting TestingInputStream Compiler - * @run junit/timeout=480 CompletionSuggestionTest + * @run junit/othervm/timeout=480 --enable-final-field-mutation=ALL-UNNAMED CompletionSuggestionTest */ import java.io.IOException; diff --git a/test/lib/jdk/test/lib/jfr/EventNames.java b/test/lib/jdk/test/lib/jfr/EventNames.java index a00898358a8..e53b242097e 100644 --- a/test/lib/jdk/test/lib/jfr/EventNames.java +++ b/test/lib/jdk/test/lib/jfr/EventNames.java @@ -220,6 +220,7 @@ public class EventNames { public static final String VirtualThreadEnd = PREFIX + "VirtualThreadEnd"; public static final String VirtualThreadPinned = PREFIX + "VirtualThreadPinned"; public static final String VirtualThreadSubmitFailed = PREFIX + "VirtualThreadSubmitFailed"; + public static final String FinalFieldMutation = PREFIX + "FinalFieldMutation"; // Containers public static final String ContainerConfiguration = PREFIX + "ContainerConfiguration"; diff --git a/test/micro/org/openjdk/bench/java/lang/reflect/FieldSet.java b/test/micro/org/openjdk/bench/java/lang/reflect/FieldSet.java new file mode 100644 index 00000000000..4b8bd59a5c8 --- /dev/null +++ b/test/micro/org/openjdk/bench/java/lang/reflect/FieldSet.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2025, 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 org.openjdk.bench.java.lang.reflect; + +import java.lang.reflect.Field; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.ThreadLocalRandom; + +import org.openjdk.jmh.annotations.*; + +@BenchmarkMode(Mode.AverageTime) +@State(Scope.Benchmark) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@Warmup(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 10, time = 2, timeUnit = TimeUnit.SECONDS) +@Fork(3) +public class FieldSet { + + static class FieldHolder { + Object obj; + int intValue; + long longValue; + FieldHolder(Object obj, int i, long l) { + this.obj = obj; + this.intValue = i; + this.longValue = l; + } + } + + static class FinalFieldHolder { + final Object obj; + final int intValue; + final long longValue; + FinalFieldHolder(Object obj, int i, long l) { + this.obj = obj; + this.intValue = i; + this.longValue = l; + } + } + + private FieldHolder fieldHolder; + private FinalFieldHolder finalFieldHolder; + + private Field objField1, objField2, objField3; + private Field intField1, intField2, intField3; + private Field longField1, longField2, longField3; + + @Setup + public void setup() throws Exception { + fieldHolder = new FieldHolder(new Object(), 1, 1L); + finalFieldHolder = new FinalFieldHolder(new Object(), 1, 1L); + + // non-final && !override + objField1 = FieldHolder.class.getDeclaredField("obj"); + intField1 = FieldHolder.class.getDeclaredField("intValue"); + longField1 = FieldHolder.class.getDeclaredField("longValue"); + + // non-final && override + objField2 = FieldHolder.class.getDeclaredField("obj"); + objField2.setAccessible(true); + intField2 = FieldHolder.class.getDeclaredField("intValue"); + intField2.setAccessible(true); + longField2 = FieldHolder.class.getDeclaredField("longValue"); + longField2.setAccessible(true); + + // final && override + objField3 = FinalFieldHolder.class.getDeclaredField("obj"); + objField3.setAccessible(true); + intField3 = FinalFieldHolder.class.getDeclaredField("intValue"); + intField3.setAccessible(true); + longField3 = FinalFieldHolder.class.getDeclaredField("longValue"); + longField3.setAccessible(true); + } + + // non-final && !override + + @Benchmark + public void setNonFinalObjectField() throws Exception { + objField1.set(fieldHolder, new Object()); + } + + @Benchmark + public void setNonFinalIntField() throws Exception { + int newValue = ThreadLocalRandom.current().nextInt(); + intField1.setInt(fieldHolder, newValue); + } + + @Benchmark + public void setNonFinalLongField() throws Exception { + long newValue = ThreadLocalRandom.current().nextLong(); + longField1.setLong(fieldHolder, newValue); + } + + // non-final && override + + @Benchmark + public void setNonFinalObjectFieldWithOverride() throws Exception { + objField2.set(fieldHolder, new Object()); + } + + @Benchmark + public void setNonFinalIntFieldWithOverride() throws Exception { + int newValue = ThreadLocalRandom.current().nextInt(); + intField2.setInt(fieldHolder, newValue); + } + + @Benchmark + public void setNonFinalLongFieldWithOverride() throws Exception { + long newValue = ThreadLocalRandom.current().nextLong(); + longField2.setLong(fieldHolder, newValue); + } + + // final && override + + @Benchmark + public void setFinalObjectField()throws Exception { + objField3.set(finalFieldHolder, new Object()); + } + + @Benchmark + public void setFinalIntField() throws Exception { + int newValue = ThreadLocalRandom.current().nextInt(); + intField3.setInt(finalFieldHolder, newValue); + } + + @Benchmark + public void setFinalLongField() throws Exception { + long newValue = ThreadLocalRandom.current().nextLong(); + longField3.setLong(finalFieldHolder, newValue); + } +} From 8af594371979b2b76ec04e0a2753413dc35b8d44 Mon Sep 17 00:00:00 2001 From: Jan Lahoda Date: Tue, 18 Nov 2025 08:13:58 +0000 Subject: [PATCH 098/418] 8370334: javadoc NPE with "import module" statement Reviewed-by: vromero --- .../com/sun/tools/javac/comp/Modules.java | 15 +++++--- .../jdk/javadoc/tool/modules/Modules.java | 31 ++++++++++++++++ .../tools/javac/modules/AddModulesTest.java | 37 ++++++++++++++++++- 3 files changed, 77 insertions(+), 6 deletions(-) diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Modules.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Modules.java index 0cef9cc6602..5a1fe70dd9b 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Modules.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Modules.java @@ -37,7 +37,7 @@ import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; -import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.Predicate; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -236,6 +236,7 @@ public class Modules extends JCTree.Visitor { setupAllModules(); //initialize the module graph Assert.checkNonNull(allModules); inInitModules = false; + return allModules; }, null); } finally { inInitModules = false; @@ -249,10 +250,11 @@ public class Modules extends JCTree.Visitor { //the next steps may query if the current module participates in preview, //and that requires a completed java.base: syms.java_base.complete(); + return modules; }, c); } - private boolean enter(List trees, Consumer> init, ClassSymbol c) { + private boolean enter(List trees, Function, Set> init, ClassSymbol c) { if (!allowModules) { for (JCCompilationUnit tree: trees) { tree.modle = syms.noModule; @@ -270,10 +272,13 @@ public class Modules extends JCTree.Visitor { setCompilationUnitModules(trees, roots, c); - init.accept(roots); + Set initialized = init.apply(roots); - for (ModuleSymbol msym: roots) { - msym.complete(); + for (ModuleSymbol msym : initialized) { + if (msym != syms.unnamedModule || + roots.contains(syms.unnamedModule)) { + msym.complete(); + } } } catch (CompletionFailure ex) { chk.completionError(null, ex); diff --git a/test/langtools/jdk/javadoc/tool/modules/Modules.java b/test/langtools/jdk/javadoc/tool/modules/Modules.java index d830470bb01..c8028ba5c7e 100644 --- a/test/langtools/jdk/javadoc/tool/modules/Modules.java +++ b/test/langtools/jdk/javadoc/tool/modules/Modules.java @@ -672,4 +672,35 @@ public class Modules extends ModuleTestBase { checkTypesIncluded("p.P"); } + @Test + public void testImportModules(Path base) throws Exception { + Path src = base.resolve("src"); + Path mod = Paths.get(src.toString(), "m1"); + tb.writeJavaFiles(mod, + """ + import module m1; + module m1 { + exports p; + uses Service; + provides Service with ServiceImpl; + } + """, + """ + package p; + public interface Service { + } + """, + """ + package p; + public class ServiceImpl implements Service { + } + """); + execTask("--module-source-path", src.toString(), + "--module", "m1"); + checkModulesSpecified("m1"); + checkPackagesIncluded("p"); + checkTypesIncluded("p.Service"); + checkTypesIncluded("p.ServiceImpl"); + } + } diff --git a/test/langtools/tools/javac/modules/AddModulesTest.java b/test/langtools/tools/javac/modules/AddModulesTest.java index f7a87c592c9..080a5edda27 100644 --- a/test/langtools/tools/javac/modules/AddModulesTest.java +++ b/test/langtools/tools/javac/modules/AddModulesTest.java @@ -23,7 +23,7 @@ /* * @test - * @bug 8167975 8173596 + * @bug 8167975 8173596 8370334 * @summary Test the --add-modules option * @library /tools/lib * @modules jdk.compiler/com.sun.tools.javac.api @@ -35,6 +35,7 @@ import java.nio.file.Path; import java.util.Arrays; +import java.util.List; import javax.tools.JavaCompiler; import javax.tools.JavaCompiler.CompilationTask; @@ -278,5 +279,39 @@ public class AddModulesTest extends ModuleTestBase { Assert.check(t.call()); } } + + @Test //JDK-8370334 + public void testModuleImportAndAddModules(Path base) throws Exception { + Path src = base.resolve("src"); + + //module that will be inserted using addModules: + Path src_m = src.resolve("m"); + tb.writeJavaFiles(src_m, + "import module m; module m { exports p1; }", + "package p1; public class C1 { }"); + //test module: + Path src_test = src.resolve("test"); + tb.writeJavaFiles(src_test, + "import module test; module test { exports p2; }", + "package p2; public class C2 { }"); + + Path classes = base.resolve("classes"); + tb.createDirectories(classes); + + JavaCompiler c = ToolProvider.getSystemJavaCompiler(); + try (StandardJavaFileManager fm = c.getStandardFileManager(null, null, null)) { + List options = List.of( + "--module-source-path", src.toString(), + "-d", classes.toString() + ); + Iterable files = + fm.getJavaFileObjects(findJavaFiles(src_test)); + + CompilationTask t = c.getTask(null, fm, null, options, null, files); + t.addModules(Arrays.asList("m")); + //expecting no errors/crashes: + Assert.check(t.call()); + } + } } From 50a30497370799e8f377a11914562a15b0a48fbb Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Tue, 18 Nov 2025 09:37:20 +0000 Subject: [PATCH 099/418] 8371643: Remove ThreadLocalAllocBuffer::_reserve_for_allocation_prefetch Reviewed-by: mdoerr, kvn, tschatzl --- src/hotspot/cpu/ppc/ppc.ad | 29 ------------------- .../gc/shared/threadLocalAllocBuffer.cpp | 28 +----------------- .../gc/shared/threadLocalAllocBuffer.hpp | 1 - src/hotspot/share/opto/macro.cpp | 3 +- src/hotspot/share/runtime/vmStructs.cpp | 1 - .../runtime/ThreadLocalAllocBuffer.java | 3 +- .../classes/sun/jvm/hotspot/runtime/VM.java | 7 ----- 7 files changed, 3 insertions(+), 69 deletions(-) diff --git a/src/hotspot/cpu/ppc/ppc.ad b/src/hotspot/cpu/ppc/ppc.ad index 7fcd096d2ad..c169d673aaf 100644 --- a/src/hotspot/cpu/ppc/ppc.ad +++ b/src/hotspot/cpu/ppc/ppc.ad @@ -6328,36 +6328,8 @@ instruct loadConD_Ex(regD dst, immD src) %{ // Prefetch instructions. // Must be safe to execute with invalid address (cannot fault). -// Special prefetch versions which use the dcbz instruction. -instruct prefetch_alloc_zero(indirectMemory mem, iRegLsrc src) %{ - match(PrefetchAllocation (AddP mem src)); - predicate(AllocatePrefetchStyle == 3); - ins_cost(MEMORY_REF_COST); - - format %{ "PREFETCH $mem, 2, $src \t// Prefetch write-many with zero" %} - size(4); - ins_encode %{ - __ dcbz($src$$Register, $mem$$base$$Register); - %} - ins_pipe(pipe_class_memory); -%} - -instruct prefetch_alloc_zero_no_offset(indirectMemory mem) %{ - match(PrefetchAllocation mem); - predicate(AllocatePrefetchStyle == 3); - ins_cost(MEMORY_REF_COST); - - format %{ "PREFETCH $mem, 2 \t// Prefetch write-many with zero" %} - size(4); - ins_encode %{ - __ dcbz($mem$$base$$Register); - %} - ins_pipe(pipe_class_memory); -%} - instruct prefetch_alloc(indirectMemory mem, iRegLsrc src) %{ match(PrefetchAllocation (AddP mem src)); - predicate(AllocatePrefetchStyle != 3); ins_cost(MEMORY_REF_COST); format %{ "PREFETCH $mem, 2, $src \t// Prefetch write-many" %} @@ -6370,7 +6342,6 @@ instruct prefetch_alloc(indirectMemory mem, iRegLsrc src) %{ instruct prefetch_alloc_no_offset(indirectMemory mem) %{ match(PrefetchAllocation mem); - predicate(AllocatePrefetchStyle != 3); ins_cost(MEMORY_REF_COST); format %{ "PREFETCH $mem, 2 \t// Prefetch write-many" %} diff --git a/src/hotspot/share/gc/shared/threadLocalAllocBuffer.cpp b/src/hotspot/share/gc/shared/threadLocalAllocBuffer.cpp index 9635ed4d0cb..2181e089663 100644 --- a/src/hotspot/share/gc/shared/threadLocalAllocBuffer.cpp +++ b/src/hotspot/share/gc/shared/threadLocalAllocBuffer.cpp @@ -37,7 +37,6 @@ #include "utilities/copy.hpp" size_t ThreadLocalAllocBuffer::_max_size = 0; -int ThreadLocalAllocBuffer::_reserve_for_allocation_prefetch = 0; unsigned int ThreadLocalAllocBuffer::_target_refills = 0; ThreadLocalAllocBuffer::ThreadLocalAllocBuffer() : @@ -225,30 +224,6 @@ void ThreadLocalAllocBuffer::startup_initialization() { // abort during VM initialization. _target_refills = MAX2(_target_refills, 2U); -#ifdef COMPILER2 - // If the C2 compiler is present, extra space is needed at the end of - // TLABs, otherwise prefetching instructions generated by the C2 - // compiler will fault (due to accessing memory outside of heap). - // The amount of space is the max of the number of lines to - // prefetch for array and for instance allocations. (Extra space must be - // reserved to accommodate both types of allocations.) - // - // Only SPARC-specific BIS instructions are known to fault. (Those - // instructions are generated if AllocatePrefetchStyle==3 and - // AllocatePrefetchInstr==1). To be on the safe side, however, - // extra space is reserved for all combinations of - // AllocatePrefetchStyle and AllocatePrefetchInstr. - // - // If the C2 compiler is not present, no space is reserved. - - // +1 for rounding up to next cache line, +1 to be safe - if (CompilerConfig::is_c2_or_jvmci_compiler_enabled()) { - int lines = MAX2(AllocatePrefetchLines, AllocateInstancePrefetchLines) + 2; - _reserve_for_allocation_prefetch = (AllocatePrefetchDistance + AllocatePrefetchStepSize * lines) / - (int)HeapWordSize; - } -#endif - // During jvm startup, the main thread is initialized // before the heap is initialized. So reinitialize it now. guarantee(Thread::current()->is_Java_thread(), "tlab initialization thread not Java thread"); @@ -454,8 +429,7 @@ void ThreadLocalAllocStats::publish() { } size_t ThreadLocalAllocBuffer::end_reserve() { - size_t reserve_size = CollectedHeap::lab_alignment_reserve(); - return MAX2(reserve_size, (size_t)_reserve_for_allocation_prefetch); + return CollectedHeap::lab_alignment_reserve(); } const HeapWord* ThreadLocalAllocBuffer::start_relaxed() const { diff --git a/src/hotspot/share/gc/shared/threadLocalAllocBuffer.hpp b/src/hotspot/share/gc/shared/threadLocalAllocBuffer.hpp index 59979646395..b64fa8d6ad1 100644 --- a/src/hotspot/share/gc/shared/threadLocalAllocBuffer.hpp +++ b/src/hotspot/share/gc/shared/threadLocalAllocBuffer.hpp @@ -58,7 +58,6 @@ private: size_t _allocated_before_last_gc; // total bytes allocated up until the last gc static size_t _max_size; // maximum size of any TLAB - static int _reserve_for_allocation_prefetch; // Reserve at the end of the TLAB static unsigned _target_refills; // expected number of refills between GCs unsigned _number_of_refills; diff --git a/src/hotspot/share/opto/macro.cpp b/src/hotspot/share/opto/macro.cpp index 90602bc2b35..6f2171bbd75 100644 --- a/src/hotspot/share/opto/macro.cpp +++ b/src/hotspot/share/opto/macro.cpp @@ -1914,8 +1914,7 @@ Node* PhaseMacroExpand::prefetch_allocation(Node* i_o, Node*& needgc_false, transform_later(cache_adr); cache_adr = new CastP2XNode(needgc_false, cache_adr); transform_later(cache_adr); - // Address is aligned to execute prefetch to the beginning of cache line size - // (it is important when BIS instruction is used on SPARC as prefetch). + // Address is aligned to execute prefetch to the beginning of cache line size. Node* mask = _igvn.MakeConX(~(intptr_t)(step_size-1)); cache_adr = new AndXNode(cache_adr, mask); transform_later(cache_adr); diff --git a/src/hotspot/share/runtime/vmStructs.cpp b/src/hotspot/share/runtime/vmStructs.cpp index a7342448522..a75e67e9b56 100644 --- a/src/hotspot/share/runtime/vmStructs.cpp +++ b/src/hotspot/share/runtime/vmStructs.cpp @@ -350,7 +350,6 @@ nonstatic_field(ThreadLocalAllocBuffer, _pf_top, HeapWord*) \ nonstatic_field(ThreadLocalAllocBuffer, _desired_size, size_t) \ nonstatic_field(ThreadLocalAllocBuffer, _refill_waste_limit, size_t) \ - static_field(ThreadLocalAllocBuffer, _reserve_for_allocation_prefetch, int) \ static_field(ThreadLocalAllocBuffer, _target_refills, unsigned) \ nonstatic_field(ThreadLocalAllocBuffer, _number_of_refills, unsigned) \ nonstatic_field(ThreadLocalAllocBuffer, _refill_waste, unsigned) \ diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/ThreadLocalAllocBuffer.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/ThreadLocalAllocBuffer.java index 1dc67330d3d..e23e63806bd 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/ThreadLocalAllocBuffer.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/ThreadLocalAllocBuffer.java @@ -76,10 +76,9 @@ public class ThreadLocalAllocBuffer extends VMObject { private long endReserve() { long labAlignmentReserve = VM.getVM().getLabAlignmentReserve(); - long reserveForAllocationPrefetch = VM.getVM().getReserveForAllocationPrefetch(); long heapWordSize = VM.getVM().getHeapWordSize(); - return Math.max(labAlignmentReserve, reserveForAllocationPrefetch) * heapWordSize; + return labAlignmentReserve * heapWordSize; } /** Support for iteration over heap -- not sure how this will diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/VM.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/VM.java index dc27a4fc59e..1607563150a 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/VM.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/VM.java @@ -123,7 +123,6 @@ public class VM { private int invocationEntryBCI; private ReversePtrs revPtrs; private VMRegImpl vmregImpl; - private int reserveForAllocationPrefetch; private int labAlignmentReserve; // System.getProperties from debuggee VM @@ -447,8 +446,6 @@ public class VM { boolType = (CIntegerType) db.lookupType("bool"); Type threadLocalAllocBuffer = db.lookupType("ThreadLocalAllocBuffer"); - CIntegerField reserveForAllocationPrefetchField = threadLocalAllocBuffer.getCIntegerField("_reserve_for_allocation_prefetch"); - reserveForAllocationPrefetch = (int)reserveForAllocationPrefetchField.getCInteger(intType); Type collectedHeap = db.lookupType("CollectedHeap"); CIntegerField labAlignmentReserveField = collectedHeap.getCIntegerField("_lab_alignment_reserve"); @@ -915,10 +912,6 @@ public class VM { return vmInternalInfo; } - public int getReserveForAllocationPrefetch() { - return reserveForAllocationPrefetch; - } - public int getLabAlignmentReserve() { return labAlignmentReserve; } From 72ebca8a0b19fac8a9483e5a3a98b454176fc342 Mon Sep 17 00:00:00 2001 From: Severin Gehwolf Date: Tue, 18 Nov 2025 09:42:28 +0000 Subject: [PATCH 100/418] 8365606: Container code should not be using jlong/julong Reviewed-by: stuefe, cnorrbin, fitzsim --- .../os/linux/cgroupSubsystem_linux.cpp | 157 +++--- .../os/linux/cgroupSubsystem_linux.hpp | 139 +++-- src/hotspot/os/linux/cgroupUtil_linux.cpp | 101 +++- src/hotspot/os/linux/cgroupUtil_linux.hpp | 9 +- .../os/linux/cgroupV1Subsystem_linux.cpp | 527 +++++++++++------- .../os/linux/cgroupV1Subsystem_linux.hpp | 76 ++- .../os/linux/cgroupV2Subsystem_linux.cpp | 320 ++++++----- .../os/linux/cgroupV2Subsystem_linux.hpp | 43 +- src/hotspot/os/linux/osContainer_linux.cpp | 196 ++++--- src/hotspot/os/linux/osContainer_linux.hpp | 59 +- src/hotspot/os/linux/os_linux.cpp | 220 ++++---- src/hotspot/os/linux/os_linux.hpp | 4 +- src/hotspot/share/jfr/jni/jfrJniMethod.cpp | 4 +- src/hotspot/share/prims/whitebox.cpp | 8 +- src/hotspot/share/runtime/os.cpp | 8 +- .../runtime/test_cgroupSubsystem_linux.cpp | 135 ++--- 16 files changed, 1207 insertions(+), 799 deletions(-) diff --git a/src/hotspot/os/linux/cgroupSubsystem_linux.cpp b/src/hotspot/os/linux/cgroupSubsystem_linux.cpp index f5c4abeb4ca..f9c6f794ebd 100644 --- a/src/hotspot/os/linux/cgroupSubsystem_linux.cpp +++ b/src/hotspot/os/linux/cgroupSubsystem_linux.cpp @@ -612,7 +612,6 @@ void CgroupSubsystemFactory::cleanup(CgroupInfo* cg_infos) { * * cpu affinity * cgroup cpu quota & cpu period - * cgroup cpu shares * * Algorithm: * @@ -623,19 +622,18 @@ void CgroupSubsystemFactory::cleanup(CgroupInfo* cg_infos) { * * All results of division are rounded up to the next whole number. * - * If quotas have not been specified, return the - * number of active processors in the system. + * If quotas have not been specified, sets the result reference to + * the number of active processors in the system. * - * If quotas have been specified, the resulting number - * returned will never exceed the number of active processors. + * If quotas have been specified, the number set in the result + * reference will never exceed the number of active processors. * * return: - * number of CPUs + * true if there were no errors. false otherwise. */ -int CgroupSubsystem::active_processor_count() { - int quota_count = 0; +bool CgroupSubsystem::active_processor_count(int& value) { int cpu_count; - int result; + int result = -1; // We use a cache with a timeout to avoid performing expensive // computations in the event this function is called frequently. @@ -643,38 +641,50 @@ int CgroupSubsystem::active_processor_count() { CachingCgroupController* contrl = cpu_controller(); CachedMetric* cpu_limit = contrl->metrics_cache(); if (!cpu_limit->should_check_metric()) { - int val = (int)cpu_limit->value(); - log_trace(os, container)("CgroupSubsystem::active_processor_count (cached): %d", val); - return val; + value = (int)cpu_limit->value(); + log_trace(os, container)("CgroupSubsystem::active_processor_count (cached): %d", value); + return true; } cpu_count = os::Linux::active_processor_count(); - result = CgroupUtil::processor_count(contrl->controller(), cpu_count); + if (!CgroupUtil::processor_count(contrl->controller(), cpu_count, result)) { + return false; + } + assert(result > 0 && result <= cpu_count, "must be"); // Update cached metric to avoid re-reading container settings too often cpu_limit->set_value(result, OSCONTAINER_CACHE_TIMEOUT); + value = result; - return result; + return true; } /* memory_limit_in_bytes * - * Return the limit of available memory for this process. + * Return the limit of available memory for this process in the provided + * physical_memory_size_type reference. If there was no limit value set in the underlying + * interface files 'value_unlimited' is returned. * * return: - * memory limit in bytes or - * -1 for unlimited - * OSCONTAINER_ERROR for not supported + * false if retrieving the value failed + * true if retrieving the value was successfull and the value was + * set in the 'value' reference. */ -jlong CgroupSubsystem::memory_limit_in_bytes(julong upper_bound) { +bool CgroupSubsystem::memory_limit_in_bytes(physical_memory_size_type upper_bound, + physical_memory_size_type& value) { CachingCgroupController* contrl = memory_controller(); CachedMetric* memory_limit = contrl->metrics_cache(); if (!memory_limit->should_check_metric()) { - return memory_limit->value(); + value = memory_limit->value(); + return true; + } + physical_memory_size_type mem_limit = 0; + if (!contrl->controller()->read_memory_limit_in_bytes(upper_bound, mem_limit)) { + return false; } - jlong mem_limit = contrl->controller()->read_memory_limit_in_bytes(upper_bound); // Update cached metric to avoid re-reading container settings too often memory_limit->set_value(mem_limit, OSCONTAINER_CACHE_TIMEOUT); - return mem_limit; + value = mem_limit; + return true; } bool CgroupController::read_string(const char* filename, char* buf, size_t buf_size) { @@ -719,36 +729,35 @@ bool CgroupController::read_string(const char* filename, char* buf, size_t buf_s return true; } -bool CgroupController::read_number(const char* filename, julong* result) { +bool CgroupController::read_number(const char* filename, uint64_t& result) { char buf[1024]; bool is_ok = read_string(filename, buf, 1024); if (!is_ok) { return false; } - int matched = sscanf(buf, JULONG_FORMAT, result); + int matched = sscanf(buf, UINT64_FORMAT, &result); if (matched == 1) { return true; } return false; } -bool CgroupController::read_number_handle_max(const char* filename, jlong* result) { +bool CgroupController::read_number_handle_max(const char* filename, uint64_t& result) { char buf[1024]; bool is_ok = read_string(filename, buf, 1024); if (!is_ok) { return false; } - jlong val = limit_from_str(buf); - if (val == OSCONTAINER_ERROR) { + uint64_t val = 0; + if (!limit_from_str(buf, val)) { return false; } - *result = val; + result = val; return true; } -bool CgroupController::read_numerical_key_value(const char* filename, const char* key, julong* result) { +bool CgroupController::read_numerical_key_value(const char* filename, const char* key, uint64_t& result) { assert(key != nullptr, "key must be given"); - assert(result != nullptr, "result pointer must not be null"); assert(filename != nullptr, "file to search in must be given"); const char* s_path = subsystem_path(); if (s_path == nullptr) { @@ -786,7 +795,7 @@ bool CgroupController::read_numerical_key_value(const char* filename, const char && after_key != '\n') { // Skip key, skip space const char* value_substr = line + key_len + 1; - int matched = sscanf(value_substr, JULONG_FORMAT, result); + int matched = sscanf(value_substr, UINT64_FORMAT, &result); found_match = matched == 1; if (found_match) { break; @@ -797,12 +806,12 @@ bool CgroupController::read_numerical_key_value(const char* filename, const char if (found_match) { return true; } - log_debug(os, container)("Type %s (key == %s) not found in file %s", JULONG_FORMAT, + log_debug(os, container)("Type %s (key == %s) not found in file %s", UINT64_FORMAT, key, absolute_path); return false; } -bool CgroupController::read_numerical_tuple_value(const char* filename, bool use_first, jlong* result) { +bool CgroupController::read_numerical_tuple_value(const char* filename, bool use_first, uint64_t& result) { char buf[1024]; bool is_ok = read_string(filename, buf, 1024); if (!is_ok) { @@ -813,80 +822,90 @@ bool CgroupController::read_numerical_tuple_value(const char* filename, bool use if (matched != 1) { return false; } - jlong val = limit_from_str(token); - if (val == OSCONTAINER_ERROR) { + uint64_t val = 0; + if (!limit_from_str(token, val)) { return false; } - *result = val; + result = val; return true; } -jlong CgroupController::limit_from_str(char* limit_str) { +bool CgroupController::limit_from_str(char* limit_str, uint64_t& value) { if (limit_str == nullptr) { - return OSCONTAINER_ERROR; + return false; } // Unlimited memory in cgroups is the literal string 'max' for // some controllers, for example the pids controller. if (strcmp("max", limit_str) == 0) { - return (jlong)-1; + value = value_unlimited; + return true; } - julong limit; - if (sscanf(limit_str, JULONG_FORMAT, &limit) != 1) { - return OSCONTAINER_ERROR; + uint64_t limit; + if (sscanf(limit_str, UINT64_FORMAT, &limit) != 1) { + return false; } - return (jlong)limit; + value = limit; + return true; } // CgroupSubsystem implementations - -jlong CgroupSubsystem::memory_and_swap_limit_in_bytes(julong upper_mem_bound, julong upper_swap_bound) { - return memory_controller()->controller()->memory_and_swap_limit_in_bytes(upper_mem_bound, upper_swap_bound); +bool CgroupSubsystem::memory_and_swap_limit_in_bytes(physical_memory_size_type upper_mem_bound, + physical_memory_size_type upper_swap_bound, + physical_memory_size_type& value) { + return memory_controller()->controller()->memory_and_swap_limit_in_bytes(upper_mem_bound, + upper_swap_bound, + value); } -jlong CgroupSubsystem::memory_and_swap_usage_in_bytes(julong upper_mem_bound, julong upper_swap_bound) { - return memory_controller()->controller()->memory_and_swap_usage_in_bytes(upper_mem_bound, upper_swap_bound); +bool CgroupSubsystem::memory_and_swap_usage_in_bytes(physical_memory_size_type upper_mem_bound, + physical_memory_size_type upper_swap_bound, + physical_memory_size_type& value) { + return memory_controller()->controller()->memory_and_swap_usage_in_bytes(upper_mem_bound, + upper_swap_bound, + value); } -jlong CgroupSubsystem::memory_soft_limit_in_bytes(julong upper_bound) { - return memory_controller()->controller()->memory_soft_limit_in_bytes(upper_bound); +bool CgroupSubsystem::memory_soft_limit_in_bytes(physical_memory_size_type upper_bound, + physical_memory_size_type& value) { + return memory_controller()->controller()->memory_soft_limit_in_bytes(upper_bound, value); } -jlong CgroupSubsystem::memory_throttle_limit_in_bytes() { - return memory_controller()->controller()->memory_throttle_limit_in_bytes(); +bool CgroupSubsystem::memory_throttle_limit_in_bytes(physical_memory_size_type& value) { + return memory_controller()->controller()->memory_throttle_limit_in_bytes(value); } -jlong CgroupSubsystem::memory_usage_in_bytes() { - return memory_controller()->controller()->memory_usage_in_bytes(); +bool CgroupSubsystem::memory_usage_in_bytes(physical_memory_size_type& value) { + return memory_controller()->controller()->memory_usage_in_bytes(value); } -jlong CgroupSubsystem::memory_max_usage_in_bytes() { - return memory_controller()->controller()->memory_max_usage_in_bytes(); +bool CgroupSubsystem::memory_max_usage_in_bytes(physical_memory_size_type& value) { + return memory_controller()->controller()->memory_max_usage_in_bytes(value); } -jlong CgroupSubsystem::rss_usage_in_bytes() { - return memory_controller()->controller()->rss_usage_in_bytes(); +bool CgroupSubsystem::rss_usage_in_bytes(physical_memory_size_type& value) { + return memory_controller()->controller()->rss_usage_in_bytes(value); } -jlong CgroupSubsystem::cache_usage_in_bytes() { - return memory_controller()->controller()->cache_usage_in_bytes(); +bool CgroupSubsystem::cache_usage_in_bytes(physical_memory_size_type& value) { + return memory_controller()->controller()->cache_usage_in_bytes(value); } -int CgroupSubsystem::cpu_quota() { - return cpu_controller()->controller()->cpu_quota(); +bool CgroupSubsystem::cpu_quota(int& value) { + return cpu_controller()->controller()->cpu_quota(value); } -int CgroupSubsystem::cpu_period() { - return cpu_controller()->controller()->cpu_period(); +bool CgroupSubsystem::cpu_period(int& value) { + return cpu_controller()->controller()->cpu_period(value); } -int CgroupSubsystem::cpu_shares() { - return cpu_controller()->controller()->cpu_shares(); +bool CgroupSubsystem::cpu_shares(int& value) { + return cpu_controller()->controller()->cpu_shares(value); } -jlong CgroupSubsystem::cpu_usage_in_micros() { - return cpuacct_controller()->cpu_usage_in_micros(); +bool CgroupSubsystem::cpu_usage_in_micros(uint64_t& value) { + return cpuacct_controller()->cpu_usage_in_micros(value); } -void CgroupSubsystem::print_version_specific_info(outputStream* st, julong upper_mem_bound) { +void CgroupSubsystem::print_version_specific_info(outputStream* st, physical_memory_size_type upper_mem_bound) { memory_controller()->controller()->print_version_specific_info(st, upper_mem_bound); } diff --git a/src/hotspot/os/linux/cgroupSubsystem_linux.hpp b/src/hotspot/os/linux/cgroupSubsystem_linux.hpp index bf0ad03fc56..522b64f8816 100644 --- a/src/hotspot/os/linux/cgroupSubsystem_linux.hpp +++ b/src/hotspot/os/linux/cgroupSubsystem_linux.hpp @@ -72,23 +72,29 @@ #define CONTAINER_READ_NUMBER_CHECKED(controller, filename, log_string, retval) \ { \ bool is_ok; \ - is_ok = controller->read_number(filename, &retval); \ + is_ok = controller->read_number(filename, retval); \ if (!is_ok) { \ - log_trace(os, container)(log_string " failed: %d", OSCONTAINER_ERROR); \ - return OSCONTAINER_ERROR; \ + log_trace(os, container)(log_string " failed"); \ + return false; \ } \ - log_trace(os, container)(log_string " is: " JULONG_FORMAT, retval); \ + log_trace(os, container)(log_string " is: " UINT64_FORMAT, retval); \ + return true; \ } #define CONTAINER_READ_NUMBER_CHECKED_MAX(controller, filename, log_string, retval) \ { \ bool is_ok; \ - is_ok = controller->read_number_handle_max(filename, &retval); \ + is_ok = controller->read_number_handle_max(filename, retval); \ if (!is_ok) { \ - log_trace(os, container)(log_string " failed: %d", OSCONTAINER_ERROR); \ - return OSCONTAINER_ERROR; \ + log_trace(os, container)(log_string " failed"); \ + return false; \ } \ - log_trace(os, container)(log_string " is: " JLONG_FORMAT, retval); \ + if (retval == value_unlimited) { \ + log_trace(os, container)(log_string " is: unlimited"); \ + } else { \ + log_trace(os, container)(log_string " is: " UINT64_FORMAT, retval); \ + } \ + return true; \ } #define CONTAINER_READ_STRING_CHECKED(controller, filename, log_string, retval, buf_size) \ @@ -96,7 +102,7 @@ bool is_ok; \ is_ok = controller->read_string(filename, retval, buf_size); \ if (!is_ok) { \ - log_trace(os, container)(log_string " failed: %d", OSCONTAINER_ERROR); \ + log_trace(os, container)(log_string " failed"); \ return nullptr; \ } \ log_trace(os, container)(log_string " is: %s", retval); \ @@ -105,12 +111,13 @@ #define CONTAINER_READ_NUMERICAL_KEY_VALUE_CHECKED(controller, filename, key, log_string, retval) \ { \ bool is_ok; \ - is_ok = controller->read_numerical_key_value(filename, key, &retval); \ + is_ok = controller->read_numerical_key_value(filename, key, retval); \ if (!is_ok) { \ - log_trace(os, container)(log_string " failed: %d", OSCONTAINER_ERROR); \ - return OSCONTAINER_ERROR; \ + log_trace(os, container)(log_string " failed"); \ + return false; \ } \ - log_trace(os, container)(log_string " is: " JULONG_FORMAT, retval); \ + log_trace(os, container)(log_string " is: " UINT64_FORMAT, retval); \ + return true; \ } class CgroupController: public CHeapObj { @@ -124,21 +131,22 @@ class CgroupController: public CHeapObj { const char* mount_point() { return _mount_point; } virtual bool needs_hierarchy_adjustment() { return false; } - /* Read a numerical value as unsigned long + /* Read a numerical value as uint64_t * * returns: false if any error occurred. true otherwise and - * the parsed value is set in the provided julong pointer. + * the parsed value is set in the provided result reference. */ - bool read_number(const char* filename, julong* result); + bool read_number(const char* filename, uint64_t& result); /* Convenience method to deal with numbers as well as the string 'max' * in interface files. Otherwise same as read_number(). * * returns: false if any error occurred. true otherwise and - * the parsed value (which might be negative) is being set in - * the provided jlong pointer. + * the parsed value will be set in the provided result reference. + * When the value was the string 'max' then 'value_unlimited' is + * being set as the value. */ - bool read_number_handle_max(const char* filename, jlong* result); + bool read_number_handle_max(const char* filename, uint64_t& result); /* Read a string of at most buf_size - 1 characters from the interface file. * The provided buffer must be at least buf_size in size so as to account @@ -156,37 +164,37 @@ class CgroupController: public CHeapObj { * parsing interface files like cpu.max which contain such tuples. * * returns: false if any error occurred. true otherwise and the parsed - * value of the appropriate tuple entry set in the provided jlong pointer. + * value of the appropriate tuple entry set in the provided result reference. */ - bool read_numerical_tuple_value(const char* filename, bool use_first, jlong* result); + bool read_numerical_tuple_value(const char* filename, bool use_first, uint64_t& result); /* Read a numerical value from a multi-line interface file. The matched line is * determined by the provided 'key'. The associated numerical value is being set - * via the passed in julong pointer. Example interface file 'memory.stat' + * via the passed in result reference. Example interface file 'memory.stat' * * returns: false if any error occurred. true otherwise and the parsed value is - * being set in the provided julong pointer. + * being set in the provided result reference. */ - bool read_numerical_key_value(const char* filename, const char* key, julong* result); + bool read_numerical_key_value(const char* filename, const char* key, uint64_t& result); private: - static jlong limit_from_str(char* limit_str); + static bool limit_from_str(char* limit_str, physical_memory_size_type& value); }; class CachedMetric : public CHeapObj{ private: - volatile jlong _metric; + volatile physical_memory_size_type _metric; volatile jlong _next_check_counter; public: CachedMetric() { - _metric = -1; + _metric = value_unlimited; _next_check_counter = min_jlong; } bool should_check_metric() { return os::elapsed_counter() > _next_check_counter; } - jlong value() { return _metric; } - void set_value(jlong value, jlong timeout) { + physical_memory_size_type value() { return _metric; } + void set_value(physical_memory_size_type value, jlong timeout) { _metric = value; // Metric is unlikely to change, but we want to remain // responsive to configuration changes. A very short grace time @@ -216,9 +224,9 @@ class CachingCgroupController : public CHeapObj { // Pure virtual class representing version agnostic CPU controllers class CgroupCpuController: public CHeapObj { public: - virtual int cpu_quota() = 0; - virtual int cpu_period() = 0; - virtual int cpu_shares() = 0; + virtual bool cpu_quota(int& value) = 0; + virtual bool cpu_period(int& value) = 0; + virtual bool cpu_shares(int& value) = 0; virtual bool needs_hierarchy_adjustment() = 0; virtual bool is_read_only() = 0; virtual const char* subsystem_path() = 0; @@ -230,7 +238,7 @@ class CgroupCpuController: public CHeapObj { // Pure virtual class representing version agnostic CPU accounting controllers class CgroupCpuacctController: public CHeapObj { public: - virtual jlong cpu_usage_in_micros() = 0; + virtual bool cpu_usage_in_micros(uint64_t& value) = 0; virtual bool needs_hierarchy_adjustment() = 0; virtual bool is_read_only() = 0; virtual const char* subsystem_path() = 0; @@ -242,16 +250,22 @@ class CgroupCpuacctController: public CHeapObj { // Pure virtual class representing version agnostic memory controllers class CgroupMemoryController: public CHeapObj { public: - virtual jlong read_memory_limit_in_bytes(julong upper_bound) = 0; - virtual jlong memory_usage_in_bytes() = 0; - virtual jlong memory_and_swap_limit_in_bytes(julong upper_mem_bound, julong upper_swap_bound) = 0; - virtual jlong memory_and_swap_usage_in_bytes(julong upper_mem_bound, julong upper_swap_bound) = 0; - virtual jlong memory_soft_limit_in_bytes(julong upper_bound) = 0; - virtual jlong memory_throttle_limit_in_bytes() = 0; - virtual jlong memory_max_usage_in_bytes() = 0; - virtual jlong rss_usage_in_bytes() = 0; - virtual jlong cache_usage_in_bytes() = 0; - virtual void print_version_specific_info(outputStream* st, julong upper_mem_bound) = 0; + virtual bool read_memory_limit_in_bytes(physical_memory_size_type upper_bound, + physical_memory_size_type& value) = 0; + virtual bool memory_usage_in_bytes(physical_memory_size_type& value) = 0; + virtual bool memory_and_swap_limit_in_bytes(physical_memory_size_type upper_mem_bound, + physical_memory_size_type upper_swap_bound, + physical_memory_size_type& value) = 0; + virtual bool memory_and_swap_usage_in_bytes(physical_memory_size_type upper_mem_bound, + physical_memory_size_type upper_swap_bound, + physical_memory_size_type& value) = 0; + virtual bool memory_soft_limit_in_bytes(physical_memory_size_type upper_bound, + physical_memory_size_type& value) = 0; + virtual bool memory_throttle_limit_in_bytes(physical_memory_size_type& value) = 0; + virtual bool memory_max_usage_in_bytes(physical_memory_size_type& value) = 0; + virtual bool rss_usage_in_bytes(physical_memory_size_type& value) = 0; + virtual bool cache_usage_in_bytes(physical_memory_size_type& value) = 0; + virtual void print_version_specific_info(outputStream* st, physical_memory_size_type upper_mem_bound) = 0; virtual bool needs_hierarchy_adjustment() = 0; virtual bool is_read_only() = 0; virtual const char* subsystem_path() = 0; @@ -262,11 +276,11 @@ class CgroupMemoryController: public CHeapObj { class CgroupSubsystem: public CHeapObj { public: - jlong memory_limit_in_bytes(julong upper_bound); - int active_processor_count(); + bool memory_limit_in_bytes(physical_memory_size_type upper_bound, physical_memory_size_type& value); + bool active_processor_count(int& value); - virtual jlong pids_max() = 0; - virtual jlong pids_current() = 0; + virtual bool pids_max(uint64_t& value) = 0; + virtual bool pids_current(uint64_t& value) = 0; virtual bool is_containerized() = 0; virtual char * cpu_cpuset_cpus() = 0; @@ -276,21 +290,26 @@ class CgroupSubsystem: public CHeapObj { virtual CachingCgroupController* cpu_controller() = 0; virtual CgroupCpuacctController* cpuacct_controller() = 0; - int cpu_quota(); - int cpu_period(); - int cpu_shares(); + bool cpu_quota(int& value); + bool cpu_period(int& value); + bool cpu_shares(int& value); - jlong cpu_usage_in_micros(); + bool cpu_usage_in_micros(uint64_t& value); - jlong memory_usage_in_bytes(); - jlong memory_and_swap_limit_in_bytes(julong upper_mem_bound, julong upper_swap_bound); - jlong memory_and_swap_usage_in_bytes(julong upper_mem_bound, julong upper_swap_bound); - jlong memory_soft_limit_in_bytes(julong upper_bound); - jlong memory_throttle_limit_in_bytes(); - jlong memory_max_usage_in_bytes(); - jlong rss_usage_in_bytes(); - jlong cache_usage_in_bytes(); - void print_version_specific_info(outputStream* st, julong upper_mem_bound); + bool memory_usage_in_bytes(physical_memory_size_type& value); + bool memory_and_swap_limit_in_bytes(physical_memory_size_type upper_mem_bound, + physical_memory_size_type upper_swap_bound, + physical_memory_size_type& value); + bool memory_and_swap_usage_in_bytes(physical_memory_size_type upper_mem_bound, + physical_memory_size_type upper_swap_bound, + physical_memory_size_type& value); + bool memory_soft_limit_in_bytes(physical_memory_size_type upper_bound, + physical_memory_size_type& value); + bool memory_throttle_limit_in_bytes(physical_memory_size_type& value); + bool memory_max_usage_in_bytes(physical_memory_size_type& value); + bool rss_usage_in_bytes(physical_memory_size_type& value); + bool cache_usage_in_bytes(physical_memory_size_type& value); + void print_version_specific_info(outputStream* st, physical_memory_size_type upper_mem_bound); }; // Utility class for storing info retrieved from /proc/cgroups, diff --git a/src/hotspot/os/linux/cgroupUtil_linux.cpp b/src/hotspot/os/linux/cgroupUtil_linux.cpp index de027db812a..7aa07d53148 100644 --- a/src/hotspot/os/linux/cgroupUtil_linux.cpp +++ b/src/hotspot/os/linux/cgroupUtil_linux.cpp @@ -25,13 +25,19 @@ #include "cgroupUtil_linux.hpp" #include "os_linux.hpp" -int CgroupUtil::processor_count(CgroupCpuController* cpu_ctrl, int host_cpus) { - assert(host_cpus > 0, "physical host cpus must be positive"); - int limit_count = host_cpus; - int quota = cpu_ctrl->cpu_quota(); - int period = cpu_ctrl->cpu_period(); +bool CgroupUtil::processor_count(CgroupCpuController* cpu_ctrl, int upper_bound, int& value) { + assert(upper_bound > 0, "upper bound of cpus must be positive"); + int limit_count = upper_bound; + int quota = -1; + int period = -1; + if (!cpu_ctrl->cpu_quota(quota)) { + return false; + } + if (!cpu_ctrl->cpu_period(period)) { + return false; + } int quota_count = 0; - int result = 0; + int result = upper_bound; if (quota > -1 && period > 0) { quota_count = ceilf((float)quota / (float)period); @@ -43,16 +49,50 @@ int CgroupUtil::processor_count(CgroupCpuController* cpu_ctrl, int host_cpus) { limit_count = quota_count; } - result = MIN2(host_cpus, limit_count); + result = MIN2(upper_bound, limit_count); log_trace(os, container)("OSContainer::active_processor_count: %d", result); - return result; + value = result; + return true; +} + +// Get an updated memory limit. The return value is strictly less than or equal to the +// passed in 'lowest' value. +physical_memory_size_type CgroupUtil::get_updated_mem_limit(CgroupMemoryController* mem, + physical_memory_size_type lowest, + physical_memory_size_type upper_bound) { + assert(lowest <= upper_bound, "invariant"); + physical_memory_size_type current_limit = value_unlimited; + if (mem->read_memory_limit_in_bytes(upper_bound, current_limit) && current_limit != value_unlimited) { + assert(current_limit <= upper_bound, "invariant"); + if (lowest > current_limit) { + return current_limit; + } + } + return lowest; +} + +// Get an updated cpu limit. The return value is strictly less than or equal to the +// passed in 'lowest' value. +int CgroupUtil::get_updated_cpu_limit(CgroupCpuController* cpu, + int lowest, + int upper_bound) { + assert(lowest > 0 && lowest <= upper_bound, "invariant"); + int cpu_limit_val = -1; + if (CgroupUtil::processor_count(cpu, upper_bound, cpu_limit_val) && cpu_limit_val != upper_bound) { + assert(cpu_limit_val <= upper_bound, "invariant"); + if (lowest > cpu_limit_val) { + return cpu_limit_val; + } + } + return lowest; } void CgroupUtil::adjust_controller(CgroupMemoryController* mem) { assert(mem->cgroup_path() != nullptr, "invariant"); if (strstr(mem->cgroup_path(), "../") != nullptr) { - log_warning(os, container)("Cgroup memory controller path at '%s' seems to have moved to '%s', detected limits won't be accurate", - mem->mount_point(), mem->cgroup_path()); + log_warning(os, container)("Cgroup memory controller path at '%s' seems to have moved " + "to '%s'. Detected limits won't be accurate", + mem->mount_point(), mem->cgroup_path()); mem->set_subsystem_path("/"); return; } @@ -65,17 +105,18 @@ void CgroupUtil::adjust_controller(CgroupMemoryController* mem) { char* cg_path = os::strdup(orig); char* last_slash; assert(cg_path[0] == '/', "cgroup path must start with '/'"); - julong phys_mem = static_cast(os::Linux::physical_memory()); + physical_memory_size_type phys_mem = os::Linux::physical_memory(); char* limit_cg_path = nullptr; - jlong limit = mem->read_memory_limit_in_bytes(phys_mem); - jlong lowest_limit = limit < 0 ? phys_mem : limit; - julong orig_limit = ((julong)lowest_limit) != phys_mem ? lowest_limit : phys_mem; + physical_memory_size_type limit = value_unlimited; + physical_memory_size_type lowest_limit = phys_mem; + lowest_limit = get_updated_mem_limit(mem, lowest_limit, phys_mem); + physical_memory_size_type orig_limit = lowest_limit != phys_mem ? lowest_limit : phys_mem; while ((last_slash = strrchr(cg_path, '/')) != cg_path) { *last_slash = '\0'; // strip path // update to shortened path and try again mem->set_subsystem_path(cg_path); - limit = mem->read_memory_limit_in_bytes(phys_mem); - if (limit >= 0 && limit < lowest_limit) { + limit = get_updated_mem_limit(mem, lowest_limit, phys_mem); + if (limit < lowest_limit) { lowest_limit = limit; os::free(limit_cg_path); // handles nullptr limit_cg_path = os::strdup(cg_path); @@ -83,24 +124,24 @@ void CgroupUtil::adjust_controller(CgroupMemoryController* mem) { } // need to check limit at mount point mem->set_subsystem_path("/"); - limit = mem->read_memory_limit_in_bytes(phys_mem); - if (limit >= 0 && limit < lowest_limit) { + limit = get_updated_mem_limit(mem, lowest_limit, phys_mem); + if (limit < lowest_limit) { lowest_limit = limit; os::free(limit_cg_path); // handles nullptr limit_cg_path = os::strdup("/"); } - assert(lowest_limit >= 0, "limit must be positive"); - if ((julong)lowest_limit != orig_limit) { + assert(lowest_limit <= phys_mem, "limit must not exceed host memory"); + if (lowest_limit != orig_limit) { // we've found a lower limit anywhere in the hierarchy, // set the path to the limit path assert(limit_cg_path != nullptr, "limit path must be set"); mem->set_subsystem_path(limit_cg_path); log_trace(os, container)("Adjusted controller path for memory to: %s. " - "Lowest limit was: " JLONG_FORMAT, + "Lowest limit was: " PHYS_MEM_TYPE_FORMAT, mem->subsystem_path(), lowest_limit); } else { - log_trace(os, container)("Lowest limit was: " JLONG_FORMAT, lowest_limit); + log_trace(os, container)("Lowest limit was: " PHYS_MEM_TYPE_FORMAT, lowest_limit); log_trace(os, container)("No lower limit found for memory in hierarchy %s, " "adjusting to original path %s", mem->mount_point(), orig); @@ -114,8 +155,9 @@ void CgroupUtil::adjust_controller(CgroupMemoryController* mem) { void CgroupUtil::adjust_controller(CgroupCpuController* cpu) { assert(cpu->cgroup_path() != nullptr, "invariant"); if (strstr(cpu->cgroup_path(), "../") != nullptr) { - log_warning(os, container)("Cgroup cpu controller path at '%s' seems to have moved to '%s', detected limits won't be accurate", - cpu->mount_point(), cpu->cgroup_path()); + log_warning(os, container)("Cgroup cpu controller path at '%s' seems to have moved " + "to '%s'. Detected limits won't be accurate", + cpu->mount_point(), cpu->cgroup_path()); cpu->set_subsystem_path("/"); return; } @@ -129,15 +171,15 @@ void CgroupUtil::adjust_controller(CgroupCpuController* cpu) { char* last_slash; assert(cg_path[0] == '/', "cgroup path must start with '/'"); int host_cpus = os::Linux::active_processor_count(); - int cpus = CgroupUtil::processor_count(cpu, host_cpus); - int lowest_limit = cpus < host_cpus ? cpus: host_cpus; + int lowest_limit = host_cpus; + int cpus = get_updated_cpu_limit(cpu, lowest_limit, host_cpus); int orig_limit = lowest_limit != host_cpus ? lowest_limit : host_cpus; char* limit_cg_path = nullptr; while ((last_slash = strrchr(cg_path, '/')) != cg_path) { *last_slash = '\0'; // strip path // update to shortened path and try again cpu->set_subsystem_path(cg_path); - cpus = CgroupUtil::processor_count(cpu, host_cpus); + cpus = get_updated_cpu_limit(cpu, lowest_limit, host_cpus); if (cpus != host_cpus && cpus < lowest_limit) { lowest_limit = cpus; os::free(limit_cg_path); // handles nullptr @@ -146,7 +188,7 @@ void CgroupUtil::adjust_controller(CgroupCpuController* cpu) { } // need to check limit at mount point cpu->set_subsystem_path("/"); - cpus = CgroupUtil::processor_count(cpu, host_cpus); + cpus = get_updated_cpu_limit(cpu, lowest_limit, host_cpus); if (cpus != host_cpus && cpus < lowest_limit) { lowest_limit = cpus; os::free(limit_cg_path); // handles nullptr @@ -160,8 +202,7 @@ void CgroupUtil::adjust_controller(CgroupCpuController* cpu) { cpu->set_subsystem_path(limit_cg_path); log_trace(os, container)("Adjusted controller path for cpu to: %s. " "Lowest limit was: %d", - cpu->subsystem_path(), - lowest_limit); + cpu->subsystem_path(), lowest_limit); } else { log_trace(os, container)("Lowest limit was: %d", lowest_limit); log_trace(os, container)("No lower limit found for cpu in hierarchy %s, " diff --git a/src/hotspot/os/linux/cgroupUtil_linux.hpp b/src/hotspot/os/linux/cgroupUtil_linux.hpp index aa63a7457cc..d72bbd1cf1e 100644 --- a/src/hotspot/os/linux/cgroupUtil_linux.hpp +++ b/src/hotspot/os/linux/cgroupUtil_linux.hpp @@ -31,13 +31,20 @@ class CgroupUtil: AllStatic { public: - static int processor_count(CgroupCpuController* cpu, int host_cpus); + static bool processor_count(CgroupCpuController* cpu, int upper_bound, int& value); // Given a memory controller, adjust its path to a point in the hierarchy // that represents the closest memory limit. static void adjust_controller(CgroupMemoryController* m); // Given a cpu controller, adjust its path to a point in the hierarchy // that represents the closest cpu limit. static void adjust_controller(CgroupCpuController* c); + private: + static physical_memory_size_type get_updated_mem_limit(CgroupMemoryController* m, + physical_memory_size_type lowest, + physical_memory_size_type upper_bound); + static int get_updated_cpu_limit(CgroupCpuController* c, + int lowest, + int upper_bound); }; #endif // CGROUP_UTIL_LINUX_HPP diff --git a/src/hotspot/os/linux/cgroupV1Subsystem_linux.cpp b/src/hotspot/os/linux/cgroupV1Subsystem_linux.cpp index 87870e647eb..ddcb0db2161 100644 --- a/src/hotspot/os/linux/cgroupV1Subsystem_linux.cpp +++ b/src/hotspot/os/linux/cgroupV1Subsystem_linux.cpp @@ -124,10 +124,13 @@ void CgroupV1Controller::set_subsystem_path(const char* cgroup_path) { } } -jlong CgroupV1MemoryController::uses_mem_hierarchy() { - julong use_hierarchy; - CONTAINER_READ_NUMBER_CHECKED(reader(), "/memory.use_hierarchy", "Use Hierarchy", use_hierarchy); - return (jlong)use_hierarchy; +bool CgroupV1MemoryController::read_use_hierarchy_val(physical_memory_size_type& result) { + CONTAINER_READ_NUMBER_CHECKED(reader(), "/memory.use_hierarchy", "Use Hierarchy", result); +} + +bool CgroupV1MemoryController::uses_mem_hierarchy() { + physical_memory_size_type use_hierarchy = 0; + return read_use_hierarchy_val(use_hierarchy) && use_hierarchy > 0; } /* @@ -141,125 +144,177 @@ bool CgroupV1Controller::needs_hierarchy_adjustment() { return strcmp(_root, _cgroup_path) != 0; } -static inline -void verbose_log(julong read_mem_limit, julong upper_mem_bound) { - if (log_is_enabled(Debug, os, container)) { - jlong mem_limit = (jlong)read_mem_limit; // account for negative values - if (mem_limit < 0 || read_mem_limit >= upper_mem_bound) { - const char *reason; - if (mem_limit == OSCONTAINER_ERROR) { - reason = "failed"; - } else if (mem_limit == -1) { - reason = "unlimited"; - } else { - assert(read_mem_limit >= upper_mem_bound, "Expected read value exceeding upper memory bound"); - // Exceeding physical memory is treated as unlimited. This implementation - // caps it at host_mem since Cg v1 has no value to represent 'max'. - reason = "ignored"; - } - log_debug(os, container)("container memory limit %s: " JLONG_FORMAT ", upper bound is " JLONG_FORMAT, - reason, mem_limit, upper_mem_bound); +bool CgroupV1MemoryController::read_memory_limit_val(physical_memory_size_type& result) { + CONTAINER_READ_NUMBER_CHECKED(reader(), "/memory.limit_in_bytes", "Memory Limit", result); +} + +bool CgroupV1MemoryController::read_hierarchical_memory_limit_val(physical_memory_size_type& result) { + CONTAINER_READ_NUMERICAL_KEY_VALUE_CHECKED(reader(), "/memory.stat", + "hierarchical_memory_limit", "Hierarchical Memory Limit", + result); +} + +bool CgroupV1MemoryController::read_memory_limit_in_bytes(physical_memory_size_type upper_bound, + physical_memory_size_type& result) { + physical_memory_size_type memlimit = 0; + if (!read_memory_limit_val(memlimit)) { + log_trace(os, container)("container memory limit failed, upper bound is " PHYS_MEM_TYPE_FORMAT, upper_bound); + return false; + } + if (memlimit >= upper_bound) { + physical_memory_size_type hierlimit = 0; + if (uses_mem_hierarchy() && read_hierarchical_memory_limit_val(hierlimit) && + hierlimit < upper_bound) { + log_trace(os, container)("Memory Limit is: " PHYS_MEM_TYPE_FORMAT, hierlimit); + result = hierlimit; + } else { + // Exceeding physical memory is treated as unlimited. This implementation + // caps it at host_mem since Cg v1 has no value to represent 'max'. + log_trace(os, container)("container memory limit ignored: " PHYS_MEM_TYPE_FORMAT + ", upper bound is " PHYS_MEM_TYPE_FORMAT, memlimit, upper_bound); + result = value_unlimited; } + } else { + result = memlimit; } + return true; } -jlong CgroupV1MemoryController::read_memory_limit_in_bytes(julong upper_bound) { - julong memlimit; - CONTAINER_READ_NUMBER_CHECKED(reader(), "/memory.limit_in_bytes", "Memory Limit", memlimit); - if (memlimit >= upper_bound && uses_mem_hierarchy()) { - CONTAINER_READ_NUMERICAL_KEY_VALUE_CHECKED(reader(), "/memory.stat", - "hierarchical_memory_limit", "Hierarchical Memory Limit", - memlimit); - } - verbose_log(memlimit, upper_bound); - return (jlong)((memlimit < upper_bound) ? memlimit : -1); +bool CgroupV1MemoryController::read_mem_swap(physical_memory_size_type& result) { + CONTAINER_READ_NUMBER_CHECKED(reader(), "/memory.memsw.limit_in_bytes", "Memory and Swap Limit", result); } -/* read_mem_swap +bool CgroupV1MemoryController::read_hierarchical_mem_swap_val(physical_memory_size_type& result) { + CONTAINER_READ_NUMERICAL_KEY_VALUE_CHECKED(reader(), "/memory.stat", + "hierarchical_memsw_limit", "Hierarchical Memory and Swap Limit", + result); +} + +/* memory_and_swap_limit_in_bytes * - * Determine the memory and swap limit metric. Returns a positive limit value strictly - * lower than the physical memory and swap limit iff there is a limit. Otherwise a - * negative value is returned indicating the determined status. + * Determine the memory and swap limit metric. Sets the 'result' reference to a positive limit value or + * 'value_unlimited' (for unlimited). * * returns: - * * A number > 0 if the limit is available and lower than a physical upper bound. - * * OSCONTAINER_ERROR if the limit cannot be retrieved (i.e. not supported) or - * * -1 if there isn't any limit in place (note: includes values which exceed a physical - * upper bound) + * * false if an error occurred. 'result' reference remains unchanged. + * * true if the limit value has been set in the 'result' reference */ -jlong CgroupV1MemoryController::read_mem_swap(julong upper_memsw_bound) { - julong memswlimit; - CONTAINER_READ_NUMBER_CHECKED(reader(), "/memory.memsw.limit_in_bytes", "Memory and Swap Limit", memswlimit); - if (memswlimit >= upper_memsw_bound && uses_mem_hierarchy()) { - CONTAINER_READ_NUMERICAL_KEY_VALUE_CHECKED(reader(), "/memory.stat", - "hierarchical_memsw_limit", "Hierarchical Memory and Swap Limit", - memswlimit); +bool CgroupV1MemoryController::memory_and_swap_limit_in_bytes(physical_memory_size_type upper_mem_bound, + physical_memory_size_type upper_swap_bound, + physical_memory_size_type& result) { + physical_memory_size_type total_mem_swap = upper_mem_bound + upper_swap_bound; + physical_memory_size_type memory_swap = 0; + bool mem_swap_read_failed = false; + if (!read_mem_swap(memory_swap)) { + mem_swap_read_failed = true; + } + if (memory_swap >= total_mem_swap) { + physical_memory_size_type hiermswlimit = 0; + if (uses_mem_hierarchy() && read_hierarchical_mem_swap_val(hiermswlimit) && + hiermswlimit < total_mem_swap) { + log_trace(os, container)("Memory and Swap Limit is: " PHYS_MEM_TYPE_FORMAT, hiermswlimit); + memory_swap = hiermswlimit; + } else { + memory_swap = value_unlimited; + } + } + if (memory_swap == value_unlimited) { + log_trace(os, container)("Memory and Swap Limit is: Unlimited"); + result = value_unlimited; + return true; } - verbose_log(memswlimit, upper_memsw_bound); - return (jlong)((memswlimit < upper_memsw_bound) ? memswlimit : -1); -} -jlong CgroupV1MemoryController::memory_and_swap_limit_in_bytes(julong upper_mem_bound, julong upper_swap_bound) { - jlong memory_swap = read_mem_swap(upper_mem_bound + upper_swap_bound); - if (memory_swap == -1) { - return memory_swap; - } // If there is a swap limit, but swappiness == 0, reset the limit // to the memory limit. Do the same for cases where swap isn't // supported. - jlong swappiness = read_mem_swappiness(); - if (swappiness == 0 || memory_swap == OSCONTAINER_ERROR) { - jlong memlimit = read_memory_limit_in_bytes(upper_mem_bound); - if (memory_swap == OSCONTAINER_ERROR) { - log_trace(os, container)("Memory and Swap Limit has been reset to " JLONG_FORMAT " because swap is not supported", memlimit); - } else { - log_trace(os, container)("Memory and Swap Limit has been reset to " JLONG_FORMAT " because swappiness is 0", memlimit); - } - return memlimit; + physical_memory_size_type swappiness = 0; + if (!read_mem_swappiness(swappiness)) { + // assume no swap + mem_swap_read_failed = true; } - return memory_swap; + if (swappiness == 0 || mem_swap_read_failed) { + physical_memory_size_type memlimit = value_unlimited; + if (!read_memory_limit_in_bytes(upper_mem_bound, memlimit)) { + return false; + } + if (memlimit == value_unlimited) { + result = value_unlimited; // No memory limit, thus no swap limit + return true; + } + if (mem_swap_read_failed) { + log_trace(os, container)("Memory and Swap Limit has been reset to " PHYS_MEM_TYPE_FORMAT + " because swap is not supported", memlimit); + } else { + log_trace(os, container)("Memory and Swap Limit has been reset to " PHYS_MEM_TYPE_FORMAT + " because swappiness is 0", memlimit); + } + result = memlimit; + return true; + } + result = memory_swap; + return true; } static inline -jlong memory_swap_usage_impl(CgroupController* ctrl) { - julong memory_swap_usage; - CONTAINER_READ_NUMBER_CHECKED(ctrl, "/memory.memsw.usage_in_bytes", "mem swap usage", memory_swap_usage); - return (jlong)memory_swap_usage; +bool memory_swap_usage_impl(CgroupController* ctrl, physical_memory_size_type& result) { + CONTAINER_READ_NUMBER_CHECKED(ctrl, "/memory.memsw.usage_in_bytes", "mem swap usage", result); } -jlong CgroupV1MemoryController::memory_and_swap_usage_in_bytes(julong upper_mem_bound, julong upper_swap_bound) { - jlong memory_sw_limit = memory_and_swap_limit_in_bytes(upper_mem_bound, upper_swap_bound); - jlong memory_limit = read_memory_limit_in_bytes(upper_mem_bound); - if (memory_sw_limit > 0 && memory_limit > 0) { - jlong delta_swap = memory_sw_limit - memory_limit; - if (delta_swap > 0) { - return memory_swap_usage_impl(reader()); +bool CgroupV1MemoryController::memory_and_swap_usage_in_bytes(physical_memory_size_type upper_mem_bound, + physical_memory_size_type upper_swap_bound, + physical_memory_size_type& result) { + physical_memory_size_type memory_sw_limit = value_unlimited; + if (!memory_and_swap_limit_in_bytes(upper_mem_bound, upper_swap_bound, memory_sw_limit)) { + return false; + } + physical_memory_size_type mem_limit_val = value_unlimited; + physical_memory_size_type memory_limit = value_unlimited; + if (read_memory_limit_in_bytes(upper_mem_bound, mem_limit_val)) { + if (mem_limit_val != value_unlimited) { + memory_limit = mem_limit_val; } } - return memory_usage_in_bytes(); -} - -jlong CgroupV1MemoryController::read_mem_swappiness() { - julong swappiness; - CONTAINER_READ_NUMBER_CHECKED(reader(), "/memory.swappiness", "Swappiness", swappiness); - return (jlong)swappiness; -} - -jlong CgroupV1MemoryController::memory_soft_limit_in_bytes(julong upper_bound) { - julong memsoftlimit; - CONTAINER_READ_NUMBER_CHECKED(reader(), "/memory.soft_limit_in_bytes", "Memory Soft Limit", memsoftlimit); - if (memsoftlimit >= upper_bound) { - log_trace(os, container)("Memory Soft Limit is: Unlimited"); - return (jlong)-1; - } else { - return (jlong)memsoftlimit; + if (memory_sw_limit != value_unlimited && memory_limit != value_unlimited) { + if (memory_limit < memory_sw_limit) { + // swap allowed and > 0 + physical_memory_size_type swap_usage = 0; + if (!memory_swap_usage_impl(reader(), swap_usage)) { + return false; + } + result = swap_usage; + return true; + } } + return memory_usage_in_bytes(result); } -jlong CgroupV1MemoryController::memory_throttle_limit_in_bytes() { +bool CgroupV1MemoryController::read_mem_swappiness(physical_memory_size_type& result) { + CONTAINER_READ_NUMBER_CHECKED(reader(), "/memory.swappiness", "Swappiness", result); +} + +bool CgroupV1MemoryController::memory_soft_limit_val(physical_memory_size_type& result) { + CONTAINER_READ_NUMBER_CHECKED(reader(), "/memory.soft_limit_in_bytes", "Memory Soft Limit", result); +} + +bool CgroupV1MemoryController::memory_soft_limit_in_bytes(physical_memory_size_type upper_bound, + physical_memory_size_type& result) { + physical_memory_size_type mem_soft_limit = 0; + if (!memory_soft_limit_val(mem_soft_limit)) { + return false; + } + if (mem_soft_limit >= upper_bound) { + log_trace(os, container)("Memory Soft Limit is: Unlimited"); + result = value_unlimited; + } else { + result = mem_soft_limit; + } + return true; +} + +bool CgroupV1MemoryController::memory_throttle_limit_in_bytes(physical_memory_size_type& result) { // Log this string at trace level so as to make tests happy. log_trace(os, container)("Memory Throttle Limit is not supported."); - return OSCONTAINER_ERROR; // not supported + return false; } // Constructor @@ -288,80 +343,129 @@ bool CgroupV1Subsystem::is_containerized() { _cpuset->is_read_only(); } -/* memory_usage_in_bytes +bool CgroupV1MemoryController::memory_usage_in_bytes(physical_memory_size_type& result) { + physical_memory_size_type memory_usage = 0; + if (!memory_usage_val(memory_usage)) { + return false; + } + result = memory_usage; + return true; +} + +/* memory_usage_val * - * Return the amount of used memory for this process. + * Read the amount of used memory for this process into the passed in reference 'result' * * return: - * memory usage in bytes or - * -1 for unlimited - * OSCONTAINER_ERROR for not supported + * true when reading of the file was successful and 'result' was set appropriately + * false when reading of the file failed */ -jlong CgroupV1MemoryController::memory_usage_in_bytes() { - julong memusage; - CONTAINER_READ_NUMBER_CHECKED(reader(), "/memory.usage_in_bytes", "Memory Usage", memusage); - return (jlong)memusage; +bool CgroupV1MemoryController::memory_usage_val(physical_memory_size_type& result) { + CONTAINER_READ_NUMBER_CHECKED(reader(), "/memory.usage_in_bytes", "Memory Usage", result); +} + +bool CgroupV1MemoryController::memory_max_usage_val(physical_memory_size_type& result) { + CONTAINER_READ_NUMBER_CHECKED(reader(), "/memory.max_usage_in_bytes", "Maximum Memory Usage", result); } /* memory_max_usage_in_bytes * - * Return the maximum amount of used memory for this process. + * Return the maximum amount of used memory for this process in the + * result reference. * * return: - * max memory usage in bytes or - * OSCONTAINER_ERROR for not supported + * true if the result reference has been set + * false otherwise (e.g. on error) */ -jlong CgroupV1MemoryController::memory_max_usage_in_bytes() { - julong memmaxusage; - CONTAINER_READ_NUMBER_CHECKED(reader(), "/memory.max_usage_in_bytes", "Maximum Memory Usage", memmaxusage); - return (jlong)memmaxusage; -} - -jlong CgroupV1MemoryController::rss_usage_in_bytes() { - julong rss; - bool is_ok = reader()->read_numerical_key_value("/memory.stat", "rss", &rss); - if (!is_ok) { - return OSCONTAINER_ERROR; +bool CgroupV1MemoryController::memory_max_usage_in_bytes(physical_memory_size_type& result) { + physical_memory_size_type memory_max_usage = 0; + if (!memory_max_usage_val(memory_max_usage)) { + return false; } - log_trace(os, container)("RSS usage is: " JULONG_FORMAT, rss); - return (jlong)rss; + result = memory_max_usage; + return true; } -jlong CgroupV1MemoryController::cache_usage_in_bytes() { - julong cache; - bool is_ok = reader()->read_numerical_key_value("/memory.stat", "cache", &cache); - if (!is_ok) { - return OSCONTAINER_ERROR; +bool CgroupV1MemoryController::rss_usage_in_bytes(physical_memory_size_type& result) { + physical_memory_size_type rss = 0; + + if (!reader()->read_numerical_key_value("/memory.stat", "rss", rss)) { + return false; } - log_trace(os, container)("Cache usage is: " JULONG_FORMAT, cache); - return cache; + log_trace(os, container)("RSS usage is: " PHYS_MEM_TYPE_FORMAT, rss); + result = rss; + return true; } -jlong CgroupV1MemoryController::kernel_memory_usage_in_bytes() { - julong kmem_usage; - CONTAINER_READ_NUMBER_CHECKED(reader(), "/memory.kmem.usage_in_bytes", "Kernel Memory Usage", kmem_usage); - return (jlong)kmem_usage; +bool CgroupV1MemoryController::cache_usage_in_bytes(physical_memory_size_type& result) { + physical_memory_size_type cache = 0; + if (!reader()->read_numerical_key_value("/memory.stat", "cache", cache)) { + return false; + } + log_trace(os, container)("Cache usage is: " PHYS_MEM_TYPE_FORMAT, cache); + result = cache; + return true; } -jlong CgroupV1MemoryController::kernel_memory_limit_in_bytes(julong upper_bound) { - julong kmem_limit; - CONTAINER_READ_NUMBER_CHECKED(reader(), "/memory.kmem.limit_in_bytes", "Kernel Memory Limit", kmem_limit); +bool CgroupV1MemoryController::kernel_memory_usage_val(physical_memory_size_type& result) { + CONTAINER_READ_NUMBER_CHECKED(reader(), "/memory.kmem.usage_in_bytes", "Kernel Memory Usage", result); +} + +bool CgroupV1MemoryController::kernel_memory_usage_in_bytes(physical_memory_size_type& result) { + physical_memory_size_type kmem_usage = 0; + if (!kernel_memory_usage_val(kmem_usage)) { + return false; + } + result = kmem_usage; + return true; +} + +bool CgroupV1MemoryController::kernel_memory_limit_val(physical_memory_size_type& result) { + CONTAINER_READ_NUMBER_CHECKED(reader(), "/memory.kmem.limit_in_bytes", "Kernel Memory Limit", result); +} + +bool CgroupV1MemoryController::kernel_memory_limit_in_bytes(physical_memory_size_type upper_bound, + physical_memory_size_type& result) { + physical_memory_size_type kmem_limit = 0; + if (!kernel_memory_limit_val(kmem_limit)) { + return false; + } if (kmem_limit >= upper_bound) { - return (jlong)-1; + kmem_limit = value_unlimited; } - return (jlong)kmem_limit; + result = kmem_limit; + return true; } -jlong CgroupV1MemoryController::kernel_memory_max_usage_in_bytes() { - julong kmem_max_usage; - CONTAINER_READ_NUMBER_CHECKED(reader(), "/memory.kmem.max_usage_in_bytes", "Maximum Kernel Memory Usage", kmem_max_usage); - return (jlong)kmem_max_usage; +bool CgroupV1MemoryController::kernel_memory_max_usage_val(physical_memory_size_type& result) { + CONTAINER_READ_NUMBER_CHECKED(reader(), "/memory.kmem.max_usage_in_bytes", "Maximum Kernel Memory Usage", result); } -void CgroupV1MemoryController::print_version_specific_info(outputStream* st, julong mem_bound) { - jlong kmem_usage = kernel_memory_usage_in_bytes(); - jlong kmem_limit = kernel_memory_limit_in_bytes(mem_bound); - jlong kmem_max_usage = kernel_memory_max_usage_in_bytes(); +bool CgroupV1MemoryController::kernel_memory_max_usage_in_bytes(physical_memory_size_type& result) { + physical_memory_size_type kmem_max_usage = 0; + if (!kernel_memory_max_usage_val(kmem_max_usage)) { + return false; + } + result = kmem_max_usage; + return true; +} + +void CgroupV1MemoryController::print_version_specific_info(outputStream* st, physical_memory_size_type mem_bound) { + MetricResult kmem_usage; + physical_memory_size_type temp = 0; + if (kernel_memory_usage_in_bytes(temp)) { + kmem_usage.set_value(temp); + } + MetricResult kmem_limit; + temp = value_unlimited; + if (kernel_memory_limit_in_bytes(mem_bound, temp)) { + kmem_limit.set_value(temp); + } + MetricResult kmem_max_usage; + temp = 0; + if (kernel_memory_max_usage_in_bytes(temp)) { + kmem_max_usage.set_value(temp); + } OSContainer::print_container_helper(st, kmem_limit, "kernel_memory_limit_in_bytes"); OSContainer::print_container_helper(st, kmem_usage, "kernel_memory_usage_in_bytes"); @@ -383,74 +487,114 @@ char* CgroupV1Subsystem::cpu_cpuset_memory_nodes() { /* cpu_quota * * Return the number of microseconds per period - * process is guaranteed to run. + * a process is guaranteed to run in the provided + * result reference. * * return: - * quota time in microseconds - * -1 for no quota - * OSCONTAINER_ERROR for not supported + * true if the value was set in the result reference + * false on failure to read the number from the file + * and the result reference has not been touched. */ -int CgroupV1CpuController::cpu_quota() { - julong quota; - bool is_ok = reader()->read_number("/cpu.cfs_quota_us", "a); - if (!is_ok) { - log_trace(os, container)("CPU Quota failed: %d", OSCONTAINER_ERROR); - return OSCONTAINER_ERROR; +bool CgroupV1CpuController::cpu_quota(int& result) { + uint64_t quota = 0; + + // intentionally not using the macro so as to not log a + // negative value as a large unsiged int + if (!reader()->read_number("/cpu.cfs_quota_us", quota)) { + log_trace(os, container)("CPU Quota failed"); + return false; } // cast to int since the read value might be negative // and we want to avoid logging -1 as a large unsigned value. - int quota_int = (int)quota; + int quota_int = static_cast(quota); log_trace(os, container)("CPU Quota is: %d", quota_int); - return quota_int; + result = quota_int; + return true; } -int CgroupV1CpuController::cpu_period() { - julong period; - CONTAINER_READ_NUMBER_CHECKED(reader(), "/cpu.cfs_period_us", "CPU Period", period); - return (int)period; +bool CgroupV1CpuController::cpu_period_val(uint64_t& result) { + CONTAINER_READ_NUMBER_CHECKED(reader(), "/cpu.cfs_period_us", "CPU Period", result); +} + +bool CgroupV1CpuController::cpu_period(int& result) { + uint64_t period = value_unlimited; + if (!cpu_period_val(period)) { + return false; + } + result = static_cast(period); + return true; +} + +bool CgroupV1CpuController::cpu_shares_val(uint64_t& result) { + CONTAINER_READ_NUMBER_CHECKED(reader(), "/cpu.shares", "CPU Shares", result); } /* cpu_shares * * Return the amount of cpu shares available to the process + * - Share number (typically a number relative to 1024) + * - (2048 typically expresses 2 CPUs worth of processing) * * return: - * Share number (typically a number relative to 1024) - * (2048 typically expresses 2 CPUs worth of processing) - * -1 for no share setup - * OSCONTAINER_ERROR for not supported + * false on error + * true if the result has been set in the result reference */ -int CgroupV1CpuController::cpu_shares() { - julong shares; - CONTAINER_READ_NUMBER_CHECKED(reader(), "/cpu.shares", "CPU Shares", shares); - int shares_int = (int)shares; - // Convert 1024 to no shares setup - if (shares_int == 1024) return -1; +bool CgroupV1CpuController::cpu_shares(int& result) { + uint64_t shares = 0; + if (!cpu_shares_val(shares)) { + return false; + } + int shares_int = static_cast(shares); + // Convert 1024 to no shares setup (-1) + if (shares_int == 1024) { + shares_int = -1; + } - return shares_int; + result = shares_int; + return true; } -jlong CgroupV1CpuacctController::cpu_usage_in_micros() { - julong cpu_usage; - CONTAINER_READ_NUMBER_CHECKED(reader(), "/cpuacct.usage", "CPU Usage", cpu_usage); +bool CgroupV1CpuacctController::cpu_usage_in_micros_val(uint64_t& result) { + CONTAINER_READ_NUMBER_CHECKED(reader(), "/cpuacct.usage", "CPU Usage", result); +} + +bool CgroupV1CpuacctController::cpu_usage_in_micros(uint64_t& result) { + uint64_t cpu_usage = 0; + if (!cpu_usage_in_micros_val(cpu_usage)) { + return false; + } // Output is in nanoseconds, convert to microseconds. - return (jlong)cpu_usage / 1000; + result = static_cast(cpu_usage / 1000); + return true; +} + +static +bool pids_max_val(CgroupController* ctrl, uint64_t& result) { + CONTAINER_READ_NUMBER_CHECKED_MAX(ctrl, "/pids.max", "Maximum number of tasks", result); } /* pids_max * * Return the maximum number of tasks available to the process + * in the passed result reference (might be value_unlimited). * * return: - * maximum number of tasks - * -1 for unlimited - * OSCONTAINER_ERROR for not supported + * false on error + * true when the result reference has been appropriately set */ -jlong CgroupV1Subsystem::pids_max() { - if (_pids == nullptr) return OSCONTAINER_ERROR; - jlong pids_max; - CONTAINER_READ_NUMBER_CHECKED_MAX(_pids, "/pids.max", "Maximum number of tasks", pids_max); - return pids_max; +bool CgroupV1Subsystem::pids_max(uint64_t& result) { + if (_pids == nullptr) return false; + uint64_t pids_val = 0; + if (!pids_max_val(_pids, pids_val)) { + return false; + } + result = pids_val; + return true; +} + +static +bool pids_current_val(CgroupController* ctrl, uint64_t& result) { + CONTAINER_READ_NUMBER_CHECKED(ctrl, "/pids.current", "Current number of tasks", result); } /* pids_current @@ -458,12 +602,15 @@ jlong CgroupV1Subsystem::pids_max() { * The number of tasks currently in the cgroup (and its descendants) of the process * * return: - * current number of tasks - * OSCONTAINER_ERROR for not supported + * true if the current number of tasks has been set in the result reference + * false if an error occurred */ -jlong CgroupV1Subsystem::pids_current() { - if (_pids == nullptr) return OSCONTAINER_ERROR; - julong pids_current; - CONTAINER_READ_NUMBER_CHECKED(_pids, "/pids.current", "Current number of tasks", pids_current); - return (jlong)pids_current; +bool CgroupV1Subsystem::pids_current(uint64_t& result) { + if (_pids == nullptr) return false; + uint64_t pids_current = 0; + if (!pids_current_val(_pids, pids_current)) { + return false; + } + result = pids_current; + return true; } diff --git a/src/hotspot/os/linux/cgroupV1Subsystem_linux.hpp b/src/hotspot/os/linux/cgroupV1Subsystem_linux.hpp index ce3184992e8..8aeb64ef18c 100644 --- a/src/hotspot/os/linux/cgroupV1Subsystem_linux.hpp +++ b/src/hotspot/os/linux/cgroupV1Subsystem_linux.hpp @@ -73,23 +73,44 @@ class CgroupV1MemoryController final : public CgroupMemoryController { private: CgroupV1Controller _reader; CgroupV1Controller* reader() { return &_reader; } + bool read_memory_limit_val(physical_memory_size_type& result); + bool read_hierarchical_memory_limit_val(physical_memory_size_type& result); + bool read_hierarchical_mem_swap_val(physical_memory_size_type& result); + bool read_use_hierarchy_val(physical_memory_size_type& result); + bool memory_usage_val(physical_memory_size_type& result); + bool read_mem_swappiness(physical_memory_size_type& result); + bool read_mem_swap(physical_memory_size_type& result); + bool memory_soft_limit_val(physical_memory_size_type& result); + bool memory_max_usage_val(physical_memory_size_type& result); + bool kernel_memory_usage_val(physical_memory_size_type& result); + bool kernel_memory_limit_val(physical_memory_size_type& result); + bool kernel_memory_max_usage_val(physical_memory_size_type& result); + bool uses_mem_hierarchy(); + public: void set_subsystem_path(const char *cgroup_path) override { reader()->set_subsystem_path(cgroup_path); } - jlong read_memory_limit_in_bytes(julong upper_bound) override; - jlong memory_usage_in_bytes() override; - jlong memory_and_swap_limit_in_bytes(julong upper_mem_bound, julong upper_swap_bound) override; - jlong memory_and_swap_usage_in_bytes(julong upper_mem_bound, julong upper_swap_bound) override; - jlong memory_soft_limit_in_bytes(julong upper_bound) override; - jlong memory_throttle_limit_in_bytes() override; - jlong memory_max_usage_in_bytes() override; - jlong rss_usage_in_bytes() override; - jlong cache_usage_in_bytes() override; - jlong kernel_memory_usage_in_bytes(); - jlong kernel_memory_limit_in_bytes(julong upper_bound); - jlong kernel_memory_max_usage_in_bytes(); - void print_version_specific_info(outputStream* st, julong upper_mem_bound) override; + bool read_memory_limit_in_bytes(physical_memory_size_type upper_bound, + physical_memory_size_type& value) override; + bool memory_usage_in_bytes(physical_memory_size_type& result) override; + bool memory_and_swap_limit_in_bytes(physical_memory_size_type upper_mem_bound, + physical_memory_size_type upper_swap_bound, + physical_memory_size_type& result) override; + bool memory_and_swap_usage_in_bytes(physical_memory_size_type upper_mem_bound, + physical_memory_size_type upper_swap_bound, + physical_memory_size_type& result) override; + bool memory_soft_limit_in_bytes(physical_memory_size_type upper_bound, + physical_memory_size_type& result) override; + bool memory_throttle_limit_in_bytes(physical_memory_size_type& result) override; + bool memory_max_usage_in_bytes(physical_memory_size_type& result) override; + bool rss_usage_in_bytes(physical_memory_size_type& result) override; + bool cache_usage_in_bytes(physical_memory_size_type& result) override; + bool kernel_memory_usage_in_bytes(physical_memory_size_type& result); + bool kernel_memory_limit_in_bytes(physical_memory_size_type upper_bound, + physical_memory_size_type& result); + bool kernel_memory_max_usage_in_bytes(physical_memory_size_type& result); + void print_version_specific_info(outputStream* st, physical_memory_size_type upper_mem_bound) override; bool needs_hierarchy_adjustment() override { return reader()->needs_hierarchy_adjustment(); } @@ -99,10 +120,6 @@ class CgroupV1MemoryController final : public CgroupMemoryController { const char* subsystem_path() override { return reader()->subsystem_path(); } const char* mount_point() override { return reader()->mount_point(); } const char* cgroup_path() override { return reader()->cgroup_path(); } - private: - jlong uses_mem_hierarchy(); - jlong read_mem_swappiness(); - jlong read_mem_swap(julong upper_memsw_bound); public: CgroupV1MemoryController(const CgroupV1Controller& reader) @@ -116,10 +133,12 @@ class CgroupV1CpuController final : public CgroupCpuController { private: CgroupV1Controller _reader; CgroupV1Controller* reader() { return &_reader; } + bool cpu_period_val(uint64_t& result); + bool cpu_shares_val(uint64_t& result); public: - int cpu_quota() override; - int cpu_period() override; - int cpu_shares() override; + bool cpu_quota(int& result) override; + bool cpu_period(int& result) override; + bool cpu_shares(int& result) override; void set_subsystem_path(const char *cgroup_path) override { reader()->set_subsystem_path(cgroup_path); } @@ -147,8 +166,9 @@ class CgroupV1CpuacctController final : public CgroupCpuacctController { private: CgroupV1Controller _reader; CgroupV1Controller* reader() { return &_reader; } + bool cpu_usage_in_micros_val(uint64_t& result); public: - jlong cpu_usage_in_micros() override; + bool cpu_usage_in_micros(uint64_t& result) override; void set_subsystem_path(const char *cgroup_path) override { reader()->set_subsystem_path(cgroup_path); } @@ -180,15 +200,15 @@ class CgroupV1Subsystem: public CgroupSubsystem { CgroupV1Controller* pids, CgroupV1MemoryController* memory); - jlong kernel_memory_usage_in_bytes(); - jlong kernel_memory_limit_in_bytes(); - jlong kernel_memory_max_usage_in_bytes(); + bool kernel_memory_usage_in_bytes(physical_memory_size_type& result); + bool kernel_memory_limit_in_bytes(physical_memory_size_type& result); + bool kernel_memory_max_usage_in_bytes(physical_memory_size_type& result); - char * cpu_cpuset_cpus(); - char * cpu_cpuset_memory_nodes(); + char * cpu_cpuset_cpus() override; + char * cpu_cpuset_memory_nodes() override; - jlong pids_max(); - jlong pids_current(); + bool pids_max(uint64_t& result) override; + bool pids_current(uint64_t& result) override; bool is_containerized(); const char * container_type() { diff --git a/src/hotspot/os/linux/cgroupV2Subsystem_linux.cpp b/src/hotspot/os/linux/cgroupV2Subsystem_linux.cpp index 41fc8db7e81..f435e53c02c 100644 --- a/src/hotspot/os/linux/cgroupV2Subsystem_linux.cpp +++ b/src/hotspot/os/linux/cgroupV2Subsystem_linux.cpp @@ -44,24 +44,35 @@ CgroupV2Controller::CgroupV2Controller(const CgroupV2Controller& o) : _mount_point = o._mount_point; } +static +bool read_cpu_shares_value(CgroupV2Controller* ctrl, uint64_t& value) { + CONTAINER_READ_NUMBER_CHECKED(ctrl, "/cpu.weight", "Raw value for CPU Shares", value); +} + /* cpu_shares * - * Return the amount of cpu shares available to the process + * Return the amount of cpu shares available to the process in the + * 'result' reference. * - * return: * Share number (typically a number relative to 1024) * (2048 typically expresses 2 CPUs worth of processing) - * -1 for no share setup - * OSCONTAINER_ERROR for not supported + * + * return: + * true if the result reference got updated + * false if there was an error */ -int CgroupV2CpuController::cpu_shares() { - julong shares; - CONTAINER_READ_NUMBER_CHECKED(reader(), "/cpu.weight", "Raw value for CPU Shares", shares); - int shares_int = (int)shares; +bool CgroupV2CpuController::cpu_shares(int& result) { + uint64_t shares = 0; + bool is_ok = read_cpu_shares_value(reader(), shares); + if (!is_ok) { + return false; + } + int shares_int = static_cast(shares); // Convert default value of 100 to no shares setup if (shares_int == 100) { - log_debug(os, container)("CPU Shares is: %d", -1); - return -1; + log_debug(os, container)("CPU Shares is: unlimited"); + result = -1; + return true; } // cg v2 values must be in range [1-10000] assert(shares_int >= 1 && shares_int <= 10000, "invariant"); @@ -97,7 +108,8 @@ int CgroupV2CpuController::cpu_shares() { // Don't do the multiples of PER_CPU_SHARES mapping since we // have a value <= PER_CPU_SHARES log_debug(os, container)("CPU Shares is: %d", x); - return x; + result = x; + return true; } int f = x/PER_CPU_SHARES; int lower_multiple = f * PER_CPU_SHARES; @@ -107,28 +119,33 @@ int CgroupV2CpuController::cpu_shares() { x = distance_lower <= distance_upper ? lower_multiple : upper_multiple; log_trace(os, container)("Closest multiple of %d of the CPU Shares value is: %d", PER_CPU_SHARES, x); log_debug(os, container)("CPU Shares is: %d", x); - return x; + result = x; + return true; } /* cpu_quota * * Return the number of microseconds per period - * process is guaranteed to run. + * process is guaranteed to run in the passed in 'result' reference. * * return: - * quota time in microseconds - * -1 for no quota - * OSCONTAINER_ERROR for not supported + * true if the result reference has been set + * false on error */ -int CgroupV2CpuController::cpu_quota() { - jlong quota_val; - bool is_ok = reader()->read_numerical_tuple_value("/cpu.max", true /* use_first */, "a_val); - if (!is_ok) { - return OSCONTAINER_ERROR; +bool CgroupV2CpuController::cpu_quota(int& result) { + uint64_t quota_val = 0; + if (!reader()->read_numerical_tuple_value("/cpu.max", true /* use_first */, quota_val)) { + return false; + } + int limit = -1; + // The read first tuple value might be 'max' which maps + // to value_unlimited. Keep that at -1; + if (quota_val != value_unlimited) { + limit = static_cast(quota_val); } - int limit = (int)quota_val; log_trace(os, container)("CPU Quota is: %d", limit); - return limit; + result = limit; + return true; } // Constructor @@ -162,80 +179,67 @@ char* CgroupV2Subsystem::cpu_cpuset_memory_nodes() { return os::strdup(mems); } -int CgroupV2CpuController::cpu_period() { - jlong period_val; - bool is_ok = reader()->read_numerical_tuple_value("/cpu.max", false /* use_first */, &period_val); - if (!is_ok) { - log_trace(os, container)("CPU Period failed: %d", OSCONTAINER_ERROR); - return OSCONTAINER_ERROR; +bool CgroupV2CpuController::cpu_period(int& result) { + uint64_t cpu_period = 0; + if (!reader()->read_numerical_tuple_value("/cpu.max", false /* use_first */, cpu_period)) { + log_trace(os, container)("CPU Period failed"); + return false; } - int period = (int)period_val; - log_trace(os, container)("CPU Period is: %d", period); - return period; + int period_int = static_cast(cpu_period); + log_trace(os, container)("CPU Period is: %d", period_int); + result = period_int; + return true; } -jlong CgroupV2CpuController::cpu_usage_in_micros() { - julong cpu_usage; - bool is_ok = reader()->read_numerical_key_value("/cpu.stat", "usage_usec", &cpu_usage); +bool CgroupV2CpuController::cpu_usage_in_micros(uint64_t& value) { + bool is_ok = reader()->read_numerical_key_value("/cpu.stat", "usage_usec", value); if (!is_ok) { - log_trace(os, container)("CPU Usage failed: %d", OSCONTAINER_ERROR); - return OSCONTAINER_ERROR; + log_trace(os, container)("CPU Usage failed"); + return false; } - log_trace(os, container)("CPU Usage is: " JULONG_FORMAT, cpu_usage); - return (jlong)cpu_usage; + log_trace(os, container)("CPU Usage is: " UINT64_FORMAT, value); + return true; } /* memory_usage_in_bytes * - * Return the amount of used memory used by this cgroup and descendents + * read the amount of used memory used by this cgroup and descendents + * into the passed in 'value' reference. * * return: - * memory usage in bytes or - * -1 for unlimited - * OSCONTAINER_ERROR for not supported + * false on failure, true otherwise. */ -jlong CgroupV2MemoryController::memory_usage_in_bytes() { - julong memusage; - CONTAINER_READ_NUMBER_CHECKED(reader(), "/memory.current", "Memory Usage", memusage); - return (jlong)memusage; +bool CgroupV2MemoryController::memory_usage_in_bytes(physical_memory_size_type& value) { + CONTAINER_READ_NUMBER_CHECKED(reader(), "/memory.current", "Memory Usage", value); } -jlong CgroupV2MemoryController::memory_soft_limit_in_bytes(julong upper_bound) { - jlong mem_soft_limit; - CONTAINER_READ_NUMBER_CHECKED_MAX(reader(), "/memory.low", "Memory Soft Limit", mem_soft_limit); - return mem_soft_limit; +bool CgroupV2MemoryController::memory_soft_limit_in_bytes(physical_memory_size_type upper_bound, + physical_memory_size_type& value) { + CONTAINER_READ_NUMBER_CHECKED_MAX(reader(), "/memory.low", "Memory Soft Limit", value); } -jlong CgroupV2MemoryController::memory_throttle_limit_in_bytes() { - jlong mem_throttle_limit; - CONTAINER_READ_NUMBER_CHECKED_MAX(reader(), "/memory.high", "Memory Throttle Limit", mem_throttle_limit); - return mem_throttle_limit; +bool CgroupV2MemoryController::memory_throttle_limit_in_bytes(physical_memory_size_type& value) { + CONTAINER_READ_NUMBER_CHECKED_MAX(reader(), "/memory.high", "Memory Throttle Limit", value); } -jlong CgroupV2MemoryController::memory_max_usage_in_bytes() { - julong mem_max_usage; - CONTAINER_READ_NUMBER_CHECKED(reader(), "/memory.peak", "Maximum Memory Usage", mem_max_usage); - return mem_max_usage; +bool CgroupV2MemoryController::memory_max_usage_in_bytes(physical_memory_size_type& value) { + CONTAINER_READ_NUMBER_CHECKED(reader(), "/memory.peak", "Maximum Memory Usage", value); } -jlong CgroupV2MemoryController::rss_usage_in_bytes() { - julong rss; - bool is_ok = reader()->read_numerical_key_value("/memory.stat", "anon", &rss); - if (!is_ok) { - return OSCONTAINER_ERROR; +bool CgroupV2MemoryController::rss_usage_in_bytes(physical_memory_size_type& value) { + if (!reader()->read_numerical_key_value("/memory.stat", "anon", value)) { + return false; } - log_trace(os, container)("RSS usage is: " JULONG_FORMAT, rss); - return (jlong)rss; + log_trace(os, container)("RSS usage is: " PHYS_MEM_TYPE_FORMAT, value); + return true; } -jlong CgroupV2MemoryController::cache_usage_in_bytes() { - julong cache; - bool is_ok = reader()->read_numerical_key_value("/memory.stat", "file", &cache); - if (!is_ok) { - return OSCONTAINER_ERROR; +bool CgroupV2MemoryController::cache_usage_in_bytes(physical_memory_size_type& value) { + if (!reader()->read_numerical_key_value("/memory.stat", "file", value)) { + return false; } - log_trace(os, container)("Cache usage is: " JULONG_FORMAT, cache); - return (jlong)cache; + log_trace(os, container)("Cache usage is: " PHYS_MEM_TYPE_FORMAT, value); + return true; } // Note that for cgroups v2 the actual limits set for swap and @@ -243,91 +247,108 @@ jlong CgroupV2MemoryController::cache_usage_in_bytes() { // respectively. In order to properly report a cgroup v1 like // compound value we need to sum the two values. Setting a swap limit // without also setting a memory limit is not allowed. -jlong CgroupV2MemoryController::memory_and_swap_limit_in_bytes(julong upper_mem_bound, - julong upper_swap_bound /* unused in cg v2 */) { - jlong swap_limit; - bool is_ok = reader()->read_number_handle_max("/memory.swap.max", &swap_limit); - if (!is_ok) { +bool CgroupV2MemoryController::memory_and_swap_limit_in_bytes(physical_memory_size_type upper_mem_bound, + physical_memory_size_type upper_swap_bound, /* unused in cg v2 */ + physical_memory_size_type& result) { + physical_memory_size_type swap_limit_val = 0; + if (!reader()->read_number_handle_max("/memory.swap.max", swap_limit_val)) { // Some container tests rely on this trace logging to happen. - log_trace(os, container)("Swap Limit failed: %d", OSCONTAINER_ERROR); + log_trace(os, container)("Swap Limit failed"); // swap disabled at kernel level, treat it as no swap - return read_memory_limit_in_bytes(upper_mem_bound); + physical_memory_size_type mem_limit = value_unlimited; + if (!read_memory_limit_in_bytes(upper_mem_bound, mem_limit)) { + return false; + } + result = mem_limit; + return true; } - log_trace(os, container)("Swap Limit is: " JLONG_FORMAT, swap_limit); - if (swap_limit >= 0) { - jlong memory_limit = read_memory_limit_in_bytes(upper_mem_bound); - assert(memory_limit >= 0, "swap limit without memory limit?"); - return memory_limit + swap_limit; + if (swap_limit_val == value_unlimited) { + log_trace(os, container)("Memory and Swap Limit is: Unlimited"); + result = swap_limit_val; + return true; + } + log_trace(os, container)("Swap Limit is: " PHYS_MEM_TYPE_FORMAT, swap_limit_val); + physical_memory_size_type memory_limit = 0; + if (read_memory_limit_in_bytes(upper_mem_bound, memory_limit)) { + assert(memory_limit != value_unlimited, "swap limit without memory limit?"); + result = memory_limit + swap_limit_val; + log_trace(os, container)("Memory and Swap Limit is: " PHYS_MEM_TYPE_FORMAT, result); + return true; + } else { + return false; } - log_trace(os, container)("Memory and Swap Limit is: " JLONG_FORMAT, swap_limit); - return swap_limit; } // memory.swap.current : total amount of swap currently used by the cgroup and its descendants static -jlong memory_swap_current_value(CgroupV2Controller* ctrl) { - julong swap_current; - CONTAINER_READ_NUMBER_CHECKED(ctrl, "/memory.swap.current", "Swap currently used", swap_current); - return (jlong)swap_current; +bool memory_swap_current_value(CgroupV2Controller* ctrl, physical_memory_size_type& result) { + CONTAINER_READ_NUMBER_CHECKED(ctrl, "/memory.swap.current", "Swap currently used", result); } -jlong CgroupV2MemoryController::memory_and_swap_usage_in_bytes(julong upper_mem_bound, julong upper_swap_bound) { - jlong memory_usage = memory_usage_in_bytes(); - if (memory_usage >= 0) { - jlong swap_current = memory_swap_current_value(reader()); - return memory_usage + (swap_current >= 0 ? swap_current : 0); +bool CgroupV2MemoryController::memory_and_swap_usage_in_bytes(physical_memory_size_type upper_mem_bound, + physical_memory_size_type upper_swap_bound, + physical_memory_size_type& result) { + physical_memory_size_type memory_usage = 0; + if (!memory_usage_in_bytes(memory_usage)) { + return false; } - return memory_usage; // not supported or unlimited case + physical_memory_size_type swap_current = 0; + if (!memory_swap_current_value(reader(), swap_current)) { + result = memory_usage; // treat as no swap usage + return true; + } + result = memory_usage + swap_current; + return true; } static -jlong memory_limit_value(CgroupV2Controller* ctrl) { - jlong memory_limit; - CONTAINER_READ_NUMBER_CHECKED_MAX(ctrl, "/memory.max", "Memory Limit", memory_limit); - return memory_limit; +bool memory_limit_value(CgroupV2Controller* ctrl, physical_memory_size_type& result) { + CONTAINER_READ_NUMBER_CHECKED_MAX(ctrl, "/memory.max", "Memory Limit", result); } /* read_memory_limit_in_bytes * - * Return the limit of available memory for this process. + * Calculate the limit of available memory for this process. The result will be + * set in the 'result' variable if the function returns true. * * return: - * memory limit in bytes or - * -1 for unlimited, OSCONTAINER_ERROR for an error + * true when the limit could be read correctly. + * false in case of any error. */ -jlong CgroupV2MemoryController::read_memory_limit_in_bytes(julong upper_bound) { - jlong limit = memory_limit_value(reader()); +bool CgroupV2MemoryController::read_memory_limit_in_bytes(physical_memory_size_type upper_bound, + physical_memory_size_type& result) { + physical_memory_size_type limit = 0; // default unlimited + if (!memory_limit_value(reader(), limit)) { + log_trace(os, container)("container memory limit failed, using host value " PHYS_MEM_TYPE_FORMAT, + upper_bound); + return false; + } + bool is_unlimited = limit == value_unlimited; + bool exceeds_physical_mem = false; + if (!is_unlimited && limit >= upper_bound) { + exceeds_physical_mem = true; + } if (log_is_enabled(Trace, os, container)) { - if (limit == -1) { - log_trace(os, container)("Memory Limit is: Unlimited"); - } else { - log_trace(os, container)("Memory Limit is: " JLONG_FORMAT, limit); + if (!is_unlimited) { + log_trace(os, container)("Memory Limit is: " PHYS_MEM_TYPE_FORMAT, limit); } - } - if (log_is_enabled(Debug, os, container)) { - julong read_limit = (julong)limit; // avoid signed/unsigned compare - if (limit < 0 || read_limit >= upper_bound) { - const char* reason; - if (limit == -1) { - reason = "unlimited"; - } else if (limit == OSCONTAINER_ERROR) { - reason = "failed"; + if (is_unlimited || exceeds_physical_mem) { + if (is_unlimited) { + log_trace(os, container)("Memory Limit is: Unlimited"); + log_trace(os, container)("container memory limit unlimited, using upper bound value " PHYS_MEM_TYPE_FORMAT, upper_bound); } else { - assert(read_limit >= upper_bound, "Expected mem limit to exceed upper memory bound"); - reason = "ignored"; + log_trace(os, container)("container memory limit ignored: " PHYS_MEM_TYPE_FORMAT ", upper bound is " PHYS_MEM_TYPE_FORMAT, + limit, upper_bound); } - log_debug(os, container)("container memory limit %s: " JLONG_FORMAT ", upper bound is " JLONG_FORMAT, - reason, limit, upper_bound); } } - return limit; + result = limit; + return true; } static -jlong memory_swap_limit_value(CgroupV2Controller* ctrl) { - jlong swap_limit; - CONTAINER_READ_NUMBER_CHECKED_MAX(ctrl, "/memory.swap.max", "Swap Limit", swap_limit); - return swap_limit; +bool memory_swap_limit_value(CgroupV2Controller* ctrl, physical_memory_size_type& value) { + CONTAINER_READ_NUMBER_CHECKED_MAX(ctrl, "/memory.swap.max", "Swap Limit", value); } void CgroupV2Controller::set_subsystem_path(const char* cgroup_path) { @@ -346,10 +367,17 @@ bool CgroupV2Controller::needs_hierarchy_adjustment() { return strcmp(_cgroup_path, "/") != 0; } -void CgroupV2MemoryController::print_version_specific_info(outputStream* st, julong upper_mem_bound) { - jlong swap_current = memory_swap_current_value(reader()); - jlong swap_limit = memory_swap_limit_value(reader()); - +void CgroupV2MemoryController::print_version_specific_info(outputStream* st, physical_memory_size_type upper_mem_bound) { + MetricResult swap_current; + physical_memory_size_type swap_current_val = 0; + if (memory_swap_current_value(reader(), swap_current_val)) { + swap_current.set_value(swap_current_val); + } + MetricResult swap_limit; + physical_memory_size_type swap_limit_val = 0; + if (memory_swap_limit_value(reader(), swap_limit_val)) { + swap_limit.set_value(swap_limit_val); + } OSContainer::print_container_helper(st, swap_current, "memory_swap_current_in_bytes"); OSContainer::print_container_helper(st, swap_limit, "memory_swap_max_limit_in_bytes"); } @@ -365,29 +393,27 @@ char* CgroupV2Controller::construct_path(char* mount_path, const char* cgroup_pa /* pids_max * - * Return the maximum number of tasks available to the process + * Calculate the maximum number of tasks available to the process. Set the + * value in the passed in 'value' reference. The value might be 'value_unlimited' when + * there is no limit. * * return: - * maximum number of tasks - * -1 for unlimited - * OSCONTAINER_ERROR for not supported + * true if the value has been set appropriately + * false if there was an error */ -jlong CgroupV2Subsystem::pids_max() { - jlong pids_max; - CONTAINER_READ_NUMBER_CHECKED_MAX(unified(), "/pids.max", "Maximum number of tasks", pids_max); - return pids_max; +bool CgroupV2Subsystem::pids_max(uint64_t& value) { + CONTAINER_READ_NUMBER_CHECKED_MAX(unified(), "/pids.max", "Maximum number of tasks", value); } /* pids_current * - * The number of tasks currently in the cgroup (and its descendants) of the process + * The number of tasks currently in the cgroup (and its descendants) of the process. Set + * in the passed in 'value' reference. * * return: - * current number of tasks - * OSCONTAINER_ERROR for not supported + * true on success + * false when there was an error */ -jlong CgroupV2Subsystem::pids_current() { - julong pids_current; - CONTAINER_READ_NUMBER_CHECKED(unified(), "/pids.current", "Current number of tasks", pids_current); - return pids_current; +bool CgroupV2Subsystem::pids_current(uint64_t& value) { + CONTAINER_READ_NUMBER_CHECKED(unified(), "/pids.current", "Current number of tasks", value); } diff --git a/src/hotspot/os/linux/cgroupV2Subsystem_linux.hpp b/src/hotspot/os/linux/cgroupV2Subsystem_linux.hpp index 07db126ce90..39a4fabe9f6 100644 --- a/src/hotspot/os/linux/cgroupV2Subsystem_linux.hpp +++ b/src/hotspot/os/linux/cgroupV2Subsystem_linux.hpp @@ -59,10 +59,10 @@ class CgroupV2CpuController: public CgroupCpuController { public: CgroupV2CpuController(const CgroupV2Controller& reader) : _reader(reader) { } - int cpu_quota() override; - int cpu_period() override; - int cpu_shares() override; - jlong cpu_usage_in_micros(); + bool cpu_quota(int& value) override; + bool cpu_period(int& value) override; + bool cpu_shares(int& value) override; + bool cpu_usage_in_micros(uint64_t& value); bool is_read_only() override { return reader()->is_read_only(); } @@ -87,8 +87,8 @@ class CgroupV2CpuacctController: public CgroupCpuacctController { CgroupV2CpuacctController(CgroupV2CpuController* reader) : _reader(reader) { } // In cgroup v2, cpu usage is a part of the cpu controller. - jlong cpu_usage_in_micros() override { - return reader()->cpu_usage_in_micros(); + bool cpu_usage_in_micros(uint64_t& result) override { + return reader()->cpu_usage_in_micros(result); } bool is_read_only() override { return reader()->is_read_only(); @@ -110,20 +110,27 @@ class CgroupV2MemoryController final: public CgroupMemoryController { private: CgroupV2Controller _reader; CgroupV2Controller* reader() { return &_reader; } + public: CgroupV2MemoryController(const CgroupV2Controller& reader) : _reader(reader) { } - jlong read_memory_limit_in_bytes(julong upper_bound) override; - jlong memory_and_swap_limit_in_bytes(julong upper_mem_bound, julong upper_swap_bound) override; - jlong memory_and_swap_usage_in_bytes(julong upper_mem_bound, julong upper_swap_bound) override; - jlong memory_soft_limit_in_bytes(julong upper_bound) override; - jlong memory_throttle_limit_in_bytes() override; - jlong memory_usage_in_bytes() override; - jlong memory_max_usage_in_bytes() override; - jlong rss_usage_in_bytes() override; - jlong cache_usage_in_bytes() override; - void print_version_specific_info(outputStream* st, julong upper_mem_bound) override; + bool read_memory_limit_in_bytes(physical_memory_size_type upper_bound, + physical_memory_size_type& result) override; + bool memory_and_swap_limit_in_bytes(physical_memory_size_type upper_mem_bound, + physical_memory_size_type upper_swap_bound, + physical_memory_size_type& result) override; + bool memory_and_swap_usage_in_bytes(physical_memory_size_type upper_mem_bound, + physical_memory_size_type upper_swap_bound, + physical_memory_size_type& result) override; + bool memory_soft_limit_in_bytes(physical_memory_size_type upper_bound, + physical_memory_size_type& result) override; + bool memory_throttle_limit_in_bytes(physical_memory_size_type& result) override; + bool memory_usage_in_bytes(physical_memory_size_type& result) override; + bool memory_max_usage_in_bytes(physical_memory_size_type& result) override; + bool rss_usage_in_bytes(physical_memory_size_type& result) override; + bool cache_usage_in_bytes(physical_memory_size_type& result) override; + void print_version_specific_info(outputStream* st, physical_memory_size_type upper_mem_bound) override; bool is_read_only() override { return reader()->is_read_only(); } @@ -160,8 +167,8 @@ class CgroupV2Subsystem: public CgroupSubsystem { char * cpu_cpuset_cpus() override; char * cpu_cpuset_memory_nodes() override; - jlong pids_max() override; - jlong pids_current() override; + bool pids_max(uint64_t& result) override; + bool pids_current(uint64_t& result) override; bool is_containerized() override; diff --git a/src/hotspot/os/linux/osContainer_linux.cpp b/src/hotspot/os/linux/osContainer_linux.cpp index 561f2d4926c..d86bbf7428a 100644 --- a/src/hotspot/os/linux/osContainer_linux.cpp +++ b/src/hotspot/os/linux/osContainer_linux.cpp @@ -84,8 +84,12 @@ void OSContainer::init() { // We can be in one of two cases: // 1.) On a physical Linux system without any limit // 2.) On a physical Linux system with a limit enforced by other means (like systemd slice) - any_mem_cpu_limit_present = memory_limit_in_bytes() > 0 || - os::Linux::active_processor_count() != active_processor_count(); + physical_memory_size_type mem_limit_val = value_unlimited; + (void)memory_limit_in_bytes(mem_limit_val); // discard error and use default + int host_cpus = os::Linux::active_processor_count(); + int cpus = host_cpus; + (void)active_processor_count(cpus); // discard error and use default + any_mem_cpu_limit_present = mem_limit_val != value_unlimited || host_cpus != cpus; if (any_mem_cpu_limit_present) { reason = " because either a cpu or a memory limit is present"; } else { @@ -103,77 +107,138 @@ const char * OSContainer::container_type() { return cgroup_subsystem->container_type(); } -bool OSContainer::available_memory_in_container(julong& value) { - jlong mem_limit = memory_limit_in_bytes(); - jlong mem_usage = memory_usage_in_bytes(); +bool OSContainer::memory_limit_in_bytes(physical_memory_size_type& value) { + assert(cgroup_subsystem != nullptr, "cgroup subsystem not available"); + physical_memory_size_type phys_mem = os::Linux::physical_memory(); + return cgroup_subsystem->memory_limit_in_bytes(phys_mem, value); +} - if (mem_limit > 0 && mem_usage <= 0) { - log_debug(os, container)("container memory usage failed: " JLONG_FORMAT, mem_usage); +bool OSContainer::available_memory_in_bytes(physical_memory_size_type& value) { + physical_memory_size_type mem_limit = value_unlimited; + physical_memory_size_type mem_usage = 0; + if (memory_limit_in_bytes(mem_limit) && memory_usage_in_bytes(mem_usage)) { + assert(mem_usage != value_unlimited, "invariant"); + if (mem_limit != value_unlimited) { + value = (mem_limit > mem_usage) ? mem_limit - mem_usage : 0; + return true; + } } + log_trace(os, container)("calculating available memory in container failed"); + return false; +} - if (mem_limit <= 0 || mem_usage <= 0) { +bool OSContainer::available_swap_in_bytes(physical_memory_size_type host_free_swap, + physical_memory_size_type& value) { + physical_memory_size_type mem_limit = 0; + physical_memory_size_type mem_swap_limit = 0; + if (memory_limit_in_bytes(mem_limit) && + memory_and_swap_limit_in_bytes(mem_swap_limit) && + mem_limit != value_unlimited && + mem_swap_limit != value_unlimited) { + if (mem_limit >= mem_swap_limit) { + value = 0; // no swap, thus no free swap + return true; + } + physical_memory_size_type swap_limit = mem_swap_limit - mem_limit; + physical_memory_size_type mem_swap_usage = 0; + physical_memory_size_type mem_usage = 0; + if (memory_and_swap_usage_in_bytes(mem_swap_usage) && + memory_usage_in_bytes(mem_usage)) { + physical_memory_size_type swap_usage = value_unlimited; + if (mem_usage > mem_swap_usage) { + swap_usage = 0; // delta usage must not be negative + } else { + swap_usage = mem_swap_usage - mem_usage; + } + // free swap is based on swap limit (upper bound) and swap usage + if (swap_usage >= swap_limit) { + value = 0; // free swap must not be negative + return true; + } + value = swap_limit - swap_usage; + return true; + } + } + // unlimited or not supported. Leave an appropriate trace message + if (log_is_enabled(Trace, os, container)) { + char mem_swap_buf[25]; // uint64_t => 20 + 1, 'unlimited' => 9 + 1; 10 < 21 < 25 + char mem_limit_buf[25]; + int num = 0; + if (mem_swap_limit == value_unlimited) { + num = os::snprintf(mem_swap_buf, sizeof(mem_swap_buf), "%s", "unlimited"); + } else { + num = os::snprintf(mem_swap_buf, sizeof(mem_swap_buf), PHYS_MEM_TYPE_FORMAT, mem_swap_limit); + } + assert(num < 25, "buffer too small"); + mem_swap_buf[num] = '\0'; + if (mem_limit == value_unlimited) { + num = os::snprintf(mem_limit_buf, sizeof(mem_limit_buf), "%s", "unlimited"); + } else { + num = os::snprintf(mem_limit_buf, sizeof(mem_limit_buf), PHYS_MEM_TYPE_FORMAT, mem_limit); + } + assert(num < 25, "buffer too small"); + mem_limit_buf[num] = '\0'; + log_trace(os,container)("OSContainer::available_swap_in_bytes: container_swap_limit=%s" + " container_mem_limit=%s, host_free_swap: " PHYS_MEM_TYPE_FORMAT, + mem_swap_buf, mem_limit_buf, host_free_swap); + } + return false; +} + +bool OSContainer::memory_and_swap_limit_in_bytes(physical_memory_size_type& value) { + assert(cgroup_subsystem != nullptr, "cgroup subsystem not available"); + physical_memory_size_type phys_mem = os::Linux::physical_memory(); + physical_memory_size_type host_swap = 0; + if (!os::Linux::host_swap(host_swap)) { return false; } - - value = mem_limit > mem_usage ? static_cast(mem_limit - mem_usage) : 0; - - return true; + return cgroup_subsystem->memory_and_swap_limit_in_bytes(phys_mem, host_swap, value); } -jlong OSContainer::memory_limit_in_bytes() { +bool OSContainer::memory_and_swap_usage_in_bytes(physical_memory_size_type& value) { assert(cgroup_subsystem != nullptr, "cgroup subsystem not available"); - julong phys_mem = static_cast(os::Linux::physical_memory()); - return cgroup_subsystem->memory_limit_in_bytes(phys_mem); + physical_memory_size_type phys_mem = os::Linux::physical_memory(); + physical_memory_size_type host_swap = 0; + if (!os::Linux::host_swap(host_swap)) { + return false; + } + return cgroup_subsystem->memory_and_swap_usage_in_bytes(phys_mem, host_swap, value); } -jlong OSContainer::memory_and_swap_limit_in_bytes() { +bool OSContainer::memory_soft_limit_in_bytes(physical_memory_size_type& value) { assert(cgroup_subsystem != nullptr, "cgroup subsystem not available"); - julong phys_mem = static_cast(os::Linux::physical_memory()); - julong host_swap = os::Linux::host_swap(); - return cgroup_subsystem->memory_and_swap_limit_in_bytes(phys_mem, host_swap); + physical_memory_size_type phys_mem = os::Linux::physical_memory(); + return cgroup_subsystem->memory_soft_limit_in_bytes(phys_mem, value); } -jlong OSContainer::memory_and_swap_usage_in_bytes() { +bool OSContainer::memory_throttle_limit_in_bytes(physical_memory_size_type& value) { assert(cgroup_subsystem != nullptr, "cgroup subsystem not available"); - julong phys_mem = static_cast(os::Linux::physical_memory()); - julong host_swap = os::Linux::host_swap(); - return cgroup_subsystem->memory_and_swap_usage_in_bytes(phys_mem, host_swap); + return cgroup_subsystem->memory_throttle_limit_in_bytes(value); } -jlong OSContainer::memory_soft_limit_in_bytes() { +bool OSContainer::memory_usage_in_bytes(physical_memory_size_type& value) { assert(cgroup_subsystem != nullptr, "cgroup subsystem not available"); - julong phys_mem = static_cast(os::Linux::physical_memory()); - return cgroup_subsystem->memory_soft_limit_in_bytes(phys_mem); + return cgroup_subsystem->memory_usage_in_bytes(value); } -jlong OSContainer::memory_throttle_limit_in_bytes() { +bool OSContainer::memory_max_usage_in_bytes(physical_memory_size_type& value) { assert(cgroup_subsystem != nullptr, "cgroup subsystem not available"); - return cgroup_subsystem->memory_throttle_limit_in_bytes(); + return cgroup_subsystem->memory_max_usage_in_bytes(value); } -jlong OSContainer::memory_usage_in_bytes() { +bool OSContainer::rss_usage_in_bytes(physical_memory_size_type& value) { assert(cgroup_subsystem != nullptr, "cgroup subsystem not available"); - return cgroup_subsystem->memory_usage_in_bytes(); + return cgroup_subsystem->rss_usage_in_bytes(value); } -jlong OSContainer::memory_max_usage_in_bytes() { +bool OSContainer::cache_usage_in_bytes(physical_memory_size_type& value) { assert(cgroup_subsystem != nullptr, "cgroup subsystem not available"); - return cgroup_subsystem->memory_max_usage_in_bytes(); -} - -jlong OSContainer::rss_usage_in_bytes() { - assert(cgroup_subsystem != nullptr, "cgroup subsystem not available"); - return cgroup_subsystem->rss_usage_in_bytes(); -} - -jlong OSContainer::cache_usage_in_bytes() { - assert(cgroup_subsystem != nullptr, "cgroup subsystem not available"); - return cgroup_subsystem->cache_usage_in_bytes(); + return cgroup_subsystem->cache_usage_in_bytes(value); } void OSContainer::print_version_specific_info(outputStream* st) { assert(cgroup_subsystem != nullptr, "cgroup subsystem not available"); - julong phys_mem = static_cast(os::Linux::physical_memory()); + physical_memory_size_type phys_mem = os::Linux::physical_memory(); cgroup_subsystem->print_version_specific_info(st, phys_mem); } @@ -187,50 +252,55 @@ char * OSContainer::cpu_cpuset_memory_nodes() { return cgroup_subsystem->cpu_cpuset_memory_nodes(); } -int OSContainer::active_processor_count() { +bool OSContainer::active_processor_count(int& value) { assert(cgroup_subsystem != nullptr, "cgroup subsystem not available"); - return cgroup_subsystem->active_processor_count(); + return cgroup_subsystem->active_processor_count(value); } -int OSContainer::cpu_quota() { +bool OSContainer::cpu_quota(int& value) { assert(cgroup_subsystem != nullptr, "cgroup subsystem not available"); - return cgroup_subsystem->cpu_quota(); + return cgroup_subsystem->cpu_quota(value); } -int OSContainer::cpu_period() { +bool OSContainer::cpu_period(int& value) { assert(cgroup_subsystem != nullptr, "cgroup subsystem not available"); - return cgroup_subsystem->cpu_period(); + return cgroup_subsystem->cpu_period(value); } -int OSContainer::cpu_shares() { +bool OSContainer::cpu_shares(int& value) { assert(cgroup_subsystem != nullptr, "cgroup subsystem not available"); - return cgroup_subsystem->cpu_shares(); + return cgroup_subsystem->cpu_shares(value); } -jlong OSContainer::cpu_usage_in_micros() { +bool OSContainer::cpu_usage_in_micros(uint64_t& value) { assert(cgroup_subsystem != nullptr, "cgroup subsystem not available"); - return cgroup_subsystem->cpu_usage_in_micros(); + return cgroup_subsystem->cpu_usage_in_micros(value); } -jlong OSContainer::pids_max() { +bool OSContainer::pids_max(uint64_t& value) { assert(cgroup_subsystem != nullptr, "cgroup subsystem not available"); - return cgroup_subsystem->pids_max(); + return cgroup_subsystem->pids_max(value); } -jlong OSContainer::pids_current() { +bool OSContainer::pids_current(uint64_t& value) { assert(cgroup_subsystem != nullptr, "cgroup subsystem not available"); - return cgroup_subsystem->pids_current(); + return cgroup_subsystem->pids_current(value); } -void OSContainer::print_container_helper(outputStream* st, jlong j, const char* metrics) { +void OSContainer::print_container_helper(outputStream* st, MetricResult& res, const char* metrics) { st->print("%s: ", metrics); - if (j >= 0) { - if (j >= 1024) { - st->print_cr(UINT64_FORMAT " k", uint64_t(j) / K); + if (res.success()) { + if (res.value() != value_unlimited) { + if (res.value() >= 1024) { + st->print_cr(PHYS_MEM_TYPE_FORMAT " k", (physical_memory_size_type)(res.value() / K)); + } else { + st->print_cr(PHYS_MEM_TYPE_FORMAT, res.value()); + } } else { - st->print_cr(UINT64_FORMAT, uint64_t(j)); + st->print_cr("%s", "unlimited"); } } else { - st->print_cr("%s", j == OSCONTAINER_ERROR ? "not supported" : "unlimited"); + // Not supported + st->print_cr("%s", "unavailable"); } } diff --git a/src/hotspot/os/linux/osContainer_linux.hpp b/src/hotspot/os/linux/osContainer_linux.hpp index 6258714c48b..895c99ba167 100644 --- a/src/hotspot/os/linux/osContainer_linux.hpp +++ b/src/hotspot/os/linux/osContainer_linux.hpp @@ -30,11 +30,30 @@ #include "utilities/macros.hpp" #include "utilities/ostream.hpp" -#define OSCONTAINER_ERROR (-2) +// Some cgroup interface files define the value 'max' for unlimited. +// Define this constant value to indicate this value. +const uint64_t value_unlimited = std::numeric_limits::max(); // 20ms timeout between re-reads of memory limit and _active_processor_count. #define OSCONTAINER_CACHE_TIMEOUT (NANOSECS_PER_SEC/50) +// Carrier object for print_container_helper() +class MetricResult: public StackObj { + private: + static const uint64_t value_unused = 0; + bool _success = false; + physical_memory_size_type _value = value_unused; + public: + void set_value(physical_memory_size_type val) { + // having a value means success + _success = true; + _value = val; + } + + bool success() { return _success; } + physical_memory_size_type value() { return _value; } +}; + class OSContainer: AllStatic { private: @@ -45,36 +64,38 @@ class OSContainer: AllStatic { public: static void init(); static void print_version_specific_info(outputStream* st); - static void print_container_helper(outputStream* st, jlong j, const char* metrics); + static void print_container_helper(outputStream* st, MetricResult& res, const char* metrics); static inline bool is_containerized(); static const char * container_type(); - static bool available_memory_in_container(julong& value); - static jlong memory_limit_in_bytes(); - static jlong memory_and_swap_limit_in_bytes(); - static jlong memory_and_swap_usage_in_bytes(); - static jlong memory_soft_limit_in_bytes(); - static jlong memory_throttle_limit_in_bytes(); - static jlong memory_usage_in_bytes(); - static jlong memory_max_usage_in_bytes(); - static jlong rss_usage_in_bytes(); - static jlong cache_usage_in_bytes(); + static bool available_memory_in_bytes(physical_memory_size_type& value); + static bool available_swap_in_bytes(physical_memory_size_type host_free_swap, + physical_memory_size_type& value); + static bool memory_limit_in_bytes(physical_memory_size_type& value); + static bool memory_and_swap_limit_in_bytes(physical_memory_size_type& value); + static bool memory_and_swap_usage_in_bytes(physical_memory_size_type& value); + static bool memory_soft_limit_in_bytes(physical_memory_size_type& value); + static bool memory_throttle_limit_in_bytes(physical_memory_size_type& value); + static bool memory_usage_in_bytes(physical_memory_size_type& value); + static bool memory_max_usage_in_bytes(physical_memory_size_type& value); + static bool rss_usage_in_bytes(physical_memory_size_type& value); + static bool cache_usage_in_bytes(physical_memory_size_type& value); - static int active_processor_count(); + static bool active_processor_count(int& value); static char * cpu_cpuset_cpus(); static char * cpu_cpuset_memory_nodes(); - static int cpu_quota(); - static int cpu_period(); + static bool cpu_quota(int& value); + static bool cpu_period(int& value); - static int cpu_shares(); + static bool cpu_shares(int& value); - static jlong cpu_usage_in_micros(); + static bool cpu_usage_in_micros(uint64_t& value); - static jlong pids_max(); - static jlong pids_current(); + static bool pids_max(uint64_t& value); + static bool pids_current(uint64_t& value); }; inline bool OSContainer::is_containerized() { diff --git a/src/hotspot/os/linux/os_linux.cpp b/src/hotspot/os/linux/os_linux.cpp index 69ef8ce7c33..a345663dd5b 100644 --- a/src/hotspot/os/linux/os_linux.cpp +++ b/src/hotspot/os/linux/os_linux.cpp @@ -214,10 +214,8 @@ static bool suppress_primordial_thread_resolution = false; // utility functions bool os::available_memory(physical_memory_size_type& value) { - julong avail_mem = 0; - if (OSContainer::is_containerized() && OSContainer::available_memory_in_container(avail_mem)) { - log_trace(os)("available container memory: " JULONG_FORMAT, avail_mem); - value = static_cast(avail_mem); + if (OSContainer::is_containerized() && OSContainer::available_memory_in_bytes(value)) { + log_trace(os)("available container memory: " PHYS_MEM_TYPE_FORMAT, value); return true; } @@ -225,36 +223,38 @@ bool os::available_memory(physical_memory_size_type& value) { } bool os::Linux::available_memory(physical_memory_size_type& value) { - julong avail_mem = static_cast(-1L); + physical_memory_size_type avail_mem = 0; + bool found_available_mem = false; FILE *fp = os::fopen("/proc/meminfo", "r"); if (fp != nullptr) { char buf[80]; do { - if (fscanf(fp, "MemAvailable: " JULONG_FORMAT " kB", &avail_mem) == 1) { + if (fscanf(fp, "MemAvailable: " PHYS_MEM_TYPE_FORMAT " kB", &avail_mem) == 1) { avail_mem *= K; + found_available_mem = true; break; } } while (fgets(buf, sizeof(buf), fp) != nullptr); fclose(fp); } - if (avail_mem == static_cast(-1L)) { + // Only enter the free memory block if we + // haven't found the available memory + if (!found_available_mem) { physical_memory_size_type free_mem = 0; if (!free_memory(free_mem)) { return false; } - avail_mem = static_cast(free_mem); + avail_mem = free_mem; } - log_trace(os)("available memory: " JULONG_FORMAT, avail_mem); - value = static_cast(avail_mem); + log_trace(os)("available memory: " PHYS_MEM_TYPE_FORMAT, avail_mem); + value = avail_mem; return true; } bool os::free_memory(physical_memory_size_type& value) { - julong free_mem = 0; - if (OSContainer::is_containerized() && OSContainer::available_memory_in_container(free_mem)) { - log_trace(os)("free container memory: " JULONG_FORMAT, free_mem); - value = static_cast(free_mem); + if (OSContainer::is_containerized() && OSContainer::available_memory_in_bytes(value)) { + log_trace(os)("free container memory: " PHYS_MEM_TYPE_FORMAT, value); return true; } @@ -269,29 +269,26 @@ bool os::Linux::free_memory(physical_memory_size_type& value) { if (ret != 0) { return false; } - julong free_mem = (julong)si.freeram * si.mem_unit; - log_trace(os)("free memory: " JULONG_FORMAT, free_mem); - value = static_cast(free_mem); + physical_memory_size_type free_mem = (physical_memory_size_type)si.freeram * si.mem_unit; + log_trace(os)("free memory: " PHYS_MEM_TYPE_FORMAT, free_mem); + value = free_mem; return true; } bool os::total_swap_space(physical_memory_size_type& value) { if (OSContainer::is_containerized()) { - jlong memory_and_swap_limit_in_bytes = OSContainer::memory_and_swap_limit_in_bytes(); - jlong memory_limit_in_bytes = OSContainer::memory_limit_in_bytes(); - if (memory_limit_in_bytes > 0 && memory_and_swap_limit_in_bytes > 0) { - value = static_cast(memory_and_swap_limit_in_bytes - memory_limit_in_bytes); - return true; + physical_memory_size_type mem_swap_limit = value_unlimited; + physical_memory_size_type memory_limit = value_unlimited; + if (OSContainer::memory_and_swap_limit_in_bytes(mem_swap_limit) && + OSContainer::memory_limit_in_bytes(memory_limit)) { + if (memory_limit != value_unlimited && mem_swap_limit != value_unlimited && + mem_swap_limit >= memory_limit /* ensure swap is >= 0 */) { + value = mem_swap_limit - memory_limit; + return true; + } } - } // fallback to the host swap space if the container did return the unbound value of -1 - struct sysinfo si; - int ret = sysinfo(&si); - if (ret != 0) { - assert(false, "sysinfo failed in total_swap_space(): %s", os::strerror(errno)); - return false; - } - value = static_cast(si.totalswap) * si.mem_unit; - return true; + } // fallback to the host swap space if the container returned unlimited + return Linux::host_swap(value); } static bool host_free_swap_f(physical_memory_size_type& value) { @@ -315,29 +312,12 @@ bool os::free_swap_space(physical_memory_size_type& value) { } physical_memory_size_type host_free_swap_val = MIN2(total_swap_space, host_free_swap); if (OSContainer::is_containerized()) { - jlong mem_swap_limit = OSContainer::memory_and_swap_limit_in_bytes(); - jlong mem_limit = OSContainer::memory_limit_in_bytes(); - if (mem_swap_limit >= 0 && mem_limit >= 0) { - jlong delta_limit = mem_swap_limit - mem_limit; - if (delta_limit <= 0) { - value = 0; - return true; - } - jlong mem_swap_usage = OSContainer::memory_and_swap_usage_in_bytes(); - jlong mem_usage = OSContainer::memory_usage_in_bytes(); - if (mem_swap_usage > 0 && mem_usage > 0) { - jlong delta_usage = mem_swap_usage - mem_usage; - if (delta_usage >= 0) { - jlong free_swap = delta_limit - delta_usage; - value = free_swap >= 0 ? static_cast(free_swap) : 0; - return true; - } - } + if (OSContainer::available_swap_in_bytes(host_free_swap_val, value)) { + return true; } - // unlimited or not supported. Fall through to return host value - log_trace(os,container)("os::free_swap_space: container_swap_limit=" JLONG_FORMAT - " container_mem_limit=" JLONG_FORMAT " returning host value: " PHYS_MEM_TYPE_FORMAT, - mem_swap_limit, mem_limit, host_free_swap_val); + // Fall through to use host value + log_trace(os,container)("os::free_swap_space: containerized value unavailable" + " returning host value: " PHYS_MEM_TYPE_FORMAT, host_free_swap_val); } value = host_free_swap_val; return true; @@ -345,10 +325,10 @@ bool os::free_swap_space(physical_memory_size_type& value) { physical_memory_size_type os::physical_memory() { if (OSContainer::is_containerized()) { - jlong mem_limit; - if ((mem_limit = OSContainer::memory_limit_in_bytes()) > 0) { - log_trace(os)("total container memory: " JLONG_FORMAT, mem_limit); - return static_cast(mem_limit); + physical_memory_size_type mem_limit = value_unlimited; + if (OSContainer::memory_limit_in_bytes(mem_limit) && mem_limit != value_unlimited) { + log_trace(os)("total container memory: " PHYS_MEM_TYPE_FORMAT, mem_limit); + return mem_limit; } } @@ -508,10 +488,15 @@ pid_t os::Linux::gettid() { // Returns the amount of swap currently configured, in bytes. // This can change at any time. -julong os::Linux::host_swap() { +bool os::Linux::host_swap(physical_memory_size_type& value) { struct sysinfo si; - sysinfo(&si); - return (julong)(si.totalswap * si.mem_unit); + int ret = sysinfo(&si); + if (ret != 0) { + assert(false, "sysinfo failed in host_swap(): %s", os::strerror(errno)); + return false; + } + value = static_cast(si.totalswap) * si.mem_unit; + return true; } // Most versions of linux have a bug where the number of processors are @@ -2469,9 +2454,11 @@ bool os::Linux::print_container_info(outputStream* st) { st->print_cr("cpu_memory_nodes: %s", p != nullptr ? p : "not supported"); free(p); - int i = OSContainer::active_processor_count(); + int i = -1; + bool supported = OSContainer::active_processor_count(i); st->print("active_processor_count: "); - if (i > 0) { + if (supported) { + assert(i > 0, "must be"); if (ActiveProcessorCount > 0) { st->print_cr("%d, but overridden by -XX:ActiveProcessorCount %d", i, ActiveProcessorCount); } else { @@ -2481,65 +2468,105 @@ bool os::Linux::print_container_info(outputStream* st) { st->print_cr("not supported"); } - i = OSContainer::cpu_quota(); + + supported = OSContainer::cpu_quota(i); st->print("cpu_quota: "); - if (i > 0) { + if (supported && i > 0) { st->print_cr("%d", i); } else { - st->print_cr("%s", i == OSCONTAINER_ERROR ? "not supported" : "no quota"); + st->print_cr("%s", !supported ? "not supported" : "no quota"); } - i = OSContainer::cpu_period(); + supported = OSContainer::cpu_period(i); st->print("cpu_period: "); - if (i > 0) { + if (supported && i > 0) { st->print_cr("%d", i); } else { - st->print_cr("%s", i == OSCONTAINER_ERROR ? "not supported" : "no period"); + st->print_cr("%s", !supported ? "not supported" : "no period"); } - i = OSContainer::cpu_shares(); + supported = OSContainer::cpu_shares(i); st->print("cpu_shares: "); - if (i > 0) { + if (supported && i > 0) { st->print_cr("%d", i); } else { - st->print_cr("%s", i == OSCONTAINER_ERROR ? "not supported" : "no shares"); + st->print_cr("%s", !supported ? "not supported" : "no shares"); } - jlong j = OSContainer::cpu_usage_in_micros(); + uint64_t j = 0; + supported = OSContainer::cpu_usage_in_micros(j); st->print("cpu_usage_in_micros: "); - if (j >= 0) { - st->print_cr(JLONG_FORMAT, j); + if (supported && j > 0) { + st->print_cr(UINT64_FORMAT, j); } else { - st->print_cr("%s", j == OSCONTAINER_ERROR ? "not supported" : "no usage"); + st->print_cr("%s", !supported ? "not supported" : "no usage"); } - OSContainer::print_container_helper(st, OSContainer::memory_limit_in_bytes(), "memory_limit_in_bytes"); - OSContainer::print_container_helper(st, OSContainer::memory_and_swap_limit_in_bytes(), "memory_and_swap_limit_in_bytes"); - OSContainer::print_container_helper(st, OSContainer::memory_soft_limit_in_bytes(), "memory_soft_limit_in_bytes"); - OSContainer::print_container_helper(st, OSContainer::memory_throttle_limit_in_bytes(), "memory_throttle_limit_in_bytes"); - OSContainer::print_container_helper(st, OSContainer::memory_usage_in_bytes(), "memory_usage_in_bytes"); - OSContainer::print_container_helper(st, OSContainer::memory_max_usage_in_bytes(), "memory_max_usage_in_bytes"); - OSContainer::print_container_helper(st, OSContainer::rss_usage_in_bytes(), "rss_usage_in_bytes"); - OSContainer::print_container_helper(st, OSContainer::cache_usage_in_bytes(), "cache_usage_in_bytes"); + MetricResult memory_limit; + physical_memory_size_type val = value_unlimited; + if (OSContainer::memory_limit_in_bytes(val)) { + memory_limit.set_value(val); + } + MetricResult mem_swap_limit; + val = value_unlimited; + if (OSContainer::memory_and_swap_limit_in_bytes(val)) { + mem_swap_limit.set_value(val); + } + MetricResult mem_soft_limit; + val = value_unlimited; + if (OSContainer::memory_soft_limit_in_bytes(val)) { + mem_soft_limit.set_value(val); + } + MetricResult mem_throttle_limit; + val = value_unlimited; + if (OSContainer::memory_throttle_limit_in_bytes(val)) { + mem_throttle_limit.set_value(val); + } + MetricResult mem_usage; + val = 0; + if (OSContainer::memory_usage_in_bytes(val)) { + mem_usage.set_value(val); + } + MetricResult mem_max_usage; + val = 0; + if (OSContainer::memory_max_usage_in_bytes(val)) { + mem_max_usage.set_value(val); + } + MetricResult rss_usage; + val = 0; + if (OSContainer::rss_usage_in_bytes(val)) { + rss_usage.set_value(val); + } + MetricResult cache_usage; + val = 0; + if (OSContainer::cache_usage_in_bytes(val)) { + cache_usage.set_value(val); + } + OSContainer::print_container_helper(st, memory_limit, "memory_limit_in_bytes"); + OSContainer::print_container_helper(st, mem_swap_limit, "memory_and_swap_limit_in_bytes"); + OSContainer::print_container_helper(st, mem_soft_limit, "memory_soft_limit_in_bytes"); + OSContainer::print_container_helper(st, mem_throttle_limit, "memory_throttle_limit_in_bytes"); + OSContainer::print_container_helper(st, mem_usage, "memory_usage_in_bytes"); + OSContainer::print_container_helper(st, mem_max_usage, "memory_max_usage_in_bytes"); + OSContainer::print_container_helper(st, rss_usage, "rss_usage_in_bytes"); + OSContainer::print_container_helper(st, cache_usage, "cache_usage_in_bytes"); OSContainer::print_version_specific_info(st); - j = OSContainer::pids_max(); + supported = OSContainer::pids_max(j); st->print("maximum number of tasks: "); - if (j > 0) { - st->print_cr(JLONG_FORMAT, j); + if (supported && j != value_unlimited) { + st->print_cr(UINT64_FORMAT, j); } else { - st->print_cr("%s", j == OSCONTAINER_ERROR ? "not supported" : "unlimited"); + st->print_cr("%s", !supported ? "not supported" : "unlimited"); } - j = OSContainer::pids_current(); + supported = OSContainer::pids_current(j); st->print("current number of tasks: "); - if (j > 0) { - st->print_cr(JLONG_FORMAT, j); + if (supported && j > 0) { + st->print_cr(UINT64_FORMAT, j); } else { - if (j == OSCONTAINER_ERROR) { - st->print_cr("not supported"); - } + st->print_cr("%s", !supported ? "not supported" : "no current tasks"); } return true; @@ -4643,7 +4670,7 @@ int os::Linux::active_processor_count() { // // 1. User option -XX:ActiveProcessorCount // 2. kernel os calls (sched_getaffinity or sysconf(_SC_NPROCESSORS_ONLN) -// 3. extracted from cgroup cpu subsystem (shares and quotas) +// 3. extracted from cgroup cpu subsystem (quotas) // // Option 1, if specified, will always override. // If the cgroup subsystem is active and configured, we @@ -4660,9 +4687,8 @@ int os::active_processor_count() { return ActiveProcessorCount; } - int active_cpus; - if (OSContainer::is_containerized()) { - active_cpus = OSContainer::active_processor_count(); + int active_cpus = -1; + if (OSContainer::is_containerized() && OSContainer::active_processor_count(active_cpus)) { log_trace(os)("active_processor_count: determined by OSContainer: %d", active_cpus); } else { diff --git a/src/hotspot/os/linux/os_linux.hpp b/src/hotspot/os/linux/os_linux.hpp index b77cd9f3c81..df96a17d8e9 100644 --- a/src/hotspot/os/linux/os_linux.hpp +++ b/src/hotspot/os/linux/os_linux.hpp @@ -45,8 +45,6 @@ class os::Linux { static GrowableArray* _cpu_to_node; static GrowableArray* _nindex_to_node; - static julong available_memory_in_container(); - protected: static physical_memory_size_type _physical_memory; @@ -117,7 +115,7 @@ class os::Linux { static uintptr_t initial_thread_stack_size(void) { return _initial_thread_stack_size; } static physical_memory_size_type physical_memory() { return _physical_memory; } - static julong host_swap(); + static bool host_swap(physical_memory_size_type& value); static intptr_t* ucontext_get_sp(const ucontext_t* uc); static intptr_t* ucontext_get_fp(const ucontext_t* uc); diff --git a/src/hotspot/share/jfr/jni/jfrJniMethod.cpp b/src/hotspot/share/jfr/jni/jfrJniMethod.cpp index cc5bbe1fc60..6a1146587bc 100644 --- a/src/hotspot/share/jfr/jni/jfrJniMethod.cpp +++ b/src/hotspot/share/jfr/jni/jfrJniMethod.cpp @@ -421,7 +421,9 @@ JVM_END JVM_ENTRY_NO_ENV(jlong, jfr_host_total_swap_memory(JNIEnv* env, jclass jvm)) #ifdef LINUX // We want the host swap memory, not the container value. - return os::Linux::host_swap(); + physical_memory_size_type host_swap = 0; + (void)os::Linux::host_swap(host_swap); // Discard return value and treat as no swap + return static_cast(host_swap); #else physical_memory_size_type total_swap_space = 0; // Return value ignored - defaulting to 0 on failure. diff --git a/src/hotspot/share/prims/whitebox.cpp b/src/hotspot/share/prims/whitebox.cpp index 8f0a2320288..5514f7d3260 100644 --- a/src/hotspot/share/prims/whitebox.cpp +++ b/src/hotspot/share/prims/whitebox.cpp @@ -2590,7 +2590,13 @@ WB_END // Physical swap of the host machine (including containers), Linux only. WB_ENTRY(jlong, WB_HostPhysicalSwap(JNIEnv* env, jobject o)) - LINUX_ONLY(return (jlong)os::Linux::host_swap();) +#ifdef LINUX + physical_memory_size_type swap_val = 0; + if (!os::Linux::host_swap(swap_val)) { + return -1; // treat as unlimited + } + return static_cast(swap_val); +#endif return -1; // Not used/implemented on other platforms WB_END diff --git a/src/hotspot/share/runtime/os.cpp b/src/hotspot/share/runtime/os.cpp index 674b0a55841..ceff9b54c33 100644 --- a/src/hotspot/share/runtime/os.cpp +++ b/src/hotspot/share/runtime/os.cpp @@ -2207,13 +2207,7 @@ static void assert_nonempty_range(const char* addr, size_t bytes) { bool os::used_memory(physical_memory_size_type& value) { #ifdef LINUX if (OSContainer::is_containerized()) { - jlong mem_usage = OSContainer::memory_usage_in_bytes(); - if (mem_usage > 0) { - value = static_cast(mem_usage); - return true; - } else { - return false; - } + return OSContainer::memory_usage_in_bytes(value); } #endif physical_memory_size_type avail_mem = 0; diff --git a/test/hotspot/gtest/runtime/test_cgroupSubsystem_linux.cpp b/test/hotspot/gtest/runtime/test_cgroupSubsystem_linux.cpp index b33d6454477..7a4f7bcb99e 100644 --- a/test/hotspot/gtest/runtime/test_cgroupSubsystem_linux.cpp +++ b/test/hotspot/gtest/runtime/test_cgroupSubsystem_linux.cpp @@ -104,35 +104,35 @@ TEST(cgroupTest, read_numerical_key_value_failure_cases) { const char* base_with_slash = path.as_string(true); TestController* controller = new TestController((char*)os::get_temp_directory()); - constexpr julong bad = 0xBAD; - julong x = bad; + constexpr uint64_t bad = 0xBAD; + uint64_t x = bad; fill_file(test_file, "foo "); - bool is_ok = controller->read_numerical_key_value(base_with_slash, "foo", &x); + bool is_ok = controller->read_numerical_key_value(base_with_slash, "foo", x); EXPECT_FALSE(is_ok) << "Value is missing in key/value case, expecting false"; EXPECT_EQ(bad, x) << "x must be unchanged"; x = bad; fill_file(test_file, "faulty_start foo 101"); - is_ok = controller->read_numerical_key_value(base_with_slash, "foo", &x); + is_ok = controller->read_numerical_key_value(base_with_slash, "foo", x); EXPECT_FALSE(is_ok) << "key must be at the start"; EXPECT_EQ(bad, x) << "x must be unchanged"; x = bad; fill_file(test_file, nullptr); - is_ok = controller->read_numerical_key_value(base_with_slash, "foo", &x); + is_ok = controller->read_numerical_key_value(base_with_slash, "foo", x); EXPECT_FALSE(is_ok) << "key not in empty file"; EXPECT_EQ(bad, x) << "x must be unchanged"; x = bad; fill_file(test_file, "foo\n"); - is_ok = controller->read_numerical_key_value(base_with_slash, "foo", &x); + is_ok = controller->read_numerical_key_value(base_with_slash, "foo", x); EXPECT_FALSE(is_ok) << "key must have a value"; EXPECT_EQ(bad, x) << "x must be unchanged"; x = bad; fill_file(test_file, "foof 1002"); - is_ok = controller->read_numerical_key_value(base_with_slash, "foo", &x); + is_ok = controller->read_numerical_key_value(base_with_slash, "foo", x); EXPECT_FALSE(is_ok) << "key must be exact match"; EXPECT_EQ(bad, x) << "x must be unchanged"; @@ -150,43 +150,43 @@ TEST(cgroupTest, read_numerical_key_value_success_cases) { const char* base_with_slash = path.as_string(true); TestController* controller = new TestController((char*)os::get_temp_directory()); - constexpr julong bad = 0xBAD; - julong x = bad; + constexpr uint64_t bad = 0xBAD; + uint64_t x = bad; fill_file(test_file, "foo 100"); - bool is_ok = controller->read_numerical_key_value(base_with_slash, "foo", &x); + bool is_ok = controller->read_numerical_key_value(base_with_slash, "foo", x); EXPECT_TRUE(is_ok); - EXPECT_EQ((julong)100, x); + EXPECT_EQ((uint64_t)100, x); x = bad; fill_file(test_file, "foo\t111"); - is_ok = controller->read_numerical_key_value(base_with_slash, "foo", &x); + is_ok = controller->read_numerical_key_value(base_with_slash, "foo", x); EXPECT_TRUE(is_ok); - EXPECT_EQ((julong)111, x); + EXPECT_EQ((uint64_t)111, x); x = bad; fill_file(test_file, "foo\nbar 333\nfoo\t111"); - is_ok = controller->read_numerical_key_value(base_with_slash, "foo", &x); + is_ok = controller->read_numerical_key_value(base_with_slash, "foo", x); EXPECT_TRUE(is_ok); - EXPECT_EQ((julong)111, x); + EXPECT_EQ((uint64_t)111, x); x = bad; fill_file(test_file, "foof 100\nfoo 133"); - is_ok = controller->read_numerical_key_value(base_with_slash, "foo", &x); + is_ok = controller->read_numerical_key_value(base_with_slash, "foo", x); EXPECT_TRUE(is_ok); - EXPECT_EQ((julong)133, x); + EXPECT_EQ((uint64_t)133, x); x = bad; fill_file(test_file, "foo\t333\nfoot 999"); - is_ok = controller->read_numerical_key_value(base_with_slash, "foo", &x); + is_ok = controller->read_numerical_key_value(base_with_slash, "foo", x); EXPECT_TRUE(is_ok); - EXPECT_EQ((julong)333, x); + EXPECT_EQ((uint64_t)333, x); x = bad; fill_file(test_file, "foo 1\nfoo car"); - is_ok = controller->read_numerical_key_value(base_with_slash, "foo", &x); + is_ok = controller->read_numerical_key_value(base_with_slash, "foo", x); EXPECT_TRUE(is_ok); - EXPECT_EQ((julong)1, x); + EXPECT_EQ((uint64_t)1, x); // Cleanup delete_file(test_file); @@ -195,10 +195,10 @@ TEST(cgroupTest, read_numerical_key_value_success_cases) { TEST(cgroupTest, read_number_null) { TestController* null_path_controller = new TestController((char*)nullptr); const char* test_file_path = "/not-used"; - constexpr julong bad = 0xBAD; - julong a = bad; + constexpr uint64_t bad = 0xBAD; + uint64_t a = bad; // null subsystem_path() case - bool is_ok = null_path_controller->read_number(test_file_path, &a); + bool is_ok = null_path_controller->read_number(test_file_path, a); EXPECT_FALSE(is_ok) << "Null subsystem path should be an error"; EXPECT_EQ(bad, a) << "Expected untouched scan value"; } @@ -221,9 +221,9 @@ TEST(cgroupTest, read_string_beyond_max_path) { TEST(cgroupTest, read_number_file_not_exist) { TestController* unknown_path_ctrl = new TestController((char*)"/do/not/exist"); const char* test_file_path = "/file-not-found"; - constexpr julong bad = 0xBAD; - julong result = bad; - bool is_ok = unknown_path_ctrl->read_number(test_file_path, &result); + constexpr uint64_t bad = 0xBAD; + uint64_t result = bad; + bool is_ok = unknown_path_ctrl->read_number(test_file_path, result); EXPECT_FALSE(is_ok) << "File not found should be an error"; EXPECT_EQ(bad, result) << "Expected untouched scan value"; } @@ -232,10 +232,10 @@ TEST(cgroupTest, read_numerical_key_value_null) { TestController* null_path_controller = new TestController((char*)nullptr); const char* test_file_path = "/not-used"; const char* key = "something"; - constexpr julong bad = 0xBAD; - julong a = bad; + constexpr uint64_t bad = 0xBAD; + uint64_t a = bad; // null subsystem_path() case - bool is_ok = null_path_controller->read_numerical_key_value(test_file_path, key, &a); + bool is_ok = null_path_controller->read_numerical_key_value(test_file_path, key, a); EXPECT_FALSE(is_ok) << "Null subsystem path should be an error"; EXPECT_EQ(bad, a) << "Expected untouched scan value"; } @@ -243,7 +243,7 @@ TEST(cgroupTest, read_numerical_key_value_null) { TEST(cgroupTest, read_number_tests) { char* test_file = temp_file("cgroups"); const char* b = basename(test_file); - constexpr julong bad = 0xBAD; + constexpr uint64_t bad = 0xBAD; EXPECT_TRUE(b != nullptr) << "basename was null"; stringStream path; path.print_raw(os::file_separator()); @@ -252,44 +252,49 @@ TEST(cgroupTest, read_number_tests) { fill_file(test_file, "8888"); TestController* controller = new TestController((char*)os::get_temp_directory()); - julong foo = bad; - bool ok = controller->read_number(base_with_slash, &foo); + uint64_t foo = bad; + bool ok = controller->read_number(base_with_slash, foo); EXPECT_TRUE(ok) << "Number parsing should have been successful"; - EXPECT_EQ((julong)8888, foo) << "Wrong value for 'foo' (NOTE: 0xBAD == " << 0xBAD << ")"; + EXPECT_EQ((uint64_t)8888, foo) << "Wrong value for 'foo' (NOTE: 0xBAD == " << 0xBAD << ")"; // Some interface files might have negative values, ensure we can read - // them and manually cast them as needed. + // them and manually cast them as needed. For example, on cgv1, the cpu.cfs_quota_us + // file might be set to -1 to indicate no cpu quota setup. fill_file(test_file, "-1"); foo = bad; - ok = controller->read_number(base_with_slash, &foo); + ok = controller->read_number(base_with_slash, foo); EXPECT_TRUE(ok) << "Number parsing should have been successful"; - EXPECT_EQ((jlong)-1, (jlong)foo) << "Wrong value for 'foo' (NOTE: 0xBAD == " << 0xBAD << ")"; + EXPECT_EQ((int)-1, (int)foo) << "Wrong value for 'foo' (NOTE: 0xBAD == " << 0xBAD << ")"; foo = bad; fill_file(test_file, nullptr); - ok = controller->read_number(base_with_slash, &foo); + ok = controller->read_number(base_with_slash, foo); EXPECT_FALSE(ok) << "Empty file should have failed"; EXPECT_EQ(bad, foo) << "foo was altered"; // Some interface files have numbers as well as the string // 'max', which means unlimited. - jlong result = -10; + uint64_t result = 0; + uint64_t unlimited = std::numeric_limits::max(); fill_file(test_file, "max\n"); - ok = controller->read_number_handle_max(base_with_slash, &result); + ok = controller->read_number_handle_max(base_with_slash, result); EXPECT_TRUE(ok) << "Number parsing for 'max' string should have been successful"; - EXPECT_EQ((jlong)-1, result) << "'max' means unlimited (-1)"; + EXPECT_EQ(unlimited, result) << "'max' means unlimited (-1)"; - result = -10; + result = 0; fill_file(test_file, "11114\n"); - ok = controller->read_number_handle_max(base_with_slash, &result); + ok = controller->read_number_handle_max(base_with_slash, result); EXPECT_TRUE(ok) << "Number parsing for should have been successful"; - EXPECT_EQ((jlong)11114, result) << "Incorrect result"; + EXPECT_EQ((uint64_t)11114, result) << "Incorrect result"; - result = -10; + result = 0; + // This is a contrived test case not matching cgroup interface files + // in the wild where numbers are positive. The value is deliberately + // negative. Yet it should work fill_file(test_file, "-51114\n"); - ok = controller->read_number_handle_max(base_with_slash, &result); + ok = controller->read_number_handle_max(base_with_slash, result); EXPECT_TRUE(ok) << "Number parsing for should have been successful"; - EXPECT_EQ((jlong)-51114, result) << "Incorrect result"; + EXPECT_EQ((int)-51114, (int)result) << "Incorrect result"; delete_file(test_file); } @@ -372,28 +377,28 @@ TEST(cgroupTest, read_number_tuple_test) { fill_file(test_file, "max 10000"); TestController* controller = new TestController((char*)os::get_temp_directory()); - jlong result = -10; - bool ok = controller->read_numerical_tuple_value(base_with_slash, true /* use_first */, &result); + uint64_t result = 0; + bool ok = controller->read_numerical_tuple_value(base_with_slash, true /* use_first */, result); EXPECT_TRUE(ok) << "Should be OK to read value"; - EXPECT_EQ((jlong)-1, result) << "max should be unlimited (-1)"; + EXPECT_EQ(value_unlimited, result) << "max should be unlimited (-1)"; - result = -10; - ok = controller->read_numerical_tuple_value(base_with_slash, false /* use_first */, &result); + result = 0; + ok = controller->read_numerical_tuple_value(base_with_slash, false /* use_first */, result); EXPECT_TRUE(ok) << "Should be OK to read the value"; - EXPECT_EQ((jlong)10000, result); + EXPECT_EQ((uint64_t)10000, result); // non-max strings fill_file(test_file, "abc 10000"); - result = -10; - ok = controller->read_numerical_tuple_value(base_with_slash, true /* use_first */, &result); + result = 0; + ok = controller->read_numerical_tuple_value(base_with_slash, true /* use_first */, result); EXPECT_FALSE(ok) << "abc should not be parsable"; - EXPECT_EQ((jlong)-10, result) << "result value should be unchanged"; + EXPECT_EQ((uint64_t)0, result) << "result value should be unchanged"; fill_file(test_file, nullptr); - result = -10; - ok = controller->read_numerical_tuple_value(base_with_slash, true /* use_first */, &result); + result = 0; + ok = controller->read_numerical_tuple_value(base_with_slash, true /* use_first */, result); EXPECT_FALSE(ok) << "Empty file should be an error"; - EXPECT_EQ((jlong)-10, result) << "result value should be unchanged"; + EXPECT_EQ((uint64_t)0, result) << "result value should be unchanged"; delete_file(test_file); } @@ -407,20 +412,20 @@ TEST(cgroupTest, read_numerical_key_beyond_max_path) { TestController* too_large_path_controller = new TestController(larger_than_max); const char* test_file_path = "/file-not-found"; const char* key = "something"; - julong a = 0xBAD; - bool is_ok = too_large_path_controller->read_numerical_key_value(test_file_path, key, &a); + uint64_t a = 0xBAD; + bool is_ok = too_large_path_controller->read_numerical_key_value(test_file_path, key, a); EXPECT_FALSE(is_ok) << "Too long path should be an error"; - EXPECT_EQ((julong)0xBAD, a) << "Expected untouched scan value"; + EXPECT_EQ((uint64_t)0xBAD, a) << "Expected untouched scan value"; } TEST(cgroupTest, read_numerical_key_file_not_exist) { TestController* unknown_path_ctrl = new TestController((char*)"/do/not/exist"); const char* test_file_path = "/file-not-found"; const char* key = "something"; - julong a = 0xBAD; - bool is_ok = unknown_path_ctrl->read_numerical_key_value(test_file_path, key, &a); + uint64_t a = 0xBAD; + bool is_ok = unknown_path_ctrl->read_numerical_key_value(test_file_path, key, a); EXPECT_FALSE(is_ok) << "File not found should be an error"; - EXPECT_EQ((julong)0xBAD, a) << "Expected untouched scan value"; + EXPECT_EQ((uint64_t)0xBAD, a) << "Expected untouched scan value"; } TEST(cgroupTest, set_cgroupv1_subsystem_path) { From 3a2845f334a59670d54699919073f0e908c038c4 Mon Sep 17 00:00:00 2001 From: Francesco Andreuzzi Date: Tue, 18 Nov 2025 09:43:28 +0000 Subject: [PATCH 101/418] 8037914: Add JFR event for string deduplication Reviewed-by: ayang, egahlin --- .../stringdedup/stringDedupProcessor.cpp | 8 +- .../gc/shared/stringdedup/stringDedupStat.cpp | 50 ++++- .../gc/shared/stringdedup/stringDedupStat.hpp | 3 +- .../shared/stringdedup/stringDedupTable.cpp | 4 + src/hotspot/share/jfr/metadata/metadata.xml | 20 +- src/jdk.jfr/share/conf/jfr/default.jfc | 5 + src/jdk.jfr/share/conf/jfr/profile.jfc | 5 + .../TestStringDeduplicationEvent.java | 172 ++++++++++++++++++ test/lib/jdk/test/lib/jfr/EventNames.java | 1 + 9 files changed, 251 insertions(+), 17 deletions(-) create mode 100644 test/jdk/jdk/jfr/event/gc/detailed/TestStringDeduplicationEvent.java diff --git a/src/hotspot/share/gc/shared/stringdedup/stringDedupProcessor.cpp b/src/hotspot/share/gc/shared/stringdedup/stringDedupProcessor.cpp index 8bf6f4e539a..b8a27f31d01 100644 --- a/src/hotspot/share/gc/shared/stringdedup/stringDedupProcessor.cpp +++ b/src/hotspot/share/gc/shared/stringdedup/stringDedupProcessor.cpp @@ -198,10 +198,8 @@ void StringDedup::Processor::run(JavaThread* thread) { void StringDedup::Processor::log_statistics() { _total_stat.add(&_cur_stat); Stat::log_summary(&_cur_stat, &_total_stat); - if (log_is_enabled(Debug, stringdedup)) { - _cur_stat.log_statistics(false); - _total_stat.log_statistics(true); - Table::log_statistics(); - } + _cur_stat.emit_statistics(false /* total */); + _total_stat.emit_statistics(true /* total */); + Table::log_statistics(); _cur_stat = Stat{}; } diff --git a/src/hotspot/share/gc/shared/stringdedup/stringDedupStat.cpp b/src/hotspot/share/gc/shared/stringdedup/stringDedupStat.cpp index 28e5e9adf20..245a0ab20e9 100644 --- a/src/hotspot/share/gc/shared/stringdedup/stringDedupStat.cpp +++ b/src/hotspot/share/gc/shared/stringdedup/stringDedupStat.cpp @@ -23,6 +23,7 @@ */ #include "gc/shared/stringdedup/stringDedupStat.hpp" +#include "jfr/jfrEvents.hpp" #include "logging/log.hpp" #include "utilities/globalDefinitions.hpp" @@ -91,13 +92,6 @@ static double strdedup_elapsed_param_ms(Tickspan t) { } void StringDedup::Stat::log_summary(const Stat* last_stat, const Stat* total_stat) { - double total_deduped_bytes_percent = 0.0; - - if (total_stat->_new_bytes > 0) { - // Avoid division by zero - total_deduped_bytes_percent = percent_of(total_stat->_deduped_bytes, total_stat->_new_bytes); - } - log_info(stringdedup)( "Concurrent String Deduplication " "%zu/" STRDEDUP_BYTES_FORMAT_NS " (new), " @@ -106,7 +100,7 @@ void StringDedup::Stat::log_summary(const Stat* last_stat, const Stat* total_sta STRDEDUP_ELAPSED_FORMAT_MS " of " STRDEDUP_ELAPSED_FORMAT_MS, last_stat->_new, STRDEDUP_BYTES_PARAM(last_stat->_new_bytes), last_stat->_deduped, STRDEDUP_BYTES_PARAM(last_stat->_deduped_bytes), - total_deduped_bytes_percent, + percent_of(total_stat->_deduped_bytes, total_stat->_new_bytes), strdedup_elapsed_param_ms(last_stat->_process_elapsed), strdedup_elapsed_param_ms(last_stat->_active_elapsed)); } @@ -208,7 +202,7 @@ void StringDedup::Stat::log_times(const char* prefix) const { } } -void StringDedup::Stat::log_statistics(bool total) const { +void StringDedup::Stat::log_statistics() const { double known_percent = percent_of(_known, _inspected); double known_shared_percent = percent_of(_known_shared, _inspected); double new_percent = percent_of(_new, _inspected); @@ -216,7 +210,6 @@ void StringDedup::Stat::log_statistics(bool total) const { double deduped_bytes_percent = percent_of(_deduped_bytes, _new_bytes); double replaced_percent = percent_of(_replaced, _new); double deleted_percent = percent_of(_deleted, _new); - log_times(total ? "Total" : "Last"); log_debug(stringdedup)(" Inspected: %12zu", _inspected); log_debug(stringdedup)(" Known: %12zu(%5.1f%%)", _known, known_percent); log_debug(stringdedup)(" Shared: %12zu(%5.1f%%)", _known_shared, known_shared_percent); @@ -229,3 +222,40 @@ void StringDedup::Stat::log_statistics(bool total) const { log_debug(stringdedup)(" Skipped: %zu (dead), %zu (incomplete), %zu (shared)", _skipped_dead, _skipped_incomplete, _skipped_shared); } + +void StringDedup::Stat::emit_statistics(bool total) const { + if (log_is_enabled(Debug, stringdedup)) { + log_times(total ? "Total" : "Last"); + log_statistics(); + } + + if (total) { + // Send only JFR events about the last stats + return; + } + + EventStringDeduplication e; + if (e.should_commit()) { + e.set_starttime(_active_start); + Ticks active_end = _active_start; + active_end += _active_elapsed; + e.set_endtime(active_end); + + e.set_inspected(_inspected); + e.set_known(_known); + e.set_shared(_known_shared); + e.set_newStrings(_new); + e.set_newSize(_new_bytes); + e.set_replaced(_replaced); + e.set_deleted(_deleted); + e.set_deduplicated(_deduped); + e.set_deduplicatedSize(_deduped_bytes); + e.set_skippedDead(_skipped_dead); + e.set_skippedIncomplete(_skipped_incomplete); + e.set_skippedShared(_skipped_shared); + e.set_processing(_process_elapsed); + e.set_tableResize(_resize_table_elapsed); + e.set_tableCleanup(_cleanup_table_elapsed); + e.commit(); + } +} diff --git a/src/hotspot/share/gc/shared/stringdedup/stringDedupStat.hpp b/src/hotspot/share/gc/shared/stringdedup/stringDedupStat.hpp index db753af3be5..fb864ab34ab 100644 --- a/src/hotspot/share/gc/shared/stringdedup/stringDedupStat.hpp +++ b/src/hotspot/share/gc/shared/stringdedup/stringDedupStat.hpp @@ -71,6 +71,7 @@ private: void report_phase_end(const char* phase, Tickspan* elapsed); void log_times(const char* prefix) const; + void log_statistics() const; public: Stat(); @@ -148,7 +149,7 @@ public: void report_active_end(); void add(const Stat* const stat); - void log_statistics(bool total) const; + void emit_statistics(bool total) const; static void log_summary(const Stat* last_stat, const Stat* total_stat); }; diff --git a/src/hotspot/share/gc/shared/stringdedup/stringDedupTable.cpp b/src/hotspot/share/gc/shared/stringdedup/stringDedupTable.cpp index 6682993766d..a376f3b96de 100644 --- a/src/hotspot/share/gc/shared/stringdedup/stringDedupTable.cpp +++ b/src/hotspot/share/gc/shared/stringdedup/stringDedupTable.cpp @@ -730,6 +730,10 @@ void StringDedup::Table::verify() { } void StringDedup::Table::log_statistics() { + if (!log_is_enabled(Debug, stringdedup)) { + return; + } + size_t dead_count; int dead_state; { diff --git a/src/hotspot/share/jfr/metadata/metadata.xml b/src/hotspot/share/jfr/metadata/metadata.xml index 6d43123ae87..eaafef37306 100644 --- a/src/hotspot/share/jfr/metadata/metadata.xml +++ b/src/hotspot/share/jfr/metadata/metadata.xml @@ -1283,7 +1283,25 @@ - + + + + + + + + + + + + + + + + + + + diff --git a/src/jdk.jfr/share/conf/jfr/default.jfc b/src/jdk.jfr/share/conf/jfr/default.jfc index e4dc3315d2b..554e09261d6 100644 --- a/src/jdk.jfr/share/conf/jfr/default.jfc +++ b/src/jdk.jfr/share/conf/jfr/default.jfc @@ -491,6 +491,11 @@ true + + true + 0 ms + + true diff --git a/src/jdk.jfr/share/conf/jfr/profile.jfc b/src/jdk.jfr/share/conf/jfr/profile.jfc index 619d7d90a53..108886fb18d 100644 --- a/src/jdk.jfr/share/conf/jfr/profile.jfc +++ b/src/jdk.jfr/share/conf/jfr/profile.jfc @@ -491,6 +491,11 @@ true + + true + 0 ms + + true diff --git a/test/jdk/jdk/jfr/event/gc/detailed/TestStringDeduplicationEvent.java b/test/jdk/jdk/jfr/event/gc/detailed/TestStringDeduplicationEvent.java new file mode 100644 index 00000000000..3221e76b4d2 --- /dev/null +++ b/test/jdk/jdk/jfr/event/gc/detailed/TestStringDeduplicationEvent.java @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2025, 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.jfr.event.gc.detailed; + +import java.lang.reflect.Field; +import java.lang.management.ManagementFactory; +import java.lang.management.GarbageCollectorMXBean; +import java.util.List; +import java.util.ArrayList; + +import jdk.jfr.consumer.RecordingStream; +import jdk.test.lib.jfr.EventNames; +import jdk.test.whitebox.WhiteBox; + +/** + * @test id=Serial + * @requires vm.flagless + * @requires vm.hasJFR + * @requires vm.gc.Serial + * @library /test/lib /test/jdk + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. --add-opens=java.base/java.lang=ALL-UNNAMED + * -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -ea + * -XX:+UseSerialGC + * -XX:+UseStringDeduplication + * -XX:StringDeduplicationAgeThreshold=1 + * -Xlog:stringdedup*=debug + * jdk.jfr.event.gc.detailed.TestStringDeduplicationEvent + */ + +/** + * @test id=Parallel + * @requires vm.flagless + * @requires vm.hasJFR + * @requires vm.gc.Parallel + * @library /test/lib /test/jdk + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. --add-opens=java.base/java.lang=ALL-UNNAMED + * -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -ea + * -XX:+UseParallelGC + * -XX:+UseStringDeduplication + * -XX:StringDeduplicationAgeThreshold=1 + * -Xlog:stringdedup*=debug + * jdk.jfr.event.gc.detailed.TestStringDeduplicationEvent + */ + +/** + * @test id=G1 + * @requires vm.flagless + * @requires vm.hasJFR + * @requires vm.gc.G1 + * @library /test/lib /test/jdk + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. --add-opens=java.base/java.lang=ALL-UNNAMED + * -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -ea + * -XX:+UseG1GC + * -XX:+UseStringDeduplication + * -XX:StringDeduplicationAgeThreshold=1 + * -Xlog:stringdedup*=debug + * jdk.jfr.event.gc.detailed.TestStringDeduplicationEvent + */ + +/** + * @test id=Z + * @requires vm.flagless + * @requires vm.hasJFR + * @requires vm.gc.Z + * @library /test/lib /test/jdk + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. --add-opens=java.base/java.lang=ALL-UNNAMED + * -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -ea + * -XX:+UseZGC + * -XX:+UseStringDeduplication + * -XX:StringDeduplicationAgeThreshold=1 + * -Xlog:stringdedup*=debug + * jdk.jfr.event.gc.detailed.TestStringDeduplicationEvent + */ + +/** + * @test id=Shenandoah + * @requires vm.flagless + * @requires vm.hasJFR + * @requires vm.gc.Shenandoah + * @library /test/lib /test/jdk + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. --add-opens=java.base/java.lang=ALL-UNNAMED + * -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -ea + * -XX:+UseShenandoahGC + * -XX:+UseStringDeduplication + * -XX:StringDeduplicationAgeThreshold=1 + * -Xlog:stringdedup*=debug + * jdk.jfr.event.gc.detailed.TestStringDeduplicationEvent + */ + +public class TestStringDeduplicationEvent { + private static Field valueField; + + static { + try { + valueField = String.class.getDeclaredField("value"); + valueField.setAccessible(true); + } catch (Exception exception) { + throw new RuntimeException(exception); + } + } + + public static void main(String[] args) throws Exception { + boolean zgc = isZgc(); + + try (RecordingStream recording = new RecordingStream()) { + recording.enable(EventNames.StringDeduplication); + recording.onEvent(EventNames.StringDeduplication, e -> recording.close()); + recording.startAsync(); + + String base = TestStringDeduplicationEvent.class.getSimpleName(); + String duplicate = new StringBuilder(base).toString(); + assert(getValue(base) != getValue(duplicate)); + + if (zgc) { + // ZGC only triggers string deduplications from major collections + WhiteBox.getWhiteBox().fullGC(); + } else { + WhiteBox.getWhiteBox().youngGC(); + } + + recording.awaitTermination(); + } + } + + private static Object getValue(String string) { + try { + return valueField.get(string); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static boolean isZgc() { + List gcs = ManagementFactory.getGarbageCollectorMXBeans(); + return gcs.getFirst().getName().contains("ZGC"); + } +} diff --git a/test/lib/jdk/test/lib/jfr/EventNames.java b/test/lib/jdk/test/lib/jfr/EventNames.java index e53b242097e..8b0113f75f4 100644 --- a/test/lib/jdk/test/lib/jfr/EventNames.java +++ b/test/lib/jdk/test/lib/jfr/EventNames.java @@ -160,6 +160,7 @@ public class EventNames { public static final String ZUncommit = PREFIX + "ZUncommit"; public static final String SystemGC = PREFIX + "SystemGC"; public static final String GCCPUTime = PREFIX + "GCCPUTime"; + public static final String StringDeduplication = PREFIX + "StringDeduplication"; // Compiler public static final String Compilation = PREFIX + "Compilation"; From 28d94d6ab4994b844af98c5c227b40b5fb8a72e5 Mon Sep 17 00:00:00 2001 From: Jonas Norlinder Date: Tue, 18 Nov 2025 10:08:17 +0000 Subject: [PATCH 102/418] 8372008: TestGetTotalGcCpuTime test failures on Windows (Some GC CPU time must have been reported) Reviewed-by: kevinw, alanb, cjplummer, dholmes --- .../MemoryMXBean/TestGetTotalGcCpuTime.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/test/jdk/java/lang/management/MemoryMXBean/TestGetTotalGcCpuTime.java b/test/jdk/java/lang/management/MemoryMXBean/TestGetTotalGcCpuTime.java index d8a760027a7..464218d1e15 100644 --- a/test/jdk/java/lang/management/MemoryMXBean/TestGetTotalGcCpuTime.java +++ b/test/jdk/java/lang/management/MemoryMXBean/TestGetTotalGcCpuTime.java @@ -78,12 +78,15 @@ import java.lang.management.ManagementFactory; import java.lang.management.MemoryMXBean; import java.lang.management.ThreadMXBean; +import java.util.ArrayList; public class TestGetTotalGcCpuTime { static final ThreadMXBean mxThreadBean = ManagementFactory.getThreadMXBean(); static final MemoryMXBean mxMemoryBean = ManagementFactory.getMemoryMXBean(); static final boolean usingEpsilonGC = ManagementFactory.getRuntimeMXBean().getInputArguments().stream().anyMatch(p -> p.contains("-XX:+UseEpsilonGC")); + private static ArrayList objs = null; + public static void main(String[] args) throws Exception { try { if (!mxThreadBean.isThreadCpuTimeEnabled()) { @@ -96,7 +99,15 @@ public class TestGetTotalGcCpuTime { return; } - System.gc(); + // Add some tracing work to ensure OSs with slower update rates would report usage + for (int i = 0; i < 200; i++) { + objs = new ArrayList(); + for (int j = 0; j < 5000; j++) { + objs.add(new Object()); + } + System.gc(); + } + long gcCpuTimeFromThread = mxMemoryBean.getTotalGcCpuTime(); if (usingEpsilonGC) { From df5b105bbb55d9cc923ac45ff99e702126626670 Mon Sep 17 00:00:00 2001 From: Stefan Karlsson Date: Tue, 18 Nov 2025 11:57:58 +0000 Subject: [PATCH 103/418] 8371698: ZGC: Call GTEST_SKIP when OS is unsupported Reviewed-by: aboldtch, jsikstro, mdoerr --- test/hotspot/gtest/gc/z/test_zForwarding.cpp | 10 ---------- test/hotspot/gtest/gc/z/test_zMapper_windows.cpp | 10 ---------- test/hotspot/gtest/gc/z/test_zVirtualMemoryManager.cpp | 10 ---------- test/hotspot/gtest/gc/z/zunittest.hpp | 10 +++++++++- 4 files changed, 9 insertions(+), 31 deletions(-) diff --git a/test/hotspot/gtest/gc/z/test_zForwarding.cpp b/test/hotspot/gtest/gc/z/test_zForwarding.cpp index 3a69ff3cbb7..5275b78fb82 100644 --- a/test/hotspot/gtest/gc/z/test_zForwarding.cpp +++ b/test/hotspot/gtest/gc/z/test_zForwarding.cpp @@ -51,11 +51,6 @@ public: zoffset _page_offset; virtual void SetUp() { - // Only run test on supported Windows versions - if (!is_os_supported()) { - GTEST_SKIP() << "OS not supported"; - } - _old_heap = ZHeap::_heap; ZHeap::_heap = (ZHeap*)os::malloc(sizeof(ZHeap), mtTest); @@ -83,11 +78,6 @@ public: } virtual void TearDown() { - if (!is_os_supported()) { - // Test skipped, nothing to cleanup - return; - } - os::free(ZHeap::_heap); ZHeap::_heap = _old_heap; ZGeneration::_old = _old_old; diff --git a/test/hotspot/gtest/gc/z/test_zMapper_windows.cpp b/test/hotspot/gtest/gc/z/test_zMapper_windows.cpp index 1789d2e3b43..95e6317dfd8 100644 --- a/test/hotspot/gtest/gc/z/test_zMapper_windows.cpp +++ b/test/hotspot/gtest/gc/z/test_zMapper_windows.cpp @@ -42,11 +42,6 @@ private: public: virtual void SetUp() { - // Only run test on supported Windows versions - if (!is_os_supported()) { - GTEST_SKIP() << "OS not supported"; - } - _zaddress_reserver.SetUp(ReservationSize); _reserver = _zaddress_reserver.reserver(); _registry = _zaddress_reserver.registry(); @@ -58,11 +53,6 @@ public: } virtual void TearDown() { - if (!is_os_supported()) { - // Test skipped, nothing to cleanup - return; - } - // Best-effort cleanup _registry = nullptr; _reserver = nullptr; diff --git a/test/hotspot/gtest/gc/z/test_zVirtualMemoryManager.cpp b/test/hotspot/gtest/gc/z/test_zVirtualMemoryManager.cpp index 2b6fbc198ca..7c41dde04cb 100644 --- a/test/hotspot/gtest/gc/z/test_zVirtualMemoryManager.cpp +++ b/test/hotspot/gtest/gc/z/test_zVirtualMemoryManager.cpp @@ -62,11 +62,6 @@ private: public: virtual void SetUp() { - // Only run test on supported Windows versions - if (!is_os_supported()) { - GTEST_SKIP() << "OS not supported"; - } - _zaddress_reserver.SetUp(ReservationSize); _reserver = _zaddress_reserver.reserver(); _registry = _zaddress_reserver.registry(); @@ -78,11 +73,6 @@ public: } virtual void TearDown() { - if (!is_os_supported()) { - // Test skipped, nothing to cleanup - return; - } - _registry = nullptr; _reserver = nullptr; _zaddress_reserver.TearDown(); diff --git a/test/hotspot/gtest/gc/z/zunittest.hpp b/test/hotspot/gtest/gc/z/zunittest.hpp index a4586d51a0e..2464f821380 100644 --- a/test/hotspot/gtest/gc/z/zunittest.hpp +++ b/test/hotspot/gtest/gc/z/zunittest.hpp @@ -76,7 +76,6 @@ public: } void SetUp(size_t reservation_size) { - GTEST_EXPECT_TRUE(ZArguments::is_os_supported()) << "Should not use SetUp on unsupported systems"; GTEST_EXPECT_FALSE(_active) << "SetUp called twice without a TearDown"; _active = true; @@ -109,12 +108,21 @@ private: ZAddressOffsetMaxSetter _zaddress_offset_max_setter; unsigned int _rand_seed; + void skip_all_tests() { + // Skipping from the constructor currently works, but according to the + // documentation the GTEST_SKIP macro should be used from the test or + // from the SetUp function. If this start to fail down the road, then + // we'll have to explicitly call this for each inheriting gtest. + GTEST_SKIP() << "OS not supported"; + } + protected: ZTest() : _zaddress_offset_max_setter(ZAddressOffsetMax), _rand_seed(static_cast(::testing::UnitTest::GetInstance()->random_seed())) { if (!is_os_supported()) { // If the OS does not support ZGC do not run initialization, as it may crash the VM. + skip_all_tests(); return; } From f94644999766e752f7d60ce52c14a7db79005035 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 18 Nov 2025 12:20:23 +0000 Subject: [PATCH 104/418] 8366178: Implement JEP 526: Lazy Constants (Second Preview) 8371882: Improve documentation for JEP 526: Lazy Constants Reviewed-by: jvernee, mcimadamore --- .../share/classes/java/lang/LazyConstant.java | 307 +++++++ .../share/classes/java/lang/StableValue.java | 756 ------------------ .../classes/java/nio/charset/Charset.java | 8 +- .../share/classes/java/util/Currency.java | 4 +- .../java/util/ImmutableCollections.java | 384 +-------- .../classes/java/util/LazyCollections.java | 584 ++++++++++++++ .../share/classes/java/util/List.java | 80 +- .../share/classes/java/util/Locale.java | 8 +- .../classes/java/util/LocaleISOData.java | 16 +- .../share/classes/java/util/Map.java | 85 +- .../share/classes/java/util/Optional.java | 6 +- .../classes/java/util/ResourceBundle.java | 2 +- .../access/JavaUtilCollectionAccess.java | 6 - .../internal/foreign/CaptureStateUtil.java | 33 +- .../jdk/internal/io/JdkConsoleImpl.java | 40 +- .../jdk/internal/javac/PreviewFeature.java | 4 +- .../jdk/internal/lang/LazyConstantImpl.java | 173 ++++ .../lang/stable/StableEnumFunction.java | 118 --- .../internal/lang/stable/StableFunction.java | 83 -- .../lang/stable/StableIntFunction.java | 80 -- .../internal/lang/stable/StableSupplier.java | 69 -- .../jdk/internal/lang/stable/StableUtil.java | 105 --- .../internal/lang/stable/StableValueImpl.java | 218 ----- .../share/classes/sun/nio/ch/Net.java | 7 +- .../classes/sun/util/locale/BaseLocale.java | 4 +- .../BreakIteratorResourceBundle.java | 2 +- .../resources/OpenListResourceBundle.java | 4 +- .../DemoContainerInjectionTest.java | 153 ++++ .../lang/LazyConstant/DemoImperativeTest.java | 83 ++ .../java/lang/LazyConstant/DemoMapTest.java | 119 +++ .../LazyConstantSafePublicationTest.java} | 71 +- .../lang/LazyConstant/LazyConstantTest.java | 236 ++++++ .../LazyConstantTestUtil.java} | 58 +- .../LazyListTest.java} | 248 ++---- .../java/lang/LazyConstant/LazyMapTest.java | 573 +++++++++++++ .../TrustedFieldTypeTest.java | 63 +- .../lang/StableValue/StableFunctionTest.java | 250 ------ .../StableValue/StableIntFunctionTest.java | 109 --- .../java/lang/StableValue/StableMapTest.java | 388 --------- .../lang/StableValue/StableSupplierTest.java | 104 --- .../StableValue/StableValueFactoriesTest.java | 43 - .../lang/StableValue/StableValueTest.java | 389 --------- test/jdk/java/util/Collection/MOAT.java | 28 +- .../jdk/jshell/CompletionSuggestionTest.java | 2 +- ...enchmark.java => StableListBenchmark.java} | 12 +- ...rk.java => StableListSingleBenchmark.java} | 12 +- ...Benchmark.java => StableMapBenchmark.java} | 14 +- ...ark.java => StableMapSingleBenchmark.java} | 33 +- .../stable/StableMethodHandleBenchmark.java | 16 +- .../lang/stable/StableSupplierBenchmark.java | 35 +- .../lang/stable/StableValueBenchmark.java | 84 +- .../lang/stable/VarHandleHolderBenchmark.java | 11 +- 52 files changed, 2784 insertions(+), 3536 deletions(-) create mode 100644 src/java.base/share/classes/java/lang/LazyConstant.java delete mode 100644 src/java.base/share/classes/java/lang/StableValue.java create mode 100644 src/java.base/share/classes/java/util/LazyCollections.java create mode 100644 src/java.base/share/classes/jdk/internal/lang/LazyConstantImpl.java delete mode 100644 src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java delete mode 100644 src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java delete mode 100644 src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java delete mode 100644 src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java delete mode 100644 src/java.base/share/classes/jdk/internal/lang/stable/StableUtil.java delete mode 100644 src/java.base/share/classes/jdk/internal/lang/stable/StableValueImpl.java create mode 100644 test/jdk/java/lang/LazyConstant/DemoContainerInjectionTest.java create mode 100644 test/jdk/java/lang/LazyConstant/DemoImperativeTest.java create mode 100644 test/jdk/java/lang/LazyConstant/DemoMapTest.java rename test/jdk/java/lang/{StableValue/StableValuesSafePublicationTest.java => LazyConstant/LazyConstantSafePublicationTest.java} (73%) create mode 100644 test/jdk/java/lang/LazyConstant/LazyConstantTest.java rename test/jdk/java/lang/{StableValue/StableTestUtil.java => LazyConstant/LazyConstantTestUtil.java} (64%) rename test/jdk/java/lang/{StableValue/StableListTest.java => LazyConstant/LazyListTest.java} (60%) create mode 100644 test/jdk/java/lang/LazyConstant/LazyMapTest.java rename test/jdk/java/lang/{StableValue => LazyConstant}/TrustedFieldTypeTest.java (61%) delete mode 100644 test/jdk/java/lang/StableValue/StableFunctionTest.java delete mode 100644 test/jdk/java/lang/StableValue/StableIntFunctionTest.java delete mode 100644 test/jdk/java/lang/StableValue/StableMapTest.java delete mode 100644 test/jdk/java/lang/StableValue/StableSupplierTest.java delete mode 100644 test/jdk/java/lang/StableValue/StableValueFactoriesTest.java delete mode 100644 test/jdk/java/lang/StableValue/StableValueTest.java rename test/micro/org/openjdk/bench/java/lang/stable/{StableIntFunctionBenchmark.java => StableListBenchmark.java} (86%) rename test/micro/org/openjdk/bench/java/lang/stable/{StableIntFunctionSingleBenchmark.java => StableListSingleBenchmark.java} (84%) rename test/micro/org/openjdk/bench/java/lang/stable/{StableFunctionBenchmark.java => StableMapBenchmark.java} (85%) rename test/micro/org/openjdk/bench/java/lang/stable/{StableFunctionSingleBenchmark.java => StableMapSingleBenchmark.java} (70%) diff --git a/src/java.base/share/classes/java/lang/LazyConstant.java b/src/java.base/share/classes/java/lang/LazyConstant.java new file mode 100644 index 00000000000..34f3d754a10 --- /dev/null +++ b/src/java.base/share/classes/java/lang/LazyConstant.java @@ -0,0 +1,307 @@ +/* + * Copyright (c) 2025, 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. 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 java.lang; + +import jdk.internal.javac.PreviewFeature; +import jdk.internal.lang.LazyConstantImpl; + +import java.io.Serializable; +import java.util.*; +import java.util.function.Function; +import java.util.function.IntFunction; +import java.util.function.Supplier; + +/** + * A lazy constant is a holder of contents that can be set at most once. + *

+ * A lazy constant is created using the factory method + * {@linkplain LazyConstant#of(Supplier) LazyConstant.of({@code })}. + * When created, the lazy constant is not initialized, meaning it has no contents. + * The lazy constant (of type {@code T}) can then be initialized + * (and its contents retrieved) by calling {@linkplain #get() get()}. The first time + * {@linkplain #get() get()} is called, the underlying computing function + * (provided at construction) will be invoked and the result will be used to initialize + * the constant. Once a lazy constant is initialized, its contents can never change + * and will be retrieved over and over again upon subsequent {@linkplain #get() get()} + * invocations. + *

+ * Consider the following example where a lazy constant field "{@code logger}" holds + * an object of type {@code Logger}: + * + * {@snippet lang = java: + * public class Component { + * + * // Creates a new uninitialized lazy constant + * private final LazyConstant logger = + * // @link substring="of" target="#of" : + * LazyConstant.of( () -> Logger.create(Component.class) ); + * + * public void process() { + * logger.get().info("Process started"); + * // ... + * } + * } + *} + *

+ * Initially, the lazy constant is not initialized. When {@code logger.get()} + * is first invoked, it evaluates the computing function and initializes the constant to + * the result; the result is then returned to the client. Hence, {@linkplain #get() get()} + * guarantees that the constant is initialized before it returns, barring + * any exceptions. + *

+ * Furthermore, {@linkplain #get() get()} guarantees that, out of several threads trying to + * invoke the computing function simultaneously, {@linkplain ##thread-safety only one is + * ever selected} for computation. This property is crucial as evaluation of the computing + * function may have side effects, for example, the call above to {@code Logger.create()} + * may result in storage resources being prepared. + * + *

Exception handling

+ * If the computing function returns {@code null}, a {@linkplain NullPointerException} + * is thrown. Hence, a lazy constant can never hold a {@code null} value. Clients who + * want to use a nullable constant can wrap the value into an {@linkplain Optional} holder. + *

+ * If the computing function recursively invokes itself (directly or indirectly via + * the lazy constant), an {@linkplain IllegalStateException} is thrown, and the lazy + * constant is not initialized. + * + *

Composing lazy constants

+ * A lazy constant can depend on other lazy constants, forming a dependency graph + * that can be lazily computed but where access to individual elements can still be + * performant. In the following example, a single {@code Foo} and a {@code Bar} + * instance (that is dependent on the {@code Foo} instance) are lazily created, both of + * which are held by lazy constants: + * + * {@snippet lang = java: + * public final class DependencyUtil { + * + * private DependencyUtil() {} + * + * public static class Foo { + * // ... + * } + * + * public static class Bar { + * public Bar(Foo foo) { + * // ... + * } + * } + * + * private static final LazyConstant FOO = LazyConstant.of( Foo::new ); + * private static final LazyConstant BAR = LazyConstant.of( () -> new Bar(FOO.get()) ); + * + * public static Foo foo() { + * return FOO.get(); + * } + * + * public static Bar bar() { + * return BAR.get(); + * } + * + * } + *} + * Calling {@code BAR.get()} will create the {@code Bar} singleton if it is not already + * created. Upon such a creation, a dependent {@code Foo} will first be created if + * the {@code Foo} does not already exist. + * + *

Thread Safety

+ * A lazy constant is guaranteed to be initialized atomically and at most once. If + * competing threads are racing to initialize a lazy constant, only one updating thread + * runs the computing function (which runs on the caller's thread and is hereafter denoted + * the computing thread), while the other threads are blocked until the constant + * is initialized, after which the other threads observe the lazy constant is initialized + * and leave the constant unchanged and will never invoke any computation. + *

+ * The invocation of the computing function and the resulting initialization of + * the constant {@linkplain java.util.concurrent##MemoryVisibility happens-before} + * the initialized constant's content is read. Hence, the initialized constant's content, + * including any {@code final} fields of any newly created objects, is safely published. + *

+ * Thread interruption does not cancel the initialization of a lazy constant. In other + * words, if the computing thread is interrupted, {@code LazyConstant::get} doesn't clear + * the interrupted thread’s status, nor does it throw an {@linkplain InterruptedException}. + *

+ * If the computing function blocks indefinitely, other threads operating on this + * lazy constant may block indefinitely; no timeouts or cancellations are provided. + * + *

Performance

+ * The contents of a lazy constant can never change after the lazy constant has been + * initialized. Therefore, a JVM implementation may, for an initialized lazy constant, + * elide all future reads of that lazy constant's contents and instead use the contents + * that has been previously observed. We call this optimization constant folding. + * This is only possible if there is a direct reference from a {@code static final} field + * to a lazy constant or if there is a chain from a {@code static final} field -- via one + * or more trusted fields (i.e., {@code static final} fields, + * {@linkplain Record record} fields, or final instance fields in hidden classes) -- + * to a lazy constant. + * + *

Miscellaneous

+ * Except for {@linkplain Object#equals(Object) equals(obj)} and + * {@linkplain #orElse(Object) orElse(other)} parameters, all method parameters + * must be non-null, or a {@link NullPointerException} will be thrown. + * + * @apiNote Once a lazy constant is initialized, its contents cannot ever be removed. + * This can be a source of an unintended memory leak. More specifically, + * a lazy constant {@linkplain java.lang.ref##reachability strongly references} + * it contents. Hence, the contents of a lazy constant will be reachable as long + * as the lazy constant itself is reachable. + *

+ * While it's possible to store an array inside a lazy constant, doing so will + * not result in improved access performance of the array elements. Instead, a + * {@linkplain List#ofLazy(int, IntFunction) lazy list} of arbitrary depth can + * be used, which provides constant components. + *

+ * The {@code LazyConstant} type is not {@link Serializable}. + *

+ * Use in static initializers may interact with class initialization order; + * cyclic initialization may result in initialization errors as described + * in section {@jls 12.4} of The Java Language Specification. + * + * @implNote + * A lazy constant is free to synchronize on itself. Hence, care must be + * taken when directly or indirectly synchronizing on a lazy constant. + * A lazy constant is unmodifiable but its contents may or may not be + * immutable (e.g., it may hold an {@linkplain ArrayList}). + * + * @param type of the constant + * + * @since 26 + * + * @see Optional + * @see Supplier + * @see List#ofLazy(int, IntFunction) + * @see Map#ofLazy(Set, Function) + * @jls 12.4 Initialization of Classes and Interfaces + * @jls 17.4.5 Happens-before Order + */ +@PreviewFeature(feature = PreviewFeature.Feature.LAZY_CONSTANTS) +public sealed interface LazyConstant + extends Supplier + permits LazyConstantImpl { + + /** + * {@return the contents of this lazy constant if initialized, otherwise, + * returns {@code other}} + *

+ * This method never triggers initialization of this lazy constant and will observe + * initialization by other threads atomically (i.e., it returns the contents + * if and only if the initialization has already completed). + * + * @param other value to return if the content is not initialized + * (can be {@code null}) + */ + T orElse(T other); + + /** + * {@return the contents of this initialized constant. If not initialized, first + * computes and initializes this constant using the computing function} + *

+ * After this method returns successfully, the constant is guaranteed to be + * initialized. + *

+ * If the computing function throws, the throwable is relayed to the caller and + * the lazy constant remains uninitialized; a subsequent call to get() may then + * attempt the computation again. + */ + T get(); + + /** + * {@return {@code true} if the constant is initialized, {@code false} otherwise} + *

+ * This method never triggers initialization of this lazy constant and will observe + * changes in the initialization state made by other threads atomically. + */ + boolean isInitialized(); + + // Object methods + + /** + * {@return if this lazy constant is the same as the provided {@code obj}} + *

+ * In other words, equals compares the identity of this lazy constant and {@code obj} + * to determine equality. Hence, two lazy constants with the same contents are + * not equal. + *

+ * This method never triggers initialization of this lazy constant. + */ + @Override + boolean equals(Object obj); + + /** + * {@return the {@linkplain System#identityHashCode(Object) identity hash code} for + * this lazy constant} + * + * This method never triggers initialization of this lazy constant. + */ + @Override + int hashCode(); + + /** + * {@return a string suitable for debugging} + *

+ * This method never triggers initialization of this lazy constant and will observe + * initialization by other threads atomically (i.e., it observes the + * contents if and only if the initialization has already completed). + *

+ * If this lazy constant is initialized, an implementation-dependent string + * containing the {@linkplain Object#toString()} of the + * contents will be returned; otherwise, an implementation-dependent string is + * returned that indicates this lazy constant is not yet initialized. + */ + @Override + String toString(); + + // Factory + + /** + * {@return a lazy constant whose contents is to be computed later via the provided + * {@code computingFunction}} + *

+ * The returned lazy constant strongly references the provided + * {@code computingFunction} at least until initialization completes successfully. + *

+ * If the provided computing function is already an instance of + * {@code LazyConstant}, the method is free to return the provided computing function + * directly. + * + * @implNote after initialization completes successfully, the computing function is + * no longer strongly referenced and becomes eligible for + * garbage collection. + * + * @param computingFunction in the form of a {@linkplain Supplier} to be used + * to initialize the constant + * @param type of the constant + * + */ + @SuppressWarnings("unchecked") + static LazyConstant of(Supplier computingFunction) { + Objects.requireNonNull(computingFunction); + if (computingFunction instanceof LazyConstant lc) { + return (LazyConstant) lc; + } + return LazyConstantImpl.ofLazy(computingFunction); + } + +} diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java deleted file mode 100644 index 1815cb1a5b1..00000000000 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ /dev/null @@ -1,756 +0,0 @@ -/* - * Copyright (c) 2025, 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. 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 java.lang; - -import jdk.internal.access.SharedSecrets; -import jdk.internal.javac.PreviewFeature; -import jdk.internal.lang.stable.StableEnumFunction; -import jdk.internal.lang.stable.StableFunction; -import jdk.internal.lang.stable.StableIntFunction; -import jdk.internal.lang.stable.StableSupplier; -import jdk.internal.lang.stable.StableUtil; -import jdk.internal.lang.stable.StableValueImpl; - -import java.io.Serializable; -import java.util.Collection; -import java.util.EnumSet; -import java.util.List; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.Objects; -import java.util.RandomAccess; -import java.util.Set; -import java.util.function.Function; -import java.util.function.IntFunction; -import java.util.function.Supplier; - -/** - * A stable value is a holder of contents that can be set at most once. - *

- * A {@code StableValue} is typically created using the factory method - * {@linkplain StableValue#of() {@code StableValue.of()}}. When created this way, - * the stable value is unset, which means it holds no contents. - * Its contents, of type {@code T}, can be set by calling - * {@linkplain #trySet(Object) trySet()}, {@linkplain #setOrThrow(Object) setOrThrow()}, - * or {@linkplain #orElseSet(Supplier) orElseSet()}. Once set, the contents - * can never change and can be retrieved by calling {@linkplain #orElseThrow() orElseThrow()} - * , {@linkplain #orElse(Object) orElse()}, or {@linkplain #orElseSet(Supplier) orElseSet()}. - *

- * Consider the following example where a stable value field "{@code logger}" is a - * shallowly immutable holder of contents of type {@code Logger} and that is initially - * created as unset, which means it holds no contents. Later in the example, the - * state of the "{@code logger}" field is checked and if it is still unset, - * the contents is set: - * - * {@snippet lang = java: - * public class Component { - * - * // Creates a new unset stable value with no contents - * // @link substring="of" target="#of" : - * private final StableValue logger = StableValue.of(); - * - * private Logger getLogger() { - * if (!logger.isSet()) { - * logger.trySet(Logger.create(Component.class)); - * } - * return logger.orElseThrow(); - * } - * - * public void process() { - * getLogger().info("Process started"); - * // ... - * } - * } - *} - *

- * If {@code getLogger()} is called from several threads, several instances of - * {@code Logger} might be created. However, the contents can only be set at most once - * meaning the first writer wins. - *

- * In order to guarantee that, even under races, only one instance of {@code Logger} is - * ever created, the {@linkplain #orElseSet(Supplier) orElseSet()} method can be used - * instead, where the contents are lazily computed, and atomically set, via a - * {@linkplain Supplier supplier}. In the example below, the supplier is provided in the - * form of a lambda expression: - * - * {@snippet lang = java: - * public class Component { - * - * // Creates a new unset stable value with no contents - * // @link substring="of" target="#of" : - * private final StableValue logger = StableValue.of(); - * - * private Logger getLogger() { - * return logger.orElseSet( () -> Logger.create(Component.class) ); - * } - * - * public void process() { - * getLogger().info("Process started"); - * // ... - * } - * } - *} - *

- * The {@code getLogger()} method calls {@code logger.orElseSet()} on the stable value to - * retrieve its contents. If the stable value is unset, then {@code orElseSet()} - * evaluates the given supplier, and sets the contents to the result; the result is then - * returned to the client. In other words, {@code orElseSet()} guarantees that a - * stable value's contents is set before it returns. - *

- * Furthermore, {@code orElseSet()} guarantees that out of one or more suppliers provided, - * only at most one is ever evaluated, and that one is only ever evaluated once, - * even when {@code logger.orElseSet()} is invoked concurrently. This property is crucial - * as evaluation of the supplier may have side effects, for example, the call above to - * {@code Logger.create()} may result in storage resources being prepared. - * - *

Stable Functions

- * Stable values provide the foundation for higher-level functional abstractions. A - * stable supplier is a supplier that computes a value and then caches it into - * a backing stable value storage for subsequent use. A stable supplier is created via the - * {@linkplain StableValue#supplier(Supplier) StableValue.supplier()} factory, by - * providing an underlying {@linkplain Supplier} which is invoked when the stable supplier - * is first accessed: - * - * {@snippet lang = java: - * public class Component { - * - * private final Supplier logger = - * // @link substring="supplier" target="#supplier(Supplier)" : - * StableValue.supplier( () -> Logger.getLogger(Component.class) ); - * - * public void process() { - * logger.get().info("Process started"); - * // ... - * } - * } - *} - * A stable supplier encapsulates access to its backing stable value storage. This means - * that code inside {@code Component} can obtain the logger object directly from the - * stable supplier, without having to go through an accessor method like {@code getLogger()}. - *

- * A stable int function is a function that takes an {@code int} parameter and - * uses it to compute a result that is then cached by the backing stable value storage - * for that parameter value. A stable {@link IntFunction} is created via the - * {@linkplain StableValue#intFunction(int, IntFunction) StableValue.intFunction()} - * factory. Upon creation, the input range (i.e. {@code [0, size)}) is specified together - * with an underlying {@linkplain IntFunction} which is invoked at most once per input - * value. In effect, the stable int function will act like a cache for the underlying - * {@linkplain IntFunction}: - * - * {@snippet lang = java: - * final class PowerOf2Util { - * - * private PowerOf2Util() {} - * - * private static final int SIZE = 6; - * private static final IntFunction UNDERLYING_POWER_OF_TWO = - * v -> 1 << v; - * - * private static final IntFunction POWER_OF_TWO = - * // @link substring="intFunction" target="#intFunction(int,IntFunction)" : - * StableValue.intFunction(SIZE, UNDERLYING_POWER_OF_TWO); - * - * public static int powerOfTwo(int a) { - * return POWER_OF_TWO.apply(a); - * } - * } - * - * int result = PowerOf2Util.powerOfTwo(4); // May eventually constant fold to 16 at runtime - * - *} - * The {@code PowerOf2Util.powerOfTwo()} function is a partial function that only - * allows a subset {@code [0, 5]} of the underlying function's {@code UNDERLYING_POWER_OF_TWO} - * input range. - * - *

- * A stable function is a function that takes a parameter (of type {@code T}) and - * uses it to compute a result (of type {@code R}) that is then cached by the backing - * stable value storage for that parameter value. A stable function is created via the - * {@linkplain StableValue#function(Set, Function) StableValue.function()} factory. - * Upon creation, the input {@linkplain Set} is specified together with an underlying - * {@linkplain Function} which is invoked at most once per input value. In effect, the - * stable function will act like a cache for the underlying {@linkplain Function}: - * - * {@snippet lang = java: - * class Log2Util { - * - * private Log2Util() {} - * - * private static final Set KEYS = - * Set.of(1, 2, 4, 8, 16, 32); - * private static final UnaryOperator UNDERLYING_LOG2 = - * i -> 31 - Integer.numberOfLeadingZeros(i); - * - * private static final Function LOG2 = - * // @link substring="function" target="#function(Set,Function)" : - * StableValue.function(KEYS, UNDERLYING_LOG2); - * - * public static int log2(int a) { - * return LOG2.apply(a); - * } - * - * } - * - * int result = Log2Util.log2(16); // May eventually constant fold to 4 at runtime - *} - * - * The {@code Log2Util.log2()} function is a partial function that only allows - * a subset {@code {1, 2, 4, 8, 16, 32}} of the underlying function's - * {@code UNDERLYING_LOG2} input range. - * - *

Stable Collections

- * Stable values can also be used as backing storage for - * {@linkplain Collection##unmodifiable unmodifiable collections}. A stable list - * is an unmodifiable list, backed by an array of stable values. The stable list elements - * are computed when they are first accessed, using a provided {@linkplain IntFunction}: - * - * {@snippet lang = java: - * final class PowerOf2Util { - * - * private PowerOf2Util() {} - * - * private static final int SIZE = 6; - * private static final IntFunction UNDERLYING_POWER_OF_TWO = - * v -> 1 << v; - * - * private static final List POWER_OF_TWO = - * // @link substring="list" target="#list(int,IntFunction)" : - * StableValue.list(SIZE, UNDERLYING_POWER_OF_TWO); - * - * public static int powerOfTwo(int a) { - * return POWER_OF_TWO.get(a); - * } - * } - * - * int result = PowerOf2Util.powerOfTwo(4); // May eventually constant fold to 16 at runtime - * - * } - *

- * Similarly, a stable map is an unmodifiable map whose keys are known at - * construction. The stable map values are computed when they are first accessed, - * using a provided {@linkplain Function}: - * - * {@snippet lang = java: - * class Log2Util { - * - * private Log2Util() {} - * - * private static final Set KEYS = - * Set.of(1, 2, 4, 8, 16, 32); - * private static final UnaryOperator UNDERLYING_LOG2 = - * i -> 31 - Integer.numberOfLeadingZeros(i); - * - * private static final Map LOG2 = - * // @link substring="map" target="#map(Set,Function)" : - * StableValue.map(CACHED_KEYS, UNDERLYING_LOG2); - * - * public static int log2(int a) { - * return LOG2.get(a); - * } - * - * } - * - * int result = Log2Util.log2(16); // May eventually constant fold to 4 at runtime - * - *} - * - *

Composing stable values

- * A stable value can depend on other stable values, forming a dependency graph - * that can be lazily computed but where access to individual elements can still be - * performant. In the following example, a single {@code Foo} and a {@code Bar} - * instance (that is dependent on the {@code Foo} instance) are lazily created, both of - * which are held by stable values: - * {@snippet lang = java: - * public final class DependencyUtil { - * - * private DependencyUtil() {} - * - * public static class Foo { - * // ... - * } - * - * public static class Bar { - * public Bar(Foo foo) { - * // ... - * } - * } - * - * private static final Supplier FOO = StableValue.supplier(Foo::new); - * private static final Supplier BAR = StableValue.supplier(() -> new Bar(FOO.get())); - * - * public static Foo foo() { - * return FOO.get(); - * } - * - * public static Bar bar() { - * return BAR.get(); - * } - * - * } - *} - * Calling {@code bar()} will create the {@code Bar} singleton if it is not already - * created. Upon such a creation, the dependent {@code Foo} will first be created if - * the {@code Foo} does not already exist. - *

- * Another example, which has a more complex dependency graph, is to compute the - * Fibonacci sequence lazily: - * {@snippet lang = java: - * public final class Fibonacci { - * - * private Fibonacci() {} - * - * private static final int MAX_SIZE_INT = 46; - * - * private static final IntFunction FIB = - * StableValue.intFunction(MAX_SIZE_INT, Fibonacci::fib); - * - * public static int fib(int n) { - * return n < 2 - * ? n - * : FIB.apply(n - 1) + FIB.apply(n - 2); - * } - * - * } - *} - * Both {@code FIB} and {@code Fibonacci::fib} recurse into each other. Because the - * stable int function {@code FIB} caches intermediate results, the initial - * computational complexity is reduced from exponential to linear compared to a - * traditional non-caching recursive fibonacci method. Once computed, the VM is free to - * constant-fold expressions like {@code Fibonacci.fib(5)}. - *

- * The fibonacci example above is a directed acyclic graph (i.e., - * it has no circular dependencies and is therefore a dependency tree): - *{@snippet lang=text : - * - * ___________fib(5)____________ - * / \ - * ____fib(4)____ ____fib(3)____ - * / \ / \ - * fib(3) fib(2) fib(2) fib(1) - * / \ / \ / \ - * fib(2) fib(1) fib(1) fib(0) fib(1) fib(0) - *} - * - * If there are circular dependencies in a dependency graph, a stable value will - * eventually throw an {@linkplain IllegalStateException} upon referencing elements in - * a circularity. - * - *

Thread Safety

- * The contents of a stable value is guaranteed to be set at most once. If competing - * threads are racing to set a stable value, only one update succeeds, while the other - * updates are blocked until the stable value is set, whereafter the other updates - * observes the stable value is set and leave the stable value unchanged. - *

- * The at-most-once write operation on a stable value that succeeds - * (e.g. {@linkplain #trySet(Object) trySet()}) - * {@linkplain java.util.concurrent##MemoryVisibility happens-before} - * any successful read operation (e.g. {@linkplain #orElseThrow()}). - * A successful write operation can be either: - *

    - *
  • a {@link #trySet(Object)} that returns {@code true},
  • - *
  • a {@link #setOrThrow(Object)} that does not throw, or
  • - *
  • an {@link #orElseSet(Supplier)} that successfully runs the supplier
  • - *
- * A successful read operation can be either: - *
    - *
  • a {@link #orElseThrow()} that does not throw,
  • - *
  • a {@link #orElse(Object) orElse(other)} that does not return the {@code other} value
  • - *
  • an {@link #orElseSet(Supplier)} that does not {@code throw}, or
  • - *
  • an {@link #isSet()} that returns {@code true}
  • - *
- *

- * The method {@link #orElseSet(Supplier)} guarantees that the provided - * {@linkplain Supplier} is invoked successfully at most once, even under race. - * Invocations of {@link #orElseSet(Supplier)} form a total order of zero or - * more exceptional invocations followed by zero (if the contents were already set) or one - * successful invocation. Since stable functions and stable collections are built on top - * of the same principles as {@linkplain StableValue#orElseSet(Supplier) orElseSet()} they - * too are thread safe and guarantee at-most-once-per-input invocation. - * - *

Performance

- * As the contents of a stable value can never change after it has been set, a JVM - * implementation may, for a set stable value, elide all future reads of that - * stable value, and instead directly use any contents that it has previously observed. - * This is true if the reference to the stable value is a constant (e.g. in cases where - * the stable value itself is stored in a {@code static final} field). Stable functions - * and collections are built on top of StableValue. As such, they might also be eligible - * for the same JVM optimizations as for StableValue. - * - * @implSpec Implementing classes of {@code StableValue} are free to synchronize on - * {@code this} and consequently, it should be avoided to - * (directly or indirectly) synchronize on a {@code StableValue}. Hence, - * synchronizing on {@code this} may lead to deadlock. - *

- * Except for a {@code StableValue}'s contents itself, - * an {@linkplain #orElse(Object) orElse(other)} parameter, and - * an {@linkplain #equals(Object) equals(obj)} parameter; all - * method parameters must be non-null or a {@link NullPointerException} - * will be thrown. - * - * @implNote A {@code StableValue} is mainly intended to be a non-public field in - * a class and is usually neither exposed directly via accessors nor passed as - * a method parameter. - *

- * Stable functions and collections make reasonable efforts to provide - * {@link Object#toString()} operations that do not trigger evaluation - * of the internal stable values when called. - * Stable collections have {@link Object#equals(Object)} operations that try - * to minimize evaluation of the internal stable values when called. - *

- * As objects can be set via stable values but never removed, this can be a - * source of unintended memory leaks. A stable value's contents are - * {@linkplain java.lang.ref##reachability strongly reachable}. - * Be advised that reachable stable values will hold their set contents until - * the stable value itself is collected. - *

- * A {@code StableValue} that has a type parameter {@code T} that is an array - * type (of arbitrary rank) will only allow the JVM to treat the - * array reference as a stable value but not its components. - * Instead, a {@linkplain #list(int, IntFunction) a stable list} of arbitrary - * depth can be used, which provides stable components. More generally, a - * stable value can hold other stable values of arbitrary depth and still - * provide transitive constantness. - *

- * Stable values, functions, and collections are not {@link Serializable}. - * - * @param type of the contents - * - * @since 25 - */ -@PreviewFeature(feature = PreviewFeature.Feature.STABLE_VALUES) -public sealed interface StableValue - permits StableValueImpl { - - /** - * Tries to set the contents of this StableValue to the provided {@code contents}. - * The contents of this StableValue can only be set once, implying this method only - * returns {@code true} once. - *

- * When this method returns, the contents of this StableValue is always set. - * - * @return {@code true} if the contents of this StableValue was set to the - * provided {@code contents}, {@code false} otherwise - * @param contents to set - * @throws IllegalStateException if a supplier invoked by {@link #orElseSet(Supplier)} - * recursively attempts to set this stable value by calling this method - * directly or indirectly. - */ - boolean trySet(T contents); - - /** - * {@return the contents if set, otherwise, returns the provided {@code other} value} - * - * @param other to return if the contents is not set - */ - T orElse(T other); - - /** - * {@return the contents if set, otherwise, throws {@code NoSuchElementException}} - * - * @throws NoSuchElementException if no contents is set - */ - T orElseThrow(); - - /** - * {@return {@code true} if the contents is set, {@code false} otherwise} - */ - boolean isSet(); - - /** - * {@return the contents; if unset, first attempts to compute and set the - * contents using the provided {@code supplier}} - *

- * The provided {@code supplier} is guaranteed to be invoked at most once if it - * completes without throwing an exception. If this method is invoked several times - * with different suppliers, only one of them will be invoked provided it completes - * without throwing an exception. - *

- * If the supplier throws an (unchecked) exception, the exception is rethrown and no - * contents is set. The most common usage is to construct a new object serving - * as a lazily computed value or memoized result, as in: - * - * {@snippet lang=java: - * Value v = stable.orElseSet(Value::new); - * } - *

- * When this method returns successfully, the contents is always set. - *

- * The provided {@code supplier} will only be invoked once even if invoked from - * several threads unless the {@code supplier} throws an exception. - * - * @param supplier to be used for computing the contents, if not previously set - * @throws IllegalStateException if the provided {@code supplier} recursively - * attempts to set this stable value. - */ - T orElseSet(Supplier supplier); - - /** - * Sets the contents of this StableValue to the provided {@code contents}, or, if - * already set, throws {@code IllegalStateException}. - *

- * When this method returns (or throws an exception), the contents is always set. - * - * @param contents to set - * @throws IllegalStateException if the contents was already set - * @throws IllegalStateException if a supplier invoked by {@link #orElseSet(Supplier)} - * recursively attempts to set this stable value by calling this method - * directly or indirectly. - */ - void setOrThrow(T contents); - - // Object methods - - /** - * {@return {@code true} if {@code this == obj}, {@code false} otherwise} - * - * @param obj to check for equality - */ - boolean equals(Object obj); - - /** - * {@return the {@linkplain System#identityHashCode(Object) identity hash code} of - * {@code this} object} - */ - int hashCode(); - - // Factories - - /** - * {@return a new unset stable value} - *

- * An unset stable value has no contents. - * - * @param type of the contents - */ - static StableValue of() { - return StableValueImpl.of(); - } - - /** - * {@return a new pre-set stable value with the provided {@code contents}} - * - * @param contents to set - * @param type of the contents - */ - static StableValue of(T contents) { - final StableValue stableValue = StableValue.of(); - stableValue.trySet(contents); - return stableValue; - } - - /** - * {@return a new stable supplier} - *

- * The returned {@linkplain Supplier supplier} is a caching supplier that records - * the value of the provided {@code underlying} supplier upon being first accessed via - * the returned supplier's {@linkplain Supplier#get() get()} method. - *

- * The provided {@code underlying} supplier is guaranteed to be successfully invoked - * at most once even in a multi-threaded environment. Competing threads invoking the - * returned supplier's {@linkplain Supplier#get() get()} method when a value is - * already under computation will block until a value is computed or an exception is - * thrown by the computing thread. The competing threads will then observe the newly - * computed value (if any) and will then never execute the {@code underlying} supplier. - *

- * If the provided {@code underlying} supplier throws an exception, it is rethrown - * to the initial caller and no contents is recorded. - *

- * If the provided {@code underlying} supplier recursively calls the returned - * supplier, an {@linkplain IllegalStateException} will be thrown. - * - * @param underlying supplier used to compute a cached value - * @param the type of results supplied by the returned supplier - */ - static Supplier supplier(Supplier underlying) { - Objects.requireNonNull(underlying); - return StableSupplier.of(underlying); - } - - /** - * {@return a new stable {@linkplain IntFunction}} - *

- * The returned function is a caching function that, for each allowed {@code int} - * input, records the values of the provided {@code underlying} - * function upon being first accessed via the returned function's - * {@linkplain IntFunction#apply(int) apply()} method. If the returned function is - * invoked with an input that is not in the range {@code [0, size)}, an - * {@link IllegalArgumentException} will be thrown. - *

- * The provided {@code underlying} function is guaranteed to be successfully invoked - * at most once per allowed input, even in a multi-threaded environment. Competing - * threads invoking the returned function's - * {@linkplain IntFunction#apply(int) apply()} method when a value is already under - * computation will block until a value is computed or an exception is thrown by - * the computing thread. - *

- * If invoking the provided {@code underlying} function throws an exception, it is - * rethrown to the initial caller and no contents is recorded. - *

- * If the provided {@code underlying} function recursively calls the returned - * function for the same input, an {@linkplain IllegalStateException} will - * be thrown. - * - * @param size the upper bound of the range {@code [0, size)} indicating - * the allowed inputs - * @param underlying {@code IntFunction} used to compute cached values - * @param the type of results delivered by the returned IntFunction - * @throws IllegalArgumentException if the provided {@code size} is negative. - */ - static IntFunction intFunction(int size, - IntFunction underlying) { - StableUtil.assertSizeNonNegative(size); - Objects.requireNonNull(underlying); - return StableIntFunction.of(size, underlying); - } - - /** - * {@return a new stable {@linkplain Function}} - *

- * The returned function is a caching function that, for each allowed - * input in the given set of {@code inputs}, records the values of the provided - * {@code underlying} function upon being first accessed via the returned function's - * {@linkplain Function#apply(Object) apply()} method. If the returned function is - * invoked with an input that is not in {@code inputs}, an {@link IllegalArgumentException} - * will be thrown. - *

- * The provided {@code underlying} function is guaranteed to be successfully invoked - * at most once per allowed input, even in a multi-threaded environment. Competing - * threads invoking the returned function's {@linkplain Function#apply(Object) apply()} - * method when a value is already under computation will block until a value is - * computed or an exception is thrown by the computing thread. - *

- * If invoking the provided {@code underlying} function throws an exception, it is - * rethrown to the initial caller and no contents is recorded. - *

- * If the provided {@code underlying} function recursively calls the returned - * function for the same input, an {@linkplain IllegalStateException} will - * be thrown. - * - * @param inputs the set of (non-null) allowed input values - * @param underlying {@code Function} used to compute cached values - * @param the type of the input to the returned Function - * @param the type of results delivered by the returned Function - * @throws NullPointerException if the provided set of {@code inputs} contains a - * {@code null} element. - */ - static Function function(Set inputs, - Function underlying) { - Objects.requireNonNull(inputs); - // Checking that the Set of inputs does not contain a `null` value is made in the - // implementing classes. - Objects.requireNonNull(underlying); - return inputs instanceof EnumSet && !inputs.isEmpty() - ? StableEnumFunction.of(inputs, underlying) - : StableFunction.of(inputs, underlying); - } - - /** - * {@return a new stable list with the provided {@code size}} - *

- * The returned list is an {@linkplain Collection##unmodifiable unmodifiable} list - * with the provided {@code size}. The list's elements are computed via the - * provided {@code mapper} when they are first accessed - * (e.g. via {@linkplain List#get(int) List::get}). - *

- * The provided {@code mapper} function is guaranteed to be successfully invoked - * at most once per list index, even in a multi-threaded environment. Competing - * threads accessing an element already under computation will block until an element - * is computed or an exception is thrown by the computing thread. - *

- * If invoking the provided {@code mapper} function throws an exception, it - * is rethrown to the initial caller and no value for the element is recorded. - *

- * Any {@link List#subList(int, int) subList} or {@link List#reversed()} views - * of the returned list are also stable. - *

- * The returned list and its {@link List#subList(int, int) subList} or - * {@link List#reversed()} views implement the {@link RandomAccess} interface. - *

- * The returned list is unmodifiable and does not implement the - * {@linkplain Collection##optional-operation optional operations} in the - * {@linkplain List} interface. - *

- * If the provided {@code mapper} recursively calls the returned list for the - * same index, an {@linkplain IllegalStateException} will be thrown. - * - * @param size the size of the returned list - * @param mapper to invoke whenever an element is first accessed - * (may return {@code null}) - * @param the type of elements in the returned list - * @throws IllegalArgumentException if the provided {@code size} is negative. - */ - static List list(int size, - IntFunction mapper) { - StableUtil.assertSizeNonNegative(size); - Objects.requireNonNull(mapper); - return SharedSecrets.getJavaUtilCollectionAccess().stableList(size, mapper); - } - - /** - * {@return a new stable map with the provided {@code keys}} - *

- * The returned map is an {@linkplain Collection##unmodifiable unmodifiable} map whose - * keys are known at construction. The map's values are computed via the provided - * {@code mapper} when they are first accessed - * (e.g. via {@linkplain Map#get(Object) Map::get}). - *

- * The provided {@code mapper} function is guaranteed to be successfully invoked - * at most once per key, even in a multi-threaded environment. Competing - * threads accessing a value already under computation will block until an element - * is computed or an exception is thrown by the computing thread. - *

- * If invoking the provided {@code mapper} function throws an exception, it - * is rethrown to the initial caller and no value associated with the provided key - * is recorded. - *

- * Any {@link Map#values()} or {@link Map#entrySet()} views of the returned map are - * also stable. - *

- * The returned map is unmodifiable and does not implement the - * {@linkplain Collection##optional-operations optional operations} in the - * {@linkplain Map} interface. - *

- * If the provided {@code mapper} recursively calls the returned map for - * the same key, an {@linkplain IllegalStateException} will be thrown. - * - * @param keys the (non-null) keys in the returned map - * @param mapper to invoke whenever an associated value is first accessed - * (may return {@code null}) - * @param the type of keys maintained by the returned map - * @param the type of mapped values in the returned map - * @throws NullPointerException if the provided set of {@code inputs} contains a - * {@code null} element. - */ - static Map map(Set keys, - Function mapper) { - Objects.requireNonNull(keys); - // Checking that the Set of keys does not contain a `null` value is made in the - // implementing class. - Objects.requireNonNull(mapper); - return SharedSecrets.getJavaUtilCollectionAccess().stableMap(keys, mapper); - } - -} diff --git a/src/java.base/share/classes/java/nio/charset/Charset.java b/src/java.base/share/classes/java/nio/charset/Charset.java index 1eb3c9c2094..736ed4f12d5 100644 --- a/src/java.base/share/classes/java/nio/charset/Charset.java +++ b/src/java.base/share/classes/java/nio/charset/Charset.java @@ -25,7 +25,6 @@ package java.nio.charset; -import jdk.internal.misc.ThreadTracker; import jdk.internal.misc.VM; import jdk.internal.util.StaticProperty; import jdk.internal.vm.annotation.Stable; @@ -41,7 +40,6 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.NoSuchElementException; -import java.util.Objects; import java.util.ServiceLoader; import java.util.Set; import java.util.SortedMap; @@ -426,7 +424,7 @@ public abstract class Charset } /* The extended set of charsets */ - private static final Supplier> EXTENDED_PROVIDERS = StableValue.supplier( + private static final LazyConstant> EXTENDED_PROVIDERS = LazyConstant.of( new Supplier<>() { public List get() { return extendedProviders0(); }}); private static List extendedProviders0() { @@ -617,7 +615,7 @@ public abstract class Charset return Collections.unmodifiableSortedMap(m); } - private static final Supplier defaultCharset = StableValue.supplier( + private static final LazyConstant defaultCharset = LazyConstant.of( new Supplier<>() { public Charset get() { return defaultCharset0(); }}); private static Charset defaultCharset0() { @@ -658,7 +656,7 @@ public abstract class Charset @Stable private final String[] aliases; @Stable - private final Supplier> aliasSet = StableValue.supplier( + private final LazyConstant> aliasSet = LazyConstant.of( new Supplier<>() { public Set get() { return Set.of(aliases); }}); /** diff --git a/src/java.base/share/classes/java/util/Currency.java b/src/java.base/share/classes/java/util/Currency.java index febae04a77b..b254bae32a1 100644 --- a/src/java.base/share/classes/java/util/Currency.java +++ b/src/java.base/share/classes/java/util/Currency.java @@ -142,8 +142,8 @@ public final class Currency implements Serializable { // class data: instance map private static ConcurrentMap instances = new ConcurrentHashMap<>(7); - private static final Supplier> available = - StableValue.supplier(Currency::computeAllCurrencies); + private static final LazyConstant> available = + LazyConstant.of(Currency::computeAllCurrencies); // Class data: currency data obtained from currency.data file. // Purpose: diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java index 1dd7808da20..abc48ff5ed9 100644 --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -36,18 +36,12 @@ import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; -import java.util.function.IntFunction; import java.util.function.Predicate; -import java.util.function.Supplier; import java.util.function.UnaryOperator; import jdk.internal.access.JavaUtilCollectionAccess; import jdk.internal.access.SharedSecrets; -import jdk.internal.lang.stable.StableUtil; -import jdk.internal.lang.stable.StableValueImpl; import jdk.internal.misc.CDS; -import jdk.internal.util.ArraysSupport; -import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.Stable; /** @@ -135,14 +129,6 @@ class ImmutableCollections { public List listFromTrustedArrayNullsAllowed(Object[] array) { return ImmutableCollections.listFromTrustedArrayNullsAllowed(array); } - public List stableList(int size, IntFunction mapper) { - // A stable list is not Serializable, so we cannot return `List.of()` if `size == 0` - return new StableList<>(size, mapper); - } - public Map stableMap(Set keys, Function mapper) { - // A stable map is not Serializable, so we cannot return `Map.of()` if `keys.isEmpty()` - return new StableMap<>(keys, mapper); - } }); } } @@ -450,7 +436,7 @@ class ImmutableCollections { } } - static sealed class SubList extends AbstractImmutableList + static final class SubList extends AbstractImmutableList implements RandomAccess { @Stable @@ -462,8 +448,10 @@ class ImmutableCollections { @Stable final int size; - private SubList(AbstractImmutableList root, int offset, int size) { - assert root instanceof List12 || root instanceof ListN || root instanceof StableList; + SubList(AbstractImmutableList root, int offset, int size) { + assert root instanceof List12 + || root instanceof ListN + || root instanceof LazyCollections.LazyList; this.root = root; this.offset = offset; this.size = size; @@ -795,187 +783,6 @@ class ImmutableCollections { } } - @FunctionalInterface - interface HasStableDelegates { - StableValueImpl[] delegates(); - } - - @jdk.internal.ValueBased - static final class StableList - extends AbstractImmutableList - implements HasStableDelegates { - - @Stable - private final IntFunction mapper; - @Stable - final StableValueImpl[] delegates; - - StableList(int size, IntFunction mapper) { - this.mapper = mapper; - this.delegates = StableUtil.array(size); - } - - @Override public boolean isEmpty() { return delegates.length == 0;} - @Override public int size() { return delegates.length; } - @Override public Object[] toArray() { return copyInto(new Object[size()]); } - - @ForceInline - @Override - public E get(int i) { - final StableValueImpl delegate; - try { - delegate = delegates[i]; - } catch (ArrayIndexOutOfBoundsException aioobe) { - throw new IndexOutOfBoundsException(i); - } - return delegate.orElseSet(new Supplier() { - @Override public E get() { return mapper.apply(i); }}); - } - - @Override - @SuppressWarnings("unchecked") - public T[] toArray(T[] a) { - final int size = delegates.length; - if (a.length < size) { - // Make a new array of a's runtime type, but my contents: - T[] n = (T[])Array.newInstance(a.getClass().getComponentType(), size); - return copyInto(n); - } - copyInto(a); - if (a.length > size) { - a[size] = null; // null-terminate - } - return a; - } - - @Override - public int indexOf(Object o) { - final int size = size(); - for (int i = 0; i < size; i++) { - if (Objects.equals(o, get(i))) { - return i; - } - } - return -1; - } - - @Override - public int lastIndexOf(Object o) { - for (int i = size() - 1; i >= 0; i--) { - if (Objects.equals(o, get(i))) { - return i; - } - } - return -1; - } - - @SuppressWarnings("unchecked") - private T[] copyInto(Object[] a) { - final int len = delegates.length; - for (int i = 0; i < len; i++) { - a[i] = get(i); - } - return (T[]) a; - } - - @Override - public List reversed() { - return new StableReverseOrderListView<>(this); - } - - @Override - public List subList(int fromIndex, int toIndex) { - subListRangeCheck(fromIndex, toIndex, size()); - return StableSubList.fromStableList(this, fromIndex, toIndex); - } - - @Override - public String toString() { - return StableUtil.renderElements(this, "StableCollection", delegates); - } - - @Override - public StableValueImpl[] delegates() { - return delegates; - } - - private static final class StableSubList extends SubList - implements HasStableDelegates { - - private StableSubList(AbstractImmutableList root, int offset, int size) { - super(root, offset, size); - } - - @Override - public List reversed() { - return new StableReverseOrderListView<>(this); - } - - @Override - public List subList(int fromIndex, int toIndex) { - subListRangeCheck(fromIndex, toIndex, size()); - return StableSubList.fromStableSubList(this, fromIndex, toIndex); - } - - @Override - public String toString() { - return StableUtil.renderElements(this, "StableCollection", delegates()); - } - - @Override - boolean allowNulls() { - return true; - } - - @Override - public StableValueImpl[] delegates() { - @SuppressWarnings("unchecked") - final var rootDelegates = ((HasStableDelegates) root).delegates(); - return Arrays.copyOfRange(rootDelegates, offset, offset + size); - } - - static SubList fromStableList(StableList list, int fromIndex, int toIndex) { - return new StableSubList<>(list, fromIndex, toIndex - fromIndex); - } - - static SubList fromStableSubList(StableSubList parent, int fromIndex, int toIndex) { - return new StableSubList<>(parent.root, parent.offset + fromIndex, toIndex - fromIndex); - } - - } - - private static final class StableReverseOrderListView - extends ReverseOrderListView.Rand - implements HasStableDelegates { - - private StableReverseOrderListView(List base) { - super(base, false); - } - - // This method does not evaluate the elements - @Override - public String toString() { - return StableUtil.renderElements(this, "StableCollection", delegates()); - } - - @Override - public List subList(int fromIndex, int toIndex) { - final int size = base.size(); - subListRangeCheck(fromIndex, toIndex, size); - return new StableReverseOrderListView<>(base.subList(size - toIndex, size - fromIndex)); - } - - @Override - public StableValueImpl[] delegates() { - @SuppressWarnings("unchecked") - final var baseDelegates = ((HasStableDelegates) base).delegates(); - return ArraysSupport.reverse( - Arrays.copyOf(baseDelegates, baseDelegates.length)); - } - } - - } - // ---------- Set Implementations ---------- @jdk.internal.ValueBased @@ -1614,187 +1421,6 @@ class ImmutableCollections { } } - static final class StableMap - extends AbstractImmutableMap { - - @Stable - private final Function mapper; - @Stable - private final Map> delegate; - - StableMap(Set keys, Function mapper) { - this.mapper = mapper; - this.delegate = StableUtil.map(keys); - } - - @Override public boolean containsKey(Object o) { return delegate.containsKey(o); } - @Override public int size() { return delegate.size(); } - @Override public Set> entrySet() { return StableMapEntrySet.of(this); } - - @ForceInline - @Override - public V get(Object key) { - return getOrDefault(key, null); - } - - @ForceInline - @Override - public V getOrDefault(Object key, V defaultValue) { - final StableValueImpl stable = delegate.get(key); - if (stable == null) { - return defaultValue; - } - @SuppressWarnings("unchecked") - final K k = (K) key; - return stable.orElseSet(new Supplier() { - @Override public V get() { return mapper.apply(k); }}); - } - - @jdk.internal.ValueBased - static final class StableMapEntrySet extends AbstractImmutableSet> { - - // Use a separate field for the outer class in order to facilitate - // a @Stable annotation. - @Stable - private final StableMap outer; - - @Stable - private final Set>> delegateEntrySet; - - private StableMapEntrySet(StableMap outer) { - this.outer = outer; - this.delegateEntrySet = outer.delegate.entrySet(); - } - - @Override public Iterator> iterator() { return LazyMapIterator.of(this); } - @Override public int size() { return delegateEntrySet.size(); } - @Override public int hashCode() { return outer.hashCode(); } - - @Override - public String toString() { - return StableUtil.renderMappings(this, "StableCollection", delegateEntrySet, false); - } - - // For @ValueBased - private static StableMapEntrySet of(StableMap outer) { - return new StableMapEntrySet<>(outer); - } - - @jdk.internal.ValueBased - static final class LazyMapIterator implements Iterator> { - - // Use a separate field for the outer class in order to facilitate - // a @Stable annotation. - @Stable - private final StableMapEntrySet outer; - - @Stable - private final Iterator>> delegateIterator; - - private LazyMapIterator(StableMapEntrySet outer) { - this.outer = outer; - this.delegateIterator = outer.delegateEntrySet.iterator(); - } - - @Override public boolean hasNext() { return delegateIterator.hasNext(); } - - @Override - public Entry next() { - final Map.Entry> inner = delegateIterator.next(); - final K k = inner.getKey(); - return new StableEntry<>(k, inner.getValue(), new Supplier() { - @Override public V get() { return outer.outer.mapper.apply(k); }}); - } - - @Override - public void forEachRemaining(Consumer> action) { - final Consumer>> innerAction = - new Consumer<>() { - @Override - public void accept(Entry> inner) { - final K k = inner.getKey(); - action.accept(new StableEntry<>(k, inner.getValue(), new Supplier() { - @Override public V get() { return outer.outer.mapper.apply(k); }})); - } - }; - delegateIterator.forEachRemaining(innerAction); - } - - // For @ValueBased - private static LazyMapIterator of(StableMapEntrySet outer) { - return new LazyMapIterator<>(outer); - } - - } - } - - private record StableEntry(K getKey, // trick - StableValueImpl stableValue, - Supplier supplier) implements Map.Entry { - - @Override public V setValue(V value) { throw uoe(); } - @Override public V getValue() { return stableValue.orElseSet(supplier); } - @Override public int hashCode() { return hash(getKey()) ^ hash(getValue()); } - @Override public String toString() { return getKey() + "=" + stableValue.toString(); } - @Override public boolean equals(Object o) { - return o instanceof Map.Entry e - && Objects.equals(getKey(), e.getKey()) - // Invoke `getValue()` as late as possible to avoid evaluation - && Objects.equals(getValue(), e.getValue()); - } - - private int hash(Object obj) { return (obj == null) ? 0 : obj.hashCode(); } - } - - @Override - public Collection values() { - return StableMapValues.of(this); - } - - @jdk.internal.ValueBased - static final class StableMapValues extends AbstractImmutableCollection { - - // Use a separate field for the outer class in order to facilitate - // a @Stable annotation. - @Stable - private final StableMap outer; - - private StableMapValues(StableMap outer) { - this.outer = outer; - } - - @Override public Iterator iterator() { return outer.new ValueIterator(); } - @Override public int size() { return outer.size(); } - @Override public boolean isEmpty() { return outer.isEmpty();} - @Override public boolean contains(Object v) { return outer.containsValue(v); } - - private static final IntFunction[]> GENERATOR = new IntFunction[]>() { - @Override - public StableValueImpl[] apply(int len) { - return new StableValueImpl[len]; - } - }; - - @Override - public String toString() { - final StableValueImpl[] values = outer.delegate.values().toArray(GENERATOR); - return StableUtil.renderElements(this, "StableCollection", values); - } - - // For @ValueBased - private static StableMapValues of(StableMap outer) { - return new StableMapValues<>(outer); - } - - } - - @Override - public String toString() { - return StableUtil.renderMappings(this, "StableMap", delegate.entrySet(), true); - } - - } - } // ---------- Serialization Proxy ---------- diff --git a/src/java.base/share/classes/java/util/LazyCollections.java b/src/java.base/share/classes/java/util/LazyCollections.java new file mode 100644 index 00000000000..0bbdad87ac4 --- /dev/null +++ b/src/java.base/share/classes/java/util/LazyCollections.java @@ -0,0 +1,584 @@ +/* + * Copyright (c) 2025, 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. 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 java.util; + +import jdk.internal.misc.Unsafe; +import jdk.internal.util.ImmutableBitSetPredicate; +import jdk.internal.vm.annotation.AOTSafeClassInitializer; +import jdk.internal.vm.annotation.ForceInline; +import jdk.internal.vm.annotation.Stable; + +import java.lang.LazyConstant; +import java.lang.reflect.Array; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.IntFunction; +import java.util.function.IntPredicate; +import java.util.function.Supplier; + +/** + * Container class for lazy collections implementations. Not part of the public API. + */ +@AOTSafeClassInitializer +final class LazyCollections { + + /** + * No instances. + */ + private LazyCollections() { } + + // Unsafe allows LazyCollection classes to be used early in the boot sequence + private static final Unsafe UNSAFE = Unsafe.getUnsafe(); + + @jdk.internal.ValueBased + static final class LazyList + extends ImmutableCollections.AbstractImmutableList { + + @Stable + private final E[] elements; + // Keeping track of `size` separately reduces bytecode size compared to + // using `elements.length`. + @Stable + private final int size; + @Stable + final FunctionHolder> functionHolder; + @Stable + private final Mutexes mutexes; + + private LazyList(int size, IntFunction computingFunction) { + this.elements = newGenericArray(size); + this.size = size; + this.functionHolder = new FunctionHolder<>(computingFunction, size); + this.mutexes = new Mutexes(size); + super(); + } + + @Override public boolean isEmpty() { return size == 0; } + @Override public int size() { return size; } + @Override public Object[] toArray() { return copyInto(new Object[size]); } + + @ForceInline + @Override + public E get(int i) { + final E e = contentsAcquire(offsetFor(Objects.checkIndex(i, size))); + return (e != null) ? e : getSlowPath(i); + } + + private E getSlowPath(int i) { + return orElseComputeSlowPath(elements, i, mutexes, i, functionHolder); + } + + @Override + @SuppressWarnings("unchecked") + public T[] toArray(T[] a) { + if (a.length < size) { + // Make a new array of a's runtime type, but my contents: + T[] n = (T[]) Array.newInstance(a.getClass().getComponentType(), size); + return copyInto(n); + } + copyInto(a); + if (a.length > size) { + a[size] = null; // null-terminate + } + return a; + } + + @Override + public int indexOf(Object o) { + for (int i = 0; i < size; i++) { + if (Objects.equals(o, get(i))) { + return i; + } + } + return -1; + } + + @Override + public int lastIndexOf(Object o) { + for (int i = size - 1; i >= 0; i--) { + if (Objects.equals(o, get(i))) { + return i; + } + } + return -1; + } + + @SuppressWarnings("unchecked") + private T[] copyInto(Object[] a) { + for (int i = 0; i < size; i++) { + a[i] = get(i); + } + return (T[]) a; + } + + @SuppressWarnings("unchecked") + @ForceInline + private E contentsAcquire(long offset) { + return (E) UNSAFE.getReferenceAcquire(elements, offset); + } + + } + + static final class LazyEnumMap, V> + extends AbstractLazyMap { + + @Stable + private final Class enumType; + @Stable + // We are using a wrapper class here to be able to use a min value of zero that + // is also stable. + private final Integer min; + @Stable + private final IntPredicate member; + + public LazyEnumMap(Set set, + Class enumType, + int min, + int backingSize, + IntPredicate member, + Function computingFunction) { + this.enumType = enumType; + this.min = min; + this.member = member; + super(set, set.size(), backingSize, computingFunction); + } + + @Override + @ForceInline + public boolean containsKey(Object o) { + return enumType.isAssignableFrom(o.getClass()) + && member.test(((Enum) o).ordinal()); + } + + @ForceInline + @Override + public V getOrDefault(Object key, V defaultValue) { + if (enumType.isAssignableFrom(key.getClass())) { + final int ordinal = ((Enum) key).ordinal(); + if (member.test(ordinal)) { + @SuppressWarnings("unchecked") + final K k = (K) key; + return orElseCompute(k, indexForAsInt(k)); + } + } + return defaultValue; + } + + @Override + Integer indexFor(K key) { + return indexForAsInt(key); + } + + private int indexForAsInt(K key) { + return key.ordinal() - min; + } + + } + + static final class LazyMap + extends AbstractLazyMap { + + // Use an unmodifiable map with known entries that are @Stable. Lookups through this map can be folded because + // it is created using Map.ofEntrie. This allows us to avoid creating a separate hashing function. + @Stable + private final Map indexMapper; + + public LazyMap(Set keys, Function computingFunction) { + @SuppressWarnings("unchecked") + final Entry[] entries = (Entry[]) new Entry[keys.size()]; + int i = 0; + for (K k : keys) { + entries[i] = Map.entry(k, i++); + } + this.indexMapper = Map.ofEntries(entries); + super(keys, i, i, computingFunction); + } + + @ForceInline + @Override + public V getOrDefault(Object key, V defaultValue) { + final Integer index = indexMapper.get(key); + if (index != null) { + @SuppressWarnings("unchecked") + final K k = (K) key; + return orElseCompute(k, index); + } + return defaultValue; + } + + @Override public boolean containsKey(Object o) { return indexMapper.containsKey(o); } + + @Override + Integer indexFor(K key) { + return indexMapper.get(key); + } + } + + static sealed abstract class AbstractLazyMap + extends ImmutableCollections.AbstractImmutableMap { + + // This field shadows AbstractMap.keySet which is not @Stable. + @Stable + Set keySet; + // This field shadows AbstractMap.values which is of another type + @Stable + final V[] values; + @Stable + Mutexes mutexes; + @Stable + private final int size; + @Stable + final FunctionHolder> functionHolder; + @Stable + private final Set> entrySet; + + private AbstractLazyMap(Set keySet, + int size, + int backingSize, + Function computingFunction) { + this.size = size; + this.functionHolder = new FunctionHolder<>(computingFunction, size); + this.values = newGenericArray(backingSize); + this.mutexes = new Mutexes(backingSize); + super(); + this.keySet = keySet; + this.entrySet = LazyMapEntrySet.of(this); + } + + // Abstract methods + @Override public abstract boolean containsKey(Object o); + abstract Integer indexFor(K key); + + // Public methods + @Override public final int size() { return size; } + @Override public final boolean isEmpty() { return size == 0; } + @Override public final Set> entrySet() { return entrySet; } + @Override public Set keySet() { return keySet; } + + @ForceInline + @Override + public final V get(Object key) { + return getOrDefault(key, null); + } + + @SuppressWarnings("unchecked") + @ForceInline + final V orElseCompute(K key, int index) { + final long offset = offsetFor(index); + final V v = (V) UNSAFE.getReferenceAcquire(values, offset); + if (v != null) { + return v; + } + return orElseComputeSlowPath(values, index, mutexes, key, functionHolder); + } + + @jdk.internal.ValueBased + static final class LazyMapEntrySet extends ImmutableCollections.AbstractImmutableSet> { + + // Use a separate field for the outer class in order to facilitate + // a @Stable annotation. + @Stable + private final AbstractLazyMap map; + + private LazyMapEntrySet(AbstractLazyMap map) { + this.map = map; + super(); + } + + @Override public Iterator> iterator() { return LazyMapIterator.of(map); } + @Override public int size() { return map.size(); } + @Override public int hashCode() { return map.hashCode(); } + + // For @ValueBased + private static LazyMapEntrySet of(AbstractLazyMap outer) { + return new LazyMapEntrySet<>(outer); + } + + @jdk.internal.ValueBased + static final class LazyMapIterator implements Iterator> { + + // Use a separate field for the outer class in order to facilitate + // a @Stable annotation. + @Stable + private final AbstractLazyMap map; + @Stable + private final Iterator keyIterator; + + private LazyMapIterator(AbstractLazyMap map) { + this.map = map; + this.keyIterator = map.keySet.iterator(); + super(); + } + + @Override public boolean hasNext() { return keyIterator.hasNext(); } + + @Override + public Entry next() { + final K k = keyIterator.next(); + return new LazyEntry<>(k, map, map.functionHolder); + } + + @Override + public void forEachRemaining(Consumer> action) { + final Consumer innerAction = + new Consumer<>() { + @Override + public void accept(K key) { + action.accept(new LazyEntry<>(key, map, map.functionHolder)); + } + }; + keyIterator.forEachRemaining(innerAction); + } + + // For @ValueBased + private static LazyMapIterator of(AbstractLazyMap map) { + return new LazyMapIterator<>(map); + } + + } + } + + private record LazyEntry(K getKey, // trick + AbstractLazyMap map, + FunctionHolder> functionHolder) implements Entry { + + @Override public V setValue(V value) { throw ImmutableCollections.uoe(); } + @Override public V getValue() { return map.orElseCompute(getKey, map.indexFor(getKey)); } + @Override public int hashCode() { return hash(getKey()) ^ hash(getValue()); } + @Override public String toString() { return getKey() + "=" + getValue(); } + + @Override + public boolean equals(Object o) { + return o instanceof Map.Entry e + && Objects.equals(getKey(), e.getKey()) + // Invoke `getValue()` as late as possible to avoid evaluation + && Objects.equals(getValue(), e.getValue()); + } + + private int hash(Object obj) { + return (obj == null) ? 0 : obj.hashCode(); + } + } + + @Override + public Collection values() { + return LazyMapValues.of(this); + } + + @jdk.internal.ValueBased + static final class LazyMapValues extends ImmutableCollections.AbstractImmutableCollection { + + // Use a separate field for the outer class in order to facilitate + // a @Stable annotation. + @Stable + private final AbstractLazyMap map; + + private LazyMapValues(AbstractLazyMap map) { + this.map = map; + super(); + } + + @Override public Iterator iterator() { return map.new ValueIterator(); } + @Override public int size() { return map.size(); } + @Override public boolean isEmpty() { return map.isEmpty(); } + @Override public boolean contains(Object v) { return map.containsValue(v); } + + // For @ValueBased + private static LazyMapValues of(AbstractLazyMap outer) { + return new LazyMapValues<>(outer); + } + + } + + } + + static final class Mutexes { + + private static final Object TOMB_STONE = new Object(); + + // Filled on demand and then discarded once it is not needed anymore. + // A mutex element can only transition like so: `null` -> `new Object()` -> `TOMB_STONE` + private volatile Object[] mutexes; + // Used to detect we have computed all elements and no longer need the `mutexes` array + private volatile AtomicInteger counter; + + private Mutexes(int length) { + this.mutexes = new Object[length]; + this.counter = new AtomicInteger(length); + } + + @ForceInline + private Object acquireMutex(long offset) { + assert mutexes != null; + // Check if there already is a mutex (Object or TOMB_STONE) + final Object mutex = UNSAFE.getReferenceVolatile(mutexes, offset); + if (mutex != null) { + return mutex; + } + // Protect against racy stores of mutex candidates + final Object candidate = new Object(); + final Object witness = UNSAFE.compareAndExchangeReference(mutexes, offset, null, candidate); + return witness == null ? candidate : witness; + } + + private void releaseMutex(long offset) { + // Replace the old mutex with a tomb stone since now the old mutex can be collected. + UNSAFE.putReference(mutexes, offset, TOMB_STONE); + if (counter != null && counter.decrementAndGet() == 0) { + mutexes = null; + counter = null; + } + } + + } + + @ForceInline + private static long offsetFor(long index) { + return Unsafe.ARRAY_OBJECT_BASE_OFFSET + Unsafe.ARRAY_OBJECT_INDEX_SCALE * index; + } + + @SuppressWarnings("unchecked") + private static E[] newGenericArray(int length) { + return (E[]) new Object[length]; + } + + public static List ofLazyList(int size, + IntFunction computingFunction) { + return new LazyList<>(size, computingFunction); + } + + public static Map ofLazyMap(Set keys, + Function computingFunction) { + return new LazyMap<>(keys, computingFunction); + } + + @SuppressWarnings("unchecked") + public static , V> + Map ofLazyMapWithEnumKeys(Set keys, + Function computingFunction) { + // The input set is not empty + final Class enumType = ((E) keys.iterator().next()).getDeclaringClass(); + final BitSet bitSet = new BitSet(enumType.getEnumConstants().length); + int min = Integer.MAX_VALUE; + int max = Integer.MIN_VALUE; + for (K t : keys) { + final int ordinal = ((E) t).ordinal(); + min = Math.min(min, ordinal); + max = Math.max(max, ordinal); + bitSet.set(ordinal); + } + final int backingSize = max - min + 1; + final IntPredicate member = ImmutableBitSetPredicate.of(bitSet); + return (Map) new LazyEnumMap<>((Set) keys, enumType, min, backingSize, member, (Function) computingFunction); + } + + @SuppressWarnings("unchecked") + static T orElseComputeSlowPath(final T[] array, + final int index, + final Mutexes mutexes, + final Object input, + final FunctionHolder functionHolder) { + final long offset = offsetFor(index); + final Object mutex = mutexes.acquireMutex(offset); + preventReentry(mutex); + synchronized (mutex) { + final T t = array[index]; // Plain semantics suffice here + if (t == null) { + final T newValue = switch (functionHolder.function()) { + case IntFunction iFun -> (T) iFun.apply((int) input); + case Function fun -> ((Function) fun).apply(input); + default -> throw new InternalError("cannot reach here"); + }; + Objects.requireNonNull(newValue); + // Reduce the counter and if it reaches zero, clear the reference + // to the underlying holder. + functionHolder.countDown(); + + // The mutex is not reentrant so we know newValue should be returned + set(array, index, mutex, newValue); + // We do not need the mutex anymore + mutexes.releaseMutex(offset); + return newValue; + } + return t; + } + } + + static void preventReentry(Object mutex) { + if (Thread.holdsLock(mutex)) { + throw new IllegalStateException("Recursive initialization of a lazy collection is illegal"); + } + } + + static void set(T[] array, int index, Object mutex, T newValue) { + assert Thread.holdsLock(mutex) : index + "didn't hold " + mutex; + // We know we hold the monitor here so plain semantic is enough + // This is an extra safety net to emulate a CAS op. + if (array[index] == null) { + UNSAFE.putReferenceRelease(array, Unsafe.ARRAY_OBJECT_BASE_OFFSET + Unsafe.ARRAY_OBJECT_INDEX_SCALE * (long) index, newValue); + } + } + + /** + * This class is thread safe. Any thread can create and use an instance of this class at + * any time. The `function` field is only accessed if `counter` is positive so the setting + * of function to `null` is safe. + * + * @param the underlying function type + */ + @AOTSafeClassInitializer + static final class FunctionHolder { + + private static final long COUNTER_OFFSET = UNSAFE.objectFieldOffset(FunctionHolder.class, "counter"); + + // This field can only transition at most once from being set to a + // non-null reference to being `null`. Once `null`, it is never read. + private U function; + // Used reflectively via Unsafe + private int counter; + + public FunctionHolder(U function, int counter) { + this.function = (counter == 0) ? null : function; + this.counter = counter; + // Safe publication + UNSAFE.storeStoreFence(); + } + + @ForceInline + public U function() { + return function; + } + + public void countDown() { + if (UNSAFE.getAndAddInt(this, COUNTER_OFFSET, -1) == 1) { + // Do not reference the underlying function anymore so it can be collected. + function = null; + } + } + } + +} diff --git a/src/java.base/share/classes/java/util/List.java b/src/java.base/share/classes/java/util/List.java index 32430298363..6ab80b83ef8 100644 --- a/src/java.base/share/classes/java/util/List.java +++ b/src/java.base/share/classes/java/util/List.java @@ -25,6 +25,11 @@ package java.util; +import jdk.internal.foreign.Utils; +import jdk.internal.javac.PreviewFeature; + +import java.io.Serializable; +import java.util.function.IntFunction; import java.util.function.UnaryOperator; /** @@ -87,9 +92,9 @@ import java.util.function.UnaryOperator; * interface. * *

Unmodifiable Lists

- *

The {@link List#of(Object...) List.of} and - * {@link List#copyOf List.copyOf} static factory methods - * provide a convenient way to create unmodifiable lists. The {@code List} + *

The {@link List#of(Object...) List.of}, + * {@link List#copyOf List.copyOf}, and {@link List#ofLazy(int, IntFunction)} static + * factory methods provide a convenient way to create unmodifiable lists. The {@code List} * instances created by these methods have the following characteristics: * *

    @@ -100,7 +105,7 @@ import java.util.function.UnaryOperator; * this may cause the List's contents to appear to change. *
  • They disallow {@code null} elements. Attempts to create them with * {@code null} elements result in {@code NullPointerException}. - *
  • They are serializable if all elements are serializable. + *
  • Unless otherwise specified, they are serializable if all elements are serializable. *
  • The order of elements in the list is the same as the order of the * provided arguments, or of the elements in the provided array. *
  • The lists and their {@link #subList(int, int) subList} views implement the @@ -1190,4 +1195,71 @@ public interface List extends SequencedCollection { static List copyOf(Collection coll) { return ImmutableCollections.listCopy(coll); } + + /** + * {@return a new lazily computed list of the provided {@code size}} + *

    + * The returned list is an {@linkplain Collection##unmodifiable unmodifiable} list + * with the provided {@code size}. The list's elements are lazily computed via the + * provided {@code computingFunction} when they are first accessed + * (e.g., via {@linkplain List#get(int) List::get}). + *

    + * The provided computing function is guaranteed to be successfully + * invoked at most once per list index, even in a multi-threaded environment. + * Competing threads accessing an element already under computation will block until + * an element is computed or the computing function completes abnormally + *

    + * If invoking the provided computing function throws an exception, it is rethrown + * to the initial caller and no value for the element is recorded. + *

    + * If the provided computing function returns {@code null}, + * a {@linkplain NullPointerException} will be thrown. Hence, just like other + * unmodifiable lists created via the {@code List::of} factories, a lazy list + * cannot contain {@code null} elements. Clients that want to use nullable elements + * can wrap elements into an {@linkplain Optional} holder. + *

    + * The elements of any {@link List#subList(int, int) subList()} or + * {@link List#reversed()} views of the returned list are also lazily computed. + *

    + * The returned list and its {@link List#subList(int, int) subList()} or + * {@link List#reversed()} views implement the {@link RandomAccess} interface. + *

    + * If the provided computing function recursively calls itself or the returned + * lazy list for the same index, an {@linkplain IllegalStateException} + * will be thrown. + *

    + * The returned list's {@linkplain Object Object methods}; + * {@linkplain Object#equals(Object) equals()}, + * {@linkplain Object#hashCode() hashCode()}, and + * {@linkplain Object#toString() toString()} methods may trigger initialization of + * one or more lazy elements. + *

    + * The returned lazy list strongly references its computing + * function used to compute elements at least so long as there are uninitialized + * elements. + *

    + * The returned List is not {@linkplain Serializable}. + * + * @implNote after all elements have been initialized successfully, the computing + * function is no longer strongly referenced and becomes eligible for + * garbage collection. + * + * @param size the size of the returned lazy list + * @param computingFunction to invoke whenever an element is first accessed + * (may not return {@code null}) + * @param the type of elements in the returned list + * @throws IllegalArgumentException if the provided {@code size} is negative. + * + * @see LazyConstant + * @since 26 + */ + @PreviewFeature(feature = PreviewFeature.Feature.LAZY_CONSTANTS) + static List ofLazy(int size, + IntFunction computingFunction) { + Utils.checkNonNegativeArgument(size, "size"); + Objects.requireNonNull(computingFunction); + // A computed list is not Serializable, so we cannot return `List.of()` if `size == 0` + return LazyCollections.ofLazyList(size, computingFunction); + } + } diff --git a/src/java.base/share/classes/java/util/Locale.java b/src/java.base/share/classes/java/util/Locale.java index 97278cdbafa..54863d58782 100644 --- a/src/java.base/share/classes/java/util/Locale.java +++ b/src/java.base/share/classes/java/util/Locale.java @@ -992,8 +992,8 @@ public final class Locale implements Cloneable, Serializable { } } - private static final Supplier> LOCALE_CACHE = - StableValue.supplier(new Supplier<>() { + private static final LazyConstant> LOCALE_CACHE = + LazyConstant.of(new Supplier<>() { @Override public ReferencedKeyMap get() { return ReferencedKeyMap.create(true, ReferencedKeyMap.concurrentHashMapSupplier()); @@ -2330,8 +2330,8 @@ public final class Locale implements Cloneable, Serializable { private static volatile Locale defaultDisplayLocale; private static volatile Locale defaultFormatLocale; - private final transient Supplier languageTag = - StableValue.supplier(new Supplier<>() { + private final transient LazyConstant languageTag = + LazyConstant.of(new Supplier<>() { @Override public String get() { return computeLanguageTag(); diff --git a/src/java.base/share/classes/java/util/LocaleISOData.java b/src/java.base/share/classes/java/util/LocaleISOData.java index 29e0b28be01..c99e88bd2bd 100644 --- a/src/java.base/share/classes/java/util/LocaleISOData.java +++ b/src/java.base/share/classes/java/util/LocaleISOData.java @@ -30,32 +30,32 @@ import java.util.function.Supplier; // Methods and suppliers for producing ISO 639/3166 resources used by Locale. class LocaleISOData { - static final Supplier ISO_639 = - StableValue.supplier(new Supplier<>() { + static final LazyConstant ISO_639 = + LazyConstant.of(new Supplier<>() { @Override public String[] get() { return getISO2Table(isoLanguageTable); } }); - static final Supplier ISO_3166_1_ALPHA2 = - StableValue.supplier(new Supplier<>() { + static final LazyConstant ISO_3166_1_ALPHA2 = + LazyConstant.of(new Supplier<>() { @Override public String[] get() { return getISO2Table(isoCountryTable); } }); - static final Supplier> ISO_3166_1_ALPHA3 = - StableValue.supplier(new Supplier<>() { + static final LazyConstant> ISO_3166_1_ALPHA3 = + LazyConstant.of(new Supplier<>() { @Override public Set get() { return computeISO3166_1Alpha3Countries(); } }); - static final Supplier> ISO_3166_3 = - StableValue.supplier(new Supplier<>() { + static final LazyConstant> ISO_3166_3 = + LazyConstant.of(new Supplier<>() { @Override public Set get() { return Set.of(ISO3166_3); diff --git a/src/java.base/share/classes/java/util/Map.java b/src/java.base/share/classes/java/util/Map.java index fbb41be5073..abee819069f 100644 --- a/src/java.base/share/classes/java/util/Map.java +++ b/src/java.base/share/classes/java/util/Map.java @@ -25,6 +25,8 @@ package java.util; +import jdk.internal.javac.PreviewFeature; + import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Function; @@ -114,8 +116,9 @@ import java.io.Serializable; * *

    Unmodifiable Maps

    *

    The {@link Map#of() Map.of}, - * {@link Map#ofEntries(Map.Entry...) Map.ofEntries}, and - * {@link Map#copyOf Map.copyOf} + * {@link Map#ofEntries(Map.Entry...) Map.ofEntries}, + * {@link Map#copyOf Map.copyOf}, and + * {@link Map#ofLazy(Set, Function)} * static factory methods provide a convenient way to create unmodifiable maps. * The {@code Map} * instances created by these methods have the following characteristics: @@ -128,7 +131,8 @@ import java.io.Serializable; * Map to behave inconsistently or its contents to appear to change. *

  • They disallow {@code null} keys and values. Attempts to create them with * {@code null} keys or values result in {@code NullPointerException}. - *
  • They are serializable if all keys and values are serializable. + *
  • Unless otherwise specified, they are serializable if all keys and values + * are serializable. *
  • They reject duplicate keys at creation time. Duplicate keys * passed to a static factory method result in {@code IllegalArgumentException}. *
  • The iteration order of mappings is unspecified and is subject to change. @@ -1746,4 +1750,79 @@ public interface Map { return (Map)Map.ofEntries(map.entrySet().toArray(new Entry[0])); } } + + /** + * {@return a new lazily computed map with the provided {@code keys}} + *

    + * The returned map is an {@linkplain Collection##unmodifiable unmodifiable} map whose + * keys are known at construction. The map's values are lazily computed via the + * provided {@code computingFunction} when they are first accessed + * (e.g., via {@linkplain Map#get(Object) Map::get}). + *

    + * The provided computing function is guaranteed to be successfully invoked + * at most once per key, even in a multi-threaded environment. Competing + * threads accessing a value already under computation will block until an element + * is computed or the computing function completes abnormally. + *

    + * If invoking the provided computing function throws an exception, it + * is rethrown to the initial caller and no value associated with the provided key + * is recorded. + *

    + * If the provided computing function returns {@code null}, + * a {@linkplain NullPointerException} will be thrown. Hence, just like other + * unmodifiable maps created via the {@code Map::of} factories, a lazy map + * cannot contain {@code null} values. Clients that want to use nullable values can + * wrap values into an {@linkplain Optional} holder. + *

    + * The values of any {@link Map#values()} or {@link Map#entrySet()} views of + * the returned map are also lazily computed. + *

    + * If the provided computing function recursively calls itself or + * the returned lazy map for the same key, an {@linkplain IllegalStateException} + * will be thrown. + *

    + * The returned map's {@linkplain Object Object methods}; + * {@linkplain Object#equals(Object) equals()}, + * {@linkplain Object#hashCode() hashCode()}, and + * {@linkplain Object#toString() toString()} methods may trigger initialization of + * one or more lazy elements. + *

    + * The returned lazy map strongly references its underlying + * computing function used to compute values at least so long as there are + * uncomputed values. + *

    + * The returned Map is not {@linkplain Serializable}. + * + * @implNote after all values have been initialized successfully, the computing + * function is no longer strongly referenced and becomes eligible for + * garbage collection. + * + * @param keys the (non-null) keys in the returned computed map + * @param computingFunction to invoke whenever an associated value is first accessed + * @param the type of keys maintained by the returned map + * @param the type of mapped values in the returned map + * @throws NullPointerException if the provided set of {@code keys} is {@code null} + * or if the set of {@code keys} contains a {@code null} element. + * + * @see LazyConstant + * @since 26 + */ + @PreviewFeature(feature = PreviewFeature.Feature.LAZY_CONSTANTS) + static Map ofLazy(Set keys, + Function computingFunction) { + // Protect against TOC-TOU attacks. + // Also, implicit null check of `keys` and all its elements + final Set keyCopies = Set.copyOf(keys); + Objects.requireNonNull(computingFunction); + // We need to check the instance type using the original `keys` parameter. + if (keys instanceof EnumSet && !keyCopies.isEmpty()) { + @SuppressWarnings("unchecked") + var enumMap = (Map) LazyCollections.ofLazyMapWithEnumKeys(keyCopies, computingFunction); + return enumMap; + } else { + // A computed map is not Serializable, so we cannot return `Map.of()` if `keys.isEmpty()` + return LazyCollections.ofLazyMap(keyCopies, computingFunction); + } + } + } diff --git a/src/java.base/share/classes/java/util/Optional.java b/src/java.base/share/classes/java/util/Optional.java index e19dde6383e..3e577bd379c 100644 --- a/src/java.base/share/classes/java/util/Optional.java +++ b/src/java.base/share/classes/java/util/Optional.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2025, 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,8 +22,11 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ + package java.util; +import jdk.internal.vm.annotation.Stable; + import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; @@ -68,6 +71,7 @@ public final class Optional { /** * If non-null, the value; if null, indicates no value is present */ + @Stable private final T value; /** diff --git a/src/java.base/share/classes/java/util/ResourceBundle.java b/src/java.base/share/classes/java/util/ResourceBundle.java index b375a8ba941..db19eda6399 100644 --- a/src/java.base/share/classes/java/util/ResourceBundle.java +++ b/src/java.base/share/classes/java/util/ResourceBundle.java @@ -488,7 +488,7 @@ public abstract class ResourceBundle { /** * A Set of the keys contained only in this ResourceBundle. */ - private final Supplier> keySet = StableValue.supplier( + private final LazyConstant> keySet = LazyConstant.of( new Supplier<>() { public Set get() { return keySet0(); }}); private Set keySet0() { diff --git a/src/java.base/share/classes/jdk/internal/access/JavaUtilCollectionAccess.java b/src/java.base/share/classes/jdk/internal/access/JavaUtilCollectionAccess.java index cac8785b158..5e7647d6106 100644 --- a/src/java.base/share/classes/jdk/internal/access/JavaUtilCollectionAccess.java +++ b/src/java.base/share/classes/jdk/internal/access/JavaUtilCollectionAccess.java @@ -26,14 +26,8 @@ package jdk.internal.access; import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.Function; -import java.util.function.IntFunction; public interface JavaUtilCollectionAccess { List listFromTrustedArray(Object[] array); List listFromTrustedArrayNullsAllowed(Object[] array); - List stableList(int size, IntFunction mapper); - Map stableMap(Set keys, Function mapper); } diff --git a/src/java.base/share/classes/jdk/internal/foreign/CaptureStateUtil.java b/src/java.base/share/classes/jdk/internal/foreign/CaptureStateUtil.java index 2f3ba7d9e05..b3a19340fb5 100644 --- a/src/java.base/share/classes/jdk/internal/foreign/CaptureStateUtil.java +++ b/src/java.base/share/classes/jdk/internal/foreign/CaptureStateUtil.java @@ -38,6 +38,8 @@ import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.invoke.VarHandle; import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.function.Function; @@ -57,7 +59,7 @@ public final class CaptureStateUtil { // of combinators needed to form an adapted method handle. // The function is lazily computed. // - private static final Function SEGMENT_EXTRACTION_HANDLE_CACHE; + private static final Map SEGMENT_EXTRACTION_HANDLE_CACHE; static { final Set inputs = new HashSet<>(); @@ -77,7 +79,7 @@ public final class CaptureStateUtil { } }; - SEGMENT_EXTRACTION_HANDLE_CACHE = StableValue.function(inputs, segmentExtractionHandle); + SEGMENT_EXTRACTION_HANDLE_CACHE = Map.ofLazy(inputs, segmentExtractionHandle); } // A key that holds both the `returnType` and the `stateName` needed to look up a @@ -188,7 +190,10 @@ public final class CaptureStateUtil { final SegmentExtractorKey key = new SegmentExtractorKey(target, stateName); // ((int | long), MemorySegment)(int | long) - final MethodHandle segmentExtractor = SEGMENT_EXTRACTION_HANDLE_CACHE.apply(key); + final MethodHandle segmentExtractor = SEGMENT_EXTRACTION_HANDLE_CACHE.get(key); + if (segmentExtractor == null) { + throw new IllegalArgumentException("Input not allowed: " + key); + } // Make `target` specific adaptations of the basic handle @@ -208,7 +213,7 @@ public final class CaptureStateUtil { // Use an `Arena` for the first argument instead and extract a segment from it. // (C0=Arena, C1-Cn)(int|long) - innerAdapted = MethodHandles.collectArguments(innerAdapted, 0, HANDLES_CACHE.apply(ALLOCATE)); + innerAdapted = MethodHandles.collectArguments(innerAdapted, 0, HANDLES_CACHE.get(ALLOCATE)); // Add an identity function for the result of the cleanup action. // ((int|long))(int|long) @@ -221,7 +226,7 @@ public final class CaptureStateUtil { // cleanup action and invoke `Arena::close` when it is run. The `cleanup` handle // does not have to have all parameters. It can have zero or more. // (Throwable, (int|long), Arena)(int|long) - cleanup = MethodHandles.collectArguments(cleanup, 2, HANDLES_CACHE.apply(ARENA_CLOSE)); + cleanup = MethodHandles.collectArguments(cleanup, 2, HANDLES_CACHE.get(ARENA_CLOSE)); // Combine the `innerAdapted` and `cleanup` action into a try/finally block. // (Arena, C1-Cn)(int|long) @@ -230,7 +235,7 @@ public final class CaptureStateUtil { // Acquire the arena from the global pool. // With this, we finally arrive at the intended method handle: // (C1-Cn)(int|long) - return MethodHandles.collectArguments(tryFinally, 0, HANDLES_CACHE.apply(ACQUIRE_ARENA)); + return MethodHandles.collectArguments(tryFinally, 0, HANDLES_CACHE.get(ACQUIRE_ARENA)); } private static MethodHandle makeSegmentExtractionHandle(SegmentExtractorKey segmentExtractorKey) { @@ -260,15 +265,15 @@ public final class CaptureStateUtil { if (segmentExtractorKey.returnType().equals(int.class)) { // (int, MemorySegment)int return MethodHandles.guardWithTest( - HANDLES_CACHE.apply(NON_NEGATIVE_INT), - HANDLES_CACHE.apply(SUCCESS_INT), - HANDLES_CACHE.apply(ERROR_INT).bindTo(intExtractor)); + HANDLES_CACHE.get(NON_NEGATIVE_INT), + HANDLES_CACHE.get(SUCCESS_INT), + HANDLES_CACHE.get(ERROR_INT).bindTo(intExtractor)); } else { // (long, MemorySegment)long return MethodHandles.guardWithTest( - HANDLES_CACHE.apply(NON_NEGATIVE_LONG), - HANDLES_CACHE.apply(SUCCESS_LONG), - HANDLES_CACHE.apply(ERROR_LONG).bindTo(intExtractor)); + HANDLES_CACHE.get(NON_NEGATIVE_LONG), + HANDLES_CACHE.get(SUCCESS_LONG), + HANDLES_CACHE.get(ERROR_LONG).bindTo(intExtractor)); } } @@ -341,8 +346,8 @@ public final class CaptureStateUtil { } }; - private static final IntFunction HANDLES_CACHE = - StableValue.intFunction(ARENA_CLOSE + 1, UNDERLYING_MAKE_HANDLE); + private static final List HANDLES_CACHE = + List.ofLazy(ARENA_CLOSE + 1, UNDERLYING_MAKE_HANDLE); private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); diff --git a/src/java.base/share/classes/jdk/internal/io/JdkConsoleImpl.java b/src/java.base/share/classes/jdk/internal/io/JdkConsoleImpl.java index 820dfe90073..a6f4174324e 100644 --- a/src/java.base/share/classes/jdk/internal/io/JdkConsoleImpl.java +++ b/src/java.base/share/classes/jdk/internal/io/JdkConsoleImpl.java @@ -39,6 +39,7 @@ import java.util.Formatter; import java.util.Locale; import java.util.Objects; import java.util.Optional; +import java.util.function.Supplier; import jdk.internal.access.SharedSecrets; import jdk.internal.util.StaticProperty; @@ -114,24 +115,29 @@ public final class JdkConsoleImpl implements JdkConsole { // check if `System.console()` returns a Console instance and use it if available. Otherwise, // it should call this method to obtain a JdkConsoleImpl. This ensures only one Console // instance exists in the Java runtime. - private static final StableValue> INSTANCE = StableValue.of(); - public static Optional passwordConsole() { - return INSTANCE.orElseSet(() -> { - // If there's already a proper console, throw an exception - if (System.console() != null) { - throw new IllegalStateException("Can’t create a dedicated password " + - "console since a real console already exists"); - } + private static final LazyConstant> PASSWORD_CONSOLE = LazyConstant.of( + new Supplier>() { + @Override + public Optional get() { + if (System.console() != null) { + throw new IllegalStateException("Can’t create a dedicated password " + + "console since a real console already exists"); + } - // If stdin is NOT redirected, return an Optional containing a JdkConsoleImpl - // instance, otherwise an empty Optional. - return SharedSecrets.getJavaIOAccess().isStdinTty() ? - Optional.of( - new JdkConsoleImpl( - Charset.forName(StaticProperty.stdinEncoding(), UTF_8.INSTANCE), - Charset.forName(StaticProperty.stdoutEncoding(), UTF_8.INSTANCE))) : - Optional.empty(); - }); + // If stdin is NOT redirected, return an Optional containing a JdkConsoleImpl + // instance, otherwise an empty Optional. + return SharedSecrets.getJavaIOAccess().isStdinTty() ? + Optional.of( + new JdkConsoleImpl( + Charset.forName(StaticProperty.stdinEncoding(), UTF_8.INSTANCE), + Charset.forName(StaticProperty.stdoutEncoding(), UTF_8.INSTANCE))) : + Optional.empty(); + } + } + ); + + public static Optional passwordConsole() { + return PASSWORD_CONSOLE.get(); } // Dedicated entry for sun.security.util.Password when stdout is redirected. diff --git a/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java b/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java index 1316b8b946f..5942cefa2a1 100644 --- a/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java +++ b/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java @@ -85,8 +85,8 @@ public @interface PreviewFeature { //--- @JEP(number=525, title="Structured Concurrency", status="Sixth Preview") STRUCTURED_CONCURRENCY, - @JEP(number = 502, title = "Stable Values", status = "Preview") - STABLE_VALUES, + @JEP(number = 526, title = "Lazy Constants", status = "Second Preview") + LAZY_CONSTANTS, @JEP(number=524, title="PEM Encodings of Cryptographic Objects", status="Second Preview") PEM_API, diff --git a/src/java.base/share/classes/jdk/internal/lang/LazyConstantImpl.java b/src/java.base/share/classes/jdk/internal/lang/LazyConstantImpl.java new file mode 100644 index 00000000000..59d0174c4c9 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/lang/LazyConstantImpl.java @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2025, 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. 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.internal.lang; + +import jdk.internal.misc.Unsafe; +import jdk.internal.vm.annotation.AOTSafeClassInitializer; +import jdk.internal.vm.annotation.ForceInline; +import jdk.internal.vm.annotation.Stable; + +import java.util.Objects; +import java.util.function.Supplier; + +/** + * The sole implementation of the LazyConstant interface. + * + * @param type of the constant + * @implNote This implementation can be used early in the boot sequence as it does not + * rely on reflection, MethodHandles, Streams etc. + */ +@AOTSafeClassInitializer +public final class LazyConstantImpl implements LazyConstant { + + // Unsafe allows `LazyConstant` instances to be used early in the boot sequence + private static final Unsafe UNSAFE = Unsafe.getUnsafe(); + + // Unsafe offset for access of the `constant` field + private static final long CONSTANT_OFFSET = + UNSAFE.objectFieldOffset(LazyConstantImpl.class, "constant"); + + // Generally, fields annotated with `@Stable` are accessed by the JVM using special + // memory semantics rules (see `parse.hpp` and `parse(1|2|3).cpp`). + // + // This field is used reflectively via Unsafe using explicit memory semantics. + // + // | Value | Meaning | + // | --------------- | -------------- | + // | `null` | Unset | + // | `other` | Set to `other` | + // + @Stable + private T constant; + + // Underlying computing function to be used to compute the `constant` field. + // The field needs to be `volatile` as a lazy constant can be + // created by one thread and computed by another thread. + // After the function is successfully invoked, the field is set to + // `null` to allow the function to be collected. + @Stable + private volatile Supplier computingFunction; + + private LazyConstantImpl(Supplier computingFunction) { + this.computingFunction = computingFunction; + } + + @ForceInline + @Override + public T get() { + final T t = getAcquire(); + return (t != null) ? t : getSlowPath(); + } + + private T getSlowPath() { + preventReentry(); + synchronized (this) { + T t = getAcquire(); + if (t == null) { + t = computingFunction.get(); + Objects.requireNonNull(t); + setRelease(t); + // Allow the underlying supplier to be collected after successful use + computingFunction = null; + } + return t; + } + } + + @ForceInline + @Override + public T orElse(T other) { + final T t = getAcquire(); + return (t == null) ? other : t; + } + + @ForceInline + @Override + public boolean isInitialized() { + return getAcquire() != null; + } + + @Override + public String toString() { + return super.toString() + "[" + toStringSuffix() + "]"; + } + + private String toStringSuffix() { + final T t = getAcquire(); + if (t == this) { + return "(this LazyConstant)"; + } else if (t != null) { + return t.toString(); + } + // Volatile read + final Supplier cf = computingFunction; + // There could be a race here + if (cf != null) { + return "computing function=" + computingFunction.toString(); + } + // As we know `computingFunction` is `null` via a volatile read, we + // can now be sure that this lazy constant is initialized + return getAcquire().toString(); + } + + + // Discussion on the memory semantics used. + // ---------------------------------------- + // Using acquire/release semantics on the `constant` field is the cheapest way to + // establish a happens-before (HB) relation between load and store operations. Every + // implementation of a method defined in the interface `LazyConstant` except + // `equals()` starts with a load of the `constant` field using acquire semantics. + // + // If the underlying supplier was guaranteed to always create a new object, + // a fence after creation and subsequent plain loads would suffice to ensure + // new objects' state are always correctly observed. However, no such restriction is + // imposed on the underlying supplier. Hence, the docs state there should be an + // HB relation meaning we will have to pay a price (on certain platforms) on every + // `get()` operation that is not constant-folded. + + @SuppressWarnings("unchecked") + @ForceInline + private T getAcquire() { + return (T) UNSAFE.getReferenceAcquire(this, CONSTANT_OFFSET); + } + + private void setRelease(T newValue) { + UNSAFE.putReferenceRelease(this, CONSTANT_OFFSET, newValue); + } + + private void preventReentry() { + if (Thread.holdsLock(this)) { + throw new IllegalStateException("Recursive invocation of a LazyConstant's computing function: " + computingFunction); + } + } + + // Factory + + public static LazyConstantImpl ofLazy(Supplier computingFunction) { + return new LazyConstantImpl<>(computingFunction); + } + +} diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java deleted file mode 100644 index d6893438be5..00000000000 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (c) 2025, 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. 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.internal.lang.stable; - -import jdk.internal.util.ImmutableBitSetPredicate; -import jdk.internal.vm.annotation.ForceInline; -import jdk.internal.vm.annotation.Stable; - -import java.util.AbstractMap; -import java.util.ArrayList; -import java.util.BitSet; -import java.util.Collection; -import java.util.EnumSet; -import java.util.Map; -import java.util.Set; -import java.util.function.Function; -import java.util.function.IntPredicate; -import java.util.function.Supplier; - -/** - * Optimized implementation of a stable Function with enums as keys. - * - * @implNote This implementation can be used early in the boot sequence as it does not - * rely on reflection, MethodHandles, Streams etc. - * - * @param enumType the class type of the Enum - * @param firstOrdinal the lowest ordinal used - * @param member an int predicate that can be used to test if an enum is a member - * of the valid inputs (as there might be "holes") - * @param delegates a delegate array of inputs to StableValue mappings - * @param original the original Function - * @param the type of the input to the function - * @param the type of the result of the function - */ -public record StableEnumFunction, R>(Class enumType, - int firstOrdinal, - IntPredicate member, - @Stable StableValueImpl[] delegates, - Function original) implements Function { - @ForceInline - @Override - public R apply(E value) { - if (!member.test(value.ordinal())) { // Implicit null-check of value - throw new IllegalArgumentException("Input not allowed: " + value); - } - final int index = value.ordinal() - firstOrdinal; - // Since we did the member.test above, we know the index is in bounds - return delegates[index].orElseSet(new Supplier() { - @Override public R get() { return original.apply(value); }}); - - } - - @Override - public int hashCode() { - return System.identityHashCode(this); - } - - @Override - public boolean equals(Object obj) { - return obj == this; - } - - @Override - public String toString() { - final Collection>> entries = new ArrayList<>(delegates.length); - final E[] enumElements = enumType.getEnumConstants(); - int ordinal = firstOrdinal; - for (int i = 0; i < delegates.length; i++, ordinal++) { - if (member.test(ordinal)) { - entries.add(new AbstractMap.SimpleImmutableEntry<>(enumElements[ordinal], delegates[i])); - } - } - return StableUtil.renderMappings(this, "StableFunction", entries, true); - } - - @SuppressWarnings("unchecked") - public static , R> Function of(Set inputs, - Function original) { - // The input set is not empty - final Class enumType = ((E) inputs.iterator().next()).getDeclaringClass(); - final BitSet bitSet = new BitSet(enumType.getEnumConstants().length); - int min = Integer.MAX_VALUE; - int max = Integer.MIN_VALUE; - for (T t : inputs) { - final int ordinal = ((E) t).ordinal(); - min = Math.min(min, ordinal); - max = Math.max(max, ordinal); - bitSet.set(ordinal); - } - final int size = max - min + 1; - final IntPredicate member = ImmutableBitSetPredicate.of(bitSet); - return (Function) new StableEnumFunction(enumType, min, member, StableUtil.array(size), (Function) original); - } - -} diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java deleted file mode 100644 index e36b4e9b25a..00000000000 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (c) 2025, 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. 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.internal.lang.stable; - -import jdk.internal.vm.annotation.ForceInline; - -import java.util.Map; -import java.util.Set; -import java.util.function.Function; -import java.util.function.Supplier; - -// Note: It would be possible to just use `StableMap::get` with some additional logic -// instead of this class but explicitly providing a class like this provides better -// debug capability, exception handling, and may provide better performance. -/** - * Implementation of a stable Function. - * - * @implNote This implementation can be used early in the boot sequence as it does not - * rely on reflection, MethodHandles, Streams etc. - * - * @param values a delegate map of inputs to StableValue mappings - * @param original the original Function - * @param the type of the input to the function - * @param the type of the result of the function - */ -public record StableFunction(Map> values, - Function original) implements Function { - - @ForceInline - @Override - public R apply(T value) { - final StableValueImpl stable = values.get(value); - if (stable == null) { - throw new IllegalArgumentException("Input not allowed: " + value); - } - return stable.orElseSet(new Supplier() { - @Override public R get() { return original.apply(value); }}); - } - - @Override - public int hashCode() { - return System.identityHashCode(this); - } - - @Override - public boolean equals(Object obj) { - return obj == this; - } - - @Override - public String toString() { - return StableUtil.renderMappings(this, "StableFunction", values.entrySet(), true); - } - - public static StableFunction of(Set inputs, - Function original) { - return new StableFunction<>(StableUtil.map(inputs), original); - } - -} diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java deleted file mode 100644 index a921a4de87b..00000000000 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (c) 2025, 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. 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.internal.lang.stable; - -import jdk.internal.vm.annotation.ForceInline; -import jdk.internal.vm.annotation.Stable; - -import java.util.function.IntFunction; -import java.util.function.Supplier; - -/** - * Implementation of a stable IntFunction. - *

    - * For performance reasons (~10%), we are not delegating to a StableList but are using - * the more primitive functions in StableValueUtil that are shared with StableList/StableValueImpl. - * - * @implNote This implementation can be used early in the boot sequence as it does not - * rely on reflection, MethodHandles, Streams etc. - * - * @param the return type - */ -public record StableIntFunction(@Stable StableValueImpl[] delegates, - IntFunction original) implements IntFunction { - - @ForceInline - @Override - public R apply(int index) { - final StableValueImpl delegate; - try { - delegate = delegates[index]; - } catch (ArrayIndexOutOfBoundsException ioob) { - throw new IllegalArgumentException("Input not allowed: " + index, ioob); - } - return delegate.orElseSet(new Supplier() { - @Override public R get() { return original.apply(index); }}); - } - - @Override - public int hashCode() { - return System.identityHashCode(this); - } - - @Override - public boolean equals(Object obj) { - return obj == this; - } - - @Override - public String toString() { - return StableUtil.renderElements(this, "StableIntFunction", delegates); - } - - public static StableIntFunction of(int size, IntFunction original) { - return new StableIntFunction<>(StableUtil.array(size), original); - } - -} diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java deleted file mode 100644 index 631a41c5710..00000000000 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2025, 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. 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.internal.lang.stable; - -import jdk.internal.vm.annotation.ForceInline; - -import java.util.function.Supplier; - -/** - * Implementation of a stable supplier. - *

    - * @implNote This implementation can be used early in the boot sequence as it does not - * rely on reflection, MethodHandles, Streams etc. - * - * @param the return type - */ -public record StableSupplier(StableValueImpl delegate, - Supplier original) implements Supplier { - - @ForceInline - @Override - public T get() { - return delegate.orElseSet(original); - } - - @Override - public int hashCode() { - return System.identityHashCode(this); - } - - @Override - public boolean equals(Object obj) { - return obj == this; - } - - @Override - public String toString() { - final Object t = delegate.wrappedContentsAcquire(); - return t == this ? "(this StableSupplier)" : StableValueImpl.renderWrapped(t); - } - - public static StableSupplier of(Supplier original) { - return new StableSupplier<>(StableValueImpl.of(), original); - } - -} diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableUtil.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableUtil.java deleted file mode 100644 index 74104ddbb49..00000000000 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableUtil.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (c) 2025, 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. 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.internal.lang.stable; - -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.StringJoiner; - -public final class StableUtil { - - private StableUtil() {} - - public static String renderElements(Object self, - String selfName, - StableValueImpl[] delegates) { - return renderElements(self, selfName, delegates, 0, delegates.length); - } - - public static String renderElements(Object self, - String selfName, - StableValueImpl[] delegates, - int offset, - int length) { - final StringJoiner sj = new StringJoiner(", ", "[", "]"); - for (int i = 0; i < length; i++) { - final Object value = delegates[i + offset].wrappedContentsAcquire(); - if (value == self) { - sj.add("(this " + selfName + ")"); - } else { - sj.add(StableValueImpl.renderWrapped(value)); - } - } - return sj.toString(); - } - - public static String renderMappings(Object self, - String selfName, - Iterable>> delegates, - boolean curly) { - final StringJoiner sj = new StringJoiner(", ", curly ? "{" : "[", curly ? "}" : "]"); - for (var e : delegates) { - final Object value = e.getValue().wrappedContentsAcquire(); - final String valueString; - if (value == self) { - valueString = "(this " + selfName + ")"; - } else { - valueString = StableValueImpl.renderWrapped(value); - } - sj.add(e.getKey() + "=" + valueString); - } - return sj.toString(); - } - - public static StableValueImpl[] array(int size) { - assertSizeNonNegative(size); - @SuppressWarnings("unchecked") - final var stableValues = (StableValueImpl[]) new StableValueImpl[size]; - for (int i = 0; i < size; i++) { - stableValues[i] = StableValueImpl.of(); - } - return stableValues; - } - - public static Map> map(Set keys) { - Objects.requireNonNull(keys); - @SuppressWarnings("unchecked") - final var entries = (Map.Entry>[]) new Map.Entry[keys.size()]; - int i = 0; - for (K key : keys) { - entries[i++] = Map.entry(key, StableValueImpl.of()); - } - return Map.ofEntries(entries); - } - - public static void assertSizeNonNegative(int size) { - if (size < 0) { - throw new IllegalArgumentException("size can not be negative: " + size); - } - } - -} diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueImpl.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueImpl.java deleted file mode 100644 index 1413fd7446e..00000000000 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueImpl.java +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright (c) 2025, 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. 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.internal.lang.stable; - -import jdk.internal.misc.Unsafe; -import jdk.internal.vm.annotation.DontInline; -import jdk.internal.vm.annotation.ForceInline; -import jdk.internal.vm.annotation.Stable; - -import java.util.NoSuchElementException; -import java.util.Objects; -import java.util.function.Supplier; - -/** - * The implementation of StableValue. - * - * @implNote This implementation can be used early in the boot sequence as it does not - * rely on reflection, MethodHandles, Streams etc. - * - * @param type of the contents - */ -public final class StableValueImpl implements StableValue { - - static final String UNSET_LABEL = ".unset"; - - // Unsafe allows StableValue to be used early in the boot sequence - static final Unsafe UNSAFE = Unsafe.getUnsafe(); - - // Unsafe offsets for direct field access - - private static final long CONTENTS_OFFSET = - UNSAFE.objectFieldOffset(StableValueImpl.class, "contents"); - - // Used to indicate a holder value is `null` (see field `contents` below) - private static final Object NULL_SENTINEL = new Object(); - - // Generally, fields annotated with `@Stable` are accessed by the JVM using special - // memory semantics rules (see `parse.hpp` and `parse(1|2|3).cpp`). - // - // This field is used directly and reflectively via Unsafe using explicit memory semantics. - // - // | Value | Meaning | - // | -------------- | ------------ | - // | null | Unset | - // | NULL_SENTINEL | Set(null) | - // | other | Set(other) | - // - @Stable - private Object contents; - - // Only allow creation via the factory `StableValueImpl::newInstance` - private StableValueImpl() {} - - @ForceInline - @Override - public boolean trySet(T contents) { - if (wrappedContentsAcquire() != null) { - return false; - } - // Prevent reentry via an orElseSet(supplier) - preventReentry(); - // Mutual exclusion is required here as `orElseSet` might also - // attempt to modify `this.contents` - synchronized (this) { - return wrapAndSet(contents); - } - } - - @ForceInline - @Override - public void setOrThrow(T contents) { - if (!trySet(contents)) { - // Neither the set contents nor the provided contents is revealed in the - // exception message as it might be sensitive. - throw new IllegalStateException("The contents is already set"); - } - } - - @ForceInline - @Override - public T orElseThrow() { - final Object t = wrappedContentsAcquire(); - if (t == null) { - throw new NoSuchElementException("No contents set"); - } - return unwrap(t); - } - - @ForceInline - @Override - public T orElse(T other) { - final Object t = wrappedContentsAcquire(); - return (t == null) ? other : unwrap(t); - } - - @ForceInline - @Override - public boolean isSet() { - return wrappedContentsAcquire() != null; - } - - @ForceInline - @Override - public T orElseSet(Supplier supplier) { - Objects.requireNonNull(supplier); - final Object t = wrappedContentsAcquire(); - return (t == null) ? orElseSetSlowPath(supplier) : unwrap(t); - } - - @DontInline - private T orElseSetSlowPath(Supplier supplier) { - preventReentry(); - synchronized (this) { - final Object t = contents; // Plain semantics suffice here - if (t == null) { - final T newValue = supplier.get(); - // The mutex is not reentrant so we know newValue should be returned - wrapAndSet(newValue); - return newValue; - } - return unwrap(t); - } - } - - // The methods equals() and hashCode() should be based on identity (defaults from Object) - - @Override - public String toString() { - final Object t = wrappedContentsAcquire(); - return t == this - ? "(this StableValue)" - : renderWrapped(t); - } - - // Internal methods shared with other internal classes - - @ForceInline - public Object wrappedContentsAcquire() { - return UNSAFE.getReferenceAcquire(this, CONTENTS_OFFSET); - } - - static String renderWrapped(Object t) { - return (t == null) ? UNSET_LABEL : Objects.toString(unwrap(t)); - } - - // Private methods - - // This method is not annotated with @ForceInline as it is always called - // in a slow path. - private void preventReentry() { - if (Thread.holdsLock(this)) { - throw new IllegalStateException("Recursive initialization of a stable value is illegal"); - } - } - - /** - * Wraps the provided {@code newValue} and tries to set the contents. - *

    - * This method ensures the {@link Stable} field is written to at most once. - * - * @param newValue to wrap and set - * @return if the contents was set - */ - @ForceInline - private boolean wrapAndSet(T newValue) { - assert Thread.holdsLock(this); - // We know we hold the monitor here so plain semantic is enough - if (contents == null) { - UNSAFE.putReferenceRelease(this, CONTENTS_OFFSET, wrap(newValue)); - return true; - } - return false; - } - - - // Wraps `null` values into a sentinel value - @ForceInline - private static Object wrap(Object t) { - return (t == null) ? NULL_SENTINEL : t; - } - - // Unwraps null sentinel values into `null` - @SuppressWarnings("unchecked") - @ForceInline - private static T unwrap(Object t) { - return t != NULL_SENTINEL ? (T) t : null; - } - - // Factory - - public static StableValueImpl of() { - return new StableValueImpl<>(); - } - -} diff --git a/src/java.base/share/classes/sun/nio/ch/Net.java b/src/java.base/share/classes/sun/nio/ch/Net.java index 9ec7975a35c..4564579d054 100644 --- a/src/java.base/share/classes/sun/nio/ch/Net.java +++ b/src/java.base/share/classes/sun/nio/ch/Net.java @@ -48,6 +48,8 @@ import java.nio.channels.UnresolvedAddressException; import java.nio.channels.UnsupportedAddressTypeException; import java.util.Enumeration; import java.util.Objects; +import java.lang.LazyConstant; +import java.util.function.Supplier; import sun.net.ext.ExtendedSocketOptions; import sun.net.util.IPAddressUtil; @@ -94,13 +96,14 @@ public class Net { return EXCLUSIVE_BIND; } - private static final StableValue SHUTDOWN_WRITE_BEFORE_CLOSE = StableValue.of(); + private static final LazyConstant SHUTDOWN_WRITE_BEFORE_CLOSE = LazyConstant.of(new Supplier() { + @Override public Boolean get() { return shouldShutdownWriteBeforeClose0(); }}); /** * Tells whether a TCP connection should be shutdown for writing before closing. */ static boolean shouldShutdownWriteBeforeClose() { - return SHUTDOWN_WRITE_BEFORE_CLOSE.orElseSet(Net::shouldShutdownWriteBeforeClose0); + return SHUTDOWN_WRITE_BEFORE_CLOSE.get(); } /** diff --git a/src/java.base/share/classes/sun/util/locale/BaseLocale.java b/src/java.base/share/classes/sun/util/locale/BaseLocale.java index 529ca1b0c13..91efc61d1bf 100644 --- a/src/java.base/share/classes/sun/util/locale/BaseLocale.java +++ b/src/java.base/share/classes/sun/util/locale/BaseLocale.java @@ -92,8 +92,8 @@ public final class BaseLocale { } // Interned BaseLocale cache - private static final Supplier> CACHE = - StableValue.supplier(new Supplier<>() { + private static final LazyConstant> CACHE = + LazyConstant.of(new Supplier<>() { @Override public ReferencedKeySet get() { return ReferencedKeySet.create(true, ReferencedKeySet.concurrentHashMapSupplier()); diff --git a/src/java.base/share/classes/sun/util/resources/BreakIteratorResourceBundle.java b/src/java.base/share/classes/sun/util/resources/BreakIteratorResourceBundle.java index d7c575962f6..49e882c5ca6 100644 --- a/src/java.base/share/classes/sun/util/resources/BreakIteratorResourceBundle.java +++ b/src/java.base/share/classes/sun/util/resources/BreakIteratorResourceBundle.java @@ -50,7 +50,7 @@ public abstract class BreakIteratorResourceBundle extends ResourceBundle { // those keys must be added to NON_DATA_KEYS. private static final Set NON_DATA_KEYS = Set.of("BreakIteratorClasses"); - private final Supplier> keys = StableValue.supplier( + private final LazyConstant> keys = LazyConstant.of( new Supplier<>() { public Set get() { return keys0(); }}); private Set keys0() { diff --git a/src/java.base/share/classes/sun/util/resources/OpenListResourceBundle.java b/src/java.base/share/classes/sun/util/resources/OpenListResourceBundle.java index 0c16f508018..9d0c1c015ce 100644 --- a/src/java.base/share/classes/sun/util/resources/OpenListResourceBundle.java +++ b/src/java.base/share/classes/sun/util/resources/OpenListResourceBundle.java @@ -119,7 +119,7 @@ public abstract class OpenListResourceBundle extends ResourceBundle { return new HashSet<>(); } - private final Supplier> lookup = StableValue.supplier( + private final LazyConstant> lookup = LazyConstant.of( new Supplier<>() { public Map get() { return lookup0(); }}); private Map lookup0() { @@ -134,7 +134,7 @@ public abstract class OpenListResourceBundle extends ResourceBundle { return temp; } - private final Supplier> keyset = StableValue.supplier( + private final LazyConstant> keyset = LazyConstant.of( new Supplier<>() { public Set get() { return keyset0(); }}); private Set keyset0() { diff --git a/test/jdk/java/lang/LazyConstant/DemoContainerInjectionTest.java b/test/jdk/java/lang/LazyConstant/DemoContainerInjectionTest.java new file mode 100644 index 00000000000..df51a562210 --- /dev/null +++ b/test/jdk/java/lang/LazyConstant/DemoContainerInjectionTest.java @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* @test + * @summary Basic tests for dependency injection + * @enablePreview + * @run junit DemoContainerInjectionTest + */ + +import org.junit.jupiter.api.Test; + +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.*; + +final class DemoContainerInjectionTest { + + interface Foo{} + interface Bar{}; + static class FooImpl implements Foo{}; + static class BarImpl implements Bar{}; + + // Provides a type safe way of associating a type to a supplier + record Provider(Class type, Supplier supplier){} + + @Test + void ComputedComponentsViaLambda() { + Container container = ComputedContainer.of(Set.of(Foo.class, Bar.class), t -> switch (t) { + case Class c when c.equals(Foo.class) -> new FooImpl(); + case Class c when c.equals(Bar.class) -> new BarImpl(); + default -> throw new IllegalArgumentException(); + }); + assertContainerPopulated(container); + } + + @Test + void SettableComponents() { + SettableContainer container = SettableScratchContainer.of(Set.of(Foo.class, Bar.class)); + container.set(Foo.class, new FooImpl()); + container.set(Bar.class, new BarImpl()); + assertContainerPopulated(container); + } + + + @Test + void ProviderComponents() { + Container container = ProviderContainer.of(Map.of( + Foo.class, FooImpl::new, + Bar.class, BarImpl::new)); + assertContainerPopulated(container); + } + + @Test + void ProviderTypedComponents() { + Container container = providerTypedContainer(Set.of( + new Provider<>(Foo.class, FooImpl::new), + new Provider<>(Bar.class, BarImpl::new) + )); + assertContainerPopulated(container); + } + + private static void assertContainerPopulated(Container container) { + assertInstanceOf(FooImpl.class, container.get(Foo.class)); + assertInstanceOf(BarImpl.class, container.get(Bar.class)); + } + + interface Container { + T get(Class type); + } + + interface SettableContainer extends Container { + void set(Class type, T implementation); + } + + record ComputedContainer(Map, ?> components) implements Container { + + @Override + public T get(Class type) { + return type.cast(components.get(type)); + } + + static Container of(Set> components, Function, ?> mapper) { + return new ComputedContainer(Map.ofLazy(components, mapper)); + } + + } + + record SettableScratchContainer(Map, Object> scratch, Map, ?> components) implements SettableContainer { + + @Override + public void set(Class type, T implementation) { + if (scratch.putIfAbsent(type, type.cast(implementation)) != null) { + throw new IllegalStateException("Can only set once for " + type); + } + } + + @Override + public T get(Class type) { + return type.cast(components.get(type)); + } + + static SettableContainer of(Set> components) { + Map, Object> scratch = new ConcurrentHashMap<>(); + return new SettableScratchContainer(scratch, Map.ofLazy(components, scratch::get)); + } + + } + + record ProviderContainer(Map, ?> components) implements Container { + + @Override + public T get(Class type) { + return type.cast(components.get(type)); + } + + static Container of(Map, Supplier> components) { + var map = Map.ofLazy(components.keySet(), t -> components.get(t).get()); + return new ProviderContainer(map); + } + + } + + static Container providerTypedContainer(Set> providers) { + return ProviderContainer.of(providers.stream() + .collect(Collectors.toMap(Provider::type, Provider::supplier))); + } + +} diff --git a/test/jdk/java/lang/LazyConstant/DemoImperativeTest.java b/test/jdk/java/lang/LazyConstant/DemoImperativeTest.java new file mode 100644 index 00000000000..bc1208e67f8 --- /dev/null +++ b/test/jdk/java/lang/LazyConstant/DemoImperativeTest.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* @test + * @summary Test of a demo of an imperative stable value based on a lazy constant + * @enablePreview + * @run junit DemoImperativeTest + */ + +import org.junit.jupiter.api.Test; + +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.*; + +final class DemoImperativeTest { + + interface ImperativeStableValue { + T orElse(T other); + boolean isSet(); + boolean trySet(T t); + T get(); + + static ImperativeStableValue of() { + var scratch = new AtomicReference(); + return new Impl<>(scratch, LazyConstant.of(scratch::get)); + } + + } + + + private record Impl(AtomicReference scratch, + LazyConstant underlying) implements ImperativeStableValue { + + @Override + public boolean trySet(T t) { + final boolean result = scratch.compareAndSet(null, t); + if (result) { + // Actually set the value + get(); + } + return result; + } + + @Override public T orElse(T other) { return underlying.orElse(other); } + @Override public boolean isSet() { return underlying.isInitialized(); } + @Override public T get() { return underlying.get(); } + + } + + @Test + void basic() { + var stableValue = ImperativeStableValue.of(); + assertFalse(stableValue.isSet()); + assertEquals(13, stableValue.orElse(13)); + assertTrue(stableValue.trySet(42)); + assertFalse(stableValue.trySet(13)); + assertTrue(stableValue.isSet()); + assertEquals(42, stableValue.get()); + assertEquals(42, stableValue.orElse(13)); + } + +} diff --git a/test/jdk/java/lang/LazyConstant/DemoMapTest.java b/test/jdk/java/lang/LazyConstant/DemoMapTest.java new file mode 100644 index 00000000000..25e37494cb9 --- /dev/null +++ b/test/jdk/java/lang/LazyConstant/DemoMapTest.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* @test + * @summary Test of a lazy map application + * @enablePreview + * @run junit DemoMapTest + */ + +import org.junit.jupiter.api.*; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.*; + +final class DemoMapTest { + + static class OrderController{} + + // NEW: + static final Map ORDERS + = Map.ofLazy( + Set.of("Customers", "Internal", "Testing"), + _ -> new OrderController() + ); + + public static OrderController orders() { + String groupName = Thread.currentThread().getThreadGroup().getName(); + return ORDERS.get(groupName); + } + + @Test + void orderController() throws InterruptedException { + Thread t = Thread.ofPlatform() + .group(new ThreadGroup("Customers")) + .start(() -> { + String groupName = Thread.currentThread().getThreadGroup().getName(); + OrderController orderController = ORDERS.get(groupName); + assertNotNull(orderController); + }); + t.join(); + } + + private static final Map SERVER_ERROR_PAGES = Map.ofLazy( + Set.of(500, 501, 502, 503, 504, 505, 506, 507, 508, 510, 511), + e -> { + try { + return Files.readString(Path.of("server_error_" + e + ".html")); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + }); + + private static String htmlServerErrorPage(int errorCode) { + return SERVER_ERROR_PAGES.get(errorCode); // Eligible for constant folding + } + + @Test + void page500() { + String page = htmlServerErrorPage(500); // Constant folds + assertEquals(DEFAULT_FS_MSG, page); + } + + static final String DEFAULT_FS_MSG = """ + + + + + Internal Server Error (500) + + + + + There was a general problem with the server, code 500 + + + """; + + @BeforeAll + public static void setup() throws IOException { + var file = Path.of("server_error_500.html"); + if (Files.notExists(file)) { + Files.createFile(file); + Files.writeString(file, DEFAULT_FS_MSG); + } + assertEquals(DEFAULT_FS_MSG, Files.readString(file)); + } + + @AfterAll + public static void cleanUp() throws IOException { + var file = Path.of("server_error_500.html"); + Files.deleteIfExists(file); + } + +} diff --git a/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java b/test/jdk/java/lang/LazyConstant/LazyConstantSafePublicationTest.java similarity index 73% rename from test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java rename to test/jdk/java/lang/LazyConstant/LazyConstantSafePublicationTest.java index 151d6f9c805..4d88169b155 100644 --- a/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java +++ b/test/jdk/java/lang/LazyConstant/LazyConstantSafePublicationTest.java @@ -22,10 +22,11 @@ */ /* @test - * @summary Basic tests for making sure StableValue publishes values safely + * @summary Basic tests for making sure ComputedConstant publishes values safely * @modules java.base/jdk.internal.misc + * @modules java.base/jdk.internal.lang * @enablePreview - * @run junit StableValuesSafePublicationTest + * @run junit LazyConstantSafePublicationTest */ import org.junit.jupiter.api.Test; @@ -36,30 +37,22 @@ import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.lang.LazyConstant; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.*; -final class StableValuesSafePublicationTest { +final class LazyConstantSafePublicationTest { private static final int SIZE = 100_000; private static final int THREADS = Runtime.getRuntime().availableProcessors(); - private static final StableValue[] STABLES = stables(); - - static StableValue[] stables() { - @SuppressWarnings("unchecked") - StableValue[] stables = (StableValue[]) new StableValue[SIZE]; - for (int i = 0; i < SIZE; i++) { - stables[i] = StableValue.of(); - } - return stables; - } static final class Holder { // These are non-final fields but should be seen - // fully initialized thanks to the HB properties of StableValue. + // fully initialized thanks to the HB properties of ComputedConstants. int a, b, c, d, e; Holder() { @@ -69,17 +62,21 @@ final class StableValuesSafePublicationTest { static final class Consumer implements Runnable { + final LazyConstant[] constants; final int[] observations = new int[SIZE]; - final StableValue[] stables = STABLES; int i = 0; + public Consumer(LazyConstant[] constants) { + this.constants = constants; + } + @Override public void run() { for (; i < SIZE; i++) { - StableValue s = stables[i]; + LazyConstant s = constants[i]; Holder h; - // Wait until the StableValue has a holder value - while ((h = s.orElse(null)) == null) {} + // Wait until the ComputedConstant has a holder value + while ((h = s.orElse(null)) == null) { Thread.onSpinWait();} int a = h.a; int b = h.b; int c = h.c; @@ -92,15 +89,19 @@ final class StableValuesSafePublicationTest { static final class Producer implements Runnable { - final StableValue[] stables = STABLES; + final LazyConstant[] constants; + + public Producer(LazyConstant[] constants) { + this.constants = constants; + } @Override public void run() { - StableValue s; + LazyConstant s; long deadlineNs = System.nanoTime(); for (int i = 0; i < SIZE; i++) { - s = stables[i]; - s.trySet(new Holder()); + s = constants[i]; + s.get(); deadlineNs += 1000; while (System.nanoTime() < deadlineNs) { Thread.onSpinWait(); @@ -110,9 +111,10 @@ final class StableValuesSafePublicationTest { } @Test - void main() { + void mainTest() { + final LazyConstant[] constants = constants(); List consumers = IntStream.range(0, THREADS) - .mapToObj(_ -> new Consumer()) + .mapToObj(_ -> new Consumer(constants)) .toList(); List consumersThreads = IntStream.range(0, THREADS) @@ -121,14 +123,14 @@ final class StableValuesSafePublicationTest { .start(consumers.get(i))) .toList(); - Producer producer = new Producer(); + Producer producer = new Producer(constants); Thread producerThread = Thread.ofPlatform() .name("Producer Thread") .start(producer); - join(consumers, producerThread); - join(consumers, consumersThreads.toArray(Thread[]::new)); + join(constants, consumers, producerThread); + join(constants, consumers, consumersThreads.toArray(Thread[]::new)); int[] histogram = new int[64]; for (Consumer consumer : consumers) { @@ -146,7 +148,7 @@ final class StableValuesSafePublicationTest { assertEquals(THREADS * SIZE, histogram[63]); } - static void join(List consumers, Thread... threads) { + static void join(final LazyConstant[] constants, List consumers, Thread... threads) { try { for (Thread t:threads) { long deadline = System.nanoTime() + TimeUnit.MINUTES.toNanos(1); @@ -163,11 +165,11 @@ final class StableValuesSafePublicationTest { } if (System.nanoTime() > deadline) { long nonNulls = CompletableFuture.supplyAsync(() -> - Stream.of(STABLES) + Arrays.stream(constants) .map(s -> s.orElse(null)) .filter(Objects::nonNull) .count(), Executors.newSingleThreadExecutor()).join(); - fail("Giving up! Set stables seen by a new thread: " + nonNulls); + fail("Giving up! Set lazy constants seen by a new thread: " + nonNulls); } } } @@ -176,4 +178,13 @@ final class StableValuesSafePublicationTest { } } + static LazyConstant[] constants() { + @SuppressWarnings("unchecked") + LazyConstant[] constants = (LazyConstant[]) new LazyConstant[SIZE]; + for (int i = 0; i < SIZE; i++) { + constants[i] = LazyConstant.of(Holder::new); + } + return constants; + } + } diff --git a/test/jdk/java/lang/LazyConstant/LazyConstantTest.java b/test/jdk/java/lang/LazyConstant/LazyConstantTest.java new file mode 100644 index 00000000000..af194cbcc01 --- /dev/null +++ b/test/jdk/java/lang/LazyConstant/LazyConstantTest.java @@ -0,0 +1,236 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* @test + * @summary Basic tests for the LazyConstant implementation + * @enablePreview + * @modules java.base/jdk.internal.lang + * @run junit/othervm --add-opens java.base/jdk.internal.lang=ALL-UNNAMED LazyConstantTest + */ + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; +import java.lang.LazyConstant; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; + +final class LazyConstantTest { + + private static final int VALUE = 42; + private static final Supplier SUPPLIER = () -> VALUE; + + @Test + void factoryInvariants() { + assertThrows(NullPointerException.class, () -> LazyConstant.of(null)); + } + + @ParameterizedTest + @MethodSource("factories") + void basic(Function, LazyConstant> factory) { + LazyConstantTestUtil.CountingSupplier cs = new LazyConstantTestUtil.CountingSupplier<>(SUPPLIER); + var lazy = factory.apply(cs); + assertFalse(lazy.isInitialized()); + assertEquals(SUPPLIER.get(), lazy.get()); + assertEquals(1, cs.cnt()); + assertEquals(SUPPLIER.get(), lazy.get()); + assertEquals(1, cs.cnt()); + assertTrue(lazy.toString().contains(Integer.toString(SUPPLIER.get()))); + } + + @ParameterizedTest + @MethodSource("factories") + void exception(Function, LazyConstant> factory) { + LazyConstantTestUtil.CountingSupplier cs = new LazyConstantTestUtil.CountingSupplier<>(() -> { + throw new UnsupportedOperationException(); + }); + var lazy = factory.apply(cs); + assertThrows(UnsupportedOperationException.class, lazy::get); + assertEquals(1, cs.cnt()); + assertThrows(UnsupportedOperationException.class, lazy::get); + assertEquals(2, cs.cnt()); + assertTrue(lazy.toString().contains("computing function")); + } + + @ParameterizedTest + @MethodSource("lazyConstants") + void orElse(LazyConstant constant) { + assertNull(constant.orElse(null)); + constant.get(); + assertEquals(VALUE, constant.orElse(null)); + } + + @ParameterizedTest + @MethodSource("lazyConstants") + void get(LazyConstant constant) { + assertEquals(VALUE, constant.get()); + } + + @ParameterizedTest + @MethodSource("lazyConstants") + void isInitialized(LazyConstant constant) { + assertFalse(constant.isInitialized()); + constant.get(); + assertTrue(constant.isInitialized()); + } + + @ParameterizedTest + @MethodSource("lazyConstants") + void testHashCode(LazyConstant constant) { + assertEquals(System.identityHashCode(constant), constant.hashCode()); + } + + @ParameterizedTest + @MethodSource("lazyConstants") + void testEquals(LazyConstant c0) { + assertNotEquals(null, c0); + LazyConstant different = LazyConstant.of(SUPPLIER); + assertNotEquals(different, c0); + assertNotEquals(c0, different); + assertNotEquals("a", c0); + } + + @ParameterizedTest + @MethodSource("lazyConstants") + void testLazyConstantAsComputingFunction(LazyConstant constant) { + LazyConstant c1 = LazyConstant.of(constant); + assertSame(constant, c1); + } + + @Test + void toStringTest() { + Supplier supplier = () -> "str"; + LazyConstant lazy = LazyConstant.of(supplier); + var expectedSubstring = "computing function=" + supplier; + assertTrue(lazy.toString().contains(expectedSubstring)); + lazy.get(); + assertTrue(lazy.toString().contains("str")); + } + + @ParameterizedTest + @MethodSource("lazyConstants") + void toStringUnset(LazyConstant constant) { + String unInitializedToString = constant.toString(); + int suffixEnd = unInitializedToString.indexOf("["); + String suffix = unInitializedToString.substring(0, suffixEnd); + String expectedUninitialized = suffix+"[computing function="; + assertTrue(unInitializedToString.startsWith(expectedUninitialized)); + constant.get(); + String expectedInitialized = suffix + "[" + VALUE + "]"; + assertEquals(expectedInitialized, constant.toString()); + } + + @Test + void toStringCircular() { + AtomicReference> ref = new AtomicReference<>(); + LazyConstant> constant = LazyConstant.of(ref::get); + ref.set(constant); + constant.get(); + String toString = assertDoesNotThrow(constant::toString); + assertTrue(constant.toString().contains("(this LazyConstant)"), toString); + } + + @Test + void recursiveCall() { + AtomicReference> ref = new AtomicReference<>(); + LazyConstant constant = LazyConstant.of(() -> ref.get().get()); + LazyConstant constant1 = LazyConstant.of(constant); + ref.set(constant1); + assertThrows(IllegalStateException.class, constant::get); + } + + @ParameterizedTest + @MethodSource("factories") + void underlying(Function, LazyConstant> factory) { + LazyConstantTestUtil.CountingSupplier cs = new LazyConstantTestUtil.CountingSupplier<>(SUPPLIER); + var f1 = factory.apply(cs); + + Supplier underlyingBefore = LazyConstantTestUtil.computingFunction(f1); + assertSame(cs, underlyingBefore); + int v = f1.get(); + Supplier underlyingAfter = LazyConstantTestUtil.computingFunction(f1); + assertNull(underlyingAfter); + } + + @ParameterizedTest + @MethodSource("factories") + void functionHolderException(Function, LazyConstant> factory) { + LazyConstantTestUtil.CountingSupplier cs = new LazyConstantTestUtil.CountingSupplier<>(() -> { + throw new UnsupportedOperationException(); + }); + var f1 = factory.apply(cs); + + Supplier underlyingBefore = LazyConstantTestUtil.computingFunction(f1); + assertSame(cs, underlyingBefore); + try { + int v = f1.get(); + } catch (UnsupportedOperationException _) { + // Expected + } + Supplier underlyingAfter = LazyConstantTestUtil.computingFunction(f1); + assertSame(cs, underlyingAfter); + } + + private static Stream> lazyConstants() { + return factories() + .map(f -> f.apply(() -> VALUE)); + } + + private static Stream, LazyConstant>> factories() { + return Stream.of( + supplier("ComputedConstant.of()", LazyConstant::of) + ); + } + + private static Function, LazyConstant> supplier(String name, + Function, LazyConstant> underlying) { + return new Function, LazyConstant>() { + @Override + public LazyConstant apply(Supplier supplier) { + return underlying.apply(supplier); + } + + @Override + public String toString() { + return name; + } + }; + } + + record Lazy(LazyConstant underlying) implements Supplier { + @Override + public T get() { return underlying.get(); } + + static Lazy of(Supplier computingFunction) { + return new Lazy<>(LazyConstant.of(computingFunction)); + } + } + +} diff --git a/test/jdk/java/lang/StableValue/StableTestUtil.java b/test/jdk/java/lang/LazyConstant/LazyConstantTestUtil.java similarity index 64% rename from test/jdk/java/lang/StableValue/StableTestUtil.java rename to test/jdk/java/lang/LazyConstant/LazyConstantTestUtil.java index f71915c28ee..502f46b726d 100644 --- a/test/jdk/java/lang/StableValue/StableTestUtil.java +++ b/test/jdk/java/lang/LazyConstant/LazyConstantTestUtil.java @@ -21,15 +21,18 @@ * questions. */ +import java.lang.reflect.Field; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.IntFunction; import java.util.function.Supplier; -final class StableTestUtil { +final class LazyConstantTestUtil { - private StableTestUtil() {} + private LazyConstantTestUtil() { } + + public static final String UNINITIALIZED_TAG = ".uninitialized"; public static final class CountingSupplier extends AbstractCounting> @@ -117,4 +120,55 @@ final class StableTestUtil { } } + static Object functionHolder(Object o) { + try { + final Field field = field(o.getClass(), "functionHolder"); + field.setAccessible(true); + return field.get(o); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + static Object functionHolderFunction(Object o) { + try { + final Field field = field(o.getClass(), "function"); + field.setAccessible(true); + return field.get(o); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + static int functionHolderCounter(Object o) { + try { + final Field field = field(o.getClass(), "counter"); + field.setAccessible(true); + return (int)field.get(o); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + static Supplier computingFunction(LazyConstant o) { + try { + final Field field = field(o.getClass(), "computingFunction"); + field.setAccessible(true); + return (Supplier) field.get(o); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + static Field field(Class clazz, String name) { + if (clazz.equals(Object.class)) { + throw new RuntimeException("No " + name); + } + try { + return clazz.getDeclaredField(name); + } catch (NoSuchFieldException e) { + return field(clazz.getSuperclass(), name); + } + } + } diff --git a/test/jdk/java/lang/StableValue/StableListTest.java b/test/jdk/java/lang/LazyConstant/LazyListTest.java similarity index 60% rename from test/jdk/java/lang/StableValue/StableListTest.java rename to test/jdk/java/lang/LazyConstant/LazyListTest.java index 2abe305b0e7..046f9107b17 100644 --- a/test/jdk/java/lang/StableValue/StableListTest.java +++ b/test/jdk/java/lang/LazyConstant/LazyListTest.java @@ -22,23 +22,18 @@ */ /* @test - * @summary Basic tests for StableList methods - * @modules java.base/jdk.internal.lang.stable + * @summary Basic tests for lazy list methods * @enablePreview - * @run junit StableListTest + * @run junit/othervm --add-opens java.base/java.util=ALL-UNNAMED LazyListTest */ -import jdk.internal.lang.stable.StableUtil; -import jdk.internal.lang.stable.StableValueImpl; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import java.io.Serializable; import java.util.Comparator; -import java.util.IdentityHashMap; import java.util.List; -import java.util.Map; import java.util.NoSuchElementException; import java.util.RandomAccess; import java.util.Set; @@ -54,7 +49,7 @@ import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.*; -final class StableListTest { +final class LazyListTest { private static final int ZERO = 0; private static final int INDEX = 7; @@ -63,26 +58,26 @@ final class StableListTest { @Test void factoryInvariants() { - assertThrows(NullPointerException.class, () -> StableValue.list(SIZE, null)); - assertThrows(IllegalArgumentException.class, () -> StableValue.list(-1, IDENTITY)); + assertThrows(NullPointerException.class, () -> List.ofLazy(SIZE, null)); + assertThrows(IllegalArgumentException.class, () -> List.ofLazy(-1, IDENTITY)); } @Test void isEmpty() { - assertFalse(newList().isEmpty()); - assertTrue(newEmptyList().isEmpty()); + assertFalse(newLazyList().isEmpty()); + assertTrue(newEmptyLazyList().isEmpty()); } @Test void size() { - assertEquals(SIZE, newList().size()); - assertEquals(ZERO, newEmptyList().size()); + assertEquals(SIZE, newLazyList().size()); + assertEquals(ZERO, newEmptyLazyList().size()); } @Test void get() { - StableTestUtil.CountingIntFunction cif = new StableTestUtil.CountingIntFunction<>(IDENTITY); - var lazy = StableValue.list(SIZE, cif); + LazyConstantTestUtil.CountingIntFunction cif = new LazyConstantTestUtil.CountingIntFunction(IDENTITY); + var lazy = List.ofLazy(SIZE, cif); for (int i = 0; i < SIZE; i++) { assertEquals(i, lazy.get(i)); assertEquals(i + 1, cif.cnt()); @@ -93,10 +88,10 @@ final class StableListTest { @Test void getException() { - StableTestUtil.CountingIntFunction cif = new StableTestUtil.CountingIntFunction<>(_ -> { + LazyConstantTestUtil.CountingIntFunction cif = new LazyConstantTestUtil.CountingIntFunction(_ -> { throw new UnsupportedOperationException(); }); - var lazy = StableValue.list(SIZE, cif); + var lazy = List.ofLazy(SIZE, cif); assertThrows(UnsupportedOperationException.class, () -> lazy.get(INDEX)); assertEquals(1, cif.cnt()); assertThrows(UnsupportedOperationException.class, () -> lazy.get(INDEX)); @@ -105,8 +100,8 @@ final class StableListTest { @Test void toArray() { - assertArrayEquals(new Object[ZERO], newEmptyList().toArray()); - assertArrayEquals(newRegularList().toArray(), newList().toArray()); + assertArrayEquals(new Object[ZERO], newEmptyLazyList().toArray()); + assertArrayEquals(newRegularList().toArray(), newLazyList().toArray()); } @Test @@ -115,8 +110,8 @@ final class StableListTest { for (int i = 0; i < SIZE; i++) { actual[INDEX] = 100 + i; } - var list = StableValue.list(INDEX, IDENTITY); - assertSame(actual, list.toArray(actual)); + var lazy = List.ofLazy(INDEX, IDENTITY); + assertSame(actual, lazy.toArray(actual)); Integer[] expected = IntStream.range(0, SIZE) .mapToObj(i -> i < INDEX ? i : null) .toArray(Integer[]::new); @@ -126,7 +121,7 @@ final class StableListTest { @Test void toArrayWithArraySmaller() { Integer[] arr = new Integer[INDEX]; - Integer[] actual = newList().toArray(arr); + Integer[] actual = newLazyList().toArray(arr); assertNotSame(arr, actual); Integer[] expected = newRegularList().toArray(new Integer[0]); assertArrayEquals(expected, actual); @@ -135,13 +130,13 @@ final class StableListTest { @Test void toArrayWithGenerator() { Integer[] expected = newRegularList().toArray(Integer[]::new); - Integer[] actual = newList().toArray(Integer[]::new); + Integer[] actual = newLazyList().toArray(Integer[]::new); assertArrayEquals(expected, actual); } @Test void firstIndex() { - var lazy = newList(); + var lazy = newLazyList(); for (int i = INDEX; i < SIZE; i++) { assertEquals(i, lazy.indexOf(i)); } @@ -150,7 +145,7 @@ final class StableListTest { @Test void lastIndex() { - var lazy = newList(); + var lazy = newLazyList(); for (int i = INDEX; i < SIZE; i++) { assertEquals(i, lazy.lastIndexOf(i)); } @@ -159,42 +154,28 @@ final class StableListTest { @Test void toStringTest() { - assertEquals("[]", newEmptyList().toString()); - var list = StableValue.list(2, IDENTITY); - assertEquals("[.unset, .unset]", list.toString()); - list.get(0); - assertEquals("[0, .unset]", list.toString()); - list.get(1); - assertEquals("[0, 1]", list.toString()); + assertEquals("[]", newEmptyLazyList().toString()); + assertEquals("[0, 1]", List.ofLazy(2, IDENTITY).toString()); } @Test void hashCodeTest() { - assertEquals(List.of().hashCode(), newEmptyList().hashCode()); - assertEquals(newRegularList().hashCode(), newList().hashCode()); + assertEquals(List.of().hashCode(), newEmptyLazyList().hashCode()); + assertEquals(newRegularList().hashCode(), newLazyList().hashCode()); } @Test void equalsTest() { - assertTrue(newEmptyList().equals(List.of())); - assertTrue(List.of().equals(newEmptyList())); - assertTrue(newList().equals(newRegularList())); - assertTrue(newRegularList().equals(newList())); - assertFalse(newList().equals("A")); - } - - @Test - void equalsPartialEvaluationTest() { - var list = StableValue.list(2, IDENTITY); - assertFalse(list.equals(List.of(0))); - assertEquals("[0, .unset]", list.toString()); - assertTrue(list.equals(List.of(0, 1))); - assertEquals("[0, 1]", list.toString()); + assertTrue(newEmptyLazyList().equals(List.of())); + assertTrue(List.of().equals(newEmptyLazyList())); + assertTrue(newLazyList().equals(newRegularList())); + assertTrue(newRegularList().equals(newLazyList())); + assertFalse(newLazyList().equals("A")); } @Test void iteratorTotal() { - var iterator = newList().iterator(); + var iterator = newLazyList().iterator(); for (int i = 0; i < SIZE; i++) { assertTrue(iterator.hasNext()); assertTrue(iterator.hasNext()); @@ -209,7 +190,7 @@ final class StableListTest { @Test void iteratorPartial() { - var iterator = newList().iterator(); + var iterator = newLazyList().iterator(); for (int i = 0; i < INDEX; i++) { assertTrue(iterator.hasNext()); assertTrue(iterator.hasNext()); @@ -225,7 +206,7 @@ final class StableListTest { @Test void subList() { - var lazy = newList(); + var lazy = newLazyList(); var lazySubList = lazy.subList(1, SIZE); assertInstanceOf(RandomAccess.class, lazySubList); var regularList = newRegularList(); @@ -235,36 +216,33 @@ final class StableListTest { @Test void subList2() { - var lazy = newList(); + var lazy = newLazyList(); var lazySubList = lazy.subList(1, SIZE); lazySubList.get(0); - var eq = newList(); + var eq = newLazyList(); eq.get(1); assertEquals(eq.toString(), lazy.toString()); } - void assertUnevaluated(List subList) { - assertEquals(asString(".unset", subList), subList.toString()); - } - @Test void reversed() { - var reversed = newList().reversed(); - assertInstanceOf(RandomAccess.class, reversed); - assertEquals(SIZE - 1, reversed.getFirst()); - assertEquals(0, reversed.getLast()); + var lazy = newLazyList(); + var reversedLazy = lazy.reversed(); + assertInstanceOf(RandomAccess.class, reversedLazy); + assertEquals(SIZE - 1, reversedLazy.getFirst()); + assertEquals(0, reversedLazy.getLast()); - var reversed2 = reversed.reversed(); - assertInstanceOf(RandomAccess.class, reversed2); - assertEquals(0, reversed2.getFirst()); - assertEquals(SIZE - 1, reversed2.getLast()); + var reversed2Lazy = reversedLazy.reversed(); + assertInstanceOf(RandomAccess.class, reversed2Lazy); + assertEquals(0, reversed2Lazy.getFirst()); + assertEquals(SIZE - 1, reversed2Lazy.getLast()); // Make sure we get back a non-reversed implementation - assertEquals("java.util.ImmutableCollections$StableList", reversed2.getClass().getName()); + assertEquals(lazy.getClass().getName(), reversed2Lazy.getClass().getName()); } @Test void sublistReversedToString() { - var actual = StableValue.list(4, IDENTITY); + var actual = List.ofLazy(4, IDENTITY); var expected = List.of(0, 1, 2, 3); for (UnaryOperation op : List.of( new UnaryOperation("subList", l -> l.subList(1, 3)), @@ -276,60 +254,17 @@ final class StableListTest { actual.getLast(); var actualToString = actual.toString(); - var expectedToString = expected.toString().replace("2", ".unset"); + var expectedToString = expected.toString(); assertEquals(expectedToString, actualToString); } - // This test makes sure successive view operations retains the property - // of being a Stable view. - @Test - void viewsStable() { - viewOperations().forEach(op0 -> { - viewOperations().forEach( op1 -> { - viewOperations().forEach(op2 -> { - var list = newList(); - var view1 = op0.apply(list); - var view2 = op1.apply(view1); - var view3 = op2.apply(view2); - var className3 = className(view3); - var transitions = className(list) + ", " + - op0 + " -> " + className(view1) + ", " + - op1 + " -> " + className(view2) + ", " + - op2 + " -> " + className3; - assertTrue(className3.contains("Stable"), transitions); - assertUnevaluated(list); - assertUnevaluated(view1); - assertUnevaluated(view2); - assertUnevaluated(view3); - }); - }); - }); - } - @Test void recursiveCall() { AtomicReference> ref = new AtomicReference<>(); - var lazy = StableValue.list(SIZE, i -> ref.get().apply(i)); + var lazy = List.ofLazy(SIZE, i -> ref.get().apply(i)); ref.set(lazy::get); var x = assertThrows(IllegalStateException.class, () -> lazy.get(INDEX)); - assertEquals("Recursive initialization of a stable value is illegal", x.getMessage()); - } - - @Test - void indexOfNullInViews() { - final int size = 5; - final int middle = 2; - viewOperations().forEach(op0 -> { - viewOperations().forEach( op1 -> { - viewOperations().forEach(op2 -> { - var list = StableValue.list(size, x -> x == middle ? null : x);; - var view1 = op0.apply(list); - var view2 = op1.apply(view1); - var view3 = op2.apply(view2); - assertEquals(middle, view3.indexOf(null)); - }); - }); - }); + assertEquals("Recursive initialization of a lazy collection is illegal", x.getMessage()); } // Immutability @@ -355,7 +290,7 @@ final class StableListTest { } static void assertThrowsForOperation(Class expectedType, Operation operation) { - var lazy = newList(); + var lazy = newLazyList(); assertThrows(expectedType, () -> operation.accept(lazy)); var sub = lazy.subList(1, SIZE / 2); assertThrows(expectedType, () -> operation.accept(sub)); @@ -367,14 +302,14 @@ final class StableListTest { @Test void serializable() { - serializable(newList()); - serializable(newEmptyList()); + serializable(newLazyList()); + serializable(newEmptyLazyList()); } void serializable(List list) { assertFalse(list instanceof Serializable); if (list.size()>INDEX) { - assertFalse(newList().subList(1, INDEX) instanceof Serializable); + assertFalse(newLazyList().subList(1, INDEX) instanceof Serializable); } assertFalse(list.iterator() instanceof Serializable); assertFalse(list.reversed() instanceof Serializable); @@ -383,54 +318,25 @@ final class StableListTest { @Test void randomAccess() { - assertInstanceOf(RandomAccess.class, newList()); - assertInstanceOf(RandomAccess.class, newEmptyList()); - assertInstanceOf(RandomAccess.class, newList().subList(1, INDEX)); + assertInstanceOf(RandomAccess.class, newLazyList()); + assertInstanceOf(RandomAccess.class, newEmptyLazyList()); + assertInstanceOf(RandomAccess.class, newLazyList().subList(1, INDEX)); } @Test - void distinct() { - StableValueImpl[] array = StableUtil.array(SIZE); - assertEquals(SIZE, array.length); - // Check, every StableValue is distinct - Map, Boolean> idMap = new IdentityHashMap<>(); - for (var e: array) { - idMap.put(e, true); + void functionHolder() { + LazyConstantTestUtil.CountingIntFunction cif = new LazyConstantTestUtil.CountingIntFunction<>(IDENTITY); + List f1 = List.ofLazy(SIZE, cif); + + Object holder = LazyConstantTestUtil.functionHolder(f1); + for (int i = 0; i < SIZE; i++) { + assertEquals(SIZE - i, LazyConstantTestUtil.functionHolderCounter(holder)); + assertSame(cif, LazyConstantTestUtil.functionHolderFunction(holder)); + int v = f1.get(i); + int v2 = f1.get(i); } - assertEquals(SIZE, idMap.size()); - } - - @Test - void childObjectOpsLazy() { - viewOperations().forEach(op0 -> { - viewOperations().forEach(op1 -> { - viewOperations().forEach(op2 -> { - childOperations().forEach(co -> { - var list = newList(); - var view1 = op0.apply(list); - var view2 = op1.apply(view1); - var view3 = op2.apply(view2); - var child = co.apply(view3); - var childClassName = className(child); - var transitions = className(list) + ", " + - op0 + " -> " + className(view1) + ", " + - op1 + " -> " + className(view2) + ", " + - op2 + " -> " + className(view3) + ", " + - co + " -> " + childClassName; - - // None of these operations should trigger evaluation - var childToString = child.toString(); - int childHashCode = child.hashCode(); - boolean childEqualToNewObj = child.equals(new Object()); - - assertUnevaluated(list); - assertUnevaluated(view1); - assertUnevaluated(view2); - assertUnevaluated(view3); - }); - }); - }); - }); + assertEquals(0, LazyConstantTestUtil.functionHolderCounter(holder)); + assertNull(LazyConstantTestUtil.functionHolderFunction(holder)); } // Support constructs @@ -516,25 +422,15 @@ final class StableListTest { ); } - static List newList() { - return StableValue.list(SIZE, IDENTITY); + static List newLazyList() { + return List.ofLazy(SIZE, IDENTITY); } - static List newEmptyList() { - return StableValue.list(ZERO, IDENTITY); + static List newEmptyLazyList() { + return List.ofLazy(ZERO, IDENTITY); } static List newRegularList() { return IntStream.range(0, SIZE).boxed().toList(); } - - static String asString(String first, List list) { - return "[" + first + ", " + Stream.generate(() -> ".unset") - .limit(list.size() - 1) - .collect(Collectors.joining(", ")) + "]"; - } - - static String className(Object o) { - return o.getClass().getName(); - } } diff --git a/test/jdk/java/lang/LazyConstant/LazyMapTest.java b/test/jdk/java/lang/LazyConstant/LazyMapTest.java new file mode 100644 index 00000000000..766313c9173 --- /dev/null +++ b/test/jdk/java/lang/LazyConstant/LazyMapTest.java @@ -0,0 +1,573 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* @test + * @summary Basic tests for lazy map methods + * @enablePreview + * @run junit/othervm --add-opens java.base/java.util=ALL-UNNAMED LazyMapTest + */ + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.io.Serializable; +import java.util.AbstractMap; +import java.util.Arrays; +import java.util.Comparator; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.mapping; +import static org.junit.jupiter.api.Assertions.*; + +final class LazyMapTest { + + enum Value { + // Zero is here so that we have enums with ordinals before the first one + // actually used in input sets (i.e. ZERO is not in the input set) + ZERO(0), + ILLEGAL_BEFORE(-1), + // Valid values + THIRTEEN(13) { + @Override + public String toString() { + // getEnumConstants will be `null` for this enum as it is overridden + return super.toString()+" (Overridden)"; + } + }, + ILLEGAL_BETWEEN(-2), + FORTY_TWO(42), + // Illegal values (not in the input set) + ILLEGAL_AFTER(-3); + + final int intValue; + + Value(int intValue) { + this.intValue = intValue; + } + + int asInt() { + return intValue; + } + + } + + private static final Function MAPPER = Value::asInt; + + private static final Value KEY = Value.FORTY_TWO; + private static final Integer VALUE = MAPPER.apply(KEY); + + @ParameterizedTest + @MethodSource("allSets") + void factoryInvariants(Set set) { + assertThrows(NullPointerException.class, () -> Map.ofLazy(set, null), set.getClass().getSimpleName()); + assertThrows(NullPointerException.class, () -> Map.ofLazy(null, MAPPER)); + Set setWithNull = new HashSet<>(); + setWithNull.add(KEY); + setWithNull.add(null); + assertThrows(NullPointerException.class, () -> Map.ofLazy(setWithNull, MAPPER)); + } + + @ParameterizedTest + @MethodSource("emptySets") + void empty(Set set) { + var lazy = newLazyMap(set); + assertTrue(lazy.isEmpty()); + assertEquals("{}", lazy.toString()); + assertThrows(NullPointerException.class, () -> lazy.get(null)); + assertNotEquals(null, lazy); + } + + @ParameterizedTest + @MethodSource("allSets") + void size(Set set) { + assertEquals(newRegularMap(set).size(), newLazyMap(set).size()); + } + + @ParameterizedTest + @MethodSource("nonEmptySets") + void get(Set set) { + LazyConstantTestUtil.CountingFunction cf = new LazyConstantTestUtil.CountingFunction<>(MAPPER); + var lazy = Map.ofLazy(set, cf); + int cnt = 1; + for (Value v : set) { + assertEquals(MAPPER.apply(v), lazy.get(v)); + assertEquals(cnt, cf.cnt()); + assertEquals(MAPPER.apply(v), lazy.get(v)); + assertEquals(cnt++, cf.cnt()); + } + assertNull(lazy.get(Value.ILLEGAL_BETWEEN)); + } + + @ParameterizedTest + @MethodSource("nonEmptySets") + void exception(Set set) { + LazyConstantTestUtil.CountingFunction cif = new LazyConstantTestUtil.CountingFunction<>(_ -> { + throw new UnsupportedOperationException(); + }); + var lazy = Map.ofLazy(set, cif); + assertThrows(UnsupportedOperationException.class, () -> lazy.get(KEY)); + assertEquals(1, cif.cnt()); + assertThrows(UnsupportedOperationException.class, () -> lazy.get(KEY)); + assertEquals(2, cif.cnt()); + assertThrows(UnsupportedOperationException.class, lazy::toString); + assertEquals(3, cif.cnt()); + } + + @ParameterizedTest + @MethodSource("allSets") + void containsKey(Set set) { + var lazy = newLazyMap(set); + for (Value v : set) { + assertTrue(lazy.containsKey(v)); + } + assertFalse(lazy.containsKey(Value.ILLEGAL_BETWEEN)); + } + + @ParameterizedTest + @MethodSource("allSets") + void containsValue(Set set) { + var lazy = newLazyMap(set); + for (Value v : set) { + assertTrue(lazy.containsValue(MAPPER.apply(v))); + } + assertFalse(lazy.containsValue(MAPPER.apply(Value.ILLEGAL_BETWEEN))); + } + + @ParameterizedTest + @MethodSource("allSets") + void forEach(Set set) { + var lazy = newLazyMap(set); + var ref = newRegularMap(set); + Set> expected = ref.entrySet(); + Set> actual = new HashSet<>(); + lazy.forEach((k, v) -> actual.add(new AbstractMap.SimpleImmutableEntry<>(k , v))); + assertEquals(expected, actual); + } + + @ParameterizedTest + @MethodSource("emptySets") + void toStringTestEmpty(Set set) { + var lazy = newLazyMap(set); + assertEquals("{}", lazy.toString()); + } + + @ParameterizedTest + @MethodSource("nonEmptySets") + void toStringTest(Set set) { + var lazy = newLazyMap(set); + var toString = lazy.toString(); + assertTrue(toString.startsWith("{")); + assertTrue(toString.endsWith("}")); + + // Key order is unspecified + for (Value key : set) { + toString = lazy.toString(); + assertTrue(toString.contains(key + "=" + MAPPER.apply(key)), toString); + } + + // One between the values + assertEquals(set.size() - 1, toString.chars().filter(ch -> ch == ',').count()); + } + + @ParameterizedTest + @MethodSource("allSets") + void hashCodeTest(Set set) { + var lazy = newLazyMap(set); + var regular = newRegularMap(set); + assertEquals(regular.hashCode(), lazy.hashCode()); + } + + @ParameterizedTest + @MethodSource("allSets") + void equality(Set set) { + var lazy = newLazyMap(set); + var regular = newRegularMap(set); + assertEquals(regular, lazy); + assertEquals(lazy, regular); + assertNotEquals("A", lazy); + } + + @ParameterizedTest + @MethodSource("nonEmptySets") + void circular(Set set) { + final AtomicReference> ref = new AtomicReference<>(); + Map> lazy = Map.ofLazy(set, _ -> ref.get()); + ref.set(lazy); + lazy.get(KEY); + var toString = lazy.toString(); + assertTrue(toString.contains("FORTY_TWO=(this Map)"), toString); + assertDoesNotThrow((() -> lazy.equals(lazy))); + } + + @ParameterizedTest + @MethodSource("nonEmptySets") + void recursiveCall(Set set) { + final AtomicReference> ref = new AtomicReference<>(); + @SuppressWarnings("unchecked") + Map> lazy = Map.ofLazy(set, k -> (Map) ref.get().get(k)); + ref.set(lazy); + var x = assertThrows(IllegalStateException.class, () -> lazy.get(KEY)); + assertEquals("Recursive initialization of a lazy collection is illegal", x.getMessage()); + } + + @ParameterizedTest + @MethodSource("nonEmptySets") + void entrySet(Set set) { + var lazy = newLazyMap(set).entrySet(); + var regular = newRegularMap(set).entrySet(); + assertTrue(regular.equals(lazy)); + assertTrue(lazy.equals(regular)); + assertTrue(regular.equals(lazy)); + } + + @ParameterizedTest + @MethodSource("nonEmptySets") + void entrySetToString(Set set) { + var lazy = newLazyMap(set); + var lazyEntrySet = lazy.entrySet(); + var toString = lazyEntrySet.toString(); + for (var key : set) { + assertTrue(toString.contains(key + "=" + MAPPER.apply(key))); + } + assertTrue(toString.startsWith("[")); + assertTrue(toString.endsWith("]")); + } + + @ParameterizedTest + @MethodSource("nonEmptySets") + void values(Set set) { + var lazy = newLazyMap(set); + var lazyValues = lazy.values(); + // Look at one of the elements + var val = lazyValues.stream().iterator().next(); + assertEquals(lazy.size() - 1, functionCounter(lazy)); + + // Mod ops + assertThrows(UnsupportedOperationException.class, () -> lazyValues.remove(val)); + assertThrows(UnsupportedOperationException.class, () -> lazyValues.add(val)); + assertThrows(UnsupportedOperationException.class, lazyValues::clear); + assertThrows(UnsupportedOperationException.class, () -> lazyValues.addAll(Set.of(VALUE))); + assertThrows(UnsupportedOperationException.class, () -> lazyValues.removeIf(i -> true)); + assertThrows(UnsupportedOperationException.class, () -> lazyValues.retainAll(Set.of(VALUE))); + } + + @ParameterizedTest + @MethodSource("allSets") + void valuesToString(Set set) { + var lazy = newLazyMap(set); + var lazyValues = lazy.values(); + var toString = lazyValues.toString(); + + // Key order is unspecified + for (Value key : set) { + assertTrue(toString.contains(MAPPER.apply(key).toString()), toString); + } + assertTrue(toString.startsWith("["), toString); + assertTrue(toString.endsWith("]"), toString); + } + + @ParameterizedTest + @MethodSource("allSets") + void iteratorNext(Set set) { + Set encountered = new HashSet<>(); + var iterator = newLazyMap(set).entrySet().iterator(); + while (iterator.hasNext()) { + var entry = iterator.next(); + assertEquals(MAPPER.apply(entry.getKey()), entry.getValue()); + encountered.add(entry.getKey()); + } + assertEquals(set, encountered); + } + + @ParameterizedTest + @MethodSource("nonEmptySets") + void iteratorForEachRemaining(Set set) { + Set encountered = new HashSet<>(); + var iterator = newLazyMap(set).entrySet().iterator(); + var entry = iterator.next(); + assertEquals(MAPPER.apply(entry.getKey()), entry.getValue()); + encountered.add(entry.getKey()); + iterator.forEachRemaining(e -> { + assertEquals(MAPPER.apply(e.getKey()), e.getValue()); + encountered.add(e.getKey()); + }); + assertEquals(set, encountered); + } + + @ParameterizedTest + @MethodSource("nonEmptySets") + void lazyEntry(Set set) { + var lazy = newLazyMap(set); + var entry = lazy.entrySet().stream() + .filter(e -> e.getKey().equals(KEY)) + .findAny() + .orElseThrow(); + + assertEquals(lazy.size(), functionCounter(lazy)); + var otherDifferent = Map.entry(Value.ZERO, -1); + assertNotEquals(entry, otherDifferent); + assertEquals(lazy.size(), functionCounter(lazy)); + var otherEqual = Map.entry(entry.getKey(), entry.getValue()); + assertEquals(entry, otherEqual); + assertEquals(lazy.size() - 1, functionCounter(lazy)); + assertEquals(entry.hashCode(), otherEqual.hashCode()); + } + + @ParameterizedTest + @MethodSource("nonEmptySets") + void lazyForEachEntry(Set set) { + var lazy = newLazyMap(set); + // Only touch the key. + lazy.entrySet().iterator().forEachRemaining(Map.Entry::getKey); + assertEquals(lazy.size(), functionCounter(lazy)); // No evaluation + // Only touch the value. + lazy.entrySet().iterator().forEachRemaining(Map.Entry::getValue); + assertEquals(0, functionCounter(lazy)); + } + + // Immutability + @ParameterizedTest + @MethodSource("unsupportedOperations") + void unsupported(Operation operation) { + assertThrowsForOperation(UnsupportedOperationException.class, operation); + } + + // Method parameter invariant checking + + @ParameterizedTest + @MethodSource("nullAverseOperations") + void nullAverse(Operation operation) { + assertThrowsForOperation(NullPointerException.class, operation); + } + + static void assertThrowsForOperation(Class expectedType, Operation operation) { + for (Set set : allSets().toList()) { + var lazy = newLazyMap(set); + assertThrows(expectedType, () -> operation.accept(lazy), set.getClass().getSimpleName() + " " + operation); + } + } + + // Implementing interfaces + + @ParameterizedTest + @MethodSource("allSets") + void serializable(Set set) { + var lazy = newLazyMap(set); + assertFalse(lazy instanceof Serializable); + assertFalse(lazy.entrySet() instanceof Serializable); + assertFalse(lazy.values() instanceof Serializable); + } + + @Test + void nullResult() { + var lazy = Map.ofLazy(Set.of(0), _ -> null); + assertThrows(NullPointerException.class, () -> lazy.getOrDefault(0, 1));; + assertTrue(lazy.containsKey(0)); + } + + @ParameterizedTest + @MethodSource("allSets") + void functionHolder(Set set) { + LazyConstantTestUtil.CountingFunction cif = new LazyConstantTestUtil.CountingFunction<>(MAPPER); + Map lazy = Map.ofLazy(set, cif); + + Object holder = LazyConstantTestUtil.functionHolder(lazy); + + int i = 0; + for (Value key : set) { + assertEquals(set.size() - i, LazyConstantTestUtil.functionHolderCounter(holder)); + assertSame(cif, LazyConstantTestUtil.functionHolderFunction(holder)); + int v = lazy.get(key); + int v2 = lazy.get(key); + i++; + } + assertEquals(0, LazyConstantTestUtil.functionHolderCounter(holder)); + assertNull(LazyConstantTestUtil.functionHolderFunction(holder)); + } + + @ParameterizedTest + @MethodSource("allSets") + void functionHolderViaEntrySet(Set set) { + LazyConstantTestUtil.CountingFunction cif = new LazyConstantTestUtil.CountingFunction<>(MAPPER); + Map lazy = Map.ofLazy(set, cif); + + Object holder = LazyConstantTestUtil.functionHolder(lazy); + + int i = 0; + for (Map.Entry e : lazy.entrySet()) { + assertEquals(set.size() - i, LazyConstantTestUtil.functionHolderCounter(holder)); + assertSame(cif, LazyConstantTestUtil.functionHolderFunction(holder)); + int v = e.getValue(); + int v2 = e.getValue(); + i++; + } + assertEquals(0, LazyConstantTestUtil.functionHolderCounter(holder)); + assertNull(LazyConstantTestUtil.functionHolderFunction(holder)); + } + + @ParameterizedTest + @MethodSource("allSets") + void underlyingRefViaEntrySetForEach(Set set) { + LazyConstantTestUtil.CountingFunction cif = new LazyConstantTestUtil.CountingFunction<>(MAPPER); + Map lazy = Map.ofLazy(set, cif); + + Object holder = LazyConstantTestUtil.functionHolder(lazy); + + final AtomicInteger i = new AtomicInteger(); + lazy.entrySet().forEach(e -> { + assertEquals(set.size() - i.get(), LazyConstantTestUtil.functionHolderCounter(holder)); + assertSame(cif, LazyConstantTestUtil.functionHolderFunction(holder)); + Integer val = e.getValue(); + Integer val2 = e.getValue(); + i.incrementAndGet(); + }); + assertEquals(0, LazyConstantTestUtil.functionHolderCounter(holder)); + assertNull(LazyConstantTestUtil.functionHolderFunction(holder)); + } + + @Test + void usesOptimizedVersion() { + Map enumMap = Map.ofLazy(EnumSet.of(KEY), Value::asInt); + assertTrue(enumMap.getClass().getName().contains("Enum"), enumMap.getClass().getName()); + Map emptyMap = Map.ofLazy(EnumSet.noneOf(Value.class), Value::asInt); + assertFalse(emptyMap.getClass().getName().contains("Enum"), emptyMap.getClass().getName()); + Map regularMap = Map.ofLazy(Set.of(KEY), Value::asInt); + assertFalse(regularMap.getClass().getName().contains("Enum"), regularMap.getClass().getName()); + } + + @Test + void overriddenEnum() { + final var overridden = Value.THIRTEEN; + Map enumMap = Map.ofLazy(EnumSet.of(overridden), MAPPER); + assertEquals(MAPPER.apply(overridden), enumMap.get(overridden), enumMap.toString()); + } + + @Test + void enumAliasing() { + enum MyEnum {FOO, BAR} + enum MySecondEnum{BAZ, QUX} + Map mapEnum = Map.ofLazy(EnumSet.allOf(MyEnum.class), MyEnum::ordinal); + assertEquals(MyEnum.BAR.ordinal(), mapEnum.get(MyEnum.BAR)); + // Make sure class is checked, not just `ordinal()` + assertNull(mapEnum.get(MySecondEnum.QUX)); + } + + // Support constructs + + record Operation(String name, + Consumer> consumer) implements Consumer> { + @java.lang.Override + public void accept(Map map) { consumer.accept(map); } + @java.lang.Override + public String toString() { return name; } + } + + static Stream nullAverseOperations() { + return Stream.of( + new Operation("forEach", m -> m.forEach(null)) + ); + } + + static Stream unsupportedOperations() { + return Stream.of( + new Operation("clear", Map::clear), + new Operation("compute", m -> m.compute(KEY, (_, _) -> 1)), + new Operation("computeIfAbsent", m -> m.computeIfAbsent(KEY, _ -> 1)), + new Operation("computeIfPresent", m -> m.computeIfPresent(KEY, (_, _) -> 1)), + new Operation("merge", m -> m.merge(KEY, VALUE, (a, _) -> a)), + new Operation("put", m -> m.put(KEY, 0)), + new Operation("putAll", m -> m.putAll(Map.of())), + new Operation("remove1", m -> m.remove(KEY)), + new Operation("remove2", m -> m.remove(KEY, VALUE)), + new Operation("replace2", m -> m.replace(KEY, 1)), + new Operation("replace3", m -> m.replace(KEY, VALUE, 1)), + new Operation("replaceAll", m -> m.replaceAll((a, _) -> MAPPER.apply(a))) + ); + } + + + static Map newLazyMap(Set set) { + return Map.ofLazy(set, MAPPER); + } + static Map newRegularMap(Set set) { + return set.stream() + .collect(Collectors.toMap(Function.identity(), MAPPER)); + } + + private static Stream> nonEmptySets() { + return Stream.of( + Set.of(KEY, Value.THIRTEEN), + linkedHashSet(Value.THIRTEEN, KEY), + treeSet(KEY, Value.THIRTEEN), + EnumSet.of(KEY, Value.THIRTEEN) + ); + } + + private static Stream> emptySets() { + return Stream.of( + Set.of(), + linkedHashSet(), + treeSet(), + EnumSet.noneOf(Value.class) + ); + } + + private static Stream> allSets() { + return Stream.concat( + nonEmptySets(), + emptySets() + ); + } + + static Set treeSet(Value... values) { + return populate(new TreeSet<>(Comparator.comparingInt(Value::asInt).reversed()),values); + } + + static Set linkedHashSet(Value... values) { + return populate(new LinkedHashSet<>(), values); + } + + static Set populate(Set set, Value... values) { + set.addAll(Arrays.asList(values)); + return set; + } + + private static int functionCounter(Map lazy) { + final Object holder = LazyConstantTestUtil.functionHolder(lazy); + return LazyConstantTestUtil.functionHolderCounter(holder); + } + +} diff --git a/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java b/test/jdk/java/lang/LazyConstant/TrustedFieldTypeTest.java similarity index 61% rename from test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java rename to test/jdk/java/lang/LazyConstant/TrustedFieldTypeTest.java index 205e5ed3a77..9e63a9b275c 100644 --- a/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java +++ b/test/jdk/java/lang/LazyConstant/TrustedFieldTypeTest.java @@ -24,14 +24,14 @@ /* @test * @summary Basic tests for TrustedFieldType implementations * @modules jdk.unsupported/sun.misc - * @modules java.base/jdk.internal.lang.stable + * @modules java.base/jdk.internal.lang * @modules java.base/jdk.internal.misc * @enablePreview - * @run junit/othervm --add-opens java.base/jdk.internal.lang.stable=ALL-UNNAMED -Dopens=true TrustedFieldTypeTest + * @run junit/othervm --add-opens java.base/jdk.internal.lang=ALL-UNNAMED -Dopens=true TrustedFieldTypeTest * @run junit/othervm -Dopens=false TrustedFieldTypeTest */ -import jdk.internal.lang.stable.StableValueImpl; +import jdk.internal.lang.LazyConstantImpl; import jdk.internal.misc.Unsafe; import org.junit.jupiter.api.Test; @@ -39,84 +39,89 @@ import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; import java.lang.reflect.Field; import java.lang.reflect.InaccessibleObjectException; +import java.lang.LazyConstant; +import java.util.function.Supplier; import static org.junit.jupiter.api.Assertions.*; final class TrustedFieldTypeTest { + private static final int VALUE = 42; + private static final Supplier SUPPLIER = () -> VALUE; + @Test void varHandle() throws NoSuchFieldException, IllegalAccessException { MethodHandles.Lookup lookup = MethodHandles.lookup(); - StableValue originalValue = StableValue.of(); + LazyConstant originalValue = LazyConstant.of(SUPPLIER); @SuppressWarnings("unchecked") - StableValue[] originalArrayValue = new StableValue[10]; + LazyConstant[] originalArrayValue = new LazyConstant[10]; final class Holder { - private final StableValue value = originalValue; + private final LazyConstant value = originalValue; } final class ArrayHolder { - private final StableValue[] array = originalArrayValue; + private final LazyConstant[] array = originalArrayValue; } - VarHandle valueVarHandle = lookup.findVarHandle(Holder.class, "value", StableValue.class); + VarHandle valueVarHandle = lookup.findVarHandle(Holder.class, "value", LazyConstant.class); Holder holder = new Holder(); assertThrows(UnsupportedOperationException.class, () -> - valueVarHandle.set(holder, StableValue.of()) + valueVarHandle.set(holder, LazyConstant.of(SUPPLIER)) ); assertThrows(UnsupportedOperationException.class, () -> - valueVarHandle.compareAndSet(holder, originalValue, StableValue.of()) + valueVarHandle.compareAndSet(holder, originalValue, LazyConstant.of(SUPPLIER)) ); - VarHandle arrayVarHandle = lookup.findVarHandle(ArrayHolder.class, "array", StableValue[].class); + VarHandle arrayVarHandle = lookup.findVarHandle(ArrayHolder.class, "array", LazyConstant[].class); ArrayHolder arrayHolder = new ArrayHolder(); assertThrows(UnsupportedOperationException.class, () -> - arrayVarHandle.set(arrayHolder, new StableValue[1]) + arrayVarHandle.set(arrayHolder, new LazyConstant[1]) ); assertThrows(UnsupportedOperationException.class, () -> - arrayVarHandle.compareAndSet(arrayHolder, originalArrayValue, new StableValue[1]) + arrayVarHandle.compareAndSet(arrayHolder, originalArrayValue, new LazyConstant[1]) ); } @Test - void updateStableValueContentVia_j_i_m_Unsafe() { - StableValue stableValue = StableValue.of(); - stableValue.trySet(42); + void updateComputedConstantContentVia_j_i_m_Unsafe() { + LazyConstant lazyConstant = LazyConstant.of(SUPPLIER); + lazyConstant.get(); jdk.internal.misc.Unsafe unsafe = Unsafe.getUnsafe(); - long offset = unsafe.objectFieldOffset(stableValue.getClass(), "contents"); + long offset = unsafe.objectFieldOffset(lazyConstant.getClass(), "constant"); assertTrue(offset > 0); // Unfortunately, it is possible to update the underlying data via jdk.internal.misc.Unsafe - Object oldData = unsafe.getAndSetReference(stableValue, offset, 13); - assertEquals(42, oldData); - assertEquals(13, stableValue.orElseThrow()); + Object oldData = unsafe.getAndSetReference(lazyConstant, offset, 13); + assertEquals(VALUE, oldData); + assertEquals(13, lazyConstant.get()); } @Test - void updateStableValueContentViaSetAccessible() throws NoSuchFieldException, IllegalAccessException { + void updateComputedConstantContentViaSetAccessible() throws NoSuchFieldException, IllegalAccessException { if (Boolean.getBoolean("opens")) { // Unfortunately, add-opens allows direct access to the `value` field - Field field = StableValueImpl.class.getDeclaredField("contents"); + Field field = LazyConstantImpl.class.getDeclaredField("constant"); field.setAccessible(true); - StableValue stableValue = StableValue.of(); - stableValue.trySet(42); + LazyConstant lazyConstant = LazyConstant.of(SUPPLIER); + lazyConstant.get(); - Object oldData = field.get(stableValue); - assertEquals(42, oldData); + Object oldData = field.get(lazyConstant); + assertEquals(VALUE, oldData); - field.set(stableValue, 13); - assertEquals(13, stableValue.orElseThrow()); + field.set(lazyConstant, 13); + assertEquals(13, lazyConstant.get()); } else { - Field field = StableValueImpl.class.getDeclaredField("contents"); + Field field = LazyConstantImpl.class.getDeclaredField("constant"); assertThrows(InaccessibleObjectException.class, ()-> field.setAccessible(true)); } } diff --git a/test/jdk/java/lang/StableValue/StableFunctionTest.java b/test/jdk/java/lang/StableValue/StableFunctionTest.java deleted file mode 100644 index 07f51470a0b..00000000000 --- a/test/jdk/java/lang/StableValue/StableFunctionTest.java +++ /dev/null @@ -1,250 +0,0 @@ -/* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -/* @test - * @summary Basic tests for StableFunction methods - * @enablePreview - * @run junit StableFunctionTest - */ - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; - -import java.util.Arrays; -import java.util.Comparator; -import java.util.EnumSet; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.TreeSet; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Function; -import java.util.stream.Stream; - -import static org.junit.jupiter.api.Assertions.*; - -final class StableFunctionTest { - - enum Value { - // Zero is here so that we have enums with ordinals before the first one - // actually used in input sets (i.e. ZERO is not in the input set) - ZERO(0), - ILLEGAL_BEFORE(-1), - // Valid values - THIRTEEN(13) { - @Override - public String toString() { - // getEnumConstants will be `null` for this enum as it is overridden - return super.toString()+" (Overridden)"; - } - }, - ILLEGAL_BETWEEN(-2), - FORTY_TWO(42), - // Illegal values (not in the input set) - ILLEGAL_AFTER(-3); - - final int intValue; - - Value(int intValue) { - this.intValue = intValue; - } - - int asInt() { - return intValue; - } - - } - - private static final Function MAPPER = Value::asInt; - - @ParameterizedTest - @MethodSource("allSets") - void factoryInvariants(Set inputs) { - assertThrows(NullPointerException.class, () -> StableValue.function(null, MAPPER)); - assertThrows(NullPointerException.class, () -> StableValue.function(inputs, null)); - } - - @ParameterizedTest - @MethodSource("nonEmptySets") - void basic(Set inputs) { - basic(inputs, MAPPER); - toStringTest(inputs, MAPPER); - basic(inputs, _ -> null); - toStringTest(inputs, _ -> null); - } - - void basic(Set inputs, Function mapper) { - StableTestUtil.CountingFunction cif = new StableTestUtil.CountingFunction<>(mapper); - var cached = StableValue.function(inputs, cif); - assertEquals(mapper.apply(Value.FORTY_TWO), cached.apply(Value.FORTY_TWO)); - assertEquals(1, cif.cnt()); - assertEquals(mapper.apply(Value.FORTY_TWO), cached.apply(Value.FORTY_TWO)); - assertEquals(1, cif.cnt()); - var x0 = assertThrows(IllegalArgumentException.class, () -> cached.apply(Value.ILLEGAL_BEFORE)); - assertEquals("Input not allowed: ILLEGAL_BEFORE", x0.getMessage()); - var x1 = assertThrows(IllegalArgumentException.class, () -> cached.apply(Value.ILLEGAL_BETWEEN)); - assertEquals("Input not allowed: ILLEGAL_BETWEEN", x1.getMessage()); - var x2 = assertThrows(IllegalArgumentException.class, () -> cached.apply(Value.ILLEGAL_AFTER)); - assertEquals("Input not allowed: ILLEGAL_AFTER", x2.getMessage()); - } - - void toStringTest(Set inputs, Function mapper) { - var cached = StableValue.function(inputs, mapper); - cached.apply(Value.FORTY_TWO); - var toString = cached.toString(); - assertTrue(toString.startsWith("{")); - // Key order is unspecified - assertTrue(toString.contains(Value.THIRTEEN + "=.unset")); - assertTrue(toString.contains(Value.FORTY_TWO + "=" + mapper.apply(Value.FORTY_TWO))); - assertTrue(toString.endsWith("}")); - // One between the values - assertEquals(1L, toString.chars().filter(ch -> ch == ',').count()); - } - - @ParameterizedTest - @MethodSource("emptySets") - void empty(Set inputs) { - Function f0 = StableValue.function(inputs, Value::asInt); - Function f1 = StableValue.function(inputs, Value::asInt); - assertEquals("{}", f0.toString()); - assertThrows(NullPointerException.class, () -> f0.apply(null)); - assertNotEquals(f0, f1); - assertNotEquals(null, f0); - } - - @ParameterizedTest - @MethodSource("nonEmptySets") - void exception(Set inputs) { - StableTestUtil.CountingFunction cif = new StableTestUtil.CountingFunction<>(_ -> { - throw new UnsupportedOperationException(); - }); - var cached = StableValue.function(inputs, cif); - assertThrows(UnsupportedOperationException.class, () -> cached.apply(Value.FORTY_TWO)); - assertEquals(1, cif.cnt()); - assertThrows(UnsupportedOperationException.class, () -> cached.apply(Value.FORTY_TWO)); - assertEquals(2, cif.cnt()); - var toString = cached.toString(); - assertTrue(toString.startsWith("{")); - // Key order is unspecified - assertTrue(toString.contains(Value.THIRTEEN + "=.unset")); - assertTrue(toString.contains(Value.FORTY_TWO + "=.unset")); - assertTrue(toString.endsWith("}")); - } - - @ParameterizedTest - @MethodSource("nonEmptySets") - void circular(Set inputs) { - final AtomicReference> ref = new AtomicReference<>(); - Function> cached = StableValue.function(inputs, _ -> ref.get()); - ref.set(cached); - cached.apply(Value.FORTY_TWO); - var toString = cached.toString(); - assertTrue(toString.contains("FORTY_TWO=(this StableFunction)"), toString); - assertDoesNotThrow(cached::hashCode); - assertDoesNotThrow((() -> cached.equals(cached))); - } - - @ParameterizedTest - @MethodSource("allSets") - void equality(Set inputs) { - Function mapper = Value::asInt; - Function f0 = StableValue.function(inputs, mapper); - Function f1 = StableValue.function(inputs, mapper); - // No function is equal to another function - assertNotEquals(f0, f1); - } - - @ParameterizedTest - @MethodSource("allSets") - void hashCodeStable(Set inputs) { - Function f0 = StableValue.function(inputs, Value::asInt); - assertEquals(System.identityHashCode(f0), f0.hashCode()); - if (!inputs.isEmpty()) { - f0.apply(Value.FORTY_TWO); - assertEquals(System.identityHashCode(f0), f0.hashCode()); - } - } - - @Test - void nullKeys() { - Set inputs = new HashSet<>(); - inputs.add(Value.FORTY_TWO); - inputs.add(null); - assertThrows(NullPointerException.class, () -> StableValue.function(inputs, MAPPER)); - } - - @Test - void usesOptimizedVersion() { - Function enumFunction = StableValue.function(EnumSet.of(Value.FORTY_TWO), Value::asInt); - assertEquals("jdk.internal.lang.stable.StableEnumFunction", enumFunction.getClass().getName()); - Function emptyFunction = StableValue.function(Set.of(), Value::asInt); - assertEquals("jdk.internal.lang.stable.StableFunction", emptyFunction.getClass().getName()); - } - - @Test - void overriddenEnum() { - final var overridden = Value.THIRTEEN; - Function enumFunction = StableValue.function(EnumSet.of(overridden), Value::asInt); - assertEquals(MAPPER.apply(overridden), enumFunction.apply(overridden)); - } - - private static Stream> nonEmptySets() { - return Stream.of( - Set.of(Value.FORTY_TWO, Value.THIRTEEN), - linkedHashSet(Value.THIRTEEN, Value.FORTY_TWO), - treeSet(Value.FORTY_TWO, Value.THIRTEEN), - EnumSet.of(Value.FORTY_TWO, Value.THIRTEEN) - ); - } - - private static Stream> emptySets() { - return Stream.of( - Set.of(), - linkedHashSet(), - treeSet(), - EnumSet.noneOf(Value.class) - ); - } - - private static Stream> allSets() { - return Stream.concat( - nonEmptySets(), - emptySets() - ); - } - - static Set treeSet(Value... values) { - return populate(new TreeSet<>(Comparator.comparingInt(Value::asInt).reversed()),values); - } - - static Set linkedHashSet(Value... values) { - return populate(new LinkedHashSet<>(), values); - } - - static Set populate(Set set, Value... values) { - set.addAll(Arrays.asList(values)); - return set; - } - -} diff --git a/test/jdk/java/lang/StableValue/StableIntFunctionTest.java b/test/jdk/java/lang/StableValue/StableIntFunctionTest.java deleted file mode 100644 index 7397a688ee6..00000000000 --- a/test/jdk/java/lang/StableValue/StableIntFunctionTest.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -/* @test - * @summary Basic tests for StableIntFunction methods - * @enablePreview - * @run junit StableIntFunctionTest - */ - -import org.junit.jupiter.api.Test; - -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.IntFunction; - -import static org.junit.jupiter.api.Assertions.*; - -final class StableIntFunctionTest { - - private static final int SIZE = 2; - private static final IntFunction MAPPER = i -> i; - - @Test - void factoryInvariants() { - assertThrows(IllegalArgumentException.class, () -> StableValue.intFunction(-1, MAPPER)); - assertThrows(NullPointerException.class, () -> StableValue.intFunction(SIZE, null)); - } - - @Test - void basic() { - basic(MAPPER); - basic(i -> null); - } - - void basic(IntFunction mapper) { - StableTestUtil.CountingIntFunction cif = new StableTestUtil.CountingIntFunction<>(mapper); - var cached = StableValue.intFunction(SIZE, cif); - assertEquals("[.unset, .unset]", cached.toString()); - assertEquals(mapper.apply(1), cached.apply(1)); - assertEquals(1, cif.cnt()); - assertEquals(mapper.apply(1), cached.apply(1)); - assertEquals(1, cif.cnt()); - assertEquals("[.unset, " + mapper.apply(1) + "]", cached.toString()); - assertThrows(IllegalArgumentException.class, () -> cached.apply(SIZE)); - assertThrows(IllegalArgumentException.class, () -> cached.apply(-1)); - assertThrows(IllegalArgumentException.class, () -> cached.apply(1_000_000)); - } - - @Test - void exception() { - StableTestUtil.CountingIntFunction cif = new StableTestUtil.CountingIntFunction<>(_ -> { - throw new UnsupportedOperationException(); - }); - var cached = StableValue.intFunction(SIZE, cif); - assertThrows(UnsupportedOperationException.class, () -> cached.apply(1)); - assertEquals(1, cif.cnt()); - assertThrows(UnsupportedOperationException.class, () -> cached.apply(1)); - assertEquals(2, cif.cnt()); - assertEquals("[.unset, .unset]", cached.toString()); - } - - @Test - void circular() { - final AtomicReference> ref = new AtomicReference<>(); - IntFunction> cached = StableValue.intFunction(SIZE, _ -> ref.get()); - ref.set(cached); - cached.apply(0); - String toString = cached.toString(); - assertEquals("[(this StableIntFunction), .unset]", toString); - assertDoesNotThrow(cached::hashCode); - assertDoesNotThrow((() -> cached.equals(cached))); - } - - @Test - void equality() { - IntFunction f0 = StableValue.intFunction(8, MAPPER); - IntFunction f1 = StableValue.intFunction(8, MAPPER); - // No function is equal to another function - assertNotEquals(f0, f1); - } - - @Test - void hashCodeStable() { - IntFunction f0 = StableValue.intFunction(8, MAPPER); - assertEquals(System.identityHashCode(f0), f0.hashCode()); - f0.apply(4); - assertEquals(System.identityHashCode(f0), f0.hashCode()); - } - -} diff --git a/test/jdk/java/lang/StableValue/StableMapTest.java b/test/jdk/java/lang/StableValue/StableMapTest.java deleted file mode 100644 index 86cf4ab3643..00000000000 --- a/test/jdk/java/lang/StableValue/StableMapTest.java +++ /dev/null @@ -1,388 +0,0 @@ -/* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -/* @test - * @summary Basic tests for StableMap methods - * @modules java.base/jdk.internal.lang.stable - * @enablePreview - * @run junit StableMapTest - */ - -import jdk.internal.lang.stable.StableUtil; -import jdk.internal.lang.stable.StableValueImpl; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; - -import java.io.Serializable; -import java.util.AbstractMap; -import java.util.HashSet; -import java.util.IdentityHashMap; -import java.util.Map; -import java.util.Set; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static org.junit.jupiter.api.Assertions.*; - -final class StableMapTest { - - private static final int NOT_PRESENT = 147; - private static final int KEY = 7; - private static final Set KEYS = Set.of(0, KEY, 13); - private static final Set EMPTY = Set.of(); - private static final Function IDENTITY = Function.identity(); - - @Test - void factoryInvariants() { - assertThrows(NullPointerException.class, () -> StableValue.map(KEYS, null)); - assertThrows(NullPointerException.class, () -> StableValue.map(null, IDENTITY)); - } - - @Test - void isEmpty() { - assertFalse(newMap().isEmpty()); - assertTrue(newEmptyMap().isEmpty()); - } - - @Test - void size() { - assertEquals(KEYS.size(), newMap().size()); - assertEquals(EMPTY.size(), newEmptyMap().size()); - } - - @Test - void get() { - StableTestUtil.CountingFunction cf = new StableTestUtil.CountingFunction<>(IDENTITY); - var lazy = StableValue.map(KEYS, cf); - int cnt = 1; - for (int i : KEYS) { - assertEquals(i, lazy.get(i)); - assertEquals(cnt, cf.cnt()); - assertEquals(i, lazy.get(i)); - assertEquals(cnt++, cf.cnt()); - } - assertNull(lazy.get(NOT_PRESENT)); - } - - @Test - void getException() { - StableTestUtil.CountingFunction cf = new StableTestUtil.CountingFunction<>(_ -> { - throw new UnsupportedOperationException(); - }); - var lazy = StableValue.map(KEYS, cf); - assertThrows(UnsupportedOperationException.class, () -> lazy.get(KEY)); - assertEquals(1, cf.cnt()); - assertThrows(UnsupportedOperationException.class, () -> lazy.get(KEY)); - assertEquals(2, cf.cnt()); - } - - @Test - void containsKey() { - var lazy = newMap(); - for (int i : KEYS) { - assertTrue(lazy.containsKey(i)); - } - assertFalse(lazy.containsKey(NOT_PRESENT)); - } - - @Test - void containsValue() { - var lazy = newMap(); - for (int i : KEYS) { - assertTrue(lazy.containsValue(i)); - } - assertFalse(lazy.containsValue(NOT_PRESENT)); - } - - @Test - void forEach() { - var lazy = newMap(); - Set> expected = KEYS.stream() - .map(i -> new AbstractMap.SimpleImmutableEntry<>(i , i)) - .collect(Collectors.toSet()); - Set> actual = new HashSet<>(); - lazy.forEach((k, v) -> actual.add(new AbstractMap.SimpleImmutableEntry<>(k , v))); - assertEquals(expected, actual); - } - - @Test - void toStringTest() { - assertEquals("{}", newEmptyMap().toString()); - var map = StableValue.map(Set.of(KEY), IDENTITY); - assertEquals("{" + KEY + "=.unset}", map.toString()); - map.get(KEY); - assertEquals("{" + KEY + "=" + KEY + "}", map.toString()); - String actual = newMap().toString(); - assertTrue(actual.startsWith("{")); - for (int key : KEYS) { - assertTrue(actual.contains(key + "=.unset")); - } - assertTrue(actual.endsWith("}")); - } - - @Test - void hashCodeTest() { - assertEquals(Map.of().hashCode(), newEmptyMap().hashCode()); - assertEquals(newRegularMap().hashCode(), newMap().hashCode()); - } - - @Test - void equalsTest() { - assertTrue(newEmptyMap().equals(Map.of())); - assertTrue(Map.of().equals(newEmptyMap())); - assertTrue(newMap().equals(newRegularMap())); - assertTrue(newRegularMap().equals(newMap())); - assertFalse(newMap().equals("A")); - } - - @Test - void entrySet() { - var regular = newRegularMap().entrySet(); - var actual = newMap().entrySet(); - assertTrue(regular.equals(actual)); - assertTrue(actual.equals(regular)); - assertTrue(regular.equals(actual)); - } - - @Test - void entrySetToString() { - var map = newMap(); - var entrySet = map.entrySet(); - var toString = entrySet.toString(); - for (var key : KEYS) { - assertTrue(toString.contains(key + "=.unset")); - } - assertTrue(toString.startsWith("[")); - assertTrue(toString.endsWith("]")); - - map.get(KEY); - for (var key : KEYS) { - if (key.equals(KEY)) { - continue; - } - assertTrue(entrySet.toString().contains(key + "=.unset")); - } - assertTrue(entrySet.toString().contains(KEY + "=" + KEY)); - } - - @Test - void values() { - var map = newMap(); - var values = map.values(); - // Look at one of the elements - var val = values.stream().iterator().next(); - var toString = map.toString(); - for (var key : KEYS) { - if (key.equals(val)) { - assertTrue(toString.contains(key + "=" + key)); - } else { - assertTrue(toString.contains(key + "=.unset")); - } - } - - // Mod ops - assertThrows(UnsupportedOperationException.class, () -> values.remove(KEY)); - assertThrows(UnsupportedOperationException.class, () -> values.add(KEY)); - assertThrows(UnsupportedOperationException.class, values::clear); - assertThrows(UnsupportedOperationException.class, () -> values.addAll(Set.of(1))); - assertThrows(UnsupportedOperationException.class, () -> values.removeIf(i -> true)); - assertThrows(UnsupportedOperationException.class, () -> values.retainAll(Set.of(KEY))); - } - - @Test - void valuesToString() { - var map = newMap(); - var values = map.values(); - assertEquals("[.unset, .unset, .unset]", values.toString()); - map.get(KEY); - var afterGet = values.toString(); - assertTrue(afterGet.contains(Integer.toString(KEY)), afterGet); - } - - @Test - void iteratorNext() { - Set encountered = new HashSet<>(); - var iterator = newMap().entrySet().iterator(); - while (iterator.hasNext()) { - var entry = iterator.next(); - assertEquals(entry.getKey(), entry.getValue()); - encountered.add(entry.getValue()); - } - assertEquals(KEYS, encountered); - } - - @Test - void iteratorForEachRemaining() { - Set encountered = new HashSet<>(); - var iterator = newMap().entrySet().iterator(); - var entry = iterator.next(); - assertEquals(entry.getKey(), entry.getValue()); - encountered.add(entry.getValue()); - iterator.forEachRemaining(e -> { - assertEquals(e.getKey(), e.getValue()); - encountered.add(e.getValue()); - }); - assertEquals(KEYS, encountered); - } - - @Test - void stableEntry() { - var map = newMap(); - var entry = map.entrySet().stream() - .filter(e -> e.getKey().equals(KEY)) - .findAny() - .orElseThrow(); - - assertEquals(KEY + "=.unset", entry.toString()); - var otherDifferent = Map.entry(-1, -1); - assertNotEquals(entry, otherDifferent); - assertEquals(KEY + "=.unset", entry.toString()); - var otherEqual = Map.entry(KEY, KEY); - assertEquals(entry, otherEqual); - assertEquals(KEY + "=" + KEY, entry.toString()); - assertEquals(entry.hashCode(), otherEqual.hashCode()); - } - - @Test - void stableForEachEntry() { - var map = newMap(); - // Only touch the key. - map.entrySet().iterator().forEachRemaining(Map.Entry::getKey); - map.entrySet().iterator() - .forEachRemaining(e -> assertTrue(e.toString().contains(".unset"))); - // Only touch the value. - map.entrySet().iterator().forEachRemaining(Map.Entry::getValue); - map.entrySet().iterator() - .forEachRemaining(e -> assertFalse(e.toString().contains(".unset"))); - } - - // Immutability - @ParameterizedTest - @MethodSource("unsupportedOperations") - void unsupported(Operation operation) { - assertThrowsForOperation(UnsupportedOperationException.class, operation); - } - - // Method parameter invariant checking - - @ParameterizedTest - @MethodSource("nullAverseOperations") - void nullAverse(Operation operation) { - assertThrowsForOperation(NullPointerException.class, operation); - } - - static void assertThrowsForOperation(Class expectedType, Operation operation) { - var lazy = newMap(); - assertThrows(expectedType, () -> operation.accept(lazy)); - } - - // Implementing interfaces - - @Test - void serializable() { - serializable(newMap()); - serializable(newEmptyMap()); - } - - void serializable(Map map) { - assertFalse(map instanceof Serializable); - assertFalse(map.entrySet() instanceof Serializable); - assertFalse(map.keySet() instanceof Serializable); - assertFalse(map.values() instanceof Serializable); - } - - @Test - void distinct() { - Map> map = StableUtil.map(Set.of(1, 2, 3)); - assertEquals(3, map.size()); - // Check, every StableValue is distinct - Map, Boolean> idMap = new IdentityHashMap<>(); - map.forEach((k, v) -> idMap.put(v, true)); - assertEquals(3, idMap.size()); - } - - @Test - void nullResult() { - var map = StableValue.map(Set.of(0), _ -> null); - assertNull(map.getOrDefault(0, 1));; - assertTrue(map.containsKey(0)); - assertNull(map.get(0)); - } - - @Test - void nullKeys() { - Set inputs = new HashSet<>(); - inputs.add(0); - inputs.add(null); - assertThrows(NullPointerException.class, () -> StableValue.map(inputs, IDENTITY)); - } - - // Support constructs - - record Operation(String name, - Consumer> consumer) implements Consumer> { - @java.lang.Override - public void accept(Map map) { consumer.accept(map); } - @java.lang.Override - public String toString() { return name; } - } - - static Stream nullAverseOperations() { - return Stream.of( - new Operation("forEach", m -> m.forEach(null)) - ); - } - - static Stream unsupportedOperations() { - return Stream.of( - new Operation("clear", Map::clear), - new Operation("compute", m -> m.compute(KEY, (_, _) -> 1)), - new Operation("computeIfAbsent", m -> m.computeIfAbsent(KEY, _ -> 1)), - new Operation("computeIfPresent", m -> m.computeIfPresent(KEY, (_, _) -> 1)), - new Operation("merge", m -> m.merge(KEY, KEY, (a, _) -> a)), - new Operation("put", m -> m.put(0, 0)), - new Operation("putAll", m -> m.putAll(Map.of())), - new Operation("remove1", m -> m.remove(KEY)), - new Operation("remove2", m -> m.remove(KEY, KEY)), - new Operation("replace2", m -> m.replace(KEY, 1)), - new Operation("replace3", m -> m.replace(KEY, KEY, 1)), - new Operation("replaceAll", m -> m.replaceAll((a, _) -> a)) - ); - } - - static Map newMap() { - return StableValue.map(KEYS, IDENTITY); - } - - static Map newEmptyMap() { - return StableValue.map(EMPTY, IDENTITY); - } - - static Map newRegularMap() { - return KEYS.stream().collect(Collectors.toMap(IDENTITY, IDENTITY)); - } - -} diff --git a/test/jdk/java/lang/StableValue/StableSupplierTest.java b/test/jdk/java/lang/StableValue/StableSupplierTest.java deleted file mode 100644 index 2d542fbf6ca..00000000000 --- a/test/jdk/java/lang/StableValue/StableSupplierTest.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -/* @test - * @summary Basic tests for StableSupplier methods - * @enablePreview - * @run junit StableSupplierTest - */ - -import org.junit.jupiter.api.Test; - -import java.util.Objects; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Supplier; - -import static org.junit.jupiter.api.Assertions.*; - -final class StableSupplierTest { - - private static final Supplier SUPPLIER = () -> 42; - - @Test - void factoryInvariants() { - assertThrows(NullPointerException.class, () -> StableValue.supplier(null)); - } - - @Test - void basic() { - basic(SUPPLIER); - basic(() -> null); - } - - void basic(Supplier supplier) { - StableTestUtil.CountingSupplier cs = new StableTestUtil.CountingSupplier<>(supplier); - var cached = StableValue.supplier(cs); - assertEquals(".unset", cached.toString()); - assertEquals(supplier.get(), cached.get()); - assertEquals(1, cs.cnt()); - assertEquals(supplier.get(), cached.get()); - assertEquals(1, cs.cnt()); - assertEquals(Objects.toString(supplier.get()), cached.toString()); - } - - @Test - void exception() { - StableTestUtil.CountingSupplier cs = new StableTestUtil.CountingSupplier<>(() -> { - throw new UnsupportedOperationException(); - }); - var cached = StableValue.supplier(cs); - assertThrows(UnsupportedOperationException.class, cached::get); - assertEquals(1, cs.cnt()); - assertThrows(UnsupportedOperationException.class, cached::get); - assertEquals(2, cs.cnt()); - assertEquals(".unset", cached.toString()); - } - - @Test - void circular() { - final AtomicReference> ref = new AtomicReference<>(); - Supplier> cached = StableValue.supplier(ref::get); - ref.set(cached); - cached.get(); - String toString = cached.toString(); - assertTrue(toString.startsWith("(this StableSupplier)")); - assertDoesNotThrow(cached::hashCode); - } - - @Test - void equality() { - Supplier f0 = StableValue.supplier(SUPPLIER); - Supplier f1 = StableValue.supplier(SUPPLIER); - // No function is equal to another function - assertNotEquals(f0, f1); - } - - @Test - void hashCodeStable() { - Supplier f0 = StableValue.supplier(SUPPLIER); - assertEquals(System.identityHashCode(f0), f0.hashCode()); - f0.get(); - assertEquals(System.identityHashCode(f0), f0.hashCode()); - } - -} diff --git a/test/jdk/java/lang/StableValue/StableValueFactoriesTest.java b/test/jdk/java/lang/StableValue/StableValueFactoriesTest.java deleted file mode 100644 index 85aee0cbeec..00000000000 --- a/test/jdk/java/lang/StableValue/StableValueFactoriesTest.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -/* @test - * @summary Basic tests for StableValue factory implementations - * @modules java.base/jdk.internal.lang.stable - * @enablePreview - * @run junit StableValueFactoriesTest - */ - -import jdk.internal.lang.stable.StableUtil; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -final class StableValueFactoriesTest { - - @Test - void array() { - assertThrows(IllegalArgumentException.class, () -> StableUtil.array(-1)); - } - -} diff --git a/test/jdk/java/lang/StableValue/StableValueTest.java b/test/jdk/java/lang/StableValue/StableValueTest.java deleted file mode 100644 index 4290c8716a0..00000000000 --- a/test/jdk/java/lang/StableValue/StableValueTest.java +++ /dev/null @@ -1,389 +0,0 @@ -/* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -/* @test - * @summary Basic tests for StableValue implementations - * @enablePreview - * @run junit StableValueTest - */ - -import org.junit.jupiter.api.Test; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; -import java.util.concurrent.locks.LockSupport; -import java.util.function.BiPredicate; -import java.util.function.Function; -import java.util.function.IntFunction; -import java.util.function.UnaryOperator; -import java.util.stream.IntStream; - -import static org.junit.jupiter.api.Assertions.*; -import static org.junit.jupiter.api.Assertions.assertEquals; - -final class StableValueTest { - - private static final int VALUE = 42; - private static final int VALUE2 = 13; - - @Test - void trySet() { - trySet(VALUE); - trySet(null); - } - - @Test - void preSet() { - StableValue stable = StableValue.of(VALUE); - assertTrue(stable.isSet()); - assertEquals(VALUE, stable.orElseThrow()); - assertEquals(VALUE, stable.orElse(VALUE2)); - assertEquals(VALUE, stable.orElseSet(() -> VALUE2)); - assertFalse(stable.trySet(VALUE2)); - var e = assertThrows(IllegalStateException.class, () -> stable.setOrThrow(VALUE2)); - assertEquals( - "The contents is already set", - e.getMessage()); - } - - void trySet(Integer initial) { - StableValue stable = StableValue.of(); - assertTrue(stable.trySet(initial)); - assertFalse(stable.trySet(null)); - assertFalse(stable.trySet(VALUE)); - assertFalse(stable.trySet(VALUE2)); - assertEquals(initial, stable.orElseThrow()); - } - - @Test - void setOrThrowValue() { - StableValue stable = StableValue.of(); - stable.setOrThrow(VALUE); - var e = assertThrows(IllegalStateException.class, () -> stable.setOrThrow(VALUE2)); - assertEquals("The contents is already set", e.getMessage()); - } - - @Test - void setOrThrowNull() { - StableValue stable = StableValue.of(); - stable.setOrThrow(null); - var e = assertThrows(IllegalStateException.class, () -> stable.setOrThrow(null)); - assertEquals("The contents is already set", e.getMessage()); - } - - @Test - void orElse() { - StableValue stable = StableValue.of(); - assertEquals(VALUE, stable.orElse(VALUE)); - assertNull(stable.orElse(null)); - stable.trySet(VALUE); - assertEquals(VALUE, stable.orElse(VALUE2)); - } - - @Test - void orElseThrow() { - StableValue stable = StableValue.of(); - var e = assertThrows(NoSuchElementException.class, stable::orElseThrow); - assertEquals("No contents set", e.getMessage()); - stable.trySet(VALUE); - assertEquals(VALUE, stable.orElseThrow()); - } - - @Test - void isSet() { - isSet(VALUE); - isSet(null); - } - - void isSet(Integer initial) { - StableValue stable = StableValue.of(); - assertFalse(stable.isSet()); - stable.trySet(initial); - assertTrue(stable.isSet()); - } - - @Test - void testOrElseSetSupplier() { - StableTestUtil.CountingSupplier cs = new StableTestUtil.CountingSupplier<>(() -> VALUE); - StableValue stable = StableValue.of(); - assertThrows(NullPointerException.class, () -> stable.orElseSet(null)); - assertEquals(VALUE, stable.orElseSet(cs)); - assertEquals(1, cs.cnt()); - assertEquals(VALUE, stable.orElseSet(cs)); - assertEquals(1, cs.cnt()); - } - - @Test - void testHashCode() { - StableValue stableValue = StableValue.of(); - // Should be Object::hashCode - assertEquals(System.identityHashCode(stableValue), stableValue.hashCode()); - } - - @Test - void testEquals() { - StableValue s0 = StableValue.of(); - assertNotEquals(null, s0); - StableValue s1 = StableValue.of(); - assertNotEquals(s0, s1); // Identity based - s0.setOrThrow(42); - s1.setOrThrow(42); - assertNotEquals(s0, s1); - assertNotEquals("a", s0); - StableValue null0 = StableValue.of(); - StableValue null1 = StableValue.of(); - null0.setOrThrow(null); - null1.setOrThrow(null); - assertNotEquals(null0, null1); - } - - @Test - void toStringUnset() { - StableValue stable = StableValue.of(); - assertEquals(".unset", stable.toString()); - } - - @Test - void toStringNull() { - StableValue stable = StableValue.of(); - assertTrue(stable.trySet(null)); - assertEquals("null", stable.toString()); - } - - @Test - void toStringNonNull() { - StableValue stable = StableValue.of(); - assertTrue(stable.trySet(VALUE)); - assertEquals(Objects.toString(VALUE), stable.toString()); - } - - @Test - void toStringCircular() { - StableValue> stable = StableValue.of(); - stable.trySet(stable); - String toString = assertDoesNotThrow(stable::toString); - assertEquals("(this StableValue)", toString); - assertDoesNotThrow(stable::hashCode); - assertDoesNotThrow((() -> stable.equals(stable))); - } - - @Test - void recursiveCall() { - StableValue stable = StableValue.of(); - AtomicReference> ref = new AtomicReference<>(stable); - assertThrows(IllegalStateException.class, () -> - stable.orElseSet(() -> { - ref.get().trySet(1); - return 1; - }) - ); - assertThrows(IllegalStateException.class, () -> - stable.orElseSet(() -> { - ref.get().orElseSet(() -> 1); - return 1; - }) - ); - } - - @Test - void intFunctionExample() { - final class SqrtUtil { - - private SqrtUtil() {} - - private static final int CACHED_SIZE = 10; - - private static final IntFunction SQRT = - // @link substring="intFunction" target="#intFunction(int,IntFunction)" : - StableValue.intFunction(CACHED_SIZE, StrictMath::sqrt); - - public static double sqrt(int a) { - return SQRT.apply(a); - } - } - - double sqrt9 = SqrtUtil.sqrt(9); // May eventually constant fold to 3.0 at runtime - - assertEquals(3, sqrt9); - assertThrows(IllegalArgumentException.class, () -> SqrtUtil.sqrt(16)); - } - - @Test - void intFunctionExample2() { - final class PowerOf2Util { - - private PowerOf2Util() {} - - private static final int SIZE = 6; - private static final IntFunction ORIGINAL_POWER_OF_TWO = - v -> 1 << v; - - private static final IntFunction POWER_OF_TWO = - // @link substring="intFunction" target="#intFunction(int,IntFunction)" : - StableValue.intFunction(SIZE, ORIGINAL_POWER_OF_TWO); - - public static int powerOfTwo(int a) { - return POWER_OF_TWO.apply(a); - } - } - - int pwr4 = PowerOf2Util.powerOfTwo(4); // May eventually constant fold to 16 at runtime - - assertEquals(16, pwr4); - assertEquals(1, PowerOf2Util.powerOfTwo(0)); - assertEquals(8, PowerOf2Util.powerOfTwo(3)); - assertEquals(32, PowerOf2Util.powerOfTwo(5)); - assertThrows(IllegalArgumentException.class, () -> PowerOf2Util.powerOfTwo(10)); - } - - @Test - void functionExample() { - - class Log2Util { - - private Log2Util() {} - - private static final Set CACHED_KEYS = - Set.of(1, 2, 4, 8, 16, 32); - private static final UnaryOperator LOG2_ORIGINAL = - i -> 31 - Integer.numberOfLeadingZeros(i); - - private static final Function LOG2_CACHED = - // @link substring="function" target="#function(Set,Function)" : - StableValue.function(CACHED_KEYS, LOG2_ORIGINAL); - - public static double log2(int a) { - if (CACHED_KEYS.contains(a)) { - return LOG2_CACHED.apply(a); - } else { - return LOG2_ORIGINAL.apply(a); - } - } - - } - - double log16 = Log2Util.log2(16); // May eventually constant fold to 4.0 at runtime - double log256 = Log2Util.log2(256); // Will not constant fold - - assertEquals(4, log16); - assertEquals(8, log256); - } - - @Test - void functionExample2() { - - class Log2Util { - - private Log2Util() {} - - private static final Set KEYS = - Set.of(1, 2, 4, 8); - private static final UnaryOperator LOG2_ORIGINAL = - i -> 31 - Integer.numberOfLeadingZeros(i); - - private static final Function LOG2 = - // @link substring="function" target="#function(Set,Function)" : - StableValue.function(KEYS, LOG2_ORIGINAL); - - public static double log2(int a) { - return LOG2.apply(a); - } - - } - - double log16 = Log2Util.log2(8); // May eventually constant fold to 3.0 at runtime - - assertEquals(3, log16); - assertThrows(IllegalArgumentException.class, () -> Log2Util.log2(3)); - } - - private static final BiPredicate, Integer> TRY_SET = StableValue::trySet; - private static final BiPredicate, Integer> SET_OR_THROW = (s, i) -> { - try { - s.setOrThrow(i); - return true; - } catch (IllegalStateException e) { - return false; - } - }; - - @Test - void raceTrySet() { - race(TRY_SET); - } - - @Test - void raceSetOrThrow() { - race(SET_OR_THROW); - } - - @Test - void raceMixed() { - race((s, i) -> switch (i % 2) { - case 0 -> TRY_SET.test(s, i); - case 1 -> SET_OR_THROW.test(s, i); - default -> fail("should not reach here"); - }); - } - - void race(BiPredicate, Integer> winnerPredicate) { - int noThreads = 10; - CountDownLatch starter = new CountDownLatch(noThreads); - StableValue stable = StableValue.of(); - Map winners = new ConcurrentHashMap<>(); - List threads = IntStream.range(0, noThreads).mapToObj(i -> new Thread(() -> { - try { - // Ready ... - starter.countDown(); - // ... set ... - starter.await(); - // Here we go! - winners.put(i, winnerPredicate.test(stable, i)); - } catch (Throwable t) { - fail(t); - } - })) - .toList(); - threads.forEach(Thread::start); - threads.forEach(StableValueTest::join); - // There can only be one winner - assertEquals(1, winners.values().stream().filter(b -> b).count()); - } - - private static void join(Thread thread) { - try { - thread.join(); - } catch (InterruptedException e) { - fail(e); - } - } - -} diff --git a/test/jdk/java/util/Collection/MOAT.java b/test/jdk/java/util/Collection/MOAT.java index ab9e4b4309d..d0d27c8f91e 100644 --- a/test/jdk/java/util/Collection/MOAT.java +++ b/test/jdk/java/util/Collection/MOAT.java @@ -220,15 +220,15 @@ public class MOAT { // Immutable List testEmptyList(List.of()); testEmptyList(List.of().subList(0,0)); - testEmptyList(StableValue.list(0, i -> i)); - testEmptyList(StableValue.list(3, i -> i).subList(0, 0)); + testEmptyList(List.ofLazy(0, i -> i)); + testEmptyList(List.ofLazy(3, i -> i).subList(0, 0)); testListMutatorsAlwaysThrow(List.of()); testListMutatorsAlwaysThrow(List.of().subList(0,0)); - testListMutatorsAlwaysThrow(StableValue.list(0, i -> i)); + testListMutatorsAlwaysThrow(List.ofLazy(0, i -> i)); testEmptyListMutatorsAlwaysThrow(List.of()); testEmptyListMutatorsAlwaysThrow(List.of().subList(0,0)); - testEmptyListMutatorsAlwaysThrow(StableValue.list(0, i -> i)); - testEmptyListMutatorsAlwaysThrow(StableValue.list(3, i -> i).subList(0, 0)); + testEmptyListMutatorsAlwaysThrow(List.ofLazy(0, i -> i)); + testEmptyListMutatorsAlwaysThrow(List.ofLazy(3, i -> i).subList(0, 0)); for (List list : Arrays.asList( List.of(), List.of(1), @@ -251,9 +251,9 @@ public class MOAT { Stream.of(1, null).toList(), Stream.of(1, null, 3).toList(), Stream.of(1, null, 3, 4).toList(), - StableValue.list(0, i -> i), - StableValue.list(3, i -> i), - StableValue.list(10, i -> i))) { + List.ofLazy(0, i -> i), + List.ofLazy(3, i -> i), + List.ofLazy(10, i -> i))) { testCollection(list); testImmutableList(list); testListMutatorsAlwaysThrow(list); @@ -365,9 +365,9 @@ public class MOAT { testEmptyMap(Map.of()); testMapMutatorsAlwaysThrow(Map.of()); testEmptyMapMutatorsAlwaysThrow(Map.of()); - testEmptyMap(StableValue.map(Set.of(), k -> k)); - testMapMutatorsAlwaysThrow(StableValue.map(Set.of(), k -> k)); - testEmptyMapMutatorsAlwaysThrow(StableValue.map(Set.of(), k -> k)); + testEmptyMap(Map.ofLazy(Set.of(), k -> k)); + testMapMutatorsAlwaysThrow(Map.ofLazy(Set.of(), k -> k)); + testEmptyMapMutatorsAlwaysThrow(Map.ofLazy(Set.of(), k -> k)); for (Map map : Arrays.asList( Map.of(), Map.of(1, 101), @@ -381,9 +381,9 @@ public class MOAT { Map.of(1, 101, 2, 202, 3, 303, 4, 404, 5, 505, 6, 606, 7, 707, 8, 808, 9, 909), Map.of(1, 101, 2, 202, 3, 303, 4, 404, 5, 505, 6, 606, 7, 707, 8, 808, 9, 909, 10, 1010), Map.ofEntries(ea), - StableValue.map(Set.of(), k -> k), - StableValue.map(Set.of(1), k -> k), - StableValue.map(Set.of(1, 2, 3), k -> k))) { + Map.ofLazy(Set.of(), k -> k), + Map.ofLazy(Set.of(1), k -> k), + Map.ofLazy(Set.of(1, 2, 3), k -> k))) { testMap(map); testImmutableMap(map); testMapMutatorsAlwaysThrow(map); diff --git a/test/langtools/jdk/jshell/CompletionSuggestionTest.java b/test/langtools/jdk/jshell/CompletionSuggestionTest.java index d31a32b63f8..7a960258cc1 100644 --- a/test/langtools/jdk/jshell/CompletionSuggestionTest.java +++ b/test/langtools/jdk/jshell/CompletionSuggestionTest.java @@ -947,7 +947,7 @@ public class CompletionSuggestionTest extends KullaTesting { public void testMultiSnippet() { assertCompletion("String s = \"\"; s.len|", true, "length()"); assertCompletion("String s() { return \"\"; } s().len|", true, "length()"); - assertCompletion("String s() { return \"\"; } import java.util.List; List.o|", true, "of("); + assertCompletion("String s() { return \"\"; } import java.util.List; List.o|", true, "of(", "ofLazy("); assertCompletion("String s() { return \"\"; } import java.ut| ", true, "util."); assertCompletion("class S { public int length() { return 0; } } new S().len|", true, "length()"); assertSignature("void f() { } f(|", "void f()"); diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableIntFunctionBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableListBenchmark.java similarity index 86% rename from test/micro/org/openjdk/bench/java/lang/stable/StableIntFunctionBenchmark.java rename to test/micro/org/openjdk/bench/java/lang/stable/StableListBenchmark.java index 0b8e5d97cac..67a60755682 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableIntFunctionBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableListBenchmark.java @@ -40,7 +40,7 @@ import java.util.concurrent.TimeUnit; import java.util.function.IntFunction; /** - * Benchmark measuring StableValue performance + * Benchmark measuring stable list performance */ @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) @@ -52,16 +52,16 @@ import java.util.function.IntFunction; }) @Threads(Threads.MAX) // Benchmark under contention @OperationsPerInvocation(100) -public class StableIntFunctionBenchmark { +public class StableListBenchmark { private static final int SIZE = 100; private static final IntFunction IDENTITY = i -> i; - private static final List LIST = StableValue.list(SIZE, IDENTITY); - private static final IntFunction INT_FUNCTION = StableValue.intFunction(SIZE, IDENTITY); + private static final List LIST = List.ofLazy(SIZE, IDENTITY); + private static final IntFunction INT_FUNCTION = LIST::get; - private final List list = StableValue.list(SIZE, IDENTITY); - private final IntFunction intFunction = StableValue.intFunction(SIZE, IDENTITY); + private final List list = List.ofLazy(SIZE, IDENTITY); + private final IntFunction intFunction = list::get; @Benchmark public int list() { diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableIntFunctionSingleBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableListSingleBenchmark.java similarity index 84% rename from test/micro/org/openjdk/bench/java/lang/stable/StableIntFunctionSingleBenchmark.java rename to test/micro/org/openjdk/bench/java/lang/stable/StableListSingleBenchmark.java index 1e8e250ba8a..9210d8a86be 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableIntFunctionSingleBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableListSingleBenchmark.java @@ -39,7 +39,7 @@ import java.util.concurrent.TimeUnit; import java.util.function.IntFunction; /** - * Benchmark measuring StableValue performance + * Benchmark measuring stable list performance */ @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) @@ -50,16 +50,16 @@ import java.util.function.IntFunction; "--enable-preview" }) @Threads(Threads.MAX) // Benchmark under contention -public class StableIntFunctionSingleBenchmark { +public class StableListSingleBenchmark { private static final int SIZE = 100; private static final IntFunction IDENTITY = i -> i; - private static final List STABLE = StableValue.list(SIZE, IDENTITY); - private static final IntFunction INT_FUNCTION = StableValue.intFunction(SIZE, IDENTITY); + private static final List STABLE = List.ofLazy(SIZE, IDENTITY); + private static final IntFunction INT_FUNCTION = STABLE::get; - private final List stable = StableValue.list(SIZE, IDENTITY); - private final IntFunction intFunction = StableValue.intFunction(SIZE, IDENTITY); + private final List stable = List.ofLazy(SIZE, IDENTITY); + private final IntFunction intFunction = stable::get; @Benchmark public int list() { diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableFunctionBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableMapBenchmark.java similarity index 85% rename from test/micro/org/openjdk/bench/java/lang/stable/StableFunctionBenchmark.java rename to test/micro/org/openjdk/bench/java/lang/stable/StableMapBenchmark.java index 44fd3f2c18e..2b8e90eee17 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableFunctionBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableMapBenchmark.java @@ -43,7 +43,7 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; /** - * Benchmark measuring StableValue performance + * Benchmark measuring stable map performance */ @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) @@ -55,16 +55,16 @@ import java.util.stream.IntStream; }) @Threads(Threads.MAX) // Benchmark under contention @OperationsPerInvocation(100) -public class StableFunctionBenchmark { +public class StableMapBenchmark { private static final int SIZE = 100; private static final Set SET = IntStream.range(0, SIZE).boxed().collect(Collectors.toSet()); - private static final Map MAP = StableValue.map(SET, Function.identity()); - private static final Function FUNCTION = StableValue.function(SET, Function.identity()); + private static final Map MAP = Map.ofLazy(SET, Function.identity()); + private static final Function FUNCTION = MAP::get; - private final Map map = StableValue.map(SET, Function.identity()); - private final Function function = StableValue.function(SET, Function.identity()); + private final Map map = Map.ofLazy(SET, Function.identity()); + private final Function function = map::get; @Benchmark public int map() { @@ -85,7 +85,7 @@ public class StableFunctionBenchmark { } @Benchmark - public int staticSMap() { + public int staticMap() { int sum = 0; for (int i = 0; i < SIZE; i++) { sum += MAP.get(i); diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableFunctionSingleBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableMapSingleBenchmark.java similarity index 70% rename from test/micro/org/openjdk/bench/java/lang/stable/StableFunctionSingleBenchmark.java rename to test/micro/org/openjdk/bench/java/lang/stable/StableMapSingleBenchmark.java index 1cb1a04582f..4965ec649eb 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableFunctionSingleBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableMapSingleBenchmark.java @@ -28,14 +28,15 @@ import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Mode; -import org.openjdk.jmh.annotations.OperationsPerInvocation; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; +import java.util.EnumSet; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.function.Function; @@ -43,7 +44,7 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; /** - * Benchmark measuring StableValue performance + * Benchmark measuring lazy map performance */ @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) @@ -54,16 +55,17 @@ import java.util.stream.IntStream; "--enable-preview" }) @Threads(Threads.MAX) // Benchmark under contention -public class StableFunctionSingleBenchmark { +public class StableMapSingleBenchmark { private static final int SIZE = 100; private static final Set SET = IntStream.range(0, SIZE).boxed().collect(Collectors.toSet()); - private static final Map MAP = StableValue.map(SET, Function.identity()); - private static final Function FUNCTION = StableValue.function(SET, Function.identity()); + private static final Map MAP = Map.ofLazy(SET, Function.identity()); + private static final Map MAP_ENUM = Map.ofLazy(EnumSet.allOf(MyEnum.class), MyEnum::ordinal); + private static final Map> MAP_ENUM_OPTIONAL = Map.ofLazy(EnumSet.allOf(MyEnum.class), e -> Optional.of(e.ordinal())); - private final Map map = StableValue.map(SET, Function.identity()); - private final Function function = StableValue.function(SET, Function.identity()); + private final Map map = Map.ofLazy(SET, Function.identity()); + private final Map mapEnum = Map.ofLazy(EnumSet.allOf(MyEnum.class), MyEnum::ordinal); @Benchmark public int map() { @@ -71,18 +73,25 @@ public class StableFunctionSingleBenchmark { } @Benchmark - public int function() { - return function.apply(1); + public int mapEnum() { + return mapEnum.get(MyEnum.BAR); } @Benchmark - public int staticSMap() { + public int staticMap() { return MAP.get(1); } @Benchmark - public int staticIntFunction() { - return FUNCTION.apply(1); + public int staticMapEnum() { + return MAP_ENUM.get(MyEnum.BAR); } + @Benchmark + public int staticMapEnumOptional() { + return MAP_ENUM_OPTIONAL.get(MyEnum.BAR).orElseThrow(); + } + + private enum MyEnum {FOO, BAR} + } diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableMethodHandleBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableMethodHandleBenchmark.java index 95721d88cec..c61e7b1861f 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableMethodHandleBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableMethodHandleBenchmark.java @@ -35,22 +35,18 @@ import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; -import java.lang.classfile.CodeBuilder; -import java.lang.classfile.constantpool.ConstantPoolBuilder; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; - -import static java.lang.constant.ConstantDescs.*; +import java.lang.LazyConstant; /** - * Benchmark measuring StableValue performance + * Benchmark measuring lazy value performance */ @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) @@ -64,17 +60,15 @@ import static java.lang.constant.ConstantDescs.*; public class StableMethodHandleBenchmark { private static final MethodHandle FINAL_MH = identityHandle(); - private static final StableValue STABLE_MH; + private static final LazyConstant STABLE_MH = LazyConstant.of(StableMethodHandleBenchmark::identityHandle); private static /* intentionally not final */ MethodHandle mh = identityHandle(); private static final Dcl DCL = new Dcl<>(StableMethodHandleBenchmark::identityHandle); private static final AtomicReference ATOMIC_REFERENCE = new AtomicReference<>(identityHandle()); private static final Map MAP = new ConcurrentHashMap<>(); - private static final Map STABLE_MAP = StableValue.map(Set.of("identityHandle"), _ -> identityHandle()); + private static final Map STABLE_MAP = Map.ofLazy(Set.of("identityHandle"), _ -> identityHandle()); static { - STABLE_MH = StableValue.of(); - STABLE_MH.setOrThrow(identityHandle()); MAP.put("identityHandle", identityHandle()); } @@ -110,7 +104,7 @@ public class StableMethodHandleBenchmark { @Benchmark public int stableMh() throws Throwable { - return (int) STABLE_MH.orElseThrow().invokeExact(1); + return (int) STABLE_MH.get().invokeExact(1); } static MethodHandle identityHandle() { diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableSupplierBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableSupplierBenchmark.java index 883f13da05a..59aaa9c014e 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableSupplierBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableSupplierBenchmark.java @@ -36,10 +36,10 @@ import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; import java.util.concurrent.TimeUnit; -import java.util.function.Supplier; +import java.lang.LazyConstant; /** - * Benchmark measuring StableValue performance + * Benchmark measuring lazy constant performance */ @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) @@ -56,39 +56,24 @@ public class StableSupplierBenchmark { private static final int VALUE = 42; private static final int VALUE2 = 23; - private static final StableValue STABLE = init(StableValue.of(), VALUE); - private static final StableValue STABLE2 = init(StableValue.of(), VALUE2); - private static final Supplier SUPPLIER = StableValue.supplier(() -> VALUE); - private static final Supplier SUPPLIER2 = StableValue.supplier(() -> VALUE); + private static final LazyConstant STABLE = init(VALUE); + private static final LazyConstant STABLE2 = init(VALUE2); - private final StableValue stable = init(StableValue.of(), VALUE); - private final StableValue stable2 = init(StableValue.of(), VALUE2); - private final Supplier supplier = StableValue.supplier(() -> VALUE); - private final Supplier supplier2 = StableValue.supplier(() -> VALUE2); + private final LazyConstant stable = init(VALUE); + private final LazyConstant stable2 = init(VALUE2); @Benchmark public int stable() { - return stable.orElseThrow() + stable2.orElseThrow(); - } - - @Benchmark - public int supplier() { - return supplier.get() + supplier2.get(); + return stable.get() + stable2.get(); } @Benchmark public int staticStable() { - return STABLE.orElseThrow() + STABLE2.orElseThrow(); + return STABLE.get() + STABLE2.get(); } - @Benchmark - public int staticSupplier() { - return SUPPLIER.get() + SUPPLIER2.get(); - } - - private static StableValue init(StableValue m, Integer value) { - m.trySet(value); - return m; + private static LazyConstant init(Integer value) { + return LazyConstant.of(() -> value); } } diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java index 505cfffdc2c..5323af36c22 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java @@ -25,12 +25,14 @@ package org.openjdk.bench.java.lang.stable; import org.openjdk.jmh.annotations.*; +import java.util.Optional; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; +import java.lang.LazyConstant; import java.util.function.Supplier; /** - * Benchmark measuring StableValue performance + * Benchmark measuring lazy constant performance */ @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) @@ -47,10 +49,10 @@ public class StableValueBenchmark { private static final int VALUE = 42; private static final int VALUE2 = 23; - private static final StableValue STABLE = init(StableValue.of(), VALUE); - private static final StableValue STABLE2 = init(StableValue.of(), VALUE2); - private static final StableValue DCL = init(StableValue.of(), VALUE); - private static final StableValue DCL2 = init(StableValue.of(), VALUE2); + private static final LazyConstant STABLE = init(VALUE); + private static final LazyConstant STABLE2 = init(VALUE2); + private static final Supplier DCL = new Dcl<>(() -> VALUE); + private static final Supplier DCL2 = new Dcl<>(() -> VALUE2); private static final AtomicReference ATOMIC = new AtomicReference<>(VALUE); private static final AtomicReference ATOMIC2 = new AtomicReference<>(VALUE2); private static final Holder HOLDER = new Holder(VALUE); @@ -58,10 +60,13 @@ public class StableValueBenchmark { private static final RecordHolder RECORD_HOLDER = new RecordHolder(VALUE); private static final RecordHolder RECORD_HOLDER2 = new RecordHolder(VALUE2); - private final StableValue stable = init(StableValue.of(), VALUE); - private final StableValue stable2 = init(StableValue.of(), VALUE2); - private final StableValue stableNull = StableValue.of(); - private final StableValue stableNull2 = StableValue.of(); + private static final LazyConstant> OPTIONAL_42 = LazyConstant.of(() -> Optional.of(42)); + private static final LazyConstant> OPTIONAL_42_2 = LazyConstant.of(() -> Optional.of(42)); + private static final LazyConstant> OPTIONAL_EMPTY = LazyConstant.of(Optional::empty); + private static final LazyConstant> OPTIONAL_EMPTY2 = LazyConstant.of(Optional::empty); + + private final LazyConstant stable = init(VALUE); + private final LazyConstant stable2 = init(VALUE2); private final Supplier dcl = new Dcl<>(() -> VALUE); private final Supplier dcl2 = new Dcl<>(() -> VALUE2); private final AtomicReference atomic = new AtomicReference<>(VALUE); @@ -69,13 +74,6 @@ public class StableValueBenchmark { private final Supplier supplier = () -> VALUE; private final Supplier supplier2 = () -> VALUE2; - - @Setup - public void setup() { - stableNull.trySet(null); - stableNull2.trySet(null); - } - @Benchmark public int atomic() { return atomic.get() + atomic2.get(); @@ -88,12 +86,7 @@ public class StableValueBenchmark { @Benchmark public int stable() { - return stable.orElseThrow() + stable2.orElseThrow(); - } - - @Benchmark - public int stableNull() { - return (stableNull.orElseThrow() == null ? VALUE : VALUE2) + (stableNull2.orElseThrow() == null ? VALUE : VALUE2); + return stable.get() + stable2.get(); } // Reference case @@ -109,7 +102,7 @@ public class StableValueBenchmark { @Benchmark public int staticDcl() { - return DCL.orElseThrow() + DCL2.orElseThrow(); + return DCL.get() + DCL2.get(); } @Benchmark @@ -117,6 +110,16 @@ public class StableValueBenchmark { return HOLDER.get() + HOLDER2.get(); } + @Benchmark + public int staticOptional42() { + return OPTIONAL_42.get().orElseThrow() + OPTIONAL_42_2.get().orElseThrow(); + } + + @Benchmark + public boolean staticOptionalEmpty() { + return OPTIONAL_EMPTY.get().isEmpty() ^ OPTIONAL_EMPTY2.get().isEmpty(); + } + @Benchmark public int staticRecordHolder() { return RECORD_HOLDER.get() + RECORD_HOLDER2.get(); @@ -124,50 +127,46 @@ public class StableValueBenchmark { @Benchmark public int staticStable() { - return STABLE.orElseThrow() + STABLE2.orElseThrow(); + return STABLE.get() + STABLE2.get(); } - private static StableValue init(StableValue m, Integer value) { - m.trySet(value); - return m; + private static LazyConstant init(Integer value) { + return LazyConstant.of(() -> value); } private static final class Holder { - private final StableValue delegate = StableValue.of(); + private final LazyConstant delegate; Holder(int value) { - delegate.setOrThrow(value); + delegate = LazyConstant.of(() -> value); } int get() { - return delegate.orElseThrow(); + return delegate.get(); } } - private record RecordHolder(StableValue delegate) { + private record RecordHolder(LazyConstant delegate) { RecordHolder(int value) { - this(StableValue.of()); - delegate.setOrThrow(value); + this(LazyConstant.of(() -> value)); } int get() { - return delegate.orElseThrow(); + return delegate.get(); } } - // Handles null values public static class Dcl implements Supplier { private final Supplier supplier; private volatile V value; - private boolean bound; public Dcl(Supplier supplier) { this.supplier = supplier; @@ -177,15 +176,10 @@ public class StableValueBenchmark { public V get() { V v = value; if (v == null) { - if (!bound) { - synchronized (this) { - v = value; - if (v == null) { - if (!bound) { - value = v = supplier.get(); - bound = true; - } - } + synchronized (this) { + v = value; + if (v == null) { + value = v = supplier.get(); } } } diff --git a/test/micro/org/openjdk/bench/java/lang/stable/VarHandleHolderBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/VarHandleHolderBenchmark.java index 65d53a42e88..759cb25b592 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/VarHandleHolderBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/VarHandleHolderBenchmark.java @@ -41,7 +41,6 @@ import java.lang.invoke.VarHandle; import java.util.Map; import java.util.Set; import java.util.function.Function; -import java.util.function.Supplier; import static java.lang.foreign.MemoryLayout.PathElement.groupElement; import static java.util.concurrent.TimeUnit.*; @@ -75,8 +74,8 @@ public class VarHandleHolderBenchmark { private static final VarHandle VH_X = VAR_HANDLE_FUNCTION.apply("x"); private static final VarHandle VH_Y = VAR_HANDLE_FUNCTION.apply("y"); - private static final Supplier SV_X = StableValue.supplier(() -> VAR_HANDLE_FUNCTION.apply("x")); - private static final Supplier SV_Y = StableValue.supplier(() -> VAR_HANDLE_FUNCTION.apply("y")); + private static final LazyConstant SV_X = LazyConstant.of(() -> VAR_HANDLE_FUNCTION.apply("x")); + private static final LazyConstant SV_Y = LazyConstant.of(() -> VAR_HANDLE_FUNCTION.apply("y")); private static final Map U_MAP = Map.of( "x", VH_X, @@ -86,13 +85,11 @@ public class VarHandleHolderBenchmark { "x", LAYOUT.varHandle(groupElement("x")), "y", LAYOUT.varHandle(groupElement("y"))); - private static final Map S_MAP = StableValue.map( + private static final Map S_MAP = Map.ofLazy( Set.of("x", "y"), VAR_HANDLE_FUNCTION); - private static final Function S_FUN = StableValue.function( - Set.of("x", "y"), - VAR_HANDLE_FUNCTION); + private static final Function S_FUN = S_MAP::get; private static final MemorySegment confined; static { From 36b66e13c8eca8e460bfd6d900f139408aff9d43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20Sj=C3=B6len?= Date: Tue, 18 Nov 2025 13:05:57 +0000 Subject: [PATCH 105/418] 8371778: Make MallocMemorySummary::_snapshot a DeferredStatic Reviewed-by: phubner, azafari --- src/hotspot/share/nmt/mallocTracker.cpp | 4 ++-- src/hotspot/share/nmt/mallocTracker.hpp | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/hotspot/share/nmt/mallocTracker.cpp b/src/hotspot/share/nmt/mallocTracker.cpp index a61a27db25d..2cf5034c0bf 100644 --- a/src/hotspot/share/nmt/mallocTracker.cpp +++ b/src/hotspot/share/nmt/mallocTracker.cpp @@ -45,7 +45,7 @@ #include "utilities/ostream.hpp" #include "utilities/vmError.hpp" -MallocMemorySnapshot MallocMemorySummary::_snapshot; +DeferredStatic MallocMemorySummary::_snapshot; void MemoryCounter::update_peak(size_t size, size_t cnt) { size_t peak_sz = peak_size(); @@ -101,7 +101,7 @@ void MallocMemorySnapshot::make_adjustment() { } void MallocMemorySummary::initialize() { - // Uses placement new operator to initialize static area. + _snapshot.initialize(); MallocLimitHandler::initialize(MallocLimit); } diff --git a/src/hotspot/share/nmt/mallocTracker.hpp b/src/hotspot/share/nmt/mallocTracker.hpp index 0ead41f2411..fc03faf7212 100644 --- a/src/hotspot/share/nmt/mallocTracker.hpp +++ b/src/hotspot/share/nmt/mallocTracker.hpp @@ -30,6 +30,7 @@ #include "nmt/memTag.hpp" #include "nmt/nmtCommon.hpp" #include "runtime/atomicAccess.hpp" +#include "utilities/deferredStatic.hpp" #include "utilities/nativeCallStack.hpp" class outputStream; @@ -204,7 +205,7 @@ class MallocMemorySnapshot { class MallocMemorySummary : AllStatic { private: // Reserve memory for placement of MallocMemorySnapshot object - static MallocMemorySnapshot _snapshot; + static DeferredStatic _snapshot; static bool _have_limits; // Called when a total limit break was detected. @@ -251,7 +252,7 @@ class MallocMemorySummary : AllStatic { } static MallocMemorySnapshot* as_snapshot() { - return &_snapshot; + return _snapshot.get(); } // MallocLimit: returns true if allocating s bytes on f would trigger From 2e68b79a3973c8a3dde6b47f19b19c0c7faacc51 Mon Sep 17 00:00:00 2001 From: Jan Lahoda Date: Tue, 18 Nov 2025 13:55:42 +0000 Subject: [PATCH 106/418] 8364991: Incorrect not-exhaustive error Reviewed-by: vromero --- .../javac/comp/ExhaustivenessComputer.java | 142 ++++++++++---- .../tools/javac/patterns/Exhaustiveness.java | 184 +++++++++++++++++- 2 files changed, 285 insertions(+), 41 deletions(-) diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ExhaustivenessComputer.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ExhaustivenessComputer.java index 3b66ac010cf..036e86dff5e 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ExhaustivenessComputer.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ExhaustivenessComputer.java @@ -39,6 +39,7 @@ import com.sun.tools.javac.tree.JCTree.*; import com.sun.tools.javac.code.Kinds.Kind; import com.sun.tools.javac.code.Type.TypeVar; +import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.function.Predicate; @@ -60,6 +61,7 @@ public class ExhaustivenessComputer { private final Types types; private final Check chk; private final Infer infer; + private final Map, Boolean> isSubtypeCache = new HashMap<>(); public static ExhaustivenessComputer instance(Context context) { ExhaustivenessComputer instance = context.get(exhaustivenessKey); @@ -120,6 +122,7 @@ public class ExhaustivenessComputer { } } Set patterns = patternSet; + Set> seenFallback = new HashSet<>(); boolean useHashes = true; try { boolean repeat = true; @@ -141,7 +144,7 @@ public class ExhaustivenessComputer { //but hashing in reduceNestedPatterns will not allow that //disable the use of hashing, and use subtyping in //reduceNestedPatterns to handle situations like this: - repeat = useHashes; + repeat = useHashes && seenFallback.add(updatedPatterns); useHashes = false; } else { //if a reduction happened, make sure hashing in reduceNestedPatterns @@ -154,6 +157,8 @@ public class ExhaustivenessComputer { } catch (CompletionFailure cf) { chk.completionError(selector.pos(), cf); return true; //error recovery + } finally { + isSubtypeCache.clear(); } } @@ -211,10 +216,9 @@ public class ExhaustivenessComputer { //if a binding pattern for clazz already exists, no need to analyze it again: !existingBindings.contains(clazz)) { //do not reduce to types unrelated to the selector type: - Type clazzErasure = types.erasure(clazz.type); + Type clazzType = clazz.type; if (components(selectorType).stream() - .map(types::erasure) - .noneMatch(c -> types.isSubtype(clazzErasure, c))) { + .noneMatch(c -> isSubtypeErasure(clazzType, c))) { continue; } @@ -238,18 +242,18 @@ public class ExhaustivenessComputer { //cover using bpOther: //all types that are permitted subtypes of bpOther's type: - pendingPermitted.removeIf(pending -> types.isSubtype(types.erasure(pending.type), - types.erasure(bpOther.type))); + pendingPermitted.removeIf(pending -> isSubtypeErasure(pending.type, + bpOther.type)); if (bpOther.type.tsym.isAbstract()) { //all types that are in a diamond hierarchy with bpOther's type //i.e. there's a common subtype of the given type and bpOther's type: Predicate check = pending -> permitted.stream() - .filter(perm -> types.isSubtype(types.erasure(perm.type), - types.erasure(bpOther.type))) - .filter(perm -> types.isSubtype(types.erasure(perm.type), - types.erasure(pending.type))) + .filter(perm -> isSubtypeErasure(perm.type, + bpOther.type)) + .filter(perm -> isSubtypeErasure(perm.type, + pending.type)) .findAny() .isPresent(); @@ -374,30 +378,15 @@ public class ExhaustivenessComputer { join.append(rpOne); - NEXT_PATTERN: for (int nextCandidate = 0; - nextCandidate < candidatesArr.length; - nextCandidate++) { + for (int nextCandidate = 0; nextCandidate < candidatesArr.length; nextCandidate++) { if (firstCandidate == nextCandidate) { continue; } RecordPattern rpOther = candidatesArr[nextCandidate]; - if (rpOne.recordType.tsym == rpOther.recordType.tsym) { - for (int i = 0; i < rpOne.nested.length; i++) { - if (i != mismatchingCandidate) { - if (!rpOne.nested[i].equals(rpOther.nested[i])) { - if (useHashes || - //when not using hashes, - //check if rpOne.nested[i] is - //a subtype of rpOther.nested[i]: - !(rpOne.nested[i] instanceof BindingPattern bpOne) || - !(rpOther.nested[i] instanceof BindingPattern bpOther) || - !types.isSubtype(types.erasure(bpOne.type), types.erasure(bpOther.type))) { - continue NEXT_PATTERN; - } - } - } - } + + if (rpOne.recordType.tsym == rpOther.recordType.tsym && + nestedComponentsEquivalent(rpOne, rpOther, mismatchingCandidate, useHashes)) { join.append(rpOther); } } @@ -418,9 +407,11 @@ public class ExhaustivenessComputer { PatternDescription[] newNested = Arrays.copyOf(rpOne.nested, rpOne.nested.length); newNested[mismatchingCandidateFin] = nested; - current.add(new RecordPattern(rpOne.recordType(), + RecordPattern nue = new RecordPattern(rpOne.recordType(), rpOne.fullComponentTypes(), - newNested)); + newNested, + new HashSet<>(join)); + current.add(nue); } } } @@ -437,6 +428,70 @@ public class ExhaustivenessComputer { return patterns; } + /* Returns true if all nested components of existing and candidate are + * equivalent (if useHashes == true), or "substitutable" (if useHashes == false). + * A candidate pattern is "substitutable" if it is a binding pattern, and: + * - it's type is a supertype of the existing pattern's type + * - it was produced by a reduction from a record pattern that is equivalent to + * the existing pattern + */ + private boolean nestedComponentsEquivalent(RecordPattern existing, + RecordPattern candidate, + int mismatchingCandidate, + boolean useHashes) { + NEXT_NESTED: + for (int i = 0; i < existing.nested.length; i++) { + if (i != mismatchingCandidate) { + if (!existing.nested[i].equals(candidate.nested[i])) { + if (useHashes) { + return false; + } + //when not using hashes, + //check if rpOne.nested[i] is + //a subtype of rpOther.nested[i]: + if (!(candidate.nested[i] instanceof BindingPattern nestedCandidate)) { + return false; + } + if (existing.nested[i] instanceof BindingPattern nestedExisting) { + if (!isSubtypeErasure(nestedExisting.type, nestedCandidate.type)) { + return false; + } + } else if (existing.nested[i] instanceof RecordPattern nestedExisting) { + java.util.List pendingReplacedPatterns = + new ArrayList<>(nestedCandidate.sourcePatterns()); + + while (!pendingReplacedPatterns.isEmpty()) { + PatternDescription currentReplaced = pendingReplacedPatterns.removeLast(); + + if (nestedExisting.equals(currentReplaced)) { + //candidate.nested[i] is substitutable for existing.nested[i] + //continue with the next nested pattern: + continue NEXT_NESTED; + } + + pendingReplacedPatterns.addAll(currentReplaced.sourcePatterns()); + } + + return false; + } else { + return false; + } + } + } + } + + return true; + } + + /*The same as types.isSubtype(types.erasure(t), types.erasure(s)), but cached. + */ + private boolean isSubtypeErasure(Type t, Type s) { + Pair key = Pair.of(t, s); + + return isSubtypeCache.computeIfAbsent(key, _ -> + types.isSubtype(types.erasure(t), types.erasure(s))); + } + /* In the set of patterns, find those for which, given: * $record($nested1, $nested2, ...) * all the $nestedX pattern cover the given record component, @@ -480,9 +535,11 @@ public class ExhaustivenessComputer { covered &= checkCovered(componentType[i], List.of(newNested)); } if (covered) { - return new BindingPattern(rpOne.recordType); + PatternDescription pd = new BindingPattern(rpOne.recordType, Set.of(pattern)); + return pd; } else if (reducedNestedPatterns != null) { - return new RecordPattern(rpOne.recordType, rpOne.fullComponentTypes(), reducedNestedPatterns); + PatternDescription pd = new RecordPattern(rpOne.recordType, rpOne.fullComponentTypes(), reducedNestedPatterns, Set.of(pattern)); + return pd; } } return pattern; @@ -517,7 +574,9 @@ public class ExhaustivenessComputer { return false; } - sealed interface PatternDescription { } + sealed interface PatternDescription { + public Set sourcePatterns(); + } public PatternDescription makePatternDescription(Type selectorType, JCPattern pattern) { if (pattern instanceof JCBindingPattern binding) { Type type = !selectorType.isPrimitive() && types.isSubtype(selectorType, binding.type) @@ -552,7 +611,12 @@ public class ExhaustivenessComputer { throw Assert.error(); } } - record BindingPattern(Type type) implements PatternDescription { + record BindingPattern(Type type, Set sourcePatterns) implements PatternDescription { + + public BindingPattern(Type type) { + this(type, Set.of()); + } + @Override public int hashCode() { return type.tsym.hashCode(); @@ -567,10 +631,14 @@ public class ExhaustivenessComputer { return type.tsym + " _"; } } - record RecordPattern(Type recordType, int _hashCode, Type[] fullComponentTypes, PatternDescription... nested) implements PatternDescription { + record RecordPattern(Type recordType, int _hashCode, Type[] fullComponentTypes, PatternDescription[] nested, Set sourcePatterns) implements PatternDescription { public RecordPattern(Type recordType, Type[] fullComponentTypes, PatternDescription[] nested) { - this(recordType, hashCode(-1, recordType, nested), fullComponentTypes, nested); + this(recordType, fullComponentTypes, nested, Set.of()); + } + + public RecordPattern(Type recordType, Type[] fullComponentTypes, PatternDescription[] nested, Set sourcePatterns) { + this(recordType, hashCode(-1, recordType, nested), fullComponentTypes, nested, sourcePatterns); } @Override diff --git a/test/langtools/tools/javac/patterns/Exhaustiveness.java b/test/langtools/tools/javac/patterns/Exhaustiveness.java index f328765b579..84e67855b3b 100644 --- a/test/langtools/tools/javac/patterns/Exhaustiveness.java +++ b/test/langtools/tools/javac/patterns/Exhaustiveness.java @@ -23,7 +23,7 @@ /** * @test - * @bug 8262891 8268871 8274363 8281100 8294670 8311038 8311815 8325215 8333169 8327368 8366968 + * @bug 8262891 8268871 8274363 8281100 8294670 8311038 8311815 8325215 8333169 8327368 8366968 8364991 * @summary Check exhaustiveness of switches over sealed types. * @library /tools/lib * @modules jdk.compiler/com.sun.tools.javac.api @@ -40,6 +40,7 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -1543,7 +1544,7 @@ public class Exhaustiveness extends TestRunner { private static final int NESTING_CONSTANT = 4; Set createDeeplyNestedVariants() { - Set variants = new HashSet<>(); + Set variants = new LinkedHashSet<>(); variants.add("C _"); variants.add("R(I _, I _, I _)"); for (int n = 0; n < NESTING_CONSTANT; n++) { @@ -1636,10 +1637,11 @@ public class Exhaustiveness extends TestRunner { @Test public void testDeeplyNestedNotExhaustive(Path base) throws Exception { List variants = createDeeplyNestedVariants().stream().collect(Collectors.toCollection(ArrayList::new)); - variants.remove((int) (Math.random() * variants.size())); + int removed = (int) (Math.random() * variants.size()); + variants.remove(removed); String code = testCodeForVariants(variants); - System.err.println("analyzing:"); + System.err.println("analyzing (removed: " + removed + "):"); System.err.println(code); doTest(base, new String[0], @@ -2311,6 +2313,180 @@ public class Exhaustiveness extends TestRunner { "1 error"); } + @Test //JDK-8364991 + public void testDifferentReductionPaths(Path base) throws Exception { + doTest(base, + new String[0], + """ + package test; + public class Test { + private int test(Root r) { + return switch (r) { + case Root(R1 _, _, _) -> 0; + case Root(R2 _, R1 _, _) -> 0; + case Root(R2 _, R2 _, R1 _) -> 0; + case Root(R2 _, R2(R1 _, R1 _), R2(R1 _, R1 _)) -> 0; + case Root(R2 _, R2(R1 _, R1 _), R2(R1 _, R2 _)) -> 0; + case Root(R2 _, R2(R1 _, R1 _), R2(R2 _, R1 _)) -> 0; + case Root(R2 _, R2(R1 _, R1 _), R2(R2 _, R2 _)) -> 0; + case Root(R2 _, R2(R1 _, R2 _), R2(R1 _, R1 _)) -> 0; + case Root(R2 _, R2(R1 _, R2 _), R2(R1 _, R2 _)) -> 0; + case Root(R2 _, R2(R1 _, R2 _), R2(R2 _, R1 _)) -> 0; + case Root(R2 _, R2(R1 _, R2 _), R2(R2 _, R2 _)) -> 0; + case Root(R2 _, R2(R2 _, R1 _), R2(R1 _, R1 _)) -> 0; + case Root(R2 _, R2(R2 _, R1 _), R2(R1 _, R2 _)) -> 0; + case Root(R2 _, R2(R2 _, R1 _), R2(R2 _, R1 _)) -> 0; + case Root(R2 _, R2(R2 _, R1 _), R2(R2 _, R2 _)) -> 0; + case Root(R2 _, R2(R2 _, R2 _), R2(R1 _, R1 _)) -> 0; + case Root(R2 _, R2(R2 _, R2 _), R2(R1 _, R2 _)) -> 0; + case Root(R2 _, R2(R2 _, R2 _), R2(R2 _, R1 _)) -> 0; + case Root(R2 _, R2(R2 _, R2 _), R2 _) -> 0; //functionally equivalent to: Root(R2 _, R2(R2 _, R2 _), R2(R2 _, R2 _)) + }; + } + sealed interface Base {} + record R1() implements Base {} + record R2(Base b1, Base b2) implements Base {} + record Root(Base b1, Base b2, Base b3) {} + } + """); + doTest(base, + new String[0], + """ + package test; + public class Test { + private int test(Root r) { + return switch (r) { + case Root(R1 _, _, _) -> 0; + case Root(R2 _, R1 _, _) -> 0; + case Root(R2 _, R2 _, R1 _) -> 0; + case Root(R2 _, R2(R1 _, R1 _), R2(R1 _, R1 _)) -> 0; + case Root(R2 _, R2(R1 _, R1 _), R2(R1 _, R2 _)) -> 0; + case Root(R2 _, R2(R1 _, R1 _), R2(R2 _, R1 _)) -> 0; + case Root(R2 _, R2(R1 _, R1 _), R2(R2 _, R2 _)) -> 0; + case Root(R2 _, R2(R1 _, R2 _), R2(R1 _, R1 _)) -> 0; + case Root(R2 _, R2(R1 _, R2 _), R2(R1 _, R2 _)) -> 0; + case Root(R2 _, R2(R1 _, R2 _), R2(R2 _, R1 _)) -> 0; + case Root(R2 _, R2(R1 _, R2 _), R2(R2 _, R2 _)) -> 0; + case Root(R2 _, R2(R2 _, R1 _), R2(R1 _, R1 _)) -> 0; + case Root(R2 _, R2(R2 _, R1 _), R2(R1 _, R2 _)) -> 0; + case Root(R2 _, R2(R2 _, R1 _), R2(R2 _, R1 _)) -> 0; + case Root(R2 _, R2(R2 _, R1 _), R2(R2 _, R2 _)) -> 0; + case Root(R2 _, R2(R2 _, R2 _), R2(R1 _, R1 _)) -> 0; + case Root(R2 _, R2(R2 _, R2 _), R2(R1 _, R2 _)) -> 0; + case Root(R2 _, R2(R2 _, R2 _), R2(R2 _, R1 _)) -> 0; + case Root(R2 _, R2(R2 _, R2 _), R2 _) -> 0; //functionally equivalent to: Root(R2 _, R2(R2 _, R2 _), R2(R2 _, R2 _)) + }; + } + sealed interface Base {} + record R1() implements Base {} + record R2(Base b1, Base b2) implements Base {} + record Root(T b1, T b2, T b3) {} + } + """); + } + + @Test //JDK-8364991 + public void testDifferentReductionPathsSimplified(Path base) throws Exception { + doTest(base, + new String[0], + """ + package test; + public class Test { + private int test1(Root r) { + return switch (r) { + case Root(R2(R1 _), R2(R1 _)) -> 0; + case Root(R2(R1 _), R2(R2 _)) -> 0; + case Root(R2(R2 _), R2(R1 _)) -> 0; + case Root(R2(R2 _), R2 _) -> 0; + }; + } + private int test2(Root r) { + return switch (r) { + case Root(R2(R1 _), R2(R1 _)) -> 0; + case Root(R2(R2 _), R2(R1 _)) -> 0; + case Root(R2(R1 _), R2(R2 _)) -> 0; + case Root(R2 _, R2(R2 _)) -> 0; + }; + } + sealed interface Base {} + record R1() implements Base {} + record R2(Base b1) implements Base {} + record Root(R2 b2, R2 b3) {} + } + """); + doTest(base, + new String[0], + """ + package test; + public class Test { + private int test1(Root> r) { + return switch (r) { + case Root(R2(R1 _), R2(R1 _)) -> 0; + case Root(R2(R1 _), R2(R2 _)) -> 0; + case Root(R2(R2 _), R2(R1 _)) -> 0; + case Root(R2(R2 _), R2 _) -> 0; + }; + } + private int test2(Root> r) { + return switch (r) { + case Root(R2(R1 _), R2(R1 _)) -> 0; + case Root(R2(R2 _), R2(R1 _)) -> 0; + case Root(R2(R1 _), R2(R2 _)) -> 0; + case Root(R2 _, R2(R2 _)) -> 0; + }; + } + sealed interface Base {} + record R1() implements Base {} + record R2(T b1) implements Base {} + record Root(T b2, T b3) {} + } + """); + } + + @Test //JDK-8364991 + public void testBindingPatternDoesNotStandInPlaceOfRecordPatterns(Path base) throws Exception { + doTest(base, + new String[0], + """ + package test; + public class Test { + private int test(Root r) { + return switch (r) { + case Root(R2 _, R2(R1 _)) -> 0; + case Root(R2(R1 _), R2(R2 _)) -> 0; + case Root(R2(R2 _), R2 _) -> 0; + }; + } + sealed interface Base {} + record R1() implements Base {} + record R2(Base b) implements Base {} + record Root(R2 b2, R2 b3) {} + } + """, + "Test.java:4:16: compiler.err.not.exhaustive", + "1 error"); + doTest(base, + new String[0], + """ + package test; + public class Test { + private int test(Root> r) { + return switch (r) { + case Root(R2 _, R2(R1 _)) -> 0; + case Root(R2(R1 _), R2(R2 _)) -> 0; + case Root(R2(R2 _), R2 _) -> 0; + }; + } + sealed interface Base {} + record R1() implements Base {} + record R2(T b) implements Base {} + record Root(T b2, T b3) {} + } + """, + "Test.java:4:16: compiler.err.not.exhaustive", + "1 error"); + } + private void doTest(Path base, String[] libraryCode, String testCode, String... expectedErrors) throws IOException { doTest(base, libraryCode, testCode, false, expectedErrors); } From dcba014ad56eae753c25c579fb30bb8ecfab69af Mon Sep 17 00:00:00 2001 From: Yasumasa Suenaga Date: Tue, 18 Nov 2025 14:44:14 +0000 Subject: [PATCH 107/418] 8371967: Add Visual Studio 2026 to build toolchain for Windows Reviewed-by: erikj --- doc/building.html | 2 +- doc/building.md | 2 +- make/autoconf/toolchain_microsoft.m4 | 17 ++++++++++++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/doc/building.html b/doc/building.html index 99eb3e0c473..19313ebf43a 100644 --- a/doc/building.html +++ b/doc/building.html @@ -668,7 +668,7 @@ update.

    (Note that this version is often presented as "MSVC 14.28", and reported by cl.exe as 19.28.) Older versions will not be accepted by configure and will not work. The maximum accepted version -of Visual Studio is 2022.

    +of Visual Studio is 2026.

    If you have multiple versions of Visual Studio installed, configure will by default pick the latest. You can request a specific version to be used by setting diff --git a/doc/building.md b/doc/building.md index 047255d1848..1fbd395a9d1 100644 --- a/doc/building.md +++ b/doc/building.md @@ -468,7 +468,7 @@ available for this update. The minimum accepted version is Visual Studio 2019 version 16.8. (Note that this version is often presented as "MSVC 14.28", and reported by cl.exe as 19.28.) Older versions will not be accepted by `configure` and will not work. -The maximum accepted version of Visual Studio is 2022. +The maximum accepted version of Visual Studio is 2026. If you have multiple versions of Visual Studio installed, `configure` will by default pick the latest. You can request a specific version to be used by diff --git a/make/autoconf/toolchain_microsoft.m4 b/make/autoconf/toolchain_microsoft.m4 index 17ad2666b3a..f577cf1a2a1 100644 --- a/make/autoconf/toolchain_microsoft.m4 +++ b/make/autoconf/toolchain_microsoft.m4 @@ -25,7 +25,7 @@ ################################################################################ # The order of these defines the priority by which we try to find them. -VALID_VS_VERSIONS="2022 2019" +VALID_VS_VERSIONS="2022 2019 2026" VS_DESCRIPTION_2019="Microsoft Visual Studio 2019" VS_VERSION_INTERNAL_2019=142 @@ -57,6 +57,21 @@ VS_SDK_PLATFORM_NAME_2022= VS_SUPPORTED_2022=true VS_TOOLSET_SUPPORTED_2022=true +VS_DESCRIPTION_2026="Microsoft Visual Studio 2026" +VS_VERSION_INTERNAL_2026=145 +VS_MSVCR_2026=vcruntime140.dll +VS_VCRUNTIME_1_2026=vcruntime140_1.dll +VS_MSVCP_2026=msvcp140.dll +VS_ENVVAR_2026="VS180COMNTOOLS" +VS_USE_UCRT_2026="true" +VS_VS_INSTALLDIR_2026="Microsoft Visual Studio/18" +VS_EDITIONS_2026="BuildTools Community Professional Enterprise" +VS_SDK_INSTALLDIR_2026= +VS_VS_PLATFORM_NAME_2026="v145" +VS_SDK_PLATFORM_NAME_2026= +VS_SUPPORTED_2026=true +VS_TOOLSET_SUPPORTED_2026=true + ################################################################################ AC_DEFUN([TOOLCHAIN_CHECK_POSSIBLE_VISUAL_STUDIO_ROOT], From 43040f30a72591a37deb9a54ab7723988c1e4b51 Mon Sep 17 00:00:00 2001 From: Brian Burkhalter Date: Tue, 18 Nov 2025 15:11:45 +0000 Subject: [PATCH 108/418] 8372012: java/nio/file/attribute/BasicFileAttributeView/SetTimesNanos.java should check ability to create links Reviewed-by: alanb, jpai --- .../attribute/BasicFileAttributeView/SetTimesNanos.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/jdk/java/nio/file/attribute/BasicFileAttributeView/SetTimesNanos.java b/test/jdk/java/nio/file/attribute/BasicFileAttributeView/SetTimesNanos.java index acdff692601..55a7ca10a6d 100644 --- a/test/jdk/java/nio/file/attribute/BasicFileAttributeView/SetTimesNanos.java +++ b/test/jdk/java/nio/file/attribute/BasicFileAttributeView/SetTimesNanos.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -67,8 +67,10 @@ public class SetTimesNanos { Path file = Files.createFile(dir.resolve("test.dat")); testNanos(file); - testNanosLink(false); - testNanosLink(true); + if (TestUtil.supportsSymbolicLinks(Path.of(""))) { + testNanosLink(false); + testNanosLink(true); + } } private static void testNanos(Path path) throws IOException { From b6d83eda6bfa76da98274aa3ad294759cb56d3a5 Mon Sep 17 00:00:00 2001 From: Chen Liang Date: Tue, 18 Nov 2025 15:14:20 +0000 Subject: [PATCH 109/418] 8371960: Missing null check in AnnotatedType annotation accessor methods Reviewed-by: alanb --- .../annotation/AnnotatedTypeFactory.java | 6 +- .../AnnotatedElementNullCheckTest.java | 101 ++++++++++++++++++ 2 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 test/jdk/java/lang/reflect/AnnotatedElement/AnnotatedElementNullCheckTest.java diff --git a/src/java.base/share/classes/sun/reflect/annotation/AnnotatedTypeFactory.java b/src/java.base/share/classes/sun/reflect/annotation/AnnotatedTypeFactory.java index aa69e29831d..b5db897b218 100644 --- a/src/java.base/share/classes/sun/reflect/annotation/AnnotatedTypeFactory.java +++ b/src/java.base/share/classes/sun/reflect/annotation/AnnotatedTypeFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2025, 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 @@ -128,7 +128,7 @@ public final class AnnotatedTypeFactory { private final Type type; private final LocationInfo location; private final TypeAnnotation[] allOnSameTargetTypeAnnotations; - private final Map, Annotation> annotations; + private final Map, Annotation> annotations; AnnotatedTypeBaseImpl(Type type, LocationInfo location, TypeAnnotation[] actualTypeAnnotations, TypeAnnotation[] allOnSameTargetTypeAnnotations) { @@ -162,11 +162,13 @@ public final class AnnotatedTypeFactory { @Override @SuppressWarnings("unchecked") public final T getDeclaredAnnotation(Class annotation) { + Objects.requireNonNull(annotation); return (T)annotations.get(annotation); } @Override public final T[] getDeclaredAnnotationsByType(Class annotation) { + Objects.requireNonNull(annotation); return AnnotationSupport.getDirectlyAndIndirectlyPresent(annotations, annotation); } diff --git a/test/jdk/java/lang/reflect/AnnotatedElement/AnnotatedElementNullCheckTest.java b/test/jdk/java/lang/reflect/AnnotatedElement/AnnotatedElementNullCheckTest.java new file mode 100644 index 00000000000..632dab252a9 --- /dev/null +++ b/test/jdk/java/lang/reflect/AnnotatedElement/AnnotatedElementNullCheckTest.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8371953 8371960 + * @summary Null checks for AnnotatedElement APIs. + * @run junit AnnotatedElementNullCheckTest + */ + +import java.lang.reflect.AnnotatedArrayType; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.AnnotatedParameterizedType; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; + +class AnnotatedElementNullCheckTest { + + // Return a stream of instances, each of a different implementation class + static AnnotatedElement[] implementations() throws Exception { + var objectHashCodeMethod = Object.class.getMethod("hashCode"); + var annotatedParameterizedType = (AnnotatedParameterizedType) Optional.class + .getMethod("ifPresent", Consumer.class) + .getAnnotatedParameterTypes()[0]; + var annotatedGenericArrayType = (AnnotatedArrayType) List.class + .getMethod("of", Object[].class) + .getAnnotatedParameterTypes()[0]; + record Rec(int a) {} + return new AnnotatedElement[] { + Object.class, + objectHashCodeMethod, + System.class.getField("out"), + Object.class.getConstructor(), + Object.class.getPackage(), + Optional.class.getTypeParameters()[0], + // AnnotatedType (direct) + objectHashCodeMethod.getAnnotatedReturnType(), + // AnnotatedParameterizedType + annotatedParameterizedType, + // AnnotatedArrayType + annotatedGenericArrayType, + // AnnotatedTypeVariable + annotatedGenericArrayType.getAnnotatedGenericComponentType(), + // AnnotatedWildcardType + annotatedParameterizedType.getAnnotatedActualTypeArguments()[0], + Rec.class.getRecordComponents()[0], + Object.class.getMethod("equals", Object.class).getParameters()[0], + Object.class.getModule(), + }; + } + + @Test + void ensureImplementationsDistinct() throws Throwable { + var set = new HashSet>(); + for (var impl : implementations()) { + var clazz = impl.getClass(); + if (!set.add(clazz)) { + fail("Duplicate implementation class %s in %s".formatted(clazz, impl)); + } + } + } + + @ParameterizedTest + @MethodSource("implementations") + void nullChecks(AnnotatedElement impl) { + assertThrows(NullPointerException.class, () -> impl.isAnnotationPresent(null)); + assertThrows(NullPointerException.class, () -> impl.getAnnotation(null)); + assertThrows(NullPointerException.class, () -> impl.getAnnotationsByType(null)); + assertThrows(NullPointerException.class, () -> impl.getDeclaredAnnotation(null)); + assertThrows(NullPointerException.class, () -> impl.getDeclaredAnnotationsByType(null)); + } +} From 1f99cf942449728cdeb9918b93fd9a97a51eb0b6 Mon Sep 17 00:00:00 2001 From: Chen Liang Date: Tue, 18 Nov 2025 15:14:49 +0000 Subject: [PATCH 110/418] 8372002: VarHandle for receiver's superclass instance fields fails describeConstable Reviewed-by: psandoz, jvernee --- .../classes/java/lang/invoke/VarHandles.java | 13 +++--- .../DescribeConstableTest.java | 45 +++++++++++++++---- .../VarHandles/describeConstable/p/C.java | 4 +- .../VarHandles/describeConstable/p/q/Q.java | 4 +- 4 files changed, 51 insertions(+), 15 deletions(-) diff --git a/src/java.base/share/classes/java/lang/invoke/VarHandles.java b/src/java.base/share/classes/java/lang/invoke/VarHandles.java index c97d44ba5d3..8c3e123f39a 100644 --- a/src/java.base/share/classes/java/lang/invoke/VarHandles.java +++ b/src/java.base/share/classes/java/lang/invoke/VarHandles.java @@ -166,12 +166,15 @@ final class VarHandles { static Field getFieldFromReceiverAndOffset(Class receiverType, long offset, Class fieldType) { - for (Field f : receiverType.getDeclaredFields()) { - if (Modifier.isStatic(f.getModifiers())) continue; + // The receiver may be a referenced class different from the declaring class + for (var declaringClass = receiverType; declaringClass != null; declaringClass = declaringClass.getSuperclass()) { + for (Field f : declaringClass.getDeclaredFields()) { + if (Modifier.isStatic(f.getModifiers())) continue; - if (offset == UNSAFE.objectFieldOffset(f)) { - assert f.getType() == fieldType; - return f; + if (offset == UNSAFE.objectFieldOffset(f)) { + assert f.getType() == fieldType; + return f; + } } } throw new InternalError("Field not found at offset"); diff --git a/test/jdk/java/lang/invoke/VarHandles/describeConstable/DescribeConstableTest.java b/test/jdk/java/lang/invoke/VarHandles/describeConstable/DescribeConstableTest.java index ecda61cac48..0eecbdc3798 100644 --- a/test/jdk/java/lang/invoke/VarHandles/describeConstable/DescribeConstableTest.java +++ b/test/jdk/java/lang/invoke/VarHandles/describeConstable/DescribeConstableTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2025, 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,18 +21,16 @@ * questions. */ -/** +/* * @test - * @bug 8302260 + * @bug 8302260 8372002 * @build p.C p.D p.I p.q.Q * @run junit DescribeConstableTest * @summary Test VarHandle::describeConstable on static fields */ -import java.lang.constant.ClassDesc; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles.Lookup; -import java.lang.invoke.VarHandle; import java.lang.invoke.VarHandle.VarHandleDesc; import java.util.stream.Stream; @@ -43,7 +41,7 @@ import static org.junit.jupiter.api.Assertions.*; public class DescribeConstableTest { private static final Lookup LOOKUP = MethodHandles.lookup(); - private static Stream testCases() { + private static Stream staticTestCases() { return Stream.of( // static field defined in p.C only Arguments.of(p.C.class, "cString", String.class, p.C.class, "CClass"), @@ -68,8 +66,8 @@ public class DescribeConstableTest { } @ParameterizedTest - @MethodSource("testCases") - void test(Class refc, String name, Class type, Class declaringClass, Object value) throws Throwable { + @MethodSource("staticTestCases") + void testStatic(Class refc, String name, Class type, Class declaringClass, Object value) throws Throwable { var vh = LOOKUP.findStaticVarHandle(refc, name, type); assertEquals(value, vh.get()); @@ -84,6 +82,37 @@ public class DescribeConstableTest { assertEquals(vhd.toString(), varHandleDescString(declaringClass, name, type, true)); } + private static Arguments[] instanceTestCases() { + return new Arguments[] { + // Basic instance field in p.q.Q + Arguments.of(p.q.Q.class, "instanceIntField", int.class, new p.q.Q(), 42), + // p.C.instanceIntField hides the superclass instanceIntField, but it still exists + Arguments.of(p.C.class, "instanceIntField", int.class, new p.C(), 76), + Arguments.of(p.q.Q.class, "instanceIntField", int.class, new p.C(), 42), + // p.D.instanceIntField points to that of p.q.Q + Arguments.of(p.D.class, "instanceIntField", int.class, new p.D(), 42), + }; + } + + @ParameterizedTest + @MethodSource("instanceTestCases") + void testInstance(Class refc, String name, Class type, Object instance, Object value) throws Throwable { + var vh = LOOKUP.findVarHandle(refc, name, type).withInvokeBehavior(); + assertEquals(value, vh.get(instance)); + + var refcDesc = refc.describeConstable().orElseThrow(); + var typeDesc = type.describeConstable().orElseThrow(); + var vhd = vh.describeConstable().orElseThrow(); + var vhd2 = VarHandleDesc.ofField(refcDesc, name, typeDesc); + + assertEquals(value, vhd.resolveConstantDesc(LOOKUP).get(instance)); + assertEquals(value, vhd2.resolveConstantDesc(LOOKUP).get(instance)); + + // The string does not use the declaring class because + // receiver is restricted on the handle + assertEquals(vhd.toString(), varHandleDescString(refc, name, type, false)); + } + static String varHandleDescString(Class declaringClass, String name, Class type, boolean staticField) { return String.format("VarHandleDesc[%s%s.%s:%s]", staticField ? "static " : "", diff --git a/test/jdk/java/lang/invoke/VarHandles/describeConstable/p/C.java b/test/jdk/java/lang/invoke/VarHandles/describeConstable/p/C.java index 353bd26dcf5..5e56451ea25 100644 --- a/test/jdk/java/lang/invoke/VarHandles/describeConstable/p/C.java +++ b/test/jdk/java/lang/invoke/VarHandles/describeConstable/p/C.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2025, 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,4 +25,6 @@ package p; public class C extends p.q.Q implements I { public static String cString = "CClass"; + + public int instanceIntField = 76; // Hides the field from Q } diff --git a/test/jdk/java/lang/invoke/VarHandles/describeConstable/p/q/Q.java b/test/jdk/java/lang/invoke/VarHandles/describeConstable/p/q/Q.java index b3bbb8adafc..0461d43b533 100644 --- a/test/jdk/java/lang/invoke/VarHandles/describeConstable/p/q/Q.java +++ b/test/jdk/java/lang/invoke/VarHandles/describeConstable/p/q/Q.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,4 +29,6 @@ public class Q { public static String stringField2 = "QClass2"; public static long longField2 = 102L; + + public int instanceIntField = 42; } From 713de231a61234632e2f9858b222b5f7fd0bdaf1 Mon Sep 17 00:00:00 2001 From: Nityanand Rai Date: Tue, 18 Nov 2025 15:47:54 +0000 Subject: [PATCH 111/418] 8371854: Shenandoah: Simplify WALK_FORWARD_IN_BLOCK_START use Reviewed-by: shade, ysr, xpeng --- .../share/gc/shenandoah/shenandoahScanRemembered.cpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.cpp b/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.cpp index 3a99023eca4..34713898fc6 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.cpp @@ -378,24 +378,20 @@ HeapWord* ShenandoahCardCluster::first_object_start(const size_t card_index, con // evacuation phase) of young collections. This is never called // during global collections during marking or update refs.. // 4. Every allocation under TAMS updates the object start array. +#ifdef ASSERT oop obj = cast_to_oop(p); assert(oopDesc::is_oop(obj), "Should be an object"); -#ifdef ASSERT -#define WALK_FORWARD_IN_BLOCK_START true -#else -#define WALK_FORWARD_IN_BLOCK_START false -#endif // ASSERT - while (WALK_FORWARD_IN_BLOCK_START && p + obj->size() < left) { + while (p + obj->size() < left) { p += obj->size(); obj = cast_to_oop(p); assert(oopDesc::is_oop(obj), "Should be an object"); assert(Klass::is_valid(obj->klass()), "Not a valid klass ptr"); // Check assumptions in previous block comment if this assert fires - guarantee(false, "Should never need forward walk in block start"); + fatal("Should never need forward walk in block start"); } -#undef WALK_FORWARD_IN_BLOCK_START assert(p <= left, "p should start at or before left end of card"); assert(p + obj->size() > left, "obj should end after left end of card"); +#endif // ASSERT return p; } From ac6f5e96512a7f003ac536611c53f2564ea912a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Walln=C3=B6fer?= Date: Tue, 18 Nov 2025 16:52:12 +0000 Subject: [PATCH 112/418] 8366094: Sealed graph for nested types creates broken links Reviewed-by: liach --- .../src/classes/build/tools/taglet/SealedGraph.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/make/jdk/src/classes/build/tools/taglet/SealedGraph.java b/make/jdk/src/classes/build/tools/taglet/SealedGraph.java index 17867b99595..3e93826c180 100644 --- a/make/jdk/src/classes/build/tools/taglet/SealedGraph.java +++ b/make/jdk/src/classes/build/tools/taglet/SealedGraph.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, 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 @@ -219,13 +219,13 @@ public final class SealedGraph implements Taglet { // This implies the module is always the same. private String relativeLink(TypeElement node) { var util = SealedGraph.this.docletEnvironment.getElementUtils(); - var rootPackage = util.getPackageOf(rootNode); var nodePackage = util.getPackageOf(node); - var backNavigator = rootPackage.getQualifiedName().toString().chars() + // Note: SVG files for nested types use the simple names of containing types as parent directories. + // We therefore need to convert all dots in the qualified name to "../" below. + var backNavigator = rootNode.getQualifiedName().toString().chars() .filter(c -> c == '.') .mapToObj(c -> "../") - .collect(joining()) + - "../"; + .collect(joining()); var forwardNavigator = nodePackage.getQualifiedName().toString() .replace(".", "/"); From 0e6c7e8664fdddd8b789851263613852fc2c55f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Walln=C3=B6fer?= Date: Tue, 18 Nov 2025 17:55:43 +0000 Subject: [PATCH 113/418] 8371896: Links in snippets can not be highlighted Reviewed-by: liach --- .../formats/html/taglets/SnippetTaglet.java | 12 +-- .../testSnippetTag/TestSnippetMarkup.java | 82 ++++++++++++++++--- 2 files changed, 78 insertions(+), 16 deletions(-) diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SnippetTaglet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SnippetTaglet.java index d16c47dd59f..38baa7b2826 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SnippetTaglet.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SnippetTaglet.java @@ -162,10 +162,11 @@ public class SnippetTaglet extends BaseTaglet { case Style.Markup m -> markupEncountered = true; } } - Content c; if (markupEncountered) { return; - } else if (linkTarget != null) { + } + Content c = Text.of(text); + if (linkTarget != null) { //disable preview tagging inside the snippets: Utils.PreviewFlagProvider prevPreviewProvider = utils.setPreviewFlagProvider(el -> false); try { @@ -174,15 +175,16 @@ public class SnippetTaglet extends BaseTaglet { null, linkTarget, ref, - false, // TODO: for now + true, Text.of(sequence.toString()), (key, args) -> { /* Error has already been reported above */ }, tagletWriter); } finally { utils.setPreviewFlagProvider(prevPreviewProvider); } - } else { - c = HtmlTree.SPAN(Text.of(text)); + } + if (!classes.isEmpty()) { + c = HtmlTree.SPAN(c); classes.forEach(((HtmlTree) c)::addStyle); } code.add(c); diff --git a/test/langtools/jdk/javadoc/doclet/testSnippetTag/TestSnippetMarkup.java b/test/langtools/jdk/javadoc/doclet/testSnippetTag/TestSnippetMarkup.java index a3e92584ae2..be60a114040 100644 --- a/test/langtools/jdk/javadoc/doclet/testSnippetTag/TestSnippetMarkup.java +++ b/test/langtools/jdk/javadoc/doclet/testSnippetTag/TestSnippetMarkup.java @@ -23,7 +23,7 @@ /* * @test - * @bug 8266666 8281969 8319339 8338833 + * @bug 8266666 8281969 8319339 8338833 8371896 * @summary Implementation for snippets * @library /tools/lib ../../lib * @modules jdk.compiler/com.sun.tools.javac.api @@ -180,7 +180,7 @@ public class TestSnippetMarkup extends SnippetTester { replace(""" link(First) link(line) Second line - """, "link\\((.+?)\\)", r -> link(true, "java.lang.Object#Object", r.group(1))) + """, "link\\((.+?)\\)", r -> link("java.lang.Object#Object", r.group(1))) ), new TestCase( """ @@ -190,7 +190,7 @@ public class TestSnippetMarkup extends SnippetTester { replace(""" First line link( )Secondlink( )line - """, "link\\((.+?)\\)", r -> link(true, "java.lang.System#out", r.group(1))) + """, "link\\((.+?)\\)", r -> link("java.lang.System#out", r.group(1))) ), new TestCase( """ @@ -200,7 +200,68 @@ public class TestSnippetMarkup extends SnippetTester { replace(""" First line link( )Secondlink( )line - """, "link\\((.+?)\\)", r -> link(true, "java.lang.System#in", r.group(1))) + """, "link\\((.+?)\\)", r -> link("java.lang.System#in", r.group(1))) + ) + ); + testPositive(base, testCases); + } + + // Test combinations of @link, @highlight and @replace on the same or overlapping substrings. + @Test + public void testLinkHighlightReplace(Path base) throws Exception { + var testCases = List.of( + new TestCase( + """ + void method(Object o); // @link substring=Object target=Object @highlight substring=Object + """, + replace(""" + void method(link(Object) o); + """, "link\\((.+?)\\)", r -> link("Object", r.group(1))) + ), + new TestCase( + """ + void method(Object o); // @replace substring=Object replacement=String \ + @highlight substring=String @link substring=String target=String + """, + replace(""" + void method(link(String) o); + """, "link\\((.+?)\\)", r -> link("String", r.group(1))) + ), + new TestCase( + """ + void method(Object o); // @highlight substring="(Object o)" @link substring=Object target=Object + """, + replace(""" + void method(link(Object) o); + """, "link\\((.+?)\\)", r -> link("Object", r.group(1))) + ), + new TestCase( + """ + void method(Object o); // @replace substring=Object replacement=String \ + @link substring=String target=String @highlight substring="(String o)" + """, + replace(""" + void method(link(String) o); + """, "link\\((.+?)\\)", r -> link("String", r.group(1))) + ), + new TestCase( + """ + void method(Object o); // @replace substring=Object replacement=String \ + @link substring="String o" target=String @highlight substring=String + """, + replace(""" + void method(link(String)link( o)); + """, "link\\((.+?)\\)", r -> link("String", r.group(1))) + ), + // replacement subtext does not retain links and highlights of the replaced subtext + new TestCase( + """ + void method(Object o); // @link substring=Object target=Object \ + @highlight substring=Object @replace substring=Object replacement=String + """, + """ + void method(String o); + """ ) ); testPositive(base, testCases); @@ -245,7 +306,7 @@ public class TestSnippetMarkup extends SnippetTester { """

    invalid reference -
    Second
    +
    Second
    """); checkNoCrashes(); } @@ -646,7 +707,7 @@ First line // @highlight : replace(""" First line link( Third line) - """, "link\\((.+?)\\)", r -> link(true, "java.lang.Object#equals(Object)", r.group(1))) + """, "link\\((.+?)\\)", r -> link("java.lang.Object#equals(Object)", r.group(1))) ), new TestCase(""" First line @@ -910,8 +971,7 @@ First line // @highlight : checkNoCrashes(); } - private static String link(boolean linkPlain, - String targetReference, + private static String link(String targetReference, String content) throws UncheckedIOException { @@ -935,7 +995,7 @@ First line // @highlight : var LABEL_PLACEHOLDER = "label"; var source = """ - /** {@link %s %s} */ + /** {@linkplain %s %s} */ public interface A { } """.formatted(targetReference, LABEL_PLACEHOLDER); @@ -1063,8 +1123,8 @@ First line // @highlight : } String output = fileManager.getFileString(DOCUMENTATION_OUTPUT, "A.html"); // use the [^<>] regex to select HTML elements that immediately enclose "content" - Matcher m = Pattern.compile("(?is)(]*\" title=\"[^<>]*\" class=\"[^<>]*\">)" - + LABEL_PLACEHOLDER + "()").matcher(output); + Matcher m = Pattern.compile("(?is)(]*\" title=\"[^<>]*\" class=\"[^<>]*\">)" + + LABEL_PLACEHOLDER + "()").matcher(output); if (!m.find()) { throw new IOException(output); } From b3e408c07891b58a312a58ffd756d6a1d18c0f6d Mon Sep 17 00:00:00 2001 From: Ioi Lam Date: Tue, 18 Nov 2025 18:12:07 +0000 Subject: [PATCH 114/418] 8372045: AOT assembly phase asserts with old class if AOT class linking is disabled Reviewed-by: shade, mgronlun --- src/hotspot/share/oops/instanceKlass.cpp | 2 +- .../cds/appcds/aotCache/OldClassSupport2.java | 104 ++++++++++++++++++ 2 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/aotCache/OldClassSupport2.java diff --git a/src/hotspot/share/oops/instanceKlass.cpp b/src/hotspot/share/oops/instanceKlass.cpp index 2d03b69ee92..24358f662bc 100644 --- a/src/hotspot/share/oops/instanceKlass.cpp +++ b/src/hotspot/share/oops/instanceKlass.cpp @@ -2870,7 +2870,7 @@ void InstanceKlass::restore_unshareable_info(ClassLoaderData* loader_data, Handl } bool InstanceKlass::can_be_verified_at_dumptime() const { - if (AOTMetaspace::in_aot_cache(this)) { + if (CDSConfig::is_dumping_dynamic_archive() && AOTMetaspace::in_aot_cache(this)) { // This is a class that was dumped into the base archive, so we know // it was verified at dump time. return true; diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/OldClassSupport2.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/OldClassSupport2.java new file mode 100644 index 00000000000..16a4d258e4f --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/OldClassSupport2.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +/* + * @test + * @summary AOT cache should exclude old classes when AOT class linking is disabled. + * @bug 8372045 + * @requires vm.cds.supports.aot.class.linking + * @library /test/jdk/lib/testlibrary /test/lib /test/hotspot/jtreg/runtime/cds/appcds/test-classes + * @build OldClass + * @build OldClassSupport2 + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar + * AppUsesOldClass2 OldClass + * @run driver OldClassSupport2 + */ + +import jdk.jfr.Event; +import jdk.test.lib.cds.CDSAppTester; +import jdk.test.lib.helpers.ClassFileInstaller; +import jdk.test.lib.process.OutputAnalyzer; + +public class OldClassSupport2 { + static final String appJar = ClassFileInstaller.getJarPath("app.jar"); + static final String mainClass = "AppUsesOldClass2"; + + public static void main(String[] args) throws Exception { + // Explicitly disable + Tester tester1 = new Tester("-XX:-AOTClassLinking"); + tester1.run(new String[] {"AOT", "--two-step-training"} ); + + // Full module graph caching is disabled with -Djdk.module.showModuleResolution=true. + // This will disable AOT class linking. + Tester tester2 = new Tester("-Djdk.module.showModuleResolution=true"); + tester2.run(new String[] {"AOT", "--two-step-training"} ); + + // Heap archiving is disable with -XX:-UseCompressedClassPointers. + // This will disable AOT class linking. + Tester tester3 = new Tester("-XX:-UseCompressedClassPointers"); + tester3.run(new String[] {"AOT", "--two-step-training"} ); + } + + static class Tester extends CDSAppTester { + String extraArg; + public Tester(String extraArg) { + super(mainClass); + this.extraArg = extraArg; + } + + @Override + public String classpath(RunMode runMode) { + return appJar; + } + + @Override + public String[] vmArgs(RunMode runMode) { + return new String[] { + "-Xlog:aot", + "-Xlog:aot+class=debug", + extraArg, + }; + } + + @Override + public String[] appCommandLine(RunMode runMode) { + return new String[] {mainClass}; + } + + @Override + public void checkExecution(OutputAnalyzer out, RunMode runMode) { + String prefix = "aot,class.* = 0x.* app *"; + if (runMode == RunMode.ASSEMBLY) { + out.shouldMatch(prefix + "AppUsesOldClass2"); + out.shouldNotMatch(prefix + "OldClass"); + } + } + } +} + +class AppUsesOldClass2 { + public static void main(String args[]) { + System.out.println("Old Class Instance: " + new OldClass()); + } +} From 4a975637a144fa8aa449a1419e656721833513b5 Mon Sep 17 00:00:00 2001 From: Naoto Sato Date: Tue, 18 Nov 2025 18:35:01 +0000 Subject: [PATCH 115/418] 8346944: Update Unicode Data Files to 17.0.0 8346947: Update ICU4J to Version 78.1 Reviewed-by: joehw --- .../share/classes/java/lang/Character.java | 459 +++-- .../java/text/CollationElementIterator.java | 6 +- .../icu/impl/CharacterIteratorWrapper.java | 6 +- .../jdk/internal/icu/impl/Norm2AllModes.java | 4 +- .../impl/ReplaceableUCharacterIterator.java | 8 +- .../jdk/internal/icu/impl/UBiDiProps.java | 4 +- .../internal/icu/impl/UCharacterProperty.java | 4 +- .../impl/data/{icudt76b => icudata}/nfc.nrm | Bin 36224 -> 36336 bytes .../impl/data/{icudt76b => icudata}/nfkc.nrm | Bin 56032 -> 56128 bytes .../impl/data/{icudt76b => icudata}/ubidi.icu | Bin 28464 -> 28768 bytes .../internal/icu/impl/data/icudata/uprops.icu | Bin 0 -> 170864 bytes .../icu/impl/data/icudt76b/uprops.icu | Bin 146880 -> 0 bytes .../jdk/internal/icu/text/NormalizerBase.java | 6 +- .../internal/icu/text/UCharacterIterator.java | 6 +- .../jdk/internal/icu/text/UnicodeSet.java | 16 +- .../jdk/internal/icu/util/VersionInfo.java | 4 +- .../jdk/internal/util/regex/Grapheme.java | 7 +- .../regex/IndicConjunctBreak.java.template | 7 +- .../share/data/unicodedata/Blocks.txt | 14 +- .../share/data/unicodedata/CaseFolding.txt | 42 +- .../unicodedata/DerivedCoreProperties.txt | 599 ++++-- .../data/unicodedata/NormalizationTest.txt | 75 +- .../share/data/unicodedata/PropList.txt | 72 +- .../data/unicodedata/PropertyValueAliases.txt | 35 +- .../share/data/unicodedata/ReadMe.txt | 19 +- .../share/data/unicodedata/Scripts.txt | 142 +- .../share/data/unicodedata/SpecialCasing.txt | 14 +- .../share/data/unicodedata/UnicodeData.txt | 489 ++++- .../auxiliary/GraphemeBreakProperty.txt | 28 +- .../auxiliary/GraphemeBreakTest.txt | 1737 +++++++---------- .../data/unicodedata/emoji/emoji-data.txt | 148 +- src/java.base/share/legal/icu.md | 4 +- src/java.base/share/legal/unicode.md | 4 +- 33 files changed, 2327 insertions(+), 1632 deletions(-) rename src/java.base/share/classes/jdk/internal/icu/impl/data/{icudt76b => icudata}/nfc.nrm (64%) rename src/java.base/share/classes/jdk/internal/icu/impl/data/{icudt76b => icudata}/nfkc.nrm (90%) rename src/java.base/share/classes/jdk/internal/icu/impl/data/{icudt76b => icudata}/ubidi.icu (74%) create mode 100644 src/java.base/share/classes/jdk/internal/icu/impl/data/icudata/uprops.icu delete mode 100644 src/java.base/share/classes/jdk/internal/icu/impl/data/icudt76b/uprops.icu diff --git a/src/java.base/share/classes/java/lang/Character.java b/src/java.base/share/classes/java/lang/Character.java index 72ff33651f9..d866202909c 100644 --- a/src/java.base/share/classes/java/lang/Character.java +++ b/src/java.base/share/classes/java/lang/Character.java @@ -63,7 +63,7 @@ import static java.lang.constant.ConstantDescs.DEFAULT_NAME; * from the Unicode Consortium at * http://www.unicode.org. *

    - * Character information is based on the Unicode Standard, version 16.0. + * Character information is based on the Unicode Standard, version 17.0. *

    * The Java platform has supported different versions of the Unicode * Standard over time. Upgrades to newer versions of the Unicode Standard @@ -75,6 +75,8 @@ import static java.lang.constant.ConstantDescs.DEFAULT_NAME; * Unicode version * * + * Java SE 26 + * Unicode 17.0 * Java SE 24 * Unicode 16.0 * Java SE 22 @@ -745,7 +747,7 @@ class Character implements java.io.Serializable, Comparable, Constabl * It should be adjusted whenever the Unicode Character Database * is upgraded. */ - private static final int NUM_ENTITIES = 782; + private static final int NUM_ENTITIES = 804; private static Map map = HashMap.newHashMap(NUM_ENTITIES); /** @@ -3715,6 +3717,85 @@ class Character implements java.io.Serializable, Comparable, Constabl "OL ONAL", "OLONAL"); + /** + * Constant for the "Sidetic" Unicode + * character block. + * @since 26 + */ + public static final UnicodeBlock SIDETIC = + new UnicodeBlock("SIDETIC"); + + /** + * Constant for the "Sharada Supplement" Unicode + * character block. + * @since 26 + */ + public static final UnicodeBlock SHARADA_SUPPLEMENT = + new UnicodeBlock("SHARADA_SUPPLEMENT", + "SHARADA SUPPLEMENT", + "SHARADASUPPLEMENT"); + + /** + * Constant for the "Tolong Siki" Unicode + * character block. + * @since 26 + */ + public static final UnicodeBlock TOLONG_SIKI = + new UnicodeBlock("TOLONG_SIKI", + "TOLONG SIKI", + "TOLONGSIKI"); + + /** + * Constant for the "Beria Erfe" Unicode + * character block. + * @since 26 + */ + public static final UnicodeBlock BERIA_ERFE = + new UnicodeBlock("BERIA_ERFE", + "BERIA ERFE", + "BERIAERFE"); + + /** + * Constant for the "Tangut Components Supplement" Unicode + * character block. + * @since 26 + */ + public static final UnicodeBlock TANGUT_COMPONENTS_SUPPLEMENT = + new UnicodeBlock("TANGUT_COMPONENTS_SUPPLEMENT", + "TANGUT COMPONENTS SUPPLEMENT", + "TANGUTCOMPONENTSSUPPLEMENT"); + + /** + * Constant for the "Miscellaneous Symbols Supplement" Unicode + * character block. + * @since 26 + */ + public static final UnicodeBlock MISCELLANEOUS_SYMBOLS_SUPPLEMENT = + new UnicodeBlock("MISCELLANEOUS_SYMBOLS_SUPPLEMENT", + "MISCELLANEOUS SYMBOLS SUPPLEMENT", + "MISCELLANEOUSSYMBOLSSUPPLEMENT"); + + /** + * Constant for the "Tai Yo" Unicode + * character block. + * @since 26 + */ + public static final UnicodeBlock TAI_YO = + new UnicodeBlock("TAI_YO", + "TAI YO", + "TAIYO"); + + /** + * Constant for the "CJK Unified Ideographs Extension J" Unicode + * character block. + * @since 26 + */ + public static final UnicodeBlock CJK_UNIFIED_IDEOGRAPHS_EXTENSION_J = + new UnicodeBlock("CJK_UNIFIED_IDEOGRAPHS_EXTENSION_J", + "CJK UNIFIED IDEOGRAPHS EXTENSION J", + "CJKUNIFIEDIDEOGRAPHSEXTENSIONJ"); + + private static final int[] blockStarts = { 0x0000, // 0000..007F; Basic Latin 0x0080, // 0080..00FF; Latin-1 Supplement @@ -3916,7 +3997,8 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x108E0, // 108E0..108FF; Hatran 0x10900, // 10900..1091F; Phoenician 0x10920, // 10920..1093F; Lydian - 0x10940, // unassigned + 0x10940, // 10940..1095F; Sidetic + 0x10960, // unassigned 0x10980, // 10980..1099F; Meroitic Hieroglyphs 0x109A0, // 109A0..109FF; Meroitic Cursive 0x10A00, // 10A00..10A5F; Kharoshthi @@ -3977,14 +4059,16 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x11AB0, // 11AB0..11ABF; Unified Canadian Aboriginal Syllabics Extended-A 0x11AC0, // 11AC0..11AFF; Pau Cin Hau 0x11B00, // 11B00..11B5F; Devanagari Extended-A - 0x11B60, // unassigned + 0x11B60, // 11B60..11B7F; Sharada Supplement + 0x11B80, // unassigned 0x11BC0, // 11BC0..11BFF; Sunuwar 0x11C00, // 11C00..11C6F; Bhaiksuki 0x11C70, // 11C70..11CBF; Marchen 0x11CC0, // unassigned 0x11D00, // 11D00..11D5F; Masaram Gondi 0x11D60, // 11D60..11DAF; Gunjala Gondi - 0x11DB0, // unassigned + 0x11DB0, // 11DB0..11DEF; Tolong Siki + 0x11DF0, // unassigned 0x11EE0, // 11EE0..11EFF; Makasar 0x11F00, // 11F00..11F5F; Kawi 0x11F60, // unassigned @@ -4011,7 +4095,8 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x16D40, // 16D40..16D7F; Kirat Rai 0x16D80, // unassigned 0x16E40, // 16E40..16E9F; Medefaidrin - 0x16EA0, // unassigned + 0x16EA0, // 16EA0..16EDF; Beria Erfe + 0x16EE0, // unassigned 0x16F00, // 16F00..16F9F; Miao 0x16FA0, // unassigned 0x16FE0, // 16FE0..16FFF; Ideographic Symbols and Punctuation @@ -4019,7 +4104,8 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x18800, // 18800..18AFF; Tangut Components 0x18B00, // 18B00..18CFF; Khitan Small Script 0x18D00, // 18D00..18D7F; Tangut Supplement - 0x18D80, // unassigned + 0x18D80, // 18D80..18DFF; Tangut Components Supplement + 0x18E00, // unassigned 0x1AFF0, // 1AFF0..1AFFF; Kana Extended-B 0x1B000, // 1B000..1B0FF; Kana Supplement 0x1B100, // 1B100..1B12F; Kana Extended-A @@ -4030,7 +4116,7 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x1BCA0, // 1BCA0..1BCAF; Shorthand Format Controls 0x1BCB0, // unassigned 0x1CC00, // 1CC00..1CEBF; Symbols for Legacy Computing Supplement - 0x1CEC0, // unassigned + 0x1CEC0, // 1CEC0..1CEFF; Miscellaneous Symbols Supplement 0x1CF00, // 1CF00..1CFCF; Znamenny Musical Notation 0x1CFD0, // unassigned 0x1D000, // 1D000..1D0FF; Byzantine Musical Symbols @@ -4058,6 +4144,8 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x1E500, // unassigned 0x1E5D0, // 1E5D0..1E5FF; Ol Onal 0x1E600, // unassigned + 0x1E6C0, // 1E6C0..1E6FF; Tai Yo + 0x1E700, // unassigned 0x1E7E0, // 1E7E0..1E7FF; Ethiopic Extended-B 0x1E800, // 1E800..1E8DF; Mende Kikakui 0x1E8E0, // unassigned @@ -4098,7 +4186,8 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x2FA20, // unassigned 0x30000, // 30000..3134F; CJK Unified Ideographs Extension G 0x31350, // 31350..323AF; CJK Unified Ideographs Extension H - 0x323B0, // unassigned + 0x323B0, // 323B0..3347F; CJK Unified Ideographs Extension J + 0x33480, // unassigned 0xE0000, // E0000..E007F; Tags 0xE0080, // unassigned 0xE0100, // E0100..E01EF; Variation Selectors Supplement @@ -4308,6 +4397,7 @@ class Character implements java.io.Serializable, Comparable, Constabl HATRAN, PHOENICIAN, LYDIAN, + SIDETIC, null, MEROITIC_HIEROGLYPHS, MEROITIC_CURSIVE, @@ -4369,6 +4459,7 @@ class Character implements java.io.Serializable, Comparable, Constabl UNIFIED_CANADIAN_ABORIGINAL_SYLLABICS_EXTENDED_A, PAU_CIN_HAU, DEVANAGARI_EXTENDED_A, + SHARADA_SUPPLEMENT, null, SUNUWAR, BHAIKSUKI, @@ -4376,6 +4467,7 @@ class Character implements java.io.Serializable, Comparable, Constabl null, MASARAM_GONDI, GUNJALA_GONDI, + TOLONG_SIKI, null, MAKASAR, KAWI, @@ -4403,6 +4495,7 @@ class Character implements java.io.Serializable, Comparable, Constabl KIRAT_RAI, null, MEDEFAIDRIN, + BERIA_ERFE, null, MIAO, null, @@ -4411,6 +4504,7 @@ class Character implements java.io.Serializable, Comparable, Constabl TANGUT_COMPONENTS, KHITAN_SMALL_SCRIPT, TANGUT_SUPPLEMENT, + TANGUT_COMPONENTS_SUPPLEMENT, null, KANA_EXTENDED_B, KANA_SUPPLEMENT, @@ -4422,7 +4516,7 @@ class Character implements java.io.Serializable, Comparable, Constabl SHORTHAND_FORMAT_CONTROLS, null, SYMBOLS_FOR_LEGACY_COMPUTING_SUPPLEMENT, - null, + MISCELLANEOUS_SYMBOLS_SUPPLEMENT, ZNAMENNY_MUSICAL_NOTATION, null, BYZANTINE_MUSICAL_SYMBOLS, @@ -4450,6 +4544,8 @@ class Character implements java.io.Serializable, Comparable, Constabl null, OL_ONAL, null, + TAI_YO, + null, ETHIOPIC_EXTENDED_B, MENDE_KIKAKUI, null, @@ -4490,6 +4586,7 @@ class Character implements java.io.Serializable, Comparable, Constabl null, CJK_UNIFIED_IDEOGRAPHS_EXTENSION_G, CJK_UNIFIED_IDEOGRAPHS_EXTENSION_H, + CJK_UNIFIED_IDEOGRAPHS_EXTENSION_J, null, TAGS, null, @@ -5547,6 +5644,30 @@ class Character implements java.io.Serializable, Comparable, Constabl */ OL_ONAL, + /** + * Unicode script "Sidetic". + * @since 26 + */ + SIDETIC, + + /** + * Unicode script "Tolong Siki". + * @since 26 + */ + TOLONG_SIKI, + + /** + * Unicode script "Beria Erfe". + * @since 26 + */ + BERIA_ERFE, + + /** + * Unicode script "Tai Yo". + * @since 26 + */ + TAI_YO, + /** * Unicode script "Unknown". */ @@ -5648,9 +5769,7 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x085F, // 085F ; UNKNOWN 0x0860, // 0860..086A; SYRIAC 0x086B, // 086B..086F; UNKNOWN - 0x0870, // 0870..088E; ARABIC - 0x088F, // 088F ; UNKNOWN - 0x0890, // 0890..0891; ARABIC + 0x0870, // 0870..0891; ARABIC 0x0892, // 0892..0896; UNKNOWN 0x0897, // 0897..08E1; ARABIC 0x08E2, // 08E2 ; COMMON @@ -5825,8 +5944,8 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x0C55, // 0C55..0C56; TELUGU 0x0C57, // 0C57 ; UNKNOWN 0x0C58, // 0C58..0C5A; TELUGU - 0x0C5B, // 0C5B..0C5C; UNKNOWN - 0x0C5D, // 0C5D ; TELUGU + 0x0C5B, // 0C5B ; UNKNOWN + 0x0C5C, // 0C5C..0C5D; TELUGU 0x0C5E, // 0C5E..0C5F; UNKNOWN 0x0C60, // 0C60..0C63; TELUGU 0x0C64, // 0C64..0C65; UNKNOWN @@ -5850,8 +5969,8 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x0CCA, // 0CCA..0CCD; KANNADA 0x0CCE, // 0CCE..0CD4; UNKNOWN 0x0CD5, // 0CD5..0CD6; KANNADA - 0x0CD7, // 0CD7..0CDC; UNKNOWN - 0x0CDD, // 0CDD..0CDE; KANNADA + 0x0CD7, // 0CD7..0CDB; UNKNOWN + 0x0CDC, // 0CDC..0CDE; KANNADA 0x0CDF, // 0CDF ; UNKNOWN 0x0CE0, // 0CE0..0CE3; KANNADA 0x0CE4, // 0CE4..0CE5; UNKNOWN @@ -6062,8 +6181,10 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x1A9A, // 1A9A..1A9F; UNKNOWN 0x1AA0, // 1AA0..1AAD; TAI_THAM 0x1AAE, // 1AAE..1AAF; UNKNOWN - 0x1AB0, // 1AB0..1ACE; INHERITED - 0x1ACF, // 1ACF..1AFF; UNKNOWN + 0x1AB0, // 1AB0..1ADD; INHERITED + 0x1ADE, // 1ADE..1ADF; UNKNOWN + 0x1AE0, // 1AE0..1AEB; INHERITED + 0x1AEC, // 1AEC..1AFF; UNKNOWN 0x1B00, // 1B00..1B4C; BALINESE 0x1B4D, // 1B4D ; UNKNOWN 0x1B4E, // 1B4E..1B7F; BALINESE @@ -6155,8 +6276,8 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x208F, // 208F ; UNKNOWN 0x2090, // 2090..209C; LATIN 0x209D, // 209D..209F; UNKNOWN - 0x20A0, // 20A0..20C0; COMMON - 0x20C1, // 20C1..20CF; UNKNOWN + 0x20A0, // 20A0..20C1; COMMON + 0x20C2, // 20C2..20CF; UNKNOWN 0x20D0, // 20D0..20F0; INHERITED 0x20F1, // 20F1..20FF; UNKNOWN 0x2100, // 2100..2125; COMMON @@ -6179,9 +6300,7 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x2800, // 2800..28FF; BRAILLE 0x2900, // 2900..2B73; COMMON 0x2B74, // 2B74..2B75; UNKNOWN - 0x2B76, // 2B76..2B95; COMMON - 0x2B96, // 2B96 ; UNKNOWN - 0x2B97, // 2B97..2BFF; COMMON + 0x2B76, // 2B76..2BFF; COMMON 0x2C00, // 2C00..2C5F; GLAGOLITIC 0x2C60, // 2C60..2C7F; LATIN 0x2C80, // 2C80..2CF3; COPTIC @@ -6282,15 +6401,9 @@ class Character implements java.io.Serializable, Comparable, Constabl 0xA700, // A700..A721; COMMON 0xA722, // A722..A787; LATIN 0xA788, // A788..A78A; COMMON - 0xA78B, // A78B..A7CD; LATIN - 0xA7CE, // A7CE..A7CF; UNKNOWN - 0xA7D0, // A7D0..A7D1; LATIN - 0xA7D2, // A7D2 ; UNKNOWN - 0xA7D3, // A7D3 ; LATIN - 0xA7D4, // A7D4 ; UNKNOWN - 0xA7D5, // A7D5..A7DC; LATIN - 0xA7DD, // A7DD..A7F1; UNKNOWN - 0xA7F2, // A7F2..A7FF; LATIN + 0xA78B, // A78B..A7DC; LATIN + 0xA7DD, // A7DD..A7F0; UNKNOWN + 0xA7F1, // A7F1..A7FF; LATIN 0xA800, // A800..A82C; SYLOTI_NAGRI 0xA82D, // A82D..A82F; UNKNOWN 0xA830, // A830..A839; COMMON @@ -6378,15 +6491,9 @@ class Character implements java.io.Serializable, Comparable, Constabl 0xFB43, // FB43..FB44; HEBREW 0xFB45, // FB45 ; UNKNOWN 0xFB46, // FB46..FB4F; HEBREW - 0xFB50, // FB50..FBC2; ARABIC - 0xFBC3, // FBC3..FBD2; UNKNOWN - 0xFBD3, // FBD3..FD3D; ARABIC + 0xFB50, // FB50..FD3D; ARABIC 0xFD3E, // FD3E..FD3F; COMMON - 0xFD40, // FD40..FD8F; ARABIC - 0xFD90, // FD90..FD91; UNKNOWN - 0xFD92, // FD92..FDC7; ARABIC - 0xFDC8, // FDC8..FDCE; UNKNOWN - 0xFDCF, // FDCF ; ARABIC + 0xFD40, // FD40..FDCF; ARABIC 0xFDD0, // FDD0..FDEF; UNKNOWN 0xFDF0, // FDF0..FDFF; ARABIC 0xFE00, // FE00..FE0F; INHERITED @@ -6555,7 +6662,8 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x10920, // 10920..10939; LYDIAN 0x1093A, // 1093A..1093E; UNKNOWN 0x1093F, // 1093F ; LYDIAN - 0x10940, // 10940..1097F; UNKNOWN + 0x10940, // 10940..10959; SIDETIC + 0x1095A, // 1095A..1097F; UNKNOWN 0x10980, // 10980..1099F; MEROITIC_HIEROGLYPHS 0x109A0, // 109A0..109B7; MEROITIC_CURSIVE 0x109B8, // 109B8..109BB; UNKNOWN @@ -6625,9 +6733,11 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x10EAE, // 10EAE..10EAF; UNKNOWN 0x10EB0, // 10EB0..10EB1; YEZIDI 0x10EB2, // 10EB2..10EC1; UNKNOWN - 0x10EC2, // 10EC2..10EC4; ARABIC - 0x10EC5, // 10EC5..10EFB; UNKNOWN - 0x10EFC, // 10EFC..10EFF; ARABIC + 0x10EC2, // 10EC2..10EC7; ARABIC + 0x10EC8, // 10EC8..10ECF; UNKNOWN + 0x10ED0, // 10ED0..10ED8; ARABIC + 0x10ED9, // 10ED9..10EF9; UNKNOWN + 0x10EFA, // 10EFA..10EFF; ARABIC 0x10F00, // 10F00..10F27; OLD_SOGDIAN 0x10F28, // 10F28..10F2F; UNKNOWN 0x10F30, // 10F30..10F59; SOGDIAN @@ -6797,7 +6907,9 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x11AC0, // 11AC0..11AF8; PAU_CIN_HAU 0x11AF9, // 11AF9..11AFF; UNKNOWN 0x11B00, // 11B00..11B09; DEVANAGARI - 0x11B0A, // 11B0A..11BBF; UNKNOWN + 0x11B0A, // 11B0A..11B5F; UNKNOWN + 0x11B60, // 11B60..11B67; SHARADA + 0x11B68, // 11B68..11BBF; UNKNOWN 0x11BC0, // 11BC0..11BE1; SUNUWAR 0x11BE2, // 11BE2..11BEF; UNKNOWN 0x11BF0, // 11BF0..11BF9; SUNUWAR @@ -6841,7 +6953,11 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x11D93, // 11D93..11D98; GUNJALA_GONDI 0x11D99, // 11D99..11D9F; UNKNOWN 0x11DA0, // 11DA0..11DA9; GUNJALA_GONDI - 0x11DAA, // 11DAA..11EDF; UNKNOWN + 0x11DAA, // 11DAA..11DAF; UNKNOWN + 0x11DB0, // 11DB0..11DDB; TOLONG_SIKI + 0x11DDC, // 11DDC..11DDF; UNKNOWN + 0x11DE0, // 11DE0..11DE9; TOLONG_SIKI + 0x11DEA, // 11DEA..11EDF; UNKNOWN 0x11EE0, // 11EE0..11EF8; MAKASAR 0x11EF9, // 11EF9..11EFF; UNKNOWN 0x11F00, // 11F00..11F10; KAWI @@ -6901,7 +7017,11 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x16D40, // 16D40..16D79; KIRAT_RAI 0x16D7A, // 16D7A..16E3F; UNKNOWN 0x16E40, // 16E40..16E9A; MEDEFAIDRIN - 0x16E9B, // 16E9B..16EFF; UNKNOWN + 0x16E9B, // 16E9B..16E9F; UNKNOWN + 0x16EA0, // 16EA0..16EB8; BERIA_ERFE + 0x16EB9, // 16EB9..16EBA; UNKNOWN + 0x16EBB, // 16EBB..16ED3; BERIA_ERFE + 0x16ED4, // 16ED4..16EFF; UNKNOWN 0x16F00, // 16F00..16F4A; MIAO 0x16F4B, // 16F4B..16F4E; UNKNOWN 0x16F4F, // 16F4F..16F87; MIAO @@ -6913,16 +7033,16 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x16FE2, // 16FE2..16FE3; HAN 0x16FE4, // 16FE4 ; KHITAN_SMALL_SCRIPT 0x16FE5, // 16FE5..16FEF; UNKNOWN - 0x16FF0, // 16FF0..16FF1; HAN - 0x16FF2, // 16FF2..16FFF; UNKNOWN - 0x17000, // 17000..187F7; TANGUT - 0x187F8, // 187F8..187FF; UNKNOWN - 0x18800, // 18800..18AFF; TANGUT + 0x16FF0, // 16FF0..16FF6; HAN + 0x16FF7, // 16FF7..16FFF; UNKNOWN + 0x17000, // 17000..18AFF; TANGUT 0x18B00, // 18B00..18CD5; KHITAN_SMALL_SCRIPT 0x18CD6, // 18CD6..18CFE; UNKNOWN 0x18CFF, // 18CFF ; KHITAN_SMALL_SCRIPT - 0x18D00, // 18D00..18D08; TANGUT - 0x18D09, // 18D09..1AFEF; UNKNOWN + 0x18D00, // 18D00..18D1E; TANGUT + 0x18D1F, // 18D1F..18D7F; UNKNOWN + 0x18D80, // 18D80..18DF2; TANGUT + 0x18DF3, // 18DF3..1AFEF; UNKNOWN 0x1AFF0, // 1AFF0..1AFF3; KATAKANA 0x1AFF4, // 1AFF4 ; UNKNOWN 0x1AFF5, // 1AFF5..1AFFB; KATAKANA @@ -6954,10 +7074,14 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x1BC9C, // 1BC9C..1BC9F; DUPLOYAN 0x1BCA0, // 1BCA0..1BCA3; COMMON 0x1BCA4, // 1BCA4..1CBFF; UNKNOWN - 0x1CC00, // 1CC00..1CCF9; COMMON - 0x1CCFA, // 1CCFA..1CCFF; UNKNOWN + 0x1CC00, // 1CC00..1CCFC; COMMON + 0x1CCFD, // 1CCFD..1CCFF; UNKNOWN 0x1CD00, // 1CD00..1CEB3; COMMON - 0x1CEB4, // 1CEB4..1CEFF; UNKNOWN + 0x1CEB4, // 1CEB4..1CEB9; UNKNOWN + 0x1CEBA, // 1CEBA..1CED0; COMMON + 0x1CED1, // 1CED1..1CEDF; UNKNOWN + 0x1CEE0, // 1CEE0..1CEF0; COMMON + 0x1CEF1, // 1CEF1..1CEFF; UNKNOWN 0x1CF00, // 1CF00..1CF2D; INHERITED 0x1CF2E, // 1CF2E..1CF2F; UNKNOWN 0x1CF30, // 1CF30..1CF46; INHERITED @@ -7072,7 +7196,13 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x1E5D0, // 1E5D0..1E5FA; OL_ONAL 0x1E5FB, // 1E5FB..1E5FE; UNKNOWN 0x1E5FF, // 1E5FF ; OL_ONAL - 0x1E600, // 1E600..1E7DF; UNKNOWN + 0x1E600, // 1E600..1E6BF; UNKNOWN + 0x1E6C0, // 1E6C0..1E6DE; TAI_YO + 0x1E6DF, // 1E6DF ; UNKNOWN + 0x1E6E0, // 1E6E0..1E6F5; TAI_YO + 0x1E6F6, // 1E6F6..1E6FD; UNKNOWN + 0x1E6FE, // 1E6FE..1E6FF; TAI_YO + 0x1E700, // 1E700..1E7DF; UNKNOWN 0x1E7E0, // 1E7E0..1E7E6; ETHIOPIC 0x1E7E7, // 1E7E7 ; UNKNOWN 0x1E7E8, // 1E7E8..1E7EB; ETHIOPIC @@ -7189,15 +7319,13 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x1F252, // 1F252..1F25F; UNKNOWN 0x1F260, // 1F260..1F265; COMMON 0x1F266, // 1F266..1F2FF; UNKNOWN - 0x1F300, // 1F300..1F6D7; COMMON - 0x1F6D8, // 1F6D8..1F6DB; UNKNOWN + 0x1F300, // 1F300..1F6D8; COMMON + 0x1F6D9, // 1F6D9..1F6DB; UNKNOWN 0x1F6DC, // 1F6DC..1F6EC; COMMON 0x1F6ED, // 1F6ED..1F6EF; UNKNOWN 0x1F6F0, // 1F6F0..1F6FC; COMMON 0x1F6FD, // 1F6FD..1F6FF; UNKNOWN - 0x1F700, // 1F700..1F776; COMMON - 0x1F777, // 1F777..1F77A; UNKNOWN - 0x1F77B, // 1F77B..1F7D9; COMMON + 0x1F700, // 1F700..1F7D9; COMMON 0x1F7DA, // 1F7DA..1F7DF; UNKNOWN 0x1F7E0, // 1F7E0..1F7EB; COMMON 0x1F7EC, // 1F7EC..1F7EF; UNKNOWN @@ -7216,35 +7344,37 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x1F8B0, // 1F8B0..1F8BB; COMMON 0x1F8BC, // 1F8BC..1F8BF; UNKNOWN 0x1F8C0, // 1F8C0..1F8C1; COMMON - 0x1F8C2, // 1F8C2..1F8FF; UNKNOWN - 0x1F900, // 1F900..1FA53; COMMON - 0x1FA54, // 1FA54..1FA5F; UNKNOWN + 0x1F8C2, // 1F8C2..1F8CF; UNKNOWN + 0x1F8D0, // 1F8D0..1F8D8; COMMON + 0x1F8D9, // 1F8D9..1F8FF; UNKNOWN + 0x1F900, // 1F900..1FA57; COMMON + 0x1FA58, // 1FA58..1FA5F; UNKNOWN 0x1FA60, // 1FA60..1FA6D; COMMON 0x1FA6E, // 1FA6E..1FA6F; UNKNOWN 0x1FA70, // 1FA70..1FA7C; COMMON 0x1FA7D, // 1FA7D..1FA7F; UNKNOWN - 0x1FA80, // 1FA80..1FA89; COMMON - 0x1FA8A, // 1FA8A..1FA8E; UNKNOWN - 0x1FA8F, // 1FA8F..1FAC6; COMMON - 0x1FAC7, // 1FAC7..1FACD; UNKNOWN - 0x1FACE, // 1FACE..1FADC; COMMON + 0x1FA80, // 1FA80..1FA8A; COMMON + 0x1FA8B, // 1FA8B..1FA8D; UNKNOWN + 0x1FA8E, // 1FA8E..1FAC6; COMMON + 0x1FAC7, // 1FAC7 ; UNKNOWN + 0x1FAC8, // 1FAC8 ; COMMON + 0x1FAC9, // 1FAC9..1FACC; UNKNOWN + 0x1FACD, // 1FACD..1FADC; COMMON 0x1FADD, // 1FADD..1FADE; UNKNOWN - 0x1FADF, // 1FADF..1FAE9; COMMON - 0x1FAEA, // 1FAEA..1FAEF; UNKNOWN - 0x1FAF0, // 1FAF0..1FAF8; COMMON + 0x1FADF, // 1FADF..1FAEA; COMMON + 0x1FAEB, // 1FAEB..1FAEE; UNKNOWN + 0x1FAEF, // 1FAEF..1FAF8; COMMON 0x1FAF9, // 1FAF9..1FAFF; UNKNOWN 0x1FB00, // 1FB00..1FB92; COMMON 0x1FB93, // 1FB93 ; UNKNOWN - 0x1FB94, // 1FB94..1FBF9; COMMON - 0x1FBFA, // 1FBFA..1FFFF; UNKNOWN + 0x1FB94, // 1FB94..1FBFA; COMMON + 0x1FBFB, // 1FBFB..1FFFF; UNKNOWN 0x20000, // 20000..2A6DF; HAN 0x2A6E0, // 2A6E0..2A6FF; UNKNOWN - 0x2A700, // 2A700..2B739; HAN - 0x2B73A, // 2B73A..2B73F; UNKNOWN - 0x2B740, // 2B740..2B81D; HAN + 0x2A700, // 2A700..2B81D; HAN 0x2B81E, // 2B81E..2B81F; UNKNOWN - 0x2B820, // 2B820..2CEA1; HAN - 0x2CEA2, // 2CEA2..2CEAF; UNKNOWN + 0x2B820, // 2B820..2CEAD; HAN + 0x2CEAE, // 2CEAE..2CEAF; UNKNOWN 0x2CEB0, // 2CEB0..2EBE0; HAN 0x2EBE1, // 2EBE1..2EBEF; UNKNOWN 0x2EBF0, // 2EBF0..2EE5D; HAN @@ -7253,8 +7383,8 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x2FA1E, // 2FA1E..2FFFF; UNKNOWN 0x30000, // 30000..3134A; HAN 0x3134B, // 3134B..3134F; UNKNOWN - 0x31350, // 31350..323AF; HAN - 0x323B0, // 323B0..E0000; UNKNOWN + 0x31350, // 31350..33479; HAN + 0x3347A, // 3347A..E0000; UNKNOWN 0xE0001, // E0001 ; COMMON 0xE0002, // E0002..E001F; UNKNOWN 0xE0020, // E0020..E007F; COMMON @@ -7359,9 +7489,7 @@ class Character implements java.io.Serializable, Comparable, Constabl UNKNOWN, // 085F SYRIAC, // 0860..086A UNKNOWN, // 086B..086F - ARABIC, // 0870..088E - UNKNOWN, // 088F - ARABIC, // 0890..0891 + ARABIC, // 0870..0891 UNKNOWN, // 0892..0896 ARABIC, // 0897..08E1 COMMON, // 08E2 @@ -7536,8 +7664,8 @@ class Character implements java.io.Serializable, Comparable, Constabl TELUGU, // 0C55..0C56 UNKNOWN, // 0C57 TELUGU, // 0C58..0C5A - UNKNOWN, // 0C5B..0C5C - TELUGU, // 0C5D + UNKNOWN, // 0C5B + TELUGU, // 0C5C..0C5D UNKNOWN, // 0C5E..0C5F TELUGU, // 0C60..0C63 UNKNOWN, // 0C64..0C65 @@ -7561,8 +7689,8 @@ class Character implements java.io.Serializable, Comparable, Constabl KANNADA, // 0CCA..0CCD UNKNOWN, // 0CCE..0CD4 KANNADA, // 0CD5..0CD6 - UNKNOWN, // 0CD7..0CDC - KANNADA, // 0CDD..0CDE + UNKNOWN, // 0CD7..0CDB + KANNADA, // 0CDC..0CDE UNKNOWN, // 0CDF KANNADA, // 0CE0..0CE3 UNKNOWN, // 0CE4..0CE5 @@ -7773,8 +7901,10 @@ class Character implements java.io.Serializable, Comparable, Constabl UNKNOWN, // 1A9A..1A9F TAI_THAM, // 1AA0..1AAD UNKNOWN, // 1AAE..1AAF - INHERITED, // 1AB0..1ACE - UNKNOWN, // 1ACF..1AFF + INHERITED, // 1AB0..1ADD + UNKNOWN, // 1ADE..1ADF + INHERITED, // 1AE0..1AEB + UNKNOWN, // 1AEC..1AFF BALINESE, // 1B00..1B4C UNKNOWN, // 1B4D BALINESE, // 1B4E..1B7F @@ -7866,8 +7996,8 @@ class Character implements java.io.Serializable, Comparable, Constabl UNKNOWN, // 208F LATIN, // 2090..209C UNKNOWN, // 209D..209F - COMMON, // 20A0..20C0 - UNKNOWN, // 20C1..20CF + COMMON, // 20A0..20C1 + UNKNOWN, // 20C2..20CF INHERITED, // 20D0..20F0 UNKNOWN, // 20F1..20FF COMMON, // 2100..2125 @@ -7890,9 +8020,7 @@ class Character implements java.io.Serializable, Comparable, Constabl BRAILLE, // 2800..28FF COMMON, // 2900..2B73 UNKNOWN, // 2B74..2B75 - COMMON, // 2B76..2B95 - UNKNOWN, // 2B96 - COMMON, // 2B97..2BFF + COMMON, // 2B76..2BFF GLAGOLITIC, // 2C00..2C5F LATIN, // 2C60..2C7F COPTIC, // 2C80..2CF3 @@ -7993,15 +8121,9 @@ class Character implements java.io.Serializable, Comparable, Constabl COMMON, // A700..A721 LATIN, // A722..A787 COMMON, // A788..A78A - LATIN, // A78B..A7CD - UNKNOWN, // A7CE..A7CF - LATIN, // A7D0..A7D1 - UNKNOWN, // A7D2 - LATIN, // A7D3 - UNKNOWN, // A7D4 - LATIN, // A7D5..A7DC - UNKNOWN, // A7DD..A7F1 - LATIN, // A7F2..A7FF + LATIN, // A78B..A7DC + UNKNOWN, // A7DD..A7F0 + LATIN, // A7F1..A7FF SYLOTI_NAGRI, // A800..A82C UNKNOWN, // A82D..A82F COMMON, // A830..A839 @@ -8089,15 +8211,9 @@ class Character implements java.io.Serializable, Comparable, Constabl HEBREW, // FB43..FB44 UNKNOWN, // FB45 HEBREW, // FB46..FB4F - ARABIC, // FB50..FBC2 - UNKNOWN, // FBC3..FBD2 - ARABIC, // FBD3..FD3D + ARABIC, // FB50..FD3D COMMON, // FD3E..FD3F - ARABIC, // FD40..FD8F - UNKNOWN, // FD90..FD91 - ARABIC, // FD92..FDC7 - UNKNOWN, // FDC8..FDCE - ARABIC, // FDCF + ARABIC, // FD40..FDCF UNKNOWN, // FDD0..FDEF ARABIC, // FDF0..FDFF INHERITED, // FE00..FE0F @@ -8266,7 +8382,8 @@ class Character implements java.io.Serializable, Comparable, Constabl LYDIAN, // 10920..10939 UNKNOWN, // 1093A..1093E LYDIAN, // 1093F - UNKNOWN, // 10940..1097F + SIDETIC, // 10940..10959 + UNKNOWN, // 1095A..1097F MEROITIC_HIEROGLYPHS, // 10980..1099F MEROITIC_CURSIVE, // 109A0..109B7 UNKNOWN, // 109B8..109BB @@ -8336,9 +8453,11 @@ class Character implements java.io.Serializable, Comparable, Constabl UNKNOWN, // 10EAE..10EAF YEZIDI, // 10EB0..10EB1 UNKNOWN, // 10EB2..10EC1 - ARABIC, // 10EC2..10EC4 - UNKNOWN, // 10EC5..10EFB - ARABIC, // 10EFC..10EFF + ARABIC, // 10EC2..10EC7 + UNKNOWN, // 10EC8..10ECF + ARABIC, // 10ED0..10ED8 + UNKNOWN, // 10ED9..10EF9 + ARABIC, // 10EFA..10EFF OLD_SOGDIAN, // 10F00..10F27 UNKNOWN, // 10F28..10F2F SOGDIAN, // 10F30..10F59 @@ -8508,7 +8627,9 @@ class Character implements java.io.Serializable, Comparable, Constabl PAU_CIN_HAU, // 11AC0..11AF8 UNKNOWN, // 11AF9..11AFF DEVANAGARI, // 11B00..11B09 - UNKNOWN, // 11B0A..11BBF + UNKNOWN, // 11B0A..11B5F + SHARADA, // 11B60..11B67 + UNKNOWN, // 11B68..11BBF SUNUWAR, // 11BC0..11BE1 UNKNOWN, // 11BE2..11BEF SUNUWAR, // 11BF0..11BF9 @@ -8552,7 +8673,11 @@ class Character implements java.io.Serializable, Comparable, Constabl GUNJALA_GONDI, // 11D93..11D98 UNKNOWN, // 11D99..11D9F GUNJALA_GONDI, // 11DA0..11DA9 - UNKNOWN, // 11DAA..11EDF + UNKNOWN, // 11DAA..11DAF + TOLONG_SIKI, // 11DB0..11DDB + UNKNOWN, // 11DDC..11DDF + TOLONG_SIKI, // 11DE0..11DE9 + UNKNOWN, // 11DEA..11EDF MAKASAR, // 11EE0..11EF8 UNKNOWN, // 11EF9..11EFF KAWI, // 11F00..11F10 @@ -8612,7 +8737,11 @@ class Character implements java.io.Serializable, Comparable, Constabl KIRAT_RAI, // 16D40..16D79 UNKNOWN, // 16D7A..16E3F MEDEFAIDRIN, // 16E40..16E9A - UNKNOWN, // 16E9B..16EFF + UNKNOWN, // 16E9B..16E9F + BERIA_ERFE, // 16EA0..16EB8 + UNKNOWN, // 16EB9..16EBA + BERIA_ERFE, // 16EBB..16ED3 + UNKNOWN, // 16ED4..16EFF MIAO, // 16F00..16F4A UNKNOWN, // 16F4B..16F4E MIAO, // 16F4F..16F87 @@ -8624,16 +8753,16 @@ class Character implements java.io.Serializable, Comparable, Constabl HAN, // 16FE2..16FE3 KHITAN_SMALL_SCRIPT, // 16FE4 UNKNOWN, // 16FE5..16FEF - HAN, // 16FF0..16FF1 - UNKNOWN, // 16FF2..16FFF - TANGUT, // 17000..187F7 - UNKNOWN, // 187F8..187FF - TANGUT, // 18800..18AFF + HAN, // 16FF0..16FF6 + UNKNOWN, // 16FF7..16FFF + TANGUT, // 17000..18AFF KHITAN_SMALL_SCRIPT, // 18B00..18CD5 UNKNOWN, // 18CD6..18CFE KHITAN_SMALL_SCRIPT, // 18CFF - TANGUT, // 18D00..18D08 - UNKNOWN, // 18D09..1AFEF + TANGUT, // 18D00..18D1E + UNKNOWN, // 18D1F..18D7F + TANGUT, // 18D80..18DF2 + UNKNOWN, // 18DF3..1AFEF KATAKANA, // 1AFF0..1AFF3 UNKNOWN, // 1AFF4 KATAKANA, // 1AFF5..1AFFB @@ -8665,10 +8794,14 @@ class Character implements java.io.Serializable, Comparable, Constabl DUPLOYAN, // 1BC9C..1BC9F COMMON, // 1BCA0..1BCA3 UNKNOWN, // 1BCA4..1CBFF - COMMON, // 1CC00..1CCF9 - UNKNOWN, // 1CCFA..1CCFF + COMMON, // 1CC00..1CCFC + UNKNOWN, // 1CCFD..1CCFF COMMON, // 1CD00..1CEB3 - UNKNOWN, // 1CEB4..1CEFF + UNKNOWN, // 1CEB4..1CEB9 + COMMON, // 1CEBA..1CED0 + UNKNOWN, // 1CED1..1CEDF + COMMON, // 1CEE0..1CEF0 + UNKNOWN, // 1CEF1..1CEFF INHERITED, // 1CF00..1CF2D UNKNOWN, // 1CF2E..1CF2F INHERITED, // 1CF30..1CF46 @@ -8783,7 +8916,13 @@ class Character implements java.io.Serializable, Comparable, Constabl OL_ONAL, // 1E5D0..1E5FA UNKNOWN, // 1E5FB..1E5FE OL_ONAL, // 1E5FF - UNKNOWN, // 1E600..1E7DF + UNKNOWN, // 1E600..1E6BF + TAI_YO, // 1E6C0..1E6DE + UNKNOWN, // 1E6DF + TAI_YO, // 1E6E0..1E6F5 + UNKNOWN, // 1E6F6..1E6FD + TAI_YO, // 1E6FE..1E6FF + UNKNOWN, // 1E700..1E7DF ETHIOPIC, // 1E7E0..1E7E6 UNKNOWN, // 1E7E7 ETHIOPIC, // 1E7E8..1E7EB @@ -8900,15 +9039,13 @@ class Character implements java.io.Serializable, Comparable, Constabl UNKNOWN, // 1F252..1F25F COMMON, // 1F260..1F265 UNKNOWN, // 1F266..1F2FF - COMMON, // 1F300..1F6D7 - UNKNOWN, // 1F6D8..1F6DB + COMMON, // 1F300..1F6D8 + UNKNOWN, // 1F6D9..1F6DB COMMON, // 1F6DC..1F6EC UNKNOWN, // 1F6ED..1F6EF COMMON, // 1F6F0..1F6FC UNKNOWN, // 1F6FD..1F6FF - COMMON, // 1F700..1F776 - UNKNOWN, // 1F777..1F77A - COMMON, // 1F77B..1F7D9 + COMMON, // 1F700..1F7D9 UNKNOWN, // 1F7DA..1F7DF COMMON, // 1F7E0..1F7EB UNKNOWN, // 1F7EC..1F7EF @@ -8927,35 +9064,37 @@ class Character implements java.io.Serializable, Comparable, Constabl COMMON, // 1F8B0..1F8BB UNKNOWN, // 1F8BC..1F8BF COMMON, // 1F8C0..1F8C1 - UNKNOWN, // 1F8C2..1F8FF - COMMON, // 1F900..1FA53 - UNKNOWN, // 1FA54..1FA5F + UNKNOWN, // 1F8C2..1F8CF + COMMON, // 1F8D0..1F8D8 + UNKNOWN, // 1F8D9..1F8FF + COMMON, // 1F900..1FA57 + UNKNOWN, // 1FA58..1FA5F COMMON, // 1FA60..1FA6D UNKNOWN, // 1FA6E..1FA6F COMMON, // 1FA70..1FA7C UNKNOWN, // 1FA7D..1FA7F - COMMON, // 1FA80..1FA89 - UNKNOWN, // 1FA8A..1FA8E - COMMON, // 1FA8F..1FAC6 - UNKNOWN, // 1FAC7..1FACD - COMMON, // 1FACE..1FADC + COMMON, // 1FA80..1FA8A + UNKNOWN, // 1FA8B..1FA8D + COMMON, // 1FA8E..1FAC6 + UNKNOWN, // 1FAC7 + COMMON, // 1FAC8 + UNKNOWN, // 1FAC9..1FACC + COMMON, // 1FACD..1FADC UNKNOWN, // 1FADD..1FADE - COMMON, // 1FADF..1FAE9 - UNKNOWN, // 1FAEA..1FAEF - COMMON, // 1FAF0..1FAF8 + COMMON, // 1FADF..1FAEA + UNKNOWN, // 1FAEB..1FAEE + COMMON, // 1FAEF..1FAF8 UNKNOWN, // 1FAF9..1FAFF COMMON, // 1FB00..1FB92 UNKNOWN, // 1FB93 - COMMON, // 1FB94..1FBF9 - UNKNOWN, // 1FBFA..1FFFF + COMMON, // 1FB94..1FBFA + UNKNOWN, // 1FBFB..1FFFF HAN, // 20000..2A6DF UNKNOWN, // 2A6E0..2A6FF - HAN, // 2A700..2B739 - UNKNOWN, // 2B73A..2B73F - HAN, // 2B740..2B81D + HAN, // 2A700..2B81D UNKNOWN, // 2B81E..2B81F - HAN, // 2B820..2CEA1 - UNKNOWN, // 2CEA2..2CEAF + HAN, // 2B820..2CEAD + UNKNOWN, // 2CEAE..2CEAF HAN, // 2CEB0..2EBE0 UNKNOWN, // 2EBE1..2EBEF HAN, // 2EBF0..2EE5D @@ -8964,8 +9103,8 @@ class Character implements java.io.Serializable, Comparable, Constabl UNKNOWN, // 2FA1E..2FFFF HAN, // 30000..3134A UNKNOWN, // 3134B..3134F - HAN, // 31350..323AF - UNKNOWN, // 323B0..E0000 + HAN, // 31350..33479 + UNKNOWN, // 3347A..E0000 COMMON, // E0001 UNKNOWN, // E0002..E001F COMMON, // E0020..E007F @@ -8989,6 +9128,7 @@ class Character implements java.io.Serializable, Comparable, Constabl aliases.put("BASS", BASSA_VAH); aliases.put("BATK", BATAK); aliases.put("BENG", BENGALI); + aliases.put("BERF", BERIA_ERFE); aliases.put("BHKS", BHAIKSUKI); aliases.put("BOPO", BOPOMOFO); aliases.put("BRAH", BRAHMI); @@ -9107,6 +9247,7 @@ class Character implements java.io.Serializable, Comparable, Constabl aliases.put("SHAW", SHAVIAN); aliases.put("SHRD", SHARADA); aliases.put("SIDD", SIDDHAM); + aliases.put("SIDT", SIDETIC); aliases.put("SIND", KHUDAWADI); aliases.put("SINH", SINHALA); aliases.put("SOGD", SOGDIAN); @@ -9124,6 +9265,7 @@ class Character implements java.io.Serializable, Comparable, Constabl aliases.put("TAML", TAMIL); aliases.put("TANG", TANGUT); aliases.put("TAVT", TAI_VIET); + aliases.put("TAYO", TAI_YO); aliases.put("TELU", TELUGU); aliases.put("TFNG", TIFINAGH); aliases.put("TGLG", TAGALOG); @@ -9133,6 +9275,7 @@ class Character implements java.io.Serializable, Comparable, Constabl aliases.put("TIRH", TIRHUTA); aliases.put("TNSA", TANGSA); aliases.put("TODR", TODHRI); + aliases.put("TOLS", TOLONG_SIKI); aliases.put("TOTO", TOTO); aliases.put("TUTG", TULU_TIGALARI); aliases.put("UGAR", UGARITIC); diff --git a/src/java.base/share/classes/java/text/CollationElementIterator.java b/src/java.base/share/classes/java/text/CollationElementIterator.java index 5469b95b11e..46fe80e1bda 100644 --- a/src/java.base/share/classes/java/text/CollationElementIterator.java +++ b/src/java.base/share/classes/java/text/CollationElementIterator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2025, 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 @@ -661,7 +661,7 @@ public final class CollationElementIterator // (the Normalizer is cloned here so that the seeking we do in the next loop // won't affect our real position in the text) - NormalizerBase tempText = (NormalizerBase)text.clone(); + NormalizerBase tempText = text.clone(); // extract the next maxLength characters in the string (we have to do this using the // Normalizer to ensure that our offsets correspond to those the rest of the @@ -732,7 +732,7 @@ public final class CollationElementIterator pair = list.lastElement(); int maxLength = pair.entryName.length(); - NormalizerBase tempText = (NormalizerBase)text.clone(); + NormalizerBase tempText = text.clone(); tempText.next(); key.setLength(0); diff --git a/src/java.base/share/classes/jdk/internal/icu/impl/CharacterIteratorWrapper.java b/src/java.base/share/classes/jdk/internal/icu/impl/CharacterIteratorWrapper.java index 79a6264d16d..698f28ea2be 100644 --- a/src/java.base/share/classes/jdk/internal/icu/impl/CharacterIteratorWrapper.java +++ b/src/java.base/share/classes/jdk/internal/icu/impl/CharacterIteratorWrapper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, 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 @@ -47,7 +47,7 @@ import jdk.internal.icu.text.UCharacterIterator; * @author ram */ -public class CharacterIteratorWrapper extends UCharacterIterator { +public class CharacterIteratorWrapper extends UCharacterIterator implements Cloneable { private CharacterIterator iterator; @@ -135,7 +135,7 @@ public class CharacterIteratorWrapper extends UCharacterIterator { * Creates a clone of this iterator. Clones the underlying character iterator. * @see UCharacterIterator#clone() */ - public Object clone(){ + public CharacterIteratorWrapper clone(){ try { CharacterIteratorWrapper result = (CharacterIteratorWrapper) super.clone(); result.iterator = (CharacterIterator)this.iterator.clone(); diff --git a/src/java.base/share/classes/jdk/internal/icu/impl/Norm2AllModes.java b/src/java.base/share/classes/jdk/internal/icu/impl/Norm2AllModes.java index 7922bb46829..67c9eff6f34 100644 --- a/src/java.base/share/classes/jdk/internal/icu/impl/Norm2AllModes.java +++ b/src/java.base/share/classes/jdk/internal/icu/impl/Norm2AllModes.java @@ -269,8 +269,8 @@ public final class Norm2AllModes { private Norm2AllModesSingleton(String name) { try { @SuppressWarnings("deprecation") - String DATA_FILE_NAME = "/jdk/internal/icu/impl/data/icudt" + - VersionInfo.ICU_DATA_VERSION_PATH + "/" + name + ".nrm"; + String DATA_FILE_NAME = "/jdk/internal/icu/impl/data/icudata/" + + name + ".nrm"; NormalizerImpl impl=new NormalizerImpl().load(DATA_FILE_NAME); allModes=new Norm2AllModes(impl); } catch (RuntimeException e) { diff --git a/src/java.base/share/classes/jdk/internal/icu/impl/ReplaceableUCharacterIterator.java b/src/java.base/share/classes/jdk/internal/icu/impl/ReplaceableUCharacterIterator.java index cf58f614155..b44a0d7675c 100644 --- a/src/java.base/share/classes/jdk/internal/icu/impl/ReplaceableUCharacterIterator.java +++ b/src/java.base/share/classes/jdk/internal/icu/impl/ReplaceableUCharacterIterator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -51,7 +51,7 @@ import jdk.internal.icu.text.UCharacterIterator; * * What are first, last, and getBeginIndex doing here?!?!?! */ -public class ReplaceableUCharacterIterator extends UCharacterIterator { +public class ReplaceableUCharacterIterator extends UCharacterIterator implements Cloneable { // public constructor ------------------------------------------------------ @@ -86,9 +86,9 @@ public class ReplaceableUCharacterIterator extends UCharacterIterator { * Replaceableobject * @return copy of this iterator */ - public Object clone(){ + public ReplaceableUCharacterIterator clone(){ try { - return super.clone(); + return (ReplaceableUCharacterIterator) super.clone(); } catch (CloneNotSupportedException e) { return null; // never invoked } diff --git a/src/java.base/share/classes/jdk/internal/icu/impl/UBiDiProps.java b/src/java.base/share/classes/jdk/internal/icu/impl/UBiDiProps.java index 287ac38c87b..e7e1e65c2a0 100644 --- a/src/java.base/share/classes/jdk/internal/icu/impl/UBiDiProps.java +++ b/src/java.base/share/classes/jdk/internal/icu/impl/UBiDiProps.java @@ -201,9 +201,7 @@ public final class UBiDiProps { // data format constants ----------------------------------------------- *** @SuppressWarnings("deprecation") private static final String DATA_FILE_NAME = - "/jdk/internal/icu/impl/data/icudt" + - VersionInfo.ICU_DATA_VERSION_PATH + - "/ubidi.icu"; + "/jdk/internal/icu/impl/data/icudata/ubidi.icu"; /* format "BiDi" */ private static final int FMT=0x42694469; diff --git a/src/java.base/share/classes/jdk/internal/icu/impl/UCharacterProperty.java b/src/java.base/share/classes/jdk/internal/icu/impl/UCharacterProperty.java index f439ae23d0f..073ffe933df 100644 --- a/src/java.base/share/classes/jdk/internal/icu/impl/UCharacterProperty.java +++ b/src/java.base/share/classes/jdk/internal/icu/impl/UCharacterProperty.java @@ -330,9 +330,7 @@ public final class UCharacterProperty */ @SuppressWarnings("deprecation") private static final String DATA_FILE_NAME_ = - "/jdk/internal/icu/impl/data/icudt" + - VersionInfo.ICU_DATA_VERSION_PATH + - "/uprops.icu"; + "/jdk/internal/icu/impl/data/icudata/uprops.icu"; /** * Shift value for lead surrogate to form a supplementary character. diff --git a/src/java.base/share/classes/jdk/internal/icu/impl/data/icudt76b/nfc.nrm b/src/java.base/share/classes/jdk/internal/icu/impl/data/icudata/nfc.nrm similarity index 64% rename from src/java.base/share/classes/jdk/internal/icu/impl/data/icudt76b/nfc.nrm rename to src/java.base/share/classes/jdk/internal/icu/impl/data/icudata/nfc.nrm index 114da46498df3ff787c0e42d58a4b309863f91c4..fa1d2917ef760977c7474f739ab8016201b9d61c 100644 GIT binary patch delta 564 zcmZpe&Gcb5(*zM6K?W2M!NB16hJm5y1Or1a2A=3(&zvCtXk!yQ2md3sPwZUmn(TI9 z=rmb?$!_vBjyo~Z0ZeKTD4oFS4`N9dz(gQ4hb&mM0Vp#;dam?Jm@JD3P-Khr5vVF5 zn7H&62uu3@R?^Sl5+7w4I5lMWScGI`WOSI+WK3k7IO<`tGC}O8U~HgVl1w2` zyA&HIhfJkR=VU!@F(#SGlc#WLGESI0fm@z&@#K447nwK790c(=FG6(k>jL%YOm^cD znXJI1BPR0>VIYhp^8utpK<2+JzpSjR7L%H+nXHR!$mAJ3j;txNIkHug@A0HFwgMH& zO-|vJVFN0d%6=KhRp6Ih3Nr*m%TC_NEk1b_uLFzxLHUco@U)q%#va&lE7#SG=5wxbEOH=SAbkiDA_C!W*7B-i*}TZ_hb`mF$+LT&a;$;^0K^}d*8l(j delta 452 zcmew`o2g+o(*zM60R|Kh!NB0x!objzz`)RpfhRiHGyBRP+}Om$VM(8#yn#`~O8OdH;-T~hP7UdQEJ89oGBQkRGAc4g9Q80+87KBrFg8#wNG1`e zU5bs9Lnc$Ea5Qd7MRJorZWeKiW7#~(tAb@SkN-DY#>~mgy-xvh$7^~3 diff --git a/src/java.base/share/classes/jdk/internal/icu/impl/data/icudt76b/nfkc.nrm b/src/java.base/share/classes/jdk/internal/icu/impl/data/icudata/nfkc.nrm similarity index 90% rename from src/java.base/share/classes/jdk/internal/icu/impl/data/icudt76b/nfkc.nrm rename to src/java.base/share/classes/jdk/internal/icu/impl/data/icudata/nfkc.nrm index 66421d6f29f65ae3e96ca6a5db716eb86da56a29..c78a9d9cac5e80594bcd617998d54e8e47a8f2ab 100644 GIT binary patch delta 832 zcmZXQUr19?9LMjyyY616b2Gt%ZgjCv^4 zkP5Lnfmu)wioW%SyKIEjf+!G?6{56!@F7e^Xb;gJd#H20ZYrH0=bZ2PeSY5`oZtL2 zTYAiP)5FT8Eh{ET@g7OaR!Y+6|Msm&gKd8(_OwgWU5~Zb+N}0N_pF7N?@{WF8QmwJ z3}h8*MPRw8&s2fce8#%gbvwQ_gqK7O^df=3jF$c3%OHSY1aQbyaM5$%wv zgg(yQ?l8}}R8@;>{XFCDG!B-Z_VZM=-=C6XN&Y*zDN+SJElRQB2;K85)x*Zfk;Mu= rMRlG&s_Md%GQ;R<>(Hvq9%h$~)5=(HW3l~&X4J)#=j0{+?bFgffQ0KN delta 730 zcmZXSUr19?9LMjy+wQ&2&0TSvjjxP{`PeMn3F8tk#1)L{UUR z1}0&DIFVpM4@#Cu%Fl>|usjjUI%;)}IHVAnB=dH)l$}!{L0-_jA4n&W|%c zV_SJ{yX|F$w5_@&sXr)5AF`5E{BPcgAFwqww@hEvbk75AN?XuA>2i5EcvNXKm-T?$ zqdrk+WrT~4VM~R-Rh`PJA1}Lq6pEeLhnuR@o)pe*+_rHlQT1_WMd4#SBYXGZ)FwEM zFV^(o>-Ge`haZXXQ#_}?+@yJ3uWG?Q;_o6JXsl_+KZ&gZobc~}rHGrqbrRXh*J^jj zAs(z3ygR}T*B-uDM|j5NQEFbF4OWSYDZ7v;!%QsSz+ z{R&w}w?GF!x`FNkh8P@%ZZv>68iQUqiylG>uEAaKqCqr*CMv|r2vW&xBQOqGSb!z? z46~4fB4l753a|n*@EVq33g#dWzo?x$sY(NMCv`_@oF-_Bo>8cy#cCk`;qH(x3)RSz zbwPvP+}Z;=Dgg4+Njgh&v`9;Io$1WaFpIGc784LN*2PY+^Jst#DwK^_X*RL-4)%h* zVei-%_B*0k|3W205nm+C`#f>6ZRM+;ERqW9s#K5)nyMjfucd@;IP$-0mkH+Ykon2e};Xmi(-tRoVbI+MO zXLg-;UAW*{pO_b?-Sxz3TJvO0duls+vo+21X&O7Kxx^vO<>`g*)6-*?x_hctEFGt5 zo+`eoyK%Q`gxO>6G#bnUMyq*5&ohsaka+^v84_~DykK5|sqWm$KJ!7IJztIV_n3j_etuTYO&;_)fHqmG4O1hQ?>0`8$Zo=pmy3L6r<8}HT?jhLc&PduH z^`htFaQ+wcYq;r73H(5RqPOX{j4;U(n3rX+9F|Y}9X^{7!Aa3B9b^wjaE60AVRP6# zwvdEaLpY<|$%|w+u@?3sd%@v2Ap*YQTaj6WC2 zSjpFtw|S6vMh@R(HOF@7qu;V$-wg6D`(BL9XvGLA{C?QoUf4yZLvvDG?qS_OSomlB z%c#xI@e{hAU$Sj}o!^G_-o$_)yk8^;86`O=Qgy$`61n;w#QQ~&m@1}=nW9Sf8#!XW zSR@+tJh8%PwXaG%PeLMq_DiD8ZcB6+IZk_QhS;d*8Qn&k*n<9d#7?6WalYsY^A3n3 zB!CG~5ub~%!htiGa1n`D!qyFOC&~!h{evwMl5Qy+kBl+eWRgsg8R*4tHz0ECAbq^8 z%ThVnMog7{@iXp^!fIDyykY5ie6QH!@MW!>i#J=U%O!F-Dn&XwC|gl^#*{d&at*3e zl~|4HE8$aLg|S0txHj1CPQsY|NcLjlF`Fb$ zItA@LY@f#?3KQjJa#miKx8+rGO5T(|%6|D5w&_zkQ52U)C8=~|Rwb&D%KmRcb}x=# zbE{p+rr1Y9Y8LO!pPZkchY8W&u z;c<_SIga*S)2I8*pju|XklC(UR4Z#z0dfi(dKp)6pgMg6ys70GojBz-HzI1IdehEU zJ8*TY9(4eGsrRI|sUr$6O`TBZ{$XCSHFXWIVv1YcLRb}0w_;rCcUxlIj_72t#E7W> zf5EIs(f>N!ickMjxGJpJ>;w-4YKFpQUncZr=Ru&_v_=e;y`eLU1QFql`Wptd|70>z`CZ(6cdu%J`#m}Nr2bOsMj@Ib-69{4aP84f@B z4IIwIvc5EkSv&^X>xaR*h7s`4qFz{D&Y-5k3w?_+p}I5{&r^1OmK}%gf)+h@0g`5W64G0 U^1_wINml05rcSk``Ca(@4RK>6)&Kwi delta 1792 zcmcIieN2^A7=JJ4ea|HyHzcNpsELT1UQAQ(mbjUs1Hr+fvP`YeXS?RE>;Uot`qfcoO+2*vS>pAax@5Ddqzusr(`8^-M=bY!9 z_qlS#(>?5I^9EClyFPHAqK2{Q1&9U=1N#kw_8A_2((ojGroPM0POM8f8h)m}*f5g9 zY<17(4?I&XiK135?yW>G&GbNMtt3$;aO#JhBn&W&M(izcE=X_!8N zqI8{88FA}lyVTMK`Xb$;xpWVH2yn=)Ls7pr;DA;%3dsiA2KpfFh^Og0b{9$|%?-H4 zdOIGxgY-RZ({9?U(YgfZ?Q$X~T@F1%&*N2ez{wt=mx22;9Rp@E{S$Nm{J$Bp3Q&|0 zE1xC1RV+P*SuUceT_UcU%Zu1l7BD~jcVQ*0%+0bzYzbSzs@Pgq6N}Wdr%@-{#+(M> zz`b@;O1t@ByS_edVQ=Vnv2x=l#yWT_hFz)~EmlqGsnfd6&_rRq?31|9&au;Gh+WV= z8)74>EyLT)uCY;`!2XJpT;$vg@id;xGXM+t!~79GpD*N}Aj;f#_F8mZf>m~*dl)3zS@GBtc zO|xt>$h!FZvD6B&6kf616jh=cj8c>Kih3{_uxNyuXTdDP9Ih<3?a#0A|=LsZ7zux)oQTx1FAQG4DZ9nGWXUG?^XGxhQk> zUN9h%%K|ebi$P|?{|~5+47wpUwR&)1dU@W2S-5MO<$?(r%?{xfxkO(AN8~cOlGe&K zXaJg653A-*2c%tPWGK z35S0guK#F)>;rhYS@y|6+3(1Zbi_mxMgHJM|0jKa?9l((-cHND#XX63O5mO(d*7^4 zp-ybdRmnBk_MY-zi8>J4uBJbL?fI3jVU-xlQaO1(wKk7hiAJIkQ8I6i%~t+G)a9U0 zt*esuyftM=&0Z$e@o<{@eP)t6U!7!6e{#F2u5S2NrB)WH96z;hZY=Srn;Q$YQnjt$!hSi42W}exRk0R^ZaVxrXa+1Omow~-wdY()p)koo>@17 SJcjZ$%(24_mPy}@eg6PY&nM>q diff --git a/src/java.base/share/classes/jdk/internal/icu/impl/data/icudata/uprops.icu b/src/java.base/share/classes/jdk/internal/icu/impl/data/icudata/uprops.icu new file mode 100644 index 0000000000000000000000000000000000000000..b72fcd903e009ddba8f3c750bd152cd0f3aa3db5 GIT binary patch literal 170864 zcmeFa2b|PY_dh%>nUd)(Gqas-tb!E~6%-3T%3{Z^*s!43zygYj1q%z@j-d0nn6!S$+sF4uLhuw|Q9y#8I+giO2gSeI+e#PMsn zT*-7w4UI^i?`iZ+_s#NM;Jdf3UyKKhodn-`+pmzpsD1 z{|NtX{*(Nt`Dgp5_^nD<68|ee-|;W?f8qbe|4YCX2>9kI zD+2LAF3=e0uN)s38dw9WwIMJ9yfzPv4vYc1D=d2j4h$R)JwHA$IWW!cZ`TB-2WFu* za6#aTz}&#CfqQ^|IIuAAY~WqsJ`Q{m_&V?^aNh>iz^_41FsiHwW`niCmf!&3h6Ohb zjt&kA?j9TyoDe)Lcua6y@but`V3`fe6~POGbAz`A=LPQ%E>K#7i-JpnF9qKWE`e<| z8n&l_;0HzeP4JhHI}{8hLYYuqaCvBOXn3eKG%z$Wv}I_A&>o?qLi>dd2^}Ar9y&jC zNoZzhjs-V{<^@|qcZVJdJrQ~)^aAj&h29H&75WBhZ3!*IHG}5T0->Lae1x<`Ii=ME zTeN0v6|){P6x-9*3eC~h)kcs^6rwiQw$`?%IAi{`YP+%CY5OXz+QG^S?Fj88ZHhKU z8LgeEouggio2$*i2<--nLmGNnyH$HwyGLCUA{OG7)SlH|)!xJ{rM(9u+oFA}eX0GR z{RZ3*VXqiVd{|>!JDjp_^~yBid}V&zD_qa{|8dSEfZHhC79JR0U0D%cCpVt4T zWk#eqva0XZ$oi4BQ6JeP(iwsG>d0P^10#nqmk#q_Il($kxzhr9r-EpSJWSkMN`olr4_Iu+H7v^f71U(;Qz`9L|2Wj z6J6amw{M7U*hl;;`ak?XGy-h)Mz@A@h9{#tMt3X9##w6M8{gdMl<4^A5vYxx6qTM& zMrQ~6gY`mKE{|Rvy&-x>^uB2EtS7oK`mXPS=*Q77qwq;H`fT*oKF*#Eljj3oNu^U` zQZrLa(&6-`=~L5pq*r9t$c)HL%iNv$KHHKVm%TQ7clJ}gQQu3yT7N&6&W*@T%gxWd zl23=Gg&u=XiX*gX+RfVM?zh9y&w9$c*O)sN#Or{FSR&RG%g2VsI$~?YMrgOj zHm4Q>%fPx#Y-d>S6+19CA$BBiQz7nn<@DI8z@HI2J9c&KV&HCo<@VU4vHO5N&GL$C z?WDlBvBj~sVn*L%pT$0meHr^9sK);A&5bMZNIZkbd1XbsH9jz27atzqG(I>!Iz9%h zrziR)R!OXp*gP>bF(NS<y5>zAb89u1O>{<*X{Zi_!)Q7QWoU2NG zoBB+&;w(@7nhvEsY~M{M(|WoNJ<_e^+ppXs@NIh4pvqciQR%hCI{tn>J=9)(dPI6; zdbC}Sn!bNFvi7k!4%?-7O7Cg4RV&l=e@4>>m}OB)AC^8LeY7$8AFVo+pf6I6?TKeNmDpQkb z&h$_JYFL)0zsszV{xGvPSk}*MlIhHBm)Rh*S!S!u4w;QId!*kqw{Mq}(V0VFIWlux zWyneZabw zXhS~Ol067VUArNDX?9}hG#+1s)(r#M0{ zW$(wIXy&m^_J>M+yZ*>yDz;7_zrQK z{I~h=Wk&n(&7U3my81?TZC^Ai>zcH_d0(oiJXVM64=WRT@%y%wnX^>|XgdQ-OuUEcAK_QV_~%^=H83g#SB*HCuQTkL5a+mCKZaG#=RE7>7`baKpWYXcyNgJ1@=_F^cCzPOPM| zcrjNsJnJ1>Q>z$Htvb~r#{D;V9Pi%b^|{KU0DCpU$&>6d%45t{pOMe3lSy#v=RXRX z8(1&$`0^D;8sAgsg|)kTj&pbeE-oyZ%?al<^EBJ5+mYlrTl7ynTB6Xl=5Bqwo)0Z-=3wi$!xMYSbj_6?DxT}=jiSB<8gRK zhp!qy9_W8>|4%_~-N?_^M$^KI-p8t=lz^geeB`e_)di~*J5R|{;`U2>nO#oz~{fn zxnL{xbp*r;oq3#Ff6q8#EAl!N`2;U&oyV~+b=xb!t%#pb5I&=A)%esxo@^Vd#cj>B z=q7GAJ73-CTH8Xo`d*5)IkjILTp;E07c3rl%;7Jp*i7RNTiWa;m+bY>$jkk$T8u{> zZVTDm!L4g=gKJp6KuI6&E$38Mz}Rx|Qy+sf*T}T!ft+=(kY&Vpe98KQ{_yE~o$kTi z8+H%s9@ZV~gn66?ExtG5_GRtZw=`zNXri)~ncZx`DE1;fN;p%^{Y5Qmh@yw}#jL2M zmP8L(e{n2WySR;IFZ95?R2|FAJc$==R^A+mHM1yjjs0c4!|lkn4z@r%QqmGdJHDtr zj+1bcoezu*lzpT$pX0x37!0Tli)(RhCl*w1# zU-Yn#6XnHdY@MA2Qy-90KZuGpk(b*=Ew;t>XBu7VFQd4oK2Tk3t8#V*ql$@=z8Z?z?e9z29v!vV%;I|-%N|&wE!Mg_7imK}7j+KCE7Iai z(oe&TS>^9ZXlDJatT(K+N_#5Hi8_3lbFjSp9f^$ujuJbX)4t90xHiYjB(`U=9JFTb zxQz1<9%Da~b2>*8M-k&$Z+IqazqrN9StGe!RyWpf9XGsE!H)r~hw^hV>^0BNj_CH! z1fm8%aWFs3%ijZPWaoPDjY#LFoufO)bnf0cu5&`?k(~qZor8VyM2g>S;Ue2^Vr9I> zQXKXMU$jkSl)pRD6%n3M=FeuC+)}BQUEvYO$~=Eah!Fx6Q9@X3J(|n!2E?>8z`MX1%;qF*xqC4H0?ap`B;JIcOrhu0`KiAS;BT^R0 z*5dXr$76&zA5cz}t$^1e=kVAmw}{$s1wNub;Bz6SAn(Qy97*UV$PA}2V%+nlVGqU=4b2iooHWIYv$27+A;~jZ$%Jv8P zP64lfus%7Rs}<{|&l_qdJo}~FufbDYXb)i?<|jR8@#AkSXZY9(hlVk-)qpL1Xq@w|)9+}YhI ztfg}2ZgM-uG7GPiaV#rev;NUL9`#nFd_FKO^~Zb+VG{FYM{L%Hy?;!@N1-pAQ+7|! z=OAZ~QWjp5?4AlIoi!u+S;4m#yDJJuiTN-}b}!s_ROqqsEMzCjEo|119x2bwy+k|m z+`_n0cFZT*W%=1>O?H)-Jh$7&*JF$^-2rU3;})QJs)hRR zD#m7u$Mc+VWyiX=F&XbuCO@-BxaS@b{MMH47ZIqZ8)h=R#Ltaz9~S~Y1NmHGDR_ny ze3Bzz+qJGbh^+0Q=6ZBj#1$3lpu4k0FU&x4*3)hOTr1Rv*XEpn-NGx7eFw`VUyDaF zGfzKJSBpP&H(R+R&r4c*e|N(>J73r=se#!;^M&6z>oKk);=Do|+m^WI`wHB1UG%9L z+%*KQaS!Sm+BFy+dpKO%NE_TavU70v@a~b_TXrj5h99=ez&L7^+Q7`LI?PNJ8NSL4 zWw9DkN2xQ^#r}|glz)bQaUc{Jl~`Oq>f+GkQRVEDM_oK>@(iq9U7T2a@r=of74~fz zjSuf1)rd8){&9^n$K$OW#;`orY*yJRJJo^yPBGS2tCg*7(=}kL-btn!xg9fbsanpd zpOxjQy!VymvwMjx*saxRXbx6F<(VsqWImF3Pj^5IbOyQt-9atb8SDynhqO?q{dno` zmK{a?6-Ucjsd|Qp<8GgGlHQqGkj*NYu3R3gy?hSZpCnW^(v|8=y%VeA*86!VyWE_|2ToNg}0*h<1Bc(Y>lt-AadyyW`cR8;^JV|znTajrg zJ5heOy11n);pH<=9%&yjvql~zXzgcc#&rsuiFnN-C7gdllz(w(RCqDy@H-OBLTC6j zs8dGS#u`Th{b*E(rVLHSxF$qfzdXghVP*Kq3iqh;5poawo|DD%(UN)n#%5-HR&gn|$ z3LQbJ>w(!Dx1bYnCRB>P)}mC=7S`mSUTI}=zw-OI7&ooVI80e6dY9rWnXT4%e21tM zeC}}09$W3`*^j;UoTW9YFhbRhJadJxsa$^hucMAKVTc;rz~rC*tcGluGv|> zxVGlA%jAtcW#6~*YwwEj$)nU5;OFvEcCyiZmi+TPS@0@>JqJE#fIVpdt?YRk$dzu7 zU2$OfMBbj?YL(Ss*0GVp`ASy_h;mK2*-Uo&c0ADY`Jnhsx#gDy_{%rClZdxZSxQlw zQ`YTG-`L~4v%WA!w!evf7+yoUDDa*{@%rg<7E&L~0)emdiZ;EDIYm zS^JoUMvK=X=gfU#?>g+B8?o>5SXKx1h)H6l*fTbF?ENaO!CdSeOx`lvR=9oGHpXKt z>SQl51KP{U<{YB8eS5O+==m|XQjS04#vZ|r%v6QlZq~5+*zDzN+;7-uIL!~rPyHj= zO37X=r99?1xz_AsmXfZ%Apw6&*%z3rDN5F>0{jzBsWW2J%v02bm45~{M>2`GDYF_n z9Bpa+CT-3nnf=9h4Rm00jsRptD! z%~C$rn)o=#Ij3BBeuEI-b!0rfUoeVS)y?1J$YMktepix$yNa^5EAw=P9y7DNtPS*~ zyO8+%6}yfv#^BQzQ^jxI(2qT{hDL$+Tb!$0TRDdr*|RFd7}QoKqbIw6CJV(!uWZQ?flGtzJ&F2fGt&ZZmwNr$7w4J<2 zU#U0qE9Z?{hce4(IlC-|>3X*N9ol?ta_6+pGdj=iytwn~&Ko-K=$sF~vYOU)M%US0 z7k6FVbwk%3UGwQTC2Zdn*KBqa^U1z#vHyl^ns$bEwsx_0wRVH%UrY$Y{ZS0{qs|Cj z+ylIWN}Z8dJg9R}=g=;Arl#1siB~!Yk+sw}j7F`477v1Zw=gfXH0omD{LnhPrwiNT z(&QNzg9M-D5sMkr84mkS&$yOlOqDzLNn&B=QO?_zETv0OtKU{QS175{rFQwd;rlB4 zi`p+kYZhDQ_WY2LtW2~imLKy~Bx?tIx?!opj1;)sw$9pS9oQN7QP!%H(IJPER}L_hn-pTwhTq!bb7|n$!e)XB8|Z}hd;@>s}$a?gnO{JPbAM+UmrcZz2e<}(_)pOIz+WHrCo`HQ$V+v#eO z^;g35oB4OTPWyO~;FU1ive==jf zLPbWd(w{D^-b`kh%q_`14+HSXn;*JIXN+r+zQ%e`cEQD5-W zqjdx=G98CXKi$H59D~n1a6BE_1;tTYB4tMnb{{6{d;8)eD1NqJpNXwG?Z4|adx-T_ z#nXPk-dV%&tPkTIz?@w^4%T+W{`Bzl?g+<5f|wg~kgv=x`pWp88mJ!?o*xu*2~Rlb zl8aE-r>O6Y(g-kGtg+M_HszAFfZqk!ztO_{^h9U&yifbs z+OL7h@>@qmw!ezG%{h4Qd5xr#ACqW=*t3tUt(TvtEW5di>LqqBfxUHn4jN^nN~u5W zyBOK{uy0qHKSh_O=B-LC;B4%9=N0d~wOd58h|lcIQ@B0p`FLk4KKZxUQ(M5VW&+Ykla5 zI-*uP$<|SzD6(S)Y=!N4tTn1wq^u9-eS_(Y5{z0X7t{1+&8y-ZR*WxPb8UGJy|D-8 zV-W7k_`)@_6334E?0YDj_v~8>^yRPet&)emW!jc(n`2ev%l9_fe4<*&pI1{oM=d&0 zd(~JcjT7qun=S0Rp6iNU=IYTx z$5yKa2JfORch8yd^O=@!VRDV=eRctX#Qxv{S2g%Jxv!25v_@7o@!wY4ux`MoOgC znoG%8MX8r$|1!C=Ua;0phN2*er%#}z&%BHAn7Q$!bMC?u%q+ zl*w#u@43BAvj!u~`Ro$TBlM#rzHU^m9;(}k^1P)u96cNpoXzGJ8zph?HS@Ci#94uv*}Iri`_+vT z+j2AqSgmZ!n(MYo?487_cZu|@IE*XbSt zPtsY`xncKU4ewLOe>8p8{tH+3d?KgOanQr>zNSm(x`RLWwZ{z_N!H5mc4QHl%X;r5 z&{7A&Rrsrwo~&IV9%7`wq-jt}!g8 zrxg`#hVHQ$3ygbMAe#9wItsFnGM;V}GM;cW3Z8LffVb1r0^Av+8ttP7dpgqS?_ho} z2k%mKT-og|*|)_O%Ej|1e#trWk(c&QIThO_UgnAWHNC6Jjx!XCv{e0Q=lQKiIJB0I z$J{&~IX)ZdctPLkh39&hmXxx$l#}jxXMhym!sB%O|IvSnq{lGVO1r^OSqf#%NX><7C;W zcB_r_r74ec_TxUt$^1~y?%2j}YZy@1n-?8#oTzL+XTGd8r#P!#tj%hZ$>qKQmc^KL zVvBa3eC?LXG_+4DPVDAL)( zY+?TL_dWZFnKYHYZNsKi3N%TmQgxNkQhA(gpXXYt972NoR+Asvn76vw#KOCIHG*Q>m8_-{5BAO2~hI4gEjbz1aWKlyU&JS|4oD6XAJ5bT)YF!Uc1=8a`nl3C1}gt z<13p#DZf>3mCJoTEo1jcpD_-f?mBJf3)YsIOm!ladsLzovzYlx zqGdjm#cm@Dqi8Sn65~txIoZVc%6b0egvw*clT|{JotCy!KT3~^eOpr;QG2`Qj8;3v z5w+T7GWyE&sH2z2lTM`Klf)S>TAlM#UZ?h{0%yD2Vs723edZ<4Ak$(Vddcjf4*f(~ z3>BGw<9I#mLS-cwgX1+qb7t_U0Ll@UCQ_FR}3b>vYP~duo0bE9NQpJl0Zn zbzvoy^!9t>-o^DUiuzG1y5g;8p4V2%mm~|L3x8V3)w&f$@PO z0#gFh0%rtf2QCa;8MrQROW^LnLxCr=j|7$kUJ1MtSQ_{u@O|L-peq-fO|t1WDg8o5}E_+8$>!7eL?Eo(2MEm z=~?Ny*;_(yrXNl}nO>6FKC@e9-^{_8Ntt6aCue4)UrE1{{!jXY#@c=l_FK>rNdG7G zW9rZBbJ>@(uVvp!Uy)s!U7B8=z9;=<`iJx%86^|RWHNP`)_#kc(=A7~KARbsSv#|S zW|Q>sna+MM_Iso8w8qm#^n0g>r9eOL_ievl4f#)8H~V0zZHc#xORMc8+dJF0Z~v|N zvF5v*=eOjVry@chG@lw;-uy;rMd)YErMX&z%}2IONJrCIEzo#T<3(D$@hT22r`79; z>{D7>!z^vJhMODiYIsl^qOIR>Lc?@zMB`b_E40nEZ5p0vSghw8-fDQS;ev)s8s-45 z(L3}t0c$n9)ObDMw#J(cdVS+PMS4@?eT|PcKHb>e_;OK$ewE-3!|QEh{eI)8jVP~Z z{8@Sg^P5ICZHbzu?R)fWWQ?pm zx}wxHuxYoZeGUHu$kudd^X|>N>s#wPG)&NU)A!L2(ht>-(vR0q(Yy4s^z-%0^=tK8 z^j-A3^@sE)^ltrS{cZgteYw6&|3?2M=gtLliCn&60z^;DHRf6yCgcX>2IYq3HqDLB zjnN;^?Vj5=cW`b}Zi>FEzJq?LJ{vsu$xVgz_}nCngNRdeUAgmem*nQ;Zp_Wg&CflW zdph@g?)BV%a-ZbB%Kv6VSEwh54cR4fO+| zj&-5sS^3TKTjf5_@0cG8*8TH`=H>xC3cMy`4f$#L)Ah&mGxO)?FN0oh4Vr5p)7H6p zpuJJQB!7od*>Cyz`N#6lfb|7^pZpv7_WV)#_wvi~U*&%U-JbOidkF4sMuG(^^ ze^$+~nvKD}dCfLZ^Px36)$9q{<@p!#&w=&;eOApu`2jT(^OFlFgFL0?_?qc8Gxgbp zeD2Gd^T2XRuDP&Beu@5h%~dtm*W8Bvthw7*KbZScp9s00fEsf(kLd^3JYDm=zP#oY z@OVc*5bLN}TJr^r+kBW+mw>xb4pTKhEUpRawj_MO_L`i$DoYrn)%f;qh)cPz#r3Il3?0I%Qm zo$I_%$E9@|jND~)@jAWmcEg0a2HogwT^qz5ST_(VUr@-w2p@_w2}bu3*kfRpK_1vQ z@=NR10zYV@Zr%Ivd1(X4gFreR#PrzYh8UI@mh;Wia25(%;m#t~;RaeSLY|r#0_EhDmk%6mrn^ zv2|b9{e=5h-5)h;8!-pgYy$hsrS)EYmcG1Rt550+>I?PtxheS<>f7p9%N<)k1h(80 zI<#NELH%a+TLRsoeh(O*uj}`tY8b7bkiV|}$a**yfX}J5C)UrbKfnHR;IFB_x&F@j z2Vk_;tADKi8U2aE+x5@azgGV)jO74md#Ta(=NN}uqyD@4U-adL%kwXQ##Q%KL!cp^ zdq3ZutE-PfAExO0G(cY(8XNj!gn`u?hCtuP=(F{0^v4S;8a6;J?6I3QK)w3Y4Lj(U zHSCUiSi`thcmJhLCp4Y9GNA3;bhNft>lH1}S+Gbuu;tX2sV%3qUeJ1hc6jrI7FWyc zmUCNnZ~ac2tW9fvMmwWruy%IKvXrWSg!^S~%FN5$pIMMulwBpe zO6J_mrI{DAP1%0gwK8vJ-pee@e3khz^JiAg{5!mg-V|Odyk0h%O=t7rjl-jKw;s%X zpZz7gU3k~;o8E|nwFYzHCNZX zU)xx_SMB+=FTh#GHg#v$EvXOJkB0ZeB@N-ww9sSlNo|BSO}kn9JUb^G4UIsAHw&MZ zIUaCA_^G1i$HC4mig7jW&N&3h1AMtQzcwEmsf{1S!pB8^C^JnJIge$v2cJJ&5i4mZw z3W;&7w%)FzzxOth#r-e-|L@}$n;M(h`!R_Ao>7+z zPwl5Qj|ndee--{V{B!#$5m)=m5kLH6V`>#OQqz906_Mu1s*$xL>qj=k>LdS(jEZd6 zepzJK$UYz)6qy(~HgdAEyzS-4jL5nD*Na>lnG?COb#D84%JTL}k$J7-BlkxZL>5I} zjJ%2Qt-G}D68S(`-ai#t-d~G+(=j;mOUJUPI||o4Iim57o2-3*-(H-yg1@RO5Z(IWZVz&5c&Lh&;yBIOXjFn(qHx{jOUH^uLa z-`Box`)=_^<4?z*cdn)5$N1}_wPRpMYy3a)PvT#}KK@bLNAVxq-j4r?9tpMo^;O~S zj`N(6h$hkr*#8i4iU>$Fh;{t^e4?f8iT>Z4@IzuiV$H*fg<4 zV%xsv?s!?iYl&S1Nwff;*gJ7x;_!~U5|a}rc3g%?z?^ToRzzZU)3u2Uo8~rM-gIlz zT}=-ru53QP`TWFntjfe~iF@%{cH)u5Q&e};7fs(Ko@@Fs@mk`&#ImLpiLVkrCjM;t zq3I8_G=1IlW7D5aUp9T;v?8fAJ<-(N^itECO;0sF*Ys-BJIP4X(*Dh5a9ICU`ybYS ztz;%yM{V|nCR>vO#rWjlVJCj7PD7*DS3BCCV4;lB^M+g7yXlqlP@LT zN`9F9Ecq>1e-UlSr`+vrDL*W+R5}IwY5%SJAJhND{;%}ksbggSJ^R1Yv1P}WsoPTb zrXJ}yCG}M5xsF2^QY}Cyr3R$-?Gs+L*7NtuskK|SORe8Bre#cO!_>c0Tc>tvy{9$N z`f%&Q)>~71LeCCLO-vn~Iw5r$^|EbH+b~!*ZX4B>Y1^)CSK#+)JE-l5wkd6Oz@O08 z+BTr=w6>Y;tF;eq*W1o(yR7Y+wwv1 zj}wvliuC^!?YHXRh?~5hLE{hCW7z2$$k zz8yNcsz-W{jwe#me` zNAw%nZ#Bp?8kY5pjNA3(-PPd6_8Zr4Lcg*74h89men%U+V_|)=!AbXTIvc(yG|rfd6U>I>P z^O?`^{sHNYwg)->$+JtN?dqUmVqn5_YMI!`6|N^(BARB%Y6I!F819j;Cf$WIKp?X?@-@a(8GWF z)ia`QPbK4R^Xc>+1|xrr_b3>VgT4EEcXG}3E^#gKo#kEIJKndCccS+y??}KNu2aGK znRi#GcQA2vt6+JGb2*U^b zJtqTqGE#@-M96?Xhjhxv&9(Bd23S1H!}7;#9^!lEc6)V;JvQ*5&*uc-#1 zUaMc}K8Za&+_RnMM9)E96N{R$KpCch^+fO?X`JDYnuCnqQ2ar~Ok+J`iyXFv zu_t;k2WB|PGa9UW8`4+&(WU4Vc~0|7dS4(v9&wpsY!mD|E|{HqMmm%a zDCPv6{~5sh{&!7ryLoo-RIy;Zu{`5n=zq)qO7P~;G5**6FB%KTsFN+BBN1Acf!W$@ zpll5^d|dd1@TstzU~niM2l7csJ==MofNeAdz}LuaM=07!W}YN5%Du-@6l?>t50_X= zTxPA|b`o{SOLO?U3D{%LbSqpJ0xtJ_YS0T^=K&_TCV0od`acGGr7U3lN{UbF-+ z44zkNA-~9uk!?yX(NCiv#~+VBn!GA`xwya{w0-q3_RbyMn;IAe>wjerc5ky1ux0jO zZ_(c?zS?llPw|J` zL*485U?@lxAhHqQU%2-CD)U$OFeC6&;Mc&fP{|je<>4BR#Ma)79d6y7b|>7?;H=;* zbys&Yw6GB@9Ys`7A9y#)>T3^;xJcnYqNrcrn{y9U z{v*O2a7UF70Y$BQ1NR2V7qy@dtsh$7U9bad037T;IJACXgn{(}W~>A9I`CHxn*@dj zK7^$g2%Ulmd|Cz{2PD9`$>OzjwDkb%7$C|dt|Jem=u`BAnZ>YaL=KpDfz4JTJ`YF` zoM+zC%u@IU32qMe9=C-NKiV)aJTLrjz})bC;k&U0-Itx2of$uvA$Fdbf^;By#m*Km zD|R7BxF9Z!UFdQpB30v%1ORgnqEh(Qfpq8}^bRYLV z>Z{ra)QKZ=;|43tT(D+{;7zg}x3!ZLob?>_}V}GCqUw4C#-v9of}k8GwK` z*YI59xdtBkdb^0XY#Gn0kMFmK)`J!VkX_S~qhfo6PID^Ds32GsuKBA-AC+YO)HxHv5|BDQdC(&<~+QJ*%lTYCW!@ z#Z{q4AgT30VGVo}uM;^nr{=h(uw8C~2Wwi>vu5~{@F!^T4z$DN3Qq`6$Om|&nai$~ z`oS~XGh2VtbEyfipD~%JvOosUY;_ex^bGKWJf7J=&jEC)@5SHE^z4V3H4y45@O%&f z>FJ*HJ?BTyz-Wj&!;Z*V94s<&ZscMPm^m%*^q5vSXB=e1YJoKYf}ZugN3&`VC-~TP zvfQY{Y-aCb{{Juv)_1TrSOZXk1?B$6(jVxX6#p4dfHjV^I;F(@%#s#7sS!2ZsmEw^O^)NdtUCz;PPzZ*#x#NEc%+r0sC;zCTa`N_0$#){W|2a6^Pn`0Q_dK7q$1GCBG%;*0IBg8{rv| zxG`~~zs^9RNbBJ9IMdYmdvs+u@+VpdY#mt`S%|IR%Rdw_*jNVpM*%m=(4cmxKWgYV zg(ofjOI_h#ACP8HS0~h!TQOtl5EXUx$Hwc(_qXAn8 z_+H?*fcKn-0$XU8YL`YHjXWNC2!1zYApTMOBi1uqyKm)yGw*A-h4Jn?O2dD#2<-s> z#n8i|4s#LDD9T)Cw`=1{+s)ayR4%S;yHK|XUZbexe7W|}UI8w^5lHNi*dbi6?d?Ac zp3F6~h|7RK*M>9w5?~IOOB+`PduukZNVfJLTPf0$Fd}_82g-qLAm0n50&$Fn_*yCU zh9`~f%`C~M%wTb}4&LpA+$^%dbG?*MAZIyG!3^MYUwMS!i-J%z_*n2UYL(OA1LYA) zgc3Z$%mp8Ig3HA$a88mArOi=X^54%9xCi;bf*w2=co6tUg5JP<|KotW{Ezx?^<+7?MMNVNra#4^l7djxPz zH3$z34}_=GTpc#fU$rZ=E8usf6(Tk=z6~xS;Ng;2#x@51#<(^kHj=}z>=-uz`w-tg zwxJ!YcWiC9#*EK}{{)>p;9afxx( zYT{ooM7}6udBlY1b!MP7dQ0?1mM8Kx;3EUeA~qC#jLgxL1n^J~LZ-Z`y4&5&EYK>}!m57-=)I8jUcgtN*#4 zz7*rZ+hk`sg#7O^drutpoV8E+?}vK^T*0k^+akihyMoC*7+m!NHVoc|IlLoX8}?u) z*Lc@Xu6jyc;vPhV@{KYk$`m*Iuq&;rM$FA^y4 zfae*st+mm{GFsb& zBl;mc=Kz-Y`tc3o8ze4G7*`irPHhKmJHTe(y#=ff;(&ANE2v%K;g65YA~8QPpT{Am zJsV$c)|jQlZe`B8EobHCXq3$H)ywb<-l_p-+Y~dkha-)gPPJFgG=Zs=!zqWT51a!y zAAnRq{$Rj?`2%65uBQyLErXPGKwHT&2>i_POha0d)(I{R8lM!@=hf$xlL5n(5kcWv zwi4+bqFZMZS(sJIs^tM*vjLYnBf0}a{^z`L9xsk1yz?F${G>FLe!WHLt`TRop?6fd8cN_xqCg&yY zN!$~Eg^sb2L$wDD%-80JhlGb{^R;^za_uZr>^%b?z!O?Nh%E(t5;J}N4L@8V9tb}W zek}Z$wZ7is{$VdCd;o+0L1Irb^44#g| z(H>hHP?TZzeHL5A4ypqEQm^;m=j3Clx2#A$U?*+A*d?*cll3wro=vWuTs!eB;5EPt ziI)M-Ci!)1p_zC$2dcRg@0Q6zsKp!o#b}h1x0ObEDg$`k0DJLpcnbAv@QmG_vmJhw zOU}i1GO%rI+xRZAo^@>3*qHe4SZQo4JH#IN2QY{_k&pgV2`D1^eQ!Y-;NEcoq_tA_ zmIQ9`U#n(34W3pT@}PA9T;K1pAkSoR&Fu!)&%Ks=fU&IZ9xS7wLTPB90)EoI(Z14t z1$?i4tbNY=0Du1dknerpySM~b1XqM!40Zb+E#e0s;v?TOAH2KxzVI#g{Rw#8*X_H% zXL;QBxbJJgx5oNw@cY{Litk0=OM7OL)m zIyN63f^5tNW=_~bSe&xKeXR=mE zSI$m;c8Zbg6}lJjP>*z1s7e&G+#j+)nCDQ`mUjHSB{vgYkoJ%$Jj`s2- z8&1um#I(kR`p^bV zMhb^i4;~L!J3p=t6q~*}eKkfREd}&1Wrr^h)g*)z$;sCK)6JGDDC<33pKll+AMU$Vpf=j@eS(Ssk8Kc=2Z!W!@$ z**T3(s2nURwtsBAfElrhf$_1^EjTq+0fc%_ikXn|ln0MSF8LROS<9v5?;^Nf&c%jU zA@_6Are{|Ir)ZedU6sHbpFV>l+YNXw`@t(0m^vTcRVERI&5N97|^XI}d z{VyvIF(2~z3-ZPtMpnHjN8juV;62J~2WW=?jAqa(wkPBB$07<_07hHwg-r@0%&>Y? zE*H)(oKx`S|AolDWx~7GEZJAeLvN5F`(hEVXN$1rmdUvrD!_^y!_7GZvOG>Y{qwT; zR2Sfs$Et^ab*lo_Xn4OmK)^u>f5IRCnLyt0AIjjjxDC0_;9uV^fq!}XBzFnr;WX`z z9Tz(;_a)bGE_*KW$+F-P$R+-;L-a^CnJ^?-MB+cdtqWlCPD!}iBk_&|ZzeuOWPiw> zl|3uhb3YEXkvg{$q(`J?rp`(kJ)}sY$wifc{LM6VM0vnnERJ6Jy8MpjjMYi$7ev7E z`~_SXI7b>Yb+DU0f5KC8OJma%0RAC{qcz>1_aU{B9oz~P{F>f5rKjMEb*j+}^g<0$ zvz1HMNQx3Y^nU5xQg!KUO2>u4$P2td3#mf77P++W4LV3!)Uj8pE!7ObDvMF+ya?vS zH56y2`9Bc_H(Qtl?^wqDZUr}TVEe5)K-98Azx+q}j|%-b;NGTw*mePagHUJ%fCqS@ z*J^+@^UDjX=D)~)orkNn`STCL^e%wd3sGxq@w%RG>oSj178qcY<& z`@=uGPOwA&Ff$&mMC_~k^>O;g`p4zkC}mHtlR7>d5Q>?zJW9<-q;0f3z zklrG7N9vCB77Q4Na_X+sU8n)e*6Goy>r?jwZUWqD;QsWRCGc9>Kqi<8V!reysX2fP zQdgp6f2A(AL-c}wyLEu*<;*98QjCNj<>P{Dj?U!f`qwK~a^{>>LMNnFZ(}D?k_5 zBiYNSh18Z3j7nKR-d390K35|G?K${v1=nUUja_f{0%@TBJpq1Q^S%C)k!@gZU~cW) zhWf9dfhD(w{)hgR{s-F3FZfSf8$=KNL^IH4)|8c|>WJGJjCkakcB+0S#7}``s@Q7f zJp3QpFIgq4WPZu~p7}9rd#}$nW;f!`k6_Hr{yBXw=HFt?Rg z>@a(jmI@caKeAn90IBiaMCk~#HUE14MYF~%6BpOObM{a%ubnIGkcW>dS~u-23Ys7Seb0BsY*`a-~0qX zG)QbM>Pzw2p&Tr4b|_pA04-Ysw>~TzWH-osfI9Ol^R^wLNA@NT$OjTzb8Q9O_1Ple zm;onskO5>p1xpt5i7)scdmBW1;pnO{1tTa&VUitWtAu9nssMNB@HgMtTLJfG?=@%5 z?*iz^x8*l=0-Pl}+9Z}X$b~r#u(m*hy4~~p;D0mc8>MK@+fWd`hcj2rCH)=F%Q$@t zeUoyK#W9_}S@O$jkv>Pv7k(b$lmo8oE@4PI9P88PFo-#;;d67#;@`Ov_e15kfvLl> zeZB-_b>+|M4S1&X?IO6oQVu@gWi3eMZ$*G5RmkUWvO~F-fDe){S#2mM->?I;FNpvdvs47$ zMz#JtQsnaY8+fRPKAJ~74!A475OA*@Vtn@gs_;;DA^P(Sg@dXE#ug5!6uc!fmsmj1 zcjF)*{YJ1pQVROcW>6%f$$<=sC!LTvJ6oq;q+gmFW{xB&w{C9T!pVRW5v1ibm#G{) zs<&MJhg{N%!WosLe7m_N46x@GIZ+1fB1Gvt`3*|j(W+m8KrX)#uB|WqY6h#`da$ij zraZErou#5=h2xRrXK75weUOv+8G0=pPp49e)I$vBsLG}M5oK^>-iE>;1ZyLIXk`#9 zV)5nHVuYP97hPD-9wTaUuQ~($oUNio%*goM$kMP!RHg~WDu*niIT}480kf{Ols>=; z?5C_HxlTJ;J38Ei(0V>I$UWuS+%J_u9u1$2cBu?b@u6p`fjvw3rSizW-vg-DoFiCY z0yRMaaI38#_3#)k)0flLLq3SbYG}PHK7h{pUakNO7%~y-Kd3HWL1&G zDAp;$vBlCox3t4EVt%%5xDL6}?PysoE34L=Ln5&%Gva84$Aq^va^-?$kv_kOpA33Y z`qK18fb&@ooYv`|2yjN+!^=3Q45ZH;CBbBgPUd$MWkH(PI7EGEK6AMUxB_I7WfJBy z>zOQSd7F7pY2^NrfyMAF<+%K)avu65C$LEU78AJl(YkRw)(^LX+Jwx95bs2DcUJ(( z{a4m}`WX(mM`xI>+rV?^Cv#rT<<}~MJ@X8!=WUQ@G;8zEl}Dkr&;VZDc2KVTGlj$Z z0{Fzl;x9EQTl0*U7DSElOh0NFDaPc+i-7Z?qRcohlip0>ymbHpGp0JMNH>T;AM-0S ztcaU4h`E^?5c=Nam08!09@~)25aOL_ne%}?i*}-x_hX({Z|y5ES7J5;=SWi-WpTJ> z+2nb+mU%L7>I;{U;|o^6BAHgSi9AIxUmnGKAhS>g?nNB;CXJm^?c_@oJw@Ig$uvA( z&LyTNrpql%o7`UxijiBnmi&lv3+Kp&lxrC$+GKXCKg#H*Kd3(_`{YLX&_=wB7S^-E z?1lNugViDPlhr2b^aXa%XHs(|l}r9+e{;kmhs(<#x7xMjZ>8~TO98!0*^}c-+Y_y& z?eS}q<12@?c9b(GIRSn-Rve?g()dliKyzZ%#H!9MS919qGnkQ?K^g5o4j>DoiA@rl zm^EezPXBvgwp1o1`X}Iz-WkL#n0k>(>31@x0-ne`p`Vk3N7@#qvb_1hxrY&A4ChnN zrJiGcq%*RdYSj7>%T%;G9OvxEm+bBgx!=W}>F3BnKDmd9lhaDyW9hn(H49ZGqv%W3 z<<$IO1e0;(Sf-+k4+{nuKHt(97pCUFrFf*wN4kD22grZx@=`2vn9bsCY%|shwx9o& zZAHEYTTlC#1M_m^hvtV`N1%EckB7O93+uS}BhhV~^0FgdfM*lE;0gxA=HLDHPSX6P zQ8w#bR}udG{QKpPLAb(IpeLU47_{OH$rCCARwVU2J@t5UM&^V}CbNW)-7mXO_CQ?p z)$}J3xorWP=Q>TxR#o)Uu|L80WQfC09pVL7%oPiJs|UvQ6^ZqF4{SUAVfw?ubUDzT zO_xLI9_07yJuq`{-QJ^gW#z5L{*Molw1LOw*DnqE$d=af?5-I6XXgIi@$jFTH+YW& zoZ$KqkRb#P@lJHz?Y-PP*Yyc-j{)Yo@ZUA}cdc-J>7DF4$~6+Oc@H{^T+Xd2@3H?JdG~U)fLAB1Pb%8bqXn>`cU^dP99SXpKG#5?+j=H>pEtZtVDX-< zK|0+vgTcS2f5%XxmJRM3+&{R#YZVjPT^oCUbgiO(;@`%<3nI8va3?L!5M3O7Hu`M* z`sA+3!;%w|6VqcePkFbp!?i~6smc&q7~CtkM-lr&MGRwtduWkxCp>dbiN1iq|3Y=G z%~AY!)D2zhx{fQ3=-aM&t{Z}<`ws-b5gZYm5PSlb3Bk#s)kAG?d9XEjl-3G(4VHe| zn;`FM%paUXW#Ia^ZOgGEe(^bX^t?Op;+mI=61_Ko#XBBntI3P|GY+4gXFNX-=G*hQ zn1J~yALW@_%sH2O&S`M694_!R<$2HbstXq(H|!;X5JmeKMVcw*;koEoA5Ry(?sFN4-xa?LG&ci^ zBOja?Jj`kF4za`Bs$JqW*lk!L8&y~rk1&kmvT?-u%=LkIL! zwG=c<0md9AJ*u=gC|)L2EcQ=dV%1O z(!lDxYglT8HiI)lkLSkZ#`HWXi=LQOEft=q1+B1WjUg?UNAwYSiUVEz{?f>eDQs36 zMnyu#VujeAxS)sK%j(0bWb46IZmZQHzZvCcO}W`%&NmqPAOfoQ2EAe2#-#vN;kM#H z30p=Dr_77Vv?e?ErC9Wk@hl6=Tx=1yUr+1iD3+h)D!=CSnp;{CdW&cQQUbuWu0NTxnk3&K&m~0RB{N%new59uc6XuvCuqpE$aHmE8pdky|#CGw8 z%CGJ@?kf@49^m^Tg|->gH=Jnv=az+u;eUB=K1WrT*-wyr~{4sEmSM?675bMJA zqP}8VZ+S?r3z@DImAT%6p8`wb6K|goT;$VzQJ>~Z_|kA$BVa=Tw0(rSNL>WaJKa;j zF22gp49`lf^Zm;=3Z9s{yMP^im4U@?6kOsPpd6qaq#W+M3x4Q&1^iI=8s%!`M&&wX zoU*sEj8%4nZXT{66_=xxsmh7U6s5Iy2)!G6HyrfctGo&CF|b4@MGucoN_~-9&X@G} z>2G|`_}&8i40sNn(Q8|jbIQTDkR$XQgK`mp+j`Go&I*1WRQ?A|DGUXm-J)*Kik)5&w0JyDWv0kaox!LFCLV%3`C%#v6=S%``@mXj<* ztu3MKWaD~SpPl2(ZD1CjiQ8m4=4rK9WuAj^^6Jp$9EldP61C)lkda2Q_jn>g^U5>7?g*t(?3a@?!Za&1(1KmD+9C zhFNQtSPe2Aug-3@Ysgzpd4+PjTw84`<}57Q+H$oyOZi)Ko7*=_@;q{_xW&vKqQrU5 zQJbi-YsqS+RzIuEyqUI?jc1j6ak~>&K2GLg*V#4Z7SYapWLwZFTGWd?<)(}zS4zcP zoadZ1535;jmusyVP_}!D8j-h0%d9e;J(6oge~~8-Q779<tvd$W-rznejrIoT6T$`hLf9!g@hIoH@!YiE*WPiXvKRJ=~vj@^+ZLoR=6&zC=Y^b$n$RL@$vS+p_Advglii zw{A7jw|q{shglLcS@&F-r%cCkI9sR9&#=B zklF29%l-k0d{$%&RhAim_5zH0oq}c`SJnB`+p( zE$7I?PR%}C;+#Fkt|{eR%4W}G)nGI&R+~&N)tcRA*T{0(bz(*49A?QL$ux2=xmKRT z?#ncGy*$2@)_H&7QRIWv-lko{BRMDcl52Ssa+Ug2>SxX=iy|FSb3Jy+*$*vbCCWAS zOy=6;cC%L8BD@9BhqOo;FKT3-q7HfTC%rj~%!51`wZ=PJOM9B*Na8V^BbyVg#IyX! zI%T)(SuUQ@seMr|^7f4Otz!1Uwn}A?*4%I0>RC3BpZLF9Qn)5>jDFRG}V zH^teUW0#)k${<5FlWX$g=`L6j3r@c!)E$0)v7g zh;Ts!4j~xufgp(B2n2a4>NO~W3TV)XNtB18<6LyE?cj_v&Miwb$NfpZz+gPP$`uDY&g?cQ&_;{cBS=FSm`o>)FEb+7gFjB&Kd2q1NA7 zT)O%(!cj*qoy%AE>m6-$q%x<*Dd&1ltw(U@R2`{$-}yD$hBSekKP|OZca+=aN7b{^ zr*bcNsJ-6NIKRNNdp3&L$R1*o>ctPX#x30WxNT)r#?>S7&sv{GC77*T)sd?GnQP;c z)uyhs)Jt8@R+}=;d9EqqD4J&Wl=VF-t=y}$#5wU;3~+cu9>9e1w#A-blh-lw%$ zDMLOkrQ3!hfq1O)c;ncOkA!$JRmOEk>Ra7Wx2^Wl{a_8GDXNdVIF)Bduj-V+vmQ0_ zu8tJ@1rAg%wzJL6%X!cB>IZ)%=E5r&a&zRi)hU|qH%e7$uiSGHX{~?6dDPp^o?$l#+G9)1>RWx@l^!8>r9JEgs?3}A_*jS(+ntBo#$L)SUpN{l z_H@?jZR``X^NVe+HY#m#S*wreeq6dn9f+AT+xfX| z{j=fG+46M}YPa>yvDPcLXkLwTYV zjra73J6^r5ZV5c@eB#p8v&F~jr)p#kDMQ>q;b<*2_iBDLjut#g&8~3pjMF=Ae$;uz z+c>Yerw*xV)6`nx{A$_xyRGwZc~slo@!IX&J>#@YKIyIl|GewE=8p^?CF_=edoU|+s3JRr14SBLyynqRau_K z-r>qL_6f0?`BWaO+zSz6s`iQZV~*2QawxsC$LV6$-zwmbyT1c(=?%KIzyR^OOPObjU#B);*J1bR@ zy62P{|BB&qopcjkilOSYcKJ&`}j>Z<%zxzFZ4n?H#^q0c++yDQh3d(x^q_hcTP@O?7( z_qoolj{J3fclX^bwv%%6Th!`YBOQ}Fq54?=K#lsVeYSJ|Z*nL7S#(}*n^fG78tZk+ zUC^T5pKDEPa(@n8zRgj#JwnUF_NA(f6UT_T(rYezaq5`olD67Mdqbl>QSHrrm!sR(^QpDXHHYR=-BGu#&8NPF zxd+wvnX5I#AqPxs)N3MjRsB;x{#4)AezFW9=MZ~ae2?JIq2V>Pl=rfZ(Z4zvGd`dK_0m7DAaY$G5?vdrS0Ob z3nenQvwbR4&UMd4ovVMfAJbRc;{CNvkH((LYrCjg`&=!T+O&25-fsMx;F~jA-j8b9 z%uAK+sP|2M#ab0cp&ZgTR2;YWO|kMv<&V;c-S3TbT({H8?o{+XyVbq$GuYqK{g&>F ztlr%1^xmE;8t88-?G55L&s~o)=eKm9-J;_2-yXiXyEV;Ism|ARv-}0` zsuVx(snlRk6ZIai>dQOzEN&wHjo4TVdVbcoW8YBUu6@OxoqKj>3+_3(=ctxGr}wm` z3wq|Nb9(COyq?xH*W4TVUDwl!)>QW9Iq!u$KiktpW7qcF*^0)lZWet`#~$c;p!bzM z_=)YSbQ>&gzsYt}PixxR)0&>_X-V7u*W#;E^fv#OU)@~22SUTpFJj?u*x;|&cShgYjX!p!=4a3R-|OF$E$&(P0#)3*u%WP_xc9)t zvG50CR9oDvTRVSS%aXpP>OH^rTzsf=g)Z#9xK}52QGy!?whC5)odXLxj(;_ zLm%TlEjb)lyYo=LT;W@VZ}uJ6x2o>v*!-sa`>DRy5A0T{{F?llz9aiitedV;ygK%Q zTLwNQpHB~bdgwm~c5614%<0&b=+Bi3R~RI3Y+TiMm%ew=x54TQeP765lRp7_x7Mn^ z9WsR@`fl#K8E*c=x&S&yT-ycQweYU}x?`%;c`EAp9o|-Q|ch2ve-xJ!q1u=a+ zy*0mo{;O8|TYg~RuLhP5EX}_a+OJl{tphty#jVAyl5&6Xr1!SVzOP)^u~GwLL;qB* zk)c}UFKJEv&(+oM`l0-jBPSN;7ut&R>xuS4TVds_x*`9Y{%7*PvHJHs^_!vdhAtR6 zbL8d1lIH5~)#LirTmB`gu%y3Z@Q(uz4L&vaRL%QVwQpcGx9rg0nkM?GokT9uPBtd( z{5bozZ8d7sG~@F|kEzh=(WA$Uq9Jr2d#d>7Mc&e!dZe-|AqY*(hh6A7>#X=?sB-c zf6gkL0i9Vmv;SHl?w!$pOKU2e-G58rY^wh={hzVhTgYO6Z~r|l>g)a6ulxHS>i_n3 zYhyp8ouTrldo#Ycb8)c$Mf|MM@TE?5N5|&|P98XU^Z}~=@nGf1=|)hu?SIj!I8s%z zKQ-UCN6#sqGrCywuk`0$KVtO%i9fl;3N?rO!K~gD3T6G8)va9RiLF@a-D8jADK1ns zFKzq&7`|1Z#t)cxc~D`W7IkZ(C5^pE&0D6@J^ti=GJ{&q{ZG( zk2qgPd4$HFv!`$7 z!Sdt7^7EYKJZgT{sg0_b_0v^bHBP8yJ??U?%!OXTv+`KeJ-G_vq4i%pOU1V76|T!E zRcBHYyU}%XSD43#*ERIAS2;Q!N`dwhavvB7T%Vi=xABMG?wF<=z&{s5JuCO!vC8e> zV}p-Lk8sz8b>Xj}ue-SX5!+a^U$Z!RT-*B>TT{7s(8%catN5XTYl|PM(iKH$;A*dH zhu$^xuCe2XcktS27#iI=y0!Q~i~2^f1&y9ie7H3Y99sNs@%PYgi+_OrwfOHtFAW=N z@YKZRpOS&ko%-^!cIB4;(UZNbxsF-I{)b7{6hxO2Ot|i``Coe;IO%A+-VgZUOUG}xM!47R3y z2Devx4N?bhlMWcH))9j(XlTLUsup$JU~4*iu)fY5gobvfEG-+_b7=3u_d;jL&$kbM zdVI&h%bIIwzrl+JFB;k}h+c4kp~EVrHrQW1OX}va zdGz)cb>Co1sy8OmHgvkG(Q&np?ml!@i~7(|Yr1NvHC;2*nywpaO}`)77P-8drExxP z`}?{jTtC#BzBtsnzB2Ts*`f?@tgo@I(QBaV!kxaQpAWAK8p(&_!)qI9q$kqTBNZC@ z8TTbG=4Un5G4{~tbre!I{-O|ZtG^T5=KW?@dJe}b&UxMN+UQK>tcTaJ{GrB#)(-cp zW%Qbg^!;HEaipGtr5=s+3?Bj=*n*h86}@M8u6p+{b$^xSn!1tSM}}L}$bUJF-WD|P zib~Bbm$89H;=MHVU8}8Qe?2xm_S0&OJU@J6eT|e_Qi9H|=q(@2}=7pvQ~-}k+F^qR2_*SE*I*n`=xoyGg> zSm0CnVIzZNBi<^L+v-^+h}Rw8BBP3Gxo^HycT8Kfol)jyj|$7 zX~lMKt%F`Cwy0Z2TGKa2TGO{iTGRJNTGKy{Y_Fagp`@RncXhbAl3VVpCF)QAGV(9j z!%uiz=e?&!Thn2q&2`o2Rq)`aTCW|Y&S_CsjJ78EmR7$j-Tt5BXU-IY>BnO&X}oPVjs2;a#t&#w zr;fL#Ik)sCJ>u-~T77i9HQg{?tDDDFcVT<$EZQ)>-I_NzZ*cr;E$S!Zt?8-pYW-u4 zrP?Ff4{u&sMX-aN|AHtE z+1fKEQ_9%7${5&f8HA;r9jP&e(tPm&7dDp);seHS!Bu{gU2;1@qU#evj-VpIj;OvTs zZJVKg_UvG1z3^OUN4?28lDDk#+}4SG>(jA$qh4$HP1jG7+cL0cN>XOw>^|2SXV30)LtwoY zZLIs3ti98qjV;XN=#O?~a@rn*ZJQA;MRxe}9K{aKMBEv%j|rbHXMZt@1LieC9N#|M zI-f|)M)hge^OCk|xxjN#-bK#0on^LtMEFH*^U712Hj1B#>Ppy-X_<~RF)v;}RYrWr zE67ou2-|uPw&&7GpH%Zz)_il!%Z`mXiK9M_dG5;AUwo#dPQ+e{{K4M86#0X_-JZLo z1qXZN|DyVLNA=JhoqKn5zQVQ*2;1}RjeL|fzOePlV`OyWp<}igSyX}L@QC_wWiruy!B4*QCi1LyO3#} zkL3jR$tYj2Z&$*$T}?*$3fp{zZOlc{xsVH)vb3v3QQKP-wY^1*Y1eg`>3;=$!uD|`-=if=GK$SxG%ui z;u-e~o^fB`8S8e>X#dK(2D3cv;$WN~$k;gGVaM(o(I4}X&zH=x-P&c%Suki1CB(s4 zN5*;>qa~SjuIpjTCDya$nZVg{#H%8n4y@}*O}qJ7EkENwU*;Gt_O>Xmw#jc!{^a5V#b@B%|Mne@WLrmP0v9uC7ovyY4NRC1_WhpZz@R&8;z* zmt)3MmT}B8?K0=;T&%2Ppk?)!aW5z1c|lmmaAh5Pm2r(&2Jr#2Z72_OKJ^@YWKOqs znbVbR86+*(r`2&7K2KmRs%bF}=s(^wbfmrJ`2w%k&}e8^L!+|Jlr}Sjm?(*GN!q{O zFDRo8bZ3n)=VG$V^_!p0C}XbQq&&hgYV*^*vbS6Zoih4n zW{bq1x}AN0^V*a?53+4*oTXAW#3^Qt<(a^U>Fwxufu}w5e%#qRHvh`obDKjP@Dk^P zvJH9OvP&UiZ!d+k=%+qT#@jjOY@V4fX~E^q|8#?}oMZa=!1tzPZ5#iT3H;BVkv3wn zmvyWtv2T`ft&Mq6?$d=g>U~pL$Al95pk*18mGxfIvWx?kr-OfXL*Nb>!^NlL`JZ?@ z`f}OD&%JQ=fsPA0r-*;yg-*6Z;Zi1)r=)YA+O_SGIUc(Bn7u5sU+dgdmOlQ1`Mktj ziTlj#?I)039|W}Lld}d5vWcU7P zpWX)??5iUCwC6UoPv=i|{_IDdqd07<&c6_)Euh_c`<7k258J1+FLM5DKWM9RUX~>< z+g8P2_})~QbAeu$RocdEy-i|D-FPX-ZP>vNuMp01d?-K7d1_U$b7Q+HJS}xDy!mPI z5q{CzvCfgT2mCj+o07AGt9fk(XN7Z_V7JejR^JW(LS~($y+``X+LUp=#&Tw6#Qrsj8MrO7w`EJ-$Hk|= zi!a=(`99c*-m|6Di~3xRvAW9?uI0H{mUAcf+4Ef;tq+y;+IdDDdPW_3#=N=u8*DRr zjaGkC%CBV*wsrE|$S$n*G{ZXAb+59n!&7-yfb}`*T-o|W&k)oOfBHoC&S`XSlDhRI zeWLq|RMz~u{#TZK6O$NIGDCbO25w7BrGn>4Q^Ai|pDT=c%_wSGL6}CQL?^B?UwS!~(u95u|$AfL@lqG-Xv2e6MbM*o*SAm;_1bUZIA&(2XyVH*?d%ejy~w*G}}od{chu+Mig ziYaV;9J8E@=wr(Uo-`9$PT^ob)%xUarq7tvL+)Elt1-z_jvu9uUhCY?0{7k!@uxzZ z0@|2sSC*w+DeE~ZZ@zvKWmeXF$97~|%Tp2W5%FFT?;Y_gBVIw)yvB}*>_~)!XupPW* z(d6mbWYqwD^qQXdAX&?K;u9flnhEVHMIUr+EJgqFjP~pq?bb8en=&53%X)_ED6p<4 zalZiGCBtJF+dY>f#@f%@F|K;X+Rrh^Qpfy`!}0SWP9YQg3z?1T&$R^CqV$*NEw7#` zD`#-+lsP{0IRouvK8JM7G1)Q4Pq5tMBv(s(aKZ3j&J_H;PtL9ZUV1L`u;Z88Gr!0s z2IGv@zp{=$;9c0)*!~^!`YLN*bIkVUnAcHx$}IM5uLWaXJz+LpKl!#UluzpaEMqWB z`fG&sl>E!!J592?Mvi$MmAM|y2(y0h>@G{Y+`FMp$TIhp)PD&oWy@sR274Q0KCgEF z{Rr)h!}GJwmoZ?=UrsB zjhwYhd@WB;RJI=Y6*7}4)5^U$@4FrIz8mbG{W4$N=W085Z0k_$y=5r_*_A;!t7RB< zZIx-+R$uzSRGG~3bX}%t$rF}wtdLnLWs|;U%ciV#B5cbkJQmq)eTtpW4G>Q`_R;zj zJMaCS55Efl@4ybR>0xOWvRpYOl#|EM=2-98Q6p`$Jk;yS-bKC;iwFy zh$o_aCz$Wj>m|N0pTQ!I;}vA4<=Enw>oLbmBes13Ey|$%OL@oz$Ba`}{{={2SUD;6 zupp)X#O2elP0`23@#8J*%4LZuvqH{&^`|o?wei7;%S+e%nr|BQJ7w*6%6uN)YruH+ z^|11M=Cy0Y)<^8%q&G3hN!Q+B^qi9E#kdU~b?Y?cNz?TKXP-31h{q%5`c3?=D4Fjl z@4z_gHRcZO8z)R^c>q5A9vuGj>9ec)3)?tgUAw|(f<9aiJLY_(tn&+a0e#A92Tz(+ zo~N80Yc^ds!e8dNlKKeSnBYm9m##TA-?0AdwQEAoeV!j7r>M76WbFz@`IY-{{bgLj zye4el+aa@Uusu88Tr!De`M!^g_F3Ap&kb2Jt|`Ck3_K;=#cQ3Shr&l0V}X?P z|2?T^JW~bl#4@ZcNf~yE{QX*@FSK)%Ej)j4%=^$jv?NYmyryK_$H`h$S=XiDgmue$ z%fp>@@*TX(WVy6yLYd{slu%%9@ySkmmiLIPy%hOKUD1y6OZ|98{g5Z|;j?tlrHC&L z%ypgf;dd66eO`!D$^?IT|HS$5+C?mBmtf4{+5`4Bo(p2;$gF3M!;ZPe1It>1^QLE9 zN5`BWJ*&N!aX5yH@{TX42ul~ADP=7z4!e1C|sUPgeYVdAM%lQtxd&Em3-Xm~! ze;HR-rtr^RF7+&RtLwTUsq>|B9XI27b>1lBc^z1vb7rJIm9_p?ssD^=&-1yNKQE5A zi786i{Md^+*Jo20hYODRoC++@n!53P$uXYE)5nj=c{~eZnTKZRzxqMcVcQEO`mg>W zSjJ?s&o?m%iX+b}@?h7_EtjNiCuTQ}MI7s32cP12_s9{*&CinQf=SB5uU6o9mvaXHEoYzWt22Mu#rC*SFzU(`e+ai{B4!g8fYZvCTYuZ^4 zoYToE;_$sDaE5laXRx$kuJOQrT=I-|2zJk!$l5;DUs-*GZ5&~K7q9;E-FiOaesDjp z7sm>6R387F3U+zNgYQ3p{W}j}zfJ~Azp(jgf6=mm@!e3w=sPTf*x8T33z#q0MPU5& z^(<`5wikWazu>c!J}XO7=B2z|E3oFIool(WmUC$ob1D1U%6n7#>>b6~hiSQ9g?*pM z{)&i~kyl_n0H1x4udJ8I!KY(_6jEQ)4xO3Y}T$U_VE33!Q|zg1kPmzh-sCz zFUO2=0N=v-KHle;&+y5UNULoxJWGe&J<~=T@Z4Uo&lpQmpW%69A$m5btoOOfdY=oH zXLr(%aGTA3$Mpd0c%J6{F{e)&u5(~tN*}Hz^<%iP~M<+X>aI9^btmXC%bvXv&MJ#*Yr_V ze_{4J*!}%AIV!WTEt|}hIn7s?_dD-k)O;)3Q!BuT2BrtIYCnjG~Y5jIg^OOknrl(}CUC)n2sU<7C8Nzfa-X zuV~U&`EyTnUBPFhMP*r6fZN;{1@3WU6gbayi1n9edVKx{yL^uj^HR0%=hhhTc~vD( z%31~X{oPone2R>9#UzL4x-Kt{dnH-V7iHW7b9_t2PNkh=X)#F=U)MQ9tTTQ}m>Yz3 ztwP4R%UI_b>vm<#xu_qqt8>dTj)DF7;2G`QF~8#mYoAs2^FHj-?>HuS*0f^hT<`3K z$j)`Rv*R5?&o(bf%Xd-W!)Fm@Wm(QwdB$_W+1Ymv{oxJb@A?|kT3$hB+c@;g8q;z@ zJKN{Vl_}F6FL{+DKDlr>)@-!9zDZ_U_Fu=Z@r<_xJa-vqm-rZm;r}Ma=QA`iWjdfBqbJs;r!i ze6oCIMSoduaJ&V}`rnpqQm%#CbsbWYe3i=)Yunf*Y6FMz99PbW&!KLtLwvB_VmC-02x?~3w$SClVU$7jR=_qlxE73F(o z6!T1;FV}?dKQoH|?kGOk*U7u1yxtwfJUj9SJOAwUlPrIk#$~ zm-JqbYo1}ZUMdOuXY8>1_0n*(MjAG$Jg?@nZpVC%4UXDmdbTfrqH8DK(+ykZeVJpf zk(4K7y`-#jhi9x|99w_C?tf%UU;M*PV#9>V_(?8{tdDr;XRGbY!TWY_n>Cq#Mqbsg-dGydU-MRwzZGRmpv z3p-ir_H_B4OUr*c;~%~@W!iJ1v|wKza0cy7(w@UI*g82UD%*Qm29BSILzZb3V(!F%xmlk9{mBw6hFrS4#QMPmM8`KU!e`&<_JTo(CU&X~6RmxuCa%Vh0S*+V%;Xur$8nb%kQUG}Y!&nXd~7O~8O zHqH%`$ct;*5tIE1^Uaime--iX0(T^29dkO+f4j`I^pX5fh_BBo`Q6ZneQwGBLlH5? zBK6TR%5$IE6O$Qc-=Q7vq6}xmKFPA#n8Gs}pUieY;+|2=Ft1feYxwRi(%R< z1NLPD>s$t(i#bnOAF%fadw;y==F1@OyxH=s3FYZ3lew-KNxD`wf-Zz6t2;5^n6F5eV&sP~~GNs78UMd}A%CrkTbU+_E=m~*AGcbv6vN|rWh{e`VR znfH&@r(|Qcv0V+Re;eBg@9E&<@7+9Oo(0R?%J)ymk-vX73%kD;^Q>vb&igNC=RK5T z-Zz2m{ZV!r?Efz$co&v4vr)>w3-jf;0sDcpuPlrG5aE)tn|I)I2>p5A0bW6$wKL*F zE;wf2U4uE4K5o7sGwmA8p`O33?B)yD-^e&CHQzVVpW`j;e(W8y;|#|Q#~5?^ly#hO z%yDMSjvMN)V~Aca%Tj)2mfQJroFPvd9X~x=f7-3TU!TLrug}3cem*TRN2H$de*0K> zN4SH#u7W8JTf^A_UmeJH~S#p!2tVbn_zi2gZ&PCU|TvZYfHHKE7tZQ2I1zVEN91eZ;YiFLw43 z*ya0Bwg>PS^PRX%So#;ASHP~U{=zmbSiggSkAH3tK8|rxEkEXQwBhTg%CNtg_Oi9R zdsW!a=DD!VlOrF$-hr&Z2Q)5rB?@C{Mg8zTQ3BRkl~yfOG>rLNd7#!dEo+Lc3} z9gpUEVf$X$xC!^U;Hcix^B70!U;32ZhmH$R$-E5q_p0DG^F1i`9k9Q@1N(Uve4guH z5x-Sg=U?zAT>nzmITw6mWC!a!t9Iwp^*%SRjVCF};9{nJeuo`pQ2RVwL)R~M0K508 zb}+qPbUfesa6I%}ig+e)HnMYl==|C5JYybo%~`^^yNK zL{9Nxe+SE$z&7BReHq-&^R@9auARW{=UmU|zslNY!SdauO{>pB(cfv8IO;zxtafG1 zSK0gcn7Gf?XN-sg*85yC*VTL<80_C41D6<|V+D93I+uwk&QkiY-NNT}^kF}>Eca_< z*Qdx)Ot8L-OS|iv;A1176C*wq@m{mth zk4E;7M)s>C`_+;Cn&=vVaosVNq72tWJ|BzHek}6;nD}TrRMz&OtnKjQ^uHBjHDZ38 z{_EA}<59i&aT`85R!^q%zbVR>?5@{MQ8{ml%6U^1^X4e+%~6@b{u~|iy4)Paxg|<_ zOJrXk*_CztSs(e_7Wv#39{$TI3KC<5y`GdXB zosk{v&5B^Bx@7K4mD{J}v*hqU6=TlN1Ex+<*5>uGZli`0W<6oXi z`EPY&5$){Dm*RPE;Hfg1F)zh>fE?xZ>8PBaj`F=d=E&cVKZtW@u!nJ@6pb5Te}7uC z|2J8$uZ|VM(R#RK*Taj^u9Wqif6wU4;Imxc1pEF7#<~}|Xj-tgXV`CLTSc8PE%+42 zzWtXHdp~~lo&zc*NU+DcI%k;l3>3k1pBogc$js|a|cUX z<#*X&|GhIg^4TGSy!eg>?01n5FDvu9^PY=#`m?`y)|j86KifI%%9@reZJ2h~ev}hg zCy*I4)pCBuU2E7sn_)~X4_W3f+TEN*j?&&u|CL$>Wi10)o^`|j?&w^=IG5|E%CyUQ zX<j8CI@Lc`l!e&R1E^H}gpxGo0`6bX3M7-gn@CohZ))W`54jXD{GS@>y>BwAjI2 z=2GUHglDZ_%uOY^cT=8lyxfhYu;Y8W3Av90`>`E-S7Zn4vk|o~asHo);(&D=hRQ0?YAC;Ow-r)Rpyd^#=b;ls3b9=9~_@zmEVfaMuO=Mc01F zw6o0w&Q60jFy`8_W7rXB}7DsKvG0z8_W!l#~&2tIk7}!5&17GjjjIy?w#U{1oXWBHBAqj11v3<|E zgz-vwQreR8u8#fp)Ubn3k@*+=Mb{pbb*xj?HUr)e`G8Rp&FdaFPQnK~5!qeZE_vS0 zwb|k%MOtT1;jg^Jgf`}RM&3)3w#PL9?b5DF5tls|*iYfp&U)r^GqB9*Y;RzFZl+vt zZ4&lD`tx@!g7Ga6%AtAbSgLXSdH_Dk7$3m?T^;bNTw4MkPd0{F|Wk0`^A@r@k$+#996SL}yLT4CNl!vEf0j<+jQ z@>imK$JcmnAP@_EY#wijOj1eK2b; zy`luWGXC$jnsD;39x_NwNG*s zAFR)!VSj-BT!)Zd-yugaAEXcaHS7;YF&_*uGd~Uf;s4GojoPharUw@pGh0%Y=WKG0 zIxlqN|G2?tgfaPl+`zs)FU5aLVEf^{Dj3gub)5kIx|_$r54d)vtnCf_p!3-~e5cd5 z3}s@?YpZ{UrH z)3y$IkVE`17A>>amwABwSOoTC5!hcl@Hb_yUr7Hj7J;=4c-FTp3C}vfJGZ4vr0;_# zcuhHyS2YVlXzZQKanXfJ9 zVlwAG*monR%FBf{jXnW@T zrL6CqIJS1&6X_fW`ws5^)`2nqdi!o}-h_RHYs1Rgw!wJTG$Cys{4J(UHQ!Bvb18Wh z><6O0S+^Y1JkJQ{?DwR3*d6zSyTu=U0qlS0HdxvZ+ZEW)eV#G*Ip*9)cGto&pUHsz z^J?%Ta^1B&kGOG+b~lcZ8HZyR*^OOb`8~sI1LTlbxV{HQ*B9)sFIcWG=OD13gAT;V zAbFv!9w=)ky)Iz?{UaE2Q%U9~@DXIzzp`Enu)m)LKjN;JvR+5~@l!I!G1f>~GV73Y8F>;P>m%b2`!ejf)`|3e&*&FmxxO3|JgeP* zcLuxf*I<8r!CO!snMW*_*vH8osrFN{TqBLItmRSGyue*Dwy1xf%`{!J^Hh4&@TCEUY4_^V9!n{XI)i&nBR|Lem{!&Xyo%~ zZ&3`U#O_6*)q$y3ti)s8z3w14nc*Ulq;tFpFv@b{xQV4wE! zD6M0r1^Y69V}BPjguiTcY=Cb&Ro%1;S{oL)C{m3!r zWw6Z4oa@0lFQYv>W}oux?YcgMeUjJOruAz=*q@L#plOvg?GIdAg3k{le$ur)*q@B; z(q^P=p`EPcID_YS{N1g}J^0-emhC$mu={o0$|O9K@r>upV9aZ}W>cP$_5=27Ht-MK z^);RNZ>fFw6%|FiLh=esqbHiTn|GRX%{p_1S)YCx16$5?^2GE}?7O|snMc#dWPdz85XToF^>XoG3g$-Jf#w$TIrw$LG6Ku;^a>l{ z!1P2|2gPd{{7y9M;MZ=`M)q_aeejcckEfr*c065%&!eU%9T(eUlG}+=9_E64#b48n zrwb$kOHb}VS?IIK+>$<;vYfZDgrL0nn8~4BIdeU>%d!0=w&N&OFZSBadeeq7B-96XFt z%riTih3F_(nopRU&E4q(4UXQN-W?rbstHNAKT=*}ZbaJk=8NV_=F8@R^!s%^mzp}h zW-GkA&G~2%UozdvFjDTHo@X-24yKUaZVCyW2&6Ze{$$cDOs_Nxlf@|Y1L@;tI9Y

    Fgv7gO1q^L!$`&Roiz3gFwAtDPuR1W#I`&A1T4c^ z6F8bd%5L%QcJ)CIo5H13{|TwNo25kE$cb^TOdn6#wzUPIB&hehP{QTrD06~&dwK@0 z_)+G#^zHB-N19Gt>l4y*;c=AhA?K$T<2oIMRK@g@CW{_4fd67Tf-gTk;|weI%&y4C z9!*D5{HJr53cqb`mnz>~;C&f-ToFD)cp#|d!!>1nyW`D{ z@|aJ@>nW|QDh2Gg9*l@z>(ret#C8DfaDIBU*)M$q>gsrW(7xMtG<%u7(_`i5!)C8^ zZ`eD{1!&b5wC!%LL`yJle9t>xVan}?_RecNtMj)QR}7~-gHaY!T$*fbGi~Lzv~6R% zX)i}QrG4!jwWGUuTIt{8^W_wEedvT7}$>;X=d$)VLsr^Uozij_4 z>Y)dJmt=R;#_KXCWzIm2d?@pGna|C-O*2<#)@L?kHfEm4%w(QLofWdB>{8U*s_b<3 ztT}Jb%BI9`%-|^QS@9May<0Bn6b$p@Y z{*Lc<{G{Vo9l!7F>>TZ!>fE>U@Xpnpr*@v(d3oo@JMZYcw{uhHkF0*#`CpyCt24tiVeJ)OVJU7Y)1?vuH@XVKSl)K=)JI8GzZ z{W|xDuI{d4?eVUOTANzB_J;QDI=pLj*Qs6Sc3s}}ajQGJ?&!MPrRw^6*VeA5x_;65 z+pgbqC*8&FiSE6-SMX@}k=<|VJ{{Zlb$_t?lihcVzTUmH`>F0)E!hww>Az zZaWEMXKT8;D*XnG!m*yfh#bjaXs-~4@(N+-t`HtiHxU0&n^T8z>NK%(SZP!WuemnW zTMflEj#H0`)r*x#GxcF5t4cAerC80VK8AHH^?9pD%p=KYvM*(R7^xqtf>``TT(P(+ zi8X-LPpm<#-(wA7Z4_%5Yp_@&xCe+eYAQ8`{}K`l_aPOUhm~Qh9k8NmrukTxR%=HS zw3BJBow2sw7Ja~c04t5439QWjoLYdpkA;!mX5MB_hE9P_g(j;sg?p7+eZe@r+c=$t zbTeZ;6P zGpa?nM{A~COmi*9D-J=s8frJu?zlU_{fXBeMzyDjwHH>r&9oG&%4XWzsP-}Y;J&+7 z%Wzj6YhSakd1a*iOmi*AolHIbt)WgcubM?~$6ZdWcbH0@Zkp>H(_B~JKEUZZbDddR zrFDk7+Us%?bTjUGuBy`hxNnVhfN3l~W>b*vF1im}?OX@Y|FRRxY)dt-4ZNL+$xoJJ_5}ocbK_4_9G#_uGj~ePr zc!s39)dYPRPh~zWy3J7cLSKpWpXNW!?UC-|)1yc~#!73m=vPMdC*$;0qdL<3W%V`V z^g8qZS4ZKwO{~|OzpUP1{<1pSw5C;fVxsyh)0*Ci=TXO)W1%-ft7~*To~bt0ad=i; zsq;*w{+Cf*Vw~@&&|*AG>|$G`ijK>MTwvZu zCH+q3pG5!Hh>}CR{wfife{HVhRVrW13-RQb`a1Mm=)XjAIB84T@Ko>A=TA@j#bRi|g3uhM@QucR;OGe2`mUQd~qys9H5 z?kaP`EV>cTvLhu`|3b|Al)N&Q`Brl!lcLuqO=Vu5MO8&fUZ<)uhuy8Fxt=jla$u18 zN6~YpS|KJVIZ9Pwqw!YnE#&9xLy+MDSWNu^FqDpY+QmHWwZ z^S$Ou21UCjs$o2}Y9jNCI{JOc6VJPvYjvV3I8;hX z=KYaAiElNl|NXUTs-&a2LbzoAS%gQ_&|yI#Eh*W>snYKDl7mD`5|OjkZj&RZTD!9( zZ=l>>^)@*{Rc(*=ll_wY%)d7goRiL=IIZJkCns-NoLD(a)%Ho#Tz4Dla~1l&3Hr8a zu1)xMx|tp|&GndZ`fo!$UZp4Sty`?8O{M-|nkvc8qVXiwZb`NFNt(+1%`D>O_|7c) zfvKy?htYNR29oA_P100VEk$?VVw$UNA~hy^&Z0w;=6Yk&R5e*o6-l;^$pf1y9>B+gNP7qYmmDi{;MET`8|` zsfi_0H>GY*O-((VdL=b4wIH=9^?mAhAudRQDYOVn3cZAth1G@igpGxv!ZyM#!rsEc z!coFu!g0c>!r8(&;bP$`;U?i0;V$6;;c?*^;RT^vctvDsx%pTED)ZlesB#J90BqGY@BG zWV$o206j0WAhRg*yQoP?NszLc?}0L;o>EC#Lh1u_KWQy#kTeviU8KFGgQabx6H_y! zGo(?{dD2nRHPWRZxf$lE(iG`oX@>NS^n&z;Fj#tD`cV2z`da!B*44qVJ{?FulT=R2 zx?Gfd$jiyS<<+FW#a+0;QmIDq5LdnsUqeUdpP{AZ1NueK#LGl*>~# zksnaDRCb_wnu68t%6`g0%HhP)+5QGA$CKJArwN0V(W0iDr(C04s$41@tV~qyP^P73 zD37APGLtV)UZ~7g-cnu?w*-s%xF(f_%D2joxTcifpk)WCaaB@HwFs1{_TX(Ptu9B_ zwz{%^t=Gm;``4z&wA8g2{T~(H0=SOULF%Szm#C@RsynNDsRyZtp}q=xvXhQbPjlO= zXM^-S^

    Iol~zOdG#hYzgkY6SXE<6HI2sP>QrK8p+V;4RfR z`P|~6)pw))sPm%bwMx~GYt=+Ob&(23_xhp!TE9(W7TH*1cA}OQ8ns-!c3GMDQ#V?i ze$`KF^_De)HM6B`kL=Rr*xnD;`S-ZlzKf^q>{`;pUT@j;vKyzq&+eFob1ZBt zdt?vD!nrzoa`vq3*zASb!?RapZvg(S*$1z z)B$DtaGiocFNw+KFfZ@{v*G9{=od?{Pg@U zg+7Jhg{uqi6x)k^ihC9(6yGeBN;{R#Eln@|(y~g+W-aH+BjqQQn6kbyQn_7O6rZPR z^7;sMBlYa~Jjg$t6$vN1GMp_gkk`*n%l@eK*N)YuX+P@y^<(uZsHeYZq~i08K1d^s zohen$9}2vQ#>cSyhH%Vf<@L?|kv1p6Je!pA-}1i}_A4ZX6yUX65xtd(}(NF!184MEPaeV9;gYa z8SwmRqJBNlH|taMNA&xEnhA5aK2LuI(gnmXzf`V~4E+=RC;cn*ZHW3`hGdvhPoo8L zOB%h5Rgo`h#sW1MlZag}is(F4xy7>6-c7;M1x zw=u(b+vql4rja|PdT(8`!kZzD$Gq-7OuiVzTon3kdksP$-;M|e9({rP9Cm<(xdG4y* z1-YrYn{x2%H1}i<>Lv-f*)H3ca-ZhD$i0=DpZg>C14=B#(ygSmoK>`XFilEXy{)ya z^{v$$%}uPGtgZMpQxA>jV#mvY))CeSYfs+R8cEChr&FT?EDE1ej>!z%g)(FyOZZIy7{{? zZ*7ThyQkgDc8=;^c|Cdh#(|{vn(jQGQ)Z%H;b41Hd#kec2Jq&n@@57pd7(7O-Vx^A z?fvcHkpBU>_Aw+P`(*oMCx4cGE{)N?+P=x2Y~N|$Z$D-~ZC`IcZ@+H8XD_g)Ig;x@ zvdF&1{t6^N*?;B5eAfOoAJ0p91MUb)_9gD}{W;;_{PHlboF9;1f|l7#njezioVU*p z%x^D!nBOnI8{vvt{@DDXyau6-j{GSg8|9ZU4(Oc6a&Ttu$Y09m{dE2sww;0ZiFhQ) zPs`uo*No=mAI(3PpM^U4ck{3Dy7@2jKj!}|2!(7R57Li$8Pd|ALI<|GGSl9^u#CL0 zup(Ut@}ks?LjS_R!Uly+3tLk=DkW)QXLmk2R~S^-t88}=rt)WnBe3+s2$)9}Mirp- zf;=cQ3zrtI0iBx*Q>eUfA5!+3G_FR|!Xvl_3gs)kLO0NGh4#-ueZVQdd?)TMx3Hi9 zPdXPT7S~ho!gq@^s&HS0#r&qD*zo&|@ED^c^bR6QXK~9Uo%g82w*dbpikaq0FNVB7 zaeLKo4&VQ&X3?gUJB_jp_gcHeU0RK~aW(4Iux_KSLu;=^u_rvO15gY6iiDxqhw}ed z>A(9ty4WvuBO<0Z2{l%9&{9tASRSYn%$CF1G~fyP(?!#)~vgsd8K6 zIfYIbd%SX-!w-&eCe~z|2wxISx4gbJNw+{!BHGfpYfWi+sE>~!7~-+85bi9svn6M*N#wL3vB!6Dg4!>*%sg< z45xA1`e)h^O9_rc2;RWU-0*g6CT@QuxEAsL1d(#e7M;5ms!7(dSYFqJi)#FO^V9XU zuD2}A*I!FMwy^TcjSERN|E(?!+%oy!plHPI)CgJAZmr;)KOWlh!TMe<+9MCwMbNf` zYu8@};}CzDNFAmfOo_3Kv*t38BIO3*A|EO5UJ>LG?QsgK59-6+_3GXIyVvXP*S$`+ z)CJpd3vlrah3l7;<6qP05v^$&R5FpfC1^z^%EugTsqXqB7dbRV4evyx$fYIm8bS5N zwjkx=Iu4pp1O3wIh-acnXkVWtcTu5bv}MOuh9bb)sd_)LT=x7L^ih-UdteuV1VeEgRP}si%J(^RW}JXoXYI z`h{~NdGWY3}Nhj>N}8ZDI&Dg;(5Fcqp3AZi4EDlX=@{o?B6sETHkDcrasVX)cU4+ zBrOVE@HcC>sE{SJ1Suca0*jAO{`z}0+$(km$#F~&iO-X$nWoJQZMa!V; z8uQWhMGb#DQNL)75s<53;+>Y^PI(zm({|x`bZej<`0bmoFVb|qd+SSNG)__qSw_^C zY~QXu8n1pC&=x&24y9;4XiE9@r0Cs$ULrMS+p)myam0IOt};jI?wYL(=$hTtAD>9` zQ^0=gh|zEGISIvdPpTz;Qd%vVs1wve$ivC)!(l-rbRsQ*t%N+B35PA+_CYzaXA&QD zN&Vw>2!sDhF63U&-2H4Hv?VXc{eazyvv z6Yw0o;~;wImz@LJg3gWbMx<-QuFbl(>l)UzZ`bgyW4ij_nStzS!dygybp&vnyGCpdn$ zc5n_Y$=1VfYYv@M@I7j>JEf$$QsYwHf|Bl*yVP#I%j~wh3f(PTZFsFY4qd<$?4NPz zUc;xvk}Uc4UyVi!etW>YRW$+@OE87U&R_}8Rcp}U^$CqfQ8M{u$Z=X+8Y|6_WuO1D zVWhp=D_NsK*Mx4pupBu8U`+lg%!cQqeb~h7xoNr$ku9=*d$%`I2htLB$&P7+V#hnG z!BVn6(02-W-9hRUcCA*9m)>uPUC{SUcUpnoZ0H_BG~7{Qj$R>z$=wztF0v$FoFGl& z#^+>J;wx`m(a)^hC3MJPWa5vxjncspgmLNmBI|?SW(iIA zE0V(~$@&gnra>B3lAlku3%4iqPq!DhBxoCQYmn#y+LttLd09K%v^-2nS!_w$fTk!xnoP9RTCPy|QUtiZW4WtV=3_ywxmZbe@cI;7t6oq`!xtB%roKB>w`F48L*jbC2g?v(CvkN`I>O9DR8?& z8CjROWcvzyGhNnc>pyM)JfrS6ZjEvMp&Q46$_7gRt|49hy9ag;>E66s80Y9=xdeow zrP4CcGl>p8(})~hVXQDml*OUqSaD8TP7h6wP0xXTJBONcI)_e>M+~hdA2D>o&=F%X zc5#k5XTsPKa|H6Xi?$l8AB_=XAoXLMaEjS`DYPMeyxzR|Fz+-D)DN@qmRfJ#TQ+@$ z>*a^#X^c#c9$2nkDy*Kh`Ki73wfXaF@g?}B^>OGnSW6o0xfV;J6JVd{&M29#%(zUq zq)1)TIH_A!la6c6?NpgZ5BjV_lj1$R*;u)9`2Rl z(qKuD1)e*}8Izn%F-K?0M-Jg(N`O~AZaH5j^+B#MFvq6ywN<|^=PHZ(!xXPj*+gi1 zHx`yMeWdU?Pt@eTr{mwD5dZvhG~`svhebvC{#dZ~c!kNXG6}`<^x6|s^28OFRE{a# zhH1(y+|RivAFq!YT?BiY^QX8L372}ODf>E|U(>bFLEBxJoxjE0oG`lz-+x8tdfsDk z*Xv-Do+A%c=Kv4iiJ%X9N6>~mVW_XIu{BVSwhB!xhm!|C^#JRJ`v~5N;T~0wEw793 zHHkfIEnZ$b?cL&oEuA$=zHt)WUw>8B1GJHUa(_D$~pyN_F2_mo`r8))l==AE$R4iqQrAZgrcA z*=3G1yV)8SM=`oyN|;rz1+QOU$?2fA=Hm%YZK&=D$ca83J_aw{T#e9M+KIsHm*&0x zu?@r_RO(Mpiu6YHPZ*V+Md?P;5xxlOYc(vhwzb*mE6E$Lw}rfNf4+W;m~g@vgY=@dOsY@zIGTfV*CTFq z%1MusLt94}CCtKYy4G!l9zp7>fm<8bU>I;qs1?n|BGge9#*`jaYh=NC)%S7UZe*2q zIAIpA9Yxo&wR-LG3{flCw!n zt~%ZR{a13;{L?#;YII<-hZQ`v)w8 zzsvI0g1jZGbxE%B`K8xiP@d>xP09ON_N-oT?mVK}0PoAA z0n)PoN=g42NJW=No;VPHJni@Im7)z!Bqh%ph#zW8S`N2Y`jmjCj8koErgHi=JyW?q zD8E_v{7Ha+y1DO@sE2AP-KI#?Y)Rkn<96qr&?abq%@TD~{Mc&dT-OX`Pr z<;Z1gg5=zuP?xt2r`>imhp`4vMUc>z_NVx~DXKl|i3?-k<3SpEtS)Q|>yEvkv)2hp&6EEs2BHh~)T4v1Vl3 z@z*PwgTBZaEVyK{u5kU3b&N+?9b>QtrY9i#&1E6%rpwW z+|42Jk*$|4alaw05w?9$|FnKI?@g(^-jw=r+XZvoI__L9)*Q@Ob8tsf&Uv2-usfWn zD`MA{Cy29ZzXx_(k{l~DNb2ygwFBdKaZ9e8Tc5W_n&kPVTfchFz4g-V!J9HICh?wy z!po|DHOIULQ{ldAk>Foc)%393C>?iP?rRjcjO4<6;q?uIKkEe1^n5{5e5&q#lM`ga z^YC4X1z#Z*d{5@-6MFPa{E{+ImwvUx&R66)K5v7)En4L_b*M-BsG)7Z>K)ylOfQT= zkECx3UIV$c>DSe-YmITxhbCXTix#^2QEAj0Mkm&O zbNa!zUg#G}8ae@}G?Y%h0mJfGZp7FLkb`@9#2f-~Y{f%wI@~B6e zr)d$l27FuGIh3ln(rU*z>Zg$u3#xV;eZxf`+ z%Y*2!dIa(5*$+#{Yk2Qjj&Qu(AWv={?6>5ohHve$9~$^O*sW152S;UgTtqKUbd^O# z27gzQGAg1lqFRYW&bJ77B1c!OLp|{e<(RO)nQ}PnI96L6G#Xl)V)zxoi0+ZyW4gz8 zPw2kBdvf>P-P5}VboYldaKCXIL0{KfbmO^tNL}$ebuE{?JNUqSrNt|Mv%0^?K=sZp z{))nVMMJ-Tq+0}dbx-8_g_NEh&~r{L!-cr&hIi;B*B1 zilnji?sy?v5x3o8WaWw5F8LO7-pp$?g%UFFv1^om}W3P`26MN2HPUndmazYyX@(;qSG@Nji2s6 zE=|bPb6rDhsV5Qc%KdMhg5vX*iC^7Tn}Xt^b-}wwZwq?z^f(mVn}zo{2KPL0JY8cP z1V_u_Q*zWG-(e!ZnTfU{`XFUA=8cc9E}HHK4|K3&!+FyC!ZXc}nEJn%;J|@u?B7ahQ zOQ-vgyA(c0yz`t!X!0$AzjW;PjQXRI()y5hF+uG^-mVgTY8{=r*D5Umx5mnum;dtC z|11dliVU8@%H>NWTNGJiw3e9CeI?j)ZQQW(d4Bb^j2cXfQM?yM2g}`8S4=yY;@gL< z4_4=(_0FUu0`!3svqy?dj8|rO5|k5rZ+xhTJeqptH1BN%YDMMf0ZSo!9x07R%%`Ld z?tO#soDiH?s4v24WXYmp6h@2_E*ZBvh1ysH_b~|fWt?zHq?95@eexa(w|jD}jJj;9 zUMkh_mrU0sS?3rP)n$8I(Dp=QVfxh0pZZJ1a%eqC1Y~WKqZ6wcp@Pcib^Imr9Cztx z=P#MnwHAtc$Cz=V`LNU-0cmMcf3kMz*JAGN*m3PFs?|R{X67S`s^&jP&hbL(PwIws zr!~fuY+MRc)P{?dmW5Yt|Pe(rwssIo7`;}OnuEA%M@UaRt2?&wiMkBwdl=4;(wNbSkqiay!agU1|v`oSOR zQKQz$9aV6wULAMdy=Jgn5*^{Yb;EM*y~2NILicKVR%W$hy0lc{iGV-2I*tolCT zx95*^*ve!Zr0wmM5)ChxUTSV7|T<;-ew1Y5QQr zZvP}l_FP<&I+5cJJwCg_t9m?iYx{G2`J`U;)sEO>9Wh?qy4(|qI(WZAZczL4YY9D* zvjQI}PuCYOM#h}sQz-$yln|Zrk%KiI;?%c4d$%80yBe7M78OkUw{6~Sunps~9;1RO_mO~Yp+OX1 zE~SI?aJ!~kd(eJHZ82q$hEu4=%JJ^$D6Q^?*(1faO1j_BcfI&Ge9#nF4`-BwaVIDz zj5|>|G3@DeaJ^U=!E(aIs7S1Vjr?a?Y=`9}6JZJC&1TDcLTX zTT+iCrE8V0z2Fr8SK^QDybp{fY6`AbI5)~yby>BeJ^NyTQKZf!Pxpi9lB$=&`w7;j zE#NJQ@}pB`6|Tp0kP6a6KKa5HpRN-iuaSu!2g1^7`yaJyq-?aITXIlZP0nomrPH{g zQ?!fL@Jrb-p2+z9nyw9+vIq%<7H*#3e>jI)hHJPbv`l~NROQrqTnu&Oj-*=7=cbl> zET)5RpxrJKn5JJobqgU&UuY$^W(r#o;MA;ek44i$<`E7?if)c z+&<{(p11UHds=Q({qXOm;PTnXY|-}N<)dGWA$SA;S`Y8DTjq~YEFT;vFUQ7o#)@=} z-9p=eq_kX?(yiXDM@D=`t*H9rUXI68>Z6gAMG`{!>xA#u==-M;-6wRN-sRVcwUM3? zVl5~eF>XY6uzNZVl=sWITK;LmFPJ0$d1}Kisrj{$?j8?+Esccl%KLToFMqk>kDaey zwfKT-#c`{Bn@G1SeoLafT2f+%OPp{SWW~5t$5AgY*qt!3r#k3~SP~B9dsAvfbeR>4 zBuGNNFfB?kZOZYdEPf*6ZFt&mN%e^bdN%c@)W4VS%~!j>tRhbY_N5GWXI;MYy|-kN zr*UE$tWgV>_u{5&u@2IqUi>mDAt}nECf+_uFHFYU*G{uPzQb)uiH*yHENLx*WQ4~w zC>5M~J$SXea?JCSSI*0j_R53uQF_#$w_M7vH4Q44@}kP8wWYdz%F5=sQ8crNE)#u( zYw%wDrSa0*+8@jE`ym{mx!&&@=@X8gnVPY!wLeT+R&eU>`w?}j_5TU~!W7{?;Sph`Fjsh0ct`k1_(J$W_(M#Is%VStVo$M;*iT$r91K_8+lsr2`-q2# zM~frGk>Y6a0`YS3I`J0qF7W~Jaq(&KdGU4eJ@FIqEAc1sue6xf(uH(q+Y;$z(<`M1 zq}OR5*12x`_4#e{JLmVxccnLkv0k=G?~>j-ePH^C^oaDx^x5h2)0d^MP2ZfJlD;qf zNP1>^Zu*tbRU)rte8wOgN%){{1twv=|1hP50Z?JFHpdPO?A^%vz|kGo)vv7o;~z^GXkwx=S-!2Dhx;azV@d z{G>82bJEGu`}q^31=1o|elPtlC#`F(%dFR7+z-ooEcb`pX5DGsZ#|#CR$kir#QM_u z(LUKe%Rbk>#J<|T$-dp5YW-;ocE*+qzm&Eq?bLFeow4#(NB)WYto*b2=dGvnujgO4 zbJlxy!S1w|wO6tS*z4LG*;|zMEZyF!v<>qKNsyjV zI;V7@lfTqiUIojkEw{A%ZY^W=ZhyV~y^gb6c5LZt*|O!X77HQwZfTQOYdKtAOCBT- zm4~)o-=ef8t<|ixQd|D!E%QR(~A_k|7q z!+$OCUkm)#0{^vurv)}?ozQt)@y6oh#R2)o;+67kZR@n|=fR%xy{&DnR%=IFzqWqz zLoLac%UhRe?bY&q+gb8c@|>1Eg`>!Sb zU)c(jp~}|EcEZ5sPyIVQ8U@4a{R+nv?k;>&T%~wK@$TX` zrBzBtlhqYea`c7M^ZKt*gZEv=h+INEcttXV2vc57>xm{Ug z_f<7{eT2G^dbZWq>Z?AT6$vN1a`y1-1@ijYY1tpO{@StHH2qlZ$J|l1!4Puy8Uu|} za^D$atfLUdW5&1Us@A>Mz2=eDcjhI=xAnukm&DfSQt#iWC9(U@{{Qdor%%&oHQok# z|6Jc@V1Cz(b*``ZU2|u@=B?qkp;wf7!obo3gt9Q?G@>Mk9cx_8I!>OtzE>ajh>sHe35 z;?ZP?KX zyuY$aTPAB|+p6qnB6tIH;o+A?7^LX zW{=9Am_51k@$6YJ^4&$*E3?;TCuZ+xyQJ+hIHEoR$7CZrGdrjKB(^t}0)7M0azJWeO-(25b-&Nm- zX;Ml*L_b;|p^tPlM|ZxVU(oVuLwHNSocJWx`gI+X81!3cdH-~(-`-=H9?R%=wLV(| z=77%2F1^>d+&lUMJ@#w{`r{qXMc`#3)1L`%qaH`-FN8}MzohNjYS7=%-`79wv7)|6 z|6c#4{j2s*;B`{QFv4T$F~TVE($2d&Z!wl2oc3edk1>{SA8xFId1H;vzW*kS_3JEs zKo7X641=+Wv4yb%U-N&SHg;*>uJddcMjLw@2N;KSUJwKW-VZHabchJ!MB_ALbhDm& zY|mjQ;{qA15Be(SiuA|NPo-?&Yjp4e&A#M=jq(g zmW8=(S{Ah|%I%QbJ-2`Eu(s`5C$|l2+qZ4A+;LE|({tzK&dXhzyN1@XZC2ZJFu&3^ zuWeG>g0@9Kf8X|dd!k)#yR&UpyWVzx+oSEp_Kx<)+n;Wq+}^XjPkX=ib=vO+*@n4^ zxjWiUM&zcoKLBn1Xl|C3Dh)+g0zv6_9#)p}|E;vN-am*z))sBY{L`>@Xy1&%+P&ku ze-qaJou@Vf>#!c1R|h=l*y7(sY4vDWC;m_4JEKRoepq9B3|U;T&i|j*ulm|OqwwLs zinssO(xLAE;BhVR82V2i=e*_pn@2e;yUzQH#eWyn)N!_%V>Gd?|M;_*Ef(9ctifYq zQ_m5ac?Lmjg?6DmU=+abV}Hkf1^f{p^NkspS~s;usv{+&%#;SpC7@sMBBU#)mQJ;% z)=t@}L;#irX)gzBfbLKZ8>VW50_7D`{gZ#E`ldGKutBOeD5*ZFOsW^u@ZX*)EgDbM z(%wFuuA~kvZzl85B0^G3{1%&-+&?xq)hqFOQcC`j%q7=Ld=T$}^z|ryY1}RiwORDMz1h(3{sDZpO+yB6XKwfV77rZ%-JBmNHgNteWTzGg~^X zjx`CHm!fUzq!jP~a#tBhx#mP6xhJff|$vFlJykT^f_TXHlUJAR8D1@rLq8_YY^i47M&1-vHC6vTvNd1I`B2T2hxu6+HNQ(E5|uFL0(thP+nK~Q1~#j zskB=d)UoPloD&x&?!^>OGx2McD-p3R%xmarCq5x|c>Mm@i1_^o&~avZ^M3KY<6$$}-xj|) zb42FQ%!lxe{&?vMT*_1B`w`+#$?f8Dx6peaz|Pnb(??Zb9Hj$emyx{_XO;p2EU zATNHC+#-H_{6v@$GzCih#>pKSKO|o3d`$eXK-~j{x8R?VIl`Rm8|lB&e`Vguyd{1w zeu*=*#qs~+c_h>WO2JaSsd5Rl{fW+uag;SHyofyfyYh`o4|L^A<%^J~Tn=B_l;_Or z@Q+BuO!d^0)Dcm0_OI;k`V;!2=7r|@<~VbVGXo8K(B}2S**mw2uj^n#SZYnmpiOZ!C(y`L9@)8X2w!MtR zCFW+u&E#VeSL*l0`^NiM&=+zwK;54raZQlE@S+=;!3c)ILIdp5hWpPVt8;g}bhqu8ln4^T_;ycH8hV#uHZlD!%Xew+J4CtK0 zCBZx?k@7JooFJV}N)nDEaQr$YLpj6Z!^AH6cw)zv!f})q1`C71%V3NM?UA2&(hn>% zz5`&}3V_ajXFq8_mj4v31sT zD37GVHo<_XT@sS#xmc1g3cNp#!14%yOUnknxHalwsZmq>DQTkIOYVg_iD&#Uu9a?d z+goqsOZiTi+YnIZU5V`z+k^G4Za5x+M^j7ab{Al#eSRk#bw!liIm z_72DI_KBUGbf?5l;*LnMshCAQniqBwKvQsH%-&J9-X`J4mU4&w9s{-KQ(;r1IpKOpVF~n2 zG1ml~%2NOo6t|E6LcFRlAt&*oAF-HnjdD%#E0hOK`6Y9oCN@fJRG5|6%nk78aA*qs z8U!G*5%g>T$ld^Y;3om+BI^3V&@LW=m!tVB{6CZjjTlXFTfVG)Uq;VOG6*`c{C84_z{`*cMUNMf=H_ zf#PI{k603)gm$D3-$i8xA}achF61W?K5TbW0e3TG)@O7Yj;!ju^xg9F^7HC@>U?!x z_FV#fzCNGU4chkt2Dsub?;$8Bbo`=dq@kZB2Kg%Jbty*k=!McG#XRHr<<;!n-dL`h zT`lD@Z+whAm(kVALwPO07BIFlwo7=bFD_#1WZ4!vN z*BXWSjQI@gpZyWi%LI(3B_N;4o!lBSpdz13DCteW3jz34stWYLw5VryD*x_>)?0%s zBQkN)zhngJlcmp++F&aG;)hngx{`kcyipI7y_CJ=SLIigy&T^!$uDWU){LF>T{M_^ z#A5nB+V;TT9+z%tJ25ztJ#`l_0)01a8$VD&C%;tNhRQJ2wsc{4ZLczbkMi7`5qP;M z10D;6^A`kqh@O-ul_ynQ&8cn5OdiFP;VIcsrK6OG126}MADgC3hvgjo8wO*T{-v?A zv9taq0#nQZ_As2MXM803=LA`$jLJc)eE51K>jui7%EI#-oU$5#TtEt0AIdt!bKk5V z{j(L=h8Ly%OZyNHxXka?2$-euh#Sl^q4cr(Xnm~qJ}l1zjCbaXAvYc{8dL2$U z_L=sv_Kx7#D52*6Ts^Pz@;RHiX3G)U+RFwe@msTvOQ?K z&0D7Is4 zpV)5j<<>fhj|pPyRIpua|JZi1J!9}hMV>{Wzvc!;2KP;S3HzphPya5hBMkzqN1$w` zCJC~y61}h`d^rS2ud7_IUQD2?*0$ERR3E+&^C9*!-~|HtZ23%QK2tuCA^S`AN1XLR z@R#Zk<09h%>JMsku6z#Q6wn?C%WE0nn)p(tM`$Hp9mXAo^VE^qDk=R_mh)4_4ppw2 zJy+F!4{m#?mCK@fKK*X}fU6y9^+AQAoZ$2Wf8>Od{-Ab*`QZ8K?*KmnFy&C%8n8ua z3mER~rBt_X?oK@q+*;;t&~w`p4&~C^^HNW#Cq^a?5r+sZLQCq^)Em;0EY{R5jUjtv zUd+#fdOTGfuwO0%ST!8k;|WTimIAia($>;)(MUbmDDaX2wxln@Kw1j=8m$6pMWTTz zYh6ldFGeG42cR|EO6{wq+B4D6&(hD*p48@OGadNu{fc`PcP;cPEDJN@@xmiGmxcol z!m?2gOJ_zJXOxwOSBs3vi#s8pN6o3`O~!4;ZTidf7^_|^f9K$9`D^7+TZtTLW zY!Eigdfwbo57`Z%AA<%#eCRuBt8-XO+XztRz*Y61+CV>O>8NMkOOgBBoNi9@VT$QT zMGKy>eeXqfDueo^`X%OQN=s&``V9g!oRlrm2DQ}B{c!l7sy`vL>H6uK^Jal<9gJPD{~eGa!%U^w~jqYDZFD8}+?;^ZIC5 zhY^6*m9Q5tTX+z9JieviT<2j4>?u_+r5)+tK70b)5Wux?MS?WR^Ghaos4wDu z;>$X7ulUMAseX@sPjN7dDwuLvQjdKc?y}&2*?6zYvKP>pBWW-6N9l?VZ$oKfwXD1R z;^52)(U5kDhSCGDlCr$AjIt`Ax6-aG#g~!VA$4Nvc$}qGq*Y*Z$fR~I+aK$txNMsU>fZoZ9kb+b1CMdCB*j8q5IOi<6g?adGlu(2=|lXE=B?@m69?VvIB| z7+@G83F6nPP4ud1W6gp}p}cBxs^?G9l1$LM`uEbWmE1Q{ohW#MAysO82Vyp0Mu*dXabsNsXY?` zqYTslW@Wx#J958LFnj6C#sPc5=g_;Z1L~3HD`~ny!P{v6B+YLe=uLXh555%p;MzcK z>;oDDwa>vLUmlcCQ|Koo5>(Hf=1wi%6a=`Fa`8zXme&U%j8ApFa*`*Jl%6PKMunmr zSL%+N<;-{L0kfl2bJp;!iKl$(x7M_{Z}DJ&Ggb$*OD$t|cyxlSSn1Mi2INY!ajp-< zg7`#x%tPOKH3RPNj?<2IBezO3AhEDzZbdJcESRz$uLW>l0(#7Y^$3OXLml`^aiDFc zZAN@4-$Jjkb%Vu`?PI)KAE>Q$k#!MTVj2b1kCMxuK!3jgc(UOvsei&%_LV;&fG4XC zMN`VJmgYy~N1#vT07S+6!SV-gER+|@AGqbv6IB_xKXQLinfX}-UzuO! zo-knv@I8frE%}VvLA+}xYo~Fz9(tOr5ooVnT#JY%Q?*=FZBE&W+_GqqO;CZJ_$@Gx1k(H zQI0zek*N6NsDhzrac{SBYx=?ax4y83=JXsqiONBDqdZUx^EBmUJngll zR!j$uvYNGdE^lRWI?e=+U!a{dm&;iiQWp0HA95Mwktx@iYXe}6Wh+bJL9`f$+PTxx zYYyIoCC(1uO!OLRmEJ@O_%GB4u$tSFDa|g;ZpkpjVl7VeEecQy05m`owQNAK^m2;{ zqYJ)QdJniym!5XV2{u}b(;{AeZFzflz!ZBDV0Zggd#^I~g|Py6vbVRlv3K>O@RGd~ z{1LfL{`_%>lRF7;c+Pj84;p2H_n5mg3f9`W$+^ka+5~8a z`P}Wf+mQp34Xkx@*X8a3+z6QH;2x_x0?%3mzgxefKdXQ43cz`}%P{Aka^wBrwd|$B zz-xumCj2-afVGUjOqg-WVSRI8VdLT`-kb5eAH2MDcrN49 z2K-nC<_UhGF<5s5;5O?v>p=&%702+PR<)=3i$gFzkNC-l@%caUzvkh;5HjV41Yl6k z1FG#!b6tgnJWyU(NR~Vwd6*|lx5S+*wiFgnfcApIC+Mx%0_ag(3P$=`fOD}OM*DiV z@HNWZQ{e+Yc#Xn=ZlKJ~shZobfS5qw*dtB2`xUMN`@Lay{tDnru8p=ovlqb_KEJ{8 zEBmwjdR{C0{roBn_B-}FBxk>0#_RT5_FG;nmWPpfB9XhgUNGNC6&Ly8)`-rP9xXi( zrBQmw4_@D1-wQZH_;^)01MO>gfDb-D(7ql#3TD*&t75g?W@R_>SLgqL4s;{FJ}U4_;o4&QGiXHx`6~ zkiQNvIX~I$(dn}`S5ixQ7~m=_R2Cpn!B?esT2dBpkT<9_qy)dmm!K=M07%fozZcGz zIPQ48xk9&>m1xzNhg*NlL1$hZUG#8)IK>3 z`S}!ua~Zroitx*1t?=5`pD%q>gzk{tsLxAr8UgHKWs1)O_g+M_&yw%z#jjsD8-e`N z3VtcvbDTrqmF4xuK>g&PV^Ke-6pjZ0Qo>_8NCoLR{G^qzgq)PSkHBqJd#SU$^pqKT>>AG;F^Es;b;b0oz$d4!f2 zU2{)4%p>|I>xS`=iY`aqO5Q3cTDOmY9Eox#zO|Gwiu3F=vukx&XP2?iNzbv)wax)J zXC_jMpry5t0-keL=pZV{n&-hELVoNX!1HSX$K~nUVg)=unzqOBfF~NRd{7Skx%ni| zb6J_i&#K_#Vh|iF=A{}{TBVvsVOSW5W#Lj6nD!opJy4^tvmdlf@Vo=1!_wk4H6Y@- zB=uW3G6dHa2o5XwKy9HGhcG&rD#1Xvk=Ud}Q1VZ~9^Ks1eboU&5eT4lj~~=m>CWOd zJm7kyoF^2^Q@%Te>$jd1=rgyZ7naF`mdjGRcNtgOh)e9t5rv^t!(QHxifkEs8A^wz zRnEsgGD9WiVC3d(Z-h%xwF3}{~v@R@%6q_*uVoA8XjHjsu z(PdVw20;?)foV~SX;Y3rrLj|8nxZC8`z;BF*#}d`NMmHMgmBGmfQhjf#UmXS1j*^?(N6U?+qviV5=JvJ28$0IhBf|kI*;K!=0SqvD z8NI?w)_Yb5dT_%&fqK-J^nyxAYHVO^;O4k<`51#+Qd`bg)>xW?UxT^XBxgNu9}T$I zzPE5<;g`ZM)N84;I0?bqFgo{i?rEY)d5)K(omgMvJ?U4}1{iM{Kuae%Tm~J7f_eE( z2$&c22C)R9Qqm+!1*f&1ql2wuNg7p9iZ+$#ekpY#NFH0Bcr43d%^)0|`K+wc{E9u+ zvaID#i?BYTvk$e?_QMqUt@B&tx5K&6TDSvI90=%FT-`OVRY$9Y)?exjg1#amrpI(; zt@?p>%|c(LRurcYlzQ@I8f!ET^kcnhy~_LZ(s`*GycX_!sjG3Ir_9NX!yQTeIp=r- zgUI;7e3g1egJ}j+Ft($Zlwj<~O_FVZ&e%_Y%Bem5QZg62FS$-~NbFOfo&ZdY&5g}X zim`8EUnWbjV`D=An^e$Mrb=-|I5aNAh2#pcr(-jc%O!gzmrM?bJ(^r4xms*CcvvyH zBBrq=Ys5sO55je{!DG+>!1%{)3H)SSeCdF28VY%3# zh3qE?9J4dF8UyAX-kPzK$}KuCc28__<{5Dw0L}=A%oHh^ znF5$D9WL!JZ!7PSnI`Wc@1bO2IZQDif2Fhi;5JkRu^WABj+{Zt{kzeA-aw0SUM}b8 zxdtRKHYseaE-j`{sF!KKaj?C;fU^tepZcSICYF6pq%~)0W_mDUpzHGUUhK6P&YW-9 zOSoKYV(Qw|wG|FV<(A%@y1~t9JO}G;I;lh0dZE6<sWabRVOZ z?iBqnU-Ve7FIU!jAm%{7UB4YTw*$&8pSd@4SJ(_arug74)t_TA_+=O&X;oM{JySZZ z4;0Hg^yjhpPFmg?(FXGbgpR$8Hk1eHpx8&i`3T@xQ3>6mF<+)hpP1z(dMSIXZ2J|> z3Ws`uDAptvy#KQAmqE5BJ$Cw1>I;spjy{(%?>t4LpedDk^5keuYD3HLr5+ofocfMP zgUVsNH_?(E8~haX{nR)*pa&!h(*IN4%jjOAGACekiB~)^LYbL4(%;3w#le*xmT1pP z^`^9ECU08J59(7Lp5}PiCs!DtMx;M4YRlQVWK^%0!==lWoj$l~Z|z={zPl(@eXUpX zPCb!yW$5XO2h=VxC2<$7qbNXJl|IIt7+D`=v@bHE!T1;#iLqE^WF*eNi%=c6u5~d_Jf_t>vybw1j zV~{uqK3nTiMtf>!A(dW$klLg*r2*+*61OJauMzLSbBAW}L!vqYo-goE9Pn6%KyrTaBj~a5Cl22v zYXh;LC#lJ&Q-8tZuyktk)KsBg>TY2zVRd1BVPNX_)GyBbW9qxqwIIion_qIXP2I5=aDVE4@Iq!>`r(zMMMcxhn%SVI`8?0jCsqwFm4US*dkjJ9 z@#evL%uYR9y$|{-8ndd%!+NB~x^=@)0Jyc!=!4h(0P+M2%6uaAE~yqD1LcvZXl?<-_zCzT_*z&hwJ`OC zw1T%j_2%_8%Sh@j$V&7JpnA)3>&uGJyIDlX2jev!RK`9|daAP=As#E9RKcm@KH}jp zBW?!}iY!kT{ov)4#?c}7appV4qr~CPJX#zrj&Bl|iH!r^mp5Te%Dsa2fL>?XsGalX z!~4{@TAn>Gs&$mZFg^FEj&-F?~oG$D|pKOdpx)k?EnnK#;vT zdoxPa7s@)vQ(ApGeHdyY&;~WLH$!eI;4hQCO`WaIMoz_&l~FGhJT7MK+(*)3crg9A zR~9fv;!<9n2J=s)o5S1bx?uE26#k;9ah#-gL-jtWeVn8~%}>%z!~dN@F^*&JNGsAY zc*4i(3Dx)_Z2TV||7#zocH4?(X8Q_8WkzMjIUxV~jma=?e?M3gj<&-_$MO#DdqYnivK?BD~?;HRbG(oygoPymz*3H&yhDYD4` z9)`nr2mSm$)B)K2j=Wz5hsXy~uf{3r*2XFNo%)?zw4pVVa*|Hx?19<+)E(9BYO$+9 z=dsMg=s5#v)j2f2$a1LX#;m71QAOF#j4a)6IWvRs^K0ciNo@INV-3;6VH@&ncS=9^ zcBGuI4)8-?0nmoqT^M-3%K1F9hh+~V9;JIYTqaSRB|YlkfpSW6Y{~f7N!Ljam2tI1 zagY2+2rh8*L;OdVPNZ1!b^g)S>*VnDZ-lO6bRz#aK)|VT{-`Fer?-JX-74SPM}Lhk zZ{Y2)Ti!eh$~XdbH41s}2<(AhWpWs%Sn42kO?3-(fV{K3Gj73^@pI`oy7i{q__=H( z-8!f{Z3PT(V#B|8ciGcf5Y|1$6zHAbR6HXR5=>3sj{gm@_=`J z@U|~|0W3#m&jg$u2IUOp4E09!y3l#NI$pgjd@f(9RnDH-FUU8XMjw45!dO|KtWP#p zcK?@Sr^=X4&$|BecbT)b8VTiAWfIJYNe(qdxl3_oh#H_7BzEO`-J(MfZ#%z^L6Y8;LC zvrs*!pGMMKW8=%PejXX$y`AM=i@>}*PJ{CK;yd;k#>V=3&K6w$Z#jF2|6k;WINoh{ zHFXuhWObtcrv9>Zr2c|F*SN*F*%=LX5-dG4xAoyQCbun;Et*~@!d=XAv>Q==TUR#$ z3{lr{&0Q+NZt5=CJ+!Zk(e5yh8+VqF?2h?NL!vp7|vTtWEMX1Bg7B{k2Wv@~XQcn%E3-cGiH^-S{ocR*- z66*uACF)pGlk85|pWR?<7%6zh4$sG*Ec<~yUC7>F!Gl@E-s<13(X`!T zYm=OkZ4Sk3FgoC)iKVkkWMS-eK(J0d+^XYNRvFi_f3`lXkwvWSEZ5Dh>x^o?Rkm?# zUKttPO%2QLoPEBGoy+t~=2B)4vqyFqUUogGcot{>&^NW6cAng;%<=|<3> znb`@=K^Fr74|H@XyD&e*Xw~yZYY>nRojrC{#bsJrk7?;L;u<1t9cUer-zERK)?NiY zTyjf8v}-N7FU`AfZ1;dST+7tOTS;pU{WQd?4ydP9%RZ5hGv(Kp60eaqrC4EY7uGGT zQT*A#Z^a*qFzZ_*wDq;Y%|PD{JgiTAIo=!^dEQal>dx<~H3u@@d*jPG#_04$5h0GHs{RLBQzkdJU4dZ#7as?`jA1mGn7B<)s*!rTaLT654( z)u!pEBDBY}$I9t6q#ge0+A~e!MXhl>r@f-R)i7Sy07lUd`o)ydVVJrOOCMMtH-Sf2 z(cIG9lJr^>EN5Ii^3o%4CDrL<*zV}`Ty(xHJIAj!|o$K6*aE{`VXaK$v{ZK~sKqqhvdEmX z790)I{>C8$u0E{TdfCBK6QA#}^+wk)%I)JqwaROmlk@!$a@(&dSB<2sftvh`c5Z8Y zEls@?4=Zgj76F+jhSQD(PtV^wHt!vGGWs?nTvV7OLksa6N=5t2?|1p8*c=s(T^YWwedzpxx97O5WBBSMp zrOtT{YnPi%`#9w5Y9Gz#>Y%T8o>hj$!ig^XILpzNVw@lr#NE$?#_ty zlelGdyJ(Hj@%X~V=?mh!N66!Icu&itxKVM_;*(zFKeES!pghiWVXMMc_J;&)JZdim zqf@VzA7H;?e{6r8A8Wr3pyO0Gn*Lzdfc%>I_44Zw*x%YeSLm`| zFW6B4`wyo%IO_aToP@^%%JJBHjvf`QaDMRb+wbMe@cfYx=kiW}$U*PG>*6*1B!+*2 zQ@pFOQ4Tq)8pm@B9cAX?d~>)b-yH7CH-|^_&EbW7qnMjVI9sUGLq0z)0ORkiYK7@Z|( zToyNvACV=RF8tUe$eQJrbSwjBl!H$9SP5=fX+DgC!5@hlp=%yxHe@u~Rinq{tSYTO zUU#%cQ{zFySke*WmDVJ4Mx2Wrt1f7qx(YA4*Y+BuP4sK;^oU>Mc(0LrLC~194UL1& zjh%jK$&a&21m{6Ix(Y5UH4Yppos56o`0~+Gb9kcU#S9Q30NM03o>m*Y(I#_1j-s8Tf(71f98c_D)g);M>2YTnO};RVKMuVF zqXihj6MZ6I*ptyJr}L~4OHv)6#R|PdEWyz{U*q^XrDyM1(T_j7;ZE@~A8G0*{_M<{ zQsx`8(>B#v;{Pk|T)^zAsyx5Xt#j($dL=hd6%!KVRB|f`AxQC(fB|oXil`t|0}mhH*Mau@uQga3SNtmTzSY8(!Fy1jxeRY&J+Mi(P11D~5UMha7 zqe%|Vc)h~&jlORJdz;MF=V{xjqu}|5&nS6!)OSUj2I>egAne;CyjI&Lyv6W%$=QHm zA2YPiE-dxz7hVhX=r_4W@{Xa%m9!acTE|B9Uc;tl{MU@nJluEKkfoo6%QolfuIMw* zTz66yt=>SJ2p3i zKAU;h=Ey(v_GbJRD4!j%X&5$1<(k}W+t?siQs~oMu1h#O;(8bK*%3FEtoNeAzJK29 zs}3r!7eIo30SK3-6ZuiE%CVU1SAk)8ZTa{d>e`WPwqfejf zYSLF~6PVYi?Cr&-Pp)0`zRiK7{cd9qroP>@zumOC-L$h){A(X7s~=&1Ez^*pLS72Y zd#XGnV`N$tJ?#5-nr}1vTzHmVqcCjPw!q?t_G8*+(fex*9PRyeQO`UU%H}9bo^Gyj zI(_=~*Nwg|b!jXKdz<~JZ=avB&B{k+x$s!xwkz*%`cwGXTw+XF`;Z)R4!NG{m?AR| zFt6^ZD}S+7^v-(O{;&yAnl_~|!#e;=i+kAcAK6JZ|@ zf!S}$#;1=B=p_z0w;o{n1RTd~-~rn3dVL=rWbW@lrYty?4a~9!nX>bZO<>y0w>F;H z{)X`nj{Y0Qet~HZx$ft(1*Yr*bFUY$tiPw=cz+jE$D&UfioI@zAB1^EHir7@ZQT=avY|3k^yVdb@CpA#OZ&+OR- z+R`m^t8$Ob`<)xuo@aB~;Pa`dD@!>ab$+kB_RjB@ZRpys)OWqY@EpVDo^~Nh!+h6; z`xWN0u0@7-vp+E(VQxa7)m*x5fNR}4T86Sv1;!c`v8sAqyGF(uH8R$akrAKDx*qU6 z9Q>f({Zg!fsR#E24)=(9oImw8Ru*Cl1av8>Mfuz3b+AuWqBuK#gu+FEqFXY0H#-LEKP zru!8f;~E>?@EJIl*w%HaGVXPI=7c$ak!5}y+nFk3e%z)!Q^w2APU?jpKZD%2HpS?{ zUMVv6M1kvL>ys_>sAoPI8=os>!OgLs-zY5CSbVwUtp!pCwx#4& z$oOkfJ^<5(GN$iAU=aVIoNX?MK*pItHHi*%GMFLQNLqhv|)S({eoc6_(817 zb$OP$e5{JS@YX_@IbZL~kj9wrw+80M(Y4Ia(1RZx5YCt%cJJhTG&G|++T!XvrO$=O zc8ZPg?x@EaM%I(C-`0Y%V!t2q3ECLd*)8_<+&C0dt^Ypo#b*)@0 zeJkTsS;whxEiLu_9Ao8r*w@qbQuaC-BO?Xly|C&F*^guWhQ3wSb$%iHU#~ZNVb#Ni_GbTBm_0%vYkptWtpAI0-JZC_ zx!iU0Tm@Vy^1L3o;U@5QZ*z@v+wXjH4*N=_=ykc;gByWU>UXOi+z8xm^x&wUZuH=& zuNyts>JJV+pxq94F)cidA{BP4o1HaIc{#`9`$O#%J%9=O83pu_R z^MM%y%6cyXGsXg!HhOJrDe_;6XHw*FF9P#^ zDRZ5f3QM2h`3Uv0CqkbDX8(|7uF19R5_HgxMp1#jLjOFkAcy`0w?L15u9VGCQ>d$z zHqAcK*Tf!YVz3XKM7B0KBZa=hTno9*81s5LBO)^fYF=;J(}CaB(?&si+0WLrUSaNA zp%3SftP6Wajp4w)-$dWplzPdbUg1pZjpr~d%Q$@g>b@qK?P*)5Wyw6V5 z^>k3$DPzjFQ(5~&*tc1DmeKR<5k5rEv&X>PJ4e?3oWsE1_Nc@O&cU1;(e{&DM~0+r zg zgSpg8t_P-lQ|;%We0{JX*FLYHeeYGn(Dl*A`-wRddgZ40j5!MWMW(Jr#{VMY|5Xk6 z@6K5^JA&=edzT%v2g`lTj$v87FWI|bgXh9?F;=<`4Y{;aatp@q5Mya5lQ!=Zo{?5? zAIDkOkTBP`WUgJYuTl2<7w8X$e>bT12TS|a54g<@NMEhIB6WrL2~L84T^mbXiGx03h4CzGIymM- z@M4zbJOVz%@Djs^mYf|ad2FzNefDnYXX#sA7j;XY_se}8!#V0Sr;KwXu%5rA(x1xO z|3hj&?pmhtJSSck^IPI(NZVqrL!ayU3G!Y&FwauJavsu-GmyYzJ3DC;^Wij{SFp|9 z5Xh)Gsl5 z?7u=;?AMsHz;D_&ZfK#KF80H!Y`k0F%BM!mw3wV=)6#?KKf%e5c$ zM;QH)hL0i-V0{alqfxJ{Nyw#5_5#~8BJF8SgqyT)mG@7)URm_zbIiKy+D&`TS#?Os zI(}8}$1={4>ln+l!CIv&Fwc|84d^xA%5z=l!&xitd*qh7(=#!NLvwyr7uNltvhD}L z*kAX`91X@D&!H)-Vmn^$ED2He3%VH$3b43CDOn%Ejw6#bS8<6g+vq(1aRLJ7w;xslBlGBkZq*bE3o*$_o2_(>$ZstGPwnAnel!cLg?FLmkRYWh}L>pIqyRo44*tSNh} z>Hp(sKbT9s$Az4Ox{jkg^BDLwv{_Mz&1*uAqMkO)C&J^AmzlD{IOpL#Jsy4}=a9?x zWUCwS-`S*}?0mx)f?HG7-^a5Hy)L~ido4LFGS=bBndBLA=x5K8ufQ=6L`Iy0ul+KZlZchJ|3o@X^d&$Alv3A~oUrd*5ic;tG}b1W?X>l?*Bj2)KsJV0ig zFTY=XdQPZkybTT(EPIyJm5YCJeFfH6)Q7P~W?7EMz^{*tC(e=ET+oYu|k=azbZCLm?YIPXp*&Ly^$8xlWY#I4T7;PAX= z7<@vAL&Ik)>zo^Si18-&tKIV73!laD!a2GJ*NVNQ>>tUzF2-c!@sgQOg1$Sl#x3<+ z?{V%RH}Dnw2sgE?@LI#;C1=Kl^K9_LxjQiPo@ZHmdzSc*T>6(irt0xFns3_@+^{i9n2Vp zd?pKM40J_n(mIRR%}^cl}rm9wa4|MM&w`nWa#H%uEEwDB>iYld9x zm79h&1{NCo<@7T+E;h?U?t*`??ls7?Z=NXEF1-AQ${}CD=CqKP$YQVe=rm(>YcA6L7uP2}VQrHOXY|SUWT200n;x^C>2dnL zL5=;H=z50d4}rNS3e0tja<8mmlyxqMjI~5yZy(n}(8sf7GUo_i7g+aq(91rK>ku%W znfA(>1RT$VmHXsuSy|77!SdYSm&J3{Sax}sk6?4!MD}w{;!Zbuu+<-`^HJh1piQA| z1M9jAKJlD-b+Eb6_yGrf8|-tLH+z!K_^dxL+dR{ib6jBNsK~=5<9*cDpvU=S3z;#) z{06QtZat^uY*_TVcSHV!jWTj0Smp)J1(8)RpZnn43cY;pqqecHNoHIzX2ANc)cmaE zBeNX(nCHPUmy#Lt%%94dGs*PHoT;quoP+s{rZU(R{UX|MjRbw%zmTct{snvznfW2| zx{}wPD10jWg!vOTWXW%2j!muum33^A>62?eav00tx0t%(x(oXA>3;=$k;8abMmzP| zp(jg!o^RR@#0FTN|mXA$8%ISL}as-QSBVXz%+LoEeUNenoXW+FVILgSqqxImF48^yB;a zO4HAXv}m)=*sL=)SJ9_$|5auCvt!9RrZVi)b&ovLW!Qt@eG2L4O!gq&=Jm$@jfO97 zz(3cbD7%$)rMd7n!&nJK{ZGl7XR_Zj_J(DQbp3PbvyRrlzD_^Nd>`I0@=w$wFGcQB zPF&iv*T$U~O^3c!l&q)z`<$MR$9EEo^ z!QCnRtDpFOAlhH2X$TpSINxzfj21QJs+K4pa<*wUEnxRflm!{fwImi%DUeH zpHCa)1o{U@8{tyFI@nxfe1dg;LD@^G_w5A7HiK6OJ>H4R4M%%Y!n6n62=!iB zwyh0k5#f53deoG}ls@LqR&W;}Df4ZX~F+`mM|d>5GWU}V+D&pbjebD#GU&n=;k`<=*IR`lHC z1U>gDfw?yU`#nfD3_gf$_U+fbz^GiyLeu_bwBeix`_~HR%3)rhp7vbxg9m6cGA=gc zdSH&TD>2{ECggK6%U+53F7o%3Lq3Q8O!^trde5Xi^C9#xH_q~TfO#V@@EzHSk(&rqujjhXb0zJMEMo_Hj-ANt6UIFC z;CyU;nvo0m;;AB0aZu5n{ulWfZ>XT#KoF$=!Y zl)cf|Z!&st^tq|DDc2seUAB^Xu0v*rF+0xdE$3fh|DMup^ZZU(&kVtF4+wrc&k`k` z6BqYT(8oO#IL>{_I`@Gu3FFA{*~-!8hA@tx2kV>&#yh>eGQN~`-c$}YZE_~bz3Kr@ z#~R~&4miB?Gskt_6L_w-VIGT|8y+t?GkUJUf<665#=I7o_eojLHpvZqalAxs8Xk{a zNlU$a7K8Q$zBqot9M^JB19QBCTNsDlzdSeOeTP2Ip^-5j19O~4j(Q!Z(91JB?^DkY z)m~ZcXG?ojudMYdN1Nyq`$|2dgMYB@E6Mc3cSHj-pD4EleS9Yh`keO68{l4Z?Y;Eh zhH(ddKW&&pz$ep&y^5$>Dz8WZHR?Y3EJG=gp?<&8E%Z zc#VO1Uv4&jZZTzVG5XC$udMlMv$46&*xY971;=`Cv-YOVcNqPh;z#|H>myqvw!!+2 zG}?Tp@fn!ca;NoE`cL0c=I8VsOXzcu{ha+}1FX5RwUza9T{PEC20uX9Cn5JJeliEt|wu$D$IT0%Z&bN!|TJI5jNo7Q1;r19DPEs zYgE{O-1zyFu?I(cu+FovzscBuqYe1rP%k*z-)!u`(HcdzAYi>tA2YRjb}FwEpTa!Lh5fDce^nvvzcu6>>N#fD z;;gmgz9yMI*J2GoHg$c$wDSw5-rFr3`G}Oq82R+)_8{gR>ms&gk{021IPH!6TiRS z&=_twjp1}$*O>A%E|%YgoBF-L&s;-7CvnSlBRH-P!EwC@?qPp=8;MUo^MyV>pCucc zIVt?}od@W@NM6xYX1{W8MLq2~Mk1@vyJ*kYhF)3Ak|l1b4>6;hpx^qMWcn=hTJ8$> z7y2)y^r`J3%iKhLn2*S&?A^2<)HWz<8_05=4EwvywSaLg+J?L3y0i^PH;v) zwVk}iGN*y#85($Fh$Ur>rA0{@OW^qKuCmUX%KG_nu&i@9w<@oe`4{}P5D&_l-;_0G zz*~$Bc#-5o)km9qL!N{l+#77b!KMx868bD)i7VqO^0;$>IoFdVmU6?*$g<9aO$+;w zXTo5ayBT9(JqJ^+hd6|Of7>jZGj$xBS`1*eQ#jtj7y zIdfeKmNmcEd!S~aKXK~7!SHW9N*$2aU9rO>Ce%gA; zY(u{*$4g+IOOqS?9s0zxj0>FaEd=kvv z$l7ai=#}xg-N0832*-0{=-(b<1H38RU$Cw{PzyND*Wj49!QU|U-((xO9)SLvA@<0| zKUmL}p?{e6TyK!WxF8#!-=Yo2FZACsKEGA^%-&Gim!I|MH+eoV%Y$doXO=6oJ#BK0 z<=?b{%}n~_-=YD>SnW^TEap${L&38$Dx${s3O@Ec637=0zXpz5{4UEueCkfLyn?8TbG&KW@s59Q0rwC6qg)6#spD7fTwtt)GBW#t^B1{+ zFK;7xCdW8}zQ+FH*pG~{0G9jf^~$Q3XJNeHypwIi7WQmO*O8bAnKJoSbz|{ML5D&2ToaKgl zm9tRS4?6X_3n%dZw3Ho}zIxhR*VAIZQ?5&x@lSi| zuSEPu)@xV&4^#2U_s1G6kRwgGJIqfhAd8G)^yq z^7HIs8Gb(xrI(5YORzTTmbqKpJ+P}nGZUJn#Wm`oSOwkwVsjL%R=M@CYVjq5epRua z7WhBwon736@3V`e@b#qYC}xZ9DXD1{O3%jfq*ZFs5?Ye~XS?;cUGTTS-BLVRuzk0% zWuR&9I#)xBYVOncz7*d#;`?m0rxQQ5xX-%^uBFeN$qsDxV~Z(|HoJHAih6_9&dNkaeUZ=Rvh3Ca`Tatu5~vQH}Cy76ZEw2 zl@iCH%%|KYceC53pFV#L<$Vky%7Vt0sJQXJ~$ z6mKosr4>CWm4e%g8&FS=UMT{%y?9uEFPdJt_M63XJ4(><+T!VgQOUbDn;y|~Uqnqy z-6`%Z?!4l?xM!!hHy7u@dN#^bakt)5Tnvj-d~9D@T#lYT1*K*bpLH2xe?NS1Zzdjb zMTXxAKe>ZZkN<5kv%tq|Ln*w5vT%~^LRhcESeXHvZoKk311$*m^Z@kfr`!#=GdIb- zxefP*{T6;d)o=N`SdRBa;$%_@=n+BmnEV62&&T(FLO;zdPIJc;r=qXU!WSYm`B!(C zJG?kU{{N^utT-I{s=Ewvd0D0Au1$K+{EPP->6$Hvcl^5YuIeu21BaAYxY5KV&7??1 zE3VS46qV5y*U~hyx@F{#lx;2lmcAza@Lr@yNAVZsn?}}TcT|2>d9LzYOIypqXi44W z*z__*54$yJ)ds}*7L1~2+_>9?7S@wI=|?+yw*ihMA*-{y;JX0C3?7|h*Tk@8ET-8v^aLbxKzOQciAg=Lx`MSMj>!196 zTg%_H{G#Pw&<`E>yZwitH%?C9mcAD~^0D-D={@`WUZfk+&FPkOG<_x=Pj{iu>iD-V z`q6Jg*>JYzkN&Q&XxRPU&6sg72k@iJchb#>Ps z@`qa0OLy&ny~^U+;@S~=l-twOBu#C(EmJ!k^3K`?)m^m@)vm5>tlhZ}d9X&=4*9Y5 z^I}~4Y3(<)7uvEtf8W-#r%p9(eUO9Oj%|BG+p4zrv|ZMAt(Q%0o7!#;rP{vQ_DI`P zZ9l5+YWro|@2b1nv-Y0$KK{1-RqZFVpNjAAY`>uW^7iXQZf^f-`y=g7wg0sJH^D>u z3olJNTt|H$GPh%K$C4@J@Q!8sl;b;2fxJ1iy8Pbp=a3JYpGP|W82Mz!^)DiuN?(5( zb$qGg8-H3!E!DB(CCcA+yd-fe+__kNTnqV(6_d)}Qkf{*D;1SbV|7XSvX|{{JN{A7qbcsMjZDXk zZ>5fT&)O!S6ks9N3b&KpJT>~d7`|+O_W)9N#6=~2NUD~tm3T9!D?wL znTxgKWI524@=7<#7{dAEDFI|s5FaxSDXN&2vp z*i*jZf_%UQS%X#28uu|H7rG1GTF6)Tka4VuRQ}3Ml+QVp?>m*xV?U#E6V|6UC2h$Z ziu^|f?>|m_C$4!*; z-PCfCn_8~HDnH2e?s_*eN!H_BU_I8_L9TM8+>HIqb(7>c>@BUl#=W@kF!6XVC%95x z>q=>2#ou%TMwYu1AyfTy}a@Ur?Lfmx-B>- z`r~9X_JBd|b)|gNebjws3i+g?+=r7Lm0MjY_v7T`3nI5U%2vn&Mt1v#`I-9} zPWmXTApb0KDbCeC?uBLF8s#$VdM^`&V46ZaGMV~=x;-#>WymQ(q*`?h-o z@_Ui!&V46}=#q4j9GIxQG4WCs6&l64-ceo#5|w!gWxki!Br1m{l)eQ2K*uC`)CKu3 zj`BBA{->k-t&!*4bMBW>w9PFE#QlAgU$_a9oIXjWB@@K`n~_PE=boJ;|I0;5x{@yU z--0A>qPXXyO!lO0Js|O7e!bg8Ne(L|SR@OK%u0gHOiDSy{Z1trEG3lZ_vAD$wZuwi z;w9K7-A3jnUcztp_B}ZW^14z=8=MjxqT+7chit<6w2@@ef2Gg8Q}Svn?z>Y<(hzw= zGNrf|_92s+lANqENnQT@4qN}r(Q6qN}|>U&F>@Vo!)_7rcB zbU^C+kQvF;(vS1=spQCHf=p=J9zP^Y-4CXgWPg!^6O|sEPE8^1@Ae{pD67S})|3(~ z@r2+-#9O@0O)YPCQ_JNpNT^c2Co3R%lDoe&@>%!U2}bLv?Fr?? zQp&QVWMPnr`tVb7yvUM7Bxv{io}5J4Qy(o%;~pj>q;P|Hhod5s; diff --git a/src/java.base/share/classes/jdk/internal/icu/text/NormalizerBase.java b/src/java.base/share/classes/jdk/internal/icu/text/NormalizerBase.java index f2566d9d419..afa09a3f537 100644 --- a/src/java.base/share/classes/jdk/internal/icu/text/NormalizerBase.java +++ b/src/java.base/share/classes/jdk/internal/icu/text/NormalizerBase.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, 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 @@ -420,10 +420,10 @@ public final class NormalizerBase implements Cloneable { * iterator's {@code clone} method does so. * @stable ICU 2.8 */ - public Object clone() { + public NormalizerBase clone() { try { NormalizerBase copy = (NormalizerBase) super.clone(); - copy.text = (UCharacterIterator) text.clone(); + copy.text = text.clone(); copy.mode = mode; copy.options = options; copy.norm2 = norm2; diff --git a/src/java.base/share/classes/jdk/internal/icu/text/UCharacterIterator.java b/src/java.base/share/classes/jdk/internal/icu/text/UCharacterIterator.java index 93978372c3a..24c54795508 100644 --- a/src/java.base/share/classes/jdk/internal/icu/text/UCharacterIterator.java +++ b/src/java.base/share/classes/jdk/internal/icu/text/UCharacterIterator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -310,8 +310,8 @@ public abstract class UCharacterIterator * @return copy of this iterator * @stable ICU 2.4 */ - public Object clone() throws CloneNotSupportedException{ - return super.clone(); + public UCharacterIterator clone() throws CloneNotSupportedException{ + return (UCharacterIterator) super.clone(); } } diff --git a/src/java.base/share/classes/jdk/internal/icu/text/UnicodeSet.java b/src/java.base/share/classes/jdk/internal/icu/text/UnicodeSet.java index 6f5919e016b..0bc68080e83 100644 --- a/src/java.base/share/classes/jdk/internal/icu/text/UnicodeSet.java +++ b/src/java.base/share/classes/jdk/internal/icu/text/UnicodeSet.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, 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 @@ -293,7 +293,7 @@ import jdk.internal.icu.util.VersionInfo; * @author Alan Liu * @stable ICU 2.0 */ -public class UnicodeSet { +public class UnicodeSet implements Cloneable { private static final int LOW = 0x000000; // LOW <= all valid values. ZERO for codepoints private static final int HIGH = 0x110000; // HIGH > all valid values. 10000 for code units. @@ -385,6 +385,18 @@ public class UnicodeSet { applyPattern(pattern, null); } + /** + * Return a new set that is equivalent to this one. + * @stable ICU 2.0 + */ + @Override + public UnicodeSet clone() { + if (isFrozen()) { + return this; + } + return new UnicodeSet(this); + } + /** * Make this object represent the same set as other. * @param other a UnicodeSet whose value will be diff --git a/src/java.base/share/classes/jdk/internal/icu/util/VersionInfo.java b/src/java.base/share/classes/jdk/internal/icu/util/VersionInfo.java index b7a52b74ae1..fa55b9df5af 100644 --- a/src/java.base/share/classes/jdk/internal/icu/util/VersionInfo.java +++ b/src/java.base/share/classes/jdk/internal/icu/util/VersionInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, 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 @@ -54,7 +54,7 @@ public final class VersionInfo * @deprecated This API is ICU internal only. */ @Deprecated - public static final String ICU_DATA_VERSION_PATH = "76b"; + public static final String ICU_DATA_VERSION_PATH = "78b"; // public methods ------------------------------------------------------ diff --git a/src/java.base/share/classes/jdk/internal/util/regex/Grapheme.java b/src/java.base/share/classes/jdk/internal/util/regex/Grapheme.java index 34a69c3045f..303a76cb545 100644 --- a/src/java.base/share/classes/jdk/internal/util/regex/Grapheme.java +++ b/src/java.base/share/classes/jdk/internal/util/regex/Grapheme.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -35,9 +35,9 @@ public final class Grapheme { *

    * See Unicode Standard Annex #29 Unicode Text Segmentation for the specification * for the extended grapheme cluster boundary rules. The following implementation - * is based on the annex for Unicode version 16.0. + * is based on the annex for Unicode version 17.0. * - * @spec http://www.unicode.org/reports/tr29/tr29-45.html + * @spec http://www.unicode.org/reports/tr29/tr29-47.html * @param src the {@code CharSequence} to be scanned * @param off offset to start looking for the next boundary in the src * @param limit limit offset in the src (exclusive) @@ -283,7 +283,6 @@ public final class Grapheme { case 0x113D1: case 0x1193F: case 0x11941: - case 0x11A3A: case 0x11A84: case 0x11A85: case 0x11A86: diff --git a/src/java.base/share/classes/jdk/internal/util/regex/IndicConjunctBreak.java.template b/src/java.base/share/classes/jdk/internal/util/regex/IndicConjunctBreak.java.template index fc0e6605eca..149b1dce660 100644 --- a/src/java.base/share/classes/jdk/internal/util/regex/IndicConjunctBreak.java.template +++ b/src/java.base/share/classes/jdk/internal/util/regex/IndicConjunctBreak.java.template @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2025, 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 @@ -49,8 +49,9 @@ final class IndicConjunctBreak { } static boolean isConsonant(int cp) { - // fast check - Devanagari to Malayalam - if (cp < 0x0900 || cp > 0x0D7F) { + // fast check - return false for code points below + // the Devanagari range (lowest among Indic scripts) + if (cp < 0x0900) { return false; } diff --git a/src/java.base/share/data/unicodedata/Blocks.txt b/src/java.base/share/data/unicodedata/Blocks.txt index 19460657ac9..5c24ab60cb1 100644 --- a/src/java.base/share/data/unicodedata/Blocks.txt +++ b/src/java.base/share/data/unicodedata/Blocks.txt @@ -1,6 +1,6 @@ -# Blocks-16.0.0.txt -# Date: 2024-02-02 -# Copyright (c) 2024 Unicode, Inc. +# Blocks-17.0.0.txt +# Date: 2025-08-01 +# © 2025 Unicode®, Inc. # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. # For terms of use and license, see https://www.unicode.org/terms_of_use.html # @@ -228,6 +228,7 @@ FFF0..FFFF; Specials 108E0..108FF; Hatran 10900..1091F; Phoenician 10920..1093F; Lydian +10940..1095F; Sidetic 10980..1099F; Meroitic Hieroglyphs 109A0..109FF; Meroitic Cursive 10A00..10A5F; Kharoshthi @@ -279,11 +280,13 @@ FFF0..FFFF; Specials 11AB0..11ABF; Unified Canadian Aboriginal Syllabics Extended-A 11AC0..11AFF; Pau Cin Hau 11B00..11B5F; Devanagari Extended-A +11B60..11B7F; Sharada Supplement 11BC0..11BFF; Sunuwar 11C00..11C6F; Bhaiksuki 11C70..11CBF; Marchen 11D00..11D5F; Masaram Gondi 11D60..11DAF; Gunjala Gondi +11DB0..11DEF; Tolong Siki 11EE0..11EFF; Makasar 11F00..11F5F; Kawi 11FB0..11FBF; Lisu Supplement @@ -304,12 +307,14 @@ FFF0..FFFF; Specials 16B00..16B8F; Pahawh Hmong 16D40..16D7F; Kirat Rai 16E40..16E9F; Medefaidrin +16EA0..16EDF; Beria Erfe 16F00..16F9F; Miao 16FE0..16FFF; Ideographic Symbols and Punctuation 17000..187FF; Tangut 18800..18AFF; Tangut Components 18B00..18CFF; Khitan Small Script 18D00..18D7F; Tangut Supplement +18D80..18DFF; Tangut Components Supplement 1AFF0..1AFFF; Kana Extended-B 1B000..1B0FF; Kana Supplement 1B100..1B12F; Kana Extended-A @@ -318,6 +323,7 @@ FFF0..FFFF; Specials 1BC00..1BC9F; Duployan 1BCA0..1BCAF; Shorthand Format Controls 1CC00..1CEBF; Symbols for Legacy Computing Supplement +1CEC0..1CEFF; Miscellaneous Symbols Supplement 1CF00..1CFCF; Znamenny Musical Notation 1D000..1D0FF; Byzantine Musical Symbols 1D100..1D1FF; Musical Symbols @@ -336,6 +342,7 @@ FFF0..FFFF; Specials 1E2C0..1E2FF; Wancho 1E4D0..1E4FF; Nag Mundari 1E5D0..1E5FF; Ol Onal +1E6C0..1E6FF; Tai Yo 1E7E0..1E7FF; Ethiopic Extended-B 1E800..1E8DF; Mende Kikakui 1E900..1E95F; Adlam @@ -367,6 +374,7 @@ FFF0..FFFF; Specials 2F800..2FA1F; CJK Compatibility Ideographs Supplement 30000..3134F; CJK Unified Ideographs Extension G 31350..323AF; CJK Unified Ideographs Extension H +323B0..3347F; CJK Unified Ideographs Extension J E0000..E007F; Tags E0100..E01EF; Variation Selectors Supplement F0000..FFFFF; Supplementary Private Use Area-A diff --git a/src/java.base/share/data/unicodedata/CaseFolding.txt b/src/java.base/share/data/unicodedata/CaseFolding.txt index 345c4f3dd03..a0b0f07fd64 100644 --- a/src/java.base/share/data/unicodedata/CaseFolding.txt +++ b/src/java.base/share/data/unicodedata/CaseFolding.txt @@ -1,6 +1,6 @@ -# CaseFolding-16.0.0.txt -# Date: 2024-04-30, 21:48:11 GMT -# © 2024 Unicode®, Inc. +# CaseFolding-17.0.0.txt +# Date: 2025-07-30, 23:54:36 GMT +# © 2025 Unicode®, Inc. # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. # For terms of use and license, see https://www.unicode.org/terms_of_use.html # @@ -18,15 +18,15 @@ # The data supports both implementations that require simple case foldings # (where string lengths don't change), and implementations that allow full case folding # (where string lengths may grow). Note that where they can be supported, the -# full case foldings are superior: for example, they allow "MASSE" and "Maße" to match. +# full case foldings are superior: for example, they allow "FUSS" and "Fuß" to match. # # All code points not listed in this file map to themselves. # # NOTE: case folding does not preserve normalization formats! # # For information on case folding, including how to have case folding -# preserve normalization formats, see Section 3.13 Default Case Algorithms in -# The Unicode Standard. +# preserve normalization formats, see the +# "Conformance" / "Default Case Algorithms" section of the core specification. # # ================================================================================ # Format @@ -1243,7 +1243,10 @@ A7C7; C; A7C8; # LATIN CAPITAL LETTER D WITH SHORT STROKE OVERLAY A7C9; C; A7CA; # LATIN CAPITAL LETTER S WITH SHORT STROKE OVERLAY A7CB; C; 0264; # LATIN CAPITAL LETTER RAMS HORN A7CC; C; A7CD; # LATIN CAPITAL LETTER S WITH DIAGONAL STROKE +A7CE; C; A7CF; # LATIN CAPITAL LETTER PHARYNGEAL VOICED FRICATIVE A7D0; C; A7D1; # LATIN CAPITAL LETTER CLOSED INSULAR G +A7D2; C; A7D3; # LATIN CAPITAL LETTER DOUBLE THORN +A7D4; C; A7D5; # LATIN CAPITAL LETTER DOUBLE WYNN A7D6; C; A7D7; # LATIN CAPITAL LETTER MIDDLE SCOTS S A7D8; C; A7D9; # LATIN CAPITAL LETTER SIGMOID S A7DA; C; A7DB; # LATIN CAPITAL LETTER LAMBDA @@ -1616,6 +1619,31 @@ FF3A; C; FF5A; # FULLWIDTH LATIN CAPITAL LETTER Z 16E5D; C; 16E7D; # MEDEFAIDRIN CAPITAL LETTER O 16E5E; C; 16E7E; # MEDEFAIDRIN CAPITAL LETTER AI 16E5F; C; 16E7F; # MEDEFAIDRIN CAPITAL LETTER Y +16EA0; C; 16EBB; # BERIA ERFE CAPITAL LETTER ARKAB +16EA1; C; 16EBC; # BERIA ERFE CAPITAL LETTER BASIGNA +16EA2; C; 16EBD; # BERIA ERFE CAPITAL LETTER DARBAI +16EA3; C; 16EBE; # BERIA ERFE CAPITAL LETTER EH +16EA4; C; 16EBF; # BERIA ERFE CAPITAL LETTER FITKO +16EA5; C; 16EC0; # BERIA ERFE CAPITAL LETTER GOWAY +16EA6; C; 16EC1; # BERIA ERFE CAPITAL LETTER HIRDEABO +16EA7; C; 16EC2; # BERIA ERFE CAPITAL LETTER I +16EA8; C; 16EC3; # BERIA ERFE CAPITAL LETTER DJAI +16EA9; C; 16EC4; # BERIA ERFE CAPITAL LETTER KOBO +16EAA; C; 16EC5; # BERIA ERFE CAPITAL LETTER LAKKO +16EAB; C; 16EC6; # BERIA ERFE CAPITAL LETTER MERI +16EAC; C; 16EC7; # BERIA ERFE CAPITAL LETTER NINI +16EAD; C; 16EC8; # BERIA ERFE CAPITAL LETTER GNA +16EAE; C; 16EC9; # BERIA ERFE CAPITAL LETTER NGAY +16EAF; C; 16ECA; # BERIA ERFE CAPITAL LETTER OI +16EB0; C; 16ECB; # BERIA ERFE CAPITAL LETTER PI +16EB1; C; 16ECC; # BERIA ERFE CAPITAL LETTER ERIGO +16EB2; C; 16ECD; # BERIA ERFE CAPITAL LETTER ERIGO TAMURA +16EB3; C; 16ECE; # BERIA ERFE CAPITAL LETTER SERI +16EB4; C; 16ECF; # BERIA ERFE CAPITAL LETTER SHEP +16EB5; C; 16ED0; # BERIA ERFE CAPITAL LETTER TATASOUE +16EB6; C; 16ED1; # BERIA ERFE CAPITAL LETTER UI +16EB7; C; 16ED2; # BERIA ERFE CAPITAL LETTER WASSE +16EB8; C; 16ED3; # BERIA ERFE CAPITAL LETTER AY 1E900; C; 1E922; # ADLAM CAPITAL LETTER ALIF 1E901; C; 1E923; # ADLAM CAPITAL LETTER DAALI 1E902; C; 1E924; # ADLAM CAPITAL LETTER LAAM @@ -1651,4 +1679,4 @@ FF3A; C; FF5A; # FULLWIDTH LATIN CAPITAL LETTER Z 1E920; C; 1E942; # ADLAM CAPITAL LETTER KPO 1E921; C; 1E943; # ADLAM CAPITAL LETTER SHA # -# EOF \ No newline at end of file +# EOF diff --git a/src/java.base/share/data/unicodedata/DerivedCoreProperties.txt b/src/java.base/share/data/unicodedata/DerivedCoreProperties.txt index 43d7af85c29..f327784bf39 100644 --- a/src/java.base/share/data/unicodedata/DerivedCoreProperties.txt +++ b/src/java.base/share/data/unicodedata/DerivedCoreProperties.txt @@ -1,6 +1,6 @@ -# DerivedCoreProperties-16.0.0.txt -# Date: 2024-05-31, 18:09:32 GMT -# Copyright (c) 2024 Unicode, Inc. +# DerivedCoreProperties-17.0.0.txt +# Date: 2025-07-30, 23:55:08 GMT +# © 2025 Unicode®, Inc. # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. # For terms of use and license, see https://www.unicode.org/terms_of_use.html # @@ -178,6 +178,7 @@ FF5E ; Math # Sm FULLWIDTH TILDE FFE2 ; Math # Sm FULLWIDTH NOT SIGN FFE9..FFEC ; Math # Sm [4] HALFWIDTH LEFTWARDS ARROW..HALFWIDTH DOWNWARDS ARROW 10D8E..10D8F ; Math # Sm [2] GARAY PLUS SIGN..GARAY MINUS SIGN +1CEF0 ; Math # Sm MEDIUM SMALL WHITE CIRCLE WITH HORIZONTAL BAR 1D400..1D454 ; Math # L& [85] MATHEMATICAL BOLD CAPITAL A..MATHEMATICAL ITALIC SMALL G 1D456..1D49C ; Math # L& [71] MATHEMATICAL ITALIC SMALL I..MATHEMATICAL SCRIPT CAPITAL A 1D49E..1D49F ; Math # L& [2] MATHEMATICAL SCRIPT CAPITAL C..MATHEMATICAL SCRIPT CAPITAL D @@ -253,8 +254,9 @@ FFE9..FFEC ; Math # Sm [4] HALFWIDTH LEFTWARDS ARROW..HALFWIDTH DOWNWARDS A 1EEA5..1EEA9 ; Math # Lo [5] ARABIC MATHEMATICAL DOUBLE-STRUCK WAW..ARABIC MATHEMATICAL DOUBLE-STRUCK YEH 1EEAB..1EEBB ; Math # Lo [17] ARABIC MATHEMATICAL DOUBLE-STRUCK LAM..ARABIC MATHEMATICAL DOUBLE-STRUCK GHAIN 1EEF0..1EEF1 ; Math # Sm [2] ARABIC MATHEMATICAL OPERATOR MEEM WITH HAH WITH TATWEEL..ARABIC MATHEMATICAL OPERATOR HAH WITH DAL +1F8D0..1F8D8 ; Math # Sm [9] LONG RIGHTWARDS ARROW OVER LONG LEFTWARDS ARROW..LONG LEFT RIGHT ARROW WITH DEPENDENT LOBE -# Total code points: 2312 +# Total code points: 2322 # ================================================ @@ -273,8 +275,8 @@ FFE9..FFEC ; Math # Sm [4] HALFWIDTH LEFTWARDS ARROW..HALFWIDTH DOWNWARDS A 01BC..01BF ; Alphabetic # L& [4] LATIN CAPITAL LETTER TONE FIVE..LATIN LETTER WYNN 01C0..01C3 ; Alphabetic # Lo [4] LATIN LETTER DENTAL CLICK..LATIN LETTER RETROFLEX CLICK 01C4..0293 ; Alphabetic # L& [208] LATIN CAPITAL LETTER DZ WITH CARON..LATIN SMALL LETTER EZH WITH CURL -0294 ; Alphabetic # Lo LATIN LETTER GLOTTAL STOP -0295..02AF ; Alphabetic # L& [27] LATIN LETTER PHARYNGEAL VOICED FRICATIVE..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL +0294..0295 ; Alphabetic # Lo [2] LATIN LETTER GLOTTAL STOP..LATIN LETTER PHARYNGEAL VOICED FRICATIVE +0296..02AF ; Alphabetic # L& [26] LATIN LETTER INVERTED GLOTTAL STOP..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL 02B0..02C1 ; Alphabetic # Lm [18] MODIFIER LETTER SMALL H..MODIFIER LETTER REVERSED GLOTTAL STOP 02C6..02D1 ; Alphabetic # Lm [12] MODIFIER LETTER CIRCUMFLEX ACCENT..MODIFIER LETTER HALF TRIANGULAR COLON 02E0..02E4 ; Alphabetic # Lm [5] MODIFIER LETTER SMALL GAMMA..MODIFIER LETTER SMALL REVERSED GLOTTAL STOP @@ -344,7 +346,7 @@ FFE9..FFEC ; Math # Sm [4] HALFWIDTH LEFTWARDS ARROW..HALFWIDTH DOWNWARDS A 0840..0858 ; Alphabetic # Lo [25] MANDAIC LETTER HALQA..MANDAIC LETTER AIN 0860..086A ; Alphabetic # Lo [11] SYRIAC LETTER MALAYALAM NGA..SYRIAC LETTER MALAYALAM SSA 0870..0887 ; Alphabetic # Lo [24] ARABIC LETTER ALEF WITH ATTACHED FATHA..ARABIC BASELINE ROUND DOT -0889..088E ; Alphabetic # Lo [6] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC VERTICAL TAIL +0889..088F ; Alphabetic # Lo [7] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC LETTER NOON WITH RING ABOVE 0897 ; Alphabetic # Mn ARABIC PEPET 08A0..08C8 ; Alphabetic # Lo [41] ARABIC LETTER BEH WITH SMALL V BELOW..ARABIC LETTER GRAF 08C9 ; Alphabetic # Lm ARABIC SMALL FARSI YEH @@ -477,7 +479,7 @@ FFE9..FFEC ; Math # Sm [4] HALFWIDTH LEFTWARDS ARROW..HALFWIDTH DOWNWARDS A 0C4A..0C4C ; Alphabetic # Mn [3] TELUGU VOWEL SIGN O..TELUGU VOWEL SIGN AU 0C55..0C56 ; Alphabetic # Mn [2] TELUGU LENGTH MARK..TELUGU AI LENGTH MARK 0C58..0C5A ; Alphabetic # Lo [3] TELUGU LETTER TSA..TELUGU LETTER RRRA -0C5D ; Alphabetic # Lo TELUGU LETTER NAKAARA POLLU +0C5C..0C5D ; Alphabetic # Lo [2] TELUGU ARCHAIC SHRII..TELUGU LETTER NAKAARA POLLU 0C60..0C61 ; Alphabetic # Lo [2] TELUGU LETTER VOCALIC RR..TELUGU LETTER VOCALIC LL 0C62..0C63 ; Alphabetic # Mn [2] TELUGU VOWEL SIGN VOCALIC L..TELUGU VOWEL SIGN VOCALIC LL 0C80 ; Alphabetic # Lo KANNADA SIGN SPACING CANDRABINDU @@ -497,7 +499,7 @@ FFE9..FFEC ; Math # Sm [4] HALFWIDTH LEFTWARDS ARROW..HALFWIDTH DOWNWARDS A 0CCA..0CCB ; Alphabetic # Mc [2] KANNADA VOWEL SIGN O..KANNADA VOWEL SIGN OO 0CCC ; Alphabetic # Mn KANNADA VOWEL SIGN AU 0CD5..0CD6 ; Alphabetic # Mc [2] KANNADA LENGTH MARK..KANNADA AI LENGTH MARK -0CDD..0CDE ; Alphabetic # Lo [2] KANNADA LETTER NAKAARA POLLU..KANNADA LETTER FA +0CDC..0CDE ; Alphabetic # Lo [3] KANNADA ARCHAIC SHRII..KANNADA LETTER FA 0CE0..0CE1 ; Alphabetic # Lo [2] KANNADA LETTER VOCALIC RR..KANNADA LETTER VOCALIC LL 0CE2..0CE3 ; Alphabetic # Mn [2] KANNADA VOWEL SIGN VOCALIC L..KANNADA VOWEL SIGN VOCALIC LL 0CF1..0CF2 ; Alphabetic # Lo [2] KANNADA SIGN JIHVAMULIYA..KANNADA SIGN UPADHMANIYA @@ -833,11 +835,8 @@ A771..A787 ; Alphabetic # L& [23] LATIN SMALL LETTER DUM..LATIN SMALL LETTER A788 ; Alphabetic # Lm MODIFIER LETTER LOW CIRCUMFLEX ACCENT A78B..A78E ; Alphabetic # L& [4] LATIN CAPITAL LETTER SALTILLO..LATIN SMALL LETTER L WITH RETROFLEX HOOK AND BELT A78F ; Alphabetic # Lo LATIN LETTER SINOLOGICAL DOT -A790..A7CD ; Alphabetic # L& [62] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN SMALL LETTER S WITH DIAGONAL STROKE -A7D0..A7D1 ; Alphabetic # L& [2] LATIN CAPITAL LETTER CLOSED INSULAR G..LATIN SMALL LETTER CLOSED INSULAR G -A7D3 ; Alphabetic # L& LATIN SMALL LETTER DOUBLE THORN -A7D5..A7DC ; Alphabetic # L& [8] LATIN SMALL LETTER DOUBLE WYNN..LATIN CAPITAL LETTER LAMBDA WITH STROKE -A7F2..A7F4 ; Alphabetic # Lm [3] MODIFIER LETTER CAPITAL C..MODIFIER LETTER CAPITAL Q +A790..A7DC ; Alphabetic # L& [77] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN CAPITAL LETTER LAMBDA WITH STROKE +A7F1..A7F4 ; Alphabetic # Lm [4] MODIFIER LETTER CAPITAL S..MODIFIER LETTER CAPITAL Q A7F5..A7F6 ; Alphabetic # L& [2] LATIN CAPITAL LETTER REVERSED HALF H..LATIN SMALL LETTER REVERSED HALF H A7F7 ; Alphabetic # Lo LATIN EPIGRAPHIC LETTER SIDEWAYS I A7F8..A7F9 ; Alphabetic # Lm [2] MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE @@ -1020,6 +1019,7 @@ FFDA..FFDC ; Alphabetic # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANG 108F4..108F5 ; Alphabetic # Lo [2] HATRAN LETTER SHIN..HATRAN LETTER TAW 10900..10915 ; Alphabetic # Lo [22] PHOENICIAN LETTER ALF..PHOENICIAN LETTER TAU 10920..10939 ; Alphabetic # Lo [26] LYDIAN LETTER A..LYDIAN LETTER C +10940..10959 ; Alphabetic # Lo [26] SIDETIC LETTER N01..SIDETIC LETTER N26 10980..109B7 ; Alphabetic # Lo [56] MEROITIC HIEROGLYPHIC LETTER A..MEROITIC CURSIVE LETTER DA 109BE..109BF ; Alphabetic # Lo [2] MEROITIC CURSIVE LOGOGRAM RMT..MEROITIC CURSIVE LOGOGRAM IMN 10A00 ; Alphabetic # Lo KHAROSHTHI LETTER A @@ -1053,7 +1053,9 @@ FFDA..FFDC ; Alphabetic # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANG 10EAB..10EAC ; Alphabetic # Mn [2] YEZIDI COMBINING HAMZA MARK..YEZIDI COMBINING MADDA MARK 10EB0..10EB1 ; Alphabetic # Lo [2] YEZIDI LETTER LAM WITH DOT ABOVE..YEZIDI LETTER YOT WITH CIRCUMFLEX ABOVE 10EC2..10EC4 ; Alphabetic # Lo [3] ARABIC LETTER DAL WITH TWO DOTS VERTICALLY BELOW..ARABIC LETTER KAF WITH TWO DOTS VERTICALLY BELOW -10EFC ; Alphabetic # Mn ARABIC COMBINING ALEF OVERLAY +10EC5 ; Alphabetic # Lm ARABIC SMALL YEH BARREE WITH TWO DOTS BELOW +10EC6..10EC7 ; Alphabetic # Lo [2] ARABIC LETTER THIN NOON..ARABIC LETTER YEH WITH FOUR DOTS BELOW +10EFA..10EFC ; Alphabetic # Mn [3] ARABIC DOUBLE VERTICAL BAR BELOW..ARABIC COMBINING ALEF OVERLAY 10F00..10F1C ; Alphabetic # Lo [29] OLD SOGDIAN LETTER ALEPH..OLD SOGDIAN LETTER FINAL TAW WITH VERTICAL TAIL 10F27 ; Alphabetic # Lo OLD SOGDIAN LIGATURE AYIN-DALETH 10F30..10F45 ; Alphabetic # Lo [22] SOGDIAN LETTER ALEPH..SOGDIAN INDEPENDENT SHIN @@ -1239,6 +1241,12 @@ FFDA..FFDC ; Alphabetic # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANG 11A97 ; Alphabetic # Mc SOYOMBO SIGN VISARGA 11A9D ; Alphabetic # Lo SOYOMBO MARK PLUTA 11AB0..11AF8 ; Alphabetic # Lo [73] CANADIAN SYLLABICS NATTILIK HI..PAU CIN HAU GLOTTAL STOP FINAL +11B60 ; Alphabetic # Mn SHARADA VOWEL SIGN OE +11B61 ; Alphabetic # Mc SHARADA VOWEL SIGN OOE +11B62..11B64 ; Alphabetic # Mn [3] SHARADA VOWEL SIGN UE..SHARADA VOWEL SIGN SHORT E +11B65 ; Alphabetic # Mc SHARADA VOWEL SIGN SHORT O +11B66 ; Alphabetic # Mn SHARADA VOWEL SIGN CANDRA E +11B67 ; Alphabetic # Mc SHARADA VOWEL SIGN CANDRA O 11BC0..11BE0 ; Alphabetic # Lo [33] SUNUWAR LETTER DEVI..SUNUWAR LETTER KLOKO 11C00..11C08 ; Alphabetic # Lo [9] BHAIKSUKI LETTER A..BHAIKSUKI LETTER VOCALIC L 11C0A..11C2E ; Alphabetic # Lo [37] BHAIKSUKI LETTER E..BHAIKSUKI LETTER HA @@ -1274,6 +1282,9 @@ FFDA..FFDC ; Alphabetic # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANG 11D95 ; Alphabetic # Mn GUNJALA GONDI SIGN ANUSVARA 11D96 ; Alphabetic # Mc GUNJALA GONDI SIGN VISARGA 11D98 ; Alphabetic # Lo GUNJALA GONDI OM +11DB0..11DD8 ; Alphabetic # Lo [41] TOLONG SIKI LETTER I..TOLONG SIKI LETTER RRH +11DD9 ; Alphabetic # Lm TOLONG SIKI SIGN SELA +11DDA..11DDB ; Alphabetic # Lo [2] TOLONG SIKI SIGN HECAKA..TOLONG SIKI UNGGA 11EE0..11EF2 ; Alphabetic # Lo [19] MAKASAR LETTER KA..MAKASAR ANGKA 11EF3..11EF4 ; Alphabetic # Mn [2] MAKASAR VOWEL SIGN I..MAKASAR VOWEL SIGN U 11EF5..11EF6 ; Alphabetic # Mc [2] MAKASAR VOWEL SIGN E..MAKASAR VOWEL SIGN O @@ -1311,6 +1322,8 @@ FFDA..FFDC ; Alphabetic # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANG 16D43..16D6A ; Alphabetic # Lo [40] KIRAT RAI LETTER A..KIRAT RAI VOWEL SIGN AU 16D6B..16D6C ; Alphabetic # Lm [2] KIRAT RAI SIGN VIRAMA..KIRAT RAI SIGN SAAT 16E40..16E7F ; Alphabetic # L& [64] MEDEFAIDRIN CAPITAL LETTER M..MEDEFAIDRIN SMALL LETTER Y +16EA0..16EB8 ; Alphabetic # L& [25] BERIA ERFE CAPITAL LETTER ARKAB..BERIA ERFE CAPITAL LETTER AY +16EBB..16ED3 ; Alphabetic # L& [25] BERIA ERFE SMALL LETTER ARKAB..BERIA ERFE SMALL LETTER AY 16F00..16F4A ; Alphabetic # Lo [75] MIAO LETTER PA..MIAO LETTER RTE 16F4F ; Alphabetic # Mn MIAO SIGN CONSONANT MODIFIER BAR 16F50 ; Alphabetic # Lo MIAO LETTER NASALIZATION @@ -1320,9 +1333,11 @@ FFDA..FFDC ; Alphabetic # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANG 16FE0..16FE1 ; Alphabetic # Lm [2] TANGUT ITERATION MARK..NUSHU ITERATION MARK 16FE3 ; Alphabetic # Lm OLD CHINESE ITERATION MARK 16FF0..16FF1 ; Alphabetic # Mc [2] VIETNAMESE ALTERNATE READING MARK CA..VIETNAMESE ALTERNATE READING MARK NHAY -17000..187F7 ; Alphabetic # Lo [6136] TANGUT IDEOGRAPH-17000..TANGUT IDEOGRAPH-187F7 -18800..18CD5 ; Alphabetic # Lo [1238] TANGUT COMPONENT-001..KHITAN SMALL SCRIPT CHARACTER-18CD5 -18CFF..18D08 ; Alphabetic # Lo [10] KHITAN SMALL SCRIPT CHARACTER-18CFF..TANGUT IDEOGRAPH-18D08 +16FF2..16FF3 ; Alphabetic # Lm [2] CHINESE SMALL SIMPLIFIED ER..CHINESE SMALL TRADITIONAL ER +16FF4..16FF6 ; Alphabetic # Nl [3] YANGQIN SIGN SLOW ONE BEAT..YANGQIN SIGN SLOW TWO BEATS +17000..18CD5 ; Alphabetic # Lo [7382] TANGUT IDEOGRAPH-17000..KHITAN SMALL SCRIPT CHARACTER-18CD5 +18CFF..18D1E ; Alphabetic # Lo [32] KHITAN SMALL SCRIPT CHARACTER-18CFF..TANGUT IDEOGRAPH-18D1E +18D80..18DF2 ; Alphabetic # Lo [115] TANGUT COMPONENT-769..TANGUT COMPONENT-883 1AFF0..1AFF3 ; Alphabetic # Lm [4] KATAKANA LETTER MINNAN TONE-2..KATAKANA LETTER MINNAN TONE-5 1AFF5..1AFFB ; Alphabetic # Lm [7] KATAKANA LETTER MINNAN TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-5 1AFFD..1AFFE ; Alphabetic # Lm [2] KATAKANA LETTER MINNAN NASALIZED TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-8 @@ -1387,6 +1402,17 @@ FFDA..FFDC ; Alphabetic # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANG 1E4EB ; Alphabetic # Lm NAG MUNDARI SIGN OJOD 1E5D0..1E5ED ; Alphabetic # Lo [30] OL ONAL LETTER O..OL ONAL LETTER EG 1E5F0 ; Alphabetic # Lo OL ONAL SIGN HODDOND +1E6C0..1E6DE ; Alphabetic # Lo [31] TAI YO LETTER LOW KO..TAI YO LETTER HIGH KVO +1E6E0..1E6E2 ; Alphabetic # Lo [3] TAI YO LETTER AA..TAI YO LETTER UE +1E6E3 ; Alphabetic # Mn TAI YO SIGN UE +1E6E4..1E6E5 ; Alphabetic # Lo [2] TAI YO LETTER U..TAI YO LETTER AE +1E6E6 ; Alphabetic # Mn TAI YO SIGN AU +1E6E7..1E6ED ; Alphabetic # Lo [7] TAI YO LETTER O..TAI YO LETTER AUE +1E6EE..1E6EF ; Alphabetic # Mn [2] TAI YO SIGN AY..TAI YO SIGN ANG +1E6F0..1E6F4 ; Alphabetic # Lo [5] TAI YO LETTER AN..TAI YO LETTER AP +1E6F5 ; Alphabetic # Mn TAI YO SIGN OM +1E6FE ; Alphabetic # Lo TAI YO SYMBOL MUEANG +1E6FF ; Alphabetic # Lm TAI YO XAM LAI 1E7E0..1E7E6 ; Alphabetic # Lo [7] ETHIOPIC SYLLABLE HHYA..ETHIOPIC SYLLABLE HHYO 1E7E8..1E7EB ; Alphabetic # Lo [4] ETHIOPIC SYLLABLE GURAGE HHWA..ETHIOPIC SYLLABLE HHWE 1E7ED..1E7EE ; Alphabetic # Lo [2] ETHIOPIC SYLLABLE GURAGE MWI..ETHIOPIC SYLLABLE GURAGE MWEE @@ -1432,16 +1458,15 @@ FFDA..FFDC ; Alphabetic # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANG 1F150..1F169 ; Alphabetic # So [26] NEGATIVE CIRCLED LATIN CAPITAL LETTER A..NEGATIVE CIRCLED LATIN CAPITAL LETTER Z 1F170..1F189 ; Alphabetic # So [26] NEGATIVE SQUARED LATIN CAPITAL LETTER A..NEGATIVE SQUARED LATIN CAPITAL LETTER Z 20000..2A6DF ; Alphabetic # Lo [42720] CJK UNIFIED IDEOGRAPH-20000..CJK UNIFIED IDEOGRAPH-2A6DF -2A700..2B739 ; Alphabetic # Lo [4154] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B739 -2B740..2B81D ; Alphabetic # Lo [222] CJK UNIFIED IDEOGRAPH-2B740..CJK UNIFIED IDEOGRAPH-2B81D -2B820..2CEA1 ; Alphabetic # Lo [5762] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEA1 +2A700..2B81D ; Alphabetic # Lo [4382] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B81D +2B820..2CEAD ; Alphabetic # Lo [5774] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEAD 2CEB0..2EBE0 ; Alphabetic # Lo [7473] CJK UNIFIED IDEOGRAPH-2CEB0..CJK UNIFIED IDEOGRAPH-2EBE0 2EBF0..2EE5D ; Alphabetic # Lo [622] CJK UNIFIED IDEOGRAPH-2EBF0..CJK UNIFIED IDEOGRAPH-2EE5D 2F800..2FA1D ; Alphabetic # Lo [542] CJK COMPATIBILITY IDEOGRAPH-2F800..CJK COMPATIBILITY IDEOGRAPH-2FA1D 30000..3134A ; Alphabetic # Lo [4939] CJK UNIFIED IDEOGRAPH-30000..CJK UNIFIED IDEOGRAPH-3134A -31350..323AF ; Alphabetic # Lo [4192] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-323AF +31350..33479 ; Alphabetic # Lo [8490] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-33479 -# Total code points: 142759 +# Total code points: 147421 # ================================================ @@ -1595,7 +1620,7 @@ FFDA..FFDC ; Alphabetic # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANG 024B ; Lowercase # L& LATIN SMALL LETTER Q WITH HOOK TAIL 024D ; Lowercase # L& LATIN SMALL LETTER R WITH STROKE 024F..0293 ; Lowercase # L& [69] LATIN SMALL LETTER Y WITH STROKE..LATIN SMALL LETTER EZH WITH CURL -0295..02AF ; Lowercase # L& [27] LATIN LETTER PHARYNGEAL VOICED FRICATIVE..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL +0296..02AF ; Lowercase # L& [26] LATIN LETTER INVERTED GLOTTAL STOP..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL 02B0..02B8 ; Lowercase # Lm [9] MODIFIER LETTER SMALL H..MODIFIER LETTER SMALL Y 02C0..02C1 ; Lowercase # Lm [2] MODIFIER LETTER GLOTTAL STOP..MODIFIER LETTER REVERSED GLOTTAL STOP 02E0..02E4 ; Lowercase # Lm [5] MODIFIER LETTER SMALL GAMMA..MODIFIER LETTER SMALL REVERSED GLOTTAL STOP @@ -2073,13 +2098,14 @@ A7C3 ; Lowercase # L& LATIN SMALL LETTER ANGLICANA W A7C8 ; Lowercase # L& LATIN SMALL LETTER D WITH SHORT STROKE OVERLAY A7CA ; Lowercase # L& LATIN SMALL LETTER S WITH SHORT STROKE OVERLAY A7CD ; Lowercase # L& LATIN SMALL LETTER S WITH DIAGONAL STROKE +A7CF ; Lowercase # L& LATIN SMALL LETTER PHARYNGEAL VOICED FRICATIVE A7D1 ; Lowercase # L& LATIN SMALL LETTER CLOSED INSULAR G A7D3 ; Lowercase # L& LATIN SMALL LETTER DOUBLE THORN A7D5 ; Lowercase # L& LATIN SMALL LETTER DOUBLE WYNN A7D7 ; Lowercase # L& LATIN SMALL LETTER MIDDLE SCOTS S A7D9 ; Lowercase # L& LATIN SMALL LETTER SIGMOID S A7DB ; Lowercase # L& LATIN SMALL LETTER LAMBDA -A7F2..A7F4 ; Lowercase # Lm [3] MODIFIER LETTER CAPITAL C..MODIFIER LETTER CAPITAL Q +A7F1..A7F4 ; Lowercase # Lm [4] MODIFIER LETTER CAPITAL S..MODIFIER LETTER CAPITAL Q A7F6 ; Lowercase # L& LATIN SMALL LETTER REVERSED HALF H A7F8..A7F9 ; Lowercase # Lm [2] MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE A7FA ; Lowercase # L& LATIN LETTER SMALL CAPITAL TURNED M @@ -2105,6 +2131,7 @@ FF41..FF5A ; Lowercase # L& [26] FULLWIDTH LATIN SMALL LETTER A..FULLWIDTH L 10D70..10D85 ; Lowercase # L& [22] GARAY SMALL LETTER A..GARAY SMALL LETTER OLD NA 118C0..118DF ; Lowercase # L& [32] WARANG CITI SMALL LETTER NGAA..WARANG CITI SMALL LETTER VIYO 16E60..16E7F ; Lowercase # L& [32] MEDEFAIDRIN SMALL LETTER M..MEDEFAIDRIN SMALL LETTER Y +16EBB..16ED3 ; Lowercase # L& [25] BERIA ERFE SMALL LETTER ARKAB..BERIA ERFE SMALL LETTER AY 1D41A..1D433 ; Lowercase # L& [26] MATHEMATICAL BOLD SMALL A..MATHEMATICAL BOLD SMALL Z 1D44E..1D454 ; Lowercase # L& [7] MATHEMATICAL ITALIC SMALL A..MATHEMATICAL ITALIC SMALL G 1D456..1D467 ; Lowercase # L& [18] MATHEMATICAL ITALIC SMALL I..MATHEMATICAL ITALIC SMALL Z @@ -2139,7 +2166,7 @@ FF41..FF5A ; Lowercase # L& [26] FULLWIDTH LATIN SMALL LETTER A..FULLWIDTH L 1E030..1E06D ; Lowercase # Lm [62] MODIFIER LETTER CYRILLIC SMALL A..MODIFIER LETTER CYRILLIC SMALL STRAIGHT U WITH STROKE 1E922..1E943 ; Lowercase # L& [34] ADLAM SMALL LETTER ALIF..ADLAM SMALL LETTER SHA -# Total code points: 2569 +# Total code points: 2595 # ================================================ @@ -2750,7 +2777,10 @@ A7C2 ; Uppercase # L& LATIN CAPITAL LETTER ANGLICANA W A7C4..A7C7 ; Uppercase # L& [4] LATIN CAPITAL LETTER C WITH PALATAL HOOK..LATIN CAPITAL LETTER D WITH SHORT STROKE OVERLAY A7C9 ; Uppercase # L& LATIN CAPITAL LETTER S WITH SHORT STROKE OVERLAY A7CB..A7CC ; Uppercase # L& [2] LATIN CAPITAL LETTER RAMS HORN..LATIN CAPITAL LETTER S WITH DIAGONAL STROKE +A7CE ; Uppercase # L& LATIN CAPITAL LETTER PHARYNGEAL VOICED FRICATIVE A7D0 ; Uppercase # L& LATIN CAPITAL LETTER CLOSED INSULAR G +A7D2 ; Uppercase # L& LATIN CAPITAL LETTER DOUBLE THORN +A7D4 ; Uppercase # L& LATIN CAPITAL LETTER DOUBLE WYNN A7D6 ; Uppercase # L& LATIN CAPITAL LETTER MIDDLE SCOTS S A7D8 ; Uppercase # L& LATIN CAPITAL LETTER SIGMOID S A7DA ; Uppercase # L& LATIN CAPITAL LETTER LAMBDA @@ -2767,6 +2797,7 @@ FF21..FF3A ; Uppercase # L& [26] FULLWIDTH LATIN CAPITAL LETTER A..FULLWIDTH 10D50..10D65 ; Uppercase # L& [22] GARAY CAPITAL LETTER A..GARAY CAPITAL LETTER OLD NA 118A0..118BF ; Uppercase # L& [32] WARANG CITI CAPITAL LETTER NGAA..WARANG CITI CAPITAL LETTER VIYO 16E40..16E5F ; Uppercase # L& [32] MEDEFAIDRIN CAPITAL LETTER M..MEDEFAIDRIN CAPITAL LETTER Y +16EA0..16EB8 ; Uppercase # L& [25] BERIA ERFE CAPITAL LETTER ARKAB..BERIA ERFE CAPITAL LETTER AY 1D400..1D419 ; Uppercase # L& [26] MATHEMATICAL BOLD CAPITAL A..MATHEMATICAL BOLD CAPITAL Z 1D434..1D44D ; Uppercase # L& [26] MATHEMATICAL ITALIC CAPITAL A..MATHEMATICAL ITALIC CAPITAL Z 1D468..1D481 ; Uppercase # L& [26] MATHEMATICAL BOLD ITALIC CAPITAL A..MATHEMATICAL BOLD ITALIC CAPITAL Z @@ -2803,7 +2834,7 @@ FF21..FF3A ; Uppercase # L& [26] FULLWIDTH LATIN CAPITAL LETTER A..FULLWIDTH 1F150..1F169 ; Uppercase # So [26] NEGATIVE CIRCLED LATIN CAPITAL LETTER A..NEGATIVE CIRCLED LATIN CAPITAL LETTER Z 1F170..1F189 ; Uppercase # So [26] NEGATIVE SQUARED LATIN CAPITAL LETTER A..NEGATIVE SQUARED LATIN CAPITAL LETTER Z -# Total code points: 1978 +# Total code points: 2006 # ================================================ @@ -2821,7 +2852,7 @@ FF21..FF3A ; Uppercase # L& [26] FULLWIDTH LATIN CAPITAL LETTER A..FULLWIDTH 00F8..01BA ; Cased # L& [195] LATIN SMALL LETTER O WITH STROKE..LATIN SMALL LETTER EZH WITH TAIL 01BC..01BF ; Cased # L& [4] LATIN CAPITAL LETTER TONE FIVE..LATIN LETTER WYNN 01C4..0293 ; Cased # L& [208] LATIN CAPITAL LETTER DZ WITH CARON..LATIN SMALL LETTER EZH WITH CURL -0295..02AF ; Cased # L& [27] LATIN LETTER PHARYNGEAL VOICED FRICATIVE..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL +0296..02AF ; Cased # L& [26] LATIN LETTER INVERTED GLOTTAL STOP..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL 02B0..02B8 ; Cased # Lm [9] MODIFIER LETTER SMALL H..MODIFIER LETTER SMALL Y 02C0..02C1 ; Cased # Lm [2] MODIFIER LETTER GLOTTAL STOP..MODIFIER LETTER REVERSED GLOTTAL STOP 02E0..02E4 ; Cased # Lm [5] MODIFIER LETTER SMALL GAMMA..MODIFIER LETTER SMALL REVERSED GLOTTAL STOP @@ -2911,11 +2942,8 @@ A722..A76F ; Cased # L& [78] LATIN CAPITAL LETTER EGYPTOLOGICAL ALEF..LATIN A770 ; Cased # Lm MODIFIER LETTER US A771..A787 ; Cased # L& [23] LATIN SMALL LETTER DUM..LATIN SMALL LETTER INSULAR T A78B..A78E ; Cased # L& [4] LATIN CAPITAL LETTER SALTILLO..LATIN SMALL LETTER L WITH RETROFLEX HOOK AND BELT -A790..A7CD ; Cased # L& [62] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN SMALL LETTER S WITH DIAGONAL STROKE -A7D0..A7D1 ; Cased # L& [2] LATIN CAPITAL LETTER CLOSED INSULAR G..LATIN SMALL LETTER CLOSED INSULAR G -A7D3 ; Cased # L& LATIN SMALL LETTER DOUBLE THORN -A7D5..A7DC ; Cased # L& [8] LATIN SMALL LETTER DOUBLE WYNN..LATIN CAPITAL LETTER LAMBDA WITH STROKE -A7F2..A7F4 ; Cased # Lm [3] MODIFIER LETTER CAPITAL C..MODIFIER LETTER CAPITAL Q +A790..A7DC ; Cased # L& [77] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN CAPITAL LETTER LAMBDA WITH STROKE +A7F1..A7F4 ; Cased # Lm [4] MODIFIER LETTER CAPITAL S..MODIFIER LETTER CAPITAL Q A7F5..A7F6 ; Cased # L& [2] LATIN CAPITAL LETTER REVERSED HALF H..LATIN SMALL LETTER REVERSED HALF H A7F8..A7F9 ; Cased # Lm [2] MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE A7FA ; Cased # L& LATIN LETTER SMALL CAPITAL TURNED M @@ -2949,6 +2977,8 @@ FF41..FF5A ; Cased # L& [26] FULLWIDTH LATIN SMALL LETTER A..FULLWIDTH LATIN 10D70..10D85 ; Cased # L& [22] GARAY SMALL LETTER A..GARAY SMALL LETTER OLD NA 118A0..118DF ; Cased # L& [64] WARANG CITI CAPITAL LETTER NGAA..WARANG CITI SMALL LETTER VIYO 16E40..16E7F ; Cased # L& [64] MEDEFAIDRIN CAPITAL LETTER M..MEDEFAIDRIN SMALL LETTER Y +16EA0..16EB8 ; Cased # L& [25] BERIA ERFE CAPITAL LETTER ARKAB..BERIA ERFE CAPITAL LETTER AY +16EBB..16ED3 ; Cased # L& [25] BERIA ERFE SMALL LETTER ARKAB..BERIA ERFE SMALL LETTER AY 1D400..1D454 ; Cased # L& [85] MATHEMATICAL BOLD CAPITAL A..MATHEMATICAL ITALIC SMALL G 1D456..1D49C ; Cased # L& [71] MATHEMATICAL ITALIC SMALL I..MATHEMATICAL SCRIPT CAPITAL A 1D49E..1D49F ; Cased # L& [2] MATHEMATICAL SCRIPT CAPITAL C..MATHEMATICAL SCRIPT CAPITAL D @@ -2988,7 +3018,7 @@ FF41..FF5A ; Cased # L& [26] FULLWIDTH LATIN SMALL LETTER A..FULLWIDTH LATIN 1F150..1F169 ; Cased # So [26] NEGATIVE CIRCLED LATIN CAPITAL LETTER A..NEGATIVE CIRCLED LATIN CAPITAL LETTER Z 1F170..1F189 ; Cased # So [26] NEGATIVE SQUARED LATIN CAPITAL LETTER A..NEGATIVE SQUARED LATIN CAPITAL LETTER Z -# Total code points: 4578 +# Total code points: 4632 # ================================================ @@ -3194,7 +3224,8 @@ FF41..FF5A ; Cased # L& [26] FULLWIDTH LATIN SMALL LETTER A..FULLWIDTH LATIN 1AA7 ; Case_Ignorable # Lm TAI THAM SIGN MAI YAMOK 1AB0..1ABD ; Case_Ignorable # Mn [14] COMBINING DOUBLED CIRCUMFLEX ACCENT..COMBINING PARENTHESES BELOW 1ABE ; Case_Ignorable # Me COMBINING PARENTHESES OVERLAY -1ABF..1ACE ; Case_Ignorable # Mn [16] COMBINING LATIN SMALL LETTER W BELOW..COMBINING LATIN SMALL LETTER INSULAR T +1ABF..1ADD ; Case_Ignorable # Mn [31] COMBINING LATIN SMALL LETTER W BELOW..COMBINING DOT-AND-RING BELOW +1AE0..1AEB ; Case_Ignorable # Mn [12] COMBINING LEFT TACK ABOVE..COMBINING DOUBLE RIGHTWARDS ARROW ABOVE 1B00..1B03 ; Case_Ignorable # Mn [4] BALINESE SIGN ULU RICEM..BALINESE SIGN SURANG 1B34 ; Case_Ignorable # Mn BALINESE SIGN REREKAN 1B36..1B3A ; Case_Ignorable # Mn [5] BALINESE VOWEL SIGN ULU..BALINESE VOWEL SIGN RA REPA @@ -3274,7 +3305,7 @@ A720..A721 ; Case_Ignorable # Sk [2] MODIFIER LETTER STRESS AND HIGH TONE.. A770 ; Case_Ignorable # Lm MODIFIER LETTER US A788 ; Case_Ignorable # Lm MODIFIER LETTER LOW CIRCUMFLEX ACCENT A789..A78A ; Case_Ignorable # Sk [2] MODIFIER LETTER COLON..MODIFIER LETTER SHORT EQUALS SIGN -A7F2..A7F4 ; Case_Ignorable # Lm [3] MODIFIER LETTER CAPITAL C..MODIFIER LETTER CAPITAL Q +A7F1..A7F4 ; Case_Ignorable # Lm [4] MODIFIER LETTER CAPITAL S..MODIFIER LETTER CAPITAL Q A7F8..A7F9 ; Case_Ignorable # Lm [2] MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE A802 ; Case_Ignorable # Mn SYLOTI NAGRI SIGN DVISVARA A806 ; Case_Ignorable # Mn SYLOTI NAGRI SIGN HASANTA @@ -3350,7 +3381,8 @@ FFF9..FFFB ; Case_Ignorable # Cf [3] INTERLINEAR ANNOTATION ANCHOR..INTERLI 10D69..10D6D ; Case_Ignorable # Mn [5] GARAY VOWEL SIGN E..GARAY CONSONANT NASALIZATION MARK 10D6F ; Case_Ignorable # Lm GARAY REDUPLICATION MARK 10EAB..10EAC ; Case_Ignorable # Mn [2] YEZIDI COMBINING HAMZA MARK..YEZIDI COMBINING MADDA MARK -10EFC..10EFF ; Case_Ignorable # Mn [4] ARABIC COMBINING ALEF OVERLAY..ARABIC SMALL LOW WORD MADDA +10EC5 ; Case_Ignorable # Lm ARABIC SMALL YEH BARREE WITH TWO DOTS BELOW +10EFA..10EFF ; Case_Ignorable # Mn [6] ARABIC DOUBLE VERTICAL BAR BELOW..ARABIC SMALL LOW WORD MADDA 10F46..10F50 ; Case_Ignorable # Mn [11] SOGDIAN COMBINING DOT BELOW..SOGDIAN COMBINING STROKE BELOW 10F82..10F85 ; Case_Ignorable # Mn [4] OLD UYGHUR COMBINING DOT ABOVE..OLD UYGHUR COMBINING TWO DOTS BELOW 11001 ; Case_Ignorable # Mn BRAHMI SIGN ANUSVARA @@ -3427,6 +3459,9 @@ FFF9..FFFB ; Case_Ignorable # Cf [3] INTERLINEAR ANNOTATION ANCHOR..INTERLI 11A59..11A5B ; Case_Ignorable # Mn [3] SOYOMBO VOWEL SIGN VOCALIC R..SOYOMBO VOWEL LENGTH MARK 11A8A..11A96 ; Case_Ignorable # Mn [13] SOYOMBO FINAL CONSONANT SIGN G..SOYOMBO SIGN ANUSVARA 11A98..11A99 ; Case_Ignorable # Mn [2] SOYOMBO GEMINATION MARK..SOYOMBO SUBJOINER +11B60 ; Case_Ignorable # Mn SHARADA VOWEL SIGN OE +11B62..11B64 ; Case_Ignorable # Mn [3] SHARADA VOWEL SIGN UE..SHARADA VOWEL SIGN SHORT E +11B66 ; Case_Ignorable # Mn SHARADA VOWEL SIGN CANDRA E 11C30..11C36 ; Case_Ignorable # Mn [7] BHAIKSUKI VOWEL SIGN I..BHAIKSUKI VOWEL SIGN VOCALIC L 11C38..11C3D ; Case_Ignorable # Mn [6] BHAIKSUKI VOWEL SIGN E..BHAIKSUKI SIGN ANUSVARA 11C3F ; Case_Ignorable # Mn BHAIKSUKI SIGN VIRAMA @@ -3442,6 +3477,7 @@ FFF9..FFFB ; Case_Ignorable # Cf [3] INTERLINEAR ANNOTATION ANCHOR..INTERLI 11D90..11D91 ; Case_Ignorable # Mn [2] GUNJALA GONDI VOWEL SIGN EE..GUNJALA GONDI VOWEL SIGN AI 11D95 ; Case_Ignorable # Mn GUNJALA GONDI SIGN ANUSVARA 11D97 ; Case_Ignorable # Mn GUNJALA GONDI VIRAMA +11DD9 ; Case_Ignorable # Lm TOLONG SIKI SIGN SELA 11EF3..11EF4 ; Case_Ignorable # Mn [2] MAKASAR VOWEL SIGN I..MAKASAR VOWEL SIGN U 11F00..11F01 ; Case_Ignorable # Mn [2] KAWI SIGN CANDRABINDU..KAWI SIGN ANUSVARA 11F36..11F3A ; Case_Ignorable # Mn [5] KAWI VOWEL SIGN I..KAWI VOWEL SIGN VOCALIC R @@ -3464,6 +3500,7 @@ FFF9..FFFB ; Case_Ignorable # Cf [3] INTERLINEAR ANNOTATION ANCHOR..INTERLI 16FE0..16FE1 ; Case_Ignorable # Lm [2] TANGUT ITERATION MARK..NUSHU ITERATION MARK 16FE3 ; Case_Ignorable # Lm OLD CHINESE ITERATION MARK 16FE4 ; Case_Ignorable # Mn KHITAN SMALL SCRIPT FILLER +16FF2..16FF3 ; Case_Ignorable # Lm [2] CHINESE SMALL SIMPLIFIED ER..CHINESE SMALL TRADITIONAL ER 1AFF0..1AFF3 ; Case_Ignorable # Lm [4] KATAKANA LETTER MINNAN TONE-2..KATAKANA LETTER MINNAN TONE-5 1AFF5..1AFFB ; Case_Ignorable # Lm [7] KATAKANA LETTER MINNAN TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-5 1AFFD..1AFFE ; Case_Ignorable # Lm [2] KATAKANA LETTER MINNAN NASALIZED TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-8 @@ -3497,6 +3534,11 @@ FFF9..FFFB ; Case_Ignorable # Cf [3] INTERLINEAR ANNOTATION ANCHOR..INTERLI 1E4EB ; Case_Ignorable # Lm NAG MUNDARI SIGN OJOD 1E4EC..1E4EF ; Case_Ignorable # Mn [4] NAG MUNDARI SIGN MUHOR..NAG MUNDARI SIGN SUTUH 1E5EE..1E5EF ; Case_Ignorable # Mn [2] OL ONAL SIGN MU..OL ONAL SIGN IKIR +1E6E3 ; Case_Ignorable # Mn TAI YO SIGN UE +1E6E6 ; Case_Ignorable # Mn TAI YO SIGN AU +1E6EE..1E6EF ; Case_Ignorable # Mn [2] TAI YO SIGN AY..TAI YO SIGN ANG +1E6F5 ; Case_Ignorable # Mn TAI YO SIGN OM +1E6FF ; Case_Ignorable # Lm TAI YO XAM LAI 1E8D0..1E8D6 ; Case_Ignorable # Mn [7] MENDE KIKAKUI COMBINING NUMBER TEENS..MENDE KIKAKUI COMBINING NUMBER MILLIONS 1E944..1E94A ; Case_Ignorable # Mn [7] ADLAM ALIF LENGTHENER..ADLAM NUKTA 1E94B ; Case_Ignorable # Lm ADLAM NASALIZATION MARK @@ -3505,13 +3547,14 @@ E0001 ; Case_Ignorable # Cf LANGUAGE TAG E0020..E007F ; Case_Ignorable # Cf [96] TAG SPACE..CANCEL TAG E0100..E01EF ; Case_Ignorable # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR-256 -# Total code points: 2749 +# Total code points: 2794 # ================================================ # Derived Property: Changes_When_Lowercased (CWL) # Characters whose normalized forms are not stable under a toLowercase mapping. -# For more information, see D139 in Section 3.13, "Default Case Algorithms". +# For more information, see the definition of "isLowercase(X)" +# in the "Conformance" / "Default Case Algorithms" section of the core specification. # Changes_When_Lowercased(X) is true when toLowercase(toNFD(X)) != toNFD(X) 0041..005A ; Changes_When_Lowercased # L& [26] LATIN CAPITAL LETTER A..LATIN CAPITAL LETTER Z @@ -4110,7 +4153,10 @@ A7C2 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER ANGLICAN A7C4..A7C7 ; Changes_When_Lowercased # L& [4] LATIN CAPITAL LETTER C WITH PALATAL HOOK..LATIN CAPITAL LETTER D WITH SHORT STROKE OVERLAY A7C9 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER S WITH SHORT STROKE OVERLAY A7CB..A7CC ; Changes_When_Lowercased # L& [2] LATIN CAPITAL LETTER RAMS HORN..LATIN CAPITAL LETTER S WITH DIAGONAL STROKE +A7CE ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER PHARYNGEAL VOICED FRICATIVE A7D0 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER CLOSED INSULAR G +A7D2 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER DOUBLE THORN +A7D4 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER DOUBLE WYNN A7D6 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER MIDDLE SCOTS S A7D8 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER SIGMOID S A7DA ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER LAMBDA @@ -4127,15 +4173,17 @@ FF21..FF3A ; Changes_When_Lowercased # L& [26] FULLWIDTH LATIN CAPITAL LETTE 10D50..10D65 ; Changes_When_Lowercased # L& [22] GARAY CAPITAL LETTER A..GARAY CAPITAL LETTER OLD NA 118A0..118BF ; Changes_When_Lowercased # L& [32] WARANG CITI CAPITAL LETTER NGAA..WARANG CITI CAPITAL LETTER VIYO 16E40..16E5F ; Changes_When_Lowercased # L& [32] MEDEFAIDRIN CAPITAL LETTER M..MEDEFAIDRIN CAPITAL LETTER Y +16EA0..16EB8 ; Changes_When_Lowercased # L& [25] BERIA ERFE CAPITAL LETTER ARKAB..BERIA ERFE CAPITAL LETTER AY 1E900..1E921 ; Changes_When_Lowercased # L& [34] ADLAM CAPITAL LETTER ALIF..ADLAM CAPITAL LETTER SHA -# Total code points: 1460 +# Total code points: 1488 # ================================================ # Derived Property: Changes_When_Uppercased (CWU) # Characters whose normalized forms are not stable under a toUppercase mapping. -# For more information, see D140 in Section 3.13, "Default Case Algorithms". +# For more information, see the definition of "isUppercase(X)" +# in the "Conformance" / "Default Case Algorithms" section of the core specification. # Changes_When_Uppercased(X) is true when toUppercase(toNFD(X)) != toNFD(X) 0061..007A ; Changes_When_Uppercased # L& [26] LATIN SMALL LETTER A..LATIN SMALL LETTER Z @@ -4747,7 +4795,10 @@ A7C3 ; Changes_When_Uppercased # L& LATIN SMALL LETTER ANGLICANA A7C8 ; Changes_When_Uppercased # L& LATIN SMALL LETTER D WITH SHORT STROKE OVERLAY A7CA ; Changes_When_Uppercased # L& LATIN SMALL LETTER S WITH SHORT STROKE OVERLAY A7CD ; Changes_When_Uppercased # L& LATIN SMALL LETTER S WITH DIAGONAL STROKE +A7CF ; Changes_When_Uppercased # L& LATIN SMALL LETTER PHARYNGEAL VOICED FRICATIVE A7D1 ; Changes_When_Uppercased # L& LATIN SMALL LETTER CLOSED INSULAR G +A7D3 ; Changes_When_Uppercased # L& LATIN SMALL LETTER DOUBLE THORN +A7D5 ; Changes_When_Uppercased # L& LATIN SMALL LETTER DOUBLE WYNN A7D7 ; Changes_When_Uppercased # L& LATIN SMALL LETTER MIDDLE SCOTS S A7D9 ; Changes_When_Uppercased # L& LATIN SMALL LETTER SIGMOID S A7DB ; Changes_When_Uppercased # L& LATIN SMALL LETTER LAMBDA @@ -4767,15 +4818,17 @@ FF41..FF5A ; Changes_When_Uppercased # L& [26] FULLWIDTH LATIN SMALL LETTER 10D70..10D85 ; Changes_When_Uppercased # L& [22] GARAY SMALL LETTER A..GARAY SMALL LETTER OLD NA 118C0..118DF ; Changes_When_Uppercased # L& [32] WARANG CITI SMALL LETTER NGAA..WARANG CITI SMALL LETTER VIYO 16E60..16E7F ; Changes_When_Uppercased # L& [32] MEDEFAIDRIN SMALL LETTER M..MEDEFAIDRIN SMALL LETTER Y +16EBB..16ED3 ; Changes_When_Uppercased # L& [25] BERIA ERFE SMALL LETTER ARKAB..BERIA ERFE SMALL LETTER AY 1E922..1E943 ; Changes_When_Uppercased # L& [34] ADLAM SMALL LETTER ALIF..ADLAM SMALL LETTER SHA -# Total code points: 1552 +# Total code points: 1580 # ================================================ # Derived Property: Changes_When_Titlecased (CWT) # Characters whose normalized forms are not stable under a toTitlecase mapping. -# For more information, see D141 in Section 3.13, "Default Case Algorithms". +# For more information, see the definition of "isTitlecase(X)" +# in the "Conformance" / "Default Case Algorithms" section of the core specification. # Changes_When_Titlecased(X) is true when toTitlecase(toNFD(X)) != toNFD(X) 0061..007A ; Changes_When_Titlecased # L& [26] LATIN SMALL LETTER A..LATIN SMALL LETTER Z @@ -5386,7 +5439,10 @@ A7C3 ; Changes_When_Titlecased # L& LATIN SMALL LETTER ANGLICANA A7C8 ; Changes_When_Titlecased # L& LATIN SMALL LETTER D WITH SHORT STROKE OVERLAY A7CA ; Changes_When_Titlecased # L& LATIN SMALL LETTER S WITH SHORT STROKE OVERLAY A7CD ; Changes_When_Titlecased # L& LATIN SMALL LETTER S WITH DIAGONAL STROKE +A7CF ; Changes_When_Titlecased # L& LATIN SMALL LETTER PHARYNGEAL VOICED FRICATIVE A7D1 ; Changes_When_Titlecased # L& LATIN SMALL LETTER CLOSED INSULAR G +A7D3 ; Changes_When_Titlecased # L& LATIN SMALL LETTER DOUBLE THORN +A7D5 ; Changes_When_Titlecased # L& LATIN SMALL LETTER DOUBLE WYNN A7D7 ; Changes_When_Titlecased # L& LATIN SMALL LETTER MIDDLE SCOTS S A7D9 ; Changes_When_Titlecased # L& LATIN SMALL LETTER SIGMOID S A7DB ; Changes_When_Titlecased # L& LATIN SMALL LETTER LAMBDA @@ -5406,15 +5462,17 @@ FF41..FF5A ; Changes_When_Titlecased # L& [26] FULLWIDTH LATIN SMALL LETTER 10D70..10D85 ; Changes_When_Titlecased # L& [22] GARAY SMALL LETTER A..GARAY SMALL LETTER OLD NA 118C0..118DF ; Changes_When_Titlecased # L& [32] WARANG CITI SMALL LETTER NGAA..WARANG CITI SMALL LETTER VIYO 16E60..16E7F ; Changes_When_Titlecased # L& [32] MEDEFAIDRIN SMALL LETTER M..MEDEFAIDRIN SMALL LETTER Y +16EBB..16ED3 ; Changes_When_Titlecased # L& [25] BERIA ERFE SMALL LETTER ARKAB..BERIA ERFE SMALL LETTER AY 1E922..1E943 ; Changes_When_Titlecased # L& [34] ADLAM SMALL LETTER ALIF..ADLAM SMALL LETTER SHA -# Total code points: 1479 +# Total code points: 1507 # ================================================ # Derived Property: Changes_When_Casefolded (CWCF) # Characters whose normalized forms are not stable under case folding. -# For more information, see D142 in Section 3.13, "Default Case Algorithms". +# For more information, see the definition of "isCasefolded(X)" +# in the "Conformance" / "Default Case Algorithms" section of the core specification. # Changes_When_Casefolded(X) is true when toCasefold(toNFD(X)) != toNFD(X) 0041..005A ; Changes_When_Casefolded # L& [26] LATIN CAPITAL LETTER A..LATIN CAPITAL LETTER Z @@ -6022,7 +6080,10 @@ A7C2 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER ANGLICAN A7C4..A7C7 ; Changes_When_Casefolded # L& [4] LATIN CAPITAL LETTER C WITH PALATAL HOOK..LATIN CAPITAL LETTER D WITH SHORT STROKE OVERLAY A7C9 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER S WITH SHORT STROKE OVERLAY A7CB..A7CC ; Changes_When_Casefolded # L& [2] LATIN CAPITAL LETTER RAMS HORN..LATIN CAPITAL LETTER S WITH DIAGONAL STROKE +A7CE ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER PHARYNGEAL VOICED FRICATIVE A7D0 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER CLOSED INSULAR G +A7D2 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER DOUBLE THORN +A7D4 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER DOUBLE WYNN A7D6 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER MIDDLE SCOTS S A7D8 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER SIGMOID S A7DA ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER LAMBDA @@ -6042,15 +6103,17 @@ FF21..FF3A ; Changes_When_Casefolded # L& [26] FULLWIDTH LATIN CAPITAL LETTE 10D50..10D65 ; Changes_When_Casefolded # L& [22] GARAY CAPITAL LETTER A..GARAY CAPITAL LETTER OLD NA 118A0..118BF ; Changes_When_Casefolded # L& [32] WARANG CITI CAPITAL LETTER NGAA..WARANG CITI CAPITAL LETTER VIYO 16E40..16E5F ; Changes_When_Casefolded # L& [32] MEDEFAIDRIN CAPITAL LETTER M..MEDEFAIDRIN CAPITAL LETTER Y +16EA0..16EB8 ; Changes_When_Casefolded # L& [25] BERIA ERFE CAPITAL LETTER ARKAB..BERIA ERFE CAPITAL LETTER AY 1E900..1E921 ; Changes_When_Casefolded # L& [34] ADLAM CAPITAL LETTER ALIF..ADLAM CAPITAL LETTER SHA -# Total code points: 1533 +# Total code points: 1561 # ================================================ # Derived Property: Changes_When_Casemapped (CWCM) # Characters whose normalized forms are not stable under case mapping. -# For more information, see D143 in Section 3.13, "Default Case Algorithms". +# For more information, see the definition of "isCased(X)" +# in the "Conformance" / "Default Case Algorithms" section of the core specification. # Changes_When_Casemapped(X) is true when CWL(X), or CWT(X), or CWU(X) 0041..005A ; Changes_When_Casemapped # L& [26] LATIN CAPITAL LETTER A..LATIN CAPITAL LETTER Z @@ -6156,9 +6219,7 @@ A779..A787 ; Changes_When_Casemapped # L& [15] LATIN CAPITAL LETTER INSULAR A78B..A78D ; Changes_When_Casemapped # L& [3] LATIN CAPITAL LETTER SALTILLO..LATIN CAPITAL LETTER TURNED H A790..A794 ; Changes_When_Casemapped # L& [5] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN SMALL LETTER C WITH PALATAL HOOK A796..A7AE ; Changes_When_Casemapped # L& [25] LATIN CAPITAL LETTER B WITH FLOURISH..LATIN CAPITAL LETTER SMALL CAPITAL I -A7B0..A7CD ; Changes_When_Casemapped # L& [30] LATIN CAPITAL LETTER TURNED K..LATIN SMALL LETTER S WITH DIAGONAL STROKE -A7D0..A7D1 ; Changes_When_Casemapped # L& [2] LATIN CAPITAL LETTER CLOSED INSULAR G..LATIN SMALL LETTER CLOSED INSULAR G -A7D6..A7DC ; Changes_When_Casemapped # L& [7] LATIN CAPITAL LETTER MIDDLE SCOTS S..LATIN CAPITAL LETTER LAMBDA WITH STROKE +A7B0..A7DC ; Changes_When_Casemapped # L& [45] LATIN CAPITAL LETTER TURNED K..LATIN CAPITAL LETTER LAMBDA WITH STROKE A7F5..A7F6 ; Changes_When_Casemapped # L& [2] LATIN CAPITAL LETTER REVERSED HALF H..LATIN SMALL LETTER REVERSED HALF H AB53 ; Changes_When_Casemapped # L& LATIN SMALL LETTER CHI AB70..ABBF ; Changes_When_Casemapped # L& [80] CHEROKEE SMALL LETTER A..CHEROKEE SMALL LETTER YA @@ -6183,9 +6244,11 @@ FF41..FF5A ; Changes_When_Casemapped # L& [26] FULLWIDTH LATIN SMALL LETTER 10D70..10D85 ; Changes_When_Casemapped # L& [22] GARAY SMALL LETTER A..GARAY SMALL LETTER OLD NA 118A0..118DF ; Changes_When_Casemapped # L& [64] WARANG CITI CAPITAL LETTER NGAA..WARANG CITI SMALL LETTER VIYO 16E40..16E7F ; Changes_When_Casemapped # L& [64] MEDEFAIDRIN CAPITAL LETTER M..MEDEFAIDRIN SMALL LETTER Y +16EA0..16EB8 ; Changes_When_Casemapped # L& [25] BERIA ERFE CAPITAL LETTER ARKAB..BERIA ERFE CAPITAL LETTER AY +16EBB..16ED3 ; Changes_When_Casemapped # L& [25] BERIA ERFE SMALL LETTER ARKAB..BERIA ERFE SMALL LETTER AY 1E900..1E943 ; Changes_When_Casemapped # L& [68] ADLAM CAPITAL LETTER ALIF..ADLAM SMALL LETTER SHA -# Total code points: 2981 +# Total code points: 3037 # ================================================ @@ -6210,8 +6273,8 @@ FF41..FF5A ; Changes_When_Casemapped # L& [26] FULLWIDTH LATIN SMALL LETTER 01BC..01BF ; ID_Start # L& [4] LATIN CAPITAL LETTER TONE FIVE..LATIN LETTER WYNN 01C0..01C3 ; ID_Start # Lo [4] LATIN LETTER DENTAL CLICK..LATIN LETTER RETROFLEX CLICK 01C4..0293 ; ID_Start # L& [208] LATIN CAPITAL LETTER DZ WITH CARON..LATIN SMALL LETTER EZH WITH CURL -0294 ; ID_Start # Lo LATIN LETTER GLOTTAL STOP -0295..02AF ; ID_Start # L& [27] LATIN LETTER PHARYNGEAL VOICED FRICATIVE..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL +0294..0295 ; ID_Start # Lo [2] LATIN LETTER GLOTTAL STOP..LATIN LETTER PHARYNGEAL VOICED FRICATIVE +0296..02AF ; ID_Start # L& [26] LATIN LETTER INVERTED GLOTTAL STOP..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL 02B0..02C1 ; ID_Start # Lm [18] MODIFIER LETTER SMALL H..MODIFIER LETTER REVERSED GLOTTAL STOP 02C6..02D1 ; ID_Start # Lm [12] MODIFIER LETTER CIRCUMFLEX ACCENT..MODIFIER LETTER HALF TRIANGULAR COLON 02E0..02E4 ; ID_Start # Lm [5] MODIFIER LETTER SMALL GAMMA..MODIFIER LETTER SMALL REVERSED GLOTTAL STOP @@ -6259,7 +6322,7 @@ FF41..FF5A ; Changes_When_Casemapped # L& [26] FULLWIDTH LATIN SMALL LETTER 0840..0858 ; ID_Start # Lo [25] MANDAIC LETTER HALQA..MANDAIC LETTER AIN 0860..086A ; ID_Start # Lo [11] SYRIAC LETTER MALAYALAM NGA..SYRIAC LETTER MALAYALAM SSA 0870..0887 ; ID_Start # Lo [24] ARABIC LETTER ALEF WITH ATTACHED FATHA..ARABIC BASELINE ROUND DOT -0889..088E ; ID_Start # Lo [6] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC VERTICAL TAIL +0889..088F ; ID_Start # Lo [7] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC LETTER NOON WITH RING ABOVE 08A0..08C8 ; ID_Start # Lo [41] ARABIC LETTER BEH WITH SMALL V BELOW..ARABIC LETTER GRAF 08C9 ; ID_Start # Lm ARABIC SMALL FARSI YEH 0904..0939 ; ID_Start # Lo [54] DEVANAGARI LETTER SHORT A..DEVANAGARI LETTER HA @@ -6327,7 +6390,7 @@ FF41..FF5A ; Changes_When_Casemapped # L& [26] FULLWIDTH LATIN SMALL LETTER 0C2A..0C39 ; ID_Start # Lo [16] TELUGU LETTER PA..TELUGU LETTER HA 0C3D ; ID_Start # Lo TELUGU SIGN AVAGRAHA 0C58..0C5A ; ID_Start # Lo [3] TELUGU LETTER TSA..TELUGU LETTER RRRA -0C5D ; ID_Start # Lo TELUGU LETTER NAKAARA POLLU +0C5C..0C5D ; ID_Start # Lo [2] TELUGU ARCHAIC SHRII..TELUGU LETTER NAKAARA POLLU 0C60..0C61 ; ID_Start # Lo [2] TELUGU LETTER VOCALIC RR..TELUGU LETTER VOCALIC LL 0C80 ; ID_Start # Lo KANNADA SIGN SPACING CANDRABINDU 0C85..0C8C ; ID_Start # Lo [8] KANNADA LETTER A..KANNADA LETTER VOCALIC L @@ -6336,7 +6399,7 @@ FF41..FF5A ; Changes_When_Casemapped # L& [26] FULLWIDTH LATIN SMALL LETTER 0CAA..0CB3 ; ID_Start # Lo [10] KANNADA LETTER PA..KANNADA LETTER LLA 0CB5..0CB9 ; ID_Start # Lo [5] KANNADA LETTER VA..KANNADA LETTER HA 0CBD ; ID_Start # Lo KANNADA SIGN AVAGRAHA -0CDD..0CDE ; ID_Start # Lo [2] KANNADA LETTER NAKAARA POLLU..KANNADA LETTER FA +0CDC..0CDE ; ID_Start # Lo [3] KANNADA ARCHAIC SHRII..KANNADA LETTER FA 0CE0..0CE1 ; ID_Start # Lo [2] KANNADA LETTER VOCALIC RR..KANNADA LETTER VOCALIC LL 0CF1..0CF2 ; ID_Start # Lo [2] KANNADA SIGN JIHVAMULIYA..KANNADA SIGN UPADHMANIYA 0D04..0D0C ; ID_Start # Lo [9] MALAYALAM LETTER VEDIC ANUSVARA..MALAYALAM LETTER VOCALIC L @@ -6561,11 +6624,8 @@ A771..A787 ; ID_Start # L& [23] LATIN SMALL LETTER DUM..LATIN SMALL LETTER I A788 ; ID_Start # Lm MODIFIER LETTER LOW CIRCUMFLEX ACCENT A78B..A78E ; ID_Start # L& [4] LATIN CAPITAL LETTER SALTILLO..LATIN SMALL LETTER L WITH RETROFLEX HOOK AND BELT A78F ; ID_Start # Lo LATIN LETTER SINOLOGICAL DOT -A790..A7CD ; ID_Start # L& [62] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN SMALL LETTER S WITH DIAGONAL STROKE -A7D0..A7D1 ; ID_Start # L& [2] LATIN CAPITAL LETTER CLOSED INSULAR G..LATIN SMALL LETTER CLOSED INSULAR G -A7D3 ; ID_Start # L& LATIN SMALL LETTER DOUBLE THORN -A7D5..A7DC ; ID_Start # L& [8] LATIN SMALL LETTER DOUBLE WYNN..LATIN CAPITAL LETTER LAMBDA WITH STROKE -A7F2..A7F4 ; ID_Start # Lm [3] MODIFIER LETTER CAPITAL C..MODIFIER LETTER CAPITAL Q +A790..A7DC ; ID_Start # L& [77] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN CAPITAL LETTER LAMBDA WITH STROKE +A7F1..A7F4 ; ID_Start # Lm [4] MODIFIER LETTER CAPITAL S..MODIFIER LETTER CAPITAL Q A7F5..A7F6 ; ID_Start # L& [2] LATIN CAPITAL LETTER REVERSED HALF H..LATIN SMALL LETTER REVERSED HALF H A7F7 ; ID_Start # Lo LATIN EPIGRAPHIC LETTER SIDEWAYS I A7F8..A7F9 ; ID_Start # Lm [2] MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE @@ -6702,6 +6762,7 @@ FFDA..FFDC ; ID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL 108F4..108F5 ; ID_Start # Lo [2] HATRAN LETTER SHIN..HATRAN LETTER TAW 10900..10915 ; ID_Start # Lo [22] PHOENICIAN LETTER ALF..PHOENICIAN LETTER TAU 10920..10939 ; ID_Start # Lo [26] LYDIAN LETTER A..LYDIAN LETTER C +10940..10959 ; ID_Start # Lo [26] SIDETIC LETTER N01..SIDETIC LETTER N26 10980..109B7 ; ID_Start # Lo [56] MEROITIC HIEROGLYPHIC LETTER A..MEROITIC CURSIVE LETTER DA 109BE..109BF ; ID_Start # Lo [2] MEROITIC CURSIVE LOGOGRAM RMT..MEROITIC CURSIVE LOGOGRAM IMN 10A00 ; ID_Start # Lo KHAROSHTHI LETTER A @@ -6729,6 +6790,8 @@ FFDA..FFDC ; ID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL 10E80..10EA9 ; ID_Start # Lo [42] YEZIDI LETTER ELIF..YEZIDI LETTER ET 10EB0..10EB1 ; ID_Start # Lo [2] YEZIDI LETTER LAM WITH DOT ABOVE..YEZIDI LETTER YOT WITH CIRCUMFLEX ABOVE 10EC2..10EC4 ; ID_Start # Lo [3] ARABIC LETTER DAL WITH TWO DOTS VERTICALLY BELOW..ARABIC LETTER KAF WITH TWO DOTS VERTICALLY BELOW +10EC5 ; ID_Start # Lm ARABIC SMALL YEH BARREE WITH TWO DOTS BELOW +10EC6..10EC7 ; ID_Start # Lo [2] ARABIC LETTER THIN NOON..ARABIC LETTER YEH WITH FOUR DOTS BELOW 10F00..10F1C ; ID_Start # Lo [29] OLD SOGDIAN LETTER ALEPH..OLD SOGDIAN LETTER FINAL TAW WITH VERTICAL TAIL 10F27 ; ID_Start # Lo OLD SOGDIAN LIGATURE AYIN-DALETH 10F30..10F45 ; ID_Start # Lo [22] SOGDIAN LETTER ALEPH..SOGDIAN INDEPENDENT SHIN @@ -6821,6 +6884,9 @@ FFDA..FFDC ; ID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL 11D67..11D68 ; ID_Start # Lo [2] GUNJALA GONDI LETTER EE..GUNJALA GONDI LETTER AI 11D6A..11D89 ; ID_Start # Lo [32] GUNJALA GONDI LETTER OO..GUNJALA GONDI LETTER SA 11D98 ; ID_Start # Lo GUNJALA GONDI OM +11DB0..11DD8 ; ID_Start # Lo [41] TOLONG SIKI LETTER I..TOLONG SIKI LETTER RRH +11DD9 ; ID_Start # Lm TOLONG SIKI SIGN SELA +11DDA..11DDB ; ID_Start # Lo [2] TOLONG SIKI SIGN HECAKA..TOLONG SIKI UNGGA 11EE0..11EF2 ; ID_Start # Lo [19] MAKASAR LETTER KA..MAKASAR ANGKA 11F02 ; ID_Start # Lo KAWI SIGN REPHA 11F04..11F10 ; ID_Start # Lo [13] KAWI LETTER A..KAWI LETTER O @@ -6847,14 +6913,18 @@ FFDA..FFDC ; ID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL 16D43..16D6A ; ID_Start # Lo [40] KIRAT RAI LETTER A..KIRAT RAI VOWEL SIGN AU 16D6B..16D6C ; ID_Start # Lm [2] KIRAT RAI SIGN VIRAMA..KIRAT RAI SIGN SAAT 16E40..16E7F ; ID_Start # L& [64] MEDEFAIDRIN CAPITAL LETTER M..MEDEFAIDRIN SMALL LETTER Y +16EA0..16EB8 ; ID_Start # L& [25] BERIA ERFE CAPITAL LETTER ARKAB..BERIA ERFE CAPITAL LETTER AY +16EBB..16ED3 ; ID_Start # L& [25] BERIA ERFE SMALL LETTER ARKAB..BERIA ERFE SMALL LETTER AY 16F00..16F4A ; ID_Start # Lo [75] MIAO LETTER PA..MIAO LETTER RTE 16F50 ; ID_Start # Lo MIAO LETTER NASALIZATION 16F93..16F9F ; ID_Start # Lm [13] MIAO LETTER TONE-2..MIAO LETTER REFORMED TONE-8 16FE0..16FE1 ; ID_Start # Lm [2] TANGUT ITERATION MARK..NUSHU ITERATION MARK 16FE3 ; ID_Start # Lm OLD CHINESE ITERATION MARK -17000..187F7 ; ID_Start # Lo [6136] TANGUT IDEOGRAPH-17000..TANGUT IDEOGRAPH-187F7 -18800..18CD5 ; ID_Start # Lo [1238] TANGUT COMPONENT-001..KHITAN SMALL SCRIPT CHARACTER-18CD5 -18CFF..18D08 ; ID_Start # Lo [10] KHITAN SMALL SCRIPT CHARACTER-18CFF..TANGUT IDEOGRAPH-18D08 +16FF2..16FF3 ; ID_Start # Lm [2] CHINESE SMALL SIMPLIFIED ER..CHINESE SMALL TRADITIONAL ER +16FF4..16FF6 ; ID_Start # Nl [3] YANGQIN SIGN SLOW ONE BEAT..YANGQIN SIGN SLOW TWO BEATS +17000..18CD5 ; ID_Start # Lo [7382] TANGUT IDEOGRAPH-17000..KHITAN SMALL SCRIPT CHARACTER-18CD5 +18CFF..18D1E ; ID_Start # Lo [32] KHITAN SMALL SCRIPT CHARACTER-18CFF..TANGUT IDEOGRAPH-18D1E +18D80..18DF2 ; ID_Start # Lo [115] TANGUT COMPONENT-769..TANGUT COMPONENT-883 1AFF0..1AFF3 ; ID_Start # Lm [4] KATAKANA LETTER MINNAN TONE-2..KATAKANA LETTER MINNAN TONE-5 1AFF5..1AFFB ; ID_Start # Lm [7] KATAKANA LETTER MINNAN TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-5 1AFFD..1AFFE ; ID_Start # Lm [2] KATAKANA LETTER MINNAN NASALIZED TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-8 @@ -6912,6 +6982,13 @@ FFDA..FFDC ; ID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL 1E4EB ; ID_Start # Lm NAG MUNDARI SIGN OJOD 1E5D0..1E5ED ; ID_Start # Lo [30] OL ONAL LETTER O..OL ONAL LETTER EG 1E5F0 ; ID_Start # Lo OL ONAL SIGN HODDOND +1E6C0..1E6DE ; ID_Start # Lo [31] TAI YO LETTER LOW KO..TAI YO LETTER HIGH KVO +1E6E0..1E6E2 ; ID_Start # Lo [3] TAI YO LETTER AA..TAI YO LETTER UE +1E6E4..1E6E5 ; ID_Start # Lo [2] TAI YO LETTER U..TAI YO LETTER AE +1E6E7..1E6ED ; ID_Start # Lo [7] TAI YO LETTER O..TAI YO LETTER AUE +1E6F0..1E6F4 ; ID_Start # Lo [5] TAI YO LETTER AN..TAI YO LETTER AP +1E6FE ; ID_Start # Lo TAI YO SYMBOL MUEANG +1E6FF ; ID_Start # Lm TAI YO XAM LAI 1E7E0..1E7E6 ; ID_Start # Lo [7] ETHIOPIC SYLLABLE HHYA..ETHIOPIC SYLLABLE HHYO 1E7E8..1E7EB ; ID_Start # Lo [4] ETHIOPIC SYLLABLE GURAGE HHWA..ETHIOPIC SYLLABLE HHWE 1E7ED..1E7EE ; ID_Start # Lo [2] ETHIOPIC SYLLABLE GURAGE MWI..ETHIOPIC SYLLABLE GURAGE MWEE @@ -6953,16 +7030,15 @@ FFDA..FFDC ; ID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL 1EEA5..1EEA9 ; ID_Start # Lo [5] ARABIC MATHEMATICAL DOUBLE-STRUCK WAW..ARABIC MATHEMATICAL DOUBLE-STRUCK YEH 1EEAB..1EEBB ; ID_Start # Lo [17] ARABIC MATHEMATICAL DOUBLE-STRUCK LAM..ARABIC MATHEMATICAL DOUBLE-STRUCK GHAIN 20000..2A6DF ; ID_Start # Lo [42720] CJK UNIFIED IDEOGRAPH-20000..CJK UNIFIED IDEOGRAPH-2A6DF -2A700..2B739 ; ID_Start # Lo [4154] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B739 -2B740..2B81D ; ID_Start # Lo [222] CJK UNIFIED IDEOGRAPH-2B740..CJK UNIFIED IDEOGRAPH-2B81D -2B820..2CEA1 ; ID_Start # Lo [5762] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEA1 +2A700..2B81D ; ID_Start # Lo [4382] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B81D +2B820..2CEAD ; ID_Start # Lo [5774] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEAD 2CEB0..2EBE0 ; ID_Start # Lo [7473] CJK UNIFIED IDEOGRAPH-2CEB0..CJK UNIFIED IDEOGRAPH-2EBE0 2EBF0..2EE5D ; ID_Start # Lo [622] CJK UNIFIED IDEOGRAPH-2EBF0..CJK UNIFIED IDEOGRAPH-2EE5D 2F800..2FA1D ; ID_Start # Lo [542] CJK COMPATIBILITY IDEOGRAPH-2F800..CJK COMPATIBILITY IDEOGRAPH-2FA1D 30000..3134A ; ID_Start # Lo [4939] CJK UNIFIED IDEOGRAPH-30000..CJK UNIFIED IDEOGRAPH-3134A -31350..323AF ; ID_Start # Lo [4192] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-323AF +31350..33479 ; ID_Start # Lo [8490] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-33479 -# Total code points: 141269 +# Total code points: 145916 # ================================================ @@ -6991,8 +7067,8 @@ FFDA..FFDC ; ID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL 01BC..01BF ; ID_Continue # L& [4] LATIN CAPITAL LETTER TONE FIVE..LATIN LETTER WYNN 01C0..01C3 ; ID_Continue # Lo [4] LATIN LETTER DENTAL CLICK..LATIN LETTER RETROFLEX CLICK 01C4..0293 ; ID_Continue # L& [208] LATIN CAPITAL LETTER DZ WITH CARON..LATIN SMALL LETTER EZH WITH CURL -0294 ; ID_Continue # Lo LATIN LETTER GLOTTAL STOP -0295..02AF ; ID_Continue # L& [27] LATIN LETTER PHARYNGEAL VOICED FRICATIVE..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL +0294..0295 ; ID_Continue # Lo [2] LATIN LETTER GLOTTAL STOP..LATIN LETTER PHARYNGEAL VOICED FRICATIVE +0296..02AF ; ID_Continue # L& [26] LATIN LETTER INVERTED GLOTTAL STOP..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL 02B0..02C1 ; ID_Continue # Lm [18] MODIFIER LETTER SMALL H..MODIFIER LETTER REVERSED GLOTTAL STOP 02C6..02D1 ; ID_Continue # Lm [12] MODIFIER LETTER CIRCUMFLEX ACCENT..MODIFIER LETTER HALF TRIANGULAR COLON 02E0..02E4 ; ID_Continue # Lm [5] MODIFIER LETTER SMALL GAMMA..MODIFIER LETTER SMALL REVERSED GLOTTAL STOP @@ -7068,7 +7144,7 @@ FFDA..FFDC ; ID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL 0859..085B ; ID_Continue # Mn [3] MANDAIC AFFRICATION MARK..MANDAIC GEMINATION MARK 0860..086A ; ID_Continue # Lo [11] SYRIAC LETTER MALAYALAM NGA..SYRIAC LETTER MALAYALAM SSA 0870..0887 ; ID_Continue # Lo [24] ARABIC LETTER ALEF WITH ATTACHED FATHA..ARABIC BASELINE ROUND DOT -0889..088E ; ID_Continue # Lo [6] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC VERTICAL TAIL +0889..088F ; ID_Continue # Lo [7] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC LETTER NOON WITH RING ABOVE 0897..089F ; ID_Continue # Mn [9] ARABIC PEPET..ARABIC HALF MADDA OVER MADDA 08A0..08C8 ; ID_Continue # Lo [41] ARABIC LETTER BEH WITH SMALL V BELOW..ARABIC LETTER GRAF 08C9 ; ID_Continue # Lm ARABIC SMALL FARSI YEH @@ -7218,7 +7294,7 @@ FFDA..FFDC ; ID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL 0C4A..0C4D ; ID_Continue # Mn [4] TELUGU VOWEL SIGN O..TELUGU SIGN VIRAMA 0C55..0C56 ; ID_Continue # Mn [2] TELUGU LENGTH MARK..TELUGU AI LENGTH MARK 0C58..0C5A ; ID_Continue # Lo [3] TELUGU LETTER TSA..TELUGU LETTER RRRA -0C5D ; ID_Continue # Lo TELUGU LETTER NAKAARA POLLU +0C5C..0C5D ; ID_Continue # Lo [2] TELUGU ARCHAIC SHRII..TELUGU LETTER NAKAARA POLLU 0C60..0C61 ; ID_Continue # Lo [2] TELUGU LETTER VOCALIC RR..TELUGU LETTER VOCALIC LL 0C62..0C63 ; ID_Continue # Mn [2] TELUGU VOWEL SIGN VOCALIC L..TELUGU VOWEL SIGN VOCALIC LL 0C66..0C6F ; ID_Continue # Nd [10] TELUGU DIGIT ZERO..TELUGU DIGIT NINE @@ -7240,7 +7316,7 @@ FFDA..FFDC ; ID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL 0CCA..0CCB ; ID_Continue # Mc [2] KANNADA VOWEL SIGN O..KANNADA VOWEL SIGN OO 0CCC..0CCD ; ID_Continue # Mn [2] KANNADA VOWEL SIGN AU..KANNADA SIGN VIRAMA 0CD5..0CD6 ; ID_Continue # Mc [2] KANNADA LENGTH MARK..KANNADA AI LENGTH MARK -0CDD..0CDE ; ID_Continue # Lo [2] KANNADA LETTER NAKAARA POLLU..KANNADA LETTER FA +0CDC..0CDE ; ID_Continue # Lo [3] KANNADA ARCHAIC SHRII..KANNADA LETTER FA 0CE0..0CE1 ; ID_Continue # Lo [2] KANNADA LETTER VOCALIC RR..KANNADA LETTER VOCALIC LL 0CE2..0CE3 ; ID_Continue # Mn [2] KANNADA VOWEL SIGN VOCALIC L..KANNADA VOWEL SIGN VOCALIC LL 0CE6..0CEF ; ID_Continue # Nd [10] KANNADA DIGIT ZERO..KANNADA DIGIT NINE @@ -7457,7 +7533,8 @@ FFDA..FFDC ; ID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL 1A90..1A99 ; ID_Continue # Nd [10] TAI THAM THAM DIGIT ZERO..TAI THAM THAM DIGIT NINE 1AA7 ; ID_Continue # Lm TAI THAM SIGN MAI YAMOK 1AB0..1ABD ; ID_Continue # Mn [14] COMBINING DOUBLED CIRCUMFLEX ACCENT..COMBINING PARENTHESES BELOW -1ABF..1ACE ; ID_Continue # Mn [16] COMBINING LATIN SMALL LETTER W BELOW..COMBINING LATIN SMALL LETTER INSULAR T +1ABF..1ADD ; ID_Continue # Mn [31] COMBINING LATIN SMALL LETTER W BELOW..COMBINING DOT-AND-RING BELOW +1AE0..1AEB ; ID_Continue # Mn [12] COMBINING LEFT TACK ABOVE..COMBINING DOUBLE RIGHTWARDS ARROW ABOVE 1B00..1B03 ; ID_Continue # Mn [4] BALINESE SIGN ULU RICEM..BALINESE SIGN SURANG 1B04 ; ID_Continue # Mc BALINESE SIGN BISAH 1B05..1B33 ; ID_Continue # Lo [47] BALINESE LETTER AKARA..BALINESE LETTER HA @@ -7646,11 +7723,8 @@ A771..A787 ; ID_Continue # L& [23] LATIN SMALL LETTER DUM..LATIN SMALL LETTE A788 ; ID_Continue # Lm MODIFIER LETTER LOW CIRCUMFLEX ACCENT A78B..A78E ; ID_Continue # L& [4] LATIN CAPITAL LETTER SALTILLO..LATIN SMALL LETTER L WITH RETROFLEX HOOK AND BELT A78F ; ID_Continue # Lo LATIN LETTER SINOLOGICAL DOT -A790..A7CD ; ID_Continue # L& [62] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN SMALL LETTER S WITH DIAGONAL STROKE -A7D0..A7D1 ; ID_Continue # L& [2] LATIN CAPITAL LETTER CLOSED INSULAR G..LATIN SMALL LETTER CLOSED INSULAR G -A7D3 ; ID_Continue # L& LATIN SMALL LETTER DOUBLE THORN -A7D5..A7DC ; ID_Continue # L& [8] LATIN SMALL LETTER DOUBLE WYNN..LATIN CAPITAL LETTER LAMBDA WITH STROKE -A7F2..A7F4 ; ID_Continue # Lm [3] MODIFIER LETTER CAPITAL C..MODIFIER LETTER CAPITAL Q +A790..A7DC ; ID_Continue # L& [77] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN CAPITAL LETTER LAMBDA WITH STROKE +A7F1..A7F4 ; ID_Continue # Lm [4] MODIFIER LETTER CAPITAL S..MODIFIER LETTER CAPITAL Q A7F5..A7F6 ; ID_Continue # L& [2] LATIN CAPITAL LETTER REVERSED HALF H..LATIN SMALL LETTER REVERSED HALF H A7F7 ; ID_Continue # Lo LATIN EPIGRAPHIC LETTER SIDEWAYS I A7F8..A7F9 ; ID_Continue # Lm [2] MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE @@ -7857,6 +7931,7 @@ FFDA..FFDC ; ID_Continue # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HAN 108F4..108F5 ; ID_Continue # Lo [2] HATRAN LETTER SHIN..HATRAN LETTER TAW 10900..10915 ; ID_Continue # Lo [22] PHOENICIAN LETTER ALF..PHOENICIAN LETTER TAU 10920..10939 ; ID_Continue # Lo [26] LYDIAN LETTER A..LYDIAN LETTER C +10940..10959 ; ID_Continue # Lo [26] SIDETIC LETTER N01..SIDETIC LETTER N26 10980..109B7 ; ID_Continue # Lo [56] MEROITIC HIEROGLYPHIC LETTER A..MEROITIC CURSIVE LETTER DA 109BE..109BF ; ID_Continue # Lo [2] MEROITIC CURSIVE LOGOGRAM RMT..MEROITIC CURSIVE LOGOGRAM IMN 10A00 ; ID_Continue # Lo KHAROSHTHI LETTER A @@ -7895,7 +7970,9 @@ FFDA..FFDC ; ID_Continue # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HAN 10EAB..10EAC ; ID_Continue # Mn [2] YEZIDI COMBINING HAMZA MARK..YEZIDI COMBINING MADDA MARK 10EB0..10EB1 ; ID_Continue # Lo [2] YEZIDI LETTER LAM WITH DOT ABOVE..YEZIDI LETTER YOT WITH CIRCUMFLEX ABOVE 10EC2..10EC4 ; ID_Continue # Lo [3] ARABIC LETTER DAL WITH TWO DOTS VERTICALLY BELOW..ARABIC LETTER KAF WITH TWO DOTS VERTICALLY BELOW -10EFC..10EFF ; ID_Continue # Mn [4] ARABIC COMBINING ALEF OVERLAY..ARABIC SMALL LOW WORD MADDA +10EC5 ; ID_Continue # Lm ARABIC SMALL YEH BARREE WITH TWO DOTS BELOW +10EC6..10EC7 ; ID_Continue # Lo [2] ARABIC LETTER THIN NOON..ARABIC LETTER YEH WITH FOUR DOTS BELOW +10EFA..10EFF ; ID_Continue # Mn [6] ARABIC DOUBLE VERTICAL BAR BELOW..ARABIC SMALL LOW WORD MADDA 10F00..10F1C ; ID_Continue # Lo [29] OLD SOGDIAN LETTER ALEPH..OLD SOGDIAN LETTER FINAL TAW WITH VERTICAL TAIL 10F27 ; ID_Continue # Lo OLD SOGDIAN LIGATURE AYIN-DALETH 10F30..10F45 ; ID_Continue # Lo [22] SOGDIAN LETTER ALEPH..SOGDIAN INDEPENDENT SHIN @@ -8122,6 +8199,12 @@ FFDA..FFDC ; ID_Continue # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HAN 11A98..11A99 ; ID_Continue # Mn [2] SOYOMBO GEMINATION MARK..SOYOMBO SUBJOINER 11A9D ; ID_Continue # Lo SOYOMBO MARK PLUTA 11AB0..11AF8 ; ID_Continue # Lo [73] CANADIAN SYLLABICS NATTILIK HI..PAU CIN HAU GLOTTAL STOP FINAL +11B60 ; ID_Continue # Mn SHARADA VOWEL SIGN OE +11B61 ; ID_Continue # Mc SHARADA VOWEL SIGN OOE +11B62..11B64 ; ID_Continue # Mn [3] SHARADA VOWEL SIGN UE..SHARADA VOWEL SIGN SHORT E +11B65 ; ID_Continue # Mc SHARADA VOWEL SIGN SHORT O +11B66 ; ID_Continue # Mn SHARADA VOWEL SIGN CANDRA E +11B67 ; ID_Continue # Mc SHARADA VOWEL SIGN CANDRA O 11BC0..11BE0 ; ID_Continue # Lo [33] SUNUWAR LETTER DEVI..SUNUWAR LETTER KLOKO 11BF0..11BF9 ; ID_Continue # Nd [10] SUNUWAR DIGIT ZERO..SUNUWAR DIGIT NINE 11C00..11C08 ; ID_Continue # Lo [9] BHAIKSUKI LETTER A..BHAIKSUKI LETTER VOCALIC L @@ -8162,6 +8245,10 @@ FFDA..FFDC ; ID_Continue # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HAN 11D97 ; ID_Continue # Mn GUNJALA GONDI VIRAMA 11D98 ; ID_Continue # Lo GUNJALA GONDI OM 11DA0..11DA9 ; ID_Continue # Nd [10] GUNJALA GONDI DIGIT ZERO..GUNJALA GONDI DIGIT NINE +11DB0..11DD8 ; ID_Continue # Lo [41] TOLONG SIKI LETTER I..TOLONG SIKI LETTER RRH +11DD9 ; ID_Continue # Lm TOLONG SIKI SIGN SELA +11DDA..11DDB ; ID_Continue # Lo [2] TOLONG SIKI SIGN HECAKA..TOLONG SIKI UNGGA +11DE0..11DE9 ; ID_Continue # Nd [10] TOLONG SIKI DIGIT ZERO..TOLONG SIKI DIGIT NINE 11EE0..11EF2 ; ID_Continue # Lo [19] MAKASAR LETTER KA..MAKASAR ANGKA 11EF3..11EF4 ; ID_Continue # Mn [2] MAKASAR VOWEL SIGN I..MAKASAR VOWEL SIGN U 11EF5..11EF6 ; ID_Continue # Mc [2] MAKASAR VOWEL SIGN E..MAKASAR VOWEL SIGN O @@ -8212,6 +8299,8 @@ FFDA..FFDC ; ID_Continue # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HAN 16D6B..16D6C ; ID_Continue # Lm [2] KIRAT RAI SIGN VIRAMA..KIRAT RAI SIGN SAAT 16D70..16D79 ; ID_Continue # Nd [10] KIRAT RAI DIGIT ZERO..KIRAT RAI DIGIT NINE 16E40..16E7F ; ID_Continue # L& [64] MEDEFAIDRIN CAPITAL LETTER M..MEDEFAIDRIN SMALL LETTER Y +16EA0..16EB8 ; ID_Continue # L& [25] BERIA ERFE CAPITAL LETTER ARKAB..BERIA ERFE CAPITAL LETTER AY +16EBB..16ED3 ; ID_Continue # L& [25] BERIA ERFE SMALL LETTER ARKAB..BERIA ERFE SMALL LETTER AY 16F00..16F4A ; ID_Continue # Lo [75] MIAO LETTER PA..MIAO LETTER RTE 16F4F ; ID_Continue # Mn MIAO SIGN CONSONANT MODIFIER BAR 16F50 ; ID_Continue # Lo MIAO LETTER NASALIZATION @@ -8222,9 +8311,11 @@ FFDA..FFDC ; ID_Continue # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HAN 16FE3 ; ID_Continue # Lm OLD CHINESE ITERATION MARK 16FE4 ; ID_Continue # Mn KHITAN SMALL SCRIPT FILLER 16FF0..16FF1 ; ID_Continue # Mc [2] VIETNAMESE ALTERNATE READING MARK CA..VIETNAMESE ALTERNATE READING MARK NHAY -17000..187F7 ; ID_Continue # Lo [6136] TANGUT IDEOGRAPH-17000..TANGUT IDEOGRAPH-187F7 -18800..18CD5 ; ID_Continue # Lo [1238] TANGUT COMPONENT-001..KHITAN SMALL SCRIPT CHARACTER-18CD5 -18CFF..18D08 ; ID_Continue # Lo [10] KHITAN SMALL SCRIPT CHARACTER-18CFF..TANGUT IDEOGRAPH-18D08 +16FF2..16FF3 ; ID_Continue # Lm [2] CHINESE SMALL SIMPLIFIED ER..CHINESE SMALL TRADITIONAL ER +16FF4..16FF6 ; ID_Continue # Nl [3] YANGQIN SIGN SLOW ONE BEAT..YANGQIN SIGN SLOW TWO BEATS +17000..18CD5 ; ID_Continue # Lo [7382] TANGUT IDEOGRAPH-17000..KHITAN SMALL SCRIPT CHARACTER-18CD5 +18CFF..18D1E ; ID_Continue # Lo [32] KHITAN SMALL SCRIPT CHARACTER-18CFF..TANGUT IDEOGRAPH-18D1E +18D80..18DF2 ; ID_Continue # Lo [115] TANGUT COMPONENT-769..TANGUT COMPONENT-883 1AFF0..1AFF3 ; ID_Continue # Lm [4] KATAKANA LETTER MINNAN TONE-2..KATAKANA LETTER MINNAN TONE-5 1AFF5..1AFFB ; ID_Continue # Lm [7] KATAKANA LETTER MINNAN TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-5 1AFFD..1AFFE ; ID_Continue # Lm [2] KATAKANA LETTER MINNAN NASALIZED TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-8 @@ -8315,6 +8406,17 @@ FFDA..FFDC ; ID_Continue # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HAN 1E5EE..1E5EF ; ID_Continue # Mn [2] OL ONAL SIGN MU..OL ONAL SIGN IKIR 1E5F0 ; ID_Continue # Lo OL ONAL SIGN HODDOND 1E5F1..1E5FA ; ID_Continue # Nd [10] OL ONAL DIGIT ZERO..OL ONAL DIGIT NINE +1E6C0..1E6DE ; ID_Continue # Lo [31] TAI YO LETTER LOW KO..TAI YO LETTER HIGH KVO +1E6E0..1E6E2 ; ID_Continue # Lo [3] TAI YO LETTER AA..TAI YO LETTER UE +1E6E3 ; ID_Continue # Mn TAI YO SIGN UE +1E6E4..1E6E5 ; ID_Continue # Lo [2] TAI YO LETTER U..TAI YO LETTER AE +1E6E6 ; ID_Continue # Mn TAI YO SIGN AU +1E6E7..1E6ED ; ID_Continue # Lo [7] TAI YO LETTER O..TAI YO LETTER AUE +1E6EE..1E6EF ; ID_Continue # Mn [2] TAI YO SIGN AY..TAI YO SIGN ANG +1E6F0..1E6F4 ; ID_Continue # Lo [5] TAI YO LETTER AN..TAI YO LETTER AP +1E6F5 ; ID_Continue # Mn TAI YO SIGN OM +1E6FE ; ID_Continue # Lo TAI YO SYMBOL MUEANG +1E6FF ; ID_Continue # Lm TAI YO XAM LAI 1E7E0..1E7E6 ; ID_Continue # Lo [7] ETHIOPIC SYLLABLE HHYA..ETHIOPIC SYLLABLE HHYO 1E7E8..1E7EB ; ID_Continue # Lo [4] ETHIOPIC SYLLABLE GURAGE HHWA..ETHIOPIC SYLLABLE HHWE 1E7ED..1E7EE ; ID_Continue # Lo [2] ETHIOPIC SYLLABLE GURAGE MWI..ETHIOPIC SYLLABLE GURAGE MWEE @@ -8360,17 +8462,16 @@ FFDA..FFDC ; ID_Continue # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HAN 1EEAB..1EEBB ; ID_Continue # Lo [17] ARABIC MATHEMATICAL DOUBLE-STRUCK LAM..ARABIC MATHEMATICAL DOUBLE-STRUCK GHAIN 1FBF0..1FBF9 ; ID_Continue # Nd [10] SEGMENTED DIGIT ZERO..SEGMENTED DIGIT NINE 20000..2A6DF ; ID_Continue # Lo [42720] CJK UNIFIED IDEOGRAPH-20000..CJK UNIFIED IDEOGRAPH-2A6DF -2A700..2B739 ; ID_Continue # Lo [4154] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B739 -2B740..2B81D ; ID_Continue # Lo [222] CJK UNIFIED IDEOGRAPH-2B740..CJK UNIFIED IDEOGRAPH-2B81D -2B820..2CEA1 ; ID_Continue # Lo [5762] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEA1 +2A700..2B81D ; ID_Continue # Lo [4382] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B81D +2B820..2CEAD ; ID_Continue # Lo [5774] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEAD 2CEB0..2EBE0 ; ID_Continue # Lo [7473] CJK UNIFIED IDEOGRAPH-2CEB0..CJK UNIFIED IDEOGRAPH-2EBE0 2EBF0..2EE5D ; ID_Continue # Lo [622] CJK UNIFIED IDEOGRAPH-2EBF0..CJK UNIFIED IDEOGRAPH-2EE5D 2F800..2FA1D ; ID_Continue # Lo [542] CJK COMPATIBILITY IDEOGRAPH-2F800..CJK COMPATIBILITY IDEOGRAPH-2FA1D 30000..3134A ; ID_Continue # Lo [4939] CJK UNIFIED IDEOGRAPH-30000..CJK UNIFIED IDEOGRAPH-3134A -31350..323AF ; ID_Continue # Lo [4192] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-323AF +31350..33479 ; ID_Continue # Lo [8490] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-33479 E0100..E01EF ; ID_Continue # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR-256 -# Total code points: 144541 +# Total code points: 149240 # ================================================ @@ -8393,8 +8494,8 @@ E0100..E01EF ; ID_Continue # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR 01BC..01BF ; XID_Start # L& [4] LATIN CAPITAL LETTER TONE FIVE..LATIN LETTER WYNN 01C0..01C3 ; XID_Start # Lo [4] LATIN LETTER DENTAL CLICK..LATIN LETTER RETROFLEX CLICK 01C4..0293 ; XID_Start # L& [208] LATIN CAPITAL LETTER DZ WITH CARON..LATIN SMALL LETTER EZH WITH CURL -0294 ; XID_Start # Lo LATIN LETTER GLOTTAL STOP -0295..02AF ; XID_Start # L& [27] LATIN LETTER PHARYNGEAL VOICED FRICATIVE..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL +0294..0295 ; XID_Start # Lo [2] LATIN LETTER GLOTTAL STOP..LATIN LETTER PHARYNGEAL VOICED FRICATIVE +0296..02AF ; XID_Start # L& [26] LATIN LETTER INVERTED GLOTTAL STOP..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL 02B0..02C1 ; XID_Start # Lm [18] MODIFIER LETTER SMALL H..MODIFIER LETTER REVERSED GLOTTAL STOP 02C6..02D1 ; XID_Start # Lm [12] MODIFIER LETTER CIRCUMFLEX ACCENT..MODIFIER LETTER HALF TRIANGULAR COLON 02E0..02E4 ; XID_Start # Lm [5] MODIFIER LETTER SMALL GAMMA..MODIFIER LETTER SMALL REVERSED GLOTTAL STOP @@ -8441,7 +8542,7 @@ E0100..E01EF ; ID_Continue # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR 0840..0858 ; XID_Start # Lo [25] MANDAIC LETTER HALQA..MANDAIC LETTER AIN 0860..086A ; XID_Start # Lo [11] SYRIAC LETTER MALAYALAM NGA..SYRIAC LETTER MALAYALAM SSA 0870..0887 ; XID_Start # Lo [24] ARABIC LETTER ALEF WITH ATTACHED FATHA..ARABIC BASELINE ROUND DOT -0889..088E ; XID_Start # Lo [6] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC VERTICAL TAIL +0889..088F ; XID_Start # Lo [7] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC LETTER NOON WITH RING ABOVE 08A0..08C8 ; XID_Start # Lo [41] ARABIC LETTER BEH WITH SMALL V BELOW..ARABIC LETTER GRAF 08C9 ; XID_Start # Lm ARABIC SMALL FARSI YEH 0904..0939 ; XID_Start # Lo [54] DEVANAGARI LETTER SHORT A..DEVANAGARI LETTER HA @@ -8509,7 +8610,7 @@ E0100..E01EF ; ID_Continue # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR 0C2A..0C39 ; XID_Start # Lo [16] TELUGU LETTER PA..TELUGU LETTER HA 0C3D ; XID_Start # Lo TELUGU SIGN AVAGRAHA 0C58..0C5A ; XID_Start # Lo [3] TELUGU LETTER TSA..TELUGU LETTER RRRA -0C5D ; XID_Start # Lo TELUGU LETTER NAKAARA POLLU +0C5C..0C5D ; XID_Start # Lo [2] TELUGU ARCHAIC SHRII..TELUGU LETTER NAKAARA POLLU 0C60..0C61 ; XID_Start # Lo [2] TELUGU LETTER VOCALIC RR..TELUGU LETTER VOCALIC LL 0C80 ; XID_Start # Lo KANNADA SIGN SPACING CANDRABINDU 0C85..0C8C ; XID_Start # Lo [8] KANNADA LETTER A..KANNADA LETTER VOCALIC L @@ -8518,7 +8619,7 @@ E0100..E01EF ; ID_Continue # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR 0CAA..0CB3 ; XID_Start # Lo [10] KANNADA LETTER PA..KANNADA LETTER LLA 0CB5..0CB9 ; XID_Start # Lo [5] KANNADA LETTER VA..KANNADA LETTER HA 0CBD ; XID_Start # Lo KANNADA SIGN AVAGRAHA -0CDD..0CDE ; XID_Start # Lo [2] KANNADA LETTER NAKAARA POLLU..KANNADA LETTER FA +0CDC..0CDE ; XID_Start # Lo [3] KANNADA ARCHAIC SHRII..KANNADA LETTER FA 0CE0..0CE1 ; XID_Start # Lo [2] KANNADA LETTER VOCALIC RR..KANNADA LETTER VOCALIC LL 0CF1..0CF2 ; XID_Start # Lo [2] KANNADA SIGN JIHVAMULIYA..KANNADA SIGN UPADHMANIYA 0D04..0D0C ; XID_Start # Lo [9] MALAYALAM LETTER VEDIC ANUSVARA..MALAYALAM LETTER VOCALIC L @@ -8742,11 +8843,8 @@ A771..A787 ; XID_Start # L& [23] LATIN SMALL LETTER DUM..LATIN SMALL LETTER A788 ; XID_Start # Lm MODIFIER LETTER LOW CIRCUMFLEX ACCENT A78B..A78E ; XID_Start # L& [4] LATIN CAPITAL LETTER SALTILLO..LATIN SMALL LETTER L WITH RETROFLEX HOOK AND BELT A78F ; XID_Start # Lo LATIN LETTER SINOLOGICAL DOT -A790..A7CD ; XID_Start # L& [62] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN SMALL LETTER S WITH DIAGONAL STROKE -A7D0..A7D1 ; XID_Start # L& [2] LATIN CAPITAL LETTER CLOSED INSULAR G..LATIN SMALL LETTER CLOSED INSULAR G -A7D3 ; XID_Start # L& LATIN SMALL LETTER DOUBLE THORN -A7D5..A7DC ; XID_Start # L& [8] LATIN SMALL LETTER DOUBLE WYNN..LATIN CAPITAL LETTER LAMBDA WITH STROKE -A7F2..A7F4 ; XID_Start # Lm [3] MODIFIER LETTER CAPITAL C..MODIFIER LETTER CAPITAL Q +A790..A7DC ; XID_Start # L& [77] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN CAPITAL LETTER LAMBDA WITH STROKE +A7F1..A7F4 ; XID_Start # Lm [4] MODIFIER LETTER CAPITAL S..MODIFIER LETTER CAPITAL Q A7F5..A7F6 ; XID_Start # L& [2] LATIN CAPITAL LETTER REVERSED HALF H..LATIN SMALL LETTER REVERSED HALF H A7F7 ; XID_Start # Lo LATIN EPIGRAPHIC LETTER SIDEWAYS I A7F8..A7F9 ; XID_Start # Lm [2] MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE @@ -8888,6 +8986,7 @@ FFDA..FFDC ; XID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGU 108F4..108F5 ; XID_Start # Lo [2] HATRAN LETTER SHIN..HATRAN LETTER TAW 10900..10915 ; XID_Start # Lo [22] PHOENICIAN LETTER ALF..PHOENICIAN LETTER TAU 10920..10939 ; XID_Start # Lo [26] LYDIAN LETTER A..LYDIAN LETTER C +10940..10959 ; XID_Start # Lo [26] SIDETIC LETTER N01..SIDETIC LETTER N26 10980..109B7 ; XID_Start # Lo [56] MEROITIC HIEROGLYPHIC LETTER A..MEROITIC CURSIVE LETTER DA 109BE..109BF ; XID_Start # Lo [2] MEROITIC CURSIVE LOGOGRAM RMT..MEROITIC CURSIVE LOGOGRAM IMN 10A00 ; XID_Start # Lo KHAROSHTHI LETTER A @@ -8915,6 +9014,8 @@ FFDA..FFDC ; XID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGU 10E80..10EA9 ; XID_Start # Lo [42] YEZIDI LETTER ELIF..YEZIDI LETTER ET 10EB0..10EB1 ; XID_Start # Lo [2] YEZIDI LETTER LAM WITH DOT ABOVE..YEZIDI LETTER YOT WITH CIRCUMFLEX ABOVE 10EC2..10EC4 ; XID_Start # Lo [3] ARABIC LETTER DAL WITH TWO DOTS VERTICALLY BELOW..ARABIC LETTER KAF WITH TWO DOTS VERTICALLY BELOW +10EC5 ; XID_Start # Lm ARABIC SMALL YEH BARREE WITH TWO DOTS BELOW +10EC6..10EC7 ; XID_Start # Lo [2] ARABIC LETTER THIN NOON..ARABIC LETTER YEH WITH FOUR DOTS BELOW 10F00..10F1C ; XID_Start # Lo [29] OLD SOGDIAN LETTER ALEPH..OLD SOGDIAN LETTER FINAL TAW WITH VERTICAL TAIL 10F27 ; XID_Start # Lo OLD SOGDIAN LIGATURE AYIN-DALETH 10F30..10F45 ; XID_Start # Lo [22] SOGDIAN LETTER ALEPH..SOGDIAN INDEPENDENT SHIN @@ -9007,6 +9108,9 @@ FFDA..FFDC ; XID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGU 11D67..11D68 ; XID_Start # Lo [2] GUNJALA GONDI LETTER EE..GUNJALA GONDI LETTER AI 11D6A..11D89 ; XID_Start # Lo [32] GUNJALA GONDI LETTER OO..GUNJALA GONDI LETTER SA 11D98 ; XID_Start # Lo GUNJALA GONDI OM +11DB0..11DD8 ; XID_Start # Lo [41] TOLONG SIKI LETTER I..TOLONG SIKI LETTER RRH +11DD9 ; XID_Start # Lm TOLONG SIKI SIGN SELA +11DDA..11DDB ; XID_Start # Lo [2] TOLONG SIKI SIGN HECAKA..TOLONG SIKI UNGGA 11EE0..11EF2 ; XID_Start # Lo [19] MAKASAR LETTER KA..MAKASAR ANGKA 11F02 ; XID_Start # Lo KAWI SIGN REPHA 11F04..11F10 ; XID_Start # Lo [13] KAWI LETTER A..KAWI LETTER O @@ -9033,14 +9137,18 @@ FFDA..FFDC ; XID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGU 16D43..16D6A ; XID_Start # Lo [40] KIRAT RAI LETTER A..KIRAT RAI VOWEL SIGN AU 16D6B..16D6C ; XID_Start # Lm [2] KIRAT RAI SIGN VIRAMA..KIRAT RAI SIGN SAAT 16E40..16E7F ; XID_Start # L& [64] MEDEFAIDRIN CAPITAL LETTER M..MEDEFAIDRIN SMALL LETTER Y +16EA0..16EB8 ; XID_Start # L& [25] BERIA ERFE CAPITAL LETTER ARKAB..BERIA ERFE CAPITAL LETTER AY +16EBB..16ED3 ; XID_Start # L& [25] BERIA ERFE SMALL LETTER ARKAB..BERIA ERFE SMALL LETTER AY 16F00..16F4A ; XID_Start # Lo [75] MIAO LETTER PA..MIAO LETTER RTE 16F50 ; XID_Start # Lo MIAO LETTER NASALIZATION 16F93..16F9F ; XID_Start # Lm [13] MIAO LETTER TONE-2..MIAO LETTER REFORMED TONE-8 16FE0..16FE1 ; XID_Start # Lm [2] TANGUT ITERATION MARK..NUSHU ITERATION MARK 16FE3 ; XID_Start # Lm OLD CHINESE ITERATION MARK -17000..187F7 ; XID_Start # Lo [6136] TANGUT IDEOGRAPH-17000..TANGUT IDEOGRAPH-187F7 -18800..18CD5 ; XID_Start # Lo [1238] TANGUT COMPONENT-001..KHITAN SMALL SCRIPT CHARACTER-18CD5 -18CFF..18D08 ; XID_Start # Lo [10] KHITAN SMALL SCRIPT CHARACTER-18CFF..TANGUT IDEOGRAPH-18D08 +16FF2..16FF3 ; XID_Start # Lm [2] CHINESE SMALL SIMPLIFIED ER..CHINESE SMALL TRADITIONAL ER +16FF4..16FF6 ; XID_Start # Nl [3] YANGQIN SIGN SLOW ONE BEAT..YANGQIN SIGN SLOW TWO BEATS +17000..18CD5 ; XID_Start # Lo [7382] TANGUT IDEOGRAPH-17000..KHITAN SMALL SCRIPT CHARACTER-18CD5 +18CFF..18D1E ; XID_Start # Lo [32] KHITAN SMALL SCRIPT CHARACTER-18CFF..TANGUT IDEOGRAPH-18D1E +18D80..18DF2 ; XID_Start # Lo [115] TANGUT COMPONENT-769..TANGUT COMPONENT-883 1AFF0..1AFF3 ; XID_Start # Lm [4] KATAKANA LETTER MINNAN TONE-2..KATAKANA LETTER MINNAN TONE-5 1AFF5..1AFFB ; XID_Start # Lm [7] KATAKANA LETTER MINNAN TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-5 1AFFD..1AFFE ; XID_Start # Lm [2] KATAKANA LETTER MINNAN NASALIZED TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-8 @@ -9098,6 +9206,13 @@ FFDA..FFDC ; XID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGU 1E4EB ; XID_Start # Lm NAG MUNDARI SIGN OJOD 1E5D0..1E5ED ; XID_Start # Lo [30] OL ONAL LETTER O..OL ONAL LETTER EG 1E5F0 ; XID_Start # Lo OL ONAL SIGN HODDOND +1E6C0..1E6DE ; XID_Start # Lo [31] TAI YO LETTER LOW KO..TAI YO LETTER HIGH KVO +1E6E0..1E6E2 ; XID_Start # Lo [3] TAI YO LETTER AA..TAI YO LETTER UE +1E6E4..1E6E5 ; XID_Start # Lo [2] TAI YO LETTER U..TAI YO LETTER AE +1E6E7..1E6ED ; XID_Start # Lo [7] TAI YO LETTER O..TAI YO LETTER AUE +1E6F0..1E6F4 ; XID_Start # Lo [5] TAI YO LETTER AN..TAI YO LETTER AP +1E6FE ; XID_Start # Lo TAI YO SYMBOL MUEANG +1E6FF ; XID_Start # Lm TAI YO XAM LAI 1E7E0..1E7E6 ; XID_Start # Lo [7] ETHIOPIC SYLLABLE HHYA..ETHIOPIC SYLLABLE HHYO 1E7E8..1E7EB ; XID_Start # Lo [4] ETHIOPIC SYLLABLE GURAGE HHWA..ETHIOPIC SYLLABLE HHWE 1E7ED..1E7EE ; XID_Start # Lo [2] ETHIOPIC SYLLABLE GURAGE MWI..ETHIOPIC SYLLABLE GURAGE MWEE @@ -9139,16 +9254,15 @@ FFDA..FFDC ; XID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGU 1EEA5..1EEA9 ; XID_Start # Lo [5] ARABIC MATHEMATICAL DOUBLE-STRUCK WAW..ARABIC MATHEMATICAL DOUBLE-STRUCK YEH 1EEAB..1EEBB ; XID_Start # Lo [17] ARABIC MATHEMATICAL DOUBLE-STRUCK LAM..ARABIC MATHEMATICAL DOUBLE-STRUCK GHAIN 20000..2A6DF ; XID_Start # Lo [42720] CJK UNIFIED IDEOGRAPH-20000..CJK UNIFIED IDEOGRAPH-2A6DF -2A700..2B739 ; XID_Start # Lo [4154] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B739 -2B740..2B81D ; XID_Start # Lo [222] CJK UNIFIED IDEOGRAPH-2B740..CJK UNIFIED IDEOGRAPH-2B81D -2B820..2CEA1 ; XID_Start # Lo [5762] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEA1 +2A700..2B81D ; XID_Start # Lo [4382] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B81D +2B820..2CEAD ; XID_Start # Lo [5774] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEAD 2CEB0..2EBE0 ; XID_Start # Lo [7473] CJK UNIFIED IDEOGRAPH-2CEB0..CJK UNIFIED IDEOGRAPH-2EBE0 2EBF0..2EE5D ; XID_Start # Lo [622] CJK UNIFIED IDEOGRAPH-2EBF0..CJK UNIFIED IDEOGRAPH-2EE5D 2F800..2FA1D ; XID_Start # Lo [542] CJK COMPATIBILITY IDEOGRAPH-2F800..CJK COMPATIBILITY IDEOGRAPH-2FA1D 30000..3134A ; XID_Start # Lo [4939] CJK UNIFIED IDEOGRAPH-30000..CJK UNIFIED IDEOGRAPH-3134A -31350..323AF ; XID_Start # Lo [4192] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-323AF +31350..33479 ; XID_Start # Lo [8490] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-33479 -# Total code points: 141246 +# Total code points: 145893 # ================================================ @@ -9174,8 +9288,8 @@ FFDA..FFDC ; XID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGU 01BC..01BF ; XID_Continue # L& [4] LATIN CAPITAL LETTER TONE FIVE..LATIN LETTER WYNN 01C0..01C3 ; XID_Continue # Lo [4] LATIN LETTER DENTAL CLICK..LATIN LETTER RETROFLEX CLICK 01C4..0293 ; XID_Continue # L& [208] LATIN CAPITAL LETTER DZ WITH CARON..LATIN SMALL LETTER EZH WITH CURL -0294 ; XID_Continue # Lo LATIN LETTER GLOTTAL STOP -0295..02AF ; XID_Continue # L& [27] LATIN LETTER PHARYNGEAL VOICED FRICATIVE..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL +0294..0295 ; XID_Continue # Lo [2] LATIN LETTER GLOTTAL STOP..LATIN LETTER PHARYNGEAL VOICED FRICATIVE +0296..02AF ; XID_Continue # L& [26] LATIN LETTER INVERTED GLOTTAL STOP..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL 02B0..02C1 ; XID_Continue # Lm [18] MODIFIER LETTER SMALL H..MODIFIER LETTER REVERSED GLOTTAL STOP 02C6..02D1 ; XID_Continue # Lm [12] MODIFIER LETTER CIRCUMFLEX ACCENT..MODIFIER LETTER HALF TRIANGULAR COLON 02E0..02E4 ; XID_Continue # Lm [5] MODIFIER LETTER SMALL GAMMA..MODIFIER LETTER SMALL REVERSED GLOTTAL STOP @@ -9250,7 +9364,7 @@ FFDA..FFDC ; XID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGU 0859..085B ; XID_Continue # Mn [3] MANDAIC AFFRICATION MARK..MANDAIC GEMINATION MARK 0860..086A ; XID_Continue # Lo [11] SYRIAC LETTER MALAYALAM NGA..SYRIAC LETTER MALAYALAM SSA 0870..0887 ; XID_Continue # Lo [24] ARABIC LETTER ALEF WITH ATTACHED FATHA..ARABIC BASELINE ROUND DOT -0889..088E ; XID_Continue # Lo [6] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC VERTICAL TAIL +0889..088F ; XID_Continue # Lo [7] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC LETTER NOON WITH RING ABOVE 0897..089F ; XID_Continue # Mn [9] ARABIC PEPET..ARABIC HALF MADDA OVER MADDA 08A0..08C8 ; XID_Continue # Lo [41] ARABIC LETTER BEH WITH SMALL V BELOW..ARABIC LETTER GRAF 08C9 ; XID_Continue # Lm ARABIC SMALL FARSI YEH @@ -9400,7 +9514,7 @@ FFDA..FFDC ; XID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGU 0C4A..0C4D ; XID_Continue # Mn [4] TELUGU VOWEL SIGN O..TELUGU SIGN VIRAMA 0C55..0C56 ; XID_Continue # Mn [2] TELUGU LENGTH MARK..TELUGU AI LENGTH MARK 0C58..0C5A ; XID_Continue # Lo [3] TELUGU LETTER TSA..TELUGU LETTER RRRA -0C5D ; XID_Continue # Lo TELUGU LETTER NAKAARA POLLU +0C5C..0C5D ; XID_Continue # Lo [2] TELUGU ARCHAIC SHRII..TELUGU LETTER NAKAARA POLLU 0C60..0C61 ; XID_Continue # Lo [2] TELUGU LETTER VOCALIC RR..TELUGU LETTER VOCALIC LL 0C62..0C63 ; XID_Continue # Mn [2] TELUGU VOWEL SIGN VOCALIC L..TELUGU VOWEL SIGN VOCALIC LL 0C66..0C6F ; XID_Continue # Nd [10] TELUGU DIGIT ZERO..TELUGU DIGIT NINE @@ -9422,7 +9536,7 @@ FFDA..FFDC ; XID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGU 0CCA..0CCB ; XID_Continue # Mc [2] KANNADA VOWEL SIGN O..KANNADA VOWEL SIGN OO 0CCC..0CCD ; XID_Continue # Mn [2] KANNADA VOWEL SIGN AU..KANNADA SIGN VIRAMA 0CD5..0CD6 ; XID_Continue # Mc [2] KANNADA LENGTH MARK..KANNADA AI LENGTH MARK -0CDD..0CDE ; XID_Continue # Lo [2] KANNADA LETTER NAKAARA POLLU..KANNADA LETTER FA +0CDC..0CDE ; XID_Continue # Lo [3] KANNADA ARCHAIC SHRII..KANNADA LETTER FA 0CE0..0CE1 ; XID_Continue # Lo [2] KANNADA LETTER VOCALIC RR..KANNADA LETTER VOCALIC LL 0CE2..0CE3 ; XID_Continue # Mn [2] KANNADA VOWEL SIGN VOCALIC L..KANNADA VOWEL SIGN VOCALIC LL 0CE6..0CEF ; XID_Continue # Nd [10] KANNADA DIGIT ZERO..KANNADA DIGIT NINE @@ -9639,7 +9753,8 @@ FFDA..FFDC ; XID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGU 1A90..1A99 ; XID_Continue # Nd [10] TAI THAM THAM DIGIT ZERO..TAI THAM THAM DIGIT NINE 1AA7 ; XID_Continue # Lm TAI THAM SIGN MAI YAMOK 1AB0..1ABD ; XID_Continue # Mn [14] COMBINING DOUBLED CIRCUMFLEX ACCENT..COMBINING PARENTHESES BELOW -1ABF..1ACE ; XID_Continue # Mn [16] COMBINING LATIN SMALL LETTER W BELOW..COMBINING LATIN SMALL LETTER INSULAR T +1ABF..1ADD ; XID_Continue # Mn [31] COMBINING LATIN SMALL LETTER W BELOW..COMBINING DOT-AND-RING BELOW +1AE0..1AEB ; XID_Continue # Mn [12] COMBINING LEFT TACK ABOVE..COMBINING DOUBLE RIGHTWARDS ARROW ABOVE 1B00..1B03 ; XID_Continue # Mn [4] BALINESE SIGN ULU RICEM..BALINESE SIGN SURANG 1B04 ; XID_Continue # Mc BALINESE SIGN BISAH 1B05..1B33 ; XID_Continue # Lo [47] BALINESE LETTER AKARA..BALINESE LETTER HA @@ -9827,11 +9942,8 @@ A771..A787 ; XID_Continue # L& [23] LATIN SMALL LETTER DUM..LATIN SMALL LETT A788 ; XID_Continue # Lm MODIFIER LETTER LOW CIRCUMFLEX ACCENT A78B..A78E ; XID_Continue # L& [4] LATIN CAPITAL LETTER SALTILLO..LATIN SMALL LETTER L WITH RETROFLEX HOOK AND BELT A78F ; XID_Continue # Lo LATIN LETTER SINOLOGICAL DOT -A790..A7CD ; XID_Continue # L& [62] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN SMALL LETTER S WITH DIAGONAL STROKE -A7D0..A7D1 ; XID_Continue # L& [2] LATIN CAPITAL LETTER CLOSED INSULAR G..LATIN SMALL LETTER CLOSED INSULAR G -A7D3 ; XID_Continue # L& LATIN SMALL LETTER DOUBLE THORN -A7D5..A7DC ; XID_Continue # L& [8] LATIN SMALL LETTER DOUBLE WYNN..LATIN CAPITAL LETTER LAMBDA WITH STROKE -A7F2..A7F4 ; XID_Continue # Lm [3] MODIFIER LETTER CAPITAL C..MODIFIER LETTER CAPITAL Q +A790..A7DC ; XID_Continue # L& [77] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN CAPITAL LETTER LAMBDA WITH STROKE +A7F1..A7F4 ; XID_Continue # Lm [4] MODIFIER LETTER CAPITAL S..MODIFIER LETTER CAPITAL Q A7F5..A7F6 ; XID_Continue # L& [2] LATIN CAPITAL LETTER REVERSED HALF H..LATIN SMALL LETTER REVERSED HALF H A7F7 ; XID_Continue # Lo LATIN EPIGRAPHIC LETTER SIDEWAYS I A7F8..A7F9 ; XID_Continue # Lm [2] MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE @@ -10044,6 +10156,7 @@ FFDA..FFDC ; XID_Continue # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HA 108F4..108F5 ; XID_Continue # Lo [2] HATRAN LETTER SHIN..HATRAN LETTER TAW 10900..10915 ; XID_Continue # Lo [22] PHOENICIAN LETTER ALF..PHOENICIAN LETTER TAU 10920..10939 ; XID_Continue # Lo [26] LYDIAN LETTER A..LYDIAN LETTER C +10940..10959 ; XID_Continue # Lo [26] SIDETIC LETTER N01..SIDETIC LETTER N26 10980..109B7 ; XID_Continue # Lo [56] MEROITIC HIEROGLYPHIC LETTER A..MEROITIC CURSIVE LETTER DA 109BE..109BF ; XID_Continue # Lo [2] MEROITIC CURSIVE LOGOGRAM RMT..MEROITIC CURSIVE LOGOGRAM IMN 10A00 ; XID_Continue # Lo KHAROSHTHI LETTER A @@ -10082,7 +10195,9 @@ FFDA..FFDC ; XID_Continue # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HA 10EAB..10EAC ; XID_Continue # Mn [2] YEZIDI COMBINING HAMZA MARK..YEZIDI COMBINING MADDA MARK 10EB0..10EB1 ; XID_Continue # Lo [2] YEZIDI LETTER LAM WITH DOT ABOVE..YEZIDI LETTER YOT WITH CIRCUMFLEX ABOVE 10EC2..10EC4 ; XID_Continue # Lo [3] ARABIC LETTER DAL WITH TWO DOTS VERTICALLY BELOW..ARABIC LETTER KAF WITH TWO DOTS VERTICALLY BELOW -10EFC..10EFF ; XID_Continue # Mn [4] ARABIC COMBINING ALEF OVERLAY..ARABIC SMALL LOW WORD MADDA +10EC5 ; XID_Continue # Lm ARABIC SMALL YEH BARREE WITH TWO DOTS BELOW +10EC6..10EC7 ; XID_Continue # Lo [2] ARABIC LETTER THIN NOON..ARABIC LETTER YEH WITH FOUR DOTS BELOW +10EFA..10EFF ; XID_Continue # Mn [6] ARABIC DOUBLE VERTICAL BAR BELOW..ARABIC SMALL LOW WORD MADDA 10F00..10F1C ; XID_Continue # Lo [29] OLD SOGDIAN LETTER ALEPH..OLD SOGDIAN LETTER FINAL TAW WITH VERTICAL TAIL 10F27 ; XID_Continue # Lo OLD SOGDIAN LIGATURE AYIN-DALETH 10F30..10F45 ; XID_Continue # Lo [22] SOGDIAN LETTER ALEPH..SOGDIAN INDEPENDENT SHIN @@ -10309,6 +10424,12 @@ FFDA..FFDC ; XID_Continue # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HA 11A98..11A99 ; XID_Continue # Mn [2] SOYOMBO GEMINATION MARK..SOYOMBO SUBJOINER 11A9D ; XID_Continue # Lo SOYOMBO MARK PLUTA 11AB0..11AF8 ; XID_Continue # Lo [73] CANADIAN SYLLABICS NATTILIK HI..PAU CIN HAU GLOTTAL STOP FINAL +11B60 ; XID_Continue # Mn SHARADA VOWEL SIGN OE +11B61 ; XID_Continue # Mc SHARADA VOWEL SIGN OOE +11B62..11B64 ; XID_Continue # Mn [3] SHARADA VOWEL SIGN UE..SHARADA VOWEL SIGN SHORT E +11B65 ; XID_Continue # Mc SHARADA VOWEL SIGN SHORT O +11B66 ; XID_Continue # Mn SHARADA VOWEL SIGN CANDRA E +11B67 ; XID_Continue # Mc SHARADA VOWEL SIGN CANDRA O 11BC0..11BE0 ; XID_Continue # Lo [33] SUNUWAR LETTER DEVI..SUNUWAR LETTER KLOKO 11BF0..11BF9 ; XID_Continue # Nd [10] SUNUWAR DIGIT ZERO..SUNUWAR DIGIT NINE 11C00..11C08 ; XID_Continue # Lo [9] BHAIKSUKI LETTER A..BHAIKSUKI LETTER VOCALIC L @@ -10349,6 +10470,10 @@ FFDA..FFDC ; XID_Continue # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HA 11D97 ; XID_Continue # Mn GUNJALA GONDI VIRAMA 11D98 ; XID_Continue # Lo GUNJALA GONDI OM 11DA0..11DA9 ; XID_Continue # Nd [10] GUNJALA GONDI DIGIT ZERO..GUNJALA GONDI DIGIT NINE +11DB0..11DD8 ; XID_Continue # Lo [41] TOLONG SIKI LETTER I..TOLONG SIKI LETTER RRH +11DD9 ; XID_Continue # Lm TOLONG SIKI SIGN SELA +11DDA..11DDB ; XID_Continue # Lo [2] TOLONG SIKI SIGN HECAKA..TOLONG SIKI UNGGA +11DE0..11DE9 ; XID_Continue # Nd [10] TOLONG SIKI DIGIT ZERO..TOLONG SIKI DIGIT NINE 11EE0..11EF2 ; XID_Continue # Lo [19] MAKASAR LETTER KA..MAKASAR ANGKA 11EF3..11EF4 ; XID_Continue # Mn [2] MAKASAR VOWEL SIGN I..MAKASAR VOWEL SIGN U 11EF5..11EF6 ; XID_Continue # Mc [2] MAKASAR VOWEL SIGN E..MAKASAR VOWEL SIGN O @@ -10399,6 +10524,8 @@ FFDA..FFDC ; XID_Continue # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HA 16D6B..16D6C ; XID_Continue # Lm [2] KIRAT RAI SIGN VIRAMA..KIRAT RAI SIGN SAAT 16D70..16D79 ; XID_Continue # Nd [10] KIRAT RAI DIGIT ZERO..KIRAT RAI DIGIT NINE 16E40..16E7F ; XID_Continue # L& [64] MEDEFAIDRIN CAPITAL LETTER M..MEDEFAIDRIN SMALL LETTER Y +16EA0..16EB8 ; XID_Continue # L& [25] BERIA ERFE CAPITAL LETTER ARKAB..BERIA ERFE CAPITAL LETTER AY +16EBB..16ED3 ; XID_Continue # L& [25] BERIA ERFE SMALL LETTER ARKAB..BERIA ERFE SMALL LETTER AY 16F00..16F4A ; XID_Continue # Lo [75] MIAO LETTER PA..MIAO LETTER RTE 16F4F ; XID_Continue # Mn MIAO SIGN CONSONANT MODIFIER BAR 16F50 ; XID_Continue # Lo MIAO LETTER NASALIZATION @@ -10409,9 +10536,11 @@ FFDA..FFDC ; XID_Continue # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HA 16FE3 ; XID_Continue # Lm OLD CHINESE ITERATION MARK 16FE4 ; XID_Continue # Mn KHITAN SMALL SCRIPT FILLER 16FF0..16FF1 ; XID_Continue # Mc [2] VIETNAMESE ALTERNATE READING MARK CA..VIETNAMESE ALTERNATE READING MARK NHAY -17000..187F7 ; XID_Continue # Lo [6136] TANGUT IDEOGRAPH-17000..TANGUT IDEOGRAPH-187F7 -18800..18CD5 ; XID_Continue # Lo [1238] TANGUT COMPONENT-001..KHITAN SMALL SCRIPT CHARACTER-18CD5 -18CFF..18D08 ; XID_Continue # Lo [10] KHITAN SMALL SCRIPT CHARACTER-18CFF..TANGUT IDEOGRAPH-18D08 +16FF2..16FF3 ; XID_Continue # Lm [2] CHINESE SMALL SIMPLIFIED ER..CHINESE SMALL TRADITIONAL ER +16FF4..16FF6 ; XID_Continue # Nl [3] YANGQIN SIGN SLOW ONE BEAT..YANGQIN SIGN SLOW TWO BEATS +17000..18CD5 ; XID_Continue # Lo [7382] TANGUT IDEOGRAPH-17000..KHITAN SMALL SCRIPT CHARACTER-18CD5 +18CFF..18D1E ; XID_Continue # Lo [32] KHITAN SMALL SCRIPT CHARACTER-18CFF..TANGUT IDEOGRAPH-18D1E +18D80..18DF2 ; XID_Continue # Lo [115] TANGUT COMPONENT-769..TANGUT COMPONENT-883 1AFF0..1AFF3 ; XID_Continue # Lm [4] KATAKANA LETTER MINNAN TONE-2..KATAKANA LETTER MINNAN TONE-5 1AFF5..1AFFB ; XID_Continue # Lm [7] KATAKANA LETTER MINNAN TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-5 1AFFD..1AFFE ; XID_Continue # Lm [2] KATAKANA LETTER MINNAN NASALIZED TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-8 @@ -10502,6 +10631,17 @@ FFDA..FFDC ; XID_Continue # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HA 1E5EE..1E5EF ; XID_Continue # Mn [2] OL ONAL SIGN MU..OL ONAL SIGN IKIR 1E5F0 ; XID_Continue # Lo OL ONAL SIGN HODDOND 1E5F1..1E5FA ; XID_Continue # Nd [10] OL ONAL DIGIT ZERO..OL ONAL DIGIT NINE +1E6C0..1E6DE ; XID_Continue # Lo [31] TAI YO LETTER LOW KO..TAI YO LETTER HIGH KVO +1E6E0..1E6E2 ; XID_Continue # Lo [3] TAI YO LETTER AA..TAI YO LETTER UE +1E6E3 ; XID_Continue # Mn TAI YO SIGN UE +1E6E4..1E6E5 ; XID_Continue # Lo [2] TAI YO LETTER U..TAI YO LETTER AE +1E6E6 ; XID_Continue # Mn TAI YO SIGN AU +1E6E7..1E6ED ; XID_Continue # Lo [7] TAI YO LETTER O..TAI YO LETTER AUE +1E6EE..1E6EF ; XID_Continue # Mn [2] TAI YO SIGN AY..TAI YO SIGN ANG +1E6F0..1E6F4 ; XID_Continue # Lo [5] TAI YO LETTER AN..TAI YO LETTER AP +1E6F5 ; XID_Continue # Mn TAI YO SIGN OM +1E6FE ; XID_Continue # Lo TAI YO SYMBOL MUEANG +1E6FF ; XID_Continue # Lm TAI YO XAM LAI 1E7E0..1E7E6 ; XID_Continue # Lo [7] ETHIOPIC SYLLABLE HHYA..ETHIOPIC SYLLABLE HHYO 1E7E8..1E7EB ; XID_Continue # Lo [4] ETHIOPIC SYLLABLE GURAGE HHWA..ETHIOPIC SYLLABLE HHWE 1E7ED..1E7EE ; XID_Continue # Lo [2] ETHIOPIC SYLLABLE GURAGE MWI..ETHIOPIC SYLLABLE GURAGE MWEE @@ -10547,17 +10687,16 @@ FFDA..FFDC ; XID_Continue # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HA 1EEAB..1EEBB ; XID_Continue # Lo [17] ARABIC MATHEMATICAL DOUBLE-STRUCK LAM..ARABIC MATHEMATICAL DOUBLE-STRUCK GHAIN 1FBF0..1FBF9 ; XID_Continue # Nd [10] SEGMENTED DIGIT ZERO..SEGMENTED DIGIT NINE 20000..2A6DF ; XID_Continue # Lo [42720] CJK UNIFIED IDEOGRAPH-20000..CJK UNIFIED IDEOGRAPH-2A6DF -2A700..2B739 ; XID_Continue # Lo [4154] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B739 -2B740..2B81D ; XID_Continue # Lo [222] CJK UNIFIED IDEOGRAPH-2B740..CJK UNIFIED IDEOGRAPH-2B81D -2B820..2CEA1 ; XID_Continue # Lo [5762] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEA1 +2A700..2B81D ; XID_Continue # Lo [4382] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B81D +2B820..2CEAD ; XID_Continue # Lo [5774] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEAD 2CEB0..2EBE0 ; XID_Continue # Lo [7473] CJK UNIFIED IDEOGRAPH-2CEB0..CJK UNIFIED IDEOGRAPH-2EBE0 2EBF0..2EE5D ; XID_Continue # Lo [622] CJK UNIFIED IDEOGRAPH-2EBF0..CJK UNIFIED IDEOGRAPH-2EE5D 2F800..2FA1D ; XID_Continue # Lo [542] CJK COMPATIBILITY IDEOGRAPH-2F800..CJK COMPATIBILITY IDEOGRAPH-2FA1D 30000..3134A ; XID_Continue # Lo [4939] CJK UNIFIED IDEOGRAPH-30000..CJK UNIFIED IDEOGRAPH-3134A -31350..323AF ; XID_Continue # Lo [4192] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-323AF +31350..33479 ; XID_Continue # Lo [8490] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-33479 E0100..E01EF ; XID_Continue # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR-256 -# Total code points: 144522 +# Total code points: 149221 # ================================================ @@ -10778,7 +10917,8 @@ E01F0..E0FFF ; Default_Ignorable_Code_Point # Cn [3600] .... -2B76..2B95 ; Pattern_Syntax # So [32] NORTH WEST TRIANGLE-HEADED ARROW TO BAR..RIGHTWARDS BLACK ARROW -2B96 ; Pattern_Syntax # Cn -2B97..2BFF ; Pattern_Syntax # So [105] SYMBOL FOR TYPE A ELECTRONICS..HELLSCHREIBER PAUSE SYMBOL +2B76..2BFF ; Pattern_Syntax # So [138] NORTH WEST TRIANGLE-HEADED ARROW TO BAR..HELLSCHREIBER PAUSE SYMBOL 2E00..2E01 ; Pattern_Syntax # Po [2] RIGHT ANGLE SUBSTITUTION MARKER..RIGHT ANGLE DOTTED SUBSTITUTION MARKER 2E02 ; Pattern_Syntax # Pi LEFT SUBSTITUTION BRACKET 2E03 ; Pattern_Syntax # Pf RIGHT SUBSTITUTION BRACKET diff --git a/src/java.base/share/data/unicodedata/PropertyValueAliases.txt b/src/java.base/share/data/unicodedata/PropertyValueAliases.txt index 97980e2d902..b92662eda28 100644 --- a/src/java.base/share/data/unicodedata/PropertyValueAliases.txt +++ b/src/java.base/share/data/unicodedata/PropertyValueAliases.txt @@ -1,6 +1,6 @@ -# PropertyValueAliases-16.0.0.txt -# Date: 2024-07-30, 19:59:00 GMT -# Copyright (c) 2024 Unicode, Inc. +# PropertyValueAliases-17.0.0.txt +# Date: 2025-06-30, 06:16:21 GMT +# © 2025 Unicode®, Inc. # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. # For terms of use and license, see https://www.unicode.org/terms_of_use.html # @@ -93,6 +93,7 @@ age; 14.0 ; V14_0 age; 15.0 ; V15_0 age; 15.1 ; V15_1 age; 16.0 ; V16_0 +age; 17.0 ; V17_0 age; NA ; Unassigned # Alphabetic (Alpha) @@ -179,6 +180,7 @@ blk; Bamum_Sup ; Bamum_Supplement blk; Bassa_Vah ; Bassa_Vah blk; Batak ; Batak blk; Bengali ; Bengali +blk; Beria_Erfe ; Beria_Erfe blk; Bhaiksuki ; Bhaiksuki blk; Block_Elements ; Block_Elements blk; Bopomofo ; Bopomofo @@ -211,6 +213,7 @@ blk; CJK_Ext_F ; CJK_Unified_Ideographs_Extension_F blk; CJK_Ext_G ; CJK_Unified_Ideographs_Extension_G blk; CJK_Ext_H ; CJK_Unified_Ideographs_Extension_H blk; CJK_Ext_I ; CJK_Unified_Ideographs_Extension_I +blk; CJK_Ext_J ; CJK_Unified_Ideographs_Extension_J blk; CJK_Radicals_Sup ; CJK_Radicals_Supplement blk; CJK_Strokes ; CJK_Strokes blk; CJK_Symbols ; CJK_Symbols_And_Punctuation @@ -360,6 +363,7 @@ blk; Misc_Math_Symbols_A ; Miscellaneous_Mathematical_Symbols_A blk; Misc_Math_Symbols_B ; Miscellaneous_Mathematical_Symbols_B blk; Misc_Pictographs ; Miscellaneous_Symbols_And_Pictographs blk; Misc_Symbols ; Miscellaneous_Symbols +blk; Misc_Symbols_Sup ; Miscellaneous_Symbols_Supplement blk; Misc_Technical ; Miscellaneous_Technical blk; Modi ; Modi blk; Modifier_Letters ; Spacing_Modifier_Letters @@ -419,9 +423,11 @@ blk; Runic ; Runic blk; Samaritan ; Samaritan blk; Saurashtra ; Saurashtra blk; Sharada ; Sharada +blk; Sharada_Sup ; Sharada_Supplement blk; Shavian ; Shavian blk; Shorthand_Format_Controls ; Shorthand_Format_Controls blk; Siddham ; Siddham +blk; Sidetic ; Sidetic blk; Sinhala ; Sinhala blk; Sinhala_Archaic_Numbers ; Sinhala_Archaic_Numbers blk; Small_Forms ; Small_Form_Variants @@ -456,12 +462,14 @@ blk; Tai_Le ; Tai_Le blk; Tai_Tham ; Tai_Tham blk; Tai_Viet ; Tai_Viet blk; Tai_Xuan_Jing ; Tai_Xuan_Jing_Symbols +blk; Tai_Yo ; Tai_Yo blk; Takri ; Takri blk; Tamil ; Tamil blk; Tamil_Sup ; Tamil_Supplement blk; Tangsa ; Tangsa blk; Tangut ; Tangut blk; Tangut_Components ; Tangut_Components +blk; Tangut_Components_Sup ; Tangut_Components_Supplement blk; Tangut_Sup ; Tangut_Supplement blk; Telugu ; Telugu blk; Thaana ; Thaana @@ -470,6 +478,7 @@ blk; Tibetan ; Tibetan blk; Tifinagh ; Tifinagh blk; Tirhuta ; Tirhuta blk; Todhri ; Todhri +blk; Tolong_Siki ; Tolong_Siki blk; Toto ; Toto blk; Transport_And_Map ; Transport_And_Map_Symbols blk; Tulu_Tigalari ; Tulu_Tigalari @@ -878,7 +887,7 @@ InPC; Bottom_And_Left ; Bottom_And_Left InPC; Bottom_And_Right ; Bottom_And_Right InPC; Left ; Left InPC; Left_And_Right ; Left_And_Right -InPC; NA ; NA +InPC; NA ; Not_Applicable InPC; Overstruck ; Overstruck InPC; Right ; Right InPC; Top ; Top @@ -1088,6 +1097,7 @@ jg ; Taw ; Taw jg ; Teh_Marbuta ; Teh_Marbuta jg ; Teh_Marbuta_Goal ; Teh_Marbuta_Goal ; Hamza_On_Heh_Goal jg ; Teth ; Teth +jg ; Thin_Noon ; Thin_Noon jg ; Thin_Yeh ; Thin_Yeh jg ; Vertical_Tail ; Vertical_Tail jg ; Waw ; Waw @@ -1131,6 +1141,7 @@ lb ; EX ; Exclamation lb ; GL ; Glue lb ; H2 ; H2 lb ; H3 ; H3 +lb ; HH ; Unambiguous_Hyphen lb ; HL ; Hebrew_Letter lb ; HY ; Hyphen lb ; ID ; Ideographic @@ -1319,6 +1330,7 @@ sc ; Bamu ; Bamum sc ; Bass ; Bassa_Vah sc ; Batk ; Batak sc ; Beng ; Bengali +sc ; Berf ; Beria_Erfe sc ; Bhks ; Bhaiksuki sc ; Bopo ; Bopomofo sc ; Brah ; Brahmi @@ -1438,6 +1450,7 @@ sc ; Sgnw ; SignWriting sc ; Shaw ; Shavian sc ; Shrd ; Sharada sc ; Sidd ; Siddham +sc ; Sidt ; Sidetic sc ; Sind ; Khudawadi sc ; Sinh ; Sinhala sc ; Sogd ; Sogdian @@ -1455,6 +1468,7 @@ sc ; Talu ; New_Tai_Lue sc ; Taml ; Tamil sc ; Tang ; Tangut sc ; Tavt ; Tai_Viet +sc ; Tayo ; Tai_Yo sc ; Telu ; Telugu sc ; Tfng ; Tifinagh sc ; Tglg ; Tagalog @@ -1464,6 +1478,7 @@ sc ; Tibt ; Tibetan sc ; Tirh ; Tirhuta sc ; Tnsa ; Tangsa sc ; Todr ; Todhri +sc ; Tols ; Tolong_Siki sc ; Toto ; Toto sc ; Tutg ; Tulu_Tigalari sc ; Ugar ; Ugaritic @@ -1705,4 +1720,16 @@ kEH_NoMirror; Y ; Yes ; T kEH_NoRotate; N ; No ; F ; False kEH_NoRotate; Y ; Yes ; T ; True +# kMandarin (cjkMandarin) + +# @missing: 0000..10FFFF; kMandarin; + +# kTotalStrokes (cjkTotalStrokes) + +# @missing: 0000..10FFFF; kTotalStrokes; + +# kUnihanCore2020 (cjkUnihanCore2020) + +# @missing: 0000..10FFFF; kUnihanCore2020; + # EOF diff --git a/src/java.base/share/data/unicodedata/ReadMe.txt b/src/java.base/share/data/unicodedata/ReadMe.txt index 81d489a0575..268f62b8a93 100644 --- a/src/java.base/share/data/unicodedata/ReadMe.txt +++ b/src/java.base/share/data/unicodedata/ReadMe.txt @@ -1,17 +1,24 @@ # Unicode Character Database -# Date: 2024-08-25 -# Copyright (c) 2024 Unicode, Inc. +# Date: 2025-08-15 +# © 2025 Unicode®, Inc. # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. # For terms of use and license, see https://www.unicode.org/terms_of_use.html # # For documentation, see the following: -# NamesList.html +# ucd/NamesList.html # UAX #38, "Unicode Han Database (Unihan)" +# UAX #42, "Unicode Character Database in XML" # UAX #44, "Unicode Character Database" # UTS #51, "Unicode Emoji" # UAX #57, "Unicode Egyptian Hieroglyph Database" # -# The UAXes and UTS #51 can be accessed at https://www.unicode.org/versions/Unicode16.0.0/ +# The UAXes and UTS #51 can be accessed at https://www.unicode.org/versions/Unicode17.0.0/ -This directory contains final data files -for the Unicode Character Database, for Version 16.0.0 of the Unicode Standard. +This directory contains the final data files +for Version 17.0.0 of the Unicode Standard. + +The "charts" subdirectory contains an archival set of +pdf code charts corresponding exactly to Version 17.0.0. + +The other subdirectories contain the data files for the +Unicode Character Database and for the synchronized Unicode Technical Standards. diff --git a/src/java.base/share/data/unicodedata/Scripts.txt b/src/java.base/share/data/unicodedata/Scripts.txt index 14d48d94162..5574fdd6ae3 100644 --- a/src/java.base/share/data/unicodedata/Scripts.txt +++ b/src/java.base/share/data/unicodedata/Scripts.txt @@ -1,6 +1,6 @@ -# Scripts-16.0.0.txt -# Date: 2024-04-30, 21:48:40 GMT -# Copyright (c) 2024 Unicode, Inc. +# Scripts-17.0.0.txt +# Date: 2025-07-24, 13:28:55 GMT +# © 2025 Unicode®, Inc. # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. # For terms of use and license, see https://www.unicode.org/terms_of_use.html # @@ -154,7 +154,7 @@ 208A..208C ; Common # Sm [3] SUBSCRIPT PLUS SIGN..SUBSCRIPT EQUALS SIGN 208D ; Common # Ps SUBSCRIPT LEFT PARENTHESIS 208E ; Common # Pe SUBSCRIPT RIGHT PARENTHESIS -20A0..20C0 ; Common # Sc [33] EURO-CURRENCY SIGN..SOM SIGN +20A0..20C1 ; Common # Sc [34] EURO-CURRENCY SIGN..SAUDI RIYAL SIGN 2100..2101 ; Common # So [2] ACCOUNT OF..ADDRESSED TO THE SUBJECT 2102 ; Common # L& DOUBLE-STRUCK CAPITAL C 2103..2106 ; Common # So [4] DEGREE CELSIUS..CADA UNA @@ -306,8 +306,7 @@ 2B45..2B46 ; Common # So [2] LEFTWARDS QUADRUPLE ARROW..RIGHTWARDS QUADRUPLE ARROW 2B47..2B4C ; Common # Sm [6] REVERSE TILDE OPERATOR ABOVE RIGHTWARDS ARROW..RIGHTWARDS ARROW ABOVE REVERSE TILDE OPERATOR 2B4D..2B73 ; Common # So [39] DOWNWARDS TRIANGLE-HEADED ZIGZAG ARROW..DOWNWARDS TRIANGLE-HEADED ARROW TO BAR -2B76..2B95 ; Common # So [32] NORTH WEST TRIANGLE-HEADED ARROW TO BAR..RIGHTWARDS BLACK ARROW -2B97..2BFF ; Common # So [105] SYMBOL FOR TYPE A ELECTRONICS..HELLSCHREIBER PAUSE SYMBOL +2B76..2BFF ; Common # So [138] NORTH WEST TRIANGLE-HEADED ARROW TO BAR..HELLSCHREIBER PAUSE SYMBOL 2E00..2E01 ; Common # Po [2] RIGHT ANGLE SUBSTITUTION MARKER..RIGHT ANGLE DOTTED SUBSTITUTION MARKER 2E02 ; Common # Pi LEFT SUBSTITUTION BRACKET 2E03 ; Common # Pf RIGHT SUBSTITUTION BRACKET @@ -524,7 +523,11 @@ FFFC..FFFD ; Common # So [2] OBJECT REPLACEMENT CHARACTER..REPLACEMENT CHAR 1BCA0..1BCA3 ; Common # Cf [4] SHORTHAND FORMAT LETTER OVERLAP..SHORTHAND FORMAT UP STEP 1CC00..1CCEF ; Common # So [240] UP-POINTING GO-KART..OUTLINED LATIN CAPITAL LETTER Z 1CCF0..1CCF9 ; Common # Nd [10] OUTLINED DIGIT ZERO..OUTLINED DIGIT NINE +1CCFA..1CCFC ; Common # So [3] SNAKE SYMBOL..NOSE SYMBOL 1CD00..1CEB3 ; Common # So [436] BLOCK OCTANT-3..BLACK RIGHT TRIANGLE CARET +1CEBA..1CED0 ; Common # So [23] FRAGILE SYMBOL..LEUKOTHEA +1CEE0..1CEEF ; Common # So [16] GEOMANTIC FIGURE POPULUS..GEOMANTIC FIGURE VIA +1CEF0 ; Common # Sm MEDIUM SMALL WHITE CIRCLE WITH HORIZONTAL BAR 1CF50..1CFC3 ; Common # So [116] ZNAMENNY NEUME KRYUK..ZNAMENNY NEUME PAUK 1D000..1D0F5 ; Common # So [246] BYZANTINE MUSICAL SYMBOL PSILI..BYZANTINE MUSICAL SYMBOL GORGON NEO KATO 1D100..1D126 ; Common # So [39] MUSICAL SYMBOL SINGLE BARLINE..MUSICAL SYMBOL DRUM CLEF-2 @@ -605,11 +608,10 @@ FFFC..FFFD ; Common # So [2] OBJECT REPLACEMENT CHARACTER..REPLACEMENT CHAR 1F260..1F265 ; Common # So [6] ROUNDED SYMBOL FOR FU..ROUNDED SYMBOL FOR CAI 1F300..1F3FA ; Common # So [251] CYCLONE..AMPHORA 1F3FB..1F3FF ; Common # Sk [5] EMOJI MODIFIER FITZPATRICK TYPE-1-2..EMOJI MODIFIER FITZPATRICK TYPE-6 -1F400..1F6D7 ; Common # So [728] RAT..ELEVATOR +1F400..1F6D8 ; Common # So [729] RAT..LANDSLIDE 1F6DC..1F6EC ; Common # So [17] WIRELESS..AIRPLANE ARRIVING 1F6F0..1F6FC ; Common # So [13] SATELLITE..ROLLER SKATE -1F700..1F776 ; Common # So [119] ALCHEMICAL SYMBOL FOR QUINTESSENCE..LUNAR ECLIPSE -1F77B..1F7D9 ; Common # So [95] HAUMEA..NINE POINTED WHITE STAR +1F700..1F7D9 ; Common # So [218] ALCHEMICAL SYMBOL FOR QUINTESSENCE..NINE POINTED WHITE STAR 1F7E0..1F7EB ; Common # So [12] LARGE ORANGE CIRCLE..LARGE BROWN SQUARE 1F7F0 ; Common # So HEAVY EQUALS SIGN 1F800..1F80B ; Common # So [12] LEFTWARDS ARROW WITH SMALL TRIANGLE ARROWHEAD..DOWNWARDS ARROW WITH LARGE TRIANGLE ARROWHEAD @@ -619,21 +621,24 @@ FFFC..FFFD ; Common # So [2] OBJECT REPLACEMENT CHARACTER..REPLACEMENT CHAR 1F890..1F8AD ; Common # So [30] LEFTWARDS TRIANGLE ARROWHEAD..WHITE ARROW SHAFT WIDTH TWO THIRDS 1F8B0..1F8BB ; Common # So [12] ARROW POINTING UPWARDS THEN NORTH WEST..SOUTH WEST ARROW FROM BAR 1F8C0..1F8C1 ; Common # So [2] LEFTWARDS ARROW FROM DOWNWARDS ARROW..RIGHTWARDS ARROW FROM DOWNWARDS ARROW -1F900..1FA53 ; Common # So [340] CIRCLED CROSS FORMEE WITH FOUR DOTS..BLACK CHESS KNIGHT-BISHOP +1F8D0..1F8D8 ; Common # Sm [9] LONG RIGHTWARDS ARROW OVER LONG LEFTWARDS ARROW..LONG LEFT RIGHT ARROW WITH DEPENDENT LOBE +1F900..1FA57 ; Common # So [344] CIRCLED CROSS FORMEE WITH FOUR DOTS..BLACK CHESS ALFIL 1FA60..1FA6D ; Common # So [14] XIANGQI RED GENERAL..XIANGQI BLACK SOLDIER 1FA70..1FA7C ; Common # So [13] BALLET SHOES..CRUTCH -1FA80..1FA89 ; Common # So [10] YO-YO..HARP -1FA8F..1FAC6 ; Common # So [56] SHOVEL..FINGERPRINT -1FACE..1FADC ; Common # So [15] MOOSE..ROOT VEGETABLE -1FADF..1FAE9 ; Common # So [11] SPLATTER..FACE WITH BAGS UNDER EYES -1FAF0..1FAF8 ; Common # So [9] HAND WITH INDEX FINGER AND THUMB CROSSED..RIGHTWARDS PUSHING HAND +1FA80..1FA8A ; Common # So [11] YO-YO..TROMBONE +1FA8E..1FAC6 ; Common # So [57] TREASURE CHEST..FINGERPRINT +1FAC8 ; Common # So HAIRY CREATURE +1FACD..1FADC ; Common # So [16] ORCA..ROOT VEGETABLE +1FADF..1FAEA ; Common # So [12] SPLATTER..DISTORTED FACE +1FAEF..1FAF8 ; Common # So [10] FIGHT CLOUD..RIGHTWARDS PUSHING HAND 1FB00..1FB92 ; Common # So [147] BLOCK SEXTANT-1..UPPER HALF INVERSE MEDIUM SHADE AND LOWER HALF BLOCK 1FB94..1FBEF ; Common # So [92] LEFT HALF INVERSE MEDIUM SHADE AND RIGHT HALF BLOCK..TOP LEFT JUSTIFIED LOWER RIGHT QUARTER BLACK CIRCLE 1FBF0..1FBF9 ; Common # Nd [10] SEGMENTED DIGIT ZERO..SEGMENTED DIGIT NINE +1FBFA ; Common # So ALARM BELL SYMBOL E0001 ; Common # Cf LANGUAGE TAG E0020..E007F ; Common # Cf [96] TAG SPACE..CANCEL TAG -# Total code points: 9053 +# Total code points: 9123 # ================================================ @@ -648,8 +653,8 @@ E0020..E007F ; Common # Cf [96] TAG SPACE..CANCEL TAG 01BC..01BF ; Latin # L& [4] LATIN CAPITAL LETTER TONE FIVE..LATIN LETTER WYNN 01C0..01C3 ; Latin # Lo [4] LATIN LETTER DENTAL CLICK..LATIN LETTER RETROFLEX CLICK 01C4..0293 ; Latin # L& [208] LATIN CAPITAL LETTER DZ WITH CARON..LATIN SMALL LETTER EZH WITH CURL -0294 ; Latin # Lo LATIN LETTER GLOTTAL STOP -0295..02AF ; Latin # L& [27] LATIN LETTER PHARYNGEAL VOICED FRICATIVE..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL +0294..0295 ; Latin # Lo [2] LATIN LETTER GLOTTAL STOP..LATIN LETTER PHARYNGEAL VOICED FRICATIVE +0296..02AF ; Latin # L& [26] LATIN LETTER INVERTED GLOTTAL STOP..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL 02B0..02B8 ; Latin # Lm [9] MODIFIER LETTER SMALL H..MODIFIER LETTER SMALL Y 02E0..02E4 ; Latin # Lm [5] MODIFIER LETTER SMALL GAMMA..MODIFIER LETTER SMALL REVERSED GLOTTAL STOP 1D00..1D25 ; Latin # L& [38] LATIN LETTER SMALL CAPITAL A..LATIN LETTER AIN @@ -676,11 +681,8 @@ A770 ; Latin # Lm MODIFIER LETTER US A771..A787 ; Latin # L& [23] LATIN SMALL LETTER DUM..LATIN SMALL LETTER INSULAR T A78B..A78E ; Latin # L& [4] LATIN CAPITAL LETTER SALTILLO..LATIN SMALL LETTER L WITH RETROFLEX HOOK AND BELT A78F ; Latin # Lo LATIN LETTER SINOLOGICAL DOT -A790..A7CD ; Latin # L& [62] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN SMALL LETTER S WITH DIAGONAL STROKE -A7D0..A7D1 ; Latin # L& [2] LATIN CAPITAL LETTER CLOSED INSULAR G..LATIN SMALL LETTER CLOSED INSULAR G -A7D3 ; Latin # L& LATIN SMALL LETTER DOUBLE THORN -A7D5..A7DC ; Latin # L& [8] LATIN SMALL LETTER DOUBLE WYNN..LATIN CAPITAL LETTER LAMBDA WITH STROKE -A7F2..A7F4 ; Latin # Lm [3] MODIFIER LETTER CAPITAL C..MODIFIER LETTER CAPITAL Q +A790..A7DC ; Latin # L& [77] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN CAPITAL LETTER LAMBDA WITH STROKE +A7F1..A7F4 ; Latin # Lm [4] MODIFIER LETTER CAPITAL S..MODIFIER LETTER CAPITAL Q A7F5..A7F6 ; Latin # L& [2] LATIN CAPITAL LETTER REVERSED HALF H..LATIN SMALL LETTER REVERSED HALF H A7F7 ; Latin # Lo LATIN EPIGRAPHIC LETTER SIDEWAYS I A7F8..A7F9 ; Latin # Lm [2] MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE @@ -702,7 +704,7 @@ FF41..FF5A ; Latin # L& [26] FULLWIDTH LATIN SMALL LETTER A..FULLWIDTH LATIN 1DF0B..1DF1E ; Latin # L& [20] LATIN SMALL LETTER ESH WITH DOUBLE BAR..LATIN SMALL LETTER S WITH CURL 1DF25..1DF2A ; Latin # L& [6] LATIN SMALL LETTER D WITH MID-HEIGHT LEFT HOOK..LATIN SMALL LETTER T WITH MID-HEIGHT LEFT HOOK -# Total code points: 1487 +# Total code points: 1492 # ================================================ @@ -869,7 +871,7 @@ FB46..FB4F ; Hebrew # Lo [10] HEBREW LETTER TSADI WITH DAGESH..HEBREW LIGATU 0750..077F ; Arabic # Lo [48] ARABIC LETTER BEH WITH THREE DOTS HORIZONTALLY BELOW..ARABIC LETTER KAF WITH TWO DOTS ABOVE 0870..0887 ; Arabic # Lo [24] ARABIC LETTER ALEF WITH ATTACHED FATHA..ARABIC BASELINE ROUND DOT 0888 ; Arabic # Sk ARABIC RAISED ROUND DOT -0889..088E ; Arabic # Lo [6] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC VERTICAL TAIL +0889..088F ; Arabic # Lo [7] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC LETTER NOON WITH RING ABOVE 0890..0891 ; Arabic # Cf [2] ARABIC POUND MARK ABOVE..ARABIC PIASTRE MARK ABOVE 0897..089F ; Arabic # Mn [9] ARABIC PEPET..ARABIC HALF MADDA OVER MADDA 08A0..08C8 ; Arabic # Lo [41] ARABIC LETTER BEH WITH SMALL V BELOW..ARABIC LETTER GRAF @@ -878,11 +880,13 @@ FB46..FB4F ; Hebrew # Lo [10] HEBREW LETTER TSADI WITH DAGESH..HEBREW LIGATU 08E3..08FF ; Arabic # Mn [29] ARABIC TURNED DAMMA BELOW..ARABIC MARK SIDEWAYS NOON GHUNNA FB50..FBB1 ; Arabic # Lo [98] ARABIC LETTER ALEF WASLA ISOLATED FORM..ARABIC LETTER YEH BARREE WITH HAMZA ABOVE FINAL FORM FBB2..FBC2 ; Arabic # Sk [17] ARABIC SYMBOL DOT ABOVE..ARABIC SYMBOL WASLA ABOVE +FBC3..FBD2 ; Arabic # So [16] ARABIC LIGATURE JALLA WA-ALAA..ARABIC LIGATURE ALAYHI AR-RAHMAH FBD3..FD3D ; Arabic # Lo [363] ARABIC LETTER NG ISOLATED FORM..ARABIC LIGATURE ALEF WITH FATHATAN ISOLATED FORM FD40..FD4F ; Arabic # So [16] ARABIC LIGATURE RAHIMAHU ALLAAH..ARABIC LIGATURE RAHIMAHUM ALLAAH FD50..FD8F ; Arabic # Lo [64] ARABIC LIGATURE TEH WITH JEEM WITH MEEM INITIAL FORM..ARABIC LIGATURE MEEM WITH KHAH WITH MEEM INITIAL FORM +FD90..FD91 ; Arabic # So [2] ARABIC LIGATURE RAHMATU ALLAAHI ALAYH..ARABIC LIGATURE RAHMATU ALLAAHI ALAYHAA FD92..FDC7 ; Arabic # Lo [54] ARABIC LIGATURE MEEM WITH JEEM WITH KHAH INITIAL FORM..ARABIC LIGATURE NOON WITH JEEM WITH YEH FINAL FORM -FDCF ; Arabic # So ARABIC LIGATURE SALAAMUHU ALAYNAA +FDC8..FDCF ; Arabic # So [8] ARABIC LIGATURE RAHIMAHU ALLAAH TAAALAA..ARABIC LIGATURE SALAAMUHU ALAYNAA FDF0..FDFB ; Arabic # Lo [12] ARABIC LIGATURE SALLA USED AS KORANIC STOP SIGN ISOLATED FORM..ARABIC LIGATURE JALLAJALALOUHOU FDFC ; Arabic # Sc RIAL SIGN FDFD..FDFF ; Arabic # So [3] ARABIC LIGATURE BISMILLAH AR-RAHMAN AR-RAHEEM..ARABIC LIGATURE AZZA WA JALL @@ -890,7 +894,11 @@ FE70..FE74 ; Arabic # Lo [5] ARABIC FATHATAN ISOLATED FORM..ARABIC KASRATAN FE76..FEFC ; Arabic # Lo [135] ARABIC FATHA ISOLATED FORM..ARABIC LIGATURE LAM WITH ALEF FINAL FORM 10E60..10E7E ; Arabic # No [31] RUMI DIGIT ONE..RUMI FRACTION TWO THIRDS 10EC2..10EC4 ; Arabic # Lo [3] ARABIC LETTER DAL WITH TWO DOTS VERTICALLY BELOW..ARABIC LETTER KAF WITH TWO DOTS VERTICALLY BELOW -10EFC..10EFF ; Arabic # Mn [4] ARABIC COMBINING ALEF OVERLAY..ARABIC SMALL LOW WORD MADDA +10EC5 ; Arabic # Lm ARABIC SMALL YEH BARREE WITH TWO DOTS BELOW +10EC6..10EC7 ; Arabic # Lo [2] ARABIC LETTER THIN NOON..ARABIC LETTER YEH WITH FOUR DOTS BELOW +10ED0 ; Arabic # Po ARABIC BIBLICAL END OF VERSE +10ED1..10ED8 ; Arabic # So [8] ARABIC LIGATURE ALAYHAA AS-SALAATU WAS-SALAAM..ARABIC LIGATURE NAWWARA ALLAAHU MARQADAH +10EFA..10EFF ; Arabic # Mn [6] ARABIC DOUBLE VERTICAL BAR BELOW..ARABIC SMALL LOW WORD MADDA 1EE00..1EE03 ; Arabic # Lo [4] ARABIC MATHEMATICAL ALEF..ARABIC MATHEMATICAL DAL 1EE05..1EE1F ; Arabic # Lo [27] ARABIC MATHEMATICAL WAW..ARABIC MATHEMATICAL DOTLESS QAF 1EE21..1EE22 ; Arabic # Lo [2] ARABIC MATHEMATICAL INITIAL BEH..ARABIC MATHEMATICAL INITIAL JEEM @@ -926,7 +934,7 @@ FE76..FEFC ; Arabic # Lo [135] ARABIC FATHA ISOLATED FORM..ARABIC LIGATURE LA 1EEAB..1EEBB ; Arabic # Lo [17] ARABIC MATHEMATICAL DOUBLE-STRUCK LAM..ARABIC MATHEMATICAL DOUBLE-STRUCK GHAIN 1EEF0..1EEF1 ; Arabic # Sm [2] ARABIC MATHEMATICAL OPERATOR MEEM WITH HAH WITH TATWEEL..ARABIC MATHEMATICAL OPERATOR HAH WITH DAL -# Total code points: 1373 +# Total code points: 1413 # ================================================ @@ -1155,7 +1163,7 @@ A8FF ; Devanagari # Mn DEVANAGARI VOWEL SIGN AY 0C4A..0C4D ; Telugu # Mn [4] TELUGU VOWEL SIGN O..TELUGU SIGN VIRAMA 0C55..0C56 ; Telugu # Mn [2] TELUGU LENGTH MARK..TELUGU AI LENGTH MARK 0C58..0C5A ; Telugu # Lo [3] TELUGU LETTER TSA..TELUGU LETTER RRRA -0C5D ; Telugu # Lo TELUGU LETTER NAKAARA POLLU +0C5C..0C5D ; Telugu # Lo [2] TELUGU ARCHAIC SHRII..TELUGU LETTER NAKAARA POLLU 0C60..0C61 ; Telugu # Lo [2] TELUGU LETTER VOCALIC RR..TELUGU LETTER VOCALIC LL 0C62..0C63 ; Telugu # Mn [2] TELUGU VOWEL SIGN VOCALIC L..TELUGU VOWEL SIGN VOCALIC LL 0C66..0C6F ; Telugu # Nd [10] TELUGU DIGIT ZERO..TELUGU DIGIT NINE @@ -1163,7 +1171,7 @@ A8FF ; Devanagari # Mn DEVANAGARI VOWEL SIGN AY 0C78..0C7E ; Telugu # No [7] TELUGU FRACTION DIGIT ZERO FOR ODD POWERS OF FOUR..TELUGU FRACTION DIGIT THREE FOR EVEN POWERS OF FOUR 0C7F ; Telugu # So TELUGU SIGN TUUMU -# Total code points: 100 +# Total code points: 101 # ================================================ @@ -1186,14 +1194,14 @@ A8FF ; Devanagari # Mn DEVANAGARI VOWEL SIGN AY 0CCA..0CCB ; Kannada # Mc [2] KANNADA VOWEL SIGN O..KANNADA VOWEL SIGN OO 0CCC..0CCD ; Kannada # Mn [2] KANNADA VOWEL SIGN AU..KANNADA SIGN VIRAMA 0CD5..0CD6 ; Kannada # Mc [2] KANNADA LENGTH MARK..KANNADA AI LENGTH MARK -0CDD..0CDE ; Kannada # Lo [2] KANNADA LETTER NAKAARA POLLU..KANNADA LETTER FA +0CDC..0CDE ; Kannada # Lo [3] KANNADA ARCHAIC SHRII..KANNADA LETTER FA 0CE0..0CE1 ; Kannada # Lo [2] KANNADA LETTER VOCALIC RR..KANNADA LETTER VOCALIC LL 0CE2..0CE3 ; Kannada # Mn [2] KANNADA VOWEL SIGN VOCALIC L..KANNADA VOWEL SIGN VOCALIC LL 0CE6..0CEF ; Kannada # Nd [10] KANNADA DIGIT ZERO..KANNADA DIGIT NINE 0CF1..0CF2 ; Kannada # Lo [2] KANNADA SIGN JIHVAMULIYA..KANNADA SIGN UPADHMANIYA 0CF3 ; Kannada # Mc KANNADA SIGN COMBINING ANUSVARA ABOVE RIGHT -# Total code points: 91 +# Total code points: 92 # ================================================ @@ -1594,17 +1602,18 @@ FA70..FAD9 ; Han # Lo [106] CJK COMPATIBILITY IDEOGRAPH-FA70..CJK COMPATIBILI 16FE2 ; Han # Po OLD CHINESE HOOK MARK 16FE3 ; Han # Lm OLD CHINESE ITERATION MARK 16FF0..16FF1 ; Han # Mc [2] VIETNAMESE ALTERNATE READING MARK CA..VIETNAMESE ALTERNATE READING MARK NHAY +16FF2..16FF3 ; Han # Lm [2] CHINESE SMALL SIMPLIFIED ER..CHINESE SMALL TRADITIONAL ER +16FF4..16FF6 ; Han # Nl [3] YANGQIN SIGN SLOW ONE BEAT..YANGQIN SIGN SLOW TWO BEATS 20000..2A6DF ; Han # Lo [42720] CJK UNIFIED IDEOGRAPH-20000..CJK UNIFIED IDEOGRAPH-2A6DF -2A700..2B739 ; Han # Lo [4154] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B739 -2B740..2B81D ; Han # Lo [222] CJK UNIFIED IDEOGRAPH-2B740..CJK UNIFIED IDEOGRAPH-2B81D -2B820..2CEA1 ; Han # Lo [5762] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEA1 +2A700..2B81D ; Han # Lo [4382] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B81D +2B820..2CEAD ; Han # Lo [5774] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEAD 2CEB0..2EBE0 ; Han # Lo [7473] CJK UNIFIED IDEOGRAPH-2CEB0..CJK UNIFIED IDEOGRAPH-2EBE0 2EBF0..2EE5D ; Han # Lo [622] CJK UNIFIED IDEOGRAPH-2EBF0..CJK UNIFIED IDEOGRAPH-2EE5D 2F800..2FA1D ; Han # Lo [542] CJK COMPATIBILITY IDEOGRAPH-2F800..CJK COMPATIBILITY IDEOGRAPH-2FA1D 30000..3134A ; Han # Lo [4939] CJK UNIFIED IDEOGRAPH-30000..CJK UNIFIED IDEOGRAPH-3134A -31350..323AF ; Han # Lo [4192] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-323AF +31350..33479 ; Han # Lo [8490] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-33479 -# Total code points: 99030 +# Total code points: 103351 # ================================================ @@ -1647,7 +1656,8 @@ A490..A4C6 ; Yi # So [55] YI RADICAL QOT..YI RADICAL KE 0951..0954 ; Inherited # Mn [4] DEVANAGARI STRESS SIGN UDATTA..DEVANAGARI ACUTE ACCENT 1AB0..1ABD ; Inherited # Mn [14] COMBINING DOUBLED CIRCUMFLEX ACCENT..COMBINING PARENTHESES BELOW 1ABE ; Inherited # Me COMBINING PARENTHESES OVERLAY -1ABF..1ACE ; Inherited # Mn [16] COMBINING LATIN SMALL LETTER W BELOW..COMBINING LATIN SMALL LETTER INSULAR T +1ABF..1ADD ; Inherited # Mn [31] COMBINING LATIN SMALL LETTER W BELOW..COMBINING DOT-AND-RING BELOW +1AE0..1AEB ; Inherited # Mn [12] COMBINING LEFT TACK ABOVE..COMBINING DOUBLE RIGHTWARDS ARROW ABOVE 1CD0..1CD2 ; Inherited # Mn [3] VEDIC TONE KARSHANA..VEDIC TONE PRENKHA 1CD4..1CE0 ; Inherited # Mn [13] VEDIC SIGN YAJURVEDIC MIDLINE SVARITA..VEDIC TONE RIGVEDIC KASHMIRI INDEPENDENT SVARITA 1CE2..1CE8 ; Inherited # Mn [7] VEDIC SIGN VISARGA SVARITA..VEDIC SIGN VISARGA ANUDATTA WITH TAIL @@ -1676,7 +1686,7 @@ FE20..FE2D ; Inherited # Mn [14] COMBINING LIGATURE LEFT HALF..COMBINING CON 1D1AA..1D1AD ; Inherited # Mn [4] MUSICAL SYMBOL COMBINING DOWN BOW..MUSICAL SYMBOL COMBINING SNAP PIZZICATO E0100..E01EF ; Inherited # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR-256 -# Total code points: 657 +# Total code points: 684 # ================================================ @@ -2347,8 +2357,14 @@ ABF0..ABF9 ; Meetei_Mayek # Nd [10] MEETEI MAYEK DIGIT ZERO..MEETEI MAYEK DI 111DB ; Sharada # Po SHARADA SIGN SIDDHAM 111DC ; Sharada # Lo SHARADA HEADSTROKE 111DD..111DF ; Sharada # Po [3] SHARADA CONTINUATION SIGN..SHARADA SECTION MARK-2 +11B60 ; Sharada # Mn SHARADA VOWEL SIGN OE +11B61 ; Sharada # Mc SHARADA VOWEL SIGN OOE +11B62..11B64 ; Sharada # Mn [3] SHARADA VOWEL SIGN UE..SHARADA VOWEL SIGN SHORT E +11B65 ; Sharada # Mc SHARADA VOWEL SIGN SHORT O +11B66 ; Sharada # Mn SHARADA VOWEL SIGN CANDRA E +11B67 ; Sharada # Mc SHARADA VOWEL SIGN CANDRA O -# Total code points: 96 +# Total code points: 104 # ================================================ @@ -2756,11 +2772,11 @@ ABF0..ABF9 ; Meetei_Mayek # Nd [10] MEETEI MAYEK DIGIT ZERO..MEETEI MAYEK DI # ================================================ 16FE0 ; Tangut # Lm TANGUT ITERATION MARK -17000..187F7 ; Tangut # Lo [6136] TANGUT IDEOGRAPH-17000..TANGUT IDEOGRAPH-187F7 -18800..18AFF ; Tangut # Lo [768] TANGUT COMPONENT-001..TANGUT COMPONENT-768 -18D00..18D08 ; Tangut # Lo [9] TANGUT IDEOGRAPH-18D00..TANGUT IDEOGRAPH-18D08 +17000..18AFF ; Tangut # Lo [6912] TANGUT IDEOGRAPH-17000..TANGUT COMPONENT-768 +18D00..18D1E ; Tangut # Lo [31] TANGUT IDEOGRAPH-18D00..TANGUT IDEOGRAPH-18D1E +18D80..18DF2 ; Tangut # Lo [115] TANGUT COMPONENT-769..TANGUT COMPONENT-883 -# Total code points: 6914 +# Total code points: 7059 # ================================================ @@ -3125,4 +3141,42 @@ ABF0..ABF9 ; Meetei_Mayek # Nd [10] MEETEI MAYEK DIGIT ZERO..MEETEI MAYEK DI # Total code points: 80 +# ================================================ + +10940..10959 ; Sidetic # Lo [26] SIDETIC LETTER N01..SIDETIC LETTER N26 + +# Total code points: 26 + +# ================================================ + +1E6C0..1E6DE ; Tai_Yo # Lo [31] TAI YO LETTER LOW KO..TAI YO LETTER HIGH KVO +1E6E0..1E6E2 ; Tai_Yo # Lo [3] TAI YO LETTER AA..TAI YO LETTER UE +1E6E3 ; Tai_Yo # Mn TAI YO SIGN UE +1E6E4..1E6E5 ; Tai_Yo # Lo [2] TAI YO LETTER U..TAI YO LETTER AE +1E6E6 ; Tai_Yo # Mn TAI YO SIGN AU +1E6E7..1E6ED ; Tai_Yo # Lo [7] TAI YO LETTER O..TAI YO LETTER AUE +1E6EE..1E6EF ; Tai_Yo # Mn [2] TAI YO SIGN AY..TAI YO SIGN ANG +1E6F0..1E6F4 ; Tai_Yo # Lo [5] TAI YO LETTER AN..TAI YO LETTER AP +1E6F5 ; Tai_Yo # Mn TAI YO SIGN OM +1E6FE ; Tai_Yo # Lo TAI YO SYMBOL MUEANG +1E6FF ; Tai_Yo # Lm TAI YO XAM LAI + +# Total code points: 55 + +# ================================================ + +11DB0..11DD8 ; Tolong_Siki # Lo [41] TOLONG SIKI LETTER I..TOLONG SIKI LETTER RRH +11DD9 ; Tolong_Siki # Lm TOLONG SIKI SIGN SELA +11DDA..11DDB ; Tolong_Siki # Lo [2] TOLONG SIKI SIGN HECAKA..TOLONG SIKI UNGGA +11DE0..11DE9 ; Tolong_Siki # Nd [10] TOLONG SIKI DIGIT ZERO..TOLONG SIKI DIGIT NINE + +# Total code points: 54 + +# ================================================ + +16EA0..16EB8 ; Beria_Erfe # L& [25] BERIA ERFE CAPITAL LETTER ARKAB..BERIA ERFE CAPITAL LETTER AY +16EBB..16ED3 ; Beria_Erfe # L& [25] BERIA ERFE SMALL LETTER ARKAB..BERIA ERFE SMALL LETTER AY + +# Total code points: 50 + # EOF diff --git a/src/java.base/share/data/unicodedata/SpecialCasing.txt b/src/java.base/share/data/unicodedata/SpecialCasing.txt index 74700b5d321..1013344a6f2 100644 --- a/src/java.base/share/data/unicodedata/SpecialCasing.txt +++ b/src/java.base/share/data/unicodedata/SpecialCasing.txt @@ -1,6 +1,6 @@ -# SpecialCasing-16.0.0.txt -# Date: 2024-05-10, 22:49:00 GMT -# Copyright (c) 2024 Unicode, Inc. +# SpecialCasing-17.0.0.txt +# Date: 2025-07-31, 22:11:55 GMT +# © 2025 Unicode®, Inc. # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. # For terms of use and license, see https://www.unicode.org/terms_of_use.html # @@ -47,8 +47,8 @@ # # A language ID is defined by BCP 47, with '-' and '_' treated equivalently. # -# A casing context for a character is defined by Section 3.13 Default Case Algorithms -# of The Unicode Standard. +# A casing context for a character is defined in the +# "Conformance" / "Default Case Algorithms" section of the core specification. # # Parsers of this file must be prepared to deal with future additions to this format: # * Additional contexts @@ -57,6 +57,10 @@ # ================================================================================ # Unconditional mappings +# The mappings in this section are not language-sensitive nor context-sensitive. +# +# Note that comments provide additional information but +# do not modify the case mapping algorithms in the core specification, chapter 3. # ================================================================================ # The German es-zed is special--the normal mapping is to SS. diff --git a/src/java.base/share/data/unicodedata/UnicodeData.txt b/src/java.base/share/data/unicodedata/UnicodeData.txt index 64258a37395..fca68e3e154 100644 --- a/src/java.base/share/data/unicodedata/UnicodeData.txt +++ b/src/java.base/share/data/unicodedata/UnicodeData.txt @@ -659,7 +659,7 @@ 0292;LATIN SMALL LETTER EZH;Ll;0;L;;;;;N;LATIN SMALL LETTER YOGH;;01B7;;01B7 0293;LATIN SMALL LETTER EZH WITH CURL;Ll;0;L;;;;;N;LATIN SMALL LETTER YOGH CURL;;;; 0294;LATIN LETTER GLOTTAL STOP;Lo;0;L;;;;;N;;;;; -0295;LATIN LETTER PHARYNGEAL VOICED FRICATIVE;Ll;0;L;;;;;N;LATIN LETTER REVERSED GLOTTAL STOP;;;; +0295;LATIN LETTER PHARYNGEAL VOICED FRICATIVE;Lo;0;L;;;;;N;LATIN LETTER REVERSED GLOTTAL STOP;;;; 0296;LATIN LETTER INVERTED GLOTTAL STOP;Ll;0;L;;;;;N;;;;; 0297;LATIN LETTER STRETCHED C;Ll;0;L;;;;;N;;;;; 0298;LATIN LETTER BILABIAL CLICK;Ll;0;L;;;;;N;LATIN LETTER BULLSEYE;;;; @@ -2121,6 +2121,7 @@ 088C;ARABIC LETTER TAH WITH THREE DOTS BELOW;Lo;0;AL;;;;;N;;;;; 088D;ARABIC LETTER KEHEH WITH TWO DOTS VERTICALLY BELOW;Lo;0;AL;;;;;N;;;;; 088E;ARABIC VERTICAL TAIL;Lo;0;AL;;;;;N;;;;; +088F;ARABIC LETTER NOON WITH RING ABOVE;Lo;0;AL;;;;;N;;;;; 0890;ARABIC POUND MARK ABOVE;Cf;0;AN;;;;;N;;;;; 0891;ARABIC PIASTRE MARK ABOVE;Cf;0;AN;;;;;N;;;;; 0897;ARABIC PEPET;Mn;230;NSM;;;;;N;;;;; @@ -2862,6 +2863,7 @@ 0C58;TELUGU LETTER TSA;Lo;0;L;;;;;N;;;;; 0C59;TELUGU LETTER DZA;Lo;0;L;;;;;N;;;;; 0C5A;TELUGU LETTER RRRA;Lo;0;L;;;;;N;;;;; +0C5C;TELUGU ARCHAIC SHRII;Lo;0;L;;;;;N;;;;; 0C5D;TELUGU LETTER NAKAARA POLLU;Lo;0;L;;;;;N;;;;; 0C60;TELUGU LETTER VOCALIC RR;Lo;0;L;;;;;N;;;;; 0C61;TELUGU LETTER VOCALIC LL;Lo;0;L;;;;;N;;;;; @@ -2958,6 +2960,7 @@ 0CCD;KANNADA SIGN VIRAMA;Mn;9;NSM;;;;;N;;;;; 0CD5;KANNADA LENGTH MARK;Mc;0;L;;;;;N;;;;; 0CD6;KANNADA AI LENGTH MARK;Mc;0;L;;;;;N;;;;; +0CDC;KANNADA ARCHAIC SHRII;Lo;0;L;;;;;N;;;;; 0CDD;KANNADA LETTER NAKAARA POLLU;Lo;0;L;;;;;N;;;;; 0CDE;KANNADA LETTER FA;Lo;0;L;;;;;N;;;;; 0CE0;KANNADA LETTER VOCALIC RR;Lo;0;L;;;;;N;;;;; @@ -6137,6 +6140,33 @@ 1ACC;COMBINING LATIN SMALL LETTER INSULAR G;Mn;230;NSM;;;;;N;;;;; 1ACD;COMBINING LATIN SMALL LETTER INSULAR R;Mn;230;NSM;;;;;N;;;;; 1ACE;COMBINING LATIN SMALL LETTER INSULAR T;Mn;230;NSM;;;;;N;;;;; +1ACF;COMBINING DOUBLE CARON;Mn;230;NSM;;;;;N;;;;; +1AD0;COMBINING VERTICAL-LINE-ACUTE;Mn;230;NSM;;;;;N;;;;; +1AD1;COMBINING GRAVE-VERTICAL-LINE;Mn;230;NSM;;;;;N;;;;; +1AD2;COMBINING VERTICAL-LINE-GRAVE;Mn;230;NSM;;;;;N;;;;; +1AD3;COMBINING ACUTE-VERTICAL-LINE;Mn;230;NSM;;;;;N;;;;; +1AD4;COMBINING VERTICAL-LINE-MACRON;Mn;230;NSM;;;;;N;;;;; +1AD5;COMBINING MACRON-VERTICAL-LINE;Mn;230;NSM;;;;;N;;;;; +1AD6;COMBINING VERTICAL-LINE-ACUTE-GRAVE;Mn;230;NSM;;;;;N;;;;; +1AD7;COMBINING VERTICAL-LINE-GRAVE-ACUTE;Mn;230;NSM;;;;;N;;;;; +1AD8;COMBINING MACRON-ACUTE-GRAVE;Mn;230;NSM;;;;;N;;;;; +1AD9;COMBINING SHARP SIGN;Mn;230;NSM;;;;;N;;;;; +1ADA;COMBINING FLAT SIGN;Mn;230;NSM;;;;;N;;;;; +1ADB;COMBINING DOWN TACK ABOVE;Mn;230;NSM;;;;;N;;;;; +1ADC;COMBINING DIAERESIS WITH RAISED LEFT DOT;Mn;230;NSM;;;;;N;;;;; +1ADD;COMBINING DOT-AND-RING BELOW;Mn;220;NSM;;;;;N;;;;; +1AE0;COMBINING LEFT TACK ABOVE;Mn;230;NSM;;;;;N;;;;; +1AE1;COMBINING RIGHT TACK ABOVE;Mn;230;NSM;;;;;N;;;;; +1AE2;COMBINING MINUS SIGN ABOVE;Mn;230;NSM;;;;;N;;;;; +1AE3;COMBINING INVERTED BRIDGE ABOVE;Mn;230;NSM;;;;;N;;;;; +1AE4;COMBINING SQUARE ABOVE;Mn;230;NSM;;;;;N;;;;; +1AE5;COMBINING SEAGULL ABOVE;Mn;230;NSM;;;;;N;;;;; +1AE6;COMBINING DOUBLE ARCH BELOW;Mn;220;NSM;;;;;N;;;;; +1AE7;COMBINING DOUBLE ARCH ABOVE;Mn;230;NSM;;;;;N;;;;; +1AE8;COMBINING EQUALS SIGN ABOVE;Mn;230;NSM;;;;;N;;;;; +1AE9;COMBINING LEFT ANGLE CENTRED ABOVE;Mn;230;NSM;;;;;N;;;;; +1AEA;COMBINING UPWARDS ARROW ABOVE;Mn;230;NSM;;;;;N;;;;; +1AEB;COMBINING DOUBLE RIGHTWARDS ARROW ABOVE;Mn;234;NSM;;;;;N;;;;; 1B00;BALINESE SIGN ULU RICEM;Mn;0;NSM;;;;;N;;;;; 1B01;BALINESE SIGN ULU CANDRA;Mn;0;NSM;;;;;N;;;;; 1B02;BALINESE SIGN CECEK;Mn;0;NSM;;;;;N;;;;; @@ -7545,6 +7575,7 @@ 20BE;LARI SIGN;Sc;0;ET;;;;;N;;;;; 20BF;BITCOIN SIGN;Sc;0;ET;;;;;N;;;;; 20C0;SOM SIGN;Sc;0;ET;;;;;N;;;;; +20C1;SAUDI RIYAL SIGN;Sc;0;ET;;;;;N;;;;; 20D0;COMBINING LEFT HARPOON ABOVE;Mn;230;NSM;;;;;N;NON-SPACING LEFT HARPOON ABOVE;;;; 20D1;COMBINING RIGHT HARPOON ABOVE;Mn;230;NSM;;;;;N;NON-SPACING RIGHT HARPOON ABOVE;;;; 20D2;COMBINING LONG VERTICAL LINE OVERLAY;Mn;1;NSM;;;;;N;NON-SPACING LONG VERTICAL BAR OVERLAY;;;; @@ -10239,6 +10270,7 @@ 2B93;NEWLINE RIGHT;So;0;ON;;;;;N;;;;; 2B94;FOUR CORNER ARROWS CIRCLING ANTICLOCKWISE;So;0;ON;;;;;N;;;;; 2B95;RIGHTWARDS BLACK ARROW;So;0;ON;;;;;N;;;;; +2B96;EQUALS SIGN WITH INFINITY ABOVE;So;0;ON;;;;;N;;;;; 2B97;SYMBOL FOR TYPE A ELECTRONICS;So;0;ON;;;;;N;;;;; 2B98;THREE-D TOP-LIGHTED LEFTWARDS EQUILATERAL ARROWHEAD;So;0;ON;;;;;N;;;;; 2B99;THREE-D RIGHT-LIGHTED UPWARDS EQUILATERAL ARROWHEAD;So;0;ON;;;;;N;;;;; @@ -14274,10 +14306,14 @@ A7CA;LATIN SMALL LETTER S WITH SHORT STROKE OVERLAY;Ll;0;L;;;;;N;;;A7C9;;A7C9 A7CB;LATIN CAPITAL LETTER RAMS HORN;Lu;0;L;;;;;N;;;;0264; A7CC;LATIN CAPITAL LETTER S WITH DIAGONAL STROKE;Lu;0;L;;;;;N;;;;A7CD; A7CD;LATIN SMALL LETTER S WITH DIAGONAL STROKE;Ll;0;L;;;;;N;;;A7CC;;A7CC +A7CE;LATIN CAPITAL LETTER PHARYNGEAL VOICED FRICATIVE;Lu;0;L;;;;;N;;;;A7CF; +A7CF;LATIN SMALL LETTER PHARYNGEAL VOICED FRICATIVE;Ll;0;L;;;;;N;;;A7CE;;A7CE A7D0;LATIN CAPITAL LETTER CLOSED INSULAR G;Lu;0;L;;;;;N;;;;A7D1; A7D1;LATIN SMALL LETTER CLOSED INSULAR G;Ll;0;L;;;;;N;;;A7D0;;A7D0 -A7D3;LATIN SMALL LETTER DOUBLE THORN;Ll;0;L;;;;;N;;;;; -A7D5;LATIN SMALL LETTER DOUBLE WYNN;Ll;0;L;;;;;N;;;;; +A7D2;LATIN CAPITAL LETTER DOUBLE THORN;Lu;0;L;;;;;N;;;;A7D3; +A7D3;LATIN SMALL LETTER DOUBLE THORN;Ll;0;L;;;;;N;;;A7D2;;A7D2 +A7D4;LATIN CAPITAL LETTER DOUBLE WYNN;Lu;0;L;;;;;N;;;;A7D5; +A7D5;LATIN SMALL LETTER DOUBLE WYNN;Ll;0;L;;;;;N;;;A7D4;;A7D4 A7D6;LATIN CAPITAL LETTER MIDDLE SCOTS S;Lu;0;L;;;;;N;;;;A7D7; A7D7;LATIN SMALL LETTER MIDDLE SCOTS S;Ll;0;L;;;;;N;;;A7D6;;A7D6 A7D8;LATIN CAPITAL LETTER SIGMOID S;Lu;0;L;;;;;N;;;;A7D9; @@ -14285,6 +14321,7 @@ A7D9;LATIN SMALL LETTER SIGMOID S;Ll;0;L;;;;;N;;;A7D8;;A7D8 A7DA;LATIN CAPITAL LETTER LAMBDA;Lu;0;L;;;;;N;;;;A7DB; A7DB;LATIN SMALL LETTER LAMBDA;Ll;0;L;;;;;N;;;A7DA;;A7DA A7DC;LATIN CAPITAL LETTER LAMBDA WITH STROKE;Lu;0;L;;;;;N;;;;019B; +A7F1;MODIFIER LETTER CAPITAL S;Lm;0;L; 0053;;;;N;;;;; A7F2;MODIFIER LETTER CAPITAL C;Lm;0;L; 0043;;;;N;;;;; A7F3;MODIFIER LETTER CAPITAL F;Lm;0;L; 0046;;;;N;;;;; A7F4;MODIFIER LETTER CAPITAL Q;Lm;0;L; 0051;;;;N;;;;; @@ -15925,6 +15962,22 @@ FBBF;ARABIC SYMBOL RING;Sk;0;AL;;;;;N;;;;; FBC0;ARABIC SYMBOL SMALL TAH ABOVE;Sk;0;AL;;;;;N;;;;; FBC1;ARABIC SYMBOL SMALL TAH BELOW;Sk;0;AL;;;;;N;;;;; FBC2;ARABIC SYMBOL WASLA ABOVE;Sk;0;AL;;;;;N;;;;; +FBC3;ARABIC LIGATURE JALLA WA-ALAA;So;0;ON;;;;;N;;;;; +FBC4;ARABIC LIGATURE DAAMAT BARAKAATUHUM;So;0;ON;;;;;N;;;;; +FBC5;ARABIC LIGATURE RAHMATU ALLAAHI TAAALAA ALAYH;So;0;ON;;;;;N;;;;; +FBC6;ARABIC LIGATURE RAHMATU ALLAAHI ALAYHIM;So;0;ON;;;;;N;;;;; +FBC7;ARABIC LIGATURE RAHMATU ALLAAHI ALAYHIMAA;So;0;ON;;;;;N;;;;; +FBC8;ARABIC LIGATURE RAHIMAHUM ALLAAHU TAAALAA;So;0;ON;;;;;N;;;;; +FBC9;ARABIC LIGATURE RAHIMAHUMAA ALLAAH;So;0;ON;;;;;N;;;;; +FBCA;ARABIC LIGATURE RAHIMAHUMAA ALLAAHU TAAALAA;So;0;ON;;;;;N;;;;; +FBCB;ARABIC LIGATURE RADI ALLAAHU TAAALAA ANHUM;So;0;ON;;;;;N;;;;; +FBCC;ARABIC LIGATURE HAFIZAHU ALLAAH;So;0;ON;;;;;N;;;;; +FBCD;ARABIC LIGATURE HAFIZAHU ALLAAHU TAAALAA;So;0;ON;;;;;N;;;;; +FBCE;ARABIC LIGATURE HAFIZAHUM ALLAAHU TAAALAA;So;0;ON;;;;;N;;;;; +FBCF;ARABIC LIGATURE HAFIZAHUMAA ALLAAHU TAAALAA;So;0;ON;;;;;N;;;;; +FBD0;ARABIC LIGATURE SALLALLAAHU TAAALAA ALAYHI WA-SALLAM;So;0;ON;;;;;N;;;;; +FBD1;ARABIC LIGATURE AJJAL ALLAAHU FARAJAHU ASH-SHAREEF;So;0;ON;;;;;N;;;;; +FBD2;ARABIC LIGATURE ALAYHI AR-RAHMAH;So;0;ON;;;;;N;;;;; FBD3;ARABIC LETTER NG ISOLATED FORM;Lo;0;AL; 06AD;;;;N;;;;; FBD4;ARABIC LETTER NG FINAL FORM;Lo;0;AL; 06AD;;;;N;;;;; FBD5;ARABIC LETTER NG INITIAL FORM;Lo;0;AL; 06AD;;;;N;;;;; @@ -16370,6 +16423,8 @@ FD8C;ARABIC LIGATURE MEEM WITH JEEM WITH HAH INITIAL FORM;Lo;0;AL; 0645 FD8D;ARABIC LIGATURE MEEM WITH JEEM WITH MEEM INITIAL FORM;Lo;0;AL; 0645 062C 0645;;;;N;;;;; FD8E;ARABIC LIGATURE MEEM WITH KHAH WITH JEEM INITIAL FORM;Lo;0;AL; 0645 062E 062C;;;;N;;;;; FD8F;ARABIC LIGATURE MEEM WITH KHAH WITH MEEM INITIAL FORM;Lo;0;AL; 0645 062E 0645;;;;N;;;;; +FD90;ARABIC LIGATURE RAHMATU ALLAAHI ALAYH;So;0;ON;;;;;N;;;;; +FD91;ARABIC LIGATURE RAHMATU ALLAAHI ALAYHAA;So;0;ON;;;;;N;;;;; FD92;ARABIC LIGATURE MEEM WITH JEEM WITH KHAH INITIAL FORM;Lo;0;AL; 0645 062C 062E;;;;N;;;;; FD93;ARABIC LIGATURE HEH WITH MEEM WITH JEEM INITIAL FORM;Lo;0;AL; 0647 0645 062C;;;;N;;;;; FD94;ARABIC LIGATURE HEH WITH MEEM WITH MEEM INITIAL FORM;Lo;0;AL; 0647 0645 0645;;;;N;;;;; @@ -16424,6 +16479,13 @@ FDC4;ARABIC LIGATURE AIN WITH JEEM WITH MEEM INITIAL FORM;Lo;0;AL; 0639 FDC5;ARABIC LIGATURE SAD WITH MEEM WITH MEEM INITIAL FORM;Lo;0;AL; 0635 0645 0645;;;;N;;;;; FDC6;ARABIC LIGATURE SEEN WITH KHAH WITH YEH FINAL FORM;Lo;0;AL; 0633 062E 064A;;;;N;;;;; FDC7;ARABIC LIGATURE NOON WITH JEEM WITH YEH FINAL FORM;Lo;0;AL; 0646 062C 064A;;;;N;;;;; +FDC8;ARABIC LIGATURE RAHIMAHU ALLAAH TAAALAA;So;0;ON;;;;;N;;;;; +FDC9;ARABIC LIGATURE RADI ALLAAHU TAAALAA ANH;So;0;ON;;;;;N;;;;; +FDCA;ARABIC LIGATURE RADI ALLAAHU TAAALAA ANHAA;So;0;ON;;;;;N;;;;; +FDCB;ARABIC LIGATURE RADI ALLAAHU TAAALAA ANHUMAA;So;0;ON;;;;;N;;;;; +FDCC;ARABIC LIGATURE SALLALLAHU ALAYHI WA-ALAA AALIHEE WA-SALLAM;So;0;ON;;;;;N;;;;; +FDCD;ARABIC LIGATURE AJJAL ALLAAHU TAAALAA FARAJAHU ASH-SHAREEF;So;0;ON;;;;;N;;;;; +FDCE;ARABIC LIGATURE KARRAMA ALLAAHU WAJHAH;So;0;ON;;;;;N;;;;; FDCF;ARABIC LIGATURE SALAAMUHU ALAYNAA;So;0;ON;;;;;N;;;;; FDF0;ARABIC LIGATURE SALLA USED AS KORANIC STOP SIGN ISOLATED FORM;Lo;0;AL; 0635 0644 06D2;;;;N;;;;; FDF1;ARABIC LIGATURE QALA USED AS KORANIC STOP SIGN ISOLATED FORM;Lo;0;AL; 0642 0644 06D2;;;;N;;;;; @@ -18708,6 +18770,32 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 10938;LYDIAN LETTER NN;Lo;0;R;;;;;N;;;;; 10939;LYDIAN LETTER C;Lo;0;R;;;;;N;;;;; 1093F;LYDIAN TRIANGULAR MARK;Po;0;R;;;;;N;;;;; +10940;SIDETIC LETTER N01;Lo;0;R;;;;;N;;;;; +10941;SIDETIC LETTER N02;Lo;0;R;;;;;N;;;;; +10942;SIDETIC LETTER N03;Lo;0;R;;;;;N;;;;; +10943;SIDETIC LETTER N04;Lo;0;R;;;;;N;;;;; +10944;SIDETIC LETTER N05;Lo;0;R;;;;;N;;;;; +10945;SIDETIC LETTER N06;Lo;0;R;;;;;N;;;;; +10946;SIDETIC LETTER N07;Lo;0;R;;;;;N;;;;; +10947;SIDETIC LETTER N08;Lo;0;R;;;;;N;;;;; +10948;SIDETIC LETTER N09;Lo;0;R;;;;;N;;;;; +10949;SIDETIC LETTER N10;Lo;0;R;;;;;N;;;;; +1094A;SIDETIC LETTER N11;Lo;0;R;;;;;N;;;;; +1094B;SIDETIC LETTER N12;Lo;0;R;;;;;N;;;;; +1094C;SIDETIC LETTER N13;Lo;0;R;;;;;N;;;;; +1094D;SIDETIC LETTER N14;Lo;0;R;;;;;N;;;;; +1094E;SIDETIC LETTER N15;Lo;0;R;;;;;N;;;;; +1094F;SIDETIC LETTER N16;Lo;0;R;;;;;N;;;;; +10950;SIDETIC LETTER N17;Lo;0;R;;;;;N;;;;; +10951;SIDETIC LETTER N18;Lo;0;R;;;;;N;;;;; +10952;SIDETIC LETTER N19;Lo;0;R;;;;;N;;;;; +10953;SIDETIC LETTER N20;Lo;0;R;;;;;N;;;;; +10954;SIDETIC LETTER N21;Lo;0;R;;;;;N;;;;; +10955;SIDETIC LETTER N22;Lo;0;R;;;;;N;;;;; +10956;SIDETIC LETTER N23;Lo;0;R;;;;;N;;;;; +10957;SIDETIC LETTER N24;Lo;0;R;;;;;N;;;;; +10958;SIDETIC LETTER N25;Lo;0;R;;;;;N;;;;; +10959;SIDETIC LETTER N26;Lo;0;R;;;;;N;;;;; 10980;MEROITIC HIEROGLYPHIC LETTER A;Lo;0;R;;;;;N;;;;; 10981;MEROITIC HIEROGLYPHIC LETTER E;Lo;0;R;;;;;N;;;;; 10982;MEROITIC HIEROGLYPHIC LETTER I;Lo;0;R;;;;;N;;;;; @@ -19541,6 +19629,20 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 10EC2;ARABIC LETTER DAL WITH TWO DOTS VERTICALLY BELOW;Lo;0;AL;;;;;N;;;;; 10EC3;ARABIC LETTER TAH WITH TWO DOTS VERTICALLY BELOW;Lo;0;AL;;;;;N;;;;; 10EC4;ARABIC LETTER KAF WITH TWO DOTS VERTICALLY BELOW;Lo;0;AL;;;;;N;;;;; +10EC5;ARABIC SMALL YEH BARREE WITH TWO DOTS BELOW;Lm;0;AL;;;;;N;;;;; +10EC6;ARABIC LETTER THIN NOON;Lo;0;AL;;;;;N;;;;; +10EC7;ARABIC LETTER YEH WITH FOUR DOTS BELOW;Lo;0;AL;;;;;N;;;;; +10ED0;ARABIC BIBLICAL END OF VERSE;Po;0;ON;;;;;N;;;;; +10ED1;ARABIC LIGATURE ALAYHAA AS-SALAATU WAS-SALAAM;So;0;ON;;;;;N;;;;; +10ED2;ARABIC LIGATURE ALAYHIM AS-SALAATU WAS-SALAAM;So;0;ON;;;;;N;;;;; +10ED3;ARABIC LIGATURE ALAYHIMAA AS-SALAATU WAS-SALAAM;So;0;ON;;;;;N;;;;; +10ED4;ARABIC LIGATURE QADDASA ALLAAHU SIRRAH;So;0;ON;;;;;N;;;;; +10ED5;ARABIC LIGATURE QUDDISA SIRRUHUM;So;0;ON;;;;;N;;;;; +10ED6;ARABIC LIGATURE QUDDISA SIRRUHUMAA;So;0;ON;;;;;N;;;;; +10ED7;ARABIC LIGATURE QUDDISAT ASRAARUHUM;So;0;ON;;;;;N;;;;; +10ED8;ARABIC LIGATURE NAWWARA ALLAAHU MARQADAH;So;0;ON;;;;;N;;;;; +10EFA;ARABIC DOUBLE VERTICAL BAR BELOW;Mn;220;NSM;;;;;N;;;;; +10EFB;ARABIC SMALL LOW NOON;Mn;220;NSM;;;;;N;;;;; 10EFC;ARABIC COMBINING ALEF OVERLAY;Mn;0;NSM;;;;;N;;;;; 10EFD;ARABIC SMALL LOW WORD SAKTA;Mn;220;NSM;;;;;N;;;;; 10EFE;ARABIC SMALL LOW WORD QASR;Mn;220;NSM;;;;;N;;;;; @@ -21521,6 +21623,14 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 11B07;DEVANAGARI SIGN WESTERN NINE-LIKE BHALE;Po;0;L;;;;;N;;;;; 11B08;DEVANAGARI SIGN REVERSED NINE-LIKE BHALE;Po;0;L;;;;;N;;;;; 11B09;DEVANAGARI SIGN MINDU;Po;0;L;;;;;N;;;;; +11B60;SHARADA VOWEL SIGN OE;Mn;0;NSM;;;;;N;;;;; +11B61;SHARADA VOWEL SIGN OOE;Mc;0;L;;;;;N;;;;; +11B62;SHARADA VOWEL SIGN UE;Mn;0;NSM;;;;;N;;;;; +11B63;SHARADA VOWEL SIGN UUE;Mn;0;NSM;;;;;N;;;;; +11B64;SHARADA VOWEL SIGN SHORT E;Mn;0;NSM;;;;;N;;;;; +11B65;SHARADA VOWEL SIGN SHORT O;Mc;0;L;;;;;N;;;;; +11B66;SHARADA VOWEL SIGN CANDRA E;Mn;0;NSM;;;;;N;;;;; +11B67;SHARADA VOWEL SIGN CANDRA O;Mc;0;L;;;;;N;;;;; 11BC0;SUNUWAR LETTER DEVI;Lo;0;L;;;;;N;;;;; 11BC1;SUNUWAR LETTER TASLA;Lo;0;L;;;;;N;;;;; 11BC2;SUNUWAR LETTER EKO;Lo;0;L;;;;;N;;;;; @@ -21868,6 +21978,60 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 11DA7;GUNJALA GONDI DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 11DA8;GUNJALA GONDI DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 11DA9;GUNJALA GONDI DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; +11DB0;TOLONG SIKI LETTER I;Lo;0;L;;;;;N;;;;; +11DB1;TOLONG SIKI LETTER E;Lo;0;L;;;;;N;;;;; +11DB2;TOLONG SIKI LETTER U;Lo;0;L;;;;;N;;;;; +11DB3;TOLONG SIKI LETTER O;Lo;0;L;;;;;N;;;;; +11DB4;TOLONG SIKI LETTER A;Lo;0;L;;;;;N;;;;; +11DB5;TOLONG SIKI LETTER AA;Lo;0;L;;;;;N;;;;; +11DB6;TOLONG SIKI LETTER P;Lo;0;L;;;;;N;;;;; +11DB7;TOLONG SIKI LETTER PH;Lo;0;L;;;;;N;;;;; +11DB8;TOLONG SIKI LETTER B;Lo;0;L;;;;;N;;;;; +11DB9;TOLONG SIKI LETTER BH;Lo;0;L;;;;;N;;;;; +11DBA;TOLONG SIKI LETTER M;Lo;0;L;;;;;N;;;;; +11DBB;TOLONG SIKI LETTER T;Lo;0;L;;;;;N;;;;; +11DBC;TOLONG SIKI LETTER TH;Lo;0;L;;;;;N;;;;; +11DBD;TOLONG SIKI LETTER D;Lo;0;L;;;;;N;;;;; +11DBE;TOLONG SIKI LETTER DH;Lo;0;L;;;;;N;;;;; +11DBF;TOLONG SIKI LETTER N;Lo;0;L;;;;;N;;;;; +11DC0;TOLONG SIKI LETTER TT;Lo;0;L;;;;;N;;;;; +11DC1;TOLONG SIKI LETTER TTH;Lo;0;L;;;;;N;;;;; +11DC2;TOLONG SIKI LETTER DD;Lo;0;L;;;;;N;;;;; +11DC3;TOLONG SIKI LETTER DDH;Lo;0;L;;;;;N;;;;; +11DC4;TOLONG SIKI LETTER NN;Lo;0;L;;;;;N;;;;; +11DC5;TOLONG SIKI LETTER C;Lo;0;L;;;;;N;;;;; +11DC6;TOLONG SIKI LETTER CH;Lo;0;L;;;;;N;;;;; +11DC7;TOLONG SIKI LETTER J;Lo;0;L;;;;;N;;;;; +11DC8;TOLONG SIKI LETTER JH;Lo;0;L;;;;;N;;;;; +11DC9;TOLONG SIKI LETTER NY;Lo;0;L;;;;;N;;;;; +11DCA;TOLONG SIKI LETTER K;Lo;0;L;;;;;N;;;;; +11DCB;TOLONG SIKI LETTER KH;Lo;0;L;;;;;N;;;;; +11DCC;TOLONG SIKI LETTER G;Lo;0;L;;;;;N;;;;; +11DCD;TOLONG SIKI LETTER GH;Lo;0;L;;;;;N;;;;; +11DCE;TOLONG SIKI LETTER NG;Lo;0;L;;;;;N;;;;; +11DCF;TOLONG SIKI LETTER Y;Lo;0;L;;;;;N;;;;; +11DD0;TOLONG SIKI LETTER R;Lo;0;L;;;;;N;;;;; +11DD1;TOLONG SIKI LETTER L;Lo;0;L;;;;;N;;;;; +11DD2;TOLONG SIKI LETTER V;Lo;0;L;;;;;N;;;;; +11DD3;TOLONG SIKI LETTER NNY;Lo;0;L;;;;;N;;;;; +11DD4;TOLONG SIKI LETTER S;Lo;0;L;;;;;N;;;;; +11DD5;TOLONG SIKI LETTER H;Lo;0;L;;;;;N;;;;; +11DD6;TOLONG SIKI LETTER X;Lo;0;L;;;;;N;;;;; +11DD7;TOLONG SIKI LETTER RR;Lo;0;L;;;;;N;;;;; +11DD8;TOLONG SIKI LETTER RRH;Lo;0;L;;;;;N;;;;; +11DD9;TOLONG SIKI SIGN SELA;Lm;0;L;;;;;N;;;;; +11DDA;TOLONG SIKI SIGN HECAKA;Lo;0;L;;;;;N;;;;; +11DDB;TOLONG SIKI UNGGA;Lo;0;L;;;;;N;;;;; +11DE0;TOLONG SIKI DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; +11DE1;TOLONG SIKI DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; +11DE2;TOLONG SIKI DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; +11DE3;TOLONG SIKI DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; +11DE4;TOLONG SIKI DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; +11DE5;TOLONG SIKI DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; +11DE6;TOLONG SIKI DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; +11DE7;TOLONG SIKI DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; +11DE8;TOLONG SIKI DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; +11DE9;TOLONG SIKI DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 11EE0;MAKASAR LETTER KA;Lo;0;L;;;;;N;;;;; 11EE1;MAKASAR LETTER GA;Lo;0;L;;;;;N;;;;; 11EE2;MAKASAR LETTER NGA;Lo;0;L;;;;;N;;;;; @@ -22088,8 +22252,8 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 12035;CUNEIFORM SIGN ARAD TIMES KUR;Lo;0;L;;;;;N;;;;; 12036;CUNEIFORM SIGN ARKAB;Lo;0;L;;;;;N;;;;; 12037;CUNEIFORM SIGN ASAL2;Lo;0;L;;;;;N;;;;; -12038;CUNEIFORM SIGN ASH;Lo;0;L;;;;;N;;;;; -12039;CUNEIFORM SIGN ASH ZIDA TENU;Lo;0;L;;;;;N;;;;; +12038;CUNEIFORM SIGN ASH;Lo;0;L;;;;1;N;;;;; +12039;CUNEIFORM SIGN ASH ZIDA TENU;Lo;0;L;;;;1;N;;;;; 1203A;CUNEIFORM SIGN ASH KABA TENU;Lo;0;L;;;;;N;;;;; 1203B;CUNEIFORM SIGN ASH OVER ASH TUG2 OVER TUG2 TUG2 OVER TUG2 PAP;Lo;0;L;;;;;N;;;;; 1203C;CUNEIFORM SIGN ASH OVER ASH OVER ASH;Lo;0;L;;;;;N;;;;; @@ -22153,7 +22317,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 12076;CUNEIFORM SIGN DIM2;Lo;0;L;;;;;N;;;;; 12077;CUNEIFORM SIGN DIN;Lo;0;L;;;;;N;;;;; 12078;CUNEIFORM SIGN DIN KASKAL U GUNU DISH;Lo;0;L;;;;;N;;;;; -12079;CUNEIFORM SIGN DISH;Lo;0;L;;;;;N;;;;; +12079;CUNEIFORM SIGN DISH;Lo;0;L;;;;1;N;;;;; 1207A;CUNEIFORM SIGN DU;Lo;0;L;;;;;N;;;;; 1207B;CUNEIFORM SIGN DU OVER DU;Lo;0;L;;;;;N;;;;; 1207C;CUNEIFORM SIGN DU GUNU;Lo;0;L;;;;;N;;;;; @@ -22582,12 +22746,12 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 12223;CUNEIFORM SIGN MA2;Lo;0;L;;;;;N;;;;; 12224;CUNEIFORM SIGN MAH;Lo;0;L;;;;;N;;;;; 12225;CUNEIFORM SIGN MAR;Lo;0;L;;;;;N;;;;; -12226;CUNEIFORM SIGN MASH;Lo;0;L;;;;;N;;;;; +12226;CUNEIFORM SIGN MASH;Lo;0;L;;;;1/2;N;;;;; 12227;CUNEIFORM SIGN MASH2;Lo;0;L;;;;;N;;;;; 12228;CUNEIFORM SIGN ME;Lo;0;L;;;;;N;;;;; 12229;CUNEIFORM SIGN MES;Lo;0;L;;;;;N;;;;; 1222A;CUNEIFORM SIGN MI;Lo;0;L;;;;;N;;;;; -1222B;CUNEIFORM SIGN MIN;Lo;0;L;;;;;N;;;;; +1222B;CUNEIFORM SIGN MIN;Lo;0;L;;;;2;N;;;;; 1222C;CUNEIFORM SIGN MU;Lo;0;L;;;;;N;;;;; 1222D;CUNEIFORM SIGN MU OVER MU;Lo;0;L;;;;;N;;;;; 1222E;CUNEIFORM SIGN MUG;Lo;0;L;;;;;N;;;;; @@ -22811,9 +22975,9 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 12308;CUNEIFORM SIGN TUM;Lo;0;L;;;;;N;;;;; 12309;CUNEIFORM SIGN TUR;Lo;0;L;;;;;N;;;;; 1230A;CUNEIFORM SIGN TUR OVER TUR ZA OVER ZA;Lo;0;L;;;;;N;;;;; -1230B;CUNEIFORM SIGN U;Lo;0;L;;;;;N;;;;; +1230B;CUNEIFORM SIGN U;Lo;0;L;;;;1;N;;;;; 1230C;CUNEIFORM SIGN U GUD;Lo;0;L;;;;;N;;;;; -1230D;CUNEIFORM SIGN U U U;Lo;0;L;;;;;N;;;;; +1230D;CUNEIFORM SIGN U U U;Lo;0;L;;;;3;N;;;;; 1230E;CUNEIFORM SIGN U OVER U PA OVER PA GAR OVER GAR;Lo;0;L;;;;;N;;;;; 1230F;CUNEIFORM SIGN U OVER U SUR OVER SUR;Lo;0;L;;;;;N;;;;; 12310;CUNEIFORM SIGN U OVER U U REVERSED OVER U REVERSED;Lo;0;L;;;;;N;;;;; @@ -22953,7 +23117,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 12396;CUNEIFORM SIGN SAG TIMES IGI GUNU;Lo;0;L;;;;;N;;;;; 12397;CUNEIFORM SIGN TI2;Lo;0;L;;;;;N;;;;; 12398;CUNEIFORM SIGN UM TIMES ME;Lo;0;L;;;;;N;;;;; -12399;CUNEIFORM SIGN U U;Lo;0;L;;;;;N;;;;; +12399;CUNEIFORM SIGN U U;Lo;0;L;;;;2;N;;;;; 12400;CUNEIFORM NUMERIC SIGN TWO ASH;Nl;0;L;;;;2;N;;;;; 12401;CUNEIFORM NUMERIC SIGN THREE ASH;Nl;0;L;;;;3;N;;;;; 12402;CUNEIFORM NUMERIC SIGN FOUR ASH;Nl;0;L;;;;4;N;;;;; @@ -30124,6 +30288,56 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 16E98;MEDEFAIDRIN FULL STOP;Po;0;L;;;;;N;;;;; 16E99;MEDEFAIDRIN SYMBOL AIVA;Po;0;L;;;;;N;;;;; 16E9A;MEDEFAIDRIN EXCLAMATION OH;Po;0;L;;;;;N;;;;; +16EA0;BERIA ERFE CAPITAL LETTER ARKAB;Lu;0;L;;;;;N;;;;16EBB; +16EA1;BERIA ERFE CAPITAL LETTER BASIGNA;Lu;0;L;;;;;N;;;;16EBC; +16EA2;BERIA ERFE CAPITAL LETTER DARBAI;Lu;0;L;;;;;N;;;;16EBD; +16EA3;BERIA ERFE CAPITAL LETTER EH;Lu;0;L;;;;;N;;;;16EBE; +16EA4;BERIA ERFE CAPITAL LETTER FITKO;Lu;0;L;;;;;N;;;;16EBF; +16EA5;BERIA ERFE CAPITAL LETTER GOWAY;Lu;0;L;;;;;N;;;;16EC0; +16EA6;BERIA ERFE CAPITAL LETTER HIRDEABO;Lu;0;L;;;;;N;;;;16EC1; +16EA7;BERIA ERFE CAPITAL LETTER I;Lu;0;L;;;;;N;;;;16EC2; +16EA8;BERIA ERFE CAPITAL LETTER DJAI;Lu;0;L;;;;;N;;;;16EC3; +16EA9;BERIA ERFE CAPITAL LETTER KOBO;Lu;0;L;;;;;N;;;;16EC4; +16EAA;BERIA ERFE CAPITAL LETTER LAKKO;Lu;0;L;;;;;N;;;;16EC5; +16EAB;BERIA ERFE CAPITAL LETTER MERI;Lu;0;L;;;;;N;;;;16EC6; +16EAC;BERIA ERFE CAPITAL LETTER NINI;Lu;0;L;;;;;N;;;;16EC7; +16EAD;BERIA ERFE CAPITAL LETTER GNA;Lu;0;L;;;;;N;;;;16EC8; +16EAE;BERIA ERFE CAPITAL LETTER NGAY;Lu;0;L;;;;;N;;;;16EC9; +16EAF;BERIA ERFE CAPITAL LETTER OI;Lu;0;L;;;;;N;;;;16ECA; +16EB0;BERIA ERFE CAPITAL LETTER PI;Lu;0;L;;;;;N;;;;16ECB; +16EB1;BERIA ERFE CAPITAL LETTER ERIGO;Lu;0;L;;;;;N;;;;16ECC; +16EB2;BERIA ERFE CAPITAL LETTER ERIGO TAMURA;Lu;0;L;;;;;N;;;;16ECD; +16EB3;BERIA ERFE CAPITAL LETTER SERI;Lu;0;L;;;;;N;;;;16ECE; +16EB4;BERIA ERFE CAPITAL LETTER SHEP;Lu;0;L;;;;;N;;;;16ECF; +16EB5;BERIA ERFE CAPITAL LETTER TATASOUE;Lu;0;L;;;;;N;;;;16ED0; +16EB6;BERIA ERFE CAPITAL LETTER UI;Lu;0;L;;;;;N;;;;16ED1; +16EB7;BERIA ERFE CAPITAL LETTER WASSE;Lu;0;L;;;;;N;;;;16ED2; +16EB8;BERIA ERFE CAPITAL LETTER AY;Lu;0;L;;;;;N;;;;16ED3; +16EBB;BERIA ERFE SMALL LETTER ARKAB;Ll;0;L;;;;;N;;;16EA0;;16EA0 +16EBC;BERIA ERFE SMALL LETTER BASIGNA;Ll;0;L;;;;;N;;;16EA1;;16EA1 +16EBD;BERIA ERFE SMALL LETTER DARBAI;Ll;0;L;;;;;N;;;16EA2;;16EA2 +16EBE;BERIA ERFE SMALL LETTER EH;Ll;0;L;;;;;N;;;16EA3;;16EA3 +16EBF;BERIA ERFE SMALL LETTER FITKO;Ll;0;L;;;;;N;;;16EA4;;16EA4 +16EC0;BERIA ERFE SMALL LETTER GOWAY;Ll;0;L;;;;;N;;;16EA5;;16EA5 +16EC1;BERIA ERFE SMALL LETTER HIRDEABO;Ll;0;L;;;;;N;;;16EA6;;16EA6 +16EC2;BERIA ERFE SMALL LETTER I;Ll;0;L;;;;;N;;;16EA7;;16EA7 +16EC3;BERIA ERFE SMALL LETTER DJAI;Ll;0;L;;;;;N;;;16EA8;;16EA8 +16EC4;BERIA ERFE SMALL LETTER KOBO;Ll;0;L;;;;;N;;;16EA9;;16EA9 +16EC5;BERIA ERFE SMALL LETTER LAKKO;Ll;0;L;;;;;N;;;16EAA;;16EAA +16EC6;BERIA ERFE SMALL LETTER MERI;Ll;0;L;;;;;N;;;16EAB;;16EAB +16EC7;BERIA ERFE SMALL LETTER NINI;Ll;0;L;;;;;N;;;16EAC;;16EAC +16EC8;BERIA ERFE SMALL LETTER GNA;Ll;0;L;;;;;N;;;16EAD;;16EAD +16EC9;BERIA ERFE SMALL LETTER NGAY;Ll;0;L;;;;;N;;;16EAE;;16EAE +16ECA;BERIA ERFE SMALL LETTER OI;Ll;0;L;;;;;N;;;16EAF;;16EAF +16ECB;BERIA ERFE SMALL LETTER PI;Ll;0;L;;;;;N;;;16EB0;;16EB0 +16ECC;BERIA ERFE SMALL LETTER ERIGO;Ll;0;L;;;;;N;;;16EB1;;16EB1 +16ECD;BERIA ERFE SMALL LETTER ERIGO TAMURA;Ll;0;L;;;;;N;;;16EB2;;16EB2 +16ECE;BERIA ERFE SMALL LETTER SERI;Ll;0;L;;;;;N;;;16EB3;;16EB3 +16ECF;BERIA ERFE SMALL LETTER SHEP;Ll;0;L;;;;;N;;;16EB4;;16EB4 +16ED0;BERIA ERFE SMALL LETTER TATASOUE;Ll;0;L;;;;;N;;;16EB5;;16EB5 +16ED1;BERIA ERFE SMALL LETTER UI;Ll;0;L;;;;;N;;;16EB6;;16EB6 +16ED2;BERIA ERFE SMALL LETTER WASSE;Ll;0;L;;;;;N;;;16EB7;;16EB7 +16ED3;BERIA ERFE SMALL LETTER AY;Ll;0;L;;;;;N;;;16EB8;;16EB8 16F00;MIAO LETTER PA;Lo;0;L;;;;;N;;;;; 16F01;MIAO LETTER BA;Lo;0;L;;;;;N;;;;; 16F02;MIAO LETTER YI PA;Lo;0;L;;;;;N;;;;; @@ -30280,8 +30494,13 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 16FE4;KHITAN SMALL SCRIPT FILLER;Mn;0;NSM;;;;;N;;;;; 16FF0;VIETNAMESE ALTERNATE READING MARK CA;Mc;6;L;;;;;N;;;;; 16FF1;VIETNAMESE ALTERNATE READING MARK NHAY;Mc;6;L;;;;;N;;;;; +16FF2;CHINESE SMALL SIMPLIFIED ER;Lm;0;L;;;;;N;;;;; +16FF3;CHINESE SMALL TRADITIONAL ER;Lm;0;L;;;;;N;;;;; +16FF4;YANGQIN SIGN SLOW ONE BEAT;Nl;0;L;;;;1;N;;;;; +16FF5;YANGQIN SIGN SLOW THREE HALF BEATS;Nl;0;L;;;;3/2;N;;;;; +16FF6;YANGQIN SIGN SLOW TWO BEATS;Nl;0;L;;;;2;N;;;;; 17000;;Lo;0;L;;;;;N;;;;; -187F7;;Lo;0;L;;;;;N;;;;; +187FF;;Lo;0;L;;;;;N;;;;; 18800;TANGUT COMPONENT-001;Lo;0;L;;;;;N;;;;; 18801;TANGUT COMPONENT-002;Lo;0;L;;;;;N;;;;; 18802;TANGUT COMPONENT-003;Lo;0;L;;;;;N;;;;; @@ -31522,7 +31741,122 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 18CD5;KHITAN SMALL SCRIPT CHARACTER-18CD5;Lo;0;L;;;;;N;;;;; 18CFF;KHITAN SMALL SCRIPT CHARACTER-18CFF;Lo;0;L;;;;;N;;;;; 18D00;;Lo;0;L;;;;;N;;;;; -18D08;;Lo;0;L;;;;;N;;;;; +18D1E;;Lo;0;L;;;;;N;;;;; +18D80;TANGUT COMPONENT-769;Lo;0;L;;;;;N;;;;; +18D81;TANGUT COMPONENT-770;Lo;0;L;;;;;N;;;;; +18D82;TANGUT COMPONENT-771;Lo;0;L;;;;;N;;;;; +18D83;TANGUT COMPONENT-772;Lo;0;L;;;;;N;;;;; +18D84;TANGUT COMPONENT-773;Lo;0;L;;;;;N;;;;; +18D85;TANGUT COMPONENT-774;Lo;0;L;;;;;N;;;;; +18D86;TANGUT COMPONENT-775;Lo;0;L;;;;;N;;;;; +18D87;TANGUT COMPONENT-776;Lo;0;L;;;;;N;;;;; +18D88;TANGUT COMPONENT-777;Lo;0;L;;;;;N;;;;; +18D89;TANGUT COMPONENT-778;Lo;0;L;;;;;N;;;;; +18D8A;TANGUT COMPONENT-779;Lo;0;L;;;;;N;;;;; +18D8B;TANGUT COMPONENT-780;Lo;0;L;;;;;N;;;;; +18D8C;TANGUT COMPONENT-781;Lo;0;L;;;;;N;;;;; +18D8D;TANGUT COMPONENT-782;Lo;0;L;;;;;N;;;;; +18D8E;TANGUT COMPONENT-783;Lo;0;L;;;;;N;;;;; +18D8F;TANGUT COMPONENT-784;Lo;0;L;;;;;N;;;;; +18D90;TANGUT COMPONENT-785;Lo;0;L;;;;;N;;;;; +18D91;TANGUT COMPONENT-786;Lo;0;L;;;;;N;;;;; +18D92;TANGUT COMPONENT-787;Lo;0;L;;;;;N;;;;; +18D93;TANGUT COMPONENT-788;Lo;0;L;;;;;N;;;;; +18D94;TANGUT COMPONENT-789;Lo;0;L;;;;;N;;;;; +18D95;TANGUT COMPONENT-790;Lo;0;L;;;;;N;;;;; +18D96;TANGUT COMPONENT-791;Lo;0;L;;;;;N;;;;; +18D97;TANGUT COMPONENT-792;Lo;0;L;;;;;N;;;;; +18D98;TANGUT COMPONENT-793;Lo;0;L;;;;;N;;;;; +18D99;TANGUT COMPONENT-794;Lo;0;L;;;;;N;;;;; +18D9A;TANGUT COMPONENT-795;Lo;0;L;;;;;N;;;;; +18D9B;TANGUT COMPONENT-796;Lo;0;L;;;;;N;;;;; +18D9C;TANGUT COMPONENT-797;Lo;0;L;;;;;N;;;;; +18D9D;TANGUT COMPONENT-798;Lo;0;L;;;;;N;;;;; +18D9E;TANGUT COMPONENT-799;Lo;0;L;;;;;N;;;;; +18D9F;TANGUT COMPONENT-800;Lo;0;L;;;;;N;;;;; +18DA0;TANGUT COMPONENT-801;Lo;0;L;;;;;N;;;;; +18DA1;TANGUT COMPONENT-802;Lo;0;L;;;;;N;;;;; +18DA2;TANGUT COMPONENT-803;Lo;0;L;;;;;N;;;;; +18DA3;TANGUT COMPONENT-804;Lo;0;L;;;;;N;;;;; +18DA4;TANGUT COMPONENT-805;Lo;0;L;;;;;N;;;;; +18DA5;TANGUT COMPONENT-806;Lo;0;L;;;;;N;;;;; +18DA6;TANGUT COMPONENT-807;Lo;0;L;;;;;N;;;;; +18DA7;TANGUT COMPONENT-808;Lo;0;L;;;;;N;;;;; +18DA8;TANGUT COMPONENT-809;Lo;0;L;;;;;N;;;;; +18DA9;TANGUT COMPONENT-810;Lo;0;L;;;;;N;;;;; +18DAA;TANGUT COMPONENT-811;Lo;0;L;;;;;N;;;;; +18DAB;TANGUT COMPONENT-812;Lo;0;L;;;;;N;;;;; +18DAC;TANGUT COMPONENT-813;Lo;0;L;;;;;N;;;;; +18DAD;TANGUT COMPONENT-814;Lo;0;L;;;;;N;;;;; +18DAE;TANGUT COMPONENT-815;Lo;0;L;;;;;N;;;;; +18DAF;TANGUT COMPONENT-816;Lo;0;L;;;;;N;;;;; +18DB0;TANGUT COMPONENT-817;Lo;0;L;;;;;N;;;;; +18DB1;TANGUT COMPONENT-818;Lo;0;L;;;;;N;;;;; +18DB2;TANGUT COMPONENT-819;Lo;0;L;;;;;N;;;;; +18DB3;TANGUT COMPONENT-820;Lo;0;L;;;;;N;;;;; +18DB4;TANGUT COMPONENT-821;Lo;0;L;;;;;N;;;;; +18DB5;TANGUT COMPONENT-822;Lo;0;L;;;;;N;;;;; +18DB6;TANGUT COMPONENT-823;Lo;0;L;;;;;N;;;;; +18DB7;TANGUT COMPONENT-824;Lo;0;L;;;;;N;;;;; +18DB8;TANGUT COMPONENT-825;Lo;0;L;;;;;N;;;;; +18DB9;TANGUT COMPONENT-826;Lo;0;L;;;;;N;;;;; +18DBA;TANGUT COMPONENT-827;Lo;0;L;;;;;N;;;;; +18DBB;TANGUT COMPONENT-828;Lo;0;L;;;;;N;;;;; +18DBC;TANGUT COMPONENT-829;Lo;0;L;;;;;N;;;;; +18DBD;TANGUT COMPONENT-830;Lo;0;L;;;;;N;;;;; +18DBE;TANGUT COMPONENT-831;Lo;0;L;;;;;N;;;;; +18DBF;TANGUT COMPONENT-832;Lo;0;L;;;;;N;;;;; +18DC0;TANGUT COMPONENT-833;Lo;0;L;;;;;N;;;;; +18DC1;TANGUT COMPONENT-834;Lo;0;L;;;;;N;;;;; +18DC2;TANGUT COMPONENT-835;Lo;0;L;;;;;N;;;;; +18DC3;TANGUT COMPONENT-836;Lo;0;L;;;;;N;;;;; +18DC4;TANGUT COMPONENT-837;Lo;0;L;;;;;N;;;;; +18DC5;TANGUT COMPONENT-838;Lo;0;L;;;;;N;;;;; +18DC6;TANGUT COMPONENT-839;Lo;0;L;;;;;N;;;;; +18DC7;TANGUT COMPONENT-840;Lo;0;L;;;;;N;;;;; +18DC8;TANGUT COMPONENT-841;Lo;0;L;;;;;N;;;;; +18DC9;TANGUT COMPONENT-842;Lo;0;L;;;;;N;;;;; +18DCA;TANGUT COMPONENT-843;Lo;0;L;;;;;N;;;;; +18DCB;TANGUT COMPONENT-844;Lo;0;L;;;;;N;;;;; +18DCC;TANGUT COMPONENT-845;Lo;0;L;;;;;N;;;;; +18DCD;TANGUT COMPONENT-846;Lo;0;L;;;;;N;;;;; +18DCE;TANGUT COMPONENT-847;Lo;0;L;;;;;N;;;;; +18DCF;TANGUT COMPONENT-848;Lo;0;L;;;;;N;;;;; +18DD0;TANGUT COMPONENT-849;Lo;0;L;;;;;N;;;;; +18DD1;TANGUT COMPONENT-850;Lo;0;L;;;;;N;;;;; +18DD2;TANGUT COMPONENT-851;Lo;0;L;;;;;N;;;;; +18DD3;TANGUT COMPONENT-852;Lo;0;L;;;;;N;;;;; +18DD4;TANGUT COMPONENT-853;Lo;0;L;;;;;N;;;;; +18DD5;TANGUT COMPONENT-854;Lo;0;L;;;;;N;;;;; +18DD6;TANGUT COMPONENT-855;Lo;0;L;;;;;N;;;;; +18DD7;TANGUT COMPONENT-856;Lo;0;L;;;;;N;;;;; +18DD8;TANGUT COMPONENT-857;Lo;0;L;;;;;N;;;;; +18DD9;TANGUT COMPONENT-858;Lo;0;L;;;;;N;;;;; +18DDA;TANGUT COMPONENT-859;Lo;0;L;;;;;N;;;;; +18DDB;TANGUT COMPONENT-860;Lo;0;L;;;;;N;;;;; +18DDC;TANGUT COMPONENT-861;Lo;0;L;;;;;N;;;;; +18DDD;TANGUT COMPONENT-862;Lo;0;L;;;;;N;;;;; +18DDE;TANGUT COMPONENT-863;Lo;0;L;;;;;N;;;;; +18DDF;TANGUT COMPONENT-864;Lo;0;L;;;;;N;;;;; +18DE0;TANGUT COMPONENT-865;Lo;0;L;;;;;N;;;;; +18DE1;TANGUT COMPONENT-866;Lo;0;L;;;;;N;;;;; +18DE2;TANGUT COMPONENT-867;Lo;0;L;;;;;N;;;;; +18DE3;TANGUT COMPONENT-868;Lo;0;L;;;;;N;;;;; +18DE4;TANGUT COMPONENT-869;Lo;0;L;;;;;N;;;;; +18DE5;TANGUT COMPONENT-870;Lo;0;L;;;;;N;;;;; +18DE6;TANGUT COMPONENT-871;Lo;0;L;;;;;N;;;;; +18DE7;TANGUT COMPONENT-872;Lo;0;L;;;;;N;;;;; +18DE8;TANGUT COMPONENT-873;Lo;0;L;;;;;N;;;;; +18DE9;TANGUT COMPONENT-874;Lo;0;L;;;;;N;;;;; +18DEA;TANGUT COMPONENT-875;Lo;0;L;;;;;N;;;;; +18DEB;TANGUT COMPONENT-876;Lo;0;L;;;;;N;;;;; +18DEC;TANGUT COMPONENT-877;Lo;0;L;;;;;N;;;;; +18DED;TANGUT COMPONENT-878;Lo;0;L;;;;;N;;;;; +18DEE;TANGUT COMPONENT-879;Lo;0;L;;;;;N;;;;; +18DEF;TANGUT COMPONENT-880;Lo;0;L;;;;;N;;;;; +18DF0;TANGUT COMPONENT-881;Lo;0;L;;;;;N;;;;; +18DF1;TANGUT COMPONENT-882;Lo;0;L;;;;;N;;;;; +18DF2;TANGUT COMPONENT-883;Lo;0;L;;;;;N;;;;; 1AFF0;KATAKANA LETTER MINNAN TONE-2;Lm;0;L;;;;;N;;;;; 1AFF1;KATAKANA LETTER MINNAN TONE-3;Lm;0;L;;;;;N;;;;; 1AFF2;KATAKANA LETTER MINNAN TONE-4;Lm;0;L;;;;;N;;;;; @@ -32629,6 +32963,9 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1CCF7;OUTLINED DIGIT SEVEN;Nd;0;EN; 0037;7;7;7;N;;;;; 1CCF8;OUTLINED DIGIT EIGHT;Nd;0;EN; 0038;8;8;8;N;;;;; 1CCF9;OUTLINED DIGIT NINE;Nd;0;EN; 0039;9;9;9;N;;;;; +1CCFA;SNAKE SYMBOL;So;0;ON;;;;;N;;;;; +1CCFB;FLYING SAUCER SYMBOL;So;0;ON;;;;;N;;;;; +1CCFC;NOSE SYMBOL;So;0;ON;;;;;N;;;;; 1CD00;BLOCK OCTANT-3;So;0;ON;;;;;N;;;;; 1CD01;BLOCK OCTANT-23;So;0;ON;;;;;N;;;;; 1CD02;BLOCK OCTANT-123;So;0;ON;;;;;N;;;;; @@ -33065,6 +33402,46 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1CEB1;KEYHOLE;So;0;ON;;;;;N;;;;; 1CEB2;OLD PERSONAL COMPUTER WITH MONITOR IN PORTRAIT ORIENTATION;So;0;ON;;;;;N;;;;; 1CEB3;BLACK RIGHT TRIANGLE CARET;So;0;ON;;;;;N;;;;; +1CEBA;FRAGILE SYMBOL;So;0;ON;;;;;N;;;;; +1CEBB;OFFICE BUILDING SYMBOL;So;0;ON;;;;;N;;;;; +1CEBC;TREE SYMBOL;So;0;ON;;;;;N;;;;; +1CEBD;APPLE SYMBOL;So;0;ON;;;;;N;;;;; +1CEBE;CHERRY SYMBOL;So;0;ON;;;;;N;;;;; +1CEBF;STRAWBERRY SYMBOL;So;0;ON;;;;;N;;;;; +1CEC0;HEBE;So;0;ON;;;;;N;;;;; +1CEC1;IRIS;So;0;ON;;;;;N;;;;; +1CEC2;FLORA;So;0;ON;;;;;N;;;;; +1CEC3;METIS;So;0;ON;;;;;N;;;;; +1CEC4;PARTHENOPE;So;0;ON;;;;;N;;;;; +1CEC5;VICTORIA;So;0;ON;;;;;N;;;;; +1CEC6;EGERIA;So;0;ON;;;;;N;;;;; +1CEC7;IRENE;So;0;ON;;;;;N;;;;; +1CEC8;EUNOMIA;So;0;ON;;;;;N;;;;; +1CEC9;PSYCHE;So;0;ON;;;;;N;;;;; +1CECA;THETIS;So;0;ON;;;;;N;;;;; +1CECB;MELPOMENE;So;0;ON;;;;;N;;;;; +1CECC;FORTUNA;So;0;ON;;;;;N;;;;; +1CECD;ASTRONOMICAL SYMBOL FOR ASTEROID PROSERPINA;So;0;ON;;;;;N;;;;; +1CECE;BELLONA;So;0;ON;;;;;N;;;;; +1CECF;AMPHITRITE;So;0;ON;;;;;N;;;;; +1CED0;LEUKOTHEA;So;0;ON;;;;;N;;;;; +1CEE0;GEOMANTIC FIGURE POPULUS;So;0;ON;;;;;N;;;;; +1CEE1;GEOMANTIC FIGURE TRISTITIA;So;0;ON;;;;;N;;;;; +1CEE2;GEOMANTIC FIGURE ALBUS;So;0;ON;;;;;N;;;;; +1CEE3;GEOMANTIC FIGURE FORTUNA MAJOR;So;0;ON;;;;;N;;;;; +1CEE4;GEOMANTIC FIGURE RUBEUS;So;0;ON;;;;;N;;;;; +1CEE5;GEOMANTIC FIGURE ACQUISITIO;So;0;ON;;;;;N;;;;; +1CEE6;GEOMANTIC FIGURE CONJUNCTIO;So;0;ON;;;;;N;;;;; +1CEE7;GEOMANTIC FIGURE CAPUT DRACONIS;So;0;ON;;;;;N;;;;; +1CEE8;GEOMANTIC FIGURE LAETITIA;So;0;ON;;;;;N;;;;; +1CEE9;GEOMANTIC FIGURE CARCER;So;0;ON;;;;;N;;;;; +1CEEA;GEOMANTIC FIGURE AMISSIO;So;0;ON;;;;;N;;;;; +1CEEB;GEOMANTIC FIGURE PUELLA;So;0;ON;;;;;N;;;;; +1CEEC;GEOMANTIC FIGURE FORTUNA MINOR;So;0;ON;;;;;N;;;;; +1CEED;GEOMANTIC FIGURE PUER;So;0;ON;;;;;N;;;;; +1CEEE;GEOMANTIC FIGURE CAUDA DRACONIS;So;0;ON;;;;;N;;;;; +1CEEF;GEOMANTIC FIGURE VIA;So;0;ON;;;;;N;;;;; +1CEF0;MEDIUM SMALL WHITE CIRCLE WITH HORIZONTAL BAR;Sm;0;ON;;;;;N;;;;; 1CF00;ZNAMENNY COMBINING MARK GORAZDO NIZKO S KRYZHEM ON LEFT;Mn;0;NSM;;;;;N;;;;; 1CF01;ZNAMENNY COMBINING MARK NIZKO S KRYZHEM ON LEFT;Mn;0;NSM;;;;;N;;;;; 1CF02;ZNAMENNY COMBINING MARK TSATA ON LEFT;Mn;0;NSM;;;;;N;;;;; @@ -36004,6 +36381,61 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1E5F9;OL ONAL DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 1E5FA;OL ONAL DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 1E5FF;OL ONAL ABBREVIATION SIGN;Po;0;L;;;;;N;;;;; +1E6C0;TAI YO LETTER LOW KO;Lo;0;L;;;;;N;;;;; +1E6C1;TAI YO LETTER HIGH KO;Lo;0;L;;;;;N;;;;; +1E6C2;TAI YO LETTER LOW KHO;Lo;0;L;;;;;N;;;;; +1E6C3;TAI YO LETTER HIGH KHO;Lo;0;L;;;;;N;;;;; +1E6C4;TAI YO LETTER GO;Lo;0;L;;;;;N;;;;; +1E6C5;TAI YO LETTER NGO;Lo;0;L;;;;;N;;;;; +1E6C6;TAI YO LETTER CO;Lo;0;L;;;;;N;;;;; +1E6C7;TAI YO LETTER LOW XO;Lo;0;L;;;;;N;;;;; +1E6C8;TAI YO LETTER HIGH XO;Lo;0;L;;;;;N;;;;; +1E6C9;TAI YO LETTER LOW NYO;Lo;0;L;;;;;N;;;;; +1E6CA;TAI YO LETTER HIGH NYO;Lo;0;L;;;;;N;;;;; +1E6CB;TAI YO LETTER DO;Lo;0;L;;;;;N;;;;; +1E6CC;TAI YO LETTER LOW TO;Lo;0;L;;;;;N;;;;; +1E6CD;TAI YO LETTER HIGH TO;Lo;0;L;;;;;N;;;;; +1E6CE;TAI YO LETTER THO;Lo;0;L;;;;;N;;;;; +1E6CF;TAI YO LETTER NO;Lo;0;L;;;;;N;;;;; +1E6D0;TAI YO LETTER BO;Lo;0;L;;;;;N;;;;; +1E6D1;TAI YO LETTER LOW PO;Lo;0;L;;;;;N;;;;; +1E6D2;TAI YO LETTER HIGH PO;Lo;0;L;;;;;N;;;;; +1E6D3;TAI YO LETTER PHO;Lo;0;L;;;;;N;;;;; +1E6D4;TAI YO LETTER LOW FO;Lo;0;L;;;;;N;;;;; +1E6D5;TAI YO LETTER HIGH FO;Lo;0;L;;;;;N;;;;; +1E6D6;TAI YO LETTER MO;Lo;0;L;;;;;N;;;;; +1E6D7;TAI YO LETTER YO;Lo;0;L;;;;;N;;;;; +1E6D8;TAI YO LETTER LO;Lo;0;L;;;;;N;;;;; +1E6D9;TAI YO LETTER VO;Lo;0;L;;;;;N;;;;; +1E6DA;TAI YO LETTER LOW HO;Lo;0;L;;;;;N;;;;; +1E6DB;TAI YO LETTER HIGH HO;Lo;0;L;;;;;N;;;;; +1E6DC;TAI YO LETTER QO;Lo;0;L;;;;;N;;;;; +1E6DD;TAI YO LETTER LOW KVO;Lo;0;L;;;;;N;;;;; +1E6DE;TAI YO LETTER HIGH KVO;Lo;0;L;;;;;N;;;;; +1E6E0;TAI YO LETTER AA;Lo;0;L;;;;;N;;;;; +1E6E1;TAI YO LETTER I;Lo;0;L;;;;;N;;;;; +1E6E2;TAI YO LETTER UE;Lo;0;L;;;;;N;;;;; +1E6E3;TAI YO SIGN UE;Mn;230;NSM;;;;;N;;;;; +1E6E4;TAI YO LETTER U;Lo;0;L;;;;;N;;;;; +1E6E5;TAI YO LETTER AE;Lo;0;L;;;;;N;;;;; +1E6E6;TAI YO SIGN AU;Mn;230;NSM;;;;;N;;;;; +1E6E7;TAI YO LETTER O;Lo;0;L;;;;;N;;;;; +1E6E8;TAI YO LETTER E;Lo;0;L;;;;;N;;;;; +1E6E9;TAI YO LETTER IA;Lo;0;L;;;;;N;;;;; +1E6EA;TAI YO LETTER UEA;Lo;0;L;;;;;N;;;;; +1E6EB;TAI YO LETTER UA;Lo;0;L;;;;;N;;;;; +1E6EC;TAI YO LETTER OO;Lo;0;L;;;;;N;;;;; +1E6ED;TAI YO LETTER AUE;Lo;0;L;;;;;N;;;;; +1E6EE;TAI YO SIGN AY;Mn;230;NSM;;;;;N;;;;; +1E6EF;TAI YO SIGN ANG;Mn;230;NSM;;;;;N;;;;; +1E6F0;TAI YO LETTER AN;Lo;0;L;;;;;N;;;;; +1E6F1;TAI YO LETTER AM;Lo;0;L;;;;;N;;;;; +1E6F2;TAI YO LETTER AK;Lo;0;L;;;;;N;;;;; +1E6F3;TAI YO LETTER AT;Lo;0;L;;;;;N;;;;; +1E6F4;TAI YO LETTER AP;Lo;0;L;;;;;N;;;;; +1E6F5;TAI YO SIGN OM;Mn;230;NSM;;;;;N;;;;; +1E6FE;TAI YO SYMBOL MUEANG;Lo;0;L;;;;;N;;;;; +1E6FF;TAI YO XAM LAI;Lm;0;L;;;;;N;;;;; 1E7E0;ETHIOPIC SYLLABLE HHYA;Lo;0;L;;;;;N;;;;; 1E7E1;ETHIOPIC SYLLABLE HHYU;Lo;0;L;;;;;N;;;;; 1E7E2;ETHIOPIC SYLLABLE HHYI;Lo;0;L;;;;;N;;;;; @@ -38079,6 +38511,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1F6D5;HINDU TEMPLE;So;0;ON;;;;;N;;;;; 1F6D6;HUT;So;0;ON;;;;;N;;;;; 1F6D7;ELEVATOR;So;0;ON;;;;;N;;;;; +1F6D8;LANDSLIDE;So;0;ON;;;;;N;;;;; 1F6DC;WIRELESS;So;0;ON;;;;;N;;;;; 1F6DD;PLAYGROUND SLIDE;So;0;ON;;;;;N;;;;; 1F6DE;WHEEL;So;0;ON;;;;;N;;;;; @@ -38228,6 +38661,10 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1F774;LOT OF FORTUNE;So;0;ON;;;;;N;;;;; 1F775;OCCULTATION;So;0;ON;;;;;N;;;;; 1F776;LUNAR ECLIPSE;So;0;ON;;;;;N;;;;; +1F777;VESTA FORM TWO;So;0;ON;;;;;N;;;;; +1F778;ASTRAEA FORM TWO;So;0;ON;;;;;N;;;;; +1F779;HYGIEA FORM TWO;So;0;ON;;;;;N;;;;; +1F77A;PARTHENOPE FORM TWO;So;0;ON;;;;;N;;;;; 1F77B;HAUMEA;So;0;ON;;;;;N;;;;; 1F77C;MAKEMAKE;So;0;ON;;;;;N;;;;; 1F77D;GONGGONG;So;0;ON;;;;;N;;;;; @@ -38498,6 +38935,15 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1F8BB;SOUTH WEST ARROW FROM BAR;So;0;ON;;;;;N;;;;; 1F8C0;LEFTWARDS ARROW FROM DOWNWARDS ARROW;So;0;ON;;;;;N;;;;; 1F8C1;RIGHTWARDS ARROW FROM DOWNWARDS ARROW;So;0;ON;;;;;N;;;;; +1F8D0;LONG RIGHTWARDS ARROW OVER LONG LEFTWARDS ARROW;Sm;0;ON;;;;;N;;;;; +1F8D1;LONG RIGHTWARDS HARPOON OVER LONG LEFTWARDS HARPOON;Sm;0;ON;;;;;N;;;;; +1F8D2;LONG RIGHTWARDS HARPOON ABOVE SHORT LEFTWARDS HARPOON;Sm;0;ON;;;;;N;;;;; +1F8D3;SHORT RIGHTWARDS HARPOON ABOVE LONG LEFTWARDS HARPOON;Sm;0;ON;;;;;N;;;;; +1F8D4;LONG LEFTWARDS HARPOON ABOVE SHORT RIGHTWARDS HARPOON;Sm;0;ON;;;;;N;;;;; +1F8D5;SHORT LEFTWARDS HARPOON ABOVE LONG RIGHTWARDS HARPOON;Sm;0;ON;;;;;N;;;;; +1F8D6;LONG RIGHTWARDS ARROW THROUGH X;Sm;0;ON;;;;;N;;;;; +1F8D7;LONG RIGHTWARDS ARROW WITH DOUBLE SLASH;Sm;0;ON;;;;;N;;;;; +1F8D8;LONG LEFT RIGHT ARROW WITH DEPENDENT LOBE;Sm;0;ON;;;;;N;;;;; 1F900;CIRCLED CROSS FORMEE WITH FOUR DOTS;So;0;ON;;;;;N;;;;; 1F901;CIRCLED CROSS FORMEE WITH TWO DOTS;So;0;ON;;;;;N;;;;; 1F902;CIRCLED CROSS FORMEE;So;0;ON;;;;;N;;;;; @@ -38838,6 +39284,10 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1FA51;BLACK CHESS KNIGHT-QUEEN;So;0;ON;;;;;N;;;;; 1FA52;BLACK CHESS KNIGHT-ROOK;So;0;ON;;;;;N;;;;; 1FA53;BLACK CHESS KNIGHT-BISHOP;So;0;ON;;;;;N;;;;; +1FA54;WHITE CHESS FERZ;So;0;ON;;;;;N;;;;; +1FA55;WHITE CHESS ALFIL;So;0;ON;;;;;N;;;;; +1FA56;BLACK CHESS FERZ;So;0;ON;;;;;N;;;;; +1FA57;BLACK CHESS ALFIL;So;0;ON;;;;;N;;;;; 1FA60;XIANGQI RED GENERAL;So;0;ON;;;;;N;;;;; 1FA61;XIANGQI RED MANDARIN;So;0;ON;;;;;N;;;;; 1FA62;XIANGQI RED ELEPHANT;So;0;ON;;;;;N;;;;; @@ -38875,6 +39325,8 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1FA87;MARACAS;So;0;ON;;;;;N;;;;; 1FA88;FLUTE;So;0;ON;;;;;N;;;;; 1FA89;HARP;So;0;ON;;;;;N;;;;; +1FA8A;TROMBONE;So;0;ON;;;;;N;;;;; +1FA8E;TREASURE CHEST;So;0;ON;;;;;N;;;;; 1FA8F;SHOVEL;So;0;ON;;;;;N;;;;; 1FA90;RINGED PLANET;So;0;ON;;;;;N;;;;; 1FA91;CHAIR;So;0;ON;;;;;N;;;;; @@ -38931,6 +39383,8 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1FAC4;PREGNANT PERSON;So;0;ON;;;;;N;;;;; 1FAC5;PERSON WITH CROWN;So;0;ON;;;;;N;;;;; 1FAC6;FINGERPRINT;So;0;ON;;;;;N;;;;; +1FAC8;HAIRY CREATURE;So;0;ON;;;;;N;;;;; +1FACD;ORCA;So;0;ON;;;;;N;;;;; 1FACE;MOOSE;So;0;ON;;;;;N;;;;; 1FACF;DONKEY;So;0;ON;;;;;N;;;;; 1FAD0;BLUEBERRIES;So;0;ON;;;;;N;;;;; @@ -38957,6 +39411,8 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1FAE7;BUBBLES;So;0;ON;;;;;N;;;;; 1FAE8;SHAKING FACE;So;0;ON;;;;;N;;;;; 1FAE9;FACE WITH BAGS UNDER EYES;So;0;ON;;;;;N;;;;; +1FAEA;DISTORTED FACE;So;0;ON;;;;;N;;;;; +1FAEF;FIGHT CLOUD;So;0;ON;;;;;N;;;;; 1FAF0;HAND WITH INDEX FINGER AND THUMB CROSSED;So;0;ON;;;;;N;;;;; 1FAF1;RIGHTWARDS HAND;So;0;ON;;;;;N;;;;; 1FAF2;LEFTWARDS HAND;So;0;ON;;;;;N;;;;; @@ -39215,14 +39671,15 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1FBF7;SEGMENTED DIGIT SEVEN;Nd;0;EN; 0037;7;7;7;N;;;;; 1FBF8;SEGMENTED DIGIT EIGHT;Nd;0;EN; 0038;8;8;8;N;;;;; 1FBF9;SEGMENTED DIGIT NINE;Nd;0;EN; 0039;9;9;9;N;;;;; +1FBFA;ALARM BELL SYMBOL;So;0;ON;;;;;N;;;;; 20000;;Lo;0;L;;;;;N;;;;; 2A6DF;;Lo;0;L;;;;;N;;;;; 2A700;;Lo;0;L;;;;;N;;;;; -2B739;;Lo;0;L;;;;;N;;;;; +2B73F;;Lo;0;L;;;;;N;;;;; 2B740;;Lo;0;L;;;;;N;;;;; 2B81D;;Lo;0;L;;;;;N;;;;; 2B820;;Lo;0;L;;;;;N;;;;; -2CEA1;;Lo;0;L;;;;;N;;;;; +2CEAD;;Lo;0;L;;;;;N;;;;; 2CEB0;;Lo;0;L;;;;;N;;;;; 2EBE0;;Lo;0;L;;;;;N;;;;; 2EBF0;;Lo;0;L;;;;;N;;;;; @@ -39773,6 +40230,8 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 3134A;;Lo;0;L;;;;;N;;;;; 31350;;Lo;0;L;;;;;N;;;;; 323AF;;Lo;0;L;;;;;N;;;;; +323B0;;Lo;0;L;;;;;N;;;;; +33479;;Lo;0;L;;;;;N;;;;; E0001;LANGUAGE TAG;Cf;0;BN;;;;;N;;;;; E0020;TAG SPACE;Cf;0;BN;;;;;N;;;;; E0021;TAG EXCLAMATION MARK;Cf;0;BN;;;;;N;;;;; diff --git a/src/java.base/share/data/unicodedata/auxiliary/GraphemeBreakProperty.txt b/src/java.base/share/data/unicodedata/auxiliary/GraphemeBreakProperty.txt index a4b7b6fbc3c..19b13571f34 100644 --- a/src/java.base/share/data/unicodedata/auxiliary/GraphemeBreakProperty.txt +++ b/src/java.base/share/data/unicodedata/auxiliary/GraphemeBreakProperty.txt @@ -1,6 +1,6 @@ -# GraphemeBreakProperty-16.0.0.txt -# Date: 2024-05-31, 18:09:38 GMT -# Copyright (c) 2024 Unicode, Inc. +# GraphemeBreakProperty-17.0.0.txt +# Date: 2025-06-30, 06:20:23 GMT +# © 2025 Unicode®, Inc. # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. # For terms of use and license, see https://www.unicode.org/terms_of_use.html # @@ -30,12 +30,11 @@ 113D1 ; Prepend # Lo TULU-TIGALARI REPHA 1193F ; Prepend # Lo DIVES AKURU PREFIXED NASAL SIGN 11941 ; Prepend # Lo DIVES AKURU INITIAL RA -11A3A ; Prepend # Lo ZANABAZAR SQUARE CLUSTER-INITIAL LETTER RA 11A84..11A89 ; Prepend # Lo [6] SOYOMBO SIGN JIHVAMULIYA..SOYOMBO CLUSTER-INITIAL LETTER SA 11D46 ; Prepend # Lo MASARAM GONDI REPHA 11F02 ; Prepend # Lo KAWI SIGN REPHA -# Total code points: 28 +# Total code points: 27 # ================================================ @@ -243,7 +242,8 @@ E01F0..E0FFF ; Control # Cn [3600] .. 1A7F ; Extend # Mn TAI THAM COMBINING CRYPTOGRAMMIC DOT 1AB0..1ABD ; Extend # Mn [14] COMBINING DOUBLED CIRCUMFLEX ACCENT..COMBINING PARENTHESES BELOW 1ABE ; Extend # Me COMBINING PARENTHESES OVERLAY -1ABF..1ACE ; Extend # Mn [16] COMBINING LATIN SMALL LETTER W BELOW..COMBINING LATIN SMALL LETTER INSULAR T +1ABF..1ADD ; Extend # Mn [31] COMBINING LATIN SMALL LETTER W BELOW..COMBINING DOT-AND-RING BELOW +1AE0..1AEB ; Extend # Mn [12] COMBINING LEFT TACK ABOVE..COMBINING DOUBLE RIGHTWARDS ARROW ABOVE 1B00..1B03 ; Extend # Mn [4] BALINESE SIGN ULU RICEM..BALINESE SIGN SURANG 1B34 ; Extend # Mn BALINESE SIGN REREKAN 1B35 ; Extend # Mc BALINESE VOWEL SIGN TEDUNG @@ -339,7 +339,7 @@ FF9E..FF9F ; Extend # Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDT 10D24..10D27 ; Extend # Mn [4] HANIFI ROHINGYA SIGN HARBAHAY..HANIFI ROHINGYA SIGN TASSI 10D69..10D6D ; Extend # Mn [5] GARAY VOWEL SIGN E..GARAY CONSONANT NASALIZATION MARK 10EAB..10EAC ; Extend # Mn [2] YEZIDI COMBINING HAMZA MARK..YEZIDI COMBINING MADDA MARK -10EFC..10EFF ; Extend # Mn [4] ARABIC COMBINING ALEF OVERLAY..ARABIC SMALL LOW WORD MADDA +10EFA..10EFF ; Extend # Mn [6] ARABIC DOUBLE VERTICAL BAR BELOW..ARABIC SMALL LOW WORD MADDA 10F46..10F50 ; Extend # Mn [11] SOGDIAN COMBINING DOT BELOW..SOGDIAN COMBINING STROKE BELOW 10F82..10F85 ; Extend # Mn [4] OLD UYGHUR COMBINING DOT ABOVE..OLD UYGHUR COMBINING TWO DOTS BELOW 11001 ; Extend # Mn BRAHMI SIGN ANUSVARA @@ -430,6 +430,9 @@ FF9E..FF9F ; Extend # Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDT 11A59..11A5B ; Extend # Mn [3] SOYOMBO VOWEL SIGN VOCALIC R..SOYOMBO VOWEL LENGTH MARK 11A8A..11A96 ; Extend # Mn [13] SOYOMBO FINAL CONSONANT SIGN G..SOYOMBO SIGN ANUSVARA 11A98..11A99 ; Extend # Mn [2] SOYOMBO GEMINATION MARK..SOYOMBO SUBJOINER +11B60 ; Extend # Mn SHARADA VOWEL SIGN OE +11B62..11B64 ; Extend # Mn [3] SHARADA VOWEL SIGN UE..SHARADA VOWEL SIGN SHORT E +11B66 ; Extend # Mn SHARADA VOWEL SIGN CANDRA E 11C30..11C36 ; Extend # Mn [7] BHAIKSUKI VOWEL SIGN I..BHAIKSUKI VOWEL SIGN VOCALIC L 11C38..11C3D ; Extend # Mn [6] BHAIKSUKI VOWEL SIGN E..BHAIKSUKI SIGN ANUSVARA 11C3F ; Extend # Mn BHAIKSUKI SIGN VIRAMA @@ -489,13 +492,17 @@ FF9E..FF9F ; Extend # Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDT 1E2EC..1E2EF ; Extend # Mn [4] WANCHO TONE TUP..WANCHO TONE KOINI 1E4EC..1E4EF ; Extend # Mn [4] NAG MUNDARI SIGN MUHOR..NAG MUNDARI SIGN SUTUH 1E5EE..1E5EF ; Extend # Mn [2] OL ONAL SIGN MU..OL ONAL SIGN IKIR +1E6E3 ; Extend # Mn TAI YO SIGN UE +1E6E6 ; Extend # Mn TAI YO SIGN AU +1E6EE..1E6EF ; Extend # Mn [2] TAI YO SIGN AY..TAI YO SIGN ANG +1E6F5 ; Extend # Mn TAI YO SIGN OM 1E8D0..1E8D6 ; Extend # Mn [7] MENDE KIKAKUI COMBINING NUMBER TEENS..MENDE KIKAKUI COMBINING NUMBER MILLIONS 1E944..1E94A ; Extend # Mn [7] ADLAM ALIF LENGTHENER..ADLAM NUKTA 1F3FB..1F3FF ; Extend # Sk [5] EMOJI MODIFIER FITZPATRICK TYPE-1-2..EMOJI MODIFIER FITZPATRICK TYPE-6 E0020..E007F ; Extend # Cf [96] TAG SPACE..CANCEL TAG E0100..E01EF ; Extend # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR-256 -# Total code points: 2198 +# Total code points: 2237 # ================================================ @@ -646,6 +653,9 @@ ABEC ; SpacingMark # Mc MEETEI MAYEK LUM IYEK 11A39 ; SpacingMark # Mc ZANABAZAR SQUARE SIGN VISARGA 11A57..11A58 ; SpacingMark # Mc [2] SOYOMBO VOWEL SIGN AI..SOYOMBO VOWEL SIGN AU 11A97 ; SpacingMark # Mc SOYOMBO SIGN VISARGA +11B61 ; SpacingMark # Mc SHARADA VOWEL SIGN OOE +11B65 ; SpacingMark # Mc SHARADA VOWEL SIGN SHORT O +11B67 ; SpacingMark # Mc SHARADA VOWEL SIGN CANDRA O 11C2F ; SpacingMark # Mc BHAIKSUKI VOWEL SIGN AA 11C3E ; SpacingMark # Mc BHAIKSUKI SIGN VISARGA 11CA9 ; SpacingMark # Mc MARCHEN SUBJOINED LETTER YA @@ -661,7 +671,7 @@ ABEC ; SpacingMark # Mc MEETEI MAYEK LUM IYEK 1612A..1612C ; SpacingMark # Mc [3] GURUNG KHEMA CONSONANT SIGN MEDIAL YA..GURUNG KHEMA CONSONANT SIGN MEDIAL HA 16F51..16F87 ; SpacingMark # Mc [55] MIAO SIGN ASPIRATION..MIAO VOWEL SIGN UI -# Total code points: 378 +# Total code points: 381 # ================================================ diff --git a/src/java.base/share/data/unicodedata/auxiliary/GraphemeBreakTest.txt b/src/java.base/share/data/unicodedata/auxiliary/GraphemeBreakTest.txt index 3eb4b307e8e..e1215547c58 100644 --- a/src/java.base/share/data/unicodedata/auxiliary/GraphemeBreakTest.txt +++ b/src/java.base/share/data/unicodedata/auxiliary/GraphemeBreakTest.txt @@ -1,6 +1,6 @@ -# GraphemeBreakTest-16.0.0.txt -# Date: 2024-05-02, 15:02:48 GMT -# Copyright (c) 2024 Unicode, Inc. +# GraphemeBreakTest-17.0.0.txt +# Date: 2025-03-24, 14:45:55 GMT +# © 2025 Unicode®, Inc. # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. # For terms of use and license, see https://www.unicode.org/terms_of_use.html # @@ -16,1106 +16,781 @@ # × wherever there is not. # the format can change, but currently it shows: # - the sample character name -# - (x) the Grapheme_Cluster_Break property value for the sample character +# - (x) the Grapheme_Cluster_Break property value for the sample character and +# any other properties relevant to the algorithm, as described in +# GraphemeBreakTest.html # - [x] the rule that determines whether there is a break or not, # as listed in the Rules section of GraphemeBreakTest.html # # These samples may be extended or changed in the future. # -÷ 0020 ÷ 0020 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 0020 × 0308 ÷ 0020 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 0020 ÷ 000D ÷ # ÷ [0.2] SPACE (Other) ÷ [5.0] (CR) ÷ [0.3] -÷ 0020 × 0308 ÷ 000D ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] -÷ 0020 ÷ 000A ÷ # ÷ [0.2] SPACE (Other) ÷ [5.0] (LF) ÷ [0.3] -÷ 0020 × 0308 ÷ 000A ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 0020 ÷ 0001 ÷ # ÷ [0.2] SPACE (Other) ÷ [5.0] (Control) ÷ [0.3] -÷ 0020 × 0308 ÷ 0001 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 0020 × 200C ÷ # ÷ [0.2] SPACE (Other) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 0020 × 0308 × 200C ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 0020 ÷ 1F1E6 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 0020 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 0020 ÷ 0600 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 0020 × 0308 ÷ 0600 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 0020 × 0A03 ÷ # ÷ [0.2] SPACE (Other) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 0020 × 0308 × 0A03 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 0020 ÷ 1100 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 0020 × 0308 ÷ 1100 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 0020 ÷ 1160 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 0020 × 0308 ÷ 1160 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 0020 ÷ 11A8 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 0020 × 0308 ÷ 11A8 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 0020 ÷ AC00 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 0020 × 0308 ÷ AC00 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 0020 ÷ AC01 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 0020 × 0308 ÷ AC01 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 0020 × 0903 ÷ # ÷ [0.2] SPACE (Other) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 0020 × 0308 × 0903 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 0020 ÷ 0904 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 0020 × 0308 ÷ 0904 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 0020 ÷ 0D4E ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 0020 × 0308 ÷ 0D4E ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 0020 ÷ 0915 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0020 × 0308 ÷ 0915 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0020 ÷ 231A ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 0020 × 0308 ÷ 231A ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 0020 × 0300 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 0020 × 0308 × 0300 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 0020 × 0900 ÷ # ÷ [0.2] SPACE (Other) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 0020 × 0308 × 0900 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 0020 × 094D ÷ # ÷ [0.2] SPACE (Other) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 0020 × 0308 × 094D ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 0020 × 200D ÷ # ÷ [0.2] SPACE (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0020 × 0308 × 200D ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0020 ÷ 0378 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] (Other) ÷ [0.3] -÷ 0020 × 0308 ÷ 0378 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 000D ÷ 0020 ÷ # ÷ [0.2] (CR) ÷ [4.0] SPACE (Other) ÷ [0.3] -÷ 000D ÷ 0308 ÷ 0020 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] ÷ 000D ÷ 000D ÷ # ÷ [0.2] (CR) ÷ [4.0] (CR) ÷ [0.3] -÷ 000D ÷ 0308 ÷ 000D ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] +÷ 000D ÷ 0308 ÷ 000D ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] ÷ 000D × 000A ÷ # ÷ [0.2] (CR) × [3.0] (LF) ÷ [0.3] -÷ 000D ÷ 0308 ÷ 000A ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 000D ÷ 0001 ÷ # ÷ [0.2] (CR) ÷ [4.0] (Control) ÷ [0.3] -÷ 000D ÷ 0308 ÷ 0001 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 000D ÷ 200C ÷ # ÷ [0.2] (CR) ÷ [4.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 000D ÷ 0308 × 200C ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] +÷ 000D ÷ 0308 ÷ 000A ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 000D ÷ 0000 ÷ # ÷ [0.2] (CR) ÷ [4.0] (Control) ÷ [0.3] +÷ 000D ÷ 0308 ÷ 0000 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 000D ÷ 094D ÷ # ÷ [0.2] (CR) ÷ [4.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 000D ÷ 0308 × 094D ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 000D ÷ 0300 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 000D ÷ 0308 × 0300 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 000D ÷ 200C ÷ # ÷ [0.2] (CR) ÷ [4.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 000D ÷ 0308 × 200C ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 000D ÷ 200D ÷ # ÷ [0.2] (CR) ÷ [4.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 000D ÷ 0308 × 200D ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] ÷ 000D ÷ 1F1E6 ÷ # ÷ [0.2] (CR) ÷ [4.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 000D ÷ 0308 ÷ 1F1E6 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 000D ÷ 0600 ÷ # ÷ [0.2] (CR) ÷ [4.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 000D ÷ 0308 ÷ 0600 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 000D ÷ 0A03 ÷ # ÷ [0.2] (CR) ÷ [4.0] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 000D ÷ 0308 × 0A03 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 000D ÷ 0308 ÷ 1F1E6 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 000D ÷ 06DD ÷ # ÷ [0.2] (CR) ÷ [4.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 000D ÷ 0308 ÷ 06DD ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 000D ÷ 0903 ÷ # ÷ [0.2] (CR) ÷ [4.0] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 000D ÷ 0308 × 0903 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] ÷ 000D ÷ 1100 ÷ # ÷ [0.2] (CR) ÷ [4.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 000D ÷ 0308 ÷ 1100 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 000D ÷ 0308 ÷ 1100 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] ÷ 000D ÷ 1160 ÷ # ÷ [0.2] (CR) ÷ [4.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 000D ÷ 0308 ÷ 1160 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 000D ÷ 0308 ÷ 1160 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] ÷ 000D ÷ 11A8 ÷ # ÷ [0.2] (CR) ÷ [4.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 000D ÷ 0308 ÷ 11A8 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 000D ÷ 0308 ÷ 11A8 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] ÷ 000D ÷ AC00 ÷ # ÷ [0.2] (CR) ÷ [4.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 000D ÷ 0308 ÷ AC00 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 000D ÷ 0308 ÷ AC00 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] ÷ 000D ÷ AC01 ÷ # ÷ [0.2] (CR) ÷ [4.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 000D ÷ 0308 ÷ AC01 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 000D ÷ 0903 ÷ # ÷ [0.2] (CR) ÷ [4.0] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 000D ÷ 0308 × 0903 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 000D ÷ 0904 ÷ # ÷ [0.2] (CR) ÷ [4.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 000D ÷ 0308 ÷ 0904 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 000D ÷ 0D4E ÷ # ÷ [0.2] (CR) ÷ [4.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 000D ÷ 0308 ÷ 0D4E ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 000D ÷ 0915 ÷ # ÷ [0.2] (CR) ÷ [4.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 000D ÷ 0308 ÷ 0915 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 000D ÷ 231A ÷ # ÷ [0.2] (CR) ÷ [4.0] WATCH (ExtPict) ÷ [0.3] -÷ 000D ÷ 0308 ÷ 231A ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 000D ÷ 0300 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 000D ÷ 0308 × 0300 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 000D ÷ 0900 ÷ # ÷ [0.2] (CR) ÷ [4.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 000D ÷ 0308 × 0900 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 000D ÷ 094D ÷ # ÷ [0.2] (CR) ÷ [4.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 000D ÷ 0308 × 094D ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 000D ÷ 200D ÷ # ÷ [0.2] (CR) ÷ [4.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 000D ÷ 0308 × 200D ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 000D ÷ 0378 ÷ # ÷ [0.2] (CR) ÷ [4.0] (Other) ÷ [0.3] -÷ 000D ÷ 0308 ÷ 0378 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 000A ÷ 0020 ÷ # ÷ [0.2] (LF) ÷ [4.0] SPACE (Other) ÷ [0.3] -÷ 000A ÷ 0308 ÷ 0020 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] +÷ 000D ÷ 0308 ÷ AC01 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 000D ÷ 0915 ÷ # ÷ [0.2] (CR) ÷ [4.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 000D ÷ 0308 ÷ 0915 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 000D ÷ 00A9 ÷ # ÷ [0.2] (CR) ÷ [4.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 000D ÷ 0308 ÷ 00A9 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 000D ÷ 0020 ÷ # ÷ [0.2] (CR) ÷ [4.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 000D ÷ 0308 ÷ 0020 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 000D ÷ 0378 ÷ # ÷ [0.2] (CR) ÷ [4.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 000D ÷ 0308 ÷ 0378 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] ÷ 000A ÷ 000D ÷ # ÷ [0.2] (LF) ÷ [4.0] (CR) ÷ [0.3] -÷ 000A ÷ 0308 ÷ 000D ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] +÷ 000A ÷ 0308 ÷ 000D ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] ÷ 000A ÷ 000A ÷ # ÷ [0.2] (LF) ÷ [4.0] (LF) ÷ [0.3] -÷ 000A ÷ 0308 ÷ 000A ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 000A ÷ 0001 ÷ # ÷ [0.2] (LF) ÷ [4.0] (Control) ÷ [0.3] -÷ 000A ÷ 0308 ÷ 0001 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 000A ÷ 200C ÷ # ÷ [0.2] (LF) ÷ [4.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 000A ÷ 0308 × 200C ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] +÷ 000A ÷ 0308 ÷ 000A ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 000A ÷ 0000 ÷ # ÷ [0.2] (LF) ÷ [4.0] (Control) ÷ [0.3] +÷ 000A ÷ 0308 ÷ 0000 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 000A ÷ 094D ÷ # ÷ [0.2] (LF) ÷ [4.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 000A ÷ 0308 × 094D ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 000A ÷ 0300 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 000A ÷ 0308 × 0300 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 000A ÷ 200C ÷ # ÷ [0.2] (LF) ÷ [4.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 000A ÷ 0308 × 200C ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 000A ÷ 200D ÷ # ÷ [0.2] (LF) ÷ [4.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 000A ÷ 0308 × 200D ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] ÷ 000A ÷ 1F1E6 ÷ # ÷ [0.2] (LF) ÷ [4.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 000A ÷ 0308 ÷ 1F1E6 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 000A ÷ 0600 ÷ # ÷ [0.2] (LF) ÷ [4.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 000A ÷ 0308 ÷ 0600 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 000A ÷ 0A03 ÷ # ÷ [0.2] (LF) ÷ [4.0] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 000A ÷ 0308 × 0A03 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 000A ÷ 0308 ÷ 1F1E6 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 000A ÷ 06DD ÷ # ÷ [0.2] (LF) ÷ [4.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 000A ÷ 0308 ÷ 06DD ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 000A ÷ 0903 ÷ # ÷ [0.2] (LF) ÷ [4.0] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 000A ÷ 0308 × 0903 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] ÷ 000A ÷ 1100 ÷ # ÷ [0.2] (LF) ÷ [4.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 000A ÷ 0308 ÷ 1100 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 000A ÷ 0308 ÷ 1100 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] ÷ 000A ÷ 1160 ÷ # ÷ [0.2] (LF) ÷ [4.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 000A ÷ 0308 ÷ 1160 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 000A ÷ 0308 ÷ 1160 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] ÷ 000A ÷ 11A8 ÷ # ÷ [0.2] (LF) ÷ [4.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 000A ÷ 0308 ÷ 11A8 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 000A ÷ 0308 ÷ 11A8 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] ÷ 000A ÷ AC00 ÷ # ÷ [0.2] (LF) ÷ [4.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 000A ÷ 0308 ÷ AC00 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 000A ÷ 0308 ÷ AC00 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] ÷ 000A ÷ AC01 ÷ # ÷ [0.2] (LF) ÷ [4.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 000A ÷ 0308 ÷ AC01 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 000A ÷ 0903 ÷ # ÷ [0.2] (LF) ÷ [4.0] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 000A ÷ 0308 × 0903 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 000A ÷ 0904 ÷ # ÷ [0.2] (LF) ÷ [4.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 000A ÷ 0308 ÷ 0904 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 000A ÷ 0D4E ÷ # ÷ [0.2] (LF) ÷ [4.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 000A ÷ 0308 ÷ 0D4E ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 000A ÷ 0915 ÷ # ÷ [0.2] (LF) ÷ [4.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 000A ÷ 0308 ÷ 0915 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 000A ÷ 231A ÷ # ÷ [0.2] (LF) ÷ [4.0] WATCH (ExtPict) ÷ [0.3] -÷ 000A ÷ 0308 ÷ 231A ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 000A ÷ 0300 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 000A ÷ 0308 × 0300 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 000A ÷ 0900 ÷ # ÷ [0.2] (LF) ÷ [4.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 000A ÷ 0308 × 0900 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 000A ÷ 094D ÷ # ÷ [0.2] (LF) ÷ [4.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 000A ÷ 0308 × 094D ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 000A ÷ 200D ÷ # ÷ [0.2] (LF) ÷ [4.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 000A ÷ 0308 × 200D ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 000A ÷ 0378 ÷ # ÷ [0.2] (LF) ÷ [4.0] (Other) ÷ [0.3] -÷ 000A ÷ 0308 ÷ 0378 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 0001 ÷ 0020 ÷ # ÷ [0.2] (Control) ÷ [4.0] SPACE (Other) ÷ [0.3] -÷ 0001 ÷ 0308 ÷ 0020 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 0001 ÷ 000D ÷ # ÷ [0.2] (Control) ÷ [4.0] (CR) ÷ [0.3] -÷ 0001 ÷ 0308 ÷ 000D ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] -÷ 0001 ÷ 000A ÷ # ÷ [0.2] (Control) ÷ [4.0] (LF) ÷ [0.3] -÷ 0001 ÷ 0308 ÷ 000A ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 0001 ÷ 0001 ÷ # ÷ [0.2] (Control) ÷ [4.0] (Control) ÷ [0.3] -÷ 0001 ÷ 0308 ÷ 0001 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 0001 ÷ 200C ÷ # ÷ [0.2] (Control) ÷ [4.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 0001 ÷ 0308 × 200C ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 0001 ÷ 1F1E6 ÷ # ÷ [0.2] (Control) ÷ [4.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 0001 ÷ 0308 ÷ 1F1E6 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 0001 ÷ 0600 ÷ # ÷ [0.2] (Control) ÷ [4.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 0001 ÷ 0308 ÷ 0600 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 0001 ÷ 0A03 ÷ # ÷ [0.2] (Control) ÷ [4.0] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 0001 ÷ 0308 × 0A03 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 0001 ÷ 1100 ÷ # ÷ [0.2] (Control) ÷ [4.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 0001 ÷ 0308 ÷ 1100 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 0001 ÷ 1160 ÷ # ÷ [0.2] (Control) ÷ [4.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 0001 ÷ 0308 ÷ 1160 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 0001 ÷ 11A8 ÷ # ÷ [0.2] (Control) ÷ [4.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 0001 ÷ 0308 ÷ 11A8 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 0001 ÷ AC00 ÷ # ÷ [0.2] (Control) ÷ [4.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 0001 ÷ 0308 ÷ AC00 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 0001 ÷ AC01 ÷ # ÷ [0.2] (Control) ÷ [4.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 0001 ÷ 0308 ÷ AC01 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 0001 ÷ 0903 ÷ # ÷ [0.2] (Control) ÷ [4.0] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 0001 ÷ 0308 × 0903 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 0001 ÷ 0904 ÷ # ÷ [0.2] (Control) ÷ [4.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 0001 ÷ 0308 ÷ 0904 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 0001 ÷ 0D4E ÷ # ÷ [0.2] (Control) ÷ [4.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 0001 ÷ 0308 ÷ 0D4E ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 0001 ÷ 0915 ÷ # ÷ [0.2] (Control) ÷ [4.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0001 ÷ 0308 ÷ 0915 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0001 ÷ 231A ÷ # ÷ [0.2] (Control) ÷ [4.0] WATCH (ExtPict) ÷ [0.3] -÷ 0001 ÷ 0308 ÷ 231A ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 0001 ÷ 0300 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 0001 ÷ 0308 × 0300 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 0001 ÷ 0900 ÷ # ÷ [0.2] (Control) ÷ [4.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 0001 ÷ 0308 × 0900 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 0001 ÷ 094D ÷ # ÷ [0.2] (Control) ÷ [4.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 0001 ÷ 0308 × 094D ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 0001 ÷ 200D ÷ # ÷ [0.2] (Control) ÷ [4.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0001 ÷ 0308 × 200D ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0001 ÷ 0378 ÷ # ÷ [0.2] (Control) ÷ [4.0] (Other) ÷ [0.3] -÷ 0001 ÷ 0308 ÷ 0378 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 200C ÷ 0020 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 200C × 0308 ÷ 0020 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 200C ÷ 000D ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [5.0] (CR) ÷ [0.3] -÷ 200C × 0308 ÷ 000D ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] -÷ 200C ÷ 000A ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [5.0] (LF) ÷ [0.3] -÷ 200C × 0308 ÷ 000A ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 200C ÷ 0001 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [5.0] (Control) ÷ [0.3] -÷ 200C × 0308 ÷ 0001 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 200C × 200C ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 200C × 0308 × 200C ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 200C ÷ 1F1E6 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 200C × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 200C ÷ 0600 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 200C × 0308 ÷ 0600 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 200C × 0A03 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 200C × 0308 × 0A03 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 200C ÷ 1100 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 200C × 0308 ÷ 1100 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 200C ÷ 1160 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 200C × 0308 ÷ 1160 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 200C ÷ 11A8 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 200C × 0308 ÷ 11A8 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 200C ÷ AC00 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 200C × 0308 ÷ AC00 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 200C ÷ AC01 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 200C × 0308 ÷ AC01 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 200C × 0903 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 200C × 0308 × 0903 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 200C ÷ 0904 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 200C × 0308 ÷ 0904 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 200C ÷ 0D4E ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 200C × 0308 ÷ 0D4E ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 200C ÷ 0915 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 200C × 0308 ÷ 0915 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 200C ÷ 231A ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 200C × 0308 ÷ 231A ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 200C × 0300 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 200C × 0308 × 0300 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 200C × 0900 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 200C × 0308 × 0900 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 200C × 094D ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 200C × 0308 × 094D ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 200C × 200D ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 200C × 0308 × 200D ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 200C ÷ 0378 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] (Other) ÷ [0.3] -÷ 200C × 0308 ÷ 0378 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 1F1E6 ÷ 0020 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 1F1E6 × 0308 ÷ 0020 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] +÷ 000A ÷ 0308 ÷ AC01 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 000A ÷ 0915 ÷ # ÷ [0.2] (LF) ÷ [4.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 000A ÷ 0308 ÷ 0915 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 000A ÷ 00A9 ÷ # ÷ [0.2] (LF) ÷ [4.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 000A ÷ 0308 ÷ 00A9 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 000A ÷ 0020 ÷ # ÷ [0.2] (LF) ÷ [4.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 000A ÷ 0308 ÷ 0020 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 000A ÷ 0378 ÷ # ÷ [0.2] (LF) ÷ [4.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 000A ÷ 0308 ÷ 0378 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0000 ÷ 000D ÷ # ÷ [0.2] (Control) ÷ [4.0] (CR) ÷ [0.3] +÷ 0000 ÷ 0308 ÷ 000D ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ 0000 ÷ 000A ÷ # ÷ [0.2] (Control) ÷ [4.0] (LF) ÷ [0.3] +÷ 0000 ÷ 0308 ÷ 000A ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 0000 ÷ 0000 ÷ # ÷ [0.2] (Control) ÷ [4.0] (Control) ÷ [0.3] +÷ 0000 ÷ 0308 ÷ 0000 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 0000 ÷ 094D ÷ # ÷ [0.2] (Control) ÷ [4.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 0000 ÷ 0308 × 094D ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 0000 ÷ 0300 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 0000 ÷ 0308 × 0300 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 0000 ÷ 200C ÷ # ÷ [0.2] (Control) ÷ [4.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 0000 ÷ 0308 × 200C ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 0000 ÷ 200D ÷ # ÷ [0.2] (Control) ÷ [4.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 0000 ÷ 0308 × 200D ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 0000 ÷ 1F1E6 ÷ # ÷ [0.2] (Control) ÷ [4.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 0000 ÷ 0308 ÷ 1F1E6 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 0000 ÷ 06DD ÷ # ÷ [0.2] (Control) ÷ [4.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 0000 ÷ 0308 ÷ 06DD ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 0000 ÷ 0903 ÷ # ÷ [0.2] (Control) ÷ [4.0] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 0000 ÷ 0308 × 0903 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 0000 ÷ 1100 ÷ # ÷ [0.2] (Control) ÷ [4.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 0000 ÷ 0308 ÷ 1100 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 0000 ÷ 1160 ÷ # ÷ [0.2] (Control) ÷ [4.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 0000 ÷ 0308 ÷ 1160 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 0000 ÷ 11A8 ÷ # ÷ [0.2] (Control) ÷ [4.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 0000 ÷ 0308 ÷ 11A8 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 0000 ÷ AC00 ÷ # ÷ [0.2] (Control) ÷ [4.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 0000 ÷ 0308 ÷ AC00 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 0000 ÷ AC01 ÷ # ÷ [0.2] (Control) ÷ [4.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 0000 ÷ 0308 ÷ AC01 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 0000 ÷ 0915 ÷ # ÷ [0.2] (Control) ÷ [4.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 0000 ÷ 0308 ÷ 0915 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 0000 ÷ 00A9 ÷ # ÷ [0.2] (Control) ÷ [4.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 0000 ÷ 0308 ÷ 00A9 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 0000 ÷ 0020 ÷ # ÷ [0.2] (Control) ÷ [4.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0000 ÷ 0308 ÷ 0020 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0000 ÷ 0378 ÷ # ÷ [0.2] (Control) ÷ [4.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0000 ÷ 0308 ÷ 0378 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 094D ÷ 000D ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ 094D × 0308 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ 094D ÷ 000A ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 094D × 0308 ÷ 000A ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 094D ÷ 0000 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 094D × 0308 ÷ 0000 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 094D × 094D ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 094D × 0308 × 094D ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 094D × 0300 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 094D × 0308 × 0300 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 094D × 200C ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 094D × 0308 × 200C ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 094D × 200D ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 094D × 0308 × 200D ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 094D ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 094D × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 094D ÷ 06DD ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 094D × 0308 ÷ 06DD ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 094D × 0903 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 094D × 0308 × 0903 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 094D ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 094D × 0308 ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 094D ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 094D × 0308 ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 094D ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 094D × 0308 ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 094D ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 094D × 0308 ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 094D ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 094D × 0308 ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 094D ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 094D × 0308 ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 094D ÷ 00A9 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 094D × 0308 ÷ 00A9 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 094D ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 094D × 0308 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 094D ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 094D × 0308 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0300 ÷ 000D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ 0300 × 0308 ÷ 000D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ 0300 ÷ 000A ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 0300 × 0308 ÷ 000A ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 0300 ÷ 0000 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 0300 × 0308 ÷ 0000 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 0300 × 094D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 0300 × 0308 × 094D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 0300 × 0300 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 0300 × 0308 × 0300 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 0300 × 200C ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 0300 × 0308 × 200C ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 0300 × 200D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 0300 × 0308 × 200D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 0300 ÷ 1F1E6 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 0300 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 0300 ÷ 06DD ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 0300 × 0308 ÷ 06DD ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 0300 × 0903 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 0300 × 0308 × 0903 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 0300 ÷ 1100 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 0300 × 0308 ÷ 1100 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 0300 ÷ 1160 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 0300 × 0308 ÷ 1160 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 0300 ÷ 11A8 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 0300 × 0308 ÷ 11A8 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 0300 ÷ AC00 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 0300 × 0308 ÷ AC00 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 0300 ÷ AC01 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 0300 × 0308 ÷ AC01 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 0300 ÷ 0915 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 0300 × 0308 ÷ 0915 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 0300 ÷ 00A9 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 0300 × 0308 ÷ 00A9 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 0300 ÷ 0020 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0300 × 0308 ÷ 0020 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0300 ÷ 0378 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0300 × 0308 ÷ 0378 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 200C ÷ 000D ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [5.0] (CR) ÷ [0.3] +÷ 200C × 0308 ÷ 000D ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ 200C ÷ 000A ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [5.0] (LF) ÷ [0.3] +÷ 200C × 0308 ÷ 000A ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 200C ÷ 0000 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [5.0] (Control) ÷ [0.3] +÷ 200C × 0308 ÷ 0000 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 200C × 094D ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 200C × 0308 × 094D ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 200C × 0300 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 200C × 0308 × 0300 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 200C × 200C ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 200C × 0308 × 200C ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 200C × 200D ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 200C × 0308 × 200D ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 200C ÷ 1F1E6 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 200C × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 200C ÷ 06DD ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 200C × 0308 ÷ 06DD ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 200C × 0903 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 200C × 0308 × 0903 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 200C ÷ 1100 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 200C × 0308 ÷ 1100 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 200C ÷ 1160 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 200C × 0308 ÷ 1160 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 200C ÷ 11A8 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 200C × 0308 ÷ 11A8 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 200C ÷ AC00 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 200C × 0308 ÷ AC00 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 200C ÷ AC01 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 200C × 0308 ÷ AC01 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 200C ÷ 0915 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 200C × 0308 ÷ 0915 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 200C ÷ 00A9 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 200C × 0308 ÷ 00A9 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 200C ÷ 0020 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 200C × 0308 ÷ 0020 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 200C ÷ 0378 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 200C × 0308 ÷ 0378 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 200D ÷ 000D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [5.0] (CR) ÷ [0.3] +÷ 200D × 0308 ÷ 000D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ 200D ÷ 000A ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [5.0] (LF) ÷ [0.3] +÷ 200D × 0308 ÷ 000A ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 200D ÷ 0000 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [5.0] (Control) ÷ [0.3] +÷ 200D × 0308 ÷ 0000 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 200D × 094D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 200D × 0308 × 094D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 200D × 0300 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 200D × 0308 × 0300 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 200D × 200C ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 200D × 0308 × 200C ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 200D × 200D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 200D × 0308 × 200D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 200D ÷ 1F1E6 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 200D × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 200D ÷ 06DD ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 200D × 0308 ÷ 06DD ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 200D × 0903 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 200D × 0308 × 0903 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 200D ÷ 1100 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 200D × 0308 ÷ 1100 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 200D ÷ 1160 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 200D × 0308 ÷ 1160 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 200D ÷ 11A8 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 200D × 0308 ÷ 11A8 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 200D ÷ AC00 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 200D × 0308 ÷ AC00 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 200D ÷ AC01 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 200D × 0308 ÷ AC01 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 200D ÷ 0915 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 200D × 0308 ÷ 0915 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 200D ÷ 00A9 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 200D × 0308 ÷ 00A9 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 200D ÷ 0020 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 200D × 0308 ÷ 0020 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 200D ÷ 0378 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 200D × 0308 ÷ 0378 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] ÷ 1F1E6 ÷ 000D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [5.0] (CR) ÷ [0.3] -÷ 1F1E6 × 0308 ÷ 000D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] +÷ 1F1E6 × 0308 ÷ 000D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] ÷ 1F1E6 ÷ 000A ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [5.0] (LF) ÷ [0.3] -÷ 1F1E6 × 0308 ÷ 000A ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 1F1E6 ÷ 0001 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [5.0] (Control) ÷ [0.3] -÷ 1F1E6 × 0308 ÷ 0001 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 1F1E6 × 200C ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 1F1E6 × 0308 × 200C ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] +÷ 1F1E6 × 0308 ÷ 000A ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 1F1E6 ÷ 0000 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [5.0] (Control) ÷ [0.3] +÷ 1F1E6 × 0308 ÷ 0000 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 1F1E6 × 094D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 1F1E6 × 0308 × 094D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 1F1E6 × 0300 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 1F1E6 × 0308 × 0300 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 1F1E6 × 200C ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 1F1E6 × 0308 × 200C ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 1F1E6 × 200D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 1F1E6 × 0308 × 200D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] ÷ 1F1E6 × 1F1E6 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [12.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 1F1E6 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 1F1E6 ÷ 0600 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 1F1E6 × 0308 ÷ 0600 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 1F1E6 × 0A03 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 1F1E6 × 0308 × 0A03 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 1F1E6 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 1F1E6 ÷ 06DD ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 1F1E6 × 0308 ÷ 06DD ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 1F1E6 × 0903 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 1F1E6 × 0308 × 0903 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] ÷ 1F1E6 ÷ 1100 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 1F1E6 × 0308 ÷ 1100 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 1F1E6 × 0308 ÷ 1100 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] ÷ 1F1E6 ÷ 1160 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 1F1E6 × 0308 ÷ 1160 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 1F1E6 × 0308 ÷ 1160 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] ÷ 1F1E6 ÷ 11A8 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 1F1E6 × 0308 ÷ 11A8 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 1F1E6 × 0308 ÷ 11A8 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] ÷ 1F1E6 ÷ AC00 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 1F1E6 × 0308 ÷ AC00 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 1F1E6 × 0308 ÷ AC00 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] ÷ 1F1E6 ÷ AC01 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 1F1E6 × 0308 ÷ AC01 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 1F1E6 × 0903 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 1F1E6 × 0308 × 0903 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 1F1E6 ÷ 0904 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 1F1E6 × 0308 ÷ 0904 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 1F1E6 ÷ 0D4E ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 1F1E6 × 0308 ÷ 0D4E ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 1F1E6 ÷ 0915 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 1F1E6 × 0308 ÷ 0915 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 1F1E6 ÷ 231A ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 1F1E6 × 0308 ÷ 231A ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 1F1E6 × 0300 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 1F1E6 × 0308 × 0300 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 1F1E6 × 0900 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 1F1E6 × 0308 × 0900 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 1F1E6 × 094D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 1F1E6 × 0308 × 094D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 1F1E6 × 200D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 1F1E6 × 0308 × 200D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 1F1E6 ÷ 0378 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] (Other) ÷ [0.3] -÷ 1F1E6 × 0308 ÷ 0378 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 0600 × 0020 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] SPACE (Other) ÷ [0.3] -÷ 0600 × 0308 ÷ 0020 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 0600 ÷ 000D ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) ÷ [5.0] (CR) ÷ [0.3] -÷ 0600 × 0308 ÷ 000D ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] -÷ 0600 ÷ 000A ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) ÷ [5.0] (LF) ÷ [0.3] -÷ 0600 × 0308 ÷ 000A ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 0600 ÷ 0001 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) ÷ [5.0] (Control) ÷ [0.3] -÷ 0600 × 0308 ÷ 0001 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 0600 × 200C ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 0600 × 0308 × 200C ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 0600 × 1F1E6 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 0600 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 0600 × 0600 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 0600 × 0308 ÷ 0600 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 0600 × 0A03 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 0600 × 0308 × 0A03 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 0600 × 1100 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 0600 × 0308 ÷ 1100 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 0600 × 1160 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 0600 × 0308 ÷ 1160 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 0600 × 11A8 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 0600 × 0308 ÷ 11A8 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 0600 × AC00 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 0600 × 0308 ÷ AC00 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 0600 × AC01 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 0600 × 0308 ÷ AC01 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 0600 × 0903 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 0600 × 0308 × 0903 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 0600 × 0904 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 0600 × 0308 ÷ 0904 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 0600 × 0D4E ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 0600 × 0308 ÷ 0D4E ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 0600 × 0915 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0600 × 0308 ÷ 0915 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0600 × 231A ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] WATCH (ExtPict) ÷ [0.3] -÷ 0600 × 0308 ÷ 231A ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 0600 × 0300 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 0600 × 0308 × 0300 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 0600 × 0900 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 0600 × 0308 × 0900 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 0600 × 094D ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 0600 × 0308 × 094D ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 0600 × 200D ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0600 × 0308 × 200D ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0600 × 0378 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] (Other) ÷ [0.3] -÷ 0600 × 0308 ÷ 0378 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 0A03 ÷ 0020 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 0A03 × 0308 ÷ 0020 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 0A03 ÷ 000D ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [5.0] (CR) ÷ [0.3] -÷ 0A03 × 0308 ÷ 000D ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] -÷ 0A03 ÷ 000A ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [5.0] (LF) ÷ [0.3] -÷ 0A03 × 0308 ÷ 000A ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 0A03 ÷ 0001 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [5.0] (Control) ÷ [0.3] -÷ 0A03 × 0308 ÷ 0001 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 0A03 × 200C ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 0A03 × 0308 × 200C ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 0A03 ÷ 1F1E6 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 0A03 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 0A03 ÷ 0600 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 0A03 × 0308 ÷ 0600 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 0A03 × 0A03 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 0A03 × 0308 × 0A03 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 0A03 ÷ 1100 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 0A03 × 0308 ÷ 1100 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 0A03 ÷ 1160 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 0A03 × 0308 ÷ 1160 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 0A03 ÷ 11A8 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 0A03 × 0308 ÷ 11A8 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 0A03 ÷ AC00 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 0A03 × 0308 ÷ AC00 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 0A03 ÷ AC01 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 0A03 × 0308 ÷ AC01 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 0A03 × 0903 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 0A03 × 0308 × 0903 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 0A03 ÷ 0904 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 0A03 × 0308 ÷ 0904 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 0A03 ÷ 0D4E ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 0A03 × 0308 ÷ 0D4E ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 0A03 ÷ 0915 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0A03 × 0308 ÷ 0915 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0A03 ÷ 231A ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 0A03 × 0308 ÷ 231A ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 0A03 × 0300 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 0A03 × 0308 × 0300 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 0A03 × 0900 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 0A03 × 0308 × 0900 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 0A03 × 094D ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 0A03 × 0308 × 094D ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 0A03 × 200D ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0A03 × 0308 × 200D ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0A03 ÷ 0378 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] (Other) ÷ [0.3] -÷ 0A03 × 0308 ÷ 0378 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 1100 ÷ 0020 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 1100 × 0308 ÷ 0020 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] +÷ 1F1E6 × 0308 ÷ AC01 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 1F1E6 ÷ 0915 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 1F1E6 × 0308 ÷ 0915 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 1F1E6 ÷ 00A9 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 1F1E6 × 0308 ÷ 00A9 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 1F1E6 ÷ 0020 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 1F1E6 × 0308 ÷ 0020 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 1F1E6 ÷ 0378 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 1F1E6 × 0308 ÷ 0378 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 06DD ÷ 000D ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) ÷ [5.0] (CR) ÷ [0.3] +÷ 06DD × 0308 ÷ 000D ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ 06DD ÷ 000A ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) ÷ [5.0] (LF) ÷ [0.3] +÷ 06DD × 0308 ÷ 000A ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 06DD ÷ 0000 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) ÷ [5.0] (Control) ÷ [0.3] +÷ 06DD × 0308 ÷ 0000 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 06DD × 094D ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 06DD × 0308 × 094D ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 06DD × 0300 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 06DD × 0308 × 0300 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 06DD × 200C ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 06DD × 0308 × 200C ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 06DD × 200D ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 06DD × 0308 × 200D ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 06DD × 1F1E6 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 06DD × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 06DD × 06DD ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.2] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 06DD × 0308 ÷ 06DD ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 06DD × 0903 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 06DD × 0308 × 0903 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 06DD × 1100 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.2] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 06DD × 0308 ÷ 1100 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 06DD × 1160 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.2] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 06DD × 0308 ÷ 1160 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 06DD × 11A8 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.2] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 06DD × 0308 ÷ 11A8 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 06DD × AC00 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.2] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 06DD × 0308 ÷ AC00 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 06DD × AC01 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.2] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 06DD × 0308 ÷ AC01 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 06DD × 0915 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 06DD × 0308 ÷ 0915 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 06DD × 00A9 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.2] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 06DD × 0308 ÷ 00A9 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 06DD × 0020 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.2] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 06DD × 0308 ÷ 0020 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 06DD × 0378 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.2] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 06DD × 0308 ÷ 0378 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0903 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [5.0] (CR) ÷ [0.3] +÷ 0903 × 0308 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ 0903 ÷ 000A ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [5.0] (LF) ÷ [0.3] +÷ 0903 × 0308 ÷ 000A ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 0903 ÷ 0000 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [5.0] (Control) ÷ [0.3] +÷ 0903 × 0308 ÷ 0000 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 0903 × 094D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 0903 × 0308 × 094D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 0903 × 0300 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 0903 × 0308 × 0300 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 0903 × 200C ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 0903 × 0308 × 200C ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 0903 × 200D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 0903 × 0308 × 200D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 0903 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 0903 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 0903 ÷ 06DD ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 0903 × 0308 ÷ 06DD ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 0903 × 0903 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 0903 × 0308 × 0903 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 0903 ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 0903 × 0308 ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 0903 ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 0903 × 0308 ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 0903 ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 0903 × 0308 ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 0903 ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 0903 × 0308 ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 0903 ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 0903 × 0308 ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 0903 ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 0903 × 0308 ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 0903 ÷ 00A9 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 0903 × 0308 ÷ 00A9 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 0903 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0903 × 0308 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0903 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0903 × 0308 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] ÷ 1100 ÷ 000D ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [5.0] (CR) ÷ [0.3] -÷ 1100 × 0308 ÷ 000D ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] +÷ 1100 × 0308 ÷ 000D ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] ÷ 1100 ÷ 000A ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [5.0] (LF) ÷ [0.3] -÷ 1100 × 0308 ÷ 000A ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 1100 ÷ 0001 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [5.0] (Control) ÷ [0.3] -÷ 1100 × 0308 ÷ 0001 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 1100 × 200C ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 1100 × 0308 × 200C ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] +÷ 1100 × 0308 ÷ 000A ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 1100 ÷ 0000 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [5.0] (Control) ÷ [0.3] +÷ 1100 × 0308 ÷ 0000 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 1100 × 094D ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 1100 × 0308 × 094D ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 1100 × 0300 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 1100 × 0308 × 0300 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 1100 × 200C ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 1100 × 0308 × 200C ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 1100 × 200D ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 1100 × 0308 × 200D ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] ÷ 1100 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 1100 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 1100 ÷ 0600 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 1100 × 0308 ÷ 0600 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 1100 × 0A03 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 1100 × 0308 × 0A03 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 1100 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 1100 ÷ 06DD ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 1100 × 0308 ÷ 06DD ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 1100 × 0903 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 1100 × 0308 × 0903 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] ÷ 1100 × 1100 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [6.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 1100 × 0308 ÷ 1100 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 1100 × 0308 ÷ 1100 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] ÷ 1100 × 1160 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [6.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 1100 × 0308 ÷ 1160 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 1100 × 0308 ÷ 1160 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] ÷ 1100 ÷ 11A8 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 1100 × 0308 ÷ 11A8 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 1100 × 0308 ÷ 11A8 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] ÷ 1100 × AC00 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [6.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 1100 × 0308 ÷ AC00 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 1100 × 0308 ÷ AC00 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] ÷ 1100 × AC01 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [6.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 1100 × 0308 ÷ AC01 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 1100 × 0903 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 1100 × 0308 × 0903 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 1100 ÷ 0904 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 1100 × 0308 ÷ 0904 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 1100 ÷ 0D4E ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 1100 × 0308 ÷ 0D4E ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 1100 ÷ 0915 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 1100 × 0308 ÷ 0915 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 1100 ÷ 231A ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 1100 × 0308 ÷ 231A ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 1100 × 0300 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 1100 × 0308 × 0300 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 1100 × 0900 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 1100 × 0308 × 0900 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 1100 × 094D ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 1100 × 0308 × 094D ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 1100 × 200D ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 1100 × 0308 × 200D ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 1100 ÷ 0378 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] (Other) ÷ [0.3] -÷ 1100 × 0308 ÷ 0378 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 1160 ÷ 0020 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 1160 × 0308 ÷ 0020 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] +÷ 1100 × 0308 ÷ AC01 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 1100 ÷ 0915 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 1100 × 0308 ÷ 0915 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 1100 ÷ 00A9 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 1100 × 0308 ÷ 00A9 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 1100 ÷ 0020 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 1100 × 0308 ÷ 0020 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 1100 ÷ 0378 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 1100 × 0308 ÷ 0378 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] ÷ 1160 ÷ 000D ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [5.0] (CR) ÷ [0.3] -÷ 1160 × 0308 ÷ 000D ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] +÷ 1160 × 0308 ÷ 000D ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] ÷ 1160 ÷ 000A ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [5.0] (LF) ÷ [0.3] -÷ 1160 × 0308 ÷ 000A ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 1160 ÷ 0001 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [5.0] (Control) ÷ [0.3] -÷ 1160 × 0308 ÷ 0001 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 1160 × 200C ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 1160 × 0308 × 200C ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] +÷ 1160 × 0308 ÷ 000A ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 1160 ÷ 0000 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [5.0] (Control) ÷ [0.3] +÷ 1160 × 0308 ÷ 0000 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 1160 × 094D ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 1160 × 0308 × 094D ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 1160 × 0300 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 1160 × 0308 × 0300 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 1160 × 200C ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 1160 × 0308 × 200C ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 1160 × 200D ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 1160 × 0308 × 200D ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] ÷ 1160 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 1160 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 1160 ÷ 0600 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 1160 × 0308 ÷ 0600 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 1160 × 0A03 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 1160 × 0308 × 0A03 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 1160 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 1160 ÷ 06DD ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 1160 × 0308 ÷ 06DD ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 1160 × 0903 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 1160 × 0308 × 0903 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] ÷ 1160 ÷ 1100 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 1160 × 0308 ÷ 1100 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 1160 × 0308 ÷ 1100 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] ÷ 1160 × 1160 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [7.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 1160 × 0308 ÷ 1160 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 1160 × 0308 ÷ 1160 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] ÷ 1160 × 11A8 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [7.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 1160 × 0308 ÷ 11A8 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 1160 × 0308 ÷ 11A8 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] ÷ 1160 ÷ AC00 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 1160 × 0308 ÷ AC00 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 1160 × 0308 ÷ AC00 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] ÷ 1160 ÷ AC01 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 1160 × 0308 ÷ AC01 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 1160 × 0903 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 1160 × 0308 × 0903 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 1160 ÷ 0904 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 1160 × 0308 ÷ 0904 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 1160 ÷ 0D4E ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 1160 × 0308 ÷ 0D4E ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 1160 ÷ 0915 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 1160 × 0308 ÷ 0915 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 1160 ÷ 231A ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 1160 × 0308 ÷ 231A ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 1160 × 0300 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 1160 × 0308 × 0300 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 1160 × 0900 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 1160 × 0308 × 0900 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 1160 × 094D ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 1160 × 0308 × 094D ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 1160 × 200D ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 1160 × 0308 × 200D ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 1160 ÷ 0378 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] (Other) ÷ [0.3] -÷ 1160 × 0308 ÷ 0378 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 11A8 ÷ 0020 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 11A8 × 0308 ÷ 0020 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] +÷ 1160 × 0308 ÷ AC01 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 1160 ÷ 0915 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 1160 × 0308 ÷ 0915 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 1160 ÷ 00A9 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 1160 × 0308 ÷ 00A9 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 1160 ÷ 0020 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 1160 × 0308 ÷ 0020 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 1160 ÷ 0378 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 1160 × 0308 ÷ 0378 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] ÷ 11A8 ÷ 000D ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [5.0] (CR) ÷ [0.3] -÷ 11A8 × 0308 ÷ 000D ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] +÷ 11A8 × 0308 ÷ 000D ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] ÷ 11A8 ÷ 000A ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [5.0] (LF) ÷ [0.3] -÷ 11A8 × 0308 ÷ 000A ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 11A8 ÷ 0001 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [5.0] (Control) ÷ [0.3] -÷ 11A8 × 0308 ÷ 0001 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 11A8 × 200C ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 11A8 × 0308 × 200C ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] +÷ 11A8 × 0308 ÷ 000A ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 11A8 ÷ 0000 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [5.0] (Control) ÷ [0.3] +÷ 11A8 × 0308 ÷ 0000 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 11A8 × 094D ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 11A8 × 0308 × 094D ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 11A8 × 0300 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 11A8 × 0308 × 0300 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 11A8 × 200C ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 11A8 × 0308 × 200C ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 11A8 × 200D ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 11A8 × 0308 × 200D ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] ÷ 11A8 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 11A8 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 11A8 ÷ 0600 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 11A8 × 0308 ÷ 0600 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 11A8 × 0A03 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 11A8 × 0308 × 0A03 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 11A8 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 11A8 ÷ 06DD ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 11A8 × 0308 ÷ 06DD ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 11A8 × 0903 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 11A8 × 0308 × 0903 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] ÷ 11A8 ÷ 1100 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 11A8 × 0308 ÷ 1100 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 11A8 × 0308 ÷ 1100 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] ÷ 11A8 ÷ 1160 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 11A8 × 0308 ÷ 1160 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 11A8 × 0308 ÷ 1160 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] ÷ 11A8 × 11A8 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [8.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 11A8 × 0308 ÷ 11A8 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 11A8 × 0308 ÷ 11A8 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] ÷ 11A8 ÷ AC00 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 11A8 × 0308 ÷ AC00 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 11A8 × 0308 ÷ AC00 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] ÷ 11A8 ÷ AC01 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 11A8 × 0308 ÷ AC01 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 11A8 × 0903 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 11A8 × 0308 × 0903 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 11A8 ÷ 0904 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 11A8 × 0308 ÷ 0904 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 11A8 ÷ 0D4E ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 11A8 × 0308 ÷ 0D4E ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 11A8 ÷ 0915 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 11A8 × 0308 ÷ 0915 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 11A8 ÷ 231A ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 11A8 × 0308 ÷ 231A ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 11A8 × 0300 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 11A8 × 0308 × 0300 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 11A8 × 0900 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 11A8 × 0308 × 0900 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 11A8 × 094D ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 11A8 × 0308 × 094D ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 11A8 × 200D ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 11A8 × 0308 × 200D ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 11A8 ÷ 0378 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] (Other) ÷ [0.3] -÷ 11A8 × 0308 ÷ 0378 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ AC00 ÷ 0020 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ AC00 × 0308 ÷ 0020 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] +÷ 11A8 × 0308 ÷ AC01 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 11A8 ÷ 0915 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 11A8 × 0308 ÷ 0915 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 11A8 ÷ 00A9 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 11A8 × 0308 ÷ 00A9 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 11A8 ÷ 0020 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 11A8 × 0308 ÷ 0020 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 11A8 ÷ 0378 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 11A8 × 0308 ÷ 0378 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] ÷ AC00 ÷ 000D ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [5.0] (CR) ÷ [0.3] -÷ AC00 × 0308 ÷ 000D ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] +÷ AC00 × 0308 ÷ 000D ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] ÷ AC00 ÷ 000A ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [5.0] (LF) ÷ [0.3] -÷ AC00 × 0308 ÷ 000A ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ AC00 ÷ 0001 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [5.0] (Control) ÷ [0.3] -÷ AC00 × 0308 ÷ 0001 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ AC00 × 200C ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ AC00 × 0308 × 200C ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] +÷ AC00 × 0308 ÷ 000A ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ AC00 ÷ 0000 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [5.0] (Control) ÷ [0.3] +÷ AC00 × 0308 ÷ 0000 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ AC00 × 094D ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ AC00 × 0308 × 094D ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ AC00 × 0300 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ AC00 × 0308 × 0300 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ AC00 × 200C ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ AC00 × 0308 × 200C ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ AC00 × 200D ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ AC00 × 0308 × 200D ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] ÷ AC00 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ AC00 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ AC00 ÷ 0600 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ AC00 × 0308 ÷ 0600 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ AC00 × 0A03 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ AC00 × 0308 × 0A03 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ AC00 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ AC00 ÷ 06DD ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ AC00 × 0308 ÷ 06DD ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ AC00 × 0903 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ AC00 × 0308 × 0903 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] ÷ AC00 ÷ 1100 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ AC00 × 0308 ÷ 1100 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ AC00 × 0308 ÷ 1100 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] ÷ AC00 × 1160 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [7.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ AC00 × 0308 ÷ 1160 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ AC00 × 0308 ÷ 1160 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] ÷ AC00 × 11A8 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [7.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ AC00 × 0308 ÷ 11A8 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ AC00 × 0308 ÷ 11A8 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] ÷ AC00 ÷ AC00 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ AC00 × 0308 ÷ AC00 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ AC00 × 0308 ÷ AC00 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] ÷ AC00 ÷ AC01 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ AC00 × 0308 ÷ AC01 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ AC00 × 0903 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ AC00 × 0308 × 0903 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ AC00 ÷ 0904 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ AC00 × 0308 ÷ 0904 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ AC00 ÷ 0D4E ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ AC00 × 0308 ÷ 0D4E ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ AC00 ÷ 0915 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ AC00 × 0308 ÷ 0915 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ AC00 ÷ 231A ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ AC00 × 0308 ÷ 231A ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ AC00 × 0300 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ AC00 × 0308 × 0300 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ AC00 × 0900 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ AC00 × 0308 × 0900 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ AC00 × 094D ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ AC00 × 0308 × 094D ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ AC00 × 200D ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ AC00 × 0308 × 200D ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ AC00 ÷ 0378 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] (Other) ÷ [0.3] -÷ AC00 × 0308 ÷ 0378 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ AC01 ÷ 0020 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ AC01 × 0308 ÷ 0020 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] +÷ AC00 × 0308 ÷ AC01 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ AC00 ÷ 0915 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ AC00 × 0308 ÷ 0915 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ AC00 ÷ 00A9 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ AC00 × 0308 ÷ 00A9 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ AC00 ÷ 0020 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ AC00 × 0308 ÷ 0020 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ AC00 ÷ 0378 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ AC00 × 0308 ÷ 0378 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] ÷ AC01 ÷ 000D ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [5.0] (CR) ÷ [0.3] -÷ AC01 × 0308 ÷ 000D ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] +÷ AC01 × 0308 ÷ 000D ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] ÷ AC01 ÷ 000A ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [5.0] (LF) ÷ [0.3] -÷ AC01 × 0308 ÷ 000A ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ AC01 ÷ 0001 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [5.0] (Control) ÷ [0.3] -÷ AC01 × 0308 ÷ 0001 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ AC01 × 200C ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ AC01 × 0308 × 200C ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] +÷ AC01 × 0308 ÷ 000A ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ AC01 ÷ 0000 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [5.0] (Control) ÷ [0.3] +÷ AC01 × 0308 ÷ 0000 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ AC01 × 094D ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ AC01 × 0308 × 094D ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ AC01 × 0300 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ AC01 × 0308 × 0300 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ AC01 × 200C ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ AC01 × 0308 × 200C ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ AC01 × 200D ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ AC01 × 0308 × 200D ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] ÷ AC01 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ AC01 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ AC01 ÷ 0600 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ AC01 × 0308 ÷ 0600 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ AC01 × 0A03 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ AC01 × 0308 × 0A03 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ AC01 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ AC01 ÷ 06DD ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ AC01 × 0308 ÷ 06DD ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ AC01 × 0903 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ AC01 × 0308 × 0903 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] ÷ AC01 ÷ 1100 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ AC01 × 0308 ÷ 1100 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ AC01 × 0308 ÷ 1100 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] ÷ AC01 ÷ 1160 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ AC01 × 0308 ÷ 1160 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ AC01 × 0308 ÷ 1160 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] ÷ AC01 × 11A8 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [8.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ AC01 × 0308 ÷ 11A8 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ AC01 × 0308 ÷ 11A8 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] ÷ AC01 ÷ AC00 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ AC01 × 0308 ÷ AC00 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ AC01 × 0308 ÷ AC00 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] ÷ AC01 ÷ AC01 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ AC01 × 0308 ÷ AC01 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ AC01 × 0903 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ AC01 × 0308 × 0903 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ AC01 ÷ 0904 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ AC01 × 0308 ÷ 0904 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ AC01 ÷ 0D4E ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ AC01 × 0308 ÷ 0D4E ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ AC01 ÷ 0915 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ AC01 × 0308 ÷ 0915 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ AC01 ÷ 231A ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ AC01 × 0308 ÷ 231A ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ AC01 × 0300 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ AC01 × 0308 × 0300 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ AC01 × 0900 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ AC01 × 0308 × 0900 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ AC01 × 094D ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ AC01 × 0308 × 094D ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ AC01 × 200D ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ AC01 × 0308 × 200D ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ AC01 ÷ 0378 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] (Other) ÷ [0.3] -÷ AC01 × 0308 ÷ 0378 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 0903 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 0903 × 0308 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 0903 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [5.0] (CR) ÷ [0.3] -÷ 0903 × 0308 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] -÷ 0903 ÷ 000A ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [5.0] (LF) ÷ [0.3] -÷ 0903 × 0308 ÷ 000A ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 0903 ÷ 0001 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [5.0] (Control) ÷ [0.3] -÷ 0903 × 0308 ÷ 0001 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 0903 × 200C ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 0903 × 0308 × 200C ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 0903 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 0903 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 0903 ÷ 0600 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 0903 × 0308 ÷ 0600 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 0903 × 0A03 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 0903 × 0308 × 0A03 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 0903 ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 0903 × 0308 ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 0903 ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 0903 × 0308 ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 0903 ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 0903 × 0308 ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 0903 ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 0903 × 0308 ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 0903 ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 0903 × 0308 ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 0903 × 0903 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 0903 × 0308 × 0903 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 0903 ÷ 0904 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 0903 × 0308 ÷ 0904 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 0903 ÷ 0D4E ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 0903 × 0308 ÷ 0D4E ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 0903 ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0903 × 0308 ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0903 ÷ 231A ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 0903 × 0308 ÷ 231A ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 0903 × 0300 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 0903 × 0308 × 0300 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 0903 × 0900 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 0903 × 0308 × 0900 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 0903 × 094D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 0903 × 0308 × 094D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 0903 × 200D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0903 × 0308 × 200D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0903 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] (Other) ÷ [0.3] -÷ 0903 × 0308 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 0904 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 0904 × 0308 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 0904 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [5.0] (CR) ÷ [0.3] -÷ 0904 × 0308 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] -÷ 0904 ÷ 000A ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [5.0] (LF) ÷ [0.3] -÷ 0904 × 0308 ÷ 000A ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 0904 ÷ 0001 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [5.0] (Control) ÷ [0.3] -÷ 0904 × 0308 ÷ 0001 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 0904 × 200C ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 0904 × 0308 × 200C ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 0904 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 0904 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 0904 ÷ 0600 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 0904 × 0308 ÷ 0600 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 0904 × 0A03 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 0904 × 0308 × 0A03 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 0904 ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 0904 × 0308 ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 0904 ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 0904 × 0308 ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 0904 ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 0904 × 0308 ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 0904 ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 0904 × 0308 ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 0904 ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 0904 × 0308 ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 0904 × 0903 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 0904 × 0308 × 0903 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 0904 ÷ 0904 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 0904 × 0308 ÷ 0904 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 0904 ÷ 0D4E ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 0904 × 0308 ÷ 0D4E ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 0904 ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0904 × 0308 ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0904 ÷ 231A ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 0904 × 0308 ÷ 231A ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 0904 × 0300 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 0904 × 0308 × 0300 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 0904 × 0900 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 0904 × 0308 × 0900 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 0904 × 094D ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 0904 × 0308 × 094D ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 0904 × 200D ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0904 × 0308 × 200D ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0904 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] (Other) ÷ [0.3] -÷ 0904 × 0308 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 0D4E × 0020 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] SPACE (Other) ÷ [0.3] -÷ 0D4E × 0308 ÷ 0020 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 0D4E ÷ 000D ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [5.0] (CR) ÷ [0.3] -÷ 0D4E × 0308 ÷ 000D ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] -÷ 0D4E ÷ 000A ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [5.0] (LF) ÷ [0.3] -÷ 0D4E × 0308 ÷ 000A ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 0D4E ÷ 0001 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [5.0] (Control) ÷ [0.3] -÷ 0D4E × 0308 ÷ 0001 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 0D4E × 200C ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 0D4E × 0308 × 200C ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 0D4E × 1F1E6 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 0D4E × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 0D4E × 0600 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 0D4E × 0308 ÷ 0600 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 0D4E × 0A03 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 0D4E × 0308 × 0A03 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 0D4E × 1100 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 0D4E × 0308 ÷ 1100 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 0D4E × 1160 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 0D4E × 0308 ÷ 1160 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 0D4E × 11A8 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 0D4E × 0308 ÷ 11A8 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 0D4E × AC00 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 0D4E × 0308 ÷ AC00 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 0D4E × AC01 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 0D4E × 0308 ÷ AC01 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 0D4E × 0903 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 0D4E × 0308 × 0903 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 0D4E × 0904 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 0D4E × 0308 ÷ 0904 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 0D4E × 0D4E ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 0D4E × 0308 ÷ 0D4E ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 0D4E × 0915 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0D4E × 0308 ÷ 0915 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0D4E × 231A ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] WATCH (ExtPict) ÷ [0.3] -÷ 0D4E × 0308 ÷ 231A ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 0D4E × 0300 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 0D4E × 0308 × 0300 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 0D4E × 0900 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 0D4E × 0308 × 0900 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 0D4E × 094D ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 0D4E × 0308 × 094D ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 0D4E × 200D ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0D4E × 0308 × 200D ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0D4E × 0378 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] (Other) ÷ [0.3] -÷ 0D4E × 0308 ÷ 0378 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 0915 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 0915 × 0308 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 0915 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [5.0] (CR) ÷ [0.3] -÷ 0915 × 0308 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] -÷ 0915 ÷ 000A ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [5.0] (LF) ÷ [0.3] -÷ 0915 × 0308 ÷ 000A ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 0915 ÷ 0001 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [5.0] (Control) ÷ [0.3] -÷ 0915 × 0308 ÷ 0001 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 0915 × 200C ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 0915 × 0308 × 200C ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 0915 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 0915 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 0915 ÷ 0600 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 0915 × 0308 ÷ 0600 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 0915 × 0A03 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 0915 × 0308 × 0A03 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 0915 ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 0915 × 0308 ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 0915 ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 0915 × 0308 ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 0915 ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 0915 × 0308 ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 0915 ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 0915 × 0308 ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 0915 ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 0915 × 0308 ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 0915 × 0903 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 0915 × 0308 × 0903 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 0915 ÷ 0904 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 0915 × 0308 ÷ 0904 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 0915 ÷ 0D4E ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 0915 × 0308 ÷ 0D4E ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 0915 ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0915 × 0308 ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0915 ÷ 231A ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 0915 × 0308 ÷ 231A ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 0915 × 0300 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 0915 × 0308 × 0300 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 0915 × 0900 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 0915 × 0308 × 0900 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 0915 × 094D ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 0915 × 0308 × 094D ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 0915 × 200D ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0915 × 0308 × 200D ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0915 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] (Other) ÷ [0.3] -÷ 0915 × 0308 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 231A ÷ 0020 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 231A × 0308 ÷ 0020 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 231A ÷ 000D ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [5.0] (CR) ÷ [0.3] -÷ 231A × 0308 ÷ 000D ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] -÷ 231A ÷ 000A ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [5.0] (LF) ÷ [0.3] -÷ 231A × 0308 ÷ 000A ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 231A ÷ 0001 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [5.0] (Control) ÷ [0.3] -÷ 231A × 0308 ÷ 0001 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 231A × 200C ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 231A × 0308 × 200C ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 231A ÷ 1F1E6 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 231A × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 231A ÷ 0600 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 231A × 0308 ÷ 0600 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 231A × 0A03 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 231A × 0308 × 0A03 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 231A ÷ 1100 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 231A × 0308 ÷ 1100 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 231A ÷ 1160 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 231A × 0308 ÷ 1160 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 231A ÷ 11A8 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 231A × 0308 ÷ 11A8 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 231A ÷ AC00 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 231A × 0308 ÷ AC00 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 231A ÷ AC01 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 231A × 0308 ÷ AC01 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 231A × 0903 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 231A × 0308 × 0903 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 231A ÷ 0904 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 231A × 0308 ÷ 0904 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 231A ÷ 0D4E ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 231A × 0308 ÷ 0D4E ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 231A ÷ 0915 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 231A × 0308 ÷ 0915 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 231A ÷ 231A ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 231A × 0308 ÷ 231A ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 231A × 0300 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 231A × 0308 × 0300 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 231A × 0900 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 231A × 0308 × 0900 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 231A × 094D ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 231A × 0308 × 094D ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 231A × 200D ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 231A × 0308 × 200D ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 231A ÷ 0378 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] (Other) ÷ [0.3] -÷ 231A × 0308 ÷ 0378 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 0300 ÷ 0020 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 0300 × 0308 ÷ 0020 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 0300 ÷ 000D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] -÷ 0300 × 0308 ÷ 000D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] -÷ 0300 ÷ 000A ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 0300 × 0308 ÷ 000A ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 0300 ÷ 0001 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 0300 × 0308 ÷ 0001 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 0300 × 200C ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 0300 × 0308 × 200C ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 0300 ÷ 1F1E6 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 0300 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 0300 ÷ 0600 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 0300 × 0308 ÷ 0600 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 0300 × 0A03 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 0300 × 0308 × 0A03 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 0300 ÷ 1100 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 0300 × 0308 ÷ 1100 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 0300 ÷ 1160 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 0300 × 0308 ÷ 1160 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 0300 ÷ 11A8 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 0300 × 0308 ÷ 11A8 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 0300 ÷ AC00 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 0300 × 0308 ÷ AC00 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 0300 ÷ AC01 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 0300 × 0308 ÷ AC01 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 0300 × 0903 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 0300 × 0308 × 0903 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 0300 ÷ 0904 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 0300 × 0308 ÷ 0904 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 0300 ÷ 0D4E ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 0300 × 0308 ÷ 0D4E ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 0300 ÷ 0915 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0300 × 0308 ÷ 0915 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0300 ÷ 231A ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 0300 × 0308 ÷ 231A ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 0300 × 0300 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 0300 × 0308 × 0300 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 0300 × 0900 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 0300 × 0308 × 0900 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 0300 × 094D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 0300 × 0308 × 094D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 0300 × 200D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0300 × 0308 × 200D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0300 ÷ 0378 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 0300 × 0308 ÷ 0378 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 0900 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 0900 × 0308 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 0900 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] -÷ 0900 × 0308 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] -÷ 0900 ÷ 000A ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 0900 × 0308 ÷ 000A ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 0900 ÷ 0001 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 0900 × 0308 ÷ 0001 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 0900 × 200C ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 0900 × 0308 × 200C ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 0900 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 0900 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 0900 ÷ 0600 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 0900 × 0308 ÷ 0600 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 0900 × 0A03 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 0900 × 0308 × 0A03 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 0900 ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 0900 × 0308 ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 0900 ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 0900 × 0308 ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 0900 ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 0900 × 0308 ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 0900 ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 0900 × 0308 ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 0900 ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 0900 × 0308 ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 0900 × 0903 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 0900 × 0308 × 0903 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 0900 ÷ 0904 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 0900 × 0308 ÷ 0904 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 0900 ÷ 0D4E ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 0900 × 0308 ÷ 0D4E ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 0900 ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0900 × 0308 ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0900 ÷ 231A ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 0900 × 0308 ÷ 231A ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 0900 × 0300 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 0900 × 0308 × 0300 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 0900 × 0900 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 0900 × 0308 × 0900 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 0900 × 094D ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 0900 × 0308 × 094D ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 0900 × 200D ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0900 × 0308 × 200D ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0900 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 0900 × 0308 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 094D ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 094D × 0308 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 094D ÷ 000D ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] -÷ 094D × 0308 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] -÷ 094D ÷ 000A ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 094D × 0308 ÷ 000A ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 094D ÷ 0001 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 094D × 0308 ÷ 0001 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 094D × 200C ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 094D × 0308 × 200C ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 094D ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 094D × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 094D ÷ 0600 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 094D × 0308 ÷ 0600 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 094D × 0A03 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 094D × 0308 × 0A03 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 094D ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 094D × 0308 ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 094D ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 094D × 0308 ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 094D ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 094D × 0308 ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 094D ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 094D × 0308 ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 094D ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 094D × 0308 ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 094D × 0903 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 094D × 0308 × 0903 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 094D ÷ 0904 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 094D × 0308 ÷ 0904 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 094D ÷ 0D4E ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 094D × 0308 ÷ 0D4E ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 094D ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 094D × 0308 ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 094D ÷ 231A ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 094D × 0308 ÷ 231A ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 094D × 0300 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 094D × 0308 × 0300 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 094D × 0900 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 094D × 0308 × 0900 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 094D × 094D ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 094D × 0308 × 094D ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 094D × 200D ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 094D × 0308 × 200D ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 094D ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 094D × 0308 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 200D ÷ 0020 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 200D × 0308 ÷ 0020 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 200D ÷ 000D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] -÷ 200D × 0308 ÷ 000D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] -÷ 200D ÷ 000A ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 200D × 0308 ÷ 000A ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 200D ÷ 0001 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 200D × 0308 ÷ 0001 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 200D × 200C ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 200D × 0308 × 200C ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 200D ÷ 1F1E6 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 200D × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 200D ÷ 0600 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 200D × 0308 ÷ 0600 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 200D × 0A03 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 200D × 0308 × 0A03 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 200D ÷ 1100 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 200D × 0308 ÷ 1100 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 200D ÷ 1160 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 200D × 0308 ÷ 1160 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 200D ÷ 11A8 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 200D × 0308 ÷ 11A8 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 200D ÷ AC00 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 200D × 0308 ÷ AC00 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 200D ÷ AC01 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 200D × 0308 ÷ AC01 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 200D × 0903 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 200D × 0308 × 0903 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 200D ÷ 0904 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 200D × 0308 ÷ 0904 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 200D ÷ 0D4E ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 200D × 0308 ÷ 0D4E ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 200D ÷ 0915 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 200D × 0308 ÷ 0915 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 200D ÷ 231A ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 200D × 0308 ÷ 231A ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 200D × 0300 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 200D × 0308 × 0300 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 200D × 0900 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 200D × 0308 × 0900 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 200D × 094D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 200D × 0308 × 094D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 200D × 200D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 200D × 0308 × 200D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 200D ÷ 0378 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 200D × 0308 ÷ 0378 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 0378 ÷ 0020 ÷ # ÷ [0.2] (Other) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 0378 × 0308 ÷ 0020 ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 0378 ÷ 000D ÷ # ÷ [0.2] (Other) ÷ [5.0] (CR) ÷ [0.3] -÷ 0378 × 0308 ÷ 000D ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] -÷ 0378 ÷ 000A ÷ # ÷ [0.2] (Other) ÷ [5.0] (LF) ÷ [0.3] -÷ 0378 × 0308 ÷ 000A ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 0378 ÷ 0001 ÷ # ÷ [0.2] (Other) ÷ [5.0] (Control) ÷ [0.3] -÷ 0378 × 0308 ÷ 0001 ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 0378 × 200C ÷ # ÷ [0.2] (Other) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 0378 × 0308 × 200C ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 0378 ÷ 1F1E6 ÷ # ÷ [0.2] (Other) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 0378 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 0378 ÷ 0600 ÷ # ÷ [0.2] (Other) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 0378 × 0308 ÷ 0600 ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 0378 × 0A03 ÷ # ÷ [0.2] (Other) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 0378 × 0308 × 0A03 ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 0378 ÷ 1100 ÷ # ÷ [0.2] (Other) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 0378 × 0308 ÷ 1100 ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 0378 ÷ 1160 ÷ # ÷ [0.2] (Other) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 0378 × 0308 ÷ 1160 ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 0378 ÷ 11A8 ÷ # ÷ [0.2] (Other) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 0378 × 0308 ÷ 11A8 ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 0378 ÷ AC00 ÷ # ÷ [0.2] (Other) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 0378 × 0308 ÷ AC00 ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 0378 ÷ AC01 ÷ # ÷ [0.2] (Other) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 0378 × 0308 ÷ AC01 ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 0378 × 0903 ÷ # ÷ [0.2] (Other) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 0378 × 0308 × 0903 ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 0378 ÷ 0904 ÷ # ÷ [0.2] (Other) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 0378 × 0308 ÷ 0904 ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 0378 ÷ 0D4E ÷ # ÷ [0.2] (Other) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 0378 × 0308 ÷ 0D4E ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 0378 ÷ 0915 ÷ # ÷ [0.2] (Other) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0378 × 0308 ÷ 0915 ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0378 ÷ 231A ÷ # ÷ [0.2] (Other) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 0378 × 0308 ÷ 231A ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 0378 × 0300 ÷ # ÷ [0.2] (Other) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 0378 × 0308 × 0300 ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 0378 × 0900 ÷ # ÷ [0.2] (Other) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 0378 × 0308 × 0900 ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 0378 × 094D ÷ # ÷ [0.2] (Other) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 0378 × 0308 × 094D ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 0378 × 200D ÷ # ÷ [0.2] (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0378 × 0308 × 200D ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0378 ÷ 0378 ÷ # ÷ [0.2] (Other) ÷ [999.0] (Other) ÷ [0.3] -÷ 0378 × 0308 ÷ 0378 ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 000D × 000A ÷ 0061 ÷ 000A ÷ 0308 ÷ # ÷ [0.2] (CR) × [3.0] (LF) ÷ [4.0] LATIN SMALL LETTER A (Other) ÷ [5.0] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [0.3] -÷ 0061 × 0308 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [0.3] -÷ 0020 × 200D ÷ 0646 ÷ # ÷ [0.2] SPACE (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] ARABIC LETTER NOON (Other) ÷ [0.3] -÷ 0646 × 200D ÷ 0020 ÷ # ÷ [0.2] ARABIC LETTER NOON (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] +÷ AC01 × 0308 ÷ AC01 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ AC01 ÷ 0915 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ AC01 × 0308 ÷ 0915 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ AC01 ÷ 00A9 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ AC01 × 0308 ÷ 00A9 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ AC01 ÷ 0020 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ AC01 × 0308 ÷ 0020 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ AC01 ÷ 0378 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ AC01 × 0308 ÷ 0378 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0915 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [5.0] (CR) ÷ [0.3] +÷ 0915 × 0308 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ 0915 ÷ 000A ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [5.0] (LF) ÷ [0.3] +÷ 0915 × 0308 ÷ 000A ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 0915 ÷ 0000 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [5.0] (Control) ÷ [0.3] +÷ 0915 × 0308 ÷ 0000 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 0915 × 094D ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 0915 × 0308 × 094D ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 0915 × 0300 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 0915 × 0308 × 0300 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 0915 × 200C ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 0915 × 0308 × 200C ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 0915 × 200D ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 0915 × 0308 × 200D ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 0915 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 0915 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 0915 ÷ 06DD ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 0915 × 0308 ÷ 06DD ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 0915 × 0903 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 0915 × 0308 × 0903 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 0915 ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 0915 × 0308 ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 0915 ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 0915 × 0308 ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 0915 ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 0915 × 0308 ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 0915 ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 0915 × 0308 ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 0915 ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 0915 × 0308 ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 0915 ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 0915 × 0308 ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 0915 ÷ 00A9 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 0915 × 0308 ÷ 00A9 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 0915 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0915 × 0308 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0915 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0915 × 0308 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 00A9 ÷ 000D ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) ÷ [5.0] (CR) ÷ [0.3] +÷ 00A9 × 0308 ÷ 000D ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ 00A9 ÷ 000A ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) ÷ [5.0] (LF) ÷ [0.3] +÷ 00A9 × 0308 ÷ 000A ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 00A9 ÷ 0000 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) ÷ [5.0] (Control) ÷ [0.3] +÷ 00A9 × 0308 ÷ 0000 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 00A9 × 094D ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 00A9 × 0308 × 094D ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 00A9 × 0300 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 00A9 × 0308 × 0300 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 00A9 × 200C ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 00A9 × 0308 × 200C ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 00A9 × 200D ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 00A9 × 0308 × 200D ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 00A9 ÷ 1F1E6 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 00A9 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 00A9 ÷ 06DD ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 00A9 × 0308 ÷ 06DD ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 00A9 × 0903 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 00A9 × 0308 × 0903 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 00A9 ÷ 1100 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 00A9 × 0308 ÷ 1100 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 00A9 ÷ 1160 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 00A9 × 0308 ÷ 1160 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 00A9 ÷ 11A8 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 00A9 × 0308 ÷ 11A8 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 00A9 ÷ AC00 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 00A9 × 0308 ÷ AC00 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 00A9 ÷ AC01 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 00A9 × 0308 ÷ AC01 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 00A9 ÷ 0915 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 00A9 × 0308 ÷ 0915 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 00A9 ÷ 00A9 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 00A9 × 0308 ÷ 00A9 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 00A9 ÷ 0020 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 00A9 × 0308 ÷ 0020 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 00A9 ÷ 0378 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 00A9 × 0308 ÷ 0378 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0020 ÷ 000D ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) ÷ [5.0] (CR) ÷ [0.3] +÷ 0020 × 0308 ÷ 000D ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ 0020 ÷ 000A ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) ÷ [5.0] (LF) ÷ [0.3] +÷ 0020 × 0308 ÷ 000A ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 0020 ÷ 0000 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) ÷ [5.0] (Control) ÷ [0.3] +÷ 0020 × 0308 ÷ 0000 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 0020 × 094D ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 0020 × 0308 × 094D ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 0020 × 0300 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 0020 × 0308 × 0300 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 0020 × 200C ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 0020 × 0308 × 200C ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 0020 × 200D ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 0020 × 0308 × 200D ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 0020 ÷ 1F1E6 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 0020 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 0020 ÷ 06DD ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 0020 × 0308 ÷ 06DD ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 0020 × 0903 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 0020 × 0308 × 0903 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 0020 ÷ 1100 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 0020 × 0308 ÷ 1100 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 0020 ÷ 1160 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 0020 × 0308 ÷ 1160 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 0020 ÷ 11A8 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 0020 × 0308 ÷ 11A8 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 0020 ÷ AC00 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 0020 × 0308 ÷ AC00 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 0020 ÷ AC01 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 0020 × 0308 ÷ AC01 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 0020 ÷ 0915 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 0020 × 0308 ÷ 0915 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 0020 ÷ 00A9 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 0020 × 0308 ÷ 00A9 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 0020 ÷ 0020 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0020 × 0308 ÷ 0020 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0020 ÷ 0378 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0020 × 0308 ÷ 0378 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0378 ÷ 000D ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) ÷ [5.0] (CR) ÷ [0.3] +÷ 0378 × 0308 ÷ 000D ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ 0378 ÷ 000A ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) ÷ [5.0] (LF) ÷ [0.3] +÷ 0378 × 0308 ÷ 000A ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 0378 ÷ 0000 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) ÷ [5.0] (Control) ÷ [0.3] +÷ 0378 × 0308 ÷ 0000 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 0378 × 094D ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 0378 × 0308 × 094D ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 0378 × 0300 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 0378 × 0308 × 0300 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 0378 × 200C ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 0378 × 0308 × 200C ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 0378 × 200D ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 0378 × 0308 × 200D ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 0378 ÷ 1F1E6 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 0378 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 0378 ÷ 06DD ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 0378 × 0308 ÷ 06DD ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 0378 × 0903 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 0378 × 0308 × 0903 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 0378 ÷ 1100 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 0378 × 0308 ÷ 1100 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 0378 ÷ 1160 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 0378 × 0308 ÷ 1160 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 0378 ÷ 11A8 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 0378 × 0308 ÷ 11A8 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 0378 ÷ AC00 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 0378 × 0308 ÷ AC00 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 0378 ÷ AC01 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 0378 × 0308 ÷ AC01 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 0378 ÷ 0915 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 0378 × 0308 ÷ 0915 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 0378 ÷ 00A9 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 0378 × 0308 ÷ 00A9 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 0378 ÷ 0020 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0378 × 0308 ÷ 0020 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0378 ÷ 0378 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0378 × 0308 ÷ 0378 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 000D × 000A ÷ 0061 ÷ 000A ÷ 0308 ÷ # ÷ [0.2] (CR) × [3.0] (LF) ÷ [4.0] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) ÷ [5.0] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 0061 × 0308 ÷ # ÷ [0.2] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 0020 × 200D ÷ 0646 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] ARABIC LETTER NOON (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0646 × 200D ÷ 0020 ÷ # ÷ [0.2] ARABIC LETTER NOON (XXmLinkingConsonantmExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] ÷ 1100 × 1100 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [6.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] ÷ AC00 × 11A8 ÷ 1100 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [7.0] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] ÷ AC01 × 11A8 ÷ 1100 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [8.0] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 1F1E6 × 1F1E7 ÷ 1F1E8 ÷ 0062 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [12.0] REGIONAL INDICATOR SYMBOL LETTER B (RI) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER C (RI) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3] -÷ 0061 ÷ 1F1E6 × 1F1E7 ÷ 1F1E8 ÷ 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [13.0] REGIONAL INDICATOR SYMBOL LETTER B (RI) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER C (RI) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3] -÷ 0061 ÷ 1F1E6 × 1F1E7 × 200D ÷ 1F1E8 ÷ 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [13.0] REGIONAL INDICATOR SYMBOL LETTER B (RI) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER C (RI) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3] -÷ 0061 ÷ 1F1E6 × 200D ÷ 1F1E7 × 1F1E8 ÷ 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER B (RI) × [13.0] REGIONAL INDICATOR SYMBOL LETTER C (RI) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3] -÷ 0061 ÷ 1F1E6 × 1F1E7 ÷ 1F1E8 × 1F1E9 ÷ 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [13.0] REGIONAL INDICATOR SYMBOL LETTER B (RI) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER C (RI) × [13.0] REGIONAL INDICATOR SYMBOL LETTER D (RI) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3] -÷ 0061 × 200D ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0061 × 0308 ÷ 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3] -÷ 0061 × 0903 ÷ 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3] -÷ 0061 ÷ 0600 × 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) × [9.2] LATIN SMALL LETTER B (Other) ÷ [0.3] -÷ 1F476 × 1F3FF ÷ 1F476 ÷ # ÷ [0.2] BABY (ExtPict) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend_ExtCccZwj) ÷ [999.0] BABY (ExtPict) ÷ [0.3] -÷ 0061 × 1F3FF ÷ 1F476 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend_ExtCccZwj) ÷ [999.0] BABY (ExtPict) ÷ [0.3] -÷ 0061 × 1F3FF ÷ 1F476 × 200D × 1F6D1 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend_ExtCccZwj) ÷ [999.0] BABY (ExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [11.0] OCTAGONAL SIGN (ExtPict) ÷ [0.3] -÷ 1F476 × 1F3FF × 0308 × 200D × 1F476 × 1F3FF ÷ # ÷ [0.2] BABY (ExtPict) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [11.0] BABY (ExtPict) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend_ExtCccZwj) ÷ [0.3] -÷ 1F6D1 × 200D × 1F6D1 ÷ # ÷ [0.2] OCTAGONAL SIGN (ExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [11.0] OCTAGONAL SIGN (ExtPict) ÷ [0.3] -÷ 0061 × 200D ÷ 1F6D1 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] OCTAGONAL SIGN (ExtPict) ÷ [0.3] -÷ 2701 × 200D × 2701 ÷ # ÷ [0.2] UPPER BLADE SCISSORS (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [11.0] UPPER BLADE SCISSORS (Other) ÷ [0.3] -÷ 0061 × 200D ÷ 2701 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] UPPER BLADE SCISSORS (Other) ÷ [0.3] -÷ 0915 ÷ 0924 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0915 × 094D × 0924 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.3] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0915 × 094D × 094D × 0924 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.3] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0915 × 094D × 200D × 0924 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.3] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0915 × 093C × 200D × 094D × 0924 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.3] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0915 × 093C × 094D × 200D × 0924 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.3] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0915 × 094D × 0924 × 094D × 092F ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.3] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.3] DEVANAGARI LETTER YA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0915 × 094D ÷ 0061 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] LATIN SMALL LETTER A (Other) ÷ [0.3] -÷ 0061 × 094D ÷ 0924 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 003F × 094D ÷ 0924 ÷ # ÷ [0.2] QUESTION MARK (Other) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0915 × 094D × 094D × 0924 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.3] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] +÷ 1F1E6 × 1F1E7 ÷ 1F1E8 ÷ 0062 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [12.0] REGIONAL INDICATOR SYMBOL LETTER B (RI) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER C (RI) ÷ [999.0] LATIN SMALL LETTER B (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0061 ÷ 1F1E6 × 1F1E7 ÷ 1F1E8 ÷ 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [13.0] REGIONAL INDICATOR SYMBOL LETTER B (RI) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER C (RI) ÷ [999.0] LATIN SMALL LETTER B (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0061 ÷ 1F1E6 × 1F1E7 × 200D ÷ 1F1E8 ÷ 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [13.0] REGIONAL INDICATOR SYMBOL LETTER B (RI) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER C (RI) ÷ [999.0] LATIN SMALL LETTER B (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0061 ÷ 1F1E6 × 200D ÷ 1F1E7 × 1F1E8 ÷ 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER B (RI) × [13.0] REGIONAL INDICATOR SYMBOL LETTER C (RI) ÷ [999.0] LATIN SMALL LETTER B (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0061 ÷ 1F1E6 × 1F1E7 ÷ 1F1E8 × 1F1E9 ÷ 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [13.0] REGIONAL INDICATOR SYMBOL LETTER B (RI) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER C (RI) × [13.0] REGIONAL INDICATOR SYMBOL LETTER D (RI) ÷ [999.0] LATIN SMALL LETTER B (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0061 × 200D ÷ # ÷ [0.2] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 0061 × 0308 ÷ 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] LATIN SMALL LETTER B (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0061 × 0903 ÷ 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] LATIN SMALL LETTER B (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0061 ÷ 0600 × 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) × [9.2] LATIN SMALL LETTER B (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 1F476 × 1F3FF ÷ 1F476 ÷ # ÷ [0.2] BABY (ExtPict) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] BABY (ExtPict) ÷ [0.3] +÷ 0061 × 1F3FF ÷ 1F476 ÷ # ÷ [0.2] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] BABY (ExtPict) ÷ [0.3] +÷ 0061 × 1F3FF ÷ 1F476 × 200D × 1F6D1 ÷ # ÷ [0.2] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] BABY (ExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ) × [11.0] OCTAGONAL SIGN (ExtPict) ÷ [0.3] +÷ 1F476 × 1F3FF × 0308 × 200D × 1F476 × 1F3FF ÷ # ÷ [0.2] BABY (ExtPict) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) × [11.0] BABY (ExtPict) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 1F6D1 × 200D × 1F6D1 ÷ # ÷ [0.2] OCTAGONAL SIGN (ExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ) × [11.0] OCTAGONAL SIGN (ExtPict) ÷ [0.3] +÷ 0061 × 200D ÷ 1F6D1 ÷ # ÷ [0.2] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] OCTAGONAL SIGN (ExtPict) ÷ [0.3] +÷ 2701 × 200D ÷ 2701 ÷ # ÷ [0.2] UPPER BLADE SCISSORS (XXmLinkingConsonantmExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] UPPER BLADE SCISSORS (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0061 × 200D ÷ 2701 ÷ # ÷ [0.2] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] UPPER BLADE SCISSORS (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0915 ÷ 0924 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [999.0] DEVANAGARI LETTER TA (LinkingConsonant) ÷ [0.3] +÷ 0915 × 094D × 0924 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.3] DEVANAGARI LETTER TA (LinkingConsonant) ÷ [0.3] +÷ 0915 × 094D × 094D × 0924 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.3] DEVANAGARI LETTER TA (LinkingConsonant) ÷ [0.3] +÷ 0915 × 094D × 200D × 0924 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) × [9.3] DEVANAGARI LETTER TA (LinkingConsonant) ÷ [0.3] +÷ 0915 × 093C × 200D × 094D × 0924 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.3] DEVANAGARI LETTER TA (LinkingConsonant) ÷ [0.3] +÷ 0915 × 093C × 094D × 200D × 0924 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) × [9.3] DEVANAGARI LETTER TA (LinkingConsonant) ÷ [0.3] +÷ 0915 × 094D × 0924 × 094D × 092F ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.3] DEVANAGARI LETTER TA (LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.3] DEVANAGARI LETTER YA (LinkingConsonant) ÷ [0.3] +÷ 0915 × 094D ÷ 0061 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [999.0] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0061 × 094D ÷ 0924 ÷ # ÷ [0.2] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [999.0] DEVANAGARI LETTER TA (LinkingConsonant) ÷ [0.3] +÷ 003F × 094D ÷ 0924 ÷ # ÷ [0.2] QUESTION MARK (XXmLinkingConsonantmExtPict) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [999.0] DEVANAGARI LETTER TA (LinkingConsonant) ÷ [0.3] +÷ 0915 × 094D × 094D × 0924 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.3] DEVANAGARI LETTER TA (LinkingConsonant) ÷ [0.3] +÷ 0AB8 × 0AFB × 0ACD × 0AB8 × 0AFB ÷ # ÷ [0.2] GUJARATI LETTER SA (LinkingConsonant) × [9.0] GUJARATI SIGN SHADDA (Extend_ConjunctExtendermConjunctLinker) × [9.0] GUJARATI SIGN VIRAMA (Extend_ConjunctLinker) × [9.3] GUJARATI LETTER SA (LinkingConsonant) × [9.0] GUJARATI SIGN SHADDA (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 1019 × 1039 × 1018 ÷ 102C × 1037 ÷ # ÷ [0.2] MYANMAR LETTER MA (LinkingConsonant) × [9.0] MYANMAR SIGN VIRAMA (Extend_ConjunctLinker) × [9.3] MYANMAR LETTER BHA (LinkingConsonant) ÷ [999.0] MYANMAR VOWEL SIGN AA (XXmLinkingConsonantmExtPict) × [9.0] MYANMAR SIGN DOT BELOW (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 1004 × 103A × 1039 × 1011 × 1039 × 1011 ÷ # ÷ [0.2] MYANMAR LETTER NGA (LinkingConsonant) × [9.0] MYANMAR SIGN ASAT (Extend_ConjunctExtendermConjunctLinker) × [9.0] MYANMAR SIGN VIRAMA (Extend_ConjunctLinker) × [9.3] MYANMAR LETTER THA (LinkingConsonant) × [9.0] MYANMAR SIGN VIRAMA (Extend_ConjunctLinker) × [9.3] MYANMAR LETTER THA (LinkingConsonant) ÷ [0.3] +÷ 1B12 × 1B01 ÷ 1B32 × 1B44 × 1B2F ÷ 1B32 × 1B44 × 1B22 × 1B44 × 1B2C ÷ 1B32 × 1B44 × 1B22 × 1B38 ÷ # ÷ [0.2] BALINESE LETTER OKARA TEDUNG (XXmLinkingConsonantmExtPict) × [9.0] BALINESE SIGN ULU CANDRA (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] BALINESE LETTER SA (LinkingConsonant) × [9.0] BALINESE ADEG ADEG (Extend_ConjunctLinker) × [9.3] BALINESE LETTER WA (LinkingConsonant) ÷ [999.0] BALINESE LETTER SA (LinkingConsonant) × [9.0] BALINESE ADEG ADEG (Extend_ConjunctLinker) × [9.3] BALINESE LETTER TA (LinkingConsonant) × [9.0] BALINESE ADEG ADEG (Extend_ConjunctLinker) × [9.3] BALINESE LETTER YA (LinkingConsonant) ÷ [999.0] BALINESE LETTER SA (LinkingConsonant) × [9.0] BALINESE ADEG ADEG (Extend_ConjunctLinker) × [9.3] BALINESE LETTER TA (LinkingConsonant) × [9.0] BALINESE VOWEL SIGN SUKU (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 179F × 17D2 × 178F × 17D2 × 179A × 17B8 ÷ # ÷ [0.2] KHMER LETTER SA (LinkingConsonant) × [9.0] KHMER SIGN COENG (Extend_ConjunctLinker) × [9.3] KHMER LETTER TA (LinkingConsonant) × [9.0] KHMER SIGN COENG (Extend_ConjunctLinker) × [9.3] KHMER LETTER RO (LinkingConsonant) × [9.0] KHMER VOWEL SIGN II (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 1B26 ÷ 1B17 × 1B44 × 1B13 ÷ # ÷ [0.2] BALINESE LETTER NA (LinkingConsonant) ÷ [999.0] BALINESE LETTER NGA (LinkingConsonant) × [9.0] BALINESE ADEG ADEG (Extend_ConjunctLinker) × [9.3] BALINESE LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 1B27 ÷ 1B13 × 1B44 × 1B0B ÷ 1B0B × 1B04 ÷ # ÷ [0.2] BALINESE LETTER PA (LinkingConsonant) ÷ [999.0] BALINESE LETTER KA (LinkingConsonant) × [9.0] BALINESE ADEG ADEG (Extend_ConjunctLinker) × [9.3] BALINESE LETTER RA REPA (LinkingConsonant) ÷ [999.0] BALINESE LETTER RA REPA (LinkingConsonant) × [9.1] BALINESE SIGN BISAH (SpacingMark) ÷ [0.3] +÷ 1795 × 17D2 × 17AF ÷ 1798 ÷ # ÷ [0.2] KHMER LETTER PHA (LinkingConsonant) × [9.0] KHMER SIGN COENG (Extend_ConjunctLinker) × [9.3] KHMER INDEPENDENT VOWEL QE (LinkingConsonant) ÷ [999.0] KHMER LETTER MO (LinkingConsonant) ÷ [0.3] +÷ 17A0 × 17D2 × 17AB ÷ 1791 × 17D0 ÷ 1799 ÷ # ÷ [0.2] KHMER LETTER HA (LinkingConsonant) × [9.0] KHMER SIGN COENG (Extend_ConjunctLinker) × [9.3] KHMER INDEPENDENT VOWEL RY (LinkingConsonant) ÷ [999.0] KHMER LETTER TO (LinkingConsonant) × [9.0] KHMER SIGN SAMYOK SANNYA (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] KHMER LETTER YO (LinkingConsonant) ÷ [0.3] # -# Lines: 1093 +# Lines: 766 # # EOF diff --git a/src/java.base/share/data/unicodedata/emoji/emoji-data.txt b/src/java.base/share/data/unicodedata/emoji/emoji-data.txt index 12f83273cf5..450252c4df3 100644 --- a/src/java.base/share/data/unicodedata/emoji/emoji-data.txt +++ b/src/java.base/share/data/unicodedata/emoji/emoji-data.txt @@ -1,11 +1,11 @@ # emoji-data.txt -# Date: 2024-05-01, 21:25:24 GMT -# Copyright (c) 2024 Unicode, Inc. +# Date: 2025-07-25, 17:54:31 GMT +# © 2025 Unicode®, Inc. # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. # For terms of use and license, see https://www.unicode.org/terms_of_use.html # # Emoji Data for UTS #51 -# Used with Emoji Version 16.0 and subsequent minor revisions (if any) +# Version: 17.0 # # For documentation and usage, see https://www.unicode.org/reports/tr51 # @@ -340,6 +340,7 @@ 1F6D1..1F6D2 ; Emoji # E3.0 [2] (🛑..🛒) stop sign..shopping cart 1F6D5 ; Emoji # E12.0 [1] (🛕) hindu temple 1F6D6..1F6D7 ; Emoji # E13.0 [2] (🛖..🛗) hut..elevator +1F6D8 ; Emoji # E17.0 [1] (🛘) landslide 1F6DC ; Emoji # E15.0 [1] (🛜) wireless 1F6DD..1F6DF ; Emoji # E14.0 [3] (🛝..🛟) playground slide..ring buoy 1F6E0..1F6E5 ; Emoji # E0.7 [6] (🛠️..🛥️) hammer and wrench..motor boat @@ -408,6 +409,8 @@ 1FA83..1FA86 ; Emoji # E13.0 [4] (🪃..🪆) boomerang..nesting dolls 1FA87..1FA88 ; Emoji # E15.0 [2] (🪇..🪈) maracas..flute 1FA89 ; Emoji # E16.0 [1] (🪉) harp +1FA8A ; Emoji # E17.0 [1] (🪊) trombone +1FA8E ; Emoji # E17.0 [1] (🪎) treasure chest 1FA8F ; Emoji # E16.0 [1] (🪏) shovel 1FA90..1FA95 ; Emoji # E12.0 [6] (🪐..🪕) ringed planet..banjo 1FA96..1FAA8 ; Emoji # E13.0 [19] (🪖..🪨) military helmet..rock @@ -421,6 +424,8 @@ 1FAC0..1FAC2 ; Emoji # E13.0 [3] (🫀..🫂) anatomical heart..people hugging 1FAC3..1FAC5 ; Emoji # E14.0 [3] (🫃..🫅) pregnant man..person with crown 1FAC6 ; Emoji # E16.0 [1] (🫆) fingerprint +1FAC8 ; Emoji # E17.0 [1] (🫈) hairy creature +1FACD ; Emoji # E17.0 [1] (🫍) orca 1FACE..1FACF ; Emoji # E15.0 [2] (🫎..🫏) moose..donkey 1FAD0..1FAD6 ; Emoji # E13.0 [7] (🫐..🫖) blueberries..teapot 1FAD7..1FAD9 ; Emoji # E14.0 [3] (🫗..🫙) pouring liquid..jar @@ -430,10 +435,12 @@ 1FAE0..1FAE7 ; Emoji # E14.0 [8] (🫠..🫧) melting face..bubbles 1FAE8 ; Emoji # E15.0 [1] (🫨) shaking face 1FAE9 ; Emoji # E16.0 [1] (🫩) face with bags under eyes +1FAEA ; Emoji # E17.0 [1] (🫪) distorted face +1FAEF ; Emoji # E17.0 [1] (🫯) fight cloud 1FAF0..1FAF6 ; Emoji # E14.0 [7] (🫰..🫶) hand with index finger and thumb crossed..heart hands 1FAF7..1FAF8 ; Emoji # E15.0 [2] (🫷..🫸) leftwards pushing hand..rightwards pushing hand -# Total elements: 1431 +# Total elements: 1438 # ================================================ @@ -640,6 +647,7 @@ 1F6D1..1F6D2 ; Emoji_Presentation # E3.0 [2] (🛑..🛒) stop sign..shopping cart 1F6D5 ; Emoji_Presentation # E12.0 [1] (🛕) hindu temple 1F6D6..1F6D7 ; Emoji_Presentation # E13.0 [2] (🛖..🛗) hut..elevator +1F6D8 ; Emoji_Presentation # E17.0 [1] (🛘) landslide 1F6DC ; Emoji_Presentation # E15.0 [1] (🛜) wireless 1F6DD..1F6DF ; Emoji_Presentation # E14.0 [3] (🛝..🛟) playground slide..ring buoy 1F6EB..1F6EC ; Emoji_Presentation # E1.0 [2] (🛫..🛬) airplane departure..airplane arrival @@ -704,6 +712,8 @@ 1FA83..1FA86 ; Emoji_Presentation # E13.0 [4] (🪃..🪆) boomerang..nesting dolls 1FA87..1FA88 ; Emoji_Presentation # E15.0 [2] (🪇..🪈) maracas..flute 1FA89 ; Emoji_Presentation # E16.0 [1] (🪉) harp +1FA8A ; Emoji_Presentation # E17.0 [1] (🪊) trombone +1FA8E ; Emoji_Presentation # E17.0 [1] (🪎) treasure chest 1FA8F ; Emoji_Presentation # E16.0 [1] (🪏) shovel 1FA90..1FA95 ; Emoji_Presentation # E12.0 [6] (🪐..🪕) ringed planet..banjo 1FA96..1FAA8 ; Emoji_Presentation # E13.0 [19] (🪖..🪨) military helmet..rock @@ -717,6 +727,8 @@ 1FAC0..1FAC2 ; Emoji_Presentation # E13.0 [3] (🫀..🫂) anatomical heart..people hugging 1FAC3..1FAC5 ; Emoji_Presentation # E14.0 [3] (🫃..🫅) pregnant man..person with crown 1FAC6 ; Emoji_Presentation # E16.0 [1] (🫆) fingerprint +1FAC8 ; Emoji_Presentation # E17.0 [1] (🫈) hairy creature +1FACD ; Emoji_Presentation # E17.0 [1] (🫍) orca 1FACE..1FACF ; Emoji_Presentation # E15.0 [2] (🫎..🫏) moose..donkey 1FAD0..1FAD6 ; Emoji_Presentation # E13.0 [7] (🫐..🫖) blueberries..teapot 1FAD7..1FAD9 ; Emoji_Presentation # E14.0 [3] (🫗..🫙) pouring liquid..jar @@ -726,10 +738,12 @@ 1FAE0..1FAE7 ; Emoji_Presentation # E14.0 [8] (🫠..🫧) melting face..bubbles 1FAE8 ; Emoji_Presentation # E15.0 [1] (🫨) shaking face 1FAE9 ; Emoji_Presentation # E16.0 [1] (🫩) face with bags under eyes +1FAEA ; Emoji_Presentation # E17.0 [1] (🫪) distorted face +1FAEF ; Emoji_Presentation # E17.0 [1] (🫯) fight cloud 1FAF0..1FAF6 ; Emoji_Presentation # E14.0 [7] (🫰..🫶) hand with index finger and thumb crossed..heart hands 1FAF7..1FAF8 ; Emoji_Presentation # E15.0 [2] (🫷..🫸) leftwards pushing hand..rightwards pushing hand -# Total elements: 1212 +# Total elements: 1219 # ================================================ @@ -827,7 +841,6 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 21A9..21AA ; Extended_Pictographic# E0.6 [2] (↩️..↪️) right arrow curving left..left arrow curving right 231A..231B ; Extended_Pictographic# E0.6 [2] (⌚..⌛) watch..hourglass done 2328 ; Extended_Pictographic# E1.0 [1] (⌨️) keyboard -2388 ; Extended_Pictographic# E0.0 [1] (⎈) HELM SYMBOL 23CF ; Extended_Pictographic# E1.0 [1] (⏏️) eject button 23E9..23EC ; Extended_Pictographic# E0.6 [4] (⏩..⏬) fast-forward button..fast down button 23ED..23EE ; Extended_Pictographic# E0.7 [2] (⏭️..⏮️) next track button..last track button @@ -844,106 +857,63 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 2600..2601 ; Extended_Pictographic# E0.6 [2] (☀️..☁️) sun..cloud 2602..2603 ; Extended_Pictographic# E0.7 [2] (☂️..☃️) umbrella..snowman 2604 ; Extended_Pictographic# E1.0 [1] (☄️) comet -2605 ; Extended_Pictographic# E0.0 [1] (★) BLACK STAR -2607..260D ; Extended_Pictographic# E0.0 [7] (☇..☍) LIGHTNING..OPPOSITION 260E ; Extended_Pictographic# E0.6 [1] (☎️) telephone -260F..2610 ; Extended_Pictographic# E0.0 [2] (☏..☐) WHITE TELEPHONE..BALLOT BOX 2611 ; Extended_Pictographic# E0.6 [1] (☑️) check box with check -2612 ; Extended_Pictographic# E0.0 [1] (☒) BALLOT BOX WITH X 2614..2615 ; Extended_Pictographic# E0.6 [2] (☔..☕) umbrella with rain drops..hot beverage -2616..2617 ; Extended_Pictographic# E0.0 [2] (☖..☗) WHITE SHOGI PIECE..BLACK SHOGI PIECE 2618 ; Extended_Pictographic# E1.0 [1] (☘️) shamrock -2619..261C ; Extended_Pictographic# E0.0 [4] (☙..☜) REVERSED ROTATED FLORAL HEART BULLET..WHITE LEFT POINTING INDEX 261D ; Extended_Pictographic# E0.6 [1] (☝️) index pointing up -261E..261F ; Extended_Pictographic# E0.0 [2] (☞..☟) WHITE RIGHT POINTING INDEX..WHITE DOWN POINTING INDEX 2620 ; Extended_Pictographic# E1.0 [1] (☠️) skull and crossbones -2621 ; Extended_Pictographic# E0.0 [1] (☡) CAUTION SIGN 2622..2623 ; Extended_Pictographic# E1.0 [2] (☢️..☣️) radioactive..biohazard -2624..2625 ; Extended_Pictographic# E0.0 [2] (☤..☥) CADUCEUS..ANKH 2626 ; Extended_Pictographic# E1.0 [1] (☦️) orthodox cross -2627..2629 ; Extended_Pictographic# E0.0 [3] (☧..☩) CHI RHO..CROSS OF JERUSALEM 262A ; Extended_Pictographic# E0.7 [1] (☪️) star and crescent -262B..262D ; Extended_Pictographic# E0.0 [3] (☫..☭) FARSI SYMBOL..HAMMER AND SICKLE 262E ; Extended_Pictographic# E1.0 [1] (☮️) peace symbol 262F ; Extended_Pictographic# E0.7 [1] (☯️) yin yang -2630..2637 ; Extended_Pictographic# E0.0 [8] (☰..☷) TRIGRAM FOR HEAVEN..TRIGRAM FOR EARTH 2638..2639 ; Extended_Pictographic# E0.7 [2] (☸️..☹️) wheel of dharma..frowning face 263A ; Extended_Pictographic# E0.6 [1] (☺️) smiling face -263B..263F ; Extended_Pictographic# E0.0 [5] (☻..☿) BLACK SMILING FACE..MERCURY 2640 ; Extended_Pictographic# E4.0 [1] (♀️) female sign -2641 ; Extended_Pictographic# E0.0 [1] (♁) EARTH 2642 ; Extended_Pictographic# E4.0 [1] (♂️) male sign -2643..2647 ; Extended_Pictographic# E0.0 [5] (♃..♇) JUPITER..PLUTO 2648..2653 ; Extended_Pictographic# E0.6 [12] (♈..♓) Aries..Pisces -2654..265E ; Extended_Pictographic# E0.0 [11] (♔..♞) WHITE CHESS KING..BLACK CHESS KNIGHT 265F ; Extended_Pictographic# E11.0 [1] (♟️) chess pawn 2660 ; Extended_Pictographic# E0.6 [1] (♠️) spade suit -2661..2662 ; Extended_Pictographic# E0.0 [2] (♡..♢) WHITE HEART SUIT..WHITE DIAMOND SUIT 2663 ; Extended_Pictographic# E0.6 [1] (♣️) club suit -2664 ; Extended_Pictographic# E0.0 [1] (♤) WHITE SPADE SUIT 2665..2666 ; Extended_Pictographic# E0.6 [2] (♥️..♦️) heart suit..diamond suit -2667 ; Extended_Pictographic# E0.0 [1] (♧) WHITE CLUB SUIT 2668 ; Extended_Pictographic# E0.6 [1] (♨️) hot springs -2669..267A ; Extended_Pictographic# E0.0 [18] (♩..♺) QUARTER NOTE..RECYCLING SYMBOL FOR GENERIC MATERIALS 267B ; Extended_Pictographic# E0.6 [1] (♻️) recycling symbol -267C..267D ; Extended_Pictographic# E0.0 [2] (♼..♽) RECYCLED PAPER SYMBOL..PARTIALLY-RECYCLED PAPER SYMBOL 267E ; Extended_Pictographic# E11.0 [1] (♾️) infinity 267F ; Extended_Pictographic# E0.6 [1] (♿) wheelchair symbol -2680..2685 ; Extended_Pictographic# E0.0 [6] (⚀..⚅) DIE FACE-1..DIE FACE-6 -2690..2691 ; Extended_Pictographic# E0.0 [2] (⚐..⚑) WHITE FLAG..BLACK FLAG 2692 ; Extended_Pictographic# E1.0 [1] (⚒️) hammer and pick 2693 ; Extended_Pictographic# E0.6 [1] (⚓) anchor 2694 ; Extended_Pictographic# E1.0 [1] (⚔️) crossed swords 2695 ; Extended_Pictographic# E4.0 [1] (⚕️) medical symbol 2696..2697 ; Extended_Pictographic# E1.0 [2] (⚖️..⚗️) balance scale..alembic -2698 ; Extended_Pictographic# E0.0 [1] (⚘) FLOWER 2699 ; Extended_Pictographic# E1.0 [1] (⚙️) gear -269A ; Extended_Pictographic# E0.0 [1] (⚚) STAFF OF HERMES 269B..269C ; Extended_Pictographic# E1.0 [2] (⚛️..⚜️) atom symbol..fleur-de-lis -269D..269F ; Extended_Pictographic# E0.0 [3] (⚝..⚟) OUTLINED WHITE STAR..THREE LINES CONVERGING LEFT 26A0..26A1 ; Extended_Pictographic# E0.6 [2] (⚠️..⚡) warning..high voltage -26A2..26A6 ; Extended_Pictographic# E0.0 [5] (⚢..⚦) DOUBLED FEMALE SIGN..MALE WITH STROKE SIGN 26A7 ; Extended_Pictographic# E13.0 [1] (⚧️) transgender symbol -26A8..26A9 ; Extended_Pictographic# E0.0 [2] (⚨..⚩) VERTICAL MALE WITH STROKE SIGN..HORIZONTAL MALE WITH STROKE SIGN 26AA..26AB ; Extended_Pictographic# E0.6 [2] (⚪..⚫) white circle..black circle -26AC..26AF ; Extended_Pictographic# E0.0 [4] (⚬..⚯) MEDIUM SMALL WHITE CIRCLE..UNMARRIED PARTNERSHIP SYMBOL 26B0..26B1 ; Extended_Pictographic# E1.0 [2] (⚰️..⚱️) coffin..funeral urn -26B2..26BC ; Extended_Pictographic# E0.0 [11] (⚲..⚼) NEUTER..SESQUIQUADRATE 26BD..26BE ; Extended_Pictographic# E0.6 [2] (⚽..⚾) soccer ball..baseball -26BF..26C3 ; Extended_Pictographic# E0.0 [5] (⚿..⛃) SQUARED KEY..BLACK DRAUGHTS KING 26C4..26C5 ; Extended_Pictographic# E0.6 [2] (⛄..⛅) snowman without snow..sun behind cloud -26C6..26C7 ; Extended_Pictographic# E0.0 [2] (⛆..⛇) RAIN..BLACK SNOWMAN 26C8 ; Extended_Pictographic# E0.7 [1] (⛈️) cloud with lightning and rain -26C9..26CD ; Extended_Pictographic# E0.0 [5] (⛉..⛍) TURNED WHITE SHOGI PIECE..DISABLED CAR 26CE ; Extended_Pictographic# E0.6 [1] (⛎) Ophiuchus 26CF ; Extended_Pictographic# E0.7 [1] (⛏️) pick -26D0 ; Extended_Pictographic# E0.0 [1] (⛐) CAR SLIDING 26D1 ; Extended_Pictographic# E0.7 [1] (⛑️) rescue worker’s helmet -26D2 ; Extended_Pictographic# E0.0 [1] (⛒) CIRCLED CROSSING LANES 26D3 ; Extended_Pictographic# E0.7 [1] (⛓️) chains 26D4 ; Extended_Pictographic# E0.6 [1] (⛔) no entry -26D5..26E8 ; Extended_Pictographic# E0.0 [20] (⛕..⛨) ALTERNATE ONE-WAY LEFT WAY TRAFFIC..BLACK CROSS ON SHIELD 26E9 ; Extended_Pictographic# E0.7 [1] (⛩️) shinto shrine 26EA ; Extended_Pictographic# E0.6 [1] (⛪) church -26EB..26EF ; Extended_Pictographic# E0.0 [5] (⛫..⛯) CASTLE..MAP SYMBOL FOR LIGHTHOUSE 26F0..26F1 ; Extended_Pictographic# E0.7 [2] (⛰️..⛱️) mountain..umbrella on ground 26F2..26F3 ; Extended_Pictographic# E0.6 [2] (⛲..⛳) fountain..flag in hole 26F4 ; Extended_Pictographic# E0.7 [1] (⛴️) ferry 26F5 ; Extended_Pictographic# E0.6 [1] (⛵) sailboat -26F6 ; Extended_Pictographic# E0.0 [1] (⛶) SQUARE FOUR CORNERS 26F7..26F9 ; Extended_Pictographic# E0.7 [3] (⛷️..⛹️) skier..person bouncing ball 26FA ; Extended_Pictographic# E0.6 [1] (⛺) tent -26FB..26FC ; Extended_Pictographic# E0.0 [2] (⛻..⛼) JAPANESE BANK SYMBOL..HEADSTONE GRAVEYARD SYMBOL 26FD ; Extended_Pictographic# E0.6 [1] (⛽) fuel pump -26FE..2701 ; Extended_Pictographic# E0.0 [4] (⛾..✁) CUP ON BLACK SQUARE..UPPER BLADE SCISSORS 2702 ; Extended_Pictographic# E0.6 [1] (✂️) scissors -2703..2704 ; Extended_Pictographic# E0.0 [2] (✃..✄) LOWER BLADE SCISSORS..WHITE SCISSORS 2705 ; Extended_Pictographic# E0.6 [1] (✅) check mark button 2708..270C ; Extended_Pictographic# E0.6 [5] (✈️..✌️) airplane..victory hand 270D ; Extended_Pictographic# E0.7 [1] (✍️) writing hand -270E ; Extended_Pictographic# E0.0 [1] (✎) LOWER RIGHT PENCIL 270F ; Extended_Pictographic# E0.6 [1] (✏️) pencil -2710..2711 ; Extended_Pictographic# E0.0 [2] (✐..✑) UPPER RIGHT PENCIL..WHITE NIB 2712 ; Extended_Pictographic# E0.6 [1] (✒️) black nib 2714 ; Extended_Pictographic# E0.6 [1] (✔️) check mark 2716 ; Extended_Pictographic# E0.6 [1] (✖️) multiply @@ -959,7 +929,6 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 2757 ; Extended_Pictographic# E0.6 [1] (❗) red exclamation mark 2763 ; Extended_Pictographic# E1.0 [1] (❣️) heart exclamation 2764 ; Extended_Pictographic# E0.6 [1] (❤️) red heart -2765..2767 ; Extended_Pictographic# E0.0 [3] (❥..❧) ROTATED HEAVY BLACK HEART BULLET..ROTATED FLORAL HEART BULLET 2795..2797 ; Extended_Pictographic# E0.6 [3] (➕..➗) plus..divide 27A1 ; Extended_Pictographic# E0.6 [1] (➡️) right arrow 27B0 ; Extended_Pictographic# E0.6 [1] (➰) curly loop @@ -973,19 +942,19 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 303D ; Extended_Pictographic# E0.6 [1] (〽️) part alternation mark 3297 ; Extended_Pictographic# E0.6 [1] (㊗️) Japanese “congratulations” button 3299 ; Extended_Pictographic# E0.6 [1] (㊙️) Japanese “secret” button -1F000..1F003 ; Extended_Pictographic# E0.0 [4] (🀀..🀃) MAHJONG TILE EAST WIND..MAHJONG TILE NORTH WIND 1F004 ; Extended_Pictographic# E0.6 [1] (🀄) mahjong red dragon -1F005..1F0CE ; Extended_Pictographic# E0.0 [202] (🀅..🃎) MAHJONG TILE GREEN DRAGON..PLAYING CARD KING OF DIAMONDS +1F02C..1F02F ; Extended_Pictographic# E0.0 [4] (🀬..🀯) .. +1F094..1F09F ; Extended_Pictographic# E0.0 [12] (🂔..🂟) .. +1F0AF..1F0B0 ; Extended_Pictographic# E0.0 [2] (🂯..🂰) .. +1F0C0 ; Extended_Pictographic# E0.0 [1] (🃀) 1F0CF ; Extended_Pictographic# E0.6 [1] (🃏) joker -1F0D0..1F0FF ; Extended_Pictographic# E0.0 [48] (🃐..🃿) .. -1F10D..1F10F ; Extended_Pictographic# E0.0 [3] (🄍..🄏) CIRCLED ZERO WITH SLASH..CIRCLED DOLLAR SIGN WITH OVERLAID BACKSLASH -1F12F ; Extended_Pictographic# E0.0 [1] (🄯) COPYLEFT SYMBOL -1F16C..1F16F ; Extended_Pictographic# E0.0 [4] (🅬..🅯) RAISED MR SIGN..CIRCLED HUMAN FIGURE +1F0D0 ; Extended_Pictographic# E0.0 [1] (🃐) +1F0F6..1F0FF ; Extended_Pictographic# E0.0 [10] (🃶..🃿) .. 1F170..1F171 ; Extended_Pictographic# E0.6 [2] (🅰️..🅱️) A button (blood type)..B button (blood type) 1F17E..1F17F ; Extended_Pictographic# E0.6 [2] (🅾️..🅿️) O button (blood type)..P button 1F18E ; Extended_Pictographic# E0.6 [1] (🆎) AB button (blood type) 1F191..1F19A ; Extended_Pictographic# E0.6 [10] (🆑..🆚) CL button..VS button -1F1AD..1F1E5 ; Extended_Pictographic# E0.0 [57] (🆭..🇥) MASK WORK SYMBOL.. +1F1AE..1F1E5 ; Extended_Pictographic# E0.0 [56] (🆮..🇥) .. 1F201..1F202 ; Extended_Pictographic# E0.6 [2] (🈁..🈂️) Japanese “here” button..Japanese “service charge” button 1F203..1F20F ; Extended_Pictographic# E0.0 [13] (🈃..🈏) .. 1F21A ; Extended_Pictographic# E0.6 [1] (🈚) Japanese “free of charge” button @@ -994,7 +963,8 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1F23C..1F23F ; Extended_Pictographic# E0.0 [4] (🈼..🈿) .. 1F249..1F24F ; Extended_Pictographic# E0.0 [7] (🉉..🉏) .. 1F250..1F251 ; Extended_Pictographic# E0.6 [2] (🉐..🉑) Japanese “bargain” button..Japanese “acceptable” button -1F252..1F2FF ; Extended_Pictographic# E0.0 [174] (🉒..🋿) .. +1F252..1F25F ; Extended_Pictographic# E0.0 [14] (🉒..🉟) .. +1F266..1F2FF ; Extended_Pictographic# E0.0 [154] (🉦..🋿) .. 1F300..1F30C ; Extended_Pictographic# E0.6 [13] (🌀..🌌) cyclone..milky way 1F30D..1F30E ; Extended_Pictographic# E0.7 [2] (🌍..🌎) globe showing Europe-Africa..globe showing Americas 1F30F ; Extended_Pictographic# E0.6 [1] (🌏) globe showing Asia-Australia @@ -1010,7 +980,6 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1F31D..1F31E ; Extended_Pictographic# E1.0 [2] (🌝..🌞) full moon face..sun with face 1F31F..1F320 ; Extended_Pictographic# E0.6 [2] (🌟..🌠) glowing star..shooting star 1F321 ; Extended_Pictographic# E0.7 [1] (🌡️) thermometer -1F322..1F323 ; Extended_Pictographic# E0.0 [2] (🌢..🌣) BLACK DROPLET..WHITE SUN 1F324..1F32C ; Extended_Pictographic# E0.7 [9] (🌤️..🌬️) sun behind small cloud..wind face 1F32D..1F32F ; Extended_Pictographic# E1.0 [3] (🌭..🌯) hot dog..burrito 1F330..1F331 ; Extended_Pictographic# E0.6 [2] (🌰..🌱) chestnut..seedling @@ -1026,11 +995,8 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1F37D ; Extended_Pictographic# E0.7 [1] (🍽️) fork and knife with plate 1F37E..1F37F ; Extended_Pictographic# E1.0 [2] (🍾..🍿) bottle with popping cork..popcorn 1F380..1F393 ; Extended_Pictographic# E0.6 [20] (🎀..🎓) ribbon..graduation cap -1F394..1F395 ; Extended_Pictographic# E0.0 [2] (🎔..🎕) HEART WITH TIP ON THE LEFT..BOUQUET OF FLOWERS 1F396..1F397 ; Extended_Pictographic# E0.7 [2] (🎖️..🎗️) military medal..reminder ribbon -1F398 ; Extended_Pictographic# E0.0 [1] (🎘) MUSICAL KEYBOARD WITH JACKS 1F399..1F39B ; Extended_Pictographic# E0.7 [3] (🎙️..🎛️) studio microphone..control knobs -1F39C..1F39D ; Extended_Pictographic# E0.0 [2] (🎜..🎝) BEAMED ASCENDING MUSICAL NOTES..BEAMED DESCENDING MUSICAL NOTES 1F39E..1F39F ; Extended_Pictographic# E0.7 [2] (🎞️..🎟️) film frames..admission tickets 1F3A0..1F3C4 ; Extended_Pictographic# E0.6 [37] (🎠..🏄) carousel horse..person surfing 1F3C5 ; Extended_Pictographic# E1.0 [1] (🏅) sports medal @@ -1045,11 +1011,9 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1F3E0..1F3E3 ; Extended_Pictographic# E0.6 [4] (🏠..🏣) house..Japanese post office 1F3E4 ; Extended_Pictographic# E1.0 [1] (🏤) post office 1F3E5..1F3F0 ; Extended_Pictographic# E0.6 [12] (🏥..🏰) hospital..castle -1F3F1..1F3F2 ; Extended_Pictographic# E0.0 [2] (🏱..🏲) WHITE PENNANT..BLACK PENNANT 1F3F3 ; Extended_Pictographic# E0.7 [1] (🏳️) white flag 1F3F4 ; Extended_Pictographic# E1.0 [1] (🏴) black flag 1F3F5 ; Extended_Pictographic# E0.7 [1] (🏵️) rosette -1F3F6 ; Extended_Pictographic# E0.0 [1] (🏶) BLACK ROSETTE 1F3F7 ; Extended_Pictographic# E0.7 [1] (🏷️) label 1F3F8..1F3FA ; Extended_Pictographic# E1.0 [3] (🏸..🏺) badminton..amphora 1F400..1F407 ; Extended_Pictographic# E1.0 [8] (🐀..🐇) rat..rabbit @@ -1086,7 +1050,6 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1F4F8 ; Extended_Pictographic# E1.0 [1] (📸) camera with flash 1F4F9..1F4FC ; Extended_Pictographic# E0.6 [4] (📹..📼) video camera..videocassette 1F4FD ; Extended_Pictographic# E0.7 [1] (📽️) film projector -1F4FE ; Extended_Pictographic# E0.0 [1] (📾) PORTABLE STEREO 1F4FF..1F502 ; Extended_Pictographic# E1.0 [4] (📿..🔂) prayer beads..repeat single button 1F503 ; Extended_Pictographic# E0.6 [1] (🔃) clockwise vertical arrows 1F504..1F507 ; Extended_Pictographic# E1.0 [4] (🔄..🔇) counterclockwise arrows button..muted speaker @@ -1097,51 +1060,30 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1F516..1F52B ; Extended_Pictographic# E0.6 [22] (🔖..🔫) bookmark..water pistol 1F52C..1F52D ; Extended_Pictographic# E1.0 [2] (🔬..🔭) microscope..telescope 1F52E..1F53D ; Extended_Pictographic# E0.6 [16] (🔮..🔽) crystal ball..downwards button -1F546..1F548 ; Extended_Pictographic# E0.0 [3] (🕆..🕈) WHITE LATIN CROSS..CELTIC CROSS 1F549..1F54A ; Extended_Pictographic# E0.7 [2] (🕉️..🕊️) om..dove 1F54B..1F54E ; Extended_Pictographic# E1.0 [4] (🕋..🕎) kaaba..menorah -1F54F ; Extended_Pictographic# E0.0 [1] (🕏) BOWL OF HYGIEIA 1F550..1F55B ; Extended_Pictographic# E0.6 [12] (🕐..🕛) one o’clock..twelve o’clock 1F55C..1F567 ; Extended_Pictographic# E0.7 [12] (🕜..🕧) one-thirty..twelve-thirty -1F568..1F56E ; Extended_Pictographic# E0.0 [7] (🕨..🕮) RIGHT SPEAKER..BOOK 1F56F..1F570 ; Extended_Pictographic# E0.7 [2] (🕯️..🕰️) candle..mantelpiece clock -1F571..1F572 ; Extended_Pictographic# E0.0 [2] (🕱..🕲) BLACK SKULL AND CROSSBONES..NO PIRACY 1F573..1F579 ; Extended_Pictographic# E0.7 [7] (🕳️..🕹️) hole..joystick 1F57A ; Extended_Pictographic# E3.0 [1] (🕺) man dancing -1F57B..1F586 ; Extended_Pictographic# E0.0 [12] (🕻..🖆) LEFT HAND TELEPHONE RECEIVER..PEN OVER STAMPED ENVELOPE 1F587 ; Extended_Pictographic# E0.7 [1] (🖇️) linked paperclips -1F588..1F589 ; Extended_Pictographic# E0.0 [2] (🖈..🖉) BLACK PUSHPIN..LOWER LEFT PENCIL 1F58A..1F58D ; Extended_Pictographic# E0.7 [4] (🖊️..🖍️) pen..crayon -1F58E..1F58F ; Extended_Pictographic# E0.0 [2] (🖎..🖏) LEFT WRITING HAND..TURNED OK HAND SIGN 1F590 ; Extended_Pictographic# E0.7 [1] (🖐️) hand with fingers splayed -1F591..1F594 ; Extended_Pictographic# E0.0 [4] (🖑..🖔) REVERSED RAISED HAND WITH FINGERS SPLAYED..REVERSED VICTORY HAND 1F595..1F596 ; Extended_Pictographic# E1.0 [2] (🖕..🖖) middle finger..vulcan salute -1F597..1F5A3 ; Extended_Pictographic# E0.0 [13] (🖗..🖣) WHITE DOWN POINTING LEFT HAND INDEX..BLACK DOWN POINTING BACKHAND INDEX 1F5A4 ; Extended_Pictographic# E3.0 [1] (🖤) black heart 1F5A5 ; Extended_Pictographic# E0.7 [1] (🖥️) desktop computer -1F5A6..1F5A7 ; Extended_Pictographic# E0.0 [2] (🖦..🖧) KEYBOARD AND MOUSE..THREE NETWORKED COMPUTERS 1F5A8 ; Extended_Pictographic# E0.7 [1] (🖨️) printer -1F5A9..1F5B0 ; Extended_Pictographic# E0.0 [8] (🖩..🖰) POCKET CALCULATOR..TWO BUTTON MOUSE 1F5B1..1F5B2 ; Extended_Pictographic# E0.7 [2] (🖱️..🖲️) computer mouse..trackball -1F5B3..1F5BB ; Extended_Pictographic# E0.0 [9] (🖳..🖻) OLD PERSONAL COMPUTER..DOCUMENT WITH PICTURE 1F5BC ; Extended_Pictographic# E0.7 [1] (🖼️) framed picture -1F5BD..1F5C1 ; Extended_Pictographic# E0.0 [5] (🖽..🗁) FRAME WITH TILES..OPEN FOLDER 1F5C2..1F5C4 ; Extended_Pictographic# E0.7 [3] (🗂️..🗄️) card index dividers..file cabinet -1F5C5..1F5D0 ; Extended_Pictographic# E0.0 [12] (🗅..🗐) EMPTY NOTE..PAGES 1F5D1..1F5D3 ; Extended_Pictographic# E0.7 [3] (🗑️..🗓️) wastebasket..spiral calendar -1F5D4..1F5DB ; Extended_Pictographic# E0.0 [8] (🗔..🗛) DESKTOP WINDOW..DECREASE FONT SIZE SYMBOL 1F5DC..1F5DE ; Extended_Pictographic# E0.7 [3] (🗜️..🗞️) clamp..rolled-up newspaper -1F5DF..1F5E0 ; Extended_Pictographic# E0.0 [2] (🗟..🗠) PAGE WITH CIRCLED TEXT..STOCK CHART 1F5E1 ; Extended_Pictographic# E0.7 [1] (🗡️) dagger -1F5E2 ; Extended_Pictographic# E0.0 [1] (🗢) LIPS 1F5E3 ; Extended_Pictographic# E0.7 [1] (🗣️) speaking head -1F5E4..1F5E7 ; Extended_Pictographic# E0.0 [4] (🗤..🗧) THREE RAYS ABOVE..THREE RAYS RIGHT 1F5E8 ; Extended_Pictographic# E2.0 [1] (🗨️) left speech bubble -1F5E9..1F5EE ; Extended_Pictographic# E0.0 [6] (🗩..🗮) RIGHT SPEECH BUBBLE..LEFT ANGER BUBBLE 1F5EF ; Extended_Pictographic# E0.7 [1] (🗯️) right anger bubble -1F5F0..1F5F2 ; Extended_Pictographic# E0.0 [3] (🗰..🗲) MOOD BUBBLE..LIGHTNING MOOD 1F5F3 ; Extended_Pictographic# E0.7 [1] (🗳️) ballot box with ballot -1F5F4..1F5F9 ; Extended_Pictographic# E0.0 [6] (🗴..🗹) BALLOT SCRIPT X..BALLOT BOX WITH BOLD CHECK 1F5FA ; Extended_Pictographic# E0.7 [1] (🗺️) world map 1F5FB..1F5FF ; Extended_Pictographic# E0.6 [5] (🗻..🗿) mount fuji..moai 1F600 ; Extended_Pictographic# E1.0 [1] (😀) grinning face @@ -1210,26 +1152,22 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1F6BF ; Extended_Pictographic# E1.0 [1] (🚿) shower 1F6C0 ; Extended_Pictographic# E0.6 [1] (🛀) person taking bath 1F6C1..1F6C5 ; Extended_Pictographic# E1.0 [5] (🛁..🛅) bathtub..left luggage -1F6C6..1F6CA ; Extended_Pictographic# E0.0 [5] (🛆..🛊) TRIANGLE WITH ROUNDED CORNERS..GIRLS SYMBOL 1F6CB ; Extended_Pictographic# E0.7 [1] (🛋️) couch and lamp 1F6CC ; Extended_Pictographic# E1.0 [1] (🛌) person in bed 1F6CD..1F6CF ; Extended_Pictographic# E0.7 [3] (🛍️..🛏️) shopping bags..bed 1F6D0 ; Extended_Pictographic# E1.0 [1] (🛐) place of worship 1F6D1..1F6D2 ; Extended_Pictographic# E3.0 [2] (🛑..🛒) stop sign..shopping cart -1F6D3..1F6D4 ; Extended_Pictographic# E0.0 [2] (🛓..🛔) STUPA..PAGODA 1F6D5 ; Extended_Pictographic# E12.0 [1] (🛕) hindu temple 1F6D6..1F6D7 ; Extended_Pictographic# E13.0 [2] (🛖..🛗) hut..elevator -1F6D8..1F6DB ; Extended_Pictographic# E0.0 [4] (🛘..🛛) .. +1F6D8 ; Extended_Pictographic# E17.0 [1] (🛘) landslide +1F6D9..1F6DB ; Extended_Pictographic# E0.0 [3] (🛙..🛛) .. 1F6DC ; Extended_Pictographic# E15.0 [1] (🛜) wireless 1F6DD..1F6DF ; Extended_Pictographic# E14.0 [3] (🛝..🛟) playground slide..ring buoy 1F6E0..1F6E5 ; Extended_Pictographic# E0.7 [6] (🛠️..🛥️) hammer and wrench..motor boat -1F6E6..1F6E8 ; Extended_Pictographic# E0.0 [3] (🛦..🛨) UP-POINTING MILITARY AIRPLANE..UP-POINTING SMALL AIRPLANE 1F6E9 ; Extended_Pictographic# E0.7 [1] (🛩️) small airplane -1F6EA ; Extended_Pictographic# E0.0 [1] (🛪) NORTHEAST-POINTING AIRPLANE 1F6EB..1F6EC ; Extended_Pictographic# E1.0 [2] (🛫..🛬) airplane departure..airplane arrival 1F6ED..1F6EF ; Extended_Pictographic# E0.0 [3] (🛭..🛯) .. 1F6F0 ; Extended_Pictographic# E0.7 [1] (🛰️) satellite -1F6F1..1F6F2 ; Extended_Pictographic# E0.0 [2] (🛱..🛲) ONCOMING FIRE ENGINE..DIESEL LOCOMOTIVE 1F6F3 ; Extended_Pictographic# E0.7 [1] (🛳️) passenger ship 1F6F4..1F6F6 ; Extended_Pictographic# E3.0 [3] (🛴..🛶) kick scooter..canoe 1F6F7..1F6F8 ; Extended_Pictographic# E5.0 [2] (🛷..🛸) sled..flying saucer @@ -1237,8 +1175,7 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1F6FA ; Extended_Pictographic# E12.0 [1] (🛺) auto rickshaw 1F6FB..1F6FC ; Extended_Pictographic# E13.0 [2] (🛻..🛼) pickup truck..roller skate 1F6FD..1F6FF ; Extended_Pictographic# E0.0 [3] (🛽..🛿) .. -1F774..1F77F ; Extended_Pictographic# E0.0 [12] (🝴..🝿) LOT OF FORTUNE..ORCUS -1F7D5..1F7DF ; Extended_Pictographic# E0.0 [11] (🟕..🟟) CIRCLED TRIANGLE.. +1F7DA..1F7DF ; Extended_Pictographic# E0.0 [6] (🟚..🟟) .. 1F7E0..1F7EB ; Extended_Pictographic# E12.0 [12] (🟠..🟫) orange circle..brown square 1F7EC..1F7EF ; Extended_Pictographic# E0.0 [4] (🟬..🟯) .. 1F7F0 ; Extended_Pictographic# E14.0 [1] (🟰) heavy equals sign @@ -1247,7 +1184,10 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1F848..1F84F ; Extended_Pictographic# E0.0 [8] (🡈..🡏) .. 1F85A..1F85F ; Extended_Pictographic# E0.0 [6] (🡚..🡟) .. 1F888..1F88F ; Extended_Pictographic# E0.0 [8] (🢈..🢏) .. -1F8AE..1F8FF ; Extended_Pictographic# E0.0 [82] (🢮..🣿) .. +1F8AE..1F8AF ; Extended_Pictographic# E0.0 [2] (🢮..🢯) .. +1F8BC..1F8BF ; Extended_Pictographic# E0.0 [4] (🢼..🢿) .. +1F8C2..1F8CF ; Extended_Pictographic# E0.0 [14] (🣂..🣏) .. +1F8D9..1F8FF ; Extended_Pictographic# E0.0 [39] (🣙..🣿) .. 1F90C ; Extended_Pictographic# E13.0 [1] (🤌) pinched fingers 1F90D..1F90F ; Extended_Pictographic# E12.0 [3] (🤍..🤏) white heart..pinching hand 1F910..1F918 ; Extended_Pictographic# E1.0 [9] (🤐..🤘) zipper-mouth face..sign of the horns @@ -1293,7 +1233,8 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1F9CD..1F9CF ; Extended_Pictographic# E12.0 [3] (🧍..🧏) person standing..deaf person 1F9D0..1F9E6 ; Extended_Pictographic# E5.0 [23] (🧐..🧦) face with monocle..socks 1F9E7..1F9FF ; Extended_Pictographic# E11.0 [25] (🧧..🧿) red envelope..nazar amulet -1FA00..1FA6F ; Extended_Pictographic# E0.0 [112] (🨀..🩯) NEUTRAL CHESS KING.. +1FA58..1FA5F ; Extended_Pictographic# E0.0 [8] (🩘..🩟) .. +1FA6E..1FA6F ; Extended_Pictographic# E0.0 [2] (🩮..🩯) .. 1FA70..1FA73 ; Extended_Pictographic# E12.0 [4] (🩰..🩳) ballet shoes..shorts 1FA74 ; Extended_Pictographic# E13.0 [1] (🩴) thong sandal 1FA75..1FA77 ; Extended_Pictographic# E15.0 [3] (🩵..🩷) light blue heart..pink heart @@ -1304,7 +1245,9 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1FA83..1FA86 ; Extended_Pictographic# E13.0 [4] (🪃..🪆) boomerang..nesting dolls 1FA87..1FA88 ; Extended_Pictographic# E15.0 [2] (🪇..🪈) maracas..flute 1FA89 ; Extended_Pictographic# E16.0 [1] (🪉) harp -1FA8A..1FA8E ; Extended_Pictographic# E0.0 [5] (🪊..🪎) .. +1FA8A ; Extended_Pictographic# E17.0 [1] (🪊) trombone +1FA8B..1FA8D ; Extended_Pictographic# E0.0 [3] (🪋..🪍) .. +1FA8E ; Extended_Pictographic# E17.0 [1] (🪎) treasure chest 1FA8F ; Extended_Pictographic# E16.0 [1] (🪏) shovel 1FA90..1FA95 ; Extended_Pictographic# E12.0 [6] (🪐..🪕) ringed planet..banjo 1FA96..1FAA8 ; Extended_Pictographic# E13.0 [19] (🪖..🪨) military helmet..rock @@ -1318,7 +1261,10 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1FAC0..1FAC2 ; Extended_Pictographic# E13.0 [3] (🫀..🫂) anatomical heart..people hugging 1FAC3..1FAC5 ; Extended_Pictographic# E14.0 [3] (🫃..🫅) pregnant man..person with crown 1FAC6 ; Extended_Pictographic# E16.0 [1] (🫆) fingerprint -1FAC7..1FACD ; Extended_Pictographic# E0.0 [7] (🫇..🫍) .. +1FAC7 ; Extended_Pictographic# E0.0 [1] (🫇) +1FAC8 ; Extended_Pictographic# E17.0 [1] (🫈) hairy creature +1FAC9..1FACC ; Extended_Pictographic# E0.0 [4] (🫉..🫌) .. +1FACD ; Extended_Pictographic# E17.0 [1] (🫍) orca 1FACE..1FACF ; Extended_Pictographic# E15.0 [2] (🫎..🫏) moose..donkey 1FAD0..1FAD6 ; Extended_Pictographic# E13.0 [7] (🫐..🫖) blueberries..teapot 1FAD7..1FAD9 ; Extended_Pictographic# E14.0 [3] (🫗..🫙) pouring liquid..jar @@ -1329,12 +1275,14 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1FAE0..1FAE7 ; Extended_Pictographic# E14.0 [8] (🫠..🫧) melting face..bubbles 1FAE8 ; Extended_Pictographic# E15.0 [1] (🫨) shaking face 1FAE9 ; Extended_Pictographic# E16.0 [1] (🫩) face with bags under eyes -1FAEA..1FAEF ; Extended_Pictographic# E0.0 [6] (🫪..🫯) .. +1FAEA ; Extended_Pictographic# E17.0 [1] (🫪) distorted face +1FAEB..1FAEE ; Extended_Pictographic# E0.0 [4] (🫫..🫮) .. +1FAEF ; Extended_Pictographic# E17.0 [1] (🫯) fight cloud 1FAF0..1FAF6 ; Extended_Pictographic# E14.0 [7] (🫰..🫶) hand with index finger and thumb crossed..heart hands 1FAF7..1FAF8 ; Extended_Pictographic# E15.0 [2] (🫷..🫸) leftwards pushing hand..rightwards pushing hand 1FAF9..1FAFF ; Extended_Pictographic# E0.0 [7] (🫹..🫿) .. 1FC00..1FFFD ; Extended_Pictographic# E0.0[1022] (🰀..🿽) .. -# Total elements: 3537 +# Total elements: 2848 #EOF diff --git a/src/java.base/share/legal/icu.md b/src/java.base/share/legal/icu.md index e27193e10be..634fea70d92 100644 --- a/src/java.base/share/legal/icu.md +++ b/src/java.base/share/legal/icu.md @@ -1,4 +1,4 @@ -## International Components for Unicode (ICU4J) v76.1 +## International Components for Unicode (ICU4J) v78.1 ### ICU4J License ``` @@ -6,7 +6,7 @@ UNICODE LICENSE V3 COPYRIGHT AND PERMISSION NOTICE -Copyright © 2016-2024 Unicode, Inc. +Copyright © 2016-2025 Unicode, Inc. NOTICE TO USER: Carefully read the following legal agreement. BY DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR diff --git a/src/java.base/share/legal/unicode.md b/src/java.base/share/legal/unicode.md index 8bd2ed8bd13..a1009c70c1c 100644 --- a/src/java.base/share/legal/unicode.md +++ b/src/java.base/share/legal/unicode.md @@ -1,4 +1,4 @@ -## The Unicode Standard, Unicode Character Database, Version 16.0.0 +## The Unicode Standard, Unicode Character Database, Version 17.0.0 ### Unicode Character Database ``` @@ -7,7 +7,7 @@ UNICODE LICENSE V3 COPYRIGHT AND PERMISSION NOTICE -Copyright © 1991-2024 Unicode, Inc. +Copyright © 1991-2025 Unicode, Inc. NOTICE TO USER: Carefully read the following legal agreement. BY DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR From 27a38d9093958ae4851bc61b8d3f0d71dc780823 Mon Sep 17 00:00:00 2001 From: Chad Rakoczy Date: Tue, 18 Nov 2025 20:28:33 +0000 Subject: [PATCH 116/418] 8371121: compiler/whitebox/DeoptimizeRelocatedNMethod.java fails with C1 Reviewed-by: thartmann, chagedorn --- .../whitebox/DeoptimizeRelocatedNMethod.java | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/test/hotspot/jtreg/compiler/whitebox/DeoptimizeRelocatedNMethod.java b/test/hotspot/jtreg/compiler/whitebox/DeoptimizeRelocatedNMethod.java index 42f29044e8c..c7cf355259b 100644 --- a/test/hotspot/jtreg/compiler/whitebox/DeoptimizeRelocatedNMethod.java +++ b/test/hotspot/jtreg/compiler/whitebox/DeoptimizeRelocatedNMethod.java @@ -28,6 +28,8 @@ * @library /test/lib / * @modules java.base/jdk.internal.misc java.management * @requires vm.opt.DeoptimizeALot != true + * @requires vm.flavor == "server" & (vm.opt.TieredStopAtLevel == null | vm.opt.TieredStopAtLevel == 4) + * @requires !vm.emulatedClient * @build jdk.test.whitebox.WhiteBox * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbatch -XX:+SegmentedCodeCache @@ -55,8 +57,8 @@ public class DeoptimizeRelocatedNMethod { // Verify not initially compiled CompilerWhiteBoxTest.checkNotCompiled(method, false); - // Call function enough to compile - callFunction(); + // Enqueue method for compilation. This will block since background compilation is disabled + WHITE_BOX.enqueueMethodForCompilation(method, CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION); // Verify now compiled CompilerWhiteBoxTest.checkCompiled(method, false); @@ -91,13 +93,6 @@ public class DeoptimizeRelocatedNMethod { function(); } - // Call function multiple times to trigger compilation - private static void callFunction() { - for (int i = 0; i < CompilerWhiteBoxTest.THRESHOLD; i++) { - function(); - } - } - public static void function() { FUNCTION_RESULT = Math.random(); } From 66fb015267058f9b5e6788eaeaa758be56ba553e Mon Sep 17 00:00:00 2001 From: Jan Kratochvil Date: Tue, 18 Nov 2025 21:51:28 +0000 Subject: [PATCH 117/418] 8357579: Compilation error: first argument in call to 'memset' is a pointer to non-trivially copyable type Co-authored-by: Ioi Lam Reviewed-by: iklam, asmehra --- src/hotspot/share/oops/resolvedFieldEntry.cpp | 13 +++- src/hotspot/share/oops/resolvedFieldEntry.hpp | 60 ++++--------------- .../share/oops/resolvedMethodEntry.cpp | 17 ++++-- .../share/oops/resolvedMethodEntry.hpp | 51 +++++++--------- 4 files changed, 52 insertions(+), 89 deletions(-) diff --git a/src/hotspot/share/oops/resolvedFieldEntry.cpp b/src/hotspot/share/oops/resolvedFieldEntry.cpp index dd0a81ce0f3..83f1a6919a6 100644 --- a/src/hotspot/share/oops/resolvedFieldEntry.cpp +++ b/src/hotspot/share/oops/resolvedFieldEntry.cpp @@ -23,8 +23,17 @@ */ #include "cds/archiveBuilder.hpp" +#include "cppstdlib/type_traits.hpp" #include "oops/resolvedFieldEntry.hpp" +static_assert(std::is_trivially_copyable_v); + +// Detect inadvertently introduced trailing padding. +class ResolvedFieldEntryWithExtra : public ResolvedFieldEntry { + u1 _extra_field; +}; +static_assert(sizeof(ResolvedFieldEntryWithExtra) > sizeof(ResolvedFieldEntry)); + void ResolvedFieldEntry::print_on(outputStream* st) const { st->print_cr("Field Entry:"); @@ -45,9 +54,7 @@ void ResolvedFieldEntry::print_on(outputStream* st) const { #if INCLUDE_CDS void ResolvedFieldEntry::remove_unshareable_info() { - u2 saved_cpool_index = _cpool_index; - memset(this, 0, sizeof(*this)); - _cpool_index = saved_cpool_index; + *this = ResolvedFieldEntry(_cpool_index); } void ResolvedFieldEntry::mark_and_relocate() { diff --git a/src/hotspot/share/oops/resolvedFieldEntry.hpp b/src/hotspot/share/oops/resolvedFieldEntry.hpp index 1df4ae8d956..77ad4815730 100644 --- a/src/hotspot/share/oops/resolvedFieldEntry.hpp +++ b/src/hotspot/share/oops/resolvedFieldEntry.hpp @@ -43,6 +43,9 @@ // Field bytecodes start with a constant pool index as their operand, which is then rewritten to // a "field index", which is an index into the array of ResolvedFieldEntry. +// The explicit paddings are necessary for generating deterministic CDS archives. They prevent +// the C++ compiler from potentially inserting random values in unused gaps. + //class InstanceKlass; class ResolvedFieldEntry { friend class VMStructs; @@ -54,17 +57,9 @@ class ResolvedFieldEntry { u1 _tos_state; // TOS state u1 _flags; // Flags: [0000|00|is_final|is_volatile] u1 _get_code, _put_code; // Get and Put bytecodes of the field - - void copy_from(const ResolvedFieldEntry& other) { - _field_holder = other._field_holder; - _field_offset = other._field_offset; - _field_index = other._field_index; - _cpool_index = other._cpool_index; - _tos_state = other._tos_state; - _flags = other._flags; - _get_code = other._get_code; - _put_code = other._put_code; - } +#ifdef _LP64 + u4 _padding; +#endif public: ResolvedFieldEntry(u2 cpi) : @@ -75,48 +70,15 @@ public: _tos_state(0), _flags(0), _get_code(0), - _put_code(0) {} + _put_code(0) +#ifdef _LP64 + , _padding(0) +#endif + {} ResolvedFieldEntry() : ResolvedFieldEntry(0) {} - // Notes on copy constructor, copy assignment operator, and copy_from(). - // These are necessary for generating deterministic CDS archives. - // - // We have some unused padding on 64-bit platforms (4 bytes at the tail end). - // - // When ResolvedFieldEntries in a ConstantPoolCache are allocated from the metaspace, - // their entire content (including the padding) is filled with zeros. They are - // then initialized with initialize_resolved_entries_array() in cpCache.cpp from a - // GrowableArray. - // - // The GrowableArray is initialized in rewriter.cpp, using ResolvedFieldEntries that - // are originally allocated from the C++ stack. Functions like GrowableArray::expand_to() - // will also allocate ResolvedFieldEntries from the stack. These may have random bits - // in the padding as the C++ compiler is allowed to leave the padding in uninitialized - // states. - // - // If we use the default copy constructor and/or default copy assignment operator, - // the random padding will be copied into the GrowableArray, from there - // to the ConstantPoolCache, and eventually to the CDS archive. As a result, the - // CDS archive will contain random bits, causing failures in - // test/hotspot/jtreg/runtime/cds/DeterministicDump.java (usually on Windows). - // - // By using copy_from(), we can prevent the random padding from being copied, - // ensuring that the ResolvedFieldEntries in a ConstantPoolCache (and thus the - // CDS archive) will have all zeros in the padding. - - // Copy constructor - ResolvedFieldEntry(const ResolvedFieldEntry& other) { - copy_from(other); - } - - // Copy assignment operator - ResolvedFieldEntry& operator=(const ResolvedFieldEntry& other) { - copy_from(other); - return *this; - } - // Bit shift to get flags // Note: Only two flags exists at the moment but more could be added enum { diff --git a/src/hotspot/share/oops/resolvedMethodEntry.cpp b/src/hotspot/share/oops/resolvedMethodEntry.cpp index 2dc533dbee0..bb96ca86012 100644 --- a/src/hotspot/share/oops/resolvedMethodEntry.cpp +++ b/src/hotspot/share/oops/resolvedMethodEntry.cpp @@ -23,9 +23,18 @@ */ #include "cds/archiveBuilder.hpp" +#include "cppstdlib/type_traits.hpp" #include "oops/method.hpp" #include "oops/resolvedMethodEntry.hpp" +static_assert(std::is_trivially_copyable_v); + +// Detect inadvertently introduced trailing padding. +class ResolvedMethodEntryWithExtra : public ResolvedMethodEntry { + u1 _extra_field; +}; +static_assert(sizeof(ResolvedMethodEntryWithExtra) > sizeof(ResolvedMethodEntry)); + bool ResolvedMethodEntry::check_no_old_or_obsolete_entry() { // return false if m refers to a non-deleted old or obsolete method if (_method != nullptr) { @@ -39,14 +48,10 @@ bool ResolvedMethodEntry::check_no_old_or_obsolete_entry() { void ResolvedMethodEntry::reset_entry() { if (has_resolved_references_index()) { u2 saved_resolved_references_index = _entry_specific._resolved_references_index; - u2 saved_cpool_index = _cpool_index; - memset(this, 0, sizeof(*this)); + *this = ResolvedMethodEntry(_cpool_index); set_resolved_references_index(saved_resolved_references_index); - _cpool_index = saved_cpool_index; } else { - u2 saved_cpool_index = _cpool_index; - memset(this, 0, sizeof(*this)); - _cpool_index = saved_cpool_index; + *this = ResolvedMethodEntry(_cpool_index); } } diff --git a/src/hotspot/share/oops/resolvedMethodEntry.hpp b/src/hotspot/share/oops/resolvedMethodEntry.hpp index c95efb751e9..802cf252a6d 100644 --- a/src/hotspot/share/oops/resolvedMethodEntry.hpp +++ b/src/hotspot/share/oops/resolvedMethodEntry.hpp @@ -61,6 +61,9 @@ // pool entry and thus the same resolved method entry. // The is_vfinal flag indicates method pointer for a final method or an index. +// The explicit paddings are necessary for generating deterministic CDS archives. They prevent +// the C++ compiler from potentially inserting random values in unused gaps. + class InstanceKlass; class ResolvedMethodEntry { friend class VMStructs; @@ -70,6 +73,7 @@ class ResolvedMethodEntry { InstanceKlass* _interface_klass; // for interface and static u2 _resolved_references_index; // Index of resolved references array that holds the appendix oop for invokehandle u2 _table_index; // vtable/itable index for virtual and interface calls + // The padding field is unused here, as the parent constructor zeroes the union. } _entry_specific; u2 _cpool_index; // Constant pool index @@ -80,51 +84,36 @@ class ResolvedMethodEntry { #ifdef ASSERT bool _has_interface_klass; bool _has_table_index; +# ifdef _LP64 + u2 _padding1; + u4 _padding2; +# else + u1 _padding1; + u1 _padding2; +# endif #endif - // See comments in resolvedFieldEntry.hpp about copy_from and padding. - // We have unused padding on debug builds. - void copy_from(const ResolvedMethodEntry& other) { - _method = other._method; - _entry_specific = other._entry_specific; - _cpool_index = other._cpool_index; - _number_of_parameters = other._number_of_parameters; - _tos_state = other._tos_state; - _flags = other._flags; - _bytecode1 = other._bytecode1; - _bytecode2 = other._bytecode2; -#ifdef ASSERT - _has_interface_klass = other._has_interface_klass; - _has_table_index = other._has_table_index; -#endif - } - // Constructors public: ResolvedMethodEntry(u2 cpi) : _method(nullptr), + _entry_specific{nullptr}, _cpool_index(cpi), _number_of_parameters(0), _tos_state(0), _flags(0), _bytecode1(0), - _bytecode2(0) { - _entry_specific._interface_klass = nullptr; - DEBUG_ONLY(_has_interface_klass = false;) - DEBUG_ONLY(_has_table_index = false;) - } + _bytecode2(0) +#ifdef ASSERT + , _has_interface_klass(false), + _has_table_index(false), + _padding1(0), + _padding2(0) +#endif + {} ResolvedMethodEntry() : ResolvedMethodEntry(0) {} - ResolvedMethodEntry(const ResolvedMethodEntry& other) { - copy_from(other); - } - - ResolvedMethodEntry& operator=(const ResolvedMethodEntry& other) { - copy_from(other); - return *this; - } - // Bit shift to get flags enum { From b086e34f7170631d7568dc50a7c075dc9c2f173b Mon Sep 17 00:00:00 2001 From: Ioi Lam Date: Tue, 18 Nov 2025 21:51:54 +0000 Subject: [PATCH 118/418] 8371771: CDS test SharedStringsStress.java failed with insufficient heap Reviewed-by: kvn --- .../runtime/cds/appcds/sharedStrings/SharedStringsStress.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/hotspot/jtreg/runtime/cds/appcds/sharedStrings/SharedStringsStress.java b/test/hotspot/jtreg/runtime/cds/appcds/sharedStrings/SharedStringsStress.java index c51a67c445b..4d176c949c6 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/sharedStrings/SharedStringsStress.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/sharedStrings/SharedStringsStress.java @@ -83,8 +83,10 @@ public class SharedStringsStress { dumpOutput.shouldContain("string table array (primary)"); dumpOutput.shouldContain("string table array (secondary)"); + // We could create up to 26MB of archived heap objects. Run with enough Xms to ensure + // SerialGC can accommodate the archived objects during VM start up. OutputAnalyzer execOutput = TestCommon.exec(appJar, - TestCommon.concat(vmOptionsPrefix, "-Xlog:aot,cds", "HelloString")); + TestCommon.concat(vmOptionsPrefix, "-Xlog:aot,cds", "-Xms128m", "HelloString")); TestCommon.checkExec(execOutput); } } From 256a9beffc106d6657a912a33f97e7f97acbb1e1 Mon Sep 17 00:00:00 2001 From: Vladimir Ivanov Date: Tue, 18 Nov 2025 22:29:37 +0000 Subject: [PATCH 119/418] 8280469: C2: CHA support for interface calls when inlining through method handle linker Reviewed-by: kvn, roland --- src/hotspot/share/ci/ciInstanceKlass.cpp | 3 +- src/hotspot/share/ci/ciInstanceKlass.hpp | 1 + src/hotspot/share/opto/doCall.cpp | 27 ++- .../cha/StrengthReduceInterfaceCall.java | 205 ++++++++++++++++-- 4 files changed, 204 insertions(+), 32 deletions(-) diff --git a/src/hotspot/share/ci/ciInstanceKlass.cpp b/src/hotspot/share/ci/ciInstanceKlass.cpp index 9bbf005356c..64b9acf9146 100644 --- a/src/hotspot/share/ci/ciInstanceKlass.cpp +++ b/src/hotspot/share/ci/ciInstanceKlass.cpp @@ -605,7 +605,7 @@ bool ciInstanceKlass::is_leaf_type() { if (is_shared()) { return is_final(); // approximately correct } else { - return !has_subklass() && (nof_implementors() == 0); + return !has_subklass() && (!is_interface() || nof_implementors() == 0); } } @@ -619,6 +619,7 @@ bool ciInstanceKlass::is_leaf_type() { // This is OK, since any dependencies we decide to assert // will be checked later under the Compile_lock. ciInstanceKlass* ciInstanceKlass::implementor() { + assert(is_interface(), "required"); ciInstanceKlass* impl = _implementor; if (impl == nullptr) { if (is_shared()) { diff --git a/src/hotspot/share/ci/ciInstanceKlass.hpp b/src/hotspot/share/ci/ciInstanceKlass.hpp index ec8fc789c7d..1f887771f54 100644 --- a/src/hotspot/share/ci/ciInstanceKlass.hpp +++ b/src/hotspot/share/ci/ciInstanceKlass.hpp @@ -259,6 +259,7 @@ public: ciInstanceKlass* unique_implementor() { assert(is_loaded(), "must be loaded"); + assert(is_interface(), "must be"); ciInstanceKlass* impl = implementor(); return (impl != this ? impl : nullptr); } diff --git a/src/hotspot/share/opto/doCall.cpp b/src/hotspot/share/opto/doCall.cpp index 754b0fa8d1c..91bb743618b 100644 --- a/src/hotspot/share/opto/doCall.cpp +++ b/src/hotspot/share/opto/doCall.cpp @@ -97,10 +97,9 @@ CallGenerator* Compile::call_generator(ciMethod* callee, int vtable_index, bool Bytecodes::Code bytecode = caller->java_code_at_bci(bci); ciMethod* orig_callee = caller->get_method_at_bci(bci); - const bool is_virtual_or_interface = (bytecode == Bytecodes::_invokevirtual) || - (bytecode == Bytecodes::_invokeinterface) || - (orig_callee->intrinsic_id() == vmIntrinsics::_linkToVirtual) || - (orig_callee->intrinsic_id() == vmIntrinsics::_linkToInterface); + const bool is_virtual = (bytecode == Bytecodes::_invokevirtual) || (orig_callee->intrinsic_id() == vmIntrinsics::_linkToVirtual); + const bool is_interface = (bytecode == Bytecodes::_invokeinterface) || (orig_callee->intrinsic_id() == vmIntrinsics::_linkToInterface); + const bool is_virtual_or_interface = is_virtual || is_interface; const bool check_access = !orig_callee->is_method_handle_intrinsic(); // method handle intrinsics don't perform access checks @@ -339,17 +338,25 @@ CallGenerator* Compile::call_generator(ciMethod* callee, int vtable_index, bool // number of implementors for decl_interface is 0 or 1. If // it's 0 then no class implements decl_interface and there's // no point in inlining. - if (call_does_dispatch && bytecode == Bytecodes::_invokeinterface) { - ciInstanceKlass* declared_interface = - caller->get_declared_method_holder_at_bci(bci)->as_instance_klass(); + if (call_does_dispatch && is_interface) { + ciInstanceKlass* declared_interface = nullptr; + if (orig_callee->intrinsic_id() == vmIntrinsics::_linkToInterface) { + // MemberName doesn't keep information about resolved interface class (REFC) once + // resolution is over, but resolved method holder (DECC) can be used as a + // conservative approximation. + declared_interface = callee->holder(); + } else { + assert(!orig_callee->is_method_handle_intrinsic(), "not allowed"); + declared_interface = caller->get_declared_method_holder_at_bci(bci)->as_instance_klass(); + } + assert(declared_interface->is_interface(), "required"); ciInstanceKlass* singleton = declared_interface->unique_implementor(); if (singleton != nullptr) { assert(singleton != declared_interface, "not a unique implementor"); - assert(check_access, "required"); ciMethod* cha_monomorphic_target = - callee->find_monomorphic_target(caller->holder(), declared_interface, singleton); + callee->find_monomorphic_target(caller->holder(), declared_interface, singleton, check_access); if (cha_monomorphic_target != nullptr && cha_monomorphic_target->holder() != env()->Object_klass()) { // subtype check against Object is useless @@ -372,7 +379,7 @@ CallGenerator* Compile::call_generator(ciMethod* callee, int vtable_index, bool } } } - } // call_does_dispatch && bytecode == Bytecodes::_invokeinterface + } // call_does_dispatch && is_interface // Nothing claimed the intrinsic, we go with straight-forward inlining // for already discovered intrinsic. diff --git a/test/hotspot/jtreg/compiler/cha/StrengthReduceInterfaceCall.java b/test/hotspot/jtreg/compiler/cha/StrengthReduceInterfaceCall.java index aa014cfa63d..5e2dc2e3d56 100644 --- a/test/hotspot/jtreg/compiler/cha/StrengthReduceInterfaceCall.java +++ b/test/hotspot/jtreg/compiler/cha/StrengthReduceInterfaceCall.java @@ -24,6 +24,7 @@ /* * @test * @requires !vm.graal.enabled + * @requires vm.opt.StressMethodHandleLinkerInlining == null | !vm.opt.StressMethodHandleLinkerInlining * @requires vm.opt.StressUnstableIfTraps == null | !vm.opt.StressUnstableIfTraps * @modules java.base/jdk.internal.misc * java.base/jdk.internal.vm.annotation @@ -55,6 +56,9 @@ package compiler.cha; import jdk.internal.vm.annotation.DontInline; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; + import static compiler.cha.Utils.*; public class StrengthReduceInterfaceCall { @@ -66,6 +70,18 @@ public class StrengthReduceInterfaceCall { run(ThreeLevelHierarchyAbstractVsDefault.class); run(ThreeLevelDefaultHierarchy.class); run(ThreeLevelDefaultHierarchy1.class); + + // Implementation limitation: CHA is not performed by C1 during inlining through MH linkers. + if (!jdk.test.whitebox.code.Compiler.isC1Enabled()) { + run(ObjectToString.TestMH.class, ObjectToString.class); + run(ObjectHashCode.TestMH.class, ObjectHashCode.class); + run(TwoLevelHierarchyLinear.TestMH.class, TwoLevelHierarchyLinear.class); + run(ThreeLevelHierarchyLinear.TestMH.class, ThreeLevelHierarchyLinear.class); + run(ThreeLevelHierarchyAbstractVsDefault.TestMH.class, ThreeLevelHierarchyAbstractVsDefault.class); + run(ThreeLevelDefaultHierarchy.TestMH.class, ThreeLevelDefaultHierarchy.class); + run(ThreeLevelDefaultHierarchy1.TestMH.class, ThreeLevelDefaultHierarchy1.class); + } + System.out.println("TEST PASSED"); } @@ -87,7 +103,7 @@ public class StrengthReduceInterfaceCall { static class DJ2 implements J { public String toString() { return "DJ2"; }} @Override - public Object test(I i) { return ObjectToStringHelper.testHelper(i); /* invokeinterface I.toString() */ } + public Object test(I i) throws Throwable { return ObjectToStringHelper.testHelper(i); /* invokeinterface I.toString() */ } @TestCase public void testMono() { @@ -155,6 +171,20 @@ public class StrengthReduceInterfaceCall { }); assertCompiled(); } + + public static class TestMH extends ObjectToString { + static final MethodHandle TEST_MH = findVirtualHelper(I.class, "toString", String.class, MethodHandles.lookup()); + + @Override + public void checkInvalidReceiver() { + // receiver type check failures trigger nmethod invalidation + } + + @Override + public Object test(I obj) throws Throwable { + return (String)TEST_MH.invokeExact(obj); // invokeinterface I.toString() + } + } } public static class ObjectHashCode extends ATest { @@ -175,7 +205,7 @@ public class StrengthReduceInterfaceCall { static class DJ2 implements J { public int hashCode() { return super.hashCode(); }} @Override - public Object test(I i) { + public Object test(I i) throws Throwable { return ObjectHashCodeHelper.testHelper(i); /* invokeinterface I.hashCode() */ } @@ -242,6 +272,20 @@ public class StrengthReduceInterfaceCall { }); assertCompiled(); } + + public static class TestMH extends ObjectHashCode { + static final MethodHandle TEST_MH = findVirtualHelper(I.class, "hashCode", int.class, MethodHandles.lookup()); + + @Override + public void checkInvalidReceiver() { + // receiver type check failures trigger nmethod invalidation + } + + @Override + public Object test(I obj) throws Throwable { + return (int)TEST_MH.invokeExact(obj); // invokeinterface I.hashCode() + } + } } public static class TwoLevelHierarchyLinear extends ATest { @@ -263,7 +307,7 @@ public class StrengthReduceInterfaceCall { static class DJ2 implements J { public Object m() { return WRONG; }} @DontInline - public Object test(I i) { + public Object test(I i) throws Throwable { return i.m(); } @@ -366,6 +410,20 @@ public class StrengthReduceInterfaceCall { }); assertCompiled(); } + + public static class TestMH extends TwoLevelHierarchyLinear { + static final MethodHandle TEST_MH = findVirtualHelper(I.class, "m", Object.class, MethodHandles.lookup()); + + @Override + public void checkInvalidReceiver() { + // receiver type check failures trigger nmethod invalidation + } + + @Override + public Object test(I obj) throws Throwable { + return TEST_MH.invokeExact(obj); // invokeinterface I.m() + } + } } public static class ThreeLevelHierarchyLinear extends ATest { @@ -385,7 +443,7 @@ public class StrengthReduceInterfaceCall { static class DJ implements J { public Object m() { return WRONG; }} @DontInline - public Object test(I i) { + public Object test(I i) throws Throwable { return i.m(); // I <: J.m ABSTRACT } @@ -404,10 +462,16 @@ public class StrengthReduceInterfaceCall { assertCompiled(); // No deopt on not-yet-seen receiver // 2. No dependency invalidation: different context - initialize(DJ.class, // DJ.m <: intf J.m ABSTRACT - K1.class, // intf K1 <: intf I <: intf J.m ABSTRACT - K2.class); // intf K2.m ABSTRACT <: intf I <: intf J.m ABSTRACT - assertCompiled(); + if (contextClass() == I.class) { + initialize(DJ.class, // DJ.m <: intf J.m ABSTRACT + K1.class, // intf K1 <: intf I <: intf J.m ABSTRACT + K2.class); // intf K2.m ABSTRACT <: intf I <: intf J.m ABSTRACT + assertCompiled(); + } else if (contextClass() == J.class) { + // no classes to initialize w/o breaking a dependency + } else { + throw new InternalError("unsupported context: " + contextClass()); + } // 3. Dependency invalidation: DI.m <: I initialize(DI.class); // DI.m <: intf I <: intf J.m ABSTRACT @@ -491,6 +555,30 @@ public class StrengthReduceInterfaceCall { }); assertCompiled(); } + + Class contextClass() { + return I.class; + } + + public static class TestMH extends ThreeLevelHierarchyLinear { + static final MethodHandle TEST_MH = findVirtualHelper(I.class, "m", Object.class, MethodHandles.lookup()); + + @Override + public void checkInvalidReceiver() { + // receiver type check failures trigger nmethod invalidation + } + + @Override + Class contextClass() { + return J.class; + } + + @Override + public Object test(I obj) throws Throwable { + return TEST_MH.invokeExact(obj); // invokeinterface I.m() + } + } + } public static class ThreeLevelHierarchyAbstractVsDefault extends ATest { @@ -503,7 +591,7 @@ public class StrengthReduceInterfaceCall { static class C implements I { public Object m() { return CORRECT; }} @DontInline - public Object test(I i) { + public Object test(I i) throws Throwable { return i.m(); // intf I.m OVERPASS } @@ -598,6 +686,20 @@ public class StrengthReduceInterfaceCall { }); assertCompiled(); } + + public static class TestMH extends ThreeLevelHierarchyAbstractVsDefault { + static final MethodHandle TEST_MH = findVirtualHelper(I.class, "m", Object.class, MethodHandles.lookup()); + + @Override + public void checkInvalidReceiver() { + // receiver type check failures trigger nmethod invalidation + } + + @Override + public Object test(I obj) throws Throwable { + return TEST_MH.invokeExact(obj); // invokeinterface I.m() + } + } } public static class ThreeLevelDefaultHierarchy extends ATest { @@ -617,7 +719,7 @@ public class StrengthReduceInterfaceCall { static class DK3 implements K3 {} @DontInline - public Object test(I i) { + public Object test(I i) throws Throwable { return i.m(); } @@ -636,11 +738,17 @@ public class StrengthReduceInterfaceCall { assertCompiled(); // 2. No dependency invalidation - initialize(DJ.class, // DJ.m <: intf J.m ABSTRACT - K1.class, // intf K1 <: intf I <: intf J.m ABSTRACT - K2.class, // intf K2.m ABSTRACT <: intf I <: intf J.m ABSTRACT - DK3.class); // DK3.m <: intf K3.m DEFAULT <: intf J.m ABSTRACT - assertCompiled(); + if (contextClass() == I.class) { + initialize(DJ.class, // DJ.m <: intf J.m ABSTRACT + K1.class, // intf K1 <: intf I <: intf J.m ABSTRACT + K2.class, // intf K2.m ABSTRACT <: intf I <: intf J.m ABSTRACT + DK3.class); // DK3.m <: intf K3.m DEFAULT <: intf J.m ABSTRACT + assertCompiled(); + } else if (contextClass() == J.class) { + // no classes to initialize w/o breaking a dependency + } else { + throw new InternalError("unsupported context: " + contextClass()); + } // 3. Dependency invalidation initialize(DI.class); // DI.m <: intf I <: intf J.m ABSTRACT @@ -666,6 +774,29 @@ public class StrengthReduceInterfaceCall { }); assertCompiled(); } + + Class contextClass() { + return I.class; + } + + public static class TestMH extends ThreeLevelDefaultHierarchy { + static final MethodHandle TEST_MH = findVirtualHelper(I.class, "m", Object.class, MethodHandles.lookup()); + + @Override + public void checkInvalidReceiver() { + // receiver type check failures trigger nmethod invalidation + } + + @Override + Class contextClass() { + return J.class; + } + + @Override + public Object test(I obj) throws Throwable { + return TEST_MH.invokeExact(obj); // invokeinterface I.m() + } + } } public static class ThreeLevelDefaultHierarchy1 extends ATest { @@ -686,7 +817,7 @@ public class StrengthReduceInterfaceCall { static class DJ2 implements J2 { public Object m() { return WRONG; }} @DontInline - public Object test(I i) { + public Object test(I i) throws Throwable { return i.m(); } @@ -705,11 +836,20 @@ public class StrengthReduceInterfaceCall { assertCompiled(); // 2. No dependency invalidation - initialize(DJ1.class, - DJ2.class, - K1.class, - K2.class, - K3.class); + if (contextClass() == I.class) { + initialize(DJ1.class, // DJ1.m <: intf J1 + DJ2.class, // DJ2.m <: intf J2.m ABSTRACT + K1.class, // intf K1 <: intf I <: intf J1, intf J2.m ABSTRACT + K2.class, // intf K2.m ABSTRACT <: intf I <: intf J1, intf J2.m ABSTRACT + K3.class); // intf K3.m DEFAULT <: intf I <: intf J1, intf J2.m ABSTRACT + } else if (contextClass() == J2.class) { + initialize(DJ1.class, // DJ1.m <: intf J1 + K1.class, // intf K1 <: intf I <: intf J1, intf J2.m ABSTRACT + K2.class, // intf K2.m ABSTRACT <: intf I <: intf J1, intf J2.m ABSTRACT + K3.class); // intf K3.m DEFAULT <: intf I <: intf J1, intf J2.m ABSTRACT + } else { + throw new InternalError("unsupported context: " + contextClass()); + } assertCompiled(); // 3. Dependency invalidation @@ -742,5 +882,28 @@ public class StrengthReduceInterfaceCall { }); assertCompiled(); } + + Class contextClass() { + return I.class; + } + + public static class TestMH extends ThreeLevelDefaultHierarchy1 { + static final MethodHandle TEST_MH = findVirtualHelper(I.class, "m", Object.class, MethodHandles.lookup()); + + @Override + public void checkInvalidReceiver() { + // receiver type check failures trigger nmethod invalidation + } + + @Override + Class contextClass() { + return J2.class; + } + + @Override + public Object test(I obj) throws Throwable { + return TEST_MH.invokeExact(obj); // invokeinterface I.m() + } + } } } From aeea8497562aabda12f292ad93c9f0f6935cc842 Mon Sep 17 00:00:00 2001 From: John Engebretson Date: Tue, 18 Nov 2025 23:37:06 +0000 Subject: [PATCH 120/418] 8371164: ArrayList.addAll() optimizations Reviewed-by: smarks, ogillespie --- .../share/classes/java/util/ArrayList.java | 14 +- .../share/classes/java/util/Collections.java | 14 ++ test/jdk/java/util/Collection/MOAT.java | 26 +++- .../java/util/ArrayListBulkOpsBenchmark.java | 128 ++++++++++++++++++ 4 files changed, 178 insertions(+), 4 deletions(-) create mode 100644 test/micro/org/openjdk/bench/java/util/ArrayListBulkOpsBenchmark.java diff --git a/src/java.base/share/classes/java/util/ArrayList.java b/src/java.base/share/classes/java/util/ArrayList.java index c00b130a553..53e818b99c5 100644 --- a/src/java.base/share/classes/java/util/ArrayList.java +++ b/src/java.base/share/classes/java/util/ArrayList.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2025, 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 @@ -750,9 +750,17 @@ public class ArrayList extends AbstractList * @throws NullPointerException if the specified collection is null */ public boolean addAll(Collection c) { - Object[] a = c.toArray(); + Object[] a; + int numNew; + if (c.getClass() == ArrayList.class) { + ArrayList src = (ArrayList) c; + a = src.elementData; + numNew = src.size; + } else { + a = c.toArray(); + numNew = a.length; + } modCount++; - int numNew = a.length; if (numNew == 0) return false; Object[] elementData; diff --git a/src/java.base/share/classes/java/util/Collections.java b/src/java.base/share/classes/java/util/Collections.java index c48dbd8cf6c..316458d6f90 100644 --- a/src/java.base/share/classes/java/util/Collections.java +++ b/src/java.base/share/classes/java/util/Collections.java @@ -5253,6 +5253,20 @@ public final class Collections { public int hashCode() { return Objects.hashCode(element); } + @Override + public Object[] toArray() { + return new Object[] {element}; + } + @Override + @SuppressWarnings("unchecked") + public T[] toArray(T[] a) { + if (a.length < 1) + a = (T[])Array.newInstance(a.getClass().getComponentType(), 1); + a[0] = (T)element; + if (a.length > 1) + a[1] = null; + return a; + } } /** diff --git a/test/jdk/java/util/Collection/MOAT.java b/test/jdk/java/util/Collection/MOAT.java index d0d27c8f91e..687ac9fbd5f 100644 --- a/test/jdk/java/util/Collection/MOAT.java +++ b/test/jdk/java/util/Collection/MOAT.java @@ -26,7 +26,7 @@ * @bug 6207984 6272521 6192552 6269713 6197726 6260652 5073546 4137464 * 4155650 4216399 4294891 6282555 6318622 6355327 6383475 6420753 * 6431845 4802633 6570566 6570575 6570631 6570924 6691185 6691215 - * 4802647 7123424 8024709 8193128 8327858 8368178 + * 4802647 7123424 8024709 8193128 8327858 8368178 8371164 * @summary Run many tests on many Collection and Map implementations * @author Martin Buchholz * @modules java.base/java.util:open @@ -887,6 +887,28 @@ public class MOAT { catch (Throwable t) { unexpected(t); } } + private static void testAddAll(Collection c) { + clear(c); + + // Test ArrayList source + ArrayList arrayListSource = new ArrayList<>(); + arrayListSource.add(42); + arrayListSource.add(99); + check(c.addAll(arrayListSource)); + equal(c.size(), arrayListSource.size()); + check(c.containsAll(arrayListSource)); + + clear(c); + + // Test non-ArrayList source + LinkedList linkedListSource = new LinkedList<>(); + linkedListSource.add(77); + linkedListSource.add(88); + check(c.addAll(linkedListSource)); + equal(c.size(), linkedListSource.size()); + check(c.containsAll(linkedListSource)); + } + private static void testConcurrentCollection(Collection c) { try { c.add(1); @@ -1294,6 +1316,8 @@ public class MOAT { clear(c); testStringElement(c); oneElement(c); testStringElement(c); + testAddAll(c); + if (c.getClass().getName().matches(".*concurrent.*")) testConcurrentCollection(c); diff --git a/test/micro/org/openjdk/bench/java/util/ArrayListBulkOpsBenchmark.java b/test/micro/org/openjdk/bench/java/util/ArrayListBulkOpsBenchmark.java new file mode 100644 index 00000000000..78b902f0c3a --- /dev/null +++ b/test/micro/org/openjdk/bench/java/util/ArrayListBulkOpsBenchmark.java @@ -0,0 +1,128 @@ +/* + * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.java.util; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; +import java.util.WeakHashMap; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + + +/** + * Benchmark measuring ArrayList addAll() performance. + * + * Tests the performance of ArrayList.addAll() when copying from another ArrayList. + */ +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@State(Scope.Benchmark) +@Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@Fork(value = 1, jvmArgs = { "-XX:+UseParallelGC", "-Xmx3g" }) +public class ArrayListBulkOpsBenchmark { + @Param({"0", "1", "5", "75"}) + int size; + + @Param({"ArrayList", "LinkedList"}) + String type; + + List source; + + @Setup(Level.Trial) + public void setup() { + switch (type) { + case "ArrayList" -> source = new ArrayList<>(size); + case "LinkedList" -> source = new LinkedList<>(); + } + for (int i = 0; i < size; i++) source.add("key" + i); + } + + @Benchmark + public ArrayList addAll() { + ArrayList result = new ArrayList<>(size); + result.addAll(source); + return result; + } + + static void poisonCallSites() { + HashMap hashMapSource = new HashMap<>(); + TreeSet treeSetSource = new TreeSet<>(); + WeakHashMap weakHashMapSource = new WeakHashMap<>(); + for (int i = 0; i < 75; i++) { + hashMapSource.put("key" + i, "value" + i); + treeSetSource.add("key" + i); + weakHashMapSource.put("key" + i, "value" + i); + } + // Poison ArrayList.addAll() with different Collection types + for (int i = 0; i < 40_000; i++) { + ArrayList temp = new ArrayList<>(); + temp.addAll(hashMapSource.entrySet()); + temp.addAll(treeSetSource); + temp.addAll(weakHashMapSource.keySet()); + } + } + + @BenchmarkMode(Mode.AverageTime) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + @State(Scope.Benchmark) + @Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS) + @Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) + @Fork(value = 1, jvmArgs = { "-XX:+UseParallelGC", "-Xmx3g" }) + public static class SingletonSet { + Set singletonSetSource = Collections.singleton("key"); + + @Param({ "false", "true" }) + private boolean poison; + + @Setup(Level.Trial) + public void setup() { + if (poison) poisonCallSites(); + } + + @Benchmark + public ArrayList addAllSingletonSet() { + ArrayList result = new ArrayList<>(1); + result.addAll(singletonSetSource); + return result; + } + } +} From 152cd4d8bab7d3428d0330c56a3cb9ed7feef313 Mon Sep 17 00:00:00 2001 From: Kim Barrett Date: Tue, 18 Nov 2025 23:43:22 +0000 Subject: [PATCH 121/418] 8371956: Convert OopStorage to use Atomic Reviewed-by: stefank, tschatzl --- src/hotspot/share/gc/shared/oopStorage.cpp | 157 +++++++++--------- src/hotspot/share/gc/shared/oopStorage.hpp | 11 +- .../share/gc/shared/oopStorage.inline.hpp | 19 ++- .../share/gc/shared/oopStorageParState.hpp | 5 +- .../gtest/gc/shared/test_oopStorage.cpp | 4 +- 5 files changed, 101 insertions(+), 95 deletions(-) diff --git a/src/hotspot/share/gc/shared/oopStorage.cpp b/src/hotspot/share/gc/shared/oopStorage.cpp index d52efc13dac..a1cc3ffa553 100644 --- a/src/hotspot/share/gc/shared/oopStorage.cpp +++ b/src/hotspot/share/gc/shared/oopStorage.cpp @@ -28,7 +28,7 @@ #include "logging/logStream.hpp" #include "memory/allocation.inline.hpp" #include "nmt/memTracker.hpp" -#include "runtime/atomicAccess.hpp" +#include "runtime/atomic.hpp" #include "runtime/globals.hpp" #include "runtime/handles.inline.hpp" #include "runtime/interfaceSupport.inline.hpp" @@ -122,7 +122,7 @@ OopStorage::ActiveArray::ActiveArray(size_t size) : {} OopStorage::ActiveArray::~ActiveArray() { - assert(_refcount == 0, "precondition"); + assert(_refcount.load_relaxed() == 0, "precondition"); } OopStorage::ActiveArray* OopStorage::ActiveArray::create(size_t size, @@ -144,32 +144,32 @@ size_t OopStorage::ActiveArray::size() const { } size_t OopStorage::ActiveArray::block_count() const { - return _block_count; + return _block_count.load_relaxed(); } size_t OopStorage::ActiveArray::block_count_acquire() const { - return AtomicAccess::load_acquire(&_block_count); + return _block_count.load_acquire(); } void OopStorage::ActiveArray::increment_refcount() const { - int new_value = AtomicAccess::add(&_refcount, 1); - assert(new_value >= 1, "negative refcount %d", new_value - 1); + int old_value = _refcount.fetch_then_add(1); + assert(old_value >= 0, "negative refcount %d", old_value); } bool OopStorage::ActiveArray::decrement_refcount() const { - int new_value = AtomicAccess::sub(&_refcount, 1); + int new_value = _refcount.sub_then_fetch(1); assert(new_value >= 0, "negative refcount %d", new_value); return new_value == 0; } bool OopStorage::ActiveArray::push(Block* block) { - size_t index = _block_count; + size_t index = _block_count.load_relaxed(); if (index < _size) { block->set_active_index(index); *block_ptr(index) = block; // Use a release_store to ensure all the setup is complete before // making the block visible. - AtomicAccess::release_store(&_block_count, index + 1); + _block_count.release_store(index + 1); return true; } else { return false; @@ -177,19 +177,19 @@ bool OopStorage::ActiveArray::push(Block* block) { } void OopStorage::ActiveArray::remove(Block* block) { - assert(_block_count > 0, "array is empty"); + assert(_block_count.load_relaxed() > 0, "array is empty"); size_t index = block->active_index(); assert(*block_ptr(index) == block, "block not present"); - size_t last_index = _block_count - 1; + size_t last_index = _block_count.load_relaxed() - 1; Block* last_block = *block_ptr(last_index); last_block->set_active_index(index); *block_ptr(index) = last_block; - _block_count = last_index; + _block_count.store_relaxed(last_index); } void OopStorage::ActiveArray::copy_from(const ActiveArray* from) { - assert(_block_count == 0, "array must be empty"); - size_t count = from->_block_count; + assert(_block_count.load_relaxed() == 0, "array must be empty"); + size_t count = from->_block_count.load_relaxed(); assert(count <= _size, "precondition"); Block* const* from_ptr = from->block_ptr(0); Block** to_ptr = block_ptr(0); @@ -198,7 +198,7 @@ void OopStorage::ActiveArray::copy_from(const ActiveArray* from) { assert(block->active_index() == i, "invariant"); *to_ptr++ = block; } - _block_count = count; + _block_count.store_relaxed(count); } // Blocks start with an array of BitsPerWord oop entries. That array @@ -230,14 +230,17 @@ OopStorage::Block::Block(const OopStorage* owner, void* memory) : assert(is_aligned(this, block_alignment), "misaligned block"); } +#ifdef ASSERT OopStorage::Block::~Block() { - assert(_release_refcount == 0, "deleting block while releasing"); - assert(_deferred_updates_next == nullptr, "deleting block with deferred update"); + assert(_release_refcount.load_relaxed() == 0, "deleting block while releasing"); + assert(_deferred_updates_next.load_relaxed() == nullptr, "deleting block with deferred update"); // Clear fields used by block_for_ptr and entry validation, which - // might help catch bugs. Volatile to prevent dead-store elimination. - const_cast(_allocated_bitmask) = 0; + // might help catch bugs. + _allocated_bitmask.store_relaxed(0); + // Volatile to prevent dead-store elimination. const_cast(_owner_address) = 0; } +#endif // ASSERT size_t OopStorage::Block::allocation_size() { // _data must be first member, so aligning Block aligns _data. @@ -272,16 +275,16 @@ uintx OopStorage::Block::bitmask_for_entry(const oop* ptr) const { bool OopStorage::Block::is_safe_to_delete() const { assert(is_empty(), "precondition"); OrderAccess::loadload(); - return (AtomicAccess::load_acquire(&_release_refcount) == 0) && - (AtomicAccess::load_acquire(&_deferred_updates_next) == nullptr); + return ((_release_refcount.load_acquire() == 0) && + (_deferred_updates_next.load_acquire() == nullptr)); } OopStorage::Block* OopStorage::Block::deferred_updates_next() const { - return _deferred_updates_next; + return _deferred_updates_next.load_relaxed(); } void OopStorage::Block::set_deferred_updates_next(Block* block) { - _deferred_updates_next = block; + _deferred_updates_next.store_relaxed(block); } bool OopStorage::Block::contains(const oop* ptr) const { @@ -321,9 +324,8 @@ void OopStorage::Block::atomic_add_allocated(uintx add) { // we can use an atomic add to implement the operation. The assert post // facto verifies the precondition held; if there were any set bits in // common, then after the add at least one of them will be zero. - uintx sum = AtomicAccess::add(&_allocated_bitmask, add); - assert((sum & add) == add, "some already present: %zu:%zu", - sum, add); + uintx sum = _allocated_bitmask.add_then_fetch(add); + assert((sum & add) == add, "some already present: %zu:%zu", sum, add); } oop* OopStorage::Block::allocate() { @@ -452,7 +454,7 @@ oop* OopStorage::allocate() { oop* result = block->allocate(); assert(result != nullptr, "allocation failed"); assert(!block->is_empty(), "postcondition"); - AtomicAccess::inc(&_allocation_count); // release updates outside lock. + _allocation_count.add_then_fetch(1u); // release updates outside lock. if (block->is_full()) { // Transitioning from not full to full. // Remove full blocks from consideration by future allocates. @@ -490,7 +492,7 @@ size_t OopStorage::allocate(oop** ptrs, size_t size) { assert(!is_empty_bitmask(taken), "invariant"); } // Drop lock, now that we've taken all available entries from block. size_t num_taken = population_count(taken); - AtomicAccess::add(&_allocation_count, num_taken); + _allocation_count.add_then_fetch(num_taken); // Fill ptrs from those taken entries. size_t limit = MIN2(num_taken, size); for (size_t i = 0; i < limit; ++i) { @@ -506,7 +508,7 @@ size_t OopStorage::allocate(oop** ptrs, size_t size) { assert(size == limit, "invariant"); assert(num_taken == (limit + population_count(taken)), "invariant"); block->release_entries(taken, this); - AtomicAccess::sub(&_allocation_count, num_taken - limit); + _allocation_count.sub_then_fetch(num_taken - limit); } log_trace(oopstorage, ref)("%s: bulk allocate %zu, returned %zu", name(), limit, num_taken - limit); @@ -527,9 +529,9 @@ bool OopStorage::try_add_block() { if (block == nullptr) return false; // Add new block to the _active_array, growing if needed. - if (!_active_array->push(block)) { + if (!_active_array.load_relaxed()->push(block)) { if (expand_active_array()) { - guarantee(_active_array->push(block), "push failed after expansion"); + guarantee(_active_array.load_relaxed()->push(block), "push failed after expansion"); } else { log_debug(oopstorage, blocks)("%s: failed active array expand", name()); Block::delete_block(*block); @@ -576,7 +578,7 @@ OopStorage::Block* OopStorage::block_for_allocation() { // indicate allocation failure. bool OopStorage::expand_active_array() { assert_lock_strong(_allocation_mutex); - ActiveArray* old_array = _active_array; + ActiveArray* old_array = _active_array.load_relaxed(); size_t new_size = 2 * old_array->size(); log_debug(oopstorage, blocks)("%s: expand active array %zu", name(), new_size); @@ -599,7 +601,7 @@ void OopStorage::replace_active_array(ActiveArray* new_array) { // Update new_array refcount to account for the new reference. new_array->increment_refcount(); // Install new_array, ensuring its initialization is complete first. - AtomicAccess::release_store(&_active_array, new_array); + _active_array.release_store(new_array); // Wait for any readers that could read the old array from _active_array. // Can't use GlobalCounter here, because this is called from allocate(), // which may be called in the scope of a GlobalCounter critical section @@ -617,7 +619,7 @@ void OopStorage::replace_active_array(ActiveArray* new_array) { // using it. OopStorage::ActiveArray* OopStorage::obtain_active_array() const { SingleWriterSynchronizer::CriticalSection cs(&_protect_active); - ActiveArray* result = AtomicAccess::load_acquire(&_active_array); + ActiveArray* result = _active_array.load_acquire(); result->increment_refcount(); return result; } @@ -625,7 +627,7 @@ OopStorage::ActiveArray* OopStorage::obtain_active_array() const { // Decrement refcount of array and destroy if refcount is zero. void OopStorage::relinquish_block_array(ActiveArray* array) const { if (array->decrement_refcount()) { - assert(array != _active_array, "invariant"); + assert(array != _active_array.load_relaxed(), "invariant"); ActiveArray::destroy(array); } } @@ -672,14 +674,14 @@ static void log_release_transitions(uintx releasing, void OopStorage::Block::release_entries(uintx releasing, OopStorage* owner) { assert(releasing != 0, "preconditon"); // Prevent empty block deletion when transitioning to empty. - AtomicAccess::inc(&_release_refcount); + _release_refcount.add_then_fetch(1u); // Atomically update allocated bitmask. - uintx old_allocated = _allocated_bitmask; + uintx old_allocated = _allocated_bitmask.load_relaxed(); while (true) { assert((releasing & ~old_allocated) == 0, "releasing unallocated entries"); uintx new_value = old_allocated ^ releasing; - uintx fetched = AtomicAccess::cmpxchg(&_allocated_bitmask, old_allocated, new_value); + uintx fetched = _allocated_bitmask.compare_exchange(old_allocated, new_value); if (fetched == old_allocated) break; // Successful update. old_allocated = fetched; // Retry with updated bitmask. } @@ -698,12 +700,12 @@ void OopStorage::Block::release_entries(uintx releasing, OopStorage* owner) { // then someone else has made such a claim and the deferred update has not // yet been processed and will include our change, so we don't need to do // anything further. - if (AtomicAccess::replace_if_null(&_deferred_updates_next, this)) { + if (_deferred_updates_next.compare_exchange(nullptr, this) == nullptr) { // Successfully claimed. Push, with self-loop for end-of-list. - Block* head = owner->_deferred_updates; + Block* head = owner->_deferred_updates.load_relaxed(); while (true) { - _deferred_updates_next = (head == nullptr) ? this : head; - Block* fetched = AtomicAccess::cmpxchg(&owner->_deferred_updates, head, this); + _deferred_updates_next.store_relaxed((head == nullptr) ? this : head); + Block* fetched = owner->_deferred_updates.compare_exchange(head, this); if (fetched == head) break; // Successful update. head = fetched; // Retry with updated head. } @@ -720,7 +722,7 @@ void OopStorage::Block::release_entries(uintx releasing, OopStorage* owner) { } } // Release hold on empty block deletion. - AtomicAccess::dec(&_release_refcount); + _release_refcount.sub_then_fetch(1u); } // Process one available deferred update. Returns true if one was processed. @@ -729,13 +731,13 @@ bool OopStorage::reduce_deferred_updates() { // Atomically pop a block off the list, if any available. // No ABA issue because this is only called by one thread at a time. // The atomicity is wrto pushes by release(). - Block* block = AtomicAccess::load_acquire(&_deferred_updates); + Block* block = _deferred_updates.load_acquire(); while (true) { if (block == nullptr) return false; // Try atomic pop of block from list. Block* tail = block->deferred_updates_next(); if (block == tail) tail = nullptr; // Handle self-loop end marker. - Block* fetched = AtomicAccess::cmpxchg(&_deferred_updates, block, tail); + Block* fetched = _deferred_updates.compare_exchange(block, tail); if (fetched == block) break; // Update successful. block = fetched; // Retry with updated block. } @@ -780,7 +782,7 @@ void OopStorage::release(const oop* ptr) { assert(block != nullptr, "%s: invalid release " PTR_FORMAT, name(), p2i(ptr)); log_trace(oopstorage, ref)("%s: releasing " PTR_FORMAT, name(), p2i(ptr)); block->release_entries(block->bitmask_for_entry(ptr), this); - AtomicAccess::dec(&_allocation_count); + _allocation_count.sub_then_fetch(1u); } void OopStorage::release(const oop* const* ptrs, size_t size) { @@ -806,7 +808,7 @@ void OopStorage::release(const oop* const* ptrs, size_t size) { } // Release the contiguous entries that are in block. block->release_entries(releasing, this); - AtomicAccess::sub(&_allocation_count, count); + _allocation_count.sub_then_fetch(count); } } @@ -837,7 +839,7 @@ OopStorage::OopStorage(const char* name, MemTag mem_tag) : _mem_tag(mem_tag), _needs_cleanup(false) { - _active_array->increment_refcount(); + _active_array.load_relaxed()->increment_refcount(); assert(_active_mutex->rank() < _allocation_mutex->rank(), "%s: active_mutex must have lower rank than allocation_mutex", _name); assert(Service_lock->rank() < _active_mutex->rank(), @@ -852,20 +854,21 @@ void OopStorage::delete_empty_block(const Block& block) { OopStorage::~OopStorage() { Block* block; - while ((block = _deferred_updates) != nullptr) { - _deferred_updates = block->deferred_updates_next(); + while ((block = _deferred_updates.load_relaxed()) != nullptr) { + _deferred_updates.store_relaxed(block->deferred_updates_next()); block->set_deferred_updates_next(nullptr); } while ((block = _allocation_list.head()) != nullptr) { _allocation_list.unlink(*block); } - bool unreferenced = _active_array->decrement_refcount(); + ActiveArray* array = _active_array.load_relaxed(); + bool unreferenced = array->decrement_refcount(); assert(unreferenced, "deleting storage while _active_array is referenced"); - for (size_t i = _active_array->block_count(); 0 < i; ) { - block = _active_array->at(--i); + for (size_t i = array->block_count(); 0 < i; ) { + block = array->at(--i); Block::delete_block(*block); } - ActiveArray::destroy(_active_array); + ActiveArray::destroy(array); os::free(const_cast(_name)); } @@ -894,7 +897,7 @@ bool OopStorage::should_report_num_dead() const { // face of frequent explicit ServiceThread wakeups, hence the defer period. // Global cleanup request state. -static volatile bool needs_cleanup_requested = false; +static Atomic needs_cleanup_requested{false}; // Time after which a cleanup is permitted. static jlong cleanup_permit_time = 0; @@ -906,12 +909,11 @@ const jlong cleanup_defer_period = 500 * NANOSECS_PER_MILLISEC; bool OopStorage::has_cleanup_work_and_reset() { assert_lock_strong(Service_lock); - if (AtomicAccess::load_acquire(&needs_cleanup_requested) && - os::javaTimeNanos() > cleanup_permit_time) { - cleanup_permit_time = - os::javaTimeNanos() + cleanup_defer_period; + if (needs_cleanup_requested.load_acquire() && + (os::javaTimeNanos() > cleanup_permit_time)) { + cleanup_permit_time = os::javaTimeNanos() + cleanup_defer_period; // Set the request flag false and return its old value. - AtomicAccess::release_store(&needs_cleanup_requested, false); + needs_cleanup_requested.release_store(false); return true; } else { return false; @@ -923,22 +925,22 @@ bool OopStorage::has_cleanup_work_and_reset() { void OopStorage::record_needs_cleanup() { // Set local flag first, else ServiceThread could wake up and miss // the request. - AtomicAccess::release_store(&_needs_cleanup, true); - AtomicAccess::release_store_fence(&needs_cleanup_requested, true); + _needs_cleanup.release_store(true); + needs_cleanup_requested.release_store_fence(true); } bool OopStorage::delete_empty_blocks() { // ServiceThread might have oopstorage work, but not for this object. // But check for deferred updates, which might provide cleanup work. - if (!AtomicAccess::load_acquire(&_needs_cleanup) && - (AtomicAccess::load_acquire(&_deferred_updates) == nullptr)) { + if (!_needs_cleanup.load_acquire() && + (_deferred_updates.load_acquire() == nullptr)) { return false; } MutexLocker ml(_allocation_mutex, Mutex::_no_safepoint_check_flag); // Clear the request before processing. - AtomicAccess::release_store_fence(&_needs_cleanup, false); + _needs_cleanup.release_store_fence(false); // Other threads could be adding to the empty block count or the // deferred update list while we're working. Set an upper bound on @@ -977,7 +979,7 @@ bool OopStorage::delete_empty_blocks() { // but don't re-notify, to avoid useless spinning of the // ServiceThread. Instead, iteration completion notifies. if (_concurrent_iteration_count > 0) return true; - _active_array->remove(block); + _active_array.load_relaxed()->remove(block); } // Remove block from _allocation_list and delete it. _allocation_list.unlink(*block); @@ -1001,8 +1003,9 @@ OopStorage::EntryStatus OopStorage::allocation_status(const oop* ptr) const { MutexLocker ml(_allocation_mutex, Mutex::_no_safepoint_check_flag); // Block could be a false positive, so get index carefully. size_t index = Block::active_index_safe(block); - if ((index < _active_array->block_count()) && - (block == _active_array->at(index)) && + ActiveArray* array = _active_array.load_relaxed(); + if ((index < array->block_count()) && + (block == array->at(index)) && block->contains(ptr)) { if ((block->allocated_bitmask() & block->bitmask_for_entry(ptr)) != 0) { return ALLOCATED_ENTRY; @@ -1015,7 +1018,7 @@ OopStorage::EntryStatus OopStorage::allocation_status(const oop* ptr) const { } size_t OopStorage::allocation_count() const { - return _allocation_count; + return _allocation_count.load_relaxed(); } size_t OopStorage::block_count() const { @@ -1084,7 +1087,7 @@ void OopStorage::BasicParState::update_concurrent_iteration_count(int value) { bool OopStorage::BasicParState::claim_next_segment(IterationData* data) { data->_processed += data->_segment_end - data->_segment_start; - size_t start = AtomicAccess::load_acquire(&_next_block); + size_t start = _next_block.load_acquire(); if (start >= _block_count) { return finish_iteration(data); // No more blocks available. } @@ -1097,11 +1100,11 @@ bool OopStorage::BasicParState::claim_next_segment(IterationData* data) { size_t max_step = 10; size_t remaining = _block_count - start; size_t step = MIN2(max_step, 1 + (remaining / _estimated_thread_count)); - // AtomicAccess::add with possible overshoot. This can perform better + // Atomic add with possible overshoot. This can perform better // than a CAS loop on some platforms when there is contention. // We can cope with the uncertainty by recomputing start/end from // the result of the add, and dealing with potential overshoot. - size_t end = AtomicAccess::add(&_next_block, step); + size_t end = _next_block.add_then_fetch(step); // _next_block may have changed, so recompute start from result of add. start = end - step; // _next_block may have changed so much that end has overshot. @@ -1128,15 +1131,15 @@ bool OopStorage::BasicParState::finish_iteration(const IterationData* data) cons } size_t OopStorage::BasicParState::num_dead() const { - return AtomicAccess::load(&_num_dead); + return _num_dead.load_relaxed(); } void OopStorage::BasicParState::increment_num_dead(size_t num_dead) { - AtomicAccess::add(&_num_dead, num_dead); + _num_dead.add_then_fetch(num_dead); } void OopStorage::BasicParState::report_num_dead() const { - _storage->report_num_dead(AtomicAccess::load(&_num_dead)); + _storage->report_num_dead(_num_dead.load_relaxed()); } const char* OopStorage::name() const { return _name; } @@ -1164,8 +1167,8 @@ bool OopStorage::Block::print_containing(const oop* addr, outputStream* st) { #ifndef PRODUCT void OopStorage::print_on(outputStream* st) const { - size_t allocations = _allocation_count; - size_t blocks = _active_array->block_count(); + size_t allocations = _allocation_count.load_relaxed(); + size_t blocks = _active_array.load_relaxed()->block_count(); double data_size = section_size * section_count; double alloc_percentage = percent_of((double)allocations, blocks * data_size); diff --git a/src/hotspot/share/gc/shared/oopStorage.hpp b/src/hotspot/share/gc/shared/oopStorage.hpp index 34c980a0586..6097eeaa4f4 100644 --- a/src/hotspot/share/gc/shared/oopStorage.hpp +++ b/src/hotspot/share/gc/shared/oopStorage.hpp @@ -27,6 +27,7 @@ #include "memory/allocation.hpp" #include "oops/oop.hpp" +#include "runtime/atomic.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/macros.hpp" #include "utilities/singleWriterSynchronizer.hpp" @@ -258,15 +259,15 @@ private: private: const char* _name; - ActiveArray* _active_array; + Atomic _active_array; AllocationList _allocation_list; - Block* volatile _deferred_updates; + Atomic _deferred_updates; Mutex* _allocation_mutex; Mutex* _active_mutex; NumDeadCallback _num_dead_callback; - // Volatile for racy unlocked accesses. - volatile size_t _allocation_count; + // Atomic for racy unlocked accesses. + Atomic _allocation_count; // Protection for _active_array. mutable SingleWriterSynchronizer _protect_active; @@ -278,7 +279,7 @@ private: MemTag _mem_tag; // Flag indicating this storage object is a candidate for empty block deletion. - volatile bool _needs_cleanup; + Atomic _needs_cleanup; // Clients construct via "create" factory function. OopStorage(const char* name, MemTag mem_tag); diff --git a/src/hotspot/share/gc/shared/oopStorage.inline.hpp b/src/hotspot/share/gc/shared/oopStorage.inline.hpp index 4fb1d8fcaf1..c2747781a6b 100644 --- a/src/hotspot/share/gc/shared/oopStorage.inline.hpp +++ b/src/hotspot/share/gc/shared/oopStorage.inline.hpp @@ -30,6 +30,7 @@ #include "cppstdlib/type_traits.hpp" #include "memory/allocation.hpp" #include "oops/oop.hpp" +#include "runtime/atomic.hpp" #include "runtime/safepoint.hpp" #include "utilities/align.hpp" #include "utilities/count_trailing_zeros.hpp" @@ -42,8 +43,8 @@ class OopStorage::ActiveArray { friend class OopStorage::TestAccess; size_t _size; - volatile size_t _block_count; - mutable volatile int _refcount; + Atomic _block_count; + mutable Atomic _refcount; // Block* _blocks[1]; // Pseudo flexible array member. ActiveArray(size_t size); @@ -104,7 +105,7 @@ inline OopStorage::Block** OopStorage::ActiveArray::block_ptr(size_t index) { } inline OopStorage::Block* OopStorage::ActiveArray::at(size_t index) const { - assert(index < _block_count, "precondition"); + assert(index < _block_count.load_relaxed(), "precondition"); return *block_ptr(index); } @@ -135,16 +136,16 @@ class OopStorage::Block /* No base class, to avoid messing up alignment. */ { oop _data[BitsPerWord]; static const unsigned _data_pos = 0; // Position of _data. - volatile uintx _allocated_bitmask; // One bit per _data element. + Atomic _allocated_bitmask; // One bit per _data element. intptr_t _owner_address; void* _memory; // Unaligned storage containing block. size_t _active_index; AllocationListEntry _allocation_list_entry; - Block* volatile _deferred_updates_next; - volatile uintx _release_refcount; + Atomic _deferred_updates_next; + Atomic _release_refcount; Block(const OopStorage* owner, void* memory); - ~Block(); + ~Block() NOT_DEBUG(= default); void check_index(unsigned index) const; unsigned get_index(const oop* ptr) const; @@ -322,7 +323,7 @@ inline const oop* OopStorage::Block::get_pointer(unsigned index) const { } inline uintx OopStorage::Block::allocated_bitmask() const { - return _allocated_bitmask; + return _allocated_bitmask.load_relaxed(); } inline uintx OopStorage::Block::bitmask_for_index(unsigned index) const { @@ -366,7 +367,7 @@ inline bool OopStorage::iterate_impl(F f, Storage* storage) { // Propagate const/non-const iteration to the block layer, by using // const or non-const blocks as corresponding to Storage. using BlockPtr = std::conditional_t::value, const Block*, Block*>; - ActiveArray* blocks = storage->_active_array; + ActiveArray* blocks = storage->_active_array.load_relaxed(); size_t limit = blocks->block_count(); for (size_t i = 0; i < limit; ++i) { BlockPtr block = blocks->at(i); diff --git a/src/hotspot/share/gc/shared/oopStorageParState.hpp b/src/hotspot/share/gc/shared/oopStorageParState.hpp index 046bf9de8c2..cad1a1f0cf6 100644 --- a/src/hotspot/share/gc/shared/oopStorageParState.hpp +++ b/src/hotspot/share/gc/shared/oopStorageParState.hpp @@ -27,6 +27,7 @@ #include "cppstdlib/type_traits.hpp" #include "gc/shared/oopStorage.hpp" +#include "runtime/atomic.hpp" #include "utilities/globalDefinitions.hpp" ////////////////////////////////////////////////////////////////////////////// @@ -131,10 +132,10 @@ class OopStorage::BasicParState { const OopStorage* _storage; ActiveArray* _active_array; size_t _block_count; - volatile size_t _next_block; + Atomic _next_block; uint _estimated_thread_count; bool _concurrent; - volatile size_t _num_dead; + Atomic _num_dead; NONCOPYABLE(BasicParState); diff --git a/test/hotspot/gtest/gc/shared/test_oopStorage.cpp b/test/hotspot/gtest/gc/shared/test_oopStorage.cpp index 285cc2630a3..b343e7fc47f 100644 --- a/test/hotspot/gtest/gc/shared/test_oopStorage.cpp +++ b/test/hotspot/gtest/gc/shared/test_oopStorage.cpp @@ -47,7 +47,7 @@ public: typedef OopStorage::ActiveArray ActiveArray; static ActiveArray& active_array(const OopStorage& storage) { - return *storage._active_array; + return *storage._active_array.load_relaxed(); } static AllocationList& allocation_list(OopStorage& storage) { @@ -90,7 +90,7 @@ public: } static void block_array_set_block_count(ActiveArray* blocks, size_t count) { - blocks->_block_count = count; + blocks->_block_count.store_relaxed(count); } static const oop* get_block_pointer(const Block& block, unsigned index) { From 902aa4dcd297fef34cb302e468b030c48665ec84 Mon Sep 17 00:00:00 2001 From: Alexander Zuev Date: Tue, 18 Nov 2025 23:51:32 +0000 Subject: [PATCH 122/418] 8372120: Add missing sound keyword to MIDI tests Reviewed-by: kcr, dholmes --- .../javax/sound/midi/MidiDeviceConnectors/TestAllDevices.java | 1 + test/jdk/javax/sound/midi/SysexMessage/SendRawSysexMessage.java | 1 + .../sound/midi/spi/MidiDeviceProvider/ExpectedNPEOnNull.java | 1 + test/jdk/javax/sound/midi/spi/MidiDeviceProvider/FakeInfo.java | 1 + .../javax/sound/midi/spi/MidiDeviceProvider/UnsupportedInfo.java | 1 + 5 files changed, 5 insertions(+) diff --git a/test/jdk/javax/sound/midi/MidiDeviceConnectors/TestAllDevices.java b/test/jdk/javax/sound/midi/MidiDeviceConnectors/TestAllDevices.java index 27290738a0c..afac39b83fb 100644 --- a/test/jdk/javax/sound/midi/MidiDeviceConnectors/TestAllDevices.java +++ b/test/jdk/javax/sound/midi/MidiDeviceConnectors/TestAllDevices.java @@ -24,6 +24,7 @@ /** * @test * @bug 4933700 + * @key sound * @summary Tests that default devices return MidiDeviceTransmitter/Receiver and returned objects return correct MidiDevice * @compile TestAllDevices.java * @run main TestAllDevices diff --git a/test/jdk/javax/sound/midi/SysexMessage/SendRawSysexMessage.java b/test/jdk/javax/sound/midi/SysexMessage/SendRawSysexMessage.java index 00c57f46c98..7b8c4b0a3f2 100644 --- a/test/jdk/javax/sound/midi/SysexMessage/SendRawSysexMessage.java +++ b/test/jdk/javax/sound/midi/SysexMessage/SendRawSysexMessage.java @@ -35,6 +35,7 @@ import static javax.sound.midi.SysexMessage.SYSTEM_EXCLUSIVE; /** * @test * @bug 8074211 8237495 8301310 + * @key sound * @summary fail with memory errors when asked to send a sysex message starting * with 0xF7 */ diff --git a/test/jdk/javax/sound/midi/spi/MidiDeviceProvider/ExpectedNPEOnNull.java b/test/jdk/javax/sound/midi/spi/MidiDeviceProvider/ExpectedNPEOnNull.java index efb57eeeae2..05ba16cded5 100644 --- a/test/jdk/javax/sound/midi/spi/MidiDeviceProvider/ExpectedNPEOnNull.java +++ b/test/jdk/javax/sound/midi/spi/MidiDeviceProvider/ExpectedNPEOnNull.java @@ -32,6 +32,7 @@ import static java.util.ServiceLoader.load; /** * @test * @bug 8143909 + * @key sound * @author Sergey Bylokhov */ public final class ExpectedNPEOnNull { diff --git a/test/jdk/javax/sound/midi/spi/MidiDeviceProvider/FakeInfo.java b/test/jdk/javax/sound/midi/spi/MidiDeviceProvider/FakeInfo.java index 8eabb992bca..71d27c4943c 100644 --- a/test/jdk/javax/sound/midi/spi/MidiDeviceProvider/FakeInfo.java +++ b/test/jdk/javax/sound/midi/spi/MidiDeviceProvider/FakeInfo.java @@ -35,6 +35,7 @@ import static java.util.ServiceLoader.load; /** * @test * @bug 8059743 + * @key sound * @summary MidiDeviceProvider shouldn't returns incorrect results in case of * some unknown MidiDevice.Info * @author Sergey Bylokhov diff --git a/test/jdk/javax/sound/midi/spi/MidiDeviceProvider/UnsupportedInfo.java b/test/jdk/javax/sound/midi/spi/MidiDeviceProvider/UnsupportedInfo.java index 685a5e8af62..5d37759fbad 100644 --- a/test/jdk/javax/sound/midi/spi/MidiDeviceProvider/UnsupportedInfo.java +++ b/test/jdk/javax/sound/midi/spi/MidiDeviceProvider/UnsupportedInfo.java @@ -30,6 +30,7 @@ import static java.util.ServiceLoader.load; /** * @test * @bug 8058115 + * @key sound * @summary MidiDeviceProvider shouldn't returns incorrect results in case of * unsupported MidiDevice.Info * @author Sergey Bylokhov From 02ff38f2d7f6abc0e4661e8226bc6780b7a11c3a Mon Sep 17 00:00:00 2001 From: Ioi Lam Date: Wed, 19 Nov 2025 05:04:34 +0000 Subject: [PATCH 123/418] 8363986: Heap region in CDS archive is not at deterministic address Reviewed-by: kvn, asmehra --- src/hotspot/share/cds/aotArtifactFinder.hpp | 2 +- src/hotspot/share/cds/aotMapLogger.cpp | 2 +- src/hotspot/share/cds/aotMappedHeapWriter.cpp | 105 ++++++++++++++++-- src/hotspot/share/cds/aotMappedHeapWriter.hpp | 44 ++++---- src/hotspot/share/cds/filemap.cpp | 10 +- src/hotspot/share/cds/heapShared.hpp | 2 +- test/hotspot/jtreg/ProblemList.txt | 1 - 7 files changed, 125 insertions(+), 41 deletions(-) diff --git a/src/hotspot/share/cds/aotArtifactFinder.hpp b/src/hotspot/share/cds/aotArtifactFinder.hpp index 405222a8753..05bcde6b0ac 100644 --- a/src/hotspot/share/cds/aotArtifactFinder.hpp +++ b/src/hotspot/share/cds/aotArtifactFinder.hpp @@ -39,7 +39,7 @@ class TypeArrayKlass; // It also decides what Klasses must be cached in aot-initialized state. // // ArchiveBuilder uses [1] as roots to scan for all MetaspaceObjs that need to be cached. -// ArchiveHeapWriter uses [2] to create an image of the archived heap. +// HeapShared uses [2] to create an image of the archived heap. // // [1] is stored in _all_cached_classes in aotArtifactFinder.cpp. // [2] is stored in HeapShared::archived_object_cache(). diff --git a/src/hotspot/share/cds/aotMapLogger.cpp b/src/hotspot/share/cds/aotMapLogger.cpp index d0a63c56093..a252eae4b84 100644 --- a/src/hotspot/share/cds/aotMapLogger.cpp +++ b/src/hotspot/share/cds/aotMapLogger.cpp @@ -796,7 +796,7 @@ void AOTMapLogger::dumptime_log_mapped_heap_region(ArchiveMappedHeapInfo* heap_i address buffer_start = address(r.start()); // start of the current oop inside the buffer address buffer_end = address(r.end()); - address requested_base = UseCompressedOops ? (address)CompressedOops::base() : (address)AOTMappedHeapWriter::NOCOOPS_REQUESTED_BASE; + address requested_base = UseCompressedOops ? AOTMappedHeapWriter::narrow_oop_base() : (address)AOTMappedHeapWriter::NOCOOPS_REQUESTED_BASE; address requested_start = UseCompressedOops ? AOTMappedHeapWriter::buffered_addr_to_requested_addr(buffer_start) : requested_base; log_region_range("heap", buffer_start, buffer_end, requested_start); diff --git a/src/hotspot/share/cds/aotMappedHeapWriter.cpp b/src/hotspot/share/cds/aotMappedHeapWriter.cpp index ff9319d266b..98f400c989c 100644 --- a/src/hotspot/share/cds/aotMappedHeapWriter.cpp +++ b/src/hotspot/share/cds/aotMappedHeapWriter.cpp @@ -55,7 +55,7 @@ GrowableArrayCHeap* AOTMappedHeapWriter::_buffer = nullptr; -// The following are offsets from buffer_bottom() +bool AOTMappedHeapWriter::_is_writing_deterministic_heap = false; size_t AOTMappedHeapWriter::_buffer_used; // Heap root segments @@ -74,7 +74,7 @@ AOTMappedHeapWriter::_buffer_offset_to_source_obj_table = nullptr; DumpedInternedStrings *AOTMappedHeapWriter::_dumped_interned_strings = nullptr; typedef HashTable< - size_t, // offset of a filler from ArchiveHeapWriter::buffer_bottom() + size_t, // offset of a filler from AOTMappedHeapWriter::buffer_bottom() size_t, // size of this filler (in bytes) 127, // prime number AnyObj::C_HEAP, @@ -96,6 +96,45 @@ void AOTMappedHeapWriter::init() { _source_objs = new GrowableArrayCHeap(10000); guarantee(MIN_GC_REGION_ALIGNMENT <= G1HeapRegion::min_region_size_in_words() * HeapWordSize, "must be"); + + if (CDSConfig::old_cds_flags_used()) { + // With the old CDS workflow, we can guatantee determninistic output: given + // the same classlist file, we can generate the same static CDS archive. + // To ensure determinism, we always use the same compressed oop encoding + // (zero-based, no shift). See set_requested_address_range(). + _is_writing_deterministic_heap = true; + } else { + // Determninistic output is not supported by the new AOT workflow, so + // we don't force the (zero-based, no shift) encoding. This way, it is more + // likely that we can avoid oop relocation in the production run. + _is_writing_deterministic_heap = false; + } + } +} + +// For AOTMappedHeapWriter::narrow_oop_{mode, base, shift}(), see comments +// in AOTMappedHeapWriter::set_requested_address_range(), +CompressedOops::Mode AOTMappedHeapWriter::narrow_oop_mode() { + if (is_writing_deterministic_heap()) { + return CompressedOops::UnscaledNarrowOop; + } else { + return CompressedOops::mode(); + } +} + +address AOTMappedHeapWriter::narrow_oop_base() { + if (is_writing_deterministic_heap()) { + return (address)0; + } else { + return CompressedOops::base(); + } +} + +int AOTMappedHeapWriter::narrow_oop_shift() { + if (is_writing_deterministic_heap()) { + return 0; + } else { + return CompressedOops::shift(); } } @@ -116,7 +155,7 @@ void AOTMappedHeapWriter::write(GrowableArrayCHeap* roots, assert(CDSConfig::is_dumping_heap(), "sanity"); allocate_buffer(); copy_source_objs_to_buffer(roots); - set_requested_address(heap_info); + set_requested_address_range(heap_info); relocate_embedded_oops(roots, heap_info); } @@ -536,14 +575,55 @@ size_t AOTMappedHeapWriter::copy_one_source_obj_to_buffer(oop src_obj) { return buffered_obj_offset; } -void AOTMappedHeapWriter::set_requested_address(ArchiveMappedHeapInfo* info) { +// Set the range [_requested_bottom, _requested_top), the requested address range of all +// the archived heap objects in the production run. +// +// (1) UseCompressedOops == true && !is_writing_deterministic_heap() +// +// The archived objects are stored using the COOPS encoding of the assembly phase. +// We pick a range within the heap used by the assembly phase. +// +// In the production run, if different COOPS encodings are used: +// - The heap contents needs to be relocated. +// +// (2) UseCompressedOops == true && is_writing_deterministic_heap() +// +// We always use zero-based, zero-shift encoding. _requested_top is aligned to 0x10000000. +// +// (3) UseCompressedOops == false: +// +// In the production run, the heap range is usually picked (randomly) by the OS, so we +// will almost always need to perform relocation, regardless of how we pick the requested +// address range. +// +// So we just hard code it to NOCOOPS_REQUESTED_BASE. +// +void AOTMappedHeapWriter::set_requested_address_range(ArchiveMappedHeapInfo* info) { assert(!info->is_used(), "only set once"); size_t heap_region_byte_size = _buffer_used; assert(heap_region_byte_size > 0, "must archived at least one object!"); if (UseCompressedOops) { - if (UseG1GC) { + if (is_writing_deterministic_heap()) { + // Pick a heap range so that requested addresses can be encoded with zero-base/no shift. + // We align the requested bottom to at least 1 MB: if the production run uses G1 with a small + // heap (e.g., -Xmx256m), it's likely that we can map the archived objects at the + // requested location to avoid relocation. + // + // For other collectors or larger heaps, relocation is unavoidable, but is usually + // quite cheap. If you really want to avoid relocation, use the AOT workflow instead. + address heap_end = (address)0x100000000; + size_t alignment = MAX2(MIN_GC_REGION_ALIGNMENT, 1024 * 1024); + if (align_up(heap_region_byte_size, alignment) >= (size_t)heap_end) { + log_error(aot, heap)("cached heap space is too large: %zu bytes", heap_region_byte_size); + AOTMetaspace::unrecoverable_writing_error(); + } + _requested_bottom = align_down(heap_end - heap_region_byte_size, alignment); + } else if (UseG1GC) { + // For G1, pick the range at the top of the current heap. If the exact same heap sizes + // are used in the production run, it's likely that we can map the archived objects + // at the requested location to avoid relocation. address heap_end = (address)G1CollectedHeap::heap()->reserved().end(); log_info(aot, heap)("Heap end = %p", heap_end); _requested_bottom = align_down(heap_end - heap_region_byte_size, G1HeapRegion::GrainBytes); @@ -612,7 +692,14 @@ oop AOTMappedHeapWriter::load_oop_from_buffer(narrowOop* buffered_addr) { template void AOTMappedHeapWriter::relocate_field_in_buffer(T* field_addr_in_buffer, oop source_referent, CHeapBitMap* oopmap) { oop request_referent = source_obj_to_requested_obj(source_referent); - store_requested_oop_in_buffer(field_addr_in_buffer, request_referent); + if (UseCompressedOops && is_writing_deterministic_heap()) { + // We use zero-based, 0-shift encoding, so the narrowOop is just the lower + // 32 bits of request_referent + intptr_t addr = cast_from_oop(request_referent); + *((narrowOop*)field_addr_in_buffer) = checked_cast(addr); + } else { + store_requested_oop_in_buffer(field_addr_in_buffer, request_referent); + } if (request_referent != nullptr) { mark_oop_pointer(field_addr_in_buffer, oopmap); } @@ -918,9 +1005,9 @@ AOTMapLogger::OopDataIterator* AOTMappedHeapWriter::oop_iterator(ArchiveMappedHe address buffer_start = address(r.start()); address buffer_end = address(r.end()); - address requested_base = UseCompressedOops ? (address)CompressedOops::base() : (address)AOTMappedHeapWriter::NOCOOPS_REQUESTED_BASE; - address requested_start = UseCompressedOops ? buffered_addr_to_requested_addr(buffer_start) : requested_base; - int requested_shift = CompressedOops::shift(); + address requested_base = UseCompressedOops ? AOTMappedHeapWriter::narrow_oop_base() : (address)AOTMappedHeapWriter::NOCOOPS_REQUESTED_BASE; + address requested_start = UseCompressedOops ? AOTMappedHeapWriter::buffered_addr_to_requested_addr(buffer_start) : requested_base; + int requested_shift = AOTMappedHeapWriter::narrow_oop_shift(); intptr_t buffer_to_requested_delta = requested_start - buffer_start; uint64_t buffer_start_narrow_oop = 0xdeadbeed; if (UseCompressedOops) { diff --git a/src/hotspot/share/cds/aotMappedHeapWriter.hpp b/src/hotspot/share/cds/aotMappedHeapWriter.hpp index 9a85b83d3d1..eafd38ac8bb 100644 --- a/src/hotspot/share/cds/aotMappedHeapWriter.hpp +++ b/src/hotspot/share/cds/aotMappedHeapWriter.hpp @@ -29,6 +29,7 @@ #include "cds/heapShared.hpp" #include "memory/allocation.hpp" #include "memory/allStatic.hpp" +#include "oops/compressedOops.hpp" #include "oops/oopHandle.hpp" #include "utilities/bitMap.hpp" #include "utilities/exceptions.hpp" @@ -71,7 +72,7 @@ class AOTMappedHeapWriter : AllStatic { // These are entered into HeapShared::archived_object_cache(). // // - "buffered objects" are copies of the "source objects", and are stored in into - // ArchiveHeapWriter::_buffer, which is a GrowableArray that sits outside of + // AOTMappedHeapWriter::_buffer, which is a GrowableArray that sits outside of // the valid heap range. Therefore we avoid using the addresses of these copies // as oops. They are usually called "buffered_addr" in the code (of the type "address"). // @@ -81,26 +82,11 @@ class AOTMappedHeapWriter : AllStatic { // - Each archived object has a "requested address" -- at run time, if the object // can be mapped at this address, we can avoid relocation. // - // The requested address is implemented differently depending on UseCompressedOops: + // The requested address of an archived object is essentially its buffered_addr + delta, + // where delta is (_requested_bottom - buffer_bottom()); // - // UseCompressedOops == true: - // The archived objects are stored assuming that the runtime COOPS compression - // scheme is exactly the same as in dump time (or else a more expensive runtime relocation - // would be needed.) - // - // At dump time, we assume that the runtime heap range is exactly the same as - // in dump time. The requested addresses of the archived objects are chosen such that - // they would occupy the top end of a G1 heap (TBD when dumping is supported by other - // collectors. See JDK-8298614). - // - // UseCompressedOops == false: - // At runtime, the heap range is usually picked (randomly) by the OS, so we will almost always - // need to perform relocation. Hence, the goal of the "requested address" is to ensure that - // the contents of the archived objects are deterministic. I.e., the oop fields of archived - // objects will always point to deterministic addresses. - // - // For G1, the archived heap is written such that the lowest archived object is placed - // at NOCOOPS_REQUESTED_BASE. (TBD after JDK-8298614). + // The requested addresses of all archived objects are within [_requested_bottom, _requested_top). + // See AOTMappedHeapWriter::set_requested_address_range() for more info. // ---------------------------------------------------------------------- public: @@ -111,6 +97,15 @@ public: // Shenandoah heap region size can never be smaller than 256K. static constexpr int MIN_GC_REGION_ALIGNMENT = 256 * K; + // The heap contents are required to be deterministic when dumping "old" CDS archives, in order + // to support reproducible lib/server/classes*.jsa when building the JDK. + static bool is_writing_deterministic_heap() { return _is_writing_deterministic_heap; } + + // The oop encoding used by the archived heap objects. + static CompressedOops::Mode narrow_oop_mode(); + static address narrow_oop_base(); + static int narrow_oop_shift(); + static const int INITIAL_TABLE_SIZE = 15889; // prime number static const int MAX_TABLE_SIZE = 1000000; @@ -121,6 +116,7 @@ private: int _field_offset; }; + static bool _is_writing_deterministic_heap; static GrowableArrayCHeap* _buffer; // The number of bytes that have written into _buffer (may be smaller than _buffer->length()). @@ -130,15 +126,15 @@ private: static HeapRootSegments _heap_root_segments; // The address range of the requested location of the archived heap objects. - static address _requested_bottom; - static address _requested_top; + static address _requested_bottom; // The requested address of the lowest archived heap object + static address _requested_top; // The exclusive end of the highest archived heap object static GrowableArrayCHeap* _native_pointers; static GrowableArrayCHeap* _source_objs; static DumpedInternedStrings *_dumped_interned_strings; // We sort _source_objs_order to minimize the number of bits in ptrmap and oopmap. - // See comments near the body of ArchiveHeapWriter::compare_objs_by_oop_fields(). + // See comments near the body of AOTMappedHeapWriter::compare_objs_by_oop_fields(). // The objects will be written in the order of: //_source_objs->at(_source_objs_order->at(0)._index) // source_objs->at(_source_objs_order->at(1)._index) @@ -200,7 +196,7 @@ private: static int filler_array_length(size_t fill_bytes); static HeapWord* init_filler_array_at_buffer_top(int array_length, size_t fill_bytes); - static void set_requested_address(ArchiveMappedHeapInfo* info); + static void set_requested_address_range(ArchiveMappedHeapInfo* info); static void mark_native_pointers(oop orig_obj); static void relocate_embedded_oops(GrowableArrayCHeap* roots, ArchiveMappedHeapInfo* info); static void compute_ptrmap(ArchiveMappedHeapInfo *info); diff --git a/src/hotspot/share/cds/filemap.cpp b/src/hotspot/share/cds/filemap.cpp index ae92ce31058..61df0a86b41 100644 --- a/src/hotspot/share/cds/filemap.cpp +++ b/src/hotspot/share/cds/filemap.cpp @@ -216,12 +216,14 @@ void FileMapHeader::populate(FileMapInfo *info, size_t core_region_alignment, _obj_alignment = ObjectAlignmentInBytes; _compact_strings = CompactStrings; _compact_headers = UseCompactObjectHeaders; +#if INCLUDE_CDS_JAVA_HEAP if (CDSConfig::is_dumping_heap()) { _object_streaming_mode = HeapShared::is_writing_streaming_mode(); - _narrow_oop_mode = CompressedOops::mode(); - _narrow_oop_base = CompressedOops::base(); - _narrow_oop_shift = CompressedOops::shift(); + _narrow_oop_mode = AOTMappedHeapWriter::narrow_oop_mode(); + _narrow_oop_base = AOTMappedHeapWriter::narrow_oop_base(); + _narrow_oop_shift = AOTMappedHeapWriter::narrow_oop_shift(); } +#endif _compressed_oops = UseCompressedOops; _compressed_class_ptrs = UseCompressedClassPointers; if (UseCompressedClassPointers) { @@ -911,7 +913,7 @@ void FileMapInfo::write_region(int region, char* base, size_t size, if (HeapShared::is_writing_mapping_mode()) { requested_base = (char*)AOTMappedHeapWriter::requested_address(); if (UseCompressedOops) { - mapping_offset = (size_t)((address)requested_base - CompressedOops::base()); + mapping_offset = (size_t)((address)requested_base - AOTMappedHeapWriter::narrow_oop_base()); assert((mapping_offset >> CompressedOops::shift()) << CompressedOops::shift() == mapping_offset, "must be"); } } else { diff --git a/src/hotspot/share/cds/heapShared.hpp b/src/hotspot/share/cds/heapShared.hpp index 2c782f7231b..118c60faa60 100644 --- a/src/hotspot/share/cds/heapShared.hpp +++ b/src/hotspot/share/cds/heapShared.hpp @@ -332,7 +332,7 @@ public: // Used by CDSHeapVerifier. OopHandle _orig_referrer; - // The location of this object inside ArchiveHeapWriter::_buffer + // The location of this object inside {AOTMappedHeapWriter, AOTStreamedHeapWriter}::_buffer size_t _buffer_offset; // One or more fields in this object are pointing to non-null oops. diff --git a/test/hotspot/jtreg/ProblemList.txt b/test/hotspot/jtreg/ProblemList.txt index a45fdc09323..934ef03a987 100644 --- a/test/hotspot/jtreg/ProblemList.txt +++ b/test/hotspot/jtreg/ProblemList.txt @@ -100,7 +100,6 @@ gc/shenandoah/TestSieveObjects.java#no-tlab-genshen 8361099 generic-all # :hotspot_runtime -runtime/cds/DeterministicDump.java 8363986 macosx-x64,macosx-aarch64 runtime/jni/terminatedThread/TestTerminatedThread.java 8317789 aix-ppc64 runtime/Monitor/SyncOnValueBasedClassTest.java 8340995 linux-s390x runtime/os/TestTracePageSizes.java#no-options 8267460 linux-aarch64 From 99135d2e05bb501fe9f9f0d36abd25894d0f93de Mon Sep 17 00:00:00 2001 From: Aggelos Biboudis Date: Wed, 19 Nov 2025 08:47:57 +0000 Subject: [PATCH 124/418] 8359145: Implement JEP 530: Primitive Types in Patterns, instanceof, and switch (Fourth Preview) Reviewed-by: jlahoda --- .../java/lang/runtime/SwitchBootstraps.java | 2 +- .../com/sun/tools/javac/code/TypeTag.java | 12 +- .../com/sun/tools/javac/code/Types.java | 135 +++++++-- .../com/sun/tools/javac/comp/Attr.java | 2 +- .../com/sun/tools/javac/comp/Check.java | 29 +- .../javac/comp/ExhaustivenessComputer.java | 4 +- .../com/sun/tools/javac/comp/Lower.java | 5 +- .../sun/tools/javac/comp/TransPatterns.java | 2 +- .../tools/javac/patterns/Domination.java | 12 +- .../tools/javac/patterns/DominationWithPP.out | 14 + .../PrimitivePatternsSwitchConstants.java | 71 +++++ .../PrimitivePatternsSwitchConstants.out | 11 + .../PrimitivePatternsSwitchErrors.java | 67 ++++- .../PrimitivePatternsSwitchErrors.out | 11 +- ...veUnconditionallyExactInAssignability.java | 138 +++++++++ ...onditionallyExactInExhaustiveSwitches.java | 280 ++++++++++++++++++ .../tools/javac/patterns/T8332463a.java | 8 +- .../tools/javac/patterns/T8332463a.out | 6 + .../tools/javac/patterns/T8332463b.java | 5 +- .../tools/javac/patterns/T8332463b.out | 2 + .../tools/javac/types/UnknownTypeTest.java | 4 +- .../tools/lib/types/TypeHarness.java | 15 + 22 files changed, 776 insertions(+), 59 deletions(-) create mode 100644 test/langtools/tools/javac/patterns/DominationWithPP.out create mode 100644 test/langtools/tools/javac/patterns/PrimitivePatternsSwitchConstants.java create mode 100644 test/langtools/tools/javac/patterns/PrimitivePatternsSwitchConstants.out create mode 100644 test/langtools/tools/javac/patterns/PrimitiveUnconditionallyExactInAssignability.java create mode 100644 test/langtools/tools/javac/patterns/PrimitiveUnconditionallyExactInExhaustiveSwitches.java create mode 100644 test/langtools/tools/javac/patterns/T8332463a.out create mode 100644 test/langtools/tools/javac/patterns/T8332463b.out diff --git a/src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java b/src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java index 99716baf439..30b6df0073e 100644 --- a/src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java +++ b/src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java @@ -777,7 +777,7 @@ public final class SwitchBootstraps { return name + "$$TypeSwitch"; } - // this method should be in sync with com.sun.tools.javac.code.Types.checkUnconditionallyExactPrimitives + // this method should be in sync with com.sun.tools.javac.code.Types.isUnconditionallyExactTypeBased private static boolean unconditionalExactnessCheck(Class selectorType, Class targetType) { Wrapper selectorWrapper = Wrapper.forBasicType(selectorType); Wrapper targetWrapper = Wrapper.forBasicType(targetType); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/TypeTag.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/TypeTag.java index 0b97b119119..b2771556eb2 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/TypeTag.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/TypeTag.java @@ -26,7 +26,7 @@ package com.sun.tools.javac.code; import com.sun.source.tree.Tree.Kind; - +import java.lang.runtime.ExactConversionsSupport; import javax.lang.model.type.TypeKind; import static com.sun.tools.javac.code.TypeTag.NumericClasses.*; @@ -186,6 +186,10 @@ public enum TypeTag { return (this.numericClass & tag.superClasses) != 0; } + public boolean isNumeric() { + return this.numericClass != 0; + } + /** Returns the number of type tags. */ public static int getTypeTagCount() { @@ -247,11 +251,11 @@ public enum TypeTag { case BOOLEAN: return 0 <= value && value <= 1; case BYTE: - return Byte.MIN_VALUE <= value && value <= Byte.MAX_VALUE; + return ExactConversionsSupport.isIntToByteExact(value); case CHAR: - return Character.MIN_VALUE <= value && value <= Character.MAX_VALUE; + return ExactConversionsSupport.isIntToCharExact(value); case SHORT: - return Short.MIN_VALUE <= value && value <= Short.MAX_VALUE; + return ExactConversionsSupport.isIntToShortExact(value); case INT: return true; default: diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java index d59505555f2..3f3eb1f9623 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java @@ -26,6 +26,7 @@ package com.sun.tools.javac.code; import java.lang.ref.SoftReference; +import java.lang.runtime.ExactConversionsSupport; import java.util.HashSet; import java.util.HashMap; import java.util.Locale; @@ -5086,46 +5087,128 @@ public class Types { } // - // - /** Check unconditionality between any combination of reference or primitive types. + // + /** Check type-based unconditional exactness between any combination of + * reference or primitive types according to JLS 5.7.2. * - * Rules: - * an identity conversion - * a widening reference conversion - * a widening primitive conversion (delegates to `checkUnconditionallyExactPrimitives`) - * a boxing conversion - * a boxing conversion followed by a widening reference conversion + * The following are unconditionally exact regardless of the input + * expression: + * + * - an identity conversion + * - a widening reference conversion + * - an exact widening primitive conversion + * - a boxing conversion + * - a boxing conversion followed by a widening reference conversion * * @param source Source primitive or reference type * @param target Target primitive or reference type */ - public boolean isUnconditionallyExact(Type source, Type target) { + public boolean isUnconditionallyExactTypeBased(Type source, Type target) { if (isSameType(source, target)) { return true; } - return target.isPrimitive() - ? isUnconditionallyExactPrimitives(source, target) - : isSubtype(boxedTypeOrType(erasure(source)), target); + if (target.isPrimitive()) { + if (source.isPrimitive() && + ((source.getTag().isStrictSubRangeOf(target.getTag())) && + !((source.hasTag(BYTE) && target.hasTag(CHAR)) || + (source.hasTag(INT) && target.hasTag(FLOAT)) || + (source.hasTag(LONG) && (target.hasTag(DOUBLE) || target.hasTag(FLOAT)))))) return true; + else { + return false; + } + } else { + return isSubtype(boxedTypeOrType(erasure(source)), target); + } } - /** Check unconditionality between primitive types. + /** Check value-based unconditional exactness between any combination of + * reference or primitive types for the value of a constant expression + * according to JLS 5.7.2. * - * - widening from one integral type to another, - * - widening from one floating point type to another, - * - widening from byte, short, or char to a floating point type, - * - widening from int to double. + * The following can be unconditionally exact if the source primitive is a + * constant expression and the conversions is exact for that constant + * expression: * - * @param selectorType Type of selector - * @param targetType Target type + * - a narrowing primitive conversion + * - a widening and narrowing primitive conversion + * - a widening primitive conversion that is not exact + * + * @param source Source primitive or reference type, should be a numeric value + * @param target Target primitive or reference type */ - public boolean isUnconditionallyExactPrimitives(Type selectorType, Type targetType) { - return isSameType(selectorType, targetType) || - (selectorType.isPrimitive() && targetType.isPrimitive()) && - ((selectorType.getTag().isStrictSubRangeOf(targetType.getTag())) && - !((selectorType.hasTag(BYTE) && targetType.hasTag(CHAR)) || - (selectorType.hasTag(INT) && targetType.hasTag(FLOAT)) || - (selectorType.hasTag(LONG) && (targetType.hasTag(DOUBLE) || targetType.hasTag(FLOAT))))); + public boolean isUnconditionallyExactValueBased(Type source, Type target) { + if (!(source.constValue() instanceof Number value) || !target.getTag().isNumeric()) return false; + + switch (source.getTag()) { + case BYTE: + switch (target.getTag()) { + case CHAR: return ExactConversionsSupport.isIntToCharExact(value.intValue()); + } + break; + case CHAR: + switch (target.getTag()) { + case BYTE: return ExactConversionsSupport.isIntToByteExact(value.intValue()); + case SHORT: return ExactConversionsSupport.isIntToShortExact(value.intValue()); + } + break; + case SHORT: + switch (target.getTag()) { + case BYTE: return ExactConversionsSupport.isIntToByteExact(value.intValue()); + case CHAR: return ExactConversionsSupport.isIntToCharExact(value.intValue()); + } + break; + case INT: + switch (target.getTag()) { + case BYTE: return ExactConversionsSupport.isIntToByteExact(value.intValue()); + case CHAR: return ExactConversionsSupport.isIntToCharExact(value.intValue()); + case SHORT: return ExactConversionsSupport.isIntToShortExact(value.intValue()); + case FLOAT: return ExactConversionsSupport.isIntToFloatExact(value.intValue()); + } + break; + case FLOAT: + switch (target.getTag()) { + case BYTE: return ExactConversionsSupport.isFloatToByteExact(value.floatValue()); + case CHAR: return ExactConversionsSupport.isFloatToCharExact(value.floatValue()); + case SHORT: return ExactConversionsSupport.isFloatToShortExact(value.floatValue()); + case INT: return ExactConversionsSupport.isFloatToIntExact(value.floatValue()); + case LONG: return ExactConversionsSupport.isFloatToLongExact(value.floatValue()); + } + break; + case LONG: + switch (target.getTag()) { + case BYTE: return ExactConversionsSupport.isLongToByteExact(value.longValue()); + case CHAR: return ExactConversionsSupport.isLongToCharExact(value.longValue()); + case SHORT: return ExactConversionsSupport.isLongToShortExact(value.longValue()); + case INT: return ExactConversionsSupport.isLongToIntExact(value.longValue()); + case FLOAT: return ExactConversionsSupport.isLongToFloatExact(value.longValue()); + case DOUBLE: return ExactConversionsSupport.isLongToDoubleExact(value.longValue()); + } + break; + case DOUBLE: + switch (target.getTag()) { + case BYTE: return ExactConversionsSupport.isDoubleToByteExact(value.doubleValue()); + case CHAR: return ExactConversionsSupport.isDoubleToCharExact(value.doubleValue()); + case SHORT: return ExactConversionsSupport.isDoubleToShortExact(value.doubleValue()); + case INT: return ExactConversionsSupport.isDoubleToIntExact(value.doubleValue()); + case FLOAT: return ExactConversionsSupport.isDoubleToFloatExact(value.doubleValue()); + case LONG: return ExactConversionsSupport.isDoubleToLongExact(value.doubleValue()); + } + break; + } + return true; + } + + /** Check both type or value-based unconditional exactness between any + * combination of reference or primitive types for the value of a constant + * expression according to JLS 5.7.2. + * + * @param source Source primitive or reference type, should be a numeric value + * @param target Target primitive or reference type + */ + public boolean isUnconditionallyExactCombined(Type currentType, Type testType) { + return isUnconditionallyExactTypeBased(currentType, testType) || + (currentType.constValue() instanceof Number && isUnconditionallyExactValueBased(currentType, testType)); } // diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java index a45f7466f9e..ad41adcc135 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java @@ -1861,7 +1861,7 @@ public class Attr extends JCTree.Visitor { boolean unconditional = unguarded && !patternType.isErroneous() && - types.isUnconditionallyExact(seltype, patternType); + types.isUnconditionallyExactTypeBased(seltype, patternType); if (unconditional) { if (hasUnconditionalPattern) { log.error(pat.pos(), Errors.DuplicateUnconditionalPattern); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java index 9098568f42a..94b14f3122f 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java @@ -28,7 +28,6 @@ package com.sun.tools.javac.comp; import java.util.*; import java.util.function.BiConsumer; import java.util.function.BiPredicate; -import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.function.ToIntBiFunction; @@ -167,6 +166,7 @@ public class Check { allowModules = Feature.MODULES.allowedInSource(source); allowRecords = Feature.RECORDS.allowedInSource(source); allowSealed = Feature.SEALED_CLASSES.allowedInSource(source); + allowPrimitivePatterns = preview.isEnabled() && Feature.PRIMITIVE_PATTERNS.allowedInSource(source); } /** Character for synthetic names @@ -190,6 +190,10 @@ public class Check { */ private final boolean allowSealed; + /** Are primitive patterns allowed + */ + private final boolean allowPrimitivePatterns; + /** Whether to force suppression of deprecation and preview warnings. * This happens when attributing import statements for JDK 9+. * @see Feature#DEPRECATION_ON_IMPORT @@ -4764,21 +4768,26 @@ public class Check { JCCase testCase = caseAndLabel.fst; JCCaseLabel testCaseLabel = caseAndLabel.snd; Type testType = labelType(testCaseLabel); + + // an unconditional pattern cannot be followed by any other label + if (allowPrimitivePatterns && unconditionalCaseLabel == testCaseLabel && unconditionalCaseLabel != label) { + log.error(label.pos(), Errors.PatternDominated); + continue; + } + boolean dominated = false; - if (types.isUnconditionallyExact(currentType, testType) && - !currentType.hasTag(ERROR) && !testType.hasTag(ERROR)) { - //the current label is potentially dominated by the existing (test) label, check: - if (label instanceof JCConstantCaseLabel) { - dominated |= !(testCaseLabel instanceof JCConstantCaseLabel) && + if (!currentType.hasTag(ERROR) && !testType.hasTag(ERROR)) { + // the current label is potentially dominated by the existing (test) label, check: + if (types.isUnconditionallyExactCombined(currentType, testType) && + label instanceof JCConstantCaseLabel) { + dominated = !(testCaseLabel instanceof JCConstantCaseLabel) && TreeInfo.unguardedCase(testCase); } else if (label instanceof JCPatternCaseLabel patternCL && testCaseLabel instanceof JCPatternCaseLabel testPatternCaseLabel && (testCase.equals(c) || TreeInfo.unguardedCase(testCase))) { - dominated = patternDominated(testPatternCaseLabel.pat, - patternCL.pat); + dominated = patternDominated(testPatternCaseLabel.pat, patternCL.pat); } } - if (dominated) { log.error(label.pos(), Errors.PatternDominated); } @@ -4798,7 +4807,7 @@ public class Check { private boolean patternDominated(JCPattern existingPattern, JCPattern currentPattern) { Type existingPatternType = types.erasure(existingPattern.type); Type currentPatternType = types.erasure(currentPattern.type); - if (!types.isUnconditionallyExact(currentPatternType, existingPatternType)) { + if (!types.isUnconditionallyExactTypeBased(currentPatternType, existingPatternType)) { return false; } if (currentPattern instanceof JCBindingPattern || diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ExhaustivenessComputer.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ExhaustivenessComputer.java index 036e86dff5e..bbc330832f3 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ExhaustivenessComputer.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ExhaustivenessComputer.java @@ -568,8 +568,8 @@ public class ExhaustivenessComputer { Type pattype = types.erasure(bp.type); return seltype.isPrimitive() ? - types.isUnconditionallyExact(seltype, pattype) : - (bp.type.isPrimitive() && types.isUnconditionallyExact(types.unboxedType(seltype), bp.type)) || types.isSubtype(seltype, pattype); + types.isUnconditionallyExactTypeBased(seltype, pattype) : + (bp.type.isPrimitive() && types.isUnconditionallyExactTypeBased(types.unboxedType(seltype), bp.type)) || types.isSubtype(seltype, pattype); } return false; } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java index aee7f0afe39..2db3435a382 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java @@ -36,7 +36,6 @@ import com.sun.tools.javac.jvm.*; import com.sun.tools.javac.jvm.PoolConstant.LoadableConstant; import com.sun.tools.javac.main.Option.PkgInfo; import com.sun.tools.javac.resources.CompilerProperties.Fragments; -import com.sun.tools.javac.resources.CompilerProperties.Notes; import com.sun.tools.javac.tree.*; import com.sun.tools.javac.util.*; import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; @@ -2835,7 +2834,7 @@ public class Lower extends TreeTranslator { JCExpression exactnessCheck; JCExpression instanceOfExpr = translate(tree.expr); - if (types.isUnconditionallyExact(tree.expr.type, tree.pattern.type)) { + if (types.isUnconditionallyExactTypeBased(tree.expr.type, tree.pattern.type)) { // instanceOfExpr; true prefixStatement = make.Exec(instanceOfExpr); exactnessCheck = make.Literal(BOOLEAN, 1).setType(syms.booleanType.constType(1)); @@ -2844,7 +2843,7 @@ public class Lower extends TreeTranslator { prefixStatement = null; exactnessCheck = getExactnessCheck(tree, instanceOfExpr); } else if (tree.expr.type.isReference()) { - if (types.isUnconditionallyExact(types.unboxedType(tree.expr.type), tree.pattern.type)) { + if (types.isUnconditionallyExactTypeBased(types.unboxedType(tree.expr.type), tree.pattern.type)) { // instanceOfExpr != null prefixStatement = null; exactnessCheck = makeBinary(NE, instanceOfExpr, makeNull()); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransPatterns.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransPatterns.java index 58c36a5cf7c..11774c313e3 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransPatterns.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransPatterns.java @@ -921,7 +921,7 @@ public class TransPatterns extends TreeTranslator { JCBindingPattern binding = (JCBindingPattern) instanceofCheck.pattern; hasUnconditional = (!types.erasure(binding.type).isPrimitive() ? instanceofCheck.allowNull : - types.isUnconditionallyExact(commonNestedExpression.type, types.erasure(binding.type))) && + types.isUnconditionallyExactTypeBased(commonNestedExpression.type, types.erasure(binding.type))) && accList.tail.isEmpty(); List newLabel; diff --git a/test/langtools/tools/javac/patterns/Domination.java b/test/langtools/tools/javac/patterns/Domination.java index a5cdb99b9c8..dd9d1153696 100644 --- a/test/langtools/tools/javac/patterns/Domination.java +++ b/test/langtools/tools/javac/patterns/Domination.java @@ -26,8 +26,8 @@ * @bug 8262891 8290709 * @summary Check the pattern domination error are reported correctly. * @compile/fail/ref=Domination.out -XDrawDiagnostics Domination.java + * @compile/fail/ref=DominationWithPP.out --enable-preview --source ${jdk.version} -XDrawDiagnostics Domination.java */ - public class Domination { int testDominatesError1(Object o) { switch (o) { @@ -218,4 +218,14 @@ public class Domination { case null : return -1; } } + + int testCasePatternDominatedbyPreceedingUnconditionalCasePattern () { + interface A {} + interface B {} + A aa = new A() {}; + switch (aa) { + case A a : return 1; + case B b : return -1; + } + } } diff --git a/test/langtools/tools/javac/patterns/DominationWithPP.out b/test/langtools/tools/javac/patterns/DominationWithPP.out new file mode 100644 index 00000000000..119cc003d07 --- /dev/null +++ b/test/langtools/tools/javac/patterns/DominationWithPP.out @@ -0,0 +1,14 @@ +Domination.java:35:18: compiler.err.pattern.dominated +Domination.java:43:18: compiler.err.pattern.dominated +Domination.java:51:18: compiler.err.pattern.dominated +Domination.java:67:18: compiler.err.pattern.dominated +Domination.java:88:18: compiler.err.pattern.dominated +Domination.java:113:18: compiler.err.pattern.dominated +Domination.java:144:18: compiler.err.pattern.dominated +Domination.java:153:18: compiler.err.pattern.dominated +Domination.java:184:18: compiler.err.pattern.dominated +Domination.java:193:18: compiler.err.pattern.dominated +Domination.java:202:18: compiler.err.pattern.dominated +Domination.java:211:18: compiler.err.pattern.dominated +Domination.java:228:18: compiler.err.pattern.dominated +13 errors diff --git a/test/langtools/tools/javac/patterns/PrimitivePatternsSwitchConstants.java b/test/langtools/tools/javac/patterns/PrimitivePatternsSwitchConstants.java new file mode 100644 index 00000000000..2890b315e62 --- /dev/null +++ b/test/langtools/tools/javac/patterns/PrimitivePatternsSwitchConstants.java @@ -0,0 +1,71 @@ +/* + * @test /nodynamiccopyright/ + * @summary Retain exhaustiveness properties of switches with a constant selector + * @enablePreview + * @compile/fail/ref=PrimitivePatternsSwitchConstants.out -XDrawDiagnostics -XDshould-stop.at=FLOW PrimitivePatternsSwitchConstants.java + */ +public class PrimitivePatternsSwitchConstants { + void testConstExpressions() { + switch (42) { // error: not exhaustive + case byte _ : + } + + switch (42l) { // error: not exhaustive + case byte _ : + } + + switch (123456) { // error: not exhaustive + case byte _ : + } + + switch (16_777_216) { // error: not exhaustive + case float _ : + } + + switch (16_777_217) { // error: not exhaustive + case float _ : + } + + switch (42d) { // error: not exhaustive + case float _ : + } + + switch (1) { // OK + case long _ : + } + + final int i = 42; + switch (i) { // OK + case long _ : + } + + switch (1) { // error: non-exhaustive + case Long _ : // error: widening primitive conversion and boxing is not supported + } + + switch (42) { + case byte bb -> {} + case int ii -> {} // OK + }; + + switch (42) { + case 42 -> {} + case int ii -> {} // OK + }; + + switch (42) { + case (byte) 42 -> {} + case int ii -> {} // OK + }; + + switch (42) { + case 42 -> {} + default -> {} // OK + }; + + switch (42) { + default -> {} // OK + case 42 -> {} + }; + } +} diff --git a/test/langtools/tools/javac/patterns/PrimitivePatternsSwitchConstants.out b/test/langtools/tools/javac/patterns/PrimitivePatternsSwitchConstants.out new file mode 100644 index 00000000000..9a983a86437 --- /dev/null +++ b/test/langtools/tools/javac/patterns/PrimitivePatternsSwitchConstants.out @@ -0,0 +1,11 @@ +PrimitivePatternsSwitchConstants.java:43:18: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: int, java.lang.Long) +PrimitivePatternsSwitchConstants.java:9:9: compiler.err.not.exhaustive.statement +PrimitivePatternsSwitchConstants.java:13:9: compiler.err.not.exhaustive.statement +PrimitivePatternsSwitchConstants.java:17:9: compiler.err.not.exhaustive.statement +PrimitivePatternsSwitchConstants.java:21:9: compiler.err.not.exhaustive.statement +PrimitivePatternsSwitchConstants.java:25:9: compiler.err.not.exhaustive.statement +PrimitivePatternsSwitchConstants.java:29:9: compiler.err.not.exhaustive.statement +PrimitivePatternsSwitchConstants.java:42:9: compiler.err.not.exhaustive.statement +- compiler.note.preview.filename: PrimitivePatternsSwitchConstants.java, DEFAULT +- compiler.note.preview.recompile +8 errors diff --git a/test/langtools/tools/javac/patterns/PrimitivePatternsSwitchErrors.java b/test/langtools/tools/javac/patterns/PrimitivePatternsSwitchErrors.java index 3d47c4ac9bc..dacd48f441c 100644 --- a/test/langtools/tools/javac/patterns/PrimitivePatternsSwitchErrors.java +++ b/test/langtools/tools/javac/patterns/PrimitivePatternsSwitchErrors.java @@ -59,7 +59,7 @@ public class PrimitivePatternsSwitchErrors { int i = 42; return switch (i) { case Integer ib -> ib; - case byte ip -> ip; // OK - not dominated! + case byte ip -> ip; // Error - dominated! }; } @@ -265,4 +265,69 @@ public class PrimitivePatternsSwitchErrors { public static boolean wideningReferenceConversionUnboxingAndNarrowingPrimitive(T i) { return i instanceof byte b; // not allowed as a conversion } + + public static void dominanceIntFloat() { + int ii = 42; + switch (ii) { + case int i -> {} + case float f -> {} // Error - dominated! + } + } + + public static void noDominanceIntFloat() { + int ii = 42; + switch (ii) { + case float f -> {} + case int i -> {} // ok + } + } + + public static void strengtheningDominance() { + byte x = 42; + switch (x) { + case short s -> {} + case 42 -> {} // error: dominated + } + + long l = 42l; + switch (l) { + case short s -> {} + case 42l -> {} // error: dominated + case long _ -> {} + } + + char c = 'a'; + switch (c) { + case short s -> {} + case 42 -> {} // error: dominated + case char _ -> {} + } + + int x2 = 42; + switch(x2) { + case float f -> {} + case 16_777_216 -> {} // error: dominated + default -> {} + } + + switch(x2) { + case float f -> {} + case 16_777_217 -> {} // OK + default -> {} + } + + switch(x2) { + case int ii -> {} + case float f -> {} // error: dominated + } + } + + public static void unconditionalFollowedByDefault() { + int ii = 42; + switch (ii) { + case int i -> {} + case float f -> {} // Error - dominated! + default -> {} // Error - unconditional and default + } + } } diff --git a/test/langtools/tools/javac/patterns/PrimitivePatternsSwitchErrors.out b/test/langtools/tools/javac/patterns/PrimitivePatternsSwitchErrors.out index 75fd62016a0..5a0822a9aef 100644 --- a/test/langtools/tools/javac/patterns/PrimitivePatternsSwitchErrors.out +++ b/test/langtools/tools/javac/patterns/PrimitivePatternsSwitchErrors.out @@ -1,6 +1,7 @@ PrimitivePatternsSwitchErrors.java:15:18: compiler.err.pattern.dominated PrimitivePatternsSwitchErrors.java:24:18: compiler.err.pattern.dominated PrimitivePatternsSwitchErrors.java:31:24: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: int, java.lang.Long) +PrimitivePatternsSwitchErrors.java:62:18: compiler.err.pattern.dominated PrimitivePatternsSwitchErrors.java:70:18: compiler.err.pattern.dominated PrimitivePatternsSwitchErrors.java:78:18: compiler.err.pattern.dominated PrimitivePatternsSwitchErrors.java:84:18: compiler.err.prob.found.req: (compiler.misc.possible.loss.of.precision: long, byte) @@ -29,6 +30,14 @@ PrimitivePatternsSwitchErrors.java:248:18: compiler.err.prob.found.req: (compile PrimitivePatternsSwitchErrors.java:255:18: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: java.lang.Long, int) PrimitivePatternsSwitchErrors.java:261:18: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: java.lang.Short, char) PrimitivePatternsSwitchErrors.java:266:16: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: T, byte) +PrimitivePatternsSwitchErrors.java:273:18: compiler.err.pattern.dominated +PrimitivePatternsSwitchErrors.java:289:18: compiler.err.pattern.dominated +PrimitivePatternsSwitchErrors.java:295:18: compiler.err.pattern.dominated +PrimitivePatternsSwitchErrors.java:302:18: compiler.err.pattern.dominated +PrimitivePatternsSwitchErrors.java:309:18: compiler.err.pattern.dominated +PrimitivePatternsSwitchErrors.java:321:18: compiler.err.pattern.dominated +PrimitivePatternsSwitchErrors.java:330:13: compiler.err.unconditional.pattern.and.default +PrimitivePatternsSwitchErrors.java:329:18: compiler.err.pattern.dominated PrimitivePatternsSwitchErrors.java:30:16: compiler.err.not.exhaustive PrimitivePatternsSwitchErrors.java:37:16: compiler.err.not.exhaustive PrimitivePatternsSwitchErrors.java:44:16: compiler.err.not.exhaustive @@ -43,4 +52,4 @@ PrimitivePatternsSwitchErrors.java:254:16: compiler.err.not.exhaustive PrimitivePatternsSwitchErrors.java:260:16: compiler.err.not.exhaustive - compiler.note.preview.filename: PrimitivePatternsSwitchErrors.java, DEFAULT - compiler.note.preview.recompile -43 errors \ No newline at end of file +52 errors diff --git a/test/langtools/tools/javac/patterns/PrimitiveUnconditionallyExactInAssignability.java b/test/langtools/tools/javac/patterns/PrimitiveUnconditionallyExactInAssignability.java new file mode 100644 index 00000000000..c75dff3f8ad --- /dev/null +++ b/test/langtools/tools/javac/patterns/PrimitiveUnconditionallyExactInAssignability.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary Check assignability of narrowing p.c. with constant expressions vs exact conversion methods + * @library /tools/lib/types + * @modules jdk.compiler/com.sun.tools.javac.code + * jdk.compiler/com.sun.tools.javac.comp + * jdk.compiler/com.sun.tools.javac.file + * jdk.compiler/com.sun.tools.javac.util + * jdk.compiler/com.sun.tools.javac.main + * jdk.compiler/com.sun.tools.javac.tree + * @build TypeHarness + * @compile PrimitiveUnconditionallyExactInAssignability.java + * @run main PrimitiveUnconditionallyExactInAssignability + */ + +import com.sun.tools.javac.code.Type; +import com.sun.tools.javac.code.TypeTag; + +import java.lang.runtime.ExactConversionsSupport; + +import static com.sun.tools.javac.code.TypeTag.ERROR; +import static com.sun.tools.javac.code.TypeTag.INT; + +public class PrimitiveUnconditionallyExactInAssignability extends TypeHarness { + PrimitiveUnconditionallyExactInAssignability() { + } + + void assertOriginalAssignmentNarrowingAndUnconditionality() { + // byte b = vs ExactConversionsSupport::isIntToByteExact + assertOriginaAndUpdatedAssignable(fac.Constant(Short.MIN_VALUE), predef.byteType, ExactConversionsSupport.isIntToByteExact(Short.MIN_VALUE)); + assertOriginaAndUpdatedAssignable(fac.Constant((short) (Byte.MIN_VALUE - 1)), predef.byteType, ExactConversionsSupport.isIntToByteExact((short) (Byte.MIN_VALUE - 1))); + assertOriginaAndUpdatedAssignable(fac.Constant((short) (Byte.MAX_VALUE + 1)), predef.byteType, ExactConversionsSupport.isIntToByteExact((short) (Byte.MAX_VALUE + 1))); + assertOriginaAndUpdatedAssignable(fac.Constant(Short.MAX_VALUE), predef.byteType, ExactConversionsSupport.isIntToByteExact(Short.MAX_VALUE)); + + // byte b = vs ExactConversionsSupport::isIntToByteExact + assertOriginaAndUpdatedAssignable(fac.Constant(Character.MIN_VALUE), predef.byteType, ExactConversionsSupport.isIntToByteExact(Character.MIN_VALUE)); + assertOriginaAndUpdatedAssignable(fac.Constant((char) (Byte.MAX_VALUE + 1)), predef.byteType, ExactConversionsSupport.isIntToByteExact((char) (Byte.MAX_VALUE + 1))); + assertOriginaAndUpdatedAssignable(fac.Constant(Character.MAX_VALUE), predef.byteType, ExactConversionsSupport.isIntToByteExact(Character.MAX_VALUE)); + + // byte b = vs ExactConversionsSupport::isIntToByteExact + assertOriginaAndUpdatedAssignable(fac.Constant(Integer.MIN_VALUE), predef.byteType, ExactConversionsSupport.isIntToByteExact(Integer.MIN_VALUE)); + assertOriginaAndUpdatedAssignable(fac.Constant((int) (Byte.MIN_VALUE - 1)), predef.byteType, ExactConversionsSupport.isIntToByteExact((int) (Byte.MIN_VALUE - 1))); + assertOriginaAndUpdatedAssignable(fac.Constant((int) (Byte.MAX_VALUE + 1)), predef.byteType, ExactConversionsSupport.isIntToByteExact((int) (Byte.MAX_VALUE + 1))); + assertOriginaAndUpdatedAssignable(fac.Constant(Integer.MAX_VALUE), predef.byteType, ExactConversionsSupport.isIntToByteExact(Integer.MAX_VALUE)); + + // char c = vs ExactConversionsSupport::isIntToCharExact + assertOriginaAndUpdatedAssignable(fac.Constant(Short.MIN_VALUE), predef.charType, ExactConversionsSupport.isIntToCharExact(Short.MIN_VALUE)); + assertOriginaAndUpdatedAssignable(fac.Constant((short) (Character.MIN_VALUE - 1)), predef.charType, ExactConversionsSupport.isIntToCharExact((short) (Character.MIN_VALUE - 1))); + assertOriginaAndUpdatedAssignable(fac.Constant((short) (Character.MAX_VALUE + 1)), predef.charType, ExactConversionsSupport.isIntToCharExact((short) (Character.MIN_VALUE + 1))); + assertOriginaAndUpdatedAssignable(fac.Constant(Short.MAX_VALUE), predef.charType, ExactConversionsSupport.isIntToCharExact(Short.MAX_VALUE)); + + // char c = vs ExactConversionsSupport::isIntToCharExact + assertOriginaAndUpdatedAssignable(fac.Constant(Integer.MIN_VALUE), predef.charType, ExactConversionsSupport.isIntToCharExact(Integer.MIN_VALUE)); + assertOriginaAndUpdatedAssignable(fac.Constant((int) (Character.MIN_VALUE - 1)), predef.charType, ExactConversionsSupport.isIntToCharExact((int) (Character.MIN_VALUE - 1))); + assertOriginaAndUpdatedAssignable(fac.Constant((int) (Character.MAX_VALUE + 1)), predef.charType, ExactConversionsSupport.isIntToCharExact((int) (Character.MAX_VALUE + 1))); + assertOriginaAndUpdatedAssignable(fac.Constant(Integer.MAX_VALUE), predef.charType, ExactConversionsSupport.isIntToCharExact(Integer.MAX_VALUE)); + + // short b = vs ExactConversionsSupport::isIntToShortExact + assertOriginaAndUpdatedAssignable(fac.Constant(Character.MIN_VALUE), predef.shortType, ExactConversionsSupport.isIntToShortExact(Character.MIN_VALUE)); + assertOriginaAndUpdatedAssignable(fac.Constant((char) (Character.MAX_VALUE + 1)), predef.shortType, ExactConversionsSupport.isIntToShortExact((char) (Character.MAX_VALUE + 1))); + assertOriginaAndUpdatedAssignable(fac.Constant(Character.MAX_VALUE), predef.shortType, ExactConversionsSupport.isIntToShortExact(Character.MAX_VALUE)); + + // short b = vs ExactConversionsSupport::isIntToShortExact + assertOriginaAndUpdatedAssignable(fac.Constant(Integer.MIN_VALUE), predef.shortType, ExactConversionsSupport.isIntToShortExact(Integer.MIN_VALUE)); + assertOriginaAndUpdatedAssignable(fac.Constant((int) (Short.MIN_VALUE - 1)), predef.shortType, ExactConversionsSupport.isIntToShortExact((int) (Short.MIN_VALUE - 1))); + assertOriginaAndUpdatedAssignable(fac.Constant((int) (Short.MAX_VALUE + 1)), predef.shortType, ExactConversionsSupport.isIntToShortExact((int) (Short.MAX_VALUE + 1))); + assertOriginaAndUpdatedAssignable(fac.Constant(Integer.MAX_VALUE), predef.shortType, ExactConversionsSupport.isIntToShortExact(Integer.MAX_VALUE)); + } + // where + public void assertOriginaAndUpdatedAssignable(Type s, Type t, boolean expected) { + assertAssignable(s, t, originalIsAssignable(s, t)); + } + public boolean originalIsAssignable(Type t, Type s) { + if (t.hasTag(ERROR)) + return true; + if (t.getTag().isSubRangeOf(INT) && t.constValue() != null) { + int value = ((Number)t.constValue()).intValue(); + switch (s.getTag()) { + case BYTE: + case CHAR: + case SHORT: + case INT: + if (originalCheckRange(s.getTag(), value)) + return true; + break; + } + } + return types.isConvertible(t, s); + } + public boolean originalCheckRange(TypeTag that, int value) { + switch (that) { + case BOOLEAN: + return 0 <= value && value <= 1; + case BYTE: + return Byte.MIN_VALUE <= value && value <= Byte.MAX_VALUE; + case CHAR: + return Character.MIN_VALUE <= value && value <= Character.MAX_VALUE; + case SHORT: + return Short.MIN_VALUE <= value && value <= Short.MAX_VALUE; + case INT: + return true; + default: + throw new AssertionError(); + } + } + + private void error(String msg) { + throw new AssertionError("Unexpected result in original isAssignable: " + msg); + } + + public static void main(String[] args) { + PrimitiveUnconditionallyExactInAssignability harness = new PrimitiveUnconditionallyExactInAssignability(); + harness.assertOriginalAssignmentNarrowingAndUnconditionality(); + } +} diff --git a/test/langtools/tools/javac/patterns/PrimitiveUnconditionallyExactInExhaustiveSwitches.java b/test/langtools/tools/javac/patterns/PrimitiveUnconditionallyExactInExhaustiveSwitches.java new file mode 100644 index 00000000000..e6a204968dd --- /dev/null +++ b/test/langtools/tools/javac/patterns/PrimitiveUnconditionallyExactInExhaustiveSwitches.java @@ -0,0 +1,280 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary Check the unconditionally exact for constant primitives used in the exhaustiveness check + * @library /tools/lib/types + * @modules jdk.compiler/com.sun.tools.javac.code + * jdk.compiler/com.sun.tools.javac.comp + * jdk.compiler/com.sun.tools.javac.file + * jdk.compiler/com.sun.tools.javac.util + * jdk.compiler/com.sun.tools.javac.main + * jdk.compiler/com.sun.tools.javac.tree + * @build TypeHarness + * @compile PrimitiveUnconditionallyExactInExhaustiveSwitches.java + * @run main PrimitiveUnconditionallyExactInExhaustiveSwitches + */ + +public class PrimitiveUnconditionallyExactInExhaustiveSwitches extends TypeHarness { + + PrimitiveUnconditionallyExactInExhaustiveSwitches() { + } + public void testByte() { + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((byte) (Byte.MAX_VALUE))), predef.byteType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((byte) (0))),predef.byteType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((byte) (Byte.MIN_VALUE))),predef.byteType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((short) (Short.MAX_VALUE))),predef.byteType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((short) (0))),predef.byteType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((short) (Short.MIN_VALUE))),predef.byteType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((char) (Character.MAX_VALUE))),predef.byteType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((char) (Character.MIN_VALUE))),predef.byteType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Integer.MAX_VALUE)),predef.byteType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((0)),predef.byteType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Integer.MIN_VALUE)),predef.byteType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Long.MAX_VALUE)),predef.byteType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((0L)),predef.byteType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Long.MIN_VALUE)),predef.byteType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.MAX_VALUE)),predef.byteType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((float) 0)),predef.byteType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.MIN_VALUE)),predef.byteType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.NaN)),predef.byteType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.POSITIVE_INFINITY)),predef.byteType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.NEGATIVE_INFINITY)),predef.byteType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((-0.0f)),predef.byteType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((+0.0f)),predef.byteType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.MAX_VALUE)),predef.byteType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((double) 0)),predef.byteType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.MIN_VALUE)),predef.byteType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.NaN)),predef.byteType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.POSITIVE_INFINITY)),predef.byteType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.NEGATIVE_INFINITY)),predef.byteType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((-0.0d)),predef.byteType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((+0.0d)),predef.byteType, true); + } + public void testShort() { + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((byte) (Byte.MAX_VALUE))),predef.shortType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((byte) (0))),predef.shortType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((byte) (Byte.MIN_VALUE))),predef.shortType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((short) (Short.MAX_VALUE))),predef.shortType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((short) (0))),predef.shortType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((short) (Short.MIN_VALUE))),predef.shortType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((char) (Character.MAX_VALUE))),predef.shortType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((char) (Character.MIN_VALUE))),predef.shortType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Integer.MAX_VALUE)),predef.shortType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((0)),predef.shortType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Integer.MIN_VALUE)),predef.shortType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Long.MAX_VALUE)),predef.shortType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((0L)),predef.shortType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Long.MIN_VALUE)),predef.shortType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.MAX_VALUE)),predef.shortType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((float) 0)),predef.shortType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.MIN_VALUE)),predef.shortType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.MIN_VALUE)),predef.shortType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.NaN)),predef.shortType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.POSITIVE_INFINITY)),predef.shortType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.NEGATIVE_INFINITY)),predef.shortType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((-0.0f)),predef.shortType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((+0.0f)),predef.shortType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.MAX_VALUE)),predef.shortType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((double) 0)),predef.shortType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.MIN_VALUE)),predef.shortType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.NaN)),predef.shortType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.POSITIVE_INFINITY)),predef.shortType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.NEGATIVE_INFINITY)),predef.shortType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((-0.0d)),predef.shortType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((+0.0d)),predef.shortType, true); + } + public void testChar() { + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((byte) (Byte.MAX_VALUE))),predef.charType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((byte) (0))),predef.charType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((byte) (Byte.MIN_VALUE))),predef.charType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((short) (Short.MAX_VALUE))),predef.charType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((short) (0))),predef.charType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((short) (Short.MIN_VALUE))),predef.charType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((char) (Character.MAX_VALUE))),predef.charType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((char) (Character.MIN_VALUE))),predef.charType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Integer.MAX_VALUE)),predef.charType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((0)),predef.charType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Integer.MIN_VALUE)),predef.charType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Long.MAX_VALUE)),predef.charType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((0L)),predef.charType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Long.MIN_VALUE)),predef.charType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.MAX_VALUE)),predef.charType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((float) 0)),predef.charType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.MIN_VALUE)),predef.charType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.NaN)),predef.charType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.POSITIVE_INFINITY)),predef.charType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.NEGATIVE_INFINITY)),predef.charType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((-0.0f)),predef.charType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((+0.0f)),predef.charType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.MAX_VALUE)),predef.charType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((double) 0)),predef.charType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.MIN_VALUE)),predef.charType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.NaN)),predef.charType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.POSITIVE_INFINITY)),predef.charType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.NEGATIVE_INFINITY)),predef.charType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((-0.0d)),predef.charType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((+0.0d)),predef.charType, true); + } + public void testInt() { + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((byte) (Byte.MAX_VALUE))),predef.intType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((byte) (0))),predef.intType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((byte) (Byte.MIN_VALUE))),predef.intType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((short) (Short.MAX_VALUE))),predef.intType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((short) (0))),predef.intType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((short) (Short.MIN_VALUE))),predef.intType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((char) (Character.MAX_VALUE))),predef.intType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((char) (Character.MIN_VALUE))),predef.intType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Integer.MAX_VALUE)),predef.intType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((0)),predef.intType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Integer.MIN_VALUE)),predef.intType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Long.MAX_VALUE)),predef.intType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((0L)),predef.intType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Long.MIN_VALUE)),predef.intType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.MAX_VALUE)),predef.intType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((float) 0)),predef.intType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.MIN_VALUE)),predef.intType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.NaN)),predef.intType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.POSITIVE_INFINITY)),predef.intType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.NEGATIVE_INFINITY)),predef.intType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((-0.0f)),predef.intType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((+0.0f)),predef.intType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.MAX_VALUE)),predef.intType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((double) 0)),predef.intType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.MIN_VALUE)),predef.intType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.NaN)),predef.intType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.POSITIVE_INFINITY)),predef.intType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.NEGATIVE_INFINITY)),predef.intType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((-0.0d)),predef.intType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((+0.0d)),predef.intType, true); + } + public void testLong() { + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((byte) (Byte.MAX_VALUE))),predef.longType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((byte) (0))),predef.longType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((byte) (Byte.MIN_VALUE))),predef.longType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((short) (Short.MAX_VALUE))),predef.longType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((short) (0))),predef.longType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((short) (Short.MIN_VALUE))),predef.longType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((char) (Character.MAX_VALUE))),predef.longType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((char) (Character.MIN_VALUE))),predef.longType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Integer.MAX_VALUE)),predef.longType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((0L)),predef.longType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Integer.MIN_VALUE)),predef.longType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Long.MAX_VALUE)),predef.longType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((0)),predef.longType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Long.MIN_VALUE)),predef.longType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.MAX_VALUE)),predef.longType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((float) 0)),predef.longType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.MIN_VALUE)),predef.longType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.NaN)),predef.longType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.POSITIVE_INFINITY)),predef.longType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.NEGATIVE_INFINITY)),predef.longType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((-0.0f)),predef.longType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((+0.0f)),predef.longType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.MAX_VALUE)),predef.longType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((double) 0)),predef.longType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.MIN_VALUE)),predef.longType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.NaN)),predef.longType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.POSITIVE_INFINITY)),predef.longType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.NEGATIVE_INFINITY)),predef.longType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((-0.0d)),predef.longType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((+0.0d)),predef.longType, true); + } + public void testFloat() { + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((byte) (Byte.MAX_VALUE))),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((byte) (0)),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((byte) (Byte.MIN_VALUE))),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((short) (Short.MAX_VALUE))),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((short) (0))),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((short) (Short.MIN_VALUE))),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((char) (Character.MAX_VALUE))),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((char) (Character.MIN_VALUE))),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Integer.MAX_VALUE)),predef.floatType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((0)),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Integer.MIN_VALUE)),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Long.MAX_VALUE)),predef.floatType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((0L)),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Long.MIN_VALUE)),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.MAX_VALUE)),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((float) 0)),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.MIN_VALUE)),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.NaN)),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.POSITIVE_INFINITY)),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.NEGATIVE_INFINITY)),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((-0.0f)),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((+0.0f)),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.MAX_VALUE)),predef.floatType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((double) 0)),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.MIN_VALUE)),predef.floatType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.NaN)),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.POSITIVE_INFINITY)),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.NEGATIVE_INFINITY)),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((-0.0d)),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((+0.0d)),predef.floatType, true); + } + public void testDouble() { + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((byte) (Byte.MAX_VALUE))),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((byte) (0))),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((byte) (Byte.MIN_VALUE))),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((short) (Short.MAX_VALUE))),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((short) (0))),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((short) (Short.MIN_VALUE))),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((char) (Character.MAX_VALUE))),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((char) (Character.MIN_VALUE))),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Integer.MAX_VALUE)),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((0)),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Integer.MIN_VALUE)),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Long.MAX_VALUE)),predef.doubleType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((0L)),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Long.MIN_VALUE)),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.MAX_VALUE)),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((float) 0)),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.MIN_VALUE)),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.NaN)),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.POSITIVE_INFINITY)),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.NEGATIVE_INFINITY)),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((-0.0f)),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((+0.0f)),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.MAX_VALUE)),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((double) 0)),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.MIN_VALUE)),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.NaN)),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.POSITIVE_INFINITY)),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.NEGATIVE_INFINITY)),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((-0.0d)),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((+0.0d)),predef.doubleType, true); + } + + + public static void main(String[] args) { + PrimitiveUnconditionallyExactInExhaustiveSwitches harness = new PrimitiveUnconditionallyExactInExhaustiveSwitches(); + harness.testByte(); + harness.testShort(); + harness.testChar(); + harness.testInt(); + harness.testDouble(); + harness.testLong(); + harness.testFloat(); + } +} diff --git a/test/langtools/tools/javac/patterns/T8332463a.java b/test/langtools/tools/javac/patterns/T8332463a.java index 96aaad86a85..2501a330af2 100644 --- a/test/langtools/tools/javac/patterns/T8332463a.java +++ b/test/langtools/tools/javac/patterns/T8332463a.java @@ -25,14 +25,14 @@ * @test * @bug 8332463 * @summary Byte conditional pattern case element dominates short constant case element - * @compile --enable-preview --source ${jdk.version} T8332463a.java + * @compile/fail/ref=T8332463a.out -XDrawDiagnostics --enable-preview --source ${jdk.version} T8332463a.java */ public class T8332463a { public int test2() { Byte i = (byte) 42; return switch (i) { case Byte ib -> 1; - case short s -> 2; + case short s -> 2; // dominated }; } @@ -40,7 +40,7 @@ public class T8332463a { int i = 42; return switch (i) { case Integer ib -> 1; - case byte ip -> 2; + case byte ip -> 2; // dominated }; } @@ -48,7 +48,7 @@ public class T8332463a { int i = 42; return switch (i) { case Integer ib -> 1; - case (byte) 0 -> 2; + case (byte) 0 -> 2; // dominated }; } } diff --git a/test/langtools/tools/javac/patterns/T8332463a.out b/test/langtools/tools/javac/patterns/T8332463a.out new file mode 100644 index 00000000000..5a4eec46d82 --- /dev/null +++ b/test/langtools/tools/javac/patterns/T8332463a.out @@ -0,0 +1,6 @@ +T8332463a.java:35:18: compiler.err.pattern.dominated +T8332463a.java:43:18: compiler.err.pattern.dominated +T8332463a.java:51:18: compiler.err.pattern.dominated +- compiler.note.preview.filename: T8332463a.java, DEFAULT +- compiler.note.preview.recompile +3 errors diff --git a/test/langtools/tools/javac/patterns/T8332463b.java b/test/langtools/tools/javac/patterns/T8332463b.java index 7956fd31f9f..73338b2bd8a 100644 --- a/test/langtools/tools/javac/patterns/T8332463b.java +++ b/test/langtools/tools/javac/patterns/T8332463b.java @@ -26,15 +26,14 @@ * @bug 8332463 * @summary Byte conditional pattern case element dominates short constant case element * @enablePreview - * @compile T8332463b.java - * @compile --enable-preview --source ${jdk.version} T8332463b.java + * @compile/fail/ref=T8332463b.out -XDrawDiagnostics --enable-preview --source ${jdk.version} T8332463b.java */ public class T8332463b { public int test1() { Byte i = (byte) 42; return switch (i) { case Byte ib -> 1; - case (short) 0 -> 2; + case (short) 0 -> 2; // dominated }; } } diff --git a/test/langtools/tools/javac/patterns/T8332463b.out b/test/langtools/tools/javac/patterns/T8332463b.out new file mode 100644 index 00000000000..f912242a6c6 --- /dev/null +++ b/test/langtools/tools/javac/patterns/T8332463b.out @@ -0,0 +1,2 @@ +T8332463b.java:36:18: compiler.err.pattern.dominated +1 error diff --git a/test/langtools/tools/javac/types/UnknownTypeTest.java b/test/langtools/tools/javac/types/UnknownTypeTest.java index edb4ab3a9be..e79c583f142 100644 --- a/test/langtools/tools/javac/types/UnknownTypeTest.java +++ b/test/langtools/tools/javac/types/UnknownTypeTest.java @@ -73,7 +73,9 @@ public class UnknownTypeTest extends TypeHarness { types::isSameType, types::isSubtype, types::isSuperType, - types::isUnconditionallyExact, + types::isUnconditionallyExactValueBased, + types::isUnconditionallyExactTypeBased, + types::isUnconditionallyExactCombined, (t1, _) -> types.isArray(t1), (t1, _) -> types.isDerivedRaw(t1), (t1, _) -> types.isReifiable(t1), diff --git a/test/langtools/tools/lib/types/TypeHarness.java b/test/langtools/tools/lib/types/TypeHarness.java index 43bf2f961e9..296ea8d3f9b 100644 --- a/test/langtools/tools/lib/types/TypeHarness.java +++ b/test/langtools/tools/lib/types/TypeHarness.java @@ -188,6 +188,21 @@ public class TypeHarness { } } + /** assert that 's' is unconditionally exact to 't' */ + public void assertIsUnconditionallyExactConstantPrimitives(Type s, Type t) { + assertIsUnconditionallyExactConstantPrimitives(s, t, true); + } + + /** assert that 's' is/is not unconditionally exact to 't' */ + public void assertIsUnconditionallyExactConstantPrimitives(Type s, Type t, boolean expected) { + if (types.isUnconditionallyExactValueBased(s, t) != expected) { + String msg = expected ? + " is not unconditionally exact to " : + " is unconditionally exact to "; + error(s + msg + t); + } + } + /** assert that generic type 't' is well-formed */ public void assertValidGenericType(Type t) { assertValidGenericType(t, true); From 54893dc5c2a4702896029b1844bc9496325c8f26 Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Wed, 19 Nov 2025 11:46:43 +0000 Subject: [PATCH 125/418] 8371985: Parallel: Move should_attempt_scavenge to ParallelScavengeHeap Reviewed-by: fandreuzzi, iwalulya --- .../gc/parallel/parallelScavengeHeap.cpp | 60 ++++++++++++++++-- .../gc/parallel/parallelScavengeHeap.hpp | 3 + src/hotspot/share/gc/parallel/psScavenge.cpp | 62 +------------------ src/hotspot/share/gc/parallel/psScavenge.hpp | 2 - 4 files changed, 59 insertions(+), 68 deletions(-) diff --git a/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp b/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp index ebaea3ecba4..747e2f3228c 100644 --- a/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp +++ b/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp @@ -370,6 +370,55 @@ void ParallelScavengeHeap::do_full_collection(bool clear_all_soft_refs) { PSParallelCompact::invoke(clear_all_soft_refs, should_do_max_compaction); } +bool ParallelScavengeHeap::should_attempt_young_gc() const { + const bool ShouldRunYoungGC = true; + const bool ShouldRunFullGC = false; + + if (!_young_gen->to_space()->is_empty()) { + log_debug(gc, ergo)("To-space is not empty; run full-gc instead."); + return ShouldRunFullGC; + } + + // Check if the predicted promoted bytes will overflow free space in old-gen. + PSAdaptiveSizePolicy* policy = _size_policy; + + size_t avg_promoted = (size_t) policy->padded_average_promoted_in_bytes(); + size_t promotion_estimate = MIN2(avg_promoted, _young_gen->used_in_bytes()); + // Total free size after possible old gen expansion + size_t free_in_old_gen_with_expansion = _old_gen->max_gen_size() - _old_gen->used_in_bytes(); + + log_trace(gc, ergo)("average_promoted %zu; padded_average_promoted %zu", + (size_t) policy->average_promoted_in_bytes(), + (size_t) policy->padded_average_promoted_in_bytes()); + + if (promotion_estimate >= free_in_old_gen_with_expansion) { + log_debug(gc, ergo)("Run full-gc; predicted promotion size >= max free space in old-gen: %zu >= %zu", + promotion_estimate, free_in_old_gen_with_expansion); + return ShouldRunFullGC; + } + + if (UseAdaptiveSizePolicy) { + // Also checking OS has enough free memory to commit and expand old-gen. + // Otherwise, the recorded gc-pause-time might be inflated to include time + // of OS preparing free memory, resulting in inaccurate young-gen resizing. + assert(_old_gen->committed().byte_size() >= _old_gen->used_in_bytes(), "inv"); + // Use uint64_t instead of size_t for 32bit compatibility. + uint64_t free_mem_in_os; + if (os::free_memory(free_mem_in_os)) { + size_t actual_free = (size_t)MIN2(_old_gen->committed().byte_size() - _old_gen->used_in_bytes() + free_mem_in_os, + (uint64_t)SIZE_MAX); + if (promotion_estimate > actual_free) { + log_debug(gc, ergo)("Run full-gc; predicted promotion size > free space in old-gen and OS: %zu > %zu", + promotion_estimate, actual_free); + return ShouldRunFullGC; + } + } + } + + // No particular reasons to run full-gc, so young-gc. + return ShouldRunYoungGC; +} + static bool check_gc_heap_free_limit(size_t free_bytes, size_t capacity_bytes) { return (free_bytes * 100 / capacity_bytes) < GCHeapFreeLimit; } @@ -516,17 +565,18 @@ void ParallelScavengeHeap::collect(GCCause::Cause cause) { VMThread::execute(&op); } -void ParallelScavengeHeap::collect_at_safepoint(bool full) { +void ParallelScavengeHeap::collect_at_safepoint(bool is_full) { assert(!GCLocker::is_active(), "precondition"); bool clear_soft_refs = GCCause::should_clear_all_soft_refs(_gc_cause); - if (!full) { - bool success = PSScavenge::invoke(clear_soft_refs); - if (success) { + if (!is_full && should_attempt_young_gc()) { + bool young_gc_success = PSScavenge::invoke(clear_soft_refs); + if (young_gc_success) { return; } - // Upgrade to Full-GC if young-gc fails + log_debug(gc, heap)("Upgrade to Full-GC since Young-gc failed."); } + const bool should_do_max_compaction = false; PSParallelCompact::invoke(clear_soft_refs, should_do_max_compaction); } diff --git a/src/hotspot/share/gc/parallel/parallelScavengeHeap.hpp b/src/hotspot/share/gc/parallel/parallelScavengeHeap.hpp index f9161afc28f..0221fd2a90e 100644 --- a/src/hotspot/share/gc/parallel/parallelScavengeHeap.hpp +++ b/src/hotspot/share/gc/parallel/parallelScavengeHeap.hpp @@ -119,6 +119,9 @@ class ParallelScavengeHeap : public CollectedHeap { void print_tracing_info() const override; void stop() override {}; + // Returns true if a young GC should be attempted, false if a full GC is preferred. + bool should_attempt_young_gc() const; + public: ParallelScavengeHeap() : CollectedHeap(), diff --git a/src/hotspot/share/gc/parallel/psScavenge.cpp b/src/hotspot/share/gc/parallel/psScavenge.cpp index e738a13d464..d1d595df529 100644 --- a/src/hotspot/share/gc/parallel/psScavenge.cpp +++ b/src/hotspot/share/gc/parallel/psScavenge.cpp @@ -313,12 +313,6 @@ bool PSScavenge::invoke(bool clear_soft_refs) { assert(SafepointSynchronize::is_at_safepoint(), "should be at safepoint"); assert(Thread::current() == (Thread*)VMThread::vm_thread(), "should be in vm thread"); - // Check for potential problems. - if (!should_attempt_scavenge()) { - log_info(gc, ergo)("Young-gc might fail so skipping"); - return false; - } - IsSTWGCActiveMark mark; _gc_timer.register_gc_start(); @@ -336,8 +330,7 @@ bool PSScavenge::invoke(bool clear_soft_refs) { PSOldGen* old_gen = heap->old_gen(); PSAdaptiveSizePolicy* size_policy = heap->size_policy(); - assert(young_gen->to_space()->is_empty(), - "Attempt to scavenge with live objects in to_space"); + assert(young_gen->to_space()->is_empty(), "precondition"); heap->increment_total_collections(); @@ -520,59 +513,6 @@ void PSScavenge::clean_up_failed_promotion() { NOT_PRODUCT(ParallelScavengeHeap::heap()->reset_promotion_should_fail();) } -bool PSScavenge::should_attempt_scavenge() { - const bool ShouldRunYoungGC = true; - const bool ShouldRunFullGC = false; - - ParallelScavengeHeap* heap = ParallelScavengeHeap::heap(); - PSYoungGen* young_gen = heap->young_gen(); - PSOldGen* old_gen = heap->old_gen(); - - if (!young_gen->to_space()->is_empty()) { - log_debug(gc, ergo)("To-space is not empty; run full-gc instead."); - return ShouldRunFullGC; - } - - // Check if the predicted promoted bytes will overflow free space in old-gen. - PSAdaptiveSizePolicy* policy = heap->size_policy(); - - size_t avg_promoted = (size_t) policy->padded_average_promoted_in_bytes(); - size_t promotion_estimate = MIN2(avg_promoted, young_gen->used_in_bytes()); - // Total free size after possible old gen expansion - size_t free_in_old_gen_with_expansion = old_gen->max_gen_size() - old_gen->used_in_bytes(); - - log_trace(gc, ergo)("average_promoted %zu; padded_average_promoted %zu", - (size_t) policy->average_promoted_in_bytes(), - (size_t) policy->padded_average_promoted_in_bytes()); - - if (promotion_estimate >= free_in_old_gen_with_expansion) { - log_debug(gc, ergo)("Run full-gc; predicted promotion size >= max free space in old-gen: %zu >= %zu", - promotion_estimate, free_in_old_gen_with_expansion); - return ShouldRunFullGC; - } - - if (UseAdaptiveSizePolicy) { - // Also checking OS has enough free memory to commit and expand old-gen. - // Otherwise, the recorded gc-pause-time might be inflated to include time - // of OS preparing free memory, resulting in inaccurate young-gen resizing. - assert(old_gen->committed().byte_size() >= old_gen->used_in_bytes(), "inv"); - // Use uint64_t instead of size_t for 32bit compatibility. - uint64_t free_mem_in_os; - if (os::free_memory(free_mem_in_os)) { - size_t actual_free = (size_t)MIN2(old_gen->committed().byte_size() - old_gen->used_in_bytes() + free_mem_in_os, - (uint64_t)SIZE_MAX); - if (promotion_estimate > actual_free) { - log_debug(gc, ergo)("Run full-gc; predicted promotion size > free space in old-gen and OS: %zu > %zu", - promotion_estimate, actual_free); - return ShouldRunFullGC; - } - } - } - - // No particular reasons to run full-gc, so young-gc. - return ShouldRunYoungGC; -} - // Adaptive size policy support. void PSScavenge::set_young_generation_boundary(HeapWord* v) { _young_generation_boundary = v; diff --git a/src/hotspot/share/gc/parallel/psScavenge.hpp b/src/hotspot/share/gc/parallel/psScavenge.hpp index c297a46a46e..af9b91f74bc 100644 --- a/src/hotspot/share/gc/parallel/psScavenge.hpp +++ b/src/hotspot/share/gc/parallel/psScavenge.hpp @@ -64,8 +64,6 @@ class PSScavenge: AllStatic { static void clean_up_failed_promotion(); - static bool should_attempt_scavenge(); - // Private accessors static PSCardTable* card_table() { assert(_card_table != nullptr, "Sanity"); return _card_table; } static const ParallelScavengeTracer* gc_tracer() { return &_gc_tracer; } From d2926dfd9a242928877d0b1e40eac498073975bd Mon Sep 17 00:00:00 2001 From: Evgeny Astigeevich Date: Wed, 19 Nov 2025 12:11:23 +0000 Subject: [PATCH 126/418] 8371649: ZGC: AArch64: redundant OrderAccess::fence in ZBarrierSetAssembler::patch_barrier_relocation Reviewed-by: aph --- src/hotspot/cpu/aarch64/gc/z/zBarrierSetAssembler_aarch64.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/hotspot/cpu/aarch64/gc/z/zBarrierSetAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/gc/z/zBarrierSetAssembler_aarch64.cpp index 5d4f0801ec6..07a2d6fbfa0 100644 --- a/src/hotspot/cpu/aarch64/gc/z/zBarrierSetAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/gc/z/zBarrierSetAssembler_aarch64.cpp @@ -879,7 +879,6 @@ void ZBarrierSetAssembler::patch_barrier_relocation(address addr, int format) { ShouldNotReachHere(); } - OrderAccess::fence(); ICache::invalidate_word((address)patch_addr); } From 0b3df489e9d3b6d876a67793e082b930c17ade3e Mon Sep 17 00:00:00 2001 From: Renjith Kannath Pariyangad Date: Wed, 19 Nov 2025 12:13:37 +0000 Subject: [PATCH 127/418] 8372048: Performance improvement on Linux remote desktop Reviewed-by: azvegint, serb --- .../sun/awt/screencast/ScreencastHelper.java | 8 +++--- .../sun/awt/screencast/TokenStorage.java | 25 ++++++++++++++++--- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/java.desktop/unix/classes/sun/awt/screencast/ScreencastHelper.java b/src/java.desktop/unix/classes/sun/awt/screencast/ScreencastHelper.java index 33af39810d5..a8f7cd41a0e 100644 --- a/src/java.desktop/unix/classes/sun/awt/screencast/ScreencastHelper.java +++ b/src/java.desktop/unix/classes/sun/awt/screencast/ScreencastHelper.java @@ -63,9 +63,11 @@ public final class ScreencastHelper { private static final int DELAY_BEFORE_SESSION_CLOSE = 2000; private static volatile TimerTask timerTask = null; - private static final Timer timerCloseSession - = new Timer("auto-close screencast session", true); + private static class TimerHolder { + private static final Timer timerCloseSession = + new Timer("auto-close screencast session", true); + } private ScreencastHelper() {} @@ -143,7 +145,7 @@ public final class ScreencastHelper { } }; - timerCloseSession.schedule(timerTask, DELAY_BEFORE_SESSION_CLOSE); + TimerHolder.timerCloseSession.schedule(timerTask, DELAY_BEFORE_SESSION_CLOSE); } public static synchronized void getRGBPixels( diff --git a/src/java.desktop/unix/classes/sun/awt/screencast/TokenStorage.java b/src/java.desktop/unix/classes/sun/awt/screencast/TokenStorage.java index 9db64725048..09dc84e74d0 100644 --- a/src/java.desktop/unix/classes/sun/awt/screencast/TokenStorage.java +++ b/src/java.desktop/unix/classes/sun/awt/screencast/TokenStorage.java @@ -238,6 +238,7 @@ final class TokenStorage { } private static WatchService watchService; + private static volatile boolean isWatcherThreadStarted = false; private static void setupWatch() { try { @@ -257,10 +258,6 @@ final class TokenStorage { "file watch %s\n", e); } } - - if (watchService != null) { - new WatcherThread(watchService).start(); - } } // called from native @@ -337,7 +334,27 @@ final class TokenStorage { return true; } + private static void startWatcherThreadIfNeeded() { + if (!isWatcherThreadStarted) { + // not sure if the double-checked locking is actually needed here + // the getTokens is only called from ScreencastHelper#getRGBPixels + // and ScreencastHelper#remoteDesktop* methods (which are synchronized), + // but it may change later. + synchronized (TokenStorage.class) { + if (!isWatcherThreadStarted) { + readTokens(PROPS_PATH); + if (watchService != null) { + new WatcherThread(watchService).start(); + } + isWatcherThreadStarted = true; + } + } + } + } + static Set getTokens(List affectedScreenBounds) { + startWatcherThreadIfNeeded(); + // We need an ordered set to store tokens // with exact matches at the beginning. LinkedHashSet result = new LinkedHashSet<>(); From ae4d9c2e6af0b899481c98742f4976c7769f39e5 Mon Sep 17 00:00:00 2001 From: Kurt Miller Date: Wed, 19 Nov 2025 12:14:07 +0000 Subject: [PATCH 128/418] 8371918: aarch64: Incorrect pointer dereference in TemplateInterpreterGenerator::generate_native_entry Reviewed-by: aph, shade --- src/hotspot/cpu/aarch64/templateInterpreterGenerator_aarch64.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/hotspot/cpu/aarch64/templateInterpreterGenerator_aarch64.cpp b/src/hotspot/cpu/aarch64/templateInterpreterGenerator_aarch64.cpp index c1eabed8ade..dd70c98797f 100644 --- a/src/hotspot/cpu/aarch64/templateInterpreterGenerator_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/templateInterpreterGenerator_aarch64.cpp @@ -1375,7 +1375,6 @@ address TemplateInterpreterGenerator::generate_native_entry(bool synchronized) { __ ldr(r10, Address(rmethod, Method::native_function_offset())); ExternalAddress unsatisfied(SharedRuntime::native_method_throw_unsatisfied_link_error_entry()); __ lea(rscratch2, unsatisfied); - __ ldr(rscratch2, rscratch2); __ cmp(r10, rscratch2); __ br(Assembler::NE, L); __ call_VM(noreg, From 0bff5f3dbe69ab2a59db771af1020b04c0132954 Mon Sep 17 00:00:00 2001 From: Anton Seoane Ampudia Date: Wed, 19 Nov 2025 13:02:07 +0000 Subject: [PATCH 129/418] 8213762: Deprecate Xmaxjitcodesize Reviewed-by: kvn, epeter --- src/hotspot/share/runtime/arguments.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp index 1ef2ee9de0d..55ee7641a5f 100644 --- a/src/hotspot/share/runtime/arguments.cpp +++ b/src/hotspot/share/runtime/arguments.cpp @@ -2483,6 +2483,9 @@ jint Arguments::parse_each_vm_init_arg(const JavaVMInitArgs* args, JVMFlagOrigin } } else if (match_option(option, "-Xmaxjitcodesize", &tail) || match_option(option, "-XX:ReservedCodeCacheSize=", &tail)) { + if (match_option(option, "-Xmaxjitcodesize", &tail)) { + warning("Option -Xmaxjitcodesize was deprecated in JDK 26 and will likely be removed in a future release."); + } julong long_ReservedCodeCacheSize = 0; ArgsRange errcode = parse_memory_size(tail, &long_ReservedCodeCacheSize, 1); From f0afd89f66c0b42ff06fbb76378a5b2028b76a10 Mon Sep 17 00:00:00 2001 From: Chen Liang Date: Wed, 19 Nov 2025 15:19:04 +0000 Subject: [PATCH 130/418] 8357728: Avoid caching synthesized names in synthesized parameters Reviewed-by: jvernee --- .../classes/java/lang/reflect/Executable.java | 2 +- .../classes/java/lang/reflect/Parameter.java | 4 +- .../Parameter/SyntheticNameRetention.java | 81 +++++++++++++++++++ 3 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 test/jdk/java/lang/reflect/Parameter/SyntheticNameRetention.java diff --git a/src/java.base/share/classes/java/lang/reflect/Executable.java b/src/java.base/share/classes/java/lang/reflect/Executable.java index a22d0fa8076..4f32d33048d 100644 --- a/src/java.base/share/classes/java/lang/reflect/Executable.java +++ b/src/java.base/share/classes/java/lang/reflect/Executable.java @@ -431,7 +431,7 @@ public abstract sealed class Executable extends AccessibleObject // modifiers? Probably not in the general case, since // we'd have no way of knowing about them, but there // may be specific cases. - out[i] = new Parameter("arg" + i, 0, this, i); + out[i] = new Parameter(null, 0, this, i); return out; } diff --git a/src/java.base/share/classes/java/lang/reflect/Parameter.java b/src/java.base/share/classes/java/lang/reflect/Parameter.java index b8a57a9790b..d4a53e193a9 100644 --- a/src/java.base/share/classes/java/lang/reflect/Parameter.java +++ b/src/java.base/share/classes/java/lang/reflect/Parameter.java @@ -55,7 +55,7 @@ public final class Parameter implements AnnotatedElement { * absent, however, then {@code Executable} uses this constructor * to synthesize them. * - * @param name The name of the parameter. + * @param name The name of the parameter, or {@code null} if absent * @param modifiers The modifier flags for the parameter. * @param executable The executable which defines this parameter. * @param index The index of the parameter. @@ -104,7 +104,7 @@ public final class Parameter implements AnnotatedElement { * to the class file. */ public boolean isNamePresent() { - return executable.hasRealParameterData() && name != null; + return name != null; } /** diff --git a/test/jdk/java/lang/reflect/Parameter/SyntheticNameRetention.java b/test/jdk/java/lang/reflect/Parameter/SyntheticNameRetention.java new file mode 100644 index 00000000000..b77a231be88 --- /dev/null +++ b/test/jdk/java/lang/reflect/Parameter/SyntheticNameRetention.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2025, 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 java.lang.invoke.MethodHandleProxies; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Parameter; +import java.util.stream.Stream; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.*; + +/* + * @test + * @bug 8357728 + * @summary Synthetic parameter names should not be retained. + * @modules java.base/java.lang.reflect:+open + * @run junit SyntheticNameRetention + */ +public class SyntheticNameRetention { + + class Inner { + Inner() {} + } + + public interface NameGetter { + String getRawName(Parameter parameter); + } + static final NameGetter GETTER; + + static { + try { + var lookup = MethodHandles.privateLookupIn(Parameter.class, MethodHandles.lookup()); + GETTER = MethodHandleProxies.asInterfaceInstance(NameGetter.class, lookup.findGetter(Parameter.class, "name", String.class)); + } catch (ReflectiveOperationException ex) { + throw new ExceptionInInitializerError(ex); + } + } + + static Stream methods() throws Throwable { + return Stream.of(Inner.class.getDeclaredConstructor(SyntheticNameRetention.class), // Has MethodParameters with flags + SyntheticNameRetention.class.getDeclaredMethod("test", Executable.class)); // No MethodParameters + } + + @ParameterizedTest + @MethodSource("methods") + public void test(Executable exec) { + var params = exec.getParameters(); + for (int i = 0; i < params.length; i++) { + var param = params[i]; + assertEquals("arg" + i, param.getName(), "name " + i); + assertFalse(param.isNamePresent(), "name present " + i); + assertNull(GETTER.getRawName(param), "raw name " + i); + boolean mandated = exec instanceof Constructor && i == 0; + assertEquals(mandated, param.isImplicit(), "mandated " + i); + } + } +} From 3949b0f23cd9c936c12ac0306534bc38b5b8d298 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Maillard?= Date: Wed, 19 Nov 2025 15:40:57 +0000 Subject: [PATCH 131/418] 8371674: C2 fails with Missed optimization opportunity in PhaseIterGVN for MoveL2D Reviewed-by: epeter, chagedorn --- src/hotspot/share/opto/node.cpp | 7 ++- .../c2/TestMissingOptMoveX2YLoadX.java | 61 +++++++++++++++++++ 2 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/c2/TestMissingOptMoveX2YLoadX.java diff --git a/src/hotspot/share/opto/node.cpp b/src/hotspot/share/opto/node.cpp index 93ded36363e..2452677caf3 100644 --- a/src/hotspot/share/opto/node.cpp +++ b/src/hotspot/share/opto/node.cpp @@ -1209,9 +1209,12 @@ bool Node::has_special_unique_user() const { if (this->is_Store()) { // Condition for back-to-back stores folding. return n->Opcode() == op && n->in(MemNode::Memory) == this; - } else if (this->is_Load() || this->is_DecodeN() || this->is_Phi()) { + } else if ((this->is_Load() || this->is_DecodeN() || this->is_Phi()) && n->Opcode() == Op_MemBarAcquire) { // Condition for removing an unused LoadNode or DecodeNNode from the MemBarAcquire precedence input - return n->Opcode() == Op_MemBarAcquire; + return true; + } else if (this->is_Load() && n->is_Move()) { + // Condition for MoveX2Y (LoadX mem) => LoadY mem + return true; } else if (op == Op_AddL) { // Condition for convL2I(addL(x,y)) ==> addI(convL2I(x),convL2I(y)) return n->Opcode() == Op_ConvL2I && n->in(1) == this; diff --git a/test/hotspot/jtreg/compiler/c2/TestMissingOptMoveX2YLoadX.java b/test/hotspot/jtreg/compiler/c2/TestMissingOptMoveX2YLoadX.java new file mode 100644 index 00000000000..1e6e01a8e33 --- /dev/null +++ b/test/hotspot/jtreg/compiler/c2/TestMissingOptMoveX2YLoadX.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8371674 + * @summary An expression of the form "MoveX2Y (LoadX mem)" should be + * transformed into "LoadY mem". This test ensures that changes + * to the number of users of the Load node propagate as expected + * and that the optimization is not missed. + * @run main/othervm -XX:+IgnoreUnrecognizedVMOptions -XX:-TieredCompilation -Xcomp + * -XX:CompileCommand=compileonly,compiler.c2.TestMissingOptMoveX2YLoadX::test* + * -XX:VerifyIterativeGVN=1110 compiler.c2.TestMissingOptMoveX2YLoadX + * @run main compiler.c2.TestMissingOptMoveX2YLoadX + * + */ + +package compiler.c2; + +public class TestMissingOptMoveX2YLoadX { + static final int N = 400; + static volatile long b; + + public static void main(String[] strArr) { + // could theoretically happen with other variants of MoveNode + // but there is no known reproducer for the other cases + Double.longBitsToDouble(0l); + testMoveL2D(); + } + + static void testMoveL2D() { + int e = 8, f, g = 9, h = 2, i[] = new int[N]; + long j[] = new long[N]; + while (++e < 37) { + for (f = 1; f < 7; f++) { + h >>>= (int)(--g - Double.longBitsToDouble(j[e])); + b -= b; + } + } + } +} \ No newline at end of file From 9ea8201b7494fe9107d4abd78c02ac765a5751d4 Mon Sep 17 00:00:00 2001 From: Alexander Matveev Date: Wed, 19 Nov 2025 16:07:20 +0000 Subject: [PATCH 132/418] 8363980: [macos] Add JDK specific keys/values to Info.plist of embedded runtime Reviewed-by: asemenyuk --- .../internal/MacPackagingPipeline.java | 21 +++++++++++-- .../internal/JLinkRuntimeBuilder.java | 5 ++++ .../internal/model/RuntimeBuilder.java | 10 +++++++ .../jdk/jpackage/test/JPackageCommand.java | 30 ++++++++++++------- .../jpackage/macosx/CustomInfoPListTest.java | 5 ++++ .../jpackage/share/AppImagePackageTest.java | 3 +- .../jpackage/share/CookedRuntimeTest.java | 17 +++++++++-- .../jpackage/share/PostImageScriptTest.java | 10 +++---- 8 files changed, 80 insertions(+), 21 deletions(-) diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPackagingPipeline.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPackagingPipeline.java index 6e63b73674e..b82b20c0c36 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPackagingPipeline.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPackagingPipeline.java @@ -364,12 +364,21 @@ final class MacPackagingPipeline { final var app = env.app(); + // If the embedded runtime contains executable(s) in the "bin" + // subdirectory, we should use the standalone runtime info plist + // template. Otherwise, the user may be unable to run the "java" + // or other executables in the "bin" subdirectory of the embedded + // runtime. + final var useRuntimeInfoPlist = app.isRuntime() || + app.runtimeBuilder().orElseThrow().withNativeCommands() || + Files.isDirectory(env.resolvedLayout().runtimeDirectory().resolve("bin")); + Map data = new HashMap<>(); data.put("CF_BUNDLE_IDENTIFIER", app.bundleIdentifier()); data.put("CF_BUNDLE_NAME", app.bundleName()); data.put("CF_BUNDLE_VERSION", app.version()); data.put("CF_BUNDLE_SHORT_VERSION_STRING", app.shortVersion().toString()); - if (app.isRuntime()) { + if (useRuntimeInfoPlist) { data.put("CF_BUNDLE_VENDOR", app.vendor()); } @@ -377,12 +386,18 @@ final class MacPackagingPipeline { final String publicName; final String category; - if (app.isRuntime()) { + if (useRuntimeInfoPlist) { template = "Runtime-Info.plist.template"; + } else { + template = "ApplicationRuntime-Info.plist.template"; + } + + // Public name and category should be based on standalone runtime vs + // embedded runtime. + if (app.isRuntime()) { publicName = "Info.plist"; category = "resource.runtime-info-plist"; } else { - template = "ApplicationRuntime-Info.plist.template"; publicName = "Runtime-Info.plist"; category = "resource.app-runtime-info-plist"; } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/JLinkRuntimeBuilder.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/JLinkRuntimeBuilder.java index 2273d385936..6ac9758e179 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/JLinkRuntimeBuilder.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/JLinkRuntimeBuilder.java @@ -83,6 +83,11 @@ final class JLinkRuntimeBuilder implements RuntimeBuilder { } } + @Override + public boolean withNativeCommands() { + return !jlinkCmdLine.contains("--strip-native-commands"); + } + static ModuleFinder createModuleFinder(Collection modulePath) { return ModuleFinder.compose( ModulePath.of(JarFile.runtimeVersion(), true, diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/RuntimeBuilder.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/RuntimeBuilder.java index a0f5f077c70..89d370b58e0 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/RuntimeBuilder.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/RuntimeBuilder.java @@ -46,6 +46,16 @@ public interface RuntimeBuilder { */ void create(AppImageLayout appImageLayout) throws PackagerException; + /** + * Returns {@code true} if "--strip-native-commands" was not used with jlink. + * Default implementation returns {@code false}. + * + * @return {@code true} if "--strip-native-commands" was not used with jlink + */ + default boolean withNativeCommands() { + return false; + } + /** * Gets the default set of paths where jlink should look up for system Java * modules. diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java index 57915b91d8b..98d991e38f3 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java @@ -306,24 +306,16 @@ public class JPackageCommand extends CommandArguments { TKit.trace(String.format("Init fake runtime in [%s] directory", fakeRuntimeDir)); - Files.createDirectories(fakeRuntimeDir); - - if (TKit.isLinux()) { - // Need to make the code in rpm spec happy as it assumes there is - // always something in application image. - fakeRuntimeDir.resolve("bin").toFile().mkdir(); - } - if (TKit.isOSX()) { // Make MacAppImageBuilder happy createBulkFile.accept(fakeRuntimeDir.resolve(Path.of( "lib/jli/libjli.dylib"))); } - // Mak sure fake runtime takes some disk space. + // Make sure fake runtime takes some disk space. // Package bundles with 0KB size are unexpected and considered // an error by PackageTest. - createBulkFile.accept(fakeRuntimeDir.resolve(Path.of("bin", "bulk"))); + createBulkFile.accept(fakeRuntimeDir.resolve(Path.of("lib", "bulk"))); cmd.setArgumentValue("--runtime-image", fakeRuntimeDir); }); @@ -1227,6 +1219,24 @@ public class JPackageCommand extends CommandArguments { MacHelper.verifyUnsignedBundleSignature(cmd); } }), + MAC_RUNTIME_PLIST_JDK_KEY(cmd -> { + if (TKit.isOSX()) { + var appLayout = cmd.appLayout(); + var plistPath = appLayout.runtimeDirectory().resolve("Contents/Info.plist"); + var keyName = "JavaVM"; + var keyValue = MacHelper.readPList(plistPath).findDictValue(keyName); + if (cmd.isRuntime() || Files.isDirectory(appLayout.runtimeHomeDirectory().resolve("bin"))) { + // There are native launchers in the runtime + TKit.assertTrue(keyValue.isPresent(), String.format( + "Check the runtime plist file [%s] contains '%s' key", + plistPath, keyName)); + } else { + TKit.assertTrue(keyValue.isEmpty(), String.format( + "Check the runtime plist file [%s] contains NO '%s' key", + plistPath, keyName)); + } + } + }), PREDEFINED_APP_IMAGE_COPY(cmd -> { Optional.ofNullable(cmd.getArgumentValue("--app-image")).filter(_ -> { return !TKit.isOSX() || !MacHelper.signPredefinedAppImage(cmd); diff --git a/test/jdk/tools/jpackage/macosx/CustomInfoPListTest.java b/test/jdk/tools/jpackage/macosx/CustomInfoPListTest.java index b690b69269c..dd94330d039 100644 --- a/test/jdk/tools/jpackage/macosx/CustomInfoPListTest.java +++ b/test/jdk/tools/jpackage/macosx/CustomInfoPListTest.java @@ -25,6 +25,7 @@ import static java.util.Collections.unmodifiableSortedSet; import static java.util.Map.entry; import jdk.jpackage.internal.util.Slot; import static jdk.jpackage.internal.util.PListWriter.writeDict; +import static jdk.jpackage.internal.util.PListWriter.writeKey; import static jdk.jpackage.internal.util.PListWriter.writePList; import static jdk.jpackage.internal.util.PListWriter.writeString; import static jdk.jpackage.internal.util.XmlUtils.createXml; @@ -431,6 +432,10 @@ public class CustomInfoPListTest { writeString(xml, "CFBundleShortVersionString", value("CF_BUNDLE_SHORT_VERSION_STRING", cmd.version())); writeString(xml, "CFBundleVersion", value("CF_BUNDLE_VERSION", cmd.version())); writeString(xml, "CustomInfoPListFA", "DEPLOY_FILE_ASSOCIATIONS"); + writeKey(xml, "JavaVM"); + writeDict(xml, toXmlConsumer(() -> { + writeString(xml, "JVMVersion", value("CF_BUNDLE_VERSION", cmd.version())); + })); })); })); } diff --git a/test/jdk/tools/jpackage/share/AppImagePackageTest.java b/test/jdk/tools/jpackage/share/AppImagePackageTest.java index 94eb086e4c6..c3faa1ffb65 100644 --- a/test/jdk/tools/jpackage/share/AppImagePackageTest.java +++ b/test/jdk/tools/jpackage/share/AppImagePackageTest.java @@ -120,7 +120,8 @@ public class AppImagePackageTest { StandardAssert.MAIN_JAR_FILE, StandardAssert.MAIN_LAUNCHER_FILES, StandardAssert.MAC_BUNDLE_STRUCTURE, - StandardAssert.RUNTIME_DIRECTORY); + StandardAssert.RUNTIME_DIRECTORY, + StandardAssert.MAC_RUNTIME_PLIST_JDK_KEY); }) .run(Action.CREATE_AND_UNPACK); } diff --git a/test/jdk/tools/jpackage/share/CookedRuntimeTest.java b/test/jdk/tools/jpackage/share/CookedRuntimeTest.java index 28fba111c8f..e3304e01f84 100644 --- a/test/jdk/tools/jpackage/share/CookedRuntimeTest.java +++ b/test/jdk/tools/jpackage/share/CookedRuntimeTest.java @@ -51,10 +51,11 @@ import jdk.jpackage.test.TKit; public final class CookedRuntimeTest { public CookedRuntimeTest(String javaAppDesc, String jlinkOutputSubdir, - String runtimeSubdir) { + String runtimeSubdir, boolean withNativeCommands) { this.javaAppDesc = javaAppDesc; this.jlinkOutputSubdir = Path.of(jlinkOutputSubdir); this.runtimeSubdir = Path.of(runtimeSubdir); + this.withNativeCommands = withNativeCommands; } @Test @@ -90,6 +91,10 @@ public final class CookedRuntimeTest { "--no-header-files", "--no-man-pages"); + if (!withNativeCommands) { + jlink.addArgument("--strip-native-commands"); + } + if (moduleName != null) { jlink.addArguments("--add-modules", moduleName, "--module-path", Path.of(cmd.getArgumentValue("--module-path")).resolve( @@ -127,7 +132,14 @@ public final class CookedRuntimeTest { List data = new ArrayList<>(); for (var javaAppDesc : javaAppDescs) { for (var pathCfg : paths) { - data.add(new Object[] { javaAppDesc, pathCfg[0], pathCfg[1] }); + if (TKit.isOSX()) { + // On OSX platform we need to test both runtime root and runtime home + // directories with and without "bin" folder. + data.add(new Object[] { javaAppDesc, pathCfg[0], pathCfg[1], true }); + data.add(new Object[] { javaAppDesc, pathCfg[0], pathCfg[1], false }); + } else { + data.add(new Object[] { javaAppDesc, pathCfg[0], pathCfg[1], true }); + } } } @@ -137,4 +149,5 @@ public final class CookedRuntimeTest { private final String javaAppDesc; private final Path jlinkOutputSubdir; private final Path runtimeSubdir; + private final boolean withNativeCommands; } diff --git a/test/jdk/tools/jpackage/share/PostImageScriptTest.java b/test/jdk/tools/jpackage/share/PostImageScriptTest.java index 5e9127d8fa8..655658f036d 100644 --- a/test/jdk/tools/jpackage/share/PostImageScriptTest.java +++ b/test/jdk/tools/jpackage/share/PostImageScriptTest.java @@ -140,7 +140,7 @@ public class PostImageScriptTest { runtimeDir = Path.of(""); } - final Path runtimeBinDir = runtimeDir.resolve("bin"); + final Path runtimeLibDir = runtimeDir.resolve("lib"); if (TKit.isWindows()) { final List script = new ArrayList<>(); @@ -148,16 +148,16 @@ public class PostImageScriptTest { script.addAll(WinGlobals.JS_FS.expr()); script.addAll(List.of( "WScript.Echo('PWD: ' + fs.GetFolder(shell.CurrentDirectory).Path)", - String.format("WScript.Echo('Probe directory: %s')", runtimeBinDir), - String.format("fs.GetFolder('%s')", runtimeBinDir.toString().replace('\\', '/')) + String.format("WScript.Echo('Probe directory: %s')", runtimeLibDir), + String.format("fs.GetFolder('%s')", runtimeLibDir.toString().replace('\\', '/')) )); JPackageUserScript.POST_IMAGE.create(cmd, script); } else { JPackageUserScript.POST_IMAGE.create(cmd, List.of( "set -e", "printf 'PWD: %s\\n' \"$PWD\"", - String.format("printf 'Probe directory: %%s\\n' '%s'", runtimeBinDir), - String.format("[ -d '%s' ]", runtimeBinDir) + String.format("printf 'Probe directory: %%s\\n' '%s'", runtimeLibDir), + String.format("[ -d '%s' ]", runtimeLibDir) )); } }); From 223cc6451860f10fe8095705da07aaf7e882188f Mon Sep 17 00:00:00 2001 From: Matthew Donovan Date: Wed, 19 Nov 2025 19:14:33 +0000 Subject: [PATCH 133/418] 8343316: Review and update tests using explicit provider names Reviewed-by: rhalade --- .../KeyAgreement/DHGenSharedSecret.java | 11 ++++++---- .../KeyAgreement/DHKeyAgreement2.java | 21 ++++++++++--------- .../KeyAgreement/DHKeyAgreement3.java | 17 ++++++++------- .../Provider/ProviderVersionCheck.java | 7 ++++++- 4 files changed, 34 insertions(+), 22 deletions(-) diff --git a/test/jdk/com/sun/crypto/provider/KeyAgreement/DHGenSharedSecret.java b/test/jdk/com/sun/crypto/provider/KeyAgreement/DHGenSharedSecret.java index 9fe96d967dc..a89efa7bddd 100644 --- a/test/jdk/com/sun/crypto/provider/KeyAgreement/DHGenSharedSecret.java +++ b/test/jdk/com/sun/crypto/provider/KeyAgreement/DHGenSharedSecret.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2025, 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 @@ -39,6 +39,9 @@ import jdk.test.lib.security.SecurityUtils; public class DHGenSharedSecret { + private static final String PROVIDER_NAME = + System.getProperty("test.provider.name", "SunJCE"); + public static void main(String[] args) throws Exception { DHGenSharedSecret test = new DHGenSharedSecret(); test.run(); @@ -57,7 +60,7 @@ public class DHGenSharedSecret { // generate keyPairs using parameters KeyPairGenerator keyGen = - KeyPairGenerator.getInstance("DH", "SunJCE"); + KeyPairGenerator.getInstance("DH", PROVIDER_NAME); keyGen.initialize(spec); // Alice generates her key pairs @@ -77,11 +80,11 @@ public class DHGenSharedSecret { // bob uses it to generate Secret X509EncodedKeySpec x509Spec = new X509EncodedKeySpec(alicePubKeyEnc); - KeyFactory bobKeyFac = KeyFactory.getInstance("DH", "SunJCE"); + KeyFactory bobKeyFac = KeyFactory.getInstance("DH", PROVIDER_NAME); PublicKey alicePubKey = bobKeyFac.generatePublic(x509Spec); - KeyAgreement bobAlice = KeyAgreement.getInstance("DH", "SunJCE"); + KeyAgreement bobAlice = KeyAgreement.getInstance("DH", PROVIDER_NAME); start = System.currentTimeMillis(); bobAlice.init(keyB.getPrivate()); bobAlice.doPhase(alicePubKey, true); diff --git a/test/jdk/com/sun/crypto/provider/KeyAgreement/DHKeyAgreement2.java b/test/jdk/com/sun/crypto/provider/KeyAgreement/DHKeyAgreement2.java index da583c9dc29..c972c8a696b 100644 --- a/test/jdk/com/sun/crypto/provider/KeyAgreement/DHKeyAgreement2.java +++ b/test/jdk/com/sun/crypto/provider/KeyAgreement/DHKeyAgreement2.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2025, 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 @@ -53,7 +53,8 @@ import jdk.test.lib.security.SecurityUtils; public class DHKeyAgreement2 { - private static final String SUNJCE = "SunJCE"; + private static final String PROVIDER_NAME = System.getProperty( + "test.provider.name", "SunJCE"); // Hex formatter to upper case with ":" delimiter private static final HexFormat HEX_FORMATTER = HexFormat.ofDelimiter(":").withUpperCase(); @@ -90,7 +91,7 @@ public class DHKeyAgreement2 { // Some central authority creates new DH parameters System.err.println("Creating Diffie-Hellman parameters ..."); AlgorithmParameterGenerator paramGen - = AlgorithmParameterGenerator.getInstance("DH", SUNJCE); + = AlgorithmParameterGenerator.getInstance("DH", PROVIDER_NAME); paramGen.init(primeSize); AlgorithmParameters params = paramGen.generateParameters(); dhParameterSpec = (DHParameterSpec)params.getParameterSpec @@ -108,7 +109,7 @@ public class DHKeyAgreement2 { * above */ System.err.println("ALICE: Generate DH keypair ..."); - KeyPairGenerator aliceKpairGen = KeyPairGenerator.getInstance("DH", SUNJCE); + KeyPairGenerator aliceKpairGen = KeyPairGenerator.getInstance("DH", PROVIDER_NAME); aliceKpairGen.initialize(dhParameterSpec); KeyPair aliceKpair = aliceKpairGen.generateKeyPair(); System.out.println("Alice DH public key:\n" + @@ -117,14 +118,14 @@ public class DHKeyAgreement2 { aliceKpair.getPrivate().toString()); DHParameterSpec dhParamSpec = ((DHPublicKey)aliceKpair.getPublic()).getParams(); - AlgorithmParameters algParams = AlgorithmParameters.getInstance("DH", SUNJCE); + AlgorithmParameters algParams = AlgorithmParameters.getInstance("DH", PROVIDER_NAME); algParams.init(dhParamSpec); System.out.println("Alice DH parameters:\n" + algParams.toString()); // Alice executes Phase1 of her version of the DH protocol System.err.println("ALICE: Execute PHASE1 ..."); - KeyAgreement aliceKeyAgree = KeyAgreement.getInstance("DH", SUNJCE); + KeyAgreement aliceKeyAgree = KeyAgreement.getInstance("DH", PROVIDER_NAME); aliceKeyAgree.init(aliceKpair.getPrivate()); // Alice encodes her public key, and sends it over to Bob. @@ -135,7 +136,7 @@ public class DHKeyAgreement2 { * in encoded format. * He instantiates a DH public key from the encoded key material. */ - KeyFactory bobKeyFac = KeyFactory.getInstance("DH", SUNJCE); + KeyFactory bobKeyFac = KeyFactory.getInstance("DH", PROVIDER_NAME); X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec (alicePubKeyEnc); PublicKey alicePubKey = bobKeyFac.generatePublic(x509KeySpec); @@ -149,7 +150,7 @@ public class DHKeyAgreement2 { // Bob creates his own DH key pair System.err.println("BOB: Generate DH keypair ..."); - KeyPairGenerator bobKpairGen = KeyPairGenerator.getInstance("DH", SUNJCE); + KeyPairGenerator bobKpairGen = KeyPairGenerator.getInstance("DH", PROVIDER_NAME); bobKpairGen.initialize(dhParamSpec); KeyPair bobKpair = bobKpairGen.generateKeyPair(); System.out.println("Bob DH public key:\n" + @@ -159,7 +160,7 @@ public class DHKeyAgreement2 { // Bob executes Phase1 of his version of the DH protocol System.err.println("BOB: Execute PHASE1 ..."); - KeyAgreement bobKeyAgree = KeyAgreement.getInstance("DH", SUNJCE); + KeyAgreement bobKeyAgree = KeyAgreement.getInstance("DH", PROVIDER_NAME); bobKeyAgree.init(bobKpair.getPrivate()); // Bob encodes his public key, and sends it over to Alice. @@ -171,7 +172,7 @@ public class DHKeyAgreement2 { * Before she can do so, she has to instanticate a DH public key * from Bob's encoded key material. */ - KeyFactory aliceKeyFac = KeyFactory.getInstance("DH", SUNJCE); + KeyFactory aliceKeyFac = KeyFactory.getInstance("DH", PROVIDER_NAME); x509KeySpec = new X509EncodedKeySpec(bobPubKeyEnc); PublicKey bobPubKey = aliceKeyFac.generatePublic(x509KeySpec); System.err.println("ALICE: Execute PHASE2 ..."); diff --git a/test/jdk/com/sun/crypto/provider/KeyAgreement/DHKeyAgreement3.java b/test/jdk/com/sun/crypto/provider/KeyAgreement/DHKeyAgreement3.java index d4f70ea2563..5315d62aee8 100644 --- a/test/jdk/com/sun/crypto/provider/KeyAgreement/DHKeyAgreement3.java +++ b/test/jdk/com/sun/crypto/provider/KeyAgreement/DHKeyAgreement3.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2025, 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 @@ -50,6 +50,9 @@ import jdk.test.lib.security.SecurityUtils; public class DHKeyAgreement3 { + private static final String PROVIDER_NAME = + System.getProperty("test.provider.name", "SunJCE"); + // Hex formatter to upper case with ":" delimiter private static final HexFormat HEX_FORMATTER = HexFormat.ofDelimiter(":").withUpperCase(); @@ -70,36 +73,36 @@ public class DHKeyAgreement3 { // Alice creates her own DH key pair System.err.println("ALICE: Generate DH keypair ..."); - KeyPairGenerator aliceKpairGen = KeyPairGenerator.getInstance("DH", "SunJCE"); + KeyPairGenerator aliceKpairGen = KeyPairGenerator.getInstance("DH", PROVIDER_NAME); aliceKpairGen.initialize(dhParamSpec); KeyPair aliceKpair = aliceKpairGen.generateKeyPair(); // Bob creates his own DH key pair System.err.println("BOB: Generate DH keypair ..."); - KeyPairGenerator bobKpairGen = KeyPairGenerator.getInstance("DH", "SunJCE"); + KeyPairGenerator bobKpairGen = KeyPairGenerator.getInstance("DH", PROVIDER_NAME); bobKpairGen.initialize(dhParamSpec); KeyPair bobKpair = bobKpairGen.generateKeyPair(); // Carol creates her own DH key pair System.err.println("CAROL: Generate DH keypair ..."); - KeyPairGenerator carolKpairGen = KeyPairGenerator.getInstance("DH", "SunJCE"); + KeyPairGenerator carolKpairGen = KeyPairGenerator.getInstance("DH", PROVIDER_NAME); carolKpairGen.initialize(dhParamSpec); KeyPair carolKpair = carolKpairGen.generateKeyPair(); // Alice initialize System.err.println("ALICE: Initialize ..."); - KeyAgreement aliceKeyAgree = KeyAgreement.getInstance("DH", "SunJCE"); + KeyAgreement aliceKeyAgree = KeyAgreement.getInstance("DH", PROVIDER_NAME); aliceKeyAgree.init(aliceKpair.getPrivate()); // Bob initialize System.err.println("BOB: Initialize ..."); - KeyAgreement bobKeyAgree = KeyAgreement.getInstance("DH", "SunJCE"); + KeyAgreement bobKeyAgree = KeyAgreement.getInstance("DH", PROVIDER_NAME); bobKeyAgree.init(bobKpair.getPrivate()); // Carol initialize System.err.println("CAROL: Initialize ..."); - KeyAgreement carolKeyAgree = KeyAgreement.getInstance("DH", "SunJCE"); + KeyAgreement carolKeyAgree = KeyAgreement.getInstance("DH", PROVIDER_NAME); carolKeyAgree.init(carolKpair.getPrivate()); diff --git a/test/jdk/java/security/Provider/ProviderVersionCheck.java b/test/jdk/java/security/Provider/ProviderVersionCheck.java index 43d1c502b3f..2af2be0a7ad 100644 --- a/test/jdk/java/security/Provider/ProviderVersionCheck.java +++ b/test/jdk/java/security/Provider/ProviderVersionCheck.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2025, 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,6 +42,11 @@ public class ProviderVersionCheck { for (Provider p: Security.getProviders()) { System.out.print(p.getName() + " "); + if (p.getName().equals(System.getProperty("test.provider.name"))) { + // Version numbers of non JDK-providers do not match JDK version number. + continue; + } + String specVersion = System.getProperty("java.specification.version"); if (p.getVersion() != Double.parseDouble(specVersion)) { System.out.println("failed. " + "Version received was " + From 6f1c5733ed4a1d1a1e099681f1f292acf827d9dc Mon Sep 17 00:00:00 2001 From: Kim Barrett Date: Wed, 19 Nov 2025 20:05:09 +0000 Subject: [PATCH 134/418] 8371923: Update LockFreeStack for Atomic Reviewed-by: iwalulya, dholmes --- src/hotspot/share/utilities/lockFreeStack.hpp | 51 +++++++++++++------ .../gtest/utilities/test_lockFreeStack.cpp | 34 ++++++------- 2 files changed, 53 insertions(+), 32 deletions(-) diff --git a/src/hotspot/share/utilities/lockFreeStack.hpp b/src/hotspot/share/utilities/lockFreeStack.hpp index 43bc58fbc44..3f63482a268 100644 --- a/src/hotspot/share/utilities/lockFreeStack.hpp +++ b/src/hotspot/share/utilities/lockFreeStack.hpp @@ -25,6 +25,7 @@ #ifndef SHARE_UTILITIES_LOCKFREESTACK_HPP #define SHARE_UTILITIES_LOCKFREESTACK_HPP +#include "runtime/atomic.hpp" #include "runtime/atomicAccess.hpp" #include "utilities/debug.hpp" #include "utilities/globalDefinitions.hpp" @@ -34,11 +35,14 @@ // a result, there is no allocation involved in adding objects to the stack // or removing them from the stack. // -// To be used in a LockFreeStack of objects of type T, an object of -// type T must have a list entry member of type T* volatile, with an -// non-member accessor function returning a pointer to that member. A -// LockFreeStack is associated with the class of its elements and an -// entry member from that class. +// To be used in a LockFreeStack of objects of type T, an object of type T +// must have a list entry member. A list entry member is a data member whose +// type is either (1) Atomic, or (2) T* volatile. There must be a +// non-member or static member function returning a pointer to that member, +// which is used to provide access to it by a LockFreeStack. A LockFreeStack +// is associated with the class of its elements and an entry member from that +// class by being specialized on the element class and a pointer to the +// function for accessing that entry member. // // An object can be in multiple stacks at the same time, so long as // each stack uses a different entry member. That is, the class of the @@ -52,12 +56,12 @@ // // \tparam T is the class of the elements in the stack. // -// \tparam next_ptr is a function pointer. Applying this function to +// \tparam next_accessor is a function pointer. Applying this function to // an object of type T must return a pointer to the list entry member // of the object associated with the LockFreeStack type. -template +template class LockFreeStack { - T* volatile _top; + Atomic _top; void prepend_impl(T* first, T* last) { T* cur = top(); @@ -65,12 +69,21 @@ class LockFreeStack { do { old = cur; set_next(*last, cur); - cur = AtomicAccess::cmpxchg(&_top, cur, first); + cur = _top.compare_exchange(cur, first); } while (old != cur); } NONCOPYABLE(LockFreeStack); + template + static constexpr void use_atomic_access_impl(NextAccessor) { + static_assert(DependentAlwaysFalse, "Invalid next accessor"); + } + static constexpr bool use_atomic_access_impl(T* volatile* (*)(T&)) { return true; } + static constexpr bool use_atomic_access_impl(Atomic* (*)(T&)) { return false; } + + static constexpr bool use_atomic_access = use_atomic_access_impl(next_accessor); + public: LockFreeStack() : _top(nullptr) {} ~LockFreeStack() { assert(empty(), "stack not empty"); } @@ -89,7 +102,7 @@ public: new_top = next(*result); } // CAS even on empty pop, for consistent membar behavior. - result = AtomicAccess::cmpxchg(&_top, result, new_top); + result = _top.compare_exchange(result, new_top); } while (result != old); if (result != nullptr) { set_next(*result, nullptr); @@ -101,7 +114,7 @@ public: // list of elements. Acts as a full memory barrier. // postcondition: empty() T* pop_all() { - return AtomicAccess::xchg(&_top, (T*)nullptr); + return _top.exchange(nullptr); } // Atomically adds value to the top of this stack. Acts as a full @@ -143,9 +156,9 @@ public: // Return true if the stack is empty. bool empty() const { return top() == nullptr; } - // Return the most recently pushed element, or nullptr if the stack is empty. + // Return the most recently pushed element, or null if the stack is empty. // The returned element is not removed from the stack. - T* top() const { return AtomicAccess::load(&_top); } + T* top() const { return _top.load_relaxed(); } // Return the number of objects in the stack. There must be no concurrent // pops while the length is being determined. @@ -160,7 +173,11 @@ public: // Return the entry following value in the list used by the // specialized LockFreeStack class. static T* next(const T& value) { - return AtomicAccess::load(next_ptr(const_cast(value))); + if constexpr (use_atomic_access) { + return AtomicAccess::load(next_accessor(const_cast(value))); + } else { + return next_accessor(const_cast(value))->load_relaxed(); + } } // Set the entry following value to new_next in the list used by the @@ -168,7 +185,11 @@ public: // if value is in an instance of this specialization of LockFreeStack, // there must be no concurrent push or pop operations on that stack. static void set_next(T& value, T* new_next) { - AtomicAccess::store(next_ptr(value), new_next); + if constexpr (use_atomic_access) { + AtomicAccess::store(next_accessor(value), new_next); + } else { + next_accessor(value)->store_relaxed(new_next); + } } }; diff --git a/test/hotspot/gtest/utilities/test_lockFreeStack.cpp b/test/hotspot/gtest/utilities/test_lockFreeStack.cpp index 3a9d24ad61e..fac17e87016 100644 --- a/test/hotspot/gtest/utilities/test_lockFreeStack.cpp +++ b/test/hotspot/gtest/utilities/test_lockFreeStack.cpp @@ -22,7 +22,7 @@ */ #include "memory/allocation.inline.hpp" -#include "runtime/atomicAccess.hpp" +#include "runtime/atomic.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/lockFreeStack.hpp" #include "threadHelper.inline.hpp" @@ -33,12 +33,12 @@ class LockFreeStackTestElement { typedef LockFreeStackTestElement Element; - Element* volatile _entry; - Element* volatile _entry1; + Atomic _entry; + Atomic _entry1; size_t _id; - static Element* volatile* entry_ptr(Element& e) { return &e._entry; } - static Element* volatile* entry1_ptr(Element& e) { return &e._entry1; } + static Atomic* entry_ptr(Element& e) { return &e._entry; } + static Atomic* entry1_ptr(Element& e) { return &e._entry1; } public: LockFreeStackTestElement(size_t id = 0) : _entry(), _entry1(), _id(id) {} @@ -202,17 +202,17 @@ class LockFreeStackTestThread : public JavaTestThread { uint _id; TestStack* _from; TestStack* _to; - volatile size_t* _processed; + Atomic* _processed; size_t _process_limit; size_t _local_processed; - volatile bool _ready; + Atomic _ready; public: LockFreeStackTestThread(Semaphore* post, uint id, TestStack* from, TestStack* to, - volatile size_t* processed, + Atomic* processed, size_t process_limit) : JavaTestThread(post), _id(id), @@ -225,21 +225,21 @@ public: {} virtual void main_run() { - AtomicAccess::release_store_fence(&_ready, true); + _ready.release_store_fence(true); while (true) { Element* e = _from->pop(); if (e != nullptr) { _to->push(*e); - AtomicAccess::inc(_processed); + _processed->fetch_then_add(1u); ++_local_processed; - } else if (AtomicAccess::load_acquire(_processed) == _process_limit) { + } else if (_processed->load_acquire() == _process_limit) { tty->print_cr("thread %u processed %zu", _id, _local_processed); return; } } } - bool ready() const { return AtomicAccess::load_acquire(&_ready); } + bool ready() const { return _ready.load_acquire(); } }; TEST_VM(LockFreeStackTest, stress) { @@ -248,8 +248,8 @@ TEST_VM(LockFreeStackTest, stress) { TestStack start_stack; TestStack middle_stack; TestStack final_stack; - volatile size_t stage1_processed = 0; - volatile size_t stage2_processed = 0; + Atomic stage1_processed{0}; + Atomic stage2_processed{0}; const size_t nelements = 10000; Element* elements = NEW_C_HEAP_ARRAY(Element, nelements, mtOther); @@ -272,7 +272,7 @@ TEST_VM(LockFreeStackTest, stress) { for (uint i = 0; i < ARRAY_SIZE(threads); ++i) { TestStack* from = &start_stack; TestStack* to = &middle_stack; - volatile size_t* processed = &stage1_processed; + Atomic* processed = &stage1_processed; if (i >= stage1_threads) { from = &middle_stack; to = &final_stack; @@ -293,8 +293,8 @@ TEST_VM(LockFreeStackTest, stress) { } // Verify expected state. - ASSERT_EQ(nelements, stage1_processed); - ASSERT_EQ(nelements, stage2_processed); + ASSERT_EQ(nelements, stage1_processed.load_relaxed()); + ASSERT_EQ(nelements, stage2_processed.load_relaxed()); ASSERT_EQ(0u, initial_stack.length()); ASSERT_EQ(0u, start_stack.length()); ASSERT_EQ(0u, middle_stack.length()); From f5bc6ee90d73da00cab5cad283b9517c692bc895 Mon Sep 17 00:00:00 2001 From: Kim Barrett Date: Wed, 19 Nov 2025 20:56:21 +0000 Subject: [PATCH 135/418] 8369187: Add wrapper for that forbids use of global allocation and deallocation functions Reviewed-by: stefank, erikj, jrose --- make/hotspot/lib/CompileGtest.gmk | 1 + src/hotspot/share/code/relocInfo.cpp | 3 +- src/hotspot/share/code/relocInfo.hpp | 3 +- src/hotspot/share/cppstdlib/new.hpp | 154 ++++++++++++++++++ src/hotspot/share/gc/shared/bufferNode.cpp | 3 +- .../share/gc/shared/partialArrayState.cpp | 3 +- .../gc/z/zDeferredConstructed.inline.hpp | 3 +- src/hotspot/share/memory/allocation.hpp | 3 +- src/hotspot/share/memory/arena.cpp | 1 + src/hotspot/share/memory/arena.hpp | 2 - src/hotspot/share/utilities/debug.cpp | 2 +- .../share/utilities/deferredStatic.hpp | 3 +- src/hotspot/share/utilities/elfFile.cpp | 2 +- .../share/utilities/globalDefinitions.hpp | 21 +++ .../gtest/utilities/test_lockFreeStack.cpp | 3 +- 15 files changed, 187 insertions(+), 20 deletions(-) create mode 100644 src/hotspot/share/cppstdlib/new.hpp diff --git a/make/hotspot/lib/CompileGtest.gmk b/make/hotspot/lib/CompileGtest.gmk index d615e254f5a..60912992134 100644 --- a/make/hotspot/lib/CompileGtest.gmk +++ b/make/hotspot/lib/CompileGtest.gmk @@ -95,6 +95,7 @@ $(eval $(call SetupJdkLibrary, BUILD_GTEST_LIBJVM, \ EXTRA_OBJECT_FILES := $(BUILD_LIBJVM_ALL_OBJS), \ DEFAULT_CFLAGS := false, \ CFLAGS := $(JVM_CFLAGS) \ + -DHOTSPOT_GTEST \ -I$(GTEST_FRAMEWORK_SRC)/googletest/include \ -I$(GTEST_FRAMEWORK_SRC)/googlemock/include \ $(addprefix -I, $(GTEST_TEST_SRC)), \ diff --git a/src/hotspot/share/code/relocInfo.cpp b/src/hotspot/share/code/relocInfo.cpp index 286d407c94b..2a6335e2118 100644 --- a/src/hotspot/share/code/relocInfo.cpp +++ b/src/hotspot/share/code/relocInfo.cpp @@ -26,6 +26,7 @@ #include "code/compiledIC.hpp" #include "code/nmethod.hpp" #include "code/relocInfo.hpp" +#include "cppstdlib/new.hpp" #include "cppstdlib/type_traits.hpp" #include "memory/resourceArea.hpp" #include "memory/universe.hpp" @@ -37,8 +38,6 @@ #include "utilities/checkedCast.hpp" #include "utilities/copy.hpp" -#include - const RelocationHolder RelocationHolder::none; // its type is relocInfo::none diff --git a/src/hotspot/share/code/relocInfo.hpp b/src/hotspot/share/code/relocInfo.hpp index a6a08815d10..6f1778ef479 100644 --- a/src/hotspot/share/code/relocInfo.hpp +++ b/src/hotspot/share/code/relocInfo.hpp @@ -25,6 +25,7 @@ #ifndef SHARE_CODE_RELOCINFO_HPP #define SHARE_CODE_RELOCINFO_HPP +#include "cppstdlib/new.hpp" #include "memory/allocation.hpp" #include "oops/oopsHierarchy.hpp" #include "runtime/osInfo.hpp" @@ -32,8 +33,6 @@ #include "utilities/globalDefinitions.hpp" #include "utilities/macros.hpp" -#include - class CodeBlob; class Metadata; class NativeMovConstReg; diff --git a/src/hotspot/share/cppstdlib/new.hpp b/src/hotspot/share/cppstdlib/new.hpp new file mode 100644 index 00000000000..3536ac13288 --- /dev/null +++ b/src/hotspot/share/cppstdlib/new.hpp @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_CPPSTDLIB_NEW_HPP +#define SHARE_CPPSTDLIB_NEW_HPP + +#include "utilities/compilerWarnings.hpp" + +// HotSpot usage: +// Only the following may be used: +// * std::nothrow_t, std::nothrow +// * std::align_val_t +// * The non-allocating forms of `operator new` and `operator new[]` are +// implicitly used by the corresponding `new` and `new[]` expressions. +// - operator new(size_t, void*) noexcept +// - operator new[](size_t, void*) noexcept +// Note that the non-allocating forms of `operator delete` and `operator +// delete[]` are not used, since they are only invoked by a placement new +// expression that fails by throwing an exception. But they might still +// end up being referenced in such a situation. + +BEGIN_ALLOW_FORBIDDEN_FUNCTIONS +#include "utilities/vmassert_uninstall.hpp" + +#include + +#include "utilities/vmassert_reinstall.hpp" // don't reorder +END_ALLOW_FORBIDDEN_FUNCTIONS + +// Deprecation declarations to forbid use of the default global allocator. +// See C++17 21.6.1 Header synopsis. + +namespace std { + +#if 0 +// We could deprecate exception types, for completeness, but don't bother. We +// already have exceptions disabled, and run into compiler bugs when we try. +// +// gcc -Wattributes => type attributes ignored after type is already defined +// See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=122167 +// +// clang -Wignored-attributes => attribute declaration must precede definition +// The clang warning is https://github.com/llvm/llvm-project/issues/135481, +// which should be fixed in clang 21. +class [[deprecated]] bad_alloc; +class [[deprecated]] bad_array_new_length; +#endif // #if 0 + +// Forbid new_handler manipulation by HotSpot code, leaving it untouched for +// use by application code. +[[deprecated]] new_handler get_new_handler() noexcept; +[[deprecated]] new_handler set_new_handler(new_handler) noexcept; + +// Prefer HotSpot mechanisms for padding. +// +// The syntax for redeclaring these for deprecation is tricky, and not +// supported by some versions of some compilers. Dispatch on compiler and +// version to decide whether to redeclare deprecated. + +#if defined(__clang__) +#if __clang_major__ >= 19 +// clang18 and earlier may accept the declaration but go wrong with uses. +// Different warnings and link-time failures are both possible. +#define CAN_DEPRECATE_HARDWARE_INTERFERENCE_SIZES 1 +#endif // restrict clang version + +#elif defined(__GNUC__) +#if (__GNUC__ > 13) || (__GNUC__ == 13 && __GNUC_MINOR__ >= 2) +// g++11.5 accepts the declaration and reports deprecation for uses, but also +// has link-time failure for uses. Haven't tested intermediate versions. +#define CAN_DEPRECATE_HARDWARE_INTERFERENCE_SIZES 1 +#endif // restrict gcc version + +#elif defined(_MSVC) +// VS2022-17.13.2 => error C2370: '...': redefinition; different storage class + +#endif // Compiler dispatch + +// Redeclare deprecated if such is supported. +#ifdef CAN_DEPRECATE_HARDWARE_INTERFERENCE_SIZES +[[deprecated]] extern const size_t hardware_destructive_interference_size; +[[deprecated]] extern const size_t hardware_constructive_interference_size; +#undef CAN_DEPRECATE_HARDWARE_INTERFERENCE_SIZES +#endif // CAN_DEPRECATE_HARDWARE_INTERFERENCE_SIZES + +} // namespace std + +// Forbid using the global allocator by HotSpot code. +// This doesn't provide complete coverage. Some global allocation and +// deallocation functions are implicitly declared in all translation units, +// without needing to include ; see C++17 6.7.4. So this doesn't remove +// the need for the link-time verification that these functions aren't used. +// +// But don't poison them when compiling gtests. The gtest framework, the +// HotSpot wrapper around it (gtestMain.cpp), and even some tests, all have +// new/new[] and delete/delete[] expressions that use the default global +// allocator. We also don't apply the link-time check for gtests, for the +// same reason. +#ifndef HOTSPOT_GTEST + +[[deprecated]] void* operator new(std::size_t); +[[deprecated]] void* operator new(std::size_t, std::align_val_t); +[[deprecated]] void* operator new(std::size_t, const std::nothrow_t&) noexcept; +[[deprecated]] void* operator new(std::size_t, std::align_val_t, + const std::nothrow_t&) noexcept; + +[[deprecated]] void operator delete(void*) noexcept; +[[deprecated]] void operator delete(void*, std::size_t) noexcept; +[[deprecated]] void operator delete(void*, std::align_val_t) noexcept; +[[deprecated]] void operator delete(void*, std::size_t, std::align_val_t) noexcept; +[[deprecated]] void operator delete(void*, const std::nothrow_t&) noexcept; +[[deprecated]] void operator delete(void*, std::align_val_t, + const std::nothrow_t&) noexcept; + +[[deprecated]] void* operator new[](std::size_t); +[[deprecated]] void* operator new[](std::size_t, std::align_val_t); +[[deprecated]] void* operator new[](std::size_t, const std::nothrow_t&) noexcept; +[[deprecated]] void* operator new[](std::size_t, std::align_val_t, + const std::nothrow_t&) noexcept; + +[[deprecated]] void operator delete[](void*) noexcept; +[[deprecated]] void operator delete[](void*, std::size_t) noexcept; +[[deprecated]] void operator delete[](void*, std::align_val_t) noexcept; +[[deprecated]] void operator delete[](void*, std::size_t, std::align_val_t) noexcept; +[[deprecated]] void operator delete[](void*, const std::nothrow_t&) noexcept; +[[deprecated]] void operator delete[](void*, std::align_val_t, + const std::nothrow_t&) noexcept; + +#endif // HOTSPOT_GTEST + +// Allow (don't poison) the non-allocating forms from [new.delete.placement]. + +#endif // SHARE_CPPSTDLIB_NEW_HPP diff --git a/src/hotspot/share/gc/shared/bufferNode.cpp b/src/hotspot/share/gc/shared/bufferNode.cpp index b064f9c7efe..90e50f52e84 100644 --- a/src/hotspot/share/gc/shared/bufferNode.cpp +++ b/src/hotspot/share/gc/shared/bufferNode.cpp @@ -22,12 +22,11 @@ * */ +#include "cppstdlib/new.hpp" #include "gc/shared/bufferNode.hpp" #include "memory/allocation.inline.hpp" #include "utilities/debug.hpp" -#include - BufferNode::AllocatorConfig::AllocatorConfig(size_t size) : _buffer_capacity(size) { diff --git a/src/hotspot/share/gc/shared/partialArrayState.cpp b/src/hotspot/share/gc/shared/partialArrayState.cpp index f913f3db4ba..39c1fe4fc78 100644 --- a/src/hotspot/share/gc/shared/partialArrayState.cpp +++ b/src/hotspot/share/gc/shared/partialArrayState.cpp @@ -22,6 +22,7 @@ * */ +#include "cppstdlib/new.hpp" #include "gc/shared/partialArrayState.hpp" #include "memory/allocation.inline.hpp" #include "memory/arena.hpp" @@ -33,8 +34,6 @@ #include "utilities/globalDefinitions.hpp" #include "utilities/macros.hpp" -#include - PartialArrayState::PartialArrayState(oop src, oop dst, size_t index, size_t length, size_t initial_refcount) diff --git a/src/hotspot/share/gc/z/zDeferredConstructed.inline.hpp b/src/hotspot/share/gc/z/zDeferredConstructed.inline.hpp index d6d35ecddcd..f686bc78d15 100644 --- a/src/hotspot/share/gc/z/zDeferredConstructed.inline.hpp +++ b/src/hotspot/share/gc/z/zDeferredConstructed.inline.hpp @@ -27,10 +27,9 @@ #include "gc/z/zDeferredConstructed.hpp" +#include "cppstdlib/new.hpp" #include "cppstdlib/type_traits.hpp" -#include - template inline ZDeferredConstructed::ZDeferredConstructed() DEBUG_ONLY(: _initialized(false)) { diff --git a/src/hotspot/share/memory/allocation.hpp b/src/hotspot/share/memory/allocation.hpp index 35180fdba5e..963ca04aadf 100644 --- a/src/hotspot/share/memory/allocation.hpp +++ b/src/hotspot/share/memory/allocation.hpp @@ -25,14 +25,13 @@ #ifndef SHARE_MEMORY_ALLOCATION_HPP #define SHARE_MEMORY_ALLOCATION_HPP +#include "cppstdlib/new.hpp" #include "memory/allStatic.hpp" #include "nmt/memTag.hpp" #include "utilities/debug.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/macros.hpp" -#include - class outputStream; class Thread; class JavaThread; diff --git a/src/hotspot/share/memory/arena.cpp b/src/hotspot/share/memory/arena.cpp index b9968083e0e..2de3f837c00 100644 --- a/src/hotspot/share/memory/arena.cpp +++ b/src/hotspot/share/memory/arena.cpp @@ -24,6 +24,7 @@ */ #include "compiler/compilationMemoryStatistic.hpp" +#include "cppstdlib/new.hpp" #include "memory/allocation.inline.hpp" #include "memory/arena.hpp" #include "memory/resourceArea.hpp" diff --git a/src/hotspot/share/memory/arena.hpp b/src/hotspot/share/memory/arena.hpp index b4a0546babf..a8450b5543a 100644 --- a/src/hotspot/share/memory/arena.hpp +++ b/src/hotspot/share/memory/arena.hpp @@ -31,8 +31,6 @@ #include "utilities/globalDefinitions.hpp" #include "utilities/powerOfTwo.hpp" -#include - // The byte alignment to be used by Arena::Amalloc. #define ARENA_AMALLOC_ALIGNMENT BytesPerLong #define ARENA_ALIGN(x) (align_up((x), ARENA_AMALLOC_ALIGNMENT)) diff --git a/src/hotspot/share/utilities/debug.cpp b/src/hotspot/share/utilities/debug.cpp index 89c0a1ebc08..de39fe32dc1 100644 --- a/src/hotspot/share/utilities/debug.cpp +++ b/src/hotspot/share/utilities/debug.cpp @@ -29,6 +29,7 @@ #include "code/vtableStubs.hpp" #include "compiler/compileBroker.hpp" #include "compiler/disassembler.hpp" +#include "cppstdlib/new.hpp" #include "gc/shared/collectedHeap.hpp" #include "interpreter/interpreter.hpp" #include "jvm.h" @@ -63,7 +64,6 @@ #include "utilities/unsigned5.hpp" #include "utilities/vmError.hpp" -#include #include #include diff --git a/src/hotspot/share/utilities/deferredStatic.hpp b/src/hotspot/share/utilities/deferredStatic.hpp index 56bdb9b8e6b..3a32f920fe8 100644 --- a/src/hotspot/share/utilities/deferredStatic.hpp +++ b/src/hotspot/share/utilities/deferredStatic.hpp @@ -25,11 +25,10 @@ #ifndef SHARE_UTILITIES_DEFERREDSTATIC_HPP #define SHARE_UTILITIES_DEFERREDSTATIC_HPP +#include "cppstdlib/new.hpp" #include "cppstdlib/type_traits.hpp" #include "utilities/globalDefinitions.hpp" -#include - // The purpose of this class is to provide control over the initialization // time for an object of type T with static storage duration. An instance of // this class provides storage for an object, sized and aligned for T. The diff --git a/src/hotspot/share/utilities/elfFile.cpp b/src/hotspot/share/utilities/elfFile.cpp index 9ea19b38276..0b7713e9ca9 100644 --- a/src/hotspot/share/utilities/elfFile.cpp +++ b/src/hotspot/share/utilities/elfFile.cpp @@ -25,6 +25,7 @@ #if !defined(_WINDOWS) && !defined(__APPLE__) +#include "cppstdlib/new.hpp" #include "jvm_io.h" #include "logging/log.hpp" #include "memory/allocation.inline.hpp" @@ -37,7 +38,6 @@ #include "utilities/ostream.hpp" #include -#include #include #include diff --git a/src/hotspot/share/utilities/globalDefinitions.hpp b/src/hotspot/share/utilities/globalDefinitions.hpp index 1910759b434..3284fd3bd15 100644 --- a/src/hotspot/share/utilities/globalDefinitions.hpp +++ b/src/hotspot/share/utilities/globalDefinitions.hpp @@ -1386,4 +1386,25 @@ template inline constexpr bool DependentAlwaysFalse = false; // handled. bool IEEE_subnormal_handling_OK(); +//---------------------------------------------------------------------------------------------------- +// Forbid using the global allocator by HotSpot code. +// +// This is a subset of allocator and deallocator functions. These are +// implicitly declared in all translation units, without needing to include +// ; see C++17 6.7.4. This isn't even the full set of those; implicit +// declarations involving std::align_val_t are not covered here, since that +// type is defined in . A translation unit that doesn't include is +// still likely to include this file. See cppstdlib/new.hpp for more details. +#ifndef HOTSPOT_GTEST + +[[deprecated]] void* operator new(std::size_t); +[[deprecated]] void operator delete(void*) noexcept; +[[deprecated]] void operator delete(void*, std::size_t) noexcept; + +[[deprecated]] void* operator new[](std::size_t); +[[deprecated]] void operator delete[](void*) noexcept; +[[deprecated]] void operator delete[](void*, std::size_t) noexcept; + +#endif // HOTSPOT_GTEST + #endif // SHARE_UTILITIES_GLOBALDEFINITIONS_HPP diff --git a/test/hotspot/gtest/utilities/test_lockFreeStack.cpp b/test/hotspot/gtest/utilities/test_lockFreeStack.cpp index fac17e87016..bdba49b48c0 100644 --- a/test/hotspot/gtest/utilities/test_lockFreeStack.cpp +++ b/test/hotspot/gtest/utilities/test_lockFreeStack.cpp @@ -21,6 +21,7 @@ * questions. */ +#include "cppstdlib/new.hpp" #include "memory/allocation.inline.hpp" #include "runtime/atomic.hpp" #include "utilities/globalDefinitions.hpp" @@ -28,8 +29,6 @@ #include "threadHelper.inline.hpp" #include "unittest.hpp" -#include - class LockFreeStackTestElement { typedef LockFreeStackTestElement Element; From 1535d08f0ee5da42d9db9e196d6a620aabe9feea Mon Sep 17 00:00:00 2001 From: Ioi Lam Date: Wed, 19 Nov 2025 20:58:23 +0000 Subject: [PATCH 136/418] 8371944: AOT configuration is corrupted when app closes System.out Reviewed-by: kvn, iveresov --- src/hotspot/share/cds/aotMetaspace.cpp | 46 ++++++++--- src/hotspot/share/cds/aotMetaspace.hpp | 2 + src/hotspot/share/cds/dynamicArchive.cpp | 1 + src/hotspot/share/cds/filemap.cpp | 2 + src/hotspot/share/cds/filemap.hpp | 1 + .../cds/appcds/aotCache/CloseSystemOut.java | 82 +++++++++++++++++++ 6 files changed, 121 insertions(+), 13 deletions(-) create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/aotCache/CloseSystemOut.java diff --git a/src/hotspot/share/cds/aotMetaspace.cpp b/src/hotspot/share/cds/aotMetaspace.cpp index 8642b1a6de8..42d41e6ae89 100644 --- a/src/hotspot/share/cds/aotMetaspace.cpp +++ b/src/hotspot/share/cds/aotMetaspace.cpp @@ -114,6 +114,7 @@ intx AOTMetaspace::_relocation_delta; char* AOTMetaspace::_requested_base_address; Array* AOTMetaspace::_archived_method_handle_intrinsics = nullptr; bool AOTMetaspace::_use_optimized_module_handling = true; +FileMapInfo* AOTMetaspace::_output_mapinfo = nullptr; // The CDS archive is divided into the following regions: // rw - read-write metadata @@ -322,6 +323,24 @@ void AOTMetaspace::initialize_for_static_dump() { AOTMetaspace::unrecoverable_writing_error(); } _symbol_region.init(&_symbol_rs, &_symbol_vs); + if (CDSConfig::is_dumping_preimage_static_archive()) { + // We are in the AOT training run. User code is executed. + // + // On Windows, if the user code closes System.out and we open the AOT config file for output + // only at VM exit, we might get back the same file HANDLE as stdout, and the AOT config + // file may get corrupted by UL logs. By opening early, we ensure that the output + // HANDLE is different than stdout so we can avoid such corruption. + open_output_mapinfo(); + } else { + // No need for the above as we won't execute any user code. + } +} + +void AOTMetaspace::open_output_mapinfo() { + const char* static_archive = CDSConfig::output_archive_path(); + assert(static_archive != nullptr, "sanity"); + _output_mapinfo = new FileMapInfo(static_archive, true); + _output_mapinfo->open_as_output(); } // Called by universe_post_init() @@ -655,15 +674,14 @@ private: public: - VM_PopulateDumpSharedSpace(StaticArchiveBuilder& b) : - VM_Operation(), _mapped_heap_info(), _streamed_heap_info(), _map_info(nullptr), _builder(b) {} + VM_PopulateDumpSharedSpace(StaticArchiveBuilder& b, FileMapInfo* map_info) : + VM_Operation(), _mapped_heap_info(), _streamed_heap_info(), _map_info(map_info), _builder(b) {} bool skip_operation() const { return false; } VMOp_Type type() const { return VMOp_PopulateDumpSharedSpace; } ArchiveMappedHeapInfo* mapped_heap_info() { return &_mapped_heap_info; } ArchiveStreamedHeapInfo* streamed_heap_info() { return &_streamed_heap_info; } - FileMapInfo* map_info() const { return _map_info; } void doit(); // outline because gdb sucks bool allow_nested_vm_operations() const { return true; } }; // class VM_PopulateDumpSharedSpace @@ -795,12 +813,6 @@ void VM_PopulateDumpSharedSpace::doit() { CppVtables::zero_archived_vtables(); // Write the archive file - if (CDSConfig::is_dumping_final_static_archive()) { - FileMapInfo::free_current_info(); // FIXME: should not free current info - } - const char* static_archive = CDSConfig::output_archive_path(); - assert(static_archive != nullptr, "sanity"); - _map_info = new FileMapInfo(static_archive, true); _map_info->populate_header(AOTMetaspace::core_region_alignment()); _map_info->set_early_serialized_data(early_serialized_data); _map_info->set_serialized_data(serialized_data); @@ -1138,7 +1150,14 @@ void AOTMetaspace::dump_static_archive_impl(StaticArchiveBuilder& builder, TRAPS } #endif - VM_PopulateDumpSharedSpace op(builder); + if (!CDSConfig::is_dumping_preimage_static_archive()) { + if (CDSConfig::is_dumping_final_static_archive()) { + FileMapInfo::free_current_info(); // FIXME: should not free current info + } + open_output_mapinfo(); + } + + VM_PopulateDumpSharedSpace op(builder, _output_mapinfo); VMThread::execute(&op); if (AOTCodeCache::is_on_for_dump() && CDSConfig::is_dumping_final_static_archive()) { @@ -1152,7 +1171,9 @@ void AOTMetaspace::dump_static_archive_impl(StaticArchiveBuilder& builder, TRAPS CDSConfig::disable_dumping_aot_code(); } - bool status = write_static_archive(&builder, op.map_info(), op.mapped_heap_info(), op.streamed_heap_info()); + bool status = write_static_archive(&builder, _output_mapinfo, op.mapped_heap_info(), op.streamed_heap_info()); + assert(!_output_mapinfo->is_open(), "Must be closed already"); + _output_mapinfo = nullptr; if (status && CDSConfig::is_dumping_preimage_static_archive()) { tty->print_cr("%s AOTConfiguration recorded: %s", CDSConfig::has_temp_aot_config_file() ? "Temporary" : "", AOTConfiguration); @@ -1173,11 +1194,10 @@ bool AOTMetaspace::write_static_archive(ArchiveBuilder* builder, // relocate the data so that it can be mapped to AOTMetaspace::requested_base_address() // without runtime relocation. builder->relocate_to_requested(); - - map_info->open_as_output(); if (!map_info->is_open()) { return false; } + map_info->prepare_for_writing(); builder->write_archive(map_info, mapped_heap_info, streamed_heap_info); return true; } diff --git a/src/hotspot/share/cds/aotMetaspace.hpp b/src/hotspot/share/cds/aotMetaspace.hpp index bfd9f4bcc75..1712a7865ad 100644 --- a/src/hotspot/share/cds/aotMetaspace.hpp +++ b/src/hotspot/share/cds/aotMetaspace.hpp @@ -60,6 +60,7 @@ class AOTMetaspace : AllStatic { static char* _requested_base_address; static bool _use_optimized_module_handling; static Array* _archived_method_handle_intrinsics; + static FileMapInfo* _output_mapinfo; public: enum { @@ -185,6 +186,7 @@ public: private: static void read_extra_data(JavaThread* current, const char* filename) NOT_CDS_RETURN; static void fork_and_dump_final_static_archive(TRAPS); + static void open_output_mapinfo(); static bool write_static_archive(ArchiveBuilder* builder, FileMapInfo* map_info, ArchiveMappedHeapInfo* mapped_heap_info, diff --git a/src/hotspot/share/cds/dynamicArchive.cpp b/src/hotspot/share/cds/dynamicArchive.cpp index 85e59e23f8c..8fae8dabf8c 100644 --- a/src/hotspot/share/cds/dynamicArchive.cpp +++ b/src/hotspot/share/cds/dynamicArchive.cpp @@ -353,6 +353,7 @@ void DynamicArchiveBuilder::write_archive(char* serialized_data, AOTClassLocatio assert(dynamic_info != nullptr, "Sanity"); dynamic_info->open_as_output(); + dynamic_info->prepare_for_writing(); ArchiveBuilder::write_archive(dynamic_info, nullptr, nullptr); address base = _requested_dynamic_archive_bottom; diff --git a/src/hotspot/share/cds/filemap.cpp b/src/hotspot/share/cds/filemap.cpp index 61df0a86b41..0eeb96bb269 100644 --- a/src/hotspot/share/cds/filemap.cpp +++ b/src/hotspot/share/cds/filemap.cpp @@ -779,7 +779,9 @@ void FileMapInfo::open_as_output() { } _fd = fd; _file_open = true; +} +void FileMapInfo::prepare_for_writing() { // Seek past the header. We will write the header after all regions are written // and their CRCs computed. size_t header_bytes = header()->header_size(); diff --git a/src/hotspot/share/cds/filemap.hpp b/src/hotspot/share/cds/filemap.hpp index 2a761843e47..fbd3c8e1681 100644 --- a/src/hotspot/share/cds/filemap.hpp +++ b/src/hotspot/share/cds/filemap.hpp @@ -365,6 +365,7 @@ public: // File manipulation. bool open_as_input() NOT_CDS_RETURN_(false); void open_as_output(); + void prepare_for_writing(); void write_header(); void write_region(int region, char* base, size_t size, bool read_only, bool allow_exec); diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/CloseSystemOut.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/CloseSystemOut.java new file mode 100644 index 00000000000..1f4111ecfb1 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/CloseSystemOut.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +/* + * @test + * @summary AOT configuration should not be corrupted even if the app closes System.out in the training run + * @bug 8371944 + * @library /test/jdk/lib/testlibrary /test/lib + * @build CloseSystemOut + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar CloseSystemOutApp + * @run driver CloseSystemOut + */ + +import java.io.PrintWriter; +import jdk.test.lib.cds.CDSAppTester; +import jdk.test.lib.helpers.ClassFileInstaller; +import jdk.test.lib.process.OutputAnalyzer; + +public class CloseSystemOut { + static final String appJar = ClassFileInstaller.getJarPath("app.jar"); + static final String mainClass = "CloseSystemOutApp"; + + public static void main(String[] args) throws Exception { + Tester tester = new Tester(); + tester.run(new String[] {"AOT", "--two-step-training"} ); + } + + static class Tester extends CDSAppTester { + public Tester() { + super(mainClass); + } + + @Override + public String classpath(RunMode runMode) { + return appJar; + } + + @Override + public String[] appCommandLine(RunMode runMode) { + return new String[] {mainClass}; + } + + @Override + public void checkExecution(OutputAnalyzer out, RunMode runMode) { + if (runMode != RunMode.ASSEMBLY) { + out.shouldContain("Hello Confused World"); + } + } + } +} + +class CloseSystemOutApp { + public static void main(String args[]) { + // Naive code that ends up closing System.out/err when we + // leave the "try" block + try (var err = new PrintWriter(System.err); + var out = new PrintWriter(System.out)) { + out.println("Hello Confused World"); + } + } +} From c8e64e7c33cabcc5c94616808b9c59ab5b7cd14e Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Wed, 19 Nov 2025 23:22:40 +0000 Subject: [PATCH 137/418] 8372118: Test tools/jpackage/macosx/DmgContentTest.java failed Reviewed-by: almatvee --- .../macosx/classes/jdk/jpackage/internal/MacDmgPackager.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgPackager.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgPackager.java index 6e13a2ff0c1..4ccc459109f 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgPackager.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgPackager.java @@ -30,6 +30,7 @@ import static jdk.jpackage.internal.util.PathUtils.normalizedAbsolutePathString; import java.io.File; import java.io.IOException; import java.nio.file.Files; +import java.nio.file.LinkOption; import java.nio.file.Path; import java.text.MessageFormat; import java.util.Base64; @@ -288,7 +289,7 @@ record MacDmgPackager(BuildEnv env, MacDmgPackage pkg, Path outputDir, // Copy app image, since we did not create DMG with it, but instead we created // empty one. if (copyAppImage) { - FileUtils.copyRecursive(srcFolder, mountedVolume); + FileUtils.copyRecursive(srcFolder, mountedVolume, LinkOption.NOFOLLOW_LINKS); } try { From 2acd8776f26686a93708eb9fc408ff4e2bbe287c Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Thu, 20 Nov 2025 01:29:49 +0000 Subject: [PATCH 138/418] 8371440: jpackage should exit with an error if it finds multiple matching signing certificates Reviewed-by: almatvee --- .../internal/SigningIdentityBuilder.java | 7 ++-- .../resources/MacResources.properties | 2 +- .../tools/jpackage/macosx/MacSignTest.java | 40 +++++++++++-------- 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/SigningIdentityBuilder.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/SigningIdentityBuilder.java index bf8f1519fe1..f90e76bb23d 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/SigningIdentityBuilder.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/SigningIdentityBuilder.java @@ -156,9 +156,10 @@ final class SigningIdentityBuilder { return certs.getFirst(); } default -> { - Log.error(I18N.format("error.multiple.certs.found", certificateSelector.signingIdentities().getFirst(), - keychain.map(Keychain::name).orElse(""))); - return certs.getFirst(); + throw I18N.buildConfigException("error.multiple.certs.found", + certificateSelector.signingIdentities().getFirst(), + keychain.map(Keychain::name).orElse("") + ).create(); } } } diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources.properties b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources.properties index 58c3bbbc025..afa71d84d5c 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources.properties +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources.properties @@ -36,7 +36,7 @@ error.must-sign-app-store=Mac App Store apps must be signed, and signing has bee error.must-sign-app-store.advice=Use --mac-sign option with appropriate user-name and keychain error.certificate.expired=Error: Certificate expired {0} error.cert.not.found=No certificate found matching [{0}] using keychain [{1}] -error.multiple.certs.found=WARNING: Multiple certificates found matching [{0}] using keychain [{1}], using first one +error.multiple.certs.found=Multiple certificates matching name [{0}] found in keychain [{1}] error.app-image.mac-sign.required=Error: --mac-sign option is required with predefined application image and with type [app-image] error.tool.failed.with.output=Error: "{0}" failed with following output: error.invalid-runtime-image-missing-file=Runtime image "{0}" is missing "{1}" file diff --git a/test/jdk/tools/jpackage/macosx/MacSignTest.java b/test/jdk/tools/jpackage/macosx/MacSignTest.java index 014fbc84548..af7cf448bdc 100644 --- a/test/jdk/tools/jpackage/macosx/MacSignTest.java +++ b/test/jdk/tools/jpackage/macosx/MacSignTest.java @@ -39,6 +39,7 @@ import jdk.jpackage.test.JPackageStringBundle; import jdk.jpackage.test.MacHelper; import jdk.jpackage.test.MacSign; import jdk.jpackage.test.MacSign.CertificateRequest; +import jdk.jpackage.test.MacSign.CertificateType; import jdk.jpackage.test.MacSignVerify; import jdk.jpackage.test.PackageType; import jdk.jpackage.test.TKit; @@ -155,20 +156,13 @@ public class MacSignTest { } @Test - // Case "--mac-signing-key-user-name": jpackage selects first certificate - // found with warning message. Certificate hash is pass to "codesign" in this - // case. - @Parameter({"IMAGE", "0", "GOOD_SIGNING_KEY_USER_NAME"}) - @Parameter({"MAC_DMG", "0", "GOOD_SIGNING_KEY_USER_NAME"}) - @Parameter({"MAC_PKG", "0", "GOOD_SIGNING_KEY_USER_NAME_PKG", "GOOD_SIGNING_KEY_USER_NAME"}) - - // Case "--mac-app-image-sign-identity": sign identity will be pass to - // "codesign" and "codesign" should fail due to multiple certificates with - // same common name found. - @Parameter({"IMAGE", "1", "GOOD_CODESIGN_SIGN_IDENTITY"}) - @Parameter({"MAC_PKG", "1", "GOOD_CODESIGN_SIGN_IDENTITY", "GOOD_PKG_SIGN_IDENTITY"}) - @Parameter({"MAC_PKG", "1", "GOOD_PKG_SIGN_IDENTITY"}) - public static void testMultipleCertificates(PackageType type, int jpackageExitCode, SignOption... options) { + @Parameter({"IMAGE", "GOOD_SIGNING_KEY_USER_NAME"}) + @Parameter({"MAC_DMG", "GOOD_SIGNING_KEY_USER_NAME"}) + @Parameter({"MAC_PKG", "GOOD_SIGNING_KEY_USER_NAME_PKG", "GOOD_SIGNING_KEY_USER_NAME"}) + @Parameter({"IMAGE", "GOOD_CODESIGN_SIGN_IDENTITY"}) + @Parameter({"MAC_PKG", "GOOD_CODESIGN_SIGN_IDENTITY", "GOOD_PKG_SIGN_IDENTITY"}) + @Parameter({"MAC_PKG", "GOOD_PKG_SIGN_IDENTITY"}) + public static void testMultipleCertificates(PackageType type, SignOption... options) { MacSign.withKeychain(keychain -> { final var cmd = MacHelper.useKeychain(JPackageCommand.helloAppImage(), keychain) @@ -176,9 +170,19 @@ public class MacSignTest { .addArguments(Stream.of(options).map(SignOption::args).flatMap(List::stream).toList()) .setPackageType(type); - SignOption.configureOutputValidation(cmd, List.of(options), opt -> { + Predicate filter = opt -> { + if (type == PackageType.MAC_PKG && options.length > 1) { + // Only the first error will be reported and it should always be + // for the app image signing, not for the PKG signing. + return opt.identityType() == CertificateType.CODE_SIGN; + } else { + return true; + } + }; + + SignOption.configureOutputValidation(cmd, Stream.of(options).filter(filter).toList(), opt -> { return JPackageStringBundle.MAIN.cannedFormattedString("error.multiple.certs.found", opt.identityName(), keychain.name()); - }).execute(jpackageExitCode); + }).execute(1); }, MacSign.Keychain.UsageBuilder::addToSearchList, SigningBase.StandardKeychain.DUPLICATE.keychain()); } @@ -244,6 +248,10 @@ public class MacSignTest { return cert.name(); } + CertificateType identityType() { + return cert.type(); + } + List args() { return List.of(option, shortName ? cert.shortName() : cert.name()); } From a3b1affbfb23eeef32749164aae316e5d55fffaa Mon Sep 17 00:00:00 2001 From: Fei Yang Date: Thu, 20 Nov 2025 02:18:44 +0000 Subject: [PATCH 139/418] 8372046: compiler/floatingpoint/TestSubNodeFloatDoubleNegation.java fails IR verification Reviewed-by: mhaessig, epeter --- .../floatingpoint/TestSubNodeFloatDoubleNegation.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/hotspot/jtreg/compiler/floatingpoint/TestSubNodeFloatDoubleNegation.java b/test/hotspot/jtreg/compiler/floatingpoint/TestSubNodeFloatDoubleNegation.java index 4c7092ec654..d96e64baa36 100644 --- a/test/hotspot/jtreg/compiler/floatingpoint/TestSubNodeFloatDoubleNegation.java +++ b/test/hotspot/jtreg/compiler/floatingpoint/TestSubNodeFloatDoubleNegation.java @@ -38,7 +38,13 @@ import jdk.test.lib.Asserts; public class TestSubNodeFloatDoubleNegation { public static void main(String[] args) { - TestFramework.runWithFlags("--add-modules=jdk.incubator.vector", "-XX:CompileCommand=inline,jdk.incubator.vector.Float16::*"); + // Disable inlining for java.lang.Float::float16ToFloat and java.lang.Float::floatToFloat16. + // Otherwise, they could be inlined into testHalfFloat on platforms where there is no support + // for fp16, which causes unexpected IR graph. + TestFramework.runWithFlags("--add-modules=jdk.incubator.vector", + "-XX:CompileCommand=inline,jdk.incubator.vector.Float16::*", + "-XX:CompileCommand=dontinline,java.lang.Float::float16ToFloat", + "-XX:CompileCommand=dontinline,java.lang.Float::floatToFloat16"); } @Run(test = { "testHalfFloat", "testFloat", "testDouble" }) From 473471c1f1d3cd42a057dfd602d452196c53aa00 Mon Sep 17 00:00:00 2001 From: Henry Jen Date: Thu, 20 Nov 2025 05:30:40 +0000 Subject: [PATCH 140/418] 8369838: Likely invalid assert or function call in jimage.cpp Reviewed-by: dholmes --- src/hotspot/share/classfile/classLoader.cpp | 37 +++++++++---------- .../share/native/libjimage/jimage.cpp | 2 +- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/src/hotspot/share/classfile/classLoader.cpp b/src/hotspot/share/classfile/classLoader.cpp index 082c745f4c3..12fbda899b9 100644 --- a/src/hotspot/share/classfile/classLoader.cpp +++ b/src/hotspot/share/classfile/classLoader.cpp @@ -412,31 +412,30 @@ ClassFileStream* ClassPathImageEntry::open_stream(JavaThread* current, const cha // ClassFileStream* ClassPathImageEntry::open_stream_for_loader(JavaThread* current, const char* name, ClassLoaderData* loader_data) { jlong size; - JImageLocationRef location = (*JImageFindResource)(jimage_non_null(), "", get_jimage_version_string(), name, &size); + JImageLocationRef location = 0; - if (location == 0) { - TempNewSymbol class_name = SymbolTable::new_symbol(name); - TempNewSymbol pkg_name = ClassLoader::package_from_class_name(class_name); + TempNewSymbol class_name = SymbolTable::new_symbol(name); + TempNewSymbol pkg_name = ClassLoader::package_from_class_name(class_name); - if (pkg_name != nullptr) { - if (!Universe::is_module_initialized()) { - location = (*JImageFindResource)(jimage_non_null(), JAVA_BASE_NAME, get_jimage_version_string(), name, &size); - } else { - PackageEntry* package_entry = ClassLoader::get_package_entry(pkg_name, loader_data); - if (package_entry != nullptr) { - ResourceMark rm(current); - // Get the module name - ModuleEntry* module = package_entry->module(); - assert(module != nullptr, "Boot classLoader package missing module"); - assert(module->is_named(), "Boot classLoader package is in unnamed module"); - const char* module_name = module->name()->as_C_string(); - if (module_name != nullptr) { - location = (*JImageFindResource)(jimage_non_null(), module_name, get_jimage_version_string(), name, &size); - } + if (pkg_name != nullptr) { + if (!Universe::is_module_initialized()) { + location = (*JImageFindResource)(jimage_non_null(), JAVA_BASE_NAME, get_jimage_version_string(), name, &size); + } else { + PackageEntry* package_entry = ClassLoader::get_package_entry(pkg_name, loader_data); + if (package_entry != nullptr) { + ResourceMark rm(current); + // Get the module name + ModuleEntry* module = package_entry->module(); + assert(module != nullptr, "Boot classLoader package missing module"); + assert(module->is_named(), "Boot classLoader package is in unnamed module"); + const char* module_name = module->name()->as_C_string(); + if (module_name != nullptr) { + location = (*JImageFindResource)(jimage_non_null(), module_name, get_jimage_version_string(), name, &size); } } } } + if (location != 0) { if (UsePerfData) { ClassLoader::perf_sys_classfile_bytes_read()->inc(size); diff --git a/src/java.base/share/native/libjimage/jimage.cpp b/src/java.base/share/native/libjimage/jimage.cpp index 10e85eb2520..91a86f992e6 100644 --- a/src/java.base/share/native/libjimage/jimage.cpp +++ b/src/java.base/share/native/libjimage/jimage.cpp @@ -110,7 +110,7 @@ JIMAGE_FindResource(JImageFile* image, size_t nameLen = strlen(name); size_t index; - // TBD: assert(moduleNameLen > 0 && "module name must be non-empty"); + assert(moduleNameLen > 0 && "module name must be non-empty"); assert(nameLen > 0 && "name must non-empty"); // If the concatenated string is too long for the buffer, return not found From 5d3e73b9e512b55cdf554158b19a4ec642dc1f1a Mon Sep 17 00:00:00 2001 From: Jan Lahoda Date: Thu, 20 Nov 2025 06:14:40 +0000 Subject: [PATCH 141/418] 8371248: Crash in -Xdoclint with invalid @link Reviewed-by: hannesw, vromero --- .../com/sun/tools/javac/api/JavacTrees.java | 4 +++ .../com/sun/tools/javac/comp/Attr.java | 2 +- .../tools/javac/doctree/ReferenceTest.java | 30 +++++++++++++++++-- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTrees.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTrees.java index 2eca26de838..f933ef36565 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTrees.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTrees.java @@ -473,6 +473,10 @@ public class JavacTrees extends DocTrees { if (memberName == null) return tsym; + if (tsym.type.isPrimitive()) { + return null; + } + final List paramTypes; if (ref.paramTypes == null) paramTypes = null; diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java index ad41adcc135..c723caf1843 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java @@ -377,7 +377,7 @@ public class Attr extends JCTree.Visitor { @Override @DefinedBy(Api.COMPILER_TREE) public Symbol visitMemberSelect(MemberSelectTree node, Env env) { Symbol site = visit(node.getExpression(), env); - if (site.kind == ERR || site.kind == ABSENT_TYP || site.kind == HIDDEN) + if (site == null || site.kind == ERR || site.kind == ABSENT_TYP || site.kind == HIDDEN) return site; Name name = (Name)node.getIdentifier(); if (site.kind == PCK) { diff --git a/test/langtools/tools/javac/doctree/ReferenceTest.java b/test/langtools/tools/javac/doctree/ReferenceTest.java index 46c3d40e73a..540cb9a6621 100644 --- a/test/langtools/tools/javac/doctree/ReferenceTest.java +++ b/test/langtools/tools/javac/doctree/ReferenceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2025, 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 7021614 8278373 8164094 + * @bug 7021614 8278373 8164094 8371248 * @summary extend com.sun.source API to support parsing javadoc comments * @summary check references in at-see and {at-link} tags * @modules jdk.compiler @@ -43,6 +43,7 @@ import com.sun.source.util.DocTrees; import com.sun.source.util.TreePath; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import javax.annotation.processing.AbstractProcessor; @@ -83,6 +84,31 @@ import javax.tools.Diagnostic.Kind; * {@link #trees Field} * {@link #getSupportedSourceVersion Method} * {@link #init(ProcessingEnvironment Method} + * {@link double Class} + * {@link double.NAN Bad} + * {@link double#NAN Bad} + * {@link double#double Bad} + * {@link java.base/double Bad} + * + * {@link List Interface} + * {@link List.add Bad} + * {@link List#add Method} + * {@link List#add(Object) Method} + * {@link Map.Entry Interface} + * {@link Map.Entry Interface} + * {@link Map.Entry.getKey Bad} + * {@link Map.Entry#getKey Method} + * {@link Map.Entry#setValue(Object) Method} + * + * {@link java.base/java.util.List Bad} + * {@link java.base/java.util.List.add Bad} + * {@link java.base/java.util.List#add Bad} + * {@link java.base/java.util.List#add(Object) Bad} + * {@link java.base/java.util.Map.Entry Bad} + * {@link java.base/java.util.Map.Entry Bad} + * {@link java.base/java.util.Map.Entry.getKey Bad} + * {@link java.base/java.util.Map.Entry#getKey Bad} + * {@link java.base/java.util.Map.Entry#setValue(Object) Bad} * * @see java.lang Package * @see java.lang.ERROR Bad From 72c45a4d923a294108995e24951bec24dfc70410 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20Sj=C3=B6len?= Date: Thu, 20 Nov 2025 07:08:46 +0000 Subject: [PATCH 142/418] 8355225: Test gtest/AsyncLogGtest.java failed at droppingMessage_vm: apparent log corruption Reviewed-by: dholmes, syan --- test/hotspot/gtest/logging/test_asynclog.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/hotspot/gtest/logging/test_asynclog.cpp b/test/hotspot/gtest/logging/test_asynclog.cpp index fdc3795e9db..2634b8dac77 100644 --- a/test/hotspot/gtest/logging/test_asynclog.cpp +++ b/test/hotspot/gtest/logging/test_asynclog.cpp @@ -253,6 +253,8 @@ TEST_VM_F(AsyncLogTest, droppingMessage) { set_log_config(TestLogFileName, "logging=debug"); test_asynclog_drop_messages(); + AsyncLogWriter::flush(); + fflush(nullptr); bool messages_dropped = file_contains_substring(TestLogFileName, "messages dropped due to async logging"); if (!messages_dropped) { stringStream content; From 852141b9d42ada168a008aea63045deddca29190 Mon Sep 17 00:00:00 2001 From: Sean Coffey Date: Thu, 20 Nov 2025 07:32:06 +0000 Subject: [PATCH 143/418] 8372004: Have SSLLogger implement System.Logger Reviewed-by: dfuchs, weijun --- .../share/classes/sun/security/ssl/Alert.java | 2 +- .../sun/security/ssl/AlpnExtension.java | 18 +- .../security/ssl/CertSignAlgsExtension.java | 8 +- .../sun/security/ssl/CertStatusExtension.java | 32 +- .../ssl/CertificateAuthoritiesExtension.java | 16 +- .../sun/security/ssl/CertificateMessage.java | 32 +- .../sun/security/ssl/CertificateRequest.java | 22 +- .../sun/security/ssl/CertificateStatus.java | 6 +- .../sun/security/ssl/CertificateVerify.java | 26 +- .../sun/security/ssl/ChangeCipherSpec.java | 6 +- .../classes/sun/security/ssl/ClientHello.java | 42 +- .../sun/security/ssl/CookieExtension.java | 10 +- .../sun/security/ssl/DHClientKeyExchange.java | 4 +- .../sun/security/ssl/DHServerKeyExchange.java | 4 +- .../sun/security/ssl/DTLSInputRecord.java | 76 ++-- .../sun/security/ssl/DTLSOutputRecord.java | 22 +- .../security/ssl/ECDHClientKeyExchange.java | 8 +- .../security/ssl/ECDHServerKeyExchange.java | 4 +- .../security/ssl/ECPointFormatsExtension.java | 6 +- .../sun/security/ssl/EncryptedExtensions.java | 4 +- .../ssl/ExtendedMasterSecretExtension.java | 10 +- .../classes/sun/security/ssl/Finished.java | 16 +- .../sun/security/ssl/HandshakeContext.java | 12 +- .../sun/security/ssl/HandshakeOutStream.java | 2 +- .../sun/security/ssl/HelloRequest.java | 10 +- .../sun/security/ssl/HelloVerifyRequest.java | 4 +- .../sun/security/ssl/KeyShareExtension.java | 32 +- .../classes/sun/security/ssl/KeyUpdate.java | 8 +- .../sun/security/ssl/MaxFragExtension.java | 18 +- .../classes/sun/security/ssl/NamedGroup.java | 8 +- .../sun/security/ssl/NewSessionTicket.java | 38 +- .../sun/security/ssl/OutputRecord.java | 4 +- .../security/ssl/PreSharedKeyExtension.java | 34 +- .../ssl/PredefinedDHParameterSpecs.java | 6 +- .../ssl/PskKeyExchangeModesExtension.java | 8 +- .../security/ssl/QuicEngineOutputRecord.java | 8 +- .../sun/security/ssl/QuicKeyManager.java | 26 +- .../sun/security/ssl/QuicTLSEngineImpl.java | 6 +- .../security/ssl/RSAClientKeyExchange.java | 4 +- .../sun/security/ssl/RSAKeyExchange.java | 11 +- .../security/ssl/RSAServerKeyExchange.java | 4 +- .../sun/security/ssl/RenegoInfoExtension.java | 24 +- .../security/ssl/SSLAlgorithmConstraints.java | 2 +- .../classes/sun/security/ssl/SSLCipher.java | 44 +- .../sun/security/ssl/SSLConfiguration.java | 8 +- .../sun/security/ssl/SSLContextImpl.java | 32 +- .../sun/security/ssl/SSLEngineImpl.java | 14 +- .../security/ssl/SSLEngineInputRecord.java | 10 +- .../security/ssl/SSLEngineOutputRecord.java | 26 +- .../sun/security/ssl/SSLExtension.java | 2 +- .../sun/security/ssl/SSLExtensions.java | 32 +- .../classes/sun/security/ssl/SSLLogger.java | 415 +++++++++--------- .../security/ssl/SSLMasterKeyDerivation.java | 3 +- .../security/ssl/SSLSessionContextImpl.java | 8 +- .../sun/security/ssl/SSLSessionImpl.java | 16 +- .../sun/security/ssl/SSLSocketImpl.java | 46 +- .../security/ssl/SSLSocketInputRecord.java | 14 +- .../security/ssl/SSLSocketOutputRecord.java | 32 +- .../sun/security/ssl/SSLTransport.java | 8 +- .../classes/sun/security/ssl/ServerHello.java | 26 +- .../sun/security/ssl/ServerHelloDone.java | 4 +- .../sun/security/ssl/ServerNameExtension.java | 20 +- .../security/ssl/SessionTicketExtension.java | 24 +- .../ssl/SignatureAlgorithmsExtension.java | 4 +- .../sun/security/ssl/SignatureScheme.java | 20 +- .../security/ssl/StatusResponseManager.java | 62 +-- .../security/ssl/SunX509KeyManagerImpl.java | 4 +- .../ssl/SupportedGroupsExtension.java | 20 +- .../ssl/SupportedVersionsExtension.java | 18 +- .../sun/security/ssl/TransportContext.java | 20 +- .../security/ssl/TrustManagerFactoryImpl.java | 10 +- .../sun/security/ssl/TrustStoreManager.java | 16 +- .../classes/sun/security/ssl/Utilities.java | 6 +- .../sun/security/ssl/X509Authentication.java | 26 +- .../ssl/X509KeyManagerCertChecking.java | 14 +- .../sun/security/ssl/X509KeyManagerImpl.java | 14 +- .../security/ssl/X509TrustManagerImpl.java | 12 +- .../classes/sun/security/util/DomainName.java | 9 +- .../sun/security/util/HostnameChecker.java | 10 +- .../SSLLogger/DebugPropertyValuesTest.java | 3 +- 80 files changed, 848 insertions(+), 847 deletions(-) diff --git a/src/java.base/share/classes/sun/security/ssl/Alert.java b/src/java.base/share/classes/sun/security/ssl/Alert.java index ed5e079bf44..d172206326f 100644 --- a/src/java.base/share/classes/sun/security/ssl/Alert.java +++ b/src/java.base/share/classes/sun/security/ssl/Alert.java @@ -238,7 +238,7 @@ public enum Alert { TransportContext tc = (TransportContext)context; AlertMessage am = new AlertMessage(tc, m); - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("Received alert message", am); } diff --git a/src/java.base/share/classes/sun/security/ssl/AlpnExtension.java b/src/java.base/share/classes/sun/security/ssl/AlpnExtension.java index aa5933ddab0..f03a65c8410 100644 --- a/src/java.base/share/classes/sun/security/ssl/AlpnExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/AlpnExtension.java @@ -157,7 +157,7 @@ final class AlpnExtension { // Is it a supported and enabled extension? if (!chc.sslConfig.isAvailable(SSLExtension.CH_ALPN)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.info( "Ignore client unavailable extension: " + SSLExtension.CH_ALPN.name); @@ -170,7 +170,7 @@ final class AlpnExtension { String[] laps = chc.sslConfig.applicationProtocols; if ((laps == null) || (laps.length == 0)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.info( "No available application protocols"); } @@ -183,7 +183,7 @@ final class AlpnExtension { int length = ap.getBytes(alpnCharset).length; if (length == 0) { // log the configuration problem - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.severe( "Application protocol name cannot be empty"); } @@ -197,7 +197,7 @@ final class AlpnExtension { listLength += (length + 1); } else { // log the configuration problem - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.severe( "Application protocol name (" + ap + ") exceeds the size limit (" + @@ -212,7 +212,7 @@ final class AlpnExtension { if (listLength > MAX_AP_LIST_LENGTH) { // log the configuration problem - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.severe( "The configured application protocols (" + Arrays.toString(laps) + @@ -266,7 +266,7 @@ final class AlpnExtension { if (!shc.sslConfig.isAvailable(SSLExtension.CH_ALPN)) { shc.applicationProtocol = ""; shc.conContext.applicationProtocol = ""; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.info( "Ignore server unavailable extension: " + SSLExtension.CH_ALPN.name); @@ -288,7 +288,7 @@ final class AlpnExtension { if (noAPSelector && noAlpnProtocols) { shc.applicationProtocol = ""; shc.conContext.applicationProtocol = ""; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore server unenabled extension: " + SSLExtension.CH_ALPN.name); @@ -378,7 +378,7 @@ final class AlpnExtension { (AlpnSpec)shc.handshakeExtensions.get(SSLExtension.CH_ALPN); if (requestedAlps == null) { // Ignore, this extension was not requested and accepted. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable extension: " + SSLExtension.SH_ALPN.name); @@ -423,7 +423,7 @@ final class AlpnExtension { // Ignore, no negotiated application layer protocol. shc.applicationProtocol = ""; shc.conContext.applicationProtocol = ""; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "Ignore, no negotiated application layer protocol"); } diff --git a/src/java.base/share/classes/sun/security/ssl/CertSignAlgsExtension.java b/src/java.base/share/classes/sun/security/ssl/CertSignAlgsExtension.java index 2125a148162..2d03d5fef98 100644 --- a/src/java.base/share/classes/sun/security/ssl/CertSignAlgsExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/CertSignAlgsExtension.java @@ -94,7 +94,7 @@ final class CertSignAlgsExtension { // Is it a supported and enabled extension? if (!chc.sslConfig.isAvailable( SSLExtension.CH_SIGNATURE_ALGORITHMS_CERT)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable " + "signature_algorithms_cert extension"); @@ -144,7 +144,7 @@ final class CertSignAlgsExtension { // Is it a supported and enabled extension? if (!shc.sslConfig.isAvailable( SSLExtension.CH_SIGNATURE_ALGORITHMS_CERT)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable " + "signature_algorithms_cert extension"); @@ -235,7 +235,7 @@ final class CertSignAlgsExtension { // Is it a supported and enabled extension? if (!shc.sslConfig.isAvailable( SSLExtension.CH_SIGNATURE_ALGORITHMS_CERT)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable " + "signature_algorithms_cert extension"); @@ -283,7 +283,7 @@ final class CertSignAlgsExtension { // Is it a supported and enabled extension? if (!chc.sslConfig.isAvailable( SSLExtension.CH_SIGNATURE_ALGORITHMS_CERT)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable " + "signature_algorithms_cert extension"); diff --git a/src/java.base/share/classes/sun/security/ssl/CertStatusExtension.java b/src/java.base/share/classes/sun/security/ssl/CertStatusExtension.java index 49713b6db11..d6c1cec5735 100644 --- a/src/java.base/share/classes/sun/security/ssl/CertStatusExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/CertStatusExtension.java @@ -144,7 +144,7 @@ final class CertStatusExtension { if (statusType == CertStatusRequestType.OCSP.id) { this.statusRequest = new OCSPStatusRequest(statusType, encoded); } else { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.info( "Unknown certificate status request " + "(status type: " + statusType + ")"); @@ -196,7 +196,7 @@ final class CertStatusExtension { if (type == CertStatusRequestType.OCSP.id) { this.statusResponse = new OCSPStatusResponse(type, respData); } else { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.info( "Unknown certificate status response " + "(status type: " + type + ")"); @@ -557,7 +557,7 @@ final class CertStatusExtension { } if (!chc.sslConfig.isAvailable(SSLExtension.CH_STATUS_REQUEST)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable extension: " + SSLExtension.CH_STATUS_REQUEST.name); @@ -598,7 +598,7 @@ final class CertStatusExtension { ServerHandshakeContext shc = (ServerHandshakeContext)context; if (!shc.sslConfig.isAvailable(SSLExtension.CH_STATUS_REQUEST)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Ignore unavailable extension: " + SSLExtension.CH_STATUS_REQUEST.name); } @@ -656,7 +656,7 @@ final class CertStatusExtension { shc.handshakeExtensions.get(SSLExtension.CH_STATUS_REQUEST); if (spec == null) { // Ignore, no status_request extension requested. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest("Ignore unavailable extension: " + SSLExtension.CH_STATUS_REQUEST.name); } @@ -666,7 +666,7 @@ final class CertStatusExtension { // Is it a session resuming? if (shc.isResumption) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest( "No status_request response for session resuming"); } @@ -839,7 +839,7 @@ final class CertStatusExtension { statusRequests.add( new OCSPStatusRequest(statusType, encoded)); } else { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.info( "Unknown certificate status request " + "(status type: " + statusType + ")"); @@ -915,7 +915,7 @@ final class CertStatusExtension { } if (!chc.sslConfig.isAvailable(SSLExtension.CH_STATUS_REQUEST_V2)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest( "Ignore unavailable status_request_v2 extension"); } @@ -957,7 +957,7 @@ final class CertStatusExtension { ServerHandshakeContext shc = (ServerHandshakeContext)context; if (!shc.sslConfig.isAvailable(SSLExtension.CH_STATUS_REQUEST_V2)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest( "Ignore unavailable status_request_v2 extension"); } @@ -1017,7 +1017,7 @@ final class CertStatusExtension { shc.handshakeExtensions.get(SSLExtension.CH_STATUS_REQUEST_V2); if (spec == null) { // Ignore, no status_request_v2 extension requested. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest( "Ignore unavailable status_request_v2 extension"); } @@ -1027,7 +1027,7 @@ final class CertStatusExtension { // Is it a session resuming? if (shc.isResumption) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest( "No status_request_v2 response for session resumption"); } @@ -1112,7 +1112,7 @@ final class CertStatusExtension { // Stapling needs to be active and have valid data to proceed if (shc.stapleParams == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest( "Stapling is disabled for this connection"); } @@ -1121,7 +1121,7 @@ final class CertStatusExtension { // There needs to be a non-null CertificateEntry to proceed if (shc.currentCertEntry == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest("Found null CertificateEntry in context"); } return null; @@ -1139,7 +1139,7 @@ final class CertStatusExtension { byte[] respBytes = shc.stapleParams.responseMap.get(x509Cert); if (respBytes == null) { // We're done with this entry. Clear it from the context - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest("No status response found for " + x509Cert.getSubjectX500Principal()); @@ -1149,7 +1149,7 @@ final class CertStatusExtension { } // Build a proper response buffer from the stapling information - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake,verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest("Found status response for " + x509Cert.getSubjectX500Principal() + ", response length: " + respBytes.length); @@ -1208,7 +1208,7 @@ final class CertStatusExtension { respList.add(spec.statusResponse.encodedResponse); chc.handshakeSession.setStatusResponses(respList); } else { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake,verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "Ignoring stapled data on resumed session"); } diff --git a/src/java.base/share/classes/sun/security/ssl/CertificateAuthoritiesExtension.java b/src/java.base/share/classes/sun/security/ssl/CertificateAuthoritiesExtension.java index 43bac16f0ea..cc513eb30ba 100644 --- a/src/java.base/share/classes/sun/security/ssl/CertificateAuthoritiesExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/CertificateAuthoritiesExtension.java @@ -192,7 +192,7 @@ final class CertificateAuthoritiesExtension { // Is it a supported and enabled extension? if (!chc.sslConfig.isAvailable( SSLExtension.CH_CERTIFICATE_AUTHORITIES)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable " + "certificate_authorities extension"); @@ -205,7 +205,7 @@ final class CertificateAuthoritiesExtension { X509Certificate[] caCerts = chc.sslContext.getX509TrustManager().getAcceptedIssuers(); if (caCerts.length == 0) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "No available certificate authorities"); } @@ -216,7 +216,7 @@ final class CertificateAuthoritiesExtension { List encodedCAs = CertificateAuthoritiesSpec.getEncodedAuthorities(caCerts); if (encodedCAs.isEmpty()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "The number of CAs exceeds the maximum size " + "of the certificate_authorities extension"); @@ -270,7 +270,7 @@ final class CertificateAuthoritiesExtension { // Is it a supported and enabled extension? if (!shc.sslConfig.isAvailable( SSLExtension.CH_CERTIFICATE_AUTHORITIES)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable " + "certificate_authorities extension"); @@ -319,7 +319,7 @@ final class CertificateAuthoritiesExtension { // Is it a supported and enabled extension? if (!shc.sslConfig.isAvailable( SSLExtension.CR_CERTIFICATE_AUTHORITIES)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable " + "certificate_authorities extension"); @@ -332,7 +332,7 @@ final class CertificateAuthoritiesExtension { X509Certificate[] caCerts = shc.sslContext.getX509TrustManager().getAcceptedIssuers(); if (caCerts.length == 0) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "No available certificate authorities"); } @@ -343,7 +343,7 @@ final class CertificateAuthoritiesExtension { List encodedCAs = CertificateAuthoritiesSpec.getEncodedAuthorities(caCerts); if (encodedCAs.isEmpty()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "Too many certificate authorities to use " + "the certificate_authorities extension"); @@ -397,7 +397,7 @@ final class CertificateAuthoritiesExtension { // Is it a supported and enabled extension? if (!chc.sslConfig.isAvailable( SSLExtension.CR_CERTIFICATE_AUTHORITIES)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable " + "certificate_authorities extension"); diff --git a/src/java.base/share/classes/sun/security/ssl/CertificateMessage.java b/src/java.base/share/classes/sun/security/ssl/CertificateMessage.java index d4587d35ae9..2a2db34cab9 100644 --- a/src/java.base/share/classes/sun/security/ssl/CertificateMessage.java +++ b/src/java.base/share/classes/sun/security/ssl/CertificateMessage.java @@ -265,7 +265,7 @@ final class CertificateMessage { shc.handshakeSession.setLocalCertificates(x509Possession.popCerts); T12CertificateMessage cm = new T12CertificateMessage(shc, x509Possession.popCerts); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced server Certificate handshake message", cm); } @@ -293,7 +293,7 @@ final class CertificateMessage { // an empty cert chain instead. if (x509Possession == null) { if (chc.negotiatedProtocol.useTLS10PlusSpec()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "No X.509 certificate for client authentication, " + "use empty Certificate message instead"); @@ -302,7 +302,7 @@ final class CertificateMessage { x509Possession = new X509Possession(null, new X509Certificate[0]); } else { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "No X.509 certificate for client authentication, " + "send a no_certificate alert"); @@ -324,7 +324,7 @@ final class CertificateMessage { } T12CertificateMessage cm = new T12CertificateMessage(chc, x509Possession.popCerts); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced client Certificate handshake message", cm); } @@ -360,13 +360,13 @@ final class CertificateMessage { T12CertificateMessage cm = new T12CertificateMessage(hc, message); if (hc.sslConfig.isClientMode) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming server Certificate handshake message", cm); } onCertificate((ClientHandshakeContext)context, cm); } else { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming client Certificate handshake message", cm); } @@ -501,7 +501,7 @@ final class CertificateMessage { try { thisSubjectAltNames = thisCert.getSubjectAlternativeNames(); } catch (CertificateParsingException cpe) { - if (SSLLogger.isOn && SSLLogger.isOn("handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("handshake")) { SSLLogger.fine( "Attempt to obtain subjectAltNames extension failed!"); } @@ -511,7 +511,7 @@ final class CertificateMessage { try { prevSubjectAltNames = prevCert.getSubjectAlternativeNames(); } catch (CertificateParsingException cpe) { - if (SSLLogger.isOn && SSLLogger.isOn("handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("handshake")) { SSLLogger.fine( "Attempt to obtain subjectAltNames extension failed!"); } @@ -980,7 +980,7 @@ final class CertificateMessage { certEnt.extensions.produce(shc, enabledCTExts); } - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Produced server Certificate message", cm); } @@ -997,7 +997,7 @@ final class CertificateMessage { ClientHelloMessage clientHello) { if (hc.peerRequestedCertSignSchemes == null || hc.peerRequestedCertSignSchemes.isEmpty()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "No signature_algorithms(_cert) in ClientHello"); } @@ -1021,7 +1021,7 @@ final class CertificateMessage { SSLPossession pos = X509Authentication .createPossession(hc, supportedKeyTypes); if (pos == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning("No available authentication scheme"); } } @@ -1034,14 +1034,14 @@ final class CertificateMessage { SSLPossession pos = choosePossession(chc, clientHello); X509Certificate[] localCerts; if (pos == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("No available client authentication scheme"); } localCerts = new X509Certificate[0]; } else { chc.handshakePossessions.add(pos); if (!(pos instanceof X509Possession x509Possession)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "No X.509 certificate for client authentication"); } @@ -1067,7 +1067,7 @@ final class CertificateMessage { throw chc.conContext.fatal(Alert.HANDSHAKE_FAILURE, "Failed to produce client Certificate message", ce); } - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Produced client Certificate message", cm); } @@ -1108,13 +1108,13 @@ final class CertificateMessage { T13CertificateMessage cm = new T13CertificateMessage(hc, message); if (hc.sslConfig.isClientMode) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming server Certificate handshake message", cm); } onConsumeCertificate((ClientHandshakeContext)context, cm); } else { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming client Certificate handshake message", cm); } diff --git a/src/java.base/share/classes/sun/security/ssl/CertificateRequest.java b/src/java.base/share/classes/sun/security/ssl/CertificateRequest.java index 66b8c048703..a297d9d21b2 100644 --- a/src/java.base/share/classes/sun/security/ssl/CertificateRequest.java +++ b/src/java.base/share/classes/sun/security/ssl/CertificateRequest.java @@ -297,7 +297,7 @@ final class CertificateRequest { shc.sslContext.getX509TrustManager().getAcceptedIssuers(); T10CertificateRequestMessage crm = new T10CertificateRequestMessage( shc, caCerts, shc.negotiatedCipherSuite.keyExchange); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced CertificateRequest handshake message", crm); } @@ -360,7 +360,7 @@ final class CertificateRequest { T10CertificateRequestMessage crm = new T10CertificateRequestMessage(chc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming CertificateRequest handshake message", crm); } @@ -400,7 +400,7 @@ final class CertificateRequest { } if (clientAlias == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning("No available client authentication"); } return; @@ -408,7 +408,7 @@ final class CertificateRequest { PrivateKey clientPrivateKey = km.getPrivateKey(clientAlias); if (clientPrivateKey == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning("No available client private key"); } return; @@ -416,7 +416,7 @@ final class CertificateRequest { X509Certificate[] clientCerts = km.getCertificateChain(clientAlias); if ((clientCerts == null) || (clientCerts.length == 0)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning("No available client certificate"); } return; @@ -655,7 +655,7 @@ final class CertificateRequest { T12CertificateRequestMessage crm = new T12CertificateRequestMessage( shc, caCerts, shc.negotiatedCipherSuite.keyExchange, certReqSignAlgs); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced CertificateRequest handshake message", crm); } @@ -717,7 +717,7 @@ final class CertificateRequest { T12CertificateRequestMessage crm = new T12CertificateRequestMessage(chc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming CertificateRequest handshake message", crm); } @@ -784,7 +784,7 @@ final class CertificateRequest { T12CertificateRequestMessage crm) { if (hc.peerRequestedCertSignSchemes == null || hc.peerRequestedCertSignSchemes.isEmpty()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning("No signature and hash algorithms " + "in CertificateRequest"); } @@ -823,7 +823,7 @@ final class CertificateRequest { SSLPossession pos = X509Authentication .createPossession(hc, supportedKeyTypes); if (pos == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning("No available authentication scheme"); } } @@ -933,7 +933,7 @@ final class CertificateRequest { SSLExtension[] extTypes = shc.sslConfig.getEnabledExtensions( SSLHandshake.CERTIFICATE_REQUEST, shc.negotiatedProtocol); crm.extensions.produce(shc, extTypes); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Produced CertificateRequest message", crm); } @@ -985,7 +985,7 @@ final class CertificateRequest { T13CertificateRequestMessage crm = new T13CertificateRequestMessage(chc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming CertificateRequest handshake message", crm); } diff --git a/src/java.base/share/classes/sun/security/ssl/CertificateStatus.java b/src/java.base/share/classes/sun/security/ssl/CertificateStatus.java index 11b2c5e587d..a1048e423d1 100644 --- a/src/java.base/share/classes/sun/security/ssl/CertificateStatus.java +++ b/src/java.base/share/classes/sun/security/ssl/CertificateStatus.java @@ -281,7 +281,7 @@ final class CertificateStatus { new CertificateStatusMessage(chc, message); // Log the message - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming server CertificateStatus handshake message", cst); @@ -325,7 +325,7 @@ final class CertificateStatus { // Create the CertificateStatus message from info in the CertificateStatusMessage csm = new CertificateStatusMessage(shc); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced server CertificateStatus handshake message", csm); } @@ -358,7 +358,7 @@ final class CertificateStatus { // status_request[_v2] extension. 2) The CertificateStatus // message was not sent. This means that cert path checking // was deferred, but must happen immediately. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Server did not send CertificateStatus, " + "checking cert chain without status info."); } diff --git a/src/java.base/share/classes/sun/security/ssl/CertificateVerify.java b/src/java.base/share/classes/sun/security/ssl/CertificateVerify.java index 09d07d8e62d..18ea2b9c3de 100644 --- a/src/java.base/share/classes/sun/security/ssl/CertificateVerify.java +++ b/src/java.base/share/classes/sun/security/ssl/CertificateVerify.java @@ -248,7 +248,7 @@ final class CertificateVerify { if (x509Possession == null || x509Possession.popPrivateKey == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "No X.509 credentials negotiated for CertificateVerify"); } @@ -258,7 +258,7 @@ final class CertificateVerify { S30CertificateVerifyMessage cvm = new S30CertificateVerifyMessage(chc, x509Possession); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced CertificateVerify handshake message", cvm); } @@ -300,7 +300,7 @@ final class CertificateVerify { S30CertificateVerifyMessage cvm = new S30CertificateVerifyMessage(shc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming CertificateVerify handshake message", cvm); } @@ -503,7 +503,7 @@ final class CertificateVerify { if (x509Possession == null || x509Possession.popPrivateKey == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "No X.509 credentials negotiated for CertificateVerify"); } @@ -513,7 +513,7 @@ final class CertificateVerify { T10CertificateVerifyMessage cvm = new T10CertificateVerifyMessage(chc, x509Possession); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced CertificateVerify handshake message", cvm); } @@ -555,7 +555,7 @@ final class CertificateVerify { T10CertificateVerifyMessage cvm = new T10CertificateVerifyMessage(shc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming CertificateVerify handshake message", cvm); } @@ -754,7 +754,7 @@ final class CertificateVerify { if (x509Possession == null || x509Possession.popPrivateKey == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "No X.509 credentials negotiated for CertificateVerify"); } @@ -764,7 +764,7 @@ final class CertificateVerify { T12CertificateVerifyMessage cvm = new T12CertificateVerifyMessage(chc, x509Possession); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced CertificateVerify handshake message", cvm); } @@ -806,7 +806,7 @@ final class CertificateVerify { T12CertificateVerifyMessage cvm = new T12CertificateVerifyMessage(shc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming CertificateVerify handshake message", cvm); } @@ -1092,7 +1092,7 @@ final class CertificateVerify { if (x509Possession == null || x509Possession.popPrivateKey == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "No X.509 credentials negotiated for CertificateVerify"); } @@ -1113,7 +1113,7 @@ final class CertificateVerify { X509Possession x509Possession) throws IOException { T13CertificateVerifyMessage cvm = new T13CertificateVerifyMessage(shc, x509Possession); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced server CertificateVerify handshake message", cvm); } @@ -1130,7 +1130,7 @@ final class CertificateVerify { X509Possession x509Possession) throws IOException { T13CertificateVerifyMessage cvm = new T13CertificateVerifyMessage(chc, x509Possession); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced client CertificateVerify handshake message", cvm); } @@ -1173,7 +1173,7 @@ final class CertificateVerify { T13CertificateVerifyMessage cvm = new T13CertificateVerifyMessage(hc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming CertificateVerify handshake message", cvm); } diff --git a/src/java.base/share/classes/sun/security/ssl/ChangeCipherSpec.java b/src/java.base/share/classes/sun/security/ssl/ChangeCipherSpec.java index 4ea61161c1d..d3eac8f13af 100644 --- a/src/java.base/share/classes/sun/security/ssl/ChangeCipherSpec.java +++ b/src/java.base/share/classes/sun/security/ssl/ChangeCipherSpec.java @@ -108,7 +108,7 @@ final class ChangeCipherSpec { ") and protocol version (" + hc.negotiatedProtocol + ")"); } - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Produced ChangeCipherSpec message"); } @@ -142,7 +142,7 @@ final class ChangeCipherSpec { throw tc.fatal(Alert.UNEXPECTED_MESSAGE, "Malformed or unexpected ChangeCipherSpec message"); } - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Consuming ChangeCipherSpec message"); } @@ -237,7 +237,7 @@ final class ChangeCipherSpec { throw tc.fatal(Alert.UNEXPECTED_MESSAGE, "Malformed or unexpected ChangeCipherSpec message"); } - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Consuming ChangeCipherSpec message"); } diff --git a/src/java.base/share/classes/sun/security/ssl/ClientHello.java b/src/java.base/share/classes/sun/security/ssl/ClientHello.java index c9432ea3979..421673d625d 100644 --- a/src/java.base/share/classes/sun/security/ssl/ClientHello.java +++ b/src/java.base/share/classes/sun/security/ssl/ClientHello.java @@ -430,7 +430,7 @@ final class ClientHello { if (!session.isRejoinable()) { session = null; - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "Can't resume, the session is not rejoinable"); @@ -443,7 +443,7 @@ final class ClientHello { sessionSuite = session.getSuite(); if (!chc.isNegotiable(sessionSuite)) { session = null; - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "Can't resume, unavailable session cipher suite"); @@ -456,7 +456,7 @@ final class ClientHello { sessionVersion = session.getProtocolVersion(); if (!chc.isNegotiable(sessionVersion)) { session = null; - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "Can't resume, unavailable protocol version"); @@ -513,7 +513,7 @@ final class ClientHello { String sessionIdentityAlg = session.getIdentificationProtocol(); if (!identityAlg.equalsIgnoreCase(sessionIdentityAlg)) { - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest("Can't resume, endpoint id" + " algorithm does not match, requested: " + @@ -524,7 +524,7 @@ final class ClientHello { } if (session != null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake,verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest("Try resuming session", session); } @@ -547,7 +547,7 @@ final class ClientHello { cipherSuites = List.of(sessionSuite); } - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "No new session is allowed, so try to resume " + @@ -634,7 +634,7 @@ final class ClientHello { SSLHandshake.CLIENT_HELLO, chc.activeProtocols); chm.extensions.produce(chc, extTypes); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Produced ClientHello handshake message", chm); } @@ -700,7 +700,7 @@ final class ClientHello { // // The HelloVerifyRequest consumer should have updated the // ClientHello handshake message with cookie. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced ClientHello(cookie) handshake message", chc.initialClientHelloMsg); @@ -734,7 +734,7 @@ final class ClientHello { // TLS 1.3 // The HelloRetryRequest consumer should have updated the // ClientHello handshake message with cookie. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced ClientHello(HRR) handshake message", chc.initialClientHelloMsg); @@ -790,7 +790,7 @@ final class ClientHello { ClientHelloMessage chm = new ClientHelloMessage(shc, message, enabledExtensions); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Consuming ClientHello handshake message", chm); } @@ -820,7 +820,7 @@ final class ClientHello { negotiateProtocol(context, clientHello.clientVersion); } context.negotiatedProtocol = negotiatedProtocol; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Negotiated protocol version: " + negotiatedProtocol.name); } @@ -980,7 +980,7 @@ final class ClientHello { boolean resumingSession = (previous != null) && previous.isRejoinable(); if (!resumingSession) { - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "Can't resume, " + @@ -993,7 +993,7 @@ final class ClientHello { previous.getProtocolVersion(); if (sessionProtocol != shc.negotiatedProtocol) { resumingSession = false; - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "Can't resume, not the same protocol version"); @@ -1008,7 +1008,7 @@ final class ClientHello { previous.getPeerPrincipal(); } catch (SSLPeerUnverifiedException e) { resumingSession = false; - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "Can't resume, " + @@ -1023,7 +1023,7 @@ final class ClientHello { if ((!shc.isNegotiable(suite)) || (!clientHello.cipherSuites.contains(suite))) { resumingSession = false; - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "Can't resume, " + @@ -1039,7 +1039,7 @@ final class ClientHello { String sessionIdentityAlg = previous.getIdentificationProtocol(); if (!identityAlg.equalsIgnoreCase(sessionIdentityAlg)) { - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest("Can't resume, endpoint id" + " algorithm does not match, requested: " + @@ -1054,7 +1054,7 @@ final class ClientHello { shc.isResumption = resumingSession; shc.resumingSession = resumingSession ? previous : null; - if (!resumingSession && SSLLogger.isOn && + if (!resumingSession && SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Session not resumed."); } @@ -1321,7 +1321,7 @@ final class ClientHello { boolean resumingSession = (previous != null) && previous.isRejoinable(); if (!resumingSession) { - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "Can't resume, " + @@ -1334,7 +1334,7 @@ final class ClientHello { previous.getProtocolVersion(); if (sessionProtocol != shc.negotiatedProtocol) { resumingSession = false; - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "Can't resume, not the same protocol version"); @@ -1350,7 +1350,7 @@ final class ClientHello { previous.getPeerPrincipal(); } catch (SSLPeerUnverifiedException e) { resumingSession = false; - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "Can't resume, " + @@ -1365,7 +1365,7 @@ final class ClientHello { if ((!shc.isNegotiable(suite)) || (!clientHello.cipherSuites.contains(suite))) { resumingSession = false; - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "Can't resume, " + diff --git a/src/java.base/share/classes/sun/security/ssl/CookieExtension.java b/src/java.base/share/classes/sun/security/ssl/CookieExtension.java index d54a1a3e63d..2c22dd121ba 100644 --- a/src/java.base/share/classes/sun/security/ssl/CookieExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/CookieExtension.java @@ -117,7 +117,7 @@ public class CookieExtension { // Is it a supported and enabled extension? if (!chc.sslConfig.isAvailable(SSLExtension.CH_COOKIE)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable cookie extension"); } @@ -154,7 +154,7 @@ public class CookieExtension { // Is it a supported and enabled extension? if (!shc.sslConfig.isAvailable(SSLExtension.CH_COOKIE)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable cookie extension"); } @@ -218,7 +218,7 @@ public class CookieExtension { // Is it a supported and enabled extension? if (!shc.sslConfig.isAvailable(SSLExtension.HRR_COOKIE)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable cookie extension"); } @@ -253,7 +253,7 @@ public class CookieExtension { // Is it a supported and enabled extension? if (!chc.sslConfig.isAvailable(SSLExtension.HRR_COOKIE)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable cookie extension"); } @@ -280,7 +280,7 @@ public class CookieExtension { // Is it a supported and enabled extension? if (!shc.sslConfig.isAvailable(SSLExtension.HRR_COOKIE)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable cookie extension"); } diff --git a/src/java.base/share/classes/sun/security/ssl/DHClientKeyExchange.java b/src/java.base/share/classes/sun/security/ssl/DHClientKeyExchange.java index fb5d6feef55..53f9896a3e4 100644 --- a/src/java.base/share/classes/sun/security/ssl/DHClientKeyExchange.java +++ b/src/java.base/share/classes/sun/security/ssl/DHClientKeyExchange.java @@ -187,7 +187,7 @@ final class DHClientKeyExchange { chc.handshakePossessions.add(dhePossession); DHClientKeyExchangeMessage ckem = new DHClientKeyExchangeMessage(chc); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced DH ClientKeyExchange handshake message", ckem); } @@ -268,7 +268,7 @@ final class DHClientKeyExchange { DHClientKeyExchangeMessage ckem = new DHClientKeyExchangeMessage(shc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming DH ClientKeyExchange handshake message", ckem); } diff --git a/src/java.base/share/classes/sun/security/ssl/DHServerKeyExchange.java b/src/java.base/share/classes/sun/security/ssl/DHServerKeyExchange.java index 2df62d50fb8..744ff59f402 100644 --- a/src/java.base/share/classes/sun/security/ssl/DHServerKeyExchange.java +++ b/src/java.base/share/classes/sun/security/ssl/DHServerKeyExchange.java @@ -481,7 +481,7 @@ final class DHServerKeyExchange { ServerHandshakeContext shc = (ServerHandshakeContext)context; DHServerKeyExchangeMessage skem = new DHServerKeyExchangeMessage(shc); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced DH ServerKeyExchange handshake message", skem); } @@ -512,7 +512,7 @@ final class DHServerKeyExchange { DHServerKeyExchangeMessage skem = new DHServerKeyExchangeMessage(chc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming DH ServerKeyExchange handshake message", skem); } diff --git a/src/java.base/share/classes/sun/security/ssl/DTLSInputRecord.java b/src/java.base/share/classes/sun/security/ssl/DTLSInputRecord.java index 4e82fd25a7b..e880f36e846 100644 --- a/src/java.base/share/classes/sun/security/ssl/DTLSInputRecord.java +++ b/src/java.base/share/classes/sun/security/ssl/DTLSInputRecord.java @@ -125,7 +125,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { return null; } - if (SSLLogger.isOn && SSLLogger.isOn("packet")) { + if (SSLLogger.isOn() && SSLLogger.isOn("packet")) { SSLLogger.fine("Raw read", packet); } @@ -150,7 +150,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { int contentLen = ((packet.get() & 0xFF) << 8) | (packet.get() & 0xFF); // pos: 11, 12 - if (SSLLogger.isOn && SSLLogger.isOn("record")) { + if (SSLLogger.isOn() && SSLLogger.isOn("record")) { SSLLogger.fine("READ: " + ProtocolVersion.nameOf(majorVersion, minorVersion) + " " + ContentType.nameOf(contentType) + ", length = " + @@ -162,7 +162,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { if (this.readEpoch > recordEpoch) { // Reset the position of the packet buffer. packet.position(recLim); - if (SSLLogger.isOn && SSLLogger.isOn("record")) { + if (SSLLogger.isOn() && SSLLogger.isOn("record")) { SSLLogger.fine("READ: discard this old record", recordEnS); } return null; @@ -181,7 +181,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { packet.position(recLim); - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine("Premature record (epoch), discard it."); } @@ -223,7 +223,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { plaintextFragment = plaintext.fragment; contentType = plaintext.contentType; } catch (GeneralSecurityException gse) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("Discard invalid record: " + gse); } @@ -241,7 +241,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { // Cleanup the handshake reassembler if necessary. if ((reassembler != null) && (reassembler.handshakeEpoch < recordEpoch)) { - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine("Cleanup the handshake reassembler"); } @@ -273,7 +273,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { if (hsFrag == null) { // invalid, discard this record - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine( "Invalid handshake message, discard it."); } @@ -296,7 +296,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { return pt == null ? null : new Plaintext[] { pt }; } - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine("The reassembler is not initialized yet."); } @@ -356,7 +356,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { int remaining = plaintextFragment.remaining(); if (remaining < handshakeHeaderSize) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("Discard invalid record: " + "too small record to hold a handshake fragment"); } @@ -368,7 +368,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { // Fail fast for unknown handshake message. byte handshakeType = plaintextFragment.get(); // pos: 0 if (!SSLHandshake.isKnown(handshakeType)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("Discard invalid record: " + "unknown handshake type size, Handshake.msg_type = " + (handshakeType & 0xFF)); @@ -404,7 +404,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { ((plaintextFragment.get() & 0xFF) << 8) | (plaintextFragment.get() & 0xFF); // pos: 9-11 if ((remaining - handshakeHeaderSize) < fragmentLength) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("Discard invalid record: " + "not a complete handshake fragment in the record"); } @@ -748,7 +748,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { // It's OK to discard retransmission as the handshake hash // is computed as if each handshake message had been sent // as a single fragment. - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine("Have got the full message, discard it."); } @@ -769,7 +769,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { // The ranges SHOULD NOT overlap. if (hole.offset > hsf.fragmentOffset || hole.limit < fragmentLimit) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("Discard invalid record: " + "handshake fragment ranges are overlapping"); } @@ -837,7 +837,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { } // Read the random (32 bytes) if (fragmentData.remaining() < 32) { - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine("Rejected client hello fragment (bad random len) " + "fo=" + hsf.fragmentOffset + " fl=" + hsf.fragmentLength); } @@ -861,7 +861,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { // Cookie byte[] cookie = Record.getBytes8(fragmentData); if (firstHello && cookie.length != 0) { - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine("Rejected initial client hello fragment (bad cookie len) " + "fo=" + hsf.fragmentOffset + " fl=" + hsf.fragmentLength); } @@ -897,7 +897,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { } } } catch (IOException ioe) { - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine("Rejected client hello fragment " + "fo=" + hsf.fragmentOffset + " fl=" + hsf.fragmentLength); } @@ -1029,7 +1029,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { int previousEpoch = nextRecordEpoch - 1; if (rf.recordEpoch < previousEpoch) { // Too old to use, discard this record. - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine( "Too old epoch to use this record, discard it."); } @@ -1075,7 +1075,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { if (!isDesired) { // Too old to use, discard this retransmitted record - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine( "Too old retransmission to use, discard it."); } @@ -1088,7 +1088,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { // Previously disordered record for the current epoch. // // Should have been retransmitted. Discard this record. - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine( "Lagging behind record (sequence), discard it."); } @@ -1126,7 +1126,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { Plaintext acquirePlaintext() throws SSLProtocolException { if (bufferedFragments.isEmpty()) { - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine("No received handshake messages"); } return null; @@ -1147,7 +1147,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { // Reset the next handshake flight. resetHandshakeFlight(precedingFlight); - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine("Received a retransmission flight."); } @@ -1159,7 +1159,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { } if (!flightIsReady) { - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine( "The handshake flight is not ready to use: " + handshakeFlight.handshakeType); @@ -1244,7 +1244,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { if (readEpoch != rFrag.recordEpoch) { if (readEpoch > rFrag.recordEpoch) { // discard old records - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine( "Discard old buffered ciphertext fragments."); } @@ -1256,7 +1256,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { flightIsReady = false; } - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine( "Not yet ready to decrypt the cached fragments."); } @@ -1273,7 +1273,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { plaintextFragment = plaintext.fragment; rFrag.contentType = plaintext.contentType; } catch (GeneralSecurityException gse) { - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine("Discard invalid record: ", gse); } @@ -1295,7 +1295,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { if (hsFrag == null) { // invalid, discard this record - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine( "Invalid handshake fragment, discard it", plaintextFragment); @@ -1446,7 +1446,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { if (expectCCSFlight) { // Have the ChangeCipherSpec/Finished flight been received? boolean isReady = hasFinishedMessage(); - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine( "Has the final flight been received? " + isReady); } @@ -1454,7 +1454,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { return isReady; } - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine("No flight is received yet."); } @@ -1467,7 +1467,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { // single handshake message flight boolean isReady = hasCompleted(flightType); - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine( "Is the handshake message completed? " + isReady); } @@ -1481,7 +1481,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { if (flightType == SSLHandshake.SERVER_HELLO.id) { // Firstly, check the first flight handshake message. if (!hasCompleted(flightType)) { - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine( "The ServerHello message is not completed yet."); } @@ -1493,7 +1493,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { // an abbreviated handshake // if (hasFinishedMessage()) { - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine("It's an abbreviated handshake."); } @@ -1507,7 +1507,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { SSLHandshake.SERVER_HELLO_DONE.id); if ((holes == null) || !holes.isEmpty()) { // Not yet got the final message of the flight. - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine( "Not yet got the ServerHelloDone message"); } @@ -1519,7 +1519,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { boolean isReady = hasCompleted(bufferedFragments, handshakeFlight.minMessageSeq, handshakeFlight.maxMessageSeq); - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine( "Is the ServerHello flight (message " + handshakeFlight.minMessageSeq + "-" + @@ -1542,7 +1542,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { // Firstly, check the first flight handshake message. if (!hasCompleted(flightType)) { - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine( "The ClientKeyExchange or client Certificate " + "message is not completed yet."); @@ -1556,7 +1556,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { if (needClientVerify(bufferedFragments) && !hasCompleted(SSLHandshake.CERTIFICATE_VERIFY.id)) { - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine( "Not yet have the CertificateVerify message"); } @@ -1567,7 +1567,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { if (!hasFinishedMessage()) { // not yet have the ChangeCipherSpec/Finished messages - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine( "Not yet have the ChangeCipherSpec and " + "Finished messages"); @@ -1580,7 +1580,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { boolean isReady = hasCompleted(bufferedFragments, handshakeFlight.minMessageSeq, handshakeFlight.maxMessageSeq); - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine( "Is the ClientKeyExchange flight (message " + handshakeFlight.minMessageSeq + "-" + @@ -1594,7 +1594,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { // // Otherwise, need to receive more handshake messages. // - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine("Need to receive more handshake messages"); } diff --git a/src/java.base/share/classes/sun/security/ssl/DTLSOutputRecord.java b/src/java.base/share/classes/sun/security/ssl/DTLSOutputRecord.java index 691ac32c26b..162dbb58eec 100644 --- a/src/java.base/share/classes/sun/security/ssl/DTLSOutputRecord.java +++ b/src/java.base/share/classes/sun/security/ssl/DTLSOutputRecord.java @@ -92,7 +92,7 @@ final class DTLSOutputRecord extends OutputRecord implements DTLSRecord { void changeWriteCiphers(SSLWriteCipher writeCipher, boolean useChangeCipherSpec) { if (isClosed()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("outbound has closed, ignore outbound " + "change_cipher_spec message"); } @@ -120,7 +120,7 @@ final class DTLSOutputRecord extends OutputRecord implements DTLSRecord { @Override void encodeAlert(byte level, byte description) { if (isClosed()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("outbound has closed, ignore outbound " + "alert message: " + Alert.nameOf(description)); } @@ -137,7 +137,7 @@ final class DTLSOutputRecord extends OutputRecord implements DTLSRecord { @Override void encodeChangeCipherSpec() { if (isClosed()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("outbound has closed, ignore outbound " + "change_cipher_spec message"); } @@ -154,7 +154,7 @@ final class DTLSOutputRecord extends OutputRecord implements DTLSRecord { void encodeHandshake(byte[] source, int offset, int length) { if (isClosed()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("outbound has closed, ignore outbound " + "handshake message", ByteBuffer.wrap(source, offset, length)); @@ -179,14 +179,14 @@ final class DTLSOutputRecord extends OutputRecord implements DTLSRecord { ByteBuffer[] dsts, int dstsOffset, int dstsLength) throws IOException { if (isClosed) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("outbound has closed, ignore outbound " + "application data or cached messages"); } return null; } else if (isCloseWaiting) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("outbound has closed, ignore outbound " + "application data"); } @@ -201,7 +201,7 @@ final class DTLSOutputRecord extends OutputRecord implements DTLSRecord { ByteBuffer destination) throws IOException { if (writeCipher.authenticator.seqNumOverflow()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine( "sequence number extremely close to overflow " + "(2^64-1 packets). Closing connection."); @@ -269,7 +269,7 @@ final class DTLSOutputRecord extends OutputRecord implements DTLSRecord { destination.limit(destination.position()); destination.position(dstContent); - if (SSLLogger.isOn && SSLLogger.isOn("record")) { + if (SSLLogger.isOn() && SSLLogger.isOn("record")) { SSLLogger.fine( "WRITE: " + protocolVersion.name + " " + ContentType.APPLICATION_DATA.name + @@ -282,7 +282,7 @@ final class DTLSOutputRecord extends OutputRecord implements DTLSRecord { dstPos, dstLim, headerSize, protocolVersion); - if (SSLLogger.isOn && SSLLogger.isOn("packet")) { + if (SSLLogger.isOn() && SSLLogger.isOn("packet")) { ByteBuffer temporary = destination.duplicate(); temporary.limit(temporary.position()); temporary.position(dstPos); @@ -497,7 +497,7 @@ final class DTLSOutputRecord extends OutputRecord implements DTLSRecord { dstBuf.limit(dstBuf.position()); dstBuf.position(dstContent); - if (SSLLogger.isOn && SSLLogger.isOn("record")) { + if (SSLLogger.isOn() && SSLLogger.isOn("record")) { SSLLogger.fine( "WRITE: " + protocolVersion.name + " " + ContentType.nameOf(memo.contentType) + @@ -511,7 +511,7 @@ final class DTLSOutputRecord extends OutputRecord implements DTLSRecord { ProtocolVersion.valueOf(memo.majorVersion, memo.minorVersion)); - if (SSLLogger.isOn && SSLLogger.isOn("packet")) { + if (SSLLogger.isOn() && SSLLogger.isOn("packet")) { ByteBuffer temporary = dstBuf.duplicate(); temporary.limit(temporary.position()); temporary.position(dstPos); diff --git a/src/java.base/share/classes/sun/security/ssl/ECDHClientKeyExchange.java b/src/java.base/share/classes/sun/security/ssl/ECDHClientKeyExchange.java index e1c1b1377ad..a626f6f34d0 100644 --- a/src/java.base/share/classes/sun/security/ssl/ECDHClientKeyExchange.java +++ b/src/java.base/share/classes/sun/security/ssl/ECDHClientKeyExchange.java @@ -199,7 +199,7 @@ final class ECDHClientKeyExchange { ECDHClientKeyExchangeMessage cke = new ECDHClientKeyExchangeMessage( chc, sslPossession.encode()); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced ECDH ClientKeyExchange handshake message", cke); } @@ -308,7 +308,7 @@ final class ECDHClientKeyExchange { // parse either handshake message containing either EC/XEC. ECDHClientKeyExchangeMessage cke = new ECDHClientKeyExchangeMessage(shc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming ECDH ClientKeyExchange handshake message", cke); } @@ -397,7 +397,7 @@ final class ECDHClientKeyExchange { new ECDHClientKeyExchangeMessage( chc, sslPossession.encode()); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced ECDHE ClientKeyExchange handshake message", cke); } @@ -490,7 +490,7 @@ final class ECDHClientKeyExchange { // parse the EC/XEC handshake message ECDHClientKeyExchangeMessage cke = new ECDHClientKeyExchangeMessage(shc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming ECDHE ClientKeyExchange handshake message", cke); } diff --git a/src/java.base/share/classes/sun/security/ssl/ECDHServerKeyExchange.java b/src/java.base/share/classes/sun/security/ssl/ECDHServerKeyExchange.java index b31c0ba9cb9..a02a8438163 100644 --- a/src/java.base/share/classes/sun/security/ssl/ECDHServerKeyExchange.java +++ b/src/java.base/share/classes/sun/security/ssl/ECDHServerKeyExchange.java @@ -489,7 +489,7 @@ final class ECDHServerKeyExchange { ServerHandshakeContext shc = (ServerHandshakeContext)context; ECDHServerKeyExchangeMessage skem = new ECDHServerKeyExchangeMessage(shc); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced ECDH ServerKeyExchange handshake message", skem); } @@ -522,7 +522,7 @@ final class ECDHServerKeyExchange { // AlgorithmConstraints are checked during decoding ECDHServerKeyExchangeMessage skem = new ECDHServerKeyExchangeMessage(chc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming ECDH ServerKeyExchange handshake message", skem); } diff --git a/src/java.base/share/classes/sun/security/ssl/ECPointFormatsExtension.java b/src/java.base/share/classes/sun/security/ssl/ECPointFormatsExtension.java index 580e1d416de..72b7c950374 100644 --- a/src/java.base/share/classes/sun/security/ssl/ECPointFormatsExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/ECPointFormatsExtension.java @@ -171,7 +171,7 @@ final class ECPointFormatsExtension { // Is it a supported and enabled extension? if (!chc.sslConfig.isAvailable(CH_EC_POINT_FORMATS)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable ec_point_formats extension"); } @@ -193,7 +193,7 @@ final class ECPointFormatsExtension { return extData; } - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Need no ec_point_formats extension"); } @@ -221,7 +221,7 @@ final class ECPointFormatsExtension { // Is it a supported and enabled extension? if (!shc.sslConfig.isAvailable(CH_EC_POINT_FORMATS)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable ec_point_formats extension"); } diff --git a/src/java.base/share/classes/sun/security/ssl/EncryptedExtensions.java b/src/java.base/share/classes/sun/security/ssl/EncryptedExtensions.java index d1975b5caa4..b5be927f0aa 100644 --- a/src/java.base/share/classes/sun/security/ssl/EncryptedExtensions.java +++ b/src/java.base/share/classes/sun/security/ssl/EncryptedExtensions.java @@ -134,7 +134,7 @@ final class EncryptedExtensions { SSLHandshake.ENCRYPTED_EXTENSIONS, shc.negotiatedProtocol); eem.extensions.produce(shc, extTypes); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Produced EncryptedExtensions message", eem); } @@ -168,7 +168,7 @@ final class EncryptedExtensions { EncryptedExtensionsMessage eem = new EncryptedExtensionsMessage(chc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming EncryptedExtensions handshake message", eem); } diff --git a/src/java.base/share/classes/sun/security/ssl/ExtendedMasterSecretExtension.java b/src/java.base/share/classes/sun/security/ssl/ExtendedMasterSecretExtension.java index ff4694c8c7c..6bacbfbd1d8 100644 --- a/src/java.base/share/classes/sun/security/ssl/ExtendedMasterSecretExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/ExtendedMasterSecretExtension.java @@ -119,7 +119,7 @@ final class ExtendedMasterSecretExtension { if (!chc.sslConfig.isAvailable(CH_EXTENDED_MASTER_SECRET) || !SSLConfiguration.useExtendedMasterSecret || !chc.conContext.protocolVersion.useTLS10PlusSpec()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable extended_master_secret extension"); } @@ -162,7 +162,7 @@ final class ExtendedMasterSecretExtension { if (!shc.sslConfig.isAvailable(CH_EXTENDED_MASTER_SECRET) || !SSLConfiguration.useExtendedMasterSecret || !shc.negotiatedProtocol.useTLS10PlusSpec()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Ignore unavailable extension: " + CH_EXTENDED_MASTER_SECRET.name); } @@ -182,7 +182,7 @@ final class ExtendedMasterSecretExtension { // with a full handshake. shc.isResumption = false; shc.resumingSession = null; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "abort session resumption which did not use " + "Extended Master Secret extension"); @@ -213,7 +213,7 @@ final class ExtendedMasterSecretExtension { // Is it a supported and enabled extension? if (!shc.sslConfig.isAvailable(CH_EXTENDED_MASTER_SECRET) || !SSLConfiguration.useExtendedMasterSecret) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Ignore unavailable extension: " + CH_EXTENDED_MASTER_SECRET.name); } @@ -252,7 +252,7 @@ final class ExtendedMasterSecretExtension { } else { // Otherwise, continue with a full handshake. shc.isResumption = false; shc.resumingSession = null; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "abort session resumption, " + "missing Extended Master Secret extension"); diff --git a/src/java.base/share/classes/sun/security/ssl/Finished.java b/src/java.base/share/classes/sun/security/ssl/Finished.java index 04fe61760d0..4238ced8f01 100644 --- a/src/java.base/share/classes/sun/security/ssl/Finished.java +++ b/src/java.base/share/classes/sun/security/ssl/Finished.java @@ -390,7 +390,7 @@ final class Finished { // Change write cipher and delivery ChangeCipherSpec message. ChangeCipherSpec.t10Producer.produce(chc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced client Finished handshake message", fm); } @@ -453,7 +453,7 @@ final class Finished { // Change write cipher and delivery ChangeCipherSpec message. ChangeCipherSpec.t10Producer.produce(shc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced server Finished handshake message", fm); } @@ -542,7 +542,7 @@ final class Finished { private void onConsumeFinished(ClientHandshakeContext chc, ByteBuffer message) throws IOException { FinishedMessage fm = new FinishedMessage(chc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming server Finished handshake message", fm); } @@ -602,7 +602,7 @@ final class Finished { } FinishedMessage fm = new FinishedMessage(shc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming client Finished handshake message", fm); } @@ -681,7 +681,7 @@ final class Finished { chc.handshakeHash.update(); FinishedMessage fm = new FinishedMessage(chc); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced client Finished handshake message", fm); } @@ -778,7 +778,7 @@ final class Finished { shc.handshakeHash.update(); FinishedMessage fm = new FinishedMessage(shc); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced server Finished handshake message", fm); } @@ -930,7 +930,7 @@ final class Finished { } FinishedMessage fm = new FinishedMessage(chc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming server Finished handshake message", fm); } @@ -1073,7 +1073,7 @@ final class Finished { } FinishedMessage fm = new FinishedMessage(shc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming client Finished handshake message", fm); } diff --git a/src/java.base/share/classes/sun/security/ssl/HandshakeContext.java b/src/java.base/share/classes/sun/security/ssl/HandshakeContext.java index 8455ddfc65d..a5f340d5203 100644 --- a/src/java.base/share/classes/sun/security/ssl/HandshakeContext.java +++ b/src/java.base/share/classes/sun/security/ssl/HandshakeContext.java @@ -284,14 +284,14 @@ abstract class HandshakeContext implements ConnectionContext { found = true; break; } - } else if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + } else if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine( "Ignore unsupported cipher suite: " + suite + " for " + protocol.name); } } - if (!found && (SSLLogger.isOn) && SSLLogger.isOn("handshake")) { + if (!found && (SSLLogger.isOn()) && SSLLogger.isOn("handshake")) { SSLLogger.fine( "No available cipher suite for " + protocol.name); } @@ -335,7 +335,7 @@ abstract class HandshakeContext implements ConnectionContext { } if (!isSupported && - SSLLogger.isOn && SSLLogger.isOn("verbose")) { + SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.finest( "Ignore unsupported cipher suite: " + suite); } @@ -556,7 +556,7 @@ abstract class HandshakeContext implements ConnectionContext { cachedStatus.put(groupType, groupAvailable); if (!groupAvailable && - SSLLogger.isOn && SSLLogger.isOn("verbose")) { + SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine( "No activated named group in " + groupType); } @@ -570,13 +570,13 @@ abstract class HandshakeContext implements ConnectionContext { } } - if (!retval && SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (!retval && SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine("No active named group(s), ignore " + suite); } return retval; - } else if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + } else if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine("Ignore disabled cipher suite: " + suite); } diff --git a/src/java.base/share/classes/sun/security/ssl/HandshakeOutStream.java b/src/java.base/share/classes/sun/security/ssl/HandshakeOutStream.java index 61936442502..2a05881180d 100644 --- a/src/java.base/share/classes/sun/security/ssl/HandshakeOutStream.java +++ b/src/java.base/share/classes/sun/security/ssl/HandshakeOutStream.java @@ -61,7 +61,7 @@ public class HandshakeOutStream extends ByteArrayOutputStream { if (!outputRecord.isClosed()) { outputRecord.encodeHandshake(buf, 0, count); } else { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("outbound has closed, ignore outbound " + "handshake messages", ByteBuffer.wrap(buf, 0, count)); } diff --git a/src/java.base/share/classes/sun/security/ssl/HelloRequest.java b/src/java.base/share/classes/sun/security/ssl/HelloRequest.java index f4da66b5dd3..39464992db5 100644 --- a/src/java.base/share/classes/sun/security/ssl/HelloRequest.java +++ b/src/java.base/share/classes/sun/security/ssl/HelloRequest.java @@ -101,7 +101,7 @@ final class HelloRequest { ServerHandshakeContext shc = (ServerHandshakeContext)context; HelloRequestMessage hrm = new HelloRequestMessage(shc); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Produced HelloRequest handshake message", hrm); } @@ -137,7 +137,7 @@ final class HelloRequest { ServerHandshakeContext shc = (ServerHandshakeContext)context; HelloRequestMessage hrm = new HelloRequestMessage(shc); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Produced HelloRequest handshake message", hrm); } @@ -177,7 +177,7 @@ final class HelloRequest { // be sent by the server at any time. Please don't clean up this // handshake consumer. HelloRequestMessage hrm = new HelloRequestMessage(chc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming HelloRequest handshake message", hrm); } @@ -190,7 +190,7 @@ final class HelloRequest { } if (!chc.conContext.secureRenegotiation) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "Continue with insecure renegotiation"); } @@ -206,7 +206,7 @@ final class HelloRequest { // SSLHandshake.CLIENT_HELLO.produce(context, hrm); } else { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore HelloRequest, handshaking is in progress"); } diff --git a/src/java.base/share/classes/sun/security/ssl/HelloVerifyRequest.java b/src/java.base/share/classes/sun/security/ssl/HelloVerifyRequest.java index f28ae16de88..8d3f1048c91 100644 --- a/src/java.base/share/classes/sun/security/ssl/HelloVerifyRequest.java +++ b/src/java.base/share/classes/sun/security/ssl/HelloVerifyRequest.java @@ -140,7 +140,7 @@ final class HelloVerifyRequest { HelloVerifyRequestMessage hvrm = new HelloVerifyRequestMessage(shc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced HelloVerifyRequest handshake message", hvrm); } @@ -197,7 +197,7 @@ final class HelloVerifyRequest { HelloVerifyRequestMessage hvrm = new HelloVerifyRequestMessage(chc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming HelloVerifyRequest handshake message", hvrm); } diff --git a/src/java.base/share/classes/sun/security/ssl/KeyShareExtension.java b/src/java.base/share/classes/sun/security/ssl/KeyShareExtension.java index 98e4693e917..8d785f7515a 100644 --- a/src/java.base/share/classes/sun/security/ssl/KeyShareExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/KeyShareExtension.java @@ -90,7 +90,7 @@ final class KeyShareExtension { Record.putInt16(m, namedGroupId); Record.putBytes16(m, keyExchange); } catch (IOException ioe) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "Unlikely IOException", ioe); } @@ -222,7 +222,7 @@ final class KeyShareExtension { // Is it a supported and enabled extension? if (!chc.sslConfig.isAvailable(SSLExtension.CH_KEY_SHARE)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable key_share extension"); } @@ -237,7 +237,7 @@ final class KeyShareExtension { namedGroups = chc.clientRequestedNamedGroups; if (namedGroups == null || namedGroups.isEmpty()) { // No supported groups. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "Ignore key_share extension, no supported groups"); } @@ -287,7 +287,7 @@ final class KeyShareExtension { NamedGroup ng) { SSLKeyExchange ke = SSLKeyExchange.valueOf(ng); if (ke == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "No key exchange for named group " + ng.name); } @@ -323,7 +323,7 @@ final class KeyShareExtension { ServerHandshakeContext shc = (ServerHandshakeContext)context; if (shc.handshakeExtensions.containsKey(SSLExtension.CH_KEY_SHARE)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "The key_share extension has been loaded"); } @@ -332,7 +332,7 @@ final class KeyShareExtension { // Is it a supported and enabled extension? if (!shc.sslConfig.isAvailable(SSLExtension.CH_KEY_SHARE)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable key_share extension"); } @@ -346,7 +346,7 @@ final class KeyShareExtension { NamedGroup ng = NamedGroup.valueOf(entry.namedGroupId); if (ng == null || !NamedGroup.isActivatable(shc.sslConfig, shc.algorithmConstraints, ng)) { - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unsupported named group: " + @@ -364,7 +364,7 @@ final class KeyShareExtension { if (!shc.algorithmConstraints.permits( EnumSet.of(CryptoPrimitive.KEY_AGREEMENT), namedGroupCredentials.getPublicKey())) { - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "key share entry of " + ng + " does not " + @@ -379,7 +379,7 @@ final class KeyShareExtension { credentials.add(kaCred); } } catch (GeneralSecurityException ex) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "Cannot decode named group: " + NamedGroup.nameOf(entry.namedGroupId)); @@ -522,7 +522,7 @@ final class KeyShareExtension { SSLExtension.CH_KEY_SHARE); if (kss == null) { // Unlikely, no key_share extension requested. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "Ignore, no client key_share extension"); } @@ -531,7 +531,7 @@ final class KeyShareExtension { // Is it a supported and enabled extension? if (!shc.sslConfig.isAvailable(SSLExtension.SH_KEY_SHARE)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "Ignore, no available server key_share extension"); } @@ -542,7 +542,7 @@ final class KeyShareExtension { if ((shc.handshakeCredentials == null) || shc.handshakeCredentials.isEmpty()) { // Unlikely, HelloRetryRequest should be used earlier. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "No available client key share entries"); } @@ -562,7 +562,7 @@ final class KeyShareExtension { SSLKeyExchange ke = SSLKeyExchange.valueOf(ng); if (ke == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "No key exchange for named group " + ng.name); } @@ -597,7 +597,7 @@ final class KeyShareExtension { if (keyShare == null) { // Unlikely, HelloRetryRequest should be used instead earlier. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "No available server key_share extension"); } @@ -708,7 +708,7 @@ final class KeyShareExtension { ClientHandshakeContext chc = (ClientHandshakeContext)context; // Cannot use the previous requested key shares anymore. - if (SSLLogger.isOn && SSLLogger.isOn("handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("handshake")) { SSLLogger.fine( "No key_share extension in ServerHello, " + "cleanup the key shares if necessary"); @@ -801,7 +801,7 @@ final class KeyShareExtension { for (NamedGroup ng : shc.clientRequestedNamedGroups) { if (NamedGroup.isActivatable(shc.sslConfig, shc.algorithmConstraints, ng)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "HelloRetryRequest selected named group: " + ng.name); diff --git a/src/java.base/share/classes/sun/security/ssl/KeyUpdate.java b/src/java.base/share/classes/sun/security/ssl/KeyUpdate.java index 2b17c7406a3..4e5a9683079 100644 --- a/src/java.base/share/classes/sun/security/ssl/KeyUpdate.java +++ b/src/java.base/share/classes/sun/security/ssl/KeyUpdate.java @@ -191,7 +191,7 @@ final class KeyUpdate { // The consuming happens in client side only. PostHandshakeContext hc = (PostHandshakeContext)context; KeyUpdateMessage km = new KeyUpdateMessage(hc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming KeyUpdate post-handshake message", km); } @@ -235,7 +235,7 @@ final class KeyUpdate { rc.baseSecret = nplus1; hc.conContext.inputRecord.changeReadCiphers(rc); - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("KeyUpdate: read key updated"); } } catch (GeneralSecurityException gse) { @@ -276,7 +276,7 @@ final class KeyUpdate { return null; } KeyUpdateMessage km = (KeyUpdateMessage)message; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced KeyUpdate post-handshake message", km); } @@ -328,7 +328,7 @@ final class KeyUpdate { // changeWriteCiphers() implementation. wc.baseSecret = nplus1; hc.conContext.outputRecord.changeWriteCiphers(wc, km.status.id); - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("KeyUpdate: write key updated"); } diff --git a/src/java.base/share/classes/sun/security/ssl/MaxFragExtension.java b/src/java.base/share/classes/sun/security/ssl/MaxFragExtension.java index a07e81be914..25500c7ac57 100644 --- a/src/java.base/share/classes/sun/security/ssl/MaxFragExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/MaxFragExtension.java @@ -176,7 +176,7 @@ final class MaxFragExtension { // Is it a supported and enabled extension? if (!chc.sslConfig.isAvailable(CH_MAX_FRAGMENT_LENGTH)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable max_fragment_length extension"); } @@ -213,7 +213,7 @@ final class MaxFragExtension { } else { // log and ignore, no MFL extension. chc.maxFragmentLength = -1; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "No available max_fragment_length extension can " + "be used for fragment size of " + @@ -243,7 +243,7 @@ final class MaxFragExtension { ServerHandshakeContext shc = (ServerHandshakeContext)context; if (!shc.sslConfig.isAvailable(CH_MAX_FRAGMENT_LENGTH)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable max_fragment_length extension"); } @@ -288,7 +288,7 @@ final class MaxFragExtension { MaxFragLenSpec spec = (MaxFragLenSpec) shc.handshakeExtensions.get(CH_MAX_FRAGMENT_LENGTH); if (spec == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest( "Ignore unavailable max_fragment_length extension"); } @@ -305,7 +305,7 @@ final class MaxFragExtension { // For better interoperability, abort the maximum // fragment length negotiation, rather than terminate // the connection with a fatal alert. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Abort the maximum fragment length negotiation, " + "may overflow the maximum packet size limit."); @@ -413,7 +413,7 @@ final class MaxFragExtension { // For better interoperability, abort the maximum // fragment length negotiation, rather than terminate // the connection with a fatal alert. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Abort the maximum fragment length negotiation, " + "may overflow the maximum packet size limit."); @@ -455,7 +455,7 @@ final class MaxFragExtension { MaxFragLenSpec spec = (MaxFragLenSpec) shc.handshakeExtensions.get(CH_MAX_FRAGMENT_LENGTH); if (spec == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest( "Ignore unavailable max_fragment_length extension"); } @@ -472,7 +472,7 @@ final class MaxFragExtension { // For better interoperability, abort the maximum // fragment length negotiation, rather than terminate // the connection with a fatal alert. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Abort the maximum fragment length negotiation, " + "may overflow the maximum packet size limit."); @@ -578,7 +578,7 @@ final class MaxFragExtension { // For better interoperability, abort the maximum // fragment length negotiation, rather than terminate // the connection with a fatal alert. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Abort the maximum fragment length negotiation, " + "may overflow the maximum packet size limit."); diff --git a/src/java.base/share/classes/sun/security/ssl/NamedGroup.java b/src/java.base/share/classes/sun/security/ssl/NamedGroup.java index 46280a05355..0c708b194cb 100644 --- a/src/java.base/share/classes/sun/security/ssl/NamedGroup.java +++ b/src/java.base/share/classes/sun/security/ssl/NamedGroup.java @@ -273,7 +273,7 @@ enum NamedGroup { | NoSuchAlgorithmException exp) { if (namedGroupSpec != NamedGroupSpec.NAMED_GROUP_XDH) { mediator = false; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "No AlgorithmParameters for " + name, exp); } @@ -294,7 +294,7 @@ enum NamedGroup { // AlgorithmParameters.getInstance(name); } catch (NoSuchAlgorithmException nsae) { mediator = false; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "No AlgorithmParameters for " + name, nsae); } @@ -382,7 +382,7 @@ enum NamedGroup { for (String ss : namedGroups) { NamedGroup ng = NamedGroup.nameOf(ss); if (ng == null || !ng.isAvailable) { - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "Ignore the named group (" + ss @@ -811,7 +811,7 @@ enum NamedGroup { } if (groupList.isEmpty() && - SSLLogger.isOn && SSLLogger.isOn("ssl")) { + SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("No default named groups"); } } diff --git a/src/java.base/share/classes/sun/security/ssl/NewSessionTicket.java b/src/java.base/share/classes/sun/security/ssl/NewSessionTicket.java index 4c879e0dc4d..89b0a72bb32 100644 --- a/src/java.base/share/classes/sun/security/ssl/NewSessionTicket.java +++ b/src/java.base/share/classes/sun/security/ssl/NewSessionTicket.java @@ -202,7 +202,7 @@ final class NewSessionTicket { this.ticket = Record.getBytes16(m); if (ticket.length == 0) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "No ticket in the NewSessionTicket handshake message"); } @@ -329,7 +329,7 @@ final class NewSessionTicket { if (hc instanceof ServerHandshakeContext) { // Is this session resumable? if (!hc.handshakeSession.isRejoinable()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("No session ticket produced: " + "session is not resumable"); } @@ -347,7 +347,7 @@ final class NewSessionTicket { SSLExtension.PSK_KEY_EXCHANGE_MODES); if (pkemSpec == null || !pkemSpec.contains(PskKeyExchangeMode.PSK_DHE_KE)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("No session ticket produced: " + "client does not support psk_dhe_ke"); } @@ -358,7 +358,7 @@ final class NewSessionTicket { // Check if we have sent a PSK already, then we know it is // using an allowable PSK exchange key mode. if (!hc.handshakeSession.isPSKable()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("No session ticket produced: " + "No session ticket allowed in this session"); } @@ -372,7 +372,7 @@ final class NewSessionTicket { hc.sslContext.engineGetServerSessionContext(); int sessionTimeoutSeconds = sessionCache.getSessionTimeout(); if (sessionTimeoutSeconds > MAX_TICKET_LIFETIME) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("No session ticket produced: " + "session timeout is too long"); } @@ -459,7 +459,7 @@ final class NewSessionTicket { if (!nstm.isValid()) { hc.statelessResumption = false; } else { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Produced NewSessionTicket stateless " + "post-handshake message", nstm); } @@ -474,7 +474,7 @@ final class NewSessionTicket { sessionCache.getSessionTimeout(), hc.sslContext.getSecureRandom(), nonce, newId.getId()); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Produced NewSessionTicket " + "post-handshake message", nstm); } @@ -488,7 +488,7 @@ final class NewSessionTicket { return nstm; } - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("No NewSessionTicket created"); } @@ -526,7 +526,7 @@ final class NewSessionTicket { shc.sslContext.engineGetServerSessionContext(); int sessionTimeoutSeconds = sessionCache.getSessionTimeout(); if (sessionTimeoutSeconds > MAX_TICKET_LIFETIME) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Session timeout is too long. No ticket sent."); } @@ -540,7 +540,7 @@ final class NewSessionTicket { NewSessionTicketMessage nstm = new T12NewSessionTicketMessage(shc, sessionTimeoutSeconds, new SessionTicketSpec().encrypt(shc, sessionCopy)); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced NewSessionTicket stateless handshake message", nstm); @@ -579,7 +579,7 @@ final class NewSessionTicket { HandshakeContext hc = (HandshakeContext)context; NewSessionTicketMessage nstm = new T13NewSessionTicketMessage(hc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming NewSessionTicket message", nstm); } @@ -590,7 +590,7 @@ final class NewSessionTicket { // discard tickets with timeout 0 if (nstm.ticketLifetime <= 0 || nstm.ticketLifetime > MAX_TICKET_LIFETIME) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Discarding NewSessionTicket with lifetime " + nstm.ticketLifetime, nstm); @@ -599,7 +599,7 @@ final class NewSessionTicket { } if (sessionCache.getSessionTimeout() > MAX_TICKET_LIFETIME) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Session cache lifetime is too long. " + "Discarding ticket."); @@ -611,7 +611,7 @@ final class NewSessionTicket { SecretKey resumptionMasterSecret = sessionToSave.getResumptionMasterSecret(); if (resumptionMasterSecret == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Session has no resumption master secret. " + "Ignoring ticket."); @@ -637,7 +637,7 @@ final class NewSessionTicket { sessionCopy.setPskIdentity(nstm.ticket); sessionCache.put(sessionCopy, sessionCopy.isPSK()); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("MultiNST PSK (Server): " + Utilities.toHexString(Arrays.copyOf(nstm.ticket, 16))); } @@ -665,7 +665,7 @@ final class NewSessionTicket { NewSessionTicketMessage nstm = new T12NewSessionTicketMessage(hc, message); if (nstm.ticket.length == 0) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("NewSessionTicket ticket was empty"); } return; @@ -674,7 +674,7 @@ final class NewSessionTicket { // discard tickets with timeout 0 if (nstm.ticketLifetime <= 0 || nstm.ticketLifetime > MAX_TICKET_LIFETIME) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Discarding NewSessionTicket with lifetime " + nstm.ticketLifetime, nstm); @@ -686,7 +686,7 @@ final class NewSessionTicket { hc.sslContext.engineGetClientSessionContext(); if (sessionCache.getSessionTimeout() > MAX_TICKET_LIFETIME) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Session cache lifetime is too long. " + "Discarding ticket."); @@ -695,7 +695,7 @@ final class NewSessionTicket { } hc.handshakeSession.setPskIdentity(nstm.ticket); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Consuming NewSessionTicket\n" + nstm); } } diff --git a/src/java.base/share/classes/sun/security/ssl/OutputRecord.java b/src/java.base/share/classes/sun/security/ssl/OutputRecord.java index f2c30b3ff72..416d5d1b5ef 100644 --- a/src/java.base/share/classes/sun/security/ssl/OutputRecord.java +++ b/src/java.base/share/classes/sun/security/ssl/OutputRecord.java @@ -188,7 +188,7 @@ abstract class OutputRecord recordLock.lock(); try { if (isClosed()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("outbound has closed, ignore outbound " + "change_cipher_spec message"); } @@ -222,7 +222,7 @@ abstract class OutputRecord recordLock.lock(); try { if (isClosed()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("outbound has closed, ignore outbound " + "key_update handshake message"); } diff --git a/src/java.base/share/classes/sun/security/ssl/PreSharedKeyExtension.java b/src/java.base/share/classes/sun/security/ssl/PreSharedKeyExtension.java index 819fdd589cb..b99c0175838 100644 --- a/src/java.base/share/classes/sun/security/ssl/PreSharedKeyExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/PreSharedKeyExtension.java @@ -341,7 +341,7 @@ final class PreSharedKeyExtension { ServerHandshakeContext shc = (ServerHandshakeContext)context; // Is it a supported and enabled extension? if (!shc.sslConfig.isAvailable(SSLExtension.CH_PRE_SHARED_KEY)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable pre_shared_key extension"); } @@ -393,7 +393,7 @@ final class PreSharedKeyExtension { } } if (b == null || s == null) { - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Stateless session ticket invalid"); @@ -402,7 +402,7 @@ final class PreSharedKeyExtension { } if (s != null && canRejoin(clientHello, shc, s)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Resuming session: ", s); } @@ -435,7 +435,7 @@ final class PreSharedKeyExtension { // Check protocol version if (result && s.getProtocolVersion() != shc.negotiatedProtocol) { - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest("Can't resume, incorrect protocol version"); @@ -449,7 +449,7 @@ final class PreSharedKeyExtension { try { s.getPeerPrincipal(); } catch (SSLPeerUnverifiedException e) { - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "Can't resume, " + @@ -466,7 +466,7 @@ final class PreSharedKeyExtension { if (result && !shc.localSupportedCertSignAlgs.containsAll(sessionSigAlgs)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Can't resume. Session uses different " + "signature algorithms"); } @@ -480,7 +480,7 @@ final class PreSharedKeyExtension { if (result && identityAlg != null) { String sessionIdentityAlg = s.getIdentificationProtocol(); if (!identityAlg.equalsIgnoreCase(sessionIdentityAlg)) { - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest("Can't resume, endpoint id" + @@ -494,7 +494,7 @@ final class PreSharedKeyExtension { // Ensure cipher suite can be negotiated if (result && (!shc.isNegotiable(s.getSuite()) || !clientHello.cipherSuites.contains(s.getSuite()))) { - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "Can't resume, unavailable session cipher suite"); @@ -653,7 +653,7 @@ final class PreSharedKeyExtension { // The producing happens in client side only. ClientHandshakeContext chc = (ClientHandshakeContext)context; if (!chc.isResumption || chc.resumingSession == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("No session to resume."); } return null; @@ -663,7 +663,7 @@ final class PreSharedKeyExtension { Collection sessionSigAlgs = chc.resumingSession.getLocalSupportedSignatureSchemes(); if (!chc.localSupportedCertSignAlgs.containsAll(sessionSigAlgs)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Existing session uses different " + "signature algorithms"); } @@ -673,7 +673,7 @@ final class PreSharedKeyExtension { // The session must have a pre-shared key SecretKey psk = chc.resumingSession.getPreSharedKey(); if (psk == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Existing session has no PSK."); } return null; @@ -687,7 +687,7 @@ final class PreSharedKeyExtension { } if (chc.pskIdentity == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "PSK has no identity, or identity was already used"); } @@ -699,7 +699,7 @@ final class PreSharedKeyExtension { chc.sslContext.engineGetClientSessionContext(); sessionCache.remove(chc.resumingSession.getSessionId(), true); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Found resumable session. Preparing PSK message."); SSLLogger.fine( @@ -836,7 +836,7 @@ final class PreSharedKeyExtension { public void absent(ConnectionContext context, HandshakeMessage message) throws IOException { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Handling pre_shared_key absence."); } @@ -901,7 +901,7 @@ final class PreSharedKeyExtension { } SHPreSharedKeySpec shPsk = new SHPreSharedKeySpec(chc, buffer); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Received pre_shared_key extension: ", shPsk); } @@ -911,7 +911,7 @@ final class PreSharedKeyExtension { "Selected identity index is not in correct range."); } - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Resuming session: ", chc.resumingSession); } @@ -925,7 +925,7 @@ final class PreSharedKeyExtension { HandshakeMessage message) throws IOException { ClientHandshakeContext chc = (ClientHandshakeContext)context; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Handling pre_shared_key absence."); } diff --git a/src/java.base/share/classes/sun/security/ssl/PredefinedDHParameterSpecs.java b/src/java.base/share/classes/sun/security/ssl/PredefinedDHParameterSpecs.java index e510fe92b0e..c826d9c89e3 100644 --- a/src/java.base/share/classes/sun/security/ssl/PredefinedDHParameterSpecs.java +++ b/src/java.base/share/classes/sun/security/ssl/PredefinedDHParameterSpecs.java @@ -246,7 +246,7 @@ final class PredefinedDHParameterSpecs { Matcher spacesMatcher = spacesPattern.matcher(property); property = spacesMatcher.replaceAll(""); - if (SSLLogger.isOn && SSLLogger.isOn("sslctx")) { + if (SSLLogger.isOn() && SSLLogger.isOn("sslctx")) { SSLLogger.fine( "The Security Property " + PROPERTY_NAME + ": " + property); @@ -262,7 +262,7 @@ final class PredefinedDHParameterSpecs { String primeModulus = paramsFinder.group(1); BigInteger p = new BigInteger(primeModulus, 16); if (!p.isProbablePrime(PRIME_CERTAINTY)) { - if (SSLLogger.isOn && SSLLogger.isOn("sslctx")) { + if (SSLLogger.isOn() && SSLLogger.isOn("sslctx")) { SSLLogger.fine( "Prime modulus p in Security Property, " + PROPERTY_NAME + ", is not a prime: " + @@ -279,7 +279,7 @@ final class PredefinedDHParameterSpecs { DHParameterSpec spec = new DHParameterSpec(p, g); defaultParams.put(primeLen, spec); } - } else if (SSLLogger.isOn && SSLLogger.isOn("sslctx")) { + } else if (SSLLogger.isOn() && SSLLogger.isOn("sslctx")) { SSLLogger.fine("Invalid Security Property, " + PROPERTY_NAME + ", definition"); } diff --git a/src/java.base/share/classes/sun/security/ssl/PskKeyExchangeModesExtension.java b/src/java.base/share/classes/sun/security/ssl/PskKeyExchangeModesExtension.java index 07707c5163a..a4f343ccb06 100644 --- a/src/java.base/share/classes/sun/security/ssl/PskKeyExchangeModesExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/PskKeyExchangeModesExtension.java @@ -184,7 +184,7 @@ final class PskKeyExchangeModesExtension { // Is it a supported and enabled extension? if (!shc.sslConfig.isAvailable( SSLExtension.PSK_KEY_EXCHANGE_MODES)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable psk_key_exchange_modes extension"); } @@ -216,7 +216,7 @@ final class PskKeyExchangeModesExtension { if (!spec.contains(PskKeyExchangeMode.PSK_DHE_KE)) { shc.isResumption = false; shc.resumingSession = null; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "abort session resumption, " + "no supported psk_dhe_ke PSK key exchange mode"); @@ -247,7 +247,7 @@ final class PskKeyExchangeModesExtension { // Is it a supported and enabled extension? if (!chc.sslConfig.isAvailable( SSLExtension.PSK_KEY_EXCHANGE_MODES)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "Ignore unavailable psk_key_exchange_modes extension"); } @@ -287,7 +287,7 @@ final class PskKeyExchangeModesExtension { if (shc.isResumption) { // resumingSession may not be set shc.isResumption = false; shc.resumingSession = null; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "abort session resumption, " + "no supported psk_dhe_ke PSK key exchange mode"); diff --git a/src/java.base/share/classes/sun/security/ssl/QuicEngineOutputRecord.java b/src/java.base/share/classes/sun/security/ssl/QuicEngineOutputRecord.java index 893eb282116..7e307ba9d27 100644 --- a/src/java.base/share/classes/sun/security/ssl/QuicEngineOutputRecord.java +++ b/src/java.base/share/classes/sun/security/ssl/QuicEngineOutputRecord.java @@ -75,14 +75,14 @@ final class QuicEngineOutputRecord extends OutputRecord implements SSLRecord { recordLock.lock(); try { if (isClosed()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("outbound has closed, ignore outbound " + "alert message: " + Alert.nameOf(description)); } return; } if (level == Alert.Level.WARNING.level) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("Suppressing warning-level " + "alert message: " + Alert.nameOf(description)); } @@ -90,7 +90,7 @@ final class QuicEngineOutputRecord extends OutputRecord implements SSLRecord { } if (alert != null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("Suppressing subsequent alert: " + description + ", original: " + alert.id); } @@ -109,7 +109,7 @@ final class QuicEngineOutputRecord extends OutputRecord implements SSLRecord { recordLock.lock(); try { if (isClosed()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("outbound has closed, ignore outbound " + "handshake message", ByteBuffer.wrap(source, offset, length)); diff --git a/src/java.base/share/classes/sun/security/ssl/QuicKeyManager.java b/src/java.base/share/classes/sun/security/ssl/QuicKeyManager.java index fb9077af022..4613dcf96ff 100644 --- a/src/java.base/share/classes/sun/security/ssl/QuicKeyManager.java +++ b/src/java.base/share/classes/sun/security/ssl/QuicKeyManager.java @@ -244,7 +244,7 @@ sealed abstract class QuicKeyManager if (toDiscard == null) { return; } - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest("discarding keys (keyphase=" + toDiscard.writeCipher.getKeyPhase() + ") of " + this.keySpace + " key space"); @@ -389,7 +389,7 @@ sealed abstract class QuicKeyManager if (toDiscard == null) { return; } - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest("discarding keys (keyphase=" + toDiscard.writeCipher.getKeyPhase() + ") of " + this.keySpace + " key space"); @@ -570,7 +570,7 @@ sealed abstract class QuicKeyManager if (series == null) { return; } - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest("discarding key (series) of " + this.keySpace + " key space"); } @@ -611,7 +611,7 @@ sealed abstract class QuicKeyManager if (series.canUseOldDecryptKey(packetNumber)) { final QuicReadCipher oldReadCipher = series.old; assert oldReadCipher != null : "old key is unexpectedly null"; - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest("using old read key to decrypt packet: " + packetNumber + ", with incoming key phase: " + keyPhase + ", current key phase: " + @@ -633,7 +633,7 @@ sealed abstract class QuicKeyManager // KEY_UPDATE_ERROR. This indicates that a peer has // received and acknowledged a packet that initiates a key // update, but has not updated keys in response. - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest("peer used incorrect key, was" + " expected to use updated key of" + " key phase: " + currentKeyPhase + @@ -646,7 +646,7 @@ sealed abstract class QuicKeyManager } return; } - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest("detected ONE_RTT key update, current key " + "phase: " + currentKeyPhase + ", incoming key phase: " + keyPhase @@ -717,7 +717,7 @@ sealed abstract class QuicKeyManager } final long numEncrypted = cipher.getNumEncrypted(); if (numEncrypted >= 0.8 * confidentialityLimit) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest("about to reach confidentiality limit, " + "attempting to initiate a 1-RTT key update," + " packet number: " + @@ -732,7 +732,7 @@ sealed abstract class QuicKeyManager : "key phase of updated key unexpectedly matches " + "the key phase " + cipher.getKeyPhase() + " of current keys"; - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest( "1-RTT key update initiated, new key phase: " + newKeyPhase); @@ -755,7 +755,7 @@ sealed abstract class QuicKeyManager // current key phase. This ensures that keys are // available to both peers before // another key update can be initiated. - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest( "skipping key update initiation because peer " + "hasn't yet sent us a packet encrypted with " + @@ -803,7 +803,7 @@ sealed abstract class QuicKeyManager // (we avoid timing attacks by not generating // keys during decryption, our key generation // only happens during encryption) - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest("next keys unavailable," + " won't decrypt a packet which appears to be" + " a key update"); @@ -815,7 +815,7 @@ sealed abstract class QuicKeyManager // use the next keys to attempt decrypting currentKeySeries.next.readCipher.decryptPacket(packetNumber, packet, headerLength, output); - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest( "decrypted using next keys for peer-initiated" + " key update; will now switch to new key phase: " + @@ -1025,14 +1025,14 @@ sealed abstract class QuicKeyManager // update the key series this.keySeries = newSeries; if (oldReadCipher != null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest( "discarding old read key of key phase: " + oldReadCipher.getKeyPhase()); } oldReadCipher.discard(false); } - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest("discarding write key of key phase: " + writeCipherToDiscard.getKeyPhase()); } diff --git a/src/java.base/share/classes/sun/security/ssl/QuicTLSEngineImpl.java b/src/java.base/share/classes/sun/security/ssl/QuicTLSEngineImpl.java index 6765f554fcc..18790a58c11 100644 --- a/src/java.base/share/classes/sun/security/ssl/QuicTLSEngineImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/QuicTLSEngineImpl.java @@ -560,7 +560,7 @@ public final class QuicTLSEngineImpl implements QuicTLSEngine, SSLTransport { // incoming crypto buffer is null. Validate message type, // check if size is available byte messageType = payload.get(payload.position()); - if (SSLLogger.isOn) { + if (SSLLogger.isOn()) { SSLLogger.fine("Received message of type 0x" + Integer.toHexString(messageType & 0xFF)); } @@ -835,7 +835,7 @@ public final class QuicTLSEngineImpl implements QuicTLSEngine, SSLTransport { final boolean confirmed = HANDSHAKE_STATE_HANDLE.compareAndSet(this, NEED_SEND_HANDSHAKE_DONE, HANDSHAKE_CONFIRMED); if (confirmed) { - if (SSLLogger.isOn) { + if (SSLLogger.isOn()) { SSLLogger.fine("QuicTLSEngine (server) marked handshake " + "state as HANDSHAKE_CONFIRMED"); } @@ -853,7 +853,7 @@ public final class QuicTLSEngineImpl implements QuicTLSEngine, SSLTransport { final boolean confirmed = HANDSHAKE_STATE_HANDLE.compareAndSet(this, NEED_RECV_HANDSHAKE_DONE, HANDSHAKE_CONFIRMED); if (confirmed) { - if (SSLLogger.isOn) { + if (SSLLogger.isOn()) { SSLLogger.fine( "QuicTLSEngine (client) received HANDSHAKE_DONE," + " marking state as HANDSHAKE_DONE"); diff --git a/src/java.base/share/classes/sun/security/ssl/RSAClientKeyExchange.java b/src/java.base/share/classes/sun/security/ssl/RSAClientKeyExchange.java index 701ba35174e..ec91cb4509a 100644 --- a/src/java.base/share/classes/sun/security/ssl/RSAClientKeyExchange.java +++ b/src/java.base/share/classes/sun/security/ssl/RSAClientKeyExchange.java @@ -190,7 +190,7 @@ final class RSAClientKeyExchange { throw chc.conContext.fatal(Alert.ILLEGAL_PARAMETER, "Cannot generate RSA premaster secret", gse); } - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced RSA ClientKeyExchange handshake message", ckem); } @@ -270,7 +270,7 @@ final class RSAClientKeyExchange { RSAClientKeyExchangeMessage ckem = new RSAClientKeyExchangeMessage(shc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming RSA ClientKeyExchange handshake message", ckem); } diff --git a/src/java.base/share/classes/sun/security/ssl/RSAKeyExchange.java b/src/java.base/share/classes/sun/security/ssl/RSAKeyExchange.java index 311ac97e744..d176d7311d0 100644 --- a/src/java.base/share/classes/sun/security/ssl/RSAKeyExchange.java +++ b/src/java.base/share/classes/sun/security/ssl/RSAKeyExchange.java @@ -35,7 +35,6 @@ import java.security.PrivateKey; import java.security.PublicKey; import java.security.SecureRandom; import java.security.interfaces.RSAPublicKey; -import java.security.spec.AlgorithmParameterSpec; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; @@ -150,7 +149,7 @@ final class RSAKeyExchange { needFailover = !KeyUtil.isOracleJCEProvider( cipher.getProvider().getName()); } catch (InvalidKeyException | UnsupportedOperationException iue) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning("The Cipher provider " + safeProviderName(cipher) + " caused exception: " + iue.getMessage()); @@ -197,7 +196,7 @@ final class RSAKeyExchange { try { return cipher.getProvider().toString(); } catch (Exception e) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Retrieving The Cipher provider name" + " caused exception ", e); } @@ -205,7 +204,7 @@ final class RSAKeyExchange { try { return cipher.toString() + " (provider name not available)"; } catch (Exception e) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Retrieving The Cipher name" + " caused exception ", e); } @@ -220,7 +219,7 @@ final class RSAKeyExchange { int clientVersion, int serverVersion, byte[] encodedSecret, SecureRandom generator) throws GeneralSecurityException { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Generating a premaster secret"); } @@ -235,7 +234,7 @@ final class RSAKeyExchange { } catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException iae) { // unlikely to happen, otherwise, must be a provider exception - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("RSA premaster secret generation error", iae); } diff --git a/src/java.base/share/classes/sun/security/ssl/RSAServerKeyExchange.java b/src/java.base/share/classes/sun/security/ssl/RSAServerKeyExchange.java index 8633b9458ce..43f2ef95ba8 100644 --- a/src/java.base/share/classes/sun/security/ssl/RSAServerKeyExchange.java +++ b/src/java.base/share/classes/sun/security/ssl/RSAServerKeyExchange.java @@ -264,7 +264,7 @@ final class RSAServerKeyExchange { RSAServerKeyExchangeMessage skem = new RSAServerKeyExchangeMessage( shc, x509Possession, rsaPossession); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced RSA ServerKeyExchange handshake message", skem); } @@ -296,7 +296,7 @@ final class RSAServerKeyExchange { RSAServerKeyExchangeMessage skem = new RSAServerKeyExchangeMessage(chc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming RSA ServerKeyExchange handshake message", skem); } diff --git a/src/java.base/share/classes/sun/security/ssl/RenegoInfoExtension.java b/src/java.base/share/classes/sun/security/ssl/RenegoInfoExtension.java index e1348badd30..90b9e999925 100644 --- a/src/java.base/share/classes/sun/security/ssl/RenegoInfoExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/RenegoInfoExtension.java @@ -138,7 +138,7 @@ final class RenegoInfoExtension { // Is it a supported and enabled extension? if (!chc.sslConfig.isAvailable(CH_RENEGOTIATION_INFO)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable renegotiation_info extension"); } @@ -182,7 +182,7 @@ final class RenegoInfoExtension { return extData; } else { // not secure renegotiation if (HandshakeContext.allowUnsafeRenegotiation) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning("Using insecure renegotiation"); } @@ -216,7 +216,7 @@ final class RenegoInfoExtension { // Is it a supported and enabled extension? if (!shc.sslConfig.isAvailable(CH_RENEGOTIATION_INFO)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Ignore unavailable extension: " + CH_RENEGOTIATION_INFO.name); } @@ -280,7 +280,7 @@ final class RenegoInfoExtension { for (int id : clientHello.cipherSuiteIds) { if (id == CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV.id) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest( "Safe renegotiation, using the SCSV signaling"); } @@ -294,7 +294,7 @@ final class RenegoInfoExtension { "Failed to negotiate the use of secure renegotiation"); } // otherwise, allow legacy hello message - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning("Warning: No renegotiation " + "indication in ClientHello, allow legacy ClientHello"); } @@ -306,13 +306,13 @@ final class RenegoInfoExtension { "Inconsistent secure renegotiation indication"); } else { // renegotiation, not secure if (HandshakeContext.allowUnsafeRenegotiation) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning("Using insecure renegotiation"); } } else { // Unsafe renegotiation should have been aborted in // earlier processes. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Terminate insecure renegotiation"); } throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE, @@ -345,7 +345,7 @@ final class RenegoInfoExtension { if (requestedSpec == null && !shc.conContext.secureRenegotiation) { // Ignore, no renegotiation_info extension or SCSV signaling // requested. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest( "Ignore unavailable renegotiation_info extension"); } @@ -354,7 +354,7 @@ final class RenegoInfoExtension { if (!shc.conContext.secureRenegotiation) { // Ignore, no secure renegotiation is negotiated. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest( "No secure renegotiation has been negotiated"); } @@ -515,7 +515,7 @@ final class RenegoInfoExtension { "Failed to negotiate the use of secure renegotiation"); } // otherwise, allow legacy hello message - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning("Warning: No renegotiation " + "indication in ServerHello, allow legacy ServerHello"); } @@ -527,13 +527,13 @@ final class RenegoInfoExtension { "Inconsistent secure renegotiation indication"); } else { // renegotiation, not secure if (HandshakeContext.allowUnsafeRenegotiation) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning("Using insecure renegotiation"); } } else { // Unsafe renegotiation should have been aborted in // earlier processes. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Terminate insecure renegotiation"); } throw chc.conContext.fatal(Alert.HANDSHAKE_FAILURE, diff --git a/src/java.base/share/classes/sun/security/ssl/SSLAlgorithmConstraints.java b/src/java.base/share/classes/sun/security/ssl/SSLAlgorithmConstraints.java index 1d5a4c4e73d..594766ea0fd 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLAlgorithmConstraints.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLAlgorithmConstraints.java @@ -454,7 +454,7 @@ final class SSLAlgorithmConstraints implements AlgorithmConstraints { .equalsIgnoreCase(paramDigestAlg)); } catch (InvalidParameterSpecException e) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("Invalid AlgorithmParameters: " + parameters + "; Error: " + e.getMessage()); } diff --git a/src/java.base/share/classes/sun/security/ssl/SSLCipher.java b/src/java.base/share/classes/sun/security/ssl/SSLCipher.java index 4a52a2ea583..5dfa5be3420 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLCipher.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLCipher.java @@ -392,7 +392,7 @@ enum SSLCipher { if (values[1].contains(tag[0])) { index = 0; } else { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("jdk.tls.keyLimits: Unknown action: " + entry); } @@ -413,13 +413,13 @@ enum SSLCipher { "Length exceeded limits"); } } catch (NumberFormatException e) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("jdk.tls.keyLimits: " + e.getMessage() + ": " + entry); } continue; } - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("jdk.tls.keyLimits: entry = " + entry + ". " + values[0] + ":" + tag[index] + " = " + size); } @@ -468,7 +468,7 @@ enum SSLCipher { Cipher.getInstance(transformation); return true; } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("Transformation " + transformation + " is" + " not available."); } @@ -860,7 +860,7 @@ enum SSLCipher { "JCE provider " + cipher.getProvider().getName(), sbe); } pt.position(pos); - if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) { + if (SSLLogger.isOn() && SSLLogger.isOn("plaintext")) { SSLLogger.fine( "Plaintext after DECRYPTION", pt.duplicate()); } @@ -930,7 +930,7 @@ enum SSLCipher { authenticator.increaseSequenceNumber(); } - if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) { + if (SSLLogger.isOn() && SSLLogger.isOn("plaintext")) { SSLLogger.finest( "Padded plaintext before ENCRYPTION", bb.duplicate()); } @@ -1050,7 +1050,7 @@ enum SSLCipher { "JCE provider " + cipher.getProvider().getName(), sbe); } - if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) { + if (SSLLogger.isOn() && SSLLogger.isOn("plaintext")) { SSLLogger.fine( "Padded plaintext after DECRYPTION", pt.duplicate().position(pos)); @@ -1182,7 +1182,7 @@ enum SSLCipher { int len = addPadding(bb, blockSize); bb.position(pos); - if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) { + if (SSLLogger.isOn() && SSLLogger.isOn("plaintext")) { SSLLogger.fine( "Padded plaintext before ENCRYPTION", bb.duplicate()); @@ -1326,7 +1326,7 @@ enum SSLCipher { "JCE provider " + cipher.getProvider().getName(), sbe); } - if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) { + if (SSLLogger.isOn() && SSLLogger.isOn("plaintext")) { SSLLogger.fine("Padded plaintext after DECRYPTION", pt.duplicate().position(pos)); } @@ -1478,7 +1478,7 @@ enum SSLCipher { int len = addPadding(bb, blockSize); bb.position(pos); - if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) { + if (SSLLogger.isOn() && SSLLogger.isOn("plaintext")) { SSLLogger.fine( "Padded plaintext before ENCRYPTION", bb.duplicate()); @@ -1650,7 +1650,7 @@ enum SSLCipher { pt.position(pos); pt.limit(pos + len); - if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) { + if (SSLLogger.isOn() && SSLLogger.isOn("plaintext")) { SSLLogger.fine( "Plaintext after DECRYPTION", pt.duplicate()); } @@ -1737,7 +1737,7 @@ enum SSLCipher { // DON'T encrypt the nonce for AEAD mode. int len, pos = bb.position(); - if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) { + if (SSLLogger.isOn() && SSLLogger.isOn("plaintext")) { SSLLogger.fine( "Plaintext before ENCRYPTION", bb.duplicate()); @@ -1823,7 +1823,7 @@ enum SSLCipher { keyLimitCountdown = cipherLimits.getOrDefault( algorithm.toUpperCase(Locale.ENGLISH) + ":" + tag[0], 0L); - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("KeyLimit read side: algorithm = " + algorithm + ":" + tag[0] + "\ncountdown value = " + keyLimitCountdown); @@ -1932,7 +1932,7 @@ enum SSLCipher { contentType = pt.get(i); pt.limit(i); - if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) { + if (SSLLogger.isOn() && SSLLogger.isOn("plaintext")) { SSLLogger.fine( "Plaintext after DECRYPTION", pt.duplicate()); } @@ -1984,7 +1984,7 @@ enum SSLCipher { keyLimitCountdown = cipherLimits.getOrDefault( algorithm.toUpperCase(Locale.ENGLISH) + ":" + tag[0], 0L); - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("KeyLimit write side: algorithm = " + algorithm + ":" + tag[0] + "\ncountdown value = " + keyLimitCountdown); @@ -2026,7 +2026,7 @@ enum SSLCipher { cipher.updateAAD(aad); int len, pos = bb.position(); - if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) { + if (SSLLogger.isOn() && SSLLogger.isOn("plaintext")) { SSLLogger.fine( "Plaintext before ENCRYPTION", bb.duplicate()); @@ -2182,7 +2182,7 @@ enum SSLCipher { pt.position(pos); pt.limit(pos + len); - if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) { + if (SSLLogger.isOn() && SSLLogger.isOn("plaintext")) { SSLLogger.fine( "Plaintext after DECRYPTION", pt.duplicate()); } @@ -2231,7 +2231,7 @@ enum SSLCipher { keyLimitCountdown = cipherLimits.getOrDefault( algorithm.toUpperCase(Locale.ENGLISH) + ":" + tag[0], 0L); - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("algorithm = " + algorithm + ":" + tag[0] + "\ncountdown value = " + keyLimitCountdown); @@ -2273,7 +2273,7 @@ enum SSLCipher { // DON'T encrypt the nonce for AEAD mode. int pos = bb.position(); - if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) { + if (SSLLogger.isOn() && SSLLogger.isOn("plaintext")) { SSLLogger.fine( "Plaintext before ENCRYPTION", bb.duplicate()); @@ -2450,7 +2450,7 @@ enum SSLCipher { contentType = pt.get(i); pt.limit(i); - if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) { + if (SSLLogger.isOn() && SSLLogger.isOn("plaintext")) { SSLLogger.fine( "Plaintext after DECRYPTION", pt.duplicate()); } @@ -2499,7 +2499,7 @@ enum SSLCipher { keyLimitCountdown = cipherLimits.getOrDefault( algorithm.toUpperCase(Locale.ENGLISH) + ":" + tag[0], 0L); - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("algorithm = " + algorithm + ":" + tag[0] + "\ncountdown value = " + keyLimitCountdown); @@ -2541,7 +2541,7 @@ enum SSLCipher { cipher.updateAAD(aad); int pos = bb.position(); - if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) { + if (SSLLogger.isOn() && SSLLogger.isOn("plaintext")) { SSLLogger.fine( "Plaintext before ENCRYPTION", bb.duplicate()); diff --git a/src/java.base/share/classes/sun/security/ssl/SSLConfiguration.java b/src/java.base/share/classes/sun/security/ssl/SSLConfiguration.java index 6d1834ad2b7..ace60e41af9 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLConfiguration.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLConfiguration.java @@ -204,7 +204,7 @@ final class SSLConfiguration implements Cloneable { if (nstServerCount == null || nstServerCount < 0 || nstServerCount > 10) { serverNewSessionTicketCount = SERVER_NST_DEFAULT; - if (nstServerCount != null && SSLLogger.isOn && + if (nstServerCount != null && SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "jdk.tls.server.newSessionTicketCount defaults to " + @@ -213,7 +213,7 @@ final class SSLConfiguration implements Cloneable { } } else { serverNewSessionTicketCount = nstServerCount; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "jdk.tls.server.newSessionTicketCount set to " + serverNewSessionTicketCount); @@ -586,7 +586,7 @@ final class SSLConfiguration implements Cloneable { String property = System.getProperty(propertyName); // this method is called from class initializer; logging here // will occasionally pin threads and deadlock if called from a virtual thread - if (SSLLogger.isOn && SSLLogger.isOn("ssl,sslctx") + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,sslctx") && !Thread.currentThread().isVirtual()) { SSLLogger.fine( "System property " + propertyName + " is set to '" + @@ -615,7 +615,7 @@ final class SSLConfiguration implements Cloneable { if (scheme != null && scheme.isAvailable) { signatureSchemes.add(schemeName); } else { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,sslctx") + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,sslctx") && !Thread.currentThread().isVirtual()) { SSLLogger.fine( "The current installed providers do not " + diff --git a/src/java.base/share/classes/sun/security/ssl/SSLContextImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLContextImpl.java index d50a9a10b76..be324eb0949 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLContextImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLContextImpl.java @@ -104,11 +104,11 @@ public abstract class SSLContextImpl extends SSLContextSpi { * first connection to time out and fail. Make sure it is * primed and ready by getting some initial output from it. */ - if (SSLLogger.isOn && SSLLogger.isOn("ssl,sslctx")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,sslctx")) { SSLLogger.finest("trigger seeding of SecureRandom"); } secureRandom.nextInt(); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,sslctx")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,sslctx")) { SSLLogger.finest("done seeding of SecureRandom"); } @@ -143,7 +143,7 @@ public abstract class SSLContextImpl extends SSLContextSpi { return (X509ExtendedKeyManager)km; } - if (SSLLogger.isOn && SSLLogger.isOn("ssl,sslctx")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,sslctx")) { SSLLogger.warning( "X509KeyManager passed to SSLContext.init(): need an " + "X509ExtendedKeyManager for SSLEngine use"); @@ -246,7 +246,7 @@ public abstract class SSLContextImpl extends SSLContextSpi { contextLock.lock(); try { if (statusResponseManager == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,sslctx")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,sslctx")) { SSLLogger.finest( "Initializing StatusResponseManager"); } @@ -383,7 +383,7 @@ public abstract class SSLContextImpl extends SSLContextSpi { suite.name, null)) { suites.add(suite); isSupported = true; - } else if (SSLLogger.isOn && + } else if (SSLLogger.isOn() && SSLLogger.isOn("ssl,sslctx,verbose")) { SSLLogger.fine( "Ignore disabled cipher suite: " + suite.name); @@ -392,7 +392,7 @@ public abstract class SSLContextImpl extends SSLContextSpi { break; } - if (!isSupported && SSLLogger.isOn && + if (!isSupported && SSLLogger.isOn() && SSLLogger.isOn("ssl,sslctx,verbose")) { SSLLogger.finest( "Ignore unsupported cipher suite: " + suite); @@ -410,7 +410,7 @@ public abstract class SSLContextImpl extends SSLContextSpi { String propertyName) { String property = System.getProperty(propertyName); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,sslctx")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,sslctx")) { SSLLogger.fine( "System property " + propertyName + " is set to '" + property + "'"); @@ -437,7 +437,7 @@ public abstract class SSLContextImpl extends SSLContextSpi { try { suite = CipherSuite.nameOf(cipherSuiteNames[i]); } catch (IllegalArgumentException iae) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,sslctx")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,sslctx")) { SSLLogger.fine( "Unknown or unsupported cipher suite name: " + cipherSuiteNames[i]); @@ -449,7 +449,7 @@ public abstract class SSLContextImpl extends SSLContextSpi { if (suite != null && suite.isAvailable()) { cipherSuites.add(suite); } else { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,sslctx")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,sslctx")) { SSLLogger.fine( "The current installed providers do not " + "support cipher suite: " + cipherSuiteNames[i]); @@ -907,7 +907,7 @@ public abstract class SSLContextImpl extends SSLContextSpi { tmMediator = getTrustManagers(); } catch (Exception e) { reserved = e; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,defaultctx")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,defaultctx")) { SSLLogger.warning( "Failed to load default trust managers", e); } @@ -919,7 +919,7 @@ public abstract class SSLContextImpl extends SSLContextSpi { kmMediator = getKeyManagers(); } catch (Exception e) { reserved = e; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,defaultctx")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,defaultctx")) { SSLLogger.warning( "Failed to load default key managers", e); } @@ -977,7 +977,7 @@ public abstract class SSLContextImpl extends SSLContextSpi { String defaultKeyStore = props.get("keyStore"); String defaultKeyStoreType = props.get("keyStoreType"); String defaultKeyStoreProvider = props.get("keyStoreProvider"); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,defaultctx")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,defaultctx")) { SSLLogger.fine("keyStore is : " + defaultKeyStore); SSLLogger.fine("keyStore type is : " + defaultKeyStoreType); @@ -1007,7 +1007,7 @@ public abstract class SSLContextImpl extends SSLContextSpi { // Try to initialize key store. if ((defaultKeyStoreType.length()) != 0) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,defaultctx")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,defaultctx")) { SSLLogger.finest("init keystore"); } if (defaultKeyStoreProvider.isEmpty()) { @@ -1030,7 +1030,7 @@ public abstract class SSLContextImpl extends SSLContextSpi { /* * Try to initialize key manager. */ - if (SSLLogger.isOn && SSLLogger.isOn("ssl,defaultctx")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,defaultctx")) { SSLLogger.fine("init keymanager of type " + KeyManagerFactory.getDefaultAlgorithm()); } @@ -1068,7 +1068,7 @@ public abstract class SSLContextImpl extends SSLContextSpi { // exception object, which may be not garbage collection // friendly as 'reservedException' is a static filed. reserved = new KeyManagementException(e.getMessage()); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,defaultctx")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,defaultctx")) { SSLLogger.warning( "Failed to load default SSLContext", e); } @@ -1097,7 +1097,7 @@ public abstract class SSLContextImpl extends SSLContextSpi { super.engineInit(DefaultManagersHolder.keyManagers, DefaultManagersHolder.trustManagers, null); } catch (Exception e) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,defaultctx")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,defaultctx")) { SSLLogger.fine("default context init failed: ", e); } throw e; diff --git a/src/java.base/share/classes/sun/security/ssl/SSLEngineImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLEngineImpl.java index 4b19f5a9d7b..5e23e6ee37b 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLEngineImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLEngineImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2025, 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 @@ -330,7 +330,7 @@ final class SSLEngineImpl extends SSLEngine implements SSLTransport { // application data may be discarded accordingly. As could // be an issue for some applications. This impact can be // mitigated by sending the last flight twice. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,verbose")) { SSLLogger.finest("retransmit the last flight messages"); } @@ -397,7 +397,7 @@ final class SSLEngineImpl extends SSLEngine implements SSLTransport { if ((conContext.handshakeContext == null) && !conContext.isOutboundClosed() && !conContext.isBroken) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest("trigger key update"); } beginHandshake(); @@ -419,7 +419,7 @@ final class SSLEngineImpl extends SSLEngine implements SSLTransport { !conContext.isOutboundClosed() && !conContext.isInboundClosed() && !conContext.isBroken) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest("trigger NST"); } conContext.conSession.updateNST = false; @@ -612,7 +612,7 @@ final class SSLEngineImpl extends SSLEngine implements SSLTransport { } catch (SSLException ssle) { // Need to discard invalid records for DTLS protocols. if (sslContext.isDTLS()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,verbose")) { SSLLogger.finest("Discard invalid DTLS records", ssle); } @@ -780,7 +780,7 @@ final class SSLEngineImpl extends SSLEngine implements SSLTransport { return; } - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest("Closing inbound of SSLEngine"); } @@ -819,7 +819,7 @@ final class SSLEngineImpl extends SSLEngine implements SSLTransport { return; } - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest("Closing outbound of SSLEngine"); } diff --git a/src/java.base/share/classes/sun/security/ssl/SSLEngineInputRecord.java b/src/java.base/share/classes/sun/security/ssl/SSLEngineInputRecord.java index 1bfbd9f51bf..6e08fc71664 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLEngineInputRecord.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLEngineInputRecord.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2025, 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 @@ -172,7 +172,7 @@ final class SSLEngineInputRecord extends InputRecord implements SSLRecord { return null; } - if (SSLLogger.isOn && SSLLogger.isOn("packet")) { + if (SSLLogger.isOn() && SSLLogger.isOn("packet")) { SSLLogger.fine("Raw read", packet); } @@ -209,7 +209,7 @@ final class SSLEngineInputRecord extends InputRecord implements SSLRecord { byte minorVersion = packet.get(); // pos: 2 int contentLen = Record.getInt16(packet); // pos: 3, 4 - if (SSLLogger.isOn && SSLLogger.isOn("record")) { + if (SSLLogger.isOn() && SSLLogger.isOn("record")) { SSLLogger.fine( "READ: " + ProtocolVersion.nameOf(majorVersion, minorVersion) + @@ -388,7 +388,7 @@ final class SSLEngineInputRecord extends InputRecord implements SSLRecord { * error message, one that's treated as fatal by * clients (Otherwise we'll hang.) */ - if (SSLLogger.isOn && SSLLogger.isOn("record")) { + if (SSLLogger.isOn() && SSLLogger.isOn("record")) { SSLLogger.fine( "Requested to negotiate unsupported SSLv2!"); } @@ -410,7 +410,7 @@ final class SSLEngineInputRecord extends InputRecord implements SSLRecord { ByteBuffer converted = convertToClientHello(packet); - if (SSLLogger.isOn && SSLLogger.isOn("packet")) { + if (SSLLogger.isOn() && SSLLogger.isOn("packet")) { SSLLogger.fine( "[Converted] ClientHello", converted); } diff --git a/src/java.base/share/classes/sun/security/ssl/SSLEngineOutputRecord.java b/src/java.base/share/classes/sun/security/ssl/SSLEngineOutputRecord.java index 4a689a84d5f..1c8751e66fe 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLEngineOutputRecord.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLEngineOutputRecord.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2025, 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 @@ -73,7 +73,7 @@ final class SSLEngineOutputRecord extends OutputRecord implements SSLRecord { @Override void encodeAlert(byte level, byte description) { if (isClosed()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("outbound has closed, ignore outbound " + "alert message: " + Alert.nameOf(description)); } @@ -91,7 +91,7 @@ final class SSLEngineOutputRecord extends OutputRecord implements SSLRecord { void encodeHandshake(byte[] source, int offset, int length) { if (isClosed()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("outbound has closed, ignore outbound " + "handshake message", ByteBuffer.wrap(source, offset, length)); @@ -138,7 +138,7 @@ final class SSLEngineOutputRecord extends OutputRecord implements SSLRecord { @Override void encodeChangeCipherSpec() { if (isClosed()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("outbound has closed, ignore outbound " + "change_cipher_spec message"); } @@ -171,14 +171,14 @@ final class SSLEngineOutputRecord extends OutputRecord implements SSLRecord { ByteBuffer[] dsts, int dstsOffset, int dstsLength) throws IOException { if (isClosed) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("outbound has closed, ignore outbound " + "application data or cached messages"); } return null; } else if (isCloseWaiting) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("outbound has closed, ignore outbound " + "application data"); } @@ -193,7 +193,7 @@ final class SSLEngineOutputRecord extends OutputRecord implements SSLRecord { ByteBuffer destination) throws IOException { if (writeCipher.authenticator.seqNumOverflow()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine( "sequence number extremely close to overflow " + "(2^64-1 packets). Closing connection."); @@ -275,7 +275,7 @@ final class SSLEngineOutputRecord extends OutputRecord implements SSLRecord { destination.limit(destination.position()); destination.position(dstContent); - if (SSLLogger.isOn && SSLLogger.isOn("record")) { + if (SSLLogger.isOn() && SSLLogger.isOn("record")) { SSLLogger.fine( "WRITE: " + protocolVersion.name + " " + ContentType.APPLICATION_DATA.name + @@ -288,7 +288,7 @@ final class SSLEngineOutputRecord extends OutputRecord implements SSLRecord { dstPos, dstLim, headerSize, protocolVersion); - if (SSLLogger.isOn && SSLLogger.isOn("packet")) { + if (SSLLogger.isOn() && SSLLogger.isOn("packet")) { ByteBuffer temporary = destination.duplicate(); temporary.limit(temporary.position()); temporary.position(dstPos); @@ -317,7 +317,7 @@ final class SSLEngineOutputRecord extends OutputRecord implements SSLRecord { // // Please don't change the limit of the destination buffer. destination.put(SSLRecord.v2NoCipher); - if (SSLLogger.isOn && SSLLogger.isOn("packet")) { + if (SSLLogger.isOn() && SSLLogger.isOn("packet")) { SSLLogger.fine("Raw write", SSLRecord.v2NoCipher); } @@ -331,7 +331,7 @@ final class SSLEngineOutputRecord extends OutputRecord implements SSLRecord { // deliver the SSLv2 format ClientHello message // // Please don't change the limit of the destination buffer. - if (SSLLogger.isOn) { + if (SSLLogger.isOn()) { if (SSLLogger.isOn("record")) { SSLLogger.fine(Thread.currentThread().getName() + ", WRITE: SSLv2 ClientHello message" + @@ -525,7 +525,7 @@ final class SSLEngineOutputRecord extends OutputRecord implements SSLRecord { dstBuf.limit(dstBuf.position()); dstBuf.position(dstContent); - if (SSLLogger.isOn && SSLLogger.isOn("record")) { + if (SSLLogger.isOn() && SSLLogger.isOn("record")) { SSLLogger.fine( "WRITE: " + protocolVersion.name + " " + ContentType.nameOf(memo.contentType) + @@ -543,7 +543,7 @@ final class SSLEngineOutputRecord extends OutputRecord implements SSLRecord { memo.encodeCipher.dispose(); } - if (SSLLogger.isOn && SSLLogger.isOn("packet")) { + if (SSLLogger.isOn() && SSLLogger.isOn("packet")) { ByteBuffer temporary = dstBuf.duplicate(); temporary.limit(temporary.position()); temporary.position(dstPos); diff --git a/src/java.base/share/classes/sun/security/ssl/SSLExtension.java b/src/java.base/share/classes/sun/security/ssl/SSLExtension.java index fb0490d70f1..47a0d0b0e44 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLExtension.java @@ -844,7 +844,7 @@ enum SSLExtension implements SSLStringizer { String property = System.getProperty(propertyName); // this method is called from class initializer; logging here // will occasionally pin threads and deadlock if called from a virtual thread - if (SSLLogger.isOn && SSLLogger.isOn("ssl,sslctx") + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,sslctx") && !Thread.currentThread().isVirtual()) { SSLLogger.fine( "System property " + propertyName + " is set to '" + diff --git a/src/java.base/share/classes/sun/security/ssl/SSLExtensions.java b/src/java.base/share/classes/sun/security/ssl/SSLExtensions.java index 5ad93cfc836..66f6293302e 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLExtensions.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLExtensions.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -43,7 +43,7 @@ final class SSLExtensions { // Extension map for debug logging private final Map logMap = - SSLLogger.isOn ? new LinkedHashMap<>() : null; + SSLLogger.isOn() ? new LinkedHashMap<>() : null; SSLExtensions(HandshakeMessage handshakeMessage) { this.handshakeMessage = handshakeMessage; @@ -93,7 +93,7 @@ final class SSLExtensions { // However, the implementation of the limit is complicated // and inefficient, and may not worthy the maintenance. isSupported = false; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "Received buggy supported_groups extension " + "in the ServerHello handshake message"); @@ -143,7 +143,7 @@ final class SSLExtensions { m.get(extData); logMap.put(extId, extData); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unknown or unsupported extension", toString(extId, extData)); @@ -171,7 +171,7 @@ final class SSLExtensions { for (SSLExtension extension : extensions) { if (context.negotiatedProtocol != null && !extension.isAvailable(context.negotiatedProtocol)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unsupported extension: " + extension.name); } @@ -181,7 +181,7 @@ final class SSLExtensions { if (!extMap.containsKey(extension)) { if (extension.onLoadAbsence != null) { extension.absentOnLoad(context, handshakeMessage); - } else if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + } else if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable extension: " + extension.name); } @@ -190,7 +190,7 @@ final class SSLExtensions { if (extension.onLoadConsumer == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "Ignore unsupported extension: " + extension.name); } @@ -200,7 +200,7 @@ final class SSLExtensions { ByteBuffer m = ByteBuffer.wrap(extMap.get(extension)); extension.consumeOnLoad(context, handshakeMessage, m); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Consumed extension: " + extension.name); } } @@ -215,7 +215,7 @@ final class SSLExtensions { if (!extMap.containsKey(extension)) { if (extension.onTradeAbsence != null) { extension.absentOnTrade(context, handshakeMessage); - } else if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + } else if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable extension: " + extension.name); } @@ -223,7 +223,7 @@ final class SSLExtensions { } if (extension.onTradeConsumer == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "Ignore impact of unsupported extension: " + extension.name); @@ -232,7 +232,7 @@ final class SSLExtensions { } extension.consumeOnTrade(context, handshakeMessage); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Populated with extension: " + extension.name); } } @@ -245,7 +245,7 @@ final class SSLExtensions { SSLExtension[] extensions) throws IOException { for (SSLExtension extension : extensions) { if (extMap.containsKey(extension)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore, duplicated extension: " + extension.name); @@ -254,7 +254,7 @@ final class SSLExtensions { } if (extension.networkProducer == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "Ignore, no extension producer defined: " + extension.name); @@ -267,7 +267,7 @@ final class SSLExtensions { extMap.put(extension, encoded); encodedLength += encoded.length + 4; // extension_type (2) // extension_data length(2) - } else if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + } else if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { // The extension is not available in the context. SSLLogger.fine( "Ignore, context unavailable extension: " + @@ -284,7 +284,7 @@ final class SSLExtensions { SSLExtension[] extensions) throws IOException { for (SSLExtension extension : extensions) { if (extension.networkProducer == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "Ignore, no extension producer defined: " + extension.name); @@ -305,7 +305,7 @@ final class SSLExtensions { encodedLength += encoded.length + 4; // extension_type (2) // extension_data length(2) - } else if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + } else if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { // The extension is not available in the context. SSLLogger.fine( "Ignore, context unavailable extension: " + diff --git a/src/java.base/share/classes/sun/security/ssl/SSLLogger.java b/src/java.base/share/classes/sun/security/ssl/SSLLogger.java index f55ab27d297..7fa6fbf91b5 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLLogger.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLLogger.java @@ -29,8 +29,6 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintStream; -import java.lang.System.Logger; -import java.lang.System.Logger.Level; import java.nio.ByteBuffer; import java.security.cert.Certificate; import java.security.cert.Extension; @@ -41,6 +39,7 @@ import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.*; +import jdk.internal.vm.annotation.ForceInline; import sun.security.util.HexDumpEncoder; import sun.security.util.Debug; import sun.security.x509.*; @@ -58,10 +57,13 @@ import static sun.security.ssl.Utilities.LINE_SEP; * logging mechanisms. If the system property "javax.net.debug" is defined * and non-empty, a private debug logger implemented in this class is used. */ -public final class SSLLogger { +public final class SSLLogger implements System.Logger { private static final System.Logger logger; private static final String property; - public static final boolean isOn; + private static final boolean isOn; + + private final String loggerName; + private final boolean useCompactFormat; static { @@ -76,7 +78,7 @@ public final class SSLLogger { help(); } - logger = new SSLConsoleLogger("javax.net.ssl", p); + logger = new SSLLogger("javax.net.ssl", p); } isOn = true; } else { @@ -86,6 +88,105 @@ public final class SSLLogger { } } + private SSLLogger(String loggerName, String options) { + this.loggerName = loggerName; + options = options.toLowerCase(Locale.ENGLISH); + this.useCompactFormat = !options.contains("expand"); + } + + /** + * Return true if the "javax.net.debug" property contains the + * debug check points, or System.Logger is used. + */ + public static boolean isOn(String checkPoints) { + if (property == null) { // debugging is turned off + return false; + } else if (property.isEmpty()) { // use System.Logger + return true; + } // use provider logger + + String[] options = checkPoints.split(","); + for (String option : options) { + option = option.trim(); + if (!SSLLogger.hasOption(option)) { + return false; + } + } + + return true; + } + + @ForceInline + public static boolean isOn() { + return isOn; + } + + private static boolean hasOption(String option) { + option = option.toLowerCase(Locale.ENGLISH); + if (property.contains("all")) { + return true; + } else { + // remove first occurrence of "sslctx" since + // it interferes with search for "ssl" + String modified = property.replaceFirst("sslctx", ""); + if (modified.contains("ssl")) { + // don't enable data and plaintext options by default + if (!(option.equals("data") + || option.equals("packet") + || option.equals("plaintext"))) { + return true; + } + } + } + + return property.contains(option); + } + + public static void severe(String msg, Object... params) { + SSLLogger.log0(Level.ERROR, msg, params); + } + + public static void warning(String msg, Object... params) { + SSLLogger.log0(Level.WARNING, msg, params); + } + + public static void info(String msg, Object... params) { + SSLLogger.log0(Level.INFO, msg, params); + } + + public static void fine(String msg, Object... params) { + SSLLogger.log0(Level.DEBUG, msg, params); + } + + public static void finer(String msg, Object... params) { + SSLLogger.log0(Level.TRACE, msg, params); + } + + public static void finest(String msg, Object... params) { + SSLLogger.log0(Level.TRACE, msg, params); + } + + private static void log0(Level level, String msg, Object... params) { + if (logger != null && logger.isLoggable(level)) { + if (params == null || params.length == 0) { + logger.log(level, msg); + } else { + try { + String formatted = + SSLSimpleFormatter.formatParameters(params); + // use the customized log method for SSLLogger + if (logger instanceof SSLLogger) { + logger.log(level, msg, formatted); + } else { + logger.log(level, msg + ":" + LINE_SEP + formatted); + } + } catch (Exception exp) { + // ignore it, just for debugging. + } + } + } + } + private static void help() { System.err.println(); System.err.println("help print the help messages"); @@ -117,94 +218,6 @@ public final class SSLLogger { System.exit(0); } - /** - * Return true if the "javax.net.debug" property contains the - * debug check points, or System.Logger is used. - */ - public static boolean isOn(String checkPoints) { - if (property == null) { // debugging is turned off - return false; - } else if (property.isEmpty()) { // use System.Logger - return true; - } // use provider logger - - String[] options = checkPoints.split(","); - for (String option : options) { - option = option.trim(); - if (!SSLLogger.hasOption(option)) { - return false; - } - } - - return true; - } - - private static boolean hasOption(String option) { - option = option.toLowerCase(Locale.ENGLISH); - if (property.contains("all")) { - return true; - } else { - // remove first occurrence of "sslctx" since - // it interferes with search for "ssl" - String modified = property.replaceFirst("sslctx", ""); - if (modified.contains("ssl")) { - // don't enable data and plaintext options by default - if (!(option.equals("data") - || option.equals("packet") - || option.equals("plaintext"))) { - return true; - } - } - } - - return property.contains(option); - } - - public static void severe(String msg, Object... params) { - SSLLogger.log(Level.ERROR, msg, params); - } - - public static void warning(String msg, Object... params) { - SSLLogger.log(Level.WARNING, msg, params); - } - - public static void info(String msg, Object... params) { - SSLLogger.log(Level.INFO, msg, params); - } - - public static void fine(String msg, Object... params) { - SSLLogger.log(Level.DEBUG, msg, params); - } - - public static void finer(String msg, Object... params) { - SSLLogger.log(Level.TRACE, msg, params); - } - - public static void finest(String msg, Object... params) { - SSLLogger.log(Level.TRACE, msg, params); - } - - private static void log(Level level, String msg, Object... params) { - if (logger != null && logger.isLoggable(level)) { - if (params == null || params.length == 0) { - logger.log(level, msg); - } else { - try { - String formatted = - SSLSimpleFormatter.formatParameters(params); - // use the customized log method for SSLConsoleLogger - if (logger instanceof SSLConsoleLogger) { - logger.log(level, msg, formatted); - } else { - logger.log(level, msg + ":" + LINE_SEP + formatted); - } - } catch (Exception exp) { - // ignore it, just for debugging. - } - } - } - } - static String toString(Object... params) { try { return SSLSimpleFormatter.formatParameters(params); @@ -216,65 +229,55 @@ public final class SSLLogger { // Logs a warning message and always returns false. This method // can be used as an OR Predicate to add a log in a stream filter. public static boolean logWarning(String option, String s) { - if (SSLLogger.isOn && SSLLogger.isOn(option)) { + if (SSLLogger.isOn() && SSLLogger.isOn(option)) { SSLLogger.warning(s); } return false; } - private static class SSLConsoleLogger implements Logger { - private final String loggerName; - private final boolean useCompactFormat; + @Override + public String getName() { + return loggerName; + } - SSLConsoleLogger(String loggerName, String options) { - this.loggerName = loggerName; - options = options.toLowerCase(Locale.ENGLISH); - this.useCompactFormat = !options.contains("expand"); - } + @Override + public boolean isLoggable(Level level) { + return level != Level.OFF; + } - @Override - public String getName() { - return loggerName; - } - - @Override - public boolean isLoggable(Level level) { - return level != Level.OFF; - } - - @Override - public void log(Level level, - ResourceBundle rb, String message, Throwable thrwbl) { - if (isLoggable(level)) { - try { - String formatted = + @Override + public void log(Level level, + ResourceBundle rb, String message, Throwable thrwbl) { + if (isLoggable(level)) { + try { + String formatted = SSLSimpleFormatter.format(this, level, message, thrwbl); - System.err.write(formatted.getBytes(UTF_8)); - } catch (Exception exp) { - // ignore it, just for debugging. - } + System.err.write(formatted.getBytes(UTF_8)); + } catch (Exception exp) { + // ignore it, just for debugging. } } + } - @Override - public void log(Level level, - ResourceBundle rb, String message, Object... params) { - if (isLoggable(level)) { - try { - String formatted = + @Override + public void log(Level level, + ResourceBundle rb, String message, Object... params) { + if (isLoggable(level)) { + try { + String formatted = SSLSimpleFormatter.format(this, level, message, params); - System.err.write(formatted.getBytes(UTF_8)); - } catch (Exception exp) { - // ignore it, just for debugging. - } + System.err.write(formatted.getBytes(UTF_8)); + } catch (Exception exp) { + // ignore it, just for debugging. } } } private static class SSLSimpleFormatter { private static final String PATTERN = "yyyy-MM-dd kk:mm:ss.SSS z"; - private static final DateTimeFormatter dateTimeFormat = DateTimeFormatter.ofPattern(PATTERN, Locale.ENGLISH) - .withZone(ZoneId.systemDefault()); + private static final DateTimeFormatter dateTimeFormat = + DateTimeFormatter.ofPattern(PATTERN, Locale.ENGLISH) + .withZone(ZoneId.systemDefault()); private static final MessageFormat basicCertFormat = new MessageFormat( """ @@ -290,68 +293,68 @@ public final class SSLLogger { Locale.ENGLISH); private static final MessageFormat extendedCertFormat = - new MessageFormat( - """ - "version" : "v{0}", - "serial number" : "{1}", - "signature algorithm": "{2}", - "issuer" : "{3}", - "not before" : "{4}", - "not after" : "{5}", - "subject" : "{6}", - "subject public key" : "{7}", - "extensions" : [ - {8} - ] - """, - Locale.ENGLISH); + new MessageFormat( + """ + "version" : "v{0}", + "serial number" : "{1}", + "signature algorithm": "{2}", + "issuer" : "{3}", + "not before" : "{4}", + "not after" : "{5}", + "subject" : "{6}", + "subject public key" : "{7}", + "extensions" : [ + {8} + ] + """, + Locale.ENGLISH); private static final MessageFormat messageFormatNoParas = - new MessageFormat( - """ - '{' - "logger" : "{0}", - "level" : "{1}", - "thread id" : "{2}", - "thread name" : "{3}", - "time" : "{4}", - "caller" : "{5}", - "message" : "{6}" - '}' - """, - Locale.ENGLISH); + new MessageFormat( + """ + '{' + "logger" : "{0}", + "level" : "{1}", + "thread id" : "{2}", + "thread name" : "{3}", + "time" : "{4}", + "caller" : "{5}", + "message" : "{6}" + '}' + """, + Locale.ENGLISH); private static final MessageFormat messageCompactFormatNoParas = - new MessageFormat( - "{0}|{1}|{2}|{3}|{4}|{5}|{6}" + LINE_SEP, - Locale.ENGLISH); + new MessageFormat( + "{0}|{1}|{2}|{3}|{4}|{5}|{6}" + LINE_SEP, + Locale.ENGLISH); private static final MessageFormat messageFormatWithParas = - new MessageFormat( - """ - '{' - "logger" : "{0}", - "level" : "{1}", - "thread id" : "{2}", - "thread name" : "{3}", - "time" : "{4}", - "caller" : "{5}", - "message" : "{6}", - "specifics" : [ - {7} - ] - '}' - """, - Locale.ENGLISH); + new MessageFormat( + """ + '{' + "logger" : "{0}", + "level" : "{1}", + "thread id" : "{2}", + "thread name" : "{3}", + "time" : "{4}", + "caller" : "{5}", + "message" : "{6}", + "specifics" : [ + {7} + ] + '}' + """, + Locale.ENGLISH); private static final MessageFormat messageCompactFormatWithParas = - new MessageFormat( - """ - {0}|{1}|{2}|{3}|{4}|{5}|{6} ( - {7} - ) - """, - Locale.ENGLISH); + new MessageFormat( + """ + {0}|{1}|{2}|{3}|{4}|{5}|{6} ( + {7} + ) + """, + Locale.ENGLISH); private static final MessageFormat keyObjectFormat = new MessageFormat( """ @@ -364,8 +367,8 @@ public final class SSLLogger { // log message // log message // ... - private static String format(SSLConsoleLogger logger, Level level, - String message, Object ... parameters) { + private static String format(SSLLogger logger, Level level, + String message, Object... parameters) { if (parameters == null || parameters.length == 0) { Object[] messageFields = { @@ -394,9 +397,9 @@ public final class SSLLogger { formatCaller(), message, (logger.useCompactFormat ? - formatParameters(parameters) : - Utilities.indent(formatParameters(parameters))) - }; + formatParameters(parameters) : + Utilities.indent(formatParameters(parameters))) + }; if (logger.useCompactFormat) { return messageCompactFormatWithParas.format(messageFields); @@ -414,7 +417,7 @@ public final class SSLLogger { .findFirst().orElse("unknown caller")); } - private static String formatParameters(Object ... parameters) { + private static String formatParameters(Object... parameters) { StringBuilder builder = new StringBuilder(512); boolean isFirst = true; for (Object parameter : parameters) { @@ -425,21 +428,21 @@ public final class SSLLogger { } if (parameter instanceof Throwable) { - builder.append(formatThrowable((Throwable)parameter)); + builder.append(formatThrowable((Throwable) parameter)); } else if (parameter instanceof Certificate) { - builder.append(formatCertificate((Certificate)parameter)); + builder.append(formatCertificate((Certificate) parameter)); } else if (parameter instanceof ByteArrayInputStream) { builder.append(formatByteArrayInputStream( - (ByteArrayInputStream)parameter)); + (ByteArrayInputStream) parameter)); } else if (parameter instanceof ByteBuffer) { - builder.append(formatByteBuffer((ByteBuffer)parameter)); + builder.append(formatByteBuffer((ByteBuffer) parameter)); } else if (parameter instanceof byte[]) { builder.append(formatByteArrayInputStream( - new ByteArrayInputStream((byte[])parameter))); + new ByteArrayInputStream((byte[]) parameter))); } else if (parameter instanceof Map.Entry) { @SuppressWarnings("unchecked") Map.Entry mapParameter = - (Map.Entry)parameter; + (Map.Entry) parameter; builder.append(formatMapEntry(mapParameter)); } else { builder.append(formatObject(parameter)); @@ -462,7 +465,7 @@ public final class SSLLogger { Object[] fields = { "throwable", builder.toString() - }; + }; return keyObjectFormat.format(fields); } @@ -479,7 +482,7 @@ public final class SSLLogger { StringBuilder builder = new StringBuilder(512); try { X509CertImpl x509 = - X509CertImpl.toImpl((X509Certificate)certificate); + X509CertImpl.toImpl((X509Certificate) certificate); X509CertInfo certInfo = x509.getInfo(); CertificateExtensions certExts = certInfo.getExtensions(); if (certExts == null) { @@ -528,7 +531,7 @@ public final class SSLLogger { Object[] fields = { "certificate", builder.toString() - }; + }; return Utilities.indent(keyObjectFormat.format(fields)); } @@ -591,13 +594,13 @@ public final class SSLLogger { formatted = builder.toString(); } else if (value instanceof byte[]) { formatted = "\"" + key + "\": \"" + - Utilities.toHexString((byte[])value) + "\""; + Utilities.toHexString((byte[]) value) + "\""; } else if (value instanceof Byte) { formatted = "\"" + key + "\": \"" + - HexFormat.of().toHexDigits((byte)value) + "\""; + HexFormat.of().toHexDigits((byte) value) + "\""; } else { formatted = "\"" + key + "\": " + - "\"" + value.toString() + "\""; + "\"" + value.toString() + "\""; } return Utilities.indent(formatted); diff --git a/src/java.base/share/classes/sun/security/ssl/SSLMasterKeyDerivation.java b/src/java.base/share/classes/sun/security/ssl/SSLMasterKeyDerivation.java index db5887c5e8e..4de29b7570a 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLMasterKeyDerivation.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLMasterKeyDerivation.java @@ -29,7 +29,6 @@ import java.io.IOException; import java.security.InvalidAlgorithmParameterException; import java.security.NoSuchAlgorithmException; import java.security.ProviderException; -import java.security.spec.AlgorithmParameterSpec; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import sun.security.internal.spec.TlsMasterSecretParameterSpec; @@ -152,7 +151,7 @@ enum SSLMasterKeyDerivation implements SSLKeyDerivationGenerator { // // For RSA premaster secrets, do not signal a protocol error // due to the Bleichenbacher attack. See comments further down. - if (SSLLogger.isOn && SSLLogger.isOn("handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("handshake")) { SSLLogger.fine("RSA master secret generation error.", iae); } throw new ProviderException(iae); diff --git a/src/java.base/share/classes/sun/security/ssl/SSLSessionContextImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLSessionContextImpl.java index 4baa3304fee..f713f723ea0 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLSessionContextImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLSessionContextImpl.java @@ -339,7 +339,7 @@ final class SSLSessionContextImpl implements SSLSessionContext { if (t < 0 || t > NewSessionTicket.MAX_TICKET_LIFETIME) { timeout = DEFAULT_SESSION_TIMEOUT; - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("Invalid timeout given " + "jdk.tls.server.sessionTicketTimeout: " + t + ". Set to default value " + timeout); @@ -349,7 +349,7 @@ final class SSLSessionContextImpl implements SSLSessionContext { } } catch (NumberFormatException e) { setSessionTimeout(DEFAULT_SESSION_TIMEOUT); - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("Invalid timeout for " + "jdk.tls.server.sessionTicketTimeout: " + s + ". Set to default value " + timeout); @@ -363,7 +363,7 @@ final class SSLSessionContextImpl implements SSLSessionContext { if (defaultCacheLimit >= 0) { return defaultCacheLimit; - } else if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + } else if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning( "invalid System Property javax.net.ssl.sessionCacheSize, " + "use the default session cache size (" + @@ -371,7 +371,7 @@ final class SSLSessionContextImpl implements SSLSessionContext { } } catch (Exception e) { // unlikely, log it for safe - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning( "the System Property javax.net.ssl.sessionCacheSize is " + "not available, use the default value (" + diff --git a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java index 1bf561c47e6..f3a4b964158 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java @@ -223,7 +223,7 @@ final class SSLSessionImpl extends ExtendedSSLSession { this.identificationProtocol = hc.sslConfig.identificationProtocol; this.boundValues = new ConcurrentHashMap<>(); - if (SSLLogger.isOn && SSLLogger.isOn("session")) { + if (SSLLogger.isOn() && SSLLogger.isOn("session")) { SSLLogger.finest("Session initialized: " + this); } } @@ -256,7 +256,7 @@ final class SSLSessionImpl extends ExtendedSSLSession { this.maximumPacketSize = baseSession.maximumPacketSize; this.boundValues = baseSession.boundValues; - if (SSLLogger.isOn && SSLLogger.isOn("session")) { + if (SSLLogger.isOn() && SSLLogger.isOn("session")) { SSLLogger.finest("Session initialized: " + this); } } @@ -455,7 +455,7 @@ final class SSLSessionImpl extends ExtendedSSLSession { if (same) { this.localCerts = ((X509Possession) pos).popCerts; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,session")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,session")) { SSLLogger.fine("Restored " + len + " local certificates from session ticket" + " for algorithms " + Arrays.toString(certAlgs)); @@ -463,7 +463,7 @@ final class SSLSessionImpl extends ExtendedSSLSession { } else { this.localCerts = null; this.invalidated = true; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,session")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,session")) { SSLLogger.warning("Local certificates can not be restored " + "from session ticket " + "for algorithms " + Arrays.toString(certAlgs)); @@ -482,7 +482,7 @@ final class SSLSessionImpl extends ExtendedSSLSession { // If there is no getMasterSecret with TLS1.2 or under, do not resume. if (!protocolVersion.useTLS13PlusSpec() && getMasterSecret().getEncoded() == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest("No MasterSecret, cannot make stateless" + " ticket"); } @@ -490,7 +490,7 @@ final class SSLSessionImpl extends ExtendedSSLSession { } if (boundValues != null && boundValues.size() > 0) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest("There are boundValues, cannot make" + " stateless ticket"); } @@ -862,7 +862,7 @@ final class SSLSessionImpl extends ExtendedSSLSession { void setSuite(CipherSuite suite) { cipherSuite = suite; - if (SSLLogger.isOn && SSLLogger.isOn("session")) { + if (SSLLogger.isOn() && SSLLogger.isOn("session")) { SSLLogger.finest("Negotiating session: " + this); } } @@ -1132,7 +1132,7 @@ final class SSLSessionImpl extends ExtendedSSLSession { return; } invalidated = true; - if (SSLLogger.isOn && SSLLogger.isOn("session")) { + if (SSLLogger.isOn() && SSLLogger.isOn("session")) { SSLLogger.finest("Invalidated session: " + this); } for (SSLSessionImpl child : childSessions) { diff --git a/src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java index be95a09006f..ab01a9d85be 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -370,7 +370,7 @@ public final class SSLSocketImpl // start handshaking, if failed, the connection will be closed. ensureNegotiated(false); } catch (IOException ioe) { - if (SSLLogger.isOn && SSLLogger.isOn("handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("handshake")) { SSLLogger.severe("handshake failed", ioe); } @@ -573,7 +573,7 @@ public final class SSLSocketImpl return; } - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("duplex close of SSLSocket"); } @@ -591,7 +591,7 @@ public final class SSLSocketImpl } } catch (IOException ioe) { // ignore the exception - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("SSLSocket duplex close failed. Debug info only. Exception details:", ioe); } } finally { @@ -601,7 +601,7 @@ public final class SSLSocketImpl closeSocket(false); } catch (IOException ioe) { // ignore the exception - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("SSLSocket close failed. Debug info only. Exception details:", ioe); } } finally { @@ -696,7 +696,7 @@ public final class SSLSocketImpl "close_notify message cannot be sent."); } else { super.shutdownOutput(); - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning( "SSLSocket output duplex close failed: " + "SO_LINGER timeout, " + @@ -717,7 +717,7 @@ public final class SSLSocketImpl // failed to send the close_notify message. // conContext.conSession.invalidate(); - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning( "Invalidate the session: SO_LINGER timeout, " + "close_notify message cannot be sent."); @@ -832,7 +832,7 @@ public final class SSLSocketImpl return; } - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("close inbound of SSLSocket"); } @@ -868,7 +868,7 @@ public final class SSLSocketImpl return; } - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("close outbound of SSLSocket"); } conContext.closeOutbound(); @@ -1027,7 +1027,7 @@ public final class SSLSocketImpl // filed is checked here, in case the closing process is // still in progress. if (hasDepleted) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("The input stream has been depleted"); } @@ -1048,7 +1048,7 @@ public final class SSLSocketImpl // Double check if the input stream has been depleted. if (hasDepleted) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("The input stream is closing"); } @@ -1134,7 +1134,7 @@ public final class SSLSocketImpl @Override public void close() throws IOException { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest("Closing input stream"); } @@ -1142,7 +1142,7 @@ public final class SSLSocketImpl SSLSocketImpl.this.close(); } catch (IOException ioe) { // ignore the exception - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("input stream close failed. Debug info only. Exception details:", ioe); } } @@ -1218,7 +1218,7 @@ public final class SSLSocketImpl socketInputRecord.deplete( conContext.isNegotiated && (getSoTimeout() > 0)); } catch (Exception ex) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning( "input stream close depletion failed", ex); } @@ -1327,7 +1327,7 @@ public final class SSLSocketImpl @Override public void close() throws IOException { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest("Closing output stream"); } @@ -1335,7 +1335,7 @@ public final class SSLSocketImpl SSLSocketImpl.this.close(); } catch (IOException ioe) { // ignore the exception - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("output stream close failed. Debug info only. Exception details:", ioe); } } @@ -1543,7 +1543,7 @@ public final class SSLSocketImpl if ((conContext.handshakeContext == null) && !conContext.isOutboundClosed() && !conContext.isBroken) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest("trigger key update"); } startHandshake(); @@ -1562,7 +1562,7 @@ public final class SSLSocketImpl !conContext.isOutboundClosed() && !conContext.isInboundClosed() && !conContext.isBroken) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest("trigger new session ticket"); } conContext.conSession.updateNST = false; @@ -1670,7 +1670,7 @@ public final class SSLSocketImpl * This method never returns normally, it always throws an IOException. */ private void handleException(Exception cause) throws IOException { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("handling exception", cause); } @@ -1747,7 +1747,7 @@ public final class SSLSocketImpl @Override public void shutdown() throws IOException { if (!isClosed()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("close the underlying socket"); } @@ -1773,7 +1773,7 @@ public final class SSLSocketImpl } private void closeSocket(boolean selfInitiated) throws IOException { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("close the SSL connection " + (selfInitiated ? "(initiative)" : "(passive)")); } @@ -1828,7 +1828,7 @@ public final class SSLSocketImpl * transport without waiting for the responding close_notify. */ private void waitForClose() throws IOException { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("wait for close_notify or alert"); } @@ -1838,7 +1838,7 @@ public final class SSLSocketImpl try { Plaintext plainText = decode(null); // discard and continue - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest( "discard plaintext while waiting for close", plainText); diff --git a/src/java.base/share/classes/sun/security/ssl/SSLSocketInputRecord.java b/src/java.base/share/classes/sun/security/ssl/SSLSocketInputRecord.java index 09223a4485d..ce7ab630730 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLSocketInputRecord.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLSocketInputRecord.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, Azul Systems, Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -210,7 +210,7 @@ final class SSLSocketInputRecord extends InputRecord implements SSLRecord { int contentLen = ((header[3] & 0xFF) << 8) + (header[4] & 0xFF); // pos: 3, 4 - if (SSLLogger.isOn && SSLLogger.isOn("record")) { + if (SSLLogger.isOn() && SSLLogger.isOn("record")) { SSLLogger.fine( "READ: " + ProtocolVersion.nameOf(majorVersion, minorVersion) + @@ -243,7 +243,7 @@ final class SSLSocketInputRecord extends InputRecord implements SSLRecord { readFully(contentLen); recordBody.flip(); - if (SSLLogger.isOn && SSLLogger.isOn("record")) { + if (SSLLogger.isOn() && SSLLogger.isOn("record")) { SSLLogger.fine( "READ: " + ProtocolVersion.nameOf(majorVersion, minorVersion) + @@ -406,7 +406,7 @@ final class SSLSocketInputRecord extends InputRecord implements SSLRecord { */ os.write(SSLRecord.v2NoCipher); // SSLv2Hello - if (SSLLogger.isOn) { + if (SSLLogger.isOn()) { if (SSLLogger.isOn("record")) { SSLLogger.fine( "Requested to negotiate unsupported SSLv2!"); @@ -445,7 +445,7 @@ final class SSLSocketInputRecord extends InputRecord implements SSLRecord { ByteBuffer converted = convertToClientHello(recordBody); - if (SSLLogger.isOn && SSLLogger.isOn("packet")) { + if (SSLLogger.isOn() && SSLLogger.isOn("packet")) { SSLLogger.fine( "[Converted] ClientHello", converted); } @@ -488,13 +488,13 @@ final class SSLSocketInputRecord extends InputRecord implements SSLRecord { private static int read(InputStream is, byte[] buf, int off, int len) throws IOException { int readLen = is.read(buf, off, len); if (readLen < 0) { - if (SSLLogger.isOn && SSLLogger.isOn("packet")) { + if (SSLLogger.isOn() && SSLLogger.isOn("packet")) { SSLLogger.fine("Raw read: EOF"); } throw new EOFException("SSL peer shut down incorrectly"); } - if (SSLLogger.isOn && SSLLogger.isOn("packet")) { + if (SSLLogger.isOn() && SSLLogger.isOn("packet")) { ByteBuffer bb = ByteBuffer.wrap(buf, off, readLen); SSLLogger.fine("Raw read", bb); } diff --git a/src/java.base/share/classes/sun/security/ssl/SSLSocketOutputRecord.java b/src/java.base/share/classes/sun/security/ssl/SSLSocketOutputRecord.java index a7809754ed0..e83ad15db22 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLSocketOutputRecord.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLSocketOutputRecord.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -55,7 +55,7 @@ final class SSLSocketOutputRecord extends OutputRecord implements SSLRecord { recordLock.lock(); try { if (isClosed()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("outbound has closed, ignore outbound " + "alert message: " + Alert.nameOf(description)); } @@ -67,7 +67,7 @@ final class SSLSocketOutputRecord extends OutputRecord implements SSLRecord { write(level); write(description); - if (SSLLogger.isOn && SSLLogger.isOn("record")) { + if (SSLLogger.isOn() && SSLLogger.isOn("record")) { SSLLogger.fine("WRITE: " + protocolVersion.name + " " + ContentType.ALERT.name + "(" + Alert.nameOf(description) + ")" + @@ -81,7 +81,7 @@ final class SSLSocketOutputRecord extends OutputRecord implements SSLRecord { deliverStream.write(buf, 0, count); // may throw IOException deliverStream.flush(); // may throw IOException - if (SSLLogger.isOn && SSLLogger.isOn("packet")) { + if (SSLLogger.isOn() && SSLLogger.isOn("packet")) { SSLLogger.fine("Raw write", (new ByteArrayInputStream(buf, 0, count))); } @@ -99,7 +99,7 @@ final class SSLSocketOutputRecord extends OutputRecord implements SSLRecord { recordLock.lock(); try { if (isClosed()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("outbound has closed, ignore outbound " + "handshake message", ByteBuffer.wrap(source, offset, length)); @@ -127,7 +127,7 @@ final class SSLSocketOutputRecord extends OutputRecord implements SSLRecord { int limit = v2ClientHello.limit(); handshakeHash.deliver(record, 2, (limit - 2)); - if (SSLLogger.isOn && SSLLogger.isOn("record")) { + if (SSLLogger.isOn() && SSLLogger.isOn("record")) { SSLLogger.fine( "WRITE: SSLv2 ClientHello message" + ", length = " + limit); @@ -141,7 +141,7 @@ final class SSLSocketOutputRecord extends OutputRecord implements SSLRecord { deliverStream.write(record, 0, limit); deliverStream.flush(); - if (SSLLogger.isOn && SSLLogger.isOn("packet")) { + if (SSLLogger.isOn() && SSLLogger.isOn("packet")) { SSLLogger.fine("Raw write", (new ByteArrayInputStream(record, 0, limit))); } @@ -177,7 +177,7 @@ final class SSLSocketOutputRecord extends OutputRecord implements SSLRecord { return; } - if (SSLLogger.isOn && SSLLogger.isOn("record")) { + if (SSLLogger.isOn() && SSLLogger.isOn("record")) { SSLLogger.fine( "WRITE: " + protocolVersion.name + " " + ContentType.HANDSHAKE.name + @@ -191,7 +191,7 @@ final class SSLSocketOutputRecord extends OutputRecord implements SSLRecord { deliverStream.write(buf, 0, count); // may throw IOException deliverStream.flush(); // may throw IOException - if (SSLLogger.isOn && SSLLogger.isOn("packet")) { + if (SSLLogger.isOn() && SSLLogger.isOn("packet")) { SSLLogger.fine("Raw write", (new ByteArrayInputStream(buf, 0, count))); } @@ -212,7 +212,7 @@ final class SSLSocketOutputRecord extends OutputRecord implements SSLRecord { recordLock.lock(); try { if (isClosed()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("outbound has closed, ignore outbound " + "change_cipher_spec message"); } @@ -231,7 +231,7 @@ final class SSLSocketOutputRecord extends OutputRecord implements SSLRecord { deliverStream.write(buf, 0, count); // may throw IOException // deliverStream.flush(); // flush in Finished - if (SSLLogger.isOn && SSLLogger.isOn("packet")) { + if (SSLLogger.isOn() && SSLLogger.isOn("packet")) { SSLLogger.fine("Raw write", (new ByteArrayInputStream(buf, 0, count))); } @@ -257,7 +257,7 @@ final class SSLSocketOutputRecord extends OutputRecord implements SSLRecord { return; } - if (SSLLogger.isOn && SSLLogger.isOn("record")) { + if (SSLLogger.isOn() && SSLLogger.isOn("record")) { SSLLogger.fine( "WRITE: " + protocolVersion.name + " " + ContentType.HANDSHAKE.name + @@ -271,7 +271,7 @@ final class SSLSocketOutputRecord extends OutputRecord implements SSLRecord { deliverStream.write(buf, 0, count); // may throw IOException deliverStream.flush(); // may throw IOException - if (SSLLogger.isOn && SSLLogger.isOn("packet")) { + if (SSLLogger.isOn() && SSLLogger.isOn("packet")) { SSLLogger.fine("Raw write", (new ByteArrayInputStream(buf, 0, count))); } @@ -293,7 +293,7 @@ final class SSLSocketOutputRecord extends OutputRecord implements SSLRecord { } if (writeCipher.authenticator.seqNumOverflow()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine( "sequence number extremely close to overflow " + "(2^64-1 packets). Closing connection."); @@ -330,7 +330,7 @@ final class SSLSocketOutputRecord extends OutputRecord implements SSLRecord { count = position; write(source, offset, fragLen); - if (SSLLogger.isOn && SSLLogger.isOn("record")) { + if (SSLLogger.isOn() && SSLLogger.isOn("record")) { SSLLogger.fine( "WRITE: " + protocolVersion.name + " " + ContentType.APPLICATION_DATA.name + @@ -345,7 +345,7 @@ final class SSLSocketOutputRecord extends OutputRecord implements SSLRecord { deliverStream.write(buf, 0, count); // may throw IOException deliverStream.flush(); // may throw IOException - if (SSLLogger.isOn && SSLLogger.isOn("packet")) { + if (SSLLogger.isOn() && SSLLogger.isOn("packet")) { SSLLogger.fine("Raw write", (new ByteArrayInputStream(buf, 0, count))); } diff --git a/src/java.base/share/classes/sun/security/ssl/SSLTransport.java b/src/java.base/share/classes/sun/security/ssl/SSLTransport.java index c248b48fb0a..9298e016f63 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLTransport.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLTransport.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2025, 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 @@ -113,7 +113,7 @@ interface SSLTransport { // Code to deliver SSLv2 error message for SSL/TLS connections. if (!context.sslContext.isDTLS()) { context.outputRecord.encodeV2NoCipher(); - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest("may be talking to SSLv2"); } } @@ -161,7 +161,7 @@ interface SSLTransport { if (context.handshakeContext != null && context.handshakeContext.sslConfig.enableRetransmissions && context.sslContext.isDTLS()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,verbose")) { SSLLogger.finest("retransmitted handshake flight"); } @@ -181,7 +181,7 @@ interface SSLTransport { // Note that JDK does not support 0-RTT yet. Otherwise, it is // needed to check early_data. if (!context.isNegotiated) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,verbose")) { SSLLogger.warning("unexpected application data " + "before handshake completion"); } diff --git a/src/java.base/share/classes/sun/security/ssl/ServerHello.java b/src/java.base/share/classes/sun/security/ssl/ServerHello.java index 1d2faa5351f..76c266a628a 100644 --- a/src/java.base/share/classes/sun/security/ssl/ServerHello.java +++ b/src/java.base/share/classes/sun/security/ssl/ServerHello.java @@ -365,7 +365,7 @@ final class ServerHello { shc.sslConfig.getEnabledExtensions( SSLHandshake.SERVER_HELLO, shc.negotiatedProtocol); shm.extensions.produce(shc, serverHelloExtensions); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Produced ServerHello handshake message", shm); } @@ -440,7 +440,7 @@ final class ServerHello { } // The cipher suite has been negotiated. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("use cipher suite " + cs.name); } @@ -453,7 +453,7 @@ final class ServerHello { if (ke != null) { SSLPossession[] hcds = ke.createPossessions(shc); if ((hcds != null) && (hcds.length != 0)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "use legacy cipher suite " + cs.name); } @@ -570,7 +570,7 @@ final class ServerHello { shc.sslConfig.getEnabledExtensions( SSLHandshake.SERVER_HELLO, shc.negotiatedProtocol); shm.extensions.produce(shc, serverHelloExtensions); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Produced ServerHello handshake message", shm); } @@ -723,14 +723,14 @@ final class ServerHello { } // The cipher suite has been negotiated. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("use cipher suite " + cs.name); } return cs; } if (legacySuite != null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "use legacy cipher suite " + legacySuite.name); } @@ -783,7 +783,7 @@ final class ServerHello { shc.sslConfig.getEnabledExtensions( SSLHandshake.HELLO_RETRY_REQUEST, shc.negotiatedProtocol); hhrm.extensions.produce(shc, serverHelloExtensions); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced HelloRetryRequest handshake message", hhrm); } @@ -845,7 +845,7 @@ final class ServerHello { shc.sslConfig.getEnabledExtensions( SSLHandshake.MESSAGE_HASH, shc.negotiatedProtocol); hhrm.extensions.produce(shc, serverHelloExtensions); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Reproduced HelloRetryRequest handshake message", hhrm); } @@ -886,7 +886,7 @@ final class ServerHello { } ServerHelloMessage shm = new ServerHelloMessage(chc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Consuming ServerHello handshake message", shm); } @@ -931,7 +931,7 @@ final class ServerHello { } chc.negotiatedProtocol = serverVersion; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Negotiated protocol version: " + serverVersion.name); } @@ -986,7 +986,7 @@ final class ServerHello { chc.conContext.protocolVersion = chc.negotiatedProtocol; chc.conContext.outputRecord.setVersion(chc.negotiatedProtocol); } - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Negotiated protocol version: " + serverVersion.name); } @@ -1132,7 +1132,7 @@ final class ServerHello { chc.handshakeSession = new SSLSessionImpl(chc, chc.negotiatedCipherSuite, newId); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Locally assigned Session Id: " + newId.toString()); } @@ -1204,7 +1204,7 @@ final class ServerHello { private static void setUpPskKD(HandshakeContext hc, SecretKey psk) throws SSLHandshakeException { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Using PSK to derive early secret"); } diff --git a/src/java.base/share/classes/sun/security/ssl/ServerHelloDone.java b/src/java.base/share/classes/sun/security/ssl/ServerHelloDone.java index 7136b36ffc2..a86257ab3d0 100644 --- a/src/java.base/share/classes/sun/security/ssl/ServerHelloDone.java +++ b/src/java.base/share/classes/sun/security/ssl/ServerHelloDone.java @@ -93,7 +93,7 @@ final class ServerHelloDone { ServerHandshakeContext shc = (ServerHandshakeContext)context; ServerHelloDoneMessage shdm = new ServerHelloDoneMessage(shc); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced ServerHelloDone handshake message", shdm); } @@ -147,7 +147,7 @@ final class ServerHelloDone { ServerHelloDoneMessage shdm = new ServerHelloDoneMessage(chc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming ServerHelloDone handshake message", shdm); } diff --git a/src/java.base/share/classes/sun/security/ssl/ServerNameExtension.java b/src/java.base/share/classes/sun/security/ssl/ServerNameExtension.java index 96c3fe2fa6a..1b3c8ec3eba 100644 --- a/src/java.base/share/classes/sun/security/ssl/ServerNameExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/ServerNameExtension.java @@ -216,7 +216,7 @@ final class ServerNameExtension { // Is it a supported and enabled extension? if (!chc.sslConfig.isAvailable(CH_SERVER_NAME)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "Ignore unavailable server_name extension"); } @@ -261,7 +261,7 @@ final class ServerNameExtension { return extData; } - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning("Unable to indicate server name"); } return null; @@ -287,7 +287,7 @@ final class ServerNameExtension { // Is it a supported and enabled extension? if (!shc.sslConfig.isAvailable(CH_SERVER_NAME)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable extension: " + CH_SERVER_NAME.name); } @@ -305,7 +305,7 @@ final class ServerNameExtension { if (!shc.sslConfig.sniMatchers.isEmpty()) { sni = chooseSni(shc.sslConfig.sniMatchers, spec.serverNames); if (sni != null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "server name indication (" + sni + ") is accepted"); @@ -322,7 +322,7 @@ final class ServerNameExtension { // connection with a "missing_extension" alert. // // We do not reject client without SNI extension currently. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "no server name matchers, " + "ignore server name indication"); @@ -347,7 +347,7 @@ final class ServerNameExtension { // so don't include the pre-shared key in the // ServerHello handshake message shc.handshakeExtensions.remove(SH_PRE_SHARED_KEY); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "abort session resumption, " + "different server name indication used"); @@ -441,7 +441,7 @@ final class ServerNameExtension { CHServerNamesSpec spec = (CHServerNamesSpec) shc.handshakeExtensions.get(CH_SERVER_NAME); if (spec == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest( "Ignore unavailable extension: " + SH_SERVER_NAME.name); } @@ -451,7 +451,7 @@ final class ServerNameExtension { // When resuming a session, the server MUST NOT include a // server_name extension in the server hello. if (shc.isResumption || shc.negotiatedServerName == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest( "No expected server name indication response"); } @@ -528,7 +528,7 @@ final class ServerNameExtension { CHServerNamesSpec spec = (CHServerNamesSpec) shc.handshakeExtensions.get(CH_SERVER_NAME); if (spec == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest( "Ignore unavailable extension: " + EE_SERVER_NAME.name); } @@ -538,7 +538,7 @@ final class ServerNameExtension { // When resuming a session, the server MUST NOT include a // server_name extension in the server hello. if (shc.isResumption || shc.negotiatedServerName == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest( "No expected server name indication response"); } diff --git a/src/java.base/share/classes/sun/security/ssl/SessionTicketExtension.java b/src/java.base/share/classes/sun/security/ssl/SessionTicketExtension.java index 9a84bbad8fd..1a0283cf859 100644 --- a/src/java.base/share/classes/sun/security/ssl/SessionTicketExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/SessionTicketExtension.java @@ -93,7 +93,7 @@ final class SessionTicketExtension { kt = Integer.parseInt(s) * 1000; // change to ms if (kt < 0 || kt > NewSessionTicket.MAX_TICKET_LIFETIME) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("Invalid timeout for " + "jdk.tls.server.statelessKeyTimeout: " + kt + ". Set to default value " + @@ -103,7 +103,7 @@ final class SessionTicketExtension { } } catch (NumberFormatException e) { kt = TIMEOUT_DEFAULT; - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("Invalid timeout for " + "jdk.tls.server.statelessKeyTimeout: " + s + ". Set to default value " + TIMEOUT_DEFAULT + @@ -252,7 +252,7 @@ final class SessionTicketExtension { Integer.BYTES + iv.length + 1, encrypted.length); return result; } catch (Exception e) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Encryption failed." + e); } return new byte[0]; @@ -294,7 +294,7 @@ final class SessionTicketExtension { return out; } catch (Exception e) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Decryption failed." + e); } } @@ -309,7 +309,7 @@ final class SessionTicketExtension { gos.write(input, 0, decompressedLen); gos.finish(); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("decompressed bytes: " + decompressedLen + "; compressed bytes: " + baos.size()); } @@ -328,7 +328,7 @@ final class SessionTicketExtension { new ByteArrayInputStream(bytes))) { byte[] out = gis.readAllBytes(); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("compressed bytes: " + compressedLen + "; decompressed bytes: " + out.length); } @@ -394,7 +394,7 @@ final class SessionTicketExtension { // If the context does not allow stateless tickets, exit if (!((SSLSessionContextImpl)chc.sslContext. engineGetClientSessionContext()).statelessEnabled()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Stateless resumption not supported"); } return null; @@ -406,7 +406,7 @@ final class SessionTicketExtension { if (!chc.isResumption || chc.resumingSession == null || chc.resumingSession.getPskIdentity() == null || chc.resumingSession.getProtocolVersion().useTLS13PlusSpec()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Stateless resumption supported"); } return new byte[0]; @@ -450,7 +450,7 @@ final class SessionTicketExtension { shc.statelessResumption = true; if (buffer.remaining() == 0) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Client accepts session tickets."); } return; @@ -462,11 +462,11 @@ final class SessionTicketExtension { if (b != null) { shc.resumingSession = new SSLSessionImpl(shc, b); shc.isResumption = true; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Valid stateless session ticket found"); } } else { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Invalid stateless session ticket found"); } } @@ -546,7 +546,7 @@ final class SessionTicketExtension { // Disable stateless resumption if server doesn't send the extension. if (chc.statelessResumption) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.info( "Server doesn't support stateless resumption"); } diff --git a/src/java.base/share/classes/sun/security/ssl/SignatureAlgorithmsExtension.java b/src/java.base/share/classes/sun/security/ssl/SignatureAlgorithmsExtension.java index b298da05e9a..dddeb523516 100644 --- a/src/java.base/share/classes/sun/security/ssl/SignatureAlgorithmsExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/SignatureAlgorithmsExtension.java @@ -182,7 +182,7 @@ final class SignatureAlgorithmsExtension { // Is it a supported and enabled extension? if (!chc.sslConfig.isAvailable( SSLExtension.CH_SIGNATURE_ALGORITHMS)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable signature_algorithms extension"); } @@ -218,7 +218,7 @@ final class SignatureAlgorithmsExtension { // Is it a supported and enabled extension? if (!shc.sslConfig.isAvailable( SSLExtension.CH_SIGNATURE_ALGORITHMS)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable signature_algorithms extension"); } diff --git a/src/java.base/share/classes/sun/security/ssl/SignatureScheme.java b/src/java.base/share/classes/sun/security/ssl/SignatureScheme.java index 5a8f103082b..b91fc17fd29 100644 --- a/src/java.base/share/classes/sun/security/ssl/SignatureScheme.java +++ b/src/java.base/share/classes/sun/security/ssl/SignatureScheme.java @@ -204,7 +204,7 @@ enum SignatureScheme { NoSuchAlgorithmException | RuntimeException exp) { // Signature.getParameters() may throw RuntimeException. mediator = false; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "RSASSA-PSS signature with " + hash + " is not supported by the underlying providers", exp); @@ -297,7 +297,7 @@ enum SignatureScheme { Signature.getInstance(algorithm); } catch (Exception e) { mediator = false; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "Signature algorithm, " + algorithm + ", is not supported by the underlying providers"); @@ -432,7 +432,7 @@ enum SignatureScheme { for (SignatureScheme ss: schemesToCheck) { if (!ss.isAvailable) { - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "Ignore unsupported signature scheme: " + ss.name); @@ -451,12 +451,12 @@ enum SignatureScheme { if (isMatch) { if (ss.isPermitted(constraints, scopes)) { supported.add(ss); - } else if (SSLLogger.isOn && + } else if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "Ignore disabled signature scheme: " + ss.name); } - } else if (SSLLogger.isOn && + } else if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "Ignore inactive signature scheme: " + ss.name); @@ -476,7 +476,7 @@ enum SignatureScheme { for (int ssid : algorithmIds) { SignatureScheme ss = SignatureScheme.valueOf(ssid); if (ss == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "Unsupported signature scheme: " + SignatureScheme.nameOf(ssid)); @@ -486,7 +486,7 @@ enum SignatureScheme { && ss.isAllowed(constraints, protocolVersion, scopes)) { supported.add(ss); } else { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "Unsupported signature scheme: " + ss.name); } @@ -545,7 +545,7 @@ enum SignatureScheme { } } - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "Ignore the signature algorithm (" + ss + @@ -574,7 +574,7 @@ enum SignatureScheme { } } - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "Ignore the legacy signature algorithm (" + ss + @@ -660,7 +660,7 @@ enum SignatureScheme { return signer; } catch (NoSuchAlgorithmException | InvalidKeyException | InvalidAlgorithmParameterException nsae) { - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "Ignore unsupported signature algorithm (" + diff --git a/src/java.base/share/classes/sun/security/ssl/StatusResponseManager.java b/src/java.base/share/classes/sun/security/ssl/StatusResponseManager.java index ec200c6e495..d8c4a8ccc3e 100644 --- a/src/java.base/share/classes/sun/security/ssl/StatusResponseManager.java +++ b/src/java.base/share/classes/sun/security/ssl/StatusResponseManager.java @@ -119,13 +119,13 @@ final class StatusResponseManager { if (cert.getExtensionValue( PKIXExtensions.OCSPNoCheck_Id.toString()) != null) { - if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { + if (SSLLogger.isOn() && SSLLogger.isOn("respmgr")) { SSLLogger.fine( "OCSP NoCheck extension found. OCSP will be skipped"); } return null; } else if (defaultResponder != null && respOverride) { - if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { + if (SSLLogger.isOn() && SSLLogger.isOn("respmgr")) { SSLLogger.fine( "Responder override: URI is " + defaultResponder); } @@ -165,7 +165,7 @@ final class StatusResponseManager { Map responseMap = new HashMap<>(); List requestList = new ArrayList<>(); - if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { + if (SSLLogger.isOn() && SSLLogger.isOn("respmgr")) { SSLLogger.fine( "Beginning check: Type = " + type + ", Chain length = " + chain.length); @@ -192,7 +192,7 @@ final class StatusResponseManager { requestList.add(new OCSPFetchCall(sInfo, ocspReq)); } } catch (IOException exc) { - if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { + if (SSLLogger.isOn() && SSLLogger.isOn("respmgr")) { SSLLogger.fine( "Exception during CertId creation: ", exc); } @@ -219,14 +219,14 @@ final class StatusResponseManager { requestList.add(new OCSPFetchCall(sInfo, ocspReq)); } } catch (IOException exc) { - if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { + if (SSLLogger.isOn() && SSLLogger.isOn("respmgr")) { SSLLogger.fine( "Exception during CertId creation: ", exc); } } } } else { - if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { + if (SSLLogger.isOn() && SSLLogger.isOn("respmgr")) { SSLLogger.fine("Unsupported status request type: " + type); } } @@ -257,7 +257,7 @@ final class StatusResponseManager { // that, otherwise just log the ExecutionException Throwable cause = Optional.ofNullable( exc.getCause()).orElse(exc); - if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { + if (SSLLogger.isOn() && SSLLogger.isOn("respmgr")) { SSLLogger.fine("Exception during OCSP fetch: " + cause); } @@ -266,13 +266,13 @@ final class StatusResponseManager { if (info != null && info.responseData != null) { responseMap.put(info.cert, info.responseData.ocspBytes); - } else if (SSLLogger.isOn && + } else if (SSLLogger.isOn() && SSLLogger.isOn("respmgr")) { SSLLogger.fine( "Completed task had no response data"); } } else { - if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { + if (SSLLogger.isOn() && SSLLogger.isOn("respmgr")) { SSLLogger.fine("Found cancelled task"); } } @@ -280,7 +280,7 @@ final class StatusResponseManager { } catch (InterruptedException intex) { // Log and reset the interrupted state Thread.currentThread().interrupt(); - if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { + if (SSLLogger.isOn() && SSLLogger.isOn("respmgr")) { SSLLogger.fine("Interrupt occurred while fetching: " + intex); } @@ -308,7 +308,7 @@ final class StatusResponseManager { for (Extension ext : ocspRequest.extensions) { if (ext.getId().equals( PKIXExtensions.OCSPNonce_Id.toString())) { - if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { + if (SSLLogger.isOn() && SSLLogger.isOn("respmgr")) { SSLLogger.fine( "Nonce extension found, skipping cache check"); } @@ -323,14 +323,14 @@ final class StatusResponseManager { // and do not return it as a cache hit. if (respEntry != null && respEntry.nextUpdate != null && respEntry.nextUpdate.before(new Date())) { - if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { + if (SSLLogger.isOn() && SSLLogger.isOn("respmgr")) { SSLLogger.fine( "nextUpdate threshold exceeded, purging from cache"); } respEntry = null; } - if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { + if (SSLLogger.isOn() && SSLLogger.isOn("respmgr")) { SSLLogger.fine( "Check cache for SN" + Debug.toString(cid.getSerialNumber()) + ": " + (respEntry != null ? "HIT" : "MISS")); @@ -493,7 +493,7 @@ final class StatusResponseManager { */ @Override public StatusInfo call() { - if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { + if (SSLLogger.isOn() && SSLLogger.isOn("respmgr")) { SSLLogger.fine( "Starting fetch for SN " + Debug.toString(statInfo.cid.getSerialNumber())); @@ -505,13 +505,13 @@ final class StatusResponseManager { if (statInfo.responder == null) { // If we have no URI then there's nothing to do // but return. - if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { + if (SSLLogger.isOn() && SSLLogger.isOn("respmgr")) { SSLLogger.fine( "Null URI detected, OCSP fetch aborted"); } return statInfo; } else { - if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { + if (SSLLogger.isOn() && SSLLogger.isOn("respmgr")) { SSLLogger.fine( "Attempting fetch from " + statInfo.responder); } @@ -541,7 +541,7 @@ final class StatusResponseManager { statInfo.cid); // Get the response status and act on it appropriately - if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { + if (SSLLogger.isOn() && SSLLogger.isOn("respmgr")) { SSLLogger.fine("OCSP Status: " + cacheEntry.status + " (" + respBytes.length + " bytes)"); } @@ -554,7 +554,7 @@ final class StatusResponseManager { addToCache(statInfo.cid, cacheEntry); } } catch (IOException ioe) { - if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { + if (SSLLogger.isOn() && SSLLogger.isOn("respmgr")) { SSLLogger.fine("Caught exception: ", ioe); } } @@ -573,12 +573,12 @@ final class StatusResponseManager { // If no cache lifetime has been set on entries then // don't cache this response if there is no nextUpdate field if (entry.nextUpdate == null && cacheLifetime == 0) { - if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { + if (SSLLogger.isOn() && SSLLogger.isOn("respmgr")) { SSLLogger.fine("Not caching this OCSP response"); } } else { responseCache.put(certId, entry); - if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { + if (SSLLogger.isOn() && SSLLogger.isOn("respmgr")) { SSLLogger.fine( "Added response for SN " + Debug.toString(certId.getSerialNumber()) + @@ -600,7 +600,7 @@ final class StatusResponseManager { // is necessary. Also, we will only staple if we're doing a full // handshake. if (!shc.sslContext.isStaplingEnabled(false) || shc.isResumption) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Staping disabled or is a resumed session"); } return null; @@ -623,7 +623,7 @@ final class StatusResponseManager { // selection yet, only accept a request if the ResponderId field // is empty. Finally, we'll only do this in (D)TLS 1.2 or earlier. if (statReqV2 != null && !shc.negotiatedProtocol.useTLS13PlusSpec()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake,verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.fine("SH Processing status_request_v2 extension"); } // RFC 6961 stapling @@ -660,7 +660,7 @@ final class StatusResponseManager { req = reqItems[ocspIdx]; type = CertStatusRequestType.valueOf(req.statusType); } else { - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest("Warning: No suitable request " + "found in the status_request_v2 extension."); @@ -678,7 +678,7 @@ final class StatusResponseManager { // we will try processing an asserted status_request. if ((statReq != null) && (ext == null || type == null || req == null)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake,verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.fine("SH Processing status_request extension"); } ext = SSLExtension.CH_STATUS_REQUEST; @@ -692,7 +692,7 @@ final class StatusResponseManager { if (ocspReq.responderIds.isEmpty()) { req = ocspReq; } else { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest("Warning: No suitable request " + "found in the status_request extension."); } @@ -704,7 +704,7 @@ final class StatusResponseManager { // find a suitable StatusRequest, then stapling is disabled. // The ext, type and req variables must have been set to continue. if (type == null || req == null || ext == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("No suitable status_request or " + "status_request_v2, stapling is disabled"); } @@ -721,7 +721,7 @@ final class StatusResponseManager { } if (x509Possession == null) { // unlikely - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest("Warning: no X.509 certificates found. " + "Stapling is disabled."); } @@ -743,7 +743,7 @@ final class StatusResponseManager { responses = statRespMgr.get(fetchType, req, certs, shc.statusRespTimeout, TimeUnit.MILLISECONDS); if (!responses.isEmpty()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest("Response manager returned " + responses.size() + " entries."); } @@ -752,7 +752,7 @@ final class StatusResponseManager { if (type == CertStatusRequestType.OCSP) { byte[] respDER = responses.get(certs[0]); if (respDER == null || respDER.length == 0) { - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest("Warning: Null or zero-length " + "response found for leaf certificate. " + @@ -763,7 +763,7 @@ final class StatusResponseManager { } params = new StaplingParameters(ext, type, req, responses); } else { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest("Warning: no OCSP responses obtained. " + "Stapling is disabled."); } @@ -771,7 +771,7 @@ final class StatusResponseManager { } else { // This should not happen, but if lazy initialization of the // StatusResponseManager doesn't occur we should turn off stapling. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest("Warning: lazy initialization " + "of the StatusResponseManager failed. " + "Stapling is disabled."); diff --git a/src/java.base/share/classes/sun/security/ssl/SunX509KeyManagerImpl.java b/src/java.base/share/classes/sun/security/ssl/SunX509KeyManagerImpl.java index 6bf138f4e45..1fa2356d1de 100644 --- a/src/java.base/share/classes/sun/security/ssl/SunX509KeyManagerImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SunX509KeyManagerImpl.java @@ -129,7 +129,7 @@ final class SunX509KeyManagerImpl extends X509KeyManagerCertChecking { X509Credentials cred = new X509Credentials((PrivateKey) key, (X509Certificate[]) certs); credentialsMap.put(alias, cred); - if (SSLLogger.isOn && SSLLogger.isOn("keymanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("keymanager")) { SSLLogger.fine("found key for : " + alias, (Object[]) certs); } } @@ -315,7 +315,7 @@ final class SunX509KeyManagerImpl extends X509KeyManagerCertChecking { } if (results == null) { - if (SSLLogger.isOn && SSLLogger.isOn("keymanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("keymanager")) { SSLLogger.fine("KeyMgr: no matching key found"); } return null; diff --git a/src/java.base/share/classes/sun/security/ssl/SupportedGroupsExtension.java b/src/java.base/share/classes/sun/security/ssl/SupportedGroupsExtension.java index 57e5f8c9093..67cb37988a1 100644 --- a/src/java.base/share/classes/sun/security/ssl/SupportedGroupsExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/SupportedGroupsExtension.java @@ -164,7 +164,7 @@ final class SupportedGroupsExtension { // Is it a supported and enabled extension? if (!chc.sslConfig.isAvailable(CH_SUPPORTED_GROUPS)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable supported_groups extension"); } @@ -177,7 +177,7 @@ final class SupportedGroupsExtension { for (String name : chc.sslConfig.namedGroups) { NamedGroup ng = NamedGroup.nameOf(name); if (ng == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unspecified named group: " + name); } @@ -193,14 +193,14 @@ final class SupportedGroupsExtension { ng.isSupported(chc.activeCipherSuites) && ng.isPermitted(chc.algorithmConstraints)) { namedGroups.add(ng); - } else if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + } else if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore inactive or disabled named group: " + ng.name); } } if (namedGroups.isEmpty()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning("no available named group"); } @@ -244,7 +244,7 @@ final class SupportedGroupsExtension { // Is it a supported and enabled extension? if (!shc.sslConfig.isAvailable(CH_SUPPORTED_GROUPS)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable supported_groups extension"); } @@ -319,7 +319,7 @@ final class SupportedGroupsExtension { // Is it a supported and enabled extension? if (!shc.sslConfig.isAvailable(EE_SUPPORTED_GROUPS)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable supported_groups extension"); } @@ -335,7 +335,7 @@ final class SupportedGroupsExtension { for (String name : shc.sslConfig.namedGroups) { NamedGroup ng = NamedGroup.nameOf(name); if (ng == null) { - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unspecified named group: " + name); @@ -352,14 +352,14 @@ final class SupportedGroupsExtension { ng.isSupported(shc.activeCipherSuites) && ng.isPermitted(shc.algorithmConstraints)) { namedGroups.add(ng); - } else if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + } else if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore inactive or disabled named group: " + ng.name); } } if (namedGroups.isEmpty()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning("no available named group"); } @@ -399,7 +399,7 @@ final class SupportedGroupsExtension { // Is it a supported and enabled extension? if (!chc.sslConfig.isAvailable(EE_SUPPORTED_GROUPS)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable supported_groups extension"); } diff --git a/src/java.base/share/classes/sun/security/ssl/SupportedVersionsExtension.java b/src/java.base/share/classes/sun/security/ssl/SupportedVersionsExtension.java index 6efdcef0d29..58597c3008c 100644 --- a/src/java.base/share/classes/sun/security/ssl/SupportedVersionsExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/SupportedVersionsExtension.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2025, 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 @@ -168,7 +168,7 @@ final class SupportedVersionsExtension { // Is it a supported and enabled extension? if (!chc.sslConfig.isAvailable(CH_SUPPORTED_VERSIONS)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable extension: " + CH_SUPPORTED_VERSIONS.name); @@ -216,7 +216,7 @@ final class SupportedVersionsExtension { // Is it a supported and enabled extension? if (!shc.sslConfig.isAvailable(CH_SUPPORTED_VERSIONS)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable extension: " + CH_SUPPORTED_VERSIONS.name); @@ -308,7 +308,7 @@ final class SupportedVersionsExtension { shc.handshakeExtensions.get(CH_SUPPORTED_VERSIONS); if (svs == null) { // Unlikely, no key_share extension requested. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "Ignore unavailable supported_versions extension"); } @@ -317,7 +317,7 @@ final class SupportedVersionsExtension { // Is it a supported and enabled extension? if (!shc.sslConfig.isAvailable(SH_SUPPORTED_VERSIONS)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable extension: " + SH_SUPPORTED_VERSIONS.name); @@ -356,7 +356,7 @@ final class SupportedVersionsExtension { // Is it a supported and enabled extension? if (!chc.sslConfig.isAvailable(SH_SUPPORTED_VERSIONS)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable extension: " + SH_SUPPORTED_VERSIONS.name); @@ -399,7 +399,7 @@ final class SupportedVersionsExtension { // Is it a supported and enabled extension? if (!shc.sslConfig.isAvailable(HRR_SUPPORTED_VERSIONS)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable extension: " + HRR_SUPPORTED_VERSIONS.name); @@ -441,7 +441,7 @@ final class SupportedVersionsExtension { // Is it a supported and enabled extension? if (!chc.sslConfig.isAvailable(HRR_SUPPORTED_VERSIONS)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable extension: " + HRR_SUPPORTED_VERSIONS.name); @@ -483,7 +483,7 @@ final class SupportedVersionsExtension { // Is it a supported and enabled extension? if (!shc.sslConfig.isAvailable(HRR_SUPPORTED_VERSIONS)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "[Reproduce] Ignore unavailable extension: " + HRR_SUPPORTED_VERSIONS.name); diff --git a/src/java.base/share/classes/sun/security/ssl/TransportContext.java b/src/java.base/share/classes/sun/security/ssl/TransportContext.java index 9595dc8b3fd..980d9c4a6ce 100644 --- a/src/java.base/share/classes/sun/security/ssl/TransportContext.java +++ b/src/java.base/share/classes/sun/security/ssl/TransportContext.java @@ -270,7 +270,7 @@ final class TransportContext implements ConnectionContext { try { outputRecord.encodeAlert(Alert.Level.WARNING.level, alert.id); } catch (IOException ioe) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning( "Warning: failed to send warning alert " + alert, ioe); } @@ -330,7 +330,7 @@ final class TransportContext implements ConnectionContext { // so we'll do it here. if (closeReason != null) { if (cause == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning( "Closed transport, general or untracked problem"); } @@ -341,7 +341,7 @@ final class TransportContext implements ConnectionContext { if (cause instanceof SSLException) { throw (SSLException)cause; } else { // unlikely, but just in case. - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning( "Closed transport, unexpected rethrowing", cause); } @@ -364,7 +364,7 @@ final class TransportContext implements ConnectionContext { } // shutdown the transport - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.severe("Fatal (" + alert + "): " + diagnostic, cause); } @@ -380,7 +380,7 @@ final class TransportContext implements ConnectionContext { try { inputRecord.close(); } catch (IOException ioe) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("Fatal: input record closure failed", ioe); } @@ -411,7 +411,7 @@ final class TransportContext implements ConnectionContext { try { outputRecord.encodeAlert(Alert.Level.FATAL.level, alert.id); } catch (IOException ioe) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning( "Fatal: failed to send fatal alert " + alert, ioe); } @@ -424,7 +424,7 @@ final class TransportContext implements ConnectionContext { try { outputRecord.close(); } catch (IOException ioe) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("Fatal: output record closure failed", ioe); } @@ -440,7 +440,7 @@ final class TransportContext implements ConnectionContext { try { transport.shutdown(); } catch (IOException ioe) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("Fatal: transport closure failed", ioe); } @@ -526,7 +526,7 @@ final class TransportContext implements ConnectionContext { passiveInboundClose(); } } catch (IOException ioe) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("inbound closure failed", ioe); } } @@ -583,7 +583,7 @@ final class TransportContext implements ConnectionContext { try { initiateOutboundClose(); } catch (IOException ioe) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("outbound closure failed", ioe); } } diff --git a/src/java.base/share/classes/sun/security/ssl/TrustManagerFactoryImpl.java b/src/java.base/share/classes/sun/security/ssl/TrustManagerFactoryImpl.java index bac7735de07..8b89fecefa8 100644 --- a/src/java.base/share/classes/sun/security/ssl/TrustManagerFactoryImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/TrustManagerFactoryImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2025, 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 @@ -48,24 +48,24 @@ abstract class TrustManagerFactoryImpl extends TrustManagerFactorySpi { trustManager = getInstance(TrustStoreManager.getTrustedCerts()); } catch (SecurityException se) { // eat security exceptions but report other throwables - if (SSLLogger.isOn && SSLLogger.isOn("trustmanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("trustmanager")) { SSLLogger.fine( "SunX509: skip default keystore", se); } } catch (Error err) { - if (SSLLogger.isOn && SSLLogger.isOn("trustmanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("trustmanager")) { SSLLogger.fine( "SunX509: skip default keystore", err); } throw err; } catch (RuntimeException re) { - if (SSLLogger.isOn && SSLLogger.isOn("trustmanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("trustmanager")) { SSLLogger.fine( "SunX509: skip default keystore", re); } throw re; } catch (Exception e) { - if (SSLLogger.isOn && SSLLogger.isOn("trustmanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("trustmanager")) { SSLLogger.fine( "SunX509: skip default keystore", e); } diff --git a/src/java.base/share/classes/sun/security/ssl/TrustStoreManager.java b/src/java.base/share/classes/sun/security/ssl/TrustStoreManager.java index e2c353847c7..a44f3a4e32d 100644 --- a/src/java.base/share/classes/sun/security/ssl/TrustStoreManager.java +++ b/src/java.base/share/classes/sun/security/ssl/TrustStoreManager.java @@ -108,7 +108,7 @@ final class TrustStoreManager { this.storeFile = storeFile; this.lastModified = lastModified; - if (SSLLogger.isOn && SSLLogger.isOn("trustmanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("trustmanager")) { SSLLogger.fine( "trustStore is: " + storeName + "\n" + "trustStore type is: " + storeType + "\n" + @@ -151,7 +151,7 @@ final class TrustStoreManager { } // Not break, the file is inaccessible. - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("trustmanager")) { SSLLogger.fine( "Inaccessible trust store: " + @@ -267,7 +267,7 @@ final class TrustStoreManager { } // Reload a new key store. - if (SSLLogger.isOn && SSLLogger.isOn("trustmanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("trustmanager")) { SSLLogger.fine("Reload the trust store"); } @@ -321,7 +321,7 @@ final class TrustStoreManager { // Reload the trust store if needed. if (ks == null) { - if (SSLLogger.isOn && SSLLogger.isOn("trustmanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("trustmanager")) { SSLLogger.fine("Reload the trust store"); } ks = loadKeyStore(descriptor); @@ -329,12 +329,12 @@ final class TrustStoreManager { } // Reload trust certs from the key store. - if (SSLLogger.isOn && SSLLogger.isOn("trustmanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("trustmanager")) { SSLLogger.fine("Reload trust certs"); } certs = loadTrustedCerts(ks); - if (SSLLogger.isOn && SSLLogger.isOn("trustmanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("trustmanager")) { SSLLogger.fine("Reloaded " + certs.size() + " trust certs"); } @@ -355,7 +355,7 @@ final class TrustStoreManager { descriptor.storeFile == null) { // No file available, no KeyStore available. - if (SSLLogger.isOn && SSLLogger.isOn("trustmanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("trustmanager")) { SSLLogger.fine("No available key store"); } @@ -382,7 +382,7 @@ final class TrustStoreManager { ks.load(bis, password); } catch (FileNotFoundException fnfe) { // No file available, no KeyStore available. - if (SSLLogger.isOn && SSLLogger.isOn("trustmanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("trustmanager")) { SSLLogger.fine( "Not available key store: " + descriptor.storeName); } diff --git a/src/java.base/share/classes/sun/security/ssl/Utilities.java b/src/java.base/share/classes/sun/security/ssl/Utilities.java index 50cd3bee751..458551b9d8a 100644 --- a/src/java.base/share/classes/sun/security/ssl/Utilities.java +++ b/src/java.base/share/classes/sun/security/ssl/Utilities.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2025, 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 @@ -70,7 +70,7 @@ final class Utilities { SNIServerName serverName = sniList.get(i); if (serverName.getType() == StandardConstants.SNI_HOST_NAME) { sniList.set(i, sniHostName); - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine( "the previous server name in SNI (" + serverName + ") was replaced with (" + sniHostName + ")"); @@ -116,7 +116,7 @@ final class Utilities { return new SNIHostName(hostname); } catch (IllegalArgumentException iae) { // don't bother to handle illegal host_name - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine(hostname + "\" " + "is not a legal HostName for server name indication"); } diff --git a/src/java.base/share/classes/sun/security/ssl/X509Authentication.java b/src/java.base/share/classes/sun/security/ssl/X509Authentication.java index 5abc2cb1bf4..6aedff02c34 100644 --- a/src/java.base/share/classes/sun/security/ssl/X509Authentication.java +++ b/src/java.base/share/classes/sun/security/ssl/X509Authentication.java @@ -201,7 +201,7 @@ enum X509Authentication implements SSLAuthentication { private static SSLPossession createClientPossession( ClientHandshakeContext chc, String[] keyTypes) { X509ExtendedKeyManager km = chc.sslContext.getX509KeyManager(); - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest("X509KeyManager class: " + km.getClass().getName()); } @@ -243,7 +243,7 @@ enum X509Authentication implements SSLAuthentication { } if (clientAlias == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest("No X.509 cert selected for " + Arrays.toString(keyTypes)); } @@ -252,7 +252,7 @@ enum X509Authentication implements SSLAuthentication { PrivateKey clientPrivateKey = km.getPrivateKey(clientAlias); if (clientPrivateKey == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest( clientAlias + " is not a private key entry"); } @@ -261,7 +261,7 @@ enum X509Authentication implements SSLAuthentication { X509Certificate[] clientCerts = km.getCertificateChain(clientAlias); if ((clientCerts == null) || (clientCerts.length == 0)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest(clientAlias + " is a private key entry with no cert chain stored"); } @@ -270,7 +270,7 @@ enum X509Authentication implements SSLAuthentication { String privateKeyAlgorithm = clientPrivateKey.getAlgorithm(); if (!Arrays.asList(keyTypes).contains(privateKeyAlgorithm)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine( clientAlias + " private key algorithm " + privateKeyAlgorithm + " not in request list"); @@ -280,7 +280,7 @@ enum X509Authentication implements SSLAuthentication { String publicKeyAlgorithm = clientCerts[0].getPublicKey().getAlgorithm(); if (!privateKeyAlgorithm.equals(publicKeyAlgorithm)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine( clientAlias + " private or public key is not of " + "same algorithm: " + @@ -296,7 +296,7 @@ enum X509Authentication implements SSLAuthentication { private static SSLPossession createServerPossession( ServerHandshakeContext shc, String[] keyTypes) { X509ExtendedKeyManager km = shc.sslContext.getX509KeyManager(); - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest("X509KeyManager class: " + km.getClass().getName()); } @@ -337,7 +337,7 @@ enum X509Authentication implements SSLAuthentication { } if (serverAlias == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest("No X.509 cert selected for " + keyType); } continue; @@ -345,7 +345,7 @@ enum X509Authentication implements SSLAuthentication { PrivateKey serverPrivateKey = km.getPrivateKey(serverAlias); if (serverPrivateKey == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest( serverAlias + " is not a private key entry"); } @@ -354,7 +354,7 @@ enum X509Authentication implements SSLAuthentication { X509Certificate[] serverCerts = km.getCertificateChain(serverAlias); if ((serverCerts == null) || (serverCerts.length == 0)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest( serverAlias + " is not a certificate entry"); } @@ -364,7 +364,7 @@ enum X509Authentication implements SSLAuthentication { PublicKey serverPublicKey = serverCerts[0].getPublicKey(); if ((!serverPrivateKey.getAlgorithm().equals(keyType)) || (!serverPublicKey.getAlgorithm().equals(keyType))) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine( serverAlias + " private or public key is not of " + keyType + " algorithm"); @@ -379,7 +379,7 @@ enum X509Authentication implements SSLAuthentication { if (!shc.negotiatedProtocol.useTLS13PlusSpec() && keyType.equals("EC")) { if (!(serverPublicKey instanceof ECPublicKey)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning(serverAlias + " public key is not an instance of ECPublicKey"); } @@ -398,7 +398,7 @@ enum X509Authentication implements SSLAuthentication { ((shc.clientRequestedNamedGroups != null) && !shc.clientRequestedNamedGroups.contains(namedGroup))) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning( "Unsupported named group (" + namedGroup + ") used in the " + serverAlias + " certificate"); diff --git a/src/java.base/share/classes/sun/security/ssl/X509KeyManagerCertChecking.java b/src/java.base/share/classes/sun/security/ssl/X509KeyManagerCertChecking.java index 9484ab4f830..2a1f01273bd 100644 --- a/src/java.base/share/classes/sun/security/ssl/X509KeyManagerCertChecking.java +++ b/src/java.base/share/classes/sun/security/ssl/X509KeyManagerCertChecking.java @@ -116,7 +116,7 @@ abstract class X509KeyManagerCertChecking extends X509ExtendedKeyManager { } if (keyIndex == -1) { - if (SSLLogger.isOn && SSLLogger.isOn("keymanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("keymanager")) { SSLLogger.fine("Ignore alias " + alias + ": key algorithm does not match"); } @@ -134,7 +134,7 @@ abstract class X509KeyManagerCertChecking extends X509ExtendedKeyManager { } } if (!found) { - if (SSLLogger.isOn && SSLLogger.isOn("keymanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("keymanager")) { SSLLogger.fine( "Ignore alias " + alias + ": issuers do not match"); @@ -150,7 +150,7 @@ abstract class X509KeyManagerCertChecking extends X509ExtendedKeyManager { !conformsToAlgorithmConstraints(constraints, chain, checkType.getValidator())) { - if (SSLLogger.isOn && SSLLogger.isOn("keymanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("keymanager")) { SSLLogger.fine("Ignore alias " + alias + ": certificate chain does not conform to " + "algorithm constraints"); @@ -219,7 +219,7 @@ abstract class X509KeyManagerCertChecking extends X509ExtendedKeyManager { checker.init(false); } catch (CertPathValidatorException cpve) { // unlikely to happen - if (SSLLogger.isOn && SSLLogger.isOn("keymanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("keymanager")) { SSLLogger.fine( "Cannot initialize algorithm constraints checker", cpve); @@ -235,7 +235,7 @@ abstract class X509KeyManagerCertChecking extends X509ExtendedKeyManager { // We don't care about the unresolved critical extensions. checker.check(cert, Collections.emptySet()); } catch (CertPathValidatorException cpve) { - if (SSLLogger.isOn && SSLLogger.isOn("keymanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("keymanager")) { SSLLogger.fine("Certificate does not conform to " + "algorithm constraints", cert, cpve); } @@ -392,7 +392,7 @@ abstract class X509KeyManagerCertChecking extends X509ExtendedKeyManager { serverName.getEncoded()); } catch (IllegalArgumentException iae) { // unlikely to happen, just in case ... - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("keymanager")) { SSLLogger.fine("Illegal server name: " + serverName); @@ -408,7 +408,7 @@ abstract class X509KeyManagerCertChecking extends X509ExtendedKeyManager { X509TrustManagerImpl.checkIdentity(hostname, cert, idAlgorithm); } catch (CertificateException e) { - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("keymanager")) { SSLLogger.fine( "Certificate identity does not match " diff --git a/src/java.base/share/classes/sun/security/ssl/X509KeyManagerImpl.java b/src/java.base/share/classes/sun/security/ssl/X509KeyManagerImpl.java index c607fe0f25d..72f079db175 100644 --- a/src/java.base/share/classes/sun/security/ssl/X509KeyManagerImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/X509KeyManagerImpl.java @@ -228,7 +228,7 @@ final class X509KeyManagerImpl extends X509KeyManagerCertChecking { || (secondDot - firstDot < 2) || (alias.length() - secondDot < 2)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,keymanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,keymanager")) { SSLLogger.warning("Invalid alias format: " + alias); } return null; @@ -255,7 +255,7 @@ final class X509KeyManagerImpl extends X509KeyManagerCertChecking { NoSuchAlgorithmException | IndexOutOfBoundsException e) { // ignore and only log exception - if (SSLLogger.isOn && SSLLogger.isOn("ssl,keymanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,keymanager")) { SSLLogger.warning("Exception thrown while getting an alias " + alias + ": " + e); } @@ -295,7 +295,7 @@ final class X509KeyManagerImpl extends X509KeyManagerCertChecking { if (results != null) { for (EntryStatus status : results) { if (status.checkResult == CheckResult.OK) { - if (SSLLogger.isOn + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,keymanager")) { SSLLogger.fine("Choosing key: " + status); } @@ -312,13 +312,13 @@ final class X509KeyManagerImpl extends X509KeyManagerCertChecking { } } if (allResults == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,keymanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,keymanager")) { SSLLogger.fine("No matching key found"); } return null; } Collections.sort(allResults); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,keymanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,keymanager")) { SSLLogger.fine( "No good matching key found, " + "returning best match out of", allResults); @@ -358,13 +358,13 @@ final class X509KeyManagerImpl extends X509KeyManagerCertChecking { } } if (allResults == null || allResults.isEmpty()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,keymanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,keymanager")) { SSLLogger.fine("No matching alias found"); } return null; } Collections.sort(allResults); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,keymanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,keymanager")) { SSLLogger.fine("Getting aliases", allResults); } return toAliases(allResults); diff --git a/src/java.base/share/classes/sun/security/ssl/X509TrustManagerImpl.java b/src/java.base/share/classes/sun/security/ssl/X509TrustManagerImpl.java index d82b94a1d7d..1bbe0bfb9c7 100644 --- a/src/java.base/share/classes/sun/security/ssl/X509TrustManagerImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/X509TrustManagerImpl.java @@ -81,7 +81,7 @@ final class X509TrustManagerImpl extends X509ExtendedTrustManager this.trustedCerts = trustedCerts; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,trustmanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,trustmanager")) { SSLLogger.fine("adding as trusted certificates", (Object[])trustedCerts.toArray(new X509Certificate[0])); } @@ -98,7 +98,7 @@ final class X509TrustManagerImpl extends X509ExtendedTrustManager trustedCerts = v.getTrustedCertificates(); serverValidator = v; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,trustmanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,trustmanager")) { SSLLogger.fine("adding as trusted certificates", (Object[])trustedCerts.toArray(new X509Certificate[0])); } @@ -242,7 +242,7 @@ final class X509TrustManagerImpl extends X509ExtendedTrustManager null, checkClientTrusted ? null : authType); } - if (SSLLogger.isOn && SSLLogger.isOn("ssl,trustmanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,trustmanager")) { SSLLogger.fine("Found trusted certificate", trustedChain[trustedChain.length - 1]); } @@ -288,7 +288,7 @@ final class X509TrustManagerImpl extends X509ExtendedTrustManager null, checkClientTrusted ? null : authType); } - if (SSLLogger.isOn && SSLLogger.isOn("ssl,trustmanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,trustmanager")) { SSLLogger.fine("Found trusted certificate", trustedChain[trustedChain.length - 1]); } @@ -331,7 +331,7 @@ final class X509TrustManagerImpl extends X509ExtendedTrustManager null, checkClientTrusted ? null : authType); } - if (SSLLogger.isOn && SSLLogger.isOn("ssl,trustmanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,trustmanager")) { SSLLogger.fine("Found trusted certificate", trustedChain[trustedChain.length - 1]); } @@ -365,7 +365,7 @@ final class X509TrustManagerImpl extends X509ExtendedTrustManager hostname = new SNIHostName(sniName.getEncoded()); } catch (IllegalArgumentException iae) { // unlikely to happen, just in case ... - if (SSLLogger.isOn && SSLLogger.isOn("ssl,trustmanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,trustmanager")) { SSLLogger.fine("Illegal server name: " + sniName); } } diff --git a/src/java.base/share/classes/sun/security/util/DomainName.java b/src/java.base/share/classes/sun/security/util/DomainName.java index 4f577f1114c..465c155ab87 100644 --- a/src/java.base/share/classes/sun/security/util/DomainName.java +++ b/src/java.base/share/classes/sun/security/util/DomainName.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2025, 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 @@ -45,7 +45,6 @@ import java.util.zip.ZipInputStream; import static java.nio.charset.StandardCharsets.UTF_8; -import jdk.internal.util.StaticProperty; import sun.security.ssl.SSLLogger; /** @@ -193,7 +192,7 @@ class DomainName { } return getRules(tld, new ZipInputStream(pubSuffixStream)); } catch (IOException e) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine( "cannot parse public suffix data for " + tld + ": " + e.getMessage()); @@ -210,7 +209,7 @@ class DomainName { is = new FileInputStream(f); } catch (FileNotFoundException e) { } if (is == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl") && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl") && SSLLogger.isOn("trustmanager")) { SSLLogger.fine( "lib/security/public_suffix_list.dat not found"); @@ -231,7 +230,7 @@ class DomainName { } } if (!found) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("Domain " + tld + " not found"); } return null; diff --git a/src/java.base/share/classes/sun/security/util/HostnameChecker.java b/src/java.base/share/classes/sun/security/util/HostnameChecker.java index 1374bc6d535..65115c9aeaf 100644 --- a/src/java.base/share/classes/sun/security/util/HostnameChecker.java +++ b/src/java.base/share/classes/sun/security/util/HostnameChecker.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 2025, 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 @@ -271,7 +271,7 @@ public class HostnameChecker { name = IDN.toUnicode(IDN.toASCII(name)); template = IDN.toUnicode(IDN.toASCII(template)); } catch (RuntimeException re) { - if (SSLLogger.isOn) { + if (SSLLogger.isOn()) { SSLLogger.fine("Failed to normalize to Unicode: " + re); } @@ -308,7 +308,7 @@ public class HostnameChecker { String template, boolean chainsToPublicCA) { // not ok if it is a single wildcard character or "*." if (template.equals("*") || template.equals("*.")) { - if (SSLLogger.isOn) { + if (SSLLogger.isOn()) { SSLLogger.fine( "Certificate domain name has illegal single " + "wildcard character: " + template); @@ -328,7 +328,7 @@ public class HostnameChecker { // not ok if there is no dot after wildcard (ex: "*com") if (firstDotIndex == -1) { - if (SSLLogger.isOn) { + if (SSLLogger.isOn()) { SSLLogger.fine( "Certificate domain name has illegal wildcard, " + "no dot after wildcard character: " + template); @@ -353,7 +353,7 @@ public class HostnameChecker { // Is it a top-level domain? if (wildcardedDomain.equalsIgnoreCase(templateDomainSuffix)) { - if (SSLLogger.isOn) { + if (SSLLogger.isOn()) { SSLLogger.fine( "Certificate domain name has illegal " + "wildcard for top-level public suffix: " + template); diff --git a/test/jdk/sun/security/ssl/SSLLogger/DebugPropertyValuesTest.java b/test/jdk/sun/security/ssl/SSLLogger/DebugPropertyValuesTest.java index fbd20e588d1..f32d0e381a6 100644 --- a/test/jdk/sun/security/ssl/SSLLogger/DebugPropertyValuesTest.java +++ b/test/jdk/sun/security/ssl/SSLLogger/DebugPropertyValuesTest.java @@ -23,7 +23,7 @@ /** * @test - * @bug 8350582 8340312 8369995 + * @bug 8350582 8340312 8369995 8372004 * @library /test/lib /javax/net/ssl/templates * @summary Correct the parsing of the ssl value in javax.net.debug * @run junit DebugPropertyValuesTest @@ -91,6 +91,7 @@ public class DebugPropertyValuesTest extends SSLSocketTemplate { List.of("FINE: adding as trusted certificates:" + System.lineSeparator() + " \"certificate\" : \\{", + "sun.security.ssl.SSLSocketImpl close", "FINE: Produced ClientHello handshake message:" + System.lineSeparator() + "\"ClientHello\": \\{", From 6fc8e4998019a2f3ef05ff3e73a4c855c0366d7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Casta=C3=B1eda=20Lozano?= Date: Thu, 20 Nov 2025 09:13:57 +0000 Subject: [PATCH 144/418] 8372097: C2: PhasePrintLevel requires setting PrintPhaseLevel explicitly to be active Reviewed-by: mhaessig, chagedorn --- src/hotspot/share/opto/c2_globals.hpp | 2 +- src/hotspot/share/opto/compile.cpp | 2 +- .../compiler/oracle/TestPhasePrintLevel.java | 110 ++++++++++++++++++ 3 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/oracle/TestPhasePrintLevel.java diff --git a/src/hotspot/share/opto/c2_globals.hpp b/src/hotspot/share/opto/c2_globals.hpp index 0a4f231c49b..2b2b4db47b1 100644 --- a/src/hotspot/share/opto/c2_globals.hpp +++ b/src/hotspot/share/opto/c2_globals.hpp @@ -428,7 +428,7 @@ "0=print nothing except PhasePrintLevel directives, " \ "6=all details printed. " \ "Level of detail of printouts can be set on a per-method level " \ - "as well by using CompileCommand=PrintPhaseLevel.") \ + "as well by using CompileCommand=PhasePrintLevel.") \ range(-1, 6) \ \ develop(bool, PrintIdealGraph, false, \ diff --git a/src/hotspot/share/opto/compile.cpp b/src/hotspot/share/opto/compile.cpp index 6babc13e1b3..89b5e36b120 100644 --- a/src/hotspot/share/opto/compile.cpp +++ b/src/hotspot/share/opto/compile.cpp @@ -5233,7 +5233,7 @@ void Compile::end_method() { #ifndef PRODUCT bool Compile::should_print_phase(const int level) const { - return PrintPhaseLevel > 0 && directive()->PhasePrintLevelOption >= level && + return PrintPhaseLevel >= 0 && directive()->PhasePrintLevelOption >= level && _method != nullptr; // Do not print phases for stubs. } diff --git a/test/hotspot/jtreg/compiler/oracle/TestPhasePrintLevel.java b/test/hotspot/jtreg/compiler/oracle/TestPhasePrintLevel.java new file mode 100644 index 00000000000..cf60c5c9be2 --- /dev/null +++ b/test/hotspot/jtreg/compiler/oracle/TestPhasePrintLevel.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2025, 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.oracle; + +import java.util.ArrayList; +import java.util.List; + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +import compiler.lib.ir_framework.CompilePhase; + +/** + * @test + * @bug 8372097 + * @summary Checks that -XX:CompileCommand=PhasePrintLevel,... interacts with + * -XX:PrintPhaseLevel as expected. + * @library /test/lib / + * @requires vm.debug & vm.compiler2.enabled & vm.flagless + * @run driver compiler.oracle.TestPhasePrintLevel + */ + +public class TestPhasePrintLevel { + + static final String level1Phase = CompilePhase.FINAL_CODE.getName(); + static final String level2Phase = CompilePhase.GLOBAL_CODE_MOTION.getName(); + + public static void main(String[] args) throws Exception { + // Test flag level < 0: nothing should be printed regardless of the compile command level. + test(-1, -1, null, level1Phase); + test(-1, 0, null, level1Phase); + test(-1, 1, null, level1Phase); + + // Test flag level = 0: the compile command level should determine what is printed. + test(0, -1, null, level1Phase); + test(0, 0, null, level1Phase); + test(0, 1, level1Phase, null); + test(0, 2, level2Phase, null); + + // Test flag level > 0: the compile command level should take precedence. + test(1, -1, null, level1Phase); + test(1, 0, null, level1Phase); + test(1, 1, level1Phase, null); + test(2, 1, level1Phase, level2Phase); + test(1, 2, level2Phase, null); + } + + static void test(int flagLevel, int compileCommandLevel, String expectedPhase, String unexpectedPhase) throws Exception { + List options = new ArrayList(); + options.add("-Xbatch"); + options.add("-XX:CompileOnly=" + getTestName()); + options.add("-XX:PrintPhaseLevel=" + flagLevel); + options.add("-XX:CompileCommand=PhasePrintLevel," + getTestName() + "," + compileCommandLevel); + options.add(getTestClass()); + OutputAnalyzer oa = ProcessTools.executeTestJava(options); + oa.shouldHaveExitValue(0) + .shouldContain("CompileCommand: PhasePrintLevel compiler/oracle/TestPhasePrintLevel$TestMain.test intx PhasePrintLevel = " + compileCommandLevel) + .shouldNotContain("CompileCommand: An error occurred during parsing") + .shouldNotContain("# A fatal error has been detected by the Java Runtime Environment"); + if (expectedPhase != null) { + oa.shouldContain(expectedPhase); + } + if (unexpectedPhase != null) { + oa.shouldNotContain(unexpectedPhase); + } + } + + static String getTestClass() { + return TestMain.class.getName(); + } + + static String getTestName() { + return getTestClass() + "::test"; + } + + static class TestMain { + public static void main(String[] args) { + for (int i = 0; i < 10_000; i++) { + test(i); + } + } + + static void test(int i) { + if ((i % 1000) == 0) { + System.out.println("Hello World!"); + } + } + } +} From b41146cd1e5d412f69b893bfb2fd65b6206bb0d2 Mon Sep 17 00:00:00 2001 From: Emanuel Peter Date: Thu, 20 Nov 2025 09:32:57 +0000 Subject: [PATCH 145/418] 8367531: Template Framework: use scopes and tokens instead of misbehaving immediate-return-queries Co-authored-by: Christian Hagedorn Reviewed-by: rcastanedalo, mhaessig, chagedorn --- .../arguments/TestMethodArguments.java | 4 +- .../jtreg/compiler/igvn/ExpressionFuzzer.java | 16 +- .../lib/template_framework/AddNameToken.java | 4 + .../lib/template_framework/CodeFrame.java | 123 +- .../lib/template_framework/DataName.java | 219 +- .../compiler/lib/template_framework/Hook.java | 92 +- .../template_framework/HookAnchorToken.java | 5 +- .../template_framework/HookInsertToken.java | 6 +- .../HookIsAnchoredToken.java | 37 + .../lib/template_framework/LetToken.java | 38 + .../template_framework/NameCountToken.java | 39 + .../template_framework/NameForEachToken.java | 41 + .../template_framework/NameHasAnyToken.java | 39 + .../template_framework/NameSampleToken.java | 43 + .../lib/template_framework/NameSet.java | 1 + .../template_framework/NamesToListToken.java | 41 + .../lib/template_framework/Renderer.java | 187 +- .../{TemplateBody.java => ScopeToken.java} | 10 +- .../template_framework/ScopeTokenImpl.java | 42 + ...othingToken.java => SetFuelCostToken.java} | 5 +- .../template_framework/StructuralName.java | 161 +- .../lib/template_framework/Template.java | 510 ++-- .../lib/template_framework/TemplateFrame.java | 111 +- .../lib/template_framework/TemplateToken.java | 10 +- .../lib/template_framework/Token.java | 29 +- .../lib/template_framework/TokenParser.java | 2 +- .../library/Expression.java | 4 +- .../library/PrimitiveType.java | 4 +- .../library/TestFrameworkClass.java | 10 +- .../superword/TestAliasingFuzzer.java | 66 +- .../examples/TestAdvanced.java | 6 +- .../examples/TestExpressions.java | 4 +- .../examples/TestPrimitiveTypes.java | 31 +- .../examples/TestSimple.java | 4 +- .../examples/TestTutorial.java | 836 +++++-- .../examples/TestWithTestFrameworkClass.java | 6 +- .../tests/TestExpression.java | 8 +- .../template_framework/tests/TestFormat.java | 6 +- .../tests/TestTemplate.java | 2207 ++++++++++++++--- 39 files changed, 3988 insertions(+), 1019 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/HookIsAnchoredToken.java create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/LetToken.java create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/NameCountToken.java create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/NameForEachToken.java create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/NameHasAnyToken.java create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/NameSampleToken.java create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/NamesToListToken.java rename test/hotspot/jtreg/compiler/lib/template_framework/{TemplateBody.java => ScopeToken.java} (78%) create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/ScopeTokenImpl.java rename test/hotspot/jtreg/compiler/lib/template_framework/{NothingToken.java => SetFuelCostToken.java} (89%) diff --git a/test/hotspot/jtreg/compiler/arguments/TestMethodArguments.java b/test/hotspot/jtreg/compiler/arguments/TestMethodArguments.java index 6ff830e85c6..306d0176aad 100644 --- a/test/hotspot/jtreg/compiler/arguments/TestMethodArguments.java +++ b/test/hotspot/jtreg/compiler/arguments/TestMethodArguments.java @@ -60,7 +60,7 @@ public class TestMethodArguments { : IntStream.range(0, numberOfArguments) .mapToObj(i -> "x" + i) .collect(Collectors.joining(" + ")); - return Template.make(() -> Template.body( + return Template.make(() -> Template.scope( Template.let("type", type.name()), Template.let("boxedType", type.boxedTypeName()), Template.let("arguments", arguments), @@ -115,7 +115,7 @@ public class TestMethodArguments { tests.add(generateTest(CodeGenerationDataNameType.longs(), i / 2).asToken()); tests.add(generateTest(CodeGenerationDataNameType.doubles(), i / 2).asToken()); } - return Template.make(() -> Template.body( + return Template.make(() -> Template.scope( Template.let("classpath", comp.getEscapedClassPathOfCompiledClasses()), """ import java.util.Arrays; diff --git a/test/hotspot/jtreg/compiler/igvn/ExpressionFuzzer.java b/test/hotspot/jtreg/compiler/igvn/ExpressionFuzzer.java index 60b11e8ffbc..40bfb2e4319 100644 --- a/test/hotspot/jtreg/compiler/igvn/ExpressionFuzzer.java +++ b/test/hotspot/jtreg/compiler/igvn/ExpressionFuzzer.java @@ -45,7 +45,7 @@ import java.util.stream.IntStream; import compiler.lib.compile_framework.*; import compiler.lib.template_framework.Template; import compiler.lib.template_framework.TemplateToken; -import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.scope; import static compiler.lib.template_framework.Template.let; import static compiler.lib.template_framework.Template.$; import compiler.lib.template_framework.library.CodeGenerationDataNameType; @@ -99,7 +99,7 @@ public class ExpressionFuzzer { // Create the body for the test. We use it twice: compiled and reference. // Execute the expression and catch expected Exceptions. - var bodyTemplate = Template.make("expression", "arguments", "checksum", (Expression expression, List arguments, String checksum) -> body( + var bodyTemplate = Template.make("expression", "arguments", "checksum", (Expression expression, List arguments, String checksum) -> scope( """ try { """, @@ -167,14 +167,14 @@ public class ExpressionFuzzer { default -> throw new RuntimeException("not handled: " + type.name()); }; StringPair cmp = cmps.get(RANDOM.nextInt(cmps.size())); - return body( + return scope( ", ", cmp.s0(), type.con(), cmp.s1() ); }); // Checksum method: returns not just the value, but also does some range / bit checks. // This gives us enhanced verification on the range / bits of the result type. - var checksumTemplate = Template.make("expression", "checksum", (Expression expression, String checksum) -> body( + var checksumTemplate = Template.make("expression", "checksum", (Expression expression, String checksum) -> scope( let("returnType", expression.returnType), """ @ForceInline @@ -201,7 +201,7 @@ public class ExpressionFuzzer { // We need to prepare some random values to pass into the test method. We generate the values // once, and pass the same values into both the compiled and reference method. - var valueTemplate = Template.make("name", "type", (String name, CodeGenerationDataNameType type) -> body( + var valueTemplate = Template.make("name", "type", (String name, CodeGenerationDataNameType type) -> scope( "#type #name = ", (type instanceof PrimitiveType pt) ? pt.callLibraryRNG() : type.con(), ";\n" @@ -213,7 +213,7 @@ public class ExpressionFuzzer { // // To ensure that both the compiled and reference method use the same constraint, we put // the computation in a ForceInline method. - var constrainArgumentMethodTemplate = Template.make("name", "type", (String name, CodeGenerationDataNameType type) -> body( + var constrainArgumentMethodTemplate = Template.make("name", "type", (String name, CodeGenerationDataNameType type) -> scope( """ @ForceInline public static #type constrain_#name(#type v) { @@ -247,7 +247,7 @@ public class ExpressionFuzzer { """ )); - var constrainArgumentTemplate = Template.make("name", (String name) -> body( + var constrainArgumentTemplate = Template.make("name", (String name) -> scope( """ #name = constrain_#name(#name); """ @@ -279,7 +279,7 @@ public class ExpressionFuzzer { } } } - return body( + return scope( let("methodArguments", methodArguments.stream().map(ma -> ma.name).collect(Collectors.joining(", "))), let("methodArgumentsWithTypes", diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/AddNameToken.java b/test/hotspot/jtreg/compiler/lib/template_framework/AddNameToken.java index 4f1f7e569bf..ceb1cc263fc 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/AddNameToken.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/AddNameToken.java @@ -23,4 +23,8 @@ package compiler.lib.template_framework; +/** + * Represents the addition of the specified {@link Name} to the current scope, + * or an outer scope if the inner scope is transparent to {@link Name}s. + */ record AddNameToken(Name name) implements Token {} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/CodeFrame.java b/test/hotspot/jtreg/compiler/lib/template_framework/CodeFrame.java index 5c4ff55614f..765e9bc42ba 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/CodeFrame.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/CodeFrame.java @@ -29,22 +29,96 @@ import java.util.ArrayList; import java.util.List; /** - * The {@link CodeFrame} represents a frame (i.e. scope) of code, appending {@link Code} to the {@code 'codeList'} + * The {@link CodeFrame} represents a frame (i.e. scope) of generated code by appending {@link Code} to the {@link #codeList} * as {@link Token}s are rendered, and adding names to the {@link NameSet}s with {@link Template#addStructuralName}/ - * {@link Template#addDataName}. {@link Hook}s can be added to a frame, which allows code to be inserted at that - * location later. When a {@link Hook} is {@link Hook#anchor}ed, it separates the Template into an outer and inner - * {@link CodeFrame}, ensuring that names that are added inside the inner frame are only available inside that frame. + * {@link Template#addDataName}. {@link Hook}s can be added to a code frame, which allows code to be inserted at that + * location later. * *

    - * On the other hand, each {@link TemplateFrame} represents the frame (or scope) of exactly one use of a - * Template. + * The {@link CodeFrame} thus implements the {@link Name} non-transparency aspect of {@link ScopeToken}. * *

    - * For simple Template nesting, the {@link CodeFrame}s and {@link TemplateFrame}s overlap exactly. - * However, when using {@link Hook#insert}, we simply nest {@link TemplateFrame}s, going further "in", - * but we jump to an outer {@link CodeFrame}, ensuring that we insert {@link Code} at the outer frame, - * and operating on the names of the outer frame. Once the {@link Hook#insert}ion is complete, we jump - * back to the caller {@link TemplateFrame} and {@link CodeFrame}. + * The {@link CodeFrame}s are nested relative to the order of the final rendered code. This can + * diverge from the nesting order of the {@link Template} when using {@link Hook#insert}, where + * the execution jumps from the current (caller) {@link CodeFrame} scope to the scope of the + * {@link Hook#anchor}. This ensures that the {@link Name}s of the anchor scope are accessed, + * and not the ones from the caller scope. Once the {@link Hook#insert}ion is complete, we + * jump back to the caller {@link CodeFrame}. + * + *

    + * Note, that {@link CodeFrame}s and {@link TemplateFrame}s often go together. But they do diverge when + * we call {@link Hook#insert}. On the {@link CodeFrame} side, the inserted scope is nested in the anchoring + * scope, so that the inserted scope has access to the Names of the anchoring scope, and not the caller + * scope. But the {@link TemplateFrame} of the inserted scope is nested in the caller scope, so + * that the inserted scope has access to hashtag replacements of the caller scope, and not the + * anchoring scope. + */ + +/* + * Below, we look at an example, and show the use of CodeFrames (c) and TemplateFrames (t). + * + * Explanations: + * - Generally, every scope has a CodeFrame and a TemplateFrame. There can be multiple + * scopes inside a Template, and so there can be multiple CodeFrames and TemplateFrames. + * In the drawing below, we draw the frames vertically, and give each a unique id. + * - When we nest scopes inside scopes, we create a new CodeFrame and a new TemplateFrame, + * and so they grow the same nested structure. Example: t3 is nested inside t2 and + * c3 is nested inside c2b. + * - The exception to this: + * - At a hook.anchor, there are two CodeFrames. The first one (e.g. c2a) we call the + * hook CodeFrame, it is kept empty until we insert code to the hook. The second + * (e.g. c2b) we call the inner CodeFrame of the anchoring, into which we keep + * generating the code that is inside the scope of the hook.anchor. + * - At a hook.insert, the TemplateFrame (e.g. t4) is nested into the caller (e.g. t3), + * while the CodeFrame (e.g. c4) is nested into the anchoring CodeFrame (e.g. c2a). + * + * Template( + * t1 c1 + * t1 c1 + * t1 c1 Anchoring Scope + * t1 c1 hook.anchor(scope( + * t1 c1 t2 c2a + * t1 c1 t2 c2a <------ CodeFrame nesting--------+ + * t1 c1 t2 c2a with generated code | + * t1 c1 t2 and Names | + * t1 c1 t2 ^ | + * t1 c1 t2 +- Two CodeFramees | + * t1 c1 t2 v | + * t1 c1 t2 | + * t1 c1 t2 c2b | + * t1 c1 t2 c2b | + * t1 c1 t2 c2b Caller Scope | + * t1 c1 t2 c2b ... scope( | + * t1 c1 t2 c2b ... t3 c3 | Insertion Scope + * t1 c1 t2 c2b ... t3 c3 | hook.insert(transparentScope( + * t1 c1 t2 c2b ... t3 c3 | t4 c4 + * t1 c1 t2 c2b ... t3 c3 +---- t4 ----c4 + * t1 c1 t2 c2b ... t3 c3 t4 c4 + * t1 c1 t2 c2b ... t3 c3 <-- TemplateFrame nesting ---t4 c4 + * t1 c1 t2 c2b ... t3 c3 with hashtag t4 c4 // t: Concerns Template Frame + * t1 c1 t2 c2b ... t3 c3 and setFuelCost t4 c4 // c: Concerns Code Frame + * t1 c1 t2 c2b ... t3 c3 t4 c4 "use hashtag #x" -> t: hashtag queried in Insertion (t4) and Caller Scope (t3) + * t1 c1 t2 c2b ... t3 c3 t4 c4 c: code added to Anchoring Scope (c2a) + * t1 c1 t2 c2b ... t3 c3 t4 c4 + * t1 c1 t2 c2b ... t3 c3 t4 c4 let("x", 42) -> t: hashtag definition escapes to Caller Scope (t3) because + * t1 c1 t2 c2b ... t3 c3 t4 c4 Insertion Scope is transparent + * t1 c1 t2 c2b ... t3 c3 t4 c4 + * t1 c1 t2 c2b ... t3 c3 t4 c4 dataNames(...)...sample() -> c: sample from Insertion (c4) and Anchoring Scope (c2a) + * t1 c1 t2 c2b ... t3 c3 t4 c4 (CodeFrame nesting: c2a -> c4) + * t1 c1 t2 c2b ... t3 c3 t4 c4 addDataName(...) -> c: names escape to the Caller Scope (c3) because + * t1 c1 t2 c2b ... t3 c3 t4 c4 Insertion Scope is transparent + * t1 c1 t2 c2b ... t3 c3 t4 c4 + * t1 c1 t2 c2b ... t3 c3 )) + * t1 c1 t2 c2b ... t3 c3 + * t1 c1 t2 c2b ... t3 c3 + * t1 c1 t2 c2b ... ) + * t1 c1 t2 c2b + * t1 c1 t2 c2b + * t1 c1 )) + * t1 c1 + * t1 c1 + * ) + * */ class CodeFrame { public final CodeFrame parent; @@ -78,25 +152,16 @@ class CodeFrame { } /** - * Creates a normal frame, which has a {@link #parent} and which defines an inner - * {@link NameSet}, for the names that are generated inside this frame. Once this - * frame is exited, the name from inside this frame are not available anymore. + * Creates a normal frame, which has a {@link #parent}. It can either be + * transparent for names, meaning that names are added and accessed to and + * from an outer frame. Names that are added in a transparent frame are + * still available in the outer frames, as far out as the next non-transparent + * frame. If a frame is non-transparent, this frame defines an inner + * {@link NameSet}, for the names that are generated inside this frame. Once + * this frame is exited, the names from inside this frame are not available. */ - public static CodeFrame make(CodeFrame parent) { - return new CodeFrame(parent, false); - } - - /** - * Creates a special frame, which has a {@link #parent} but uses the {@link NameSet} - * from the parent frame, allowing {@link Template#addDataName}/ - * {@link Template#addStructuralName} to persist in the outer frame when the current frame - * is exited. This is necessary for {@link Hook#insert}, where we would possibly want to - * make field or variable definitions during the insertion that are not just local to the - * insertion but affect the {@link CodeFrame} that we {@link Hook#anchor} earlier and are - * now {@link Hook#insert}ing into. - */ - public static CodeFrame makeTransparentForNames(CodeFrame parent) { - return new CodeFrame(parent, true); + public static CodeFrame make(CodeFrame parent, boolean isTransparentForNames) { + return new CodeFrame(parent, isTransparentForNames); } void addString(String s) { diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/DataName.java b/test/hotspot/jtreg/compiler/lib/template_framework/DataName.java index f45a4db8a1e..4a82608567f 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/DataName.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/DataName.java @@ -24,6 +24,7 @@ package compiler.lib.template_framework; import java.util.List; +import java.util.function.Function; /** * {@link DataName}s represent things like fields and local variables, and can be added to the local @@ -114,18 +115,36 @@ public record DataName(String name, DataName.Type type, boolean mutable, int wei this(mutability, null, null); } + // Wrap the FilteredSet as a Predicate. + private record DataNamePredicate(FilteredSet fs) implements NameSet.Predicate { + public boolean check(Name type) { + return fs.check(type); + } + public String toString() { + return fs.toString(); + } + } + NameSet.Predicate predicate() { if (subtype == null && supertype == null) { throw new UnsupportedOperationException("Must first call 'subtypeOf', 'supertypeOf', or 'exactOf'."); } - return (Name name) -> { - if (!(name instanceof DataName dataName)) { return false; } - if (mutability == Mutability.MUTABLE && !dataName.mutable()) { return false; } - if (mutability == Mutability.IMMUTABLE && dataName.mutable()) { return false; } - if (subtype != null && !dataName.type().isSubtypeOf(subtype)) { return false; } - if (supertype != null && !supertype.isSubtypeOf(dataName.type())) { return false; } - return true; - }; + return new DataNamePredicate(this); + } + + boolean check(Name name) { + if (!(name instanceof DataName dataName)) { return false; } + if (mutability == Mutability.MUTABLE && !dataName.mutable()) { return false; } + if (mutability == Mutability.IMMUTABLE && dataName.mutable()) { return false; } + if (subtype != null && !dataName.type().isSubtypeOf(subtype)) { return false; } + if (supertype != null && !supertype.isSubtypeOf(dataName.type())) { return false; } + return true; + } + + public String toString() { + String msg1 = (subtype == null) ? "" : ", subtypeOf(" + subtype + ")"; + String msg2 = (supertype == null) ? "" : ", supertypeOf(" + supertype + ")"; + return "DataName.FilterdSet(" + mutability + msg1 + msg2 + ")"; } /** @@ -173,55 +192,179 @@ public record DataName(String name, DataName.Type type, boolean mutable, int wei /** * Samples a random {@link DataName} from the filtered set, according to the weights - * of the contained {@link DataName}s. + * of the contained {@link DataName}s, making the sampled {@link DataName} + * available to an inner scope. * - * @return The sampled {@link DataName}. - * @throws UnsupportedOperationException If the type was not constrained with either of - * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. - * @throws RendererException If the set was empty. - */ - public DataName sample() { - DataName n = (DataName)Renderer.getCurrent().sampleName(predicate()); - if (n == null) { - String msg1 = (subtype == null) ? "" : ", subtypeOf(" + subtype + ")"; - String msg2 = (supertype == null) ? "" : ", supertypeOf(" + supertype + ")"; - throw new RendererException("No variable: " + mutability + msg1 + msg2 + "."); - } - return n; - } - - /** - * Counts the number of {@link DataName}s in the filtered set. - * - * @return The number of {@link DataName}s in the filtered set. + * @param function The {@link Function} that creates the inner {@link ScopeToken} given + * the sampled {@link DataName}. + * @return a token that represents the sampling and inner scope. * @throws UnsupportedOperationException If the type was not constrained with either of * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. */ - public int count() { - return Renderer.getCurrent().countNames(predicate()); + public Token sample(Function function) { + return new NameSampleToken<>(predicate(), null, null, function); } /** - * Checks if there are any {@link DataName}s in the filtered set. + * Samples a random {@link DataName} from the filtered set, according to the weights + * of the contained {@link DataName}s, and makes a hashtag replacement for both + * the name and type of the {@link DataName}, in the current scope. * - * @return Returns {@code true} iff there is at least one {@link DataName} in the filtered set. + *

    + * Note, that the following two do the equivalent: + * + *

    + * {@snippet lang=java : + * var template = Template.make(() -> scope( + * dataNames(MUTABLE).subtypeOf(type).sampleAndLetAs("name", "type"), + * """ + * #name #type + * """ + * )); + * } + * + *

    + * {@snippet lang=java : + * var template = Template.make(() -> scope( + * dataNames(MUTABLE).subtypeOf(type).sample((DataName dn) -> transparentScope( + * // The "let" hashtag definitions escape the "transparentScope". + * let("name", dn.name()), + * let("type", dn.type()) + * )), + * """ + * #name #type + * """ + * )); + * } + * + * @param name the key of the hashtag replacement for the {@link DataName} name. + * @param type the key of the hashtag replacement for the {@link DataName} type. + * @return a token that represents the sampling and hashtag replacement definition. * @throws UnsupportedOperationException If the type was not constrained with either of * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. */ - public boolean hasAny() { - return Renderer.getCurrent().hasAnyNames(predicate()); + public Token sampleAndLetAs(String name, String type) { + return new NameSampleToken(predicate(), name, type, n -> Template.transparentScope()); } /** - * Collects all {@link DataName}s in the filtered set. + * Samples a random {@link DataName} from the filtered set, according to the weights + * of the contained {@link DataName}s, and makes a hashtag replacement for the + * name of the {@link DataName}, in the current scope. * + *

    + * Note, that the following two do the equivalent: + * + *

    + * {@snippet lang=java : + * var template = Template.make(() -> scope( + * dataNames(MUTABLE).subtypeOf(type).sampleAndLetAs("name"), + * """ + * #name + * """ + * )); + * } + * + *

    + * {@snippet lang=java : + * var template = Template.make(() -> scope( + * dataNames(MUTABLE).subtypeOf(type).sample((DataName dn) -> transparentScope( + * // The "let" hashtag definition escape the "transparentScope". + * let("name", dn.name()) + * )), + * """ + * #name + * """ + * )); + * } + * + * @param name the key of the hashtag replacement for the {@link DataName} name. + * @return a token that represents the sampling and hashtag replacement definition. + * @throws UnsupportedOperationException If the type was not constrained with either of + * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. + */ + public Token sampleAndLetAs(String name) { + return new NameSampleToken(predicate(), name, null, n -> Template.transparentScope()); + } + + /** + * Counts the number of {@link DataName}s in the filtered set, making the count + * available to an inner scope. + * + * @param function The {@link Function} that creates the inner {@link ScopeToken} given + * the count. + * @return a token that represents the counting and inner scope. + * @throws UnsupportedOperationException If the type was not constrained with either of + * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. + */ + public Token count(Function function) { + return new NameCountToken(predicate(), function); + } + + /** + * Checks if there are any {@link DataName}s in the filtered set, making the resulting boolean + * available to an inner scope. + * + * @param function The {@link Function} that creates the inner {@link ScopeToken} given + * the boolean indicating iff there are any {@link DataName}s in the filtered set. + * @return a token that represents the checking and inner scope. + * @throws UnsupportedOperationException If the type was not constrained with either of + * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. + */ + public Token hasAny(Function function) { + return new NameHasAnyToken(predicate(), function); + } + + /** + * Collects all {@link DataName}s in the filtered set, making the collected list + * available to an inner scope. + * + * @param function The {@link Function} that creates the inner {@link ScopeToken} given + * the list of {@link DataName}. * @return A {@link List} of all {@link DataName}s in the filtered set. * @throws UnsupportedOperationException If the type was not constrained with either of * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. */ - public List toList() { - List list = Renderer.getCurrent().listNames(predicate()); - return list.stream().map(n -> (DataName)n).toList(); + public Token toList(Function, ScopeToken> function) { + return new NamesToListToken<>(predicate(), function); + } + + /** + * Calls the provided {@code function} for each {@link DataName}s in the filtered set, + * making each of these {@link DataName}s available to a separate inner scope. + * + * @param function The {@link Function} that is called to create the inner {@link ScopeToken}s + * for each of the {@link DataName}s in the filtered set. + * @return The token representing the for-each execution and the respective inner scopes. + * @throws UnsupportedOperationException If the type was not constrained with either of + * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. + */ + public Token forEach(Function function) { + return new NameForEachToken<>(predicate(), null, null, function); + } + + /** + * Calls the provided {@code function} for each {@link DataName}s in the filtered set, + * making each of these {@link DataName}s available to a separate inner scope, and additionally + * setting hashtag replacements for the {@code name} and {@code type} of the respective + * {@link DataName}s. + * + *

    + * Note, to avoid duplication of the {@code name} and {@code type} + * hashtag replacements, the scope created by the provided {@code function} should be + * non-transparent to hashtag replacements, for example {@link Template#scope} or + * {@link Template#hashtagScope}. + * + * @param name the key of the hashtag replacement for each individual {@link DataName} name. + * @param type the key of the hashtag replacement for each individual {@link DataName} type. + * @param function The {@link Function} that is called to create the inner {@link ScopeToken}s + * for each of the {@link DataName}s in the filtereds set. + * @return The token representing the for-each execution and the respective inner scopes. + * @throws UnsupportedOperationException If the type was not constrained with either of + * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. + */ + public Token forEach(String name, String type, Function function) { + return new NameForEachToken<>(predicate(), name, type, function); } } } diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/Hook.java b/test/hotspot/jtreg/compiler/lib/template_framework/Hook.java index 8ee2689eb2f..ef5a5df6ce0 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/Hook.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/Hook.java @@ -23,59 +23,84 @@ package compiler.lib.template_framework; +import java.util.function.Function; + /** - * {@link Hook}s can be {@link #anchor}ed for a certain scope in a Template, and all nested - * Templates in this scope, and then from within this scope, any Template can - * {@link #insert} code to where the {@link Hook} was {@link #anchor}ed. This can be useful to reach - * "back" or to some outer scope, e.g. while generating code for a method, one can reach out - * to the class scope to insert fields. + * A {@link Hook} can be {@link #anchor}ed for a certain scope ({@link ScopeToken}), and that + * anchoring stays active for any nested scope or nested {@link Template}. With {@link #insert}, + * one can insert a template ({@link TemplateToken}) or scope ({@link ScopeToken}) to where the + * {@link Hook} was {@link #anchor}'ed. If the hook was anchored for multiple outer scopes, the + * innermost is chosen for insertion. * *

    + * This can be useful to reach "back" or to some outer scope, e.g. while generating code for a + * method, one can reach out to the class scope to insert fields. Or one may want to reach back + * to the beginning of a method to insert local variables that should be live for the whole method. + * + *

    + * The choice of {@link ScopeToken} is very important and powerful. + * For example, if you want to insert a {@link DataName} to the scope of an anchor, + * it is important that the scope of the insertion is transparent for {@link DataName}s, + * e.g. using {@link Template#transparentScope}. In most cases, we want {@link DataName}s to escape + * the inserted scope but not the anchor scope, so the anchor scope should be + * non-transparent for {@link DataName}s, e.g. using {@link Template#scope}. * Example: + * + *

    * {@snippet lang=java : * var myHook = new Hook("MyHook"); * - * var template1 = Template.make("name", (String name) -> body( - * """ - * public static int #name = 42; - * """ - * )); - * - * var template2 = Template.make(() -> body( + * var template = Template.make(() -> scope( * """ * public class Test { * """, * // Anchor the hook here. - * myHook.anchor( + * myHook.anchor(scope( * """ * public static void main(String[] args) { * System.out.println("$field: " + $field) * """, - * // Reach out to where the hook was anchored, and insert the code of template1. - * myHook.insert(template1.asToken($("field"))), + * // Reach out to where the hook was anchored, and insert some code. + * myHook.insert(transparentScope( + * // The field (DataName) escapes because the inserted scope is "transparentScope" + * addDataName($("field"), Primitives.INTS, MUTABLE), + * """ + * public static int $field = 42; + * """ + * )), * """ * } * """ - * ), + * )), * """ * } * """ * )); * } * + *

    + * Note that if we use {@link #insert} with {@link Template#transparentScope}, then + * {@link DataName}s and {@link StructuralName}s escape from the inserted scope to the + * anchor scope, but hashtag replacements and {@link Template#setFuelCost} escape to + * the caller, i.e. from where we inserted the scope. This makes sense if we consider + * {@link DataName}s belonging to the structure of the generated code and the inserted + * scope belonging to the anchor scope. On the other hand, hashtag replacements and + * {@link Template#setFuelCost} rather belong to the code generation that happens + * within the context of a template. + * * @param name The name of the Hook, for debugging purposes only. */ public record Hook(String name) { /** - * Anchor this {@link Hook} for the scope of the provided {@code 'tokens'}. + * Anchor this {@link Hook} for the provided inner scope. * From anywhere inside this scope, even in nested Templates, code can be * {@link #insert}ed back to the location where this {@link Hook} was {@link #anchor}ed. * - * @param tokens A list of tokens, which have the same restrictions as {@link Template#body}. - * @return A {@link Token} that captures the anchoring of the scope and the list of validated {@link Token}s. + * @param innerScope An inner scope, for which the {@link Hook} is anchored. + * @return A {@link Token} that captures the anchoring and the inner scope. */ - public Token anchor(Object... tokens) { - return new HookAnchorToken(this, TokenParser.parse(tokens)); + public Token anchor(ScopeToken innerScope) { + return new HookAnchorToken(this, innerScope); } /** @@ -83,18 +108,31 @@ public record Hook(String name) { * This could be in the same Template, or one nested further out. * * @param templateToken The Template with applied arguments to be inserted at the {@link Hook}. - * @return The {@link Token} which when used inside a {@link Template#body} performs the code insertion into the {@link Hook}. + * @return The {@link Token} which represents the code insertion into the {@link Hook}. */ public Token insert(TemplateToken templateToken) { - return new HookInsertToken(this, templateToken); + return new HookInsertToken(this, Template.transparentScope(templateToken)); } /** - * Checks if the {@link Hook} was {@link Hook#anchor}ed for the current scope or an outer scope. + * Inserts a scope ({@link ScopeToken}) to the innermost location where this {@link Hook} was {@link #anchor}ed. + * This could be in the same Template, or one nested further out. * - * @return If the {@link Hook} was {@link Hook#anchor}ed for the current scope or an outer scope. + * @param scopeToken The scope to be inserted at the {@link Hook}. + * @return The {@link Token} which represents the code insertion into the {@link Hook}. */ - public boolean isAnchored() { - return Renderer.getCurrent().isAnchored(this); + public Token insert(ScopeToken scopeToken) { + return new HookInsertToken(this, scopeToken); + } + + /** + * Checks if the {@link Hook} was {@link Hook#anchor}ed for the current scope or an outer scope, + * and makes the boolean result available to an inner scope. + * + * @param function the function that generates the inner scope given the boolean result. + * @return the token that represents the check and inner scope. + */ + public Token isAnchored(Function function) { + return new HookIsAnchoredToken(this, function); } } diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/HookAnchorToken.java b/test/hotspot/jtreg/compiler/lib/template_framework/HookAnchorToken.java index b025c5ff041..4979365b3d0 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/HookAnchorToken.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/HookAnchorToken.java @@ -25,4 +25,7 @@ package compiler.lib.template_framework; import java.util.List; -record HookAnchorToken(Hook hook, List tokens) implements Token {} +/** + * Represents the {@link Hook#anchor} with its inner scope. + */ +record HookAnchorToken(Hook hook, ScopeToken innerScope) implements Token {} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/HookInsertToken.java b/test/hotspot/jtreg/compiler/lib/template_framework/HookInsertToken.java index de8b60bbf24..a433d472a6e 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/HookInsertToken.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/HookInsertToken.java @@ -23,4 +23,8 @@ package compiler.lib.template_framework; -record HookInsertToken(Hook hook, TemplateToken templateToken) implements Token {} +/** + * Represents the {@link Hook#insert} with the {@link ScopeToken} of the + * scope that is to be inserted. + */ +record HookInsertToken(Hook hook, ScopeToken scopeToken) implements Token {} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/HookIsAnchoredToken.java b/test/hotspot/jtreg/compiler/lib/template_framework/HookIsAnchoredToken.java new file mode 100644 index 00000000000..5c7b92ec1fe --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/HookIsAnchoredToken.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2025, 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.lib.template_framework; + +import java.util.function.Function; + +/** + * Represents an {@link Hook#isAnchored} query with the function that creates an inner scope + * given the boolean answer. + */ +record HookIsAnchoredToken(Hook hook, Function function) implements Token { + + ScopeToken getScopeToken(boolean isAnchored) { + return function().apply(isAnchored); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/LetToken.java b/test/hotspot/jtreg/compiler/lib/template_framework/LetToken.java new file mode 100644 index 00000000000..ee18dd440b7 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/LetToken.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2025, 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.lib.template_framework; + +import java.util.function.Function; + +/** + * Represents a let (aka hashtag) definition. The hashtag replacement is active for the + * scope ({@link ScopeToken}) that the {@code function} creates, but can escape that + * scope if it is transparent to hashtags. + */ +record LetToken(String key, T value, Function function) implements Token { + + ScopeToken getScopeToken() { + return function().apply(value); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/NameCountToken.java b/test/hotspot/jtreg/compiler/lib/template_framework/NameCountToken.java new file mode 100644 index 00000000000..f0344efdd08 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/NameCountToken.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2025, 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.lib.template_framework; + +import java.util.function.Function; + +/** + * Represents the counting of {@link Name}s, and the function that is called + * to create an inner scope given the count. + */ +record NameCountToken( + NameSet.Predicate predicate, + Function function) implements Token { + + ScopeToken getScopeToken(int count) { + return function().apply(count); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/NameForEachToken.java b/test/hotspot/jtreg/compiler/lib/template_framework/NameForEachToken.java new file mode 100644 index 00000000000..0e629740be1 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/NameForEachToken.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2025, 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.lib.template_framework; + +import java.util.function.Function; + +/** + * Represents the for-each execution of the provided function and (optional) hashtag replacement + * keys for name and type of each name. + */ +record NameForEachToken( + NameSet.Predicate predicate, + String name, + String type, + Function function) implements Token { + + ScopeToken getScopeToken(Name n) { + return function().apply((N)n); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/NameHasAnyToken.java b/test/hotspot/jtreg/compiler/lib/template_framework/NameHasAnyToken.java new file mode 100644 index 00000000000..a31990af210 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/NameHasAnyToken.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2025, 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.lib.template_framework; + +import java.util.function.Function; + +/** + * Represents the check if there is any name and the function that is to + * be called given the boolean value (true iff there are any names). + */ +record NameHasAnyToken( + NameSet.Predicate predicate, + Function function) implements Token { + + ScopeToken getScopeToken(boolean hasAny) { + return function().apply(hasAny); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/NameSampleToken.java b/test/hotspot/jtreg/compiler/lib/template_framework/NameSampleToken.java new file mode 100644 index 00000000000..0b01f00fcd9 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/NameSampleToken.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2025, 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.lib.template_framework; + +import java.util.function.Function; + +/** + * Represents the sampling of {@link Name}s, and the function that is called given + * the sampled name, as well as the (optional) hashtag replacement keys for the + * name and type of the sampled name, which are then available in the inner scope + * created by the provided function. + */ +record NameSampleToken( + NameSet.Predicate predicate, + String name, + String type, + Function function) implements Token { + + ScopeToken getScopeToken(Name n) { + return function().apply((N)n); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/NameSet.java b/test/hotspot/jtreg/compiler/lib/template_framework/NameSet.java index ef79c33d48a..403dbdc694f 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/NameSet.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/NameSet.java @@ -43,6 +43,7 @@ class NameSet { interface Predicate { boolean check(Name type); + String toString(); // used when sampling fails. } NameSet(NameSet parent) { diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/NamesToListToken.java b/test/hotspot/jtreg/compiler/lib/template_framework/NamesToListToken.java new file mode 100644 index 00000000000..40710a01297 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/NamesToListToken.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2025, 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.lib.template_framework; + +import java.util.function.Function; +import java.util.List; + +/** + * Represents the {@code toList} on a filtered name set, including the collection of the + * names and the creation of the inner scope with the function. + */ +record NamesToListToken( + NameSet.Predicate predicate, + Function, ScopeToken> function) implements Token { + + ScopeToken getScopeToken(List names) { + List castNames = names.stream().map(n -> (N)n).toList(); + return function().apply(castNames); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/Renderer.java b/test/hotspot/jtreg/compiler/lib/template_framework/Renderer.java index 14adfc81d3f..61ab9ab343c 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/Renderer.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/Renderer.java @@ -76,7 +76,7 @@ final class Renderer { *

    * When using nested templates, the user of the Template Framework may be tempted to first render * the nested template to a {@link String}, and then use this {@link String} as a token in an outer - * {@link Template#body}. This would be a bad pattern: the outer and nested {@link Template} would + * {@link Template#scope}. This would be a bad pattern: the outer and nested {@link Template} would * be rendered separately, and could not interact. For example, the nested {@link Template} would * not have access to the scopes of the outer {@link Template}. The inner {@link Template} could * not access {@link Name}s and {@link Hook}s from the outer {@link Template}. The user might assume @@ -84,8 +84,8 @@ final class Renderer { * be separated. This could lead to unexpected behavior or even bugs. * *

    - * Instead, the user should create a {@link TemplateToken} from the inner {@link Template}, and - * use that {@link TemplateToken} in the {@link Template#body} of the outer {@link Template}. + * Instead, the user must create a {@link TemplateToken} from the inner {@link Template}, and + * use that {@link TemplateToken} in the {@link Template#scope} of the outer {@link Template}. * This way, the inner and outer {@link Template}s get rendered together, and the inner {@link Template} * has access to the {@link Name}s and {@link Hook}s of the outer {@link Template}. * @@ -113,7 +113,7 @@ final class Renderer { static Renderer getCurrent() { if (renderer == null) { - throw new RendererException("A Template method such as '$', 'let', 'sample', 'count' etc. was called outside a template rendering."); + throw new RendererException("A Template method such as '$', 'fuel', etc. was called outside a template rendering call."); } return renderer; } @@ -171,26 +171,6 @@ final class Renderer { return currentTemplateFrame.fuel; } - void setFuelCost(float fuelCost) { - currentTemplateFrame.setFuelCost(fuelCost); - } - - Name sampleName(NameSet.Predicate predicate) { - return currentCodeFrame.sampleName(predicate); - } - - int countNames(NameSet.Predicate predicate) { - return currentCodeFrame.countNames(predicate); - } - - boolean hasAnyNames(NameSet.Predicate predicate) { - return currentCodeFrame.hasAnyNames(predicate); - } - - List listNames(NameSet.Predicate predicate) { - return currentCodeFrame.listNames(predicate); - } - /** * Formats values to {@link String} with the goal of using them in Java code. * By default, we use the overrides of {@link Object#toString}. @@ -243,12 +223,16 @@ final class Renderer { } private void renderTemplateToken(TemplateToken templateToken) { + // We need a TemplateFrame in all cases, this ensures that the outermost scope of the template + // is not transparent for hashtags and setFuelCost, and also that the id of the template is + // unique. TemplateFrame templateFrame = TemplateFrame.make(currentTemplateFrame, nextTemplateFrameId++); currentTemplateFrame = templateFrame; templateToken.visitArguments((name, value) -> addHashtagReplacement(name, format(value))); - TemplateBody body = templateToken.instantiate(); - renderTokenList(body.tokens()); + + // If the ScopeToken is transparent to Names, then the Template is transparent to names. + renderScopeToken(templateToken.instantiate()); if (currentTemplateFrame != templateFrame) { throw new RuntimeException("Internal error: TemplateFrame mismatch!"); @@ -256,29 +240,76 @@ final class Renderer { currentTemplateFrame = currentTemplateFrame.parent; } + private void renderScopeToken(ScopeToken st) { + renderScopeToken(st, () -> {}); + } + + private void renderScopeToken(ScopeToken st, Runnable preamble) { + if (!(st instanceof ScopeTokenImpl(List tokens, + boolean isTransparentForNames, + boolean isTransparentForHashtags, + boolean isTransparentForSetFuelCost))) { + throw new RuntimeException("Internal error: could not unpack ScopeTokenImpl."); + } + + // We need the CodeFrame for local names. + CodeFrame outerCodeFrame = currentCodeFrame; + if (!isTransparentForNames) { + currentCodeFrame = CodeFrame.make(currentCodeFrame, false); + } + + // We need to be able to define local hashtag replacements, but still + // see the outer ones. We also need to have the same id for dollar + // replacement as the outer frame. And we need to be able to allow + // local setFuelCost definitions. + TemplateFrame innerTemplateFrame = null; + if (!isTransparentForHashtags || !isTransparentForSetFuelCost) { + innerTemplateFrame = TemplateFrame.makeInnerScope(currentTemplateFrame, + isTransparentForHashtags, + isTransparentForSetFuelCost); + currentTemplateFrame = innerTemplateFrame; + } + + // Allow definition of hashtags and variables to be placed in the nested frames. + preamble.run(); + + // Now render the nested code. + renderTokenList(tokens); + + if (!isTransparentForHashtags || !isTransparentForSetFuelCost) { + if (currentTemplateFrame != innerTemplateFrame) { + throw new RuntimeException("Internal error: TemplateFrame mismatch!"); + } + currentTemplateFrame = currentTemplateFrame.parent; + } + + // Tear down CodeFrame nesting. If no nesting happened, the code is already + // in the currentCodeFrame. + if (!isTransparentForNames) { + outerCodeFrame.addCode(currentCodeFrame.getCode()); + currentCodeFrame = outerCodeFrame; + } + } + private void renderToken(Token token) { switch (token) { case StringToken(String s) -> { renderStringWithDollarAndHashtagReplacements(s); } - case NothingToken() -> { - // Nothing. - } - case HookAnchorToken(Hook hook, List tokens) -> { + case HookAnchorToken(Hook hook, ScopeTokenImpl innerScope) -> { CodeFrame outerCodeFrame = currentCodeFrame; - // We need a CodeFrame to which the hook can insert code. That way, name - // definitions at the hook cannot escape the hookCodeFrame. - CodeFrame hookCodeFrame = CodeFrame.make(outerCodeFrame); + // We need a CodeFrame to which the hook can insert code. If the nested names + // are to be local, the CodeFrame must be non-transparent for names. + CodeFrame hookCodeFrame = CodeFrame.make(outerCodeFrame, innerScope.isTransparentForNames()); hookCodeFrame.addHook(hook); - // We need a CodeFrame where the tokens can be rendered. That way, name - // definitions from the tokens cannot escape the innerCodeFrame to the - // hookCodeFrame. - CodeFrame innerCodeFrame = CodeFrame.make(hookCodeFrame); + // We need a CodeFrame where the tokens can be rendered for code that is + // generated inside the anchor scope, but not inserted directly to the hook. + CodeFrame innerCodeFrame = CodeFrame.make(hookCodeFrame, innerScope.isTransparentForNames()); currentCodeFrame = innerCodeFrame; - renderTokenList(tokens); + renderScopeToken(innerScope); // Close the hookCodeFrame and innerCodeFrame. hookCodeFrame code comes before the // innerCodeFrame code from the tokens. @@ -286,20 +317,20 @@ final class Renderer { currentCodeFrame.addCode(hookCodeFrame.getCode()); currentCodeFrame.addCode(innerCodeFrame.getCode()); } - case HookInsertToken(Hook hook, TemplateToken templateToken) -> { + case HookInsertToken(Hook hook, ScopeTokenImpl scopeToken) -> { // Switch to hook CodeFrame. CodeFrame callerCodeFrame = currentCodeFrame; CodeFrame hookCodeFrame = codeFrameForHook(hook); // Use a transparent nested CodeFrame. We need a CodeFrame so that the code generated - // by the TemplateToken can be collected, and hook insertions from it can still - // be made to the hookCodeFrame before the code from the TemplateToken is added to + // by the scopeToken can be collected, and hook insertions from it can still + // be made to the hookCodeFrame before the code from the scopeToken is added to // the hookCodeFrame. // But the CodeFrame must be transparent, so that its name definitions go out to - // the hookCodeFrame, and are not limited to the CodeFrame for the TemplateToken. - currentCodeFrame = CodeFrame.makeTransparentForNames(hookCodeFrame); + // the hookCodeFrame, and are not limited to the CodeFrame for the scopeToken. + currentCodeFrame = CodeFrame.make(hookCodeFrame, true); - renderTemplateToken(templateToken); + renderScopeToken(scopeToken); hookCodeFrame.addCode(currentCodeFrame.getCode()); @@ -307,18 +338,68 @@ final class Renderer { currentCodeFrame = callerCodeFrame; } case TemplateToken templateToken -> { - // Use a nested CodeFrame. - CodeFrame callerCodeFrame = currentCodeFrame; - currentCodeFrame = CodeFrame.make(currentCodeFrame); - renderTemplateToken(templateToken); - - callerCodeFrame.addCode(currentCodeFrame.getCode()); - currentCodeFrame = callerCodeFrame; } case AddNameToken(Name name) -> { currentCodeFrame.addName(name); } + case ScopeToken scopeToken -> { + renderScopeToken(scopeToken); + } + case NameSampleToken nameScopeToken -> { + Name name = currentCodeFrame.sampleName(nameScopeToken.predicate()); + if (name == null) { + throw new RendererException("No Name found for " + nameScopeToken.predicate().toString()); + } + ScopeToken scopeToken = nameScopeToken.getScopeToken(name); + renderScopeToken(scopeToken, () -> { + if (nameScopeToken.name() != null) { + addHashtagReplacement(nameScopeToken.name(), name.name()); + } + if (nameScopeToken.type() != null) { + addHashtagReplacement(nameScopeToken.type(), name.type()); + } + }); + } + case NameForEachToken nameForEachToken -> { + List list = currentCodeFrame.listNames(nameForEachToken.predicate()); + list.stream().forEach(name -> { + ScopeToken scopeToken = nameForEachToken.getScopeToken(name); + renderScopeToken(scopeToken, () -> { + if (nameForEachToken.name() != null) { + addHashtagReplacement(nameForEachToken.name(), name.name()); + } + if (nameForEachToken.type() != null) { + addHashtagReplacement(nameForEachToken.type(), name.type()); + } + }); + }); + } + case NamesToListToken nameToListToken -> { + List list = currentCodeFrame.listNames(nameToListToken.predicate()); + renderScopeToken(nameToListToken.getScopeToken(list)); + } + case NameCountToken nameCountToken -> { + int count = currentCodeFrame.countNames(nameCountToken.predicate()); + renderScopeToken(nameCountToken.getScopeToken(count)); + } + case NameHasAnyToken nameHasAnyToken -> { + boolean hasAny = currentCodeFrame.hasAnyNames(nameHasAnyToken.predicate()); + renderScopeToken(nameHasAnyToken.getScopeToken(hasAny)); + } + case SetFuelCostToken(float fuelCost) -> { + currentTemplateFrame.setFuelCost(fuelCost); + } + case LetToken letToken -> { + ScopeToken scopeToken = letToken.getScopeToken(); + renderScopeToken(scopeToken, () -> { + addHashtagReplacement(letToken.key(), letToken.value()); + }); + } + case HookIsAnchoredToken hookIsAnchoredToken -> { + boolean isAnchored = currentCodeFrame.codeFrameForHook(hookIsAnchoredToken.hook()) != null; + renderScopeToken(hookIsAnchoredToken.getScopeToken(isAnchored)); + } } } @@ -423,10 +504,6 @@ final class Renderer { )); } - boolean isAnchored(Hook hook) { - return currentCodeFrame.codeFrameForHook(hook) != null; - } - private CodeFrame codeFrameForHook(Hook hook) { CodeFrame codeFrame = currentCodeFrame.codeFrameForHook(hook); if (codeFrame == null) { diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/TemplateBody.java b/test/hotspot/jtreg/compiler/lib/template_framework/ScopeToken.java similarity index 78% rename from test/hotspot/jtreg/compiler/lib/template_framework/TemplateBody.java rename to test/hotspot/jtreg/compiler/lib/template_framework/ScopeToken.java index 440766b3f79..f81215da36b 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/TemplateBody.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/ScopeToken.java @@ -23,12 +23,8 @@ package compiler.lib.template_framework; -import java.util.List; - /** - * A Template generates a {@link TemplateBody}, which is a list of {@link Token}s, - * which are then later rendered to {@link String}s. - * - * @param tokens The list of {@link Token}s that are later rendered to {@link String}s. + * A {@link ScopeToken} represents a scope in a {@link Template}, which can be + * created with {@link Template#scope}, {@link Template#transparentScope}, and other related methods. */ -public record TemplateBody(List tokens) {} +public sealed interface ScopeToken extends Token permits ScopeTokenImpl {} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/ScopeTokenImpl.java b/test/hotspot/jtreg/compiler/lib/template_framework/ScopeTokenImpl.java new file mode 100644 index 00000000000..df95bd56722 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/ScopeTokenImpl.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2025, 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.lib.template_framework; + +import java.util.List; + +/** + * Represents a scope with its tokens. Boolean flags indicate if names, + * hashtag replacements and {@link Template#setFuelCost} are local, or escape to + * outer scopes. + * + *

    + * Note: We want the {@link ScopeToken} to be public, but the internals of the + * record should be private. One way to solve this is with a public interface + * that exposes nothing but its name, and a private implementation via a + * record that allows easy destructuring with pattern matching. + */ +record ScopeTokenImpl(List tokens, + boolean isTransparentForNames, + boolean isTransparentForHashtags, + boolean isTransparentForSetFuelCost) implements ScopeToken, Token {} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/NothingToken.java b/test/hotspot/jtreg/compiler/lib/template_framework/SetFuelCostToken.java similarity index 89% rename from test/hotspot/jtreg/compiler/lib/template_framework/NothingToken.java rename to test/hotspot/jtreg/compiler/lib/template_framework/SetFuelCostToken.java index 540eaf1e14c..08e219b2cd9 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/NothingToken.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/SetFuelCostToken.java @@ -23,4 +23,7 @@ package compiler.lib.template_framework; -record NothingToken() implements Token {} +/** + * Represents the setting of the fuel cost in the current scope. + */ +record SetFuelCostToken(float fuelCost) implements Token {} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/StructuralName.java b/test/hotspot/jtreg/compiler/lib/template_framework/StructuralName.java index 866ac6dbfb8..8a1090bc5ab 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/StructuralName.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/StructuralName.java @@ -24,6 +24,7 @@ package compiler.lib.template_framework; import java.util.List; +import java.util.function.Function; /** * {@link StructuralName}s represent things like method and class names, and can be added to the local @@ -89,16 +90,34 @@ public record StructuralName(String name, StructuralName.Type type, int weight) this(null, null); } + // Wrap the FilteredSet as a Predicate. + private record StructuralNamePredicate(FilteredSet fs) implements NameSet.Predicate { + public boolean check(Name type) { + return fs.check(type); + } + public String toString() { + return fs.toString(); + } + } + NameSet.Predicate predicate() { if (subtype == null && supertype == null) { throw new UnsupportedOperationException("Must first call 'subtypeOf', 'supertypeOf', or 'exactOf'."); } - return (Name name) -> { - if (!(name instanceof StructuralName structuralName)) { return false; } - if (subtype != null && !structuralName.type().isSubtypeOf(subtype)) { return false; } - if (supertype != null && !supertype.isSubtypeOf(structuralName.type())) { return false; } - return true; - }; + return new StructuralNamePredicate(this); + } + + boolean check(Name name) { + if (!(name instanceof StructuralName structuralName)) { return false; } + if (subtype != null && !structuralName.type().isSubtypeOf(subtype)) { return false; } + if (supertype != null && !supertype.isSubtypeOf(structuralName.type())) { return false; } + return true; + } + + public String toString() { + String msg1 = (subtype == null) ? "" : " subtypeOf(" + subtype + ")"; + String msg2 = (supertype == null) ? "" : " supertypeOf(" + supertype + ")"; + return "StructuralName.FilteredSet(" + msg1 + msg2 + ")"; } /** @@ -146,55 +165,125 @@ public record StructuralName(String name, StructuralName.Type type, int weight) /** * Samples a random {@link StructuralName} from the filtered set, according to the weights - * of the contained {@link StructuralName}s. + * of the contained {@link StructuralName}s, making the sampled {@link StructuralName} + * available to an inner scope. * - * @return The sampled {@link StructuralName}. - * @throws UnsupportedOperationException If the type was not constrained with either of - * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. - * @throws RendererException If the set was empty. - */ - public StructuralName sample() { - StructuralName n = (StructuralName)Renderer.getCurrent().sampleName(predicate()); - if (n == null) { - String msg1 = (subtype == null) ? "" : " subtypeOf(" + subtype + ")"; - String msg2 = (supertype == null) ? "" : " supertypeOf(" + supertype + ")"; - throw new RendererException("No variable:" + msg1 + msg2 + "."); - } - return n; - } - - /** - * Counts the number of {@link StructuralName}s in the filtered set. - * - * @return The number of {@link StructuralName}s in the filtered set. + * @param function The {@link Function} that creates the inner {@link ScopeToken} given + * the sampled {@link StructuralName}. + * @return a token that represents the sampling and inner scope. * @throws UnsupportedOperationException If the type was not constrained with either of * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. */ - public int count() { - return Renderer.getCurrent().countNames(predicate()); + public Token sample(Function function) { + return new NameSampleToken<>(predicate(), null, null, function); } /** - * Checks if there are any {@link StructuralName}s in the filtered set. + * Samples a random {@link StructuralName} from the filtered set, according to the weights + * of the contained {@link StructuralName}s, and makes a hashtag replacement for both + * the name and type of the {@link StructuralName}, in the current scope. * - * @return Returns {@code true} iff there is at least one {@link StructuralName} in the filtered set. + * @param name the key of the hashtag replacement for the {@link StructuralName} name. + * @param type the key of the hashtag replacement for the {@link StructuralName} type. + * @return a token that represents the sampling and hashtag replacement definition. * @throws UnsupportedOperationException If the type was not constrained with either of * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. */ - public boolean hasAny() { - return Renderer.getCurrent().hasAnyNames(predicate()); + public Token sampleAndLetAs(String name, String type) { + return new NameSampleToken(predicate(), name, type, n -> Template.transparentScope()); } /** - * Collects all {@link StructuralName}s in the filtered set. + * Samples a random {@link StructuralName} from the filtered set, according to the weights + * of the contained {@link StructuralName}s, and makes a hashtag replacement for the + * name of the {@link StructuralName}, in the current scope. * + * @param name the key of the hashtag replacement for the {@link StructuralName} name. + * @return a token that represents the sampling and hashtag replacement definition. + * @throws UnsupportedOperationException If the type was not constrained with either of + * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. + */ + public Token sampleAndLetAs(String name) { + return new NameSampleToken(predicate(), name, null, n -> Template.transparentScope()); + } + + /** + * Counts the number of {@link StructuralName}s in the filtered set, making the count + * available to an inner scope. + * + * @param function The {@link Function} that creates the inner {@link ScopeToken} given + * the count. + * @return a token that represents the counting and inner scope. + * @throws UnsupportedOperationException If the type was not constrained with either of + * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. + */ + public Token count(Function function) { + return new NameCountToken(predicate(), function); + } + + /** + * Checks if there are any {@link StructuralName}s in the filtered set, making the resulting boolean + * available to an inner scope. + * + * @param function The {@link Function} that creates the inner {@link ScopeToken} given + * the boolean indicating iff there are any {@link StructuralName}s in the filtered set. + * @return a token that represents the checking and inner scope. + * @throws UnsupportedOperationException If the type was not constrained with either of + * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. + */ + public Token hasAny(Function function) { + return new NameHasAnyToken(predicate(), function); + } + /** + * Collects all {@link StructuralName}s in the filtered set, making the collected list + * available to an inner scope. + * + * @param function The {@link Function} that creates the inner {@link ScopeToken} given + * the list of {@link StructuralName}. * @return A {@link List} of all {@link StructuralName}s in the filtered set. * @throws UnsupportedOperationException If the type was not constrained with either of * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. */ - public List toList() { - List list = Renderer.getCurrent().listNames(predicate()); - return list.stream().map(n -> (StructuralName)n).toList(); + public Token toList(Function, ScopeToken> function) { + return new NamesToListToken<>(predicate(), function); + } + + /** + * Calls the provided {@code function} for each {@link StructuralName}s in the filtered set, + * making each of these {@link StructuralName}s available to a separate inner scope. + * + * @param function The {@link Function} that is called to create the inner {@link ScopeToken}s + * for each of the {@link StructuralName}s in the filtereds set. + * @return The token representing the for-each execution and the respective inner scopes. + * @throws UnsupportedOperationException If the type was not constrained with either of + * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. + */ + public Token forEach(Function function) { + return new NameForEachToken<>(predicate(), null, null, function); + } + + /** + * Calls the provided {@code function} for each {@link StructuralName}s in the filtered set, + * making each of these {@link StructuralName}s available to a separate inner scope, and additionally + * setting hashtag replacements for the {@code name} and {@code type} of the respective + * {@link StructuralName}s. + * + *

    + * Note, to avoid duplication of the {@code name} and {@code type} + * hashtag replacements, the scope created by the provided {@code function} should be + * non-transparent to hashtag replacements, for example {@link Template#scope} or + * {@link Template#hashtagScope}. + * + * @param name the key of the hashtag replacement for each individual {@link StructuralName} name. + * @param type the key of the hashtag replacement for each individual {@link StructuralName} type. + * @param function The {@link Function} that is called to create the inner {@link ScopeToken}s + * for each of the {@link StructuralName}s in the filtereds set. + * @return The token representing the for-each execution and the respective inner scopes. + * @throws UnsupportedOperationException If the type was not constrained with either of + * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. + */ + public Token forEach(String name, String type, Function function) { + return new NameForEachToken<>(predicate(), name, type, function); } } } diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/Template.java b/test/hotspot/jtreg/compiler/lib/template_framework/Template.java index 57d06e732bb..f245cda0501 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/Template.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/Template.java @@ -65,7 +65,7 @@ import compiler.lib.ir_framework.TestFramework; * *

    * {@snippet lang=java : - * var testTemplate = Template.make("typeName", "operator", "generator", (String typeName, String operator, MyGenerator generator) -> body( + * var testTemplate = Template.make("typeName", "operator", "generator", (String typeName, String operator, MyGenerator generator) -> scope( * let("con1", generator.next()), * let("con2", generator.next()), * """ @@ -86,13 +86,13 @@ import compiler.lib.ir_framework.TestFramework; * } * *

    - * To get an executable test, we define a {@link Template} that produces a class body with a main method. The Template + * To get an executable test, we define a {@link Template} that produces a class scope with a main method. The Template * takes a list of types, and calls the {@code testTemplate} defined above for each type and operator. We use * the {@link TestFramework} to call our {@code @Test} methods. * *

    * {@snippet lang=java : - * var classTemplate = Template.make("types", (List types) -> body( + * var classTemplate = Template.make("types", (List types) -> scope( * let("classpath", comp.getEscapedClassPathOfCompiledClasses()), * """ * package p.xyz; @@ -148,12 +148,12 @@ import compiler.lib.ir_framework.TestFramework; * {@link Template#make(String, Function)}. For each number of arguments there is an implementation * (e.g. {@link Template.TwoArgs} for two arguments). This allows the use of generics for the * {@link Template} argument types which enables type checking of the {@link Template} arguments. - * It is currently only allowed to use up to three arguments. + * It is currently only allowed to use up to three arguments. * *

    * A {@link Template} can be rendered to a {@link String} (e.g. {@link Template.ZeroArgs#render()}). * Alternatively, we can generate a {@link Token} (more specifically, a {@link TemplateToken}) with {@code asToken()} - * (e.g. {@link Template.ZeroArgs#asToken()}), and use the {@link Token} inside another {@link Template#body}. + * (e.g. {@link Template.ZeroArgs#asToken()}), and use the {@link Token} inside another {@link Template#scope}. * *

    * Ideally, we would have used string templates to inject these Template @@ -161,6 +161,11 @@ import compiler.lib.ir_framework.TestFramework; * hashtag replacements in the {@link String}s: the Template argument names are captured, and * the argument values automatically replace any {@code "#name"} in the {@link String}s. See the different overloads * of {@link #make} for examples. Additional hashtag replacements can be defined with {@link #let}. + * We have decided to keep hashtag replacements constrained to the scope of one Template. They + * do not escape to outer or inner Template uses. If one needs to pass values to inner Templates, + * this can be done with Template arguments. Keeping hashtag replacements local to Templates + * has the benefit that there is no conflict in recursive templates, where outer and inner Templates + * define the same hashtag replacement. * *

    * When using nested Templates, there can be collisions with identifiers (e.g. variable names and method names). @@ -176,25 +181,6 @@ import compiler.lib.ir_framework.TestFramework; * {@code #{name}}. * *

    - * A {@link TemplateToken} cannot just be used in {@link Template#body}, but it can also be - * {@link Hook#insert}ed to where a {@link Hook} was {@link Hook#anchor}ed earlier (in some outer scope of the code). - * For example, while generating code in a method, one can reach out to the scope of the class, and insert a - * new field, or define a utility method. - * - *

    - * A {@link TemplateBinding} allows the recursive use of Templates. With the indirection of such a binding, - * a Template can reference itself. - * - *

    - * The writer of recursive {@link Template}s must ensure that this recursion terminates. To unify the - * approach across {@link Template}s, we introduce the concept of {@link #fuel}. Templates are rendered starting - * with a limited amount of {@link #fuel} (default: 100, see {@link #DEFAULT_FUEL}), which is decreased at each - * Template nesting by a certain amount (default: 10, see {@link #DEFAULT_FUEL_COST}). The default fuel for a - * template can be changed when we {@code render()} it (e.g. {@link ZeroArgs#render(float)}) and the default - * fuel cost with {@link #setFuelCost}) when defining the {@link #body(Object...)}. Recursive templates are - * supposed to terminate once the {@link #fuel} is depleted (i.e. reaches zero). - * - *

    * Code generation can involve keeping track of fields and variables, as well as the scopes in which they * are available, and if they are mutable or immutable. We model fields and variables with {@link DataName}s, * which we can add to the current scope with {@link #addDataName}. We can access the {@link DataName}s with @@ -211,61 +197,70 @@ import compiler.lib.ir_framework.TestFramework; * are not concerned about mutability. * *

    - * When working with {@link DataName}s and {@link StructuralName}s, it is important to be aware of the - * relevant scopes, as well as the execution order of the {@link Template} lambdas and the evaluation - * of the {@link Template#body} tokens. When a {@link Template} is rendered, its lambda is invoked. In the - * lambda, we generate the tokens, and create the {@link Template#body}. Once the lambda returns, the - * tokens are evaluated one by one. While evaluating the tokens, the {@link Renderer} might encounter a nested - * {@link TemplateToken}, which in turn triggers the evaluation of that nested {@link Template}, i.e. - * the evaluation of its lambda and later the evaluation of its tokens. It is important to keep in mind - * that the lambda is always executed first, and the tokens are evaluated afterwards. A method like - * {@code dataNames(MUTABLE).exactOf(type).count()} is a method that is executed during the evaluation - * of the lambda. But a method like {@link #addDataName} returns a token, and does not immediately add - * the {@link DataName}. This ensures that the {@link DataName} is only inserted when the tokens are - * evaluated, so that it is inserted at the exact scope where we would expect it. + * Code generation can involve keeping track of scopes in the code (e.g. liveness and availability of + * {@link DataName}s) and of the hashtag replacements in the templates. The {@link ScopeToken} serves + * this purpose, and allows the definition of transparent scopes (e.g. {@link #transparentScope}) and + * non-transparent scopes (e.g. {@link #scope}). + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    Scopes and (non-)transparency
    hashtag {@link DataName} and {@link StructuralName} {@link #setFuelCost}
    {@link #scope} non-transparent non-transparent non-transparent
    {@link #hashtagScope} non-transparent transparent transparent
    {@link #nameScope} transparent non-transparent transparent
    {@link #setFuelCostScope} transparent transparent non-transparent
    {@link #transparentScope} transparent transparent transparent
    * *

    - * Let us look at the following example to better understand the execution order. + * In some cases, we may be deeper nested in templates and scopes, and would like to reach "back" or + * to outer scopes. This is possible with {@link Hook#anchor}ing in some outer scope, and later + * {@link Hook#insert}ing from an inner scope to the scope of the anchoring. For example, while + * generating code in a method, one can reach out to the scope of the class, and insert a new field, + * or define a utility method. * *

    - * {@snippet lang=java : - * var testTemplate = Template.make(() -> body( - * // The lambda has just been invoked. - * // We count the DataNames and assign the count to the hashtag replacement "c1". - * let("c1", dataNames(MUTABLE).exactOf(someType).count()), - * // We want to define a DataName "v1", and create a token for it. - * addDataName($("v1"), someType, MUTABLE), - * // We count the DataNames again, but the count does NOT change compared to "c1". - * // This is because the token for "v1" is only evaluated later. - * let("c2", dataNames(MUTABLE).exactOf(someType).count()), - * // Create a nested scope. - * METHOD_HOOK.anchor( - * // We want to define a DataName "v2", which is only valid inside this - * // nested scope. - * addDataName($("v2"), someType, MUTABLE), - * // The count is still not different to "c1". - * let("c3", dataNames(MUTABLE).exactOf(someType).count()), - * // We nest a Template. This creates a TemplateToken, which is later evaluated. - * // By the time the TemplateToken is evaluated, the tokens from above will - * // be already evaluated. Hence, "v1" and "v2" are added by then, and if the - * // "otherTemplate" were to count the DataNames, the count would be increased - * // by 2 compared to "c1". - * otherTemplate.asToken() - * ), - * // After closing the scope, "v2" is no longer available. - * // The count is still the same as "c1", since "v1" is still only a token. - * let("c4", dataNames(MUTABLE).exactOf(someType).count()), - * // We nest another Template. Again, this creates a TemplateToken, which is only - * // evaluated later. By that time, the token for "v1" is evaluated, and so the - * // nested Template would observe an increment in the count. - * anotherTemplate.asToken() - * // By this point, all methods are called, and the tokens generated. - * // The lambda returns the "body", which is all of the tokens that we just - * // generated. After returning from the lambda, the tokens will be evaluated - * // one by one. - * )); - * } - + * A {@link TemplateBinding} allows the recursive use of Templates. With the indirection of such a binding, + * a Template can reference itself. + * + *

    + * The writer of recursive {@link Template}s must ensure that this recursion terminates. To unify the + * approach across {@link Template}s, we introduce the concept of {@link #fuel}. Templates are rendered starting + * with a limited amount of {@link #fuel} (default: 100, see {@link #DEFAULT_FUEL}), which is decreased at each + * Template nesting by a certain amount (default: 10, see {@link #DEFAULT_FUEL_COST}). The default fuel for a + * template can be changed when we {@code render()} it (e.g. {@link ZeroArgs#render(float)}) and the default + * fuel cost with {@link #setFuelCost}) when defining the {@link #scope(Object...)}. Recursive templates are + * supposed to terminate once the {@link #fuel} is depleted (i.e. reaches zero). + * + *

    + * A note from the implementor to the user: We have decided to implement the Template Framework using + * a functional (lambdas) and data-oriented (tokens) model. The consequence is that there are three + * orders in template rendering: (1) the execution order in lambdas, where we usually assemble the + * tokens and pass them to some scope ({@link ScopeToken}) as arguments. (2) the token evaluation + * order, which occurs in the order of how tokens are listed in a scope. By design, the token order + * is the same order as execution in lambdas. To keep the lambda and token order in sync, most of the + * queries about the state of code generation, such as {@link DataName}s and {@link Hook}s cannot + * return the values immediately, but have to be expressed as tokens. If we had a mix of tokens and + * immediate queries, then the immediate queries would "float" by the tokens, because the immediate + * queries are executed during the lambda execution, but the tokens are only executed later. Having + * to express everything as tokens can be a little more cumbersome (e.g. sample requires a lambda + * that captures the {@link DataName}, and sample does not return the {@link DataName} directly). + * But this ensures that reasoning about execution order is relatively straight forward, namely in + * the order of the specified tokens. (3) the final code order is the same as the lambda and token + * order, except when using {@link Hook#insert}, which places the code at the innermost {@link Hook#anchor}. + * *

    * More examples for these functionalities can be found in {@code TestTutorial.java}, {@code TestSimple.java}, * and {@code TestAdvanced.java}, which all produce compilable Java code. Additional examples can be found in @@ -281,10 +276,10 @@ public sealed interface Template permits Template.ZeroArgs, /** * A {@link Template} with no arguments. * - * @param function The {@link Supplier} that creates the {@link TemplateBody}. + * @param function The {@link Supplier} that creates the {@link ScopeToken}. */ - record ZeroArgs(Supplier function) implements Template { - TemplateBody instantiate() { + record ZeroArgs(Supplier function) implements Template { + ScopeToken instantiate() { return function.get(); } @@ -324,10 +319,10 @@ public sealed interface Template permits Template.ZeroArgs, * * @param arg1Name The name of the (first) argument, used for hashtag replacements in the {@link Template}. * @param The type of the (first) argument. - * @param function The {@link Function} that creates the {@link TemplateBody} given the template argument. + * @param function The {@link Function} that creates the {@link ScopeToken} given the template argument. */ - record OneArg(String arg1Name, Function function) implements Template { - TemplateBody instantiate(T1 arg1) { + record OneArg(String arg1Name, Function function) implements Template { + ScopeToken instantiate(T1 arg1) { return function.apply(arg1); } @@ -372,10 +367,10 @@ public sealed interface Template permits Template.ZeroArgs, * @param arg2Name The name of the second argument, used for hashtag replacements in the {@link Template}. * @param The type of the first argument. * @param The type of the second argument. - * @param function The {@link BiFunction} that creates the {@link TemplateBody} given the template arguments. + * @param function The {@link BiFunction} that creates the {@link ScopeToken} given the template arguments. */ - record TwoArgs(String arg1Name, String arg2Name, BiFunction function) implements Template { - TemplateBody instantiate(T1 arg1, T2 arg2) { + record TwoArgs(String arg1Name, String arg2Name, BiFunction function) implements Template { + ScopeToken instantiate(T1 arg1, T2 arg2) { return function.apply(arg1, arg2); } @@ -447,10 +442,10 @@ public sealed interface Template permits Template.ZeroArgs, * @param The type of the first argument. * @param The type of the second argument. * @param The type of the third argument. - * @param function The function with three arguments that creates the {@link TemplateBody} given the template arguments. + * @param function The function with three arguments that creates the {@link ScopeToken} given the template arguments. */ - record ThreeArgs(String arg1Name, String arg2Name, String arg3Name, TriFunction function) implements Template { - TemplateBody instantiate(T1 arg1, T2 arg2, T3 arg3) { + record ThreeArgs(String arg1Name, String arg2Name, String arg3Name, TriFunction function) implements Template { + ScopeToken instantiate(T1 arg1, T2 arg2, T3 arg3) { return function.apply(arg1, arg2, arg3); } @@ -496,28 +491,28 @@ public sealed interface Template permits Template.ZeroArgs, /** * Creates a {@link Template} with no arguments. - * See {@link #body} for more details about how to construct a Template with {@link Token}s. + * See {@link #scope} for more details about how to construct a Template with {@link Token}s. * *

    * Example: * {@snippet lang=java : - * var template = Template.make(() -> body( + * var template = Template.make(() -> scope( * """ * Multi-line string or other tokens. * """ * )); * } * - * @param body The {@link TemplateBody} created by {@link Template#body}. + * @param scope The {@link ScopeToken} created by {@link Template#scope}. * @return A {@link Template} with zero arguments. */ - static Template.ZeroArgs make(Supplier body) { - return new Template.ZeroArgs(body); + static Template.ZeroArgs make(Supplier scope) { + return new Template.ZeroArgs(scope); } /** * Creates a {@link Template} with one argument. - * See {@link #body} for more details about how to construct a Template with {@link Token}s. + * See {@link #scope} for more details about how to construct a Template with {@link Token}s. * Good practice but not enforced but not enforced: {@code arg1Name} should match the lambda argument name. * *

    @@ -525,7 +520,7 @@ public sealed interface Template permits Template.ZeroArgs, * for use in hashtag replacements, and captured once as lambda argument with the corresponding type * of the generic argument. * {@snippet lang=java : - * var template = Template.make("a", (Integer a) -> body( + * var template = Template.make("a", (Integer a) -> scope( * """ * Multi-line string or other tokens. * We can use the hashtag replacement #a to directly insert the String value of a. @@ -534,18 +529,18 @@ public sealed interface Template permits Template.ZeroArgs, * )); * } * - * @param body The {@link TemplateBody} created by {@link Template#body}. + * @param scope The {@link ScopeToken} created by {@link Template#scope}. * @param Type of the (first) argument. * @param arg1Name The name of the (first) argument for hashtag replacement. * @return A {@link Template} with one argument. */ - static Template.OneArg make(String arg1Name, Function body) { - return new Template.OneArg<>(arg1Name, body); + static Template.OneArg make(String arg1Name, Function scope) { + return new Template.OneArg<>(arg1Name, scope); } /** * Creates a {@link Template} with two arguments. - * See {@link #body} for more details about how to construct a Template with {@link Token}s. + * See {@link #scope} for more details about how to construct a Template with {@link Token}s. * Good practice but not enforced: {@code arg1Name} and {@code arg2Name} should match the lambda argument names. * *

    @@ -553,7 +548,7 @@ public sealed interface Template permits Template.ZeroArgs, * for use in hashtag replacements, and captured once as lambda arguments with the corresponding types * of the generic arguments. * {@snippet lang=java : - * var template = Template.make("a", "b", (Integer a, String b) -> body( + * var template = Template.make("a", "b", (Integer a, String b) -> scope( * """ * Multi-line string or other tokens. * We can use the hashtag replacement #a and #b to directly insert the String value of a and b. @@ -562,23 +557,23 @@ public sealed interface Template permits Template.ZeroArgs, * )); * } * - * @param body The {@link TemplateBody} created by {@link Template#body}. + * @param scope The {@link ScopeToken} created by {@link Template#scope}. * @param Type of the first argument. * @param arg1Name The name of the first argument for hashtag replacement. * @param Type of the second argument. * @param arg2Name The name of the second argument for hashtag replacement. * @return A {@link Template} with two arguments. */ - static Template.TwoArgs make(String arg1Name, String arg2Name, BiFunction body) { - return new Template.TwoArgs<>(arg1Name, arg2Name, body); + static Template.TwoArgs make(String arg1Name, String arg2Name, BiFunction scope) { + return new Template.TwoArgs<>(arg1Name, arg2Name, scope); } /** * Creates a {@link Template} with three arguments. - * See {@link #body} for more details about how to construct a Template with {@link Token}s. + * See {@link #scope} for more details about how to construct a Template with {@link Token}s. * Good practice but not enforced: {@code arg1Name}, {@code arg2Name}, and {@code arg3Name} should match the lambda argument names. * - * @param body The {@link TemplateBody} created by {@link Template#body}. + * @param scope The {@link ScopeToken} created by {@link Template#scope}. * @param Type of the first argument. * @param arg1Name The name of the first argument for hashtag replacement. * @param Type of the second argument. @@ -587,18 +582,35 @@ public sealed interface Template permits Template.ZeroArgs, * @param arg3Name The name of the third argument for hashtag replacement. * @return A {@link Template} with three arguments. */ - static Template.ThreeArgs make(String arg1Name, String arg2Name, String arg3Name, Template.TriFunction body) { - return new Template.ThreeArgs<>(arg1Name, arg2Name, arg3Name, body); + static Template.ThreeArgs make(String arg1Name, String arg2Name, String arg3Name, Template.TriFunction scope) { + return new Template.ThreeArgs<>(arg1Name, arg2Name, arg3Name, scope); } /** - * Creates a {@link TemplateBody} from a list of tokens, which can be {@link String}s, - * boxed primitive types (for example {@link Integer} or auto-boxed {@code int}), any {@link Token}, - * or {@link List}s of any of these. + * Creates a {@link ScopeToken} that represents a scope that is completely + * non-transparent, not allowing anything to escape. This + * means that no {@link DataName}, {@link StructuralName}s, hashtag-replacement + * or {@link #setFuelCost} defined inside the scope is available outside. All + * these usages are only local to the defining scope here. + * + *

    + * The scope is formed from a list of tokens, which can be {@link String}s, + * boxed primitive types (for example {@link Integer} or auto-boxed {@code int}), + * any {@link Token}, or {@link List}s of any of these. + * + *

    + * If you require a scope that is either fully transparent (i.e. everything escapes) + * or only restricts a specific kind to not escape, consider using one of the other + * provided scopes: {@link #transparentScope}, {@link #nameScope}, {@link #hashtagScope}, + * or {@link #setFuelCostScope}. A "scope-transparency-matrix" can also be found in + * the interface comment for {@link Template}. + * + *

    + * The most common use of {@link #scope} is in the construction of templates: * *

    * {@snippet lang=java : - * var template = Template.make(() -> body( + * var template = Template.make(() -> scope( * """ * Multi-line string * """, @@ -608,14 +620,200 @@ public sealed interface Template permits Template.ZeroArgs, * )); * } * + *

    + * Note that regardless of the chosen scope for {@code Template.make}, + * hashtag-replacements and {@link #setFuelCost} are always implicitly + * non-transparent (i.e. non-escaping). For example, {@link #let} will + * not escape the template scope even when using {@link #transparentScope}. + * As a default, it is recommended to use {@link #scope} for + * {@code Template.make} since in most cases template scopes align with + * code scopes that are non-transparent for fields, variables, etc. In + * rare cases, where the scope of the template needs to be transparent + * (e.g. because we need to insert a variable or field into an outer scope), + * it is recommended to use {@link #transparentScope}. This allows to make + * {@link DataName}s and {@link StructuralName}s available outside this + * template crossing the template boundary. + * + *

    + * We can also use nested scopes inside of templates: + * + *

    + * {@snippet lang=java : + * var template = Template.make(() -> scope( + * // CODE1: some code in the outer scope + * scope( + * // CODE2: some code in the inner scope. Names, hashtags and setFuelCost + * // do not escape the inner scope. + * ), + * // CODE3: more code in the outer scope, names and hashtags from CODE2 are + * // not available anymore because of the non-transparent "scope". + * transparentScope( + * // CODE4: some code in the inner "transparentScope". Names, hashtags and setFuelCost + * // escape the "transparentScope" and are still available after the "transparentScope" + * // closes. + * ) + * // CODE5: we still have access to names and hashtags from CODE4. + * )); + * } + * * @param tokens A list of tokens, which can be {@link String}s, boxed primitive types * (for example {@link Integer}), any {@link Token}, or {@link List}s * of any of these. - * @return The {@link TemplateBody} which captures the list of validated {@link Token}s. + * @return The {@link ScopeToken} which captures the list of validated {@link Token}s. * @throws IllegalArgumentException if the list of tokens contains an unexpected object. */ - static TemplateBody body(Object... tokens) { - return new TemplateBody(TokenParser.parse(tokens)); + static ScopeToken scope(Object... tokens) { + return new ScopeTokenImpl(TokenParser.parse(tokens), false, false, false); + } + + /** + * Creates a {@link ScopeToken} that represents a completely transparent scope. + * This means that {@link DataName}s, {@link StructuralName}s, + * hashtag-replacements and {@link #setFuelCost} declared inside the scope will be available + * in the outer scope. + * The scope is formed from a list of tokens, which can be {@link String}s, + * boxed primitive types (for example {@link Integer} or auto-boxed {@code int}), + * any {@link Token}, or {@link List}s of any of these. + * + *

    + * If you require a scope that is non-transparent (i.e. nothing escapes) or only restricts + * a specific kind to not escape, consider using one of the other provided scopes: + * {@link #scope}, {@link #nameScope}, {@link #hashtagScope}, or {@link #setFuelCostScope}. + * A "scope-transparency-matrix" can also be found in the interface comment for {@link Template}. + * + * @param tokens A list of tokens, which can be {@link String}s, boxed primitive types + * (for example {@link Integer}), any {@link Token}, or {@link List}s + * of any of these. + * @return The {@link ScopeToken} which captures the list of validated {@link Token}s. + * @throws IllegalArgumentException if the list of tokens contains an unexpected object. + */ + static ScopeToken transparentScope(Object... tokens) { + return new ScopeTokenImpl(TokenParser.parse(tokens), true, true, true); + } + + /** + * Creates a {@link ScopeToken} that represents a scope that is non-transparent for + * {@link DataName}s and {@link StructuralName}s (i.e. cannot escape), but + * transparent for hashtag-replacements and {@link #setFuelCost} (i.e. available + * in outer scope). + * + *

    + * The scope is formed from a list of tokens, which can be {@link String}s, + * boxed primitive types (for example {@link Integer} or auto-boxed {@code int}), + * any {@link Token}, or {@link List}s of any of these. + * + *

    + * If you require a scope that is transparent or uses a different restriction, consider + * using one of the other provided scopes: {@link #scope}, {@link #transparentScope}, + * {@link #hashtagScope}, or {@link #setFuelCostScope}. A "scope-transparency-matrix" can + * also be found in the interface comment for {@link Template}. + * + * @param tokens A list of tokens, which can be {@link String}s, boxed primitive types + * (for example {@link Integer}), any {@link Token}, or {@link List}s + * of any of these. + * @return The {@link ScopeToken} which captures the list of validated {@link Token}s. + * @throws IllegalArgumentException if the list of tokens contains an unexpected object. + */ + static ScopeToken nameScope(Object... tokens) { + return new ScopeTokenImpl(TokenParser.parse(tokens), false, true, true); + } + + /** + * Creates a {@link ScopeToken} that represents a scope that is non-transparent for + * hashtag-replacements (i.e. cannot escape), but transparent for {@link DataName}s + * and {@link StructuralName}s and {@link #setFuelCost} (i.e. available in outer scope). + * + *

    + * The scope is formed from a list of tokens, which can be {@link String}s, + * boxed primitive types (for example {@link Integer} or auto-boxed {@code int}), + * any {@link Token}, or {@link List}s of any of these. + * + *

    + * If you require a scope that is transparent or uses a different restriction, consider + * using one of the other provided scopes: {@link #scope}, {@link #transparentScope}, + * {@link #nameScope}, or {@link #setFuelCostScope}. A "scope-transparency-matrix" can + * also be found in the interface comment for {@link Template}. + * + *

    + * Keeping hashtag-replacements local but letting {@link DataName}s escape can be + * useful in cases like the following, where we may want to reuse the hashtag + * multiple times: + * + *

    + * {@snippet lang=java : + * var template = Template.make(() -> scope( + * List.of("a", "b", "c").stream().map(name -> hashtagScope( + * let("name", name), // assumes values: a, b, c + * addDataName(name, PrimitiveType.INTS, MUTABLE), // escapes + * """ + * int #name = 42; + * """ + * )) + * // We still have access to the three DataNames. + * )); + * } + * + * @param tokens A list of tokens, which can be {@link String}s, boxed primitive types + * (for example {@link Integer}), any {@link Token}, or {@link List}s + * of any of these. + * @return The {@link ScopeToken} which captures the list of validated {@link Token}s. + * @throws IllegalArgumentException if the list of tokens contains an unexpected object. + */ + static ScopeToken hashtagScope(Object... tokens) { + return new ScopeTokenImpl(TokenParser.parse(tokens), true, false, true); + } + + /** + * Creates a {@link ScopeToken} that represents a scope that is non-transparent for + * {@link #setFuelCost} (i.e. cannot escape), but transparent for hashtag-replacements, + * {@link DataName}s and {@link StructuralName}s (i.e. available in outer scope). + * The scope is formed from a list of tokens, which can be {@link String}s, + * boxed primitive types (for example {@link Integer} or auto-boxed {@code int}), + * any {@link Token}, or {@link List}s of any of these. + * + *

    + * If you require a scope that is transparent or uses a different restriction, consider + * using one of the other provided scopes: {@link #scope}, {@link #transparentScope}, + * {@link #hashtagScope}, or {@link #nameScope}. A "scope-transparency-matrix" can + * also be found in the interface comment for {@link Template}. + * + *

    + * In some cases, it can be helpful to have different {@link #setFuelCost} within + * a single template, depending on the code nesting depth. Example: + * + *

    + * {@snippet lang=java : + * var template = Template.make(() -> scope( + * setFuelCost(1), + * // CODE1: some shallow code, allowing recursive template uses here + * // to use more fuel. + * """ + * for (int i = 0; i < 1000; i++) { + * """, + * setFuelCostScope( + * setFuelCost(100) + * // CODE2: with the for-loop, we already have a deeper nesting + * // depth, and recursive template uses should not get + * // as much fuel as in CODE1. + * ), + * """ + * } + * """ + * // CODE3: we are back in the outer scope of CODE1, and can use + * // more fuel again in nested template uses. setFuelCost + * // is automatically restored to what was set before the + * // inner scope. + * )); + * } + * + * @param tokens A list of tokens, which can be {@link String}s, boxed primitive types + * (for example {@link Integer}), any {@link Token}, or {@link List}s + * of any of these. + * @return The {@link ScopeToken} which captures the list of validated {@link Token}s. + * @throws IllegalArgumentException if the list of tokens contains an unexpected object. + */ + static ScopeToken setFuelCostScope(Object... tokens) { + return new ScopeTokenImpl(TokenParser.parse(tokens), true, true, false); } /** @@ -628,7 +826,7 @@ public sealed interface Template permits Template.ZeroArgs, * with an implicit dollar replacement, and then captures that dollar replacement * using {@link #$} for the use inside a nested template. * {@snippet lang=java : - * var template = Template.make(() -> body( + * var template = Template.make(() -> scope( * """ * int $var = 42; * """, @@ -640,6 +838,9 @@ public sealed interface Template permits Template.ZeroArgs, * @return The dollar replacement for the {@code 'name'}. */ static String $(String name) { + // Note, since the dollar replacements do not change within a template + // and the retrieval has no side effects, we can return the value immediately, + // and do not need a token. return Renderer.getCurrent().$(name); } @@ -648,7 +849,7 @@ public sealed interface Template permits Template.ZeroArgs, * *

    * {@snippet lang=java : - * var template = Template.make("a", (Integer a) -> body( + * var template = Template.make("a", (Integer a) -> scope( * let("b", a * 5), * """ * System.out.println("Use a and b with hashtag replacement: #a and #b"); @@ -656,41 +857,50 @@ public sealed interface Template permits Template.ZeroArgs, * )); * } * + *

    + * Note that a {@code let} definition makes the hashtag replacement available + * for anything that follows it, until the the end of the next outer scope + * that is non-transparent for hashtag replacements. Additionally, hashtag + * replacements are limited to the template they were defined in. + * If you want to pass values from an outer to an inner template, this cannot + * be done with hashtags directly. Instead, one has to pass the values via + * template arguments. + * * @param key Name for the hashtag replacement. * @param value The value that the hashtag is replaced with. - * @return A token that does nothing, so that the {@link #let} can easily be put in a list of tokens - * inside a {@link Template#body}. - * @throws RendererException if there is a duplicate hashtag {@code key}. + * @return A token that represents the hashtag replacement definition. */ static Token let(String key, Object value) { - Renderer.getCurrent().addHashtagReplacement(key, value); - return new NothingToken(); + return new LetToken(key, value, v -> transparentScope()); } /** * Define a hashtag replacement for {@code "#key"}, with a specific value, which is also captured - * by the provided {@code function} with type {@code }. + * by the provided {@code function} with type {@code }. While the argument of the lambda that + * captures the value is naturally bounded to the scope of the lambda, the hashtag replacement + * may be bound to the scope or escape it, depending on the choice of scope, see {@link #scope} + * and {@link #transparentScope}. * *

    * {@snippet lang=java : - * var template = Template.make("a", (Integer a) -> let("b", a * 2, (Integer b) -> body( - * """ - * System.out.println("Use a and b with hashtag replacement: #a and #b"); - * """, - * "System.out.println(\"Use a and b as capture variables:\"" + a + " and " + b + ");\n" - * ))); + * var template = Template.make("a", (Integer a) -> scope( + * let("b", a * 2, (Integer b) -> scope( + * """ + * System.out.println("Use a and b with hashtag replacement: #a and #b"); + * """, + * "System.out.println(\"Use a and b as capture variables:\"" + a + " and " + b + ");\n" + * )) + * )); * } * * @param key Name for the hashtag replacement. * @param value The value that the hashtag is replaced with. * @param The type of the value. * @param function The function that is applied with the provided {@code value}. - * @return A {@link TemplateBody}. - * @throws RendererException if there is a duplicate hashtag {@code key}. + * @return A {@link Token} representing the hashtag replacement definition and inner scope. */ - static TemplateBody let(String key, T value, Function function) { - Renderer.getCurrent().addHashtagReplacement(key, value); - return function.apply(value); + static Token let(String key, T value, Function function) { + return new LetToken(key, value, function); } /** @@ -702,7 +912,7 @@ public sealed interface Template permits Template.ZeroArgs, /** * The default amount of fuel spent per Template. It is subtracted from the current {@link #fuel} at every * nesting level, and once the {@link #fuel} reaches zero, the nesting is supposed to terminate. Can be changed - * with {@link #setFuelCost(float)} inside {@link #body(Object...)}. + * with {@link #setFuelCost(float)} inside {@link #scope(Object...)}. */ float DEFAULT_FUEL_COST = 10.0f; @@ -721,7 +931,7 @@ public sealed interface Template permits Template.ZeroArgs, *

    * {@snippet lang=java : * var binding = new TemplateBinding>(); - * var template = Template.make("depth", (Integer depth) -> body( + * var template = Template.make("depth", (Integer depth) -> scope( * setFuelCost(5.0f), * let("fuel", fuel()), * """ @@ -737,6 +947,9 @@ public sealed interface Template permits Template.ZeroArgs, * @return The amount of fuel left for nested Template use. */ static float fuel() { + // Note, since the fuel amount does not change within a template + // and the retrieval has no side effects, we can return the value immediately, + // and do not need a token. return Renderer.getCurrent().fuel(); } @@ -745,16 +958,17 @@ public sealed interface Template permits Template.ZeroArgs, * {@link Template#DEFAULT_FUEL_COST}. * * @param fuelCost The amount of fuel used for the current Template. - * @return A token for convenient use in {@link Template#body}. + * @return A token for convenient use in {@link Template#scope}. */ static Token setFuelCost(float fuelCost) { - Renderer.getCurrent().setFuelCost(fuelCost); - return new NothingToken(); + return new SetFuelCostToken(fuelCost); } /** - * Add a {@link DataName} in the current scope, that is the innermost of either - * {@link Template#body} or {@link Hook#anchor}. + * Add a {@link DataName} in the current {@link #scope}. + * If the current scope is transparent to {@link DataName}s, it escapes to the next + * outer scope that is non-transparent, and is available for everything that follows + * the {@code addDataName} until the end of that non-transparent scope. * * @param name The name of the {@link DataName}, i.e. the {@link String} used in code. * @param type The type of the {@link DataName}. @@ -779,8 +993,10 @@ public sealed interface Template permits Template.ZeroArgs, } /** - * Add a {@link DataName} in the current scope, that is the innermost of either - * {@link Template#body} or {@link Hook#anchor}, with a {@code weight} of 1. + * Add a {@link DataName} in the current {@link #scope}, with a {@code weight} of 1. + * If the current scope is transparent to {@link DataName}s, it escapes to the next + * outer scope that is non-transparent, and is available for everything that follows + * the {@code addDataName} until the end of that non-transparent scope. * * @param name The name of the {@link DataName}, i.e. the {@link String} used in code. * @param type The type of the {@link DataName}. @@ -804,8 +1020,10 @@ public sealed interface Template permits Template.ZeroArgs, } /** - * Add a {@link StructuralName} in the current scope, that is the innermost of either - * {@link Template#body} or {@link Hook#anchor}. + * Add a {@link StructuralName} in the current {@link #scope}. + * If the current scope is transparent to {@link StructuralName}s, it escapes to the next + * outer scope that is non-transparent, and is available for everything that follows + * the {@code addStructuralName} until the end of that non-transparent scope. * * @param name The name of the {@link StructuralName}, i.e. the {@link String} used in code. * @param type The type of the {@link StructuralName}. @@ -822,8 +1040,10 @@ public sealed interface Template permits Template.ZeroArgs, } /** - * Add a {@link StructuralName} in the current scope, that is the innermost of either - * {@link Template#body} or {@link Hook#anchor}, with a {@code weight} of 1. + * Add a {@link StructuralName} in the current {@link #scope}, with a {@code weight} of 1. + * If the current scope is transparent to {@link StructuralName}s, it escapes to the next + * outer scope that is non-transparent, and is available for everything that follows + * the {@code addStructuralName} until the end of that non-transparent scope. * * @param name The name of the {@link StructuralName}, i.e. the {@link String} used in code. * @param type The type of the {@link StructuralName}. diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/TemplateFrame.java b/test/hotspot/jtreg/compiler/lib/template_framework/TemplateFrame.java index cf8c4afb321..04305dff02f 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/TemplateFrame.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/TemplateFrame.java @@ -27,38 +27,96 @@ import java.util.HashMap; import java.util.Map; /** - * The {@link TemplateFrame} is the frame for a {@link Template}, i.e. the corresponding - * {@link TemplateToken}. It ensures that each template use has its own unique {@link #id} - * used to deconflict names using {@link Template#$}. It also has a set of hashtag - * replacements, which combine the key-value pairs from the template argument and the - * {@link Template#let} definitions. The {@link #parent} relationship provides a trace - * for the use chain of templates. The {@link #fuel} is reduced over this chain, to give - * a heuristic on how much time is spent on the code from the template corresponding to - * the frame, and to give a termination criterion to avoid nesting templates too deeply. + * The {@link TemplateFrame} keeps track of the nested hashtag replacements available + * inside the {@link Template}, as well as the unique id of the {@link Template} use, + * and how much fuel is available for recursive {@link Template} calls. The name of + * the {@link TemplateFrame} indicates that it corresponds to the structure of the + * {@link Template}, whereas the {@link CodeFrame} corresponds to the structure of + * the generated code. * *

    - * See also {@link CodeFrame} for more explanations about the frames. + * The unique id is used to deconflict names using {@link Template#$}. + * + *

    + * A {@link Template} can have multiple {@link TemplateFrame}s, if there are nested + * scopes. The outermost {@link TemplateFrame} determines the id of the {@link Template} + * use and performs the subtraction of fuel from the outer {@link Template}. Inner + * {@link TemplateFrame}s ensure the correct availability of hashtag replacement and + * {@link Template#setFuelCost} definitions, so that they are local to their scope and + * nested scopes, and only escape if the scope is transparent. + * + *

    + * The hashtag replacements are a set of key-value pairs from the template arguments + * and queries such as {@link Template#let} definitions. Each {@link TemplateFrame} + * has such a set of hashtag replacements, and implicitly provides access to the + * hashtag replacements of the outer {@link TemplateFrame}s, up to the outermost + * of the current {@link Template}. If a hashtag replacement is added in a scope, + * we have to traverse to outer scopes until we find one that is not transparent + * for hashtags (at most it is the frame of the Template), and insert it there. + * The hashtag replacent is local to that frame, and accessible for any frames nested + * inside it, but not inside other Templates. The hashtag replacement disappears once + * the corresponding scope is exited, i.e. the frame removed. + * + *

    + * The {@link #parent} relationship provides a trace for the use chain of templates and + * their inner scopes. The {@link #fuel} is reduced over this chain to give a heuristic + * on how deeply nested the code is at a given point, correlating to the runtime that + * would be spent if the code was executed. The idea is that once the fuel is depleated, + * we do not want to nest more deeply, so that there is a reasonable chance that the + * execution of the generated code can terminate. + * + *

    + * The {@link TemplateFrame} thus implements the hashtag and {@link Template#setFuelCost} + * non-transparency aspect of {@link ScopeToken}. + * + *

    + * See also {@link CodeFrame} for more explanations about the frames. Note, that while + * {@link TemplateFrame} always nests inward, even with {@link Hook#insert}, the + * {@link CodeFrame} can also jump to the {@link Hook#anchor} {@link CodeFrame} when + * using {@link Hook#insert}. */ class TemplateFrame { final TemplateFrame parent; + private final boolean isInnerScope; private final int id; private final Map hashtagReplacements = new HashMap<>(); final float fuel; private float fuelCost; + private final boolean isTransparentForHashtag; + private final boolean isTransparentForFuel; public static TemplateFrame makeBase(int id, float fuel) { - return new TemplateFrame(null, id, fuel, 0.0f); + return new TemplateFrame(null, false, id, fuel, 0.0f, false, false); } public static TemplateFrame make(TemplateFrame parent, int id) { - return new TemplateFrame(parent, id, parent.fuel - parent.fuelCost, Template.DEFAULT_FUEL_COST); + float fuel = parent.fuel - parent.fuelCost; + return new TemplateFrame(parent, false, id, fuel, Template.DEFAULT_FUEL_COST, false, false); } - private TemplateFrame(TemplateFrame parent, int id, float fuel, float fuelCost) { + public static TemplateFrame makeInnerScope(TemplateFrame parent, + boolean isTransparentForHashtag, + boolean isTransparentForFuel) { + // We keep the id of the parent, so that we have the same dollar replacements. + // And we subtract no fuel, but forward the cost. + return new TemplateFrame(parent, true, parent.id, parent.fuel, parent.fuelCost, + isTransparentForHashtag, isTransparentForFuel); + } + + private TemplateFrame(TemplateFrame parent, + boolean isInnerScope, + int id, + float fuel, + float fuelCost, + boolean isTransparentForHashtag, + boolean isTransparentForFuel) { this.parent = parent; + this.isInnerScope = isInnerScope; this.id = id; this.fuel = fuel; this.fuelCost = fuelCost; + this.isTransparentForHashtag = isTransparentForHashtag; + this.isTransparentForFuel = isTransparentForFuel; } public String $(String name) { @@ -78,8 +136,15 @@ class TemplateFrame { if (!Renderer.isValidHashtagOrDollarName(key)) { throw new RendererException("Is not a valid hashtag replacement name: '" + key + "'."); } - if (hashtagReplacements.putIfAbsent(key, value) != null) { - throw new RendererException("Duplicate hashtag replacement for #" + key); + String previous = findHashtagReplacementInScopes(key); + if (previous != null) { + throw new RendererException("Duplicate hashtag replacement for #" + key + ". " + + "previous: " + previous + ", new: " + value); + } + if (isTransparentForHashtag) { + parent.addHashtagReplacement(key, value); + } else { + hashtagReplacements.put(key, value); } } @@ -87,13 +152,27 @@ class TemplateFrame { if (!Renderer.isValidHashtagOrDollarName(key)) { throw new RendererException("Is not a valid hashtag replacement name: '" + key + "'."); } - if (hashtagReplacements.containsKey(key)) { - return hashtagReplacements.get(key); + String value = findHashtagReplacementInScopes(key); + if (value != null) { + return value; } throw new RendererException("Missing hashtag replacement for #" + key); } + private String findHashtagReplacementInScopes(String key) { + if (hashtagReplacements.containsKey(key)) { + return hashtagReplacements.get(key); + } + if (!isInnerScope) { + return null; + } + return parent.findHashtagReplacementInScopes(key); + } + void setFuelCost(float fuelCost) { this.fuelCost = fuelCost; + if (isTransparentForFuel) { + parent.setFuelCost(fuelCost); + } } } diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/TemplateToken.java b/test/hotspot/jtreg/compiler/lib/template_framework/TemplateToken.java index 47262f152d4..ffbfcfdf2d0 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/TemplateToken.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/TemplateToken.java @@ -49,7 +49,7 @@ public sealed abstract class TemplateToken implements Token } @Override - public TemplateBody instantiate() { + public ScopeToken instantiate() { return zeroArgs.instantiate(); } @@ -74,7 +74,7 @@ public sealed abstract class TemplateToken implements Token } @Override - public TemplateBody instantiate() { + public ScopeToken instantiate() { return oneArgs.instantiate(arg1); } @@ -104,7 +104,7 @@ public sealed abstract class TemplateToken implements Token } @Override - public TemplateBody instantiate() { + public ScopeToken instantiate() { return twoArgs.instantiate(arg1, arg2); } @@ -138,7 +138,7 @@ public sealed abstract class TemplateToken implements Token } @Override - public TemplateBody instantiate() { + public ScopeToken instantiate() { return threeArgs.instantiate(arg1, arg2, arg3); } @@ -150,7 +150,7 @@ public sealed abstract class TemplateToken implements Token } } - abstract TemplateBody instantiate(); + abstract ScopeToken instantiate(); @FunctionalInterface interface ArgumentVisitor { diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/Token.java b/test/hotspot/jtreg/compiler/lib/template_framework/Token.java index 0e9f9b272c5..6e9d5f7650a 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/Token.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/Token.java @@ -24,16 +24,25 @@ package compiler.lib.template_framework; /** - * The {@link Template#body} and {@link Hook#anchor} are given a list of tokens, which are either + * The {@link Template#scope} and {@link Hook#anchor} are given a list of tokens, which are either * {@link Token}s or {@link String}s or some permitted boxed primitives. */ public sealed interface Token permits StringToken, - TemplateToken, - TemplateToken.ZeroArgs, - TemplateToken.OneArg, - TemplateToken.TwoArgs, - TemplateToken.ThreeArgs, - HookAnchorToken, - HookInsertToken, - AddNameToken, - NothingToken {} + TemplateToken, + TemplateToken.ZeroArgs, + TemplateToken.OneArg, + TemplateToken.TwoArgs, + TemplateToken.ThreeArgs, + HookAnchorToken, + HookInsertToken, + HookIsAnchoredToken, + AddNameToken, + NameSampleToken, + NameForEachToken, + NamesToListToken, + NameCountToken, + NameHasAnyToken, + LetToken, + ScopeToken, + ScopeTokenImpl, + SetFuelCostToken {} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/TokenParser.java b/test/hotspot/jtreg/compiler/lib/template_framework/TokenParser.java index 0c335bd4fb8..bee6246bdc5 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/TokenParser.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/TokenParser.java @@ -31,7 +31,7 @@ import java.util.List; * Helper class for {@link Token}, to keep the parsing methods package private. * *

    - * The {@link Template#body} and {@link Hook#anchor} are given a list of tokens, which are either + * The {@link Template#scope} and {@link Hook#anchor} are given a list of tokens, which are either * {@link Token}s or {@link String}s or some permitted boxed primitives. These are then parsed * and all non-{@link Token}s are converted to {@link StringToken}s. The parsing also flattens * {@link List}s. diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/library/Expression.java b/test/hotspot/jtreg/compiler/lib/template_framework/library/Expression.java index 360937c8f7f..43ab16af415 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/library/Expression.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/library/Expression.java @@ -33,7 +33,7 @@ import java.util.stream.Collectors; import compiler.lib.template_framework.Template; import compiler.lib.template_framework.TemplateToken; -import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.scope; /** * {@link Expression}s model Java expressions, that have a list of arguments with specified @@ -357,7 +357,7 @@ public class Expression { } tokens.add(strings.getLast()); - var template = Template.make(() -> body( + var template = Template.make(() -> scope( tokens )); return template.asToken(); diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/library/PrimitiveType.java b/test/hotspot/jtreg/compiler/lib/template_framework/library/PrimitiveType.java index 46a9d5bbabe..c0db3d51545 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/library/PrimitiveType.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/library/PrimitiveType.java @@ -33,7 +33,7 @@ import compiler.lib.generators.RestrictableGenerator; import compiler.lib.template_framework.DataName; import compiler.lib.template_framework.Template; import compiler.lib.template_framework.TemplateToken; -import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.scope; /** * The {@link PrimitiveType} models Java's primitive types, and provides a set @@ -190,7 +190,7 @@ public final class PrimitiveType implements CodeGenerationDataNameType { * @return a TemplateToken that holds all the {@code LibraryRNG} class. */ public static TemplateToken generateLibraryRNG() { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( """ public static class LibraryRNG { private static final Random RANDOM = Utils.getRandomInstance(); diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/library/TestFrameworkClass.java b/test/hotspot/jtreg/compiler/lib/template_framework/library/TestFrameworkClass.java index 5194b75af43..a9db9285b78 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/library/TestFrameworkClass.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/library/TestFrameworkClass.java @@ -30,7 +30,7 @@ import compiler.lib.ir_framework.TestFramework; import compiler.lib.compile_framework.CompileFramework; import compiler.lib.template_framework.Template; import compiler.lib.template_framework.TemplateToken; -import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.scope; import static compiler.lib.template_framework.Template.let; /** @@ -51,7 +51,7 @@ public final class TestFrameworkClass { private TestFrameworkClass() {} /** - * This method renders a list of {@code testTemplateTokens} into the body of a class + * This method renders a list of {@code testTemplateTokens} into the scope of a class * and generates a {@code main} method which launches the {@link TestFramework} * to run the generated tests. * @@ -81,7 +81,7 @@ public final class TestFrameworkClass { final Set imports, final String classpath, final List testTemplateTokens) { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( let("packageName", packageName), let("className", className), let("classpath", classpath), @@ -96,7 +96,7 @@ public final class TestFrameworkClass { public class #className { // --- CLASS_HOOK insertions start --- """, - Hooks.CLASS_HOOK.anchor( + Hooks.CLASS_HOOK.anchor(scope( """ // --- CLASS_HOOK insertions end --- public static void main(String[] vmFlags) { @@ -108,7 +108,7 @@ public final class TestFrameworkClass { // --- LIST OF TESTS start --- """, testTemplateTokens - ), + )), """ // --- LIST OF TESTS end --- } diff --git a/test/hotspot/jtreg/compiler/loopopts/superword/TestAliasingFuzzer.java b/test/hotspot/jtreg/compiler/loopopts/superword/TestAliasingFuzzer.java index 62e474ecb2c..5d20ce659b9 100644 --- a/test/hotspot/jtreg/compiler/loopopts/superword/TestAliasingFuzzer.java +++ b/test/hotspot/jtreg/compiler/loopopts/superword/TestAliasingFuzzer.java @@ -61,7 +61,7 @@ import compiler.lib.compile_framework.*; import compiler.lib.generators.Generators; import compiler.lib.template_framework.Template; import compiler.lib.template_framework.TemplateToken; -import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.scope; import static compiler.lib.template_framework.Template.let; import static compiler.lib.template_framework.Template.$; @@ -333,7 +333,7 @@ public class TestAliasingFuzzer { } public TemplateToken index(String invar0, String[] invarRest) { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( let("con", con), let("ivScale", ivScale), let("invar0Scale", invar0Scale), @@ -349,7 +349,7 @@ public class TestAliasingFuzzer { // MemorySegment need to be long-addressed, otherwise there can be int-overflow // in the index, and that prevents RangeCheck Elimination and Vectorization. public TemplateToken indexLong(String invar0, String[] invarRest) { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( let("con", con), let("ivScale", ivScale), let("invar0Scale", invar0Scale), @@ -365,7 +365,7 @@ public class TestAliasingFuzzer { // Mirror the IndexForm from the generator to the test. public static TemplateToken generateIndexForm() { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( """ private static final Random RANDOM = Utils.getRandomInstance(); @@ -610,7 +610,7 @@ public class TestAliasingFuzzer { for (int i = 0; i < indexFormNames.length; i++) { indexFormNames[i] = $("index" + i); } - return body( + return scope( """ // --- $test start --- """, @@ -662,7 +662,7 @@ public class TestAliasingFuzzer { } private TemplateToken generateArrayField(String name, MyType type) { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( let("size", containerByteSize / type.byteSize()), let("name", name), let("type", type), @@ -676,7 +676,7 @@ public class TestAliasingFuzzer { } private TemplateToken generateMemorySegmentField(String name, MyType type) { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( let("size", containerByteSize / type.byteSize()), let("byteSize", containerByteSize), let("name", name), @@ -698,7 +698,7 @@ public class TestAliasingFuzzer { } private TemplateToken generateIndexField(String name, IndexForm form) { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( let("name", name), let("form", form.generate()), """ @@ -709,7 +709,7 @@ public class TestAliasingFuzzer { } private TemplateToken generateTestFields(String[] invarRest, String[] containerNames, String[] indexFormNames) { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( let("ivType", isLongIvType ? "long" : "int"), """ // invarRest fields: @@ -741,7 +741,7 @@ public class TestAliasingFuzzer { } private TemplateToken generateContainerInitArray(String name) { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( let("size", containerByteSize / containerElementType.byteSize()), let("name", name), """ @@ -753,7 +753,7 @@ public class TestAliasingFuzzer { } private TemplateToken generateContainerInitMemorySegment(String name) { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( let("size", containerByteSize / containerElementType.byteSize()), let("name", name), """ @@ -765,7 +765,7 @@ public class TestAliasingFuzzer { } private TemplateToken generateContainerInit(String[] containerNames) { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( """ // Init containers from original data: """, @@ -784,7 +784,7 @@ public class TestAliasingFuzzer { } private TemplateToken generateContainerAliasingAssignment(int i, String name1, String name2, String iterations) { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( let("i", i), let("name1", name1), let("name2", name2), @@ -798,7 +798,7 @@ public class TestAliasingFuzzer { } private TemplateToken generateContainerAliasing(String[] containerNames, String iterations) { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( """ // Container aliasing: """, @@ -832,7 +832,7 @@ public class TestAliasingFuzzer { if (accessIndexForm.length != 2) { throw new RuntimeException("not yet implemented"); } - var templateSplitRanges = Template.make(() -> body( + var templateSplitRanges = Template.make(() -> scope( let("size", size), """ int middle = RANDOM.nextInt(#size / 3, #size * 2 / 3); @@ -865,7 +865,7 @@ public class TestAliasingFuzzer { """ )); - var templateWholeRanges = Template.make(() -> body( + var templateWholeRanges = Template.make(() -> scope( let("size", size), """ var r0 = new IndexForm.Range(0, #size); @@ -873,7 +873,7 @@ public class TestAliasingFuzzer { """ )); - var templateRandomRanges = Template.make(() -> body( + var templateRandomRanges = Template.make(() -> scope( let("size", size), """ int lo0 = RANDOM.nextInt(0, #size * 3 / 4); @@ -883,7 +883,7 @@ public class TestAliasingFuzzer { """ )); - var templateSmallOverlapRanges = Template.make(() -> body( + var templateSmallOverlapRanges = Template.make(() -> scope( // Idea: same size ranges, with size "range". A small overlap, // so that bad runtime checks would create wrong results. let("size", size), @@ -907,7 +907,7 @@ public class TestAliasingFuzzer { // -> safe with rnd = size/10 )); - var templateAnyRanges = Template.make(() -> body( + var templateAnyRanges = Template.make(() -> scope( switch(RANDOM.nextInt(4)) { case 0 -> templateSplitRanges.asToken(); case 1 -> templateWholeRanges.asToken(); @@ -917,7 +917,7 @@ public class TestAliasingFuzzer { } )); - var template = Template.make(() -> body( + var template = Template.make(() -> scope( """ // Generate ranges: """, @@ -941,7 +941,7 @@ public class TestAliasingFuzzer { // We want there to be at least 1000 iterations. final int minIvRange = ivStrideAbs * 1000; - var template = Template.make(() -> body( + var template = Template.make(() -> scope( let("containerByteSize", containerByteSize), """ // Compute loop bounds and loop invariants. @@ -949,7 +949,7 @@ public class TestAliasingFuzzer { int ivHi = ivLo + #containerByteSize; """, IntStream.range(0, indexFormNames.length).mapToObj(i -> - Template.make(() -> body( + Template.make(() -> scope( let("i", i), let("form", indexFormNames[i]), """ @@ -990,7 +990,7 @@ public class TestAliasingFuzzer { """, IntStream.range(0, indexFormNames.length).mapToObj(i1 -> IntStream.range(0, i1).mapToObj(i2 -> - Template.make(() -> body( + Template.make(() -> scope( let("i1", i1), let("i2", i2), // i1 < i2 or i1 > i2 @@ -1021,7 +1021,7 @@ public class TestAliasingFuzzer { private TemplateToken generateCallMethod(String output, String methodName, String containerPrefix) { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( let("output", output), let("methodName", methodName), "var #output = #methodName(", @@ -1034,7 +1034,7 @@ public class TestAliasingFuzzer { } private TemplateToken generateIRRules() { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( switch (containerKind) { case ContainerKind.ARRAY -> generateIRRulesArray(); @@ -1094,7 +1094,7 @@ public class TestAliasingFuzzer { // Regular array-accesses are vectorized quite predictably, and we can create nice // IR rules - even for cases where we do not expect vectorization. private TemplateToken generateIRRulesArray() { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( let("T", containerElementType.letter()), switch (accessScenario) { case COPY_LOAD_STORE -> @@ -1145,7 +1145,7 @@ public class TestAliasingFuzzer { } private TemplateToken generateIRRulesMemorySegmentAtIndex() { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( """ // Unfortunately, there are some issues that prevent RangeCheck elimination. // The cases are currently quite unpredictable, so we cannot create any IR @@ -1158,7 +1158,7 @@ public class TestAliasingFuzzer { } private TemplateToken generateIRRulesMemorySegmentLongAdrStride() { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( """ // Unfortunately, there are some issues that prevent RangeCheck elimination. // The cases are currently quite unpredictable, so we cannot create any IR @@ -1169,7 +1169,7 @@ public class TestAliasingFuzzer { } private TemplateToken generateIRRulesMemorySegmentLongAdrScale() { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( """ // Unfortunately, there are some issues that prevent RangeCheck elimination. // The cases are currently quite unpredictable, so we cannot create any IR @@ -1180,7 +1180,7 @@ public class TestAliasingFuzzer { } private TemplateToken generateTestMethod(String methodName, String[] invarRest) { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( let("methodName", methodName), let("containerElementType", containerElementType), let("ivStrideAbs", ivStrideAbs), @@ -1230,7 +1230,7 @@ public class TestAliasingFuzzer { } private TemplateToken generateTestLoopIterationArray(String[] invarRest) { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( let("type", containerElementType), switch (accessScenario) { case COPY_LOAD_STORE -> @@ -1245,7 +1245,7 @@ public class TestAliasingFuzzer { } private TemplateToken generateTestLoopIterationMemorySegmentAtIndex(String[] invarRest) { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( let("type0", accessType[0]), let("type1", accessType[1]), let("type0Layout", accessType[0].layout()), @@ -1265,7 +1265,7 @@ public class TestAliasingFuzzer { } private TemplateToken generateTestLoopIterationMemorySegmentLongAdr(String[] invarRest) { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( let("type0", accessType[0]), let("type1", accessType[1]), let("type0Layout", accessType[0].layout()), diff --git a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestAdvanced.java b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestAdvanced.java index c5a4528f63d..784f1ded065 100644 --- a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestAdvanced.java +++ b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestAdvanced.java @@ -43,7 +43,7 @@ import compiler.lib.generators.RestrictableGenerator; import compiler.lib.compile_framework.*; import compiler.lib.template_framework.Template; -import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.scope; import static compiler.lib.template_framework.Template.let; /** @@ -96,7 +96,7 @@ public class TestAdvanced { // - The GOLD value is computed at the beginning, hopefully by the interpreter. // - The test method is eventually compiled, and the values are verified by the // check method. - var testTemplate = Template.make("typeName", "operator", "generator", (String typeName, String operator, MyGenerator generator) -> body( + var testTemplate = Template.make("typeName", "operator", "generator", (String typeName, String operator, MyGenerator generator) -> scope( let("con1", generator.next()), let("con2", generator.next()), """ @@ -116,7 +116,7 @@ public class TestAdvanced { )); // Template for the Class. - var classTemplate = Template.make("types", (List types) -> body( + var classTemplate = Template.make("types", (List types) -> scope( let("classpath", comp.getEscapedClassPathOfCompiledClasses()), """ package p.xyz; diff --git a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestExpressions.java b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestExpressions.java index c21d2492fc7..6a0a2d3786a 100644 --- a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestExpressions.java +++ b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestExpressions.java @@ -40,7 +40,7 @@ import java.util.Set; import compiler.lib.compile_framework.*; import compiler.lib.template_framework.Template; import compiler.lib.template_framework.TemplateToken; -import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.scope; import static compiler.lib.template_framework.Template.let; import compiler.lib.template_framework.library.Expression; import compiler.lib.template_framework.library.Operations; @@ -78,7 +78,7 @@ public class TestExpressions { // precision results from some operators. We only compare the results if we know that the // result is deterministically the same. TemplateToken expressionToken = expression.asToken(expression.argumentTypes.stream().map(t -> t.con()).toList()); - return body( + return scope( let("returnType", expression.returnType), """ @Test diff --git a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestPrimitiveTypes.java b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestPrimitiveTypes.java index a04a5771cb4..b1f5f74e682 100644 --- a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestPrimitiveTypes.java +++ b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestPrimitiveTypes.java @@ -41,7 +41,8 @@ import java.util.HashMap; import compiler.lib.compile_framework.*; import compiler.lib.template_framework.Template; import compiler.lib.template_framework.TemplateToken; -import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.scope; +import static compiler.lib.template_framework.Template.transparentScope; import static compiler.lib.template_framework.Template.dataNames; import static compiler.lib.template_framework.Template.let; import static compiler.lib.template_framework.Template.$; @@ -77,7 +78,7 @@ public class TestPrimitiveTypes { Map tests = new HashMap<>(); // The boxing tests check if we can autobox with "boxedTypeName". - var boxingTemplate = Template.make("name", "type", (String name, PrimitiveType type) -> body( + var boxingTemplate = Template.make("name", "type", (String name, PrimitiveType type) -> scope( let("CON1", type.con()), let("CON2", type.con()), let("Boxed", type.boxedTypeName()), @@ -99,7 +100,7 @@ public class TestPrimitiveTypes { } // Integral and Float types have a size. Also test if "isFloating" is correct. - var integralFloatTemplate = Template.make("name", "type", (String name, PrimitiveType type) -> body( + var integralFloatTemplate = Template.make("name", "type", (String name, PrimitiveType type) -> scope( let("size", type.byteSize()), let("isFloating", type.isFloating()), """ @@ -129,27 +130,31 @@ public class TestPrimitiveTypes { // Finally, test the type by creating some DataNames (variables), and sampling // from them. There should be no cross-over between the types. - var variableTemplate = Template.make("type", (PrimitiveType type) -> body( + // IMPORTANT: since we are adding the DataName via an inserted Template, we + // must chose a "transparentScope", so that the DataName escapes. If we + // instead chose "scope", the test would fail, because it later + // finds no DataNames when we sample. + var variableTemplate = Template.make("type", (PrimitiveType type) -> transparentScope( let("CON", type.con()), - addDataName($("var"), type, MUTABLE), + addDataName($("var"), type, MUTABLE), // escapes the Template """ #type $var = #CON; """ )); - var sampleTemplate = Template.make("type", (PrimitiveType type) -> body( - let("var", dataNames(MUTABLE).exactOf(type).sample().name()), + var sampleTemplate = Template.make("type", (PrimitiveType type) -> scope( let("CON", type.con()), + dataNames(MUTABLE).exactOf(type).sampleAndLetAs("var"), """ #var = #CON; """ )); - var namesTemplate = Template.make(() -> body( + var namesTemplate = Template.make(() -> scope( """ public static void test_names() { """, - Hooks.METHOD_HOOK.anchor( + Hooks.METHOD_HOOK.anchor(scope( Collections.nCopies(10, CodeGenerationDataNameType.PRIMITIVE_TYPES.stream().map(type -> Hooks.METHOD_HOOK.insert(variableTemplate.asToken(type)) @@ -161,7 +166,7 @@ public class TestPrimitiveTypes { Collections.nCopies(10, CodeGenerationDataNameType.PRIMITIVE_TYPES.stream().map(sampleTemplate::asToken).toList() ) - ), + )), """ } """ @@ -172,7 +177,7 @@ public class TestPrimitiveTypes { // Test runtime random value generation with LibraryRNG // Runtime random number generation of a given primitive type can be very helpful // when writing tests that require random inputs. - var libraryRNGWithTypeTemplate = Template.make("type", (PrimitiveType type) -> body( + var libraryRNGWithTypeTemplate = Template.make("type", (PrimitiveType type) -> scope( """ { // Fill an array with 1_000 random values. Every type has at least 2 values, @@ -196,7 +201,7 @@ public class TestPrimitiveTypes { """ )); - var libraryRNGTemplate = Template.make(() -> body( + var libraryRNGTemplate = Template.make(() -> scope( // Make sure we instantiate the LibraryRNG class. PrimitiveType.generateLibraryRNG(), // Now we can use it inside the test. @@ -213,7 +218,7 @@ public class TestPrimitiveTypes { // Finally, put all the tests together in a class, and invoke all // tests from the main method. - var template = Template.make(() -> body( + var template = Template.make(() -> scope( """ package p.xyz; diff --git a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestSimple.java b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestSimple.java index e06671ca951..c8afb34e423 100644 --- a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestSimple.java +++ b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestSimple.java @@ -34,7 +34,7 @@ package template_framework.examples; import compiler.lib.compile_framework.*; import compiler.lib.template_framework.Template; -import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.scope; public class TestSimple { @@ -61,7 +61,7 @@ public class TestSimple { // Generate a source Java file as String public static String generate() { // Create a Template with two arguments. - var template = Template.make("arg1", "arg2", (Integer arg1, String arg2) -> body( + var template = Template.make("arg1", "arg2", (Integer arg1, String arg2) -> scope( """ package p.xyz; public class InnerTest { diff --git a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestTutorial.java b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestTutorial.java index faa05b29d82..ed542180bad 100644 --- a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestTutorial.java +++ b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestTutorial.java @@ -43,7 +43,9 @@ import compiler.lib.template_framework.Hook; import compiler.lib.template_framework.TemplateBinding; import compiler.lib.template_framework.DataName; import compiler.lib.template_framework.StructuralName; -import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.scope; +import static compiler.lib.template_framework.Template.transparentScope; +import static compiler.lib.template_framework.Template.hashtagScope; import static compiler.lib.template_framework.Template.let; import static compiler.lib.template_framework.Template.$; import static compiler.lib.template_framework.Template.fuel; @@ -68,13 +70,14 @@ public class TestTutorial { comp.addJavaSourceCode("p.xyz.InnerTest2", generateWithTemplateArguments()); comp.addJavaSourceCode("p.xyz.InnerTest3", generateWithHashtagAndDollarReplacements()); comp.addJavaSourceCode("p.xyz.InnerTest3b", generateWithHashtagAndDollarReplacements2()); + comp.addJavaSourceCode("p.xyz.InnerTest3c", generateWithHashtagAndDollarReplacements3()); comp.addJavaSourceCode("p.xyz.InnerTest4", generateWithCustomHooks()); comp.addJavaSourceCode("p.xyz.InnerTest5", generateWithLibraryHooks()); comp.addJavaSourceCode("p.xyz.InnerTest6", generateWithRecursionAndBindingsAndFuel()); comp.addJavaSourceCode("p.xyz.InnerTest7", generateWithDataNamesSimple()); comp.addJavaSourceCode("p.xyz.InnerTest8", generateWithDataNamesForFieldsAndVariables()); - comp.addJavaSourceCode("p.xyz.InnerTest9a", generateWithDataNamesAndScopes1()); - comp.addJavaSourceCode("p.xyz.InnerTest9b", generateWithDataNamesAndScopes2()); + comp.addJavaSourceCode("p.xyz.InnerTest9a", generateWithScopes1()); + comp.addJavaSourceCode("p.xyz.InnerTest9b", generateWithScopes2()); comp.addJavaSourceCode("p.xyz.InnerTest10", generateWithDataNamesForFuzzing()); comp.addJavaSourceCode("p.xyz.InnerTest11", generateWithStructuralNamesForMethods()); @@ -91,6 +94,7 @@ public class TestTutorial { comp.invoke("p.xyz.InnerTest2", "main", new Object[] {}); comp.invoke("p.xyz.InnerTest3", "main", new Object[] {}); comp.invoke("p.xyz.InnerTest3b", "main", new Object[] {}); + comp.invoke("p.xyz.InnerTest3c", "main", new Object[] {}); comp.invoke("p.xyz.InnerTest4", "main", new Object[] {}); comp.invoke("p.xyz.InnerTest5", "main", new Object[] {}); comp.invoke("p.xyz.InnerTest6", "main", new Object[] {}); @@ -105,9 +109,9 @@ public class TestTutorial { // This example shows the use of various Tokens. public static String generateWithListOfTokens() { // A Template is essentially a function / lambda that produces a - // token body, which is a list of Tokens that are concatenated. - var templateClass = Template.make(() -> body( - // The "body" method is filled by a sequence of "Tokens". + // scope, which contains a list of Tokens that are concatenated. + var templateClass = Template.make(() -> scope( + // The "scope" arguments are a sequence of "Tokens". // These can be Strings and multi-line Strings, but also // boxed primitives. """ @@ -141,14 +145,14 @@ public class TestTutorial { // This example shows the use of Templates, with and without arguments. public static String generateWithTemplateArguments() { // A Template with no arguments. - var templateHello = Template.make(() -> body( + var templateHello = Template.make(() -> scope( """ System.out.println("Hello"); """ )); // A Template with a single Integer argument. - var templateCompare = Template.make("arg", (Integer arg) -> body( + var templateCompare = Template.make("arg", (Integer arg) -> scope( "System.out.println(", arg, ");\n", // capture arg via lambda argument "System.out.println(#arg);\n", // capture arg via hashtag replacement "System.out.println(#{arg});\n", // capture arg via hashtag replacement with brackets @@ -156,7 +160,7 @@ public class TestTutorial { // argument values into Strings. However, since these are not (yet) // available, the Template Framework provides two alternative ways of // formatting Strings: - // 1) By appending to the comma-separated list of Tokens passed to body(). + // 1) By appending to the comma-separated list of Tokens passed to scope(). // Appending as a Token works whenever one has a reference to the Object // in Java code. But often, this is rather cumbersome and looks awkward, // given all the additional quotes and commands required. Hence, it @@ -180,7 +184,7 @@ public class TestTutorial { // A Template that creates the body of the Class and main method, and then // uses the two Templates above inside it. - var templateClass = Template.make(() -> body( + var templateClass = Template.make(() -> scope( """ package p.xyz; @@ -204,8 +208,16 @@ public class TestTutorial { // Note: hashtag replacements are a workaround for the missing string templates. // If we had string templates, we could just capture the typed lambda // arguments, and use them directly in the String via string templating. + // + // Important: hashtag replacements are always constrained to a single template + // and are not available in any nested templates. Hashtag replacements + // are only there to facilitate string templating within the limited + // scope of a template. You may consider it like a "local variable" + // for code generation purposes only. + // If you need to pass some value to a nested Template, consider using + // a Template argument, and capturing that Template argument. public static String generateWithHashtagAndDollarReplacements() { - var template1 = Template.make("x", (Integer x) -> body( + var template1 = Template.make("x", (Integer x) -> scope( // We have the "#x" hashtag replacement from the argument capture above. // Additionally, we can define "#con" as a hashtag replacement from let: let("con", 3 * x), @@ -219,29 +231,27 @@ public class TestTutorial { """ )); - var template2 = Template.make("x", (Integer x) -> + var template2 = Template.make("x", (Integer x) -> scope( // Sometimes it can be helpful to not just create a hashtag replacement // with let, but also to capture the variable to use it as lambda parameter. - let("y", 11 * x, y -> - body( - """ - System.out.println("T2: #x, #y"); - """, - template1.asToken(y) - ) - ) - ); + let("y", 11 * x, y -> scope( + """ + System.out.println("T2: #x, #y"); + """, + template1.asToken(y) + )) + )); // This template generates an int variable and assigns it a value. // Together with template4, we see that each template has a unique renaming // for a $-name replacement. - var template3 = Template.make("name", "value", (String name, Integer value) -> body( + var template3 = Template.make("name", "value", (String name, Integer value) -> scope( """ int #name = #value; // Note: $var is not #name """ )); - var template4 = Template.make(() -> body( + var template4 = Template.make(() -> scope( """ // We will define the variable $var: """, @@ -252,7 +262,7 @@ public class TestTutorial { """ )); - var templateClass = Template.make(() -> body( + var templateClass = Template.make(() -> scope( // The Template Framework API only guarantees that every Template use // has a unique ID. When using the Templates, all we need is that // variables from different Template uses do not conflict. But it can @@ -300,7 +310,7 @@ public class TestTutorial { // "INT_CON" and "LONG_CON". public static String generateWithHashtagAndDollarReplacements2() { // Let us define some final static variables of a specific type. - var template1 = Template.make("type", (String type) -> body( + var template1 = Template.make("type", (String type) -> scope( // The type (e.g. "int") is lower case, let us create the upper case "INT_CON" from it. let("TYPE", type.toUpperCase()), """ @@ -309,7 +319,7 @@ public class TestTutorial { )); // Let's write a simple class to demonstrate that this works, i.e. produces compilable code. - var templateClass = Template.make(() -> body( + var templateClass = Template.make(() -> scope( """ package p.xyz; @@ -331,50 +341,221 @@ public class TestTutorial { return templateClass.render(); } + // We already have used "scope" multiple times, but not explained it yet. + // So far, we have seen "scope" mostly in the context of Template scopes, but they + // can be used in many contexts as we will see below. They can also be used on + // their own and in the use of "let", as we will show right now. + // + // Scopes are even more relevant for DataNames and Structural names. + // See: generateWithDataNamesForFieldsAndVariables + // See: generateWithScopes1 + // See: generateWithScopes2 + public static String generateWithHashtagAndDollarReplacements3() { + + var template1 = Template.make(() -> scope( + // We can use scopes to limit the liveness of hashtag replacements. + scope( + let("x", 3), // does not escape + """ + static int v1_3 = #x; + """ + ), + scope( + let("x", 5), // does not escape + """ + static int v1_5 = #x; + """ + ), + // Using "scope" does not just limit the liveness / availability + // of hashtag replacements, but also of DataNames, StructuralNames, + // and setFuelCost. We can use "hashtagScope" to only limit hashtag + // replacements. + hashtagScope( + let("x", 7), // does not escape + """ + static int v1_7 = #x; + """ + ), + // Using "transparentScope" means the scope is transparent, and the hashtag + // replacements escape the scope. + transparentScope( + let("x", 11), // escapes the "transparentScope". + """ + static int v1_11a = #x; + """ + ), + // The hashtag replacement from the "transparentScope" escaped, and is + // still available. + """ + static int v1_11b = #x; + """ + )); + + var template2 = Template.make("x", (Integer x) -> scope( + // We can map a list of values to a list of scopes. Using a scope that is + // non-transparent for hashtag replacements means that we can reuse the same + // hashtag key when looping / streaming over multiple values. + List.of(3, 5, 7).stream().map(y -> scope( + let("y", y), // does not escape -> allows reuse of hashtag key "y". + """ + static int v2_#{x}_#{y} = #x * #y; + """ + )).toList() + )); + + var template3 = Template.make("x", (Integer x) -> scope( + // When using a "let" that captures the value in a lambda argument, we have + // to choose what kind of scope we generate. In most cases "scope" or + // "hashtagScope" are the best, because they limit the hashtag replacement + // of "y" to the same scope as the lambda argument. + let("y", x * 11, y -> scope( + """ + static int v3a_#{x} = #y; + """ + )), + // But in rare cases, we may want "y" and some nested "z" to escape. + let("y", x * 11, y -> transparentScope( + let("z", y * 2), + """ + static int v3b_#{x} = #y - #z; + """ + )), + // Because of the "transparentScope", "y" and "z" have escaped. + """ + static int v3c_#{x} = #y - #z; + """, + // Side note: We can simulate a "let" without lambda with a "let" that has a lambda. + // That is not very useful, but a similar trick can be used for other queries, that + // only provide a lambda version, and where we only want to use the hashtag replacement. + // + // Below we see the standard use of "let", where we add a hashtag replacement for "a" + // for the rest of the enclosing scope. We then also use a lambda version of "let" + // with a transparent scope, which means that "b" escapes that scope and is also + // available in the enclosing scope. In the implementation of the framework, we + // actually use a "transparentScope", so the standard "let" is really just syntactic + // sugar for the lambda "let" with "transparentScope". + let("a", -x), + let("b", -x, b -> transparentScope()), + """ + static int v3d_#{x} = #a + #b; + """ + )); + + // Let's write a simple class to demonstrate that this works, i.e. produces compilable code. + var templateClass = Template.make(() -> scope( + """ + package p.xyz; + + public class InnerTest3c { + """, + template1.asToken(), + template2.asToken(1), + template2.asToken(2), + template3.asToken(2), + """ + public static void main() { + if (v1_3 != 3 || + v1_5 != 5 || + v1_7 != 7 || + v1_11a != 11 || + v1_11b != 11 || + v2_1_3 != 3 || + v2_1_5 != 5 || + v2_1_7 != 7 || + v2_2_3 != 6 || + v2_2_5 != 10 || + v2_2_7 != 14 || + v3a_2 != 22 || + v3b_2 != -22 || + v3c_2 != -22 || + v3d_2 != -4) { + throw new RuntimeException("Wrong result!"); + } + } + } + """ + )); + + // Render templateClass to String. + return templateClass.render(); + } + // In this example, we look at the use of Hooks. They allow us to reach back, to outer // scopes. For example, we can reach out from inside a method body to a hook anchored at // the top of the class, and insert a field. + // + // When we insert to a hook, we have 3 relevant scopes: + // - Anchor scope: the scope defined at "hook.anchor(scope(...))" + // - Insertion scope: the scope that is inserted, see "hook.insert(scope(...))" + // - Caller scope: the scope we insert from. + // + // The choice of transparency of an insertion scope (the scope that is inserted) is quite + // important. A common use case is to insert a DataName. + // See: generateWithDataNamesForFieldsAndVariables + // See: generateWithScopes1 + // See: generateWithScopes2 public static String generateWithCustomHooks() { // We can define a custom hook. // Note: generally we prefer using the pre-defined CLASS_HOOK and METHOD_HOOK from the library, // whenever possible. See also the example after this one. var myHook = new Hook("MyHook"); - var template1 = Template.make("name", "value", (String name, Integer value) -> body( + var template1 = Template.make("name", "value", (String name, Integer value) -> scope( """ public static int #name = #value; """ )); - var template2 = Template.make("x", (Integer x) -> body( + var template2 = Template.make("x", (Integer x) -> scope( """ - // Let us go back to where we anchored the hook with anchor() and define a field named $field there. - // Note that in the Java code we have not defined anchor() on the hook, yet. But since it's a lambda - // expression, it is not evaluated, yet! Eventually, anchor() will be evaluated before insert() in - // this example. + // Let us go back to where we anchored the hook with anchor() (see 'templateClass' below) and define a field + // named $field1 there. """, - myHook.insert(template1.asToken($("field"), x)), + myHook.insert(scope( // <- insertion scope + """ + public static int $field1 = #x; + """ + // Note that we were able to use the dollar replacement "$field1" and the hashtag + // replacement "#x" inside the scope that is inserted to myHook. + )), """ - System.out.println("$field: " + $field); - if ($field != #x) { throw new RuntimeException("Wrong value!"); } + // We can do that by inserting a scope like above, or by inserting a template, like below. + // + // Which method is used is up to the user. General guidance is if the same code may also + // be inserted elsewhere, one should lean towards inserting templates. But in many cases + // it is nice to see the inserted code directly, and to be able to use hashtag replacements + // from the outer scope directly, without having to route them via template arguments, + // as we have to do below. + """, + // <- caller scope + myHook.insert(template1.asToken($("field2"), x)), + """ + System.out.println("$field1: " + $field1); + System.out.println("$field2: " + $field2); + if ($field1 != #x) { throw new RuntimeException("Wrong value 1!"); } + if ($field2 != #x) { throw new RuntimeException("Wrong value 2!"); } """ )); - var templateClass = Template.make(() -> body( + var templateClass = Template.make(() -> scope( """ package p.xyz; public class InnerTest4 { """, // We anchor a Hook outside the main method, but inside the Class. - // Anchoring a Hook creates a scope, spanning the braces of the - // "anchor" call. Any Hook.insert that happens inside this scope - // goes to the top of that scope. - myHook.anchor( + // Anchoring a Hook requires the definition of an inner scope, + // aka the "anchor scope", spanning the braces of the "anchor" call. + // Any Hook.insert that happens inside this scope goes to the top of + // that scope. + myHook.anchor(scope( // <- anchor scope // Any Hook.insert goes here. // - // <-------- field_X = 5 ------------------+ - // <-------- field_Y = 7 -------------+ | + // <-------- field1_X = 5 -----------------+ + // field2_X = 5 | + // | + // <-------- field1_Y = 7 ------------+ | + // field2_Y = 7 | | // | | """ public static void main() { @@ -384,7 +565,7 @@ public class TestTutorial { """ } """ - ), // The Hook scope ends here. + )), // The Hook scope ends here. """ } """ @@ -408,46 +589,54 @@ public class TestTutorial { // there is a class scope inside another class scope. Similarly, we can nest lambda bodies // inside method bodies, so also METHOD_HOOK can be used in such a "re-entrant" way. public static String generateWithLibraryHooks() { - var templateStaticField = Template.make("name", "value", (String name, Integer value) -> body( - """ - static { System.out.println("Defining static field #name"); } - public static int #name = #value; - """ - )); - var templateLocalVariable = Template.make("name", "value", (String name, Integer value) -> body( - """ - System.out.println("Defining local variable #name"); - int #name = #value; - """ - )); - - var templateMethodBody = Template.make(() -> body( + var templateMethodBody = Template.make(() -> scope( """ // Let's define a local variable $var and a static field $field. - """, - Hooks.CLASS_HOOK.insert(templateStaticField.asToken($("field"), 5)), - Hooks.METHOD_HOOK.insert(templateLocalVariable.asToken($("var"), 11)), - """ + // Since we are inserting them at the anchor before the code below, + // they will already be available: System.out.println("$field: " + $field); System.out.println("$var: " + $var); + """, + Hooks.CLASS_HOOK.insert(scope( + """ + static { System.out.println("Defining static field $field"); } + public static int $field = 5; + """ + )), + Hooks.METHOD_HOOK.insert(scope( + """ + System.out.println("Defining local variable $var"); + int $var = 11; + """ + )), + """ if ($field * $var != 55) { throw new RuntimeException("Wrong value!"); } """ + // Note: we have used "scope" for the "insert" scope. This is fine here as + // we are only working with code and hashtags, but not with DataNames. If + // we were to also "addDataName" inside the insert scope, we would have to + // make sure that the scope is transparent for DataNames, so that they can + // escape to the anchor scope, and can be available to the caller of the + // insertion. One might want to use "transparentScope" for the insertion scope. + // See: generateWithDataNamesForFieldsAndVariables. + // See: generateWithScopes1 + // See: generateWithScopes2 )); - var templateClass = Template.make(() -> body( + var templateClass = Template.make(() -> scope( """ package p.xyz; public class InnerTest5 { """, // Class Hook for fields. - Hooks.CLASS_HOOK.anchor( + Hooks.CLASS_HOOK.anchor(scope( """ public static void main() { """, // Method Hook for local variables, and earlier computations. - Hooks.METHOD_HOOK.anchor( + Hooks.METHOD_HOOK.anchor(scope( """ // This is the beginning of the "main" method body. System.out.println("Welcome to main!"); @@ -457,7 +646,7 @@ public class TestTutorial { System.out.println("Going to call other..."); other(); """ - ), + )), """ } @@ -465,7 +654,7 @@ public class TestTutorial { """, // Have a separate method hook for other, so that it can insert // its own local variables. - Hooks.METHOD_HOOK.anchor( + Hooks.METHOD_HOOK.anchor(scope( """ System.out.println("Welcome to other!"); """, @@ -473,11 +662,11 @@ public class TestTutorial { """ System.out.println("Done with other."); """ - ), + )), """ } """ - ), + )), """ } """ @@ -493,7 +682,7 @@ public class TestTutorial { public static String generateWithRecursionAndBindingsAndFuel() { // Binding allows the use of template1 inside of template1, via the binding indirection. var binding1 = new TemplateBinding>(); - var template1 = Template.make("depth", (Integer depth) -> body( + var template1 = Template.make("depth", (Integer depth) -> scope( let("fuel", fuel()), """ System.out.println("At depth #depth with fuel #fuel."); @@ -514,7 +703,7 @@ public class TestTutorial { )); binding1.bind(template1); - var templateClass = Template.make(() -> body( + var templateClass = Template.make(() -> scope( """ package p.xyz; @@ -561,6 +750,12 @@ public class TestTutorial { // // To get started, we show an example where all DataNames have the same type, and where // all Names are mutable. For simplicity, our type represents the primitive int type. + // + // Note: the template library contains a lot of types that model the Java types, + // such as primitive types ({@code PrimitiveType}). The following examples + // give insight into how those types work. If you are just interested in + // how to use the predefined types, then you can find other examples in + // {@code examples/TestPrimitiveTypes.java}. private record MySimpleInt() implements DataName.Type { // The type is only subtype of itself. This is relevant when sampling or weighing // DataNames, because we do not just sample from the given type, but also its subtypes. @@ -577,31 +772,25 @@ public class TestTutorial { private static final MySimpleInt mySimpleInt = new MySimpleInt(); // In this example, we generate 3 fields, and add their names to the - // current scope. In a nested Template, we can then sample one of these - // DataNames, which gives us one of the fields. We increment that randomly - // chosen field. At the end, we print all three fields. + // current scope. We can then sample some of these DataNames, which + // gives us one of those fields each time. We increment those randomly + // chosen fields. At the end, we print all three fields. public static String generateWithDataNamesSimple() { - var templateMain = Template.make(() -> body( - // Sample a random DataName, i.e. field, and assign its name to - // the hashtag replacement "#f". - // We are picking a mutable DataName, because we are not just - // reading but also writing to the field. - let("f", dataNames(MUTABLE).exactOf(mySimpleInt).sample().name()), - """ - // Let us now sample a random field #f, and increment it. - #f += 42; - """ - )); - - var templateClass = Template.make(() -> body( + var templateClass = Template.make(() -> scope( // Let us define the names for the three fields. - // We can then sample from these names in a nested Template. // We make all DataNames mutable, and with the same weight of 1, // so that they have equal probability of being sampled. // Note: the default weight is 1, so we can also omit the weight. + // + // Also note that DataNames are only available once they are defined: + // + // Nothing defined, yet: dataNames() = {} addDataName($("f1"), mySimpleInt, MUTABLE, 1), + // Only now dataNames() contains f1: dataNames() = {f1} addDataName($("f2"), mySimpleInt, MUTABLE, 1), + // dataNames() = {f1, f2} addDataName($("f3"), mySimpleInt, MUTABLE), // omit weight, default is 1. + // dataNames() = {f1, f2, f3} """ package p.xyz; @@ -612,18 +801,35 @@ public class TestTutorial { public static int $f3 = 0; public static void main() { - // Let us now call the nested template that samples - // a random field and increments it. + // Let us now sample a random field and assign its name to + // the hashtag replacement "a". """, - templateMain.asToken(), + dataNames(MUTABLE).exactOf(mySimpleInt).sampleAndLetAs("a"), + """ + // We can now access the field, and increment it. + #a += 42; + // If we are also interested in the type of the field, we can do: + """, + dataNames(MUTABLE).exactOf(mySimpleInt).sampleAndLetAs("b", "bType"), + """ + #b += 7; + // In some cases, we may want to capture the DataName directly, which + // requires capturing the value in a lambda that creates an inner scope: + """, + dataNames(MUTABLE).exactOf(mySimpleInt).sample((DataName dn) -> scope( + let("c", dn.name()), + """ + #c += 12; + """ + )), """ // Now, we can print all three fields, and see which - // one was incremented. + // ones were incremented. System.out.println("f1: " + $f1); System.out.println("f2: " + $f2); System.out.println("f3: " + $f3); - // We have two zeros, and one 42. - if ($f1 + $f2 + $f3 != 42) { throw new RuntimeException("wrong result!"); } + // Make sure they add up to the correct sum. + if ($f1 + $f2 + $f3 != 42 + 7 + 12) { throw new RuntimeException("wrong result!"); } } } """ @@ -662,8 +868,15 @@ public class TestTutorial { public static String generateWithDataNamesForFieldsAndVariables() { // Define a static field. - var templateStaticField = Template.make("type", (DataName.Type type) -> body( - addDataName($("field"), type, MUTABLE), + // Note: it is very important that we use a "transparentScope" for the template here, + // so that the DataName can escape to outer scopes, so that it is available to + // everything that follows the DataName definition in the outer scope. + // (We could also use "hashtagScope", since those are also transparent for + // names. But it is not great style, because template boundaries are + // non-transparent for hashtags and setFuelCost anyway. So we might as + // well just use "transparentScope".) + var templateStaticField = Template.make("type", (DataName.Type type) -> transparentScope( + addDataName($("field"), type, MUTABLE), // escapes template because of "transparentScope" // Note: since we have overridden MyPrimitive::toString, we can use // the type directly as "#type" in the template, which then // gets hashtag replaced with "int" or "long". @@ -673,8 +886,10 @@ public class TestTutorial { )); // Define a local variable. - var templateLocalVariable = Template.make("type", (DataName.Type type) -> body( - addDataName($("var"), type, MUTABLE), + // Note: it is very important that we use a "transparentScope" for the template here, + // so that the DataName can escape to outer scopes. + var templateLocalVariable = Template.make("type", (DataName.Type type) -> transparentScope( + addDataName($("var"), type, MUTABLE), // escapes template because of "transparentScope" """ #type $var = 0; """ @@ -682,8 +897,8 @@ public class TestTutorial { // Sample a random field or variable, from those that are available at // the current scope. - var templateSample = Template.make("type", (DataName.Type type) -> body( - let("name", dataNames(MUTABLE).exactOf(type).sample().name()), + var templateSample = Template.make("type", (DataName.Type type) -> scope( + dataNames(MUTABLE).exactOf(type).sampleAndLetAs("name"), // Note: we could also sample from MUTABLE_OR_IMMUTABLE, we will // cover the concept of mutability in an example further down. """ @@ -692,18 +907,36 @@ public class TestTutorial { )); // Check how many fields and variables are available at the current scope. - var templateStatus = Template.make(() -> body( - let("ints", dataNames(MUTABLE).exactOf(myInt).count()), - let("longs", dataNames(MUTABLE).exactOf(myLong).count()), - // Note: we could also count the MUTABLE_OR_IMMUTABLE, we will - // cover the concept of mutability in an example further down. + var templateStatus = Template.make(() -> scope( + dataNames(MUTABLE).exactOf(myInt).count(ints -> scope( + dataNames(MUTABLE).exactOf(myLong).count(longs -> scope( + // We have now captured the values as Java variables, and can + // use them inside the scope in some "let" definitions. + let("ints", ints), + let("longs", longs), + // Note: we could also count the MUTABLE_OR_IMMUTABLE, we will + // cover the concept of mutability in an example further down. + """ + System.out.println("Status: #ints ints, #longs longs."); + """ + )) + )), + // In a real code generation case, we would most likely want to + // have the count as a Java variable so that one can take conditional + // action based on the value. For that we have to capture the count + // with a lambda and inner scope as above. If we only need to have + // the count as a hashtag replacement, we can also use the following + // trick: + dataNames(MUTABLE).exactOf(myInt).count(c -> transparentScope(let("ints", c))), + dataNames(MUTABLE).exactOf(myLong).count(c -> transparentScope(let("longs", c))), + // Because of the "transparentScope", the hashtag replacements escape. """ System.out.println("Status: #ints ints, #longs longs."); """ )); // Definition of the main method body. - var templateMain = Template.make(() -> body( + var templateMain = Template.make(() -> scope( """ System.out.println("Starting inside main..."); """, @@ -736,7 +969,7 @@ public class TestTutorial { // Definition of another method's body. It is in the same class // as the main method, so it has access to the same static fields. - var templateOther = Template.make(() -> body( + var templateOther = Template.make(() -> scope( """ System.out.println("Starting inside other..."); """, @@ -755,19 +988,19 @@ public class TestTutorial { )); // Finally, we put it all together in a class. - var templateClass = Template.make(() -> body( + var templateClass = Template.make(() -> scope( """ package p.xyz; public class InnerTest8 { """, // Class Hook for fields. - Hooks.CLASS_HOOK.anchor( + Hooks.CLASS_HOOK.anchor(scope( """ public static void main() { """, // Method Hook for local variables. - Hooks.METHOD_HOOK.anchor( + Hooks.METHOD_HOOK.anchor(scope( """ // This is the beginning of the "main" method body. System.out.println("Welcome to main!"); @@ -777,7 +1010,7 @@ public class TestTutorial { System.out.println("Going to call other..."); other(); """ - ), + )), """ } @@ -785,7 +1018,7 @@ public class TestTutorial { """, // Have a separate method hook for other, where it could insert // its own local variables (but happens not to). - Hooks.METHOD_HOOK.anchor( + Hooks.METHOD_HOOK.anchor(scope( """ System.out.println("Welcome to other!"); """, @@ -793,11 +1026,11 @@ public class TestTutorial { """ System.out.println("Done with other."); """ - ), + )), """ } """ - ), + )), """ } """ @@ -807,83 +1040,119 @@ public class TestTutorial { return templateClass.render(); } - // Let us have a closer look at how DataNames interact with scopes created by - // Templates and Hooks. Additionally, we see how the execution order of the - // lambdas and token evaluation affects the availability of DataNames. - // - // We inject the results directly into verification inside the code, so it - // is relatively simple to see what the expected results are. - // - // For simplicity, we define a simple "list" function. It collects all - // field and variable names, and immediately returns the comma separated - // list of the names. We can use that to visualize the available names - // at any point. - public static String listNames() { - return "{" + String.join(", ", dataNames(MUTABLE).exactOf(myInt).toList() - .stream().map(DataName::name).toList()) + "}"; - } + public static String generateWithScopes1() { - // Even simpler: count the available variables and return the count immediately. - public static int countNames() { - return dataNames(MUTABLE).exactOf(myInt).count(); - } - - // Having defined these helper methods, let us start with the first example. - // You should start reading this example bottom-up, starting at - // templateClass, then going to templateMain and last to templateInner. - public static String generateWithDataNamesAndScopes1() { - - var templateInner = Template.make(() -> body( - // We just got called from the templateMain. All tokens from there - // are already evaluated, so "v1" is now available: - let("l1", listNames()), + // For the examples below, we need a convenient way of asserting the state + // of the available DataNames. + var templateVerify = Template.make("count", "hasAny", "toList", (Integer count, Boolean hasAny, String toList) -> scope( + dataNames(MUTABLE).exactOf(myInt).count(c -> transparentScope(let("count2", c))), + dataNames(MUTABLE).exactOf(myInt).hasAny(h -> transparentScope(let("hasAny2", h))), + dataNames(MUTABLE).exactOf(myInt).toList(list -> transparentScope( + let("toList2", String.join(", ", list.stream().map(DataName::name).toList())) + )), """ - if (!"{v1}".equals("#l1")) { throw new RuntimeException("l1 should have been '{v1}' but was '#l1'"); } + if (#count != #count2 || + #hasAny != #hasAny2 || + !"#toList".equals("#toList2")) { + throw new RuntimeException("verify failed"); + } """ )); - var templateMain = Template.make(() -> body( - // So far, no names were defined. We expect "c1" to be zero. - let("c1", countNames()), - """ - if (#c1 != 0) { throw new RuntimeException("c1 was not zero but #c1"); } - """, - // We now add a local variable "v1" to the scope of this templateMain. - // This only generates a token, and does not immediately add the name. - // The name is only added once we evaluate the tokens, and arrive at - // this particular token. + var templateMain = Template.make(() -> scope( + "// Start with nothing:\n", + templateVerify.asToken(0, false, ""), + "// Add v1:\n", addDataName("v1", myInt, MUTABLE), - // We count again with "c2". The variable "v1" is at this point still - // in token form, hence it is not yet made available while executing - // the template lambda of templateMain. - let("c2", countNames()), + "int v1 = 1;\n", + "// Check that it is visible:\n", + templateVerify.asToken(1, true, "v1"), + "// Add v2:\n", + addDataName("v2", myInt, MUTABLE), + "int v2 = 2;\n", + "// Check that both are visible:\n", + templateVerify.asToken(2, true, "v1, v2"), + + "// Create a local scope:\n", + "{\n", scope( // for consistency, we model the code and template scope together. + "// Add v3:\n", + addDataName("v3", myInt, MUTABLE), + "int v3 = 3;\n", + "// Check that all are visible:\n", + templateVerify.asToken(3, true, "v1, v2, v3") + ), "}\n", + "// But after the scope, v3 is no longer available:\n", + templateVerify.asToken(2, true, "v1, v2"), + + "// Now let's create a list of variables.\n", + List.of(4, 5, 6).stream().map(i -> hashtagScope( + // The hashtagScope allows hashtag replacements to be local, + // and DataNames to escape, so we can use them afterwards. + let("i", i), + addDataName("v" + i, myInt, MUTABLE), + "int v#i = #i;\n" + )).toList(), + templateVerify.asToken(5, true, "v1, v2, v4, v5, v6"), + + "// Let's multiply all variables by a factor of 2, using forEach:\n", + dataNames(MUTABLE).exactOf(myInt).forEach(dn -> scope( + let("v", dn.name()), + "#v *= 2;\n" + )), + "// We can also capture the name (v) and type of the DataName:\n", + dataNames(MUTABLE).exactOf(myInt).forEach("v", "type", dn -> scope( + "#v *= 2;\n" + )), + "// Yet another option is using toList, but here that is more cumbersome:\n", + dataNames(MUTABLE).exactOf(myInt).toList(list -> scope( + list.stream().map(dn -> scope( + let("v", dn.name()), + "#v *= 2;\n" + )).toList() + )), + """ - if (#c2 != 0) { throw new RuntimeException("c2 was not zero but #c2"); } + // We verify the result again. """, - // But now we call an inner Template. This is added as a TemplateToken. - // This means it is not evaluated immediately, but only once we evaluate - // the tokens. By that time, all tokens from above are already evaluated - // and we see that "v1" is available. - templateInner.asToken() + templateVerify.asToken(5, true, "v1, v2, v4, v5, v6"), + """ + if (v1 != 1 * 8 || + v2 != 2 * 8 || + v4 != 4 * 8 || + v5 != 5 * 8 || + v6 != 6 * 8) { + throw new RuntimeException("wrong value!"); + } + """, + + "// Let us copy each variable:\n", + dataNames(MUTABLE).exactOf(myInt).forEach("v", "type", dn -> hashtagScope( + // Note that we need a hashtagScope here, so that we can reuse "v" and + // "type" as hashtag replacements in each iteration, but still let the + // copied DataNames escape. + addDataName(dn.name() + "_copy", myInt, MUTABLE), + "#type #{v}_copy = #v;\n" + )), + templateVerify.asToken(10, true, "v1, v2, v4, v5, v6, v1_copy, v2_copy, v4_copy, v5_copy, v6_copy") )); - var templateClass = Template.make(() -> body( + var templateClass = Template.make(() -> scope( """ package p.xyz; public class InnerTest9a { """, - Hooks.CLASS_HOOK.anchor( + Hooks.CLASS_HOOK.anchor(scope( """ public static void main() { """, - Hooks.METHOD_HOOK.anchor( + Hooks.METHOD_HOOK.anchor(scope( templateMain.asToken() - ), + )), """ } """ - ), + )), """ } """ @@ -893,111 +1162,129 @@ public class TestTutorial { return templateClass.render(); } - // Now that we understand this simple example, we go to a more complicated one - // where we use Hook.insert. Just as above, you should read this example - // bottom-up, starting at templateClass. - public static String generateWithDataNamesAndScopes2() { + public static String generateWithScopes2() { - var templateFields = Template.make(() -> body( - // We were just called from templateMain. But the code is not - // generated into the main scope, rather into the class scope - // out in templateClass. - // Let us now add a field "f1". - addDataName("f1", myInt, MUTABLE), - // And let's also generate the code for it. + // In this section, we will look at some subtle facts about the behavior of + // transparent scopes around hook insertion. This is intended for expert users + // so feel free to skip it until you extensively use hook insertion. + // More info can also be found in the Javadocs of the Hook class. + + // Helper method to check that the expected DataNames are available. + var templateVerify = Template.make("toList", (String toList) -> scope( + dataNames(MUTABLE).exactOf(myInt).toList(list -> transparentScope( + let("toList2", String.join(", ", list.stream().map(DataName::name).toList())) + )), """ - public static int f1 = 42; - """, - // But why is this DataName now available inside the scope of - // templateInner? Does that not mean that "f1" escapes this - // templateFields here? Yes it does! - // For normal template nesting, the names do not escape the - // scope of the nested template. But this here is no normal - // template nesting, rather it is an insertion into a Hook, - // and we treat those differently. We make the scope of the - // inserted templateFields transparent, so that any added - // DataNames are added to the scope of the Hook we just - // inserted into, i.e. the CLASS_HOOK. This is very important, - // if we did not make that scope transparent, we could not - // add any DataNames to the class scope anymore, and we could - // not add any fields that would be available in the class - // scope. - Hooks.METHOD_HOOK.anchor( - // We now create a separate scope. This one is not the - // template scope from above, and it is not transparent. - // Hence, "f2" will not be available outside of this + if (!"#toList".equals("#toList2")) { + throw new RuntimeException("verify failed: '#toList' vs '#toList2'."); + } + """ + )); + + var myHook = new Hook("MyHook"); + + var templateMain = Template.make(() -> scope( + // Start with nothing: + templateVerify.asToken(""), + addDataName("v1", myInt, MUTABLE), + templateVerify.asToken("v1"), + // Non-transparent hook anchor: + myHook.anchor(scope( + templateVerify.asToken("v1"), + addDataName("v2", myInt, MUTABLE), + templateVerify.asToken("v1, v2"), + // Insert a non-transparent scope: nothing escapes. + myHook.insert(scope( + // Note that at the anchor insertion point, v2 is not yet + // available, because it is added after the anchoring. + templateVerify.asToken("v1"), + let("x3", 42), + addDataName("v3", myInt, MUTABLE), + templateVerify.asToken("v1, v3") + )), + // Note: x3 and v3 do not escape. + let("x3", 7), // we can define it again. + templateVerify.asToken("v1, v2"), + // While not letting hashtags escape may be helpful, it is probably + // not very helpful if the DataNames don't escape. For example, if + // we are inserting some variable at an outer scope, we would like + // it to be available for the rest of the scope. + // That's where a transparent scope can be helpful. + myHook.insert(transparentScope( + // At the anchoring, still only v1 is available. + templateVerify.asToken("v1"), + let("x4", 42), // escapes to caller scope + addDataName("v4", myInt, MUTABLE), // escapes to anchor scope + templateVerify.asToken("v1, v4") + )), + // x4 escapes to the caller out here, and not to the anchor scope. + "// x4: #x4\n", + // And v4 escapes to the anchor scope, which is available from here too. + // Interesting detail: the ordering in the list indicates that v1 + // is from the outermost scope of the template, v4 is located at the + // anchor scope, and v2 is located inside the anchor scope, and + // thus comes last. + templateVerify.asToken("v1, v4, v2"), + // In most practical cases we probably don't want to let the hashtag + // escape, because they just represent something local. So we can + // use a hashtagScope, so that DataNames escape, but not hashtags. + myHook.insert(hashtagScope( + // Note: both v1 and v4 are now available at the anchoring, since + // v1 was inserted outside the anchoring scope, and v4 was just + // inserted to the anchoring scope. + templateVerify.asToken("v1, v4"), + let("x5", 42), // local, does not escape. + addDataName("v5", myInt, MUTABLE), // escapes to anchor scope + templateVerify.asToken("v1, v4, v5") + )), + let("x5", 7), // we can define it again. + templateVerify.asToken("v1, v4, v5, v2") + )), + // We left the non-transparent anchoring scope which does not let anything escape + templateVerify.asToken("v1"), + + // Let us now do something that probably should never be done. But still + // we want to demonstrate it for educational purposes: transparent anchoring + // scopes. + myHook.anchor(transparentScope( + templateVerify.asToken("v1"), + // For one, this means that DataName escape the scope directly. + addDataName("v6", myInt, MUTABLE), + templateVerify.asToken("v1, v6"), + // But also if we insert to the anchoring scope, DataNames don't just + // escape from the anchoring scope, but further out to the enclosing // scope. - addDataName("f2", myInt, MUTABLE), - // And let's also generate the code for it. - """ - public static int f2 = 666; - """ - // Similarly, if we called any nested Template here, - // and added DataNames inside, this would happen inside - // nested scopes that are not transparent. If one wanted - // to add names to the CLASS_HOOK from there, one would - // have to do another Hook.insert, and make sure that - // the names are added from the outermost scope of that - // inserted Template, because only that outermost scope - // is transparent to the CLASS_HOOK. - ) + myHook.insert(transparentScope( + templateVerify.asToken("v1, v6"), + addDataName("v7", myInt, MUTABLE), + templateVerify.asToken("v1, v6, v7") + )), + templateVerify.asToken("v1, v6, v7"), + let("x6", 42) // escapes the anchor scope + )), + // We left the transparent anchoring scope which lets the DataNames and + // hashtags escape. + "// x6: #x6\n", + templateVerify.asToken("v1, v6, v7") )); - var templateInner = Template.make(() -> body( - // We just got called from the templateMain. All tokens from there - // are already evaluated, so there should be some fields available. - // We can see field "f1". - let("l1", listNames()), - """ - if (!"{f1}".equals("#l1")) { throw new RuntimeException("l1 should have been '{f1}' but was '#l1'"); } - """ - // Now go and have a look at templateFields, to understand how that - // field was added, and why not any others. - )); - - var templateMain = Template.make(() -> body( - // So far, no names were defined. We expect "c1" to be zero. - let("c1", countNames()), - """ - if (#c1 != 0) { throw new RuntimeException("c1 was not zero but #c1"); } - """, - // We would now like to add some fields to the class scope, out in the - // templateClass. This creates a token, which is only evaluated after - // the completion of the templateMain lambda. Before you go and look - // at templateFields, just assume that it does add some fields, and - // continue reading in templateMain. - Hooks.CLASS_HOOK.insert(templateFields.asToken()), - // We count again with "c2". The fields we wanted to add above are not - // yet available, because the token is not yet evaluated. Hence, we - // still only count zero names. - let("c2", countNames()), - """ - if (#c2 != 0) { throw new RuntimeException("c2 was not zero but #c2"); } - """, - // Now we call an inner Template. This also creates a token, and so it - // is not evaluated immediately. And by the time this token is evaluated - // the tokens from above are already evaluated, and so the fields should - // be available. Go have a look at templateInner now. - templateInner.asToken() - )); - - var templateClass = Template.make(() -> body( + var templateClass = Template.make(() -> scope( """ package p.xyz; public class InnerTest9b { """, - Hooks.CLASS_HOOK.anchor( + Hooks.CLASS_HOOK.anchor(scope( """ public static void main() { """, - Hooks.METHOD_HOOK.anchor( + Hooks.METHOD_HOOK.anchor(scope( templateMain.asToken() - ), + )), """ } """ - ), + )), """ } """ @@ -1006,8 +1293,6 @@ public class TestTutorial { // Render templateClass to String. return templateClass.render(); } - - // There are two more concepts to understand more deeply with DataNames. // // One is the use of mutable and immutable DataNames. @@ -1045,38 +1330,40 @@ public class TestTutorial { private static final List myClassList = List.of(myClassA, myClassA1, myClassA2, myClassA11, myClassB); public static String generateWithDataNamesForFuzzing() { - var templateStaticField = Template.make("type", "mutable", (DataName.Type type, Boolean mutable) -> body( - addDataName($("field"), type, mutable ? MUTABLE : IMMUTABLE), + // This template is used to insert a DataName (field) into an outer scope, hence we must use + // "transparentScope" instead of "scope". + var templateStaticField = Template.make("type", "mutable", (DataName.Type type, Boolean mutable) -> transparentScope( + addDataName($("field"), type, mutable ? MUTABLE : IMMUTABLE), // Escapes the template. let("isFinal", mutable ? "" : "final"), """ public static #isFinal #type $field = new #type(); """ )); - var templateLoad = Template.make("type", (DataName.Type type) -> body( + var templateLoad = Template.make("type", (DataName.Type type) -> scope( // We only load from the field, so we do not need a mutable one, // we can load from final and non-final fields. // We want to find any field from which we can read the value and store // it in our variable v of our given type. Hence, we can take a field // of the given type or any subtype thereof. - let("field", dataNames(MUTABLE_OR_IMMUTABLE).subtypeOf(type).sample().name()), + dataNames(MUTABLE_OR_IMMUTABLE).subtypeOf(type).sampleAndLetAs("field"), """ #type $v = #field; System.out.println("#field: " + $v); """ )); - var templateStore = Template.make("type", (DataName.Type type) -> body( + var templateStore = Template.make("type", (DataName.Type type) -> scope( // We are storing to a field, so it better be non-final, i.e. mutable. // We want to store a new instance of our given type to a field. This // field must be of the given type or any supertype. - let("field", dataNames(MUTABLE).supertypeOf(type).sample().name()), + dataNames(MUTABLE).supertypeOf(type).sampleAndLetAs("field"), """ #field = new #type(); """ )); - var templateClass = Template.make(() -> body( + var templateClass = Template.make(() -> scope( """ package p.xyz; @@ -1094,7 +1381,7 @@ public class TestTutorial { // addDataName is restricted to the scope of the templateStaticField. But // with the insertion to CLASS_HOOK, the addDataName goes through the scope // of the templateStaticField out to the scope of the CLASS_HOOK. - Hooks.CLASS_HOOK.anchor( + Hooks.CLASS_HOOK.anchor(scope( myClassList.stream().map(c -> (Object)Hooks.CLASS_HOOK.insert(templateStaticField.asToken(c, true)) ).toList(), @@ -1118,7 +1405,7 @@ public class TestTutorial { """ } """ - ), + )), """ } """ @@ -1126,7 +1413,6 @@ public class TestTutorial { // Render templateClass to String. return templateClass.render(); - } // "DataNames" are useful for modeling fields and variables. They hold data, @@ -1165,9 +1451,9 @@ public class TestTutorial { public static String generateWithStructuralNamesForMethods() { // Define a method, which takes two ints, returns the result of op. - var templateMethod = Template.make("op", (String op) -> body( + var templateMethod = Template.make("op", (String op) -> transparentScope( // Register the method name, so we can later sample. - addStructuralName($("methodName"), myMethodType), + addStructuralName($("methodName"), myMethodType), // escapes the template because of "transparentScope" """ public static int $methodName(int a, int b) { return a #op b; @@ -1175,16 +1461,16 @@ public class TestTutorial { """ )); - var templateSample = Template.make(() -> body( + var templateSample = Template.make(() -> scope( // Sample a random method, and retrieve its name. - let("methodName", structuralNames().exactOf(myMethodType).sample().name()), + structuralNames().exactOf(myMethodType).sampleAndLetAs("methodName"), """ System.out.println("Calling #methodName with inputs 7 and 11"); System.out.println(" result: " + #methodName(7, 11)); """ )); - var templateClass = Template.make(() -> body( + var templateClass = Template.make(() -> scope( """ package p.xyz; @@ -1192,7 +1478,7 @@ public class TestTutorial { // Let us define some methods that we can sample from later. """, // We must anchor a CLASS_HOOK here, and insert the method definitions to that hook. - Hooks.CLASS_HOOK.anchor( + Hooks.CLASS_HOOK.anchor(scope( // If we directly nest the templateMethod, then the addStructuralName goes to the nested // scope, and is not available at the class scope, i.e. it is not visible // for sampleStructuralName outside of the templateMethod. @@ -1218,7 +1504,7 @@ public class TestTutorial { } } """ - ) + )) )); // Render templateClass to String. diff --git a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestWithTestFrameworkClass.java b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestWithTestFrameworkClass.java index 813f2976ef2..01b49db2c01 100644 --- a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestWithTestFrameworkClass.java +++ b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestWithTestFrameworkClass.java @@ -43,7 +43,7 @@ import compiler.lib.generators.Generators; import compiler.lib.template_framework.Template; import compiler.lib.template_framework.TemplateToken; -import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.scope; import static compiler.lib.template_framework.Template.let; import compiler.lib.template_framework.library.Hooks; @@ -82,7 +82,7 @@ public class TestWithTestFrameworkClass { // Generate a source Java file as String public static String generate(CompileFramework comp) { // A simple template that adds a comment. - var commentTemplate = Template.make(() -> body( + var commentTemplate = Template.make(() -> scope( """ // Comment inserted from test method to class hook. """ @@ -103,7 +103,7 @@ public class TestWithTestFrameworkClass { // - The test method makes use of hashtag replacements (#con2 and #op). // - The Check method verifies the results of the test method with the // GOLD value. - var testTemplate = Template.make("op", (String op) -> body( + var testTemplate = Template.make("op", (String op) -> scope( let("size", Generators.G.safeRestrict(Generators.G.ints(), 10_000, 20_000).next()), let("con1", Generators.G.ints().next()), let("con2", Generators.G.safeRestrict(Generators.G.ints(), 1, Integer.MAX_VALUE).next()), diff --git a/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestExpression.java b/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestExpression.java index 2dac740dd93..b34538c39c1 100644 --- a/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestExpression.java +++ b/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestExpression.java @@ -38,7 +38,7 @@ import java.util.Set; import compiler.lib.template_framework.DataName; import compiler.lib.template_framework.Template; import compiler.lib.template_framework.TemplateToken; -import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.scope; import compiler.lib.template_framework.library.CodeGenerationDataNameType; import compiler.lib.template_framework.library.Expression; @@ -93,7 +93,7 @@ public class TestExpression { Expression e3 = Expression.make(myTypeA, "[", myTypeA, ",", myTypeB, ",", myTypeA1, "]"); Expression e4 = Expression.make(myTypeA, "[", myTypeA, ",", myTypeB, ",", myTypeA1, ",", myTypeA, "]"); - var template = Template.make(() -> body( + var template = Template.make(() -> scope( "xx", e1.toString(), "yy\n", "xx", e2.toString(), "yy\n", "xx", e3.toString(), "yy\n", @@ -141,7 +141,7 @@ public class TestExpression { Expression e3e1 = e3.nest(0, e1); Expression e4e5 = e4.nest(1, e5); - var template = Template.make(() -> body( + var template = Template.make(() -> scope( "xx", e1e1.toString(), "yy\n", "xx", e2e1.toString(), "yy\n", "xx", e3e1.toString(), "yy\n", @@ -184,7 +184,7 @@ public class TestExpression { // Alternating pattern Expression deep2 = Expression.nestRandomly(myTypeA, List.of(e5, e3), 5); - var template = Template.make(() -> body( + var template = Template.make(() -> scope( "xx", e1e2.toString(), "yy\n", "xx", e1ex.toString(), "yy\n", "xx", e1e4.toString(), "yy\n", diff --git a/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestFormat.java b/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestFormat.java index fe267a3ff63..577542e085b 100644 --- a/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestFormat.java +++ b/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestFormat.java @@ -39,7 +39,7 @@ import compiler.lib.compile_framework.*; import compiler.lib.generators.*; import compiler.lib.verify.*; import compiler.lib.template_framework.Template; -import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.scope; import static compiler.lib.template_framework.Template.let; public class TestFormat { @@ -84,7 +84,7 @@ public class TestFormat { private static String generate(List list) { // Generate 2 "get" methods, one that formats via "let" (hashtag), the other via direct token. - var template1 = Template.make("info", (FormatInfo info) -> body( + var template1 = Template.make("info", (FormatInfo info) -> scope( let("id", info.id()), let("type", info.type()), let("value", info.value()), @@ -95,7 +95,7 @@ public class TestFormat { )); // For each FormatInfo in list, generate the "get" methods inside InnerTest class. - var template2 = Template.make(() -> body( + var template2 = Template.make(() -> scope( """ package p.xyz; public class InnerTest { diff --git a/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestTemplate.java b/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestTemplate.java index 35d020b6080..9be74d232a7 100644 --- a/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestTemplate.java +++ b/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestTemplate.java @@ -44,7 +44,11 @@ import compiler.lib.template_framework.StructuralName; import compiler.lib.template_framework.Hook; import compiler.lib.template_framework.TemplateBinding; import compiler.lib.template_framework.RendererException; -import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.scope; +import static compiler.lib.template_framework.Template.transparentScope; +import static compiler.lib.template_framework.Template.nameScope; +import static compiler.lib.template_framework.Template.hashtagScope; +import static compiler.lib.template_framework.Template.setFuelCostScope; import static compiler.lib.template_framework.Template.$; import static compiler.lib.template_framework.Template.let; import static compiler.lib.template_framework.Template.fuel; @@ -121,41 +125,56 @@ public class TestTemplate { // The following tests all pass, i.e. have no errors during rendering. testSingleLine(); testMultiLine(); - testBodyTokens(); + testBasicTokens(); testWithOneArgument(); testWithTwoArguments(); testWithThreeArguments(); - testNested(); - testHookSimple(); + testNestedTemplates(); + testHookSimple1(); + testHookSimple2(); + testHookSimple3(); testHookIsAnchored(); testHookNested(); testHookWithNestedTemplates(); testHookRecursion(); testDollar(); - testLet(); + testLet1(); + testLet2(); testDollarAndHashtagBrackets(); testSelector(); testRecursion(); testFuel(); testFuelCustom(); + testFuelAndScopes(); + testDataNames0a(); + testDataNames0b(); + testDataNames0c(); + testDataNames0d(); testDataNames1(); testDataNames2(); testDataNames3(); testDataNames4(); testDataNames5(); + testDataNames6(); + testStructuralNames0(); testStructuralNames1(); testStructuralNames2(); + testStructuralNames3(); + testStructuralNames4(); + testStructuralNames5(); + testStructuralNames6(); testListArgument(); + testNestedScopes1(); + testNestedScopes2(); + testTemplateScopes(); + testHookAndScopes1(); + testHookAndScopes2(); + testHookAndScopes3(); // The following tests should all fail, with an expected exception and message. expectRendererException(() -> testFailingNestedRendering(), "Nested render not allowed."); expectRendererException(() -> $("name"), "A Template method such as"); - expectRendererException(() -> let("x","y"), "A Template method such as"); expectRendererException(() -> fuel(), "A Template method such as"); - expectRendererException(() -> setFuelCost(1.0f), "A Template method such as"); - expectRendererException(() -> dataNames(MUTABLE_OR_IMMUTABLE).exactOf(myInt).count(), "A Template method such as"); - expectRendererException(() -> dataNames(MUTABLE_OR_IMMUTABLE).exactOf(myInt).sample(), "A Template method such as"); - expectRendererException(() -> (new Hook("abc")).isAnchored(), "A Template method such as"); expectRendererException(() -> testFailingDollarName1(), "Is not a valid '$' name: ''."); expectRendererException(() -> testFailingDollarName2(), "Is not a valid '$' name: '#abc'."); expectRendererException(() -> testFailingDollarName3(), "Is not a valid '$' name: 'abc#'."); @@ -178,20 +197,31 @@ public class TestTemplate { expectRendererException(() -> testFailingDollarHashtagName3(), "Is not a valid '#' replacement pattern: '#' in '#$name'."); expectRendererException(() -> testFailingDollarHashtagName4(), "Is not a valid '$' replacement pattern: '$' in '$#name'."); expectRendererException(() -> testFailingHook(), "Hook 'Hook1' was referenced but not found!"); - expectRendererException(() -> testFailingSample1(), "No variable: MUTABLE, subtypeOf(int), supertypeOf(int)."); + expectRendererException(() -> testFailingSample1a(), "No Name found for DataName.FilterdSet(MUTABLE, subtypeOf(int), supertypeOf(int))"); + expectRendererException(() -> testFailingSample1b(), "No Name found for StructuralName.FilteredSet( subtypeOf(StructuralA) supertypeOf(StructuralA))"); expectRendererException(() -> testFailingHashtag1(), "Duplicate hashtag replacement for #a"); expectRendererException(() -> testFailingHashtag2(), "Duplicate hashtag replacement for #a"); expectRendererException(() -> testFailingHashtag3(), "Duplicate hashtag replacement for #a"); expectRendererException(() -> testFailingHashtag4(), "Missing hashtag replacement for #a"); + expectRendererException(() -> testFailingHashtag5(), "Missing hashtag replacement for #a"); expectRendererException(() -> testFailingBinding1(), "Duplicate 'bind' not allowed."); expectRendererException(() -> testFailingBinding2(), "Cannot 'get' before 'bind'."); - expectIllegalArgumentException(() -> body(null), "Unexpected tokens: null"); - expectIllegalArgumentException(() -> body("x", null), "Unexpected token: null"); - expectIllegalArgumentException(() -> body(new Hook("Hook1")), "Unexpected token:"); + expectIllegalArgumentException(() -> scope(null), "Unexpected tokens: null"); + expectIllegalArgumentException(() -> scope("x", null), "Unexpected token: null"); + expectIllegalArgumentException(() -> scope(new Hook("Hook1")), "Unexpected token:"); + expectIllegalArgumentException(() -> transparentScope(null), "Unexpected tokens: null"); + expectIllegalArgumentException(() -> transparentScope("x", null), "Unexpected token: null"); + expectIllegalArgumentException(() -> transparentScope(new Hook("Hook1")), "Unexpected token:"); + expectIllegalArgumentException(() -> nameScope(null), "Unexpected tokens: null"); + expectIllegalArgumentException(() -> nameScope("x", null), "Unexpected token: null"); + expectIllegalArgumentException(() -> nameScope(new Hook("Hook1")), "Unexpected token:"); + expectIllegalArgumentException(() -> hashtagScope(null), "Unexpected tokens: null"); + expectIllegalArgumentException(() -> hashtagScope("x", null), "Unexpected token: null"); + expectIllegalArgumentException(() -> hashtagScope(new Hook("Hook1")), "Unexpected token:"); + expectIllegalArgumentException(() -> setFuelCostScope(null), "Unexpected tokens: null"); + expectIllegalArgumentException(() -> setFuelCostScope("x", null), "Unexpected token: null"); + expectIllegalArgumentException(() -> setFuelCostScope(new Hook("Hook1")), "Unexpected token:"); Hook hook1 = new Hook("Hook1"); - expectIllegalArgumentException(() -> hook1.anchor(null), "Unexpected tokens: null"); - expectIllegalArgumentException(() -> hook1.anchor("x", null), "Unexpected token: null"); - expectIllegalArgumentException(() -> hook1.anchor(hook1), "Unexpected token:"); expectIllegalArgumentException(() -> testFailingAddDataName1(), "Unexpected mutability: MUTABLE_OR_IMMUTABLE"); expectIllegalArgumentException(() -> testFailingAddDataName2(), "Unexpected weight: "); expectIllegalArgumentException(() -> testFailingAddDataName3(), "Unexpected weight: "); @@ -199,7 +229,8 @@ public class TestTemplate { expectIllegalArgumentException(() -> testFailingAddStructuralName1(), "Unexpected weight: "); expectIllegalArgumentException(() -> testFailingAddStructuralName2(), "Unexpected weight: "); expectIllegalArgumentException(() -> testFailingAddStructuralName3(), "Unexpected weight: "); - expectUnsupportedOperationException(() -> testFailingSample2(), "Must first call 'subtypeOf', 'supertypeOf', or 'exactOf'."); + expectUnsupportedOperationException(() -> testFailingSample2a(), "Must first call 'subtypeOf', 'supertypeOf', or 'exactOf'."); + expectUnsupportedOperationException(() -> testFailingSample2b(), "Must first call 'subtypeOf', 'supertypeOf', or 'exactOf'."); expectRendererException(() -> testFailingAddNameDuplication1(), "Duplicate name:"); expectRendererException(() -> testFailingAddNameDuplication2(), "Duplicate name:"); expectRendererException(() -> testFailingAddNameDuplication3(), "Duplicate name:"); @@ -208,16 +239,23 @@ public class TestTemplate { expectRendererException(() -> testFailingAddNameDuplication6(), "Duplicate name:"); expectRendererException(() -> testFailingAddNameDuplication7(), "Duplicate name:"); expectRendererException(() -> testFailingAddNameDuplication8(), "Duplicate name:"); + expectRendererException(() -> testFailingScope1(), "Duplicate hashtag replacement for #x. previous: x1, new: x2"); + expectRendererException(() -> testFailingScope2(), "Duplicate hashtag replacement for #x. previous: x1, new: x2"); + expectRendererException(() -> testFailingScope3(), "Duplicate hashtag replacement for #x. previous: a, new: b"); + expectRendererException(() -> testFailingScope4(), "Duplicate hashtag replacement for #x. previous: a, new: b"); + expectRendererException(() -> testFailingScope5(), "Duplicate name:"); + expectRendererException(() -> testFailingScope6(), "Duplicate name:"); + expectRendererException(() -> testFailingScope7(), "Duplicate name:"); } public static void testSingleLine() { - var template = Template.make(() -> body("Hello World!")); + var template = Template.make(() -> scope("Hello World!")); String code = template.render(); checkEQ(code, "Hello World!"); } public static void testMultiLine() { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( """ Code on more than a single line @@ -232,10 +270,10 @@ public class TestTemplate { checkEQ(code, expected); } - public static void testBodyTokens() { - // We can fill the body with Objects of different types, and they get concatenated. - // Lists get flattened into the body. - var template = Template.make(() -> body( + public static void testBasicTokens() { + // We can fill the scope with Objects of different types, and they get concatenated. + // Lists get flattened into the scope. + var template = Template.make(() -> scope( "start ", Integer.valueOf(1), 1, Long.valueOf(2), 2L, @@ -250,31 +288,31 @@ public class TestTemplate { public static void testWithOneArgument() { // Capture String argument via String name. - var template1 = Template.make("a", (String a) -> body("start #a end")); + var template1 = Template.make("a", (String a) -> scope("start #a end")); checkEQ(template1.render("x"), "start x end"); checkEQ(template1.render("a"), "start a end"); checkEQ(template1.render("" ), "start end"); // Capture String argument via typed lambda argument. - var template2 = Template.make("a", (String a) -> body("start ", a, " end")); + var template2 = Template.make("a", (String a) -> scope("start ", a, " end")); checkEQ(template2.render("x"), "start x end"); checkEQ(template2.render("a"), "start a end"); checkEQ(template2.render("" ), "start end"); // Capture Integer argument via String name. - var template3 = Template.make("a", (Integer a) -> body("start #a end")); + var template3 = Template.make("a", (Integer a) -> scope("start #a end")); checkEQ(template3.render(0 ), "start 0 end"); checkEQ(template3.render(22 ), "start 22 end"); checkEQ(template3.render(444), "start 444 end"); // Capture Integer argument via templated lambda argument. - var template4 = Template.make("a", (Integer a) -> body("start ", a, " end")); + var template4 = Template.make("a", (Integer a) -> scope("start ", a, " end")); checkEQ(template4.render(0 ), "start 0 end"); checkEQ(template4.render(22 ), "start 22 end"); checkEQ(template4.render(444), "start 444 end"); // Test Strings with backslashes: - var template5 = Template.make("a", (String a) -> body("start #a " + a + " end")); + var template5 = Template.make("a", (String a) -> scope("start #a " + a + " end")); checkEQ(template5.render("/"), "start / / end"); checkEQ(template5.render("\\"), "start \\ \\ end"); checkEQ(template5.render("\\\\"), "start \\\\ \\\\ end"); @@ -282,25 +320,25 @@ public class TestTemplate { public static void testWithTwoArguments() { // Capture 2 String arguments via String names. - var template1 = Template.make("a1", "a2", (String a1, String a2) -> body("start #a1 #a2 end")); + var template1 = Template.make("a1", "a2", (String a1, String a2) -> scope("start #a1 #a2 end")); checkEQ(template1.render("x", "y"), "start x y end"); checkEQ(template1.render("a", "b"), "start a b end"); checkEQ(template1.render("", "" ), "start end"); // Capture 2 String arguments via typed lambda arguments. - var template2 = Template.make("a1", "a2", (String a1, String a2) -> body("start ", a1, " ", a2, " end")); + var template2 = Template.make("a1", "a2", (String a1, String a2) -> scope("start ", a1, " ", a2, " end")); checkEQ(template2.render("x", "y"), "start x y end"); checkEQ(template2.render("a", "b"), "start a b end"); checkEQ(template2.render("", "" ), "start end"); // Capture 2 Integer arguments via String names. - var template3 = Template.make("a1", "a2", (Integer a1, Integer a2) -> body("start #a1 #a2 end")); + var template3 = Template.make("a1", "a2", (Integer a1, Integer a2) -> scope("start #a1 #a2 end")); checkEQ(template3.render(0, 1 ), "start 0 1 end"); checkEQ(template3.render(22, 33 ), "start 22 33 end"); checkEQ(template3.render(444, 555), "start 444 555 end"); // Capture 2 Integer arguments via templated lambda arguments. - var template4 = Template.make("a1", "a2", (Integer a1, Integer a2) -> body("start ", a1, " ", a2, " end")); + var template4 = Template.make("a1", "a2", (Integer a1, Integer a2) -> scope("start ", a1, " ", a2, " end")); checkEQ(template4.render(0, 1 ), "start 0 1 end"); checkEQ(template4.render(22, 33 ), "start 22 33 end"); checkEQ(template4.render(444, 555), "start 444 555 end"); @@ -308,46 +346,46 @@ public class TestTemplate { public static void testWithThreeArguments() { // Capture 3 String arguments via String names. - var template1 = Template.make("a1", "a2", "a3", (String a1, String a2, String a3) -> body("start #a1 #a2 #a3 end")); + var template1 = Template.make("a1", "a2", "a3", (String a1, String a2, String a3) -> scope("start #a1 #a2 #a3 end")); checkEQ(template1.render("x", "y", "z"), "start x y z end"); checkEQ(template1.render("a", "b", "c"), "start a b c end"); checkEQ(template1.render("", "", "" ), "start end"); // Capture 3 String arguments via typed lambda arguments. - var template2 = Template.make("a1", "a2", "a3", (String a1, String a2, String a3) -> body("start ", a1, " ", a2, " ", a3, " end")); + var template2 = Template.make("a1", "a2", "a3", (String a1, String a2, String a3) -> scope("start ", a1, " ", a2, " ", a3, " end")); checkEQ(template1.render("x", "y", "z"), "start x y z end"); checkEQ(template1.render("a", "b", "c"), "start a b c end"); checkEQ(template1.render("", "", "" ), "start end"); // Capture 3 Integer arguments via String names. - var template3 = Template.make("a1", "a2", "a3", (Integer a1, Integer a2, Integer a3) -> body("start #a1 #a2 #a3 end")); + var template3 = Template.make("a1", "a2", "a3", (Integer a1, Integer a2, Integer a3) -> scope("start #a1 #a2 #a3 end")); checkEQ(template3.render(0, 1 , 2 ), "start 0 1 2 end"); checkEQ(template3.render(22, 33 , 44 ), "start 22 33 44 end"); checkEQ(template3.render(444, 555, 666), "start 444 555 666 end"); // Capture 2 Integer arguments via templated lambda arguments. - var template4 = Template.make("a1", "a2", "a3", (Integer a1, Integer a2, Integer a3) -> body("start ", a1, " ", a2, " ", a3, " end")); + var template4 = Template.make("a1", "a2", "a3", (Integer a1, Integer a2, Integer a3) -> scope("start ", a1, " ", a2, " ", a3, " end")); checkEQ(template3.render(0, 1 , 2 ), "start 0 1 2 end"); checkEQ(template3.render(22, 33 , 44 ), "start 22 33 44 end"); checkEQ(template3.render(444, 555, 666), "start 444 555 666 end"); } - public static void testNested() { - var template1 = Template.make(() -> body("proton")); + public static void testNestedTemplates() { + var template1 = Template.make(() -> scope("proton")); - var template2 = Template.make("a1", "a2", (String a1, String a2) -> body( + var template2 = Template.make("a1", "a2", (String a1, String a2) -> scope( "electron #a1\n", "neutron #a2\n" )); - var template3 = Template.make("a1", "a2", (String a1, String a2) -> body( + var template3 = Template.make("a1", "a2", (String a1, String a2) -> scope( "Universe ", template1.asToken(), " {\n", template2.asToken("up", "down"), template2.asToken(a1, a2), "}\n" )); - var template4 = Template.make(() -> body( + var template4 = Template.make(() -> scope( template3.asToken("low", "high"), "{\n", template3.asToken("42", "24"), @@ -374,19 +412,19 @@ public class TestTemplate { checkEQ(code, expected); } - public static void testHookSimple() { + public static void testHookSimple1() { var hook1 = new Hook("Hook1"); - var template1 = Template.make(() -> body("Hello\n")); + var template1 = Template.make(() -> scope("Hello\n")); - var template2 = Template.make(() -> body( + var template2 = Template.make(() -> scope( "{\n", - hook1.anchor( + hook1.anchor(scope( "World\n", // Note: "Hello" from the template below will be inserted // above "World" above. hook1.insert(template1.asToken()) - ), + )), "}" )); @@ -400,21 +438,85 @@ public class TestTemplate { checkEQ(code, expected); } + public static void testHookSimple2() { + var hook1 = new Hook("Hook1"); + + var template2 = Template.make(() -> scope( + "{\n", + hook1.anchor(scope( + "World\n", + // Note: "Hello" from the scope below will be inserted + // above "World" above. + hook1.insert(scope( + "Hello\n" + )) + )), + "}" + )); + + String code = template2.render(); + String expected = + """ + { + Hello + World + }"""; + checkEQ(code, expected); + } + + public static void testHookSimple3() { + var hook1 = new Hook("Hook1"); + + // Ensure that insert inside insert really goes first. + var template = Template.make(() -> scope( + "{\n", + hook1.anchor(scope( + "Outer Insert\n" + )), + ">Anchor\n" + )), + "}" + )); + + String code = template.render(); + String expected = + """ + { + Inner Insert + Outer Insert + Anchor + }"""; + checkEQ(code, expected); + } + public static void testHookIsAnchored() { var hook1 = new Hook("Hook1"); - var template0 = Template.make(() -> body("isAnchored: ", hook1.isAnchored(), "\n")); + var template0 = Template.make(() -> scope("t0 isAnchored: ", hook1.isAnchored(a -> scope(a)), "\n")); - var template1 = Template.make(() -> body("Hello\n", template0.asToken())); + var template1 = Template.make(() -> scope("Hello\n", template0.asToken())); - var template2 = Template.make(() -> body( + var template2 = Template.make(() -> scope( "{\n", + "t2 isAnchored: ", hook1.isAnchored(a -> scope(a)), "\n", template0.asToken(), - hook1.anchor( + hook1.anchor(scope( "World\n", + "t2 isAnchored: ", hook1.isAnchored(a -> scope(a)), "\n", template0.asToken(), - hook1.insert(template1.asToken()) - ), + hook1.insert(template1.asToken()), + hook1.insert(scope("Beautiful\n", template0.asToken())), + "t2 isAnchored: ", hook1.isAnchored(a -> scope(a)), "\n" + )), + "t2 isAnchored: ", hook1.isAnchored(a -> scope(a)), "\n", template0.asToken(), "}" )); @@ -423,12 +525,18 @@ public class TestTemplate { String expected = """ { - isAnchored: false + t2 isAnchored: false + t0 isAnchored: false Hello - isAnchored: true + t0 isAnchored: true + Beautiful + t0 isAnchored: true World - isAnchored: true - isAnchored: false + t2 isAnchored: true + t0 isAnchored: true + t2 isAnchored: true + t2 isAnchored: false + t0 isAnchored: false }"""; checkEQ(code, expected); } @@ -436,36 +544,41 @@ public class TestTemplate { public static void testHookNested() { var hook1 = new Hook("Hook1"); - var template1 = Template.make("a", (String a) -> body("x #a x\n")); + var template1 = Template.make("a", (String a) -> scope("x #a x\n")); // Test nested use of hooks in the same template. - var template2 = Template.make(() -> body( + var template2 = Template.make(() -> scope( "{\n", - hook1.anchor(), // empty + hook1.anchor(scope()), // empty "zero\n", - hook1.anchor( + hook1.anchor(scope( template1.asToken("one"), template1.asToken("two"), hook1.insert(template1.asToken("intoHook1a")), hook1.insert(template1.asToken("intoHook1b")), + hook1.insert(scope("y 1 y\n")), + hook1.insert(scope("y 2 y\n")), template1.asToken("three"), - hook1.anchor( + hook1.anchor(scope( template1.asToken("four"), hook1.insert(template1.asToken("intoHook1c")), + hook1.insert(scope("y 3 y\n")), template1.asToken("five") - ), + )), template1.asToken("six"), - hook1.anchor(), // empty + hook1.anchor(scope()), // empty template1.asToken("seven"), hook1.insert(template1.asToken("intoHook1d")), + hook1.insert(scope("y 4 y\n")), template1.asToken("eight"), - hook1.anchor( + hook1.anchor(scope( template1.asToken("nine"), hook1.insert(template1.asToken("intoHook1e")), + hook1.insert(scope("y 5 y\n")), template1.asToken("ten") - ), + )), template1.asToken("eleven") - ), + )), "}" )); @@ -476,17 +589,22 @@ public class TestTemplate { zero x intoHook1a x x intoHook1b x + y 1 y + y 2 y x intoHook1d x + y 4 y x one x x two x x three x x intoHook1c x + y 3 y x four x x five x x six x x seven x x eight x x intoHook1e x + y 5 y x nine x x ten x x eleven x @@ -498,30 +616,30 @@ public class TestTemplate { var hook1 = new Hook("Hook1"); var hook2 = new Hook("Hook2"); - var template1 = Template.make("a", (String a) -> body("x #a x\n")); + var template1 = Template.make("a", (String a) -> scope("x #a x\n")); - var template2 = Template.make("b", (String b) -> body( + var template2 = Template.make("b", (String b) -> scope( "{\n", template1.asToken(b + "A"), hook1.insert(template1.asToken(b + "B")), hook2.insert(template1.asToken(b + "C")), template1.asToken(b + "D"), - hook1.anchor( + hook1.anchor(scope( template1.asToken(b + "E"), hook1.insert(template1.asToken(b + "F")), hook2.insert(template1.asToken(b + "G")), template1.asToken(b + "H"), - hook2.anchor( + hook2.anchor(scope( template1.asToken(b + "I"), hook1.insert(template1.asToken(b + "J")), hook2.insert(template1.asToken(b + "K")), template1.asToken(b + "L") - ), + )), template1.asToken(b + "M"), hook1.insert(template1.asToken(b + "N")), hook2.insert(template1.asToken(b + "O")), template1.asToken(b + "O") - ), + )), template1.asToken(b + "P"), hook1.insert(template1.asToken(b + "Q")), hook2.insert(template1.asToken(b + "R")), @@ -530,18 +648,18 @@ public class TestTemplate { )); // Test use of hooks across templates. - var template3 = Template.make(() -> body( + var template3 = Template.make(() -> scope( "{\n", "base-A\n", - hook1.anchor( + hook1.anchor(scope( "base-B\n", - hook2.anchor( + hook2.anchor(scope( "base-C\n", template2.asToken("sub-"), "base-D\n" - ), + )), "base-E\n" - ), + )), "base-F\n", "}\n" )); @@ -586,32 +704,32 @@ public class TestTemplate { public static void testHookRecursion() { var hook1 = new Hook("Hook1"); - var template1 = Template.make("a", (String a) -> body("x #a x\n")); + var template1 = Template.make("a", (String a) -> scope("x #a x\n")); - var template2 = Template.make("b", (String b) -> body( + var template2 = Template.make("b", (String b) -> scope( "<\n", template1.asToken(b + "A"), hook1.insert(template1.asToken(b + "B")), // sub-B is rendered before template2. template1.asToken(b + "C"), "inner-hook-start\n", - hook1.anchor( + hook1.anchor(scope( "inner-hook-end\n", template1.asToken(b + "E"), hook1.insert(template1.asToken(b + "E")), template1.asToken(b + "F") - ), + )), ">\n" )); // Test use of hooks across templates. - var template3 = Template.make(() -> body( + var template3 = Template.make(() -> scope( "{\n", "hook-start\n", - hook1.anchor( + hook1.anchor(scope( "hook-end\n", hook1.insert(template2.asToken("sub-")), "base-C\n" - ), + )), "base-D\n", "}\n" )); @@ -642,16 +760,16 @@ public class TestTemplate { public static void testDollar() { var hook1 = new Hook("Hook1"); - var template1 = Template.make("a", (String a) -> body("x $name #a x\n")); + var template1 = Template.make("a", (String a) -> scope("x $name #a x\n")); - var template2 = Template.make("a", (String a) -> body( + var template2 = Template.make("a", (String a) -> scope( "{\n", "y $name #a y\n", template1.asToken($("name")), "}\n" )); - var template3 = Template.make(() -> body( + var template3 = Template.make(() -> scope( "{\n", "$name\n", "$name", "\n", @@ -660,11 +778,11 @@ public class TestTemplate { template1.asToken("name"), // does not capture -> literal "$name" template1.asToken("$name"), // does not capture -> literal "$name" template1.asToken($("name")), // capture replacement name "name_1" - hook1.anchor( + hook1.anchor(scope( "$name\n" - ), + )), "break\n", - hook1.anchor( + hook1.anchor(scope( "one\n", hook1.insert(template1.asToken($("name"))), "two\n", @@ -672,7 +790,7 @@ public class TestTemplate { "three\n", hook1.insert(template2.asToken($("name"))), "four\n" - ), + )), "}\n" )); @@ -704,10 +822,10 @@ public class TestTemplate { checkEQ(code, expected); } - public static void testLet() { + public static void testLet1() { var hook1 = new Hook("Hook1"); - var template1 = Template.make("a", (String a) -> body( + var template1 = Template.make("a", (String a) -> scope( "{\n", "y #a y\n", let("b", "<" + a + ">"), @@ -715,25 +833,25 @@ public class TestTemplate { "}\n" )); - var template2 = Template.make("a", (Integer a) -> let("b", a * 10, b -> - body( + var template2 = Template.make("a", (Integer a) -> scope( + let("b", a * 10, b -> scope( let("c", b * 3), "abc = #a #b #c\n" - ) + )) )); - var template3 = Template.make(() -> body( + var template3 = Template.make(() -> scope( "{\n", let("x", "abc"), template1.asToken("alpha"), "break\n", "x1 = #x\n", - hook1.anchor( + hook1.anchor(transparentScope( // transparentScope allows hashtags to escape "x2 = #x\n", // leaks inside template1.asToken("beta"), let("y", "one"), "y1 = #y\n" - ), + )), "break\n", "y2 = #y\n", // leaks outside "break\n", @@ -766,8 +884,30 @@ public class TestTemplate { checkEQ(code, expected); } + public static void testLet2() { + var template = Template.make(() -> scope( + "outer {\n", + let("x", "x1", x -> scope( + "x: #x ", x, ".\n" + )), + let("x", "x2"), // definition above is limited to its scope + "x: #x\n", + "} outer\n" + )); + + String code = template.render(); + String expected = + """ + outer { + x: x1 x1. + x: x2 + } outer + """; + checkEQ(code, expected); + } + public static void testDollarAndHashtagBrackets() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( let("xyz", "abc"), let("xyz_", "def"), let("xyz_klm", "ghi"), @@ -792,19 +932,19 @@ public class TestTemplate { } public static void testSelector() { - var template1 = Template.make("a", (String a) -> body( + var template1 = Template.make("a", (String a) -> scope( "<\n", "x #a x\n", ">\n" )); - var template2 = Template.make("a", (String a) -> body( + var template2 = Template.make("a", (String a) -> scope( "<\n", "y #a y\n", ">\n" )); - var template3 = Template.make("a", (Integer a) -> body( + var template3 = Template.make("a", (Integer a) -> scope( "[\n", "z #a z\n", // Select which template should be used: @@ -813,7 +953,7 @@ public class TestTemplate { "]\n" )); - var template4 = Template.make(() -> body( + var template4 = Template.make(() -> scope( "{\n", template3.asToken(-1), "break\n", @@ -865,7 +1005,7 @@ public class TestTemplate { // Binding allows use of template1 inside template1, via the Binding indirection. var binding1 = new TemplateBinding>(); - var template1 = Template.make("i", (Integer i) -> body( + var template1 = Template.make("i", (Integer i) -> scope( "[ #i\n", // We cannot yet use the template1 directly, as it is being defined. // So we use binding1 instead. @@ -874,7 +1014,7 @@ public class TestTemplate { )); binding1.bind(template1); - var template2 = Template.make(() -> body( + var template2 = Template.make(() -> scope( "{\n", // Now, we can use template1 normally, as it is already defined. template1.asToken(3), @@ -902,7 +1042,7 @@ public class TestTemplate { } public static void testFuel() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( let("f", fuel()), "<#f>\n" @@ -910,7 +1050,7 @@ public class TestTemplate { // Binding allows use of template2 inside template2, via the Binding indirection. var binding2 = new TemplateBinding>(); - var template2 = Template.make("i", (Integer i) -> body( + var template2 = Template.make("i", (Integer i) -> scope( let("f", fuel()), "[ #i #f\n", @@ -920,7 +1060,7 @@ public class TestTemplate { )); binding2.bind(template2); - var template3 = Template.make(() -> body( + var template3 = Template.make(() -> scope( "{\n", template2.asToken(3), "}\n" @@ -948,7 +1088,7 @@ public class TestTemplate { } public static void testFuelCustom() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( setFuelCost(2.0f), let("f", fuel()), @@ -957,7 +1097,7 @@ public class TestTemplate { // Binding allows use of template2 inside template2, via the Binding indirection. var binding2 = new TemplateBinding>(); - var template2 = Template.make("i", (Integer i) -> body( + var template2 = Template.make("i", (Integer i) -> scope( setFuelCost(3.0f), let("f", fuel()), @@ -968,7 +1108,7 @@ public class TestTemplate { )); binding2.bind(template2); - var template3 = Template.make(() -> body( + var template3 = Template.make(() -> scope( setFuelCost(5.0f), let("f", fuel()), @@ -1002,46 +1142,277 @@ public class TestTemplate { checkEQ(code, expected); } + public static void testFuelAndScopes() { + var readFuelTemplate = Template.make(() -> scope( + let("f", fuel()), + "<#f>\n" + )); + + var template = Template.make(() -> scope( + let("f", fuel()), + "{#f}\n", + readFuelTemplate.asToken(), + + "scope:\n", + setFuelCost(1.0f), + scope( + readFuelTemplate.asToken(), + setFuelCost(2.0f), + readFuelTemplate.asToken() + ), + readFuelTemplate.asToken(), + + "transparentScope:\n", + setFuelCost(4.0f), + transparentScope( + readFuelTemplate.asToken(), + setFuelCost(8.0f), + readFuelTemplate.asToken() + ), + readFuelTemplate.asToken(), + + "nameScope:\n", + setFuelCost(16.0f), + nameScope( + readFuelTemplate.asToken(), + setFuelCost(32.0f), + readFuelTemplate.asToken() + ), + readFuelTemplate.asToken(), + + "hashtagScope:\n", + setFuelCost(64.0f), + hashtagScope( + readFuelTemplate.asToken(), + setFuelCost(128.0f), + readFuelTemplate.asToken() + ), + readFuelTemplate.asToken(), + + "setFuelCostScope:\n", + setFuelCost(256.0f), + setFuelCostScope( + readFuelTemplate.asToken(), + setFuelCost(512.0f), + readFuelTemplate.asToken() + ), + readFuelTemplate.asToken() + )); + + String code = template.render(1000.0f); + String expected = + """ + {1000.0f} + <990.0f> + scope: + <999.0f> + <998.0f> + <999.0f> + transparentScope: + <996.0f> + <992.0f> + <992.0f> + nameScope: + <984.0f> + <968.0f> + <968.0f> + hashtagScope: + <936.0f> + <872.0f> + <872.0f> + setFuelCostScope: + <744.0f> + <488.0f> + <744.0f> + """; + checkEQ(code, expected); + } + + public static void testDataNames0a() { + var template = Template.make(() -> scope( + // When a DataName is added, it is immediately available afterwards. + // This may seem trivial, but it requires that either both "add" and + // "sample" happen in lambda execution, or in token evaluation. + // Otherwise, one can float above the other, and lead to unintuitive + // behavior. + addDataName("x", myInt, MUTABLE), + dataNames(MUTABLE).exactOf(myInt).sampleAndLetAs("v"), + "sample: #v." + )); + + String code = template.render(); + checkEQ(code, "sample: x."); + } + + public static void testDataNames0b() { + // Test that the scope keeps local DataNames only for the scope, but that + // we can see DataNames of outer scopes. + var template = Template.make(() -> scope( + // Outer scope DataName: + addDataName("outerInt", myInt, MUTABLE), + dataNames(MUTABLE).exactOf(myInt).sample((DataName dn) -> scope( + let("name1", dn.name()), + "sample: #name1.\n", + // We can also see the outer DataName: + dataNames(MUTABLE).exactOf(myInt).sampleAndLetAs("name2"), + "sample: #name2.\n", + // Local DataName: + addDataName("innerLong", myLong, MUTABLE), + dataNames(MUTABLE).exactOf(myLong).sampleAndLetAs("name3"), + "sample: #name3.\n" + )), + // We can still see the outer scope DataName: + dataNames(MUTABLE).exactOf(myInt).sampleAndLetAs("name4"), + "sample: #name4.\n", + // But we cannot see the DataNames that are local to the inner scope. + // So here, we will always see "outerLong", and never "innerLong". + addDataName("outerLong", myLong, MUTABLE), + dataNames(MUTABLE).exactOf(myLong).sampleAndLetAs("name5"), + "sample: #name5.\n" + )); + + String code = template.render(); + String expected = + """ + sample: outerInt. + sample: outerInt. + sample: innerLong. + sample: outerInt. + sample: outerLong. + """; + checkEQ(code, expected); + } + + public static void testDataNames0c() { + // Test that hashtag replacements that are local to inner scopes are + // only visible to inner scopes, but dollar replacements are the same + // for the whole Template. + var template = Template.make(() -> scope( + let("global", "GLOBAL"), + "g: #global. $a\n", + // Create a dummy DataName so we don't get an exception from sample. + addDataName("x", myInt, MUTABLE), + dataNames(MUTABLE).exactOf(myInt).sample((DataName dn) -> scope( + "g: #global. $b\n", + let("local", "LOCAL1"), + "l: #local. $c\n" + )), + "g: #global. $d\n", + // Open the scope again just to see if we can create the local again there. + dataNames(MUTABLE).exactOf(myInt).sample((DataName dn) -> scope( + "g: #global. $e\n", + let("local", "LOCAL2"), + "l: #local. $f\n" + )), + // We can now use the "local" hashtag replacement again, since it + // was previously only defined in an inner scope. + let("local", "LOCAL3"), + "g: #global. $g\n", + "l: #local. $h\n" + )); + + String code = template.render(); + String expected = + """ + g: GLOBAL. a_1 + g: GLOBAL. b_1 + l: LOCAL1. c_1 + g: GLOBAL. d_1 + g: GLOBAL. e_1 + l: LOCAL2. f_1 + g: GLOBAL. g_1 + l: LOCAL3. h_1 + """; + checkEQ(code, expected); + } + + public static void testDataNames0d() { + var template = Template.make(() -> scope( + addDataName("x", myInt, MUTABLE), + addDataName("y", myInt, MUTABLE), + addDataName("z", myInt, MUTABLE), + addDataName("a", myLong, MUTABLE), + addDataName("b", myLong, MUTABLE), + addDataName("c", myLong, MUTABLE), + dataNames(MUTABLE).exactOf(myInt).forEach((DataName dn) -> scope( + let("name", dn.name()), + let("type", dn.type()), + "listI: #name #type.\n" + )), + dataNames(MUTABLE).exactOf(myLong).forEach((DataName dn) -> scope( + let("name", dn.name()), + let("type", dn.type()), + "listL: #name #type.\n" + )) + )); + + String code = template.render(); + String expected = + """ + listI: x int. + listI: y int. + listI: z int. + listL: a long. + listL: b long. + listL: c long. + """; + checkEQ(code, expected); + } public static void testDataNames1() { var hook1 = new Hook("Hook1"); - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "[", - dataNames(MUTABLE_OR_IMMUTABLE).exactOf(myInt).hasAny(), + dataNames(MUTABLE_OR_IMMUTABLE).exactOf(myInt).hasAny(h -> scope(h)), ", ", - dataNames(MUTABLE_OR_IMMUTABLE).exactOf(myInt).count(), + dataNames(MUTABLE_OR_IMMUTABLE).exactOf(myInt).count(c -> scope(c)), ", names: {", - String.join(", ", dataNames(MUTABLE_OR_IMMUTABLE).exactOf(myInt).toList().stream().map(DataName::name).toList()), + dataNames(MUTABLE_OR_IMMUTABLE).exactOf(myInt).toList(list -> scope( + String.join(", ", list.stream().map(DataName::name).toList()) + )), "}]\n" )); - var template2 = Template.make("name", "type", (String name, DataName.Type type) -> body( - addDataName(name, type, MUTABLE), + // Note: the scope of the template must be transparentScope, so that the addDataName can escape. + var template2 = Template.make("name", "type", (String name, DataName.Type type) -> transparentScope( + addDataName(name, type, MUTABLE), // escapes "define #type #name\n", template1.asToken() )); - var template3 = Template.make(() -> body( + var template3 = Template.make(() -> scope( "<\n", hook1.insert(template2.asToken($("name"), myInt)), "$name = 5\n", ">\n" )); - var template4 = Template.make(() -> body( + var template4 = Template.make(() -> scope( "{\n", template1.asToken(), - hook1.anchor( + hook1.anchor(scope( template1.asToken(), "something\n", - template3.asToken(), + template3.asToken(), // name_4 is inserted to hook1 "more\n", template1.asToken(), "more\n", - template2.asToken($("name"), myInt), + template2.asToken($("name"), myInt), // name_1 escapes "more\n", + template1.asToken(), + "extra\n", + hook1.insert(scope( + addDataName($("extra1"), myInt, MUTABLE), // does not escape + "$extra1 = 666\n" + )), + hook1.insert(transparentScope( + addDataName($("extra2"), myInt, MUTABLE), // escapes + "$extra2 = 42\n" + )), template1.asToken() - ), + )), + // But no names escape to down here, because the anchor scope is "scope". + "final:\n", template1.asToken(), "}\n" )); @@ -1053,6 +1424,8 @@ public class TestTemplate { [false, 0, names: {}] define int name_4 [true, 1, names: {name_4}] + extra1_1 = 666 + extra2_1 = 42 [false, 0, names: {}] something < @@ -1064,7 +1437,10 @@ public class TestTemplate { define int name_1 [true, 2, names: {name_4, name_1}] more - [true, 1, names: {name_4}] + [true, 2, names: {name_4, name_1}] + extra + [true, 3, names: {name_4, extra2_1, name_1}] + final: [false, 0, names: {}] } """; @@ -1074,17 +1450,19 @@ public class TestTemplate { public static void testDataNames2() { var hook1 = new Hook("Hook1"); - var template0 = Template.make("type", "mutability", (DataName.Type type, DataName.Mutability mutability) -> body( + var template0 = Template.make("type", "mutability", (DataName.Type type, DataName.Mutability mutability) -> scope( " #mutability: [", - dataNames(mutability).exactOf(myInt).hasAny(), + dataNames(mutability).exactOf(myInt).hasAny(h -> scope(h)), ", ", - dataNames(mutability).exactOf(myInt).count(), + dataNames(mutability).exactOf(myInt).count(c -> scope(c)), ", names: {", - String.join(", ", dataNames(mutability).exactOf(myInt).toList().stream().map(DataName::name).toList()), + dataNames(mutability).exactOf(myInt).toList(list -> scope( + String.join(", ", list.stream().map(DataName::name).toList()) + )), "}]\n" )); - var template1 = Template.make("type", (DataName.Type type) -> body( + var template1 = Template.make("type", (DataName.Type type) -> scope( "[#type:\n", template0.asToken(type, MUTABLE), template0.asToken(type, IMMUTABLE), @@ -1092,51 +1470,51 @@ public class TestTemplate { "]\n" )); - var template2 = Template.make("name", "type", (String name, DataName.Type type) -> body( - addDataName(name, type, MUTABLE), + var template2 = Template.make("name", "type", (String name, DataName.Type type) -> transparentScope( + addDataName(name, type, MUTABLE), // escapes "define mutable #type #name\n", template1.asToken(type) )); - var template3 = Template.make("name", "type", (String name, DataName.Type type) -> body( - addDataName(name, type, IMMUTABLE), + var template3 = Template.make("name", "type", (String name, DataName.Type type) -> transparentScope( + addDataName(name, type, IMMUTABLE), // escapes "define immutable #type #name\n", template1.asToken(type) )); - var template4 = Template.make("type", (DataName.Type type) -> body( + var template4 = Template.make("type", (DataName.Type type) -> scope( "{ $store\n", hook1.insert(template2.asToken($("name"), type)), "$name = 5\n", "} $store\n" )); - var template5 = Template.make("type", (DataName.Type type) -> body( + var template5 = Template.make("type", (DataName.Type type) -> scope( "{ $load\n", hook1.insert(template3.asToken($("name"), type)), "blackhole($name)\n", "} $load\n" )); - var template6 = Template.make("type", (DataName.Type type) -> body( - let("v", dataNames(MUTABLE).exactOf(type).sample().name()), + var template6 = Template.make("type", (DataName.Type type) -> scope( + dataNames(MUTABLE).exactOf(type).sampleAndLetAs("v"), "{ $sample\n", "#v = 7\n", "} $sample\n" )); - var template7 = Template.make("type", (DataName.Type type) -> body( - let("v", dataNames(MUTABLE_OR_IMMUTABLE).exactOf(type).sample().name()), + var template7 = Template.make("type", (DataName.Type type) -> scope( + dataNames(MUTABLE_OR_IMMUTABLE).exactOf(type).sampleAndLetAs("v"), "{ $sample\n", "blackhole(#v)\n", "} $sample\n" )); - var template8 = Template.make(() -> body( + var template8 = Template.make(() -> scope( "class $X {\n", template1.asToken(myInt), - hook1.anchor( - "begin $body\n", + hook1.anchor(scope( + "begin $scope\n", template1.asToken(myInt), "start with immutable\n", template5.asToken(myInt), @@ -1148,7 +1526,7 @@ public class TestTemplate { "then store to it\n", template6.asToken(myInt), template1.asToken(myInt) - ), + )), template1.asToken(myInt), "}\n" )); @@ -1174,7 +1552,7 @@ public class TestTemplate { IMMUTABLE: [true, 1, names: {name_10}] MUTABLE_OR_IMMUTABLE: [true, 2, names: {name_10, name_21}] ] - begin body_1 + begin scope_1 [int: MUTABLE: [false, 0, names: {}] IMMUTABLE: [false, 0, names: {}] @@ -1219,17 +1597,19 @@ public class TestTemplate { public static void testDataNames3() { var hook1 = new Hook("Hook1"); - var template0 = Template.make("type", "mutability", (DataName.Type type, DataName.Mutability mutability) -> body( + var template0 = Template.make("type", "mutability", (DataName.Type type, DataName.Mutability mutability) -> scope( " #mutability: [", - dataNames(mutability).exactOf(myInt).hasAny(), + dataNames(mutability).exactOf(myInt).hasAny(h -> scope(h)), ", ", - dataNames(mutability).exactOf(myInt).count(), + dataNames(mutability).exactOf(myInt).count(c -> scope(c)), ", names: {", - String.join(", ", dataNames(mutability).exactOf(myInt).toList().stream().map(DataName::name).toList()), + dataNames(mutability).exactOf(myInt).toList(list -> scope( + String.join(", ", list.stream().map(DataName::name).toList()) + )), "}]\n" )); - var template1 = Template.make("type", (DataName.Type type) -> body( + var template1 = Template.make("type", (DataName.Type type) -> scope( "[#type:\n", template0.asToken(type, MUTABLE), template0.asToken(type, IMMUTABLE), @@ -1237,11 +1617,11 @@ public class TestTemplate { "]\n" )); - var template2 = Template.make(() -> body( + var template2 = Template.make(() -> scope( "class $Y {\n", template1.asToken(myInt), - hook1.anchor( - "begin $body\n", + hook1.anchor(scope( + "begin $scope\n", template1.asToken(myInt), "define mutable $v1\n", addDataName($("v1"), myInt, MUTABLE), @@ -1249,7 +1629,7 @@ public class TestTemplate { "define immutable $v2\n", addDataName($("v2"), myInt, IMMUTABLE), template1.asToken(myInt) - ), + )), template1.asToken(myInt), "}\n" )); @@ -1263,7 +1643,7 @@ public class TestTemplate { IMMUTABLE: [false, 0, names: {}] MUTABLE_OR_IMMUTABLE: [false, 0, names: {}] ] - begin body_1 + begin scope_1 [int: MUTABLE: [false, 0, names: {}] IMMUTABLE: [false, 0, names: {}] @@ -1294,47 +1674,62 @@ public class TestTemplate { public static void testDataNames4() { var hook1 = new Hook("Hook1"); - var template1 = Template.make("type", (DataName.Type type) -> body( + var template1 = Template.make("type", (DataName.Type type) -> scope( "[#type:\n", " exact: ", - dataNames(MUTABLE).exactOf(type).hasAny(), + dataNames(MUTABLE).exactOf(type).hasAny(h -> scope(h)), ", ", - dataNames(MUTABLE).exactOf(type).count(), + dataNames(MUTABLE).exactOf(type).count(c -> scope(c)), ", {", - String.join(", ", dataNames(MUTABLE).exactOf(type).toList().stream().map(DataName::name).toList()), + dataNames(MUTABLE).exactOf(type).toList(list -> scope( + String.join(", ", list.stream().map(DataName::name).toList()) + )), "}\n", " subtype: ", - dataNames(MUTABLE).subtypeOf(type).hasAny(), + dataNames(MUTABLE).subtypeOf(type).hasAny(h -> scope(h)), ", ", - dataNames(MUTABLE).subtypeOf(type).count(), + dataNames(MUTABLE).subtypeOf(type).count(c -> scope(c)), ", {", - String.join(", ", dataNames(MUTABLE).subtypeOf(type).toList().stream().map(DataName::name).toList()), + dataNames(MUTABLE).subtypeOf(type).toList(list -> scope( + String.join(", ", list.stream().map(DataName::name).toList()) + )), + "}\n", " supertype: ", - dataNames(MUTABLE).supertypeOf(type).hasAny(), + dataNames(MUTABLE).supertypeOf(type).hasAny(h -> scope(h)), ", ", - dataNames(MUTABLE).supertypeOf(type).count(), + dataNames(MUTABLE).supertypeOf(type).count(c -> scope(c)), ", {", - String.join(", ", dataNames(MUTABLE).supertypeOf(type).toList().stream().map(DataName::name).toList()), + dataNames(MUTABLE).supertypeOf(type).toList(list -> scope( + String.join(", ", list.stream().map(DataName::name).toList()) + )), "}\n", "]\n" )); List types = List.of(myClassA, myClassA1, myClassA2, myClassA11, myClassB); - var template2 = Template.make(() -> body( + var template2 = Template.make(() -> scope( "DataNames:\n", types.stream().map(t -> template1.asToken(t)).toList() )); - var template3 = Template.make("type", (DataName.Type type) -> body( - let("name", dataNames(MUTABLE).subtypeOf(type).sample()), - "Sample #type: #name\n" + var template3 = Template.make("type", (DataName.Type type) -> scope( + dataNames(MUTABLE).subtypeOf(type).sampleAndLetAs("name1"), + "Sample #type: #name1\n", + dataNames(MUTABLE).subtypeOf(type).sampleAndLetAs("name2", "type2"), + "Sample #type: #name2 #type2\n", + dataNames(MUTABLE).subtypeOf(type).sample((DataName dn) -> scope( + let("name3", dn.name()), + let("type3", dn.type()), + let("dn", dn), // format the whole DataName with toString + "Sample #type: #name3 #type3 #dn\n" + )) )); - var template4 = Template.make(() -> body( + var template4 = Template.make(() -> scope( "class $W {\n", template2.asToken(), - hook1.anchor( + hook1.anchor(scope( "Create name for myClassA11, should be visible for the super classes\n", addDataName($("v1"), myClassA11, MUTABLE), template3.asToken(myClassA11), @@ -1345,7 +1740,7 @@ public class TestTemplate { template3.asToken(myClassA11), template3.asToken(myClassA1), template2.asToken() - ), + )), template2.asToken(), "}\n" )); @@ -1381,12 +1776,22 @@ public class TestTemplate { supertype: false, 0, {} ] Create name for myClassA11, should be visible for the super classes - Sample myClassA11: DataName[name=v1_1, type=myClassA11, mutable=true, weight=1] - Sample myClassA1: DataName[name=v1_1, type=myClassA11, mutable=true, weight=1] - Sample myClassA: DataName[name=v1_1, type=myClassA11, mutable=true, weight=1] + Sample myClassA11: v1_1 + Sample myClassA11: v1_1 myClassA11 + Sample myClassA11: v1_1 myClassA11 DataName[name=v1_1, type=myClassA11, mutable=true, weight=1] + Sample myClassA1: v1_1 + Sample myClassA1: v1_1 myClassA11 + Sample myClassA1: v1_1 myClassA11 DataName[name=v1_1, type=myClassA11, mutable=true, weight=1] + Sample myClassA: v1_1 + Sample myClassA: v1_1 myClassA11 + Sample myClassA: v1_1 myClassA11 DataName[name=v1_1, type=myClassA11, mutable=true, weight=1] Create name for myClassA, should never be visible for the sub classes - Sample myClassA11: DataName[name=v1_1, type=myClassA11, mutable=true, weight=1] - Sample myClassA1: DataName[name=v1_1, type=myClassA11, mutable=true, weight=1] + Sample myClassA11: v1_1 + Sample myClassA11: v1_1 myClassA11 + Sample myClassA11: v1_1 myClassA11 DataName[name=v1_1, type=myClassA11, mutable=true, weight=1] + Sample myClassA1: v1_1 + Sample myClassA1: v1_1 myClassA11 + Sample myClassA1: v1_1 myClassA11 DataName[name=v1_1, type=myClassA11, mutable=true, weight=1] DataNames: [myClassA: exact: true, 1, {v2_1} @@ -1449,49 +1854,61 @@ public class TestTemplate { var hook1 = new Hook("Hook1"); var hook2 = new Hook("Hook2"); - // It is safe in separate Hook scopes. - var template1 = Template.make(() -> body( - hook1.anchor( + // It is safe in separate scopes. + var template1 = Template.make(() -> scope( + scope( addDataName("name1", myInt, MUTABLE) ), - hook1.anchor( + scope( addDataName("name1", myInt, MUTABLE) - ) + ), + nameScope( + addDataName("name1", myInt, MUTABLE) + ), + nameScope( + addDataName("name1", myInt, MUTABLE) + ), + hook1.anchor(scope( + addDataName("name1", myInt, MUTABLE) + )), + hook1.anchor(scope( + addDataName("name1", myInt, MUTABLE) + )) )); // It is safe in separate Template scopes. - var template2 = Template.make(() -> body( + var template2 = Template.make(() -> scope( addDataName("name2", myInt, MUTABLE) )); - var template3 = Template.make(() -> body( + var template3 = Template.make(() -> scope( template2.asToken(), template2.asToken() )); - var template4 = Template.make(() -> body( + var template4 = Template.make(() -> scope( // The following is not safe, it would collide // with (1), because it would be inserted to the // hook1.anchor in template5, and hence be available // inside the scope where (1) is available. // See: testFailingAddNameDuplication8 // addDataName("name", myInt, MUTABLE), - hook2.anchor( + hook2.anchor(scope( // (2) This one is added second. Since it is // inside the hook2.anchor, it does not go // out to the hook1.anchor, and is not // available inside the scope of (1). addDataName("name3", myInt, MUTABLE) - ) + )) )); - var template5 = Template.make(() -> body( - hook1.anchor( + var template5 = Template.make(() -> scope( + hook1.anchor(scope( // (1) this is the first one we add. addDataName("name3", myInt, MUTABLE) - ) + )) )); // Put it all together into a single test. - var template6 = Template.make(() -> body( + var template6 = Template.make(() -> scope( template1.asToken(), template3.asToken(), template5.asToken() @@ -1502,31 +1919,128 @@ public class TestTemplate { checkEQ(code, expected); } + public static void testDataNames6() { + var template = Template.make(() -> scope( + addDataName("x", myInt, IMMUTABLE), + "int x = 5;\n", + // A DataName can be captured, and used to define a new one with the same type. + // It is important that the new DataName can escape the hashtagScope, so we have + // access to it later. + // Using "scope", it does not escape. + dataNames(IMMUTABLE).exactOf(myInt).sample(dn -> scope( + addDataName("a", dn.type(), MUTABLE), + let("v1", "a"), + "int #v1 = x + 1;\n" + )), + // Using "transparentScope", it is available. + dataNames(IMMUTABLE).exactOf(myInt).sample(dn -> transparentScope( + addDataName("b", dn.type(), MUTABLE), + let("v2", "b"), + "int #v2 = x + 2;\n" + )), + // Using "nameScope", it does not escape. + dataNames(IMMUTABLE).exactOf(myInt).sample(dn -> nameScope( + addDataName("c", dn.type(), MUTABLE), + let("v3", "c"), + "int #v3 = x + 3;\n" + )), + // Using "hashtagScope", it is available. + dataNames(IMMUTABLE).exactOf(myInt).sample(dn -> hashtagScope( + addDataName("d", dn.type(), MUTABLE), + let("v4", "d"), + "int #v4 = x + 4;\n" + )), + // Using "setFuelCostScope", it is available. + dataNames(IMMUTABLE).exactOf(myInt).sample(dn -> setFuelCostScope( + addDataName("e", dn.type(), MUTABLE), + let("v5", "e"), + "int #v5 = x + 5;\n" + )), + dataNames(MUTABLE_OR_IMMUTABLE).exactOf(myInt).forEach("name", "type", dn -> scope( + "available1: #name #type.\n" + )), + dataNames(MUTABLE_OR_IMMUTABLE).exactOf(myInt).forEach("name", "type", dn -> hashtagScope( + "available2: #name #type.\n" + )), + // Check that hashtags escape correctly too. + "hashtag v2: #v2.\n", + "hashtag v3: #v3.\n", + "hashtag v5: #v5.\n", + let("v1", "aaa"), + let("v4", "ddd") + )); + + String code = template.render(); + String expected = + """ + int x = 5; + int a = x + 1; + int b = x + 2; + int c = x + 3; + int d = x + 4; + int e = x + 5; + available1: x int. + available1: b int. + available1: d int. + available1: e int. + available2: x int. + available2: b int. + available2: d int. + available2: e int. + hashtag v2: b. + hashtag v3: c. + hashtag v5: e. + """; + checkEQ(code, expected); + } + + public static void testStructuralNames0() { + var template = Template.make(() -> scope( + // When a StructuralName is added, it is immediately available afterwards. + // This may seem trivial, but it requires that either both "add" and + // "sample" happen in lambda execution, or in token evaluation. + // Otherwise, one can float above the other, and lead to unintuitive + // behavior. + addStructuralName("x", myStructuralTypeA), + structuralNames().exactOf(myStructuralTypeA).sampleAndLetAs("v"), + "sample: #v." + )); + + String code = template.render(); + checkEQ(code, "sample: x."); + } + public static void testStructuralNames1() { var hook1 = new Hook("Hook1"); - var template1 = Template.make("type", (StructuralName.Type type) -> body( + var template1 = Template.make("type", (StructuralName.Type type) -> scope( "[#type:\n", " exact: ", - structuralNames().exactOf(type).hasAny(), + structuralNames().exactOf(type).hasAny(h -> scope(h)), ", ", - structuralNames().exactOf(type).count(), + structuralNames().exactOf(type).count(c -> scope(c)), ", {", - String.join(", ", structuralNames().exactOf(type).toList().stream().map(StructuralName::name).toList()), + structuralNames().exactOf(type).toList(list -> scope( + String.join(", ", list.stream().map(StructuralName::name).toList()) + )), "}\n", " subtype: ", - structuralNames().subtypeOf(type).hasAny(), + structuralNames().subtypeOf(type).hasAny(h -> scope(h)), ", ", - structuralNames().subtypeOf(type).count(), + structuralNames().subtypeOf(type).count(c -> scope(c)), ", {", - String.join(", ", structuralNames().subtypeOf(type).toList().stream().map(StructuralName::name).toList()), + structuralNames().subtypeOf(type).toList(list -> scope( + String.join(", ", list.stream().map(StructuralName::name).toList()) + )), "}\n", " supertype: ", - structuralNames().supertypeOf(type).hasAny(), + structuralNames().supertypeOf(type).hasAny(h -> scope(h)), ", ", - structuralNames().supertypeOf(type).count(), + structuralNames().supertypeOf(type).count(c -> scope(c)), ", {", - String.join(", ", structuralNames().supertypeOf(type).toList().stream().map(StructuralName::name).toList()), + structuralNames().supertypeOf(type).toList(list -> scope( + String.join(", ", list.stream().map(StructuralName::name).toList()) + )), "}\n", "]\n" )); @@ -1536,20 +2050,28 @@ public class TestTemplate { myStructuralTypeA2, myStructuralTypeA11, myStructuralTypeB); - var template2 = Template.make(() -> body( + var template2 = Template.make(() -> scope( "StructuralNames:\n", types.stream().map(t -> template1.asToken(t)).toList() )); - var template3 = Template.make("type", (StructuralName.Type type) -> body( - let("name", structuralNames().subtypeOf(type).sample()), - "Sample #type: #name\n" + var template3 = Template.make("type", (StructuralName.Type type) -> scope( + structuralNames().subtypeOf(type).sampleAndLetAs("name1"), + "Sample #type: #name1\n", + structuralNames().subtypeOf(type).sampleAndLetAs("name2", "type2"), + "Sample #type: #name2 #type2\n", + structuralNames().subtypeOf(type).sample((StructuralName sn) -> scope( + let("name3", sn.name()), + let("type3", sn.type()), + let("sn", sn), // format the whole StructuralName with toString + "Sample #type: #name3 #type3 #sn\n" + )) )); - var template4 = Template.make(() -> body( + var template4 = Template.make(() -> scope( "class $Q {\n", template2.asToken(), - hook1.anchor( + hook1.anchor(scope( "Create name for myStructuralTypeA11, should be visible for the supertypes\n", addStructuralName($("v1"), myStructuralTypeA11), template3.asToken(myStructuralTypeA11), @@ -1560,7 +2082,7 @@ public class TestTemplate { template3.asToken(myStructuralTypeA11), template3.asToken(myStructuralTypeA1), template2.asToken() - ), + )), template2.asToken(), "}\n" )); @@ -1596,12 +2118,22 @@ public class TestTemplate { supertype: false, 0, {} ] Create name for myStructuralTypeA11, should be visible for the supertypes - Sample StructuralA11: StructuralName[name=v1_1, type=StructuralA11, weight=1] - Sample StructuralA1: StructuralName[name=v1_1, type=StructuralA11, weight=1] - Sample StructuralA: StructuralName[name=v1_1, type=StructuralA11, weight=1] + Sample StructuralA11: v1_1 + Sample StructuralA11: v1_1 StructuralA11 + Sample StructuralA11: v1_1 StructuralA11 StructuralName[name=v1_1, type=StructuralA11, weight=1] + Sample StructuralA1: v1_1 + Sample StructuralA1: v1_1 StructuralA11 + Sample StructuralA1: v1_1 StructuralA11 StructuralName[name=v1_1, type=StructuralA11, weight=1] + Sample StructuralA: v1_1 + Sample StructuralA: v1_1 StructuralA11 + Sample StructuralA: v1_1 StructuralA11 StructuralName[name=v1_1, type=StructuralA11, weight=1] Create name for myStructuralTypeA, should never be visible for the subtypes - Sample StructuralA11: StructuralName[name=v1_1, type=StructuralA11, weight=1] - Sample StructuralA1: StructuralName[name=v1_1, type=StructuralA11, weight=1] + Sample StructuralA11: v1_1 + Sample StructuralA11: v1_1 StructuralA11 + Sample StructuralA11: v1_1 StructuralA11 StructuralName[name=v1_1, type=StructuralA11, weight=1] + Sample StructuralA1: v1_1 + Sample StructuralA1: v1_1 StructuralA11 + Sample StructuralA1: v1_1 StructuralA11 StructuralName[name=v1_1, type=StructuralA11, weight=1] StructuralNames: [StructuralA: exact: true, 1, {v2_1} @@ -1662,41 +2194,43 @@ public class TestTemplate { public static void testStructuralNames2() { var hook1 = new Hook("Hook1"); - var template1 = Template.make("type", (StructuralName.Type type) -> body( + var template1 = Template.make("type", (StructuralName.Type type) -> scope( "[#type: ", - structuralNames().exactOf(type).hasAny(), + structuralNames().exactOf(type).hasAny(h -> scope(h)), ", ", - structuralNames().exactOf(type).count(), + structuralNames().exactOf(type).count(c -> scope(c)), ", names: {", - String.join(", ", structuralNames().exactOf(type).toList().stream().map(StructuralName::name).toList()), + structuralNames().exactOf(type).toList(list -> scope( + String.join(", ", list.stream().map(StructuralName::name).toList()) + )), "}]\n" )); - var template2 = Template.make("name", "type", (String name, StructuralName.Type type) -> body( - addStructuralName(name, type), + var template2 = Template.make("name", "type", (String name, StructuralName.Type type) -> transparentScope( + addStructuralName(name, type), // escapes "define #type #name\n" )); - var template3 = Template.make("type", (StructuralName.Type type) -> body( + var template3 = Template.make("type", (StructuralName.Type type) -> scope( "{ $access\n", hook1.insert(template2.asToken($("name"), type)), "$name = 5\n", "} $access\n" )); - var template4 = Template.make("type", (StructuralName.Type type) -> body( - let("v", structuralNames().exactOf(type).sample().name()), + var template4 = Template.make("type", (StructuralName.Type type) -> scope( + structuralNames().exactOf(type).sampleAndLetAs("v"), "{ $sample\n", "blackhole(#v)\n", "} $sample\n" )); - var template8 = Template.make(() -> body( + var template8 = Template.make(() -> scope( "class $X {\n", template1.asToken(myStructuralTypeA), template1.asToken(myStructuralTypeB), - hook1.anchor( - "begin $body\n", + hook1.anchor(scope( + "begin $scope\n", template1.asToken(myStructuralTypeA), template1.asToken(myStructuralTypeB), "start with A\n", @@ -1711,7 +2245,7 @@ public class TestTemplate { template4.asToken(myStructuralTypeB), template1.asToken(myStructuralTypeA), template1.asToken(myStructuralTypeB) - ), + )), template1.asToken(myStructuralTypeA), template1.asToken(myStructuralTypeB), "}\n" @@ -1725,7 +2259,7 @@ public class TestTemplate { [StructuralB: false, 0, names: {}] define StructuralA name_6 define StructuralB name_11 - begin body_1 + begin scope_1 [StructuralA: false, 0, names: {}] [StructuralB: false, 0, names: {}] start with A @@ -1755,16 +2289,269 @@ public class TestTemplate { checkEQ(code, expected); } + public static void testStructuralNames3() { + var template = Template.make(() -> scope( + addStructuralName("a", myStructuralTypeA), + addStructuralName("b", myStructuralTypeA), + structuralNames().exactOf(myStructuralTypeA).forEach(sn -> scope( + let("name1", sn.name()), + "sn1: #name1.\n", + addStructuralName("scope_garbage1", myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).forEach(sn -> nameScope( + // We cannot use "let" here (at least not easily), otherwise we get + // a duplicate hashtag replacement. It would probably be better style + // to use a "let", but we are just checking that "nameScope" works + // for reuse of names. + "sn2: ", sn.name(), ".\n", + // But for testing, we still do a "let", just with different key. + // (This is probably bad practice, we just do this for testing) + let("name2_" + sn.name(), sn.name()), + addStructuralName("scope_garbage2", myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).forEach(sn -> transparentScope( + // Same issue with hashtags as with "nameScope". + "sn3: ", sn.name(), ".\n", + let("name3_" + sn.name(), sn.name()), + // Using the same name for each would lead to duplicates, + // so we have to modify the name here. + addStructuralName("x_" + sn.name(), myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).forEach(sn -> hashtagScope( + let("name4", sn.name()), + "sn4: #name4.\n", + // Same issue with duplicate names as with "transparentScope". + addStructuralName("y_" + sn.name(), myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).forEach(sn -> setFuelCostScope( + // Same issue with hashtags as with "nameScope". + "sn5: ", sn.name(), ".\n", + let("name5_" + sn.name(), sn.name()), + // Same issue with duplicate names as with "transparentScope". + addStructuralName("z_" + sn.name(), myStructuralTypeA) + )), + "sn2: #name2_a #name2_b.\n", // hashtags escaped + "sn3: #name3_a #name3_b.\n", // hashtags escaped + "sn5: #name5_a #name5_b #name5_x_a #name5_x_b.\n", // hashtags escaped + let("name1", "shouldBeOK1"), // hashtag did not escape + let("name4", "shouldBeOk4") // hashtag did not escape + )); + + String code = template.render(); + String expected = + """ + sn1: a. + sn1: b. + sn2: a. + sn2: b. + sn3: a. + sn3: b. + sn4: a. + sn4: b. + sn4: x_a. + sn4: x_b. + sn5: a. + sn5: b. + sn5: x_a. + sn5: x_b. + sn5: y_a. + sn5: y_b. + sn5: y_x_a. + sn5: y_x_b. + sn2: a b. + sn3: a b. + sn5: a b x_a x_b. + """; + checkEQ(code, expected); + } + + public static void testStructuralNames4() { + var template = Template.make(() -> scope( + addStructuralName("a", myStructuralTypeA), + addStructuralName("b", myStructuralTypeA), + structuralNames().exactOf(myStructuralTypeA).toList(list -> scope( + let("name1", list.size()), + "list1: #name1.\n", + addStructuralName("scope_garbage1", myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).toList(list -> nameScope( + let("name2", list.size()), + "list2: #name2.\n", + addStructuralName("scope_garbage2", myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).toList(list -> transparentScope( + let("name3", list.size()), + "list3: #name3.\n", + addStructuralName("x", myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).toList(list -> hashtagScope( + let("name4", list.size()), + "list4: #name4.\n", + addStructuralName("y", myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).toList(list -> setFuelCostScope( + let("name5", list.size()), + "list5: #name5.\n", + addStructuralName("z", myStructuralTypeA) + )), + "list2: #name2.\n", // hashtag escaped + "list3: #name3.\n", // hashtag escaped + "list5: #name5.\n", // hashtag escaped + let("name1", "shouldBeOk4"), // hashtag did not escape + let("name4", "shouldBeOk4"), // hashtag did not escape + structuralNames().exactOf(myStructuralTypeA).forEach("name", "type", sn -> scope( + "available: #name #type.\n" + )) + )); + + String code = template.render(); + String expected = + """ + list1: 2. + list2: 2. + list3: 2. + list4: 3. + list5: 4. + list2: 2. + list3: 2. + list5: 4. + available: a StructuralA. + available: b StructuralA. + available: x StructuralA. + available: y StructuralA. + available: z StructuralA. + """; + checkEQ(code, expected); + } + + public static void testStructuralNames5() { + var template = Template.make(() -> scope( + addStructuralName("a", myStructuralTypeA), + addStructuralName("b", myStructuralTypeA), + structuralNames().exactOf(myStructuralTypeA).count(c -> scope( + let("name1", c), + "list1: #name1.\n", + addStructuralName("scope_garbage1", myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).count(c -> nameScope( + let("name2", c), + "list2: #name2.\n", + addStructuralName("scope_garbage2", myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).count(c -> transparentScope( + let("name3", c), + "list3: #name3.\n", + addStructuralName("x", myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).count(c -> hashtagScope( + let("name4", c), + "list4: #name4.\n", + addStructuralName("y", myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).count(c -> setFuelCostScope( + let("name5", c), + "list5: #name5.\n", + addStructuralName("z", myStructuralTypeA) + )), + "list2: #name2.\n", // hashtag escaped + "list3: #name3.\n", // hashtag escaped + "list5: #name5.\n", // hashtag escaped + let("name1", "shouldBeOk4"), // hashtag did not escape + let("name4", "shouldBeOk4"), // hashtag did not escape + structuralNames().exactOf(myStructuralTypeA).forEach("name", "type", sn -> scope( + "available: #name #type.\n" + )) + )); + + String code = template.render(); + String expected = + """ + list1: 2. + list2: 2. + list3: 2. + list4: 3. + list5: 4. + list2: 2. + list3: 2. + list5: 4. + available: a StructuralA. + available: b StructuralA. + available: x StructuralA. + available: y StructuralA. + available: z StructuralA. + """; + checkEQ(code, expected); + } + + public static void testStructuralNames6() { + var template = Template.make(() -> scope( + addStructuralName("a", myStructuralTypeA), + addStructuralName("b", myStructuralTypeA), + structuralNames().exactOf(myStructuralTypeA).hasAny(h -> scope( + let("name1", h), + "list1: #name1.\n", + addStructuralName("scope_garbage1", myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).hasAny(h -> nameScope( + let("name2", h), + "list2: #name2.\n", + addStructuralName("scope_garbage2", myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).hasAny(h -> transparentScope( + let("name3", h), + "list3: #name3.\n", + addStructuralName("x", myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).hasAny(h -> hashtagScope( + let("name4", h), + "list4: #name4.\n", + addStructuralName("y", myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).hasAny(h -> setFuelCostScope( + let("name5", h), + "list5: #name5.\n", + addStructuralName("z", myStructuralTypeA) + )), + "list2: #name2.\n", // hashtag escaped + "list3: #name3.\n", // hashtag escaped + "list5: #name5.\n", // hashtag escaped + let("name1", "shouldBeOk4"), // hashtag did not escape + let("name4", "shouldBeOk4"), // hashtag did not escape + structuralNames().exactOf(myStructuralTypeA).forEach("name", "type", sn -> scope( + "available: #name #type.\n" + )) + )); + + String code = template.render(); + String expected = + """ + list1: true. + list2: true. + list3: true. + list4: true. + list5: true. + list2: true. + list3: true. + list5: true. + available: a StructuralA. + available: b StructuralA. + available: x StructuralA. + available: y StructuralA. + available: z StructuralA. + """; + checkEQ(code, expected); + } + record MyItem(DataName.Type type, String op) {} public static void testListArgument() { - var template1 = Template.make("item", (MyItem item) -> body( + var template1 = Template.make("item", (MyItem item) -> scope( let("type", item.type()), let("op", item.op()), "#type apply #op\n" )); - var template2 = Template.make("list", (List list) -> body( + var template2 = Template.make("list", (List list) -> scope( "class $Z {\n", // Use template1 for every item in the list. list.stream().map(item -> template1.asToken(item)).toList(), @@ -1797,12 +2584,746 @@ public class TestTemplate { checkEQ(code, expected); } + public static void testNestedScopes1() { + var listDataNames = Template.make(() -> scope( + "dataNames: {", + dataNames(MUTABLE).exactOf(myInt).forEach("name", "type", (DataName dn) -> scope( + "#name #type; " + )), + "}\n" + )); + + var template = Template.make("x", (String x) -> scope( + "$start\n", + addDataName("vx", myInt, MUTABLE), + "x: #x.\n", + listDataNames.asToken(), + // A "transparentScope" nesting essencially does nothing but create + // a list of tokens. It passes through names and hashtags. + "open transparentScope:\n", + transparentScope( + "$transparentScope\n", + let("y", "YYY"), + addDataName("vy", myInt, MUTABLE), + "x: #x.\n", + "y: #y.\n", + listDataNames.asToken() + ), + "close transparentScope.\n", + "x: #x.\n", + "y: #y.\n", + listDataNames.asToken(), + // A "hashtagScope" nesting makes hashtags local, but names + // escape the nesting. + "open hashtagScope:\n", + hashtagScope( + "$hashtagScope\n", + let("z", "ZZZ1"), + "z: #z.\n", + addDataName("vz", myInt, MUTABLE), + listDataNames.asToken() + ), + "close hashtagScope.\n", + let("z", "ZZZ2"), // we can define it again outside. + "z: #z.\n", + listDataNames.asToken(), + // We can also use hashtagScopes for loops. + List.of("a", "b", "c").stream().map(str -> hashtagScope( + "$hashtagScope\n", + let("str", str), // the hashtag is local to every element + "str: #str.\n", + addDataName("v_" + str, myInt, MUTABLE), + listDataNames.asToken() + )).toList(), + "finish str list.\n", + listDataNames.asToken(), + // A "nameScope" nesting makes names local, but hashtags + // escape the nesting. + "open nameScope:\n", + nameScope( + "$nameScope\n", + let("p", "PPP"), + "p: #p.\n", + addDataName("vp", myInt, MUTABLE), + listDataNames.asToken() + ), + "close hashtagScope.\n", + "p: #p.\n", + listDataNames.asToken(), + // A "scope" nesting makes names and hashtags local + "open scope:\n", + scope( + "$scope\n", + let("q", "QQQ1"), + "q: #q.\n", + addDataName("vq", myInt, MUTABLE), + listDataNames.asToken() + ), + "close scope.\n", + let("q", "QQQ2"), + "q: #q.\n", + listDataNames.asToken(), + // A "setFuelCostScope" nesting behaves the same as "transparentScope", as we are not using fuel here. + "open setFuelCostScope:\n", + setFuelCostScope( + "$setFuelCostScope\n", + let("r", "RRR"), + "r: #r.\n", + addDataName("vr", myInt, MUTABLE), + listDataNames.asToken() + ), + "close setFuelCostScope.\n", + "r: #r.\n", + listDataNames.asToken() + + )); + + String code = template.render("XXX"); + String expected = + """ + start_1 + x: XXX. + dataNames: {vx int; } + open transparentScope: + transparentScope_1 + x: XXX. + y: YYY. + dataNames: {vx int; vy int; } + close transparentScope. + x: XXX. + y: YYY. + dataNames: {vx int; vy int; } + open hashtagScope: + hashtagScope_1 + z: ZZZ1. + dataNames: {vx int; vy int; vz int; } + close hashtagScope. + z: ZZZ2. + dataNames: {vx int; vy int; vz int; } + hashtagScope_1 + str: a. + dataNames: {vx int; vy int; vz int; v_a int; } + hashtagScope_1 + str: b. + dataNames: {vx int; vy int; vz int; v_a int; v_b int; } + hashtagScope_1 + str: c. + dataNames: {vx int; vy int; vz int; v_a int; v_b int; v_c int; } + finish str list. + dataNames: {vx int; vy int; vz int; v_a int; v_b int; v_c int; } + open nameScope: + nameScope_1 + p: PPP. + dataNames: {vx int; vy int; vz int; v_a int; v_b int; v_c int; vp int; } + close hashtagScope. + p: PPP. + dataNames: {vx int; vy int; vz int; v_a int; v_b int; v_c int; } + open scope: + scope_1 + q: QQQ1. + dataNames: {vx int; vy int; vz int; v_a int; v_b int; v_c int; vq int; } + close scope. + q: QQQ2. + dataNames: {vx int; vy int; vz int; v_a int; v_b int; v_c int; } + open setFuelCostScope: + setFuelCostScope_1 + r: RRR. + dataNames: {vx int; vy int; vz int; v_a int; v_b int; v_c int; vr int; } + close setFuelCostScope. + r: RRR. + dataNames: {vx int; vy int; vz int; v_a int; v_b int; v_c int; vr int; } + """; + checkEQ(code, expected); + } + + public static void testNestedScopes2() { + var listDataNames = Template.make(() -> scope( + "dataNames: {", + dataNames(MUTABLE).exactOf(myInt).forEach("name", "type", (DataName dn) -> scope( + "#name #type; " + )), + "}\n" + )); + + var template = Template.make(() -> scope( + // Define some global variables. + List.of("a", "b", "c").stream().map(str -> hashtagScope( + let("var", "g_" + str), + addDataName("g_" + str, myInt, MUTABLE), + "def global #var.\n" + )).toList(), + listDataNames.asToken(), + scope( + "open scope:\n", + // Define some variables. + List.of("i", "j", "k").stream().map(str -> hashtagScope( + let("var", "v_" + str), + addDataName("v_" + str, myInt, MUTABLE), + "def #var.\n" + )).toList(), + listDataNames.asToken(), + scope( + "open inner scope:\n", + addDataName("v_local", myInt, MUTABLE), + "def v_local.\n", + listDataNames.asToken(), + "close inner scope.\n" + ), + listDataNames.asToken(), + "close scope.\n" + ), + listDataNames.asToken() + )); + + String code = template.render(); + String expected = + """ + def global g_a. + def global g_b. + def global g_c. + dataNames: {g_a int; g_b int; g_c int; } + open scope: + def v_i. + def v_j. + def v_k. + dataNames: {g_a int; g_b int; g_c int; v_i int; v_j int; v_k int; } + open inner scope: + def v_local. + dataNames: {g_a int; g_b int; g_c int; v_i int; v_j int; v_k int; v_local int; } + close inner scope. + dataNames: {g_a int; g_b int; g_c int; v_i int; v_j int; v_k int; } + close scope. + dataNames: {g_a int; g_b int; g_c int; } + """; + checkEQ(code, expected); + } + + public static void testTemplateScopes() { + var statusTemplate = Template.make(() -> scope( + "{", + structuralNames().exactOf(myStructuralTypeA).toList(list -> scope( + String.join(", ", list.stream().map(StructuralName::name).toList()) + )), + "}\n", + let("fuel", fuel()), + "fuel: #fuel\n" + )); + + var scopeTemplate = Template.make(() -> scope( + "scope:\n", + let("local", "inner scope"), + addStructuralName("x", myStructuralTypeA), + statusTemplate.asToken(), + setFuelCost(50) + )); + + var transparentScopeTemplate = Template.make(() -> transparentScope( + "transparentScope:\n", + let("local", "inner flag"), + addStructuralName("y", myStructuralTypeA), // should escape + statusTemplate.asToken(), + setFuelCost(50) + )); + + var template = Template.make(() -> scope( + setFuelCost(1), + let("local", "root"), + addStructuralName("a", myStructuralTypeA), + statusTemplate.asToken(), + scopeTemplate.asToken(), + statusTemplate.asToken(), + transparentScopeTemplate.asToken(), + statusTemplate.asToken() + )); + + String code = template.render(); + String expected = + """ + {a} + fuel: 99.0f + scope: + {a, x} + fuel: 89.0f + {a} + fuel: 99.0f + transparentScope: + {a, y} + fuel: 89.0f + {a, y} + fuel: 99.0f + """; + checkEQ(code, expected); + } + + public static void testHookAndScopes1() { + Hook hook1 = new Hook("Hook1"); + + var listNamesTemplate = Template.make(() -> scope( + "{", + structuralNames().exactOf(myStructuralTypeA).toList(list -> scope( + String.join(", ", list.stream().map(StructuralName::name).toList()) + )), + "}\n" + )); + + var insertScopeTemplate = Template.make("name", (String name) -> scope( + let("local", "insert scope garbage"), + addStructuralName(name, myStructuralTypeA), + "inserted scope: #name\n", + listNamesTemplate.asToken() + )); + + var insertTransparentScopeTemplate = Template.make("name", (String name) -> transparentScope( + let("local", "insert transparentScope garbage"), + addStructuralName(name, myStructuralTypeA), + "inserted transparentScope: #name\n", + listNamesTemplate.asToken() + )); + + var probeTemplate = Template.make(() -> scope( + "inserted probe:\n", + listNamesTemplate.asToken() + )); + + var template = Template.make(() -> scope( + "scope:\n", + hook1.anchor(scope( + let("local", "scope garbage"), + addStructuralName("x1a", myStructuralTypeA), + "scope before insert scope:\n", + listNamesTemplate.asToken(), + hook1.insert(insertScopeTemplate.asToken("x1b")), + "scope after insert scope:\n", + listNamesTemplate.asToken(), + "scope before insert transparentScope:\n", + listNamesTemplate.asToken(), + hook1.insert(insertTransparentScopeTemplate.asToken("x1c")), + "scope after insert transparentScope:\n", + listNamesTemplate.asToken(), + "scope insert probe.\n", + hook1.insert(probeTemplate.asToken()) + )), + "after scope:\n", + listNamesTemplate.asToken(), + + "transparentScope:\n", + hook1.anchor(transparentScope( + let("transparentScope2", "abc"), + addStructuralName("x2a", myStructuralTypeA), + "transparentScope before insert scope:\n", + listNamesTemplate.asToken(), + hook1.insert(insertScopeTemplate.asToken("x2b")), + "transparentScope after insert scope:\n", + listNamesTemplate.asToken(), + "transparentScope before insert transparentScope:\n", + listNamesTemplate.asToken(), + hook1.insert(insertTransparentScopeTemplate.asToken("x2c")), + "transparentScope after insert transparentScope:\n", + listNamesTemplate.asToken(), + "transparentScope insert probe.\n", + hook1.insert(probeTemplate.asToken()) + )), + "after transparentScope:\n", + listNamesTemplate.asToken(), + "transparentScope2: #transparentScope2\n", + + "hashtagScope:\n", + hook1.anchor(hashtagScope( + let("local", "hashtagScope garbage"), + addStructuralName("x3a", myStructuralTypeA), + "hashtagScope before insert scope:\n", + listNamesTemplate.asToken(), + hook1.insert(insertScopeTemplate.asToken("x3b")), + "hashtagScope after insert scope:\n", + listNamesTemplate.asToken(), + "hashtagScope before insert transparentScope:\n", + listNamesTemplate.asToken(), + hook1.insert(insertTransparentScopeTemplate.asToken("x3c")), + "hashtagScope after insert transparentScope:\n", + listNamesTemplate.asToken(), + "hashtagScope insert probe.\n", + hook1.insert(probeTemplate.asToken()) + )), + "after hashtagScope:\n", + listNamesTemplate.asToken(), + + "nameScope:\n", + hook1.anchor(nameScope( + let("transparentScope4", "abcde"), + addStructuralName("x4a", myStructuralTypeA), + "nameScope before insert scope:\n", + listNamesTemplate.asToken(), + hook1.insert(insertScopeTemplate.asToken("x4b")), + "nameScope after insert scope:\n", + listNamesTemplate.asToken(), + "nameScope before insert transparentScope:\n", + listNamesTemplate.asToken(), + hook1.insert(insertTransparentScopeTemplate.asToken("x4c")), + "nameScope after insert transparentScope:\n", + listNamesTemplate.asToken(), + "nameScope insert probe.\n", + hook1.insert(probeTemplate.asToken()) + )), + "after nameScope:\n", + listNamesTemplate.asToken(), + "transparentScope4: #transparentScope4\n", + + let("local", "outer garbage") + )); + + String code = template.render(); + String expected = + """ + scope: + inserted scope: x1b + {x1b} + inserted transparentScope: x1c + {x1c} + inserted probe: + {x1c} + scope before insert scope: + {x1a} + scope after insert scope: + {x1a} + scope before insert transparentScope: + {x1a} + scope after insert transparentScope: + {x1c, x1a} + scope insert probe. + after scope: + {} + transparentScope: + inserted scope: x2b + {x2a, x2b} + inserted transparentScope: x2c + {x2a, x2c} + inserted probe: + {x2a, x2c} + transparentScope before insert scope: + {x2a} + transparentScope after insert scope: + {x2a} + transparentScope before insert transparentScope: + {x2a} + transparentScope after insert transparentScope: + {x2a, x2c} + transparentScope insert probe. + after transparentScope: + {x2a, x2c} + transparentScope2: abc + hashtagScope: + inserted scope: x3b + {x2a, x2c, x3a, x3b} + inserted transparentScope: x3c + {x2a, x2c, x3a, x3c} + inserted probe: + {x2a, x2c, x3a, x3c} + hashtagScope before insert scope: + {x2a, x2c, x3a} + hashtagScope after insert scope: + {x2a, x2c, x3a} + hashtagScope before insert transparentScope: + {x2a, x2c, x3a} + hashtagScope after insert transparentScope: + {x2a, x2c, x3a, x3c} + hashtagScope insert probe. + after hashtagScope: + {x2a, x2c, x3a, x3c} + nameScope: + inserted scope: x4b + {x2a, x2c, x3a, x3c, x4b} + inserted transparentScope: x4c + {x2a, x2c, x3a, x3c, x4c} + inserted probe: + {x2a, x2c, x3a, x3c, x4c} + nameScope before insert scope: + {x2a, x2c, x3a, x3c, x4a} + nameScope after insert scope: + {x2a, x2c, x3a, x3c, x4a} + nameScope before insert transparentScope: + {x2a, x2c, x3a, x3c, x4a} + nameScope after insert transparentScope: + {x2a, x2c, x3a, x3c, x4c, x4a} + nameScope insert probe. + after nameScope: + {x2a, x2c, x3a, x3c} + transparentScope4: abcde + """; + checkEQ(code, expected); + } + + public static void testHookAndScopes2() { + Hook hook1 = new Hook("Hook1"); + + var listNamesTemplate = Template.make(() -> scope( + "{", + structuralNames().exactOf(myStructuralTypeA).toList(list -> scope( + String.join(", ", list.stream().map(StructuralName::name).toList()) + )), + "}\n" + )); + + var template = Template.make(() -> scope( + "scope:\n", + hook1.anchor(scope( + let("local0", "scope garbage"), + let("local1", "LOCAL1"), + addStructuralName("x1a", myStructuralTypeA), + + "scope before insert scope:\n", + listNamesTemplate.asToken(), + hook1.insert(scope( + let("local2", "insert scope garbage"), + let("name", "x1b"), + addStructuralName("x1b", myStructuralTypeA), // does NOT escape to anchor scope + "inserted scope: #name\n", + "local1: #local1\n", + listNamesTemplate.asToken() + )), + "scope after insert scope:\n", + listNamesTemplate.asToken(), + + "scope before insert transparentScope:\n", + listNamesTemplate.asToken(), + hook1.insert(transparentScope( + let("nameTransparentScope", "x1c"), // escapes to caller + addStructuralName("x1c", myStructuralTypeA), // escapes to anchor scope + "inserted transparentScope: #nameTransparentScope\n", + "local1: #local1\n", + listNamesTemplate.asToken() + )), + "scope after insert transparentScope:\n", + "nameTransparentScope: #nameTransparentScope\n", + listNamesTemplate.asToken(), + + "scope before insert nameScope:\n", + listNamesTemplate.asToken(), + hook1.insert(nameScope( + let("nameNameScope", "x1d"), // escapes to caller + addStructuralName("x1d", myStructuralTypeA), // does NOT escape to anchor scope + "inserted nameScope: #nameNameScope\n", + "local1: #local1\n", + listNamesTemplate.asToken() + )), + "scope after insert nameScope:\n", + "nameNameScope: #nameNameScope\n", + listNamesTemplate.asToken(), + + "scope before insert hashtagScope:\n", + listNamesTemplate.asToken(), + hook1.insert(hashtagScope( + let("local2", "insert hashtagScope garbage"), + let("name", "x1e"), // escapes to caller + addStructuralName("x1e", myStructuralTypeA), // escapes to anchor scope + "inserted hashtagScope: #name\n", + "local1: #local1\n", + listNamesTemplate.asToken() + )), + "scope after insert hashtagScope:\n", + listNamesTemplate.asToken(), + + "scope insert probe.\n", + hook1.insert(scope( + "inserted probe:\n", + listNamesTemplate.asToken() + )) + )), + "after scope:\n", + listNamesTemplate.asToken(), + + let("name", "name garbage"), + let("local0", "outer garbage 0"), + let("local1", "outer garbage 1"), + let("local2", "outer garbage 2"), + let("nameTransparentScope", "outer garbage nameTransparentScope"), + let("nameNameScope", "outer garbage nameNameScope") + )); + + String code = template.render(); + String expected = + """ + scope: + inserted scope: x1b + local1: LOCAL1 + {x1b} + inserted transparentScope: x1c + local1: LOCAL1 + {x1c} + inserted nameScope: x1d + local1: LOCAL1 + {x1c, x1d} + inserted hashtagScope: x1e + local1: LOCAL1 + {x1c, x1e} + inserted probe: + {x1c, x1e} + scope before insert scope: + {x1a} + scope after insert scope: + {x1a} + scope before insert transparentScope: + {x1a} + scope after insert transparentScope: + nameTransparentScope: x1c + {x1c, x1a} + scope before insert nameScope: + {x1c, x1a} + scope after insert nameScope: + nameNameScope: x1d + {x1c, x1a} + scope before insert hashtagScope: + {x1c, x1a} + scope after insert hashtagScope: + {x1c, x1e, x1a} + scope insert probe. + after scope: + {} + """; + checkEQ(code, expected); + } + + // Analogue to testHookAndScopes2, but with "transparentScope" instead of "scope". + public static void testHookAndScopes3() { + Hook hook1 = new Hook("Hook1"); + + var listNamesTemplate = Template.make(() -> scope( + "{", + structuralNames().exactOf(myStructuralTypeA).toList(list -> scope( + String.join(", ", list.stream().map(StructuralName::name).toList()) + )), + "}\n" + )); + + var template = Template.make(() -> scope( + "transparentScope:\n", + hook1.anchor(transparentScope( + let("global0", "transparentScope garbage"), + let("global1", "GLOBAL1"), + addStructuralName("x1a", myStructuralTypeA), + + "transparentScope before insert scope:\n", + listNamesTemplate.asToken(), + hook1.insert(scope( + let("local2", "insert scope garbage"), + let("name", "x1b"), + addStructuralName("x1b", myStructuralTypeA), // does NOT escape to anchor scope + "inserted scope: #name\n", + "global1: #global1\n", + listNamesTemplate.asToken() + )), + "transparentScope after insert scope:\n", + listNamesTemplate.asToken(), + + "transparentScope before insert transparentScope:\n", + listNamesTemplate.asToken(), + hook1.insert(transparentScope( + let("nameTransparentScope", "x1c"), // escapes to caller + addStructuralName("x1c", myStructuralTypeA), // escapes to anchor scope + "inserted transparentScope: #nameTransparentScope\n", + "global1: #global1\n", + listNamesTemplate.asToken() + )), + "transparentScope after insert transparentScope:\n", + "nameTransparentScope: #nameTransparentScope\n", + listNamesTemplate.asToken(), + + "transparentScope before insert nameScope:\n", + listNamesTemplate.asToken(), + hook1.insert(nameScope( + let("nameNameScope", "x1d"), // escapes to caller + addStructuralName("x1d", myStructuralTypeA), // does NOT escape to anchor scope + "inserted nameScope: #nameNameScope\n", + "global1: #global1\n", + listNamesTemplate.asToken() + )), + "transparentScope after insert nameScope:\n", + "nameNameScope: #nameNameScope\n", + listNamesTemplate.asToken(), + + "transparentScope before insert hashtagScope:\n", + listNamesTemplate.asToken(), + hook1.insert(hashtagScope( + let("local2", "insert hashtagScope garbage"), + let("name", "x1e"), // escapes to caller + addStructuralName("x1e", myStructuralTypeA), // escapes to anchor scope + "inserted hashtagScope: #name\n", + "global1: #global1\n", + listNamesTemplate.asToken() + )), + "transparentScope after insert hashtagScope:\n", + listNamesTemplate.asToken(), + + "transparentScope insert probe.\n", + hook1.insert(scope( + "inserted probe:\n", + listNamesTemplate.asToken() + )) + )), + "after transparentScope:\n", + listNamesTemplate.asToken(), + """ + global0: #global0 + global1: #global1 + nameTransparentScope: #nameTransparentScope + nameNameScope: #nameNameScope + """, + let("name", "name garbage"), + let("local2", "outer garbage 2") + )); + + String code = template.render(); + String expected = + """ + transparentScope: + inserted scope: x1b + global1: GLOBAL1 + {x1a, x1b} + inserted transparentScope: x1c + global1: GLOBAL1 + {x1a, x1c} + inserted nameScope: x1d + global1: GLOBAL1 + {x1a, x1c, x1d} + inserted hashtagScope: x1e + global1: GLOBAL1 + {x1a, x1c, x1e} + inserted probe: + {x1a, x1c, x1e} + transparentScope before insert scope: + {x1a} + transparentScope after insert scope: + {x1a} + transparentScope before insert transparentScope: + {x1a} + transparentScope after insert transparentScope: + nameTransparentScope: x1c + {x1a, x1c} + transparentScope before insert nameScope: + {x1a, x1c} + transparentScope after insert nameScope: + nameNameScope: x1d + {x1a, x1c} + transparentScope before insert hashtagScope: + {x1a, x1c} + transparentScope after insert hashtagScope: + {x1a, x1c, x1e} + transparentScope insert probe. + after transparentScope: + {x1a, x1c, x1e} + global0: transparentScope garbage + global1: GLOBAL1 + nameTransparentScope: x1c + nameNameScope: x1d + """; + checkEQ(code, expected); + } + public static void testFailingNestedRendering() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "alpha\n" )); - var template2 = Template.make(() -> body( + var template2 = Template.make(() -> scope( "beta\n", // Nested "render" call not allowed! template1.render(), @@ -1813,63 +3334,63 @@ public class TestTemplate { } public static void testFailingDollarName1() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( let("x", $("")) // empty string not allowed )); String code = template1.render(); } public static void testFailingDollarName2() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( let("x", $("#abc")) // "#" character not allowed )); String code = template1.render(); } public static void testFailingDollarName3() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( let("x", $("abc#")) // "#" character not allowed )); String code = template1.render(); } public static void testFailingDollarName4() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( let("x", $(null)) // Null input to dollar )); String code = template1.render(); } public static void testFailingDollarName5() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "$" // empty dollar name )); String code = template1.render(); } public static void testFailingDollarName6() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "asdf$" // empty dollar name )); String code = template1.render(); } public static void testFailingDollarName7() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "asdf$1" // Bad pattern after dollar )); String code = template1.render(); } public static void testFailingDollarName8() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "abc$$abc" // empty dollar name )); String code = template1.render(); } public static void testFailingLetName1() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( let(null, $("abc")) // Null input for hashtag name )); String code = template1.render(); @@ -1877,20 +3398,20 @@ public class TestTemplate { public static void testFailingHashtagName1() { // Empty Template argument - var template1 = Template.make("", (String x) -> body( + var template1 = Template.make("", (String x) -> scope( )); String code = template1.render("abc"); } public static void testFailingHashtagName2() { // "#" character not allowed in template argument - var template1 = Template.make("abc#abc", (String x) -> body( + var template1 = Template.make("abc#abc", (String x) -> scope( )); String code = template1.render("abc"); } public static void testFailingHashtagName3() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( // Empty let hashtag name not allowed let("", "abc") )); @@ -1898,7 +3419,7 @@ public class TestTemplate { } public static void testFailingHashtagName4() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( // "#" character not allowed in let hashtag name let("xyz#xyz", "abc") )); @@ -1906,56 +3427,56 @@ public class TestTemplate { } public static void testFailingHashtagName5() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "#" // empty hashtag name )); String code = template1.render(); } public static void testFailingHashtagName6() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "asdf#" // empty hashtag name )); String code = template1.render(); } public static void testFailingHashtagName7() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "asdf#1" // Bad pattern after hashtag )); String code = template1.render(); } public static void testFailingHashtagName8() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "abc##abc" // empty hashtag name )); String code = template1.render(); } public static void testFailingDollarHashtagName1() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "#$" // empty hashtag name )); String code = template1.render(); } public static void testFailingDollarHashtagName2() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "$#" // empty dollar name )); String code = template1.render(); } public static void testFailingDollarHashtagName3() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "#$name" // empty hashtag name )); String code = template1.render(); } public static void testFailingDollarHashtagName4() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "$#name" // empty dollar name )); String code = template1.render(); @@ -1964,11 +3485,11 @@ public class TestTemplate { public static void testFailingHook() { var hook1 = new Hook("Hook1"); - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "alpha\n" )); - var template2 = Template.make(() -> body( + var template2 = Template.make(() -> scope( "beta\n", // Use hook without hook1.anchor hook1.insert(template1.asToken()), @@ -1978,20 +3499,40 @@ public class TestTemplate { String code = template2.render(); } - public static void testFailingSample1() { - var template1 = Template.make(() -> body( - // No variable added yet. - let("v", dataNames(MUTABLE).exactOf(myInt).sample().name()), + public static void testFailingSample1a() { + var template1 = Template.make(() -> scope( + // No DataName added yet. + dataNames(MUTABLE).exactOf(myInt).sampleAndLetAs("v"), "v is #v\n" )); String code = template1.render(); } - public static void testFailingSample2() { - var template1 = Template.make(() -> body( + public static void testFailingSample1b() { + var template1 = Template.make(() -> scope( + // No StructuralName added yet. + structuralNames().exactOf(myStructuralTypeA).sampleAndLetAs("v"), + "v is #v\n" + )); + + String code = template1.render(); + } + + public static void testFailingSample2a() { + var template1 = Template.make(() -> scope( // no type restriction - let("v", dataNames(MUTABLE).sample().name()), + dataNames(MUTABLE).sampleAndLetAs("v"), + "v is #v\n" + )); + + String code = template1.render(); + } + + public static void testFailingSample2b() { + var template1 = Template.make(() -> scope( + // no type restriction + structuralNames().sampleAndLetAs("v"), "v is #v\n" )); @@ -2000,7 +3541,7 @@ public class TestTemplate { public static void testFailingHashtag1() { // Duplicate hashtag definition from arguments. - var template1 = Template.make("a", "a", (String _, String _) -> body( + var template1 = Template.make("a", "a", (String _, String _) -> scope( "nothing\n" )); @@ -2008,7 +3549,7 @@ public class TestTemplate { } public static void testFailingHashtag2() { - var template1 = Template.make("a", (String _) -> body( + var template1 = Template.make("a", (String _) -> scope( // Duplicate hashtag name let("a", "x"), "nothing\n" @@ -2018,7 +3559,7 @@ public class TestTemplate { } public static void testFailingHashtag3() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( let("a", "x"), // Duplicate hashtag name let("a", "y"), @@ -2029,7 +3570,7 @@ public class TestTemplate { } public static void testFailingHashtag4() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( // Missing hashtag name definition "#a\n" )); @@ -2037,9 +3578,20 @@ public class TestTemplate { String code = template1.render(); } + public static void testFailingHashtag5() { + var template1 = Template.make(() -> scope( + "use before definition: #a\n", + // let is a token, and is only evaluated after + // the string above, and so the string above fails. + let("a", "x") + )); + + String code = template1.render(); + } + public static void testFailingBinding1() { var binding = new TemplateBinding(); - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "nothing\n" )); binding.bind(template1); @@ -2049,7 +3601,7 @@ public class TestTemplate { public static void testFailingBinding2() { var binding = new TemplateBinding(); - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "nothing\n", // binding was never bound. binding.get() @@ -2059,7 +3611,7 @@ public class TestTemplate { } public static void testFailingAddDataName1() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( // Must pick either MUTABLE or IMMUTABLE. addDataName("name", myInt, MUTABLE_OR_IMMUTABLE) )); @@ -2067,7 +3619,7 @@ public class TestTemplate { } public static void testFailingAddDataName2() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( // weight out of bounds [0..1000] addDataName("name", myInt, MUTABLE, 0) )); @@ -2075,7 +3627,7 @@ public class TestTemplate { } public static void testFailingAddDataName3() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( // weight out of bounds [0..1000] addDataName("name", myInt, MUTABLE, -1) )); @@ -2083,7 +3635,7 @@ public class TestTemplate { } public static void testFailingAddDataName4() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( // weight out of bounds [0..1000] addDataName("name", myInt, MUTABLE, 1001) )); @@ -2091,7 +3643,7 @@ public class TestTemplate { } public static void testFailingAddStructuralName1() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( // weight out of bounds [0..1000] addStructuralName("name", myStructuralTypeA, 0) )); @@ -2099,7 +3651,7 @@ public class TestTemplate { } public static void testFailingAddStructuralName2() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( // weight out of bounds [0..1000] addStructuralName("name", myStructuralTypeA, -1) )); @@ -2107,7 +3659,7 @@ public class TestTemplate { } public static void testFailingAddStructuralName3() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( // weight out of bounds [0..1000] addStructuralName("name", myStructuralTypeA, 1001) )); @@ -2116,7 +3668,7 @@ public class TestTemplate { // Duplicate name in the same scope, name identical -> expect RendererException. public static void testFailingAddNameDuplication1() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( addDataName("name", myInt, MUTABLE), addDataName("name", myInt, MUTABLE) )); @@ -2125,7 +3677,7 @@ public class TestTemplate { // Duplicate name in the same scope, names have different mutability -> expect RendererException. public static void testFailingAddNameDuplication2() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( addDataName("name", myInt, MUTABLE), addDataName("name", myInt, IMMUTABLE) )); @@ -2134,7 +3686,7 @@ public class TestTemplate { // Duplicate name in the same scope, names have different type -> expect RendererException. public static void testFailingAddNameDuplication3() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( addDataName("name", myInt, MUTABLE), addDataName("name", myLong, MUTABLE) )); @@ -2143,7 +3695,7 @@ public class TestTemplate { // Duplicate name in the same scope, name identical -> expect RendererException. public static void testFailingAddNameDuplication4() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( addStructuralName("name", myStructuralTypeA), addStructuralName("name", myStructuralTypeA) )); @@ -2152,7 +3704,7 @@ public class TestTemplate { // Duplicate name in the same scope, names have different type -> expect RendererException. public static void testFailingAddNameDuplication5() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( addStructuralName("name", myStructuralTypeA), addStructuralName("name", myStructuralTypeB) )); @@ -2161,10 +3713,10 @@ public class TestTemplate { // Duplicate name in inner Template, name identical -> expect RendererException. public static void testFailingAddNameDuplication6() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( addDataName("name", myInt, MUTABLE) )); - var template2 = Template.make(() -> body( + var template2 = Template.make(() -> scope( addDataName("name", myInt, MUTABLE), template1.asToken() )); @@ -2175,11 +3727,11 @@ public class TestTemplate { public static void testFailingAddNameDuplication7() { var hook1 = new Hook("Hook1"); - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( addDataName("name", myInt, MUTABLE), - hook1.anchor( + hook1.anchor(scope( addDataName("name", myInt, MUTABLE) - ) + )) )); String code = template1.render(); } @@ -2188,19 +3740,94 @@ public class TestTemplate { public static void testFailingAddNameDuplication8() { var hook1 = new Hook("Hook1"); - var template1 = Template.make(() -> body( - addDataName("name", myInt, MUTABLE) + var template1 = Template.make(() -> transparentScope( + addDataName("name", myInt, MUTABLE) // escapes )); - var template2 = Template.make(() -> body( - hook1.anchor( + var template2 = Template.make(() -> scope( + hook1.anchor(scope( addDataName("name", myInt, MUTABLE), hook1.insert(template1.asToken()) - ) + )) )); String code = template2.render(); } + public static void testFailingScope1() { + var template = Template.make(() -> scope( + transparentScope( + let("x", "x1") // escapes + ), + let("x", "x2") // second definition + )); + String code = template.render(); + } + + public static void testFailingScope2() { + var template = Template.make(() -> scope( + nameScope( + let("x", "x1") // escapes + ), + let("x", "x2") // second definition + )); + String code = template.render(); + } + + public static void testFailingScope3() { + var template = Template.make(() -> scope( + addStructuralName("a", myStructuralTypeA), + addStructuralName("b", myStructuralTypeA), + structuralNames().exactOf(myStructuralTypeA).forEach(sn -> transparentScope( + let("x", sn.name()) // leads to duplicate hashtag + )) + )); + String code = template.render(); + } + + public static void testFailingScope4() { + var template = Template.make(() -> scope( + addStructuralName("a", myStructuralTypeA), + addStructuralName("b", myStructuralTypeA), + structuralNames().exactOf(myStructuralTypeA).forEach(sn -> nameScope( + let("x", sn.name()) // leads to duplicate hashtag + )) + )); + String code = template.render(); + } + + public static void testFailingScope5() { + var template = Template.make(() -> scope( + addStructuralName("a", myStructuralTypeA), + addStructuralName("b", myStructuralTypeA), + structuralNames().exactOf(myStructuralTypeA).forEach(sn -> transparentScope( + addStructuralName("x", myStructuralTypeA) // leads to duplicate name + )) + )); + String code = template.render(); + } + + public static void testFailingScope6() { + var template = Template.make(() -> scope( + addStructuralName("a", myStructuralTypeA), + addStructuralName("b", myStructuralTypeA), + structuralNames().exactOf(myStructuralTypeA).forEach(sn -> hashtagScope( + addStructuralName("x", myStructuralTypeA) // leads to duplicate name + )) + )); + String code = template.render(); + } + + public static void testFailingScope7() { + var template = Template.make(() -> scope( + addStructuralName("a", myStructuralTypeA), + addStructuralName("b", myStructuralTypeA), + structuralNames().exactOf(myStructuralTypeA).forEach(sn -> setFuelCostScope( + addStructuralName("x", myStructuralTypeA) // leads to duplicate name + )) + )); + String code = template.render(); + } + public static void expectRendererException(FailingTest test, String errorPrefix) { try { test.run(); From ad38a1253ae3ff92f7e0cf0fbc4d4726957b1443 Mon Sep 17 00:00:00 2001 From: Daniel Fuchs Date: Thu, 20 Nov 2025 10:19:57 +0000 Subject: [PATCH 146/418] 8371557: java/net/httpclient/http3/H3RequestRejectedTest.java: javax.net.ssl.SSLHandshakeException: local endpoint (wildcard) and remote endpoint (loopback) ports conflict Reviewed-by: jpai --- .../jdk/java/net/httpclient/http3/H3RequestRejectedTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/jdk/java/net/httpclient/http3/H3RequestRejectedTest.java b/test/jdk/java/net/httpclient/http3/H3RequestRejectedTest.java index 361a78fdd47..1731ddae833 100644 --- a/test/jdk/java/net/httpclient/http3/H3RequestRejectedTest.java +++ b/test/jdk/java/net/httpclient/http3/H3RequestRejectedTest.java @@ -50,6 +50,7 @@ import static java.net.http.HttpOption.H3_DISCOVERY; import static java.net.http.HttpOption.Http3DiscoveryMode.HTTP_3_URI_ONLY; import static java.net.http.HttpRequest.BodyPublishers; import static java.nio.charset.StandardCharsets.US_ASCII; +import static jdk.httpclient.test.lib.common.HttpServerAdapters.createClientBuilderForH3; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -100,7 +101,7 @@ class H3RequestRejectedTest { */ @Test void testAlwaysRejected() throws Exception { - try (final HttpClient client = HttpClient.newBuilder() + try (final HttpClient client = createClientBuilderForH3() .sslContext(sslContext).proxy(NO_PROXY).version(HTTP_3) .build()) { @@ -134,7 +135,7 @@ class H3RequestRejectedTest { */ @Test void testRejectedRequest() throws Exception { - try (final HttpClient client = HttpClient.newBuilder().sslContext(sslContext) + try (final HttpClient client = createClientBuilderForH3().sslContext(sslContext) .proxy(NO_PROXY).version(HTTP_3) .build()) { From c419dda4e99c3b72fbee95b93159db2e23b994b6 Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Thu, 20 Nov 2025 11:37:07 +0000 Subject: [PATCH 147/418] 8372163: G1: Remove unused G1HeapRegion::remove_code_root Reviewed-by: tschatzl --- src/hotspot/share/gc/g1/g1HeapRegion.cpp | 4 ---- src/hotspot/share/gc/g1/g1HeapRegion.hpp | 1 - 2 files changed, 5 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1HeapRegion.cpp b/src/hotspot/share/gc/g1/g1HeapRegion.cpp index b1eeb333d8d..361e19d4be5 100644 --- a/src/hotspot/share/gc/g1/g1HeapRegion.cpp +++ b/src/hotspot/share/gc/g1/g1HeapRegion.cpp @@ -307,10 +307,6 @@ void G1HeapRegion::add_code_root(nmethod* nm) { rem_set()->add_code_root(nm); } -void G1HeapRegion::remove_code_root(nmethod* nm) { - rem_set()->remove_code_root(nm); -} - void G1HeapRegion::code_roots_do(NMethodClosure* blk) const { rem_set()->code_roots_do(blk); } diff --git a/src/hotspot/share/gc/g1/g1HeapRegion.hpp b/src/hotspot/share/gc/g1/g1HeapRegion.hpp index 17ec3055b52..fe915b0dafe 100644 --- a/src/hotspot/share/gc/g1/g1HeapRegion.hpp +++ b/src/hotspot/share/gc/g1/g1HeapRegion.hpp @@ -543,7 +543,6 @@ public: // Routines for managing a list of code roots (attached to the // this region's RSet) that point into this heap region. void add_code_root(nmethod* nm); - void remove_code_root(nmethod* nm); // Applies blk->do_nmethod() to each of the entries in // the code roots list for this region From 7b11bd1b1d8dbc9bedcd8cf14e78c8f5eb06a71f Mon Sep 17 00:00:00 2001 From: Chen Liang Date: Thu, 20 Nov 2025 13:39:49 +0000 Subject: [PATCH 148/418] 8372047: ClassTransform.transformingMethodBodies andThen composes incorrectly Reviewed-by: asotona --- .../classfile/impl/TransformImpl.java | 18 ++--- test/jdk/jdk/classfile/TransformTests.java | 74 +++++++++++++++---- 2 files changed, 67 insertions(+), 25 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/TransformImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/TransformImpl.java index 23387fcb098..4cb0517ec76 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/TransformImpl.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/TransformImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, 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 @@ -120,9 +120,9 @@ public final class TransformImpl { @Override public ClassTransform andThen(ClassTransform next) { - if (next instanceof ClassMethodTransform cmt) - return new ClassMethodTransform(transform.andThen(cmt.transform), - mm -> filter.test(mm) && cmt.filter.test(mm)); + // Optimized for shared _ -> true filter in ClassTransform.transformingMethods(MethodTransform) + if (next instanceof ClassMethodTransform(var nextTransform, var nextFilter) && filter == nextFilter) + return new ClassMethodTransform(transform.andThen(nextTransform), filter); else return UnresolvedClassTransform.super.andThen(next); } @@ -143,9 +143,9 @@ public final class TransformImpl { @Override public ClassTransform andThen(ClassTransform next) { - if (next instanceof ClassFieldTransform cft) - return new ClassFieldTransform(transform.andThen(cft.transform), - mm -> filter.test(mm) && cft.filter.test(mm)); + // Optimized for shared _ -> true filter in ClassTransform.transformingFields(FieldTransform) + if (next instanceof ClassFieldTransform(var nextTransform, var nextFilter) && filter == nextFilter) + return new ClassFieldTransform(transform.andThen(nextTransform), filter); else return UnresolvedClassTransform.super.andThen(next); } @@ -208,8 +208,8 @@ public final class TransformImpl { @Override public MethodTransform andThen(MethodTransform next) { - return (next instanceof TransformImpl.MethodCodeTransform mct) - ? new TransformImpl.MethodCodeTransform(xform.andThen(mct.xform)) + return (next instanceof MethodCodeTransform(var nextXform)) + ? new TransformImpl.MethodCodeTransform(xform.andThen(nextXform)) : UnresolvedMethodTransform.super.andThen(next); } diff --git a/test/jdk/jdk/classfile/TransformTests.java b/test/jdk/jdk/classfile/TransformTests.java index b78da8b4311..351aa1ae35a 100644 --- a/test/jdk/jdk/classfile/TransformTests.java +++ b/test/jdk/jdk/classfile/TransformTests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, 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,25 +23,15 @@ /* * @test - * @bug 8335935 8336588 + * @bug 8335935 8336588 8372047 * @summary Testing ClassFile transformations. * @run junit TransformTests */ -import java.lang.classfile.ClassBuilder; -import java.lang.classfile.ClassElement; -import java.lang.classfile.ClassFile; -import java.lang.classfile.ClassModel; -import java.lang.classfile.ClassTransform; -import java.lang.classfile.CodeBuilder; -import java.lang.classfile.CodeElement; -import java.lang.classfile.CodeModel; -import java.lang.classfile.CodeTransform; -import java.lang.classfile.FieldModel; -import java.lang.classfile.FieldTransform; -import java.lang.classfile.Label; -import java.lang.classfile.MethodModel; -import java.lang.classfile.MethodTransform; +import java.lang.classfile.*; +import java.lang.classfile.attribute.AnnotationDefaultAttribute; +import java.lang.classfile.attribute.ConstantValueAttribute; +import java.lang.classfile.attribute.SourceDebugExtensionAttribute; import java.lang.classfile.instruction.BranchInstruction; import java.lang.classfile.instruction.ConstantInstruction; import java.lang.classfile.instruction.LabelTarget; @@ -54,8 +44,10 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.HashSet; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; import helpers.ByteArrayClassLoader; +import jdk.internal.classfile.impl.TransformImpl; import org.junit.jupiter.api.Test; import static java.lang.classfile.ClassFile.*; @@ -334,4 +326,54 @@ class TransformTests { return "foo"; } } + + @Test + void testFilteringTransformChaining() { + var cf = ClassFile.of(); + var clazz = cf.parse(cf.build(ClassDesc.of("Test"), clb -> clb + .withField("one", CD_int, fb -> fb.with(ConstantValueAttribute.of(1))) + .withField("two", CD_int, fb -> fb.with(ConstantValueAttribute.of(2))) + .withMethod("one", MTD_void, 0, mb -> mb.with(AnnotationDefaultAttribute.of(AnnotationValue.ofInt(1))).withCode(CodeBuilder::return_)) + .withMethod("two", MTD_void, 0, mb -> mb.with(AnnotationDefaultAttribute.of(AnnotationValue.ofInt(2))).withCode(CodeBuilder::return_)))); + + AtomicBoolean oneFieldCalled = new AtomicBoolean(false); + var oneFieldTransform = new TransformImpl.ClassFieldTransform((fb, fe) -> { + if (fe instanceof ConstantValueAttribute cv) { + assertEquals(1, ((Integer) cv.constant().constantValue()), "Should only transform one"); + } + oneFieldCalled.set(true); + fb.with(fe); + }, fm -> fm.fieldName().equalsString("one")); + AtomicBoolean twoFieldCalled = new AtomicBoolean(false); + var twoFieldTransform = new TransformImpl.ClassFieldTransform((fb, fe) -> { + if (fe instanceof ConstantValueAttribute cv) { + assertEquals(2, ((Integer) cv.constant().constantValue()), "Should only transform two"); + } + twoFieldCalled.set(true); + fb.with(fe); + }, fm -> fm.fieldName().equalsString("two")); + cf.transformClass(clazz, oneFieldTransform.andThen(twoFieldTransform)); + assertTrue(oneFieldCalled.get(), "Field one not transformed"); + assertTrue(twoFieldCalled.get(), "Field two not transformed"); + + AtomicBoolean oneMethodCalled = new AtomicBoolean(false); + var oneMethodTransform = ClassTransform.transformingMethods(mm -> mm.methodName().equalsString("one"), (mb, me) -> { + if (me instanceof AnnotationDefaultAttribute ada) { + assertEquals(1, ((AnnotationValue.OfInt) ada.defaultValue()).intValue(), "Should only transform one"); + } + oneMethodCalled.set(true); + mb.with(me); + }); + AtomicBoolean twoMethodCalled = new AtomicBoolean(false); + var twoMethodTransform = ClassTransform.transformingMethods(mm -> mm.methodName().equalsString("two"), (mb, me) -> { + if (me instanceof AnnotationDefaultAttribute ada) { + assertEquals(2, ((AnnotationValue.OfInt) ada.defaultValue()).intValue(), "Should only transform two"); + } + twoMethodCalled.set(true); + mb.with(me); + }); + cf.transformClass(clazz, oneMethodTransform.andThen(twoMethodTransform)); + assertTrue(oneMethodCalled.get(), "Method one not transformed"); + assertTrue(twoMethodCalled.get(), "Method two not transformed"); + } } From f125c76f5b53d90a09f58c22d6def7d843feaa50 Mon Sep 17 00:00:00 2001 From: Matthew Donovan Date: Thu, 20 Nov 2025 14:09:55 +0000 Subject: [PATCH 149/418] 8247690: RunTest does not support running of JTREG manual tests Reviewed-by: erikj --- doc/testing.html | 2 ++ doc/testing.md | 4 ++++ make/RunTests.gmk | 10 ++++++++-- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/doc/testing.html b/doc/testing.html index b9838735e4f..31f4fbd1778 100644 --- a/doc/testing.html +++ b/doc/testing.html @@ -535,6 +535,8 @@ failure. This helps to reproduce intermittent test failures. Defaults to

    REPORT

    Use this report style when reporting test results (sent to JTReg as -report). Defaults to files.

    +

    MANUAL

    +

    Set to true to execute manual tests only.

    Gtest keywords

    REPEAT

    The number of times to repeat the tests diff --git a/doc/testing.md b/doc/testing.md index 0144610a5bf..b95f59de9fd 100644 --- a/doc/testing.md +++ b/doc/testing.md @@ -512,6 +512,10 @@ helps to reproduce intermittent test failures. Defaults to 0. Use this report style when reporting test results (sent to JTReg as `-report`). Defaults to `files`. +#### MANUAL + +Set to `true` to execute manual tests only. + ### Gtest keywords #### REPEAT diff --git a/make/RunTests.gmk b/make/RunTests.gmk index 947389f64f9..21eb178343e 100644 --- a/make/RunTests.gmk +++ b/make/RunTests.gmk @@ -206,7 +206,7 @@ $(eval $(call ParseKeywordVariable, JTREG, \ SINGLE_KEYWORDS := JOBS TIMEOUT_FACTOR FAILURE_HANDLER_TIMEOUT \ TEST_MODE ASSERT VERBOSE RETAIN TEST_THREAD_FACTORY JVMTI_STRESS_AGENT \ MAX_MEM RUN_PROBLEM_LISTS RETRY_COUNT REPEAT_COUNT MAX_OUTPUT REPORT \ - AOT_JDK $(CUSTOM_JTREG_SINGLE_KEYWORDS), \ + AOT_JDK MANUAL $(CUSTOM_JTREG_SINGLE_KEYWORDS), \ STRING_KEYWORDS := OPTIONS JAVA_OPTIONS VM_OPTIONS KEYWORDS \ EXTRA_PROBLEM_LISTS LAUNCHER_OPTIONS \ $(CUSTOM_JTREG_STRING_KEYWORDS), \ @@ -911,7 +911,13 @@ define SetupRunJtregTestBody -vmoption:-Dtest.boot.jdk="$$(BOOT_JDK)" \ -vmoption:-Djava.io.tmpdir="$$($1_TEST_TMP_DIR)" - $1_JTREG_BASIC_OPTIONS += -automatic -ignore:quiet + $1_JTREG_BASIC_OPTIONS += -ignore:quiet + + ifeq ($$(JTREG_MANUAL), true) + $1_JTREG_BASIC_OPTIONS += -manual + else + $1_JTREG_BASIC_OPTIONS += -automatic + endif # Make it possible to specify the JIB_DATA_DIR for tests using the # JIB Artifact resolver From b9ee9541cffb6c5a737b08a69ae04472b3bcab3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20=C3=96sterlund?= Date: Thu, 20 Nov 2025 14:33:40 +0000 Subject: [PATCH 150/418] 8371200: ZGC: C2 allocation deopt race Reviewed-by: aboldtch, stefank --- src/hotspot/share/gc/z/zBarrier.inline.hpp | 4 -- src/hotspot/share/gc/z/zBarrierSet.cpp | 20 ---------- src/hotspot/share/gc/z/zGeneration.cpp | 42 ++++++++++++-------- src/hotspot/share/gc/z/zRelocate.cpp | 45 ++++++++++++++++------ src/hotspot/share/gc/z/zRelocate.hpp | 1 + 5 files changed, 60 insertions(+), 52 deletions(-) diff --git a/src/hotspot/share/gc/z/zBarrier.inline.hpp b/src/hotspot/share/gc/z/zBarrier.inline.hpp index b5923f01628..766a6eb8e4c 100644 --- a/src/hotspot/share/gc/z/zBarrier.inline.hpp +++ b/src/hotspot/share/gc/z/zBarrier.inline.hpp @@ -86,10 +86,6 @@ inline void ZBarrier::self_heal(ZBarrierFastPath fast_path, volatile zpointer* p assert(ZPointer::is_remapped(heal_ptr), "invariant"); for (;;) { - if (ptr == zpointer::null) { - assert(!ZVerifyOops || !ZHeap::heap()->is_in(uintptr_t(p)) || !ZHeap::heap()->is_old(p), "No raw null in old"); - } - assert_transition_monotonicity(ptr, heal_ptr); // Heal diff --git a/src/hotspot/share/gc/z/zBarrierSet.cpp b/src/hotspot/share/gc/z/zBarrierSet.cpp index 87f93043bdf..643eba1947e 100644 --- a/src/hotspot/share/gc/z/zBarrierSet.cpp +++ b/src/hotspot/share/gc/z/zBarrierSet.cpp @@ -223,27 +223,7 @@ void ZBarrierSet::on_slowpath_allocation_exit(JavaThread* thread, oop new_obj) { // breaks that promise. Take a few steps in the interpreter instead, which has // no such assumptions about where an object resides. deoptimize_allocation(thread); - return; } - - if (!ZGeneration::young()->is_phase_mark_complete()) { - return; - } - - if (!page->is_relocatable()) { - return; - } - - if (ZRelocate::compute_to_age(age) != ZPageAge::old) { - return; - } - - // If the object is young, we have to still be careful that it isn't racingly - // about to get promoted to the old generation. That causes issues when null - // pointers are supposed to be coloured, but the JIT is a bit sloppy and - // reinitializes memory with raw nulls. We detect this situation and detune - // rather than relying on the JIT to never be sloppy with redundant initialization. - deoptimize_allocation(thread); } void ZBarrierSet::print_on(outputStream* st) const { diff --git a/src/hotspot/share/gc/z/zGeneration.cpp b/src/hotspot/share/gc/z/zGeneration.cpp index d1680b6c336..2b632ef29a9 100644 --- a/src/hotspot/share/gc/z/zGeneration.cpp +++ b/src/hotspot/share/gc/z/zGeneration.cpp @@ -111,6 +111,16 @@ static const ZStatSampler ZSamplerJavaThreads("System", "Java Threads", ZStatUni ZGenerationYoung* ZGeneration::_young; ZGenerationOld* ZGeneration::_old; +class ZRendezvousHandshakeClosure : public HandshakeClosure { +public: + ZRendezvousHandshakeClosure() + : HandshakeClosure("ZRendezvous") {} + + void do_thread(Thread* thread) { + // Does nothing + } +}; + ZGeneration::ZGeneration(ZGenerationId id, ZPageTable* page_table, ZPageAllocator* page_allocator) : _id(id), _page_allocator(page_allocator), @@ -168,11 +178,19 @@ void ZGeneration::free_empty_pages(ZRelocationSetSelector* selector, int bulk) { } void ZGeneration::flip_age_pages(const ZRelocationSetSelector* selector) { - if (is_young()) { - _relocate.flip_age_pages(selector->not_selected_small()); - _relocate.flip_age_pages(selector->not_selected_medium()); - _relocate.flip_age_pages(selector->not_selected_large()); - } + _relocate.flip_age_pages(selector->not_selected_small()); + _relocate.flip_age_pages(selector->not_selected_medium()); + _relocate.flip_age_pages(selector->not_selected_large()); + + // Perform a handshake between flip promotion and running the promotion barrier. This ensures + // that ZBarrierSet::on_slowpath_allocation_exit() observing a young page that was then racingly + // flip promoted, will run any stores without barriers to completion before responding to the + // handshake at the subsequent safepoint poll. This ensures that the flip promotion barriers always + // run after compiled code missing barriers, but before relocate start. + ZRendezvousHandshakeClosure cl; + Handshake::execute(&cl); + + _relocate.barrier_flip_promoted_pages(_relocation_set.flip_promoted_pages()); } static double fragmentation_limit(ZGenerationId generation) { @@ -235,7 +253,9 @@ void ZGeneration::select_relocation_set(bool promote_all) { _relocation_set.install(&selector); // Flip age young pages that were not selected - flip_age_pages(&selector); + if (is_young()) { + flip_age_pages(&selector); + } // Setup forwarding table ZRelocationSetIterator rs_iter(&_relocation_set); @@ -1280,16 +1300,6 @@ bool ZGenerationOld::uses_clear_all_soft_reference_policy() const { return _reference_processor.uses_clear_all_soft_reference_policy(); } -class ZRendezvousHandshakeClosure : public HandshakeClosure { -public: - ZRendezvousHandshakeClosure() - : HandshakeClosure("ZRendezvous") {} - - void do_thread(Thread* thread) { - // Does nothing - } -}; - class ZRendezvousGCThreads: public VM_Operation { public: VMOp_Type type() const { return VMOp_ZRendezvousGCThreads; } diff --git a/src/hotspot/share/gc/z/zRelocate.cpp b/src/hotspot/share/gc/z/zRelocate.cpp index 69233da6f54..180ce22b041 100644 --- a/src/hotspot/share/gc/z/zRelocate.cpp +++ b/src/hotspot/share/gc/z/zRelocate.cpp @@ -1322,7 +1322,7 @@ private: public: ZFlipAgePagesTask(const ZArray* pages) - : ZTask("ZPromotePagesTask"), + : ZTask("ZFlipAgePagesTask"), _iter(pages) {} virtual void work() { @@ -1337,16 +1337,6 @@ public: // Figure out if this is proper promotion const bool promotion = to_age == ZPageAge::old; - if (promotion) { - // Before promoting an object (and before relocate start), we must ensure that all - // contained zpointers are store good. The marking code ensures that for non-null - // pointers, but null pointers are ignored. This code ensures that even null pointers - // are made store good, for the promoted objects. - prev_page->object_iterate([&](oop obj) { - ZIterator::basic_oop_iterate_safe(obj, ZBarrier::promote_barrier_on_young_oop_field); - }); - } - // Logging prev_page->log_msg(promotion ? " (flip promoted)" : " (flip survived)"); @@ -1360,7 +1350,7 @@ public: if (promotion) { ZGeneration::young()->flip_promote(prev_page, new_page); - // Defer promoted page registration times the lock is taken + // Defer promoted page registration promoted_pages.push(prev_page); } @@ -1371,11 +1361,42 @@ public: } }; +class ZPromoteBarrierTask : public ZTask { +private: + ZArrayParallelIterator _iter; + +public: + ZPromoteBarrierTask(const ZArray* pages) + : ZTask("ZPromoteBarrierTask"), + _iter(pages) {} + + virtual void work() { + SuspendibleThreadSetJoiner sts_joiner; + + for (ZPage* page; _iter.next(&page);) { + // When promoting an object (and before relocate start), we must ensure that all + // contained zpointers are store good. The marking code ensures that for non-null + // pointers, but null pointers are ignored. This code ensures that even null pointers + // are made store good, for the promoted objects. + page->object_iterate([&](oop obj) { + ZIterator::basic_oop_iterate_safe(obj, ZBarrier::promote_barrier_on_young_oop_field); + }); + + SuspendibleThreadSet::yield(); + } + } +}; + void ZRelocate::flip_age_pages(const ZArray* pages) { ZFlipAgePagesTask flip_age_task(pages); workers()->run(&flip_age_task); } +void ZRelocate::barrier_flip_promoted_pages(const ZArray* pages) { + ZPromoteBarrierTask promote_barrier_task(pages); + workers()->run(&promote_barrier_task); +} + void ZRelocate::synchronize() { _queue.synchronize(); } diff --git a/src/hotspot/share/gc/z/zRelocate.hpp b/src/hotspot/share/gc/z/zRelocate.hpp index d0ddf7deecf..50111f24ee5 100644 --- a/src/hotspot/share/gc/z/zRelocate.hpp +++ b/src/hotspot/share/gc/z/zRelocate.hpp @@ -119,6 +119,7 @@ public: void relocate(ZRelocationSet* relocation_set); void flip_age_pages(const ZArray* pages); + void barrier_flip_promoted_pages(const ZArray* pages); void synchronize(); void desynchronize(); From 45a2fd37f0ebda35789006b4e607422f7c369017 Mon Sep 17 00:00:00 2001 From: Weijun Wang Date: Thu, 20 Nov 2025 15:15:41 +0000 Subject: [PATCH 151/418] 8325448: Hybrid Public Key Encryption Reviewed-by: mullan, ascarpino, abarashev --- .../com/sun/crypto/provider/DHKEM.java | 361 +++++++---- .../classes/com/sun/crypto/provider/HPKE.java | 588 ++++++++++++++++++ .../com/sun/crypto/provider/SunJCE.java | 2 + .../javax/crypto/spec/HPKEParameterSpec.java | 443 +++++++++++++ .../spec/snippet-files/PackageSnippets.java | 76 +++ .../sun/security/util/SliceableSecretKey.java | 51 ++ .../provider/Cipher/HPKE/Compliance.java | 289 +++++++++ .../provider/Cipher/HPKE/Functions.java | 113 ++++ .../crypto/provider/Cipher/HPKE/KAT9180.java | 126 ++++ .../sun/crypto/provider/DHKEM/Compliance.java | 136 ++-- .../security/provider/all/Deterministic.java | 10 +- .../SliceableSecretKey/SoftSliceable.java | 153 +++++ 12 files changed, 2119 insertions(+), 229 deletions(-) create mode 100644 src/java.base/share/classes/com/sun/crypto/provider/HPKE.java create mode 100644 src/java.base/share/classes/javax/crypto/spec/HPKEParameterSpec.java create mode 100644 src/java.base/share/classes/javax/crypto/spec/snippet-files/PackageSnippets.java create mode 100644 src/java.base/share/classes/sun/security/util/SliceableSecretKey.java create mode 100644 test/jdk/com/sun/crypto/provider/Cipher/HPKE/Compliance.java create mode 100644 test/jdk/com/sun/crypto/provider/Cipher/HPKE/Functions.java create mode 100644 test/jdk/com/sun/crypto/provider/Cipher/HPKE/KAT9180.java create mode 100644 test/jdk/sun/security/util/SliceableSecretKey/SoftSliceable.java diff --git a/src/java.base/share/classes/com/sun/crypto/provider/DHKEM.java b/src/java.base/share/classes/com/sun/crypto/provider/DHKEM.java index b27320ed24b..c7372a4c2c8 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/DHKEM.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/DHKEM.java @@ -26,26 +26,51 @@ package com.sun.crypto.provider; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.Serial; import java.math.BigInteger; -import java.security.*; -import java.security.interfaces.ECKey; +import java.security.AsymmetricKey; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.ProviderException; +import java.security.PublicKey; +import java.security.SecureRandom; import java.security.interfaces.ECPublicKey; -import java.security.interfaces.XECKey; import java.security.interfaces.XECPublicKey; -import java.security.spec.*; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.ECPrivateKeySpec; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.security.spec.NamedParameterSpec; +import java.security.spec.XECPrivateKeySpec; +import java.security.spec.XECPublicKeySpec; import java.util.Arrays; import java.util.Objects; -import javax.crypto.*; -import javax.crypto.spec.SecretKeySpec; +import javax.crypto.DecapsulateException; +import javax.crypto.KDF; +import javax.crypto.KEM; +import javax.crypto.KEMSpi; +import javax.crypto.KeyAgreement; +import javax.crypto.SecretKey; import javax.crypto.spec.HKDFParameterSpec; +import javax.crypto.spec.SecretKeySpec; import sun.security.jca.JCAUtil; -import sun.security.util.*; - -import jdk.internal.access.SharedSecrets; +import sun.security.util.ArrayUtil; +import sun.security.util.CurveDB; +import sun.security.util.ECUtil; +import sun.security.util.InternalPrivateKey; +import sun.security.util.NamedCurve; +import sun.security.util.SliceableSecretKey; // Implementing DHKEM defined inside https://www.rfc-editor.org/rfc/rfc9180.html, -// without the AuthEncap and AuthDecap functions public class DHKEM implements KEMSpi { private static final byte[] KEM = new byte[] @@ -65,80 +90,86 @@ public class DHKEM implements KEMSpi { private static final byte[] EMPTY = new byte[0]; private record Handler(Params params, SecureRandom secureRandom, - PrivateKey skR, PublicKey pkR) + PrivateKey skS, PublicKey pkS, // sender keys + PrivateKey skR, PublicKey pkR) // receiver keys implements EncapsulatorSpi, DecapsulatorSpi { @Override public KEM.Encapsulated engineEncapsulate(int from, int to, String algorithm) { - Objects.checkFromToIndex(from, to, params.Nsecret); + Objects.checkFromToIndex(from, to, params.nsecret); Objects.requireNonNull(algorithm, "null algorithm"); KeyPair kpE = params.generateKeyPair(secureRandom); PrivateKey skE = kpE.getPrivate(); PublicKey pkE = kpE.getPublic(); - byte[] pkEm = params.SerializePublicKey(pkE); - byte[] pkRm = params.SerializePublicKey(pkR); - byte[] kem_context = concat(pkEm, pkRm); - byte[] key = null; + byte[] pkEm = params.serializePublicKey(pkE); + byte[] pkRm = params.serializePublicKey(pkR); try { - byte[] dh = params.DH(skE, pkR); - key = params.ExtractAndExpand(dh, kem_context); - return new KEM.Encapsulated( - new SecretKeySpec(key, from, to - from, algorithm), - pkEm, null); + SecretKey key; + if (skS == null) { + byte[] kem_context = concat(pkEm, pkRm); + key = params.deriveKey(algorithm, from, to, kem_context, + params.dh(skE, pkR)); + } else { + byte[] pkSm = params.serializePublicKey(pkS); + byte[] kem_context = concat(pkEm, pkRm, pkSm); + key = params.deriveKey(algorithm, from, to, kem_context, + params.dh(skE, pkR), params.dh(skS, pkR)); + } + return new KEM.Encapsulated(key, pkEm, null); + } catch (UnsupportedOperationException e) { + throw e; } catch (Exception e) { throw new ProviderException("internal error", e); - } finally { - // `key` has been cloned into the `SecretKeySpec` within the - // returned `KEM.Encapsulated`, so it can now be cleared. - if (key != null) { - Arrays.fill(key, (byte)0); - } } } @Override public SecretKey engineDecapsulate(byte[] encapsulation, int from, int to, String algorithm) throws DecapsulateException { - Objects.checkFromToIndex(from, to, params.Nsecret); + Objects.checkFromToIndex(from, to, params.nsecret); Objects.requireNonNull(algorithm, "null algorithm"); Objects.requireNonNull(encapsulation, "null encapsulation"); - if (encapsulation.length != params.Npk) { + if (encapsulation.length != params.npk) { throw new DecapsulateException("incorrect encapsulation size"); } - byte[] key = null; try { - PublicKey pkE = params.DeserializePublicKey(encapsulation); - byte[] dh = params.DH(skR, pkE); - byte[] pkRm = params.SerializePublicKey(pkR); - byte[] kem_context = concat(encapsulation, pkRm); - key = params.ExtractAndExpand(dh, kem_context); - return new SecretKeySpec(key, from, to - from, algorithm); + PublicKey pkE = params.deserializePublicKey(encapsulation); + byte[] pkRm = params.serializePublicKey(pkR); + if (pkS == null) { + byte[] kem_context = concat(encapsulation, pkRm); + return params.deriveKey(algorithm, from, to, kem_context, + params.dh(skR, pkE)); + } else { + byte[] pkSm = params.serializePublicKey(pkS); + byte[] kem_context = concat(encapsulation, pkRm, pkSm); + return params.deriveKey(algorithm, from, to, kem_context, + params.dh(skR, pkE), params.dh(skR, pkS)); + } + } catch (UnsupportedOperationException e) { + throw e; } catch (IOException | InvalidKeyException e) { throw new DecapsulateException("Cannot decapsulate", e); } catch (Exception e) { throw new ProviderException("internal error", e); - } finally { - if (key != null) { - Arrays.fill(key, (byte)0); - } } } @Override public int engineSecretSize() { - return params.Nsecret; + return params.nsecret; } @Override public int engineEncapsulationSize() { - return params.Npk; + return params.npk; } } // Not really a random. For KAT test only. It generates key pair from ikm. public static class RFC9180DeriveKeyPairSR extends SecureRandom { - static final long serialVersionUID = 0L; + @Serial + private static final long serialVersionUID = 0L; private final byte[] ikm; @@ -147,7 +178,7 @@ public class DHKEM implements KEMSpi { this.ikm = ikm; } - public KeyPair derive(Params params) { + private KeyPair derive(Params params) { try { return params.deriveKeyPair(ikm); } catch (Exception e) { @@ -183,9 +214,9 @@ public class DHKEM implements KEMSpi { ; private final int kem_id; - private final int Nsecret; - private final int Nsk; - private final int Npk; + private final int nsecret; + private final int nsk; + private final int npk; private final String kaAlgorithm; private final String keyAlgorithm; private final AlgorithmParameterSpec spec; @@ -193,18 +224,18 @@ public class DHKEM implements KEMSpi { private final byte[] suiteId; - Params(int kem_id, int Nsecret, int Nsk, int Npk, + Params(int kem_id, int nsecret, int nsk, int npk, String kaAlgorithm, String keyAlgorithm, AlgorithmParameterSpec spec, String hkdfAlgorithm) { this.kem_id = kem_id; this.spec = spec; - this.Nsecret = Nsecret; - this.Nsk = Nsk; - this.Npk = Npk; + this.nsecret = nsecret; + this.nsk = nsk; + this.npk = npk; this.kaAlgorithm = kaAlgorithm; this.keyAlgorithm = keyAlgorithm; this.hkdfAlgorithm = hkdfAlgorithm; - suiteId = concat(KEM, I2OSP(kem_id, 2)); + suiteId = concat(KEM, i2OSP(kem_id, 2)); } private boolean isEC() { @@ -224,18 +255,18 @@ public class DHKEM implements KEMSpi { } } - private byte[] SerializePublicKey(PublicKey k) { + private byte[] serializePublicKey(PublicKey k) { if (isEC()) { ECPoint w = ((ECPublicKey) k).getW(); return ECUtil.encodePoint(w, ((NamedCurve) spec).getCurve()); } else { byte[] uArray = ((XECPublicKey) k).getU().toByteArray(); ArrayUtil.reverse(uArray); - return Arrays.copyOf(uArray, Npk); + return Arrays.copyOf(uArray, npk); } } - private PublicKey DeserializePublicKey(byte[] data) + private PublicKey deserializePublicKey(byte[] data) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { KeySpec keySpec; if (isEC()) { @@ -251,29 +282,59 @@ public class DHKEM implements KEMSpi { return KeyFactory.getInstance(keyAlgorithm).generatePublic(keySpec); } - private byte[] DH(PrivateKey skE, PublicKey pkR) + private SecretKey dh(PrivateKey skE, PublicKey pkR) throws NoSuchAlgorithmException, InvalidKeyException { KeyAgreement ka = KeyAgreement.getInstance(kaAlgorithm); ka.init(skE); ka.doPhase(pkR, true); - return ka.generateSecret(); + return ka.generateSecret("Generic"); } - private byte[] ExtractAndExpand(byte[] dh, byte[] kem_context) - throws NoSuchAlgorithmException, InvalidKeyException { - KDF hkdf = KDF.getInstance(hkdfAlgorithm); - SecretKey eae_prk = LabeledExtract(hkdf, suiteId, EAE_PRK, dh); - try { - return LabeledExpand(hkdf, suiteId, eae_prk, SHARED_SECRET, - kem_context, Nsecret); - } finally { - if (eae_prk instanceof SecretKeySpec s) { - SharedSecrets.getJavaxCryptoSpecAccess() - .clearSecretKeySpec(s); + // The final shared secret derivation of either the encapsulator + // or the decapsulator. The key slicing is implemented inside. + // Throws UOE if a slice of the key cannot be found. + private SecretKey deriveKey(String alg, int from, int to, + byte[] kem_context, SecretKey... dhs) + throws NoSuchAlgorithmException { + if (from == 0 && to == nsecret) { + return extractAndExpand(kem_context, alg, dhs); + } else { + // First get shared secrets in "Generic" and then get a slice + // of it in the requested algorithm. + var fullKey = extractAndExpand(kem_context, "Generic", dhs); + if ("RAW".equalsIgnoreCase(fullKey.getFormat())) { + byte[] km = fullKey.getEncoded(); + if (km == null) { + // Should not happen if format is "RAW" + throw new UnsupportedOperationException("Key extract failed"); + } else { + try { + return new SecretKeySpec(km, from, to - from, alg); + } finally { + Arrays.fill(km, (byte)0); + } + } + } else if (fullKey instanceof SliceableSecretKey ssk) { + return ssk.slice(alg, from, to); + } else { + throw new UnsupportedOperationException("Cannot extract key"); } } } + private SecretKey extractAndExpand(byte[] kem_context, String alg, SecretKey... dhs) + throws NoSuchAlgorithmException { + var kdf = KDF.getInstance(hkdfAlgorithm); + var builder = labeledExtract(suiteId, EAE_PRK); + for (var dh : dhs) builder.addIKM(dh); + try { + return kdf.deriveKey(alg, + labeledExpand(builder, suiteId, SHARED_SECRET, kem_context, nsecret)); + } catch (InvalidAlgorithmParameterException e) { + throw new ProviderException(e); + } + } + private PublicKey getPublicKey(PrivateKey sk) throws InvalidKeyException { if (!(sk instanceof InternalPrivateKey)) { @@ -298,45 +359,37 @@ public class DHKEM implements KEMSpi { // For KAT tests only. See RFC9180DeriveKeyPairSR. public KeyPair deriveKeyPair(byte[] ikm) throws Exception { - KDF hkdf = KDF.getInstance(hkdfAlgorithm); - SecretKey dkp_prk = LabeledExtract(hkdf, suiteId, DKP_PRK, ikm); - try { - if (isEC()) { - NamedCurve curve = (NamedCurve) spec; - BigInteger sk = BigInteger.ZERO; - int counter = 0; - while (sk.signum() == 0 || - sk.compareTo(curve.getOrder()) >= 0) { - if (counter > 255) { - throw new RuntimeException(); - } - byte[] bytes = LabeledExpand(hkdf, suiteId, dkp_prk, - CANDIDATE, I2OSP(counter, 1), Nsk); - // bitmask is defined to be 0xFF for P-256 and P-384, - // and 0x01 for P-521 - if (this == Params.P521) { - bytes[0] = (byte) (bytes[0] & 0x01); - } - sk = new BigInteger(1, (bytes)); - counter = counter + 1; + var kdf = KDF.getInstance(hkdfAlgorithm); + var builder = labeledExtract(suiteId, DKP_PRK).addIKM(ikm); + if (isEC()) { + NamedCurve curve = (NamedCurve) spec; + BigInteger sk = BigInteger.ZERO; + int counter = 0; + while (sk.signum() == 0 || sk.compareTo(curve.getOrder()) >= 0) { + if (counter > 255) { + // So unlucky and should not happen + throw new ProviderException("DeriveKeyPairError"); } - PrivateKey k = DeserializePrivateKey(sk.toByteArray()); - return new KeyPair(getPublicKey(k), k); - } else { - byte[] sk = LabeledExpand(hkdf, suiteId, dkp_prk, SK, EMPTY, - Nsk); - PrivateKey k = DeserializePrivateKey(sk); - return new KeyPair(getPublicKey(k), k); - } - } finally { - if (dkp_prk instanceof SecretKeySpec s) { - SharedSecrets.getJavaxCryptoSpecAccess() - .clearSecretKeySpec(s); + byte[] bytes = kdf.deriveData(labeledExpand(builder, + suiteId, CANDIDATE, i2OSP(counter, 1), nsk)); + // bitmask is defined to be 0xFF for P-256 and P-384, and 0x01 for P-521 + if (this == Params.P521) { + bytes[0] = (byte) (bytes[0] & 0x01); + } + sk = new BigInteger(1, (bytes)); + counter = counter + 1; } + PrivateKey k = deserializePrivateKey(sk.toByteArray()); + return new KeyPair(getPublicKey(k), k); + } else { + byte[] sk = kdf.deriveData(labeledExpand(builder, + suiteId, SK, EMPTY, nsk)); + PrivateKey k = deserializePrivateKey(sk); + return new KeyPair(getPublicKey(k), k); } } - private PrivateKey DeserializePrivateKey(byte[] data) throws Exception { + private PrivateKey deserializePrivateKey(byte[] data) throws Exception { KeySpec keySpec = isEC() ? new ECPrivateKeySpec(new BigInteger(1, (data)), (NamedCurve) spec) : new XECPrivateKeySpec(spec, data); @@ -359,7 +412,22 @@ public class DHKEM implements KEMSpi { throw new InvalidAlgorithmParameterException("no spec needed"); } Params params = paramsFromKey(pk); - return new Handler(params, getSecureRandom(secureRandom), null, pk); + return new Handler(params, getSecureRandom(secureRandom), null, null, null, pk); + } + + // AuthEncap is not public KEM API + public EncapsulatorSpi engineNewAuthEncapsulator(PublicKey pkR, PrivateKey skS, + AlgorithmParameterSpec spec, SecureRandom secureRandom) + throws InvalidAlgorithmParameterException, InvalidKeyException { + if (pkR == null || skS == null) { + throw new InvalidKeyException("input key is null"); + } + if (spec != null) { + throw new InvalidAlgorithmParameterException("no spec needed"); + } + Params params = paramsFromKey(pkR); + return new Handler(params, getSecureRandom(secureRandom), + skS, params.getPublicKey(skS), null, pkR); } @Override @@ -372,20 +440,34 @@ public class DHKEM implements KEMSpi { throw new InvalidAlgorithmParameterException("no spec needed"); } Params params = paramsFromKey(sk); - return new Handler(params, null, sk, params.getPublicKey(sk)); + return new Handler(params, null, null, null, sk, params.getPublicKey(sk)); } - private Params paramsFromKey(Key k) throws InvalidKeyException { - if (k instanceof ECKey eckey) { - if (ECUtil.equals(eckey.getParams(), CurveDB.P_256)) { + // AuthDecap is not public KEM API + public DecapsulatorSpi engineNewAuthDecapsulator( + PrivateKey skR, PublicKey pkS, AlgorithmParameterSpec spec) + throws InvalidAlgorithmParameterException, InvalidKeyException { + if (skR == null || pkS == null) { + throw new InvalidKeyException("input key is null"); + } + if (spec != null) { + throw new InvalidAlgorithmParameterException("no spec needed"); + } + Params params = paramsFromKey(skR); + return new Handler(params, null, null, pkS, skR, params.getPublicKey(skR)); + } + + private Params paramsFromKey(AsymmetricKey k) throws InvalidKeyException { + var p = k.getParams(); + if (p instanceof ECParameterSpec ecp) { + if (ECUtil.equals(ecp, CurveDB.P_256)) { return Params.P256; - } else if (ECUtil.equals(eckey.getParams(), CurveDB.P_384)) { + } else if (ECUtil.equals(ecp, CurveDB.P_384)) { return Params.P384; - } else if (ECUtil.equals(eckey.getParams(), CurveDB.P_521)) { + } else if (ECUtil.equals(ecp, CurveDB.P_521)) { return Params.P521; } - } else if (k instanceof XECKey xkey - && xkey.getParams() instanceof NamedParameterSpec ns) { + } else if (p instanceof NamedParameterSpec ns) { if (ns.getName().equalsIgnoreCase("X25519")) { return Params.X25519; } else if (ns.getName().equalsIgnoreCase("X448")) { @@ -401,8 +483,11 @@ public class DHKEM implements KEMSpi { return o.toByteArray(); } - private static byte[] I2OSP(int n, int w) { - assert n < 256; + // I2OSP(n, w) as defined in RFC 9180 Section 3. + // In DHKEM and HPKE, number is always <65536 + // and converted to at most 2 bytes. + public static byte[] i2OSP(int n, int w) { + assert n < 65536; assert w == 1 || w == 2; if (w == 1) { return new byte[] { (byte) n }; @@ -411,32 +496,32 @@ public class DHKEM implements KEMSpi { } } - private static SecretKey LabeledExtract(KDF hkdf, byte[] suite_id, - byte[] label, byte[] ikm) throws InvalidKeyException { - SecretKeySpec s = new SecretKeySpec(concat(HPKE_V1, suite_id, label, - ikm), "IKM"); - try { - HKDFParameterSpec spec = - HKDFParameterSpec.ofExtract().addIKM(s).extractOnly(); - return hkdf.deriveKey("Generic", spec); - } catch (InvalidAlgorithmParameterException | - NoSuchAlgorithmException e) { - throw new InvalidKeyException(e.getMessage(), e); - } finally { - SharedSecrets.getJavaxCryptoSpecAccess().clearSecretKeySpec(s); - } + // Create a LabeledExtract builder with labels. + // You can add more IKM and salt into the result. + public static HKDFParameterSpec.Builder labeledExtract( + byte[] suiteId, byte[] label) { + return HKDFParameterSpec.ofExtract() + .addIKM(HPKE_V1).addIKM(suiteId).addIKM(label); } - private static byte[] LabeledExpand(KDF hkdf, byte[] suite_id, - SecretKey prk, byte[] label, byte[] info, int L) - throws InvalidKeyException { - byte[] labeled_info = concat(I2OSP(L, 2), HPKE_V1, suite_id, label, - info); - try { - return hkdf.deriveData(HKDFParameterSpec.expandOnly( - prk, labeled_info, L)); - } catch (InvalidAlgorithmParameterException iape) { - throw new InvalidKeyException(iape.getMessage(), iape); - } + // Create a labeled info from info and labels + private static byte[] labeledInfo( + byte[] suiteId, byte[] label, byte[] info, int length) { + return concat(i2OSP(length, 2), HPKE_V1, suiteId, label, info); + } + + // LabeledExpand from a builder + public static HKDFParameterSpec labeledExpand( + HKDFParameterSpec.Builder builder, + byte[] suiteId, byte[] label, byte[] info, int length) { + return builder.thenExpand( + labeledInfo(suiteId, label, info, length), length); + } + + // LabeledExpand from a prk + public static HKDFParameterSpec labeledExpand( + SecretKey prk, byte[] suiteId, byte[] label, byte[] info, int length) { + return HKDFParameterSpec.expandOnly( + prk, labeledInfo(suiteId, label, info, length), length); } } diff --git a/src/java.base/share/classes/com/sun/crypto/provider/HPKE.java b/src/java.base/share/classes/com/sun/crypto/provider/HPKE.java new file mode 100644 index 00000000000..eee5f59cc75 --- /dev/null +++ b/src/java.base/share/classes/com/sun/crypto/provider/HPKE.java @@ -0,0 +1,588 @@ +/* + * Copyright (c) 2025, 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. 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 com.sun.crypto.provider; + +import sun.security.util.CurveDB; +import sun.security.util.ECUtil; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.CipherSpi; +import javax.crypto.DecapsulateException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.KDF; +import javax.crypto.KEM; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.ShortBufferException; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.HPKEParameterSpec; +import javax.crypto.spec.IvParameterSpec; +import java.io.ByteArrayOutputStream; +import java.nio.ByteBuffer; +import java.security.AlgorithmParameters; +import java.security.AsymmetricKey; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.ProviderException; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.ECParameterSpec; +import java.security.spec.NamedParameterSpec; +import java.util.Arrays; + +public class HPKE extends CipherSpi { + + private static final byte[] HPKE = new byte[] + {'H', 'P', 'K', 'E'}; + private static final byte[] SEC = new byte[] + {'s', 'e', 'c'}; + private static final byte[] PSK_ID_HASH = new byte[] + {'p', 's', 'k', '_', 'i', 'd', '_', 'h', 'a', 's', 'h'}; + private static final byte[] INFO_HASH = new byte[] + {'i', 'n', 'f', 'o', '_', 'h', 'a', 's', 'h'}; + private static final byte[] SECRET = new byte[] + {'s', 'e', 'c', 'r', 'e', 't'}; + private static final byte[] EXP = new byte[] + {'e', 'x', 'p'}; + private static final byte[] KEY = new byte[] + {'k', 'e', 'y'}; + private static final byte[] BASE_NONCE = new byte[] + {'b', 'a', 's', 'e', '_', 'n', 'o', 'n', 'c', 'e'}; + + private static final int BEGIN = 1; + private static final int EXPORT_ONLY = 2; // init done with aead_id == 65535 + private static final int ENCRYPT_AND_EXPORT = 3; // int done with AEAD + private static final int AFTER_FINAL = 4; // after doFinal, need reinit internal cipher + + private int state = BEGIN; + private Impl impl; + + @Override + protected void engineSetMode(String mode) throws NoSuchAlgorithmException { + throw new NoSuchAlgorithmException(mode); + } + + @Override + protected void engineSetPadding(String padding) throws NoSuchPaddingException { + throw new NoSuchPaddingException(padding); + } + + @Override + protected int engineGetBlockSize() { + if (state == ENCRYPT_AND_EXPORT || state == AFTER_FINAL) { + return impl.aead.cipher.getBlockSize(); + } else { + return 0; + } + } + + @Override + protected int engineGetOutputSize(int inputLen) { + if (state == ENCRYPT_AND_EXPORT || state == AFTER_FINAL) { + return impl.aead.cipher.getOutputSize(inputLen); + } else { + return 0; + } + } + + @Override + protected byte[] engineGetIV() { + return (state == BEGIN || impl.kemEncaps == null) + ? null : impl.kemEncaps.clone(); + } + + @Override + protected AlgorithmParameters engineGetParameters() { + return null; + } + + @Override + protected void engineInit(int opmode, Key key, SecureRandom random) + throws InvalidKeyException { + throw new InvalidKeyException("HPKEParameterSpec must be provided"); + } + + @Override + protected void engineInit(int opmode, Key key, + AlgorithmParameterSpec params, SecureRandom random) + throws InvalidKeyException, InvalidAlgorithmParameterException { + impl = new Impl(opmode); + if (!(key instanceof AsymmetricKey ak)) { + throw new InvalidKeyException("Not an asymmetric key"); + } + if (params == null) { + throw new InvalidAlgorithmParameterException( + "HPKEParameterSpec must be provided"); + } else if (params instanceof HPKEParameterSpec hps) { + impl.init(ak, hps, random); + } else { + throw new InvalidAlgorithmParameterException( + "Unsupported params type: " + params.getClass()); + } + if (impl.hasEncrypt()) { + impl.aead.start(impl.opmode, impl.context.k, impl.context.computeNonce()); + state = ENCRYPT_AND_EXPORT; + } else { + state = EXPORT_ONLY; + } + } + + @Override + protected void engineInit(int opmode, Key key, + AlgorithmParameters params, SecureRandom random) + throws InvalidKeyException, InvalidAlgorithmParameterException { + throw new InvalidKeyException("HPKEParameterSpec must be provided"); + } + + // state is ENCRYPT_AND_EXPORT after this call succeeds + private void maybeReinitInternalCipher() { + if (state == BEGIN) { + throw new IllegalStateException("Illegal state: " + state); + } + if (state == EXPORT_ONLY) { + throw new UnsupportedOperationException(); + } + if (state == AFTER_FINAL) { + impl.aead.start(impl.opmode, impl.context.k, impl.context.computeNonce()); + state = ENCRYPT_AND_EXPORT; + } + } + + @Override + protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) { + maybeReinitInternalCipher(); + return impl.aead.cipher.update(input, inputOffset, inputLen); + } + + @Override + protected int engineUpdate(byte[] input, int inputOffset, int inputLen, + byte[] output, int outputOffset) throws ShortBufferException { + maybeReinitInternalCipher(); + return impl.aead.cipher.update( + input, inputOffset, inputLen, output, outputOffset); + } + + @Override + protected void engineUpdateAAD(byte[] src, int offset, int len) { + maybeReinitInternalCipher(); + impl.aead.cipher.updateAAD(src, offset, len); + } + + @Override + protected void engineUpdateAAD(ByteBuffer src) { + maybeReinitInternalCipher(); + impl.aead.cipher.updateAAD(src); + } + + @Override + protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen) + throws IllegalBlockSizeException, BadPaddingException { + maybeReinitInternalCipher(); + impl.context.IncrementSeq(); + state = AFTER_FINAL; + if (input == null) { // a bug in doFinal(null, ?, ?) + return impl.aead.cipher.doFinal(); + } else { + return impl.aead.cipher.doFinal(input, inputOffset, inputLen); + } + } + + @Override + protected int engineDoFinal(byte[] input, int inputOffset, int inputLen, + byte[] output, int outputOffset) throws ShortBufferException, + IllegalBlockSizeException, BadPaddingException { + maybeReinitInternalCipher(); + impl.context.IncrementSeq(); + state = AFTER_FINAL; + return impl.aead.cipher.doFinal( + input, inputOffset, inputLen, output, outputOffset); + } + + //@Override + protected SecretKey engineExportKey(String algorithm, byte[] context, int length) { + if (state == BEGIN) { + throw new IllegalStateException("State: " + state); + } else { + return impl.context.exportKey(algorithm, context, length); + } + } + + //@Override + protected byte[] engineExportData(byte[] context, int length) { + if (state == BEGIN) { + throw new IllegalStateException("State: " + state); + } else { + return impl.context.exportData(context, length); + } + } + + private static class AEAD { + final Cipher cipher; + final int nk, nn, nt; + final int id; + public AEAD(int id) throws InvalidAlgorithmParameterException { + this.id = id; + try { + switch (id) { + case HPKEParameterSpec.AEAD_AES_128_GCM -> { + cipher = Cipher.getInstance("AES/GCM/NoPadding"); + nk = 16; + } + case HPKEParameterSpec.AEAD_AES_256_GCM -> { + cipher = Cipher.getInstance("AES/GCM/NoPadding"); + nk = 32; + } + case HPKEParameterSpec.AEAD_CHACHA20_POLY1305 -> { + cipher = Cipher.getInstance("ChaCha20-Poly1305"); + nk = 32; + } + case HPKEParameterSpec.EXPORT_ONLY -> { + cipher = null; + nk = -1; + } + default -> throw new InvalidAlgorithmParameterException( + "Unknown aead_id: " + id); + } + } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { + throw new ProviderException("Internal error", e); + } + nn = 12; nt = 16; + } + + void start(int opmode, SecretKey key, byte[] nonce) { + try { + if (id == HPKEParameterSpec.AEAD_CHACHA20_POLY1305) { + cipher.init(opmode, key, new IvParameterSpec(nonce)); + } else { + cipher.init(opmode, key, new GCMParameterSpec(nt * 8, nonce)); + } + } catch (InvalidAlgorithmParameterException | InvalidKeyException e) { + throw new ProviderException("Internal error", e); + } + } + } + + private static class Impl { + + final int opmode; + + HPKEParameterSpec params; + Context context; + AEAD aead; + + byte[] suite_id; + String kdfAlg; + int kdfNh; + + // only used on sender side + byte[] kemEncaps; + + class Context { + final SecretKey k; // null if only export + final byte[] base_nonce; + final SecretKey exporter_secret; + + byte[] seq = new byte[aead.nn]; + + public Context(SecretKey sk, byte[] base_nonce, + SecretKey exporter_secret) { + this.k = sk; + this.base_nonce = base_nonce; + this.exporter_secret = exporter_secret; + } + + SecretKey exportKey(String algorithm, byte[] exporter_context, int length) { + if (exporter_context == null) { + throw new IllegalArgumentException("Null exporter_context"); + } + try { + var kdf = KDF.getInstance(kdfAlg); + return kdf.deriveKey(algorithm, DHKEM.labeledExpand( + exporter_secret, suite_id, SEC, exporter_context, length)); + } catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException e) { + // algorithm not accepted by HKDF, length too big or too small + throw new IllegalArgumentException("Invalid input", e); + } + } + + byte[] exportData(byte[] exporter_context, int length) { + if (exporter_context == null) { + throw new IllegalArgumentException("Null exporter_context"); + } + try { + var kdf = KDF.getInstance(kdfAlg); + return kdf.deriveData(DHKEM.labeledExpand( + exporter_secret, suite_id, SEC, exporter_context, length)); + } catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException e) { + // algorithm not accepted by HKDF, length too big or too small + throw new IllegalArgumentException("Invalid input", e); + } + } + + private byte[] computeNonce() { + var result = new byte[aead.nn]; + for (var i = 0; i < result.length; i++) { + result[i] = (byte)(seq[i] ^ base_nonce[i]); + } + return result; + } + + private void IncrementSeq() { + for (var i = seq.length - 1; i >= 0; i--) { + if ((seq[i] & 0xff) == 0xff) { + seq[i] = 0; + } else { + seq[i]++; + return; + } + } + // seq >= (1 << (8*aead.Nn)) - 1 when this method is called + throw new ProviderException("MessageLimitReachedError"); + } + } + + public Impl(int opmode) { + this.opmode = opmode; + } + + public boolean hasEncrypt() { + return params.aead_id() != 65535; + } + + // Section 7.2.1 of RFC 9180 has restrictions on size of psk, psk_id, + // info, and exporter_context (~2^61 for HMAC-SHA256 and ~2^125 for + // HMAC-SHA384 and HMAC-SHA512). This method does not pose any + // restrictions. + public void init(AsymmetricKey key, HPKEParameterSpec p, SecureRandom rand) + throws InvalidKeyException, InvalidAlgorithmParameterException { + if (opmode != Cipher.ENCRYPT_MODE && opmode != Cipher.DECRYPT_MODE) { + throw new UnsupportedOperationException( + "Can only be used for encryption and decryption"); + } + setParams(p); + SecretKey shared_secret; + if (opmode == Cipher.ENCRYPT_MODE) { + if (!(key instanceof PublicKey pk)) { + throw new InvalidKeyException( + "Cannot encrypt with private key"); + } + if (p.encapsulation() != null) { + throw new InvalidAlgorithmParameterException( + "Must not provide key encapsulation message on sender side"); + } + checkMatch(false, pk, params.kem_id()); + KEM.Encapsulated enc; + switch (p.authKey()) { + case null -> { + var e = kem().newEncapsulator(pk, rand); + enc = e.encapsulate(); + } + case PrivateKey skS -> { + checkMatch(true, skS, params.kem_id()); + // AuthEncap not public KEM API but it's internally supported + var e = new DHKEM().engineNewAuthEncapsulator(pk, skS, null, rand); + enc = e.engineEncapsulate(0, e.engineSecretSize(), "Generic"); + } + default -> throw new InvalidAlgorithmParameterException( + "Cannot auth with public key"); + } + kemEncaps = enc.encapsulation(); + shared_secret = enc.key(); + } else { + if (!(key instanceof PrivateKey sk)) { + throw new InvalidKeyException("Cannot decrypt with public key"); + } + checkMatch(false, sk, params.kem_id()); + try { + var encap = p.encapsulation(); + if (encap == null) { + throw new InvalidAlgorithmParameterException( + "Must provide key encapsulation message on recipient side"); + } + switch (p.authKey()) { + case null -> { + var d = kem().newDecapsulator(sk); + shared_secret = d.decapsulate(encap); + } + case PublicKey pkS -> { + checkMatch(true, pkS, params.kem_id()); + // AuthDecap not public KEM API but it's internally supported + var d = new DHKEM().engineNewAuthDecapsulator(sk, pkS, null); + shared_secret = d.engineDecapsulate( + encap, 0, d.engineSecretSize(), "Generic"); + } + default -> throw new InvalidAlgorithmParameterException( + "Cannot auth with private key"); + } + } catch (DecapsulateException e) { + throw new InvalidAlgorithmParameterException(e); + } + } + + var usePSK = usePSK(params.psk()); + int mode = params.authKey() == null ? (usePSK ? 1 : 0) : (usePSK ? 3 : 2); + context = keySchedule(mode, shared_secret, + params.info(), + params.psk(), + params.psk_id()); + } + + private static void checkMatch(boolean inSpec, AsymmetricKey k, int kem_id) + throws InvalidKeyException, InvalidAlgorithmParameterException { + var p = k.getParams(); + switch (p) { + case ECParameterSpec ecp -> { + if ((!ECUtil.equals(ecp, CurveDB.P_256) + || kem_id != HPKEParameterSpec.KEM_DHKEM_P_256_HKDF_SHA256) + && (!ECUtil.equals(ecp, CurveDB.P_384) + || kem_id != HPKEParameterSpec.KEM_DHKEM_P_384_HKDF_SHA384) + && (!ECUtil.equals(ecp, CurveDB.P_521) + || kem_id != HPKEParameterSpec.KEM_DHKEM_P_521_HKDF_SHA512)) { + var name = ECUtil.getCurveName(ecp); + throw new InvalidAlgorithmParameterException( + name + " does not match " + kem_id); + } + } + case NamedParameterSpec ns -> { + var name = ns.getName(); + if ((!name.equalsIgnoreCase("x25519") + || kem_id != HPKEParameterSpec.KEM_DHKEM_X25519_HKDF_SHA256) + && (!name.equalsIgnoreCase("x448") + || kem_id != HPKEParameterSpec.KEM_DHKEM_X448_HKDF_SHA512)) { + throw new InvalidAlgorithmParameterException( + name + " does not match " + kem_id); + } + } + case null, default -> { + var msg = k.getClass() + " does not match " + kem_id; + if (inSpec) { + throw new InvalidAlgorithmParameterException(msg); + } else { + throw new InvalidKeyException(msg); + } + } + } + } + + private KEM kem() { + try { + return KEM.getInstance("DHKEM"); + } catch (NoSuchAlgorithmException e) { + throw new ProviderException("Internal error", e); + } + } + + private void setParams(HPKEParameterSpec p) + throws InvalidAlgorithmParameterException { + params = p; + suite_id = concat( + HPKE, + DHKEM.i2OSP(params.kem_id(), 2), + DHKEM.i2OSP(params.kdf_id(), 2), + DHKEM.i2OSP(params.aead_id(), 2)); + switch (params.kdf_id()) { + case HPKEParameterSpec.KDF_HKDF_SHA256 -> { + kdfAlg = "HKDF-SHA256"; + kdfNh = 32; + } + case HPKEParameterSpec.KDF_HKDF_SHA384 -> { + kdfAlg = "HKDF-SHA384"; + kdfNh = 48; + } + case HPKEParameterSpec.KDF_HKDF_SHA512 -> { + kdfAlg = "HKDF-SHA512"; + kdfNh = 64; + } + default -> throw new InvalidAlgorithmParameterException( + "Unsupported kdf_id: " + params.kdf_id()); + } + aead = new AEAD(params.aead_id()); + } + + private Context keySchedule(int mode, + SecretKey shared_secret, + byte[] info, + SecretKey psk, + byte[] psk_id) { + try { + var psk_id_hash_x = DHKEM.labeledExtract(suite_id, PSK_ID_HASH) + .addIKM(psk_id).extractOnly(); + var info_hash_x = DHKEM.labeledExtract(suite_id, INFO_HASH) + .addIKM(info).extractOnly(); + + // deriveData must and can be called because all info to + // thw builder are just byte arrays. Any KDF impl can handle this. + var kdf = KDF.getInstance(kdfAlg); + var key_schedule_context = concat(new byte[]{(byte) mode}, + kdf.deriveData(psk_id_hash_x), + kdf.deriveData(info_hash_x)); + + var secret_x_builder = DHKEM.labeledExtract(suite_id, SECRET); + if (psk != null) { + secret_x_builder.addIKM(psk); + } + secret_x_builder.addSalt(shared_secret); + var secret_x = kdf.deriveKey("Generic", secret_x_builder.extractOnly()); + + // A new KDF object must be created because secret_x_builder + // might contain provider-specific keys which the previous + // KDF (provider already chosen) cannot handle. + kdf = KDF.getInstance(kdfAlg); + var exporter_secret = kdf.deriveKey("Generic", DHKEM.labeledExpand( + secret_x, suite_id, EXP, key_schedule_context, kdfNh)); + + if (hasEncrypt()) { + // ChaCha20-Poly1305 does not care about algorithm name + var key = kdf.deriveKey("AES", DHKEM.labeledExpand(secret_x, + suite_id, KEY, key_schedule_context, aead.nk)); + // deriveData must be called because we need to increment nonce + var base_nonce = kdf.deriveData(DHKEM.labeledExpand(secret_x, + suite_id, BASE_NONCE, key_schedule_context, aead.nn)); + return new Context(key, base_nonce, exporter_secret); + } else { + return new Context(null, null, exporter_secret); + } + } catch (InvalidAlgorithmParameterException + | NoSuchAlgorithmException | UnsupportedOperationException e) { + throw new ProviderException("Internal error", e); + } + } + } + + private static boolean usePSK(SecretKey psk) { + return psk != null; + } + + private static byte[] concat(byte[]... inputs) { + var o = new ByteArrayOutputStream(); + Arrays.stream(inputs).forEach(o::writeBytes); + return o.toByteArray(); + } +} diff --git a/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java b/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java index 22d5f17c6e0..4b38bd55809 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java @@ -371,6 +371,8 @@ public final class SunJCE extends Provider { ps("Cipher", "PBEWithHmacSHA512/256AndAES_256", "com.sun.crypto.provider.PBES2Core$HmacSHA512_256AndAES_256"); + ps("Cipher", "HPKE", "com.sun.crypto.provider.HPKE"); + /* * Key(pair) Generator engines */ diff --git a/src/java.base/share/classes/javax/crypto/spec/HPKEParameterSpec.java b/src/java.base/share/classes/javax/crypto/spec/HPKEParameterSpec.java new file mode 100644 index 00000000000..6776ddcdb75 --- /dev/null +++ b/src/java.base/share/classes/javax/crypto/spec/HPKEParameterSpec.java @@ -0,0 +1,443 @@ +/* + * Copyright (c) 2025, 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. 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 javax.crypto.spec; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import java.nio.charset.StandardCharsets; +import java.security.AsymmetricKey; +import java.security.Key; +import java.security.spec.AlgorithmParameterSpec; +import java.util.Arrays; +import java.util.HexFormat; +import java.util.Objects; + +/** + * This immutable class specifies the set of parameters used with a {@code Cipher} for the + * Hybrid Public Key Encryption + * (HPKE) algorithm. HPKE is a public key encryption scheme for encrypting + * arbitrary-sized plaintexts with a recipient's public key. It combines a key + * encapsulation mechanism (KEM), a key derivation function (KDF), and an + * authenticated encryption with additional data (AEAD) cipher. + *

    + * The + * standard algorithm name for the cipher is "HPKE". Unlike most other + * ciphers, HPKE is not expressed as a transformation string of the form + * "algorithm/mode/padding". Therefore, the argument to {@code Cipher.getInstance} + * must be the single algorithm name "HPKE". + *

    + * In HPKE, the sender's {@code Cipher} is always initialized with the + * recipient's public key in {@linkplain Cipher#ENCRYPT_MODE encrypt mode}, + * while the recipient's {@code Cipher} object is initialized with its own + * private key in {@linkplain Cipher#DECRYPT_MODE decrypt mode}. + *

    + * An {@code HPKEParameterSpec} object must be provided at HPKE + * {@linkplain Cipher#init(int, Key, AlgorithmParameterSpec) cipher initialization}. + *

    + * The {@link #of(int, int, int)} static method returns an {@code HPKEParameterSpec} + * object with the specified KEM, KDF, and AEAD algorithm identifiers. + * The terms "KEM algorithm identifiers", "KDF algorithm identifiers", and + * "AEAD algorithm identifiers" refer to their respective numeric values + * (specifically, {@code kem_id}, {@code kdf_id}, and {@code aead_id}) as + * defined in Section 7 + * of RFC 9180 and maintained on the + * IANA HPKE page. + *

    + * Once an {@code HPKEParameterSpec} object is created, additional methods + * are available to generate new {@code HPKEParameterSpec} objects with + * different features: + *

      + *
    • + * Application-supplied information can be provided using the + * {@link #withInfo(byte[])} method by both sides. + *
    • + * To authenticate using a pre-shared key ({@code mode_psk}), the + * pre-shared key and its identifier must be provided using the + * {@link #withPsk(SecretKey, byte[])} method by both sides. + *
    • + * To authenticate using an asymmetric key ({@code mode_auth}), + * the asymmetric keys must be provided using the {@link #withAuthKey(AsymmetricKey)} + * method. Precisely, the sender must call this method with its own private key + * and the recipient must call it with the sender's public key. + *
    • + * To authenticate using both a PSK and an asymmetric key + * ({@code mode_auth_psk}), both {@link #withAuthKey(AsymmetricKey)} and + * {@link #withPsk(SecretKey, byte[])} methods must be called as described above. + *
    • + * In HPKE, a shared secret is negotiated during the KEM step and a key + * encapsulation message must be transmitted from the sender to the recipient + * so that the recipient can recover the shared secret. On the sender side, + * after the cipher is initialized, the key encapsulation message can be + * retrieved using the {@link Cipher#getIV()} method. On the recipient side, + * this message must be supplied as part of an {@code HPKEParameterSpec} + * object obtained from the {@link #withEncapsulation(byte[])} method. + *
    + * For successful interoperability, both sides need to have identical algorithm + * identifiers, and supply identical + * {@code info}, {@code psk}, and {@code psk_id} or matching authentication + * keys if provided. For details about HPKE modes, refer to + * Section 5 + * of RFC 9180. + *

    + * If an HPKE cipher is {@linkplain Cipher#init(int, Key) initialized without + * parameters}, an {@code InvalidKeyException} is thrown. + *

    + * At HPKE cipher initialization, if no HPKE implementation supports the + * provided key type, an {@code InvalidKeyException} is thrown. If the provided + * {@code HPKEParameterSpec} is not accepted by any HPKE implementation, + * an {@code InvalidAlgorithmParameterException} is thrown. For example: + *

      + *
    • An algorithm identifier is unsupported or does not match the provided key type. + *
    • A key encapsulation message is provided on the sender side. + *
    • A key encapsulation message is not provided on the recipient side. + *
    • An attempt to use {@code withAuthKey(key)} is made with an incompatible key. + *
    • An attempt to use {@code withAuthKey(key)} is made but {@code mode_auth} + * or {@code mode_auth_psk} is not supported by the KEM algorithm used. + *
    + * After initialization, both the sender and recipient can process multiple + * messages in sequence with repeated {@code doFinal} calls, optionally preceded + * by one or more {@code updateAAD} and {@code update}. Each {@code doFinal} + * performs a complete HPKE encryption or decryption operation using a distinct + * IV derived from an internal sequence counter, as specified in + * Section 5.2 + * of RFC 9180. On the recipient side, each {@code doFinal} call must correspond + * to exactly one complete ciphertext, and the number and order of calls must + * match those on the sender side. This differs from the direct use of an AEAD + * cipher, where the caller must provide a fresh IV and reinitialize the cipher + * for each message. By managing IVs internally, HPKE allows a single + * initialization to support multiple messages while still ensuring IV + * uniqueness and preserving AEAD security guarantees. + *

    + * This example shows a sender and a recipient using HPKE to securely exchange + * messages with an X25519 key pair. + * {@snippet lang=java class="PackageSnippets" region="hpke-spec-example"} + * + * @implNote This class defines constants for some of the standard algorithm + * identifiers such as {@link #KEM_DHKEM_P_256_HKDF_SHA256}, + * {@link #KDF_HKDF_SHA256}, and {@link #AEAD_AES_128_GCM}. An HPKE {@code Cipher} + * implementation may support all, some, or none of the algorithm identifiers + * defined here. An implementation may also support additional identifiers not + * listed here, including private or experimental values. + * + * @spec https://www.rfc-editor.org/info/rfc9180 + * RFC 9180: Hybrid Public Key Encryption + * @spec security/standard-names.html + * Java Security Standard Algorithm Names + * @since 26 + */ +public final class HPKEParameterSpec implements AlgorithmParameterSpec { + + /** + * KEM algorithm identifier for DHKEM(P-256, HKDF-SHA256) as defined in RFC 9180. + */ + public static final int KEM_DHKEM_P_256_HKDF_SHA256 = 0x10; + + /** + * KEM algorithm identifier for DHKEM(P-384, HKDF-SHA384) as defined in RFC 9180. + */ + public static final int KEM_DHKEM_P_384_HKDF_SHA384 = 0x11; + + /** + * KEM algorithm identifier for DHKEM(P-521, HKDF-SHA512) as defined in RFC 9180. + */ + public static final int KEM_DHKEM_P_521_HKDF_SHA512 = 0x12; + + /** + * KEM algorithm identifier for DHKEM(X25519, HKDF-SHA256) as defined in RFC 9180. + */ + public static final int KEM_DHKEM_X25519_HKDF_SHA256 = 0x20; + + /** + * KEM algorithm identifier for DHKEM(X448, HKDF-SHA512) as defined in RFC 9180. + */ + public static final int KEM_DHKEM_X448_HKDF_SHA512 = 0x21; + + /** + * KDF algorithm identifier for HKDF-SHA256 as defined in RFC 9180. + */ + public static final int KDF_HKDF_SHA256 = 0x1; + + /** + * KDF algorithm identifier for HKDF-SHA384 as defined in RFC 9180. + */ + public static final int KDF_HKDF_SHA384 = 0x2; + + /** + * KDF algorithm identifier for HKDF-SHA512 as defined in RFC 9180. + */ + public static final int KDF_HKDF_SHA512 = 0x3; + + /** + * AEAD algorithm identifier for AES-128-GCM as defined in RFC 9180. + */ + public static final int AEAD_AES_128_GCM = 0x1; + + /** + * AEAD algorithm identifier for AES-256-GCM as defined in RFC 9180. + */ + public static final int AEAD_AES_256_GCM = 0x2; + + /** + * AEAD algorithm identifier for ChaCha20Poly1305 as defined in RFC 9180. + */ + public static final int AEAD_CHACHA20_POLY1305 = 0x3; + + /** + * AEAD algorithm identifier for Export-only as defined in RFC 9180. + */ + public static final int EXPORT_ONLY = 0xffff; + + private final int kem_id; + private final int kdf_id; + private final int aead_id; + private final byte[] info; // never null, can be empty + private final SecretKey psk; // null if not used + private final byte[] psk_id; // never null, can be empty + private final AsymmetricKey kS; // null if not used + private final byte[] encapsulation; // null if none + + // Note: this constructor does not clone array arguments. + private HPKEParameterSpec(int kem_id, int kdf_id, int aead_id, byte[] info, + SecretKey psk, byte[] psk_id, AsymmetricKey kS, byte[] encapsulation) { + this.kem_id = kem_id; + this.kdf_id = kdf_id; + this.aead_id = aead_id; + this.info = info; + this.psk = psk; + this.psk_id = psk_id; + this.kS = kS; + this.encapsulation = encapsulation; + } + + /** + * A factory method to create a new {@code HPKEParameterSpec} object with + * specified KEM, KDF, and AEAD algorithm identifiers in {@code mode_base} + * mode with an empty {@code info}. + * + * @param kem_id algorithm identifier for KEM, must be between 0 and 65535 (inclusive) + * @param kdf_id algorithm identifier for KDF, must be between 0 and 65535 (inclusive) + * @param aead_id algorithm identifier for AEAD, must be between 0 and 65535 (inclusive) + * @return a new {@code HPKEParameterSpec} object + * @throws IllegalArgumentException if any input value + * is out of range (must be between 0 and 65535, inclusive). + */ + public static HPKEParameterSpec of(int kem_id, int kdf_id, int aead_id) { + if (kem_id < 0 || kem_id > 65535) { + throw new IllegalArgumentException("Invalid kem_id: " + kem_id); + } + if (kdf_id < 0 || kdf_id > 65535) { + throw new IllegalArgumentException("Invalid kdf_id: " + kdf_id); + } + if (aead_id < 0 || aead_id > 65535) { + throw new IllegalArgumentException("Invalid aead_id: " + aead_id); + } + return new HPKEParameterSpec(kem_id, kdf_id, aead_id, + new byte[0], null, new byte[0], null, null); + } + + /** + * Creates a new {@code HPKEParameterSpec} object with the specified + * {@code info} value. + *

    + * For interoperability, RFC 9180 Section 7.2.1 recommends limiting + * this value to a maximum of 64 bytes. + * + * @param info application-supplied information. + * The contents of the array are copied to protect + * against subsequent modification. + * @return a new {@code HPKEParameterSpec} object + * @throws NullPointerException if {@code info} is {@code null} + * @throws IllegalArgumentException if {@code info} is empty. + */ + public HPKEParameterSpec withInfo(byte[] info) { + Objects.requireNonNull(info); + if (info.length == 0) { + throw new IllegalArgumentException("info is empty"); + } + return new HPKEParameterSpec(kem_id, kdf_id, aead_id, + info.clone(), psk, psk_id, kS, encapsulation); + } + + /** + * Creates a new {@code HPKEParameterSpec} object with the specified + * {@code psk} and {@code psk_id} values. + *

    + * RFC 9180 Section 5.1.2 requires the PSK MUST have at least 32 bytes + * of entropy. For interoperability, RFC 9180 Section 7.2.1 recommends + * limiting the key size and identifier length to a maximum of 64 bytes. + * + * @param psk pre-shared key + * @param psk_id identifier for PSK. The contents of the array are copied + * to protect against subsequent modification. + * @return a new {@code HPKEParameterSpec} object + * @throws NullPointerException if {@code psk} or {@code psk_id} is {@code null} + * @throws IllegalArgumentException if {@code psk} is shorter than 32 bytes + * or {@code psk_id} is empty + */ + public HPKEParameterSpec withPsk(SecretKey psk, byte[] psk_id) { + Objects.requireNonNull(psk); + Objects.requireNonNull(psk_id); + if (psk_id.length == 0) { + throw new IllegalArgumentException("psk_id is empty"); + } + if ("RAW".equalsIgnoreCase(psk.getFormat())) { + // We can only check when psk is extractable. We can only + // check the length and not the real entropy size + var keyBytes = psk.getEncoded(); + assert keyBytes != null; + Arrays.fill(keyBytes, (byte)0); + if (keyBytes.length < 32) { + throw new IllegalArgumentException("psk is too short"); + } + } + return new HPKEParameterSpec(kem_id, kdf_id, aead_id, + info, psk, psk_id.clone(), kS, encapsulation); + } + + /** + * Creates a new {@code HPKEParameterSpec} object with the specified + * key encapsulation message value that will be used by the recipient. + * + * @param encapsulation the key encapsulation message. + * The contents of the array are copied to protect against + * subsequent modification. + * + * @return a new {@code HPKEParameterSpec} object + * @throws NullPointerException if {@code encapsulation} is {@code null} + */ + public HPKEParameterSpec withEncapsulation(byte[] encapsulation) { + return new HPKEParameterSpec(kem_id, kdf_id, aead_id, + info, psk, psk_id, kS, + Objects.requireNonNull(encapsulation).clone()); + } + + /** + * Creates a new {@code HPKEParameterSpec} object with the specified + * authentication key value. + *

    + * Note: this method does not check whether the KEM algorithm supports + * {@code mode_auth} or {@code mode_auth_psk}. If the resulting object is + * used to initialize an HPKE cipher with an unsupported mode, an + * {@code InvalidAlgorithmParameterException} will be thrown at that time. + * + * @param kS the authentication key + * @return a new {@code HPKEParameterSpec} object + * @throws NullPointerException if {@code kS} is {@code null} + */ + public HPKEParameterSpec withAuthKey(AsymmetricKey kS) { + return new HPKEParameterSpec(kem_id, kdf_id, aead_id, + info, psk, psk_id, + Objects.requireNonNull(kS), + encapsulation); + } + + /** + * {@return the algorithm identifier for KEM } + */ + public int kem_id() { + return kem_id; + } + + /** + * {@return the algorithm identifier for KDF } + */ + public int kdf_id() { + return kdf_id; + } + + /** + * {@return the algorithm identifier for AEAD } + */ + public int aead_id() { + return aead_id; + } + + /** + * {@return a copy of the application-supplied information, empty if none} + */ + public byte[] info() { + return info.clone(); + } + + /** + * {@return pre-shared key, {@code null} if none} + */ + public SecretKey psk() { + return psk; + } + + /** + * {@return a copy of the identifier for PSK, empty if none} + */ + public byte[] psk_id() { + return psk_id.clone(); + } + + /** + * {@return the key for authentication, {@code null} if none} + */ + public AsymmetricKey authKey() { + return kS; + } + + /** + * {@return a copy of the key encapsulation message, {@code null} if none} + */ + public byte[] encapsulation() { + return encapsulation == null ? null : encapsulation.clone(); + } + + @Override + public String toString() { + return "HPKEParameterSpec{" + + "kem_id=" + kem_id + + ", kdf_id=" + kdf_id + + ", aead_id=" + aead_id + + ", info=" + bytesToString(info) + + ", " + (psk == null + ? (kS == null ? "mode_base" : "mode_auth") + : (kS == null ? "mode_psk" : "mode_auth_psk")) + "}"; + } + + // Returns a human-readable representation of a byte array. + private static String bytesToString(byte[] input) { + if (input.length == 0) { + return "(empty)"; + } else { + for (byte b : input) { + if (b < 0x20 || b > 0x7E || b == '"') { + // Non-ASCII or control characters are hard to read, and + // `"` requires character escaping. If any of these are + // present, return only the HEX representation. + return HexFormat.of().formatHex(input); + } + } + // Otherwise, all characters are printable and safe. + // Return both HEX and ASCII representations. + return HexFormat.of().formatHex(input) + + " (\"" + new String(input, StandardCharsets.US_ASCII) + "\")"; + } + } +} diff --git a/src/java.base/share/classes/javax/crypto/spec/snippet-files/PackageSnippets.java b/src/java.base/share/classes/javax/crypto/spec/snippet-files/PackageSnippets.java new file mode 100644 index 00000000000..e4074c1c4a9 --- /dev/null +++ b/src/java.base/share/classes/javax/crypto/spec/snippet-files/PackageSnippets.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2025, 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. 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. + */ +import javax.crypto.Cipher; +import javax.crypto.spec.HPKEParameterSpec; +import java.nio.charset.StandardCharsets; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.util.Arrays; +import java.util.HexFormat; + +class PackageSnippets { + public static void main(String[] args) throws Exception { + + // @start region="hpke-spec-example" + // Recipient key pair generation + KeyPairGenerator g = KeyPairGenerator.getInstance("X25519"); + KeyPair kp = g.generateKeyPair(); + + // The HPKE sender cipher is initialized with the recipient's public + // key and an HPKEParameterSpec using specified algorithm identifiers + // and application-supplied info. + Cipher senderCipher = Cipher.getInstance("HPKE"); + HPKEParameterSpec ps = HPKEParameterSpec.of( + HPKEParameterSpec.KEM_DHKEM_X25519_HKDF_SHA256, + HPKEParameterSpec.KDF_HKDF_SHA256, + HPKEParameterSpec.AEAD_AES_128_GCM) + .withInfo(HexFormat.of().parseHex("010203040506")); + senderCipher.init(Cipher.ENCRYPT_MODE, kp.getPublic(), ps); + + // Retrieve the key encapsulation message (from the KEM step) from + // the sender. + byte[] kemEncap = senderCipher.getIV(); + + // The HPKE recipient cipher is initialized with its own private key, + // an HPKEParameterSpec using the same algorithm identifiers as used by + // the sender, and the key encapsulation message from the sender. + Cipher recipientCipher = Cipher.getInstance("HPKE"); + HPKEParameterSpec pr = HPKEParameterSpec.of( + HPKEParameterSpec.KEM_DHKEM_X25519_HKDF_SHA256, + HPKEParameterSpec.KDF_HKDF_SHA256, + HPKEParameterSpec.AEAD_AES_128_GCM) + .withInfo(HexFormat.of().parseHex("010203040506")) + .withEncapsulation(kemEncap); + recipientCipher.init(Cipher.DECRYPT_MODE, kp.getPrivate(), pr); + + // Encryption and decryption + byte[] msg = "Hello World".getBytes(StandardCharsets.UTF_8); + byte[] ct = senderCipher.doFinal(msg); + byte[] pt = recipientCipher.doFinal(ct); + + assert Arrays.equals(msg, pt); + // @end + } +} diff --git a/src/java.base/share/classes/sun/security/util/SliceableSecretKey.java b/src/java.base/share/classes/sun/security/util/SliceableSecretKey.java new file mode 100644 index 00000000000..4dc3fe0a3e8 --- /dev/null +++ b/src/java.base/share/classes/sun/security/util/SliceableSecretKey.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2025, 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. 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 sun.security.util; + +import javax.crypto.SecretKey; + +/** + * An interface for SecretKeys that support using its slice as a new + * SecretKey. + *

    + * This is mainly used by PKCS #11 implementations that support the + * EXTRACT_KEY_FROM_KEY mechanism even if the key itself is sensitive + * and non-extractable. + */ +public interface SliceableSecretKey { + + /** + * Returns a slice as a new SecretKey. + * + * @param alg the new algorithm name + * @param from the byte offset of the new key in the full key + * @param to the to offset (exclusive) of the new key in the full key + * @return the new key + * @throws ArrayIndexOutOfBoundsException for improper from + * and to values + * @throws UnsupportedOperationException if slicing is not supported + */ + SecretKey slice(String alg, int from, int to); +} diff --git a/test/jdk/com/sun/crypto/provider/Cipher/HPKE/Compliance.java b/test/jdk/com/sun/crypto/provider/Cipher/HPKE/Compliance.java new file mode 100644 index 00000000000..2e10bb23e82 --- /dev/null +++ b/test/jdk/com/sun/crypto/provider/Cipher/HPKE/Compliance.java @@ -0,0 +1,289 @@ +/* + * Copyright (c) 2025, 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 javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.spec.HPKEParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.spec.NamedParameterSpec; + +import static javax.crypto.spec.HPKEParameterSpec.AEAD_AES_256_GCM; +import static javax.crypto.spec.HPKEParameterSpec.KDF_HKDF_SHA256; +import static javax.crypto.spec.HPKEParameterSpec.KEM_DHKEM_X25519_HKDF_SHA256; + +/* + * @test + * @bug 8325448 + * @library /test/lib + * @summary HPKE compliance test + */ +public class Compliance { + public static void main(String[] args) throws Exception { + + var kp = KeyPairGenerator.getInstance("X25519").generateKeyPair(); + var info = "info".getBytes(StandardCharsets.UTF_8); + var psk = new SecretKeySpec(new byte[32], "ONE"); + var shortKey = new SecretKeySpec(new byte[31], "ONE"); + var psk_id = "psk_id".getBytes(StandardCharsets.UTF_8); + var emptyKey = new SecretKey() { + public String getAlgorithm() { return "GENERIC"; } + public String getFormat() { return "RAW"; } + public byte[] getEncoded() { return new byte[0]; } + }; + + // HPKEParameterSpec + + // A typical spec + var spec = HPKEParameterSpec.of( + KEM_DHKEM_X25519_HKDF_SHA256, + KDF_HKDF_SHA256, + AEAD_AES_256_GCM); + Asserts.assertEQ(spec.kem_id(), KEM_DHKEM_X25519_HKDF_SHA256); + Asserts.assertEQ(spec.kdf_id(), KDF_HKDF_SHA256); + Asserts.assertEQ(spec.aead_id(), AEAD_AES_256_GCM); + Asserts.assertEQ(spec.authKey(), null); + Asserts.assertEQ(spec.encapsulation(), null); + Asserts.assertEqualsByteArray(spec.info(), new byte[0]); + Asserts.assertEQ(spec.psk(), null); + Asserts.assertEqualsByteArray(spec.psk_id(), new byte[0]); + + // A fake spec but still valid + var specZero = HPKEParameterSpec.of(0, 0, 0); + Asserts.assertEQ(specZero.kem_id(), 0); + Asserts.assertEQ(specZero.kdf_id(), 0); + Asserts.assertEQ(specZero.aead_id(), 0); + Asserts.assertEQ(specZero.authKey(), null); + Asserts.assertEQ(specZero.encapsulation(), null); + Asserts.assertEqualsByteArray(specZero.info(), new byte[0]); + Asserts.assertEQ(specZero.psk(), null); + Asserts.assertEqualsByteArray(specZero.psk_id(), new byte[0]); + + // identifiers + HPKEParameterSpec.of(65535, 65535, 65535); + Asserts.assertThrows(IllegalArgumentException.class, + () -> HPKEParameterSpec.of(-1, 0, 0)); + Asserts.assertThrows(IllegalArgumentException.class, + () -> HPKEParameterSpec.of(0, -1, 0)); + Asserts.assertThrows(IllegalArgumentException.class, + () -> HPKEParameterSpec.of(0, 0, -1)); + Asserts.assertThrows(IllegalArgumentException.class, + () -> HPKEParameterSpec.of(65536, 0, 0)); + Asserts.assertThrows(IllegalArgumentException.class, + () -> HPKEParameterSpec.of(0, 65536, 0)); + Asserts.assertThrows(IllegalArgumentException.class, + () -> HPKEParameterSpec.of(0, 0, 65536)); + + // auth key + Asserts.assertTrue(spec.withAuthKey(kp.getPrivate()).authKey() != null); + Asserts.assertTrue(spec.withAuthKey(kp.getPublic()).authKey() != null); + Asserts.assertThrows(NullPointerException.class, () -> spec.withAuthKey(null)); + + // info + Asserts.assertEqualsByteArray(spec.withInfo(info).info(), info); + Asserts.assertThrows(NullPointerException.class, () -> spec.withInfo(null)); + Asserts.assertThrows(IllegalArgumentException.class, () -> spec.withInfo(new byte[0])); + + // encapsulation + Asserts.assertEqualsByteArray(spec.withEncapsulation(info).encapsulation(), info); + Asserts.assertThrows(NullPointerException.class, () -> spec.withEncapsulation(null)); + Asserts.assertTrue(spec.withEncapsulation(new byte[0]).encapsulation().length == 0); // not emptiness check (yet) + + // psk_id and psk + Asserts.assertEqualsByteArray(spec.withPsk(psk, psk_id).psk().getEncoded(), psk.getEncoded()); + Asserts.assertEqualsByteArray(spec.withPsk(psk, psk_id).psk_id(), psk_id); + Asserts.assertThrows(NullPointerException.class, () -> spec.withPsk(psk, null)); + Asserts.assertThrows(NullPointerException.class, () -> spec.withPsk(null, psk_id)); + Asserts.assertThrows(NullPointerException.class, () -> spec.withPsk(null, null)); + Asserts.assertThrows(IllegalArgumentException.class, () -> spec.withPsk(psk, new byte[0])); + Asserts.assertThrows(IllegalArgumentException.class, () -> spec.withPsk(emptyKey, psk_id)); + Asserts.assertThrows(IllegalArgumentException.class, () -> spec.withPsk(shortKey, psk_id)); + + // toString + Asserts.assertTrue(spec.toString().contains("kem_id=32, kdf_id=1, aead_id=2")); + Asserts.assertTrue(spec.toString().contains("info=(empty),")); + Asserts.assertTrue(spec.withInfo(new byte[3]).toString().contains("info=000000,")); + Asserts.assertTrue(spec.withInfo("info".getBytes(StandardCharsets.UTF_8)) + .toString().contains("info=696e666f (\"info\"),")); + Asserts.assertTrue(spec.withInfo("\"info\"".getBytes(StandardCharsets.UTF_8)) + .toString().contains("info=22696e666f22,")); + Asserts.assertTrue(spec.withInfo("'info'".getBytes(StandardCharsets.UTF_8)) + .toString().contains("info=27696e666f27 (\"'info'\"),")); + Asserts.assertTrue(spec.withInfo("i\\n\\f\\o".getBytes(StandardCharsets.UTF_8)) + .toString().contains("info=695c6e5c665c6f (\"i\\n\\f\\o\"),")); + Asserts.assertTrue(spec.toString().contains("mode_base}")); + Asserts.assertTrue(spec.withPsk(psk, psk_id).toString().contains("mode_psk}")); + Asserts.assertTrue(spec.withAuthKey(kp.getPrivate()).toString().contains("mode_auth}")); + Asserts.assertTrue(spec.withAuthKey(kp.getPrivate()).withPsk(psk, psk_id).toString().contains("mode_auth_psk}")); + + var c1 = Cipher.getInstance("HPKE"); + + Asserts.assertThrows(NoSuchAlgorithmException.class, () -> Cipher.getInstance("HPKE/None/NoPadding")); + + // Still at BEGIN, not initialized + Asserts.assertEQ(c1.getIV(), null); + Asserts.assertEQ(c1.getParameters(), null); + Asserts.assertEquals(0, c1.getBlockSize()); + Asserts.assertThrows(IllegalStateException.class, () -> c1.getOutputSize(100)); + Asserts.assertThrows(IllegalStateException.class, () -> c1.update(new byte[1])); + Asserts.assertThrows(IllegalStateException.class, () -> c1.update(new byte[1], 0, 1)); + Asserts.assertThrows(IllegalStateException.class, () -> c1.updateAAD(new byte[1])); + Asserts.assertThrows(IllegalStateException.class, () -> c1.updateAAD(new byte[1], 0, 1)); + Asserts.assertThrows(IllegalStateException.class, () -> c1.doFinal()); + Asserts.assertThrows(IllegalStateException.class, () -> c1.doFinal(new byte[1])); + Asserts.assertThrows(IllegalStateException.class, () -> c1.doFinal(new byte[1], 0, 1)); + Asserts.assertThrows(IllegalStateException.class, () -> c1.doFinal(new byte[1], 0, 1, new byte[1024], 0)); + + c1.init(Cipher.ENCRYPT_MODE, kp.getPublic(), spec); + var encap = c1.getIV(); + + // Does not support WRAP and UNWRAP mode + Asserts.assertThrows(UnsupportedOperationException.class, + () -> c1.init(Cipher.WRAP_MODE, kp.getPublic(), spec)); + Asserts.assertThrows(UnsupportedOperationException.class, + () -> c1.init(Cipher.UNWRAP_MODE, kp.getPublic(), spec)); + + // Nulls + Asserts.assertThrows(InvalidKeyException.class, + () -> c1.init(Cipher.ENCRYPT_MODE, null, spec)); + Asserts.assertThrows(InvalidAlgorithmParameterException.class, + () -> c1.init(Cipher.ENCRYPT_MODE, kp.getPublic(), (HPKEParameterSpec) null)); + + // Cannot init sender with private key + Asserts.assertThrows(InvalidKeyException.class, + () -> c1.init(Cipher.ENCRYPT_MODE, kp.getPrivate(), spec)); + + // Cannot provide key encap msg to sender + Asserts.assertThrows(InvalidAlgorithmParameterException.class, + () -> c1.init(Cipher.ENCRYPT_MODE, kp.getPublic(), + spec.withEncapsulation(encap))); + + // Cannot init without HPKEParameterSpec + Asserts.assertThrows(InvalidKeyException.class, + () -> c1.init(Cipher.ENCRYPT_MODE, kp.getPublic())); + Asserts.assertThrows(InvalidKeyException.class, + () -> c1.init(Cipher.DECRYPT_MODE, kp.getPrivate())); + + // Cannot init with a spec not HPKEParameterSpec + Asserts.assertThrows(InvalidAlgorithmParameterException.class, + () -> c1.init(Cipher.ENCRYPT_MODE, kp.getPublic(), + NamedParameterSpec.X25519)); + Asserts.assertThrows(InvalidAlgorithmParameterException.class, + () -> c1.init(Cipher.DECRYPT_MODE, kp.getPrivate(), + NamedParameterSpec.X25519)); + + // Cannot init recipient with public key + Asserts.assertThrows(InvalidKeyException.class, + () -> c1.init(Cipher.DECRYPT_MODE, kp.getPublic(), + spec.withEncapsulation(new byte[32]))); + // Cannot provide key encap msg to sender + Asserts.assertThrows(InvalidAlgorithmParameterException.class, + () -> c1.init(Cipher.ENCRYPT_MODE, kp.getPublic(), spec.withEncapsulation(encap))); + // Must provide key encap msg to recipient + Asserts.assertThrows(InvalidAlgorithmParameterException.class, + () -> c1.init(Cipher.DECRYPT_MODE, kp.getPrivate(), spec)); + + // Unsupported identifiers + Asserts.assertThrows(InvalidAlgorithmParameterException.class, + () -> c1.init(Cipher.ENCRYPT_MODE, kp.getPublic(), + HPKEParameterSpec.of(0, KDF_HKDF_SHA256, AEAD_AES_256_GCM))); + Asserts.assertThrows(InvalidAlgorithmParameterException.class, + () -> c1.init(Cipher.ENCRYPT_MODE, kp.getPublic(), + HPKEParameterSpec.of(0x200, KDF_HKDF_SHA256, AEAD_AES_256_GCM))); + Asserts.assertThrows(InvalidAlgorithmParameterException.class, + () -> c1.init(Cipher.ENCRYPT_MODE, kp.getPublic(), + HPKEParameterSpec.of(KEM_DHKEM_X25519_HKDF_SHA256, 4, AEAD_AES_256_GCM))); + Asserts.assertThrows(InvalidAlgorithmParameterException.class, + () -> c1.init(Cipher.ENCRYPT_MODE, kp.getPublic(), + HPKEParameterSpec.of(KEM_DHKEM_X25519_HKDF_SHA256, KDF_HKDF_SHA256, 4))); + + // HPKE + checkEncryptDecrypt(kp, spec, spec); + + // extra features + var kp2 = KeyPairGenerator.getInstance("X25519").generateKeyPair(); + checkEncryptDecrypt(kp, + spec.withInfo(info), + spec.withInfo(info)); + checkEncryptDecrypt(kp, + spec.withPsk(psk, psk_id), + spec.withPsk(psk, psk_id)); + checkEncryptDecrypt(kp, + spec.withAuthKey(kp2.getPrivate()), + spec.withAuthKey(kp2.getPublic())); + checkEncryptDecrypt(kp, + spec.withInfo(info).withPsk(psk, psk_id).withAuthKey(kp2.getPrivate()), + spec.withInfo(info).withPsk(psk, psk_id).withAuthKey(kp2.getPublic())); + + // wrong keys + var kpRSA = KeyPairGenerator.getInstance("RSA").generateKeyPair(); + var kpEC = KeyPairGenerator.getInstance("EC").generateKeyPair(); + + Asserts.assertThrows(InvalidKeyException.class, + () -> c1.init(Cipher.ENCRYPT_MODE, kpRSA.getPublic(), spec)); + Asserts.assertThrows(InvalidAlgorithmParameterException.class, + () -> c1.init(Cipher.ENCRYPT_MODE, kpEC.getPublic(), spec)); + + // mod_auth, wrong key type + Asserts.assertThrows(InvalidAlgorithmParameterException.class, + () -> c1.init(Cipher.ENCRYPT_MODE, kp.getPublic(), + spec.withAuthKey(kp2.getPublic()))); + Asserts.assertThrows(InvalidAlgorithmParameterException.class, + () -> c1.init(Cipher.DECRYPT_MODE, kp.getPrivate(), + spec.withAuthKey(kp2.getPrivate()))); + Asserts.assertThrows(InvalidAlgorithmParameterException.class, + () -> c1.init(Cipher.ENCRYPT_MODE, kp.getPublic(), + spec.withAuthKey(kpRSA.getPrivate()))); + Asserts.assertThrows(InvalidAlgorithmParameterException.class, + () -> c1.init(Cipher.ENCRYPT_MODE, kp.getPublic(), + spec.withAuthKey(kpEC.getPrivate()))); + } + + static void checkEncryptDecrypt(KeyPair kp, HPKEParameterSpec ps, + HPKEParameterSpec pr) throws Exception { + + var c1 = Cipher.getInstance("HPKE"); + var c2 = Cipher.getInstance("HPKE"); + var aad = "AAD".getBytes(StandardCharsets.UTF_8); + + c1.init(Cipher.ENCRYPT_MODE, kp.getPublic(), ps); + Asserts.assertEquals(16, c1.getBlockSize()); + Asserts.assertEquals(116, c1.getOutputSize(100)); + c1.updateAAD(aad); + var ct = c1.doFinal(new byte[2]); + + c2.init(Cipher.DECRYPT_MODE, kp.getPrivate(), + pr.withEncapsulation(c1.getIV())); + Asserts.assertEquals(16, c2.getBlockSize()); + Asserts.assertEquals(84, c2.getOutputSize(100)); + c2.updateAAD(aad); + Asserts.assertEqualsByteArray(c2.doFinal(ct), new byte[2]); + } +} diff --git a/test/jdk/com/sun/crypto/provider/Cipher/HPKE/Functions.java b/test/jdk/com/sun/crypto/provider/Cipher/HPKE/Functions.java new file mode 100644 index 00000000000..9ebf4ce5c09 --- /dev/null +++ b/test/jdk/com/sun/crypto/provider/Cipher/HPKE/Functions.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2025, 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 javax.crypto.Cipher; +import javax.crypto.spec.HPKEParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.spec.ECGenParameterSpec; +import java.util.List; + +import static javax.crypto.spec.HPKEParameterSpec.AEAD_AES_128_GCM; +import static javax.crypto.spec.HPKEParameterSpec.AEAD_AES_256_GCM; +import static javax.crypto.spec.HPKEParameterSpec.AEAD_CHACHA20_POLY1305; +import static javax.crypto.spec.HPKEParameterSpec.KDF_HKDF_SHA256; +import static javax.crypto.spec.HPKEParameterSpec.KDF_HKDF_SHA384; +import static javax.crypto.spec.HPKEParameterSpec.KDF_HKDF_SHA512; +import static javax.crypto.spec.HPKEParameterSpec.KEM_DHKEM_P_256_HKDF_SHA256; +import static javax.crypto.spec.HPKEParameterSpec.KEM_DHKEM_P_384_HKDF_SHA384; +import static javax.crypto.spec.HPKEParameterSpec.KEM_DHKEM_P_521_HKDF_SHA512; +import static javax.crypto.spec.HPKEParameterSpec.KEM_DHKEM_X25519_HKDF_SHA256; +import static javax.crypto.spec.HPKEParameterSpec.KEM_DHKEM_X448_HKDF_SHA512; + +/* + * @test + * @bug 8325448 + * @library /test/lib + * @summary HPKE running with different keys + */ +public class Functions { + + record Params(String name, int kem) {} + static List PARAMS = List.of( + new Params("secp256r1", KEM_DHKEM_P_256_HKDF_SHA256), + new Params("secp384r1", KEM_DHKEM_P_384_HKDF_SHA384), + new Params("secp521r1", KEM_DHKEM_P_521_HKDF_SHA512), + new Params("X25519", KEM_DHKEM_X25519_HKDF_SHA256), + new Params("X448", KEM_DHKEM_X448_HKDF_SHA512) + ); + + public static void main(String[] args) throws Exception { + + var msg = "hello".getBytes(StandardCharsets.UTF_8); + var msg2 = "goodbye".getBytes(StandardCharsets.UTF_8); + var info = "info".getBytes(StandardCharsets.UTF_8); + var psk = new SecretKeySpec("K".repeat(32).getBytes(StandardCharsets.UTF_8), "Generic"); + var psk_id = "psk1".getBytes(StandardCharsets.UTF_8); + + for (var param : PARAMS) { + var c1 = Cipher.getInstance("HPKE"); + var c2 = Cipher.getInstance("HPKE"); + var kp = genKeyPair(param.name()); + var kp2 = genKeyPair(param.name()); + for (var kdf : List.of(KDF_HKDF_SHA256, KDF_HKDF_SHA384, KDF_HKDF_SHA512)) { + for (var aead : List.of(AEAD_AES_256_GCM, AEAD_AES_128_GCM, AEAD_CHACHA20_POLY1305)) { + + var params = HPKEParameterSpec.of(param.kem, kdf, aead); + System.out.println(params); + + c1.init(Cipher.ENCRYPT_MODE, kp.getPublic(), params); + c2.init(Cipher.DECRYPT_MODE, kp.getPrivate(), params.withEncapsulation(c1.getIV())); + Asserts.assertEqualsByteArray(msg, c2.doFinal(c1.doFinal(msg))); + Asserts.assertEqualsByteArray(msg2, c2.doFinal(c1.doFinal(msg2))); + + c1.init(Cipher.ENCRYPT_MODE, kp.getPublic(), params + .withAuthKey(kp2.getPrivate()) + .withInfo(info) + .withPsk(psk, psk_id)); + c2.init(Cipher.DECRYPT_MODE, kp.getPrivate(), params + .withAuthKey(kp2.getPublic()) + .withInfo(info) + .withPsk(psk, psk_id) + .withEncapsulation(c1.getIV())); + Asserts.assertEqualsByteArray(msg, c2.doFinal(c1.doFinal(msg))); + Asserts.assertEqualsByteArray(msg2, c2.doFinal(c1.doFinal(msg2))); + } + } + } + } + + static KeyPair genKeyPair(String name) throws Exception { + if (name.startsWith("secp")) { + var g = KeyPairGenerator.getInstance("EC"); + g.initialize(new ECGenParameterSpec(name)); + return g.generateKeyPair(); + } else { + return KeyPairGenerator.getInstance(name).generateKeyPair(); + } + } +} diff --git a/test/jdk/com/sun/crypto/provider/Cipher/HPKE/KAT9180.java b/test/jdk/com/sun/crypto/provider/Cipher/HPKE/KAT9180.java new file mode 100644 index 00000000000..f4717f57883 --- /dev/null +++ b/test/jdk/com/sun/crypto/provider/Cipher/HPKE/KAT9180.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8325448 + * @summary KAT inside RFC 9180 + * @library /test/lib + * @modules java.base/com.sun.crypto.provider + */ +import jdk.test.lib.Asserts; +import jdk.test.lib.artifacts.Artifact; +import jdk.test.lib.artifacts.ArtifactResolver; +import jdk.test.lib.json.JSONValue; + +import com.sun.crypto.provider.DHKEM; + +import javax.crypto.Cipher; +import javax.crypto.spec.HPKEParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HexFormat; + +/// This test is based on Appendix A (Test Vectors) of +/// [RFC 9180](https://datatracker.ietf.org/doc/html/rfc9180#name-test-vectors) +/// The test data is available as a JSON file at: +/// https://github.com/cfrg/draft-irtf-cfrg-hpke/blob/5f503c564da00b0687b3de75f1dfbdfc4079ad31/test-vectors.json. +/// +/// The JSON file can either be hosted on an artifactory server or +/// provided via a local path with +/// ``` +/// jtreg -Djdk.test.lib.artifacts.rfc9180-test-vectors= KAT9180.java +/// ``` +public class KAT9180 { + + @Artifact( + organization = "jpg.tests.jdk.ietf", + name = "rfc9180-test-vectors", + revision = "5f503c5", + extension = "json", + unpack = false) + private static class RFC_9180_KAT { + } + + + public static void main(String[] args) throws Exception { + var h = HexFormat.of(); + Path archivePath = ArtifactResolver.fetchOne(RFC_9180_KAT.class); + System.out.println("Data path: " + archivePath); + var c1 = Cipher.getInstance("HPKE"); + var c2 = Cipher.getInstance("HPKE"); + var ts = JSONValue.parse(new String(Files.readAllBytes(archivePath), StandardCharsets.UTF_8)); + for (var tg : ts.asArray()) { + var mode = Integer.parseInt(tg.get("mode").asString()); + System.err.print('I'); + var kem_id = Integer.parseInt(tg.get("kem_id").asString()); + var kdf_id = Integer.parseInt(tg.get("kdf_id").asString()); + var aead_id = Integer.parseInt(tg.get("aead_id").asString()); + var ikmR = h.parseHex(tg.get("ikmR").asString()); + var ikmE = h.parseHex(tg.get("ikmE").asString()); + var info = h.parseHex(tg.get("info").asString()); + + var kpR = new DHKEM.RFC9180DeriveKeyPairSR(ikmR).derive(kem_id); + var spec = HPKEParameterSpec.of(kem_id, kdf_id, aead_id).withInfo(info); + var rand = new DHKEM.RFC9180DeriveKeyPairSR(ikmE); + + if (mode == 1 || mode == 3) { + spec = spec.withPsk( + new SecretKeySpec(h.parseHex(tg.get("psk").asString()), "Generic"), + h.parseHex(tg.get("psk_id").asString())); + } + if (mode == 0 || mode == 1) { + c1.init(Cipher.ENCRYPT_MODE, kpR.getPublic(), spec, rand); + c2.init(Cipher.DECRYPT_MODE, kpR.getPrivate(), + spec.withEncapsulation(c1.getIV())); + } else { + var ikmS = h.parseHex(tg.get("ikmS").asString()); + var kpS = new DHKEM.RFC9180DeriveKeyPairSR(ikmS).derive(kem_id); + c1.init(Cipher.ENCRYPT_MODE, kpR.getPublic(), + spec.withAuthKey(kpS.getPrivate()), rand); + c2.init(Cipher.DECRYPT_MODE, kpR.getPrivate(), + spec.withEncapsulation(c1.getIV()).withAuthKey(kpS.getPublic())); + } + var enc = tg.get("encryptions"); + if (enc != null) { + System.err.print('e'); + var count = 0; + for (var p : enc.asArray()) { + var aad = h.parseHex(p.get("aad").asString()); + var pt = h.parseHex(p.get("pt").asString()); + var ct = h.parseHex(p.get("ct").asString()); + c1.updateAAD(aad); + var ct1 = c1.doFinal(pt); + Asserts.assertEqualsByteArray(ct, ct1); + c2.updateAAD(aad); + var pt1 = c2.doFinal(ct); + Asserts.assertEqualsByteArray(pt, pt1); + count++; + } + System.err.print(count); + } + } + } +} diff --git a/test/jdk/com/sun/crypto/provider/DHKEM/Compliance.java b/test/jdk/com/sun/crypto/provider/DHKEM/Compliance.java index 22c5c89b57b..d8814513b12 100644 --- a/test/jdk/com/sun/crypto/provider/DHKEM/Compliance.java +++ b/test/jdk/com/sun/crypto/provider/DHKEM/Compliance.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2025, 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 @@ -31,7 +31,6 @@ * @run main/othervm Compliance */ import jdk.test.lib.Asserts; -import jdk.test.lib.Utils; import javax.crypto.DecapsulateException; import javax.crypto.KEM; @@ -41,9 +40,7 @@ import java.security.*; import java.security.interfaces.ECPublicKey; import java.security.spec.*; import java.util.Arrays; -import java.util.Objects; import java.util.Random; -import java.util.function.Consumer; import com.sun.crypto.provider.DHKEM; @@ -66,12 +63,10 @@ public class Compliance { private static void conform() { new KEM.Encapsulated(new SecretKeySpec(new byte[1], "X"), new byte[0], new byte[0]); new KEM.Encapsulated(new SecretKeySpec(new byte[1], "X"), new byte[0], null); - Utils.runAndCheckException( - () -> new KEM.Encapsulated(null, new byte[0], null), - NullPointerException.class); - Utils.runAndCheckException( - () -> new KEM.Encapsulated(new SecretKeySpec(new byte[1], "X"), null, null), - NullPointerException.class); + Asserts.assertThrows(NullPointerException.class, + () -> new KEM.Encapsulated(null, new byte[0], null)); + Asserts.assertThrows(NullPointerException.class, + () -> new KEM.Encapsulated(new SecretKeySpec(new byte[1], "X"), null, null)); } // basic should and shouldn't behaviors @@ -86,37 +81,33 @@ public class Compliance { KEM.getInstance("DHKEM", (String) null); KEM.getInstance("DHKEM", (Provider) null); KEM kem = KEM.getInstance("DHKEM"); - Utils.runAndCheckException( - () -> KEM.getInstance("OLALA"), - NoSuchAlgorithmException.class); - Utils.runAndCheckException( - () -> KEM.getInstance("DHKEM", "NoWhere"), - NoSuchProviderException.class); - Utils.runAndCheckException( - () -> KEM.getInstance("DHKEM", "SunRsaSign"), - NoSuchAlgorithmException.class); + Asserts.assertThrows(NoSuchAlgorithmException.class, + () -> KEM.getInstance("OLALA")); + Asserts.assertThrows(NoSuchProviderException.class, + () -> KEM.getInstance("DHKEM", "NoWhere")); + Asserts.assertThrows(NoSuchAlgorithmException.class, + () -> KEM.getInstance("DHKEM", "SunRsaSign")); - Utils.runAndCheckException( - () -> kem.newEncapsulator(null), - InvalidKeyException.class); - Utils.runAndCheckException( - () -> kem.newDecapsulator(null), - InvalidKeyException.class); + Asserts.assertThrows(InvalidKeyException.class, + () -> kem.newEncapsulator(null)); + Asserts.assertThrows(InvalidKeyException.class, + () -> kem.newDecapsulator(null)); // Still an EC key, rejected by implementation - Utils.runAndCheckException( - () -> kem.newEncapsulator(badECKey()), - ExChecker.of(InvalidKeyException.class).by(DHKEM.class)); + checkThrownBy(Asserts.assertThrows( + InvalidKeyException.class, + () -> kem.newEncapsulator(badECKey())), + DHKEM.class.getName()); // Not an EC key at all, rejected by framework coz it's not // listed in "SupportedKeyClasses" in SunJCE.java. - Utils.runAndCheckException( - () -> kem.newEncapsulator(kpRSA.getPublic()), - ExChecker.of(InvalidKeyException.class).by(KEM.class.getName() + "$DelayedKEM")); + checkThrownBy(Asserts.assertThrows( + InvalidKeyException.class, + () -> kem.newEncapsulator(kpRSA.getPublic())), + KEM.class.getName() + "$DelayedKEM"); - Utils.runAndCheckException( - () -> kem.newDecapsulator(kpRSA.getPrivate()), - InvalidKeyException.class); + Asserts.assertThrows(InvalidKeyException.class, + () -> kem.newDecapsulator(kpRSA.getPrivate())); kem.newEncapsulator(kpX.getPublic(), null); kem.newEncapsulator(kpX.getPublic(), null, null); @@ -125,15 +116,12 @@ public class Compliance { Asserts.assertEQ(enc1.key().getEncoded().length, e2.secretSize()); Asserts.assertEQ(enc1.key().getAlgorithm(), "AES"); - Utils.runAndCheckException( - () -> e2.encapsulate(-1, 12, "AES"), - IndexOutOfBoundsException.class); - Utils.runAndCheckException( - () -> e2.encapsulate(0, e2.secretSize() + 1, "AES"), - IndexOutOfBoundsException.class); - Utils.runAndCheckException( - () -> e2.encapsulate(0, e2.secretSize(), null), - NullPointerException.class); + Asserts.assertThrows(IndexOutOfBoundsException.class, + () -> e2.encapsulate(-1, 12, "AES")); + Asserts.assertThrows(IndexOutOfBoundsException.class, + () -> e2.encapsulate(0, e2.secretSize() + 1, "AES")); + Asserts.assertThrows(NullPointerException.class, + () -> e2.encapsulate(0, e2.secretSize(), null)); KEM.Encapsulated enc = e2.encapsulate(); Asserts.assertEQ(enc.key().getEncoded().length, e2.secretSize()); @@ -162,29 +150,23 @@ public class Compliance { d.secretSize() - 16, d.secretSize(), "AES"); Asserts.assertEQ(encTail.key(), decTail); - Utils.runAndCheckException( - () -> d.decapsulate(null), - NullPointerException.class); - Utils.runAndCheckException( - () -> d.decapsulate(enc.encapsulation(), -1, 12, "AES"), - IndexOutOfBoundsException.class); - Utils.runAndCheckException( - () -> d.decapsulate(enc.encapsulation(), 0, d.secretSize() + 1, "AES"), - IndexOutOfBoundsException.class); - Utils.runAndCheckException( - () -> d.decapsulate(enc.encapsulation(), 0, d.secretSize(), null), - NullPointerException.class); + Asserts.assertThrows(NullPointerException.class, + () -> d.decapsulate(null)); + Asserts.assertThrows(IndexOutOfBoundsException.class, + () -> d.decapsulate(enc.encapsulation(), -1, 12, "AES")); + Asserts.assertThrows(IndexOutOfBoundsException.class, + () -> d.decapsulate(enc.encapsulation(), 0, d.secretSize() + 1, "AES")); + Asserts.assertThrows(NullPointerException.class, + () -> d.decapsulate(enc.encapsulation(), 0, d.secretSize(), null)); KEM.Encapsulator e3 = kem.newEncapsulator(kpEC.getPublic()); KEM.Encapsulated enc2 = e3.encapsulate(); KEM.Decapsulator d3 = kem.newDecapsulator(kpX.getPrivate()); - Utils.runAndCheckException( - () -> d3.decapsulate(enc2.encapsulation()), - DecapsulateException.class); + Asserts.assertThrows(DecapsulateException.class, + () -> d3.decapsulate(enc2.encapsulation())); - Utils.runAndCheckException( - () -> d3.decapsulate(new byte[100]), - DecapsulateException.class); + Asserts.assertThrows(DecapsulateException.class, + () -> d3.decapsulate(new byte[100])); } static class MySecureRandom extends SecureRandom { @@ -273,34 +255,8 @@ public class Compliance { }; } - // Used by Utils.runAndCheckException. Checks for type and final thrower. - record ExChecker(Class ex, String caller) - implements Consumer { - ExChecker { - Objects.requireNonNull(ex); - } - static ExChecker of(Class ex) { - return new ExChecker(ex, null); - } - ExChecker by(String caller) { - return new ExChecker(ex(), caller); - } - ExChecker by(Class caller) { - return new ExChecker(ex(), caller.getName()); - } - @Override - public void accept(Throwable t) { - if (t == null) { - throw new AssertionError("no exception thrown"); - } else if (!ex.isAssignableFrom(t.getClass())) { - throw new AssertionError("exception thrown is " + t.getClass()); - } else if (caller == null) { - return; - } else if (t.getStackTrace()[0].getClassName().equals(caller)) { - return; - } else { - throw new AssertionError("thrown by " + t.getStackTrace()[0].getClassName()); - } - } + // Ensures `t` is thrown by `caller` + static void checkThrownBy(T t, String caller) { + Asserts.assertEquals(caller, t.getStackTrace()[0].getClassName()); } } diff --git a/test/jdk/sun/security/provider/all/Deterministic.java b/test/jdk/sun/security/provider/all/Deterministic.java index 8fb0e943768..60c56cd1b93 100644 --- a/test/jdk/sun/security/provider/all/Deterministic.java +++ b/test/jdk/sun/security/provider/all/Deterministic.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, 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,6 +42,7 @@ import javax.crypto.KeyGenerator; import javax.crypto.spec.ChaCha20ParameterSpec; import javax.crypto.spec.DHParameterSpec; import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.HPKEParameterSpec; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.PBEParameterSpec; import javax.crypto.spec.SecretKeySpec; @@ -96,6 +97,11 @@ public class Deterministic { key = new SecretKeySpec("isthisakey".getBytes(StandardCharsets.UTF_8), "PBE"); // Some cipher requires salt to be 8 byte long spec = new PBEParameterSpec("saltsalt".getBytes(StandardCharsets.UTF_8), 100); + } else if (alg.equals("HPKE")) { + key = KeyPairGenerator.getInstance("x25519").generateKeyPair().getPublic(); + spec = HPKEParameterSpec.of(HPKEParameterSpec.KEM_DHKEM_X25519_HKDF_SHA256, + HPKEParameterSpec.KDF_HKDF_SHA256, + HPKEParameterSpec.AEAD_AES_256_GCM); } else { key = generateKey(alg.split("/")[0], s.getProvider()); if (!alg.contains("/") || alg.contains("/ECB/")) { @@ -239,6 +245,8 @@ public class Deterministic { return g.generateKey(); } if (s.equals("RSA")) { return generateKeyPair("RSA", 3).getPublic(); + } if (s.equals("HPKE")) { + return generateKeyPair("EC", 3).getPublic(); } else { var g = KeyGenerator.getInstance(s, p); g.init(new SeededSecureRandom(SEED + 4)); diff --git a/test/jdk/sun/security/util/SliceableSecretKey/SoftSliceable.java b/test/jdk/sun/security/util/SliceableSecretKey/SoftSliceable.java new file mode 100644 index 00000000000..6340b520a55 --- /dev/null +++ b/test/jdk/sun/security/util/SliceableSecretKey/SoftSliceable.java @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2025, 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 sun.security.util.SliceableSecretKey; + +import javax.crypto.KDF; +import javax.crypto.KDFParameters; +import javax.crypto.KDFSpi; +import javax.crypto.KEM; +import javax.crypto.SecretKey; +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Provider; +import java.security.Security; +import java.security.spec.AlgorithmParameterSpec; +import java.util.Arrays; + +/* + * @test + * @bug 8325448 + * @library /test/lib /test/jdk/security/unsignedjce + * @build java.base/javax.crypto.ProviderVerifier + * @modules java.base/sun.security.util + * @run main/othervm SoftSliceable + * @summary Showcase how Sliceable can be used in DHKEM + */ +public class SoftSliceable { + + public static void main(String[] args) throws Exception { + + // Put an HKDF-SHA256 impl that is preferred to the SunJCE one + Security.insertProviderAt(new ProviderImpl(), 1); + + // Just plain KEM calls + var kp = KeyPairGenerator.getInstance("X25519").generateKeyPair(); + var k = KEM.getInstance("DHKEM"); + var e = k.newEncapsulator(kp.getPublic()); + var d = k.newDecapsulator(kp.getPrivate()); + var enc = e.encapsulate(3, 9, "Generic"); + var k2 = d.decapsulate(enc.encapsulation(), 3, 9, "Generic"); + var k2full = d.decapsulate(enc.encapsulation()); + + if (enc.key() instanceof KeyImpl ki1 + && k2 instanceof KeyImpl ki2 + && k2full instanceof KeyImpl ki2full) { + // So the keys do come from the new provider, and + // 1. It has the correct length + Asserts.assertEquals(6, ki1.bytes.length); + // 2. encaps and decaps result in same keys + Asserts.assertEqualsByteArray(ki1.bytes, ki2.bytes); + // 3. The key is the correct slice from the full shared secret + Asserts.assertEqualsByteArray( + Arrays.copyOfRange(ki2full.bytes, 3, 9), ki2.bytes); + } else { + throw new Exception("Unexpected key types"); + } + } + + // A trivial SliceableSecretKey that is non-extractable with getBytes() + public static class KeyImpl implements SecretKey, SliceableSecretKey { + + private final byte[] bytes; + private final String algorithm; + + public KeyImpl(byte[] bytes, String algorithm) { + this.bytes = bytes.clone(); + this.algorithm = algorithm; + } + + @Override + public String getAlgorithm() { + return algorithm; + } + + @Override + public String getFormat() { + return null; + } + + @Override + public byte[] getEncoded() { + return null; + } + + @Override + public SecretKey slice(String alg, int from, int to) { + return new KeyImpl(Arrays.copyOfRange(bytes, from, to), algorithm); + } + } + + // Our new provider + public static class ProviderImpl extends Provider { + public ProviderImpl() { + super("A", "A", "A"); + put("KDF.HKDF-SHA256", KDFImpl.class.getName()); + } + } + + // Our new HKDF-SHA256 impl that always returns a KeyImpl object + public static class KDFImpl extends KDFSpi { + + public KDFImpl(KDFParameters p) + throws InvalidAlgorithmParameterException { + super(p); + } + + @Override + protected KDFParameters engineGetParameters() { + return null; + } + + @Override + protected SecretKey engineDeriveKey(String alg, AlgorithmParameterSpec spec) + throws InvalidAlgorithmParameterException { + try { + var kdf = KDF.getInstance("HKDF-SHA256", "SunJCE"); + var bytes = kdf.deriveData(spec); + return new KeyImpl(bytes, alg); + } catch (NoSuchAlgorithmException | NoSuchProviderException e) { + throw new AssertionError("Cannot happen", e); + } + } + + @Override + protected byte[] engineDeriveData(AlgorithmParameterSpec spec) { + throw new UnsupportedOperationException("Cannot derive data"); + } + } +} From a89018582160a9d876f66925618c8b8f93190e67 Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Thu, 20 Nov 2025 15:17:44 +0000 Subject: [PATCH 152/418] 8333727: Use JOpt in jpackage to parse command line 8371384: libapplauncher.so is copied to a wrong location in two step packaging when --install-dir=/usr Reviewed-by: almatvee --- .../share/classes/module-info.java | 3 +- .../jpackage/internal/LinuxAppBundler.java | 41 - .../internal/LinuxBundlingEnvironment.java | 114 ++ .../jpackage/internal/LinuxDebBundler.java | 79 - .../jpackage/internal/LinuxFromOptions.java | 119 ++ .../jpackage/internal/LinuxFromParams.java | 146 -- .../internal/LinuxPackageBundler.java | 88 - .../jdk/jpackage/internal/LinuxPackager.java | 3 +- .../internal/LinuxPackagingPipeline.java | 27 +- .../jpackage/internal/LinuxRpmBundler.java | 80 - .../internal/model/LinuxLauncher.java | 6 +- .../resources/LinuxResources.properties | 4 - .../linux/classes/module-info.java.extra | 9 +- .../jdk/jpackage/internal/MacAppBundler.java | 81 - .../internal/MacBundlingEnvironment.java | 110 ++ .../jdk/jpackage/internal/MacDmgBundler.java | 98 -- .../jdk/jpackage/internal/MacFromOptions.java | 331 ++++ .../jdk/jpackage/internal/MacFromParams.java | 384 ----- .../internal/MacPackagingPipeline.java | 16 +- .../jdk/jpackage/internal/MacPkgBundler.java | 99 -- .../internal/model/MacApplication.java | 38 +- .../resources/MacResources.properties | 8 - .../macosx/classes/module-info.java.extra | 9 +- .../internal/AddLauncherArguments.java | 212 --- .../jpackage/internal/AppImageBundler.java | 168 -- .../jdk/jpackage/internal/AppImageFile.java | 341 ++-- .../jpackage/internal/ApplicationBuilder.java | 41 +- .../internal/ApplicationLayoutUtils.java | 71 - .../jdk/jpackage/internal/Arguments.java | 867 ---------- .../jdk/jpackage/internal/BasicBundlers.java | 86 - .../internal/BuildEnvFromOptions.java | 104 ++ .../jpackage/internal/BuildEnvFromParams.java | 74 - .../jdk/jpackage/internal/BundleParams.java | 72 - .../jdk/jpackage/internal/Bundler.java | 126 -- .../jpackage/internal/BundlerParamInfo.java | 179 --- .../jdk/jpackage/internal/Bundlers.java | 112 -- .../jdk/jpackage/internal/CLIHelp.java | 116 -- .../jdk/jpackage/internal/CfgFile.java | 19 +- .../internal/DefaultBundlingEnvironment.java | 283 ++++ .../jdk/jpackage/internal/DeployParams.java | 362 ----- .../internal/FileAssociationGroup.java | 4 + .../jdk/jpackage/internal/FromOptions.java | 237 +++ .../jdk/jpackage/internal/FromParams.java | 251 --- .../jdk/jpackage/internal/IOUtils.java | 10 +- .../internal/JLinkRuntimeBuilder.java | 10 +- .../internal/JPackageToolProvider.java | 58 - .../jdk/jpackage/internal/LauncherData.java | 307 ---- .../internal/LauncherFromOptions.java | 189 +++ .../jpackage/internal/LauncherFromParams.java | 156 -- .../internal/LauncherStartupInfoBuilder.java | 207 ++- .../jdk/jpackage/internal/OptionUtils.java | 54 + .../jpackage/internal/OptionsTransformer.java | 86 + .../jdk/jpackage/internal/Packager.java | 3 +- .../jpackage/internal/PackagingPipeline.java | 33 +- .../internal/StandardBundlerParam.java | 513 ------ ...bstractBundler.java => TempDirectory.java} | 55 +- .../jdk/jpackage/internal/ValidOptions.java | 187 --- .../internal/cli/AdditionalLauncher.java} | 10 +- .../cli/BundlingOperationModifier.java | 42 + .../cli/BundlingOperationOptionScope.java | 38 + .../internal/cli/CliBundlingEnvironment.java | 47 + .../jpackage/internal/cli/DefaultOptions.java | 191 +++ .../jpackage/internal/cli/HelpFormatter.java | 178 ++ .../jdk/jpackage/internal/cli/I18N.java} | 41 +- .../cli/JOptSimpleOptionsBuilder.java | 817 ++++++++++ .../jdk/jpackage/internal/cli/Main.java | 224 +++ .../internal/cli/MessageFormatUtils.java | 79 + .../jdk/jpackage/internal/cli/Option.java | 54 + .../OptionArrayValueConverter.java} | 24 +- .../internal/cli/OptionIdentifier.java | 53 + .../jdk/jpackage/internal/cli/OptionName.java | 68 + .../OptionScope.java} | 11 +- .../jpackage/internal/cli/OptionSource.java | 68 + .../jdk/jpackage/internal/cli/OptionSpec.java | 180 +++ .../internal/cli/OptionSpecBuilder.java | 475 ++++++ .../cli/OptionSpecMapperOptionScope.java | 157 ++ .../jpackage/internal/cli/OptionValue.java | 190 +++ .../internal/cli/OptionValueConverter.java | 273 ++++ .../cli/OptionValueExceptionFactory.java | 183 +++ .../jdk/jpackage/internal/cli/Options.java | 202 +++ .../internal/cli/OptionsAnalyzer.java | 430 +++++ .../internal/cli/OptionsProcessor.java | 428 +++++ .../cli/StandardAppImageFileOption.java | 246 +++ .../cli/StandardBundlingOperation.java | 173 ++ .../internal/cli/StandardFaOption.java | 111 ++ .../internal/cli/StandardHelpFormatter.java | 398 +++++ .../jpackage/internal/cli/StandardOption.java | 802 +++++++++ .../internal/cli/StandardOptionContext.java | 68 + .../StandardOptionValueExceptionFactory.java | 79 + .../internal/cli/StandardValidator.java | 162 ++ .../internal/cli/StandardValueConverter.java | 83 + .../jpackage/internal/cli/StringToken.java | 57 + .../jdk/jpackage/internal/cli/Utils.java | 109 ++ .../jdk/jpackage/internal/cli/Validator.java | 265 +++ .../jpackage/internal/cli/ValueConverter.java | 68 + .../internal/cli/WithOptionIdentifier.java | 38 + .../cli/WithOptionIdentifierStub.java} | 14 +- .../internal/model/BundlingEnvironment.java | 47 +- .../model/BundlingOperationDescriptor.java | 53 + .../internal/model/ConfigException.java | 7 +- .../internal/model/ExternalApplication.java | 145 +- .../internal/model/JPackageException.java} | 27 +- .../LauncherModularStartupInfoMixin.java | 13 +- .../internal/model/PackagerException.java | 84 - .../internal/model/RuntimeBuilder.java | 2 +- .../resources/HelpResources.properties | 629 ++++---- .../resources/MainResources.properties | 55 +- .../jdk/jpackage/internal/util/FileUtils.java | 14 + .../jpackage/internal/util/SetBuilder.java | 87 + .../share/classes/jdk/jpackage/main/Main.java | 86 +- .../share/classes/module-info.java | 14 +- .../internal/WinBundlingEnvironment.java | 109 ++ .../jdk/jpackage/internal/WinExeBundler.java | 62 +- .../jdk/jpackage/internal/WinFromOpions.java | 115 ++ .../jdk/jpackage/internal/WinFromParams.java | 164 -- .../jdk/jpackage/internal/WinMsiBundler.java | 122 -- .../jdk/jpackage/internal/WinMsiPackager.java | 5 +- .../internal/WinPackagingPipeline.java | 45 +- .../jpackage/internal/model/WinLauncher.java | 10 +- .../resources/WinResources.properties | 5 - .../windows/classes/module-info.java.extra | 9 +- test/jdk/tools/jpackage/TEST.properties | 2 + .../jdk/jpackage/test/AppImageFile.java | 98 +- .../helpers/jdk/jpackage/test/MacHelper.java | 2 +- .../jdk/jpackage/test/PackageType.java | 90 +- test/jdk/tools/jpackage/junit/TEST.properties | 8 +- .../jpackage/internal/AppImageFileTest.java | 678 ++++++-- .../DefaultBundlingEnvironmentTest.java | 58 + .../jpackage/internal/DeployParamsTest.java | 74 - .../LauncherStartupInfoBuilderTest.java | 155 ++ .../internal/PackagingPipelineTest.java | 43 +- .../internal/cli/DefaultOptionsTest.java | 57 + .../internal/cli/ExpectedOptions.java | 94 ++ .../jdk/jpackage/internal/cli/HelpTest.java | 181 +++ .../cli/JOptSimpleOptionsBuilderTest.java | 1428 +++++++++++++++++ .../jdk/jpackage/internal/cli/MainTest.java | 203 +++ .../cli/MockupCliBundlingEnvironment.java | 160 ++ .../internal/cli/OptionIdentifierTest.java | 63 + .../jpackage/internal/cli/OptionNameTest.java | 152 ++ .../cli/OptionSpecMutatorOptionScopeTest.java | 144 ++ .../jpackage/internal/cli/OptionSpecTest.java | 365 +++++ .../cli/OptionValueConverterTest.java | 216 +++ .../cli/OptionValueExceptionFactoryTest.java | 152 ++ .../internal/cli/OptionValueTest.java | 212 +++ .../internal/cli/OptionsProcessorTest.java | 786 +++++++++ .../jpackage/internal/cli/OptionsTest.java | 272 ++++ .../cli/OptionsValidationFailTest.excludes | 46 + .../cli/OptionsValidationFailTest.java | 245 +++ .../cli/StandardBundlingOperationTest.java | 103 ++ .../internal/cli/StandardOptionTest.java | 639 ++++++++ .../internal/cli/StandardValidatorTest.java | 259 +++ .../cli/StandardValueConverterTest.java | 161 ++ .../internal/cli/StringTokenTest.java | 49 + .../jdk/jpackage/internal/cli/TestUtils.java | 179 +++ .../jdk/jpackage/internal/cli/UtilsTest.java | 91 ++ .../jpackage/internal/cli/ValidatorTest.java | 284 ++++ .../jdk/jpackage/internal/cli/help-linux.txt | 197 +++ .../jdk/jpackage/internal/cli/help-macos.txt | 238 +++ .../jpackage/internal/cli/help-windows.txt | 205 +++ .../jpackage/internal/cli/jpackage-options.md | 63 + .../jpackage/share/AppImagePackageTest.java | 6 +- test/jdk/tools/jpackage/share/AsyncTest.java | 222 +++ test/jdk/tools/jpackage/share/ErrorTest.java | 8 +- 163 files changed, 18527 insertions(+), 6692 deletions(-) delete mode 100644 src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxAppBundler.java create mode 100644 src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxBundlingEnvironment.java delete mode 100644 src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebBundler.java create mode 100644 src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxFromOptions.java delete mode 100644 src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxFromParams.java delete mode 100644 src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageBundler.java delete mode 100644 src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxRpmBundler.java delete mode 100644 src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacAppBundler.java create mode 100644 src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBundlingEnvironment.java delete mode 100644 src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgBundler.java create mode 100644 src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFromOptions.java delete mode 100644 src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFromParams.java delete mode 100644 src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgBundler.java delete mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/AddLauncherArguments.java delete mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageBundler.java delete mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationLayoutUtils.java delete mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/Arguments.java delete mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/BasicBundlers.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/BuildEnvFromOptions.java delete mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/BuildEnvFromParams.java delete mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/BundleParams.java delete mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/Bundler.java delete mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/BundlerParamInfo.java delete mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/Bundlers.java delete mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/CLIHelp.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/DefaultBundlingEnvironment.java delete mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/DeployParams.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromOptions.java delete mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromParams.java delete mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/JPackageToolProvider.java delete mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherData.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherFromOptions.java delete mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherFromParams.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/OptionUtils.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/OptionsTransformer.java delete mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/StandardBundlerParam.java rename src/jdk.jpackage/share/classes/jdk/jpackage/internal/{AbstractBundler.java => TempDirectory.java} (53%) delete mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/ValidOptions.java rename src/jdk.jpackage/{macosx/classes/jdk/jpackage/internal/MacBuildEnvFromParams.java => share/classes/jdk/jpackage/internal/cli/AdditionalLauncher.java} (76%) create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/BundlingOperationModifier.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/BundlingOperationOptionScope.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/CliBundlingEnvironment.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/DefaultOptions.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/HelpFormatter.java rename src/jdk.jpackage/{macosx/classes/jdk/jpackage/internal/MacBaseInstallerBundler.java => share/classes/jdk/jpackage/internal/cli/I18N.java} (56%) create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/JOptSimpleOptionsBuilder.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/Main.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/MessageFormatUtils.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/Option.java rename src/jdk.jpackage/share/classes/jdk/jpackage/internal/{model/BundleCreator.java => cli/OptionArrayValueConverter.java} (68%) create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/OptionIdentifier.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/OptionName.java rename src/jdk.jpackage/share/classes/jdk/jpackage/internal/{model/BundlingOperation.java => cli/OptionScope.java} (81%) create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/OptionSource.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/OptionSpec.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/OptionSpecBuilder.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/OptionSpecMapperOptionScope.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/OptionValue.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/OptionValueConverter.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/OptionValueExceptionFactory.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/Options.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/OptionsAnalyzer.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/OptionsProcessor.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/StandardAppImageFileOption.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/StandardBundlingOperation.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/StandardFaOption.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/StandardHelpFormatter.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/StandardOption.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/StandardOptionContext.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/StandardOptionValueExceptionFactory.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/StandardValidator.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/StandardValueConverter.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/StringToken.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/Utils.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/Validator.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/ValueConverter.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/WithOptionIdentifier.java rename src/jdk.jpackage/{macosx/classes/jdk/jpackage/internal/MacAppImageFileExtras.java => share/classes/jdk/jpackage/internal/cli/WithOptionIdentifierStub.java} (69%) create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/BundlingOperationDescriptor.java rename src/jdk.jpackage/{windows/classes/jdk/jpackage/internal/WinAppBundler.java => share/classes/jdk/jpackage/internal/model/JPackageException.java} (69%) delete mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/PackagerException.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/SetBuilder.java create mode 100644 src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinBundlingEnvironment.java create mode 100644 src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinFromOpions.java delete mode 100644 src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinFromParams.java delete mode 100644 src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/DefaultBundlingEnvironmentTest.java delete mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/DeployParamsTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/LauncherStartupInfoBuilderTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/DefaultOptionsTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/ExpectedOptions.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/HelpTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/JOptSimpleOptionsBuilderTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/MainTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/MockupCliBundlingEnvironment.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/OptionIdentifierTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/OptionNameTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/OptionSpecMutatorOptionScopeTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/OptionSpecTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/OptionValueConverterTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/OptionValueExceptionFactoryTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/OptionValueTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/OptionsProcessorTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/OptionsTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/OptionsValidationFailTest.excludes create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/OptionsValidationFailTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/StandardBundlingOperationTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/StandardOptionTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/StandardValidatorTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/StandardValueConverterTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/StringTokenTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/TestUtils.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/UtilsTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/ValidatorTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/help-linux.txt create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/help-macos.txt create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/help-windows.txt create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/jpackage-options.md create mode 100644 test/jdk/tools/jpackage/share/AsyncTest.java diff --git a/src/jdk.internal.opt/share/classes/module-info.java b/src/jdk.internal.opt/share/classes/module-info.java index ba6987f1ea9..728c2de500d 100644 --- a/src/jdk.internal.opt/share/classes/module-info.java +++ b/src/jdk.internal.opt/share/classes/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -32,6 +32,7 @@ module jdk.internal.opt { exports jdk.internal.joptsimple to jdk.jlink, jdk.jshell, + jdk.jpackage, jdk.jdeps; exports jdk.internal.opt to jdk.compiler, diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxAppBundler.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxAppBundler.java deleted file mode 100644 index fe8d6bcf34f..00000000000 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxAppBundler.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2012, 2025, 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. 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.jpackage.internal; - -import java.util.Optional; - -public class LinuxAppBundler extends AppImageBundler { - public LinuxAppBundler() { - setAppImageSupplier((params, output) -> { - // Order is important! - var app = LinuxFromParams.APPLICATION.fetchFrom(params); - var env = BuildEnvFromParams.BUILD_ENV.fetchFrom(params); - LinuxPackagingPipeline.build(Optional.empty()) - .excludeDirFromCopying(output.getParent()) - .create().execute(BuildEnv.withAppImageDir(env, output), app); - }); - } -} diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxBundlingEnvironment.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxBundlingEnvironment.java new file mode 100644 index 00000000000..cfd8ab391bb --- /dev/null +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxBundlingEnvironment.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2025, 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. 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.jpackage.internal; + +import static java.util.stream.Collectors.toMap; +import static jdk.jpackage.internal.LinuxFromOptions.createLinuxApplication; +import static jdk.jpackage.internal.LinuxPackagingPipeline.APPLICATION_LAYOUT; +import static jdk.jpackage.internal.cli.StandardBundlingOperation.CREATE_LINUX_APP_IMAGE; +import static jdk.jpackage.internal.cli.StandardBundlingOperation.CREATE_LINUX_DEB; +import static jdk.jpackage.internal.cli.StandardBundlingOperation.CREATE_LINUX_RPM; + +import java.util.Map; +import java.util.Optional; +import java.util.stream.Stream; +import jdk.jpackage.internal.cli.Options; +import jdk.jpackage.internal.cli.StandardBundlingOperation; +import jdk.jpackage.internal.model.BundlingOperationDescriptor; +import jdk.jpackage.internal.model.LinuxPackage; +import jdk.jpackage.internal.model.PackageType; +import jdk.jpackage.internal.util.Result; + +public class LinuxBundlingEnvironment extends DefaultBundlingEnvironment { + + public LinuxBundlingEnvironment() { + super(build() + .defaultOperation(() -> { + return LazyLoad.SYS_ENV.value().map(LinuxSystemEnvironment::nativePackageType).map(DESCRIPTORS::get); + }) + .bundler(CREATE_LINUX_APP_IMAGE, LinuxBundlingEnvironment::createAppImage) + .bundler(CREATE_LINUX_DEB, LazyLoad::debSysEnv, LinuxBundlingEnvironment::createDebPackage) + .bundler(CREATE_LINUX_RPM, LazyLoad::rpmSysEnv, LinuxBundlingEnvironment::createRpmPackage)); + } + + private static void createDebPackage(Options options, LinuxDebSystemEnvironment sysEnv) { + + createNativePackage(options, + LinuxFromOptions::createLinuxDebPackage, + buildEnv()::create, + LinuxBundlingEnvironment::buildPipeline, + (env, pkg, outputDir) -> { + return new LinuxDebPackager(env, pkg, outputDir, sysEnv); + }); + } + + private static void createRpmPackage(Options options, LinuxRpmSystemEnvironment sysEnv) { + + createNativePackage(options, + LinuxFromOptions::createLinuxRpmPackage, + buildEnv()::create, + LinuxBundlingEnvironment::buildPipeline, + (env, pkg, outputDir) -> { + return new LinuxRpmPackager(env, pkg, outputDir, sysEnv); + }); + } + + private static void createAppImage(Options options) { + + final var app = createLinuxApplication(options); + + createApplicationImage(options, app, LinuxPackagingPipeline.build(Optional.empty())); + } + + private static PackagingPipeline.Builder buildPipeline(LinuxPackage pkg) { + return LinuxPackagingPipeline.build(Optional.of(pkg)); + } + + private static BuildEnvFromOptions buildEnv() { + return new BuildEnvFromOptions().predefinedAppImageLayout(APPLICATION_LAYOUT); + } + + private static final class LazyLoad { + + static Result debSysEnv() { + return DEB_SYS_ENV; + } + + static Result rpmSysEnv() { + return RPM_SYS_ENV; + } + + private static final Result SYS_ENV = LinuxSystemEnvironment.create(); + + private static final Result DEB_SYS_ENV = LinuxDebSystemEnvironment.create(SYS_ENV); + + private static final Result RPM_SYS_ENV = LinuxRpmSystemEnvironment.create(SYS_ENV); + } + + private static final Map DESCRIPTORS = Stream.of( + CREATE_LINUX_DEB, + CREATE_LINUX_RPM + ).collect(toMap(StandardBundlingOperation::packageType, StandardBundlingOperation::descriptor)); +} diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebBundler.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebBundler.java deleted file mode 100644 index 76a08519b48..00000000000 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebBundler.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (c) 2012, 2025, 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. 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.jpackage.internal; - -import java.nio.file.Path; -import java.util.Map; -import java.util.Optional; -import jdk.jpackage.internal.model.LinuxDebPackage; -import jdk.jpackage.internal.model.PackagerException; -import jdk.jpackage.internal.model.StandardPackageType; -import jdk.jpackage.internal.util.Result; - -public class LinuxDebBundler extends LinuxPackageBundler { - - public LinuxDebBundler() { - super(LinuxFromParams.DEB_PACKAGE); - } - - @Override - public String getName() { - return I18N.getString("deb.bundler.name"); - } - - @Override - public String getID() { - return "deb"; - } - - @Override - public Path execute(Map params, Path outputParentDir) throws PackagerException { - - var pkg = LinuxFromParams.DEB_PACKAGE.fetchFrom(params); - - return Packager.build().outputDir(outputParentDir) - .pkg(pkg) - .env(BuildEnvFromParams.BUILD_ENV.fetchFrom(params)) - .pipelineBuilderMutatorFactory((env, _, outputDir) -> { - return new LinuxDebPackager(env, pkg, outputDir, sysEnv.orElseThrow()); - }).execute(LinuxPackagingPipeline.build(Optional.of(pkg))); - } - - @Override - protected Result sysEnv() { - return sysEnv; - } - - @Override - public boolean isDefault() { - return sysEnv.value() - .map(LinuxSystemEnvironment::nativePackageType) - .map(StandardPackageType.LINUX_DEB::equals) - .orElse(false); - } - - private final Result sysEnv = LinuxDebSystemEnvironment.create(SYS_ENV); -} diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxFromOptions.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxFromOptions.java new file mode 100644 index 00000000000..799c92ce2e1 --- /dev/null +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxFromOptions.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2025, 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. 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.jpackage.internal; + +import static jdk.jpackage.internal.FromOptions.buildApplicationBuilder; +import static jdk.jpackage.internal.FromOptions.createPackageBuilder; +import static jdk.jpackage.internal.LinuxPackagingPipeline.APPLICATION_LAYOUT; +import static jdk.jpackage.internal.cli.StandardOption.LINUX_APP_CATEGORY; +import static jdk.jpackage.internal.cli.StandardOption.LINUX_DEB_MAINTAINER_EMAIL; +import static jdk.jpackage.internal.cli.StandardOption.LINUX_MENU_GROUP; +import static jdk.jpackage.internal.cli.StandardOption.LINUX_PACKAGE_DEPENDENCIES; +import static jdk.jpackage.internal.cli.StandardOption.LINUX_PACKAGE_NAME; +import static jdk.jpackage.internal.cli.StandardOption.LINUX_RELEASE; +import static jdk.jpackage.internal.cli.StandardOption.LINUX_RPM_LICENSE_TYPE; +import static jdk.jpackage.internal.cli.StandardOption.LINUX_SHORTCUT_HINT; +import static jdk.jpackage.internal.model.StandardPackageType.LINUX_DEB; +import static jdk.jpackage.internal.model.StandardPackageType.LINUX_RPM; + +import jdk.jpackage.internal.cli.Options; +import jdk.jpackage.internal.model.Launcher; +import jdk.jpackage.internal.model.LinuxApplication; +import jdk.jpackage.internal.model.LinuxDebPackage; +import jdk.jpackage.internal.model.LinuxLauncher; +import jdk.jpackage.internal.model.LinuxLauncherMixin; +import jdk.jpackage.internal.model.LinuxRpmPackage; +import jdk.jpackage.internal.model.StandardPackageType; + +final class LinuxFromOptions { + + static LinuxApplication createLinuxApplication(Options options) { + + final var launcherFromOptions = new LauncherFromOptions().faWithDefaultDescription(); + + final var appBuilder = buildApplicationBuilder().create(options, launcherOptions -> { + + final var launcher = launcherFromOptions.create(launcherOptions); + + final var shortcut = LINUX_SHORTCUT_HINT.findIn(launcherOptions); + + return LinuxLauncher.create(launcher, new LinuxLauncherMixin.Stub(shortcut)); + + }, (LinuxLauncher linuxLauncher, Launcher launcher) -> { + return LinuxLauncher.create(launcher, linuxLauncher); + }, APPLICATION_LAYOUT); + + appBuilder.launchers().map(LinuxPackagingPipeline::normalizeShortcuts).ifPresent(appBuilder::launchers); + + return LinuxApplication.create(appBuilder.create()); + } + + static LinuxRpmPackage createLinuxRpmPackage(Options options) { + + final var superPkgBuilder = createLinuxPackageBuilder(options, LINUX_RPM); + + final var pkgBuilder = new LinuxRpmPackageBuilder(superPkgBuilder); + + LINUX_RPM_LICENSE_TYPE.ifPresentIn(options, pkgBuilder::licenseType); + + return pkgBuilder.create(); + } + + static LinuxDebPackage createLinuxDebPackage(Options options) { + + final var superPkgBuilder = createLinuxPackageBuilder(options, LINUX_DEB); + + final var pkgBuilder = new LinuxDebPackageBuilder(superPkgBuilder); + + LINUX_DEB_MAINTAINER_EMAIL.ifPresentIn(options, pkgBuilder::maintainerEmail); + + final var pkg = pkgBuilder.create(); + + // Show warning if license file is missing + if (pkg.licenseFile().isEmpty()) { + Log.verbose(I18N.getString("message.debs-like-licenses")); + } + + return pkg; + } + + private static LinuxPackageBuilder createLinuxPackageBuilder(Options options, StandardPackageType type) { + + final var app = createLinuxApplication(options); + + final var superPkgBuilder = createPackageBuilder(options, app, type); + + final var pkgBuilder = new LinuxPackageBuilder(superPkgBuilder); + + LINUX_PACKAGE_DEPENDENCIES.ifPresentIn(options, pkgBuilder::additionalDependencies); + LINUX_APP_CATEGORY.ifPresentIn(options, pkgBuilder::category); + LINUX_MENU_GROUP.ifPresentIn(options, pkgBuilder::menuGroupName); + LINUX_RELEASE.ifPresentIn(options, pkgBuilder::release); + LINUX_PACKAGE_NAME.ifPresentIn(options, pkgBuilder::literalName); + + return pkgBuilder; + } + +} diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxFromParams.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxFromParams.java deleted file mode 100644 index e9d1416b5c3..00000000000 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxFromParams.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (c) 2025, 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. 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.jpackage.internal; - -import static jdk.jpackage.internal.BundlerParamInfo.createStringBundlerParam; -import static jdk.jpackage.internal.FromParams.createApplicationBuilder; -import static jdk.jpackage.internal.FromParams.createApplicationBundlerParam; -import static jdk.jpackage.internal.FromParams.createPackageBuilder; -import static jdk.jpackage.internal.FromParams.createPackageBundlerParam; -import static jdk.jpackage.internal.FromParams.findLauncherShortcut; -import static jdk.jpackage.internal.LinuxPackagingPipeline.APPLICATION_LAYOUT; -import static jdk.jpackage.internal.model.StandardPackageType.LINUX_DEB; -import static jdk.jpackage.internal.model.StandardPackageType.LINUX_RPM; -import static jdk.jpackage.internal.util.function.ThrowingFunction.toFunction; - -import java.io.IOException; -import java.util.Map; -import jdk.jpackage.internal.model.ConfigException; -import jdk.jpackage.internal.model.LinuxApplication; -import jdk.jpackage.internal.model.LinuxDebPackage; -import jdk.jpackage.internal.model.LinuxLauncher; -import jdk.jpackage.internal.model.LinuxLauncherMixin; -import jdk.jpackage.internal.model.LinuxRpmPackage; -import jdk.jpackage.internal.model.Launcher; -import jdk.jpackage.internal.model.StandardPackageType; - -final class LinuxFromParams { - - private static LinuxApplication createLinuxApplication( - Map params) throws ConfigException, IOException { - final var launcherFromParams = new LauncherFromParams(); - - final var app = createApplicationBuilder(params, toFunction(launcherParams -> { - final var launcher = launcherFromParams.create(launcherParams); - final var shortcut = findLauncherShortcut(LINUX_SHORTCUT_HINT, params, launcherParams); - return LinuxLauncher.create(launcher, new LinuxLauncherMixin.Stub(shortcut)); - }), (LinuxLauncher linuxLauncher, Launcher launcher) -> { - return LinuxLauncher.create(launcher, linuxLauncher); - }, APPLICATION_LAYOUT).create(); - return LinuxApplication.create(app); - } - - private static LinuxPackageBuilder createLinuxPackageBuilder( - Map params, StandardPackageType type) throws ConfigException, IOException { - - final var app = APPLICATION.fetchFrom(params); - - final var superPkgBuilder = createPackageBuilder(params, app, type); - - final var pkgBuilder = new LinuxPackageBuilder(superPkgBuilder); - - LINUX_PACKAGE_DEPENDENCIES.copyInto(params, pkgBuilder::additionalDependencies); - LINUX_CATEGORY.copyInto(params, pkgBuilder::category); - LINUX_MENU_GROUP.copyInto(params, pkgBuilder::menuGroupName); - RELEASE.copyInto(params, pkgBuilder::release); - LINUX_PACKAGE_NAME.copyInto(params, pkgBuilder::literalName); - - return pkgBuilder; - } - - private static LinuxRpmPackage createLinuxRpmPackage( - Map params) throws ConfigException, IOException { - - final var superPkgBuilder = createLinuxPackageBuilder(params, LINUX_RPM); - - final var pkgBuilder = new LinuxRpmPackageBuilder(superPkgBuilder); - - LICENSE_TYPE.copyInto(params, pkgBuilder::licenseType); - - return pkgBuilder.create(); - } - - private static LinuxDebPackage createLinuxDebPackage( - Map params) throws ConfigException, IOException { - - final var superPkgBuilder = createLinuxPackageBuilder(params, LINUX_DEB); - - final var pkgBuilder = new LinuxDebPackageBuilder(superPkgBuilder); - - MAINTAINER_EMAIL.copyInto(params, pkgBuilder::maintainerEmail); - - final var pkg = pkgBuilder.create(); - - // Show warning if license file is missing - if (pkg.licenseFile().isEmpty()) { - Log.verbose(I18N.getString("message.debs-like-licenses")); - } - - return pkg; - } - - static final BundlerParamInfo APPLICATION = createApplicationBundlerParam( - LinuxFromParams::createLinuxApplication); - - static final BundlerParamInfo RPM_PACKAGE = createPackageBundlerParam( - LinuxFromParams::createLinuxRpmPackage); - - static final BundlerParamInfo DEB_PACKAGE = createPackageBundlerParam( - LinuxFromParams::createLinuxDebPackage); - - private static final BundlerParamInfo LINUX_SHORTCUT_HINT = createStringBundlerParam( - Arguments.CLIOptions.LINUX_SHORTCUT_HINT.getId()); - - private static final BundlerParamInfo LINUX_CATEGORY = createStringBundlerParam( - Arguments.CLIOptions.LINUX_CATEGORY.getId()); - - private static final BundlerParamInfo LINUX_PACKAGE_DEPENDENCIES = createStringBundlerParam( - Arguments.CLIOptions.LINUX_PACKAGE_DEPENDENCIES.getId()); - - private static final BundlerParamInfo LINUX_MENU_GROUP = createStringBundlerParam( - Arguments.CLIOptions.LINUX_MENU_GROUP.getId()); - - private static final BundlerParamInfo RELEASE = createStringBundlerParam( - Arguments.CLIOptions.RELEASE.getId()); - - private static final BundlerParamInfo LINUX_PACKAGE_NAME = createStringBundlerParam( - Arguments.CLIOptions.LINUX_BUNDLE_NAME.getId()); - - private static final BundlerParamInfo LICENSE_TYPE = createStringBundlerParam( - Arguments.CLIOptions.LINUX_RPM_LICENSE_TYPE.getId()); - - private static final BundlerParamInfo MAINTAINER_EMAIL = createStringBundlerParam( - Arguments.CLIOptions.LINUX_DEB_MAINTAINER.getId()); -} diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageBundler.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageBundler.java deleted file mode 100644 index 1f674c0be11..00000000000 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageBundler.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (c) 2019, 2025, 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. 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.jpackage.internal; - -import java.util.Map; -import java.util.Objects; -import jdk.jpackage.internal.model.ConfigException; -import jdk.jpackage.internal.model.LinuxPackage; -import jdk.jpackage.internal.util.Result; - -abstract class LinuxPackageBundler extends AbstractBundler { - - LinuxPackageBundler(BundlerParamInfo pkgParam) { - this.pkgParam = Objects.requireNonNull(pkgParam); - } - - @Override - public final boolean validate(Map params) - throws ConfigException { - - // Order is important! - pkgParam.fetchFrom(params); - BuildEnvFromParams.BUILD_ENV.fetchFrom(params); - - LinuxSystemEnvironment sysEnv; - try { - sysEnv = sysEnv().orElseThrow(); - } catch (RuntimeException ex) { - throw ConfigException.rethrowConfigException(ex); - } - - if (!isDefault()) { - Log.verbose(I18N.format( - "message.not-default-bundler-no-dependencies-lookup", - getName())); - } else if (!sysEnv.soLookupAvailable()) { - final String advice; - if ("deb".equals(getID())) { - advice = "message.deb-ldd-not-available.advice"; - } else { - advice = "message.rpm-ldd-not-available.advice"; - } - // Let user know package dependencies will not be generated. - Log.error(String.format("%s\n%s", I18N.getString( - "message.ldd-not-available"), I18N.getString(advice))); - } - - return true; - } - - @Override - public final String getBundleType() { - return "INSTALLER"; - } - - @Override - public boolean supported(boolean runtimeInstaller) { - return sysEnv().hasValue(); - } - - protected abstract Result sysEnv(); - - private final BundlerParamInfo pkgParam; - - static final Result SYS_ENV = LinuxSystemEnvironment.create(); -} diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackager.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackager.java index 806592904d1..af7f5288cc5 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackager.java +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackager.java @@ -40,7 +40,6 @@ import jdk.jpackage.internal.PackagingPipeline.PrimaryTaskID; import jdk.jpackage.internal.PackagingPipeline.TaskID; import jdk.jpackage.internal.model.ConfigException; import jdk.jpackage.internal.model.LinuxPackage; -import jdk.jpackage.internal.model.PackagerException; abstract class LinuxPackager implements Consumer { @@ -95,7 +94,7 @@ abstract class LinuxPackager implements Consumer { + // Return "true" if shortcut is not configured for the launcher. + return launcher.shortcut().isEmpty(); + }, (LinuxLauncher launcher) -> { + return launcher.shortcut().flatMap(LauncherShortcut::startupDirectory); + }, (launcher, shortcut) -> { + return LinuxLauncher.create(launcher, new LinuxLauncherMixin.Stub(Optional.of(new LauncherShortcut(shortcut)))); + }); + } + private static void writeLauncherLib( AppImageBuildEnv env) throws IOException { @@ -90,6 +106,15 @@ final class LinuxPackagingPipeline { }); } + private static final ApplicationLayout LINUX_APPLICATION_LAYOUT = ApplicationLayout.build() + .launchersDirectory("bin") + .appDirectory("lib/app") + .runtimeDirectory("lib/runtime") + .desktopIntegrationDirectory("lib") + .appModsDirectory("lib/app/mods") + .contentDirectory("lib") + .create(); + static final LinuxApplicationLayout APPLICATION_LAYOUT = LinuxApplicationLayout.create( - ApplicationLayoutUtils.PLATFORM_APPLICATION_LAYOUT, Path.of("lib/libapplauncher.so")); + LINUX_APPLICATION_LAYOUT, Path.of("lib/libapplauncher.so")); } diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxRpmBundler.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxRpmBundler.java deleted file mode 100644 index c134aa91d6a..00000000000 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxRpmBundler.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (c) 2012, 2025, 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. 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.jpackage.internal; - -import java.nio.file.Path; -import java.util.Map; -import java.util.Optional; -import jdk.jpackage.internal.model.LinuxRpmPackage; -import jdk.jpackage.internal.model.PackagerException; -import jdk.jpackage.internal.model.StandardPackageType; -import jdk.jpackage.internal.util.Result; - - -public class LinuxRpmBundler extends LinuxPackageBundler { - - public LinuxRpmBundler() { - super(LinuxFromParams.RPM_PACKAGE); - } - - @Override - public String getName() { - return I18N.getString("rpm.bundler.name"); - } - - @Override - public String getID() { - return "rpm"; - } - - @Override - public Path execute(Map params, Path outputParentDir) throws PackagerException { - - var pkg = LinuxFromParams.RPM_PACKAGE.fetchFrom(params); - - return Packager.build().outputDir(outputParentDir) - .pkg(pkg) - .env(BuildEnvFromParams.BUILD_ENV.fetchFrom(params)) - .pipelineBuilderMutatorFactory((env, _, outputDir) -> { - return new LinuxRpmPackager(env, pkg, outputDir, sysEnv.orElseThrow()); - }).execute(LinuxPackagingPipeline.build(Optional.of(pkg))); - } - - @Override - protected Result sysEnv() { - return sysEnv; - } - - @Override - public boolean isDefault() { - return sysEnv.value() - .map(LinuxSystemEnvironment::nativePackageType) - .map(StandardPackageType.LINUX_RPM::equals) - .orElse(false); - } - - private final Result sysEnv = LinuxRpmSystemEnvironment.create(SYS_ENV); -} diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/model/LinuxLauncher.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/model/LinuxLauncher.java index c84b5e3bbf5..3c654f604c2 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/model/LinuxLauncher.java +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/model/LinuxLauncher.java @@ -24,6 +24,8 @@ */ package jdk.jpackage.internal.model; +import static jdk.jpackage.internal.cli.StandardAppImageFileOption.LINUX_LAUNCHER_SHORTCUT; + import java.util.HashMap; import java.util.Map; import jdk.jpackage.internal.util.CompositeProxy; @@ -39,7 +41,7 @@ public interface LinuxLauncher extends Launcher, LinuxLauncherMixin { default Map extraAppImageFileData() { Map map = new HashMap<>(); shortcut().ifPresent(shortcut -> { - shortcut.store(SHORTCUT_ID, map::put); + shortcut.store(LINUX_LAUNCHER_SHORTCUT.getName(), map::put); }); return map; } @@ -55,6 +57,4 @@ public interface LinuxLauncher extends Launcher, LinuxLauncherMixin { public static LinuxLauncher create(Launcher launcher, LinuxLauncherMixin mixin) { return CompositeProxy.create(LinuxLauncher.class, launcher, mixin); } - - public static final String SHORTCUT_ID = "linux-shortcut"; } diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources.properties b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources.properties index a732d02c7d1..6f05b623064 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources.properties +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources.properties @@ -23,10 +23,6 @@ # questions. # # -app.bundler.name=Linux Application Image -deb.bundler.name=DEB Bundle -rpm.bundler.name=RPM Bundle - param.license-type.default=Unknown resource.deb-control-file=DEB control file diff --git a/src/jdk.jpackage/linux/classes/module-info.java.extra b/src/jdk.jpackage/linux/classes/module-info.java.extra index d32314b0429..7bef2286214 100644 --- a/src/jdk.jpackage/linux/classes/module-info.java.extra +++ b/src/jdk.jpackage/linux/classes/module-info.java.extra @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2025, 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,8 +23,5 @@ * questions. */ -provides jdk.jpackage.internal.Bundler with - jdk.jpackage.internal.LinuxAppBundler, - jdk.jpackage.internal.LinuxDebBundler, - jdk.jpackage.internal.LinuxRpmBundler; - +provides jdk.jpackage.internal.cli.CliBundlingEnvironment with + jdk.jpackage.internal.LinuxBundlingEnvironment; diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacAppBundler.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacAppBundler.java deleted file mode 100644 index cce35ece117..00000000000 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacAppBundler.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) 2012, 2025, 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. 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.jpackage.internal; - -import static jdk.jpackage.internal.StandardBundlerParam.OUTPUT_DIR; -import static jdk.jpackage.internal.StandardBundlerParam.SIGN_BUNDLE; - -import java.util.Map; -import java.util.Optional; -import jdk.jpackage.internal.model.ConfigException; -import jdk.jpackage.internal.util.function.ExceptionBox; - -public class MacAppBundler extends AppImageBundler { - public MacAppBundler() { - setAppImageSupplier((params, output) -> { - - // Order is important! - final var app = MacFromParams.APPLICATION.fetchFrom(params); - final BuildEnv env; - - if (StandardBundlerParam.hasPredefinedAppImage(params)) { - env = MacBuildEnvFromParams.BUILD_ENV.fetchFrom(params); - final var pkg = MacPackagingPipeline.createSignAppImagePackage(app, env); - MacPackagingPipeline.build(Optional.of(pkg)).create().execute(env, pkg, output); - } else { - env = BuildEnv.withAppImageDir(MacBuildEnvFromParams.BUILD_ENV.fetchFrom(params), output); - MacPackagingPipeline.build(Optional.empty()) - .excludeDirFromCopying(output.getParent()) - .excludeDirFromCopying(OUTPUT_DIR.fetchFrom(params)).create().execute(env, app); - } - - }); - setParamsValidator(MacAppBundler::doValidate); - } - - private static void doValidate(Map params) - throws ConfigException { - - try { - MacFromParams.APPLICATION.fetchFrom(params); - } catch (ExceptionBox ex) { - if (ex.getCause() instanceof ConfigException cfgEx) { - throw cfgEx; - } else { - throw ex; - } - } - - if (StandardBundlerParam.hasPredefinedAppImage(params)) { - if (!Optional.ofNullable( - SIGN_BUNDLE.fetchFrom(params)).orElse(Boolean.FALSE)) { - throw new ConfigException( - I18N.getString("error.app-image.mac-sign.required"), - null); - } - } - } -} diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBundlingEnvironment.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBundlingEnvironment.java new file mode 100644 index 00000000000..371a3c7307a --- /dev/null +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBundlingEnvironment.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2025, 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. 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.jpackage.internal; + +import static jdk.jpackage.internal.MacFromOptions.createMacApplication; +import static jdk.jpackage.internal.MacPackagingPipeline.APPLICATION_LAYOUT; +import static jdk.jpackage.internal.MacPackagingPipeline.createSignAppImagePackage; +import static jdk.jpackage.internal.cli.StandardBundlingOperation.CREATE_MAC_APP_IMAGE; +import static jdk.jpackage.internal.cli.StandardBundlingOperation.CREATE_MAC_DMG; +import static jdk.jpackage.internal.cli.StandardBundlingOperation.CREATE_MAC_PKG; +import static jdk.jpackage.internal.cli.StandardBundlingOperation.SIGN_MAC_APP_IMAGE; + +import java.util.Optional; +import jdk.jpackage.internal.cli.Options; +import jdk.jpackage.internal.model.MacPackage; +import jdk.jpackage.internal.model.Package; +import jdk.jpackage.internal.util.Result; + +public class MacBundlingEnvironment extends DefaultBundlingEnvironment { + + public MacBundlingEnvironment() { + super(build() + .defaultOperation(CREATE_MAC_DMG) + .bundler(SIGN_MAC_APP_IMAGE, MacBundlingEnvironment::signAppImage) + .bundler(CREATE_MAC_APP_IMAGE, MacBundlingEnvironment::createAppImage) + .bundler(CREATE_MAC_DMG, LazyLoad::dmgSysEnv, MacBundlingEnvironment::createDmdPackage) + .bundler(CREATE_MAC_PKG, MacBundlingEnvironment::createPkgPackage)); + } + + private static void createDmdPackage(Options options, MacDmgSystemEnvironment sysEnv) { + createNativePackage(options, + MacFromOptions::createMacDmgPackage, + buildEnv()::create, + MacBundlingEnvironment::buildPipeline, + (env, pkg, outputDir) -> { + Log.verbose(I18N.format("message.building-dmg", pkg.app().name())); + return new MacDmgPackager(env, pkg, outputDir, sysEnv); + }); + } + + private static void createPkgPackage(Options options) { + createNativePackage(options, + MacFromOptions::createMacPkgPackage, + buildEnv()::create, + MacBundlingEnvironment::buildPipeline, + (env, pkg, outputDir) -> { + Log.verbose(I18N.format("message.building-pkg", pkg.app().name())); + return new MacPkgPackager(env, pkg, outputDir); + }); + } + + private static void signAppImage(Options options) { + + final var app = createMacApplication(options); + + final var env = buildEnv().create(options, app); + + final var pkg = createSignAppImagePackage(app, env); + + buildPipeline(pkg).create().execute(env, pkg, env.appImageDir()); + } + + private static void createAppImage(Options options) { + + final var app = createMacApplication(options); + + createApplicationImage(options, app, MacPackagingPipeline.build(Optional.empty())); + } + + private static PackagingPipeline.Builder buildPipeline(Package pkg) { + return MacPackagingPipeline.build(Optional.of(pkg)); + } + + private static BuildEnvFromOptions buildEnv() { + return new BuildEnvFromOptions() + .predefinedAppImageLayout(APPLICATION_LAYOUT) + .predefinedRuntimeImageLayout(MacPackage::guessRuntimeLayout); + } + + private static final class LazyLoad { + + static Result dmgSysEnv() { + return DMG_SYS_ENV; + } + + private static final Result DMG_SYS_ENV = MacDmgSystemEnvironment.create(); + } +} diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgBundler.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgBundler.java deleted file mode 100644 index 0ddb987dbee..00000000000 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgBundler.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) 2012, 2025, 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. 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.jpackage.internal; - -import java.nio.file.Path; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import jdk.jpackage.internal.model.ConfigException; -import jdk.jpackage.internal.model.MacDmgPackage; -import jdk.jpackage.internal.model.PackagerException; -import jdk.jpackage.internal.util.Result; - -public class MacDmgBundler extends MacBaseInstallerBundler { - - @Override - public String getName() { - return I18N.getString("dmg.bundler.name"); - } - - @Override - public String getID() { - return "dmg"; - } - - @Override - public boolean validate(Map params) - throws ConfigException { - try { - Objects.requireNonNull(params); - - MacFromParams.DMG_PACKAGE.fetchFrom(params); - - //run basic validation to ensure requirements are met - //we are not interested in return code, only possible exception - validateAppImageAndBundeler(params); - - return true; - } catch (RuntimeException re) { - if (re.getCause() instanceof ConfigException) { - throw (ConfigException) re.getCause(); - } else { - throw new ConfigException(re); - } - } - } - - @Override - public Path execute(Map params, - Path outputParentDir) throws PackagerException { - - var pkg = MacFromParams.DMG_PACKAGE.fetchFrom(params); - - Log.verbose(I18N.format("message.building-dmg", pkg.app().name())); - - return Packager.build().outputDir(outputParentDir) - .pkg(pkg) - .env(MacBuildEnvFromParams.BUILD_ENV.fetchFrom(params)) - .pipelineBuilderMutatorFactory((env, _, outputDir) -> { - return new MacDmgPackager(env, pkg, outputDir, sysEnv.orElseThrow()); - }).execute(MacPackagingPipeline.build(Optional.of(pkg))); - } - - @Override - public boolean supported(boolean runtimeInstaller) { - return sysEnv.hasValue(); - } - - @Override - public boolean isDefault() { - return true; - } - - private final Result sysEnv = MacDmgSystemEnvironment.create(); -} diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFromOptions.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFromOptions.java new file mode 100644 index 00000000000..074014dede0 --- /dev/null +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFromOptions.java @@ -0,0 +1,331 @@ +/* + * Copyright (c) 2025, 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. 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.jpackage.internal; + +import static jdk.jpackage.internal.FromOptions.buildApplicationBuilder; +import static jdk.jpackage.internal.FromOptions.createPackageBuilder; +import static jdk.jpackage.internal.MacPackagingPipeline.APPLICATION_LAYOUT; +import static jdk.jpackage.internal.MacRuntimeValidator.validateRuntimeHasJliLib; +import static jdk.jpackage.internal.MacRuntimeValidator.validateRuntimeHasNoBinDir; +import static jdk.jpackage.internal.cli.StandardBundlingOperation.SIGN_MAC_APP_IMAGE; +import static jdk.jpackage.internal.cli.StandardOption.ICON; +import static jdk.jpackage.internal.cli.StandardOption.APPCLASS; +import static jdk.jpackage.internal.cli.StandardOption.MAC_APP_CATEGORY; +import static jdk.jpackage.internal.cli.StandardOption.MAC_APP_IMAGE_SIGN_IDENTITY; +import static jdk.jpackage.internal.cli.StandardOption.MAC_APP_STORE; +import static jdk.jpackage.internal.cli.StandardOption.MAC_BUNDLE_IDENTIFIER; +import static jdk.jpackage.internal.cli.StandardOption.MAC_BUNDLE_NAME; +import static jdk.jpackage.internal.cli.StandardOption.MAC_BUNDLE_SIGNING_PREFIX; +import static jdk.jpackage.internal.cli.StandardOption.MAC_DMG_CONTENT; +import static jdk.jpackage.internal.cli.StandardOption.MAC_ENTITLEMENTS; +import static jdk.jpackage.internal.cli.StandardOption.MAC_INSTALLER_SIGN_IDENTITY; +import static jdk.jpackage.internal.cli.StandardOption.MAC_SIGN; +import static jdk.jpackage.internal.cli.StandardOption.MAC_SIGNING_KEYCHAIN; +import static jdk.jpackage.internal.cli.StandardOption.MAC_SIGNING_KEY_NAME; +import static jdk.jpackage.internal.cli.StandardOption.PREDEFINED_APP_IMAGE; +import static jdk.jpackage.internal.cli.StandardOption.PREDEFINED_RUNTIME_IMAGE; +import static jdk.jpackage.internal.model.MacPackage.RUNTIME_BUNDLE_LAYOUT; +import static jdk.jpackage.internal.model.StandardPackageType.MAC_DMG; +import static jdk.jpackage.internal.model.StandardPackageType.MAC_PKG; +import static jdk.jpackage.internal.util.function.ExceptionBox.rethrowUnchecked; + +import java.nio.file.Path; +import java.util.Objects; +import java.util.Optional; +import jdk.jpackage.internal.ApplicationBuilder.MainLauncherStartupInfo; +import jdk.jpackage.internal.SigningIdentityBuilder.ExpiredCertificateException; +import jdk.jpackage.internal.SigningIdentityBuilder.StandardCertificateSelector; +import jdk.jpackage.internal.cli.Options; +import jdk.jpackage.internal.cli.StandardFaOption; +import jdk.jpackage.internal.model.ApplicationLaunchers; +import jdk.jpackage.internal.model.ExternalApplication; +import jdk.jpackage.internal.model.FileAssociation; +import jdk.jpackage.internal.model.Launcher; +import jdk.jpackage.internal.model.MacApplication; +import jdk.jpackage.internal.model.MacDmgPackage; +import jdk.jpackage.internal.model.MacFileAssociation; +import jdk.jpackage.internal.model.MacLauncher; +import jdk.jpackage.internal.model.MacPackage; +import jdk.jpackage.internal.model.MacPkgPackage; +import jdk.jpackage.internal.model.PackageType; +import jdk.jpackage.internal.model.RuntimeLayout; +import jdk.jpackage.internal.util.Result; +import jdk.jpackage.internal.util.function.ExceptionBox; + + +final class MacFromOptions { + + static MacApplication createMacApplication(Options options) { + return createMacApplicationInternal(options).app(); + } + + static MacDmgPackage createMacDmgPackage(Options options) { + + final var app = createMacApplicationInternal(options); + + final var superPkgBuilder = createMacPackageBuilder(options, app, MAC_DMG); + + final var pkgBuilder = new MacDmgPackageBuilder(superPkgBuilder); + + MAC_DMG_CONTENT.ifPresentIn(options, pkgBuilder::dmgContent); + + return pkgBuilder.create(); + } + + static MacPkgPackage createMacPkgPackage(Options options) { + + // + // One of "MacSignTest.testExpiredCertificate" test cases expects + // two error messages about expired certificates in the output: one for + // certificate for signing an app image, another certificate for signing a PKG. + // So creation of a PKG package is a bit messy. + // + + final boolean sign = MAC_SIGN.findIn(options).orElse(false); + final boolean appStore = MAC_APP_STORE.findIn(options).orElse(false); + + final var appResult = Result.create(() -> createMacApplicationInternal(options)); + + final Optional pkgBuilder; + if (appResult.hasValue()) { + final var superPkgBuilder = createMacPackageBuilder(options, appResult.orElseThrow(), MAC_PKG); + pkgBuilder = Optional.of(new MacPkgPackageBuilder(superPkgBuilder)); + } else { + // Failed to create an app. Is it because of the expired certificate? + rethrowIfNotExpiredCertificateException(appResult); + // Yes, the certificate for signing the app image has expired. + // Keep going, try to create a signing config for the package. + pkgBuilder = Optional.empty(); + } + + if (sign) { + final var signingIdentityBuilder = createSigningIdentityBuilder(options); + MAC_INSTALLER_SIGN_IDENTITY.ifPresentIn(options, signingIdentityBuilder::signingIdentity); + MAC_SIGNING_KEY_NAME.findIn(options).ifPresent(userName -> { + final StandardCertificateSelector domain; + if (appStore) { + domain = StandardCertificateSelector.APP_STORE_PKG_INSTALLER; + } else { + domain = StandardCertificateSelector.PKG_INSTALLER; + } + + signingIdentityBuilder.certificateSelector(StandardCertificateSelector.create(userName, domain)); + }); + + if (pkgBuilder.isPresent()) { + pkgBuilder.orElseThrow().signingBuilder(signingIdentityBuilder); + } else { + // + // The certificate for signing the app image has expired. Can not create a + // package because there is no app. + // Try to create a signing config for the package and see if the certificate for + // signing the package is also expired. + // + + final var expiredAppCertException = appResult.firstError().orElseThrow(); + + final var pkgSignConfigResult = Result.create(signingIdentityBuilder::create); + try { + rethrowIfNotExpiredCertificateException(pkgSignConfigResult); + // The certificate for the package signing config is also expired! + } catch (RuntimeException ex) { + // Some error occurred trying to configure the signing config for the package. + // Ignore it, bail out with the first error. + rethrowUnchecked(expiredAppCertException); + } + + Log.error(pkgSignConfigResult.firstError().orElseThrow().getMessage()); + rethrowUnchecked(expiredAppCertException); + } + } + + return pkgBuilder.orElseThrow().create(); + } + + private record ApplicationWithDetails(MacApplication app, Optional externalApp) { + ApplicationWithDetails { + Objects.requireNonNull(app); + Objects.requireNonNull(externalApp); + } + } + + private static ApplicationWithDetails createMacApplicationInternal(Options options) { + + final var predefinedRuntimeLayout = PREDEFINED_RUNTIME_IMAGE.findIn(options) + .map(MacPackage::guessRuntimeLayout); + + predefinedRuntimeLayout.ifPresent(layout -> { + validateRuntimeHasJliLib(layout); + if (MAC_APP_STORE.containsIn(options)) { + validateRuntimeHasNoBinDir(layout); + } + }); + + final var launcherFromOptions = new LauncherFromOptions().faMapper(MacFromOptions::createMacFa); + + final var superAppBuilder = buildApplicationBuilder() + .runtimeLayout(RUNTIME_BUNDLE_LAYOUT) + .predefinedRuntimeLayout(predefinedRuntimeLayout.map(RuntimeLayout::unresolve).orElse(null)) + .create(options, launcherOptions -> { + var launcher = launcherFromOptions.create(launcherOptions); + return MacLauncher.create(launcher); + }, (MacLauncher _, Launcher launcher) -> { + return MacLauncher.create(launcher); + }, APPLICATION_LAYOUT); + + if (PREDEFINED_APP_IMAGE.containsIn(options)) { + // Set the main launcher start up info. + // AppImageFile assumes the main launcher start up info is available when + // it is constructed from Application instance. + // This happens when jpackage signs predefined app image. + final var appImageFileOptions = superAppBuilder.externalApplication().orElseThrow().extra(); + final var mainLauncherStartupInfo = new MainLauncherStartupInfo(APPCLASS.getFrom(appImageFileOptions)); + final var launchers = superAppBuilder.launchers().orElseThrow(); + final var mainLauncher = ApplicationBuilder.overrideLauncherStartupInfo(launchers.mainLauncher(), mainLauncherStartupInfo); + superAppBuilder.launchers(new ApplicationLaunchers(MacLauncher.create(mainLauncher), launchers.additionalLaunchers())); + } + + final var app = superAppBuilder.create(); + + final var appBuilder = new MacApplicationBuilder(app); + + PREDEFINED_APP_IMAGE.findIn(options) + .map(MacBundle::new) + .map(MacBundle::infoPlistFile) + .ifPresent(appBuilder::externalInfoPlistFile); + + ICON.ifPresentIn(options, appBuilder::icon); + MAC_BUNDLE_NAME.ifPresentIn(options, appBuilder::bundleName); + MAC_BUNDLE_IDENTIFIER.ifPresentIn(options, appBuilder::bundleIdentifier); + MAC_APP_CATEGORY.ifPresentIn(options, appBuilder::category); + + final boolean sign; + final boolean appStore; + + if (PREDEFINED_APP_IMAGE.containsIn(options) && OptionUtils.bundlingOperation(options) != SIGN_MAC_APP_IMAGE) { + final var appImageFileOptions = superAppBuilder.externalApplication().orElseThrow().extra(); + sign = MAC_SIGN.getFrom(appImageFileOptions); + appStore = MAC_APP_STORE.getFrom(appImageFileOptions); + } else { + sign = MAC_SIGN.getFrom(options); + appStore = MAC_APP_STORE.getFrom(options); + } + + appBuilder.appStore(appStore); + + if (sign) { + final var signingIdentityBuilder = createSigningIdentityBuilder(options); + MAC_APP_IMAGE_SIGN_IDENTITY.ifPresentIn(options, signingIdentityBuilder::signingIdentity); + MAC_SIGNING_KEY_NAME.findIn(options).ifPresent(userName -> { + final StandardCertificateSelector domain; + if (appStore) { + domain = StandardCertificateSelector.APP_STORE_APP_IMAGE; + } else { + domain = StandardCertificateSelector.APP_IMAGE; + } + + signingIdentityBuilder.certificateSelector(StandardCertificateSelector.create(userName, domain)); + }); + + final var signingBuilder = new AppImageSigningConfigBuilder(signingIdentityBuilder); + if (appStore) { + signingBuilder.entitlementsResourceName("sandbox.plist"); + } + + app.mainLauncher().flatMap(Launcher::startupInfo).ifPresentOrElse( + signingBuilder::signingIdentifierPrefix, + () -> { + // Runtime installer does not have the main launcher, use + // 'bundleIdentifier' as the prefix by default. + var bundleIdentifier = appBuilder.create().bundleIdentifier(); + signingBuilder.signingIdentifierPrefix(bundleIdentifier + "."); + }); + MAC_BUNDLE_SIGNING_PREFIX.ifPresentIn(options, signingBuilder::signingIdentifierPrefix); + + MAC_ENTITLEMENTS.ifPresentIn(options, signingBuilder::entitlements); + + appBuilder.signingBuilder(signingBuilder); + } + + return new ApplicationWithDetails(appBuilder.create(), superAppBuilder.externalApplication()); + } + + private static MacPackageBuilder createMacPackageBuilder(Options options, ApplicationWithDetails app, PackageType type) { + + final var builder = new MacPackageBuilder(createPackageBuilder(options, app.app(), type)); + + app.externalApp() + .map(ExternalApplication::extra) + .flatMap(MAC_SIGN::findIn) + .ifPresent(builder::predefinedAppImageSigned); + + PREDEFINED_RUNTIME_IMAGE.findIn(options) + .map(MacBundle::new) + .filter(MacBundle::isValid) + .map(MacBundle::isSigned) + .ifPresent(builder::predefinedAppImageSigned); + + return builder; + } + + private static void rethrowIfNotExpiredCertificateException(Result result) { + final var ex = result.firstError().orElseThrow(); + + if (ex instanceof ExpiredCertificateException) { + return; + } + + if (ex instanceof ExceptionBox box) { + if (box.getCause() instanceof Exception cause) { + rethrowIfNotExpiredCertificateException(Result.ofError(cause)); + } + } + + rethrowUnchecked(ex); + } + + private static SigningIdentityBuilder createSigningIdentityBuilder(Options options) { + final var builder = new SigningIdentityBuilder(); + MAC_SIGNING_KEYCHAIN.findIn(options).map(Path::toString).ifPresent(builder::keychain); + return builder; + } + + private static MacFileAssociation createMacFa(Options options, FileAssociation fa) { + + final var builder = new MacFileAssociationBuilder(); + + StandardFaOption.MAC_CFBUNDLETYPEROLE.ifPresentIn(options, builder::cfBundleTypeRole); + StandardFaOption.MAC_LSHANDLERRANK.ifPresentIn(options, builder::lsHandlerRank); + StandardFaOption.MAC_NSSTORETYPEKEY.ifPresentIn(options, builder::nsPersistentStoreTypeKey); + StandardFaOption.MAC_NSDOCUMENTCLASS.ifPresentIn(options, builder::nsDocumentClass); + StandardFaOption.MAC_LSTYPEISPACKAGE.ifPresentIn(options, builder::lsTypeIsPackage); + StandardFaOption.MAC_LSDOCINPLACE.ifPresentIn(options, builder::lsSupportsOpeningDocumentsInPlace); + StandardFaOption.MAC_UIDOCBROWSER.ifPresentIn(options, builder::uiSupportsDocumentBrowser); + StandardFaOption.MAC_NSEXPORTABLETYPES.ifPresentIn(options, builder::nsExportableTypes); + StandardFaOption.MAC_UTTYPECONFORMSTO.ifPresentIn(options, builder::utTypeConformsTo); + + return builder.create(fa); + } +} diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFromParams.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFromParams.java deleted file mode 100644 index 72c33ef6475..00000000000 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFromParams.java +++ /dev/null @@ -1,384 +0,0 @@ -/* - * Copyright (c) 2025, 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. 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.jpackage.internal; - -import static jdk.jpackage.internal.BundlerParamInfo.createBooleanBundlerParam; -import static jdk.jpackage.internal.BundlerParamInfo.createPathBundlerParam; -import static jdk.jpackage.internal.BundlerParamInfo.createStringBundlerParam; -import static jdk.jpackage.internal.FromParams.createApplicationBuilder; -import static jdk.jpackage.internal.FromParams.createApplicationBundlerParam; -import static jdk.jpackage.internal.FromParams.createPackageBuilder; -import static jdk.jpackage.internal.FromParams.createPackageBundlerParam; -import static jdk.jpackage.internal.MacPackagingPipeline.APPLICATION_LAYOUT; -import static jdk.jpackage.internal.MacRuntimeValidator.validateRuntimeHasJliLib; -import static jdk.jpackage.internal.MacRuntimeValidator.validateRuntimeHasNoBinDir; -import static jdk.jpackage.internal.StandardBundlerParam.DMG_CONTENT; -import static jdk.jpackage.internal.StandardBundlerParam.ICON; -import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE; -import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE_FILE; -import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_RUNTIME_IMAGE; -import static jdk.jpackage.internal.StandardBundlerParam.SIGN_BUNDLE; -import static jdk.jpackage.internal.StandardBundlerParam.hasPredefinedAppImage; -import static jdk.jpackage.internal.model.MacPackage.RUNTIME_BUNDLE_LAYOUT; -import static jdk.jpackage.internal.model.StandardPackageType.MAC_DMG; -import static jdk.jpackage.internal.model.StandardPackageType.MAC_PKG; -import static jdk.jpackage.internal.util.function.ThrowingFunction.toFunction; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.Callable; -import java.util.function.Predicate; -import jdk.jpackage.internal.ApplicationBuilder.MainLauncherStartupInfo; -import jdk.jpackage.internal.SigningIdentityBuilder.ExpiredCertificateException; -import jdk.jpackage.internal.SigningIdentityBuilder.StandardCertificateSelector; -import jdk.jpackage.internal.model.ApplicationLaunchers; -import jdk.jpackage.internal.model.ConfigException; -import jdk.jpackage.internal.model.FileAssociation; -import jdk.jpackage.internal.model.Launcher; -import jdk.jpackage.internal.model.MacApplication; -import jdk.jpackage.internal.model.MacDmgPackage; -import jdk.jpackage.internal.model.MacFileAssociation; -import jdk.jpackage.internal.model.MacLauncher; -import jdk.jpackage.internal.model.MacPackage; -import jdk.jpackage.internal.model.MacPkgPackage; -import jdk.jpackage.internal.model.PackageType; -import jdk.jpackage.internal.model.RuntimeLayout; -import jdk.jpackage.internal.util.function.ExceptionBox; - - -final class MacFromParams { - - private static MacApplication createMacApplication( - Map params) throws ConfigException, IOException { - - final var predefinedRuntimeLayout = PREDEFINED_RUNTIME_IMAGE.findIn(params) - .map(MacPackage::guessRuntimeLayout); - - if (predefinedRuntimeLayout.isPresent()) { - validateRuntimeHasJliLib(predefinedRuntimeLayout.orElseThrow()); - if (APP_STORE.findIn(params).orElse(false)) { - validateRuntimeHasNoBinDir(predefinedRuntimeLayout.orElseThrow()); - } - } - - final var launcherFromParams = new LauncherFromParams(Optional.of(MacFromParams::createMacFa)); - - final var superAppBuilder = createApplicationBuilder(params, toFunction(launcherParams -> { - var launcher = launcherFromParams.create(launcherParams); - return MacLauncher.create(launcher); - }), (MacLauncher _, Launcher launcher) -> { - return MacLauncher.create(launcher); - }, APPLICATION_LAYOUT, RUNTIME_BUNDLE_LAYOUT, predefinedRuntimeLayout.map(RuntimeLayout::unresolve)); - - if (hasPredefinedAppImage(params)) { - // Set the main launcher start up info. - // AppImageFile assumes the main launcher start up info is available when - // it is constructed from Application instance. - // This happens when jpackage signs predefined app image. - final var mainLauncherStartupInfo = new MainLauncherStartupInfo(superAppBuilder.mainLauncherClassName().orElseThrow()); - final var launchers = superAppBuilder.launchers().orElseThrow(); - final var mainLauncher = ApplicationBuilder.overrideLauncherStartupInfo(launchers.mainLauncher(), mainLauncherStartupInfo); - superAppBuilder.launchers(new ApplicationLaunchers(MacLauncher.create(mainLauncher), launchers.additionalLaunchers())); - } - - final var app = superAppBuilder.create(); - - final var appBuilder = new MacApplicationBuilder(app); - - if (hasPredefinedAppImage(params)) { - appBuilder.externalInfoPlistFile(PREDEFINED_APP_IMAGE.findIn(params).map(MacBundle::new).orElseThrow().infoPlistFile()); - } - - ICON.copyInto(params, appBuilder::icon); - MAC_CF_BUNDLE_NAME.copyInto(params, appBuilder::bundleName); - MAC_CF_BUNDLE_IDENTIFIER.copyInto(params, appBuilder::bundleIdentifier); - APP_CATEGORY.copyInto(params, appBuilder::category); - - final boolean sign; - final boolean appStore; - - if (hasPredefinedAppImage(params) && PACKAGE_TYPE.findIn(params).filter(Predicate.isEqual("app-image")).isEmpty()) { - final var appImageFileExtras = new MacAppImageFileExtras(superAppBuilder.externalApplication().orElseThrow()); - sign = appImageFileExtras.signed(); - appStore = appImageFileExtras.appStore(); - } else { - sign = SIGN_BUNDLE.findIn(params).orElse(false); - appStore = APP_STORE.findIn(params).orElse(false); - } - - appBuilder.appStore(appStore); - - if (sign) { - final var signingIdentityBuilder = createSigningIdentityBuilder(params); - APP_IMAGE_SIGN_IDENTITY.copyInto(params, signingIdentityBuilder::signingIdentity); - SIGNING_KEY_USER.findIn(params).ifPresent(userName -> { - final StandardCertificateSelector domain; - if (appStore) { - domain = StandardCertificateSelector.APP_STORE_APP_IMAGE; - } else { - domain = StandardCertificateSelector.APP_IMAGE; - } - - signingIdentityBuilder.certificateSelector(StandardCertificateSelector.create(userName, domain)); - }); - - final var signingBuilder = new AppImageSigningConfigBuilder(signingIdentityBuilder); - if (appStore) { - signingBuilder.entitlementsResourceName("sandbox.plist"); - } - - final var bundleIdentifier = appBuilder.create().bundleIdentifier(); - app.mainLauncher().flatMap(Launcher::startupInfo).ifPresentOrElse( - signingBuilder::signingIdentifierPrefix, - () -> { - // Runtime installer does not have main launcher, so use - // 'bundleIdentifier' as prefix by default. - signingBuilder.signingIdentifierPrefix( - bundleIdentifier + "."); - }); - SIGN_IDENTIFIER_PREFIX.copyInto(params, signingBuilder::signingIdentifierPrefix); - - ENTITLEMENTS.copyInto(params, signingBuilder::entitlements); - - appBuilder.signingBuilder(signingBuilder); - } - - return appBuilder.create(); - } - - private static MacPackageBuilder createMacPackageBuilder( - Map params, MacApplication app, - PackageType type) throws ConfigException { - final var builder = new MacPackageBuilder(createPackageBuilder(params, app, type)); - - PREDEFINED_APP_IMAGE_FILE.findIn(params) - .map(MacAppImageFileExtras::new) - .map(MacAppImageFileExtras::signed) - .ifPresent(builder::predefinedAppImageSigned); - - PREDEFINED_RUNTIME_IMAGE.findIn(params) - .map(MacBundle::new) - .filter(MacBundle::isValid) - .map(MacBundle::isSigned) - .ifPresent(builder::predefinedAppImageSigned); - - return builder; - } - - private static MacDmgPackage createMacDmgPackage( - Map params) throws ConfigException, IOException { - - final var app = APPLICATION.fetchFrom(params); - - final var superPkgBuilder = createMacPackageBuilder(params, app, MAC_DMG); - - final var pkgBuilder = new MacDmgPackageBuilder(superPkgBuilder); - - DMG_CONTENT.copyInto(params, pkgBuilder::dmgContent); - - return pkgBuilder.create(); - } - - private record WithExpiredCertificateException(Optional obj, Optional certEx) { - WithExpiredCertificateException { - if (obj.isEmpty() == certEx.isEmpty()) { - throw new IllegalArgumentException(); - } - } - - static WithExpiredCertificateException of(Callable callable) { - try { - return new WithExpiredCertificateException<>(Optional.of(callable.call()), Optional.empty()); - } catch (ExpiredCertificateException ex) { - return new WithExpiredCertificateException<>(Optional.empty(), Optional.of(ex)); - } catch (ExceptionBox ex) { - if (ex.getCause() instanceof ExpiredCertificateException certEx) { - return new WithExpiredCertificateException<>(Optional.empty(), Optional.of(certEx)); - } - throw ex; - } catch (RuntimeException ex) { - throw ex; - } catch (Throwable t) { - throw ExceptionBox.rethrowUnchecked(t); - } - } - } - - private static MacPkgPackage createMacPkgPackage( - Map params) throws ConfigException, IOException { - - // This is over complicated to make "MacSignTest.testExpiredCertificate" test pass. - - final boolean sign = SIGN_BUNDLE.findIn(params).orElse(false); - final boolean appStore = APP_STORE.findIn(params).orElse(false); - - final var appOrExpiredCertEx = WithExpiredCertificateException.of(() -> { - return APPLICATION.fetchFrom(params); - }); - - final Optional pkgBuilder; - if (appOrExpiredCertEx.obj().isPresent()) { - final var superPkgBuilder = createMacPackageBuilder(params, appOrExpiredCertEx.obj().orElseThrow(), MAC_PKG); - pkgBuilder = Optional.of(new MacPkgPackageBuilder(superPkgBuilder)); - } else { - pkgBuilder = Optional.empty(); - } - - if (sign) { - final var signingIdentityBuilder = createSigningIdentityBuilder(params); - INSTALLER_SIGN_IDENTITY.copyInto(params, signingIdentityBuilder::signingIdentity); - SIGNING_KEY_USER.findIn(params).ifPresent(userName -> { - final StandardCertificateSelector domain; - if (appStore) { - domain = StandardCertificateSelector.APP_STORE_PKG_INSTALLER; - } else { - domain = StandardCertificateSelector.PKG_INSTALLER; - } - - signingIdentityBuilder.certificateSelector(StandardCertificateSelector.create(userName, domain)); - }); - - if (pkgBuilder.isPresent()) { - pkgBuilder.orElseThrow().signingBuilder(signingIdentityBuilder); - } else { - final var expiredPkgCert = WithExpiredCertificateException.of(() -> { - return signingIdentityBuilder.create(); - }).certEx(); - expiredPkgCert.map(ConfigException::getMessage).ifPresent(Log::error); - throw appOrExpiredCertEx.certEx().orElseThrow(); - } - } - - return pkgBuilder.orElseThrow().create(); - } - - private static SigningIdentityBuilder createSigningIdentityBuilder(Map params) { - final var builder = new SigningIdentityBuilder(); - SIGNING_KEYCHAIN.copyInto(params, builder::keychain); - return builder; - } - - private static MacFileAssociation createMacFa(FileAssociation fa, Map params) { - - final var builder = new MacFileAssociationBuilder(); - - FA_MAC_CFBUNDLETYPEROLE.copyInto(params, builder::cfBundleTypeRole); - FA_MAC_LSHANDLERRANK.copyInto(params, builder::lsHandlerRank); - FA_MAC_NSSTORETYPEKEY.copyInto(params, builder::nsPersistentStoreTypeKey); - FA_MAC_NSDOCUMENTCLASS.copyInto(params, builder::nsDocumentClass); - FA_MAC_LSTYPEISPACKAGE.copyInto(params, builder::lsTypeIsPackage); - FA_MAC_LSDOCINPLACE.copyInto(params, builder::lsSupportsOpeningDocumentsInPlace); - FA_MAC_UIDOCBROWSER.copyInto(params, builder::uiSupportsDocumentBrowser); - FA_MAC_NSEXPORTABLETYPES.copyInto(params, builder::nsExportableTypes); - FA_MAC_UTTYPECONFORMSTO.copyInto(params, builder::utTypeConformsTo); - - return toFunction(builder::create).apply(fa); - } - - static final BundlerParamInfo APPLICATION = createApplicationBundlerParam( - MacFromParams::createMacApplication); - - static final BundlerParamInfo DMG_PACKAGE = createPackageBundlerParam( - MacFromParams::createMacDmgPackage); - - static final BundlerParamInfo PKG_PACKAGE = createPackageBundlerParam( - MacFromParams::createMacPkgPackage); - - private static final BundlerParamInfo MAC_CF_BUNDLE_NAME = createStringBundlerParam( - Arguments.CLIOptions.MAC_BUNDLE_NAME.getId()); - - private static final BundlerParamInfo APP_CATEGORY = createStringBundlerParam( - Arguments.CLIOptions.MAC_CATEGORY.getId()); - - private static final BundlerParamInfo ENTITLEMENTS = createPathBundlerParam( - Arguments.CLIOptions.MAC_ENTITLEMENTS.getId()); - - private static final BundlerParamInfo MAC_CF_BUNDLE_IDENTIFIER = createStringBundlerParam( - Arguments.CLIOptions.MAC_BUNDLE_IDENTIFIER.getId()); - - private static final BundlerParamInfo SIGN_IDENTIFIER_PREFIX = createStringBundlerParam( - Arguments.CLIOptions.MAC_BUNDLE_SIGNING_PREFIX.getId()); - - private static final BundlerParamInfo APP_IMAGE_SIGN_IDENTITY = createStringBundlerParam( - Arguments.CLIOptions.MAC_APP_IMAGE_SIGN_IDENTITY.getId()); - - private static final BundlerParamInfo INSTALLER_SIGN_IDENTITY = createStringBundlerParam( - Arguments.CLIOptions.MAC_INSTALLER_SIGN_IDENTITY.getId()); - - private static final BundlerParamInfo SIGNING_KEY_USER = createStringBundlerParam( - Arguments.CLIOptions.MAC_SIGNING_KEY_NAME.getId()); - - private static final BundlerParamInfo SIGNING_KEYCHAIN = createStringBundlerParam( - Arguments.CLIOptions.MAC_SIGNING_KEYCHAIN.getId()); - - private static final BundlerParamInfo PACKAGE_TYPE = createStringBundlerParam( - Arguments.CLIOptions.PACKAGE_TYPE.getId()); - - private static final BundlerParamInfo APP_STORE = createBooleanBundlerParam( - Arguments.CLIOptions.MAC_APP_STORE.getId()); - - private static final BundlerParamInfo FA_MAC_CFBUNDLETYPEROLE = createStringBundlerParam( - Arguments.MAC_CFBUNDLETYPEROLE); - - private static final BundlerParamInfo FA_MAC_LSHANDLERRANK = createStringBundlerParam( - Arguments.MAC_LSHANDLERRANK); - - private static final BundlerParamInfo FA_MAC_NSSTORETYPEKEY = createStringBundlerParam( - Arguments.MAC_NSSTORETYPEKEY); - - private static final BundlerParamInfo FA_MAC_NSDOCUMENTCLASS = createStringBundlerParam( - Arguments.MAC_NSDOCUMENTCLASS); - - private static final BundlerParamInfo FA_MAC_LSTYPEISPACKAGE = createBooleanBundlerParam( - Arguments.MAC_LSTYPEISPACKAGE); - - private static final BundlerParamInfo FA_MAC_LSDOCINPLACE = createBooleanBundlerParam( - Arguments.MAC_LSDOCINPLACE); - - private static final BundlerParamInfo FA_MAC_UIDOCBROWSER = createBooleanBundlerParam( - Arguments.MAC_UIDOCBROWSER); - - @SuppressWarnings("unchecked") - private static final BundlerParamInfo> FA_MAC_NSEXPORTABLETYPES = - new BundlerParamInfo<>( - Arguments.MAC_NSEXPORTABLETYPES, - (Class>) (Object) List.class, - null, - (s, p) -> Arrays.asList(s.split("(,|\\s)+")) - ); - - @SuppressWarnings("unchecked") - private static final BundlerParamInfo> FA_MAC_UTTYPECONFORMSTO = - new BundlerParamInfo<>( - Arguments.MAC_UTTYPECONFORMSTO, - (Class>) (Object) List.class, - null, - (s, p) -> Arrays.asList(s.split("(,|\\s)+")) - ); -} diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPackagingPipeline.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPackagingPipeline.java index b82b20c0c36..51fd15afabb 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPackagingPipeline.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPackagingPipeline.java @@ -75,7 +75,6 @@ import jdk.jpackage.internal.model.MacFileAssociation; import jdk.jpackage.internal.model.MacPackage; import jdk.jpackage.internal.model.Package; import jdk.jpackage.internal.model.PackageType; -import jdk.jpackage.internal.model.PackagerException; import jdk.jpackage.internal.util.FileUtils; import jdk.jpackage.internal.util.PathUtils; import jdk.jpackage.internal.util.function.ThrowingConsumer; @@ -268,7 +267,7 @@ final class MacPackagingPipeline { static AppImageTaskAction withBundleLayout(AppImageTaskAction action) { return new AppImageTaskAction<>() { @Override - public void execute(AppImageBuildEnv env) throws IOException, PackagerException { + public void execute(AppImageBuildEnv env) throws IOException { if (!env.envLayout().runtimeDirectory().getName(0).equals(Path.of("Contents"))) { env = LayoutUtils.fromPackagerLayout(env); } @@ -610,11 +609,20 @@ final class MacPackagingPipeline { } @Override - public void execute(TaskAction taskAction) throws IOException, PackagerException { + public void execute(TaskAction taskAction) throws IOException { delegate.execute(taskAction); } } + private static final ApplicationLayout MAC_APPLICATION_LAYOUT = ApplicationLayout.build() + .launchersDirectory("Contents/MacOS") + .appDirectory("Contents/app") + .runtimeDirectory("Contents/runtime/Contents/Home") + .desktopIntegrationDirectory("Contents/Resources") + .appModsDirectory("Contents/app/mods") + .contentDirectory("Contents") + .create(); + static final MacApplicationLayout APPLICATION_LAYOUT = MacApplicationLayout.create( - ApplicationLayoutUtils.PLATFORM_APPLICATION_LAYOUT, Path.of("Contents/runtime")); + MAC_APPLICATION_LAYOUT, Path.of("Contents/runtime")); } diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgBundler.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgBundler.java deleted file mode 100644 index e827f238db3..00000000000 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgBundler.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (c) 2014, 2025, 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. 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.jpackage.internal; - -import java.nio.file.Path; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import jdk.jpackage.internal.model.ConfigException; -import jdk.jpackage.internal.model.MacPkgPackage; -import jdk.jpackage.internal.model.PackagerException; - -public class MacPkgBundler extends MacBaseInstallerBundler { - - @Override - public String getName() { - return I18N.getString("pkg.bundler.name"); - } - - @Override - public String getID() { - return "pkg"; - } - - @Override - public boolean validate(Map params) - throws ConfigException { - try { - Objects.requireNonNull(params); - - final var pkg = MacFromParams.PKG_PACKAGE.fetchFrom(params); - - // run basic validation to ensure requirements are met - // we are not interested in return code, only possible exception - validateAppImageAndBundeler(params); - - // hdiutil is always available so there's no need - // to test for availability. - - return true; - } catch (RuntimeException re) { - if (re.getCause() instanceof ConfigException) { - throw (ConfigException) re.getCause(); - } else { - throw new ConfigException(re); - } - } - } - - @Override - public Path execute(Map params, - Path outputParentDir) throws PackagerException { - - var pkg = MacFromParams.PKG_PACKAGE.fetchFrom(params); - - Log.verbose(I18N.format("message.building-pkg", pkg.app().name())); - - return Packager.build().outputDir(outputParentDir) - .pkg(pkg) - .env(MacBuildEnvFromParams.BUILD_ENV.fetchFrom(params)) - .pipelineBuilderMutatorFactory((env, _, outputDir) -> { - return new MacPkgPackager(env, pkg, outputDir); - }).execute(MacPackagingPipeline.build(Optional.of(pkg))); - } - - @Override - public boolean supported(boolean runtimeInstaller) { - return true; - } - - @Override - public boolean isDefault() { - return false; - } - -} diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/model/MacApplication.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/model/MacApplication.java index 04ab7042ac5..cfe10e8a012 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/model/MacApplication.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/model/MacApplication.java @@ -25,13 +25,18 @@ package jdk.jpackage.internal.model; import static java.util.stream.Collectors.joining; -import static java.util.stream.Collectors.toMap; +import static java.util.stream.Collectors.toUnmodifiableMap; +import static jdk.jpackage.internal.cli.StandardAppImageFileOption.MAC_APP_STORE; +import static jdk.jpackage.internal.cli.StandardAppImageFileOption.MAC_MAIN_CLASS; +import static jdk.jpackage.internal.cli.StandardAppImageFileOption.MAC_SIGNED; import java.nio.file.Path; import java.util.Map; +import java.util.Optional; import java.util.function.Function; import java.util.stream.IntStream; import java.util.stream.Stream; +import jdk.jpackage.internal.cli.OptionValue; import jdk.jpackage.internal.util.CompositeProxy; public interface MacApplication extends Application, MacApplicationMixin { @@ -76,7 +81,14 @@ public interface MacApplication extends Application, MacApplicationMixin { @Override default Map extraAppImageFileData() { - return Stream.of(ExtraAppImageFileField.values()).collect(toMap(ExtraAppImageFileField::fieldName, x -> x.asString(this))); + return Stream.of(ExtraAppImageFileField.values()).map(field -> { + return field.findStringValue(this).map(value -> { + return Map.entry(field.fieldName(), value); + }); + }) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue)); } public static MacApplication create(Application app, MacApplicationMixin mixin) { @@ -84,23 +96,31 @@ public interface MacApplication extends Application, MacApplicationMixin { } public enum ExtraAppImageFileField { - SIGNED("signed", app -> Boolean.toString(app.sign())), - APP_STORE("app-store", app -> Boolean.toString(app.appStore())); + SIGNED(MAC_SIGNED, app -> { + return Optional.of(Boolean.toString(app.sign())); + }), + APP_STORE(MAC_APP_STORE, app -> { + return Optional.of(Boolean.toString(app.appStore())); + }), + APP_CLASS(MAC_MAIN_CLASS, app -> { + return app.mainLauncher().flatMap(Launcher::startupInfo).map(LauncherStartupInfo::qualifiedClassName); + }), + ; - ExtraAppImageFileField(String fieldName, Function getter) { - this.fieldName = fieldName; + ExtraAppImageFileField(OptionValue option, Function> getter) { + this.fieldName = option.getName(); this.getter = getter; } - public String fieldName() { + String fieldName() { return fieldName; } - String asString(MacApplication app) { + Optional findStringValue(MacApplication app) { return getter.apply(app); } private final String fieldName; - private final Function getter; + private final Function> getter; } } diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources.properties b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources.properties index afa71d84d5c..7ce20925439 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources.properties +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources.properties @@ -23,12 +23,6 @@ # questions. # # - -app.bundler.name=Mac Application Image -store.bundler.name=Mac App Store Ready Bundler -dmg.bundler.name=Mac DMG Package -pkg.bundler.name=Mac PKG Package - error.invalid-cfbundle-version.advice=Set a compatible 'app-version' value. Valid versions are one to three integers separated by dots. error.explicit-sign-no-cert=Signature explicitly requested but no signing certificate found error.explicit-sign-no-cert.advice=Specify a valid mac-signing-key-user-name and mac-signing-keychain @@ -60,7 +54,6 @@ resource.pkg-background-image=pkg background image resource.pkg-pdf=project definition file resource.launchd-plist-file=launchd plist file - message.bundle-name-too-long-warning={0} is set to ''{1}'', which is longer than 16 characters. For a better Mac experience consider shortening it. message.preparing-info-plist=Preparing Info.plist: {0}. message.icon-not-icns= The specified icon "{0}" is not an ICNS file and will not be used. The default icon will be used in it's place. @@ -70,7 +63,6 @@ message.creating-association-with-null-extension=Creating association with null message.ignoring.symlink=Warning: codesign is skipping the symlink {0}. message.already.signed=File already signed: {0}. message.keychain.error=Error: unable to get keychain list. -message.building-bundle=Building Mac App Store Package for {0}. message.invalid-identifier=invalid mac bundle identifier [{0}]. message.invalid-identifier.advice=specify identifier with "--mac-package-identifier". message.building-dmg=Building DMG package for {0}. diff --git a/src/jdk.jpackage/macosx/classes/module-info.java.extra b/src/jdk.jpackage/macosx/classes/module-info.java.extra index 1496167cd4a..e6202dd1156 100644 --- a/src/jdk.jpackage/macosx/classes/module-info.java.extra +++ b/src/jdk.jpackage/macosx/classes/module-info.java.extra @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2025, 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,8 +23,5 @@ * questions. */ -provides jdk.jpackage.internal.Bundler with - jdk.jpackage.internal.MacAppBundler, - jdk.jpackage.internal.MacDmgBundler, - jdk.jpackage.internal.MacPkgBundler; - +provides jdk.jpackage.internal.cli.CliBundlingEnvironment with + jdk.jpackage.internal.MacBundlingEnvironment; diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AddLauncherArguments.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AddLauncherArguments.java deleted file mode 100644 index 93d037c6a45..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AddLauncherArguments.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright (c) 2018, 2023, 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. 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.jpackage.internal; - -import java.nio.file.Path; -import java.util.HashMap; -import java.util.Map; -import java.util.List; -import java.util.Optional; - -import jdk.internal.util.OperatingSystem; - -import jdk.jpackage.internal.Arguments.CLIOptions; -import static jdk.jpackage.internal.StandardBundlerParam.LAUNCHER_DATA; -import static jdk.jpackage.internal.StandardBundlerParam.APP_NAME; - -/* - * AddLauncherArguments - * - * Processes a add-launcher properties file to create the Map of - * bundle params applicable to the add-launcher: - * - * BundlerParams p = (new AddLauncherArguments(file)).getLauncherMap(); - * - * A add-launcher is another executable program generated by either the - * create-app-image mode or the create-installer mode. - * The add-launcher may be the same program with different configuration, - * or a completely different program created from the same files. - * - * There may be multiple add-launchers, each created by using the - * command line arg "--add-launcher - * - * The add-launcher properties file may have any of: - * - * appVersion - * description - * module - * main-jar - * main-class - * icon - * arguments - * java-options - * launcher-as-service - * win-console - * win-shortcut - * win-menu - * linux-app-category - * linux-shortcut - * - */ -class AddLauncherArguments { - - private final String name; - private final String filename; - private Map allArgs; - private Map bundleParams; - - AddLauncherArguments(String name, String filename) { - this.name = name; - this.filename = filename; - } - - private void initLauncherMap() { - if (bundleParams != null) { - return; - } - - allArgs = Arguments.getPropertiesFromFile(filename); - allArgs.put(CLIOptions.NAME.getId(), name); - - bundleParams = new HashMap<>(); - String mainJar = getOptionValue(CLIOptions.MAIN_JAR); - String mainClass = getOptionValue(CLIOptions.APPCLASS); - String module = getOptionValue(CLIOptions.MODULE); - - if (module != null && mainClass != null) { - Arguments.putUnlessNull(bundleParams, CLIOptions.MODULE.getId(), - module + "/" + mainClass); - } else if (module != null) { - Arguments.putUnlessNull(bundleParams, CLIOptions.MODULE.getId(), - module); - } else { - Arguments.putUnlessNull(bundleParams, CLIOptions.MAIN_JAR.getId(), - mainJar); - Arguments.putUnlessNull(bundleParams, CLIOptions.APPCLASS.getId(), - mainClass); - } - - Arguments.putUnlessNull(bundleParams, CLIOptions.NAME.getId(), - getOptionValue(CLIOptions.NAME)); - - Arguments.putUnlessNull(bundleParams, CLIOptions.VERSION.getId(), - getOptionValue(CLIOptions.VERSION)); - - Arguments.putUnlessNull(bundleParams, CLIOptions.DESCRIPTION.getId(), - getOptionValue(CLIOptions.DESCRIPTION)); - - Arguments.putUnlessNull(bundleParams, CLIOptions.RELEASE.getId(), - getOptionValue(CLIOptions.RELEASE)); - - Arguments.putUnlessNull(bundleParams, CLIOptions.ICON.getId(), - Optional.ofNullable(getOptionValue(CLIOptions.ICON)).map( - Path::of).orElse(null)); - - Arguments.putUnlessNull(bundleParams, - CLIOptions.LAUNCHER_AS_SERVICE.getId(), getOptionValue( - CLIOptions.LAUNCHER_AS_SERVICE)); - - if (OperatingSystem.isWindows()) { - Arguments.putUnlessNull(bundleParams, - CLIOptions.WIN_CONSOLE_HINT.getId(), - getOptionValue(CLIOptions.WIN_CONSOLE_HINT)); - Arguments.putUnlessNull(bundleParams, CLIOptions.WIN_SHORTCUT_HINT.getId(), - getOptionValue(CLIOptions.WIN_SHORTCUT_HINT)); - Arguments.putUnlessNull(bundleParams, CLIOptions.WIN_MENU_HINT.getId(), - getOptionValue(CLIOptions.WIN_MENU_HINT)); - } - - if (OperatingSystem.isLinux()) { - Arguments.putUnlessNull(bundleParams, CLIOptions.LINUX_CATEGORY.getId(), - getOptionValue(CLIOptions.LINUX_CATEGORY)); - Arguments.putUnlessNull(bundleParams, CLIOptions.LINUX_SHORTCUT_HINT.getId(), - getOptionValue(CLIOptions.LINUX_SHORTCUT_HINT)); - } - - // "arguments" and "java-options" even if value is null: - if (allArgs.containsKey(CLIOptions.ARGUMENTS.getId())) { - String argumentStr = getOptionValue(CLIOptions.ARGUMENTS); - bundleParams.put(CLIOptions.ARGUMENTS.getId(), - Arguments.getArgumentList(argumentStr)); - } - - if (allArgs.containsKey(CLIOptions.JAVA_OPTIONS.getId())) { - String jvmargsStr = getOptionValue(CLIOptions.JAVA_OPTIONS); - bundleParams.put(CLIOptions.JAVA_OPTIONS.getId(), - Arguments.getArgumentList(jvmargsStr)); - } - } - - private String getOptionValue(CLIOptions option) { - if (option == null || allArgs == null) { - return null; - } - - String id = option.getId(); - - if (allArgs.containsKey(id)) { - return allArgs.get(id); - } - - return null; - } - - Map getLauncherMap() { - initLauncherMap(); - return bundleParams; - } - - static Map merge( - Map original, - Map additional, String... exclude) { - Map tmp = new HashMap<>(original); - List.of(exclude).forEach(tmp::remove); - - // remove LauncherData from map so it will be re-computed - tmp.remove(LAUNCHER_DATA.getID()); - // remove "application-name" so it will be re-computed - tmp.remove(APP_NAME.getID()); - - if (additional.containsKey(CLIOptions.MODULE.getId())) { - tmp.remove(CLIOptions.MAIN_JAR.getId()); - tmp.remove(CLIOptions.APPCLASS.getId()); - } else if (additional.containsKey(CLIOptions.MAIN_JAR.getId())) { - tmp.remove(CLIOptions.MODULE.getId()); - } - if (additional.containsKey(CLIOptions.ARGUMENTS.getId())) { - // if add launcher properties file contains "arguments", even with - // null value, disregard the "arguments" from command line - tmp.remove(CLIOptions.ARGUMENTS.getId()); - } - if (additional.containsKey(CLIOptions.JAVA_OPTIONS.getId())) { - // same thing for java-options - tmp.remove(CLIOptions.JAVA_OPTIONS.getId()); - } - tmp.putAll(additional); - return tmp; - } - -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageBundler.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageBundler.java deleted file mode 100644 index 192630a5656..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageBundler.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright (c) 2015, 2025, 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. 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.jpackage.internal; - -import static jdk.jpackage.internal.StandardBundlerParam.APP_NAME; -import static jdk.jpackage.internal.StandardBundlerParam.LAUNCHER_DATA; -import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.text.MessageFormat; -import java.util.Map; -import java.util.Objects; -import jdk.internal.util.OperatingSystem; -import jdk.jpackage.internal.model.ConfigException; -import jdk.jpackage.internal.model.PackagerException; - - -class AppImageBundler extends AbstractBundler { - - @Override - public final String getName() { - return I18N.getString("app.bundler.name"); - } - - @Override - public final String getID() { - return "app"; - } - - @Override - public final String getBundleType() { - return "IMAGE"; - } - - @Override - public final boolean validate(Map params) - throws ConfigException { - try { - Objects.requireNonNull(params); - - if (!params.containsKey(PREDEFINED_APP_IMAGE.getID()) - && !StandardBundlerParam.isRuntimeInstaller(params)) { - LAUNCHER_DATA.fetchFrom(params); - } - - if (paramsValidator != null) { - paramsValidator.validate(params); - } - } catch (RuntimeException re) { - if (re.getCause() instanceof ConfigException) { - throw (ConfigException) re.getCause(); - } else { - throw new ConfigException(re); - } - } - - return true; - } - - @Override - public final Path execute(Map params, - Path outputParentDir) throws PackagerException { - - final var predefinedAppImage = PREDEFINED_APP_IMAGE.fetchFrom(params); - - try { - if (predefinedAppImage == null) { - Path rootDirectory = createRoot(params, outputParentDir); - appImageSupplier.prepareApplicationFiles(params, rootDirectory); - return rootDirectory; - } else { - appImageSupplier.prepareApplicationFiles(params, predefinedAppImage); - return predefinedAppImage; - } - } catch (PackagerException pe) { - throw pe; - } catch (RuntimeException|IOException ex) { - Log.verbose(ex); - throw new PackagerException(ex); - } - } - - @Override - public final boolean supported(boolean runtimeInstaller) { - return true; - } - - @Override - public final boolean isDefault() { - return false; - } - - @FunctionalInterface - static interface AppImageSupplier { - - void prepareApplicationFiles(Map params, - Path root) throws PackagerException, IOException; - } - - final AppImageBundler setAppImageSupplier(AppImageSupplier v) { - appImageSupplier = v; - return this; - } - - final AppImageBundler setParamsValidator(ParamsValidator v) { - paramsValidator = v; - return this; - } - - @FunctionalInterface - interface ParamsValidator { - void validate(Map params) throws ConfigException; - } - - private Path createRoot(Map params, - Path outputDirectory) throws PackagerException, IOException { - - IOUtils.writableOutputDir(outputDirectory); - - String imageName = APP_NAME.fetchFrom(params); - if (OperatingSystem.isMacOS()) { - imageName = imageName + ".app"; - } - - Log.verbose(MessageFormat.format( - I18N.getString("message.creating-app-bundle"), - imageName, outputDirectory.toAbsolutePath())); - - // Create directory structure - Path rootDirectory = outputDirectory.resolve(imageName); - if (Files.exists(rootDirectory)) { - throw new PackagerException("error.root-exists", - rootDirectory.toAbsolutePath().toString()); - } - - Files.createDirectories(rootDirectory); - - return rootDirectory; - } - - private ParamsValidator paramsValidator; - private AppImageSupplier appImageSupplier; -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageFile.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageFile.java index 8b8a22edc56..ec8c9a31173 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageFile.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageFile.java @@ -25,148 +25,148 @@ package jdk.jpackage.internal; import static java.util.stream.Collectors.joining; -import static java.util.stream.Collectors.toMap; -import static java.util.stream.Collectors.toSet; +import static java.util.stream.Collectors.toUnmodifiableMap; +import static java.util.stream.Collectors.toUnmodifiableSet; +import static jdk.jpackage.internal.cli.StandardAppImageFileOption.APP_VERSION; +import static jdk.jpackage.internal.cli.StandardAppImageFileOption.LAUNCHER_AS_SERVICE; +import static jdk.jpackage.internal.cli.StandardAppImageFileOption.LAUNCHER_NAME; import static jdk.jpackage.internal.util.function.ThrowingFunction.toFunction; import java.io.IOException; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; import java.util.Objects; -import java.util.Optional; import java.util.Set; import java.util.stream.Stream; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import jdk.internal.util.OperatingSystem; +import jdk.jpackage.internal.cli.OptionValue; +import jdk.jpackage.internal.cli.Options; +import jdk.jpackage.internal.cli.StandardAppImageFileOption.AppImageFileOptionScope; +import jdk.jpackage.internal.cli.StandardAppImageFileOption.InvalidOptionValueException; +import jdk.jpackage.internal.cli.StandardAppImageFileOption.MissingMandatoryOptionException; import jdk.jpackage.internal.model.Application; import jdk.jpackage.internal.model.ApplicationLayout; -import jdk.jpackage.internal.model.ConfigException; import jdk.jpackage.internal.model.ExternalApplication; +import jdk.jpackage.internal.model.JPackageException; import jdk.jpackage.internal.model.Launcher; import jdk.jpackage.internal.util.XmlUtils; +import jdk.jpackage.internal.util.function.ExceptionBox; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.xml.sax.SAXException; -final class AppImageFile implements ExternalApplication { +final class AppImageFile { AppImageFile(Application app) { - this(new ApplicationData(app)); - } - - private AppImageFile(ApplicationData app) { - - appVersion = app.version(); - launcherName = app.mainLauncherName(); - mainClass = app.mainLauncherMainClassName(); - extra = app.extra; - creatorVersion = getVersion(); - creatorPlatform = getPlatform(); - addLauncherInfos = app.additionalLaunchers; - } - - @Override - public List getAddLaunchers() { - return addLauncherInfos; - } - - @Override - public String getAppVersion() { - return appVersion; - } - - @Override - public String getAppName() { - return launcherName; - } - - @Override - public String getLauncherName() { - return launcherName; - } - - @Override - public String getMainClass() { - return mainClass; - } - - @Override - public Map getExtra() { - return extra; + appVersion = Objects.requireNonNull(app.version()); + extra = Objects.requireNonNull(app.extraAppImageFileData()); + launcherInfos = app.launchers().stream().map(LauncherInfo::new).toList(); } /** - * Saves file with application image info in application image using values - * from this instance. + * Writes the values captured in this instance into the application image info + * file in the given application layout. + *

    + * It is an equivalent to calling + * {@link #save(ApplicationLayout, OperatingSystem)} method with + * {@code OperatingSystem.current()} for the second parameter. + * + * @param appLayout the application layout + * @throws IOException if an I/O error occurs when writing */ void save(ApplicationLayout appLayout) throws IOException { + save(appLayout, OperatingSystem.current()); + } + + /** + * Writes the values captured in this instance into the application image info + * file in the given application layout. + * + * @param appLayout the application layout + * @param os the target OS + * @throws IOException if an I/O error occurs when writing + */ + void save(ApplicationLayout appLayout, OperatingSystem os) throws IOException { XmlUtils.createXml(getPathInAppImage(appLayout), xml -> { xml.writeStartElement("jpackage-state"); - xml.writeAttribute("version", creatorVersion); - xml.writeAttribute("platform", creatorPlatform); + xml.writeAttribute("version", getVersion()); + xml.writeAttribute("platform", getPlatform(os)); xml.writeStartElement("app-version"); xml.writeCharacters(appVersion); xml.writeEndElement(); - xml.writeStartElement("main-launcher"); - xml.writeCharacters(launcherName); - xml.writeEndElement(); - - xml.writeStartElement("main-class"); - xml.writeCharacters(mainClass); - xml.writeEndElement(); - for (var extraKey : extra.keySet().stream().sorted().toList()) { xml.writeStartElement(extraKey); xml.writeCharacters(extra.get(extraKey)); xml.writeEndElement(); } - for (var li : addLauncherInfos) { - xml.writeStartElement("add-launcher"); - xml.writeAttribute("name", li.name()); - xml.writeAttribute("service", Boolean.toString(li.service())); - for (var extraKey : li.extra().keySet().stream().sorted().toList()) { - xml.writeStartElement(extraKey); - xml.writeCharacters(li.extra().get(extraKey)); - xml.writeEndElement(); - } - xml.writeEndElement(); + launcherInfos.getFirst().save(xml, "main-launcher"); + + for (var li : launcherInfos.subList(1, launcherInfos.size())) { + li.save(xml, "add-launcher"); } }); } /** - * Returns path to application image info file. - * @param appLayout - application layout + * Returns the path to the application image info file in the given application layout. + * + * @param appLayout the application layout */ static Path getPathInAppImage(ApplicationLayout appLayout) { return appLayout.appDirectory().resolve(FILENAME); } /** - * Loads application image info from application image. - * @param appImageDir - path at which to resolve the given application layout - * @param appLayout - application layout + * Loads application image info from the specified application layout. + *

    + * It is an equivalent to calling + * {@link #load(ApplicationLayout, OperatingSystem)} method with + * {@code OperatingSystem.current()} for the second parameter. + * + * @param appLayout the application layout */ - static AppImageFile load(Path appImageDir, ApplicationLayout appLayout) throws ConfigException, IOException { - var srcFilePath = getPathInAppImage(appLayout.resolveAt(appImageDir)); + static ExternalApplication load(ApplicationLayout appLayout) { + return load(appLayout, OperatingSystem.current()); + } + + /** + * Loads application image info from the specified application layout and OS. + * + * @param appLayout the application layout + * @param os the OS defining extra properties of the application and + * additional launchers + */ + static ExternalApplication load(ApplicationLayout appLayout, OperatingSystem os) { + Objects.requireNonNull(appLayout); + Objects.requireNonNull(os); + + final var appImageDir = appLayout.rootDirectory(); + final var appImageFilePath = getPathInAppImage(appLayout); + final var relativeAppImageFilePath = appImageDir.relativize(appImageFilePath); + try { - final Document doc = XmlUtils.initDocumentBuilder().parse(Files.newInputStream(srcFilePath)); + final Document doc = XmlUtils.initDocumentBuilder().parse(Files.newInputStream(appImageFilePath)); final XPath xPath = XPathFactory.newInstance().newXPath(); final var isPlatformValid = XmlUtils.queryNodes(doc, xPath, "/jpackage-state/@platform").findFirst().map( - Node::getNodeValue).map(getPlatform()::equals).orElse(false); + Node::getNodeValue).map(getPlatform(os)::equals).orElse(false); if (!isPlatformValid) { throw new InvalidAppImageFileException(); } @@ -177,102 +177,73 @@ final class AppImageFile implements ExternalApplication { throw new InvalidAppImageFileException(); } - final AppImageProperties props; + final var appOptions = AppImageFileOptionScope.APP.parse(appImageFilePath, AppImageProperties.main(doc, xPath), os); + + final var mainLauncherOptions = LauncherElement.MAIN.readAll(doc, xPath).stream().reduce((_, second) -> { + return second; + }).map(launcherProps -> { + return AppImageFileOptionScope.LAUNCHER.parse(appImageFilePath, launcherProps, os); + }).orElseThrow(InvalidAppImageFileException::new); + + final var addLauncherOptions = LauncherElement.ADDITIONAL.readAll(doc, xPath).stream().map(launcherProps -> { + return AppImageFileOptionScope.LAUNCHER.parse(appImageFilePath, launcherProps, os); + }).toList(); + try { - props = AppImageProperties.main(doc, xPath); - } catch (IllegalArgumentException ex) { + return ExternalApplication.create(Options.concat(appOptions, mainLauncherOptions), addLauncherOptions, os); + } catch (NoSuchElementException ex) { throw new InvalidAppImageFileException(ex); } - final var additionalLaunchers = AppImageProperties.launchers(doc, xPath).stream().map(launcherProps -> { - try { - return new LauncherInfo(launcherProps.get("name"), - launcherProps.find("service").map(Boolean::parseBoolean).orElse(false), launcherProps.getExtra()); - } catch (IllegalArgumentException ex) { - throw new InvalidAppImageFileException(ex); - } - }).toList(); - - return new AppImageFile(new ApplicationData(props.get("app-version"), props.get("main-launcher"), - props.get("main-class"), props.getExtra(), additionalLaunchers)); - } catch (XPathExpressionException ex) { // This should never happen as XPath expressions should be correct - throw new RuntimeException(ex); + throw ExceptionBox.rethrowUnchecked(ex); } catch (SAXException ex) { - // Exception reading input XML (probably malformed XML) - throw new IOException(ex); + // Malformed input XML + throw new JPackageException(I18N.format("error.malformed-app-image-file", relativeAppImageFilePath, appImageDir), ex); } catch (NoSuchFileException ex) { - throw I18N.buildConfigException("error.foreign-app-image", appImageDir).create(); - } catch (InvalidAppImageFileException ex) { + // Don't save the original exception as its error message is redundant. + throw new JPackageException(I18N.format("error.missing-app-image-file", relativeAppImageFilePath, appImageDir)); + } catch (InvalidAppImageFileException|InvalidOptionValueException|MissingMandatoryOptionException ex) { // Invalid input XML - throw I18N.buildConfigException("error.invalid-app-image", appImageDir, srcFilePath).create(); + throw new JPackageException(I18N.format("error.invalid-app-image-file", relativeAppImageFilePath, appImageDir), ex); + } catch (IOException ex) { + throw new JPackageException(I18N.format("error.reading-app-image-file", relativeAppImageFilePath, appImageDir), ex); } } - static boolean getBooleanExtraFieldValue(String fieldId, ExternalApplication appImageFile) { - Objects.requireNonNull(fieldId); - Objects.requireNonNull(appImageFile); - return Optional.ofNullable(appImageFile.getExtra().get(fieldId)).map(Boolean::parseBoolean).orElse(false); - } - static String getVersion() { return System.getProperty("java.version"); } - static String getPlatform() { - return PLATFORM_LABELS.get(OperatingSystem.current()); + static String getPlatform(OperatingSystem os) { + return Objects.requireNonNull(PLATFORM_LABELS.get(Objects.requireNonNull(os))); } + private static final class AppImageProperties { - private AppImageProperties(Map data, Set stdKeys) { - this.data = data; - this.stdKeys = stdKeys; + + static Map main(Document xml, XPath xPath) throws XPathExpressionException { + return queryProperties(xml.getDocumentElement(), xPath, MAIN_PROPERTIES_XPATH_QUERY); } - static AppImageProperties main(Document xml, XPath xPath) throws XPathExpressionException { - final var data = queryProperties(xml.getDocumentElement(), xPath, MAIN_PROPERTIES_XPATH_QUERY); - return new AppImageProperties(data, MAIN_ELEMENT_NAMES); - } + static Map launcher(Element launcherNode, XPath xPath) throws XPathExpressionException { + final var attrData = XmlUtils.toStream(launcherNode.getAttributes()) + .collect(toUnmodifiableMap(Node::getNodeName, Node::getNodeValue)); - static AppImageProperties launcher(Element addLauncherNode, XPath xPath) throws XPathExpressionException { - final var attrData = XmlUtils.toStream(addLauncherNode.getAttributes()) - .collect(toMap(Node::getNodeName, Node::getNodeValue)); - - final var extraData = queryProperties(addLauncherNode, xPath, LAUNCHER_PROPERTIES_XPATH_QUERY); + final var extraData = queryProperties(launcherNode, xPath, LAUNCHER_PROPERTIES_XPATH_QUERY); final Map data = new HashMap<>(attrData); data.putAll(extraData); - return new AppImageProperties(data, LAUNCHER_ATTR_NAMES); - } - - static List launchers(Document xml, XPath xPath) throws XPathExpressionException { - return XmlUtils.queryNodes(xml, xPath, "/jpackage-state/add-launcher") - .map(Element.class::cast).map(toFunction(e -> { - return launcher(e, xPath); - })).toList(); - } - - String get(String name) { - return find(name).orElseThrow(InvalidAppImageFileException::new); - } - - Optional find(String name) { - return Optional.ofNullable(data.get(name)); - } - - Map getExtra() { - Map extra = new HashMap<>(data); - stdKeys.forEach(extra::remove); - return extra; + return data; } private static Map queryProperties(Element e, XPath xPath, String xpathExpr) throws XPathExpressionException { return XmlUtils.queryNodes(e, xPath, xpathExpr) .map(Element.class::cast) - .collect(toMap(Node::getNodeName, selectedElement -> { + .collect(toUnmodifiableMap(Node::getNodeName, selectedElement -> { return selectedElement.getTextContent(); }, (a, b) -> b)); } @@ -285,13 +256,14 @@ final class AppImageFile implements ExternalApplication { return String.format("*[(%s) and not(*)]", otherElementNames); } - private final Map data; - private final Set stdKeys; - - private static final Set LAUNCHER_ATTR_NAMES = Set.of("name", "service"); + private static final Set LAUNCHER_ATTR_NAMES = Stream.of( + LAUNCHER_NAME + ).map(OptionValue::getName).collect(toUnmodifiableSet()); private static final String LAUNCHER_PROPERTIES_XPATH_QUERY = xpathQueryForExtraProperties(LAUNCHER_ATTR_NAMES); - private static final Set MAIN_ELEMENT_NAMES = Set.of("app-version", "main-launcher", "main-class"); + private static final Set MAIN_ELEMENT_NAMES = Stream.of( + APP_VERSION + ).map(OptionValue::getName).collect(toUnmodifiableSet()); private static final String MAIN_PROPERTIES_XPATH_QUERY; static { @@ -301,40 +273,64 @@ final class AppImageFile implements ExternalApplication { MAIN_PROPERTIES_XPATH_QUERY = String.format("%s|/jpackage-state/%s", nonEmptyMainElements, xpathQueryForExtraProperties(Stream.concat(MAIN_ELEMENT_NAMES.stream(), - Stream.of("add-launcher")).collect(toSet()))); + Stream.of("main-launcher", "add-launcher")).collect(toUnmodifiableSet()))); } } - private record ApplicationData(String version, String mainLauncherName, String mainLauncherMainClassName, - Map extra, List additionalLaunchers) { - ApplicationData { - Objects.requireNonNull(version); - Objects.requireNonNull(mainLauncherName); - Objects.requireNonNull(mainLauncherMainClassName); - Objects.requireNonNull(extra); - Objects.requireNonNull(additionalLaunchers); + private enum LauncherElement { + MAIN("main-launcher"), + ADDITIONAL("add-launcher"); - for (final var property : List.of(version, mainLauncherName, mainLauncherMainClassName)) { - if (property.isBlank()) { - throw new IllegalArgumentException(); - } + LauncherElement(String elementName) { + this.elementName = Objects.requireNonNull(elementName); + } + + List> readAll(Document xml, XPath xPath) throws XPathExpressionException { + return XmlUtils.queryNodes(xml, xPath, "/jpackage-state/" + elementName + "[@name]") + .map(Element.class::cast).map(toFunction(e -> { + return AppImageProperties.launcher(e, xPath); + })).toList(); + } + + private final String elementName; + } + + private record LauncherInfo(String name, Map properties) { + LauncherInfo { + Objects.requireNonNull(name); + Objects.requireNonNull(properties); + } + + LauncherInfo(Launcher launcher) { + this(launcher.name(), properties(launcher)); + } + + void save(XMLStreamWriter xml, String elementName) throws IOException, XMLStreamException { + xml.writeStartElement(elementName); + xml.writeAttribute("name", name()); + for (var key : properties().keySet().stream().sorted().toList()) { + xml.writeStartElement(key); + xml.writeCharacters(properties().get(key)); + xml.writeEndElement(); } + xml.writeEndElement(); } - ApplicationData(Application app) { - this(app, app.mainLauncher().orElseThrow()); - } + private static Map properties(Launcher launcher) { + List> standardProps = new ArrayList<>(); + if (launcher.isService()) { + standardProps.add(Map.entry(LAUNCHER_AS_SERVICE.getName(), Boolean.TRUE.toString())); + } - private ApplicationData(Application app, Launcher mainLauncher) { - this(app.version(), mainLauncher.name(), mainLauncher.startupInfo().orElseThrow().qualifiedClassName(), - app.extraAppImageFileData(), app.additionalLaunchers().stream().map(launcher -> { - return new LauncherInfo(launcher.name(), launcher.isService(), - launcher.extraAppImageFileData()); - }).toList()); + return Stream.concat( + standardProps.stream(), + launcher.extraAppImageFileData().entrySet().stream() + ).collect(toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue)); } } + private static class InvalidAppImageFileException extends RuntimeException { InvalidAppImageFileException() { @@ -347,13 +343,10 @@ final class AppImageFile implements ExternalApplication { private static final long serialVersionUID = 1L; } + private final String appVersion; - private final String launcherName; - private final String mainClass; private final Map extra; - private final List addLauncherInfos; - private final String creatorVersion; - private final String creatorPlatform; + private final List launcherInfos; private static final String FILENAME = ".jpackage.xml"; diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationBuilder.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationBuilder.java index 76a5fc1a50c..e9324f65671 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationBuilder.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationBuilder.java @@ -35,12 +35,13 @@ import java.util.Optional; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Predicate; +import java.util.stream.Collectors; import jdk.jpackage.internal.model.AppImageLayout; import jdk.jpackage.internal.model.Application; import jdk.jpackage.internal.model.ApplicationLaunchers; import jdk.jpackage.internal.model.ConfigException; import jdk.jpackage.internal.model.ExternalApplication; -import jdk.jpackage.internal.model.ExternalApplication.LauncherInfo; +import jdk.jpackage.internal.model.JPackageException; import jdk.jpackage.internal.model.Launcher; import jdk.jpackage.internal.model.LauncherIcon; import jdk.jpackage.internal.model.LauncherStartupInfo; @@ -55,11 +56,9 @@ final class ApplicationBuilder { final var launchersAsList = Optional.ofNullable(launchers).map( ApplicationLaunchers::asList).orElseGet(List::of); - final var launcherCount = launchersAsList.size(); - - if (launcherCount != launchersAsList.stream().map(Launcher::name).distinct().count()) { - throw buildConfigException("ERR_NoUniqueName").create(); - } + launchersAsList.stream().collect(Collectors.toMap(Launcher::name, x -> x, (a, b) -> { + throw new JPackageException(I18N.format("error.launcher-duplicate-name", a.name())); + })); final String effectiveName; if (name != null) { @@ -88,25 +87,8 @@ final class ApplicationBuilder { return this; } - ApplicationBuilder initFromExternalApplication(ExternalApplication app, - Function mapper) { - - externalApp = Objects.requireNonNull(app); - - if (version == null) { - version = app.getAppVersion(); - } - if (name == null) { - name = app.getAppName(); - } - runtimeBuilder = null; - - var mainLauncherInfo = new LauncherInfo(app.getLauncherName(), false, Map.of()); - - launchers = new ApplicationLaunchers( - mapper.apply(mainLauncherInfo), - app.getAddLaunchers().stream().map(mapper).toList()); - + ApplicationBuilder externalApplication(ExternalApplication v) { + externalApp = v; return this; } @@ -123,15 +105,6 @@ final class ApplicationBuilder { return Optional.ofNullable(externalApp); } - Optional mainLauncherClassName() { - return launchers() - .map(ApplicationLaunchers::mainLauncher) - .flatMap(Launcher::startupInfo) - .map(LauncherStartupInfo::qualifiedClassName).or(() -> { - return externalApplication().map(ExternalApplication::getMainClass); - }); - } - ApplicationBuilder appImageLayout(AppImageLayout v) { appImageLayout = v; return this; diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationLayoutUtils.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationLayoutUtils.java deleted file mode 100644 index 0ea72ae160c..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationLayoutUtils.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) 2025, 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. 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.jpackage.internal; - -import java.nio.file.Path; -import jdk.internal.util.OperatingSystem; -import jdk.jpackage.internal.model.ApplicationLayout; - - -final class ApplicationLayoutUtils { - - public static final ApplicationLayout PLATFORM_APPLICATION_LAYOUT; - - private static final ApplicationLayout WIN_APPLICATION_LAYOUT = ApplicationLayout.build() - .setAll("") - .appDirectory("app") - .runtimeDirectory("runtime") - .appModsDirectory(Path.of("app", "mods")) - .create(); - - private static final ApplicationLayout MAC_APPLICATION_LAYOUT = ApplicationLayout.build() - .launchersDirectory("Contents/MacOS") - .appDirectory("Contents/app") - .runtimeDirectory("Contents/runtime/Contents/Home") - .desktopIntegrationDirectory("Contents/Resources") - .appModsDirectory("Contents/app/mods") - .contentDirectory("Contents") - .create(); - - private static final ApplicationLayout LINUX_APPLICATION_LAYOUT = ApplicationLayout.build() - .launchersDirectory("bin") - .appDirectory("lib/app") - .runtimeDirectory("lib/runtime") - .desktopIntegrationDirectory("lib") - .appModsDirectory("lib/app/mods") - .contentDirectory("lib") - .create(); - - static { - switch (OperatingSystem.current()) { - case WINDOWS -> PLATFORM_APPLICATION_LAYOUT = WIN_APPLICATION_LAYOUT; - case MACOS -> PLATFORM_APPLICATION_LAYOUT = MAC_APPLICATION_LAYOUT; - case LINUX -> PLATFORM_APPLICATION_LAYOUT = LINUX_APPLICATION_LAYOUT; - default -> { - throw new UnsupportedOperationException(); - } - } - } -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Arguments.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Arguments.java deleted file mode 100644 index f0323bbd841..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Arguments.java +++ /dev/null @@ -1,867 +0,0 @@ -/* - * Copyright (c) 2018, 2025, 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. 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.jpackage.internal; - -import jdk.internal.util.OperatingSystem; -import jdk.jpackage.internal.model.ConfigException; -import jdk.jpackage.internal.model.PackagerException; -import java.io.IOException; -import java.io.Reader; -import java.nio.file.Files; -import java.nio.file.Path; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Properties; -import java.util.ResourceBundle; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Arguments - * - * This class encapsulates and processes the command line arguments, - * in effect, implementing all the work of jpackage tool. - * - * The primary entry point, processArguments(): - * Processes and validates command line arguments, constructing DeployParams. - * Validates the DeployParams, and generate the BundleParams. - * Generates List of Bundlers from BundleParams valid for this platform. - * Executes each Bundler in the list. - */ -public class Arguments { - private static final ResourceBundle I18N = ResourceBundle.getBundle( - "jdk.jpackage.internal.resources.MainResources"); - - private static final String FA_EXTENSIONS = "extension"; - private static final String FA_CONTENT_TYPE = "mime-type"; - private static final String FA_DESCRIPTION = "description"; - private static final String FA_ICON = "icon"; - - // Mac specific file association keys - // String - public static final String MAC_CFBUNDLETYPEROLE = "mac.CFBundleTypeRole"; - public static final String MAC_LSHANDLERRANK = "mac.LSHandlerRank"; - public static final String MAC_NSSTORETYPEKEY = "mac.NSPersistentStoreTypeKey"; - public static final String MAC_NSDOCUMENTCLASS = "mac.NSDocumentClass"; - // Boolean - public static final String MAC_LSTYPEISPACKAGE = "mac.LSTypeIsPackage"; - public static final String MAC_LSDOCINPLACE = "mac.LSSupportsOpeningDocumentsInPlace"; - public static final String MAC_UIDOCBROWSER = "mac.UISupportsDocumentBrowser"; - // Array of strings - public static final String MAC_NSEXPORTABLETYPES = "mac.NSExportableTypes"; - public static final String MAC_UTTYPECONFORMSTO = "mac.UTTypeConformsTo"; - - // regexp for parsing args (for example, for additional launchers) - private static Pattern pattern = Pattern.compile( - "(?:(?:([\"'])(?:\\\\\\1|.)*?(?:\\1|$))|(?:\\\\[\"'\\s]|[^\\s]))++"); - - private DeployParams deployParams = null; - - private int pos = 0; - private List argList = null; - - private List allOptions = null; - - private boolean hasMainJar = false; - private boolean hasMainClass = false; - private boolean hasMainModule = false; - public boolean userProvidedBuildRoot = false; - - private String buildRoot = null; - private String mainJarPath = null; - - private boolean runtimeInstaller = false; - - private List addLaunchers = null; - - private static final Map argIds = new HashMap<>(); - private static final Map argShortIds = new HashMap<>(); - - static { - // init maps for parsing arguments - (EnumSet.allOf(CLIOptions.class)).forEach(option -> { - argIds.put(option.getIdWithPrefix(), option); - if (option.getShortIdWithPrefix() != null) { - argShortIds.put(option.getShortIdWithPrefix(), option); - } - }); - } - - private static final InheritableThreadLocal instance = - new InheritableThreadLocal(); - - public Arguments(String[] args) { - instance.set(this); - - argList = new ArrayList(args.length); - for (String arg : args) { - argList.add(arg); - } - - pos = 0; - - deployParams = new DeployParams(); - - allOptions = new ArrayList<>(); - - addLaunchers = new ArrayList<>(); - } - - // CLIOptions is public for DeployParamsTest - public enum CLIOptions { - PACKAGE_TYPE("type", "t", OptionCategories.PROPERTY, () -> { - var type = popArg(); - context().deployParams.setTargetFormat(type); - setOptionValue("type", type); - }), - - INPUT ("input", "i", OptionCategories.PROPERTY, () -> { - setOptionValue("input", popArg()); - }), - - OUTPUT ("dest", "d", OptionCategories.PROPERTY, () -> { - var path = Path.of(popArg()); - setOptionValue("dest", path); - }), - - DESCRIPTION ("description", OptionCategories.PROPERTY), - - VENDOR ("vendor", OptionCategories.PROPERTY), - - APPCLASS ("main-class", OptionCategories.PROPERTY, () -> { - context().hasMainClass = true; - setOptionValue("main-class", popArg()); - }), - - NAME ("name", "n", OptionCategories.PROPERTY), - - VERBOSE ("verbose", OptionCategories.PROPERTY, () -> { - setOptionValue("verbose", true); - Log.setVerbose(); - }), - - RESOURCE_DIR("resource-dir", - OptionCategories.PROPERTY, () -> { - String resourceDir = popArg(); - setOptionValue("resource-dir", resourceDir); - }), - - DMG_CONTENT ("mac-dmg-content", OptionCategories.PROPERTY, () -> { - List content = getArgumentList(popArg()); - content.forEach(a -> setOptionValue("mac-dmg-content", a)); - }), - - ARGUMENTS ("arguments", OptionCategories.PROPERTY, () -> { - List arguments = getArgumentList(popArg()); - setOptionValue("arguments", arguments); - }), - - JLINK_OPTIONS ("jlink-options", OptionCategories.PROPERTY, () -> { - List options = getArgumentList(popArg()); - setOptionValue("jlink-options", options); - }), - - ICON ("icon", OptionCategories.PROPERTY), - - COPYRIGHT ("copyright", OptionCategories.PROPERTY), - - LICENSE_FILE ("license-file", OptionCategories.PROPERTY), - - VERSION ("app-version", OptionCategories.PROPERTY), - - RELEASE ("linux-app-release", OptionCategories.PROPERTY), - - ABOUT_URL ("about-url", OptionCategories.PROPERTY), - - JAVA_OPTIONS ("java-options", OptionCategories.PROPERTY, () -> { - List args = getArgumentList(popArg()); - args.forEach(a -> setOptionValue("java-options", a)); - }), - - APP_CONTENT ("app-content", OptionCategories.PROPERTY, () -> { - getArgumentList(popArg()).forEach( - a -> setOptionValue("app-content", a)); - }), - - FILE_ASSOCIATIONS ("file-associations", - OptionCategories.PROPERTY, () -> { - Map args = new HashMap<>(); - - // load .properties file - Map initialMap = getPropertiesFromFile(popArg()); - - putUnlessNull(args, StandardBundlerParam.FA_EXTENSIONS.getID(), - initialMap.get(FA_EXTENSIONS)); - - putUnlessNull(args, StandardBundlerParam.FA_CONTENT_TYPE.getID(), - initialMap.get(FA_CONTENT_TYPE)); - - putUnlessNull(args, StandardBundlerParam.FA_DESCRIPTION.getID(), - initialMap.get(FA_DESCRIPTION)); - - putUnlessNull(args, StandardBundlerParam.FA_ICON.getID(), - initialMap.get(FA_ICON)); - - // Mac extended file association arguments - putUnlessNull(args, MAC_CFBUNDLETYPEROLE, - initialMap.get(MAC_CFBUNDLETYPEROLE)); - - putUnlessNull(args, MAC_LSHANDLERRANK, - initialMap.get(MAC_LSHANDLERRANK)); - - putUnlessNull(args, MAC_NSSTORETYPEKEY, - initialMap.get(MAC_NSSTORETYPEKEY)); - - putUnlessNull(args, MAC_NSDOCUMENTCLASS, - initialMap.get(MAC_NSDOCUMENTCLASS)); - - putUnlessNull(args, MAC_LSTYPEISPACKAGE, - initialMap.get(MAC_LSTYPEISPACKAGE)); - - putUnlessNull(args, MAC_LSDOCINPLACE, - initialMap.get(MAC_LSDOCINPLACE)); - - putUnlessNull(args, MAC_UIDOCBROWSER, - initialMap.get(MAC_UIDOCBROWSER)); - - putUnlessNull(args, MAC_NSEXPORTABLETYPES, - initialMap.get(MAC_NSEXPORTABLETYPES)); - - putUnlessNull(args, MAC_UTTYPECONFORMSTO, - initialMap.get(MAC_UTTYPECONFORMSTO)); - - ArrayList> associationList = - new ArrayList>(); - - associationList.add(args); - - // check that we really add _another_ value to the list - setOptionValue("file-associations", associationList); - - }), - - ADD_LAUNCHER ("add-launcher", - OptionCategories.PROPERTY, () -> { - String spec = popArg(); - String name = null; - String filename = spec; - if (spec.contains("=")) { - String[] values = spec.split("=", 2); - name = values[0]; - filename = values[1]; - } - context().addLaunchers.add( - new AddLauncherArguments(name, filename)); - }), - - TEMP_ROOT ("temp", OptionCategories.PROPERTY, () -> { - context().buildRoot = popArg(); - context().userProvidedBuildRoot = true; - setOptionValue("temp", context().buildRoot); - }), - - INSTALL_DIR ("install-dir", OptionCategories.PROPERTY), - - PREDEFINED_APP_IMAGE ("app-image", OptionCategories.PROPERTY), - - PREDEFINED_RUNTIME_IMAGE ("runtime-image", OptionCategories.PROPERTY), - - MAIN_JAR ("main-jar", OptionCategories.PROPERTY, () -> { - context().mainJarPath = popArg(); - context().hasMainJar = true; - setOptionValue("main-jar", context().mainJarPath); - }), - - MODULE ("module", "m", OptionCategories.MODULAR, () -> { - context().hasMainModule = true; - setOptionValue("module", popArg()); - }), - - ADD_MODULES ("add-modules", OptionCategories.MODULAR), - - MODULE_PATH ("module-path", "p", OptionCategories.MODULAR), - - LAUNCHER_AS_SERVICE ("launcher-as-service", OptionCategories.PROPERTY, () -> { - setOptionValue("launcher-as-service", true); - }), - - MAC_SIGN ("mac-sign", "s", OptionCategories.PLATFORM_MAC, () -> { - setOptionValue("mac-sign", true); - }), - - MAC_APP_STORE ("mac-app-store", OptionCategories.PLATFORM_MAC, () -> { - setOptionValue("mac-app-store", true); - }), - - MAC_CATEGORY ("mac-app-category", OptionCategories.PLATFORM_MAC), - - MAC_BUNDLE_NAME ("mac-package-name", OptionCategories.PLATFORM_MAC), - - MAC_BUNDLE_IDENTIFIER("mac-package-identifier", - OptionCategories.PLATFORM_MAC), - - MAC_BUNDLE_SIGNING_PREFIX ("mac-package-signing-prefix", - OptionCategories.PLATFORM_MAC), - - MAC_SIGNING_KEY_NAME ("mac-signing-key-user-name", - OptionCategories.PLATFORM_MAC), - - MAC_APP_IMAGE_SIGN_IDENTITY ("mac-app-image-sign-identity", - OptionCategories.PLATFORM_MAC), - - MAC_INSTALLER_SIGN_IDENTITY ("mac-installer-sign-identity", - OptionCategories.PLATFORM_MAC), - - MAC_SIGNING_KEYCHAIN ("mac-signing-keychain", - OptionCategories.PLATFORM_MAC), - - MAC_ENTITLEMENTS ("mac-entitlements", OptionCategories.PLATFORM_MAC), - - WIN_HELP_URL ("win-help-url", OptionCategories.PLATFORM_WIN), - - WIN_UPDATE_URL ("win-update-url", OptionCategories.PLATFORM_WIN), - - WIN_MENU_HINT ("win-menu", OptionCategories.PLATFORM_WIN, - createArgumentWithOptionalValueAction("win-menu")), - - WIN_MENU_GROUP ("win-menu-group", OptionCategories.PLATFORM_WIN), - - WIN_SHORTCUT_HINT ("win-shortcut", OptionCategories.PLATFORM_WIN, - createArgumentWithOptionalValueAction("win-shortcut")), - - WIN_SHORTCUT_PROMPT ("win-shortcut-prompt", - OptionCategories.PLATFORM_WIN, () -> { - setOptionValue("win-shortcut-prompt", true); - }), - - WIN_PER_USER_INSTALLATION ("win-per-user-install", - OptionCategories.PLATFORM_WIN, () -> { - setOptionValue("win-per-user-install", false); - }), - - WIN_DIR_CHOOSER ("win-dir-chooser", - OptionCategories.PLATFORM_WIN, () -> { - setOptionValue("win-dir-chooser", true); - }), - - WIN_UPGRADE_UUID ("win-upgrade-uuid", - OptionCategories.PLATFORM_WIN), - - WIN_CONSOLE_HINT ("win-console", OptionCategories.PLATFORM_WIN, () -> { - setOptionValue("win-console", true); - }), - - LINUX_BUNDLE_NAME ("linux-package-name", - OptionCategories.PLATFORM_LINUX), - - LINUX_DEB_MAINTAINER ("linux-deb-maintainer", - OptionCategories.PLATFORM_LINUX), - - LINUX_CATEGORY ("linux-app-category", - OptionCategories.PLATFORM_LINUX), - - LINUX_RPM_LICENSE_TYPE ("linux-rpm-license-type", - OptionCategories.PLATFORM_LINUX), - - LINUX_PACKAGE_DEPENDENCIES ("linux-package-deps", - OptionCategories.PLATFORM_LINUX), - - LINUX_SHORTCUT_HINT ("linux-shortcut", OptionCategories.PLATFORM_LINUX, - createArgumentWithOptionalValueAction("linux-shortcut")), - - LINUX_MENU_GROUP ("linux-menu-group", OptionCategories.PLATFORM_LINUX); - - private final String id; - private final String shortId; - private final OptionCategories category; - private final Runnable action; - private static Arguments argContext; - - private CLIOptions(String id, OptionCategories category) { - this(id, null, category, null); - } - - private CLIOptions(String id, String shortId, - OptionCategories category) { - this(id, shortId, category, null); - } - - private CLIOptions(String id, - OptionCategories category, Runnable action) { - this(id, null, category, action); - } - - private CLIOptions(String id, String shortId, - OptionCategories category, Runnable action) { - this.id = id; - this.shortId = shortId; - this.action = action; - this.category = category; - } - - public static Arguments context() { - return instance.get(); - } - - public String getId() { - return this.id; - } - - String getIdWithPrefix() { - return "--" + this.id; - } - - String getShortIdWithPrefix() { - return this.shortId == null ? null : "-" + this.shortId; - } - - void execute() { - if (action != null) { - action.run(); - } else { - defaultAction(); - } - } - - private void defaultAction() { - context().deployParams.addBundleArgument(id, popArg()); - } - - private static void setOptionValue(String option, Object value) { - context().deployParams.addBundleArgument(option, value); - } - - private static String popArg() { - nextArg(); - return (context().pos >= context().argList.size()) ? - "" : context().argList.get(context().pos); - } - - private static String getArg() { - return (context().pos >= context().argList.size()) ? - "" : context().argList.get(context().pos); - } - - private static void nextArg() { - context().pos++; - } - - private static void prevArg() { - Objects.checkIndex(context().pos, context().argList.size()); - context().pos--; - } - - private static boolean hasNextArg() { - return context().pos < context().argList.size(); - } - - private static Runnable createArgumentWithOptionalValueAction(String option) { - Objects.requireNonNull(option); - return () -> { - nextArg(); - if (hasNextArg()) { - var value = getArg(); - if (value.startsWith("-")) { - prevArg(); - setOptionValue(option, true); - } else { - setOptionValue(option, value); - } - } else { - setOptionValue(option, true); - } - }; - } - } - - enum OptionCategories { - MODULAR, - PROPERTY, - PLATFORM_MAC, - PLATFORM_WIN, - PLATFORM_LINUX; - } - - public boolean processArguments() { - try { - // parse cmd line - String arg; - CLIOptions option; - for (; CLIOptions.hasNextArg(); CLIOptions.nextArg()) { - arg = CLIOptions.getArg(); - if ((option = toCLIOption(arg)) != null) { - // found a CLI option - allOptions.add(option); - option.execute(); - } else { - throw new PackagerException("ERR_InvalidOption", arg); - } - } - - // display error for arguments that are not supported - // for current configuration. - - validateArguments(); - - List> launchersAsMap = - new ArrayList<>(); - - for (AddLauncherArguments sl : addLaunchers) { - launchersAsMap.add(sl.getLauncherMap()); - } - - deployParams.addBundleArgument( - StandardBundlerParam.ADD_LAUNCHERS.getID(), - launchersAsMap); - - // at this point deployParams should be already configured - - deployParams.validate(); - - BundleParams bp = deployParams.getBundleParams(); - - // validate name(s) - ArrayList usedNames = new ArrayList(); - usedNames.add(bp.getName()); // add main app name - - for (AddLauncherArguments sl : addLaunchers) { - Map slMap = sl.getLauncherMap(); - String slName = - (String) slMap.get(Arguments.CLIOptions.NAME.getId()); - if (slName == null) { - throw new PackagerException("ERR_NoAddLauncherName"); - } - // same rules apply to additional launcher names as app name - DeployParams.validateName(slName, false); - for (String usedName : usedNames) { - if (slName.equals(usedName)) { - throw new PackagerException("ERR_NoUniqueName"); - } - } - usedNames.add(slName); - } - - generateBundle(bp.getBundleParamsAsMap()); - return true; - } catch (Exception e) { - Log.verbose(e); - String msg1 = e.getMessage(); - Log.fatalError(msg1); - if (e.getCause() != null && e.getCause() != e) { - String msg2 = e.getCause().getMessage(); - if (msg2 != null && !msg1.contains(msg2)) { - Log.fatalError(msg2); - } - } - return false; - } - } - - private void validateArguments() throws PackagerException { - String type = deployParams.getTargetFormat(); - String ptype = (type != null) ? type : "default"; - boolean imageOnly = deployParams.isTargetAppImage(); - boolean hasAppImage = allOptions.contains( - CLIOptions.PREDEFINED_APP_IMAGE); - boolean hasRuntime = allOptions.contains( - CLIOptions.PREDEFINED_RUNTIME_IMAGE); - boolean installerOnly = !imageOnly && hasAppImage; - boolean isMac = OperatingSystem.isMacOS(); - runtimeInstaller = !imageOnly && hasRuntime && !hasAppImage && - !hasMainModule && !hasMainJar; - - for (CLIOptions option : allOptions) { - if (!ValidOptions.checkIfSupported(option)) { - // includes option valid only on different platform - throw new PackagerException("ERR_UnsupportedOption", - option.getIdWithPrefix()); - } - if ((imageOnly && !isMac) || (imageOnly && !hasAppImage && isMac)) { - if (!ValidOptions.checkIfImageSupported(option)) { - throw new PackagerException("ERR_InvalidTypeOption", - option.getIdWithPrefix(), type); - } - } else if (imageOnly && hasAppImage && isMac) { // Signing app image - if (!ValidOptions.checkIfSigningSupported(option)) { - throw new PackagerException( - "ERR_InvalidOptionWithAppImageSigning", - option.getIdWithPrefix()); - } - } else if (installerOnly || runtimeInstaller) { - if (!ValidOptions.checkIfInstallerSupported(option)) { - if (runtimeInstaller) { - throw new PackagerException("ERR_NoInstallerEntryPoint", - option.getIdWithPrefix()); - } else { - throw new PackagerException("ERR_InvalidTypeOption", - option.getIdWithPrefix(), ptype); - } - } - } - } - if (hasRuntime) { - if (hasAppImage) { - // note --runtime-image is only for image or runtime installer. - throw new PackagerException("ERR_MutuallyExclusiveOptions", - CLIOptions.PREDEFINED_RUNTIME_IMAGE.getIdWithPrefix(), - CLIOptions.PREDEFINED_APP_IMAGE.getIdWithPrefix()); - } - if (allOptions.contains(CLIOptions.ADD_MODULES)) { - throw new PackagerException("ERR_MutuallyExclusiveOptions", - CLIOptions.PREDEFINED_RUNTIME_IMAGE.getIdWithPrefix(), - CLIOptions.ADD_MODULES.getIdWithPrefix()); - } - if (allOptions.contains(CLIOptions.JLINK_OPTIONS)) { - throw new PackagerException("ERR_MutuallyExclusiveOptions", - CLIOptions.PREDEFINED_RUNTIME_IMAGE.getIdWithPrefix(), - CLIOptions.JLINK_OPTIONS.getIdWithPrefix()); - } - } - if (allOptions.contains(CLIOptions.MAC_SIGNING_KEY_NAME) && - allOptions.contains(CLIOptions.MAC_APP_IMAGE_SIGN_IDENTITY)) { - throw new PackagerException("ERR_MutuallyExclusiveOptions", - CLIOptions.MAC_SIGNING_KEY_NAME.getIdWithPrefix(), - CLIOptions.MAC_APP_IMAGE_SIGN_IDENTITY.getIdWithPrefix()); - } - if (allOptions.contains(CLIOptions.MAC_SIGNING_KEY_NAME) && - allOptions.contains(CLIOptions.MAC_INSTALLER_SIGN_IDENTITY)) { - throw new PackagerException("ERR_MutuallyExclusiveOptions", - CLIOptions.MAC_SIGNING_KEY_NAME.getIdWithPrefix(), - CLIOptions.MAC_INSTALLER_SIGN_IDENTITY.getIdWithPrefix()); - } - if (isMac && (imageOnly || "dmg".equals(type)) && - allOptions.contains(CLIOptions.MAC_INSTALLER_SIGN_IDENTITY)) { - throw new PackagerException("ERR_InvalidTypeOption", - CLIOptions.MAC_INSTALLER_SIGN_IDENTITY.getIdWithPrefix(), - type); - } - if (allOptions.contains(CLIOptions.DMG_CONTENT) - && !("dmg".equals(type))) { - throw new PackagerException("ERR_InvalidTypeOption", - CLIOptions.DMG_CONTENT.getIdWithPrefix(), ptype); - } - if (hasMainJar && hasMainModule) { - throw new PackagerException("ERR_BothMainJarAndModule"); - } - if (imageOnly && !hasAppImage && !hasMainJar && !hasMainModule) { - throw new PackagerException("ERR_NoEntryPoint"); - } - } - - private jdk.jpackage.internal.Bundler getPlatformBundler() { - boolean appImage = deployParams.isTargetAppImage(); - String type = deployParams.getTargetFormat(); - String bundleType = (appImage ? "IMAGE" : "INSTALLER"); - - for (jdk.jpackage.internal.Bundler bundler : - Bundlers.createBundlersInstance().getBundlers(bundleType)) { - if (type == null) { - if (bundler.isDefault()) { - return bundler; - } - } else { - if (appImage || type.equalsIgnoreCase(bundler.getID())) { - return bundler; - } - } - } - return null; - } - - private void generateBundle(Map params) - throws PackagerException { - - // the temp dir needs to be fetched from the params early, - // to prevent each copy of the params (such as may be used for - // additional launchers) from generating a separate temp dir when - // the default is used (the default is a new temp directory) - // The bundler.cleanup() below would not otherwise be able to - // clean these extra (and unneeded) temp directories. - StandardBundlerParam.TEMP_ROOT.fetchFrom(params); - - // determine what bundler to run - jdk.jpackage.internal.Bundler bundler = getPlatformBundler(); - - if (bundler == null || !bundler.supported(runtimeInstaller)) { - String type = Optional.ofNullable(bundler).map(Bundler::getID).orElseGet( - () -> deployParams.getTargetFormat()); - throw new PackagerException("ERR_InvalidInstallerType", type); - } - - Map localParams = new HashMap<>(params); - try { - Path result = executeBundler(bundler, params, localParams); - if (result == null) { - throw new PackagerException("MSG_BundlerFailed", - bundler.getID(), bundler.getName()); - } - Log.verbose(MessageFormat.format( - I18N.getString("message.bundle-created"), - bundler.getName())); - } catch (ConfigException e) { - Log.verbose(e); - if (e.getAdvice() != null) { - throw new PackagerException(e, "MSG_BundlerConfigException", - bundler.getName(), e.getMessage(), e.getAdvice()); - } else { - throw new PackagerException(e, - "MSG_BundlerConfigExceptionNoAdvice", - bundler.getName(), e.getMessage()); - } - } catch (RuntimeException re) { - Log.verbose(re); - throw new PackagerException(re, "MSG_BundlerRuntimeException", - bundler.getName(), re.toString()); - } finally { - if (userProvidedBuildRoot) { - Log.verbose(MessageFormat.format( - I18N.getString("message.debug-working-directory"), - (Path.of(buildRoot)).toAbsolutePath().toString())); - } else { - // always clean up the temporary directory created - // when --temp option not used. - bundler.cleanup(localParams); - } - } - } - - private static Path executeBundler(Bundler bundler, Map params, - Map localParams) throws ConfigException, PackagerException { - try { - bundler.validate(localParams); - return bundler.execute(localParams, StandardBundlerParam.OUTPUT_DIR.fetchFrom(params)); - } catch (ConfigException|PackagerException ex) { - throw ex; - } catch (RuntimeException ex) { - if (ex.getCause() instanceof ConfigException cfgEx) { - throw cfgEx; - } else if (ex.getCause() instanceof PackagerException pkgEx) { - throw pkgEx; - } else { - throw ex; - } - } - } - - static CLIOptions toCLIOption(String arg) { - CLIOptions option; - if ((option = argIds.get(arg)) == null) { - option = argShortIds.get(arg); - } - return option; - } - - static Map getPropertiesFromFile(String filename) { - Map map = new HashMap<>(); - // load properties file - Properties properties = new Properties(); - try (Reader reader = Files.newBufferedReader(Path.of(filename))) { - properties.load(reader); - } catch (IOException e) { - Log.error("Exception: " + e.getMessage()); - } - - for (final String name: properties.stringPropertyNames()) { - map.put(name, properties.getProperty(name)); - } - - return map; - } - - static List getArgumentList(String inputString) { - List list = new ArrayList<>(); - if (inputString == null || inputString.isEmpty()) { - return list; - } - - // The "pattern" regexp attempts to abide to the rule that - // strings are delimited by whitespace unless surrounded by - // quotes, then it is anything (including spaces) in the quotes. - Matcher m = pattern.matcher(inputString); - while (m.find()) { - String s = inputString.substring(m.start(), m.end()).trim(); - // Ensure we do not have an empty string. trim() will take care of - // whitespace only strings. The regex preserves quotes and escaped - // chars so we need to clean them before adding to the List - if (!s.isEmpty()) { - list.add(unquoteIfNeeded(s)); - } - } - return list; - } - - static void putUnlessNull(Map params, - String param, Object value) { - if (value != null) { - params.put(param, value); - } - } - - private static String unquoteIfNeeded(String in) { - if (in == null) { - return null; - } - - if (in.isEmpty()) { - return ""; - } - - // Use code points to preserve non-ASCII chars - StringBuilder sb = new StringBuilder(); - int codeLen = in.codePointCount(0, in.length()); - int quoteChar = -1; - for (int i = 0; i < codeLen; i++) { - int code = in.codePointAt(i); - if (code == '"' || code == '\'') { - // If quote is escaped make sure to copy it - if (i > 0 && in.codePointAt(i - 1) == '\\') { - sb.deleteCharAt(sb.length() - 1); - sb.appendCodePoint(code); - continue; - } - if (quoteChar != -1) { - if (code == quoteChar) { - // close quote, skip char - quoteChar = -1; - } else { - sb.appendCodePoint(code); - } - } else { - // opening quote, skip char - quoteChar = code; - } - } else { - sb.appendCodePoint(code); - } - } - return sb.toString(); - } -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BasicBundlers.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BasicBundlers.java deleted file mode 100644 index 7f444fe7337..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BasicBundlers.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) 2014, 2019, 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. 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.jpackage.internal; - -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.ServiceLoader; -import java.util.concurrent.CopyOnWriteArrayList; - -/** - * BasicBundlers - * - * A basic bundlers collection that loads the default bundlers. - * Loads the common bundlers. - *

      - *
    • Windows file image
    • - *
    • Mac .app
    • - *
    • Linux file image
    • - *
    • Windows MSI
    • - *
    • Windows EXE
    • - *
    • Mac DMG
    • - *
    • Mac PKG
    • - *
    • Linux DEB
    • - *
    • Linux RPM
    • - * - *
    - */ -public class BasicBundlers implements Bundlers { - - boolean defaultsLoaded = false; - - private final Collection bundlers = new CopyOnWriteArrayList<>(); - - @Override - public Collection getBundlers() { - return Collections.unmodifiableCollection(bundlers); - } - - @Override - public Collection getBundlers(String type) { - if (type == null) return Collections.emptySet(); - switch (type) { - case "NONE": - return Collections.emptySet(); - case "ALL": - return getBundlers(); - default: - return Arrays.asList(getBundlers().stream() - .filter(b -> type.equalsIgnoreCase(b.getBundleType())) - .toArray(Bundler[]::new)); - } - } - - // Loads bundlers from the META-INF/services direct - @Override - public void loadBundlersFromServices(ClassLoader cl) { - ServiceLoader loader = ServiceLoader.load(Bundler.class, cl); - for (Bundler aLoader : loader) { - bundlers.add(aLoader); - } - } -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BuildEnvFromOptions.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BuildEnvFromOptions.java new file mode 100644 index 00000000000..8faabe97a3d --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BuildEnvFromOptions.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2025, 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. 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.jpackage.internal; + +import static jdk.jpackage.internal.cli.StandardOption.PREDEFINED_APP_IMAGE; +import static jdk.jpackage.internal.cli.StandardOption.PREDEFINED_RUNTIME_IMAGE; +import static jdk.jpackage.internal.cli.StandardOption.RESOURCE_DIR; +import static jdk.jpackage.internal.cli.StandardOption.TEMP_ROOT; +import static jdk.jpackage.internal.cli.StandardOption.VERBOSE; + +import java.nio.file.Path; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; +import jdk.jpackage.internal.cli.Options; +import jdk.jpackage.internal.model.Application; +import jdk.jpackage.internal.model.ApplicationLayout; +import jdk.jpackage.internal.model.Package; +import jdk.jpackage.internal.model.RuntimeLayout; + +final class BuildEnvFromOptions { + + BuildEnvFromOptions() { + predefinedRuntimeImageLayout(RuntimeLayout.DEFAULT); + } + + BuildEnvFromOptions predefinedAppImageLayout(Function v) { + predefinedAppImageLayout = v; + return this; + } + + BuildEnvFromOptions predefinedAppImageLayout(ApplicationLayout v) { + return predefinedAppImageLayout(path -> v.resolveAt(path)); + } + + BuildEnvFromOptions predefinedRuntimeImageLayout(Function v) { + predefinedRuntimeImageLayout = v; + return this; + } + + BuildEnvFromOptions predefinedRuntimeImageLayout(RuntimeLayout v) { + return predefinedRuntimeImageLayout(path -> v.resolveAt(path)); + } + + BuildEnv create(Options options, Application app) { + return create(options, app, Optional.empty()); + } + + BuildEnv create(Options options, Package pkg) { + return create(options, pkg.app(), Optional.of(pkg)); + } + + private BuildEnv create(Options options, Application app, Optional pkg) { + Objects.requireNonNull(options); + Objects.requireNonNull(app); + Objects.requireNonNull(pkg); + Objects.requireNonNull(predefinedAppImageLayout); + Objects.requireNonNull(predefinedRuntimeImageLayout); + + final var builder = new BuildEnvBuilder(TEMP_ROOT.getFrom(options)); + + RESOURCE_DIR.ifPresentIn(options, builder::resourceDir); + VERBOSE.ifPresentIn(options, builder::verbose); + + if (app.isRuntime()) { + var path = PREDEFINED_RUNTIME_IMAGE.getFrom(options); + builder.appImageLayout(predefinedRuntimeImageLayout.apply(path)); + } else if (PREDEFINED_APP_IMAGE.containsIn(options)) { + var path = PREDEFINED_APP_IMAGE.getFrom(options); + builder.appImageLayout(predefinedAppImageLayout.apply(path)); + } else { + pkg.ifPresentOrElse(builder::appImageDirFor, () -> { + builder.appImageDirFor(app); + }); + } + + return builder.create(); + } + + private Function predefinedAppImageLayout; + private Function predefinedRuntimeImageLayout; +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BuildEnvFromParams.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BuildEnvFromParams.java deleted file mode 100644 index 6fb1a342fbf..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BuildEnvFromParams.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (c) 2025, 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. 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.jpackage.internal; - -import static jdk.jpackage.internal.ApplicationLayoutUtils.PLATFORM_APPLICATION_LAYOUT; -import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE; -import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_RUNTIME_IMAGE; -import static jdk.jpackage.internal.StandardBundlerParam.RESOURCE_DIR; -import static jdk.jpackage.internal.StandardBundlerParam.TEMP_ROOT; -import static jdk.jpackage.internal.StandardBundlerParam.VERBOSE; - -import java.nio.file.Path; -import java.util.Map; -import java.util.function.Function; -import jdk.jpackage.internal.model.ApplicationLayout; -import jdk.jpackage.internal.model.ConfigException; -import jdk.jpackage.internal.model.RuntimeLayout; - -final class BuildEnvFromParams { - - static BuildEnv create(Map params, - Function predefinedAppImageLayoutProvider, - Function predefinedRuntimeImageLayoutProvider) throws ConfigException { - - final var builder = new BuildEnvBuilder(TEMP_ROOT.fetchFrom(params)); - - RESOURCE_DIR.copyInto(params, builder::resourceDir); - VERBOSE.copyInto(params, builder::verbose); - - final var app = FromParams.APPLICATION.findIn(params).orElseThrow(); - - final var pkg = FromParams.getCurrentPackage(params); - - if (app.isRuntime()) { - var layout = predefinedRuntimeImageLayoutProvider.apply(PREDEFINED_RUNTIME_IMAGE.findIn(params).orElseThrow()); - builder.appImageLayout(layout); - } else if (StandardBundlerParam.hasPredefinedAppImage(params)) { - var layout = predefinedAppImageLayoutProvider.apply(PREDEFINED_APP_IMAGE.findIn(params).orElseThrow()); - builder.appImageLayout(layout); - } else if (pkg.isPresent()) { - builder.appImageDirFor(pkg.orElseThrow()); - } else { - builder.appImageDirFor(app); - } - - return builder.create(); - } - - static final BundlerParamInfo BUILD_ENV = BundlerParamInfo.createBundlerParam(BuildEnv.class, params -> { - return create(params, PLATFORM_APPLICATION_LAYOUT::resolveAt, RuntimeLayout.DEFAULT::resolveAt); - }); -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BundleParams.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BundleParams.java deleted file mode 100644 index 937a3655e8b..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BundleParams.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2012, 2022, 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. 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.jpackage.internal; - -import java.util.HashMap; -import java.util.Map; -import static jdk.jpackage.internal.StandardBundlerParam.APP_NAME; - -public class BundleParams { - - protected final Map params; - - /** - * create a new bundle with all default values - */ - public BundleParams() { - params = new HashMap<>(); - } - - /** - * Create a bundle params with a copy of the params - * @param params map of initial parameters to be copied in. - */ - public BundleParams(Map params) { - this.params = new HashMap<>(params); - } - - public void addAllBundleParams(Map params) { - this.params.putAll(params); - } - - // NOTE: we do not care about application parameters here - // as they will be embedded into jar file manifest and - // java launcher will take care of them! - - public Map getBundleParamsAsMap() { - return new HashMap<>(params); - } - - public String getName() { - return APP_NAME.fetchFrom(params); - } - - private void putUnlessNull(String param, Object value) { - if (value != null) { - params.put(param, value); - } - } -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Bundler.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Bundler.java deleted file mode 100644 index 0d29677e826..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Bundler.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (c) 2014, 2025, 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. 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.jpackage.internal; - -import java.nio.file.Path; -import java.util.Map; -import jdk.jpackage.internal.model.ConfigException; -import jdk.jpackage.internal.model.PackagerException; - -/** - * Bundler - * - * The basic interface implemented by all Bundlers. - */ -public interface Bundler { - /** - * @return User Friendly name of this bundler. - */ - String getName(); - - /** - * @return Command line identifier of the bundler. Should be unique. - */ - String getID(); - - /** - * @return The bundle type of the bundle that is created by this bundler. - */ - String getBundleType(); - - /** - * Determines if this bundler will execute with the given parameters. - * - * @param params The parameters to be validate. Validation may modify - * the map, so if you are going to be using the same map - * across multiple bundlers you should pass in a deep copy. - * @return true if valid - * @throws ConfigException If the configuration params are incorrect. The - * exception may contain advice on how to modify the params map - * to make it valid. - */ - public boolean validate(Map params) - throws ConfigException; - - /** - * Creates a bundle from existing content. - * - * If a call to {@link #validate(java.util.Map)} date} returns true with - * the parameters map, then you can expect a valid output. - * However if an exception was thrown out of validate or it returned - * false then you should not expect sensible results from this call. - * It may or may not return a value, and it may or may not throw an - * exception. But any output should not be considered valid or sane. - * - * @param params The Bundle parameters, - * Keyed by the id from the ParamInfo. Execution may - * modify the map, so if you are going to be using the - * same map across multiple bundlers you should pass - * in a deep copy. - * @param outputParentDir - * The parent dir that the returned bundle will be placed in. - * @return The resulting bundled file - * - * For a bundler that produces a single artifact file this will be the - * location of that artifact (.exe file, .deb file, etc) - * - * For a bundler that produces a specific directory format output this will - * be the location of that specific directory (.app file, etc). - * - * For a bundler that produce multiple files, this will be a parent - * directory of those files (linux and windows images), whose name is not - * relevant to the result. - * - * @throws java.lang.IllegalArgumentException for any of the following - * reasons: - *
      - *
    • A required parameter is not found in the params list, for - * example missing the main class.
    • - *
    • A parameter has the wrong type of an object, for example a - * String where a File is required
    • - *
    • Bundler specific incompatibilities with the parameters, for - * example a bad version number format or an application id with - * forward slashes.
    • - *
    - */ - public Path execute(Map params, - Path outputParentDir) throws PackagerException; - - /** - * Removes temporary files that are used for bundling. - */ - public void cleanup(Map params); - - /** - * Returns "true" if this bundler is supported on current platform. - */ - public boolean supported(boolean runtimeInstaller); - - /** - * Returns "true" if this bundler is he default for the current platform. - */ - public boolean isDefault(); -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BundlerParamInfo.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BundlerParamInfo.java deleted file mode 100644 index 81030b18f6d..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BundlerParamInfo.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright (c) 2014, 2025, 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. 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.jpackage.internal; - -import java.nio.file.Path; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.function.BiFunction; -import java.util.function.Consumer; -import java.util.function.Function; -import jdk.jpackage.internal.util.function.ThrowingFunction; - -/** - * BundlerParamInfo - * - * A BundlerParamInfo encapsulates an individual bundler parameter of type . - * - * @param id The command line and hashmap name of the parameter - * - * @param valueType Type of the parameter - * - * @param defaultValueFunction If the value is not set, and no fallback value is found, the - * parameter uses the value returned by the producer. - * - * @param stringConverter An optional string converter for command line arguments. - */ -record BundlerParamInfo(String id, Class valueType, - Function, T> defaultValueFunction, - BiFunction, T> stringConverter) { - - BundlerParamInfo { - Objects.requireNonNull(id); - Objects.requireNonNull(valueType); - } - - static BundlerParamInfo createStringBundlerParam(String id) { - return new BundlerParamInfo<>(id, String.class, null, null); - } - - static BundlerParamInfo createBooleanBundlerParam(String id) { - return new BundlerParamInfo<>(id, Boolean.class, null, BundlerParamInfo::toBoolean); - } - - static BundlerParamInfo createPathBundlerParam(String id) { - return new BundlerParamInfo<>(id, Path.class, null, BundlerParamInfo::toPath); - } - - @SuppressWarnings({"unchecked", "rawtypes"}) - static BundlerParamInfo createBundlerParam(String id, Class valueType, - ThrowingFunction, U> valueCtor) { - return new BundlerParamInfo(id, valueType, ThrowingFunction.toFunction(valueCtor), null); - } - - static BundlerParamInfo createBundlerParam(Class valueType, - ThrowingFunction, U> valueCtor) { - return createBundlerParam(valueType.getName(), valueType, valueCtor); - } - - static boolean toBoolean(String value, Map params) { - if (value == null || "null".equalsIgnoreCase(value)) { - return false; - } else { - return Boolean.valueOf(value); - } - } - - static Path toPath(String value, Map params) { - return Path.of(value); - } - - String getID() { - return id; - } - - Class getValueType() { - return valueType; - } - - /** - * Returns true if value was not provided on command line for this parameter. - * - * @param params - params from which value will be fetch - * @return true if value was not provided on command line, false otherwise - */ - boolean getIsDefaultValue(Map params) { - Object o = params.get(getID()); - if (o != null) { - return false; // We have user provided value - } - - if (params.containsKey(getID())) { - return false; // explicit nulls are allowed for provided value - } - - return true; - } - - Function, T> getDefaultValueFunction() { - return defaultValueFunction; - } - - BiFunction, T> getStringConverter() { - return stringConverter; - } - - final T fetchFrom(Map params) { - return fetchFrom(params, true); - } - - @SuppressWarnings("unchecked") - final T fetchFrom(Map params, - boolean invokeDefault) { - Object o = params.get(getID()); - if (o instanceof String && getStringConverter() != null) { - return getStringConverter().apply((String) o, params); - } - - Class klass = getValueType(); - if (klass.isInstance(o)) { - return (T) o; - } - if (o != null) { - throw new IllegalArgumentException("Param " + getID() - + " should be of type " + getValueType() - + " but is a " + o.getClass()); - } - if (params.containsKey(getID())) { - // explicit nulls are allowed - return null; - } - - if (invokeDefault && (getDefaultValueFunction() != null)) { - T result = getDefaultValueFunction().apply(params); - if (result != null) { - params.put(getID(), result); - } - return result; - } - - // ultimate fallback - return null; - } - - Optional findIn(Map params) { - if (params.containsKey(getID())) { - return Optional.of(fetchFrom(params, true)); - } else { - return Optional.empty(); - } - } - - void copyInto(Map params, Consumer consumer) { - findIn(params).ifPresent(consumer); - } -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Bundlers.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Bundlers.java deleted file mode 100644 index 955952d563d..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Bundlers.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (c) 2014, 2019, 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. 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.jpackage.internal; - -import java.util.Collection; -import java.util.Iterator; -import java.util.ServiceLoader; - -/** - * Bundlers - * - * The interface implemented by BasicBundlers - */ -public interface Bundlers { - - /** - * This convenience method will call - * {@link #createBundlersInstance(ClassLoader)} - * with the classloader that this Bundlers is loaded from. - * - * @return an instance of Bundlers loaded and configured from - * the current ClassLoader. - */ - public static Bundlers createBundlersInstance() { - return createBundlersInstance(Bundlers.class.getClassLoader()); - } - - /** - * This convenience method will automatically load a Bundlers instance - * from either META-INF/services or the default - * {@link BasicBundlers} if none are found in - * the services meta-inf. - * - * After instantiating the bundlers instance it will load the default - * bundlers via {@link #loadDefaultBundlers()} as well as requesting - * the services loader to load any other bundelrs via - * {@link #loadBundlersFromServices(ClassLoader)}. - - * - * @param servicesClassLoader the classloader to search for - * META-INF/service registered bundlers - * @return an instance of Bundlers loaded and configured from - * the specified ClassLoader - */ - public static Bundlers createBundlersInstance( - ClassLoader servicesClassLoader) { - ServiceLoader bundlersLoader = - ServiceLoader.load(Bundlers.class, servicesClassLoader); - Bundlers bundlers = null; - Iterator iter = bundlersLoader.iterator(); - if (iter.hasNext()) { - bundlers = iter.next(); - } - if (bundlers == null) { - bundlers = new BasicBundlers(); - } - - bundlers.loadBundlersFromServices(servicesClassLoader); - return bundlers; - } - - /** - * Returns all of the preconfigured, requested, and manually - * configured bundlers loaded with this instance. - * - * @return a read-only collection of the requested bundlers - */ - Collection getBundlers(); - - /** - * Returns all of the preconfigured, requested, and manually - * configured bundlers loaded with this instance that are of - * a specific BundleType, such as disk images, installers, or - * remote installers. - * - * @return a read-only collection of the requested bundlers - */ - Collection getBundlers(String type); - - /** - * Loads bundlers from the META-INF/services directly. - * - * This method is called from the - * {@link #createBundlersInstance(ClassLoader)} - * and {@link #createBundlersInstance()} methods. - */ - void loadBundlersFromServices(ClassLoader cl); - -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/CLIHelp.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/CLIHelp.java deleted file mode 100644 index 7790ebb3ebb..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/CLIHelp.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (c) 2018, 2025, 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. 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.jpackage.internal; - -import jdk.internal.util.OperatingSystem; - -import java.util.ResourceBundle; -import java.io.File; -import java.text.MessageFormat; - - -/** - * CLIHelp - * - * Generate and show the command line interface help message(s). - */ -public class CLIHelp { - - private static final ResourceBundle I18N = ResourceBundle.getBundle( - "jdk.jpackage.internal.resources.HelpResources"); - - // generates --help for jpackage's CLI - public static void showHelp(boolean noArgs) { - - if (noArgs) { - Log.info(I18N.getString("MSG_Help_no_args")); - } else { - OperatingSystem platform = OperatingSystem.current(); - String types; - String pLaunchOptions; - String pInstallOptions; - String pInstallDir; - String pAppImageDescription; - String pSignSampleUsage; - String pAppContentNote; - switch (platform) { - case MACOS: - types = "{\"app-image\", \"dmg\", \"pkg\"}"; - pLaunchOptions = I18N.getString("MSG_Help_mac_launcher"); - pInstallOptions = I18N.getString("MSG_Help_mac_install"); - pInstallDir - = I18N.getString("MSG_Help_mac_linux_install_dir"); - pAppImageDescription - = I18N.getString("MSG_Help_mac_app_image"); - pSignSampleUsage - = I18N.getString("MSG_Help_mac_sign_sample_usage"); - pAppContentNote - = I18N.getString("MSG_Help_mac_app_content_note"); - break; - case LINUX: - types = "{\"app-image\", \"rpm\", \"deb\"}"; - pLaunchOptions = ""; - pInstallOptions = I18N.getString("MSG_Help_linux_install"); - pInstallDir - = I18N.getString("MSG_Help_mac_linux_install_dir"); - pAppImageDescription - = I18N.getString("MSG_Help_default_app_image"); - pSignSampleUsage = ""; - pAppContentNote = ""; - break; - case WINDOWS: - types = "{\"app-image\", \"exe\", \"msi\"}"; - pLaunchOptions = I18N.getString("MSG_Help_win_launcher"); - pInstallOptions = I18N.getString("MSG_Help_win_install"); - pInstallDir - = I18N.getString("MSG_Help_win_install_dir"); - pAppImageDescription - = I18N.getString("MSG_Help_default_app_image"); - pSignSampleUsage = ""; - pAppContentNote = ""; - break; - default: - types = "{\"app-image\", \"exe\", \"msi\", \"rpm\", \"deb\", \"pkg\", \"dmg\"}"; - pLaunchOptions = I18N.getString("MSG_Help_win_launcher") - + I18N.getString("MSG_Help_mac_launcher"); - pInstallOptions = I18N.getString("MSG_Help_win_install") - + I18N.getString("MSG_Help_linux_install") - + I18N.getString("MSG_Help_mac_install"); - pInstallDir - = I18N.getString("MSG_Help_default_install_dir"); - pAppImageDescription - = I18N.getString("MSG_Help_default_app_image"); - pSignSampleUsage = ""; - pAppContentNote = ""; - break; - } - Log.info(MessageFormat.format(I18N.getString("MSG_Help"), - File.pathSeparator, types, pLaunchOptions, - pInstallOptions, pInstallDir, pAppImageDescription, - pSignSampleUsage, pAppContentNote)); - } - } -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/CfgFile.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/CfgFile.java index 6b4ba3c4410..9cb9fb5cba0 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/CfgFile.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/CfgFile.java @@ -30,7 +30,7 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Optional; +import java.util.Objects; import java.util.stream.Stream; import jdk.jpackage.internal.model.Application; import jdk.jpackage.internal.model.ApplicationLayout; @@ -47,10 +47,14 @@ final class CfgFile { CfgFile(Application app, Launcher launcher) { startupInfo = launcher.startupInfo().orElseThrow(); outputFileName = launcher.executableName() + ".cfg"; - version = app.version(); + version = Objects.requireNonNull(app.version()); } void create(ApplicationLayout appLayout) throws IOException { + Objects.requireNonNull(appLayout); + + Objects.requireNonNull(startupInfo.qualifiedClassName()); + List> content = new ArrayList<>(); final var refs = new Referencies(appLayout); @@ -58,7 +62,7 @@ final class CfgFile { content.add(Map.entry("[Application]", SECTION_TAG)); if (startupInfo instanceof LauncherModularStartupInfo modularStartupInfo) { - content.add(Map.entry("app.mainmodule", modularStartupInfo.moduleName() + content.add(Map.entry("app.mainmodule", Objects.requireNonNull(modularStartupInfo.moduleName()) + "/" + startupInfo.qualifiedClassName())); } else if (startupInfo instanceof LauncherJarStartupInfo jarStartupInfo) { Path mainJarPath = refs.appDirectory().resolve(jarStartupInfo.jarPath()); @@ -67,16 +71,13 @@ final class CfgFile { content.add(Map.entry("app.mainjar", mainJarPath)); } else { content.add(Map.entry("app.classpath", mainJarPath)); - } - - if (!jarStartupInfo.isJarWithMainClass()) { content.add(Map.entry("app.mainclass", startupInfo.qualifiedClassName())); } } else { throw new UnsupportedOperationException(); } - for (var value : Optional.ofNullable(startupInfo.classPath()).orElseGet(List::of)) { + for (var value : startupInfo.classPath()) { content.add(Map.entry("app.classpath", refs.appDirectory().resolve(value).toString())); } @@ -88,7 +89,7 @@ final class CfgFile { "java-options", "-Djpackage.app-version=" + version)); // add user supplied java options if there are any - for (var value : Optional.ofNullable(startupInfo.javaOptions()).orElseGet(List::of)) { + for (var value : startupInfo.javaOptions()) { content.add(Map.entry("java-options", value)); } @@ -98,7 +99,7 @@ final class CfgFile { content.add(Map.entry("java-options", refs.appModsDirectory())); } - var arguments = Optional.ofNullable(startupInfo.defaultParameters()).orElseGet(List::of); + var arguments = startupInfo.defaultParameters(); if (!arguments.isEmpty()) { content.add(Map.entry("[ArgOptions]", SECTION_TAG)); for (var value : arguments) { diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/DefaultBundlingEnvironment.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/DefaultBundlingEnvironment.java new file mode 100644 index 00000000000..0bf7d6f41b2 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/DefaultBundlingEnvironment.java @@ -0,0 +1,283 @@ +/* + * Copyright (c) 2025, 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. 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.jpackage.internal; + +import static java.util.stream.Collectors.toMap; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; +import jdk.internal.util.OperatingSystem; +import jdk.jpackage.internal.cli.CliBundlingEnvironment; +import jdk.jpackage.internal.cli.Options; +import jdk.jpackage.internal.cli.StandardBundlingOperation; +import jdk.jpackage.internal.model.AppImagePackageType; +import jdk.jpackage.internal.model.Application; +import jdk.jpackage.internal.model.BundlingOperationDescriptor; +import jdk.jpackage.internal.model.JPackageException; +import jdk.jpackage.internal.model.Package; +import jdk.jpackage.internal.model.PackageType; +import jdk.jpackage.internal.model.StandardPackageType; +import jdk.jpackage.internal.util.Result; + +class DefaultBundlingEnvironment implements CliBundlingEnvironment { + + DefaultBundlingEnvironment(Builder builder) { + this(Optional.ofNullable(builder.defaultOperationSupplier), builder.bundlers); + } + + DefaultBundlingEnvironment(Optional>> defaultOperationSupplier, + Map>>> bundlers) { + + this.bundlers = bundlers.entrySet().stream().collect(toMap(Map.Entry::getKey, e -> { + return new CachingSupplier<>(e.getValue()); + })); + + this.defaultOperationSupplier = Objects.requireNonNull(defaultOperationSupplier).map(CachingSupplier::new); + } + + + static final class Builder { + + Builder defaultOperation(Supplier> v) { + defaultOperationSupplier = v; + return this; + } + + Builder defaultOperation(StandardBundlingOperation v) { + return defaultOperation(() -> Optional.of(v.descriptor())); + } + + Builder bundler(StandardBundlingOperation op, Supplier>> bundlerSupplier) { + bundlers.put(Objects.requireNonNull(op.descriptor()), Objects.requireNonNull(bundlerSupplier)); + return this; + } + + Builder bundler(StandardBundlingOperation op, + Supplier> sysEnvResultSupplier, BiConsumer bundler) { + return bundler(op, createBundlerSupplier(sysEnvResultSupplier, bundler)); + } + + Builder bundler(StandardBundlingOperation op, Consumer bundler) { + Objects.requireNonNull(bundler); + return bundler(op, () -> Result.ofValue(bundler)); + } + + private Supplier> defaultOperationSupplier; + private final Map>>> bundlers = new HashMap<>(); + } + + + static Builder build() { + return new Builder(); + } + + static Supplier>> createBundlerSupplier( + Supplier> sysEnvResultSupplier, BiConsumer bundler) { + Objects.requireNonNull(sysEnvResultSupplier); + Objects.requireNonNull(bundler); + return () -> { + return sysEnvResultSupplier.get().map(sysEnv -> { + return options -> { + bundler.accept(options, sysEnv); + }; + }); + }; + } + + static void createApplicationImage(Options options, Application app, PackagingPipeline.Builder pipelineBuilder) { + Objects.requireNonNull(options); + Objects.requireNonNull(app); + Objects.requireNonNull(pipelineBuilder); + + final var outputDir = OptionUtils.outputDir(options).resolve(app.appImageDirName()); + + IOUtils.writableOutputDir(outputDir.getParent()); + + final var env = new BuildEnvFromOptions() + .predefinedAppImageLayout(app.asApplicationLayout().orElseThrow()) + .create(options, app); + + Log.verbose(I18N.format("message.creating-app-bundle", outputDir.getFileName(), outputDir.toAbsolutePath().getParent())); + + if (Files.exists(outputDir)) { + throw new JPackageException(I18N.format("error.root-exists", outputDir.toAbsolutePath())); + } + + pipelineBuilder.excludeDirFromCopying(outputDir.getParent()) + .create().execute(BuildEnv.withAppImageDir(env, outputDir), app); + } + + static void createNativePackage(Options options, + Function createPackage, + BiFunction createBuildEnv, + PackagingPipeline.Builder pipelineBuilder, + Packager.PipelineBuilderMutatorFactory pipelineBuilderMutatorFactory) { + + Objects.requireNonNull(pipelineBuilder); + createNativePackage(options, createPackage, createBuildEnv, _ -> pipelineBuilder, pipelineBuilderMutatorFactory); + } + + static void createNativePackage(Options options, + Function createPackage, + BiFunction createBuildEnv, + Function createPipelineBuilder, + Packager.PipelineBuilderMutatorFactory pipelineBuilderMutatorFactory) { + + Objects.requireNonNull(options); + Objects.requireNonNull(createPackage); + Objects.requireNonNull(createBuildEnv); + Objects.requireNonNull(createPipelineBuilder); + Objects.requireNonNull(pipelineBuilderMutatorFactory); + + var pkg = Objects.requireNonNull(createPackage.apply(options)); + + Packager.build().pkg(pkg) + .outputDir(OptionUtils.outputDir(options)) + .env(Objects.requireNonNull(createBuildEnv.apply(options, pkg))) + .pipelineBuilderMutatorFactory(pipelineBuilderMutatorFactory) + .execute(Objects.requireNonNull(createPipelineBuilder.apply(pkg))); + } + + @Override + public Optional defaultOperation() { + return defaultOperationSupplier.flatMap(Supplier::get); + } + + @Override + public void createBundle(BundlingOperationDescriptor op, Options cmdline) { + final var bundler = getBundlerSupplier(op).get().orElseThrow(); + Optional permanentWorkDirectory = Optional.empty(); + try (var tempDir = new TempDirectory(cmdline)) { + if (!tempDir.deleteOnClose()) { + permanentWorkDirectory = Optional.of(tempDir.path()); + } + bundler.accept(tempDir.options()); + + var packageType = OptionUtils.bundlingOperation(cmdline).packageType(); + + Log.verbose(I18N.format("message.bundle-created", I18N.getString(bundleTypeDescription(packageType, op.os())))); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } finally { + permanentWorkDirectory.ifPresent(workDir -> { + Log.verbose(I18N.format("message.debug-working-directory", workDir.toAbsolutePath())); + }); + } + } + + @Override + public Collection configurationErrors(BundlingOperationDescriptor op) { + return getBundlerSupplier(op).get().errors(); + } + + private Supplier>> getBundlerSupplier(BundlingOperationDescriptor op) { + return Optional.ofNullable(bundlers.get(op)).orElseThrow(NoSuchElementException::new); + } + + private String bundleTypeDescription(PackageType type, OperatingSystem os) { + switch (type) { + case StandardPackageType stdType -> { + switch (stdType) { + case WIN_MSI -> { + return "bundle-type.win-msi"; + } + case WIN_EXE -> { + return "bundle-type.win-exe"; + } + case LINUX_DEB -> { + return "bundle-type.linux-deb"; + } + case LINUX_RPM -> { + return "bundle-type.linux-rpm"; + } + case MAC_DMG -> { + return "bundle-type.mac-dmg"; + } + case MAC_PKG -> { + return "bundle-type.mac-pkg"; + } + default -> { + throw new AssertionError(); + } + } + } + case AppImagePackageType appImageType -> { + switch (os) { + case WINDOWS -> { + return "bundle-type.win-app"; + } + case LINUX -> { + return "bundle-type.linux-app"; + } + case MACOS -> { + return "bundle-type.mac-app"; + } + default -> { + throw new AssertionError(); + } + } + } + default -> { + throw new AssertionError(); + } + } + } + + + private static final class CachingSupplier implements Supplier { + + CachingSupplier(Supplier getter) { + this.getter = Objects.requireNonNull(getter); + } + + @Override + public T get() { + return cachedValue.updateAndGet(v -> { + return Optional.ofNullable(v).orElseGet(getter); + }); + } + + private final Supplier getter; + private final AtomicReference cachedValue = new AtomicReference<>(); + } + + + private final Map>>> bundlers; + private final Optional>> defaultOperationSupplier; +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/DeployParams.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/DeployParams.java deleted file mode 100644 index d7b4052d34a..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/DeployParams.java +++ /dev/null @@ -1,362 +0,0 @@ -/* - * Copyright (c) 2011, 2025, 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. 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.jpackage.internal; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.InvalidPathException; -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; -import java.util.stream.Stream; -import jdk.jpackage.internal.model.PackagerException; - -/** - * DeployParams - * - * This class is generated and used in Arguments.processArguments() as - * intermediate step in generating the BundleParams and ultimately the Bundles - */ -public class DeployParams { - - String targetFormat = null; // means default type for this platform - - // raw arguments to the bundler - Map bundlerArguments = new LinkedHashMap<>(); - - static class Template { - Path in; - Path out; - - Template(Path in, Path out) { - this.in = in; - this.out = out; - } - } - - // we need to expand as in some cases - // (most notably jpackage) - // we may get "." as filename and assumption is we include - // everything in the given folder - // (IOUtils.copyfiles() have recursive behavior) - List expandFileset(Path root) throws IOException { - List files = new LinkedList<>(); - if (!Files.isSymbolicLink(root)) { - if (Files.isDirectory(root)) { - try (Stream stream = Files.list(root)) { - List children = stream.toList(); - if (children != null && children.size() > 0) { - children.forEach(f -> { - try { - files.addAll(expandFileset(f)); - } catch (IOException ex) { - throw new RuntimeException(ex); - } - }); - } else { - // Include empty folders - files.add(root); - } - } - } else { - files.add(root); - } - } - return files; - } - - static void validateName(String s, boolean forApp) - throws PackagerException { - - String exceptionKey = forApp ? - "ERR_InvalidAppName" : "ERR_InvalidSLName"; - - if (s == null) { - if (forApp) { - return; - } else { - throw new PackagerException(exceptionKey); - } - } - if (s.length() == 0 || s.charAt(s.length() - 1) == '\\') { - throw new PackagerException(exceptionKey, s); - } - try { - // name must be valid path element for this file system - Path p = Path.of(s); - // and it must be a single name element in a path - if (p.getNameCount() != 1) { - throw new PackagerException(exceptionKey, s); - } - } catch (InvalidPathException ipe) { - throw new PackagerException(ipe, exceptionKey, s); - } - - for (int i = 0; i < s.length(); i++) { - char a = s.charAt(i); - // We check for ASCII codes first which we accept. If check fails, - // check if it is acceptable extended ASCII or unicode character. - if (a < ' ' || a > '~') { - // Accept anything else including special chars like copyright - // symbols. Note: space will be included by ASCII check above, - // but other whitespace like tabs or new line will be rejected. - if (Character.isISOControl(a) || - Character.isWhitespace(a)) { - throw new PackagerException(exceptionKey, s); - } - } else if (a == '"' || a == '%') { - throw new PackagerException(exceptionKey, s); - } - } - } - - @SuppressWarnings("unchecked") - public void validate() throws PackagerException { - boolean hasModule = (bundlerArguments.get( - Arguments.CLIOptions.MODULE.getId()) != null); - boolean hasAppImage = (bundlerArguments.get( - Arguments.CLIOptions.PREDEFINED_APP_IMAGE.getId()) != null); - boolean hasMain = (bundlerArguments.get( - Arguments.CLIOptions.MAIN_JAR.getId()) != null); - boolean hasRuntimeImage = (bundlerArguments.get( - Arguments.CLIOptions.PREDEFINED_RUNTIME_IMAGE.getId()) != null); - boolean hasInput = (bundlerArguments.get( - Arguments.CLIOptions.INPUT.getId()) != null); - boolean hasModulePath = (bundlerArguments.get( - Arguments.CLIOptions.MODULE_PATH.getId()) != null); - boolean hasMacAppStore = (bundlerArguments.get( - Arguments.CLIOptions.MAC_APP_STORE.getId()) != null); - boolean runtimeInstaller = !isTargetAppImage() && - !hasAppImage && !hasModule && !hasMain && hasRuntimeImage; - - if (isTargetAppImage()) { - // Module application requires --runtime-image or --module-path - if (hasModule) { - if (!hasModulePath && !hasRuntimeImage && !hasAppImage) { - throw new PackagerException("ERR_MissingArgument", - "--runtime-image or --module-path"); - } - } else { - if (!hasInput && !hasAppImage) { - throw new PackagerException("error.no-input-parameter"); - } - } - } else { - if (!runtimeInstaller) { - if (hasModule) { - if (!hasModulePath && !hasRuntimeImage && !hasAppImage) { - throw new PackagerException("ERR_MissingArgument", - "--runtime-image, --module-path or --app-image"); - } - } else { - if (!hasInput && !hasAppImage) { - throw new PackagerException("ERR_MissingArgument", - "--input or --app-image"); - } - } - } - } - - // if bundling non-modular image, or installer without app-image - // then we need some resources and a main class - if (!hasModule && !hasAppImage && !runtimeInstaller && !hasMain) { - throw new PackagerException("ERR_MissingArgument", "--main-jar"); - } - - String name = (String)bundlerArguments.get( - Arguments.CLIOptions.NAME.getId()); - validateName(name, true); - - // Validate app image if set - String appImage = (String)bundlerArguments.get( - Arguments.CLIOptions.PREDEFINED_APP_IMAGE.getId()); - if (appImage != null) { - Path appImageDir = Path.of(appImage); - if (!Files.exists(appImageDir) - || appImageDir.toFile().list() == null - || appImageDir.toFile().list().length == 0) { - throw new PackagerException("ERR_AppImageNotExist", appImage); - } - } - - // Validate temp dir - String root = (String)bundlerArguments.get( - Arguments.CLIOptions.TEMP_ROOT.getId()); - if (root != null && Files.exists(Path.of(root))) { - try (Stream stream = Files.walk(Path.of(root), 1)) { - Path [] contents = stream.toArray(Path[]::new); - // contents.length > 1 because Files.walk(path) includes path - if (contents != null && contents.length > 1) { - throw new PackagerException( - "ERR_BuildRootInvalid", root); - } - } catch (IOException ioe) { - throw new PackagerException(ioe); - } - } - - // Validate resource dir - String resources = (String)bundlerArguments.get( - Arguments.CLIOptions.RESOURCE_DIR.getId()); - if (resources != null) { - if (!(Files.exists(Path.of(resources)))) { - throw new PackagerException( - "message.resource-dir-does-not-exist", - Arguments.CLIOptions.RESOURCE_DIR.getId(), resources); - } - } - - // Validate predefined runtime dir - String runtime = (String)bundlerArguments.get( - Arguments.CLIOptions.PREDEFINED_RUNTIME_IMAGE.getId()); - if (runtime != null) { - if (!(Files.exists(Path.of(runtime)))) { - throw new PackagerException( - "message.runtime-image-dir-does-not-exist", - Arguments.CLIOptions.PREDEFINED_RUNTIME_IMAGE.getId(), - runtime); - } - } - - - // Validate license file if set - String license = (String)bundlerArguments.get( - Arguments.CLIOptions.LICENSE_FILE.getId()); - if (license != null) { - if (!(Files.exists(Path.of(license)))) { - throw new PackagerException("ERR_LicenseFileNotExit"); - } - } - - // Validate icon file if set - String icon = (String)bundlerArguments.get( - Arguments.CLIOptions.ICON.getId()); - if (icon != null) { - if (!(Files.exists(Path.of(icon)))) { - throw new PackagerException("ERR_IconFileNotExit", - Path.of(icon).toAbsolutePath().toString()); - } - } - - - if (hasMacAppStore) { - // Validate jlink-options if mac-app-store is set - Object jlinkOptions = bundlerArguments.get( - Arguments.CLIOptions.JLINK_OPTIONS.getId()); - if (jlinkOptions instanceof List) { - List options = (List) jlinkOptions; - if (!options.contains("--strip-native-commands")) { - throw new PackagerException( - "ERR_MissingJLinkOptMacAppStore", - "--strip-native-commands"); - } - } - } - } - - void setTargetFormat(String t) { - targetFormat = t; - } - - String getTargetFormat() { - return targetFormat; - } - - boolean isTargetAppImage() { - return ("app-image".equals(targetFormat)); - } - - private static final Set multi_args = new TreeSet<>(Arrays.asList( - StandardBundlerParam.JAVA_OPTIONS.getID(), - StandardBundlerParam.ARGUMENTS.getID(), - StandardBundlerParam.MODULE_PATH.getID(), - StandardBundlerParam.ADD_MODULES.getID(), - StandardBundlerParam.LIMIT_MODULES.getID(), - StandardBundlerParam.FILE_ASSOCIATIONS.getID(), - StandardBundlerParam.DMG_CONTENT.getID(), - StandardBundlerParam.APP_CONTENT.getID(), - StandardBundlerParam.JLINK_OPTIONS.getID() - )); - - @SuppressWarnings("unchecked") - public void addBundleArgument(String key, Object value) { - // special hack for multi-line arguments - if (multi_args.contains(key)) { - Object existingValue = bundlerArguments.get(key); - if (existingValue instanceof String && value instanceof String) { - String delim = "\n\n"; - if (key.equals(StandardBundlerParam.MODULE_PATH.getID())) { - delim = File.pathSeparator; - } else if ( - key.equals(StandardBundlerParam.DMG_CONTENT.getID()) || - key.equals(StandardBundlerParam.APP_CONTENT.getID()) || - key.equals(StandardBundlerParam.ADD_MODULES.getID())) { - delim = ","; - } - bundlerArguments.put(key, existingValue + delim + value); - } else if (existingValue instanceof List && value instanceof List) { - ((List)existingValue).addAll((List)value); - } else if (existingValue instanceof Map && - value instanceof String && ((String)value).contains("=")) { - String[] mapValues = ((String)value).split("=", 2); - ((Map)existingValue).put(mapValues[0], mapValues[1]); - } else { - bundlerArguments.put(key, value); - } - } else { - bundlerArguments.put(key, value); - } - } - - BundleParams getBundleParams() { - BundleParams bundleParams = new BundleParams(); - - // check for collisions - TreeSet keys = new TreeSet<>(bundlerArguments.keySet()); - keys.retainAll(bundleParams.getBundleParamsAsMap().keySet()); - - if (!keys.isEmpty()) { - throw new RuntimeException("Deploy Params and Bundler Arguments " - + "overlap in the following values:" + keys.toString()); - } - - bundleParams.addAllBundleParams(bundlerArguments); - - return bundleParams; - } - - @Override - public String toString() { - return "DeployParams {" + "output: " + "}"; - } - -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FileAssociationGroup.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FileAssociationGroup.java index 349a09f237c..ed89ffe1ef6 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FileAssociationGroup.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FileAssociationGroup.java @@ -111,6 +111,10 @@ final record FileAssociationGroup(List items) { return this; } + Optional description() { + return Optional.ofNullable(description); + } + Builder mimeTypes(Collection v) { mimeTypes = Set.copyOf(v); return this; diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromOptions.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromOptions.java new file mode 100644 index 00000000000..6b74cab4e65 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromOptions.java @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2025, 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. 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.jpackage.internal; + +import static jdk.jpackage.internal.ApplicationBuilder.normalizeIcons; +import static jdk.jpackage.internal.JLinkRuntimeBuilder.ensureBaseModuleInModulePath; +import static jdk.jpackage.internal.OptionUtils.isRuntimeInstaller; +import static jdk.jpackage.internal.cli.StandardOption.ABOUT_URL; +import static jdk.jpackage.internal.cli.StandardOption.ADDITIONAL_LAUNCHERS; +import static jdk.jpackage.internal.cli.StandardOption.ADD_MODULES; +import static jdk.jpackage.internal.cli.StandardOption.APP_CONTENT; +import static jdk.jpackage.internal.cli.StandardOption.APP_VERSION; +import static jdk.jpackage.internal.cli.StandardOption.COPYRIGHT; +import static jdk.jpackage.internal.cli.StandardOption.DESCRIPTION; +import static jdk.jpackage.internal.cli.StandardOption.INPUT; +import static jdk.jpackage.internal.cli.StandardOption.INSTALL_DIR; +import static jdk.jpackage.internal.cli.StandardOption.JLINK_OPTIONS; +import static jdk.jpackage.internal.cli.StandardOption.LICENSE_FILE; +import static jdk.jpackage.internal.cli.StandardOption.MODULE_PATH; +import static jdk.jpackage.internal.cli.StandardOption.NAME; +import static jdk.jpackage.internal.cli.StandardOption.PREDEFINED_APP_IMAGE; +import static jdk.jpackage.internal.cli.StandardOption.PREDEFINED_RUNTIME_IMAGE; +import static jdk.jpackage.internal.cli.StandardOption.RESOURCE_DIR; +import static jdk.jpackage.internal.cli.StandardOption.VENDOR; + +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.Function; +import jdk.jpackage.internal.cli.Options; +import jdk.jpackage.internal.model.Application; +import jdk.jpackage.internal.model.ApplicationLaunchers; +import jdk.jpackage.internal.model.ApplicationLayout; +import jdk.jpackage.internal.model.Launcher; +import jdk.jpackage.internal.model.LauncherModularStartupInfo; +import jdk.jpackage.internal.model.PackageType; +import jdk.jpackage.internal.model.RuntimeLayout; + +final class FromOptions { + + static ApplicationBuilderBuilder buildApplicationBuilder() { + return new ApplicationBuilderBuilder(); + } + + static PackageBuilder createPackageBuilder(Options options, Application app, PackageType type) { + + final var builder = new PackageBuilder(app, type); + + NAME.ifPresentIn(options, builder::name); + DESCRIPTION.ifPresentIn(options, builder::description); + APP_VERSION.ifPresentIn(options, builder::version); + ABOUT_URL.ifPresentIn(options, builder::aboutURL); + LICENSE_FILE.ifPresentIn(options, builder::licenseFile); + PREDEFINED_APP_IMAGE.ifPresentIn(options, builder::predefinedAppImage); + PREDEFINED_RUNTIME_IMAGE.ifPresentIn(options, builder::predefinedAppImage); + INSTALL_DIR.ifPresentIn(options, builder::installDir); + + return builder; + } + + + static final class ApplicationBuilderBuilder { + + private ApplicationBuilderBuilder() { + } + + ApplicationBuilder create(Options options, + Function launcherCtor, + BiFunction launcherOverrideCtor, + ApplicationLayout appLayout) { + + final Optional thePredefinedRuntimeLayout; + if (PREDEFINED_RUNTIME_IMAGE.containsIn(options)) { + thePredefinedRuntimeLayout = Optional.ofNullable( + predefinedRuntimeLayout).or(() -> Optional.of(RuntimeLayout.DEFAULT)); + } else { + thePredefinedRuntimeLayout = Optional.empty(); + } + + final var transfomer = new OptionsTransformer(options, appLayout); + final var appBuilder = createApplicationBuilder( + transfomer.appOptions(), + launcherCtor, + launcherOverrideCtor, + appLayout, + Optional.ofNullable(runtimeLayout).orElse(RuntimeLayout.DEFAULT), + thePredefinedRuntimeLayout); + + transfomer.externalApp().ifPresent(appBuilder::externalApplication); + + return appBuilder; + } + + /** + * Sets the layout of the predefined runtime image. + * @param v the layout of the predefined runtime image. Null is permitted. + * @return this + */ + ApplicationBuilderBuilder predefinedRuntimeLayout(RuntimeLayout v) { + predefinedRuntimeLayout = v; + return this; + } + + /** + * Sets the layout of a runtime bundle. + * @param v the layout of a runtime bundle. Null is permitted. + * @return this + */ + ApplicationBuilderBuilder runtimeLayout(RuntimeLayout v) { + runtimeLayout = v; + return this; + } + + private RuntimeLayout runtimeLayout; + private RuntimeLayout predefinedRuntimeLayout; + } + + + private static ApplicationBuilder createApplicationBuilder(Options options, + Function launcherCtor, + BiFunction launcherOverrideCtor, + ApplicationLayout appLayout, RuntimeLayout runtimeLayout, + Optional predefinedRuntimeLayout) { + + final var appBuilder = new ApplicationBuilder(); + + final var isRuntimeInstaller = isRuntimeInstaller(options); + + final var predefinedRuntimeImage = PREDEFINED_RUNTIME_IMAGE.findIn(options); + + final var predefinedRuntimeDirectory = predefinedRuntimeLayout.flatMap(layout -> { + return predefinedRuntimeImage.map(layout::resolveAt); + }).map(RuntimeLayout::runtimeDirectory); + + NAME.findIn(options).or(() -> { + if (isRuntimeInstaller) { + return predefinedRuntimeImage.map(Path::getFileName).map(Path::toString); + } else { + return Optional.empty(); + } + }).ifPresent(appBuilder::name); + DESCRIPTION.ifPresentIn(options, appBuilder::description); + APP_VERSION.ifPresentIn(options, appBuilder::version); + VENDOR.ifPresentIn(options, appBuilder::vendor); + COPYRIGHT.ifPresentIn(options, appBuilder::copyright); + INPUT.ifPresentIn(options, appBuilder::srcDir); + APP_CONTENT.ifPresentIn(options, appBuilder::contentDirs); + + if (isRuntimeInstaller) { + appBuilder.appImageLayout(runtimeLayout); + } else { + appBuilder.appImageLayout(appLayout); + + final var launchers = createLaunchers(options, launcherCtor); + + if (PREDEFINED_APP_IMAGE.containsIn(options)) { + appBuilder.launchers(launchers); + } else { + appBuilder.launchers(normalizeIcons(launchers, RESOURCE_DIR.findIn(options), launcherOverrideCtor)); + + final var runtimeBuilderBuilder = new RuntimeBuilderBuilder(); + + runtimeBuilderBuilder.modulePath(ensureBaseModuleInModulePath(MODULE_PATH.findIn(options).orElseGet(List::of))); + + if (!APP_VERSION.containsIn(options)) { + // Version is not specified explicitly. Try to get it from the app's module. + launchers.mainLauncher().startupInfo().ifPresent(startupInfo -> { + if (startupInfo instanceof LauncherModularStartupInfo modularStartupInfo) { + modularStartupInfo.moduleVersion().ifPresent(moduleVersion -> { + appBuilder.version(moduleVersion); + Log.verbose(I18N.format("message.module-version", + moduleVersion, modularStartupInfo.moduleName())); + }); + } + }); + } + + predefinedRuntimeDirectory.ifPresentOrElse(runtimeBuilderBuilder::forRuntime, () -> { + final var startupInfos = launchers.asList().stream() + .map(Launcher::startupInfo) + .map(Optional::orElseThrow).toList(); + final var jlinkOptionsBuilder = runtimeBuilderBuilder.forNewRuntime(startupInfos); + ADD_MODULES.findIn(options).map(Set::copyOf).ifPresent(jlinkOptionsBuilder::addModules); + JLINK_OPTIONS.ifPresentIn(options, jlinkOptionsBuilder::options); + jlinkOptionsBuilder.apply(); + }); + + appBuilder.runtimeBuilder(runtimeBuilderBuilder.create()); + } + } + + return appBuilder; + } + + private static ApplicationLaunchers createLaunchers(Options options, Function launcherCtor) { + var launchers = ADDITIONAL_LAUNCHERS.getFrom(options); + + var mainLauncher = launcherCtor.apply(options); + + // + // Additional launcher should: + // - Use description from the main launcher by default. + // + var mainLauncherDefaults = Options.of(Map.of(DESCRIPTION, mainLauncher.description())); + + var additionalLaunchers = launchers.stream().map(launcherOptions -> { + return launcherOptions.copyWithParent(mainLauncherDefaults); + }).map(launcherCtor).toList(); + + return new ApplicationLaunchers(mainLauncher, additionalLaunchers); + } +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromParams.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromParams.java deleted file mode 100644 index cee7492dbc7..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromParams.java +++ /dev/null @@ -1,251 +0,0 @@ -/* - * Copyright (c) 2025, 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. 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.jpackage.internal; - -import static jdk.jpackage.internal.Arguments.CLIOptions.LINUX_SHORTCUT_HINT; -import static jdk.jpackage.internal.Arguments.CLIOptions.WIN_MENU_HINT; -import static jdk.jpackage.internal.Arguments.CLIOptions.WIN_SHORTCUT_HINT; -import static jdk.jpackage.internal.StandardBundlerParam.ABOUT_URL; -import static jdk.jpackage.internal.StandardBundlerParam.ADD_LAUNCHERS; -import static jdk.jpackage.internal.StandardBundlerParam.ADD_MODULES; -import static jdk.jpackage.internal.StandardBundlerParam.APP_CONTENT; -import static jdk.jpackage.internal.StandardBundlerParam.APP_NAME; -import static jdk.jpackage.internal.StandardBundlerParam.COPYRIGHT; -import static jdk.jpackage.internal.StandardBundlerParam.DESCRIPTION; -import static jdk.jpackage.internal.StandardBundlerParam.FILE_ASSOCIATIONS; -import static jdk.jpackage.internal.StandardBundlerParam.ICON; -import static jdk.jpackage.internal.StandardBundlerParam.INSTALLER_NAME; -import static jdk.jpackage.internal.StandardBundlerParam.INSTALL_DIR; -import static jdk.jpackage.internal.StandardBundlerParam.JLINK_OPTIONS; -import static jdk.jpackage.internal.StandardBundlerParam.LAUNCHER_AS_SERVICE; -import static jdk.jpackage.internal.StandardBundlerParam.LICENSE_FILE; -import static jdk.jpackage.internal.StandardBundlerParam.LIMIT_MODULES; -import static jdk.jpackage.internal.StandardBundlerParam.MODULE_PATH; -import static jdk.jpackage.internal.StandardBundlerParam.NAME; -import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE; -import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE_FILE; -import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_RUNTIME_IMAGE; -import static jdk.jpackage.internal.StandardBundlerParam.RESOURCE_DIR; -import static jdk.jpackage.internal.StandardBundlerParam.SOURCE_DIR; -import static jdk.jpackage.internal.StandardBundlerParam.VENDOR; -import static jdk.jpackage.internal.StandardBundlerParam.VERSION; -import static jdk.jpackage.internal.StandardBundlerParam.hasPredefinedAppImage; -import static jdk.jpackage.internal.StandardBundlerParam.isRuntimeInstaller; -import static jdk.jpackage.internal.util.function.ThrowingFunction.toFunction; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.function.BiFunction; -import java.util.function.Function; -import jdk.jpackage.internal.model.Application; -import jdk.jpackage.internal.model.ApplicationLaunchers; -import jdk.jpackage.internal.model.ApplicationLayout; -import jdk.jpackage.internal.model.ConfigException; -import jdk.jpackage.internal.model.ExternalApplication; -import jdk.jpackage.internal.model.ExternalApplication.LauncherInfo; -import jdk.jpackage.internal.model.Launcher; -import jdk.jpackage.internal.model.LauncherShortcut; -import jdk.jpackage.internal.model.LauncherShortcutStartupDirectory; -import jdk.jpackage.internal.model.PackageType; -import jdk.jpackage.internal.model.ParseUtils; -import jdk.jpackage.internal.model.RuntimeLayout; -import jdk.jpackage.internal.util.function.ThrowingFunction; - -final class FromParams { - - static ApplicationBuilder createApplicationBuilder(Map params, - Function, Launcher> launcherMapper, - BiFunction launcherOverrideCtor, - ApplicationLayout appLayout) throws ConfigException, IOException { - return createApplicationBuilder(params, launcherMapper, launcherOverrideCtor, appLayout, RuntimeLayout.DEFAULT, Optional.of(RuntimeLayout.DEFAULT)); - } - - static ApplicationBuilder createApplicationBuilder(Map params, - Function, Launcher> launcherMapper, - BiFunction launcherOverrideCtor, - ApplicationLayout appLayout, RuntimeLayout runtimeLayout, - Optional predefinedRuntimeLayout) throws ConfigException, IOException { - - final var appBuilder = new ApplicationBuilder(); - - APP_NAME.copyInto(params, appBuilder::name); - DESCRIPTION.copyInto(params, appBuilder::description); - appBuilder.version(VERSION.fetchFrom(params)); - VENDOR.copyInto(params, appBuilder::vendor); - COPYRIGHT.copyInto(params, appBuilder::copyright); - SOURCE_DIR.copyInto(params, appBuilder::srcDir); - APP_CONTENT.copyInto(params, appBuilder::contentDirs); - - final var isRuntimeInstaller = isRuntimeInstaller(params); - - final var predefinedRuntimeImage = PREDEFINED_RUNTIME_IMAGE.findIn(params); - - final var predefinedRuntimeDirectory = predefinedRuntimeLayout.flatMap( - layout -> predefinedRuntimeImage.map(layout::resolveAt)).map(RuntimeLayout::runtimeDirectory); - - if (isRuntimeInstaller) { - appBuilder.appImageLayout(runtimeLayout); - } else { - appBuilder.appImageLayout(appLayout); - - if (hasPredefinedAppImage(params)) { - final var appImageFile = PREDEFINED_APP_IMAGE_FILE.fetchFrom(params); - appBuilder.initFromExternalApplication(appImageFile, launcherInfo -> { - var launcherParams = mapLauncherInfo(appImageFile, launcherInfo); - return launcherMapper.apply(mergeParams(params, launcherParams)); - }); - } else { - final var launchers = createLaunchers(params, launcherMapper); - - final var runtimeBuilderBuilder = new RuntimeBuilderBuilder(); - - runtimeBuilderBuilder.modulePath(MODULE_PATH.fetchFrom(params)); - - predefinedRuntimeDirectory.ifPresentOrElse(runtimeBuilderBuilder::forRuntime, () -> { - final var startupInfos = launchers.asList().stream() - .map(Launcher::startupInfo) - .map(Optional::orElseThrow).toList(); - final var jlinkOptionsBuilder = runtimeBuilderBuilder.forNewRuntime(startupInfos); - ADD_MODULES.copyInto(params, jlinkOptionsBuilder::addModules); - LIMIT_MODULES.copyInto(params, jlinkOptionsBuilder::limitModules); - JLINK_OPTIONS.copyInto(params, jlinkOptionsBuilder::options); - jlinkOptionsBuilder.apply(); - }); - - final var normalizedLaunchers = ApplicationBuilder.normalizeIcons(launchers, RESOURCE_DIR.findIn(params), launcherOverrideCtor); - - appBuilder.launchers(normalizedLaunchers).runtimeBuilder(runtimeBuilderBuilder.create()); - } - } - - return appBuilder; - } - - static PackageBuilder createPackageBuilder( - Map params, Application app, - PackageType type) throws ConfigException { - - final var builder = new PackageBuilder(app, type); - - builder.name(INSTALLER_NAME.fetchFrom(params)); - DESCRIPTION.copyInto(params, builder::description); - VERSION.copyInto(params, builder::version); - ABOUT_URL.copyInto(params, builder::aboutURL); - LICENSE_FILE.findIn(params).map(Path::of).ifPresent(builder::licenseFile); - PREDEFINED_APP_IMAGE.findIn(params).ifPresent(builder::predefinedAppImage); - PREDEFINED_RUNTIME_IMAGE.findIn(params).ifPresent(builder::predefinedAppImage); - INSTALL_DIR.findIn(params).map(Path::of).ifPresent(builder::installDir); - - return builder; - } - - static BundlerParamInfo createApplicationBundlerParam( - ThrowingFunction, T> ctor) { - return BundlerParamInfo.createBundlerParam(Application.class, ctor); - } - - static BundlerParamInfo createPackageBundlerParam( - ThrowingFunction, T> ctor) { - return BundlerParamInfo.createBundlerParam(jdk.jpackage.internal.model.Package.class, ctor); - } - - static Optional getCurrentPackage(Map params) { - return Optional.ofNullable((jdk.jpackage.internal.model.Package)params.get( - jdk.jpackage.internal.model.Package.class.getName())); - } - - static Optional findLauncherShortcut( - BundlerParamInfo shortcutParam, - Map mainParams, - Map launcherParams) { - - Optional launcherValue; - if (launcherParams == mainParams) { - // The main launcher - launcherValue = Optional.empty(); - } else { - launcherValue = shortcutParam.findIn(launcherParams); - } - - return launcherValue.map(ParseUtils::parseLauncherShortcutForAddLauncher).or(() -> { - return Optional.ofNullable(mainParams.get(shortcutParam.getID())).map(toFunction(value -> { - if (value instanceof Boolean) { - return new LauncherShortcut(LauncherShortcutStartupDirectory.DEFAULT); - } else { - try { - return ParseUtils.parseLauncherShortcutForMainLauncher((String)value); - } catch (IllegalArgumentException ex) { - throw I18N.buildConfigException("error.invalid-option-value", value, "--" + shortcutParam.getID()).create(); - } - } - })); - }); - } - - private static ApplicationLaunchers createLaunchers( - Map params, - Function, Launcher> launcherMapper) { - var launchers = ADD_LAUNCHERS.findIn(params).orElseGet(List::of); - - var mainLauncher = launcherMapper.apply(params); - var additionalLaunchers = launchers.stream().map(launcherParams -> { - return launcherMapper.apply(mergeParams(params, launcherParams)); - }).toList(); - - return new ApplicationLaunchers(mainLauncher, additionalLaunchers); - } - - private static Map mapLauncherInfo(ExternalApplication appImageFile, LauncherInfo launcherInfo) { - Map launcherParams = new HashMap<>(); - launcherParams.put(NAME.getID(), launcherInfo.name()); - if (!appImageFile.getLauncherName().equals(launcherInfo.name())) { - // This is not the main launcher, accept the value - // of "launcher-as-service" from the app image file (.jpackage.xml). - launcherParams.put(LAUNCHER_AS_SERVICE.getID(), Boolean.toString(launcherInfo.service())); - } - launcherParams.putAll(launcherInfo.extra()); - return launcherParams; - } - - private static Map mergeParams(Map mainParams, - Map launcherParams) { - if (!launcherParams.containsKey(DESCRIPTION.getID())) { - launcherParams = new HashMap<>(launcherParams); -// FIXME: this is a good improvement but it fails existing tests -// launcherParams.put(DESCRIPTION.getID(), String.format("%s (%s)", DESCRIPTION.fetchFrom( -// mainParams), APP_NAME.fetchFrom(launcherParams))); - launcherParams.put(DESCRIPTION.getID(), DESCRIPTION.fetchFrom(mainParams)); - } - return AddLauncherArguments.merge(mainParams, launcherParams, ICON.getID(), - ADD_LAUNCHERS.getID(), FILE_ASSOCIATIONS.getID(), WIN_MENU_HINT.getId(), - WIN_SHORTCUT_HINT.getId(), LINUX_SHORTCUT_HINT.getId()); - } - - static final BundlerParamInfo APPLICATION = createApplicationBundlerParam(null); -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/IOUtils.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/IOUtils.java index 427051719bb..aac113d7777 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/IOUtils.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/IOUtils.java @@ -32,7 +32,7 @@ import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.List; -import jdk.jpackage.internal.model.PackagerException; +import jdk.jpackage.internal.model.JPackageException; /** * IOUtils @@ -90,19 +90,17 @@ final class IOUtils { } } - static void writableOutputDir(Path outdir) throws PackagerException { + static void writableOutputDir(Path outdir) { if (!Files.isDirectory(outdir)) { try { Files.createDirectories(outdir); } catch (IOException ex) { - throw new PackagerException("error.cannot-create-output-dir", - outdir.toAbsolutePath().toString()); + throw new JPackageException(I18N.format("error.cannot-create-output-dir", outdir.toAbsolutePath())); } } if (!Files.isWritable(outdir)) { - throw new PackagerException("error.cannot-write-to-output-dir", - outdir.toAbsolutePath().toString()); + throw new JPackageException(I18N.format("error.cannot-write-to-output-dir", outdir.toAbsolutePath())); } } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/JLinkRuntimeBuilder.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/JLinkRuntimeBuilder.java index 6ac9758e179..50d8049bc2d 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/JLinkRuntimeBuilder.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/JLinkRuntimeBuilder.java @@ -36,7 +36,6 @@ import java.lang.module.ModuleReference; import java.lang.module.ResolvedModule; import java.nio.file.Files; import java.nio.file.Path; -import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; @@ -52,9 +51,9 @@ import java.util.stream.Stream; import jdk.internal.module.ModulePath; import jdk.jpackage.internal.model.AppImageLayout; import jdk.jpackage.internal.model.ConfigException; +import jdk.jpackage.internal.model.JPackageException; import jdk.jpackage.internal.model.LauncherModularStartupInfo; import jdk.jpackage.internal.model.LauncherStartupInfo; -import jdk.jpackage.internal.model.PackagerException; import jdk.jpackage.internal.model.RuntimeBuilder; final class JLinkRuntimeBuilder implements RuntimeBuilder { @@ -64,7 +63,7 @@ final class JLinkRuntimeBuilder implements RuntimeBuilder { } @Override - public void create(AppImageLayout appImageLayout) throws PackagerException { + public void create(AppImageLayout appImageLayout) { var args = new ArrayList(); args.add("--output"); args.add(appImageLayout.runtimeDirectory().toString()); @@ -79,7 +78,7 @@ final class JLinkRuntimeBuilder implements RuntimeBuilder { args.add(0, "jlink"); Log.verbose(args, List.of(jlinkOut), retVal, -1); if (retVal != 0) { - throw new PackagerException("error.jlink.failed", jlinkOut); + throw new JPackageException(I18N.format("error.jlink.failed", jlinkOut)); } } @@ -182,8 +181,7 @@ final class JLinkRuntimeBuilder implements RuntimeBuilder { for (String option : options) { switch (option) { case "--output", "--add-modules", "--module-path" -> { - throw new ConfigException(MessageFormat.format(I18N.getString( - "error.blocked.option"), option), null); + throw I18N.buildConfigException("error.blocked.option", option).create(); } default -> { args.add(option); diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/JPackageToolProvider.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/JPackageToolProvider.java deleted file mode 100644 index 079d03a076c..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/JPackageToolProvider.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2017, 2022, 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. 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.jpackage.internal; - -import java.io.PrintWriter; -import java.util.Optional; -import java.util.spi.ToolProvider; - -/** - * JPackageToolProvider - * - * This is the ToolProvider implementation exported - * to java.util.spi.ToolProvider and ultimately javax.tools.ToolProvider - */ -public class JPackageToolProvider implements ToolProvider { - - public String name() { - return "jpackage"; - } - - public Optional description() { - return Optional.of(jdk.jpackage.main.Main.I18N.getString("jpackage.description")); - } - - public synchronized int run( - PrintWriter out, PrintWriter err, String... args) { - try { - return new jdk.jpackage.main.Main().execute(out, err, args); - } catch (RuntimeException re) { - Log.fatalError(re.getMessage()); - Log.verbose(re); - return 1; - } - } -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherData.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherData.java deleted file mode 100644 index 488e9106479..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherData.java +++ /dev/null @@ -1,307 +0,0 @@ -/* - * Copyright (c) 2020, 2025, 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. 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.jpackage.internal; - -import jdk.jpackage.internal.model.ConfigException; -import java.io.File; -import java.io.IOException; -import java.lang.module.ModuleReference; -import java.nio.file.Files; -import java.nio.file.InvalidPathException; -import java.nio.file.Path; -import java.text.MessageFormat; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.function.Supplier; -import java.util.jar.Attributes; -import java.util.jar.JarFile; -import java.util.jar.Manifest; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_RUNTIME_IMAGE; - -/** - * Extracts data needed to run application from parameters. - */ -final class LauncherData { - boolean isModular() { - return moduleInfo != null; - } - - String qualifiedClassName() { - return qualifiedClassName; - } - - boolean isClassNameFromMainJar() { - return jarMainClass != null; - } - - String packageName() { - int sepIdx = qualifiedClassName.lastIndexOf('.'); - if (sepIdx < 0) { - return ""; - } - return qualifiedClassName.substring(sepIdx + 1); - } - - String moduleName() { - verifyIsModular(true); - return moduleInfo.name(); - } - - List modulePath() { - verifyIsModular(true); - return modulePath; - } - - Path mainJarName() { - verifyIsModular(false); - return mainJarName; - } - - List classPath() { - return classPath; - } - - String getAppVersion() { - if (isModular()) { - return moduleInfo.version().orElse(null); - } - - return null; - } - - private LauncherData() { - } - - private void verifyIsModular(boolean isModular) { - if ((moduleInfo == null) == isModular) { - throw new IllegalStateException(); - } - } - - static LauncherData create(Map params) throws - ConfigException, IOException { - - final String mainModule = getMainModule(params); - final LauncherData result; - if (mainModule == null) { - result = createNonModular(params); - } else { - result = createModular(mainModule, params); - } - result.initClasspath(params); - return result; - } - - private static LauncherData createModular(String mainModule, - Map params) throws ConfigException, - IOException { - - LauncherData launcherData = new LauncherData(); - - final int sepIdx = mainModule.indexOf("/"); - final String moduleName; - if (sepIdx > 0) { - launcherData.qualifiedClassName = mainModule.substring(sepIdx + 1); - moduleName = mainModule.substring(0, sepIdx); - } else { - moduleName = mainModule; - } - launcherData.modulePath = getModulePath(params); - - // Try to find module in the specified module path list. - ModuleReference moduleRef = JLinkRuntimeBuilder.createModuleFinder( - launcherData.modulePath).find(moduleName).orElse(null); - - if (moduleRef != null) { - launcherData.moduleInfo = ModuleInfo.fromModuleReference(moduleRef); - } else if (params.containsKey(PREDEFINED_RUNTIME_IMAGE.getID())) { - // Failed to find module in the specified module path list and - // there is external runtime given to jpackage. - // Lookup module in this runtime. - Path cookedRuntime = PREDEFINED_RUNTIME_IMAGE.fetchFrom(params); - launcherData.moduleInfo = ModuleInfo.fromCookedRuntime(moduleName, - cookedRuntime).orElse(null); - } - - if (launcherData.moduleInfo == null) { - throw new ConfigException(MessageFormat.format(I18N.getString( - "error.no-module-in-path"), moduleName), null); - } - - if (launcherData.qualifiedClassName == null) { - launcherData.qualifiedClassName = launcherData.moduleInfo.mainClass().orElse(null); - if (launcherData.qualifiedClassName == null) { - throw new ConfigException(I18N.getString("ERR_NoMainClass"), null); - } - } - - return launcherData; - } - - private static LauncherData createNonModular( - Map params) throws ConfigException, IOException { - LauncherData launcherData = new LauncherData(); - - launcherData.qualifiedClassName = getMainClass(params); - - launcherData.mainJarName = getMainJarName(params); - - Path mainJarDir = StandardBundlerParam.SOURCE_DIR.fetchFrom(params); - - final Path mainJarPath; - if (launcherData.mainJarName != null && mainJarDir != null) { - mainJarPath = mainJarDir.resolve(launcherData.mainJarName); - if (!Files.exists(mainJarPath)) { - throw new ConfigException(MessageFormat.format(I18N.getString( - "error.main-jar-does-not-exist"), - launcherData.mainJarName), I18N.getString( - "error.main-jar-does-not-exist.advice")); - } - } else { - mainJarPath = null; - } - - if (launcherData.qualifiedClassName == null) { - if (mainJarPath == null) { - throw new ConfigException(I18N.getString("error.no-main-class"), - I18N.getString("error.no-main-class.advice")); - } - - try (JarFile jf = new JarFile(mainJarPath.toFile())) { - Manifest m = jf.getManifest(); - Attributes attrs = (m != null) ? m.getMainAttributes() : null; - if (attrs != null) { - launcherData.qualifiedClassName = attrs.getValue( - Attributes.Name.MAIN_CLASS); - launcherData.jarMainClass = launcherData.qualifiedClassName; - } - } - } - - if (launcherData.qualifiedClassName == null) { - throw new ConfigException(MessageFormat.format(I18N.getString( - "error.no-main-class-with-main-jar"), - launcherData.mainJarName), MessageFormat.format( - I18N.getString( - "error.no-main-class-with-main-jar.advice"), - launcherData.mainJarName)); - } - - return launcherData; - } - - private void initClasspath(Map params) - throws IOException { - Path inputDir = StandardBundlerParam.SOURCE_DIR.fetchFrom(params); - if (inputDir == null) { - classPath = Collections.emptyList(); - } else { - try (Stream walk = Files.walk(inputDir, Integer.MAX_VALUE)) { - Set jars = walk.filter(Files::isRegularFile) - .filter(file -> file.toString().endsWith(".jar")) - .map(p -> inputDir.toAbsolutePath() - .relativize(p.toAbsolutePath())) - .collect(Collectors.toSet()); - jars.remove(mainJarName); - classPath = jars.stream().sorted().toList(); - } - } - } - - private static String getMainClass(Map params) { - return getStringParam(params, Arguments.CLIOptions.APPCLASS.getId()); - } - - private static Path getMainJarName(Map params) - throws ConfigException { - return getPathParam(params, Arguments.CLIOptions.MAIN_JAR.getId()); - } - - private static String getMainModule(Map params) { - return getStringParam(params, Arguments.CLIOptions.MODULE.getId()); - } - - private static String getStringParam(Map params, - String paramName) { - Optional value = Optional.ofNullable(params.get(paramName)); - return value.map(Object::toString).orElse(null); - } - - private static T getPathParam(String paramName, Supplier func) throws ConfigException { - try { - return func.get(); - } catch (InvalidPathException ex) { - throw new ConfigException(MessageFormat.format(I18N.getString( - "error.not-path-parameter"), paramName, - ex.getLocalizedMessage()), null, ex); - } - } - - private static Path getPathParam(Map params, - String paramName) throws ConfigException { - return getPathParam(paramName, () -> { - String value = getStringParam(params, paramName); - Path result = null; - if (value != null) { - result = Path.of(value); - } - return result; - }); - } - - private static List getModulePath(Map params) - throws ConfigException { - List modulePath = getPathListParameter(Arguments.CLIOptions.MODULE_PATH.getId(), params); - - if (params.containsKey(PREDEFINED_RUNTIME_IMAGE.getID())) { - Path runtimePath = PREDEFINED_RUNTIME_IMAGE.fetchFrom(params); - runtimePath = runtimePath.resolve("lib"); - modulePath = Stream.of(modulePath, List.of(runtimePath)) - .flatMap(List::stream) - .toList(); - } - - return modulePath; - } - - private static List getPathListParameter(String paramName, - Map params) throws ConfigException { - return getPathParam(paramName, () -> - params.get(paramName) instanceof String value ? - Stream.of(value.split(File.pathSeparator)).map(Path::of).toList() : List.of()); - } - - private String qualifiedClassName; - private String jarMainClass; - private Path mainJarName; - private List classPath; - private List modulePath; - private ModuleInfo moduleInfo; -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherFromOptions.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherFromOptions.java new file mode 100644 index 00000000000..0749cc48b9b --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherFromOptions.java @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2025, 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. 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.jpackage.internal; + +import static jdk.jpackage.internal.cli.StandardOption.APPCLASS; +import static jdk.jpackage.internal.cli.StandardOption.ARGUMENTS; +import static jdk.jpackage.internal.cli.StandardOption.DESCRIPTION; +import static jdk.jpackage.internal.cli.StandardOption.FILE_ASSOCIATIONS; +import static jdk.jpackage.internal.cli.StandardOption.ICON; +import static jdk.jpackage.internal.cli.StandardOption.INPUT; +import static jdk.jpackage.internal.cli.StandardOption.JAVA_OPTIONS; +import static jdk.jpackage.internal.cli.StandardOption.LAUNCHER_AS_SERVICE; +import static jdk.jpackage.internal.cli.StandardOption.MAIN_JAR; +import static jdk.jpackage.internal.cli.StandardOption.MODULE; +import static jdk.jpackage.internal.cli.StandardOption.MODULE_PATH; +import static jdk.jpackage.internal.cli.StandardOption.NAME; +import static jdk.jpackage.internal.cli.StandardOption.PREDEFINED_APP_IMAGE; +import static jdk.jpackage.internal.cli.StandardOption.PREDEFINED_RUNTIME_IMAGE; + +import java.nio.file.Path; +import java.util.List; +import java.util.Optional; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.stream.IntStream; +import jdk.internal.util.OperatingSystem; +import jdk.jpackage.internal.FileAssociationGroup.FileAssociationException; +import jdk.jpackage.internal.FileAssociationGroup.FileAssociationNoExtensionsException; +import jdk.jpackage.internal.FileAssociationGroup.FileAssociationNoMimesException; +import jdk.jpackage.internal.cli.Options; +import jdk.jpackage.internal.cli.StandardFaOption; +import jdk.jpackage.internal.model.CustomLauncherIcon; +import jdk.jpackage.internal.model.DefaultLauncherIcon; +import jdk.jpackage.internal.model.FileAssociation; +import jdk.jpackage.internal.model.Launcher; +import jdk.jpackage.internal.model.LauncherIcon; + +final class LauncherFromOptions { + + LauncherFromOptions() { + } + + LauncherFromOptions faGroupBuilderMutator(BiConsumer v) { + faGroupBuilderMutator = v; + return this; + } + + LauncherFromOptions faMapper(BiFunction v) { + faMapper = v; + return this; + } + + LauncherFromOptions faWithDefaultDescription() { + return faGroupBuilderMutator((faGroupBuilder, launcherBuilder) -> { + if (faGroupBuilder.description().isEmpty()) { + var description = String.format("%s association", launcherBuilder.create().name()); + faGroupBuilder.description(description); + } + }); + } + + Launcher create(Options options) { + final var builder = new LauncherBuilder().defaultIconResourceName(defaultIconResourceName()); + + DESCRIPTION.ifPresentIn(options, builder::description); + builder.icon(toLauncherIcon(ICON.findIn(options).orElse(null))); + LAUNCHER_AS_SERVICE.ifPresentIn(options, builder::isService); + NAME.ifPresentIn(options, builder::name); + + if (PREDEFINED_APP_IMAGE.findIn(options).isEmpty()) { + final var startupInfoBuilder = new LauncherStartupInfoBuilder(); + + INPUT.ifPresentIn(options, startupInfoBuilder::inputDir); + ARGUMENTS.ifPresentIn(options, startupInfoBuilder::defaultParameters); + JAVA_OPTIONS.ifPresentIn(options, startupInfoBuilder::javaOptions); + MAIN_JAR.ifPresentIn(options, startupInfoBuilder::mainJar); + APPCLASS.ifPresentIn(options, startupInfoBuilder::mainClassName); + MODULE.ifPresentIn(options, startupInfoBuilder::moduleName); + MODULE_PATH.ifPresentIn(options, startupInfoBuilder::modulePath); + PREDEFINED_RUNTIME_IMAGE.ifPresentIn(options, startupInfoBuilder::predefinedRuntimeImage); + + builder.startupInfo(startupInfoBuilder.create()); + } + + final var faOptionsList = FILE_ASSOCIATIONS.findIn(options).orElseGet(List::of); + + final var faGroups = IntStream.range(0, faOptionsList.size()).mapToObj(idx -> { + final var faOptions = faOptionsList.get(idx); + + final var faGroupBuilder = FileAssociationGroup.build(); + + StandardFaOption.DESCRIPTION.ifPresentIn(faOptions, faGroupBuilder::description); + StandardFaOption.ICON.ifPresentIn(faOptions, faGroupBuilder::icon); + StandardFaOption.EXTENSIONS.ifPresentIn(faOptions, faGroupBuilder::extensions); + StandardFaOption.CONTENT_TYPE.ifPresentIn(faOptions, faGroupBuilder::mimeTypes); + + faGroupBuilderMutator().ifPresent(mutator -> { + mutator.accept(faGroupBuilder, builder); + }); + + final var faID = idx + 1; + + final FileAssociationGroup faGroup; + try { + faGroup = faGroupBuilder.create(); + } catch (FileAssociationNoMimesException ex) { + throw I18N.buildConfigException() + .message("error.no-content-types-for-file-association", faID) + .advice("error.no-content-types-for-file-association.advice", faID) + .create(); + } catch (FileAssociationNoExtensionsException ex) { + // TODO: Must do something about this condition! + throw new AssertionError(); + } catch (FileAssociationException ex) { + // Should never happen + throw new UnsupportedOperationException(ex); + } + + return faMapper().map(mapper -> { + return new FileAssociationGroup(faGroup.items().stream().map(fa -> { + return mapper.apply(faOptions, fa); + }).toList()); + }).orElse(faGroup); + + }).toList(); + + return builder.faGroups(faGroups).create(); + } + + private Optional> faGroupBuilderMutator() { + return Optional.ofNullable(faGroupBuilderMutator); + } + + private Optional> faMapper() { + return Optional.ofNullable(faMapper); + } + + private static LauncherIcon toLauncherIcon(Path launcherIconPath) { + if (launcherIconPath == null) { + return DefaultLauncherIcon.INSTANCE; + } else if (launcherIconPath.toString().isEmpty()) { + return null; + } else { + return CustomLauncherIcon.create(launcherIconPath); + } + } + + private static String defaultIconResourceName() { + switch (OperatingSystem.current()) { + case WINDOWS -> { + return "JavaApp.ico"; + } + case LINUX -> { + return "JavaApp.png"; + } + case MACOS -> { + return "JavaApp.icns"; + } + default -> { + throw new UnsupportedOperationException(); + } + } + } + + private BiConsumer faGroupBuilderMutator; + private BiFunction faMapper; +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherFromParams.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherFromParams.java deleted file mode 100644 index b4ce1cdb200..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherFromParams.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (c) 2025, 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. 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.jpackage.internal; - -import static jdk.jpackage.internal.I18N.buildConfigException; -import static jdk.jpackage.internal.StandardBundlerParam.ARGUMENTS; -import static jdk.jpackage.internal.StandardBundlerParam.DESCRIPTION; -import static jdk.jpackage.internal.StandardBundlerParam.FA_CONTENT_TYPE; -import static jdk.jpackage.internal.StandardBundlerParam.FA_DESCRIPTION; -import static jdk.jpackage.internal.StandardBundlerParam.FA_EXTENSIONS; -import static jdk.jpackage.internal.StandardBundlerParam.FA_ICON; -import static jdk.jpackage.internal.StandardBundlerParam.FILE_ASSOCIATIONS; -import static jdk.jpackage.internal.StandardBundlerParam.ICON; -import static jdk.jpackage.internal.StandardBundlerParam.JAVA_OPTIONS; -import static jdk.jpackage.internal.StandardBundlerParam.LAUNCHER_AS_SERVICE; -import static jdk.jpackage.internal.StandardBundlerParam.LAUNCHER_DATA; -import static jdk.jpackage.internal.StandardBundlerParam.NAME; -import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE; -import static jdk.jpackage.internal.util.function.ThrowingSupplier.toSupplier; - -import java.nio.file.Path; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.function.BiFunction; -import java.util.stream.IntStream; -import jdk.internal.util.OperatingSystem; -import jdk.jpackage.internal.model.ConfigException; -import jdk.jpackage.internal.model.CustomLauncherIcon; -import jdk.jpackage.internal.model.DefaultLauncherIcon; -import jdk.jpackage.internal.model.FileAssociation; -import jdk.jpackage.internal.model.Launcher; -import jdk.jpackage.internal.model.LauncherIcon; - -record LauncherFromParams(Optional, FileAssociation>> faExtension) { - - LauncherFromParams { - Objects.requireNonNull(faExtension); - } - - LauncherFromParams() { - this(Optional.empty()); - } - - Launcher create(Map params) throws ConfigException { - final var builder = new LauncherBuilder().defaultIconResourceName(defaultIconResourceName()); - - DESCRIPTION.copyInto(params, builder::description); - builder.icon(toLauncherIcon(ICON.findIn(params).orElse(null))); - LAUNCHER_AS_SERVICE.copyInto(params, builder::isService); - NAME.copyInto(params, builder::name); - - if (PREDEFINED_APP_IMAGE.findIn(params).isEmpty()) { - final var startupInfoBuilder = new LauncherStartupInfoBuilder(); - - startupInfoBuilder.launcherData(LAUNCHER_DATA.fetchFrom(params)); - ARGUMENTS.copyInto(params, startupInfoBuilder::defaultParameters); - JAVA_OPTIONS.copyInto(params, startupInfoBuilder::javaOptions); - - builder.startupInfo(startupInfoBuilder.create()); - } - - final var faParamsList = FILE_ASSOCIATIONS.findIn(params).orElseGet(List::of); - - final var faGroups = IntStream.range(0, faParamsList.size()).mapToObj(idx -> { - final var faParams = faParamsList.get(idx); - return toSupplier(() -> { - final var faGroupBuilder = FileAssociationGroup.build(); - - if (OperatingSystem.current() == OperatingSystem.MACOS) { - FA_DESCRIPTION.copyInto(faParams, faGroupBuilder::description); - } else { - faGroupBuilder.description(FA_DESCRIPTION.findIn(faParams).orElseGet(() -> { - return String.format("%s association", toSupplier(builder::create).get().name()); - })); - } - - FA_ICON.copyInto(faParams, faGroupBuilder::icon); - FA_EXTENSIONS.copyInto(faParams, faGroupBuilder::extensions); - FA_CONTENT_TYPE.copyInto(faParams, faGroupBuilder::mimeTypes); - - final var faID = idx + 1; - - final FileAssociationGroup faGroup; - try { - faGroup = faGroupBuilder.create(); - } catch (FileAssociationGroup.FileAssociationNoMimesException ex) { - throw buildConfigException() - .message("error.no-content-types-for-file-association", faID) - .advice("error.no-content-types-for-file-association.advice", faID) - .create(); - } - - if (faExtension.isPresent()) { - return new FileAssociationGroup(faGroup.items().stream().map(fa -> { - return faExtension.get().apply(fa, faParams); - }).toList()); - } else { - return faGroup; - } - }).get(); - }).toList(); - - return builder.faGroups(faGroups).create(); - } - - private static LauncherIcon toLauncherIcon(Path launcherIconPath) { - if (launcherIconPath == null) { - return DefaultLauncherIcon.INSTANCE; - } else if (launcherIconPath.toString().isEmpty()) { - return null; - } else { - return CustomLauncherIcon.create(launcherIconPath); - } - } - - private static String defaultIconResourceName() { - switch (OperatingSystem.current()) { - case WINDOWS -> { - return "JavaApp.ico"; - } - case LINUX -> { - return "JavaApp.png"; - } - case MACOS -> { - return "JavaApp.icns"; - } - default -> { - throw new UnsupportedOperationException(); - } - } - } -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherStartupInfoBuilder.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherStartupInfoBuilder.java index 5273f2d251c..b2fc48af9e4 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherStartupInfoBuilder.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherStartupInfoBuilder.java @@ -24,69 +24,216 @@ */ package jdk.jpackage.internal; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; import java.nio.file.Path; import java.util.List; -import java.util.function.UnaryOperator; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.Predicate; +import java.util.jar.Attributes; +import java.util.jar.JarFile; +import java.util.jar.Manifest; +import java.util.stream.Stream; +import jdk.jpackage.internal.model.JPackageException; import jdk.jpackage.internal.model.LauncherJarStartupInfo; import jdk.jpackage.internal.model.LauncherJarStartupInfoMixin; import jdk.jpackage.internal.model.LauncherModularStartupInfo; import jdk.jpackage.internal.model.LauncherModularStartupInfoMixin; -import jdk.jpackage.internal.model.LauncherStartupInfo.Stub; import jdk.jpackage.internal.model.LauncherStartupInfo; final class LauncherStartupInfoBuilder { LauncherStartupInfo create() { - return decorator.apply(new Stub(qualifiedClassName, javaOptions, - defaultParameters, classPath)); + if (moduleName != null) { + return createModular(); + } else if (mainJar != null) { + return createNonModular(); + } else { + throw new JPackageException(I18N.format("ERR_NoEntryPoint")); + } } - LauncherStartupInfoBuilder launcherData(LauncherData launcherData) { - if (launcherData.isModular()) { - decorator = new ModuleStartupInfo(launcherData.moduleName()); - } else { - decorator = new JarStartupInfo(launcherData.mainJarName(), - launcherData.isClassNameFromMainJar()); - } - classPath = launcherData.classPath(); - qualifiedClassName = launcherData.qualifiedClassName(); + LauncherStartupInfoBuilder inputDir(Path v) { + inputDir = v; return this; } LauncherStartupInfoBuilder javaOptions(List v) { + if (v != null) { + v.forEach(Objects::requireNonNull); + } javaOptions = v; return this; } LauncherStartupInfoBuilder defaultParameters(List v) { + if (v != null) { + v.forEach(Objects::requireNonNull); + } defaultParameters = v; return this; } - private static record ModuleStartupInfo(String moduleName) implements UnaryOperator { + LauncherStartupInfoBuilder mainJar(Path v) { + mainJar = v; + return this; + } - @Override - public LauncherStartupInfo apply(LauncherStartupInfo base) { - return LauncherModularStartupInfo.create(base, - new LauncherModularStartupInfoMixin.Stub(moduleName)); + LauncherStartupInfoBuilder mainClassName(String v) { + mainClassName = v; + return this; + } + + LauncherStartupInfoBuilder predefinedRuntimeImage(Path v) { + cookedRuntimePath = v; + return this; + } + + LauncherStartupInfoBuilder moduleName(String v) { + if (v == null) { + moduleName = null; + } else { + var slashIdx = v.indexOf('/'); + if (slashIdx < 0) { + moduleName = v; + } else { + moduleName = v.substring(0, slashIdx); + if (slashIdx < v.length() - 1) { + mainClassName(v.substring(slashIdx + 1)); + } + } + } + return this; + } + + LauncherStartupInfoBuilder modulePath(List v) { + modulePath = v; + return this; + } + + private Optional inputDir() { + return Optional.ofNullable(inputDir); + } + + private Optional mainClassName() { + return Optional.ofNullable(mainClassName); + } + + private Optional cookedRuntimePath() { + return Optional.ofNullable(cookedRuntimePath); + } + + private LauncherStartupInfo createLauncherStartupInfo(String mainClassName, List classpath) { + Objects.requireNonNull(mainClassName); + classpath.forEach(Objects::requireNonNull); + return new LauncherStartupInfo.Stub(mainClassName, + Optional.ofNullable(javaOptions).orElseGet(List::of), + Optional.ofNullable(defaultParameters).orElseGet(List::of), + classpath); + } + + private static List createClasspath(Path inputDir, Set excludes) { + excludes.forEach(Objects::requireNonNull); + try (final var walk = Files.walk(inputDir)) { + return walk.filter(Files::isRegularFile) + .filter(file -> file.getFileName().toString().endsWith(".jar")) + .map(inputDir::relativize) + .filter(Predicate.not(excludes::contains)) + .distinct() + .toList(); + } catch (IOException ex) { + throw new UncheckedIOException(ex); } } - private static record JarStartupInfo(Path jarPath, - boolean isClassNameFromMainJar) implements - UnaryOperator { + private LauncherModularStartupInfo createModular() { + final var fullModulePath = getFullModulePath(); - @Override - public LauncherStartupInfo apply(LauncherStartupInfo base) { - return LauncherJarStartupInfo.create(base, - new LauncherJarStartupInfoMixin.Stub(jarPath, - isClassNameFromMainJar)); - } + // Try to find the module in the specified module path list. + final var moduleInfo = JLinkRuntimeBuilder.createModuleFinder(fullModulePath).find(moduleName) + .map(ModuleInfo::fromModuleReference).or(() -> { + // Failed to find the module in the specified module path list. + return cookedRuntimePath().flatMap(cookedRuntime -> { + // Lookup the module in the external runtime. + return ModuleInfo.fromCookedRuntime(moduleName, cookedRuntime); + }); + }).orElseThrow(() -> { + return I18N.buildConfigException("error.no-module-in-path", moduleName).create(); + }); + + final var effectiveMainClassName = mainClassName().or(moduleInfo::mainClass).orElseThrow(() -> { + return I18N.buildConfigException("ERR_NoMainClass").create(); + }); + + // If module is located in the file system, exclude it from the classpath. + final var classpath = inputDir().map(theInputDir -> { + var classpathExcludes = moduleInfo.fileLocation().filter(moduleFile -> { + return moduleFile.startsWith(theInputDir); + }).map(theInputDir::relativize).map(Set::of).orElseGet(Set::of); + return createClasspath(theInputDir, classpathExcludes); + }).orElseGet(List::of); + + return LauncherModularStartupInfo.create( + createLauncherStartupInfo(effectiveMainClassName, classpath), + new LauncherModularStartupInfoMixin.Stub(moduleInfo.name(), moduleInfo.version())); } - private String qualifiedClassName; + private List getFullModulePath() { + return cookedRuntimePath().map(runtimeImage -> { + return Stream.of(modulePath(), List.of(runtimeImage.resolve("lib"))).flatMap(List::stream).toList(); + }).orElse(modulePath()); + } + + private List modulePath() { + return Optional.ofNullable(modulePath).orElseGet(List::of); + } + + private LauncherJarStartupInfo createNonModular() { + final var theInputDir = inputDir().orElseThrow(); + + final var mainJarPath = theInputDir.resolve(mainJar); + + if (!Files.exists(mainJarPath)) { + throw I18N.buildConfigException() + .message("error.main-jar-does-not-exist", mainJar) + .advice("error.main-jar-does-not-exist.advice") + .create(); + } + + final var effectiveMainClassName = mainClassName().or(() -> { + try (final var jf = new JarFile(mainJarPath.toFile())) { + return Optional.ofNullable(jf.getManifest()).map(Manifest::getMainAttributes).map(attrs -> { + return attrs.getValue(Attributes.Name.MAIN_CLASS); + }); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + }).orElseThrow(() -> { + return I18N.buildConfigException() + .message("error.no-main-class-with-main-jar", mainJar) + .advice("error.no-main-class-with-main-jar.advice", mainJar) + .create(); + }); + + return LauncherJarStartupInfo.create( + createLauncherStartupInfo(effectiveMainClassName, createClasspath(theInputDir, Set.of(mainJar))), + new LauncherJarStartupInfoMixin.Stub(mainJar, mainClassName().isEmpty())); + } + + // Modular options + private String moduleName; + private List modulePath; + + // Non-modular options + private Path mainJar; + + // Common options + private Path inputDir; + private String mainClassName; private List javaOptions; private List defaultParameters; - private List classPath; - private UnaryOperator decorator; + private Path cookedRuntimePath; } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/OptionUtils.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/OptionUtils.java new file mode 100644 index 00000000000..97e1274f078 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/OptionUtils.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2025, 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. 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.jpackage.internal; + +import static jdk.jpackage.internal.cli.StandardOption.BUNDLING_OPERATION_DESCRIPTOR; +import static jdk.jpackage.internal.cli.StandardOption.DEST; +import static jdk.jpackage.internal.cli.StandardOption.MAIN_JAR; +import static jdk.jpackage.internal.cli.StandardOption.MODULE; +import static jdk.jpackage.internal.cli.StandardOption.PREDEFINED_APP_IMAGE; +import static jdk.jpackage.internal.cli.StandardOption.PREDEFINED_RUNTIME_IMAGE; + +import java.nio.file.Path; +import jdk.jpackage.internal.cli.Options; +import jdk.jpackage.internal.cli.StandardBundlingOperation; + +final class OptionUtils { + + static boolean isRuntimeInstaller(Options options) { + return PREDEFINED_RUNTIME_IMAGE.containsIn(options) + && !PREDEFINED_APP_IMAGE.containsIn(options) + && !MAIN_JAR.containsIn(options) + && !MODULE.containsIn(options); + } + + static Path outputDir(Options options) { + return DEST.getFrom(options); + } + + static StandardBundlingOperation bundlingOperation(Options options) { + return StandardBundlingOperation.valueOf(BUNDLING_OPERATION_DESCRIPTOR.getFrom(options)).orElseThrow(); + } +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/OptionsTransformer.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/OptionsTransformer.java new file mode 100644 index 00000000000..b146e5a7f8d --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/OptionsTransformer.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2025, 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. 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.jpackage.internal; + +import static jdk.jpackage.internal.cli.StandardOption.ADDITIONAL_LAUNCHERS; +import static jdk.jpackage.internal.cli.StandardOption.APP_VERSION; +import static jdk.jpackage.internal.cli.StandardOption.DESCRIPTION; +import static jdk.jpackage.internal.cli.StandardOption.ICON; +import static jdk.jpackage.internal.cli.StandardOption.NAME; +import static jdk.jpackage.internal.cli.StandardOption.PREDEFINED_APP_IMAGE; + +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import jdk.jpackage.internal.cli.Options; +import jdk.jpackage.internal.cli.WithOptionIdentifier; +import jdk.jpackage.internal.model.ApplicationLayout; +import jdk.jpackage.internal.model.ExternalApplication; + +record OptionsTransformer(Options mainOptions, Optional externalApp) { + + OptionsTransformer { + Objects.requireNonNull(mainOptions); + Objects.requireNonNull(externalApp); + } + + OptionsTransformer(Options mainOptions, ApplicationLayout appLayout) { + this(mainOptions, PREDEFINED_APP_IMAGE.findIn(mainOptions).map(appLayout::resolveAt).map(AppImageFile::load)); + } + + Options appOptions() { + return externalApp.map(ea -> { + var overrideOptions = Map.of( + NAME, ea.appName(), + APP_VERSION, ea.appVersion(), + ADDITIONAL_LAUNCHERS, ea.addLaunchers().stream().map(li -> { + return Options.concat(li.extra(), Options.of(Map.of( + NAME, li.name(), + // This should prevent the code building the Launcher instance + // from the Options object from trying to create a startup info object. + PREDEFINED_APP_IMAGE, PREDEFINED_APP_IMAGE.getFrom(mainOptions), + // + // For backward compatibility, descriptions of the additional + // launchers in the predefined app image will be set to + // the application description, if available, or to the name + // of the main launcher in the predefined app image. + // + // All launchers in the predefined app image will have the same description. + // This is wrong and should be revised. + // + DESCRIPTION, DESCRIPTION.findIn(mainOptions).orElseGet(ea::appName) + ))); + }).toList() + ); + return Options.concat( + Options.of(overrideOptions), + ea.extra(), + // Remove icon if any from the application/launcher options. + // If the icon is specified in the main options, it for the installer. + mainOptions.copyWithout(ICON.id()) + ); + }).orElse(mainOptions); + } +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Packager.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Packager.java index 501fd64bdca..8e47b046eb1 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Packager.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Packager.java @@ -29,7 +29,6 @@ import java.util.Objects; import java.util.Optional; import java.util.function.Consumer; import jdk.jpackage.internal.model.Package; -import jdk.jpackage.internal.model.PackagerException; final class Packager { @@ -69,7 +68,7 @@ final class Packager { return Objects.requireNonNull(env); } - Path execute(PackagingPipeline.Builder pipelineBuilder) throws PackagerException { + Path execute(PackagingPipeline.Builder pipelineBuilder) { Objects.requireNonNull(pkg); Objects.requireNonNull(env); Objects.requireNonNull(outputDir); diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/PackagingPipeline.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/PackagingPipeline.java index 6f4e0d0d2d8..a2750fee260 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/PackagingPipeline.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/PackagingPipeline.java @@ -46,7 +46,6 @@ import jdk.jpackage.internal.model.AppImageLayout; import jdk.jpackage.internal.model.Application; import jdk.jpackage.internal.model.ApplicationLayout; import jdk.jpackage.internal.model.Package; -import jdk.jpackage.internal.model.PackagerException; import jdk.jpackage.internal.pipeline.DirectedEdge; import jdk.jpackage.internal.pipeline.FixedDAG; import jdk.jpackage.internal.pipeline.TaskPipelineBuilder; @@ -62,7 +61,7 @@ final class PackagingPipeline { * @param env the build environment * @param app the application */ - void execute(BuildEnv env, Application app) throws PackagerException { + void execute(BuildEnv env, Application app) { execute(contextMapper.apply(createTaskContext(env, app))); } @@ -81,7 +80,7 @@ final class PackagingPipeline { * @param pkg the package * @param outputDir the output directory for the package file */ - void execute(BuildEnv env, Package pkg, Path outputDir) throws PackagerException { + void execute(BuildEnv env, Package pkg, Path outputDir) { execute((StartupParameters)createPackagingTaskContext(env, pkg, outputDir, taskConfig)); } @@ -91,7 +90,7 @@ final class PackagingPipeline { * * @param startupParameters the pipeline startup parameters */ - void execute(StartupParameters startupParameters) throws PackagerException { + void execute(StartupParameters startupParameters) { execute(contextMapper.apply(createTaskContext((PackagingTaskContext)startupParameters))); } @@ -132,7 +131,7 @@ final class PackagingPipeline { } interface TaskContext extends Predicate { - void execute(TaskAction taskAction) throws IOException, PackagerException; + void execute(TaskAction taskAction) throws IOException; } record AppImageBuildEnv(BuildEnv env, T app) { @@ -161,27 +160,27 @@ final class PackagingPipeline { @FunctionalInterface interface ApplicationImageTaskAction extends TaskAction { - void execute(AppImageBuildEnv env) throws IOException, PackagerException; + void execute(AppImageBuildEnv env) throws IOException; } @FunctionalInterface interface AppImageTaskAction extends TaskAction { - void execute(AppImageBuildEnv env) throws IOException, PackagerException; + void execute(AppImageBuildEnv env) throws IOException; } @FunctionalInterface interface CopyAppImageTaskAction extends TaskAction { - void execute(T pkg, AppImageLayout srcAppImage, AppImageLayout dstAppImage) throws IOException, PackagerException; + void execute(T pkg, AppImageLayout srcAppImage, AppImageLayout dstAppImage) throws IOException; } @FunctionalInterface interface PackageTaskAction extends TaskAction { - void execute(PackageBuildEnv env) throws IOException, PackagerException; + void execute(PackageBuildEnv env) throws IOException; } @FunctionalInterface interface NoArgTaskAction extends TaskAction { - void execute() throws IOException, PackagerException; + void execute() throws IOException; } record TaskConfig(Optional action) { @@ -493,7 +492,7 @@ final class PackagingPipeline { return new PackagingTaskContext(BuildEnv.withAppImageLayout(env, dstLayout), pkg, outputDir, srcLayout); } - private void execute(TaskContext context) throws PackagerException { + private void execute(TaskContext context) { final Map> tasks = taskConfig.entrySet().stream().collect(toMap(Map.Entry::getKey, task -> { return createTask(context, task.getKey(), task.getValue()); })); @@ -508,14 +507,8 @@ final class PackagingPipeline { try { builder.create().call(); - } catch (ExceptionBox ex) { - throw new PackagerException(ex.getCause()); - } catch (RuntimeException ex) { - throw ex; - } catch (PackagerException ex) { - throw ex; } catch (Exception ex) { - throw new PackagerException(ex); + throw ExceptionBox.rethrowUnchecked(ex); } } @@ -546,7 +539,7 @@ final class PackagingPipeline { @SuppressWarnings("unchecked") @Override - public void execute(TaskAction taskAction) throws IOException, PackagerException { + public void execute(TaskAction taskAction) throws IOException { if (taskAction instanceof PackageTaskAction) { ((PackageTaskAction)taskAction).execute(pkgBuildEnv()); } else if (taskAction instanceof CopyAppImageTaskAction) { @@ -600,7 +593,7 @@ final class PackagingPipeline { @SuppressWarnings("unchecked") @Override - public void execute(TaskAction taskAction) throws IOException, PackagerException { + public void execute(TaskAction taskAction) throws IOException { if (taskAction instanceof AppImageTaskAction) { final var taskEnv = pkg.map(PackagingTaskContext::appImageBuildEnv).orElseGet(this::appBuildEnv); ((AppImageTaskAction)taskAction).execute(taskEnv); diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/StandardBundlerParam.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/StandardBundlerParam.java deleted file mode 100644 index 2b35a6830f8..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/StandardBundlerParam.java +++ /dev/null @@ -1,513 +0,0 @@ -/* - * Copyright (c) 2014, 2025, 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. 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.jpackage.internal; - - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Date; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Stream; -import jdk.jpackage.internal.model.ConfigException; -import jdk.jpackage.internal.model.ExternalApplication; -import static jdk.jpackage.internal.ApplicationLayoutUtils.PLATFORM_APPLICATION_LAYOUT; - -/** - * Standard bundler parameters. - * - * Contains static definitions of all of the common bundler parameters. - * (additional platform specific and mode specific bundler parameters - * are defined in each of the specific bundlers) - * - * Also contains static methods that operate on maps of parameters. - */ -final class StandardBundlerParam { - - private static final String DEFAULT_VERSION = "1.0"; - private static final String DEFAULT_RELEASE = "1"; - private static final String[] DEFAULT_JLINK_OPTIONS = { - "--strip-native-commands", - "--strip-debug", - "--no-man-pages", - "--no-header-files"}; - - static final BundlerParamInfo LAUNCHER_DATA = BundlerParamInfo.createBundlerParam( - LauncherData.class, LauncherData::create); - - static final BundlerParamInfo SOURCE_DIR = - new BundlerParamInfo<>( - Arguments.CLIOptions.INPUT.getId(), - Path.class, - p -> null, - (s, p) -> Path.of(s) - ); - - static final BundlerParamInfo OUTPUT_DIR = - new BundlerParamInfo<>( - Arguments.CLIOptions.OUTPUT.getId(), - Path.class, - p -> Path.of("").toAbsolutePath(), - (s, p) -> Path.of(s) - ); - - // note that each bundler is likely to replace this one with - // their own converter - static final BundlerParamInfo MAIN_JAR = - new BundlerParamInfo<>( - Arguments.CLIOptions.MAIN_JAR.getId(), - Path.class, - params -> LAUNCHER_DATA.fetchFrom(params).mainJarName(), - null - ); - - static final BundlerParamInfo PREDEFINED_APP_IMAGE = - new BundlerParamInfo<>( - Arguments.CLIOptions.PREDEFINED_APP_IMAGE.getId(), - Path.class, - params -> null, - (s, p) -> Path.of(s)); - - static final BundlerParamInfo PREDEFINED_APP_IMAGE_FILE = BundlerParamInfo.createBundlerParam( - ExternalApplication.class, params -> { - if (hasPredefinedAppImage(params)) { - var appImage = PREDEFINED_APP_IMAGE.fetchFrom(params); - return AppImageFile.load(appImage, PLATFORM_APPLICATION_LAYOUT); - } else { - return null; - } - }); - - static final BundlerParamInfo MAIN_CLASS = - new BundlerParamInfo<>( - Arguments.CLIOptions.APPCLASS.getId(), - String.class, - params -> { - if (isRuntimeInstaller(params)) { - return null; - } else if (hasPredefinedAppImage(params)) { - PREDEFINED_APP_IMAGE_FILE.fetchFrom(params).getMainClass(); - } - return LAUNCHER_DATA.fetchFrom(params).qualifiedClassName(); - }, - (s, p) -> s - ); - - static final BundlerParamInfo PREDEFINED_RUNTIME_IMAGE = - new BundlerParamInfo<>( - Arguments.CLIOptions.PREDEFINED_RUNTIME_IMAGE.getId(), - Path.class, - params -> null, - (s, p) -> Path.of(s) - ); - - // this is the raw --app-name arg - used in APP_NAME and INSTALLER_NAME - static final BundlerParamInfo NAME = - new BundlerParamInfo<>( - Arguments.CLIOptions.NAME.getId(), - String.class, - params -> null, - (s, p) -> s - ); - - // this is the application name, either from the app-image (if given), - // the name (if given) derived from the main-class, or the runtime image - static final BundlerParamInfo APP_NAME = - new BundlerParamInfo<>( - "application-name", - String.class, - params -> { - String appName = NAME.fetchFrom(params); - if (hasPredefinedAppImage(params)) { - appName = PREDEFINED_APP_IMAGE_FILE.fetchFrom(params).getLauncherName(); - } else if (appName == null) { - String s = MAIN_CLASS.fetchFrom(params); - if (s != null) { - int idx = s.lastIndexOf("."); - appName = (idx < 0) ? s : s.substring(idx+1); - } else if (isRuntimeInstaller(params)) { - Path f = PREDEFINED_RUNTIME_IMAGE.fetchFrom(params); - if (f != null) { - appName = f.getFileName().toString(); - } - } - } - return appName; - }, - (s, p) -> s - ); - - static final BundlerParamInfo INSTALLER_NAME = - new BundlerParamInfo<>( - "installer-name", - String.class, - params -> { - String installerName = NAME.fetchFrom(params); - return (installerName != null) ? installerName : - APP_NAME.fetchFrom(params); - }, - (s, p) -> s - ); - - static final BundlerParamInfo ICON = - new BundlerParamInfo<>( - Arguments.CLIOptions.ICON.getId(), - Path.class, - params -> null, - (s, p) -> Path.of(s) - ); - - static final BundlerParamInfo ABOUT_URL = - new BundlerParamInfo<>( - Arguments.CLIOptions.ABOUT_URL.getId(), - String.class, - params -> null, - (s, p) -> s - ); - - static final BundlerParamInfo VENDOR = - new BundlerParamInfo<>( - Arguments.CLIOptions.VENDOR.getId(), - String.class, - params -> I18N.getString("param.vendor.default"), - (s, p) -> s - ); - - static final BundlerParamInfo DESCRIPTION = - new BundlerParamInfo<>( - Arguments.CLIOptions.DESCRIPTION.getId(), - String.class, - params -> params.containsKey(APP_NAME.getID()) - ? APP_NAME.fetchFrom(params) - : I18N.getString("param.description.default"), - (s, p) -> s - ); - - static final BundlerParamInfo COPYRIGHT = - new BundlerParamInfo<>( - Arguments.CLIOptions.COPYRIGHT.getId(), - String.class, - params -> MessageFormat.format(I18N.getString( - "param.copyright.default"), new Date()), - (s, p) -> s - ); - - @SuppressWarnings("unchecked") - static final BundlerParamInfo> ARGUMENTS = - new BundlerParamInfo<>( - Arguments.CLIOptions.ARGUMENTS.getId(), - (Class>) (Object) List.class, - params -> Collections.emptyList(), - (s, p) -> null - ); - - @SuppressWarnings("unchecked") - static final BundlerParamInfo> JAVA_OPTIONS = - new BundlerParamInfo<>( - Arguments.CLIOptions.JAVA_OPTIONS.getId(), - (Class>) (Object) List.class, - params -> Collections.emptyList(), - (s, p) -> Arrays.asList(s.split("\n\n")) - ); - - static final BundlerParamInfo VERSION = - new BundlerParamInfo<>( - Arguments.CLIOptions.VERSION.getId(), - String.class, - StandardBundlerParam::getDefaultAppVersion, - (s, p) -> s - ); - - static final BundlerParamInfo RELEASE = - new BundlerParamInfo<>( - Arguments.CLIOptions.RELEASE.getId(), - String.class, - params -> DEFAULT_RELEASE, - (s, p) -> s - ); - - public static final BundlerParamInfo LICENSE_FILE = - new BundlerParamInfo<>( - Arguments.CLIOptions.LICENSE_FILE.getId(), - String.class, - params -> null, - (s, p) -> s - ); - - static final BundlerParamInfo TEMP_ROOT = - new BundlerParamInfo<>( - Arguments.CLIOptions.TEMP_ROOT.getId(), - Path.class, - params -> { - try { - return Files.createTempDirectory("jdk.jpackage"); - } catch (IOException ioe) { - return null; - } - }, - (s, p) -> Path.of(s) - ); - - public static final BundlerParamInfo CONFIG_ROOT = - new BundlerParamInfo<>( - "configRoot", - Path.class, - params -> { - Path root = TEMP_ROOT.fetchFrom(params).resolve("config"); - try { - Files.createDirectories(root); - } catch (IOException ioe) { - return null; - } - return root; - }, - (s, p) -> null - ); - - static final BundlerParamInfo VERBOSE = - new BundlerParamInfo<>( - Arguments.CLIOptions.VERBOSE.getId(), - Boolean.class, - params -> false, - // valueOf(null) is false, and we actually do want null - (s, p) -> (s == null || "null".equalsIgnoreCase(s)) ? - true : Boolean.valueOf(s) - ); - - static final BundlerParamInfo RESOURCE_DIR = - new BundlerParamInfo<>( - Arguments.CLIOptions.RESOURCE_DIR.getId(), - Path.class, - params -> null, - (s, p) -> Path.of(s) - ); - - static final BundlerParamInfo INSTALL_DIR = - new BundlerParamInfo<>( - Arguments.CLIOptions.INSTALL_DIR.getId(), - String.class, - params -> null, - (s, p) -> s - ); - - static final BundlerParamInfo LAUNCHER_AS_SERVICE = - new BundlerParamInfo<>( - Arguments.CLIOptions.LAUNCHER_AS_SERVICE.getId(), - Boolean.class, - params -> false, - // valueOf(null) is false, and we actually do want null - (s, p) -> (s == null || "null".equalsIgnoreCase(s)) ? - true : Boolean.valueOf(s) - ); - - - @SuppressWarnings("unchecked") - static final BundlerParamInfo>> ADD_LAUNCHERS = - new BundlerParamInfo<>( - Arguments.CLIOptions.ADD_LAUNCHER.getId(), - (Class>>) (Object) - List.class, - params -> new ArrayList<>(1), - // valueOf(null) is false, and we actually do want null - (s, p) -> null - ); - - @SuppressWarnings("unchecked") - static final BundlerParamInfo - >> FILE_ASSOCIATIONS = - new BundlerParamInfo<>( - Arguments.CLIOptions.FILE_ASSOCIATIONS.getId(), - (Class>>) (Object) - List.class, - params -> new ArrayList<>(1), - // valueOf(null) is false, and we actually do want null - (s, p) -> null - ); - - @SuppressWarnings("unchecked") - static final BundlerParamInfo> FA_EXTENSIONS = - new BundlerParamInfo<>( - "fileAssociation.extension", - (Class>) (Object) List.class, - params -> null, // null means not matched to an extension - (s, p) -> Arrays.asList(s.split("(,|\\s)+")) - ); - - @SuppressWarnings("unchecked") - static final BundlerParamInfo> FA_CONTENT_TYPE = - new BundlerParamInfo<>( - "fileAssociation.contentType", - (Class>) (Object) List.class, - params -> null, - // null means not matched to a content/mime type - (s, p) -> Arrays.asList(s.split("(,|\\s)+")) - ); - - static final BundlerParamInfo FA_DESCRIPTION = - new BundlerParamInfo<>( - "fileAssociation.description", - String.class, - p -> null, - (s, p) -> s - ); - - static final BundlerParamInfo FA_ICON = - new BundlerParamInfo<>( - "fileAssociation.icon", - Path.class, - ICON::fetchFrom, - (s, p) -> Path.of(s) - ); - - @SuppressWarnings("unchecked") - static final BundlerParamInfo> DMG_CONTENT = - new BundlerParamInfo<>( - Arguments.CLIOptions.DMG_CONTENT.getId(), - (Class>) (Object)List.class, - p -> Collections.emptyList(), - (s, p) -> Stream.of(s.split(",")).map(Path::of).toList() - ); - - @SuppressWarnings("unchecked") - static final BundlerParamInfo> APP_CONTENT = - new BundlerParamInfo<>( - Arguments.CLIOptions.APP_CONTENT.getId(), - (Class>) (Object)List.class, - p->Collections.emptyList(), - (s, p) -> Stream.of(s.split(",")).map(Path::of).toList() - ); - - @SuppressWarnings("unchecked") - static final BundlerParamInfo> MODULE_PATH = - new BundlerParamInfo<>( - Arguments.CLIOptions.MODULE_PATH.getId(), - (Class>) (Object)List.class, - p -> JLinkRuntimeBuilder.ensureBaseModuleInModulePath(List.of()), - (s, p) -> { - List modulePath = Stream.of(s.split(File.pathSeparator)) - .map(Path::of) - .toList(); - return JLinkRuntimeBuilder.ensureBaseModuleInModulePath(modulePath); - }); - - static final BundlerParamInfo MODULE = - new BundlerParamInfo<>( - Arguments.CLIOptions.MODULE.getId(), - String.class, - p -> null, - (s, p) -> { - return String.valueOf(s); - }); - - @SuppressWarnings("unchecked") - static final BundlerParamInfo> ADD_MODULES = - new BundlerParamInfo<>( - Arguments.CLIOptions.ADD_MODULES.getId(), - (Class>) (Object) Set.class, - p -> new LinkedHashSet(), - (s, p) -> new LinkedHashSet<>(Arrays.asList(s.split(","))) - ); - - @SuppressWarnings("unchecked") - static final BundlerParamInfo> JLINK_OPTIONS = - new BundlerParamInfo<>( - Arguments.CLIOptions.JLINK_OPTIONS.getId(), - (Class>) (Object) List.class, - p -> Arrays.asList(DEFAULT_JLINK_OPTIONS), - (s, p) -> null); - - @SuppressWarnings("unchecked") - static final BundlerParamInfo> LIMIT_MODULES = - new BundlerParamInfo<>( - "limit-modules", - (Class>) (Object) Set.class, - p -> new LinkedHashSet(), - (s, p) -> new LinkedHashSet<>(Arrays.asList(s.split(","))) - ); - - static final BundlerParamInfo SIGN_BUNDLE = - new BundlerParamInfo<>( - Arguments.CLIOptions.MAC_SIGN.getId(), - Boolean.class, - params -> false, - (s, p) -> (s == null || "null".equalsIgnoreCase(s)) ? - null : Boolean.valueOf(s) - ); - - static boolean isRuntimeInstaller(Map params) { - if (params.containsKey(MODULE.getID()) || - params.containsKey(MAIN_JAR.getID()) || - params.containsKey(PREDEFINED_APP_IMAGE.getID())) { - return false; // we are building or are given an application - } - // runtime installer requires --runtime-image, if this is false - // here then we should have thrown error validating args. - return params.containsKey(PREDEFINED_RUNTIME_IMAGE.getID()); - } - - static boolean hasPredefinedAppImage(Map params) { - return params.containsKey(PREDEFINED_APP_IMAGE.getID()); - } - - private static String getDefaultAppVersion(Map params) { - String appVersion = DEFAULT_VERSION; - - if (isRuntimeInstaller(params)) { - return appVersion; - } - - LauncherData launcherData = null; - try { - launcherData = LAUNCHER_DATA.fetchFrom(params); - } catch (RuntimeException ex) { - if (ex.getCause() instanceof ConfigException) { - return appVersion; - } - throw ex; - } - - if (launcherData.isModular()) { - String moduleVersion = launcherData.getAppVersion(); - if (moduleVersion != null) { - Log.verbose(MessageFormat.format(I18N.getString( - "message.module-version"), - moduleVersion, - launcherData.moduleName())); - appVersion = moduleVersion; - } - } - - return appVersion; - } -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AbstractBundler.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/TempDirectory.java similarity index 53% rename from src/jdk.jpackage/share/classes/jdk/jpackage/internal/AbstractBundler.java rename to src/jdk.jpackage/share/classes/jdk/jpackage/internal/TempDirectory.java index 762b65b530e..50d1701bf0d 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AbstractBundler.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/TempDirectory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 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,36 +22,51 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ - package jdk.jpackage.internal; +import java.io.Closeable; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; -import java.util.Map; +import jdk.jpackage.internal.cli.Options; +import jdk.jpackage.internal.cli.StandardOption; import jdk.jpackage.internal.util.FileUtils; +final class TempDirectory implements Closeable { -/** - * AbstractBundler - * - * This is the base class all bundlers extend from. - * It contains methods and parameters common to all bundlers. - * The concrete implementations are in the platform specific bundlers. - */ -abstract class AbstractBundler implements Bundler { + TempDirectory(Options options) throws IOException { + final var tempDir = StandardOption.TEMP_ROOT.findIn(options); + if (tempDir.isPresent()) { + this.path = tempDir.orElseThrow(); + this.options = options; + } else { + this.path = Files.createTempDirectory("jdk.jpackage"); + this.options = options.copyWithDefaultValue(StandardOption.TEMP_ROOT, path); + } - @Override - public String toString() { - return getName(); + deleteOnClose = tempDir.isEmpty(); + } + + Options options() { + return options; + } + + Path path() { + return path; + } + + boolean deleteOnClose() { + return deleteOnClose; } @Override - public void cleanup(Map params) { - try { - FileUtils.deleteRecursive( - StandardBundlerParam.TEMP_ROOT.fetchFrom(params)); - } catch (IOException e) { - Log.verbose(e.getMessage()); + public void close() throws IOException { + if (deleteOnClose) { + FileUtils.deleteRecursive(path); } } + + private final Path path; + private final Options options; + private final boolean deleteOnClose; } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ValidOptions.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ValidOptions.java deleted file mode 100644 index 89a2e4b56fe..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ValidOptions.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright (c) 2018, 2023, 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. 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.jpackage.internal; - -import java.util.EnumSet; -import java.util.HashMap; - -import jdk.internal.util.OperatingSystem; -import jdk.jpackage.internal.Arguments.CLIOptions; - -/** - * ValidOptions - * - * Two basic methods for validating command line options. - * - * initArgs() - * Computes the Map of valid options for each mode on this Platform. - * - * checkIfSupported(CLIOptions arg) - * Determine if the given arg is valid on this platform. - * - * checkIfImageSupported(CLIOptions arg) - * Determine if the given arg is valid for creating app image. - * - * checkIfInstallerSupported(CLIOptions arg) - * Determine if the given arg is valid for creating installer. - * - * checkIfSigningSupported(CLIOptions arg) - * Determine if the given arg is valid for signing app image. - * - */ -class ValidOptions { - - enum USE { - ALL, // valid in all cases - LAUNCHER, // valid when creating a launcher - INSTALL, // valid when creating an installer - SIGN, // valid when signing is requested - } - - private static final HashMap> options = new HashMap<>(); - - // initializing list of mandatory arguments - static { - put(CLIOptions.NAME.getId(), USE.ALL); - put(CLIOptions.VERSION.getId(), USE.ALL); - put(CLIOptions.OUTPUT.getId(), USE.ALL); - put(CLIOptions.TEMP_ROOT.getId(), USE.ALL); - put(CLIOptions.VERBOSE.getId(), - EnumSet.of(USE.ALL, USE.SIGN)); - put(CLIOptions.PREDEFINED_RUNTIME_IMAGE.getId(), USE.ALL); - put(CLIOptions.RESOURCE_DIR.getId(), USE.ALL); - put(CLIOptions.DESCRIPTION.getId(), USE.ALL); - put(CLIOptions.VENDOR.getId(), USE.ALL); - put(CLIOptions.COPYRIGHT.getId(), USE.ALL); - put(CLIOptions.PACKAGE_TYPE.getId(), - EnumSet.of(USE.ALL, USE.SIGN)); - put(CLIOptions.ICON.getId(), USE.ALL); - - put(CLIOptions.INPUT.getId(), USE.LAUNCHER); - put(CLIOptions.MODULE.getId(), USE.LAUNCHER); - put(CLIOptions.MODULE_PATH.getId(), USE.LAUNCHER); - put(CLIOptions.ADD_MODULES.getId(), USE.LAUNCHER); - put(CLIOptions.MAIN_JAR.getId(), USE.LAUNCHER); - put(CLIOptions.APPCLASS.getId(), USE.LAUNCHER); - put(CLIOptions.ARGUMENTS.getId(), USE.LAUNCHER); - put(CLIOptions.JAVA_OPTIONS.getId(), USE.LAUNCHER); - put(CLIOptions.ADD_LAUNCHER.getId(), USE.LAUNCHER); - put(CLIOptions.JLINK_OPTIONS.getId(), USE.LAUNCHER); - put(CLIOptions.APP_CONTENT.getId(), USE.LAUNCHER); - - put(CLIOptions.LICENSE_FILE.getId(), USE.INSTALL); - put(CLIOptions.INSTALL_DIR.getId(), USE.INSTALL); - put(CLIOptions.PREDEFINED_APP_IMAGE.getId(), - (OperatingSystem.isMacOS()) ? - EnumSet.of(USE.INSTALL, USE.SIGN) : - EnumSet.of(USE.INSTALL)); - put(CLIOptions.LAUNCHER_AS_SERVICE.getId(), USE.INSTALL); - - put(CLIOptions.ABOUT_URL.getId(), USE.INSTALL); - - put(CLIOptions.FILE_ASSOCIATIONS.getId(), - (OperatingSystem.isMacOS()) ? USE.ALL : USE.INSTALL); - - if (OperatingSystem.isWindows()) { - put(CLIOptions.WIN_CONSOLE_HINT.getId(), USE.LAUNCHER); - - put(CLIOptions.WIN_HELP_URL.getId(), USE.INSTALL); - put(CLIOptions.WIN_UPDATE_URL.getId(), USE.INSTALL); - - put(CLIOptions.WIN_MENU_HINT.getId(), USE.INSTALL); - put(CLIOptions.WIN_MENU_GROUP.getId(), USE.INSTALL); - put(CLIOptions.WIN_SHORTCUT_HINT.getId(), USE.INSTALL); - put(CLIOptions.WIN_SHORTCUT_PROMPT.getId(), USE.INSTALL); - put(CLIOptions.WIN_DIR_CHOOSER.getId(), USE.INSTALL); - put(CLIOptions.WIN_UPGRADE_UUID.getId(), USE.INSTALL); - put(CLIOptions.WIN_PER_USER_INSTALLATION.getId(), - USE.INSTALL); - } - - if (OperatingSystem.isMacOS()) { - put(CLIOptions.MAC_SIGN.getId(), - EnumSet.of(USE.ALL, USE.SIGN)); - put(CLIOptions.MAC_BUNDLE_NAME.getId(), USE.ALL); - put(CLIOptions.MAC_BUNDLE_IDENTIFIER.getId(), USE.ALL); - put(CLIOptions.MAC_BUNDLE_SIGNING_PREFIX.getId(), - EnumSet.of(USE.ALL, USE.SIGN)); - put(CLIOptions.MAC_SIGNING_KEY_NAME.getId(), - EnumSet.of(USE.ALL, USE.SIGN)); - put(CLIOptions.MAC_APP_IMAGE_SIGN_IDENTITY.getId(), - EnumSet.of(USE.ALL, USE.SIGN)); - put(CLIOptions.MAC_INSTALLER_SIGN_IDENTITY.getId(), - EnumSet.of(USE.INSTALL, USE.SIGN)); - put(CLIOptions.MAC_SIGNING_KEYCHAIN.getId(), - EnumSet.of(USE.ALL, USE.SIGN)); - put(CLIOptions.MAC_APP_STORE.getId(), USE.ALL); - put(CLIOptions.MAC_CATEGORY.getId(), USE.ALL); - put(CLIOptions.MAC_ENTITLEMENTS.getId(), - EnumSet.of(USE.ALL, USE.SIGN)); - put(CLIOptions.DMG_CONTENT.getId(), USE.INSTALL); - } - - if (OperatingSystem.isLinux()) { - put(CLIOptions.LINUX_BUNDLE_NAME.getId(), USE.INSTALL); - put(CLIOptions.LINUX_DEB_MAINTAINER.getId(), USE.INSTALL); - put(CLIOptions.LINUX_CATEGORY.getId(), USE.INSTALL); - put(CLIOptions.LINUX_RPM_LICENSE_TYPE.getId(), USE.INSTALL); - put(CLIOptions.LINUX_PACKAGE_DEPENDENCIES.getId(), - USE.INSTALL); - put(CLIOptions.LINUX_MENU_GROUP.getId(), USE.INSTALL); - put(CLIOptions.RELEASE.getId(), USE.INSTALL); - put(CLIOptions.LINUX_SHORTCUT_HINT.getId(), USE.INSTALL); - } - } - - static boolean checkIfSupported(CLIOptions arg) { - return options.containsKey(arg.getId()); - } - - static boolean checkIfImageSupported(CLIOptions arg) { - EnumSet value = options.get(arg.getId()); - return value.contains(USE.ALL) || - value.contains(USE.LAUNCHER) || - value.contains(USE.SIGN); - } - - static boolean checkIfInstallerSupported(CLIOptions arg) { - EnumSet value = options.get(arg.getId()); - return value.contains(USE.ALL) || value.contains(USE.INSTALL); - } - - static boolean checkIfSigningSupported(CLIOptions arg) { - EnumSet value = options.get(arg.getId()); - return value.contains(USE.SIGN); - } - - private static EnumSet put(String key, USE value) { - return options.put(key, EnumSet.of(value)); - } - - private static EnumSet put(String key, EnumSet value) { - return options.put(key, value); - } -} diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBuildEnvFromParams.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/AdditionalLauncher.java similarity index 76% rename from src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBuildEnvFromParams.java rename to src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/AdditionalLauncher.java index 31759c8c529..34019ff9e81 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBuildEnvFromParams.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/AdditionalLauncher.java @@ -22,13 +22,11 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package jdk.jpackage.internal; -import jdk.jpackage.internal.model.MacPackage; +package jdk.jpackage.internal.cli; -final class MacBuildEnvFromParams { +import java.nio.file.Path; - static final BundlerParamInfo BUILD_ENV = BundlerParamInfo.createBundlerParam(BuildEnv.class, params -> { - return BuildEnvFromParams.create(params, MacPackagingPipeline.APPLICATION_LAYOUT::resolveAt, MacPackage::guessRuntimeLayout); - }); + +record AdditionalLauncher(String name, Path propertyFile) { } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/BundlingOperationModifier.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/BundlingOperationModifier.java new file mode 100644 index 00000000000..33525f3e54d --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/BundlingOperationModifier.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2025, 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. 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.jpackage.internal.cli; + +/** + * Modifiers for jpackage operations. + */ +enum BundlingOperationModifier implements OptionScope { + /** + * Create runtime native bundle. + */ + BUNDLE_RUNTIME, + + /** + * Create native bundle from the predefined app image. + */ + BUNDLE_PREDEFINED_APP_IMAGE, + + ; +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/BundlingOperationOptionScope.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/BundlingOperationOptionScope.java new file mode 100644 index 00000000000..04af5865baa --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/BundlingOperationOptionScope.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2025, 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. 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.jpackage.internal.cli; + + +import jdk.jpackage.internal.model.BundlingOperationDescriptor; + +/** + * Bundling operation scope. + *

    + * The scope of bundling operations. E.g., app image or native package bundling. + */ +interface BundlingOperationOptionScope extends OptionScope { + BundlingOperationDescriptor descriptor(); +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/CliBundlingEnvironment.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/CliBundlingEnvironment.java new file mode 100644 index 00000000000..09ff0997c14 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/CliBundlingEnvironment.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2025, 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. 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.jpackage.internal.cli; + +import java.util.NoSuchElementException; +import jdk.jpackage.internal.model.BundlingEnvironment; +import jdk.jpackage.internal.model.BundlingOperationDescriptor; + +/** + * CLI bundling environment. + */ +public interface CliBundlingEnvironment extends BundlingEnvironment { + + /** + * Requests to run a bundling operation denoted with the given descriptor with + * the given values of command line options. + * + * @param op the descriptor of the requested bundling operation + * @param cmdline the validated values of the command line options + * @throws NoSuchElementException if the specified descriptor is not one of the + * items in the list returned by + * {@link #supportedOperations()} method + */ + void createBundle(BundlingOperationDescriptor op, Options cmdline); +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/DefaultOptions.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/DefaultOptions.java new file mode 100644 index 00000000000..91342500277 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/DefaultOptions.java @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2025, 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. 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.jpackage.internal.cli; + +import static java.util.stream.Collectors.toUnmodifiableMap; +import static java.util.stream.Collectors.toUnmodifiableSet; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + + +final class DefaultOptions implements Options { + + DefaultOptions(Map values) { + this(values, Optional.empty()); + } + + DefaultOptions( + Map values, + Predicate optionNamesFilter) { + + this(values, Optional.of(optionNamesFilter)); + } + + DefaultOptions( + Map values, + Optional> optionNamesFilter) { + + map = values.entrySet().stream().collect(toUnmodifiableMap(e -> { + return e.getKey().id(); + }, e -> { + return new OptionIdentifierWithValue(e.getKey(), e.getValue()); + })); + + var optionNamesStream = optionNames(values.keySet().stream()); + optionNames = optionNamesFilter.map(optionNamesStream::filter).orElse(optionNamesStream) + .collect(toUnmodifiableSet()); + } + + private DefaultOptions(Snapshot snapshot) { + map = snapshot.map(); + optionNames = snapshot.optionNames(); + } + + static DefaultOptions create(Snapshot snapshot) { + var options = new DefaultOptions(snapshot); + + var mapOptionNames = optionNames( + options.map.values().stream().map(OptionIdentifierWithValue::withId) + ).collect(toUnmodifiableSet()); + + for (var e : options.map.entrySet()) { + if (e.getKey() != e.getValue().withId().id()) { + throw new IllegalArgumentException("Corrupted options map"); + } + } + + if (!mapOptionNames.containsAll(snapshot.optionNames())) { + throw new IllegalArgumentException("Unexpected option names"); + } + return options; + } + + @Override + public Optional find(OptionIdentifier id) { + return Optional.ofNullable(map.get(Objects.requireNonNull(id))).map(OptionIdentifierWithValue::value); + } + + @Override + public boolean contains(OptionName optionName) { + return optionNames.contains(Objects.requireNonNull(optionName)); + } + + @Override + public Set ids() { + return Collections.unmodifiableSet(map.keySet()); + } + + @Override + public DefaultOptions copyWithout(Iterable ids) { + return copy(StreamSupport.stream(ids.spliterator(), false), false); + } + + @Override + public DefaultOptions copyWith(Iterable ids) { + return copy(StreamSupport.stream(ids.spliterator(), false), true); + } + + DefaultOptions add(DefaultOptions other) { + return new DefaultOptions(new Snapshot(Stream.of(this, other).flatMap(v -> { + return v.map.values().stream(); + }).collect(toUnmodifiableMap(OptionIdentifierWithValue::id, x -> x, (first, _) -> { + return first; + })), Stream.of(this, other) + .map(DefaultOptions::optionNames) + .flatMap(Collection::stream) + .collect(toUnmodifiableSet()))); + } + + Set optionNames() { + return optionNames; + } + + Set withOptionIdentifierSet() { + return map.values().stream() + .map(OptionIdentifierWithValue::withId) + .collect(toUnmodifiableSet()); + } + + record Snapshot(Map map, Set optionNames) { + Snapshot { + Objects.requireNonNull(map); + Objects.requireNonNull(optionNames); + } + } + + record OptionIdentifierWithValue(WithOptionIdentifier withId, Object value) { + OptionIdentifierWithValue { + Objects.requireNonNull(withId); + Objects.requireNonNull(value); + } + + OptionIdentifier id() { + return withId.id(); + } + + OptionIdentifierWithValue copyWithValue(Object value) { + return new OptionIdentifierWithValue(withId, value); + } + } + + private DefaultOptions copy(Stream ids, boolean includes) { + var includeIds = ids.collect(toUnmodifiableSet()); + return new DefaultOptions(map.values().stream().filter(v -> { + return includeIds.contains(v.id()) == includes; + }).collect(toUnmodifiableMap(OptionIdentifierWithValue::withId, OptionIdentifierWithValue::value))); + } + + private static Stream optionNames(Stream options) { + return options.map(v -> { + Optional> spec; + switch (v) { + case Option option -> { + spec = Optional.of(option.spec()); + } + case OptionValue optionValue -> { + spec = optionValue.asOption().map(Option::spec); + } + default -> { + spec = Optional.empty(); + } + } + return spec; + }).filter(Optional::isPresent).map(Optional::get).map(OptionSpec::names).flatMap(Collection::stream); + } + + static final DefaultOptions EMPTY = new DefaultOptions(Map.of()); + + private final Map map; + private final Set optionNames; +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/HelpFormatter.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/HelpFormatter.java new file mode 100644 index 00000000000..1d8a5eefc79 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/HelpFormatter.java @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2025, 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. 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.jpackage.internal.cli; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; + + +/** + * Generic help formatter. + */ +final class HelpFormatter { + + private HelpFormatter(List optionGroups, OptionGroupFormatter formatter) { + this.optionGroups = Objects.requireNonNull(optionGroups); + this.formatter = Objects.requireNonNull(formatter); + } + + void format(Consumer sink) { + for (var group : optionGroups) { + formatter.format(group, sink); + } + } + + static Builder build() { + return new Builder(); + } + + + static final class Builder { + + private Builder() { + } + + HelpFormatter create() { + return new HelpFormatter(groups, validatedGroupFormatter()); + } + + Builder groups(Collection v) { + groups.addAll(v); + return this; + } + + Builder groups(OptionGroup... v) { + return groups(List.of(v)); + } + + Builder groupFormatter(OptionGroupFormatter v) { + groupFormatter = v; + return this; + } + + private OptionGroupFormatter validatedGroupFormatter() { + return Optional.ofNullable(groupFormatter).orElseGet(Builder::createConsoleFormatter); + } + + private static OptionGroupFormatter createConsoleFormatter() { + return new ConsoleOptionGroupFormatter(new ConsoleOptionFormatter(2, 10)); + } + + private final List groups = new ArrayList<>(); + private OptionGroupFormatter groupFormatter; + } + + + interface OptionFormatter { + + public default void format(OptionSpec optionSpec, Consumer sink) { + format(optionSpec.names().stream().map(OptionName::formatForCommandLine).collect(Collectors.joining(" ")), + optionSpec.valuePattern(), + optionSpec.description(), sink); + } + + void format(String optionNames, Optional valuePattern, String description, Consumer sink); + } + + interface OptionGroupFormatter { + + default void format(OptionGroup group, Consumer sink) { + formatHeader(group.name(), sink); + formatBody(group.options(), sink); + } + + void formatHeader(String gropName, Consumer sink); + + void formatBody(Iterable> optionSpecs, Consumer sink); + } + + + record ConsoleOptionFormatter(int nameOffset, int descriptionOffset) implements OptionFormatter { + + @Override + public void format(String optionNames, Optional valuePattern, String description, Consumer sink) { + sink.accept(" ".repeat(nameOffset)); + sink.accept(optionNames); + valuePattern.map(v -> " " + v).ifPresent(sink); + eol(sink); + final var descriptionOffsetStr = " ".repeat(descriptionOffset); + Stream.of(description.split("\\R")).map(line -> { + return descriptionOffsetStr + line; + }).forEach(line -> { + sink.accept(line); + eol(sink); + }); + } + } + + + record ConsoleOptionGroupFormatter(OptionFormatter optionFormatter) implements OptionGroupFormatter { + + ConsoleOptionGroupFormatter { + Objects.requireNonNull(optionFormatter); + } + + @Override + public void formatHeader(String groupName, Consumer sink) { + Objects.requireNonNull(groupName); + eol(sink); + sink.accept(groupName + ":"); + eol(sink); + } + + @Override + public void formatBody(Iterable> optionSpecs, Consumer sink) { + optionSpecs.forEach(optionSpec -> { + optionFormatter.format(optionSpec, sink); + }); + } + } + + + record OptionGroup(String name, List> options) { + + OptionGroup { + Objects.requireNonNull(name); + Objects.requireNonNull(options); + } + } + + + static Consumer eol(Consumer sink) { + sink.accept(System.lineSeparator()); + return sink; + } + + + private final List optionGroups; + private final OptionGroupFormatter formatter; +} diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBaseInstallerBundler.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/I18N.java similarity index 56% rename from src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBaseInstallerBundler.java rename to src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/I18N.java index 5c912728c32..63c2b88039f 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBaseInstallerBundler.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/I18N.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2025, 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,31 +22,34 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ +package jdk.jpackage.internal.cli; -package jdk.jpackage.internal; - -import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE; - +import java.util.List; import java.util.Map; -import jdk.jpackage.internal.model.ConfigException; +import jdk.internal.util.OperatingSystem; +import jdk.jpackage.internal.util.MultiResourceBundle; +import jdk.jpackage.internal.util.StringBundle; -public abstract class MacBaseInstallerBundler extends AbstractBundler { +final class I18N { - public MacBaseInstallerBundler() { - appImageBundler = new MacAppBundler(); + private I18N() { } - protected void validateAppImageAndBundeler( - Map params) throws ConfigException { - if (PREDEFINED_APP_IMAGE.fetchFrom(params) == null) { - appImageBundler.validate(params); - } + static String format(String key, Object ... args) { + return BUNDLE.format(key, args); } - @Override - public String getBundleType() { - return "INSTALLER"; - } + private static final StringBundle BUNDLE; - private final Bundler appImageBundler; + static { + var prefix = "jdk.jpackage.internal.resources."; + BUNDLE = StringBundle.fromResourceBundle(MultiResourceBundle.create( + prefix + "MainResources", + Map.of( + OperatingSystem.LINUX, List.of(prefix + "LinuxResources"), + OperatingSystem.MACOS, List.of(prefix + "MacResources"), + OperatingSystem.WINDOWS, List.of(prefix + "WinResources") + ) + )); + } } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/JOptSimpleOptionsBuilder.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/JOptSimpleOptionsBuilder.java new file mode 100644 index 00000000000..57b92471e4a --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/JOptSimpleOptionsBuilder.java @@ -0,0 +1,817 @@ +/* + * Copyright (c) 2025, 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. 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.jpackage.internal.cli; + +import static java.util.stream.Collectors.toUnmodifiableMap; +import static java.util.stream.Collectors.toUnmodifiableSet; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.function.UnaryOperator; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; +import jdk.internal.joptsimple.ArgumentAcceptingOptionSpec; +import jdk.internal.joptsimple.OptionParser; +import jdk.internal.joptsimple.OptionSet; +import jdk.jpackage.internal.cli.DefaultOptions.OptionIdentifierWithValue; +import jdk.jpackage.internal.cli.DefaultOptions.Snapshot; +import jdk.jpackage.internal.cli.OptionSpec.MergePolicy; +import jdk.jpackage.internal.util.Result; + + +/** + * Builds an instance of {@link Options} interface backed with joptsimple command + * line parser. + * + * Two types of command line argument processing are supported: + *
      + *
    1. Parse command line. Parsed data is stored as a map of strings. + *
    2. Convert strings to objects. Parsed data is stored as a map of objects. + *
    + */ +final class JOptSimpleOptionsBuilder { + + Function> create() { + return createJOptSimpleParser()::parse; + } + + JOptSimpleOptionsBuilder options(Collection v) { + v.stream().map(u -> { + switch (u) { + case Option o -> { + return o; + } + case OptionValue ov -> { + return ov.getOption(); + } + default -> { + throw new IllegalArgumentException(); + } + } + }).forEach(options::add); + return this; + } + + JOptSimpleOptionsBuilder options(WithOptionIdentifier... v) { + return options(List.of(v)); + } + + JOptSimpleOptionsBuilder optionSpecMapper(UnaryOperator> v) { + optionSpecMapper = v; + return this; + } + + JOptSimpleOptionsBuilder jOptSimpleParserErrorHandler(Function v) { + jOptSimpleParserErrorHandler = v; + return this; + } + + private JOptSimpleParser createJOptSimpleParser() { + return JOptSimpleParser.create(options, Optional.ofNullable(optionSpecMapper), + Optional.ofNullable(jOptSimpleParserErrorHandler)); + } + + + static final class ConvertedOptionsBuilder { + + private ConvertedOptionsBuilder(TypedOptions options) { + impl = Objects.requireNonNull(options); + } + + Options create() { + return impl; + } + + ConvertedOptionsBuilder copyWithExcludes(Collection v) { + return new ConvertedOptionsBuilder(impl.copyWithout(v)); + } + + List nonOptionArguments() { + return impl.nonOptionArguments(); + } + + List detectedOptions() { + return impl.detectedOptions(); + } + + private final TypedOptions impl; + } + + + static final class OptionsBuilder { + + private OptionsBuilder(UntypedOptions options) { + impl = Objects.requireNonNull(options); + } + + Result convertedOptions() { + return impl.toTypedOptions().map(ConvertedOptionsBuilder::new); + } + + Options create() { + return impl; + } + + OptionsBuilder copyWithExcludes(Collection v) { + return new OptionsBuilder(impl.copyWithout(v)); + } + + List nonOptionArguments() { + return impl.nonOptionArguments(); + } + + List detectedOptions() { + return impl.detectedOptions(); + } + + private final UntypedOptions impl; + } + + + enum JOptSimpleErrorType { + + // jdk.internal.joptsimple.UnrecognizedOptionException + UNRECOGNIZED_OPTION(() -> { + new OptionParser(false).parse("--foo"); + }), + + // jdk.internal.joptsimple.OptionMissingRequiredArgumentException + OPTION_MISSING_REQUIRED_ARGUMENT(() -> { + var parser = new OptionParser(false); + parser.accepts("foo").withRequiredArg(); + parser.parse("--foo"); + }), + ; + + JOptSimpleErrorType(Runnable initializer) { + try { + initializer.run(); + // Should never get to this point as the above line is expected to throw + // an exception of type `jdk.internal.joptsimple.OptionException`. + throw new AssertionError(); + } catch (jdk.internal.joptsimple.OptionException ex) { + type = ex.getClass(); + } + } + + private final Class type; + } + + + record JOptSimpleError(JOptSimpleErrorType type, OptionName optionName) { + + JOptSimpleError { + Objects.requireNonNull(type); + Objects.requireNonNull(optionName); + } + + static JOptSimpleError create(jdk.internal.joptsimple.OptionException ex) { + var optionName = OptionName.of(ex.options().getFirst()); + return Stream.of(JOptSimpleErrorType.values()).filter(v -> { + return v.type.isInstance(ex); + }).findFirst().map(v -> { + return new JOptSimpleError(v, optionName); + }).orElseThrow(); + } + } + + + private record JOptSimpleParser( + OptionParser parser, + Map> optionMap, + Optional> jOptSimpleParserErrorHandler) { + + private JOptSimpleParser { + Objects.requireNonNull(parser); + Objects.requireNonNull(optionMap); + Objects.requireNonNull(jOptSimpleParserErrorHandler); + } + + Result parse(String... args) { + return applyParser(parser, args).map(optionSet -> { + final OptionSet mergerOptionSet; + if (optionMap.values().stream().allMatch(spec -> spec.names().size() == 1)) { + // No specs with multiple names, merger not needed. + mergerOptionSet = optionSet; + } else { + final var parser2 = createOptionParser(); + final var optionSpecApplier = new OptionSpecApplier(); + for (final var spec : optionMap.values()) { + optionSpecApplier.applyToParser(parser2, spec); + } + + mergerOptionSet = parser2.parse(args); + } + return new OptionsBuilder(new UntypedOptions(optionSet, mergerOptionSet, optionMap)); + }); + } + + static JOptSimpleParser create(Iterable