From 021a2f3b716fd233f855be7968df25bdbd5105f1 Mon Sep 17 00:00:00 2001 From: Goetz Lindenmaier Date: Wed, 3 Jun 2026 07:19:17 +0000 Subject: [PATCH 01/86] 8385507: Bump update version for OpenJDK: jdk-25.0.5 Reviewed-by: mdoerr --- .jcheck/conf | 2 +- make/conf/version-numbers.conf | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.jcheck/conf b/.jcheck/conf index 84e19c1d09ba..35ce4c49590f 100644 --- a/.jcheck/conf +++ b/.jcheck/conf @@ -1,7 +1,7 @@ [general] project=jdk-updates jbs=JDK -version=25.0.4 +version=25.0.5 [checks] error=author,committer,reviewers,merge,issues,executable,symlink,message,hg-tag,whitespace,problemlists,copyright diff --git a/make/conf/version-numbers.conf b/make/conf/version-numbers.conf index 46b6c430f542..ae507960576e 100644 --- a/make/conf/version-numbers.conf +++ b/make/conf/version-numbers.conf @@ -28,12 +28,12 @@ DEFAULT_VERSION_FEATURE=25 DEFAULT_VERSION_INTERIM=0 -DEFAULT_VERSION_UPDATE=4 +DEFAULT_VERSION_UPDATE=5 DEFAULT_VERSION_PATCH=0 DEFAULT_VERSION_EXTRA1=0 DEFAULT_VERSION_EXTRA2=0 DEFAULT_VERSION_EXTRA3=0 -DEFAULT_VERSION_DATE=2026-07-21 +DEFAULT_VERSION_DATE=2026-10-20 DEFAULT_VERSION_CLASSFILE_MAJOR=69 # "`$EXPR $DEFAULT_VERSION_FEATURE + 44`" DEFAULT_VERSION_CLASSFILE_MINOR=0 DEFAULT_VERSION_DOCS_API_SINCE=11 From 12b4515a03e6a28e17b0d6596027835ccb1b8837 Mon Sep 17 00:00:00 2001 From: Severin Gehwolf Date: Wed, 3 Jun 2026 09:34:45 +0000 Subject: [PATCH 02/86] 8247690: RunTest does not support running of JTREG manual tests Reviewed-by: phh Backport-of: f125c76f5b53d90a09f58c22d6def7d843feaa50 --- doc/testing.html | 2 ++ doc/testing.md | 4 ++++ make/RunTests.gmk | 11 +++++++++-- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/doc/testing.html b/doc/testing.html index b9d1f4ed22f0..13fff5f885b7 100644 --- a/doc/testing.html +++ b/doc/testing.html @@ -523,6 +523,8 @@

REPEAT_COUNT

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 bb56c05c295b..537560435202 100644 --- a/doc/testing.md +++ b/doc/testing.md @@ -499,6 +499,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 60ae1bd4763c..23cdcf57ec21 100644 --- a/make/RunTests.gmk +++ b/make/RunTests.gmk @@ -205,7 +205,8 @@ $(eval $(call SetTestOpt,AOT_JDK,JTREG)) $(eval $(call ParseKeywordVariable, JTREG, \ SINGLE_KEYWORDS := JOBS TIMEOUT_FACTOR FAILURE_HANDLER_TIMEOUT \ TEST_MODE ASSERT VERBOSE RETAIN TEST_THREAD_FACTORY MAX_MEM RUN_PROBLEM_LISTS \ - RETRY_COUNT REPEAT_COUNT MAX_OUTPUT REPORT AOT_JDK $(CUSTOM_JTREG_SINGLE_KEYWORDS), \ + RETRY_COUNT REPEAT_COUNT MAX_OUTPUT REPORT AOT_JDK MANUAL \ + $(CUSTOM_JTREG_SINGLE_KEYWORDS), \ STRING_KEYWORDS := OPTIONS JAVA_OPTIONS VM_OPTIONS KEYWORDS \ EXTRA_PROBLEM_LISTS LAUNCHER_OPTIONS \ $(CUSTOM_JTREG_STRING_KEYWORDS), \ @@ -901,7 +902,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 cab0f954be5a1332151bcb49d5f6afc453d29690 Mon Sep 17 00:00:00 2001 From: Frederic Thevenet Date: Wed, 3 Jun 2026 10:07:33 +0000 Subject: [PATCH 03/86] 8362884: [GCC static analyzer] unix NetworkInterface.c addif leak on early returns Backport-of: a2da75a6b69f56be41741bffba2c6874a93dfa40 --- .../unix/native/libnet/NetworkInterface.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/java.base/unix/native/libnet/NetworkInterface.c b/src/java.base/unix/native/libnet/NetworkInterface.c index 61267ec13d53..ceb5dd5f7512 100644 --- a/src/java.base/unix/native/libnet/NetworkInterface.c +++ b/src/java.base/unix/native/libnet/NetworkInterface.c @@ -58,10 +58,14 @@ #endif #define CHECKED_MALLOC3(_pointer, _type, _size) \ + CHECKED_MALLOC4(_pointer, _type, _size, {}) + +#define CHECKED_MALLOC4(_pointer, _type, _size, _onFailure) \ do { \ _pointer = (_type)malloc(_size); \ if (_pointer == NULL) { \ JNU_ThrowOutOfMemoryError(env, "Native heap allocation failed"); \ + do _onFailure while (0); \ return ifs; /* return untouched list */ \ } \ } while(0) @@ -995,7 +999,7 @@ static netif *addif(JNIEnv *env, int sock, const char *if_name, netif *ifs, // If "new" then create a netif structure and insert it into the list. if (currif == NULL) { - CHECKED_MALLOC3(currif, netif *, sizeof(netif) + IFNAMESIZE); + CHECKED_MALLOC4(currif, netif *, sizeof(netif) + IFNAMESIZE, { free(addrP); }); currif->name = (char *)currif + sizeof(netif); strncpy(currif->name, name, IFNAMESIZE); currif->name[IFNAMESIZE - 1] = '\0'; @@ -1027,7 +1031,10 @@ static netif *addif(JNIEnv *env, int sock, const char *if_name, netif *ifs, } if (currif == NULL) { - CHECKED_MALLOC3(currif, netif *, sizeof(netif) + IFNAMESIZE); + CHECKED_MALLOC4(currif, netif *, sizeof(netif) + IFNAMESIZE, { + free(addrP); + free(parent); + }); currif->name = (char *)currif + sizeof(netif); strncpy(currif->name, vname, IFNAMESIZE); currif->name[IFNAMESIZE - 1] = '\0'; @@ -1039,7 +1046,11 @@ static netif *addif(JNIEnv *env, int sock, const char *if_name, netif *ifs, parent->childs = currif; } - CHECKED_MALLOC3(tmpaddr, netaddr *, sizeof(netaddr) + 2 * addr_size); + CHECKED_MALLOC4(tmpaddr, netaddr *, sizeof(netaddr) + 2 * addr_size, { + free(addrP); + free(parent); + free(currif); + }); memcpy(tmpaddr, addrP, sizeof(netaddr)); if (addrP->addr != NULL) { tmpaddr->addr = (struct sockaddr *) From 3729e033c57f6eda7bea0a4f792a454fc6597e44 Mon Sep 17 00:00:00 2001 From: Severin Gehwolf Date: Wed, 3 Jun 2026 12:43:19 +0000 Subject: [PATCH 04/86] 8385584: CAInterop.java#buypassclass3ca fails with Intermediate Root CA not found in the chain Backport-of: 4a0a8587dbc0a132d763f7305e595e05e6b2e2e6 --- .../cert/CertPathValidator/certification/CAInterop.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/jdk/security/infra/java/security/cert/CertPathValidator/certification/CAInterop.java b/test/jdk/security/infra/java/security/cert/CertPathValidator/certification/CAInterop.java index 4ce583680023..c6f5dc9aa0b4 100644 --- a/test/jdk/security/infra/java/security/cert/CertPathValidator/certification/CAInterop.java +++ b/test/jdk/security/infra/java/security/cert/CertPathValidator/certification/CAInterop.java @@ -607,8 +607,8 @@ private CATestURLs getTestURLs(String alias) { new CATestURLs("https://valid.business.ca22.ssl.buypass.no", "https://revoked.business.ca22.ssl.buypass.no"); case "buypassclass3ca" -> - new CATestURLs("https://valid.qcevident.ca23.ssl.buypass.no", - "https://revoked.qcevident.ca23.ssl.buypass.no"); + new CATestURLs("https://valid.evident.ca23.ssl.buypass.no", + "https://revoked.evident.ca23.ssl.buypass.no"); case "comodorsaca" -> new CATestURLs("https://comodorsacertificationauthority-ev.comodoca.com", From b0aebfd297df632fecf3c4027b907519d2de4bbf Mon Sep 17 00:00:00 2001 From: Severin Gehwolf Date: Wed, 3 Jun 2026 12:44:13 +0000 Subject: [PATCH 05/86] 8374886: CAInterop.java#microsoftrsa2017 test fails as EE certificate does not specify OCSP responder Backport-of: 08ecb87b3d716fbd4704a3cb64cf265f3f427ec9 --- .../cert/CertPathValidator/certification/CAInterop.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/jdk/security/infra/java/security/cert/CertPathValidator/certification/CAInterop.java b/test/jdk/security/infra/java/security/cert/CertPathValidator/certification/CAInterop.java index c6f5dc9aa0b4..15fcb2fb04fd 100644 --- a/test/jdk/security/infra/java/security/cert/CertPathValidator/certification/CAInterop.java +++ b/test/jdk/security/infra/java/security/cert/CertPathValidator/certification/CAInterop.java @@ -290,9 +290,8 @@ * @summary Interoperability tests with Microsoft TLS root CAs * @library /test/lib * @build jtreg.SkippedException ValidatePathWithURL CAInterop - * @run main/othervm/manual -Djava.security.debug=certpath,ocsp CAInterop microsoftrsa2017 OCSP - * @run main/othervm/manual -Djava.security.debug=certpath,ocsp -Dcom.sun.security.ocsp.useget=false CAInterop microsoftrsa2017 OCSP - * @run main/othervm/manual -Djava.security.debug=certpath CAInterop microsoftrsa2017 CRL + * @run main/othervm/manual -Djava.security.debug=certpath,ocsp CAInterop microsoftrsa2017 DEFAULT + * @run main/othervm/manual -Djava.security.debug=certpath,ocsp -Dcom.sun.security.ocsp.useget=false CAInterop microsoftrsa2017 DEFAULT */ /* From fcc82d095320fc54c35e1837898223a43478688e Mon Sep 17 00:00:00 2001 From: Mohamed Issa Date: Thu, 4 Jun 2026 17:59:39 +0000 Subject: [PATCH 06/86] 8360116: Add support for AVX10 floating point minmax instruction Backport-of: 5e30bf68353d989aadc2d8176181226b2debd283 --- src/hotspot/cpu/x86/assembler_x86.cpp | 108 ++++++++ src/hotspot/cpu/x86/assembler_x86.hpp | 22 ++ src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp | 43 ++- src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp | 3 + src/hotspot/cpu/x86/macroAssembler_x86.cpp | 16 ++ src/hotspot/cpu/x86/x86.ad | 251 +++++++++++++++--- src/hotspot/cpu/x86/x86_64.ad | 60 ++++- 7 files changed, 461 insertions(+), 42 deletions(-) diff --git a/src/hotspot/cpu/x86/assembler_x86.cpp b/src/hotspot/cpu/x86/assembler_x86.cpp index cd4daf146397..3f1140c937ba 100644 --- a/src/hotspot/cpu/x86/assembler_x86.cpp +++ b/src/hotspot/cpu/x86/assembler_x86.cpp @@ -8222,6 +8222,14 @@ void Assembler::vmaxsh(XMMRegister dst, XMMRegister nds, XMMRegister src) { emit_int16(0x5F, (0xC0 | encode)); } +void Assembler::eminmaxsh(XMMRegister dst, XMMRegister nds, XMMRegister src, int imm8) { + assert(VM_Version::supports_avx10_2(), ""); + InstructionAttr attributes(AVX_128bit, /* vex_w */ false, /* legacy_mode */ false, /* no_mask_reg */ true, /* uses_vl */ false); + attributes.set_is_evex_instruction(); + int encode = vex_prefix_and_encode(dst->encoding(), nds->encoding(), src->encoding(), VEX_SIMD_NONE, VEX_OPCODE_0F_3A, &attributes); + emit_int24(0x53, (0xC0 | encode), imm8); +} + void Assembler::vminsh(XMMRegister dst, XMMRegister nds, XMMRegister src) { assert(VM_Version::supports_avx512_fp16(), "requires AVX512-FP16"); InstructionAttr attributes(AVX_128bit, /* vex_w */ false, /* legacy_mode */ false, /* no_mask_reg */ true, /* uses_vl */ false); @@ -8736,12 +8744,68 @@ void Assembler::vmaxps(XMMRegister dst, XMMRegister nds, XMMRegister src, int ve emit_int16(0x5F, (0xC0 | encode)); } +void Assembler::evminmaxps(XMMRegister dst, KRegister mask, XMMRegister nds, XMMRegister src, bool merge, int imm8, int vector_len) { + assert(VM_Version::supports_avx10_2(), ""); + InstructionAttr attributes(vector_len, /* vex_w */ false, /* legacy_mode */ false, /* no_mask_reg */ false, /* uses_vl */ true); + attributes.set_is_evex_instruction(); + attributes.set_embedded_opmask_register_specifier(mask); + if (merge) { + attributes.reset_is_clear_context(); + } + int encode = vex_prefix_and_encode(dst->encoding(), nds->encoding(), src->encoding(), VEX_SIMD_66, VEX_OPCODE_0F_3A, &attributes); + emit_int24(0x52, (0xC0 | encode), imm8); +} + +void Assembler::evminmaxps(XMMRegister dst, KRegister mask, XMMRegister nds, Address src, bool merge, int imm8, int vector_len) { + assert(VM_Version::supports_avx10_2(), ""); + InstructionMark im(this); + InstructionAttr attributes(vector_len, /* vex_w */ false, /* legacy_mode */ false, /* no_mask_reg */ false, /* uses_vl */ true); + attributes.set_is_evex_instruction(); + attributes.set_embedded_opmask_register_specifier(mask); + attributes.set_address_attributes(/* tuple_type */ EVEX_FV, /* input_size_in_bits */ EVEX_NObit); + if (merge) { + attributes.reset_is_clear_context(); + } + vex_prefix(src, nds->encoding(), dst->encoding(), VEX_SIMD_66, VEX_OPCODE_0F_3A, &attributes); + emit_int8(0x52); + emit_operand(dst, src, 0); + emit_int8(imm8); +} + void Assembler::maxpd(XMMRegister dst, XMMRegister src) { InstructionAttr attributes(AVX_128bit, /* rex_w */ false, /* legacy_mode */ false, /* no_mask_reg */ true, /* uses_vl */ true); int encode = simd_prefix_and_encode(dst, xnoreg, src, VEX_SIMD_66, VEX_OPCODE_0F, &attributes); emit_int16(0x5F, (0xC0 | encode)); } +void Assembler::evminmaxpd(XMMRegister dst, KRegister mask, XMMRegister nds, XMMRegister src, bool merge, int imm8, int vector_len) { + assert(VM_Version::supports_avx10_2(), ""); + InstructionAttr attributes(vector_len, /* vex_w */ true, /* legacy_mode */ false, /* no_mask_reg */ false,/* uses_vl */ true); + attributes.set_is_evex_instruction(); + attributes.set_embedded_opmask_register_specifier(mask); + if (merge) { + attributes.reset_is_clear_context(); + } + int encode = vex_prefix_and_encode(dst->encoding(), nds->encoding(), src->encoding(), VEX_SIMD_66, VEX_OPCODE_0F_3A, &attributes); + emit_int24(0x52, (0xC0 | encode), imm8); +} + +void Assembler::evminmaxpd(XMMRegister dst, KRegister mask, XMMRegister nds, Address src, bool merge, int imm8, int vector_len) { + assert(VM_Version::supports_avx10_2(), ""); + InstructionMark im(this); + InstructionAttr attributes(vector_len, /* vex_w */ true, /* legacy_mode */ false, /* no_mask_reg */ false, /* uses_vl */ true); + attributes.set_is_evex_instruction(); + attributes.set_embedded_opmask_register_specifier(mask); + attributes.set_address_attributes(/* tuple_type */ EVEX_FV, /* input_size_in_bits */ EVEX_NObit); + if (merge) { + attributes.reset_is_clear_context(); + } + vex_prefix(src, nds->encoding(), dst->encoding(), VEX_SIMD_66, VEX_OPCODE_0F_3A, &attributes); + emit_int8(0x52); + emit_operand(dst, src, 0); + emit_int8(imm8); +} + void Assembler::vmaxpd(XMMRegister dst, XMMRegister nds, XMMRegister src, int vector_len) { assert(vector_len >= AVX_512bit ? VM_Version::supports_evex() : VM_Version::supports_avx(), ""); InstructionAttr attributes(vector_len, /* vex_w */true, /* legacy_mode */ false, /* no_mask_reg */ true, /* uses_vl */ true); @@ -13130,6 +13194,14 @@ void Assembler::vminss(XMMRegister dst, XMMRegister nds, XMMRegister src) { emit_int16(0x5D, (0xC0 | encode)); } +void Assembler::eminmaxss(XMMRegister dst, XMMRegister nds, XMMRegister src, int imm8) { + assert(VM_Version::supports_avx10_2(), ""); + InstructionAttr attributes(AVX_128bit, /* vex_w */ false, /* legacy_mode */ false, /* no_mask_reg */ true, /* uses_vl */ false); + attributes.set_is_evex_instruction(); + int encode = vex_prefix_and_encode(dst->encoding(), nds->encoding(), src->encoding(), VEX_SIMD_66, VEX_OPCODE_0F_3A, &attributes); + emit_int24(0x53, (0xC0 | encode), imm8); +} + void Assembler::vminsd(XMMRegister dst, XMMRegister nds, XMMRegister src) { assert(VM_Version::supports_avx(), ""); InstructionAttr attributes(AVX_128bit, /* vex_w */ VM_Version::supports_evex(), /* legacy_mode */ false, /* no_mask_reg */ true, /* uses_vl */ false); @@ -13138,6 +13210,14 @@ void Assembler::vminsd(XMMRegister dst, XMMRegister nds, XMMRegister src) { emit_int16(0x5D, (0xC0 | encode)); } +void Assembler::eminmaxsd(XMMRegister dst, XMMRegister nds, XMMRegister src, int imm8) { + assert(VM_Version::supports_avx10_2(), ""); + InstructionAttr attributes(AVX_128bit, /* vex_w */ true, /* legacy_mode */ false, /* no_mask_reg */ true, /* uses_vl */ false); + attributes.set_is_evex_instruction(); + int encode = vex_prefix_and_encode(dst->encoding(), nds->encoding(), src->encoding(), VEX_SIMD_66, VEX_OPCODE_0F_3A, &attributes); + emit_int24(0x53, (0xC0 | encode), imm8); +} + void Assembler::vcmppd(XMMRegister dst, XMMRegister nds, XMMRegister src, int cop, int vector_len) { assert(VM_Version::supports_avx(), ""); assert(vector_len <= AVX_256bit, ""); @@ -16506,6 +16586,34 @@ void Assembler::evminph(XMMRegister dst, XMMRegister nds, Address src, int vecto emit_operand(dst, src, 0); } +void Assembler::evminmaxph(XMMRegister dst, KRegister mask, XMMRegister nds, XMMRegister src, bool merge, int imm8, int vector_len) { + assert(VM_Version::supports_avx10_2(), ""); + InstructionAttr attributes(vector_len, /* vex_w */ false, /* legacy_mode */ false, /* no_mask_reg */ false,/* uses_vl */ true); + attributes.set_is_evex_instruction(); + attributes.set_embedded_opmask_register_specifier(mask); + if (merge) { + attributes.reset_is_clear_context(); + } + int encode = vex_prefix_and_encode(dst->encoding(), nds->encoding(), src->encoding(), VEX_SIMD_NONE, VEX_OPCODE_0F_3A, &attributes); + emit_int24(0x52, (0xC0 | encode), imm8); +} + +void Assembler::evminmaxph(XMMRegister dst, KRegister mask, XMMRegister nds, Address src, bool merge, int imm8, int vector_len) { + assert(VM_Version::supports_avx10_2(), ""); + InstructionMark im(this); + InstructionAttr attributes(vector_len, /* vex_w */ false, /* legacy_mode */ false, /* no_mask_reg */ false, /* uses_vl */ true); + attributes.set_is_evex_instruction(); + attributes.set_embedded_opmask_register_specifier(mask); + if (merge) { + attributes.reset_is_clear_context(); + } + attributes.set_address_attributes(/* tuple_type */ EVEX_FV, /* input_size_in_bits */ EVEX_NObit); + vex_prefix(src, nds->encoding(), dst->encoding(), VEX_SIMD_NONE, VEX_OPCODE_0F_3A, &attributes); + emit_int8(0x52); + emit_operand(dst, src, 0); + emit_int8(imm8); +} + void Assembler::evmaxph(XMMRegister dst, XMMRegister nds, XMMRegister src, int vector_len) { assert(VM_Version::supports_avx512_fp16(), "requires AVX512-FP16"); assert(vector_len == Assembler::AVX_512bit || VM_Version::supports_avx512vl(), ""); diff --git a/src/hotspot/cpu/x86/assembler_x86.hpp b/src/hotspot/cpu/x86/assembler_x86.hpp index 28e0cde21578..99dade412b29 100644 --- a/src/hotspot/cpu/x86/assembler_x86.hpp +++ b/src/hotspot/cpu/x86/assembler_x86.hpp @@ -441,6 +441,17 @@ class InstructionAttr; // See fxsave and xsave(EVEX enabled) documentation for layout const int FPUStateSizeInWords = 2688 / wordSize; + +// AVX10 new minmax instruction control mask encoding. +// +// imm8[4] = 0 (please refer to Table 11.1 of section 11.2 of AVX10 manual[1] for details) +// imm8[3:2] (sign control) = 01 (select sign, please refer to Table 11.5 of section 11.2 of AVX10 manual[1] for details) +// imm8[1:0] = 00 (min) / 01 (max) +// +// [1] https://www.intel.com/content/www/us/en/content-details/856721/intel-advanced-vector-extensions-10-2-intel-avx10-2-architecture-specification.html?wapkw=AVX10 +const int AVX10_MINMAX_MAX_COMPARE_SIGN = 0x5; +const int AVX10_MINMAX_MIN_COMPARE_SIGN = 0x4; + // The Intel x86/Amd64 Assembler: Pure assembler doing NO optimizations on the instruction // level (e.g. mov rax, 0 is not translated into xor rax, rax!); i.e., what you write // is what you get. The Assembler is generating code into a CodeBuffer. @@ -2752,6 +2763,17 @@ class Assembler : public AbstractAssembler { void minpd(XMMRegister dst, XMMRegister src); void vminpd(XMMRegister dst, XMMRegister src1, XMMRegister src2, int vector_len); + // AVX10.2 floating point minmax instructions + void eminmaxsh(XMMRegister dst, XMMRegister nds, XMMRegister src, int imm8); + void eminmaxss(XMMRegister dst, XMMRegister nds, XMMRegister src, int imm8); + void eminmaxsd(XMMRegister dst, XMMRegister nds, XMMRegister src, int imm8); + void evminmaxph(XMMRegister dst, KRegister mask, XMMRegister nds, XMMRegister src, bool merge, int imm8, int vector_len); + void evminmaxph(XMMRegister dst, KRegister mask, XMMRegister nds, Address src, bool merge, int imm8, int vector_len); + void evminmaxps(XMMRegister dst, KRegister mask, XMMRegister nds, XMMRegister src, bool merge, int imm8, int vector_len); + void evminmaxps(XMMRegister dst, KRegister mask, XMMRegister nds, Address src, bool merge, int imm8, int vector_len); + void evminmaxpd(XMMRegister dst, KRegister mask, XMMRegister nds, XMMRegister src, bool merge, int imm8, int vector_len); + void evminmaxpd(XMMRegister dst, KRegister mask, XMMRegister nds, Address src, bool merge, int imm8, int vector_len); + // Maximum of packed integers void pmaxsb(XMMRegister dst, XMMRegister src); void vpmaxsb(XMMRegister dst, XMMRegister src1, XMMRegister src2, int vector_len); diff --git a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp index 4317bb3d0182..82b8e275371f 100644 --- a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp @@ -1238,6 +1238,21 @@ void C2_MacroAssembler::evminmax_fp(int opcode, BasicType elem_bt, } } +void C2_MacroAssembler::vminmax_fp(int opc, BasicType elem_bt, XMMRegister dst, KRegister mask, + XMMRegister src1, XMMRegister src2, int vlen_enc) { + assert(opc == Op_MinV || opc == Op_MinReductionV || + opc == Op_MaxV || opc == Op_MaxReductionV, "sanity"); + + int imm8 = (opc == Op_MinV || opc == Op_MinReductionV) ? AVX10_MINMAX_MIN_COMPARE_SIGN + : AVX10_MINMAX_MAX_COMPARE_SIGN; + if (elem_bt == T_FLOAT) { + evminmaxps(dst, mask, src1, src2, true, imm8, vlen_enc); + } else { + assert(elem_bt == T_DOUBLE, ""); + evminmaxpd(dst, mask, src1, src2, true, imm8, vlen_enc); + } +} + // Float/Double signum void C2_MacroAssembler::signum_fp(int opcode, XMMRegister dst, XMMRegister zero, XMMRegister one) { assert(opcode == Op_SignumF || opcode == Op_SignumD, "sanity"); @@ -2545,12 +2560,21 @@ void C2_MacroAssembler::reduceFloatMinMax(int opcode, int vlen, bool is_dst_vali } else { // i = [0,1] vpermilps(wtmp, wsrc, permconst[i], vlen_enc); } - vminmax_fp(opcode, T_FLOAT, wdst, wtmp, wsrc, tmp, atmp, btmp, vlen_enc); + + if (VM_Version::supports_avx10_2()) { + vminmax_fp(opcode, T_FLOAT, wdst, k0, wtmp, wsrc, vlen_enc); + } else { + vminmax_fp(opcode, T_FLOAT, wdst, wtmp, wsrc, tmp, atmp, btmp, vlen_enc); + } wsrc = wdst; vlen_enc = Assembler::AVX_128bit; } if (is_dst_valid) { - vminmax_fp(opcode, T_FLOAT, dst, wdst, dst, tmp, atmp, btmp, Assembler::AVX_128bit); + if (VM_Version::supports_avx10_2()) { + vminmax_fp(opcode, T_FLOAT, dst, k0, wdst, dst, Assembler::AVX_128bit); + } else { + vminmax_fp(opcode, T_FLOAT, dst, wdst, dst, tmp, atmp, btmp, Assembler::AVX_128bit); + } } } @@ -2576,12 +2600,23 @@ void C2_MacroAssembler::reduceDoubleMinMax(int opcode, int vlen, bool is_dst_val assert(i == 0, "%d", i); vpermilpd(wtmp, wsrc, 1, vlen_enc); } - vminmax_fp(opcode, T_DOUBLE, wdst, wtmp, wsrc, tmp, atmp, btmp, vlen_enc); + + if (VM_Version::supports_avx10_2()) { + vminmax_fp(opcode, T_DOUBLE, wdst, k0, wtmp, wsrc, vlen_enc); + } else { + vminmax_fp(opcode, T_DOUBLE, wdst, wtmp, wsrc, tmp, atmp, btmp, vlen_enc); + } + wsrc = wdst; vlen_enc = Assembler::AVX_128bit; } + if (is_dst_valid) { - vminmax_fp(opcode, T_DOUBLE, dst, wdst, dst, tmp, atmp, btmp, Assembler::AVX_128bit); + if (VM_Version::supports_avx10_2()) { + vminmax_fp(opcode, T_DOUBLE, dst, k0, wdst, dst, Assembler::AVX_128bit); + } else { + vminmax_fp(opcode, T_DOUBLE, dst, wdst, dst, tmp, atmp, btmp, Assembler::AVX_128bit); + } } } diff --git a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp index 713eb73d68f3..ee6fecb9f885 100644 --- a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp +++ b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp @@ -72,6 +72,9 @@ XMMRegister tmp, XMMRegister atmp, XMMRegister btmp, int vlen_enc); + void vminmax_fp(int opc, BasicType elem_bt, XMMRegister dst, KRegister mask, + XMMRegister src1, XMMRegister src2, int vlen_enc); + void vpuminmaxq(int opcode, XMMRegister dst, XMMRegister src1, XMMRegister src2, XMMRegister xtmp1, XMMRegister xtmp2, int vlen_enc); void evminmax_fp(int opcode, BasicType elem_bt, diff --git a/src/hotspot/cpu/x86/macroAssembler_x86.cpp b/src/hotspot/cpu/x86/macroAssembler_x86.cpp index ac11ed7ee9f3..46a1a103d180 100644 --- a/src/hotspot/cpu/x86/macroAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/macroAssembler_x86.cpp @@ -8866,6 +8866,10 @@ void MacroAssembler::evpmins(BasicType type, XMMRegister dst, KRegister mask, XM evpminsd(dst, mask, nds, src, merge, vector_len); break; case T_LONG: evpminsq(dst, mask, nds, src, merge, vector_len); break; + case T_FLOAT: + evminmaxps(dst, mask, nds, src, merge, AVX10_MINMAX_MIN_COMPARE_SIGN, vector_len); break; + case T_DOUBLE: + evminmaxpd(dst, mask, nds, src, merge, AVX10_MINMAX_MIN_COMPARE_SIGN, vector_len); break; default: fatal("Unexpected type argument %s", type2name(type)); break; } @@ -8881,6 +8885,10 @@ void MacroAssembler::evpmaxs(BasicType type, XMMRegister dst, KRegister mask, XM evpmaxsd(dst, mask, nds, src, merge, vector_len); break; case T_LONG: evpmaxsq(dst, mask, nds, src, merge, vector_len); break; + case T_FLOAT: + evminmaxps(dst, mask, nds, src, merge, AVX10_MINMAX_MAX_COMPARE_SIGN, vector_len); break; + case T_DOUBLE: + evminmaxpd(dst, mask, nds, src, merge, AVX10_MINMAX_MAX_COMPARE_SIGN, vector_len); break; default: fatal("Unexpected type argument %s", type2name(type)); break; } @@ -8896,6 +8904,10 @@ void MacroAssembler::evpmins(BasicType type, XMMRegister dst, KRegister mask, XM evpminsd(dst, mask, nds, src, merge, vector_len); break; case T_LONG: evpminsq(dst, mask, nds, src, merge, vector_len); break; + case T_FLOAT: + evminmaxps(dst, mask, nds, src, merge, AVX10_MINMAX_MIN_COMPARE_SIGN, vector_len); break; + case T_DOUBLE: + evminmaxpd(dst, mask, nds, src, merge, AVX10_MINMAX_MIN_COMPARE_SIGN, vector_len); break; default: fatal("Unexpected type argument %s", type2name(type)); break; } @@ -8911,6 +8923,10 @@ void MacroAssembler::evpmaxs(BasicType type, XMMRegister dst, KRegister mask, XM evpmaxsd(dst, mask, nds, src, merge, vector_len); break; case T_LONG: evpmaxsq(dst, mask, nds, src, merge, vector_len); break; + case T_FLOAT: + evminmaxps(dst, mask, nds, src, merge, AVX10_MINMAX_MAX_COMPARE_SIGN, vector_len); break; + case T_DOUBLE: + evminmaxps(dst, mask, nds, src, merge, AVX10_MINMAX_MAX_COMPARE_SIGN, vector_len); break; default: fatal("Unexpected type argument %s", type2name(type)); break; } diff --git a/src/hotspot/cpu/x86/x86.ad b/src/hotspot/cpu/x86/x86.ad index 6ad6d0332b93..c45581942287 100644 --- a/src/hotspot/cpu/x86/x86.ad +++ b/src/hotspot/cpu/x86/x86.ad @@ -2024,7 +2024,7 @@ bool Matcher::match_rule_supported_vector_masked(int opcode, int vlen, BasicType if (is_subword_type(bt) && !VM_Version::supports_avx512bw()) { return false; // Implementation limitation } - if (is_floating_point_type(bt)) { + if (is_floating_point_type(bt) && !VM_Version::supports_avx10_2()) { return false; // Implementation limitation } return true; @@ -5293,9 +5293,9 @@ instruct mul_reduction64B(rRegI dst, rRegI src1, legVec src2, legVec vtmp1, legV //--------------------Min/Max Float Reduction -------------------- // Float Min Reduction -instruct minmax_reduction2F(legRegF dst, immF src1, legVec src2, legVec tmp, - legVec atmp, legVec btmp, legVec xmm_1, rFlagsReg cr) %{ - predicate(Matcher::vector_element_basic_type(n->in(2)) == T_FLOAT && +instruct minmax_reduction2F(legRegF dst, immF src1, legVec src2, legVec tmp, legVec atmp, + legVec btmp, legVec xmm_1, rFlagsReg cr) %{ + predicate(!VM_Version::supports_avx10_2() && Matcher::vector_element_basic_type(n->in(2)) == T_FLOAT && ((n->Opcode() == Op_MinReductionV && n->in(1)->bottom_type() == TypeF::POS_INF) || (n->Opcode() == Op_MaxReductionV && n->in(1)->bottom_type() == TypeF::NEG_INF)) && Matcher::vector_length(n->in(2)) == 2); @@ -5316,7 +5316,7 @@ instruct minmax_reduction2F(legRegF dst, immF src1, legVec src2, legVec tmp, instruct minmax_reductionF(legRegF dst, immF src1, legVec src2, legVec tmp, legVec atmp, legVec btmp, legVec xmm_0, legVec xmm_1, rFlagsReg cr) %{ - predicate(Matcher::vector_element_basic_type(n->in(2)) == T_FLOAT && + predicate(!VM_Version::supports_avx10_2() && Matcher::vector_element_basic_type(n->in(2)) == T_FLOAT && ((n->Opcode() == Op_MinReductionV && n->in(1)->bottom_type() == TypeF::POS_INF) || (n->Opcode() == Op_MaxReductionV && n->in(1)->bottom_type() == TypeF::NEG_INF)) && Matcher::vector_length(n->in(2)) >= 4); @@ -5335,9 +5335,9 @@ instruct minmax_reductionF(legRegF dst, immF src1, legVec src2, legVec tmp, legV ins_pipe( pipe_slow ); %} -instruct minmax_reduction2F_av(legRegF dst, legVec src, legVec tmp, - legVec atmp, legVec btmp, legVec xmm_1, rFlagsReg cr) %{ - predicate(Matcher::vector_element_basic_type(n->in(2)) == T_FLOAT && +instruct minmax_reduction2F_av(legRegF dst, legVec src, legVec tmp, legVec atmp, + legVec btmp, legVec xmm_1, rFlagsReg cr) %{ + predicate(!VM_Version::supports_avx10_2() && Matcher::vector_element_basic_type(n->in(2)) == T_FLOAT && Matcher::vector_length(n->in(2)) == 2); match(Set dst (MinReductionV dst src)); match(Set dst (MaxReductionV dst src)); @@ -5355,9 +5355,9 @@ instruct minmax_reduction2F_av(legRegF dst, legVec src, legVec tmp, %} -instruct minmax_reductionF_av(legRegF dst, legVec src, legVec tmp, - legVec atmp, legVec btmp, legVec xmm_0, legVec xmm_1, rFlagsReg cr) %{ - predicate(Matcher::vector_element_basic_type(n->in(2)) == T_FLOAT && +instruct minmax_reductionF_av(legRegF dst, legVec src, legVec tmp, legVec atmp, legVec btmp, + legVec xmm_0, legVec xmm_1, rFlagsReg cr) %{ + predicate(!VM_Version::supports_avx10_2() && Matcher::vector_element_basic_type(n->in(2)) == T_FLOAT && Matcher::vector_length(n->in(2)) >= 4); match(Set dst (MinReductionV dst src)); match(Set dst (MaxReductionV dst src)); @@ -5374,12 +5374,78 @@ instruct minmax_reductionF_av(legRegF dst, legVec src, legVec tmp, ins_pipe( pipe_slow ); %} +instruct minmax_reduction2F_avx10(regF dst, immF src1, vec src2, vec xtmp1) %{ + predicate(VM_Version::supports_avx10_2() && Matcher::vector_element_basic_type(n->in(2)) == T_FLOAT && + ((n->Opcode() == Op_MinReductionV && n->in(1)->bottom_type() == TypeF::POS_INF) || + (n->Opcode() == Op_MaxReductionV && n->in(1)->bottom_type() == TypeF::NEG_INF)) && + Matcher::vector_length(n->in(2)) == 2); + match(Set dst (MinReductionV src1 src2)); + match(Set dst (MaxReductionV src1 src2)); + effect(TEMP dst, TEMP xtmp1); + format %{ "vector_minmax_reduction $dst, $src1, $src2 \t; using $xtmp1 as TEMP" %} + ins_encode %{ + int opcode = this->ideal_Opcode(); + int vlen = Matcher::vector_length(this, $src2); + __ reduceFloatMinMax(opcode, vlen, false, $dst$$XMMRegister, $src2$$XMMRegister, + xnoreg, xnoreg, xnoreg, $xtmp1$$XMMRegister); + %} + ins_pipe( pipe_slow ); +%} + +instruct minmax_reductionF_avx10(regF dst, immF src1, vec src2, vec xtmp1, vec xtmp2) %{ + predicate(VM_Version::supports_avx10_2() && Matcher::vector_element_basic_type(n->in(2)) == T_FLOAT && + ((n->Opcode() == Op_MinReductionV && n->in(1)->bottom_type() == TypeF::POS_INF) || + (n->Opcode() == Op_MaxReductionV && n->in(1)->bottom_type() == TypeF::NEG_INF)) && + Matcher::vector_length(n->in(2)) >= 4); + match(Set dst (MinReductionV src1 src2)); + match(Set dst (MaxReductionV src1 src2)); + effect(TEMP dst, TEMP xtmp1, TEMP xtmp2); + format %{ "vector_minmax_reduction $dst, $src1, $src2 \t; using $xtmp1 and $xtmp2 as TEMP" %} + ins_encode %{ + int opcode = this->ideal_Opcode(); + int vlen = Matcher::vector_length(this, $src2); + __ reduceFloatMinMax(opcode, vlen, false, $dst$$XMMRegister, $src2$$XMMRegister, xnoreg, xnoreg, + xnoreg, $xtmp1$$XMMRegister, $xtmp2$$XMMRegister); + %} + ins_pipe( pipe_slow ); +%} + +instruct minmax_reduction2F_avx10_av(regF dst, vec src, vec xtmp1) %{ + predicate(VM_Version::supports_avx10_2() && Matcher::vector_element_basic_type(n->in(2)) == T_FLOAT && + Matcher::vector_length(n->in(2)) == 2); + match(Set dst (MinReductionV dst src)); + match(Set dst (MaxReductionV dst src)); + effect(TEMP dst, TEMP xtmp1); + format %{ "vector_minmax2F_reduction $dst, $src \t; using $xtmp1 as TEMP" %} + ins_encode %{ + int opcode = this->ideal_Opcode(); + int vlen = Matcher::vector_length(this, $src); + __ reduceFloatMinMax(opcode, vlen, true, $dst$$XMMRegister, $src$$XMMRegister, xnoreg, xnoreg, xnoreg, + $xtmp1$$XMMRegister); + %} + ins_pipe( pipe_slow ); +%} + +instruct minmax_reductionF_avx10_av(regF dst, vec src, vec xtmp1, vec xtmp2) %{ + predicate(VM_Version::supports_avx10_2() && Matcher::vector_element_basic_type(n->in(2)) == T_FLOAT && + Matcher::vector_length(n->in(2)) >= 4); + match(Set dst (MinReductionV dst src)); + match(Set dst (MaxReductionV dst src)); + effect(TEMP dst, TEMP xtmp1, TEMP xtmp2); + format %{ "vector_minmax2F_reduction $dst, $src \t; using $xtmp1 and $xtmp2 as TEMP" %} + ins_encode %{ + int opcode = this->ideal_Opcode(); + int vlen = Matcher::vector_length(this, $src); + __ reduceFloatMinMax(opcode, vlen, true, $dst$$XMMRegister, $src$$XMMRegister, xnoreg, xnoreg, xnoreg, + $xtmp1$$XMMRegister, $xtmp2$$XMMRegister); + %} + ins_pipe( pipe_slow ); +%} //--------------------Min Double Reduction -------------------- -instruct minmax_reduction2D(legRegD dst, immD src1, legVec src2, - legVec tmp1, legVec tmp2, legVec tmp3, legVec tmp4, // TEMPs - rFlagsReg cr) %{ - predicate(Matcher::vector_element_basic_type(n->in(2)) == T_DOUBLE && +instruct minmax_reduction2D(legRegD dst, immD src1, legVec src2, legVec tmp1, legVec tmp2, + legVec tmp3, legVec tmp4, rFlagsReg cr) %{ + predicate(!VM_Version::supports_avx10_2() && Matcher::vector_element_basic_type(n->in(2)) == T_DOUBLE && ((n->Opcode() == Op_MinReductionV && n->in(1)->bottom_type() == TypeD::POS_INF) || (n->Opcode() == Op_MaxReductionV && n->in(1)->bottom_type() == TypeD::NEG_INF)) && Matcher::vector_length(n->in(2)) == 2); @@ -5398,10 +5464,9 @@ instruct minmax_reduction2D(legRegD dst, immD src1, legVec src2, ins_pipe( pipe_slow ); %} -instruct minmax_reductionD(legRegD dst, immD src1, legVec src2, - legVec tmp1, legVec tmp2, legVec tmp3, legVec tmp4, legVec tmp5, // TEMPs - rFlagsReg cr) %{ - predicate(Matcher::vector_element_basic_type(n->in(2)) == T_DOUBLE && +instruct minmax_reductionD(legRegD dst, immD src1, legVec src2, legVec tmp1, legVec tmp2, + legVec tmp3, legVec tmp4, legVec tmp5, rFlagsReg cr) %{ + predicate(!VM_Version::supports_avx10_2() && Matcher::vector_element_basic_type(n->in(2)) == T_DOUBLE && ((n->Opcode() == Op_MinReductionV && n->in(1)->bottom_type() == TypeD::POS_INF) || (n->Opcode() == Op_MaxReductionV && n->in(1)->bottom_type() == TypeD::NEG_INF)) && Matcher::vector_length(n->in(2)) >= 4); @@ -5421,10 +5486,9 @@ instruct minmax_reductionD(legRegD dst, immD src1, legVec src2, %} -instruct minmax_reduction2D_av(legRegD dst, legVec src, - legVec tmp1, legVec tmp2, legVec tmp3, legVec tmp4, // TEMPs - rFlagsReg cr) %{ - predicate(Matcher::vector_element_basic_type(n->in(2)) == T_DOUBLE && +instruct minmax_reduction2D_av(legRegD dst, legVec src, legVec tmp1, legVec tmp2, + legVec tmp3, legVec tmp4, rFlagsReg cr) %{ + predicate(!VM_Version::supports_avx10_2() && Matcher::vector_element_basic_type(n->in(2)) == T_DOUBLE && Matcher::vector_length(n->in(2)) == 2); match(Set dst (MinReductionV dst src)); match(Set dst (MaxReductionV dst src)); @@ -5441,10 +5505,9 @@ instruct minmax_reduction2D_av(legRegD dst, legVec src, ins_pipe( pipe_slow ); %} -instruct minmax_reductionD_av(legRegD dst, legVec src, - legVec tmp1, legVec tmp2, legVec tmp3, legVec tmp4, legVec tmp5, // TEMPs - rFlagsReg cr) %{ - predicate(Matcher::vector_element_basic_type(n->in(2)) == T_DOUBLE && +instruct minmax_reductionD_av(legRegD dst, legVec src, legVec tmp1, legVec tmp2, legVec tmp3, + legVec tmp4, legVec tmp5, rFlagsReg cr) %{ + predicate(!VM_Version::supports_avx10_2() && Matcher::vector_element_basic_type(n->in(2)) == T_DOUBLE && Matcher::vector_length(n->in(2)) >= 4); match(Set dst (MinReductionV dst src)); match(Set dst (MaxReductionV dst src)); @@ -5461,6 +5524,75 @@ instruct minmax_reductionD_av(legRegD dst, legVec src, ins_pipe( pipe_slow ); %} +instruct minmax_reduction2D_avx10(regD dst, immD src1, vec src2, vec xtmp1) %{ + predicate(VM_Version::supports_avx10_2() && Matcher::vector_element_basic_type(n->in(2)) == T_DOUBLE && + ((n->Opcode() == Op_MinReductionV && n->in(1)->bottom_type() == TypeD::POS_INF) || + (n->Opcode() == Op_MaxReductionV && n->in(1)->bottom_type() == TypeD::NEG_INF)) && + Matcher::vector_length(n->in(2)) == 2); + match(Set dst (MinReductionV src1 src2)); + match(Set dst (MaxReductionV src1 src2)); + effect(TEMP dst, TEMP xtmp1); + format %{ "vector_minmax2D_reduction $dst, $src1, $src2 ; using $xtmp1 as TEMP" %} + ins_encode %{ + int opcode = this->ideal_Opcode(); + int vlen = Matcher::vector_length(this, $src2); + __ reduceDoubleMinMax(opcode, vlen, false, $dst$$XMMRegister, $src2$$XMMRegister, xnoreg, + xnoreg, xnoreg, $xtmp1$$XMMRegister); + %} + ins_pipe( pipe_slow ); +%} + +instruct minmax_reductionD_avx10(regD dst, immD src1, vec src2, vec xtmp1, vec xtmp2) %{ + predicate(VM_Version::supports_avx10_2() && Matcher::vector_element_basic_type(n->in(2)) == T_DOUBLE && + ((n->Opcode() == Op_MinReductionV && n->in(1)->bottom_type() == TypeD::POS_INF) || + (n->Opcode() == Op_MaxReductionV && n->in(1)->bottom_type() == TypeD::NEG_INF)) && + Matcher::vector_length(n->in(2)) >= 4); + match(Set dst (MinReductionV src1 src2)); + match(Set dst (MaxReductionV src1 src2)); + effect(TEMP dst, TEMP xtmp1, TEMP xtmp2); + format %{ "vector_minmaxD_reduction $dst, $src1, $src2 ; using $xtmp1 and $xtmp2 as TEMP" %} + ins_encode %{ + int opcode = this->ideal_Opcode(); + int vlen = Matcher::vector_length(this, $src2); + __ reduceDoubleMinMax(opcode, vlen, false, $dst$$XMMRegister, $src2$$XMMRegister, xnoreg, xnoreg, + xnoreg, $xtmp1$$XMMRegister, $xtmp2$$XMMRegister); + %} + ins_pipe( pipe_slow ); +%} + + +instruct minmax_reduction2D_av_avx10(regD dst, vec src, vec xtmp1) %{ + predicate(VM_Version::supports_avx10_2() && Matcher::vector_element_basic_type(n->in(2)) == T_DOUBLE && + Matcher::vector_length(n->in(2)) == 2); + match(Set dst (MinReductionV dst src)); + match(Set dst (MaxReductionV dst src)); + effect(TEMP dst, TEMP xtmp1); + format %{ "vector_minmax2D_reduction $dst, $src ; using $xtmp1 as TEMP" %} + ins_encode %{ + int opcode = this->ideal_Opcode(); + int vlen = Matcher::vector_length(this, $src); + __ reduceDoubleMinMax(opcode, vlen, true, $dst$$XMMRegister, $src$$XMMRegister, + xnoreg, xnoreg, xnoreg, $xtmp1$$XMMRegister); + %} + ins_pipe( pipe_slow ); +%} + +instruct minmax_reductionD_av_avx10(regD dst, vec src, vec xtmp1, vec xtmp2) %{ + predicate(VM_Version::supports_avx10_2() && Matcher::vector_element_basic_type(n->in(2)) == T_DOUBLE && + Matcher::vector_length(n->in(2)) >= 4); + match(Set dst (MinReductionV dst src)); + match(Set dst (MaxReductionV dst src)); + effect(TEMP dst, TEMP xtmp1, TEMP xtmp2); + format %{ "vector_minmaxD_reduction $dst, $src ; using $xtmp1 and $xtmp2 as TEMP" %} + ins_encode %{ + int opcode = this->ideal_Opcode(); + int vlen = Matcher::vector_length(this, $src); + __ reduceDoubleMinMax(opcode, vlen, true, $dst$$XMMRegister, $src$$XMMRegister, + xnoreg, xnoreg, xnoreg, $xtmp1$$XMMRegister, $xtmp2$$XMMRegister); + %} + ins_pipe( pipe_slow ); +%} + // ====================VECTOR ARITHMETIC======================================= // --------------------------------- ADD -------------------------------------- @@ -6347,9 +6479,25 @@ instruct vminmaxL_reg_evex(vec dst, vec src1, vec src2) %{ ins_pipe( pipe_slow ); %} +// Float/Double vector Min/Max +instruct minmaxFP_avx10_reg(vec dst, vec a, vec b) %{ + predicate(VM_Version::supports_avx10_2() && + is_floating_point_type(Matcher::vector_element_basic_type(n))); // T_FLOAT, T_DOUBLE + match(Set dst (MinV a b)); + match(Set dst (MaxV a b)); + format %{ "vector_minmaxFP $dst, $a, $b" %} + ins_encode %{ + int vlen_enc = vector_length_encoding(this); + int opcode = this->ideal_Opcode(); + BasicType elem_bt = Matcher::vector_element_basic_type(this); + __ vminmax_fp(opcode, elem_bt, $dst$$XMMRegister, k0, $a$$XMMRegister, $b$$XMMRegister, vlen_enc); + %} + ins_pipe( pipe_slow ); +%} + // Float/Double vector Min/Max instruct minmaxFP_reg(legVec dst, legVec a, legVec b, legVec tmp, legVec atmp, legVec btmp) %{ - predicate(Matcher::vector_length_in_bytes(n) <= 32 && + predicate(!VM_Version::supports_avx10_2() && Matcher::vector_length_in_bytes(n) <= 32 && is_floating_point_type(Matcher::vector_element_basic_type(n)) && // T_FLOAT, T_DOUBLE UseAVX > 0); match(Set dst (MinV a b)); @@ -6370,8 +6518,8 @@ instruct minmaxFP_reg(legVec dst, legVec a, legVec b, legVec tmp, legVec atmp, l ins_pipe( pipe_slow ); %} -instruct evminmaxFP_reg_eavx(vec dst, vec a, vec b, vec atmp, vec btmp, kReg ktmp) %{ - predicate(Matcher::vector_length_in_bytes(n) == 64 && +instruct evminmaxFP_reg_evex(vec dst, vec a, vec b, vec atmp, vec btmp, kReg ktmp) %{ + predicate(!VM_Version::supports_avx10_2() && Matcher::vector_length_in_bytes(n) == 64 && is_floating_point_type(Matcher::vector_element_basic_type(n))); // T_FLOAT, T_DOUBLE match(Set dst (MinV a b)); match(Set dst (MaxV a b)); @@ -10687,8 +10835,22 @@ instruct scalar_binOps_HF_reg(regF dst, regF src1, regF src2) ins_pipe(pipe_slow); %} +instruct scalar_minmax_HF_avx10_reg(regF dst, regF src1, regF src2) +%{ + predicate(VM_Version::supports_avx10_2()); + match(Set dst (MaxHF src1 src2)); + match(Set dst (MinHF src1 src2)); + format %{ "scalar_min_max_fp16 $dst, $src1, $src2" %} + ins_encode %{ + int function = this->ideal_Opcode() == Op_MinHF ? AVX10_MINMAX_MIN_COMPARE_SIGN : AVX10_MINMAX_MAX_COMPARE_SIGN; + __ eminmaxsh($dst$$XMMRegister, $src1$$XMMRegister, $src2$$XMMRegister, function); + %} + ins_pipe( pipe_slow ); +%} + instruct scalar_minmax_HF_reg(regF dst, regF src1, regF src2, kReg ktmp, regF xtmp1, regF xtmp2) %{ + predicate(!VM_Version::supports_avx10_2()); match(Set dst (MaxHF src1 src2)); match(Set dst (MinHF src1 src2)); effect(TEMP_DEF dst, TEMP ktmp, TEMP xtmp1, TEMP xtmp2); @@ -10788,8 +10950,37 @@ instruct vector_fma_HF_mem(vec dst, memory src1, vec src2) ins_pipe( pipe_slow ); %} +instruct vector_minmax_HF_avx10_mem(vec dst, vec src1, memory src2) +%{ + predicate(VM_Version::supports_avx10_2()); + match(Set dst (MinVHF src1 (VectorReinterpret (LoadVector src2)))); + match(Set dst (MaxVHF src1 (VectorReinterpret (LoadVector src2)))); + format %{ "vector_min_max_fp16_mem $dst, $src1, $src2" %} + ins_encode %{ + int vlen_enc = vector_length_encoding(this); + int function = this->ideal_Opcode() == Op_MinVHF ? AVX10_MINMAX_MIN_COMPARE_SIGN : AVX10_MINMAX_MAX_COMPARE_SIGN; + __ evminmaxph($dst$$XMMRegister, k0, $src1$$XMMRegister, $src2$$Address, true, function, vlen_enc); + %} + ins_pipe( pipe_slow ); +%} + +instruct vector_minmax_HF_avx10_reg(vec dst, vec src1, vec src2) +%{ + predicate(VM_Version::supports_avx10_2()); + match(Set dst (MinVHF src1 src2)); + match(Set dst (MaxVHF src1 src2)); + format %{ "vector_min_max_fp16 $dst, $src1, $src2" %} + ins_encode %{ + int vlen_enc = vector_length_encoding(this); + int function = this->ideal_Opcode() == Op_MinVHF ? AVX10_MINMAX_MIN_COMPARE_SIGN : AVX10_MINMAX_MAX_COMPARE_SIGN; + __ evminmaxph($dst$$XMMRegister, k0, $src1$$XMMRegister, $src2$$XMMRegister, true, function, vlen_enc); + %} + ins_pipe( pipe_slow ); +%} + instruct vector_minmax_HF_reg(vec dst, vec src1, vec src2, kReg ktmp, vec xtmp1, vec xtmp2) %{ + predicate(!VM_Version::supports_avx10_2()); match(Set dst (MinVHF src1 src2)); match(Set dst (MaxVHF src1 src2)); effect(TEMP_DEF dst, TEMP ktmp, TEMP xtmp1, TEMP xtmp2); diff --git a/src/hotspot/cpu/x86/x86_64.ad b/src/hotspot/cpu/x86/x86_64.ad index 7f8b7dbc9f3d..eb37837a14de 100644 --- a/src/hotspot/cpu/x86/x86_64.ad +++ b/src/hotspot/cpu/x86/x86_64.ad @@ -4446,9 +4446,20 @@ instruct loadD(regD dst, memory mem) ins_pipe(pipe_slow); // XXX %} +// max = java.lang.Math.max(float a, float b) +instruct maxF_avx10_reg(regF dst, regF a, regF b) %{ + predicate(VM_Version::supports_avx10_2()); + match(Set dst (MaxF a b)); + format %{ "maxF $dst, $a, $b" %} + ins_encode %{ + __ eminmaxss($dst$$XMMRegister, $a$$XMMRegister, $b$$XMMRegister, AVX10_MINMAX_MAX_COMPARE_SIGN); + %} + ins_pipe( pipe_slow ); +%} + // max = java.lang.Math.max(float a, float b) instruct maxF_reg(legRegF dst, legRegF a, legRegF b, legRegF tmp, legRegF atmp, legRegF btmp) %{ - predicate(UseAVX > 0 && !VLoopReductions::is_reduction(n)); + predicate(!VM_Version::supports_avx10_2() && UseAVX > 0 && !VLoopReductions::is_reduction(n)); match(Set dst (MaxF a b)); effect(USE a, USE b, TEMP tmp, TEMP atmp, TEMP btmp); format %{ "maxF $dst, $a, $b \t! using $tmp, $atmp and $btmp as TEMP" %} @@ -4459,7 +4470,7 @@ instruct maxF_reg(legRegF dst, legRegF a, legRegF b, legRegF tmp, legRegF atmp, %} instruct maxF_reduction_reg(legRegF dst, legRegF a, legRegF b, legRegF xtmp, rRegI rtmp, rFlagsReg cr) %{ - predicate(UseAVX > 0 && VLoopReductions::is_reduction(n)); + predicate(!VM_Version::supports_avx10_2() && UseAVX > 0 && VLoopReductions::is_reduction(n)); match(Set dst (MaxF a b)); effect(USE a, USE b, TEMP xtmp, TEMP rtmp, KILL cr); @@ -4471,9 +4482,20 @@ instruct maxF_reduction_reg(legRegF dst, legRegF a, legRegF b, legRegF xtmp, rRe ins_pipe( pipe_slow ); %} +// max = java.lang.Math.max(double a, double b) +instruct maxD_avx10_reg(regD dst, regD a, regD b) %{ + predicate(VM_Version::supports_avx10_2()); + match(Set dst (MaxD a b)); + format %{ "maxD $dst, $a, $b" %} + ins_encode %{ + __ eminmaxsd($dst$$XMMRegister, $a$$XMMRegister, $b$$XMMRegister, AVX10_MINMAX_MAX_COMPARE_SIGN); + %} + ins_pipe( pipe_slow ); +%} + // max = java.lang.Math.max(double a, double b) instruct maxD_reg(legRegD dst, legRegD a, legRegD b, legRegD tmp, legRegD atmp, legRegD btmp) %{ - predicate(UseAVX > 0 && !VLoopReductions::is_reduction(n)); + predicate(!VM_Version::supports_avx10_2() && UseAVX > 0 && !VLoopReductions::is_reduction(n)); match(Set dst (MaxD a b)); effect(USE a, USE b, TEMP atmp, TEMP btmp, TEMP tmp); format %{ "maxD $dst, $a, $b \t! using $tmp, $atmp and $btmp as TEMP" %} @@ -4484,7 +4506,7 @@ instruct maxD_reg(legRegD dst, legRegD a, legRegD b, legRegD tmp, legRegD atmp, %} instruct maxD_reduction_reg(legRegD dst, legRegD a, legRegD b, legRegD xtmp, rRegL rtmp, rFlagsReg cr) %{ - predicate(UseAVX > 0 && VLoopReductions::is_reduction(n)); + predicate(!VM_Version::supports_avx10_2() && UseAVX > 0 && VLoopReductions::is_reduction(n)); match(Set dst (MaxD a b)); effect(USE a, USE b, TEMP xtmp, TEMP rtmp, KILL cr); @@ -4496,9 +4518,20 @@ instruct maxD_reduction_reg(legRegD dst, legRegD a, legRegD b, legRegD xtmp, rRe ins_pipe( pipe_slow ); %} +// max = java.lang.Math.min(float a, float b) +instruct minF_avx10_reg(regF dst, regF a, regF b) %{ + predicate(VM_Version::supports_avx10_2()); + match(Set dst (MinF a b)); + format %{ "minF $dst, $a, $b" %} + ins_encode %{ + __ eminmaxss($dst$$XMMRegister, $a$$XMMRegister, $b$$XMMRegister, AVX10_MINMAX_MIN_COMPARE_SIGN); + %} + ins_pipe( pipe_slow ); +%} + // min = java.lang.Math.min(float a, float b) instruct minF_reg(legRegF dst, legRegF a, legRegF b, legRegF tmp, legRegF atmp, legRegF btmp) %{ - predicate(UseAVX > 0 && !VLoopReductions::is_reduction(n)); + predicate(!VM_Version::supports_avx10_2() && UseAVX > 0 && !VLoopReductions::is_reduction(n)); match(Set dst (MinF a b)); effect(USE a, USE b, TEMP tmp, TEMP atmp, TEMP btmp); format %{ "minF $dst, $a, $b \t! using $tmp, $atmp and $btmp as TEMP" %} @@ -4509,7 +4542,7 @@ instruct minF_reg(legRegF dst, legRegF a, legRegF b, legRegF tmp, legRegF atmp, %} instruct minF_reduction_reg(legRegF dst, legRegF a, legRegF b, legRegF xtmp, rRegI rtmp, rFlagsReg cr) %{ - predicate(UseAVX > 0 && VLoopReductions::is_reduction(n)); + predicate(!VM_Version::supports_avx10_2() && UseAVX > 0 && VLoopReductions::is_reduction(n)); match(Set dst (MinF a b)); effect(USE a, USE b, TEMP xtmp, TEMP rtmp, KILL cr); @@ -4521,9 +4554,20 @@ instruct minF_reduction_reg(legRegF dst, legRegF a, legRegF b, legRegF xtmp, rRe ins_pipe( pipe_slow ); %} +// max = java.lang.Math.min(double a, double b) +instruct minD_avx10_reg(regD dst, regD a, regD b) %{ + predicate(VM_Version::supports_avx10_2()); + match(Set dst (MinD a b)); + format %{ "minD $dst, $a, $b" %} + ins_encode %{ + __ eminmaxsd($dst$$XMMRegister, $a$$XMMRegister, $b$$XMMRegister, AVX10_MINMAX_MIN_COMPARE_SIGN); + %} + ins_pipe( pipe_slow ); +%} + // min = java.lang.Math.min(double a, double b) instruct minD_reg(legRegD dst, legRegD a, legRegD b, legRegD tmp, legRegD atmp, legRegD btmp) %{ - predicate(UseAVX > 0 && !VLoopReductions::is_reduction(n)); + predicate(!VM_Version::supports_avx10_2() && UseAVX > 0 && !VLoopReductions::is_reduction(n)); match(Set dst (MinD a b)); effect(USE a, USE b, TEMP tmp, TEMP atmp, TEMP btmp); format %{ "minD $dst, $a, $b \t! using $tmp, $atmp and $btmp as TEMP" %} @@ -4534,7 +4578,7 @@ instruct minD_reg(legRegD dst, legRegD a, legRegD b, legRegD tmp, legRegD atmp, %} instruct minD_reduction_reg(legRegD dst, legRegD a, legRegD b, legRegD xtmp, rRegL rtmp, rFlagsReg cr) %{ - predicate(UseAVX > 0 && VLoopReductions::is_reduction(n)); + predicate(!VM_Version::supports_avx10_2() && UseAVX > 0 && VLoopReductions::is_reduction(n)); match(Set dst (MinD a b)); effect(USE a, USE b, TEMP xtmp, TEMP rtmp, KILL cr); From a0f7604d8d675e427b6cfd4c954f9301ee0155c3 Mon Sep 17 00:00:00 2001 From: Goetz Lindenmaier Date: Mon, 8 Jun 2026 09:03:47 +0000 Subject: [PATCH 07/86] 8375125: assert(false) failed: "Attempting to acquire lock NativeHeapTrimmer_lock/nosafepoint out of order with lock ConcurrentHashTableResize_lock/nosafepoint-2 -- possible deadlock" when using native heap trimmer Backport-of: a67979c4e6dcea70e63cc79a105be12a9306c660 --- src/hotspot/share/classfile/stringTable.cpp | 7 +- src/hotspot/share/classfile/symbolTable.cpp | 7 +- ...stTrimNativeHeapIntervalTablesCleanup.java | 107 ++++++++++++++++++ 3 files changed, 117 insertions(+), 4 deletions(-) create mode 100644 test/hotspot/jtreg/runtime/os/TestTrimNativeHeapIntervalTablesCleanup.java diff --git a/src/hotspot/share/classfile/stringTable.cpp b/src/hotspot/share/classfile/stringTable.cpp index 957ecd8ebe87..959abdcc37da 100644 --- a/src/hotspot/share/classfile/stringTable.cpp +++ b/src/hotspot/share/classfile/stringTable.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -615,6 +615,10 @@ struct StringTableDeleteCheck : StackObj { }; void StringTable::clean_dead_entries(JavaThread* jt) { + // BulkDeleteTask::prepare() may take ConcurrentHashTableResize_lock (nosafepoint-2). + // When NativeHeapTrimmer is enabled, SuspendMark may take NativeHeapTrimmer::_lock (nosafepoint). + // Take SuspendMark first to keep lock order and avoid deadlock. + NativeHeapTrimmer::SuspendMark sm("stringtable"); StringTableHash::BulkDeleteTask bdt(_local_table); if (!bdt.prepare(jt)) { return; @@ -622,7 +626,6 @@ void StringTable::clean_dead_entries(JavaThread* jt) { StringTableDeleteCheck stdc; StringTableDoDelete stdd; - NativeHeapTrimmer::SuspendMark sm("stringtable"); { TraceTime timer("Clean", TRACETIME_LOG(Debug, stringtable, perf)); while(bdt.do_task(jt, stdc, stdd)) { diff --git a/src/hotspot/share/classfile/symbolTable.cpp b/src/hotspot/share/classfile/symbolTable.cpp index e6889e6248d6..814259aa8850 100644 --- a/src/hotspot/share/classfile/symbolTable.cpp +++ b/src/hotspot/share/classfile/symbolTable.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -765,6 +765,10 @@ struct SymbolTableDeleteCheck : StackObj { }; void SymbolTable::clean_dead_entries(JavaThread* jt) { + // BulkDeleteTask::prepare() may take ConcurrentHashTableResize_lock (nosafepoint-2). + // When NativeHeapTrimmer is enabled, SuspendMark may take NativeHeapTrimmer::_lock (nosafepoint). + // Take SuspendMark first to keep lock order and avoid deadlock. + NativeHeapTrimmer::SuspendMark sm("symboltable"); SymbolTableHash::BulkDeleteTask bdt(_local_table); if (!bdt.prepare(jt)) { return; @@ -772,7 +776,6 @@ void SymbolTable::clean_dead_entries(JavaThread* jt) { SymbolTableDeleteCheck stdc; SymbolTableDoDelete stdd; - NativeHeapTrimmer::SuspendMark sm("symboltable"); { TraceTime timer("Clean", TRACETIME_LOG(Debug, symboltable, perf)); while (bdt.do_task(jt, stdc, stdd)) { diff --git a/test/hotspot/jtreg/runtime/os/TestTrimNativeHeapIntervalTablesCleanup.java b/test/hotspot/jtreg/runtime/os/TestTrimNativeHeapIntervalTablesCleanup.java new file mode 100644 index 000000000000..3ced98b616d5 --- /dev/null +++ b/test/hotspot/jtreg/runtime/os/TestTrimNativeHeapIntervalTablesCleanup.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * 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 8375125 + * @summary Trigger StringTable::clean_dead_entries or SymbolTable::clean_dead_entries + * with -XX:TrimNativeHeapInterval enabled,should not violate lock ordering. + * @requires vm.debug + * @requires vm.gc != "Epsilon" + * @library /test/lib + * @modules java.compiler + * @run main/othervm -Xms128m -Xmx128m + * -XX:TrimNativeHeapInterval=300000 + * TestTrimNativeHeapIntervalTablesCleanup string + * @run main/othervm -Xms128m -Xmx128m + * -XX:TrimNativeHeapInterval=300000 + * TestTrimNativeHeapIntervalTablesCleanup symbol + */ + +import java.util.LinkedList; +import jdk.test.lib.compiler.InMemoryJavaCompiler; + +public class TestTrimNativeHeapIntervalTablesCleanup { + + public static void main(String[] args) throws Exception{ + if (args.length != 1) { + throw new IllegalArgumentException("Expected 1 argument: string|symbol"); + } + switch (args[0]) { + case "string": + testStringTableCleanup(); + break; + case "symbol": + testSymbolTableCleanup(); + break; + default: + throw new IllegalArgumentException("Unknown mode: " + args[0]); + } + System.out.println("passed: " + args[0]); + } + + static void testStringTableCleanup() throws Exception{ + final int rounds = 30; + final int maxSize = 200_000; + final int pruneEvery = 50_000; + final int pruneCount = 25_000; + long stringNum = 0; + + for (int round = 0; round < rounds; round++) { + LinkedList list = new LinkedList<>(); + for (int i = 0; i < maxSize; i++, stringNum++) { + if (i != 0 && (i % pruneEvery) == 0) { + int toRemove = Math.min(pruneCount, list.size()); + list.subList(0, toRemove).clear(); + } + list.push(Long.toString(stringNum).intern()); + } + System.gc(); + Thread.sleep(1000); + } + } + + static void testSymbolTableCleanup() throws Exception { + final int rounds = 10; + final int classesPerRound = 100; + + for (int r = 0; r < rounds; r++) { + for (int i = 0; i < classesPerRound; i++) { + String cn = "C" + r + "_" + i; + byte[] bytes = InMemoryJavaCompiler.compile( + cn, + "public class " + cn + " { int m" + i + "() { return " + i + "; } }" + ); + new ClassLoader(null) { + @Override + protected Class findClass(String name) throws ClassNotFoundException { + if (!name.equals(cn)) throw new ClassNotFoundException(name); + return defineClass(name, bytes, 0, bytes.length); + } + }.loadClass(cn).getDeclaredConstructor().newInstance(); + } + System.gc(); + Thread.sleep(1000); + } + } +} \ No newline at end of file From 42174d881d66cb6278489173444c0643e29888b3 Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Tue, 9 Jun 2026 14:24:49 +0000 Subject: [PATCH 08/86] 8378462: Build fails with --enable-linktime-gc set when using some devkits/toolchains Backport-of: f1eac215ffd86e5a4694b6a6bf99832bc8a236d8 --- make/modules/java.desktop/lib/ClientLibraries.gmk | 2 ++ 1 file changed, 2 insertions(+) diff --git a/make/modules/java.desktop/lib/ClientLibraries.gmk b/make/modules/java.desktop/lib/ClientLibraries.gmk index 6a115a81dba9..c9eb87fa7c40 100644 --- a/make/modules/java.desktop/lib/ClientLibraries.gmk +++ b/make/modules/java.desktop/lib/ClientLibraries.gmk @@ -397,6 +397,8 @@ $(eval $(call SetupJdkLibrary, BUILD_LIBFONTMANAGER, \ AccelGlyphCache.c, \ CFLAGS := $(LIBFONTMANAGER_CFLAGS), \ CXXFLAGS := $(LIBFONTMANAGER_CFLAGS), \ + CXXFLAGS_gcc := -fno-rtti -fno-exceptions, \ + CXXFLAGS_clang := -fno-rtti -fno-exceptions, \ OPTIMIZATION := HIGHEST, \ CFLAGS_windows = -DCC_NOEX, \ EXTRA_HEADER_DIRS := $(LIBFONTMANAGER_EXTRA_HEADER_DIRS), \ From c6388b2c27b3bfb15a2c4de6c80cd04dc0b46076 Mon Sep 17 00:00:00 2001 From: Frederic Thevenet Date: Wed, 10 Jun 2026 08:24:51 +0000 Subject: [PATCH 09/86] =?UTF-8?q?8378180:=20Compiling=20OpenJDK=20with=20C?= =?UTF-8?q?23=20C-Compiler=20gives=20warning:=20initialization=20discards?= =?UTF-8?q?=20=E2=80=98const=E2=80=99=20qualifier=20from=20pointer=20targe?= =?UTF-8?q?t=20type?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backport-of: 76a44b3e0341a9c59eaff0bfe8884ad104bda8d5 --- src/java.base/share/native/libjli/java.c | 2 +- src/java.base/share/native/libjli/jli_util.c | 4 ++-- src/java.base/share/native/libverify/check_code.c | 2 +- src/java.base/unix/native/libjava/TimeZone_md.c | 4 ++-- src/java.base/unix/native/libnet/NetworkInterface.c | 4 ++-- .../share/native/libinstrument/JPLISAgent.c | 4 ++-- .../unix/native/libinstrument/FileSystemSupport_md.c | 4 ++-- src/jdk.jdwp.agent/share/native/libjdwp/log_messages.c | 6 +++--- .../linux/native/applauncher/LinuxPackage.c | 10 +++++----- 9 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/java.base/share/native/libjli/java.c b/src/java.base/share/native/libjli/java.c index 4621ab588d19..e19ba280fa7b 100644 --- a/src/java.base/share/native/libjli/java.c +++ b/src/java.base/share/native/libjli/java.c @@ -1058,7 +1058,7 @@ static void SetMainModule(const char *s) { static const char format[] = "-Djdk.module.main=%s"; - char* slash = JLI_StrChr(s, '/'); + const char* slash = JLI_StrChr(s, '/'); size_t s_len, def_len; char *def; diff --git a/src/java.base/share/native/libjli/jli_util.c b/src/java.base/share/native/libjli/jli_util.c index 3b24a784491f..51c2d8b85731 100644 --- a/src/java.base/share/native/libjli/jli_util.c +++ b/src/java.base/share/native/libjli/jli_util.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -87,7 +87,7 @@ JLI_MemFree(void *ptr) jboolean JLI_HasSuffix(const char *s1, const char *s2) { - char *p = JLI_StrRChr(s1, '.'); + const char* p = JLI_StrRChr(s1, '.'); if (p == NULL || *p == '\0') { return JNI_FALSE; } diff --git a/src/java.base/share/native/libverify/check_code.c b/src/java.base/share/native/libverify/check_code.c index 32df102dcb3a..70e4f63bf7d9 100644 --- a/src/java.base/share/native/libverify/check_code.c +++ b/src/java.base/share/native/libverify/check_code.c @@ -3832,7 +3832,7 @@ signature_to_fieldtype(context_type *context, case JVM_SIGNATURE_CLASS: { char buffer_space[256]; char *buffer = buffer_space; - char *finish = strchr(p, JVM_SIGNATURE_ENDCLASS); + const char* finish = strchr(p, JVM_SIGNATURE_ENDCLASS); int length; if (finish == NULL) { /* Signature must have ';' after the class name. diff --git a/src/java.base/unix/native/libjava/TimeZone_md.c b/src/java.base/unix/native/libjava/TimeZone_md.c index cd253edde601..2f163cf27f1f 100644 --- a/src/java.base/unix/native/libjava/TimeZone_md.c +++ b/src/java.base/unix/native/libjava/TimeZone_md.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. * 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,7 @@ getZoneName(char *str) { static const char *zidir = "zoneinfo/"; - char *pos = strstr((const char *)str, zidir); + char* pos = strstr(str, zidir); if (pos == NULL) { return NULL; } diff --git a/src/java.base/unix/native/libnet/NetworkInterface.c b/src/java.base/unix/native/libnet/NetworkInterface.c index ceb5dd5f7512..5ed99f256d80 100644 --- a/src/java.base/unix/native/libnet/NetworkInterface.c +++ b/src/java.base/unix/native/libnet/NetworkInterface.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -205,7 +205,7 @@ JNIEXPORT jobject JNICALL Java_java_net_NetworkInterface_getByName0 netif *ifs, *curr; jboolean isCopy; const char* name_utf; - char *colonP; + const char* colonP; jobject obj = NULL; if (name != NULL) { diff --git a/src/java.instrument/share/native/libinstrument/JPLISAgent.c b/src/java.instrument/share/native/libinstrument/JPLISAgent.c index c65bfb9f2f96..a48defd471d0 100644 --- a/src/java.instrument/share/native/libinstrument/JPLISAgent.c +++ b/src/java.instrument/share/native/libinstrument/JPLISAgent.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -780,7 +780,7 @@ getModuleObject(jvmtiEnv* jvmti, jobject moduleObject = NULL; /* find last slash in the class name */ - char* last_slash = (cname == NULL) ? NULL : strrchr(cname, '/'); + const char* last_slash = (cname == NULL) ? NULL : strrchr(cname, '/'); int len = (last_slash == NULL) ? 0 : (int)(last_slash - cname); char* pkg_name_buf = (char*)malloc(len + 1); diff --git a/src/java.instrument/unix/native/libinstrument/FileSystemSupport_md.c b/src/java.instrument/unix/native/libinstrument/FileSystemSupport_md.c index f7ea013412d5..b9771e55b447 100644 --- a/src/java.instrument/unix/native/libinstrument/FileSystemSupport_md.c +++ b/src/java.instrument/unix/native/libinstrument/FileSystemSupport_md.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -36,7 +36,7 @@ #define slash '/' char* basePath(const char* path) { - char* last = strrchr(path, slash); + const char* last = strrchr(path, slash); if (last == NULL) { return (char*)path; } else { diff --git a/src/jdk.jdwp.agent/share/native/libjdwp/log_messages.c b/src/jdk.jdwp.agent/share/native/libjdwp/log_messages.c index e24fcd571568..c6d878fa8fd6 100644 --- a/src/jdk.jdwp.agent/share/native/libjdwp/log_messages.c +++ b/src/jdk.jdwp.agent/share/native/libjdwp/log_messages.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -90,8 +90,8 @@ get_time_stamp(char *tbuf, size_t ltbuf) static const char * file_basename(const char *file) { - char *p1; - char *p2; + const char* p1; + const char* p2; if ( file==NULL ) return "unknown"; diff --git a/src/jdk.jpackage/linux/native/applauncher/LinuxPackage.c b/src/jdk.jpackage/linux/native/applauncher/LinuxPackage.c index 26d65f8061c7..d346e241035a 100644 --- a/src/jdk.jpackage/linux/native/applauncher/LinuxPackage.c +++ b/src/jdk.jpackage/linux/native/applauncher/LinuxPackage.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * 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,7 +109,7 @@ static PackageDesc* initPackageDesc(PackageDesc* desc, const char* str, #define POPEN_CALLBACK_USE 1 #define POPEN_CALLBACK_IGNORE 0 -typedef int (*popenCallbackType)(void*, const char*); +typedef int (*popenCallbackType)(void*, char*); static int popenCommand(const char* cmdlineFormat, const char* arg, popenCallbackType callback, void* callbackData) { @@ -220,13 +220,13 @@ static char* concat(const char *x, const char *y) { } -static int initRpmPackage(void* desc, const char* str) { +static int initRpmPackage(void* desc, char* str) { initPackageDesc((PackageDesc*)desc, str, PACKAGE_TYPE_RPM); return POPEN_CALLBACK_IGNORE; } -static int initDebPackage(void* desc, const char* str) { +static int initDebPackage(void* desc, char* str) { char* colonChrPos = strchr(str, ':'); if (colonChrPos) { *colonChrPos = 0; @@ -238,7 +238,7 @@ static int initDebPackage(void* desc, const char* str) { #define LAUNCHER_LIB_NAME "/libapplauncher.so" -static int findLauncherLib(void* launcherLibPath, const char* str) { +static int findLauncherLib(void* launcherLibPath, char* str) { char* buf = 0; const size_t strLen = strlen(str); const size_t launcherLibNameLen = strlen(LAUNCHER_LIB_NAME); From 395159dd68f521389bb087014540e8adce7a4595 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Wed, 10 Jun 2026 14:34:49 +0000 Subject: [PATCH 10/86] 8364106: Include java.runtime.version in thread dump output Backport-of: 158e59ab9184127089f9693ce256001f64b5945c --- src/hotspot/share/runtime/threads.cpp | 16 +++++++++++++++- .../serviceability/dcmd/thread/PrintTest.java | 5 ++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/hotspot/share/runtime/threads.cpp b/src/hotspot/share/runtime/threads.cpp index 7e164ed16e2e..54fbf120f56b 100644 --- a/src/hotspot/share/runtime/threads.cpp +++ b/src/hotspot/share/runtime/threads.cpp @@ -1366,10 +1366,24 @@ void Threads::print_on(outputStream* st, bool print_stacks, char buf[32]; st->print_raw_cr(os::local_time_string(buf, sizeof(buf))); - st->print_cr("Full thread dump %s (%s %s):", + st->print_cr("Full thread dump %s (%s %s)", VM_Version::vm_name(), VM_Version::vm_release(), VM_Version::vm_info_string()); + JDK_Version::current().to_string(buf, sizeof(buf)); + const char* runtime_name = JDK_Version::runtime_name() != nullptr ? + JDK_Version::runtime_name() : ""; + const char* runtime_version = JDK_Version::runtime_version() != nullptr ? + JDK_Version::runtime_version() : ""; + const char* vendor_version = JDK_Version::runtime_vendor_version() != nullptr ? + JDK_Version::runtime_vendor_version() : ""; + const char* jdk_debug_level = VM_Version::printable_jdk_debug_level() != nullptr ? + VM_Version::printable_jdk_debug_level() : ""; + + st->print_cr(" JDK version: %s%s%s (%s) (%sbuild %s)", runtime_name, + (*vendor_version != '\0') ? " " : "", vendor_version, + buf, jdk_debug_level, runtime_version); + st->cr(); #if INCLUDE_SERVICES diff --git a/test/hotspot/jtreg/serviceability/dcmd/thread/PrintTest.java b/test/hotspot/jtreg/serviceability/dcmd/thread/PrintTest.java index 29c462a56b39..a4a5eb5721b5 100644 --- a/test/hotspot/jtreg/serviceability/dcmd/thread/PrintTest.java +++ b/test/hotspot/jtreg/serviceability/dcmd/thread/PrintTest.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 @@ -168,6 +168,9 @@ public void run(CommandExecutor executor) { output.shouldNotMatch(jucLockPattern1); output.shouldNotMatch(jucLockPattern2); } + + /* Check for presence of version string */ + output.shouldContain("JDK version:"); } @Test From 5a9b65bf93ca54480a428e06b4384542f1417cb7 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Wed, 10 Jun 2026 16:28:07 +0000 Subject: [PATCH 11/86] 8372546: UnixMultiResolutionSplashTest.java fails intermittently on Ubuntu_24.04 Remove .../unix/UnixMultiResolutionSplashTest.java Backport-of: 24d31296e12040316ae9fb4500a3ea88b2192c3b --- .../unix/UnixMultiResolutionSplashTest.java | 244 ------------------ 1 file changed, 244 deletions(-) delete mode 100644 test/jdk/java/awt/SplashScreen/MultiResolutionSplash/unix/UnixMultiResolutionSplashTest.java diff --git a/test/jdk/java/awt/SplashScreen/MultiResolutionSplash/unix/UnixMultiResolutionSplashTest.java b/test/jdk/java/awt/SplashScreen/MultiResolutionSplash/unix/UnixMultiResolutionSplashTest.java deleted file mode 100644 index 99cc58e13a39..000000000000 --- a/test/jdk/java/awt/SplashScreen/MultiResolutionSplash/unix/UnixMultiResolutionSplashTest.java +++ /dev/null @@ -1,244 +0,0 @@ -/* - * Copyright (c) 2016, 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. - * - * 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.awt.Color; -import java.awt.Dialog; -import java.awt.Frame; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.Panel; -import java.awt.Rectangle; -import java.awt.Robot; -import java.awt.SplashScreen; -import java.awt.TextField; -import java.awt.Window; -import java.awt.event.KeyEvent; -import java.awt.image.BufferedImage; -import java.io.BufferedReader; -import java.io.File; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import javax.imageio.ImageIO; - -/** - * @test - * @key headful - * @bug 8145174 8151787 8168657 - * @summary HiDPI splash screen support on Linux - * @modules java.desktop/sun.java2d - * @requires (os.family == "linux") - * @run main UnixMultiResolutionSplashTest - */ - -public class UnixMultiResolutionSplashTest { - - private static final int IMAGE_WIDTH = 300; - private static final int IMAGE_HEIGHT = 200; - private static int inx = 0; - private static final ImageInfo[] tests = { - new ImageInfo("splash1.png", "splash1@200pct.png", Color.BLUE, Color.GREEN), - new ImageInfo("splash2", "splash2@2x", Color.WHITE, Color.BLACK), - new ImageInfo("splash3.", "splash3@200pct.", Color.YELLOW, Color.RED) - }; - - public static void main(String[] args) throws Exception { - - if (args.length == 0) { - generateImages(); - for (ImageInfo test : tests) { - createChildProcess(test); - } - } else { - int index = Integer.parseInt(args[0]); - testSplash(tests[index]); - } - } - - static void createChildProcess(ImageInfo test) { - String javaPath = System.getProperty("java.home"); - File file = new File(test.name1x); - String classPathDir = System.getProperty("java.class.path"); - Map env = new HashMap(); - env.put("GDK_SCALE", "2"); - int exitValue = doExec(env, javaPath + File.separator + "bin" + File.separator - + "java", "-splash:" + file.getAbsolutePath(), "-cp", - classPathDir, "UnixMultiResolutionSplashTest", String.valueOf(inx++)); - if (exitValue != 0) { - throw new RuntimeException("Test Failed"); - } - } - - static void testSplash(ImageInfo test) throws Exception { - SplashScreen splashScreen = SplashScreen.getSplashScreen(); - if (splashScreen == null) { - throw new RuntimeException("Splash screen is not shown!"); - } - Graphics2D g = splashScreen.createGraphics(); - Rectangle splashBounds = splashScreen.getBounds(); - int screenX = (int) splashBounds.getCenterX(); - int screenY = (int) splashBounds.getCenterY(); - Robot robot = new Robot(); - Color splashScreenColor = robot.getPixelColor(screenX, screenY); - - float scaleFactor = getScaleFactor(); - Color testColor = (1 < scaleFactor) ? test.color2x : test.color1x; - if (!compare(testColor, splashScreenColor)) { - throw new RuntimeException( - "Image with wrong resolution is used for splash screen!"); - } - } - - static int doExec(Map envToSet, String... cmds) { - Process p = null; - ProcessBuilder pb = new ProcessBuilder(cmds); - Map env = pb.environment(); - for (String cmd : cmds) { - System.out.print(cmd + " "); - } - System.out.println(); - if (envToSet != null) { - env.putAll(envToSet); - } - BufferedReader rdr = null; - try { - List outputList = new ArrayList<>(); - pb.redirectErrorStream(true); - p = pb.start(); - rdr = new BufferedReader(new InputStreamReader(p.getInputStream())); - String in = rdr.readLine(); - while (in != null) { - outputList.add(in); - in = rdr.readLine(); - System.out.println(in); - } - p.waitFor(); - p.destroy(); - } catch (Exception ex) { - ex.printStackTrace(); - } - return p.exitValue(); - } - - static void testFocus() throws Exception { - - System.out.println("Focus Test!"); - Robot robot = new Robot(); - robot.setAutoDelay(50); - Frame frame = new Frame(); - frame.setSize(100, 100); - String test = "123"; - TextField textField = new TextField(test); - textField.selectAll(); - frame.add(textField); - frame.setVisible(true); - robot.waitForIdle(); - - robot.keyPress(KeyEvent.VK_A); - robot.keyRelease(KeyEvent.VK_A); - robot.keyPress(KeyEvent.VK_B); - robot.keyRelease(KeyEvent.VK_B); - robot.waitForIdle(); - - frame.dispose(); - if (!textField.getText().equals("ab")) { - throw new RuntimeException("Focus is lost! " + - "Expected 'ab' got " + "'" + textField.getText() + "'."); - } - } - - static boolean compare(Color c1, Color c2) { - return compare(c1.getRed(), c2.getRed()) - && compare(c1.getGreen(), c2.getGreen()) - && compare(c1.getBlue(), c2.getBlue()); - } - - static boolean compare(int n, int m) { - return Math.abs(n - m) <= 50; - } - - static float getScaleFactor() { - - final Dialog dialog = new Dialog((Window) null); - dialog.setSize(100, 100); - dialog.setModal(true); - float[] scaleFactors = new float[1]; - Panel panel = new Panel() { - - @Override - public void paint(Graphics g) { - String scaleStr = System.getenv("GDK_SCALE"); - if (scaleStr != null && !scaleStr.equals("")) { - try { - scaleFactors[0] = Float.valueOf(scaleStr); - } catch (NumberFormatException ex) { - scaleFactors[0] = 1.0f; - } - } - dialog.setVisible(false); - } - }; - dialog.add(panel); - dialog.setVisible(true); - dialog.dispose(); - return scaleFactors[0]; - } - - static void generateImages() throws Exception { - for (ImageInfo test : tests) { - generateImage(test.name1x, test.color1x, 1); - generateImage(test.name2x, test.color2x, 2); - } - } - - static void generateImage(String name, Color color, int scale) throws Exception { - File file = new File(name); - if (file.exists()) { - return; - } - BufferedImage image = new BufferedImage(scale * IMAGE_WIDTH, scale * IMAGE_HEIGHT, - BufferedImage.TYPE_INT_RGB); - Graphics g = image.getGraphics(); - g.setColor(color); - g.fillRect(0, 0, scale * IMAGE_WIDTH, scale * IMAGE_HEIGHT); - ImageIO.write(image, "png", file); - } - - static class ImageInfo { - - final String name1x; - final String name2x; - final Color color1x; - final Color color2x; - - public ImageInfo(String name1x, String name2x, Color color1x, Color color2x) { - this.name1x = name1x; - this.name2x = name2x; - this.color1x = color1x; - this.color2x = color2x; - } - } -} - From 59e4f5c9cb417c10ad95839e2c1681b43aede569 Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Thu, 11 Jun 2026 12:51:28 +0000 Subject: [PATCH 12/86] 8385165: [ubsan] TestDockerMemoryMetricsSubgroup.java jtreg test fails with ubsan-enabled binaries Backport-of: 56b2dcfd59e2579ac70b3dcf5265ff13a6954f13 --- .../platform/docker/TestDockerMemoryMetricsSubgroup.java | 1 + 1 file changed, 1 insertion(+) diff --git a/test/jdk/jdk/internal/platform/docker/TestDockerMemoryMetricsSubgroup.java b/test/jdk/jdk/internal/platform/docker/TestDockerMemoryMetricsSubgroup.java index da23acbf8b7d..5e3963e50df3 100644 --- a/test/jdk/jdk/internal/platform/docker/TestDockerMemoryMetricsSubgroup.java +++ b/test/jdk/jdk/internal/platform/docker/TestDockerMemoryMetricsSubgroup.java @@ -41,6 +41,7 @@ * @summary Cgroup v1 subsystem fails to set subsystem path * @requires container.support * @requires !vm.asan + * @requires !vm.ubsan * @library /test/lib * @modules java.base/jdk.internal.platform * @build MetricsMemoryTester From b177fb8b546861c62fbfa63a6a77f1274a1336f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mar=C3=ADa=20Arias=20de=20Reyna=20Dom=C3=ADnguez?= Date: Fri, 12 Jun 2026 12:41:13 +0000 Subject: [PATCH 13/86] 8382166: [AOT Cache] CRC was not checked on Code Region Reviewed-by: iklam Backport-of: 3dcd3653c82e92fc38f7725e5be78362a85ee39e --- src/hotspot/share/cds/filemap.cpp | 7 ++ .../appcds/aotCache/AOTCacheConsistency.java | 82 +++++++++++++++++++ test/lib/jdk/test/lib/cds/CDSAppTester.java | 4 + .../lib/jdk/test/lib/cds/CDSArchiveUtils.java | 5 ++ .../jdk/test/lib/cds/SimpleCDSAppTester.java | 26 +++++- 5 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTCacheConsistency.java diff --git a/src/hotspot/share/cds/filemap.cpp b/src/hotspot/share/cds/filemap.cpp index 9dd1db0c4c12..1afa13cabbe7 100644 --- a/src/hotspot/share/cds/filemap.cpp +++ b/src/hotspot/share/cds/filemap.cpp @@ -1317,6 +1317,13 @@ bool FileMapInfo::map_aot_code_region(ReservedSpace rs) { return false; } else { assert(mapped_base == requested_base, "must be"); + + if (VerifySharedSpaces && !r->check_region_crc(mapped_base)) { + aot_log_error(aot)("region %d CRC error", MetaspaceShared::ac); + os::unmap_memory(mapped_base, r->used_aligned()); + return false; + } + r->set_mapped_from_file(true); r->set_mapped_base(mapped_base); aot_log_info(aot)("Mapped static region #%d at base " INTPTR_FORMAT " top " INTPTR_FORMAT " (%s)", diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTCacheConsistency.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTCacheConsistency.java new file mode 100644 index 000000000000..9c340ab21600 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTCacheConsistency.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * 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 + * @key randomness + * @summary AOTCacheConsistency This test checks that there is a CRC validation of the AOT Cache regions. + * @bug 8382166 + * @requires vm.cds.supports.aot.class.linking + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox AOTCacheConsistency + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar HelloApp + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI AOTCacheConsistency + */ + +import jdk.test.lib.cds.CDSArchiveUtils; +import jdk.test.lib.cds.SimpleCDSAppTester; +import jdk.test.lib.process.OutputAnalyzer; +import java.io.File; + +public class AOTCacheConsistency { + public static void main(String args[]) throws Exception { + // Train and run the app + SimpleCDSAppTester tester = SimpleCDSAppTester.of("AOTCacheConsistency") + .classpath("app.jar") + .appCommandLine("HelloApp") + .setProductionChecker((OutputAnalyzer out) -> { + out.shouldContain("HelloWorld"); + }) + .runAOTWorkflow(); + + String aotCache = tester.aotCacheFile(); + + String[] regions = CDSArchiveUtils.getRegions(); + String orig = aotCache + ".orig"; + CDSArchiveUtils.copyArchiveFile(new File(aotCache), orig); // save original copy + + // Modify each of the region individually. The production should fail to run + // with these args; + String extraVMArgs[] = {"-XX:+VerifySharedSpaces", "-XX:AOTMode=on"}; + tester.setCheckExitValue(false); + + for (int i = 0; i < regions.length; i++) { + File f = CDSArchiveUtils.copyArchiveFile(new File(orig), aotCache); + System.out.println("\n=======\nTesting region " + i + " = " + regions[i]); + if (CDSArchiveUtils.modifyRegionContent(i, f)) { + tester.setProductionChecker((OutputAnalyzer out) -> { + out.shouldContain("Checksum verification failed."); + }); + tester.rerunProduction(extraVMArgs); + } + } + } +} + +class HelloApp { + public static void main(String[] args) { + System.out.println("HelloWorld"); + } +} \ No newline at end of file diff --git a/test/lib/jdk/test/lib/cds/CDSAppTester.java b/test/lib/jdk/test/lib/cds/CDSAppTester.java index fd244c6acc60..dfb2f12c6228 100644 --- a/test/lib/jdk/test/lib/cds/CDSAppTester.java +++ b/test/lib/jdk/test/lib/cds/CDSAppTester.java @@ -58,6 +58,10 @@ abstract public class CDSAppTester { private String whiteBoxJar = null; private boolean inOneStepTraining = false; + public String aotCacheFile() { + return this.aotCacheFile; + } + /** * All files created in the CDS/AOT workflow will be name + extension. E.g. * - name.aot diff --git a/test/lib/jdk/test/lib/cds/CDSArchiveUtils.java b/test/lib/jdk/test/lib/cds/CDSArchiveUtils.java index f616b22ef38f..addc87958e1a 100644 --- a/test/lib/jdk/test/lib/cds/CDSArchiveUtils.java +++ b/test/lib/jdk/test/lib/cds/CDSArchiveUtils.java @@ -75,9 +75,14 @@ public class CDSArchiveUtils { "ro", // ReadOnly "bm", // relocation bitmaps "hp", // heap + "ac", // aot code }; private static int num_regions = shared_region_name.length; + public static String[] getRegions() { + return shared_region_name; + } + static { WhiteBox wb; try { diff --git a/test/lib/jdk/test/lib/cds/SimpleCDSAppTester.java b/test/lib/jdk/test/lib/cds/SimpleCDSAppTester.java index c869cfa366bf..b4d380605bc5 100644 --- a/test/lib/jdk/test/lib/cds/SimpleCDSAppTester.java +++ b/test/lib/jdk/test/lib/cds/SimpleCDSAppTester.java @@ -58,9 +58,11 @@ public class SimpleCDSAppTester { private String modulepath; private String[] appCommandLine; private String[] vmArgs = new String[] {}; + private Tester tester; private SimpleCDSAppTester(String name) { this.name = name; + this.tester = new Tester(name); } public static SimpleCDSAppTester of(String name) { @@ -181,17 +183,35 @@ public void checkExecution(OutputAnalyzer out, RunMode runMode) throws Exception } public SimpleCDSAppTester runStaticWorkflow() throws Exception { - (new Tester(name)).runStaticWorkflow(); + tester.runStaticWorkflow(); return this; } public SimpleCDSAppTester runAOTWorkflow() throws Exception { - (new Tester(name)).runAOTWorkflow(); + tester.runAOTWorkflow(); return this; } public SimpleCDSAppTester run(String args[]) throws Exception { - (new Tester(name)).run(args); + tester.run(args); return this; } + + public SimpleCDSAppTester rerunProduction(String... extraVmArgs) throws Exception { + tester.productionRun(extraVmArgs); + return this; + } + + public SimpleCDSAppTester rerunProduction(String[] extraVmArgs, String... extraAppArgs) throws Exception { + tester.productionRun(extraVmArgs, extraAppArgs); + return this; + } + + public String aotCacheFile() { + return tester.aotCacheFile(); + } + + public void setCheckExitValue(boolean b) { + tester.setCheckExitValue(b); + } } From 8fd86abe9f20f55963c2b411945f16edea8966e5 Mon Sep 17 00:00:00 2001 From: Goetz Lindenmaier Date: Mon, 15 Jun 2026 10:08:02 +0000 Subject: [PATCH 14/86] 8386345: Remove redundant @requires from TestGarbageCollectionEventWithZMinor Backport-of: d46298d681f78d8f16ee4ed8031520dcd76d3c4c --- .../gc/collection/TestGarbageCollectionEventWithZMinor.java | 1 - 1 file changed, 1 deletion(-) diff --git a/test/jdk/jdk/jfr/event/gc/collection/TestGarbageCollectionEventWithZMinor.java b/test/jdk/jdk/jfr/event/gc/collection/TestGarbageCollectionEventWithZMinor.java index 8e0d48682fcc..da00274f5a66 100644 --- a/test/jdk/jdk/jfr/event/gc/collection/TestGarbageCollectionEventWithZMinor.java +++ b/test/jdk/jdk/jfr/event/gc/collection/TestGarbageCollectionEventWithZMinor.java @@ -41,7 +41,6 @@ * @test * @requires vm.flagless * @requires vm.hasJFR & vm.gc.Z - * @requires vm.flagless * @library /test/lib /test/jdk * @build jdk.test.whitebox.WhiteBox * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox From 86e5f47a12f42f171706a0e7da162adbc09ac625 Mon Sep 17 00:00:00 2001 From: Goetz Lindenmaier Date: Mon, 15 Jun 2026 10:08:26 +0000 Subject: [PATCH 15/86] 8366926: Unexpected exception occurs when executing code in a "local" JShell environment Backport-of: d316d3f74fd951613eef3870ee3da2c2dc5b719c --- .../execution/LocalExecutionControl.java | 28 ++++++-- .../LocalExecutionInstrumentationCHRTest.java | 64 +++++++++++++++++++ 2 files changed, 87 insertions(+), 5 deletions(-) create mode 100644 test/langtools/jdk/jshell/LocalExecutionInstrumentationCHRTest.java diff --git a/src/jdk.jshell/share/classes/jdk/jshell/execution/LocalExecutionControl.java b/src/jdk.jshell/share/classes/jdk/jshell/execution/LocalExecutionControl.java index 3db08b80bff5..f3c98c561e26 100644 --- a/src/jdk.jshell/share/classes/jdk/jshell/execution/LocalExecutionControl.java +++ b/src/jdk.jshell/share/classes/jdk/jshell/execution/LocalExecutionControl.java @@ -24,6 +24,7 @@ */ package jdk.jshell.execution; +import java.io.ByteArrayInputStream; import java.lang.constant.ClassDesc; import java.lang.constant.ConstantDescs; import java.lang.reflect.Field; @@ -34,6 +35,7 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Stream; import java.lang.classfile.ClassFile; +import java.lang.classfile.ClassHierarchyResolver; import java.lang.classfile.ClassTransform; import java.lang.classfile.CodeBuilder; import java.lang.classfile.CodeElement; @@ -85,9 +87,7 @@ public LocalExecutionControl(ClassLoader parent) { @Override public void load(ClassBytecodes[] cbcs) throws ClassInstallException, NotImplementedException, EngineTerminationException { - super.load(Stream.of(cbcs) - .map(cbc -> new ClassBytecodes(cbc.name(), instrument(cbc.bytecodes()))) - .toArray(ClassBytecodes[]::new)); + super.load(instrument(cbcs)); } private static final String CANCEL_CLASS = "REPL.$Cancel$"; @@ -95,8 +95,26 @@ public void load(ClassBytecodes[] cbcs) private static final String STOP_CHECK = "stopCheck"; private static final ClassDesc CD_ThreadDeath = ClassDesc.of("java.lang.ThreadDeath"); - private static byte[] instrument(byte[] classFile) { - var cc = ClassFile.of(); + private static ClassBytecodes[] instrument(ClassBytecodes[] cbcs) { + var cc = ClassFile.of(ClassFile.ClassHierarchyResolverOption.of( + ClassHierarchyResolver.defaultResolver().orElse( + ClassHierarchyResolver.ofResourceParsing(cd -> { + String cName = cd.descriptorString(); + cName = cName.substring(1, cName.length() - 1).replace('/', '.'); + for (ClassBytecodes cbc : cbcs) { + if (cName.equals(cbc.name())) { + return new ByteArrayInputStream(cbc.bytecodes()); + } + } + return null; + })))); + + return Stream.of(cbcs) + .map(cbc -> new ClassBytecodes(cbc.name(), instrument(cc, cbc.bytecodes()))) + .toArray(ClassBytecodes[]::new); + } + + private static byte[] instrument(ClassFile cc, byte[] classFile) { return cc.transformClass(cc.parse(classFile), ClassTransform.transformingMethodBodies( CodeTransform.ofStateful(StopCheckWeaver::new))); diff --git a/test/langtools/jdk/jshell/LocalExecutionInstrumentationCHRTest.java b/test/langtools/jdk/jshell/LocalExecutionInstrumentationCHRTest.java new file mode 100644 index 000000000000..682c1814c66f --- /dev/null +++ b/test/langtools/jdk/jshell/LocalExecutionInstrumentationCHRTest.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 8366926 + * @summary Verify the instrumenation class hierarchy resolution works properly in local execution mode + * @library /tools/lib + * @modules + * jdk.compiler/com.sun.tools.javac.api + * jdk.compiler/com.sun.tools.javac.main + * @build KullaTesting + * @run junit/othervm LocalExecutionInstrumentationCHRTest + */ + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class LocalExecutionInstrumentationCHRTest extends ReplToolTesting { + + @Test + public void verifyMyClassFoundOnClassPath() { + test(new String[] { "--execution", "local" }, + a -> assertCommand(a, "public interface TestInterface {}", "| created interface TestInterface"), + a -> assertCommand(a, + "public class TestClass {" + + "public TestInterface foo(boolean b) {" + + "TestInterface test; " + + "if (b) {" + + "test = new TestInterfaceImpl1();" + + "} else {" + + "test = new TestInterfaceImpl2();" + + "}" + + "return test;" + + "}" + + "private class TestInterfaceImpl1 implements TestInterface {}" + + "private class TestInterfaceImpl2 implements TestInterface {}" + + "}", "| created class TestClass"), + a -> assertCommand(a, "new TestClass().foo(true).getClass();", "$3 ==> class TestClass$TestInterfaceImpl1"), + a -> assertCommand(a, "new TestClass().foo(false).getClass();", "$4 ==> class TestClass$TestInterfaceImpl2") + ); + } +} From 5d05ef08b4bbc156a736afe762f1e63bb093903d Mon Sep 17 00:00:00 2001 From: Goetz Lindenmaier Date: Mon, 15 Jun 2026 10:14:02 +0000 Subject: [PATCH 16/86] 8372731: Detailed authentication failure messages Backport-of: 895232fc65cab9ba3863b48cab27b688096a7435 --- .../www/protocol/http/AuthenticationInfo.java | 8 +- .../protocol/http/BasicAuthentication.java | 6 +- .../protocol/http/DigestAuthentication.java | 58 +++--- .../www/protocol/http/HttpURLConnection.java | 60 +++++-- .../http/NegotiateAuthentication.java | 29 ++- .../http/ntlm/NTLMAuthentication.java | 15 +- .../protocol/http/ntlm/NTLMAuthSequence.java | 3 +- .../http/ntlm/NTLMAuthentication.java | 11 +- .../windows/native/libnet/NTLMAuthSequence.c | 4 + .../net/www/protocol/http/NTLMFailTest.java | 167 ++++++++++++++++++ 10 files changed, 276 insertions(+), 85 deletions(-) create mode 100644 test/jdk/sun/net/www/protocol/http/NTLMFailTest.java diff --git a/src/java.base/share/classes/sun/net/www/protocol/http/AuthenticationInfo.java b/src/java.base/share/classes/sun/net/www/protocol/http/AuthenticationInfo.java index f567d7bd643d..9c9766e2ce2d 100644 --- a/src/java.base/share/classes/sun/net/www/protocol/http/AuthenticationInfo.java +++ b/src/java.base/share/classes/sun/net/www/protocol/http/AuthenticationInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1995, 2024, 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 @@ -25,6 +25,7 @@ package sun.net.www.protocol.http; +import java.io.IOException; import java.net.PasswordAuthentication; import java.net.URL; import java.util.HashMap; @@ -428,9 +429,10 @@ public String getHeaderName() { * @param conn The connection to apply the header(s) to * @param p A source of header values for this connection, if needed. * @param raw The raw header field (if needed) - * @return true if all goes well, false if no headers were set. + * @throws IOException if no headers were set */ - public abstract boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw); + public abstract void setHeaders(HttpURLConnection conn, HeaderParser p, String raw) + throws IOException; /** * Check if the header indicates that the current auth. parameters are stale. diff --git a/src/java.base/share/classes/sun/net/www/protocol/http/BasicAuthentication.java b/src/java.base/share/classes/sun/net/www/protocol/http/BasicAuthentication.java index f008c185b5d6..aa2a9625a01e 100644 --- a/src/java.base/share/classes/sun/net/www/protocol/http/BasicAuthentication.java +++ b/src/java.base/share/classes/sun/net/www/protocol/http/BasicAuthentication.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 @@ -125,15 +125,13 @@ public boolean supportsPreemptiveAuthorization() { * @param conn The connection to apply the header(s) to * @param p A source of header values for this connection, if needed. * @param raw The raw header values for this connection, if needed. - * @return true if all goes well, false if no headers were set. */ @Override - public boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) { + public void setHeaders(HttpURLConnection conn, HeaderParser p, String raw) { // no need to synchronize here: // already locked by s.n.w.p.h.HttpURLConnection assert conn.isLockHeldByCurrentThread(); conn.setAuthenticationProperty(getHeaderName(), getHeaderValue(null,null)); - return true; } /** diff --git a/src/java.base/share/classes/sun/net/www/protocol/http/DigestAuthentication.java b/src/java.base/share/classes/sun/net/www/protocol/http/DigestAuthentication.java index 28d7bc5cf4e0..87ea7c17085f 100644 --- a/src/java.base/share/classes/sun/net/www/protocol/http/DigestAuthentication.java +++ b/src/java.base/share/classes/sun/net/www/protocol/http/DigestAuthentication.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 @@ -321,7 +321,11 @@ public boolean supportsPreemptiveAuthorization() { */ @Override public String getHeaderValue(URL url, String method) { - return getHeaderValueImpl(url.getFile(), method); + try { + return getHeaderValueImpl(url.getFile(), method); + } catch (IOException _) { + return null; + } } /** @@ -339,7 +343,11 @@ public String getHeaderValue(URL url, String method) { * @return the value of the HTTP header this authentication wants set */ String getHeaderValue(String requestURI, String method) { - return getHeaderValueImpl(requestURI, method); + try { + return getHeaderValueImpl(requestURI, method); + } catch (IOException _) { + return null; + } } /** @@ -369,10 +377,11 @@ public boolean isAuthorizationStale (String header) { * @param conn The connection to apply the header(s) to * @param p A source of header values for this connection, if needed. * @param raw Raw header values for this connection, if needed. - * @return true if all goes well, false if no headers were set. + * @throws IOException if no headers were set */ @Override - public boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) { + public void setHeaders(HttpURLConnection conn, HeaderParser p, String raw) + throws IOException { // no need to synchronize here: // already locked by s.n.w.p.h.HttpURLConnection assert conn.isLockHeldByCurrentThread(); @@ -380,14 +389,14 @@ public boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) { params.setNonce (p.findValue("nonce")); params.setOpaque (p.findValue("opaque")); params.setQop (p.findValue("qop")); - params.setUserhash (Boolean.valueOf(p.findValue("userhash"))); + params.setUserhash (Boolean.parseBoolean(p.findValue("userhash"))); String charset = p.findValue("charset"); if (charset == null) { charset = "ISO_8859_1"; } else if (!charset.equalsIgnoreCase("UTF-8")) { // UTF-8 is only valid value. ISO_8859_1 represents default behavior // when the parameter is not set. - return false; + throw new IOException("Illegal charset in header"); } params.setCharset(charset.toUpperCase(Locale.ROOT)); @@ -405,7 +414,7 @@ public boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) { } if (params.nonce == null || authMethod == null || pw == null || realm == null) { - return false; + throw new IOException("Server challenge incomplete"); } if (authMethod.length() >= 1) { // Method seems to get converted to all lower case elsewhere. @@ -415,8 +424,7 @@ public boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) { + authMethod.substring(1).toLowerCase(Locale.ROOT); } - if (!setAlgorithmNames(p, params)) - return false; + setAlgorithmNames(p, params); // If authQop is true, then the server is doing RFC2617 and // has offered qop=auth. We do not support any other modes @@ -426,20 +434,17 @@ public boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) { params.setNewCnonce(); } - String value = getHeaderValueImpl (uri, method); - if (value != null) { - conn.setAuthenticationProperty(getHeaderName(), value); - return true; - } else { - return false; - } + String value = getHeaderValueImpl(uri, method); + assert value != null; + conn.setAuthenticationProperty(getHeaderName(), value); } // Algorithm name is stored in two separate fields (of Paramaeters) // This allows for variations in digest algorithm name (aliases) // and also allow for the -sess variant defined in HTTP Digest protocol - // returns false if algorithm not supported - private static boolean setAlgorithmNames(HeaderParser p, Parameters params) { + // throws IOException if algorithm not supported + private static void setAlgorithmNames(HeaderParser p, Parameters params) + throws IOException { String algorithm = p.findValue("algorithm"); String digestName = algorithm; if (algorithm == null || algorithm.isEmpty()) { @@ -459,18 +464,17 @@ private static boolean setAlgorithmNames(HeaderParser p, Parameters params) { var oid = KnownOIDs.findMatch(digestName); if (oid == null) { log("unknown algorithm: " + algorithm); - return false; + throw new IOException("Unknown algorithm: " + algorithm); } digestName = oid.stdName(); params.setAlgorithm (algorithm); params.setDigestName (digestName); - return true; } /* Calculate the Authorization header field given the request URI * and based on the authorization information in params */ - private String getHeaderValueImpl (String uri, String method) { + private String getHeaderValueImpl (String uri, String method) throws IOException { String response; char[] passwd = pw.getPassword(); boolean qop = params.authQop(); @@ -479,11 +483,7 @@ private String getHeaderValueImpl (String uri, String method) { String nonce = params.getNonce (); String algorithm = params.getAlgorithm (); String digest = params.getDigestName (); - try { - validateDigest(digest); - } catch (IOException e) { - return null; - } + validateDigest(digest); Charset charset = params.getCharset(); boolean userhash = params.getUserhash (); params.incrementNC (); @@ -505,7 +505,7 @@ private String getHeaderValueImpl (String uri, String method) { digest, session, charset); } catch (CharacterCodingException | NoSuchAlgorithmException ex) { log(ex.getMessage()); - return null; + throw new IOException("Failed to compute digest", ex); } String ncfield = "\""; @@ -534,7 +534,7 @@ private String getHeaderValueImpl (String uri, String method) { } } catch (CharacterCodingException | NoSuchAlgorithmException ex) { log(ex.getMessage()); - return null; + throw new IOException("Failed to compute user hash", ex); } String value = authMethod diff --git a/src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java b/src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java index 0ac1f75b86a0..62754bcff2cb 100644 --- a/src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java +++ b/src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java @@ -61,6 +61,7 @@ import java.util.StringJoiner; import jdk.internal.access.JavaNetHttpCookieAccess; import jdk.internal.access.SharedSecrets; +import jdk.internal.util.Exceptions; import sun.net.NetProperties; import sun.net.NetworkClient; import sun.net.util.IPAddressUtil; @@ -1464,16 +1465,29 @@ private InputStream getInputStream0() throws IOException { /* in this case, only one header field will be present */ String raw = responses.findValue ("Proxy-Authenticate"); reset (); - if (!proxyAuthentication.setHeaders(this, - authhdr.headerParser(), raw)) { + try { + proxyAuthentication.setHeaders(this, + authhdr.headerParser(), raw); + } catch (IOException ex) { disconnectInternal(); - throw new IOException ("Authentication failure"); + if (Exceptions.enhancedNonSocketExceptions()) { + throw new IOException ("Authentication failure", ex); + } else { + throw new IOException ("Authentication failure"); + } } - if (serverAuthentication != null && srvHdr != null && - !serverAuthentication.setHeaders(this, - srvHdr.headerParser(), raw)) { - disconnectInternal (); - throw new IOException ("Authentication failure"); + if (serverAuthentication != null && srvHdr != null) { + try { + serverAuthentication.setHeaders(this, + srvHdr.headerParser(), raw); + } catch (IOException ex) { + disconnectInternal(); + if (Exceptions.enhancedNonSocketExceptions()) { + throw new IOException ("Authentication failure", ex); + } else { + throw new IOException ("Authentication failure"); + } + } } authObj = null; doingNTLMp2ndStage = false; @@ -1552,9 +1566,15 @@ private InputStream getInputStream0() throws IOException { } else { reset (); /* header not used for ntlm */ - if (!serverAuthentication.setHeaders(this, null, raw)) { + try { + serverAuthentication.setHeaders(this, null, raw); + } catch (IOException ex) { disconnectWeb(); - throw new IOException ("Authentication failure"); + if (Exceptions.enhancedNonSocketExceptions()) { + throw new IOException ("Authentication failure", ex); + } else { + throw new IOException ("Authentication failure"); + } } doingNTLM2ndStage = false; authObj = null; @@ -1944,10 +1964,16 @@ private void doTunneling0() throws IOException { } else { String raw = responses.findValue ("Proxy-Authenticate"); reset (); - if (!proxyAuthentication.setHeaders(this, - authhdr.headerParser(), raw)) { + try { + proxyAuthentication.setHeaders(this, + authhdr.headerParser(), raw); + } catch (IOException ex) { disconnectInternal(); - throw new IOException ("Authentication failure"); + if (Exceptions.enhancedNonSocketExceptions()) { + throw new IOException ("Authentication failure", ex); + } else { + throw new IOException ("Authentication failure"); + } } authObj = null; doingNTLMp2ndStage = false; @@ -2210,7 +2236,9 @@ yield new DigestAuthentication(true, host, port, realm, }; } if (ret != null) { - if (!ret.setHeaders(this, p, raw)) { + try { + ret.setHeaders(this, p, raw); + } catch (IOException e) { ret.disposeContext(); ret = null; } @@ -2367,7 +2395,9 @@ private AuthenticationInfo getServerAuthentication(AuthenticationHeader authhdr) } } if (ret != null ) { - if (!ret.setHeaders(this, p, raw)) { + try { + ret.setHeaders(this, p, raw); + } catch (IOException e) { ret.disposeContext(); ret = null; } diff --git a/src/java.base/share/classes/sun/net/www/protocol/http/NegotiateAuthentication.java b/src/java.base/share/classes/sun/net/www/protocol/http/NegotiateAuthentication.java index c27d866f5eff..c016b0dae298 100644 --- a/src/java.base/share/classes/sun/net/www/protocol/http/NegotiateAuthentication.java +++ b/src/java.base/share/classes/sun/net/www/protocol/http/NegotiateAuthentication.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 @@ -168,29 +168,24 @@ public boolean isAuthorizationStale (String header) { * @param p A source of header values for this connection, not used because * HeaderParser converts the fields to lower case, use raw instead * @param raw The raw header field. - * @return true if all goes well, false if no headers were set. + * @throws IOException if no headers were set */ @Override - public boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) { + public void setHeaders(HttpURLConnection conn, HeaderParser p, String raw) throws IOException { // no need to synchronize here: // already locked by s.n.w.p.h.HttpURLConnection assert conn.isLockHeldByCurrentThread(); - try { - String response; - byte[] incoming = null; - String[] parts = raw.split("\\s+"); - if (parts.length > 1) { - incoming = Base64.getDecoder().decode(parts[1]); - } - response = hci.scheme + " " + Base64.getEncoder().encodeToString( - incoming==null?firstToken():nextToken(incoming)); - - conn.setAuthenticationProperty(getHeaderName(), response); - return true; - } catch (IOException e) { - return false; + String response; + byte[] incoming = null; + String[] parts = raw.split("\\s+"); + if (parts.length > 1) { + incoming = Base64.getDecoder().decode(parts[1]); } + response = hci.scheme + " " + Base64.getEncoder().encodeToString( + incoming==null?firstToken():nextToken(incoming)); + + conn.setAuthenticationProperty(getHeaderName(), response); } /** diff --git a/src/java.base/unix/classes/sun/net/www/protocol/http/ntlm/NTLMAuthentication.java b/src/java.base/unix/classes/sun/net/www/protocol/http/ntlm/NTLMAuthentication.java index dc56abb86dfe..efe72fc760fc 100644 --- a/src/java.base/unix/classes/sun/net/www/protocol/http/ntlm/NTLMAuthentication.java +++ b/src/java.base/unix/classes/sun/net/www/protocol/http/ntlm/NTLMAuthentication.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 @@ -35,7 +35,6 @@ import java.security.GeneralSecurityException; import java.util.Base64; import java.util.Locale; -import java.util.Properties; import sun.net.www.HeaderParser; import sun.net.www.protocol.http.AuthenticationInfo; @@ -203,10 +202,10 @@ public boolean isAuthorizationStale (String header) { * @param p A source of header values for this connection, not used because * HeaderParser converts the fields to lower case, use raw instead * @param raw The raw header field. - * @return true if all goes well, false if no headers were set. + * @throws IOException if no headers were set */ @Override - public boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) { + public void setHeaders(HttpURLConnection conn, HeaderParser p, String raw) throws IOException { // no need to synchronize here: // already locked by s.n.w.p.h.HttpURLConnection assert conn.isLockHeldByCurrentThread(); @@ -220,9 +219,8 @@ public boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) { response = buildType3Msg (msg); } conn.setAuthenticationProperty(getHeaderName(), response); - return true; - } catch (IOException | GeneralSecurityException e) { - return false; + } catch (GeneralSecurityException e) { + throw new IOException(e); } } @@ -232,8 +230,7 @@ private String buildType1Msg () { return result; } - private String buildType3Msg (String challenge) throws GeneralSecurityException, - IOException { + private String buildType3Msg (String challenge) throws GeneralSecurityException { /* First decode the type2 message to get the server nonce */ /* nonce is located at type2[24] for 8 bytes */ diff --git a/src/java.base/windows/classes/sun/net/www/protocol/http/ntlm/NTLMAuthSequence.java b/src/java.base/windows/classes/sun/net/www/protocol/http/ntlm/NTLMAuthSequence.java index 517b4801e2c9..768900457247 100644 --- a/src/java.base/windows/classes/sun/net/www/protocol/http/ntlm/NTLMAuthSequence.java +++ b/src/java.base/windows/classes/sun/net/www/protocol/http/ntlm/NTLMAuthSequence.java @@ -91,6 +91,7 @@ public boolean isComplete() { private native long getCredentialsHandle (String user, String domain, String password); - private native byte[] getNextToken (long crdHandle, byte[] lastToken, Status returned); + private native byte[] getNextToken (long crdHandle, byte[] lastToken, Status returned) + throws IOException; } diff --git a/src/java.base/windows/classes/sun/net/www/protocol/http/ntlm/NTLMAuthentication.java b/src/java.base/windows/classes/sun/net/www/protocol/http/ntlm/NTLMAuthentication.java index a7056082e129..75a9dc027b12 100644 --- a/src/java.base/windows/classes/sun/net/www/protocol/http/ntlm/NTLMAuthentication.java +++ b/src/java.base/windows/classes/sun/net/www/protocol/http/ntlm/NTLMAuthentication.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 @@ -26,9 +26,7 @@ package sun.net.www.protocol.http.ntlm; import java.io.IOException; -import java.net.InetAddress; import java.net.PasswordAuthentication; -import java.net.UnknownHostException; import java.net.URL; import java.util.Locale; import sun.net.NetProperties; @@ -204,10 +202,10 @@ public boolean isAuthorizationStale (String header) { * @param p A source of header values for this connection, not used because * HeaderParser converts the fields to lower case, use raw instead * @param raw The raw header field. - * @return true if all goes well, false if no headers were set. + * @throws IOException if no headers were set */ @Override - public boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) { + public void setHeaders(HttpURLConnection conn, HeaderParser p, String raw) throws IOException { // no need to synchronize here: // already locked by s.n.w.p.h.HttpURLConnection @@ -224,10 +222,9 @@ public boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) { if (seq.isComplete()) { conn.authObj(null); } - return true; } catch (IOException e) { conn.authObj(null); - return false; + throw e; } } } diff --git a/src/java.base/windows/native/libnet/NTLMAuthSequence.c b/src/java.base/windows/native/libnet/NTLMAuthSequence.c index 3864ceedd51a..5062fe6c8e96 100644 --- a/src/java.base/windows/native/libnet/NTLMAuthSequence.c +++ b/src/java.base/windows/native/libnet/NTLMAuthSequence.c @@ -232,6 +232,8 @@ JNIEXPORT jbyteArray JNICALL Java_sun_net_www_protocol_http_ntlm_NTLMAuthSequenc } if (ss < 0) { + SetLastError(ss); + JNU_ThrowIOExceptionWithLastError(env, "InitializeSecurityContext"); endSequence (pCred, pCtx, env, status); return 0; } @@ -240,6 +242,8 @@ JNIEXPORT jbyteArray JNICALL Java_sun_net_www_protocol_http_ntlm_NTLMAuthSequenc ss = CompleteAuthToken( pCtx, &OutBuffDesc ); if (ss < 0) { + SetLastError(ss); + JNU_ThrowIOExceptionWithLastError(env, "CompleteAuthToken"); endSequence (pCred, pCtx, env, status); return 0; } diff --git a/test/jdk/sun/net/www/protocol/http/NTLMFailTest.java b/test/jdk/sun/net/www/protocol/http/NTLMFailTest.java new file mode 100644 index 000000000000..86db3d400da0 --- /dev/null +++ b/test/jdk/sun/net/www/protocol/http/NTLMFailTest.java @@ -0,0 +1,167 @@ +/* + * 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 8372731 + * @library /test/lib + * @run main/othervm NTLMFailTest + * @run main/othervm -Djdk.includeInExceptions= NTLMFailTest + * @summary check that the Authentication failure exception + * honors the jdk.includeInExceptions setting + */ + +import jdk.test.lib.net.HttpHeaderParser; +import jdk.test.lib.net.URIBuilder; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Authenticator; +import java.net.HttpURLConnection; +import java.net.InetAddress; +import java.net.PasswordAuthentication; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.URL; + +public class NTLMFailTest { + + static final int BODY_LEN = 8192; + + static final String RESP_SERVER_AUTH = + "HTTP/1.1 401 Unauthorized\r\n" + + "WWW-Authenticate: NTLM\r\n" + + "Connection: close\r\n" + + "Content-Length: " + BODY_LEN + "\r\n" + + "\r\n"; + + static final String RESP_SERVER_NTLM = + "HTTP/1.1 401 Unauthorized\r\n" + + "WWW-Authenticate: NTLM InvalidChallenge\r\n" + + "Connection: Keep-Alive\r\n" + + "Content-Length: " + BODY_LEN + "\r\n" + + "\r\n"; + + public static void main(String[] args) throws Exception { + Authenticator.setDefault(new TestAuthenticator()); + try (NTLMServer server = startServer(new ServerSocket(0, 0, InetAddress.getLoopbackAddress()))) { + URL url = URIBuilder.newBuilder() + .scheme("http") + .loopback() + .port(server.getLocalPort()) + .path("/") + .toURLUnchecked(); + HttpURLConnection uc = (HttpURLConnection) url.openConnection(); + uc.setRequestMethod("HEAD"); + uc.getInputStream().readAllBytes(); + throw new RuntimeException("Expected exception was not thrown"); + } catch (IOException e) { + if (e.getMessage().contains("Authentication failure")) { + System.err.println("Got expected exception:"); + e.printStackTrace(); + if (System.getProperty("jdk.includeInExceptions") == null) { + // detailed message enabled by default + if (e.getCause() == null) { + throw new RuntimeException("Expected a detailed exception", e); + } + // no checks on the detailed message; it's platform-specific and may be translated + } else { + // detailed message disabled + if (e.getCause() != null) { + throw new RuntimeException("Unexpected detailed exception", e); + } + } + } else { + throw e; + } + } + } + + static class NTLMServer extends Thread implements AutoCloseable { + final ServerSocket ss; + volatile boolean closed; + + NTLMServer(ServerSocket serverSS) { + super(); + setDaemon(true); + this.ss = serverSS; + } + + int getLocalPort() { return ss.getLocalPort(); } + + @Override + public void run() { + boolean doing2ndStageNTLM = false; + while (!closed) { + try { + Socket s = ss.accept(); + InputStream is = s.getInputStream(); + OutputStream os = s.getOutputStream(); + doServer(is, os, doing2ndStageNTLM); + if (!doing2ndStageNTLM) { + doing2ndStageNTLM = true; + } else { + os.close(); + } + } catch (IOException ioe) { + if (!closed) { + ioe.printStackTrace(); + } + } + } + } + + @Override + public void close() { + if (closed) return; + synchronized(this) { + if (closed) return; + closed = true; + } + try { ss.close(); } catch (IOException x) { }; + } + } + + static NTLMServer startServer(ServerSocket serverSS) { + NTLMServer server = new NTLMServer(serverSS); + server.start(); + return server; + } + + static void doServer(InputStream is, OutputStream os, boolean doing2ndStageNTLM) throws IOException { + if (!doing2ndStageNTLM) { + new HttpHeaderParser(is); + os.write(RESP_SERVER_AUTH.getBytes("ASCII")); + } else { + new HttpHeaderParser(is); + os.write(RESP_SERVER_NTLM.getBytes("ASCII")); + } + } + + static class TestAuthenticator extends Authenticator { + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication("test", "secret".toCharArray()); + } + } +} From 6d212e8d15d34251590b15e6e188939d5369ad4e Mon Sep 17 00:00:00 2001 From: Goetz Lindenmaier Date: Tue, 16 Jun 2026 12:42:23 +0000 Subject: [PATCH 17/86] 8386551: Windows build broken because of MSys2/Make update Backport-of: 771bebdc683c9e6e9a7456971595fd440af71ae3 --- make/autoconf/basic_tools.m4 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/make/autoconf/basic_tools.m4 b/make/autoconf/basic_tools.m4 index 0fa001c5c90d..675e1c7eec7e 100644 --- a/make/autoconf/basic_tools.m4 +++ b/make/autoconf/basic_tools.m4 @@ -148,7 +148,7 @@ AC_DEFUN([BASIC_CHECK_MAKE_VERSION], if test "x$OPENJDK_BUILD_OS_ENV" = "xwindows.cygwin"; then MAKE_EXPECTED_ENV='cygwin' elif test "x$OPENJDK_BUILD_OS_ENV" = "xwindows.msys2"; then - MAKE_EXPECTED_ENV='msys' + MAKE_EXPECTED_ENV='cygwin|msys' elif test "x$OPENJDK_BUILD_OS_ENV" = "xwindows.wsl1" || test "x$OPENJDK_BUILD_OS_ENV" = "xwindows.wsl2"; then if test "x$OPENJDK_BUILD_CPU" = "xaarch64"; then MAKE_EXPECTED_ENV='aarch64-.*-linux-gnu' @@ -159,7 +159,7 @@ AC_DEFUN([BASIC_CHECK_MAKE_VERSION], AC_MSG_ERROR([Unknown Windows environment]) fi MAKE_BUILT_FOR=`$MAKE_CANDIDATE --version | $GREP -i 'built for'` - IS_MAKE_CORRECT_ENV=`$ECHO $MAKE_BUILT_FOR | $GREP $MAKE_EXPECTED_ENV` + IS_MAKE_CORRECT_ENV=`$ECHO $MAKE_BUILT_FOR | $GREP -E $MAKE_EXPECTED_ENV` else # Not relevant for non-Windows IS_MAKE_CORRECT_ENV=true From fcc3f6bb2a1a28d65cce048030f00ca125d0905e Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Tue, 16 Jun 2026 15:28:47 +0000 Subject: [PATCH 18/86] 8381771: Add a check for DNS label not to end with a hyphen Backport-of: 135451eed03f1cb568f64f3335a8854c35950f40 --- .../classes/sun/security/x509/DNSName.java | 96 +++++----- .../x509/GeneralName/DNSNameTest.java | 176 +++++++++--------- 2 files changed, 139 insertions(+), 133 deletions(-) diff --git a/src/java.base/share/classes/sun/security/x509/DNSName.java b/src/java.base/share/classes/sun/security/x509/DNSName.java index 597652022ced..ce903a3d16cf 100644 --- a/src/java.base/share/classes/sun/security/x509/DNSName.java +++ b/src/java.base/share/classes/sun/security/x509/DNSName.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -52,8 +52,8 @@ public class DNSName implements GeneralNameInterface { private final String name; - private static final String alphaDigits = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + private static final String DNS_ALLOWED = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-"; /** * Create the DNSName object from the passed encoded Der value. @@ -73,52 +73,64 @@ public DNSName(DerValue derValue) throws IOException { * @throws IOException if the name is not a valid DNSName */ public DNSName(String name, boolean allowWildcard) throws IOException { - if (name == null || name.isEmpty()) + + // Check the full name. + if (name == null || name.isEmpty()) { throw new IOException("DNSName must not be null or empty"); - if (name.contains(" ")) - throw new IOException("DNSName with blank components is not permitted"); - if (name.startsWith(".") || name.endsWith(".")) + } + + if (name.contains(" ")) { + throw new IOException( + "DNSName with blank labels is not permitted"); + } + + if (name.startsWith(".") || name.endsWith(".")) { throw new IOException("DNSName may not begin or end with a ."); - /* - * Name will consist of label components separated by "." - * startIndex is the index of the first character of a component - * endIndex is the index of the last character of a component plus 1 - */ - for (int endIndex,startIndex = 0; startIndex < name.length(); startIndex = endIndex+1) { - endIndex = name.indexOf('.', startIndex); - if (endIndex < 0) { - endIndex = name.length(); + } + + // RFC 1123 Section 2.1 and RFC 2181 Section 11 + if (name.length() > 253) { + throw new IOException( + "DNSName can't be longer than 253 characters"); + } + + // Check the labels. + String[] labels = name.split("\\."); + + for (int i = 0; i < labels.length; i++) { + String label = labels[i]; + + if (label.isEmpty()) { + throw new IOException( + "DNSName with empty labels is not permitted"); } - if (endIndex - startIndex < 1) - throw new IOException("DNSName with empty components are not permitted"); - - if (allowWildcard) { - // RFC 1123: DNSName components must begin with a letter or digit - // or RFC 4592: the first component of a DNSName can have only a wildcard - // character * (asterisk), i.e. *.example.com. Asterisks at other components - // will not be allowed as a wildcard. - if (alphaDigits.indexOf(name.charAt(startIndex)) < 0) { - // Checking to make sure the wildcard only appears in the first component, - // and it has to be at least 3-char long with the form of *.[alphaDigit] - if ((name.length() < 3) || (name.indexOf('*') != 0) || - (name.charAt(startIndex+1) != '.') || - (alphaDigits.indexOf(name.charAt(startIndex+2)) < 0)) - throw new IOException("DNSName components must begin with a letter, digit, " - + "or the first component can have only a wildcard character *"); - } - } else { - // RFC 1123: DNSName components must begin with a letter or digit - if (alphaDigits.indexOf(name.charAt(startIndex)) < 0) - throw new IOException("DNSName components must begin with a letter or digit"); + + // RFC 1123 Section 2.1 + if (label.length() > 63) { + throw new IOException( + "DNSName label can't be longer than 63 characters"); + } + + // RFC 1035 Section 2.3.1 + if (label.startsWith("-") || label.endsWith("-")) { + throw new IOException( + "DNSName label may not begin or end with a hyphen"); } - //nonStartIndex: index for characters in the component beyond the first one - for (int nonStartIndex=startIndex+1; nonStartIndex < endIndex; nonStartIndex++) { - char x = name.charAt(nonStartIndex); - if ((alphaDigits).indexOf(x) < 0 && x != '-') - throw new IOException("DNSName components must consist of letters, digits, and hyphens"); + // RFC 9525 Section 6.3 + if (allowWildcard && label.equals("*") && i == 0 + && labels.length > 1) { + continue; + } + + for (char c : label.toCharArray()) { + if (DNS_ALLOWED.indexOf(c) < 0) { + throw new IOException("DNSName labels must consist of " + + "letters, digits, and hyphens"); + } } } + this.name = name; } diff --git a/test/jdk/sun/security/x509/GeneralName/DNSNameTest.java b/test/jdk/sun/security/x509/GeneralName/DNSNameTest.java index c4905cd5eb1c..8b3a23985726 100644 --- a/test/jdk/sun/security/x509/GeneralName/DNSNameTest.java +++ b/test/jdk/sun/security/x509/GeneralName/DNSNameTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * 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,132 +21,126 @@ * questions. */ -/** +import static jdk.test.lib.Asserts.fail; + +import java.io.IOException; +import java.net.IDN; +import java.util.List; +import java.util.stream.Stream; +import sun.security.x509.DNSName; + +/* * @test * @summary DNSName parsing tests - * @bug 8213952 8186143 + * @bug 8213952 8186143 8381771 + * @library /test/lib * @modules java.base/sun.security.x509 - * @run testng DNSNameTest + * @run main DNSNameTest */ -import java.io.IOException; -import sun.security.x509.DNSName; +public class DNSNameTest { -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; + private static final List GOOD_NAMES = List.of( + "abc", + String.join(".", "a".repeat(63), "b".repeat(63), + "c".repeat(63), "d".repeat(61)), + "abc.com", + "tesT.Abc.com", + "ABC.COM", + "a12.com", + "a1b2c3.com", + "1abc.com", + "123.com", + "a-b-c.com", // hyphens + IDN.toASCII("公司.江利子") // IDN punycode + ); -import static org.testng.Assert.*; + private static final List GOOD_SAN_NAMES = Stream.concat( + Stream.of( + "*.domain.com", // wildcard in 1st level subdomain + "*.com"), + GOOD_NAMES.stream()) + .toList(); -public class DNSNameTest { - @DataProvider(name = "goodNames") - public Object[][] goodNames() { - Object[][] data = { - {"abc.com"}, - {"ABC.COM"}, - {"a12.com"}, - {"a1b2c3.com"}, - {"1abc.com"}, - {"123.com"}, - {"abc.com-"}, // end with hyphen - {"a-b-c.com"}, // hyphens - }; - return data; - } + private static final List BAD_NAMES = List.of( + // DNSName too long + String.join(".", "a".repeat(63), "b".repeat(63), + "c".repeat(63), "d".repeat(62)), + // DNSName label too long + "a".repeat(64), + " 1abc.com", // begin with space + "1abc.com ", // end with space + "1a bc.com ", // no space allowed + "-abc.com", // name begins with a hyphen + "abc.com-", // name ends with a hyphen + "abc.-com", // label begins with a hyphen + "abc-.com", // label ends with a hyphen + "a..b", // .. + ".a", // begin with . + "a.", // end with . + "", // empty + " ", // space only + "*.domain.com", // wildcard not allowed + "a*.com" // only allow letter, digit, or hyphen + ); - @DataProvider(name = "goodSanNames") - public Object[][] goodSanNames() { - Object[][] data = { - {"abc.com"}, - {"ABC.COM"}, - {"a12.com"}, - {"a1b2c3.com"}, - {"1abc.com"}, - {"123.com"}, - {"abc.com-"}, // end with hyphen - {"a-b-c.com"}, // hyphens - {"*.domain.com"}, // wildcard in 1st level subdomain - {"*.com"}, - }; - return data; - } + private static final List BAD_SAN_NAMES = Stream.concat( + Stream.of( + "*", // wildcard only + "*.", // wildcard with a period + "*a.com", // partial wildcard disallowed + "abc.*.com", // wildcard not allowed in 2nd level + "**.domain.com", // double wildcard not allowed + "*.domain.com*", // can't end with wildcard + "a*.com"), // only allow letter, digit, or hyphen + BAD_NAMES.stream().filter(n -> !n.contains("*"))) + .toList(); - @DataProvider(name = "badNames") - public Object[][] badNames() { - Object[][] data = { - {" 1abc.com"}, // begin with space - {"1abc.com "}, // end with space - {"1a bc.com "}, // no space allowed - {"-abc.com"}, // begin with hyphen - {"a..b"}, // .. - {".a"}, // begin with . - {"a."}, // end with . - {""}, // empty - {" "}, // space only - {"*.domain.com"}, // wildcard not allowed - {"a*.com"}, // only allow letter, digit, or hyphen - }; - return data; - } - @DataProvider(name = "badSanNames") - public Object[][] badSanNames() { - Object[][] data = { - {" 1abc.com"}, // begin with space - {"1abc.com "}, // end with space - {"1a bc.com "}, // no space allowed - {"-abc.com"}, // begin with hyphen - {"a..b"}, // .. - {".a"}, // begin with . - {"a."}, // end with . - {""}, // empty - {" "}, // space only - {"*"}, // wildcard only - {"*a.com"}, // partial wildcard disallowed - {"abc.*.com"}, // wildcard not allowed in 2nd level - {"*.*.domain.com"}, // double wildcard not allowed - {"a*.com"}, // only allow letter, digit, or hyphen - }; - return data; + public static void main(String[] args) { + GOOD_NAMES.forEach(DNSNameTest::testGoodDNSName); + GOOD_SAN_NAMES.forEach(DNSNameTest::testGoodSanDNSName); + BAD_NAMES.forEach(DNSNameTest::testBadDNSName); + BAD_SAN_NAMES.forEach(DNSNameTest::testBadSanDNSName); } - - @Test(dataProvider = "goodNames") - public void testGoodDNSName(String dnsNameString) { + private static void testGoodDNSName(String dnsNameString) { try { DNSName dn = new DNSName(dnsNameString); } catch (IOException e) { - fail("Unexpected IOException"); + fail("Unexpected IOException with input " + dnsNameString + ": " + + e.getMessage()); } } - @Test(dataProvider = "goodSanNames") - public void testGoodSanDNSName(String dnsNameString) { + private static void testGoodSanDNSName(String dnsNameString) { try { DNSName dn = new DNSName(dnsNameString, true); } catch (IOException e) { - fail("Unexpected IOException"); + fail("Unexpected IOException with input " + dnsNameString + ": " + + e.getMessage()); } } - @Test(dataProvider = "badNames") - public void testBadDNSName(String dnsNameString) { + private static void testBadDNSName(String dnsNameString) { try { DNSName dn = new DNSName(dnsNameString); - fail("IOException expected"); + fail("IOException expected with input " + dnsNameString); } catch (IOException e) { - if (!e.getMessage().contains("DNSName")) + if (!e.getMessage().contains("DNSName")) { fail("Unexpected message: " + e); + } } } - @Test(dataProvider = "badSanNames") - public void testBadSanDNSName(String dnsNameString) { + private static void testBadSanDNSName(String dnsNameString) { try { DNSName dn = new DNSName(dnsNameString, true); - fail("IOException expected"); + fail("IOException expected with input " + dnsNameString); } catch (IOException e) { - if (!e.getMessage().contains("DNSName")) + if (!e.getMessage().contains("DNSName")) { fail("Unexpected message: " + e); + } } } } From 11743f5a8c4535fa5f90721bf4d2c6df5890178b Mon Sep 17 00:00:00 2001 From: William Kemper Date: Tue, 16 Jun 2026 16:11:02 +0000 Subject: [PATCH 19/86] 8369447: GenShen: Regulator thread may observe inconsistent states Reviewed-by: kdnilsen, phh Backport-of: 926f61f2e358c92cdb7ccdf75c853aa599f4dde3 --- .../share/gc/shenandoah/shenandoahRegulatorThread.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahRegulatorThread.cpp b/src/hotspot/share/gc/shenandoah/shenandoahRegulatorThread.cpp index 774c4f7d9413..ec4b7c7217c1 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahRegulatorThread.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahRegulatorThread.cpp @@ -58,6 +58,7 @@ void ShenandoahRegulatorThread::run_service() { void ShenandoahRegulatorThread::regulate_young_and_old_cycles() { while (!should_terminate()) { + SuspendibleThreadSetJoiner joiner; ShenandoahGenerationalControlThread::GCMode mode = _control_thread->gc_mode(); if (mode == ShenandoahGenerationalControlThread::none) { if (should_start_metaspace_gc()) { @@ -95,6 +96,7 @@ void ShenandoahRegulatorThread::regulate_young_and_old_cycles() { void ShenandoahRegulatorThread::regulate_young_and_global_cycles() { while (!should_terminate()) { + SuspendibleThreadSetJoiner joiner; if (_control_thread->gc_mode() == ShenandoahGenerationalControlThread::none) { if (start_global_cycle()) { log_debug(gc)("Heuristics request for global collection accepted."); @@ -122,6 +124,7 @@ void ShenandoahRegulatorThread::regulator_sleep() { _last_sleep_adjust_time = current; } + SuspendibleThreadSetLeaver leaver; os::naked_short_sleep(_sleep); if (LogTarget(Debug, gc, thread)::is_enabled()) { double elapsed = os::elapsedTime() - current; @@ -146,6 +149,13 @@ bool ShenandoahRegulatorThread::start_global_cycle() const { bool ShenandoahRegulatorThread::request_concurrent_gc(ShenandoahGeneration* generation) const { double now = os::elapsedTime(); + + // This call may find the control thread waiting on workers which have suspended + // to allow a safepoint to run. If this regulator thread does not yield, the safepoint + // will not run. The worker threads won't progress, the control thread won't progress, + // and the regulator thread may never yield. Therefore, we leave the suspendible + // thread set before making this call. + SuspendibleThreadSetLeaver leaver; bool accepted = _control_thread->request_concurrent_gc(generation); if (LogTarget(Debug, gc, thread)::is_enabled() && accepted) { double wait_time = os::elapsedTime() - now; From 3674bc68738994877ee9389c8849e1481e4c008c Mon Sep 17 00:00:00 2001 From: Mohamed Issa Date: Tue, 16 Jun 2026 22:15:11 +0000 Subject: [PATCH 20/86] 8364305: Support AVX10 saturating floating point conversion instructions Reviewed-by: phh Backport-of: 37f0e74d328d909810b54f7889cca991426d7488 --- src/hotspot/cpu/x86/assembler_x86.cpp | 152 +++++++++++++ src/hotspot/cpu/x86/assembler_x86.hpp | 16 ++ src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp | 90 +++++++- src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp | 7 + src/hotspot/cpu/x86/x86.ad | 71 +++++- src/hotspot/cpu/x86/x86_64.ad | 92 ++++++++ .../floatingpoint/ScalarFPtoIntCastTest.java | 207 ++++++++++++++++++ .../compiler/lib/ir_framework/IRNode.java | 80 +++++++ .../ir_framework/test/IREncodingPrinter.java | 1 + .../vectorapi/VectorFPtoIntCastTest.java | 70 ++++-- .../runner/ArrayTypeConvertTest.java | 76 +++++-- 11 files changed, 818 insertions(+), 44 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/floatingpoint/ScalarFPtoIntCastTest.java diff --git a/src/hotspot/cpu/x86/assembler_x86.cpp b/src/hotspot/cpu/x86/assembler_x86.cpp index 3f1140c937ba..fd62e9358bfb 100644 --- a/src/hotspot/cpu/x86/assembler_x86.cpp +++ b/src/hotspot/cpu/x86/assembler_x86.cpp @@ -2225,6 +2225,44 @@ void Assembler::cvttss2sil(Register dst, XMMRegister src) { emit_int16(0x2C, (0xC0 | encode)); } +void Assembler::evcvttss2sisl(Register dst, XMMRegister src) { + assert(VM_Version::supports_avx10_2(), ""); + InstructionAttr attributes(AVX_128bit, /* vex_w */ false, /* legacy_mode */ false, /* no_mask_reg */ true, /* uses_vl */ false); + attributes.set_is_evex_instruction(); + int encode = vex_prefix_and_encode(dst->encoding(), 0, src->encoding(), VEX_SIMD_F3, VEX_OPCODE_MAP5, &attributes); + emit_int16(0x6D, (0xC0 | encode)); +} + +void Assembler::evcvttss2sisl(Register dst, Address src) { + assert(VM_Version::supports_avx10_2(), ""); + InstructionMark im(this); + InstructionAttr attributes(AVX_128bit, /* vex_w */ false, /* legacy_mode */ false, /* no_mask_reg */ true, /* uses_vl */ false); + attributes.set_address_attributes(/* tuple_type */ EVEX_T1S, /* input_size_in_bits */ EVEX_32bit); + attributes.set_is_evex_instruction(); + vex_prefix(src, 0, dst->encoding(), VEX_SIMD_F3, VEX_OPCODE_MAP5, &attributes); + emit_int8((unsigned char)0x6D); + emit_operand(dst, src, 0); +} + +void Assembler::evcvttss2sisq(Register dst, XMMRegister src) { + assert(VM_Version::supports_avx10_2(), ""); + InstructionAttr attributes(AVX_128bit, /* vex_w */ true, /* legacy_mode */ false, /* no_mask_reg */ true, /* uses_vl */ false); + attributes.set_is_evex_instruction(); + int encode = vex_prefix_and_encode(dst->encoding(), 0, src->encoding(), VEX_SIMD_F3, VEX_OPCODE_MAP5, &attributes); + emit_int16(0x6D, (0xC0 | encode)); +} + +void Assembler::evcvttss2sisq(Register dst, Address src) { + assert(VM_Version::supports_avx10_2(), ""); + InstructionMark im(this); + InstructionAttr attributes(AVX_128bit, /* vex_w */ true, /* legacy_mode */ false, /* no_mask_reg */ true, /* uses_vl */ false); + attributes.set_address_attributes(/* tuple_type */ EVEX_T1S, /* input_size_in_bits */ EVEX_32bit); + attributes.set_is_evex_instruction(); + vex_prefix(src, 0, dst->encoding(), VEX_SIMD_F3, VEX_OPCODE_MAP5, &attributes); + emit_int8((unsigned char)0x6D); + emit_operand(dst, src, 0); +} + void Assembler::cvttpd2dq(XMMRegister dst, XMMRegister src) { int vector_len = VM_Version::supports_avx512novl() ? AVX_512bit : AVX_128bit; InstructionAttr attributes(vector_len, /* rex_w */ true, /* legacy_mode */ false, /* no_mask_reg */ true, /* uses_vl */ true); @@ -2310,6 +2348,25 @@ void Assembler::vcvttps2dq(XMMRegister dst, XMMRegister src, int vector_len) { emit_int16(0x5B, (0xC0 | encode)); } +void Assembler::evcvttps2dqs(XMMRegister dst, XMMRegister src, int vector_len) { + assert(VM_Version::supports_avx10_2(), ""); + InstructionAttr attributes(vector_len, /* rex_w */ false, /* legacy_mode */ false, /* no_mask_reg */ true, /* uses_vl */ true); + attributes.set_is_evex_instruction(); + int encode = vex_prefix_and_encode(dst->encoding(), 0, src->encoding(), VEX_SIMD_NONE, VEX_OPCODE_MAP5, &attributes); + emit_int16(0x6D, (0xC0 | encode)); +} + +void Assembler::evcvttps2dqs(XMMRegister dst, Address src, int vector_len) { + assert(VM_Version::supports_avx10_2(), ""); + InstructionMark im(this); + InstructionAttr attributes(vector_len, /* rex_w */ false, /* legacy_mode */ false, /* no_mask_reg */ true, /* uses_vl */ true); + attributes.set_address_attributes(/* tuple_type */ EVEX_FV, /* input_size_in_bits */ EVEX_32bit); + attributes.set_is_evex_instruction(); + vex_prefix(src, 0, dst->encoding(), VEX_SIMD_NONE, VEX_OPCODE_MAP5, &attributes); + emit_int8((unsigned char)0x6D); + emit_operand(dst, src, 0); +} + void Assembler::vcvttpd2dq(XMMRegister dst, XMMRegister src, int vector_len) { assert(vector_len <= AVX_256bit ? VM_Version::supports_avx() : VM_Version::supports_evex(), ""); InstructionAttr attributes(vector_len, /* rex_w */ true, /* legacy_mode */ false, /* no_mask_reg */ true, /* uses_vl */ true); @@ -2317,6 +2374,25 @@ void Assembler::vcvttpd2dq(XMMRegister dst, XMMRegister src, int vector_len) { emit_int16((unsigned char)0xE6, (0xC0 | encode)); } +void Assembler::evcvttpd2dqs(XMMRegister dst, XMMRegister src, int vector_len) { + assert(VM_Version::supports_avx10_2(), ""); + InstructionAttr attributes(vector_len, /* rex_w */ true, /* legacy_mode */ false, /* no_mask_reg */ true, /* uses_vl */ true); + attributes.set_is_evex_instruction(); + int encode = vex_prefix_and_encode(dst->encoding(), 0, src->encoding(), VEX_SIMD_NONE, VEX_OPCODE_MAP5, &attributes); + emit_int16(0x6D, (0xC0 | encode)); +} + +void Assembler::evcvttpd2dqs(XMMRegister dst, Address src, int vector_len) { + assert(VM_Version::supports_avx10_2(), ""); + InstructionMark im(this); + InstructionAttr attributes(vector_len, /* rex_w */ true, /* legacy_mode */ false, /* no_mask_reg */ true, /* uses_vl */ true); + attributes.set_address_attributes(/* tuple_type */ EVEX_FV, /* input_size_in_bits */ EVEX_64bit); + attributes.set_is_evex_instruction(); + vex_prefix(src, 0, dst->encoding(), VEX_SIMD_NONE, VEX_OPCODE_MAP5, &attributes); + emit_int8((unsigned char)0x6D); + emit_operand(dst, src, 0); +} + void Assembler::vcvtps2dq(XMMRegister dst, XMMRegister src, int vector_len) { assert(vector_len <= AVX_256bit ? VM_Version::supports_avx() : VM_Version::supports_evex(), ""); InstructionAttr attributes(vector_len, /* rex_w */ false, /* legacy_mode */ false, /* no_mask_reg */ true, /* uses_vl */ true); @@ -2332,6 +2408,25 @@ void Assembler::evcvttps2qq(XMMRegister dst, XMMRegister src, int vector_len) { emit_int16(0x7A, (0xC0 | encode)); } +void Assembler::evcvttps2qqs(XMMRegister dst, XMMRegister src, int vector_len) { + assert(VM_Version::supports_avx10_2(), ""); + InstructionAttr attributes(vector_len, /* rex_w */ false, /* legacy_mode */ false, /* no_mask_reg */ true, /* uses_vl */ true); + attributes.set_is_evex_instruction(); + int encode = vex_prefix_and_encode(dst->encoding(), 0, src->encoding(), VEX_SIMD_66, VEX_OPCODE_MAP5, &attributes); + emit_int16(0x6D, (0xC0 | encode)); +} + +void Assembler::evcvttps2qqs(XMMRegister dst, Address src, int vector_len) { + assert(VM_Version::supports_avx10_2(), ""); + InstructionMark im(this); + InstructionAttr attributes(vector_len, /* rex_w */ false, /* legacy_mode */ false, /* no_mask_reg */ true, /* uses_vl */ true); + attributes.set_address_attributes(/* tuple_type */ EVEX_HV, /* input_size_in_bits */ EVEX_32bit); + attributes.set_is_evex_instruction(); + vex_prefix(src, 0, dst->encoding(), VEX_SIMD_66, VEX_OPCODE_MAP5, &attributes); + emit_int8((unsigned char)0x6D); + emit_operand(dst, src, 0); +} + void Assembler::evcvtpd2qq(XMMRegister dst, XMMRegister src, int vector_len) { assert(VM_Version::supports_avx512dq(), ""); InstructionAttr attributes(vector_len, /* rex_w */ true, /* legacy_mode */ false, /* no_mask_reg */ true, /* uses_vl */ true); @@ -2356,6 +2451,25 @@ void Assembler::evcvttpd2qq(XMMRegister dst, XMMRegister src, int vector_len) { emit_int16(0x7A, (0xC0 | encode)); } +void Assembler::evcvttpd2qqs(XMMRegister dst, XMMRegister src, int vector_len) { + assert(VM_Version::supports_avx10_2(), ""); + InstructionAttr attributes(vector_len, /* rex_w */ true, /* legacy_mode */ false, /* no_mask_reg */ true, /* uses_vl */ true); + attributes.set_is_evex_instruction(); + int encode = vex_prefix_and_encode(dst->encoding(), 0, src->encoding(), VEX_SIMD_66, VEX_OPCODE_MAP5, &attributes); + emit_int16(0x6D, (0xC0 | encode)); +} + +void Assembler::evcvttpd2qqs(XMMRegister dst, Address src, int vector_len) { + assert(VM_Version::supports_avx10_2(), ""); + InstructionMark im(this); + InstructionAttr attributes(vector_len, /* rex_w */ true, /* legacy_mode */ false, /* no_mask_reg */ true, /* uses_vl */ true); + attributes.set_address_attributes(/* tuple_type */ EVEX_FV, /* input_size_in_bits */ EVEX_64bit); + attributes.set_is_evex_instruction(); + vex_prefix(src, 0, dst->encoding(), VEX_SIMD_66, VEX_OPCODE_MAP5, &attributes); + emit_int8((unsigned char)0x6D); + emit_operand(dst, src, 0); +} + void Assembler::evcvtqq2pd(XMMRegister dst, XMMRegister src, int vector_len) { assert(VM_Version::supports_avx512dq(), ""); InstructionAttr attributes(vector_len, /* rex_w */ true, /* legacy_mode */ false, /* no_mask_reg */ true, /* uses_vl */ true); @@ -14988,6 +15102,44 @@ void Assembler::cvttsd2siq(Register dst, Address src) { emit_operand(dst, src, 0); } +void Assembler::evcvttsd2sisl(Register dst, XMMRegister src) { + assert(VM_Version::supports_avx10_2(), ""); + InstructionAttr attributes(AVX_128bit, /* vex_w */ false, /* legacy_mode */ false, /* no_mask_reg */ true, /* uses_vl */ false); + attributes.set_is_evex_instruction(); + int encode = vex_prefix_and_encode(dst->encoding(), 0, src->encoding(), VEX_SIMD_F2, VEX_OPCODE_MAP5, &attributes); + emit_int16(0x6D, (0xC0 | encode)); +} + +void Assembler::evcvttsd2sisl(Register dst, Address src) { + assert(VM_Version::supports_avx10_2(), ""); + InstructionMark im(this); + InstructionAttr attributes(AVX_128bit, /* vex_w */ false, /* legacy_mode */ false, /* no_mask_reg */ true, /* uses_vl */ false); + attributes.set_address_attributes(/* tuple_type */ EVEX_T1S, /* input_size_in_bits */ EVEX_64bit); + attributes.set_is_evex_instruction(); + vex_prefix(src, 0, dst->encoding(), VEX_SIMD_F2, VEX_OPCODE_MAP5, &attributes); + emit_int8((unsigned char)0x6D); + emit_operand(dst, src, 0); +} + +void Assembler::evcvttsd2sisq(Register dst, XMMRegister src) { + assert(VM_Version::supports_avx10_2(), ""); + InstructionAttr attributes(AVX_128bit, /* vex_w */ true, /* legacy_mode */ false, /* no_mask_reg */ true, /* uses_vl */ false); + attributes.set_is_evex_instruction(); + int encode = vex_prefix_and_encode(dst->encoding(), 0, src->encoding(), VEX_SIMD_F2, VEX_OPCODE_MAP5, &attributes); + emit_int16(0x6D, (0xC0 | encode)); +} + +void Assembler::evcvttsd2sisq(Register dst, Address src) { + assert(VM_Version::supports_avx10_2(), ""); + InstructionMark im(this); + InstructionAttr attributes(AVX_128bit, /* vex_w */ true, /* legacy_mode */ false, /* no_mask_reg */ true, /* uses_vl */ false); + attributes.set_address_attributes(/* tuple_type */ EVEX_T1S, /* input_size_in_bits */ EVEX_64bit); + attributes.set_is_evex_instruction(); + vex_prefix(src, 0, dst->encoding(), VEX_SIMD_F2, VEX_OPCODE_MAP5, &attributes); + emit_int8((unsigned char)0x6D); + emit_operand(dst, src, 0); +} + void Assembler::cvttsd2siq(Register dst, XMMRegister src) { InstructionAttr attributes(AVX_128bit, /* rex_w */ true, /* legacy_mode */ false, /* no_mask_reg */ true, /* uses_vl */ false); int encode = simd_prefix_and_encode(as_XMMRegister(dst->encoding()), xnoreg, src, VEX_SIMD_F2, VEX_OPCODE_0F, &attributes); diff --git a/src/hotspot/cpu/x86/assembler_x86.hpp b/src/hotspot/cpu/x86/assembler_x86.hpp index 99dade412b29..c863191df4cf 100644 --- a/src/hotspot/cpu/x86/assembler_x86.hpp +++ b/src/hotspot/cpu/x86/assembler_x86.hpp @@ -1316,11 +1316,19 @@ class Assembler : public AbstractAssembler { void cvttsd2sil(Register dst, XMMRegister src); void cvttsd2siq(Register dst, Address src); void cvttsd2siq(Register dst, XMMRegister src); + void evcvttsd2sisl(Register dst, XMMRegister src); + void evcvttsd2sisl(Register dst, Address src); + void evcvttsd2sisq(Register dst, XMMRegister src); + void evcvttsd2sisq(Register dst, Address src); // Convert with Truncation Scalar Single-Precision Floating-Point Value to Doubleword Integer void cvttss2sil(Register dst, XMMRegister src); void cvttss2siq(Register dst, XMMRegister src); void cvtss2sil(Register dst, XMMRegister src); + void evcvttss2sisl(Register dst, XMMRegister src); + void evcvttss2sisl(Register dst, Address src); + void evcvttss2sisq(Register dst, XMMRegister src); + void evcvttss2sisq(Register dst, Address src); // Convert vector double to int void cvttpd2dq(XMMRegister dst, XMMRegister src); @@ -1332,7 +1340,11 @@ class Assembler : public AbstractAssembler { // Convert vector float to int/long void vcvtps2dq(XMMRegister dst, XMMRegister src, int vector_len); void vcvttps2dq(XMMRegister dst, XMMRegister src, int vector_len); + void evcvttps2dqs(XMMRegister dst, XMMRegister src, int vector_len); + void evcvttps2dqs(XMMRegister dst, Address src, int vector_len); void evcvttps2qq(XMMRegister dst, XMMRegister src, int vector_len); + void evcvttps2qqs(XMMRegister dst, XMMRegister src, int vector_len); + void evcvttps2qqs(XMMRegister dst, Address src, int vector_len); // Convert vector long to vector FP void evcvtqq2ps(XMMRegister dst, XMMRegister src, int vector_len); @@ -1341,9 +1353,13 @@ class Assembler : public AbstractAssembler { // Convert vector double to long void evcvtpd2qq(XMMRegister dst, XMMRegister src, int vector_len); void evcvttpd2qq(XMMRegister dst, XMMRegister src, int vector_len); + void evcvttpd2qqs(XMMRegister dst, XMMRegister src, int vector_len); + void evcvttpd2qqs(XMMRegister dst, Address src, int vector_len); // Convert vector double to int void vcvttpd2dq(XMMRegister dst, XMMRegister src, int vector_len); + void evcvttpd2dqs(XMMRegister dst, XMMRegister src, int vector_len); + void evcvttpd2dqs(XMMRegister dst, Address src, int vector_len); // Evex casts with truncation void evpmovwb(XMMRegister dst, XMMRegister src, int vector_len); diff --git a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp index 82b8e275371f..d835631575b3 100644 --- a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp @@ -5279,12 +5279,12 @@ void C2_MacroAssembler::vector_cast_int_to_subword(BasicType to_elem_bt, XMMRegi } vpackuswb(dst, dst, zero, vec_enc); break; - default: assert(false, "%s", type2name(to_elem_bt)); + default: assert(false, "Unexpected basic type for target of vector cast int to subword: %s", type2name(to_elem_bt)); } } /* - * Algorithm for vector D2L and F2I conversions:- + * Algorithm for vector D2L and F2I conversions (AVX 10.2 unsupported):- * a) Perform vector D2L/F2I cast. * b) Choose fast path if none of the result vector lane contains 0x80000000 value. * It signifies that source value could be any of the special floating point @@ -5322,7 +5322,7 @@ void C2_MacroAssembler::vector_castF2X_evex(BasicType to_elem_bt, XMMRegister ds case T_BYTE: evpmovdb(dst, dst, vec_enc); break; - default: assert(false, "%s", type2name(to_elem_bt)); + default: assert(false, "Unexpected basic type for target of vector castF2X EVEX: %s", type2name(to_elem_bt)); } } @@ -5369,7 +5369,7 @@ void C2_MacroAssembler::vector_castD2X_evex(BasicType to_elem_bt, XMMRegister ds evpmovsqd(dst, dst, vec_enc); evpmovdb(dst, dst, vec_enc); break; - default: assert(false, "%s", type2name(to_elem_bt)); + default: assert(false, "Unexpected basic type for target of vector castD2X AVX512DQ EVEX: %s", type2name(to_elem_bt)); } } else { assert(type2aelembytes(to_elem_bt) <= 4, ""); @@ -5384,11 +5384,91 @@ void C2_MacroAssembler::vector_castD2X_evex(BasicType to_elem_bt, XMMRegister ds case T_BYTE: evpmovdb(dst, dst, vec_enc); break; - default: assert(false, "%s", type2name(to_elem_bt)); + default: assert(false, "Unexpected basic type for target of vector castD2X EVEX: %s", type2name(to_elem_bt)); } } } +void C2_MacroAssembler::vector_castF2X_avx10(BasicType to_elem_bt, XMMRegister dst, XMMRegister src, int vec_enc) { + switch(to_elem_bt) { + case T_LONG: + evcvttps2qqs(dst, src, vec_enc); + break; + case T_INT: + evcvttps2dqs(dst, src, vec_enc); + break; + case T_SHORT: + evcvttps2dqs(dst, src, vec_enc); + evpmovdw(dst, dst, vec_enc); + break; + case T_BYTE: + evcvttps2dqs(dst, src, vec_enc); + evpmovdb(dst, dst, vec_enc); + break; + default: assert(false, "Unexpected basic type for target of vector castF2X AVX10 (reg src): %s", type2name(to_elem_bt)); + } +} + +void C2_MacroAssembler::vector_castF2X_avx10(BasicType to_elem_bt, XMMRegister dst, Address src, int vec_enc) { + switch(to_elem_bt) { + case T_LONG: + evcvttps2qqs(dst, src, vec_enc); + break; + case T_INT: + evcvttps2dqs(dst, src, vec_enc); + break; + case T_SHORT: + evcvttps2dqs(dst, src, vec_enc); + evpmovdw(dst, dst, vec_enc); + break; + case T_BYTE: + evcvttps2dqs(dst, src, vec_enc); + evpmovdb(dst, dst, vec_enc); + break; + default: assert(false, "Unexpected basic type for target of vector castF2X AVX10 (mem src): %s", type2name(to_elem_bt)); + } +} + +void C2_MacroAssembler::vector_castD2X_avx10(BasicType to_elem_bt, XMMRegister dst, XMMRegister src, int vec_enc) { + switch(to_elem_bt) { + case T_LONG: + evcvttpd2qqs(dst, src, vec_enc); + break; + case T_INT: + evcvttpd2dqs(dst, src, vec_enc); + break; + case T_SHORT: + evcvttpd2dqs(dst, src, vec_enc); + evpmovdw(dst, dst, vec_enc); + break; + case T_BYTE: + evcvttpd2dqs(dst, src, vec_enc); + evpmovdb(dst, dst, vec_enc); + break; + default: assert(false, "Unexpected basic type for target of vector castD2X AVX10 (reg src): %s", type2name(to_elem_bt)); + } +} + +void C2_MacroAssembler::vector_castD2X_avx10(BasicType to_elem_bt, XMMRegister dst, Address src, int vec_enc) { + switch(to_elem_bt) { + case T_LONG: + evcvttpd2qqs(dst, src, vec_enc); + break; + case T_INT: + evcvttpd2dqs(dst, src, vec_enc); + break; + case T_SHORT: + evcvttpd2dqs(dst, src, vec_enc); + evpmovdw(dst, dst, vec_enc); + break; + case T_BYTE: + evcvttpd2dqs(dst, src, vec_enc); + evpmovdb(dst, dst, vec_enc); + break; + default: assert(false, "Unexpected basic type for target of vector castD2X AVX10 (mem src): %s", type2name(to_elem_bt)); + } +} + void C2_MacroAssembler::vector_round_double_evex(XMMRegister dst, XMMRegister src, AddressLiteral double_sign_flip, AddressLiteral new_mxcsr, int vec_enc, Register tmp, XMMRegister xtmp1, XMMRegister xtmp2, KRegister ktmp1, KRegister ktmp2) { diff --git a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp index ee6fecb9f885..d222cd37783c 100644 --- a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp +++ b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp @@ -352,6 +352,13 @@ XMMRegister xtmp2, XMMRegister xtmp3, XMMRegister xtmp4, XMMRegister xtmp5, AddressLiteral float_sign_flip, Register rscratch, int vec_enc); + void vector_castF2X_avx10(BasicType to_elem_bt, XMMRegister dst, XMMRegister src, int vec_enc); + + void vector_castF2X_avx10(BasicType to_elem_bt, XMMRegister dst, Address src, int vec_enc); + + void vector_castD2X_avx10(BasicType to_elem_bt, XMMRegister dst, XMMRegister src, int vec_enc); + + void vector_castD2X_avx10(BasicType to_elem_bt, XMMRegister dst, Address src, int vec_enc); void vector_cast_double_to_int_special_cases_avx(XMMRegister dst, XMMRegister src, XMMRegister xtmp1, XMMRegister xtmp2, XMMRegister xtmp3, XMMRegister xtmp4, XMMRegister xtmp5, Register rscratch, diff --git a/src/hotspot/cpu/x86/x86.ad b/src/hotspot/cpu/x86/x86.ad index c45581942287..d9f9cc802fe0 100644 --- a/src/hotspot/cpu/x86/x86.ad +++ b/src/hotspot/cpu/x86/x86.ad @@ -7778,8 +7778,11 @@ instruct vcastFtoD_reg(vec dst, vec src) %{ instruct castFtoX_reg_avx(vec dst, vec src, vec xtmp1, vec xtmp2, vec xtmp3, vec xtmp4, rFlagsReg cr) %{ - predicate(!VM_Version::supports_avx512vl() && Matcher::vector_length_in_bytes(n->in(1)) < 64 && - type2aelembytes(Matcher::vector_element_basic_type(n)) <= 4); + predicate(!VM_Version::supports_avx10_2() && + !VM_Version::supports_avx512vl() && + Matcher::vector_length_in_bytes(n->in(1)) < 64 && + type2aelembytes(Matcher::vector_element_basic_type(n)) <= 4 && + is_integral_type(Matcher::vector_element_basic_type(n))); match(Set dst (VectorCastF2X src)); effect(TEMP dst, TEMP xtmp1, TEMP xtmp2, TEMP xtmp3, TEMP xtmp4, KILL cr); format %{ "vector_cast_f2x $dst,$src\t! using $xtmp1, $xtmp2, $xtmp3 and $xtmp4 as TEMP" %} @@ -7801,7 +7804,8 @@ instruct castFtoX_reg_avx(vec dst, vec src, vec xtmp1, vec xtmp2, vec xtmp3, vec %} instruct castFtoX_reg_evex(vec dst, vec src, vec xtmp1, vec xtmp2, kReg ktmp1, kReg ktmp2, rFlagsReg cr) %{ - predicate((VM_Version::supports_avx512vl() || Matcher::vector_length_in_bytes(n->in(1)) == 64) && + predicate(!VM_Version::supports_avx10_2() && + (VM_Version::supports_avx512vl() || Matcher::vector_length_in_bytes(n->in(1)) == 64) && is_integral_type(Matcher::vector_element_basic_type(n))); match(Set dst (VectorCastF2X src)); effect(TEMP dst, TEMP xtmp1, TEMP xtmp2, TEMP ktmp1, TEMP ktmp2, KILL cr); @@ -7823,6 +7827,33 @@ instruct castFtoX_reg_evex(vec dst, vec src, vec xtmp1, vec xtmp2, kReg ktmp1, k ins_pipe( pipe_slow ); %} +instruct castFtoX_reg_avx10(vec dst, vec src) %{ + predicate(VM_Version::supports_avx10_2() && + is_integral_type(Matcher::vector_element_basic_type(n))); + match(Set dst (VectorCastF2X src)); + format %{ "vector_cast_f2x_avx10 $dst, $src\t!" %} + ins_encode %{ + BasicType to_elem_bt = Matcher::vector_element_basic_type(this); + int vlen_enc = (to_elem_bt == T_LONG) ? vector_length_encoding(this) : vector_length_encoding(this, $src); + __ vector_castF2X_avx10(to_elem_bt, $dst$$XMMRegister, $src$$XMMRegister, vlen_enc); + %} + ins_pipe( pipe_slow ); +%} + +instruct castFtoX_mem_avx10(vec dst, memory src) %{ + predicate(VM_Version::supports_avx10_2() && + is_integral_type(Matcher::vector_element_basic_type(n))); + match(Set dst (VectorCastF2X (LoadVector src))); + format %{ "vector_cast_f2x_avx10 $dst, $src\t!" %} + ins_encode %{ + int vlen = Matcher::vector_length(this); + BasicType to_elem_bt = Matcher::vector_element_basic_type(this); + int vlen_enc = (to_elem_bt == T_LONG) ? vector_length_encoding(this) : vector_length_encoding(vlen * sizeof(jfloat)); + __ vector_castF2X_avx10(to_elem_bt, $dst$$XMMRegister, $src$$Address, vlen_enc); + %} + ins_pipe( pipe_slow ); +%} + instruct vcastDtoF_reg(vec dst, vec src) %{ predicate(Matcher::vector_element_basic_type(n) == T_FLOAT); match(Set dst (VectorCastD2X src)); @@ -7835,7 +7866,9 @@ instruct vcastDtoF_reg(vec dst, vec src) %{ %} instruct castDtoX_reg_avx(vec dst, vec src, vec xtmp1, vec xtmp2, vec xtmp3, vec xtmp4, vec xtmp5, rFlagsReg cr) %{ - predicate(!VM_Version::supports_avx512vl() && Matcher::vector_length_in_bytes(n->in(1)) < 64 && + predicate(!VM_Version::supports_avx10_2() && + !VM_Version::supports_avx512vl() && + Matcher::vector_length_in_bytes(n->in(1)) < 64 && is_integral_type(Matcher::vector_element_basic_type(n))); match(Set dst (VectorCastD2X src)); effect(TEMP dst, TEMP xtmp1, TEMP xtmp2, TEMP xtmp3, TEMP xtmp4, TEMP xtmp5, KILL cr); @@ -7851,7 +7884,8 @@ instruct castDtoX_reg_avx(vec dst, vec src, vec xtmp1, vec xtmp2, vec xtmp3, vec %} instruct castDtoX_reg_evex(vec dst, vec src, vec xtmp1, vec xtmp2, kReg ktmp1, kReg ktmp2, rFlagsReg cr) %{ - predicate((VM_Version::supports_avx512vl() || Matcher::vector_length_in_bytes(n->in(1)) == 64) && + predicate(!VM_Version::supports_avx10_2() && + (VM_Version::supports_avx512vl() || Matcher::vector_length_in_bytes(n->in(1)) == 64) && is_integral_type(Matcher::vector_element_basic_type(n))); match(Set dst (VectorCastD2X src)); effect(TEMP dst, TEMP xtmp1, TEMP xtmp2, TEMP ktmp1, TEMP ktmp2, KILL cr); @@ -7867,6 +7901,33 @@ instruct castDtoX_reg_evex(vec dst, vec src, vec xtmp1, vec xtmp2, kReg ktmp1, k ins_pipe( pipe_slow ); %} +instruct castDtoX_reg_avx10(vec dst, vec src) %{ + predicate(VM_Version::supports_avx10_2() && + is_integral_type(Matcher::vector_element_basic_type(n))); + match(Set dst (VectorCastD2X src)); + format %{ "vector_cast_d2x_avx10 $dst, $src\t!" %} + ins_encode %{ + int vlen_enc = vector_length_encoding(this, $src); + BasicType to_elem_bt = Matcher::vector_element_basic_type(this); + __ vector_castD2X_avx10(to_elem_bt, $dst$$XMMRegister, $src$$XMMRegister, vlen_enc); + %} + ins_pipe( pipe_slow ); +%} + +instruct castDtoX_mem_avx10(vec dst, memory src) %{ + predicate(VM_Version::supports_avx10_2() && + is_integral_type(Matcher::vector_element_basic_type(n))); + match(Set dst (VectorCastD2X (LoadVector src))); + format %{ "vector_cast_d2x_avx10 $dst, $src\t!" %} + ins_encode %{ + int vlen = Matcher::vector_length(this); + int vlen_enc = vector_length_encoding(vlen * sizeof(jdouble)); + BasicType to_elem_bt = Matcher::vector_element_basic_type(this); + __ vector_castD2X_avx10(to_elem_bt, $dst$$XMMRegister, $src$$Address, vlen_enc); + %} + ins_pipe( pipe_slow ); +%} + instruct vucast(vec dst, vec src) %{ match(Set dst (VectorUCastB2X src)); match(Set dst (VectorUCastS2X src)); diff --git a/src/hotspot/cpu/x86/x86_64.ad b/src/hotspot/cpu/x86/x86_64.ad index eb37837a14de..1f4a5f106f72 100644 --- a/src/hotspot/cpu/x86/x86_64.ad +++ b/src/hotspot/cpu/x86/x86_64.ad @@ -11715,6 +11715,7 @@ instruct convD2F_reg_mem(regF dst, memory src) // XXX do mem variants instruct convF2I_reg_reg(rRegI dst, regF src, rFlagsReg cr) %{ + predicate(!VM_Version::supports_avx10_2()); match(Set dst (ConvF2I src)); effect(KILL cr); format %{ "convert_f2i $dst, $src" %} @@ -11724,8 +11725,31 @@ instruct convF2I_reg_reg(rRegI dst, regF src, rFlagsReg cr) ins_pipe(pipe_slow); %} +instruct convF2I_reg_reg_avx10(rRegI dst, regF src) +%{ + predicate(VM_Version::supports_avx10_2()); + match(Set dst (ConvF2I src)); + format %{ "evcvttss2sisl $dst, $src" %} + ins_encode %{ + __ evcvttss2sisl($dst$$Register, $src$$XMMRegister); + %} + ins_pipe(pipe_slow); +%} + +instruct convF2I_reg_mem_avx10(rRegI dst, memory src) +%{ + predicate(VM_Version::supports_avx10_2()); + match(Set dst (ConvF2I (LoadF src))); + format %{ "evcvttss2sisl $dst, $src" %} + ins_encode %{ + __ evcvttss2sisl($dst$$Register, $src$$Address); + %} + ins_pipe(pipe_slow); +%} + instruct convF2L_reg_reg(rRegL dst, regF src, rFlagsReg cr) %{ + predicate(!VM_Version::supports_avx10_2()); match(Set dst (ConvF2L src)); effect(KILL cr); format %{ "convert_f2l $dst, $src"%} @@ -11735,8 +11759,31 @@ instruct convF2L_reg_reg(rRegL dst, regF src, rFlagsReg cr) ins_pipe(pipe_slow); %} +instruct convF2L_reg_reg_avx10(rRegL dst, regF src) +%{ + predicate(VM_Version::supports_avx10_2()); + match(Set dst (ConvF2L src)); + format %{ "evcvttss2sisq $dst, $src" %} + ins_encode %{ + __ evcvttss2sisq($dst$$Register, $src$$XMMRegister); + %} + ins_pipe(pipe_slow); +%} + +instruct convF2L_reg_mem_avx10(rRegL dst, memory src) +%{ + predicate(VM_Version::supports_avx10_2()); + match(Set dst (ConvF2L (LoadF src))); + format %{ "evcvttss2sisq $dst, $src" %} + ins_encode %{ + __ evcvttss2sisq($dst$$Register, $src$$Address); + %} + ins_pipe(pipe_slow); +%} + instruct convD2I_reg_reg(rRegI dst, regD src, rFlagsReg cr) %{ + predicate(!VM_Version::supports_avx10_2()); match(Set dst (ConvD2I src)); effect(KILL cr); format %{ "convert_d2i $dst, $src"%} @@ -11746,8 +11793,31 @@ instruct convD2I_reg_reg(rRegI dst, regD src, rFlagsReg cr) ins_pipe(pipe_slow); %} +instruct convD2I_reg_reg_avx10(rRegI dst, regD src) +%{ + predicate(VM_Version::supports_avx10_2()); + match(Set dst (ConvD2I src)); + format %{ "evcvttsd2sisl $dst, $src" %} + ins_encode %{ + __ evcvttsd2sisl($dst$$Register, $src$$XMMRegister); + %} + ins_pipe(pipe_slow); +%} + +instruct convD2I_reg_mem_avx10(rRegI dst, memory src) +%{ + predicate(VM_Version::supports_avx10_2()); + match(Set dst (ConvD2I (LoadD src))); + format %{ "evcvttsd2sisl $dst, $src" %} + ins_encode %{ + __ evcvttsd2sisl($dst$$Register, $src$$Address); + %} + ins_pipe(pipe_slow); +%} + instruct convD2L_reg_reg(rRegL dst, regD src, rFlagsReg cr) %{ + predicate(!VM_Version::supports_avx10_2()); match(Set dst (ConvD2L src)); effect(KILL cr); format %{ "convert_d2l $dst, $src"%} @@ -11757,6 +11827,28 @@ instruct convD2L_reg_reg(rRegL dst, regD src, rFlagsReg cr) ins_pipe(pipe_slow); %} +instruct convD2L_reg_reg_avx10(rRegL dst, regD src) +%{ + predicate(VM_Version::supports_avx10_2()); + match(Set dst (ConvD2L src)); + format %{ "evcvttsd2sisq $dst, $src" %} + ins_encode %{ + __ evcvttsd2sisq($dst$$Register, $src$$XMMRegister); + %} + ins_pipe(pipe_slow); +%} + +instruct convD2L_reg_mem_avx10(rRegL dst, memory src) +%{ + predicate(VM_Version::supports_avx10_2()); + match(Set dst (ConvD2L (LoadD src))); + format %{ "evcvttsd2sisq $dst, $src" %} + ins_encode %{ + __ evcvttsd2sisq($dst$$Register, $src$$Address); + %} + ins_pipe(pipe_slow); +%} + instruct round_double_reg(rRegL dst, regD src, rRegL rtmp, rcx_RegL rcx, rFlagsReg cr) %{ match(Set dst (RoundD src)); diff --git a/test/hotspot/jtreg/compiler/floatingpoint/ScalarFPtoIntCastTest.java b/test/hotspot/jtreg/compiler/floatingpoint/ScalarFPtoIntCastTest.java new file mode 100644 index 000000000000..e6d1c8752507 --- /dev/null +++ b/test/hotspot/jtreg/compiler/floatingpoint/ScalarFPtoIntCastTest.java @@ -0,0 +1,207 @@ +/* + * 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 8364305 +* @summary Test scalar float/double to integral cast +* @requires vm.compiler2.enabled +* @library /test/lib / +* @run main/othervm/native compiler.floatingpoint.ScalarFPtoIntCastTest +*/ + +package compiler.floatingpoint; + +import compiler.lib.ir_framework.*; +import compiler.lib.generators.Generator; +import static compiler.lib.generators.Generators.G; +import compiler.lib.verify.Verify; + +public class ScalarFPtoIntCastTest { + private static final int COUNT = 16; + + private float[] float_arr; + private double[] double_arr; + private long[] long_float_arr; + private long[] long_double_arr; + private int[] int_float_arr; + private int[] int_double_arr; + private short[] short_float_arr; + private short[] short_double_arr; + private byte[] byte_float_arr; + private byte[] byte_double_arr; + + private static final Generator genF = G.floats(); + private static final Generator genD = G.doubles(); + + public static void main(String[] args) { + TestFramework testFramework = new TestFramework(); + testFramework.start(); + } + + public ScalarFPtoIntCastTest() { + long_float_arr = new long[COUNT]; + long_double_arr = new long[COUNT]; + int_float_arr = new int[COUNT]; + int_double_arr = new int[COUNT]; + short_float_arr = new short[COUNT]; + short_double_arr = new short[COUNT]; + byte_float_arr = new byte[COUNT]; + byte_double_arr = new byte[COUNT]; + float_arr = new float[COUNT]; + double_arr = new double[COUNT]; + + G.fill(genF, float_arr); + G.fill(genD, double_arr); + for (int i = 0; i < COUNT; i++) { + long_float_arr[i] = (long) float_arr[i]; + long_double_arr[i] = (long) double_arr[i]; + int_float_arr[i] = (int) float_arr[i]; + int_double_arr[i] = (int) double_arr[i]; + short_float_arr[i] = (short) float_arr[i]; + short_double_arr[i] = (short) double_arr[i]; + byte_float_arr[i] = (byte) float_arr[i]; + byte_double_arr[i] = (byte) double_arr[i]; + } + } + + @Test + @IR(counts = {IRNode.CONV_F2I, "> 0"}) + @IR(counts = {IRNode.X86_SCONV_F2I, "> 0"}, + applyIfCPUFeature = {"avx10_2", "false"}) + @IR(counts = {IRNode.X86_SCONV_F2I_AVX10, "> 0"}, + applyIfCPUFeature = {"avx10_2", "true"}) + public void float2int() { + for (int i = 0; i < COUNT; i++) { + float float_val = float_arr[i]; + int computed = (int) float_val; + int expected = int_float_arr[i]; + Verify.checkEQ(computed, expected); + } + } + + @Test + @IR(counts = {IRNode.CONV_F2L, "> 0"}) + @IR(counts = {IRNode.X86_SCONV_F2L, "> 0"}, + applyIfCPUFeature = {"avx10_2", "false"}) + @IR(counts = {IRNode.X86_SCONV_F2L_AVX10, "> 0"}, + applyIfCPUFeature = {"avx10_2", "true"}) + public void float2long() { + for (int i = 0; i < COUNT; i++) { + float float_val = float_arr[i]; + long computed = (long) float_val; + long expected = long_float_arr[i]; + Verify.checkEQ(computed, expected); + } + } + + @Test + @IR(counts = {IRNode.CONV_F2I, "> 0"}) + @IR(counts = {IRNode.X86_SCONV_F2I, "> 0"}, + applyIfCPUFeature = {"avx10_2", "false"}) + @IR(counts = {IRNode.X86_SCONV_F2I_AVX10, "> 0"}, + applyIfCPUFeature = {"avx10_2", "true"}) + public void float2short() { + for (int i = 0; i < COUNT; i++) { + float float_val = float_arr[i]; + short computed = (short) float_val; + short expected = short_float_arr[i]; + Verify.checkEQ(computed, expected); + } + } + + @Test + @IR(counts = {IRNode.CONV_F2I, "> 0"}) + @IR(counts = {IRNode.X86_SCONV_F2I, "> 0"}, + applyIfCPUFeature = {"avx10_2", "false"}) + @IR(counts = {IRNode.X86_SCONV_F2I_AVX10, "> 0"}, + applyIfCPUFeature = {"avx10_2", "true"}) + public void float2byte() { + for (int i = 0; i < COUNT; i++) { + float float_val = float_arr[i]; + byte computed = (byte) float_val; + byte expected = byte_float_arr[i]; + Verify.checkEQ(computed, expected); + } + } + + @Test + @IR(counts = {IRNode.CONV_D2I, "> 0"}) + @IR(counts = {IRNode.X86_SCONV_D2I, "> 0"}, + applyIfCPUFeature = {"avx10_2", "false"}) + @IR(counts = {IRNode.X86_SCONV_D2I_AVX10, "> 0"}, + applyIfCPUFeature = {"avx10_2", "true"}) + public void double2int() { + for (int i = 0; i < COUNT; i++) { + double double_val = double_arr[i]; + int computed = (int) double_val; + int expected = int_double_arr[i]; + Verify.checkEQ(computed, expected); + } + } + + @Test + @IR(counts = {IRNode.CONV_D2L, "> 0"}) + @IR(counts = {IRNode.X86_SCONV_D2L, "> 0"}, + applyIfCPUFeature = {"avx10_2", "false"}) + @IR(counts = {IRNode.X86_SCONV_D2L_AVX10, "> 0"}, + applyIfCPUFeature = {"avx10_2", "true"}) + public void double2long() { + for (int i = 0; i < COUNT; i++) { + double double_val = double_arr[i]; + long computed = (long) double_val; + long expected = long_double_arr[i]; + Verify.checkEQ(computed, expected); + } + } + + @Test + @IR(counts = {IRNode.CONV_D2I, "> 0"}) + @IR(counts = {IRNode.X86_SCONV_D2I, "> 0"}, + applyIfCPUFeature = {"avx10_2", "false"}) + @IR(counts = {IRNode.X86_SCONV_D2I_AVX10, "> 0"}, + applyIfCPUFeature = {"avx10_2", "true"}) + public void double2short() { + for (int i = 0; i < COUNT; i++) { + double double_val = double_arr[i]; + short computed = (short) double_val; + short expected = short_double_arr[i]; + Verify.checkEQ(computed, expected); + } + } + + @Test + @IR(counts = {IRNode.CONV_D2I, "> 0"}) + @IR(counts = {IRNode.X86_SCONV_D2I, "> 0"}, + applyIfCPUFeature = {"avx10_2", "false"}) + @IR(counts = {IRNode.X86_SCONV_D2I_AVX10, "> 0"}, + applyIfCPUFeature = {"avx10_2", "true"}) + public void double2byte() { + for (int i = 0; i < COUNT; i++) { + double double_val = double_arr[i]; + byte computed = (byte) double_val; + byte expected = byte_double_arr[i]; + Verify.checkEQ(computed, expected); + } + } +} diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java index 7b751f1a72c0..f41ccd84071b 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java @@ -610,11 +610,31 @@ public class IRNode { beforeMatchingNameRegex(CONV, "Conv"); } + public static final String CONV_D2I = PREFIX + "CONV_D2I" + POSTFIX; + static { + beforeMatchingNameRegex(CONV_D2I, "ConvD2I"); + } + + public static final String CONV_D2L = PREFIX + "CONV_D2L" + POSTFIX; + static { + beforeMatchingNameRegex(CONV_D2L, "ConvD2L"); + } + public static final String CONV_F2HF = PREFIX + "CONV_F2HF" + POSTFIX; static { beforeMatchingNameRegex(CONV_F2HF, "ConvF2HF"); } + public static final String CONV_F2I = PREFIX + "CONV_F2I" + POSTFIX; + static { + beforeMatchingNameRegex(CONV_F2I, "ConvF2I"); + } + + public static final String CONV_F2L = PREFIX + "CONV_F2L" + POSTFIX; + static { + beforeMatchingNameRegex(CONV_F2L, "ConvF2L"); + } + public static final String CONV_I2L = PREFIX + "CONV_I2L" + POSTFIX; static { beforeMatchingNameRegex(CONV_I2L, "ConvI2L"); @@ -2629,6 +2649,66 @@ public class IRNode { machOnlyNameRegex(VSTOREMASK_TRUECOUNT, "vstoremask_truecount_neon"); } + public static final String X86_SCONV_D2I = PREFIX + "X86_SCONV_D2I" + POSTFIX; + static { + machOnlyNameRegex(X86_SCONV_D2I, "convD2I_reg_reg"); + } + + public static final String X86_SCONV_D2L = PREFIX + "X86_SCONV_D2L" + POSTFIX; + static { + machOnlyNameRegex(X86_SCONV_D2L, "convD2L_reg_reg"); + } + + public static final String X86_SCONV_F2I = PREFIX + "X86_SCONV_F2I" + POSTFIX; + static { + machOnlyNameRegex(X86_SCONV_F2I, "convF2I_reg_reg"); + } + + public static final String X86_SCONV_F2L = PREFIX + "X86_SCONV_F2L" + POSTFIX; + static { + machOnlyNameRegex(X86_SCONV_F2L, "convF2L_reg_reg"); + } + + public static final String X86_SCONV_D2I_AVX10 = PREFIX + "X86_SCONV2_D2I_AVX10" + POSTFIX; + static { + machOnlyNameRegex(X86_SCONV_D2I_AVX10, "convD2I_(reg_reg|reg_mem)_avx10"); + } + + public static final String X86_SCONV_D2L_AVX10 = PREFIX + "X86_SCONV_D2L_AVX10" + POSTFIX; + static { + machOnlyNameRegex(X86_SCONV_D2L_AVX10, "convD2L_(reg_reg|reg_mem)_avx10"); + } + + public static final String X86_SCONV_F2I_AVX10 = PREFIX + "X86_SCONV_F2I_AVX10" + POSTFIX; + static { + machOnlyNameRegex(X86_SCONV_F2I_AVX10, "convF2I_(reg_reg|reg_mem)_avx10"); + } + + public static final String X86_SCONV_F2L_AVX10 = PREFIX + "X86_SCONV_F2L_AVX10" + POSTFIX; + static { + machOnlyNameRegex(X86_SCONV_F2L_AVX10, "convF2L_(reg_reg|reg_mem)_avx10"); + } + + public static final String X86_VCAST_F2X = PREFIX + "X86_VCAST_F2X" + POSTFIX; + static { + machOnlyNameRegex(X86_VCAST_F2X, "castFtoX_reg_(av|eve)x"); + } + + public static final String X86_VCAST_D2X = PREFIX + "X86_VCAST_D2X" + POSTFIX; + static { + machOnlyNameRegex(X86_VCAST_D2X, "castDtoX_reg_(av|eve)x"); + } + + public static final String X86_VCAST_F2X_AVX10 = PREFIX + "X86_VCAST_F2X_AVX10" + POSTFIX; + static { + machOnlyNameRegex(X86_VCAST_F2X_AVX10, "castFtoX_(reg|mem)_avx10"); + } + + public static final String X86_VCAST_D2X_AVX10 = PREFIX + "X86_VCAST_D2X_AVX10" + POSTFIX; + static { + machOnlyNameRegex(X86_VCAST_D2X_AVX10, "castDtoX_(reg|mem)_avx10"); + } + public static final String XOR = PREFIX + "XOR" + POSTFIX; static { beforeMatchingNameRegex(XOR, "Xor(I|L)"); 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 4ad95ab786f5..3ed1e8585b67 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/test/IREncodingPrinter.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/test/IREncodingPrinter.java @@ -104,6 +104,7 @@ public class IREncodingPrinter { "avx512f", "avx512_fp16", "avx512_vnni", + "avx10_2", // AArch64 "sha3", "asimd", diff --git a/test/hotspot/jtreg/compiler/vectorapi/VectorFPtoIntCastTest.java b/test/hotspot/jtreg/compiler/vectorapi/VectorFPtoIntCastTest.java index 8d5d872375ff..1037a2989f92 100644 --- a/test/hotspot/jtreg/compiler/vectorapi/VectorFPtoIntCastTest.java +++ b/test/hotspot/jtreg/compiler/vectorapi/VectorFPtoIntCastTest.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,8 +23,8 @@ /** * @test -* @bug 8287835 -* @summary Test float/double to integral cast +* @bug 8287835 8364305 +* @summary Test vector float/double to integral cast * @modules jdk.incubator.vector * @requires vm.compiler2.enabled * @library /test/lib / @@ -87,7 +87,11 @@ public VectorFPtoIntCastTest() { @Test @IR(counts = {IRNode.VECTOR_CAST_F2I, IRNode.VECTOR_SIZE_16, "> 0"}, - applyIfCPUFeature = {"avx512f", "true"}) + applyIfCPUFeatureOr = {"avx512f", "true", "avx10_2", "true"}) + @IR(counts = {IRNode.X86_VCAST_F2X, "> 0"}, + applyIfCPUFeatureAnd = {"avx512f", "true", "avx10_2", "false"}) + @IR(counts = {IRNode.X86_VCAST_F2X_AVX10, "> 0"}, + applyIfCPUFeature = {"avx10_2", "true"}) public void float2int() { var cvec = (IntVector)fvec512.convertShape(VectorOperators.F2I, ispec512, 0); cvec.intoArray(int_arr, 0); @@ -96,7 +100,7 @@ public void float2int() { public void checkf2int(int len) { for (int i = 0; i < len; i++) { - int expected = (int)float_arr[i]; + int expected = (int) float_arr[i]; if (int_arr[i] != expected) { throw new RuntimeException("Invalid result: int_arr[" + i + "] = " + int_arr[i] + " != " + expected); } @@ -105,7 +109,11 @@ public void checkf2int(int len) { @Test @IR(counts = {IRNode.VECTOR_CAST_F2L, IRNode.VECTOR_SIZE_8, "> 0"}, - applyIfCPUFeature = {"avx512dq", "true"}) + applyIfCPUFeatureOr = {"avx512dq", "true", "avx10_2", "true"}) + @IR(counts = {IRNode.X86_VCAST_F2X, "> 0"}, + applyIfCPUFeatureAnd = {"avx512dq", "true", "avx10_2", "false"}) + @IR(counts = {IRNode.X86_VCAST_F2X_AVX10, "> 0"}, + applyIfCPUFeature = {"avx10_2", "true"}) public void float2long() { var cvec = (LongVector)fvec512.convertShape(VectorOperators.F2L, lspec512, 0); cvec.intoArray(long_arr, 0); @@ -114,7 +122,7 @@ public void float2long() { public void checkf2long(int len) { for (int i = 0; i < len; i++) { - long expected = (long)float_arr[i]; + long expected = (long) float_arr[i]; if (long_arr[i] != expected) { throw new RuntimeException("Invalid result: long_arr[" + i + "] = " + long_arr[i] + " != " + expected); } @@ -123,7 +131,11 @@ public void checkf2long(int len) { @Test @IR(counts = {IRNode.VECTOR_CAST_F2S, IRNode.VECTOR_SIZE_16, "> 0"}, - applyIfCPUFeature = {"avx512f", "true"}) + applyIfCPUFeatureOr = {"avx512f", "true", "avx10_2", "true"}) + @IR(counts = {IRNode.X86_VCAST_F2X, "> 0"}, + applyIfCPUFeatureAnd = {"avx512f", "true", "avx10_2", "false"}) + @IR(counts = {IRNode.X86_VCAST_F2X_AVX10, "> 0"}, + applyIfCPUFeature = {"avx10_2", "true"}) public void float2short() { var cvec = (ShortVector)fvec512.convertShape(VectorOperators.F2S, sspec256, 0); cvec.intoArray(short_arr, 0); @@ -132,7 +144,7 @@ public void float2short() { public void checkf2short(int len) { for (int i = 0; i < len; i++) { - short expected = (short)float_arr[i]; + short expected = (short) float_arr[i]; if (short_arr[i] != expected) { throw new RuntimeException("Invalid result: short_arr[" + i + "] = " + short_arr[i] + " != " + expected); } @@ -141,7 +153,11 @@ public void checkf2short(int len) { @Test @IR(counts = {IRNode.VECTOR_CAST_F2B, IRNode.VECTOR_SIZE_16, "> 0"}, - applyIfCPUFeature = {"avx512f", "true"}) + applyIfCPUFeatureOr = {"avx512f", "true", "avx10_2", "true"}) + @IR(counts = {IRNode.X86_VCAST_F2X, "> 0"}, + applyIfCPUFeatureAnd = {"avx512f", "true", "avx10_2", "false"}) + @IR(counts = {IRNode.X86_VCAST_F2X_AVX10, "> 0"}, + applyIfCPUFeature = {"avx10_2", "true"}) public void float2byte() { var cvec = (ByteVector)fvec512.convertShape(VectorOperators.F2B, bspec128, 0); cvec.intoArray(byte_arr, 0); @@ -150,7 +166,7 @@ public void float2byte() { public void checkf2byte(int len) { for (int i = 0; i < len; i++) { - byte expected = (byte)float_arr[i]; + byte expected = (byte) float_arr[i]; if (byte_arr[i] != expected) { throw new RuntimeException("Invalid result: byte_arr[" + i + "] = " + byte_arr[i] + " != " + expected); } @@ -159,7 +175,11 @@ public void checkf2byte(int len) { @Test @IR(counts = {IRNode.VECTOR_CAST_D2I, IRNode.VECTOR_SIZE_8, "> 0"}, - applyIfCPUFeature = {"avx512f", "true"}) + applyIfCPUFeatureOr = {"avx512f", "true", "avx10_2", "true"}) + @IR(counts = {IRNode.X86_VCAST_D2X, "> 0"}, + applyIfCPUFeatureAnd = {"avx512f", "true", "avx10_2", "false"}) + @IR(counts = {IRNode.X86_VCAST_D2X_AVX10, "> 0"}, + applyIfCPUFeature = {"avx10_2", "true"}) public void double2int() { var cvec = (IntVector)dvec512.convertShape(VectorOperators.D2I, ispec256, 0); cvec.intoArray(int_arr, 0); @@ -168,7 +188,7 @@ public void double2int() { public void checkd2int(int len) { for (int i = 0; i < len; i++) { - int expected = (int)double_arr[i]; + int expected = (int) double_arr[i]; if (int_arr[i] != expected) { throw new RuntimeException("Invalid result: int_arr[" + i + "] = " + int_arr[i] + " != " + expected); } @@ -177,7 +197,11 @@ public void checkd2int(int len) { @Test @IR(counts = {IRNode.VECTOR_CAST_D2L, IRNode.VECTOR_SIZE_8, "> 0"}, - applyIfCPUFeature = {"avx512dq", "true"}) + applyIfCPUFeatureOr = {"avx512dq", "true", "avx10_2", "true"}) + @IR(counts = {IRNode.X86_VCAST_D2X, "> 0"}, + applyIfCPUFeatureAnd = {"avx512dq", "true", "avx10_2", "false"}) + @IR(counts = {IRNode.X86_VCAST_D2X_AVX10, "> 0"}, + applyIfCPUFeature = {"avx10_2", "true"}) public void double2long() { var cvec = (LongVector)dvec512.convertShape(VectorOperators.D2L, lspec512, 0); cvec.intoArray(long_arr, 0); @@ -186,7 +210,7 @@ public void double2long() { public void checkd2long(int len) { for (int i = 0; i < len; i++) { - long expected = (long)double_arr[i]; + long expected = (long) double_arr[i]; if (long_arr[i] != expected) { throw new RuntimeException("Invalid result: long_arr[" + i + "] = " + long_arr[i] + " != " + expected); } @@ -195,7 +219,11 @@ public void checkd2long(int len) { @Test @IR(counts = {IRNode.VECTOR_CAST_D2S, IRNode.VECTOR_SIZE_8, "> 0"}, - applyIfCPUFeature = {"avx512f", "true"}) + applyIfCPUFeatureOr = {"avx512f", "true", "avx10_2", "true"}) + @IR(counts = {IRNode.X86_VCAST_D2X, "> 0"}, + applyIfCPUFeatureAnd = {"avx512f", "true", "avx10_2", "false"}) + @IR(counts = {IRNode.X86_VCAST_D2X_AVX10, "> 0"}, + applyIfCPUFeature = {"avx10_2", "true"}) public void double2short() { var cvec = (ShortVector)dvec512.convertShape(VectorOperators.D2S, sspec128, 0); cvec.intoArray(short_arr, 0); @@ -204,7 +232,7 @@ public void double2short() { public void checkd2short(int len) { for (int i = 0; i < len; i++) { - short expected = (short)double_arr[i]; + short expected = (short) double_arr[i]; if (short_arr[i] != expected) { throw new RuntimeException("Invalid result: short_arr[" + i + "] = " + short_arr[i] + " != " + expected); } @@ -213,7 +241,11 @@ public void checkd2short(int len) { @Test @IR(counts = {IRNode.VECTOR_CAST_D2B, IRNode.VECTOR_SIZE_8, "> 0"}, - applyIfCPUFeature = {"avx512f", "true"}) + applyIfCPUFeatureOr = {"avx512f", "true", "avx10_2", "true"}) + @IR(counts = {IRNode.X86_VCAST_D2X, "> 0"}, + applyIfCPUFeatureAnd = {"avx512f", "true", "avx10_2", "false"}) + @IR(counts = {IRNode.X86_VCAST_D2X_AVX10, "> 0"}, + applyIfCPUFeature = {"avx10_2", "true"}) public void double2byte() { var cvec = (ByteVector)dvec512.convertShape(VectorOperators.D2B, bspec64, 0); cvec.intoArray(byte_arr, 0); @@ -222,7 +254,7 @@ public void double2byte() { public void checkd2byte(int len) { for (int i = 0; i < len; i++) { - byte expected = (byte)double_arr[i]; + byte expected = (byte) double_arr[i]; if (byte_arr[i] != expected) { throw new RuntimeException("Invalid result: byte_arr[" + i + "] = " + byte_arr[i] + " != " + expected); } diff --git a/test/hotspot/jtreg/compiler/vectorization/runner/ArrayTypeConvertTest.java b/test/hotspot/jtreg/compiler/vectorization/runner/ArrayTypeConvertTest.java index 11b07d57dd9d..a9429ba4365f 100644 --- a/test/hotspot/jtreg/compiler/vectorization/runner/ArrayTypeConvertTest.java +++ b/test/hotspot/jtreg/compiler/vectorization/runner/ArrayTypeConvertTest.java @@ -291,8 +291,12 @@ public double[] convertCharToDouble() { // ---------------- Convert F/D to I/L ---------------- @Test - @IR(applyIfCPUFeatureOr = {"asimd", "true", "avx", "true", "rvv", "true"}, - counts = {IRNode.VECTOR_CAST_F2I, IRNode.VECTOR_SIZE + "min(max_float, max_int)", ">0"}) + @IR(applyIfCPUFeatureOr = {"asimd", "true", "avx", "true", "avx10_2", "true", "rvv", "true"}, + counts = {IRNode.VECTOR_CAST_F2I, IRNode.VECTOR_SIZE + "min(max_float, max_int)", "> 0"}) + @IR(counts = {IRNode.X86_VCAST_F2X, "> 0"}, + applyIfCPUFeatureAnd = {"avx", "true", "avx10_2", "false"}) + @IR(counts = {IRNode.X86_VCAST_F2X_AVX10, "> 0"}, + applyIfCPUFeature = {"avx10_2", "true"}) public int[] convertFloatToInt() { int[] res = new int[SIZE]; for (int i = 0; i < SIZE; i++) { @@ -302,8 +306,12 @@ public int[] convertFloatToInt() { } @Test - @IR(applyIfCPUFeatureOr = {"asimd", "true", "avx512dq", "true", "rvv", "true"}, - counts = {IRNode.VECTOR_CAST_F2L, IRNode.VECTOR_SIZE + "min(max_float, max_long)", ">0"}) + @IR(applyIfCPUFeatureOr = {"asimd", "true", "avx512dq", "true", "avx10_2", "true", "rvv", "true"}, + counts = {IRNode.VECTOR_CAST_F2L, IRNode.VECTOR_SIZE + "min(max_float, max_long)", "> 0"}) + @IR(counts = {IRNode.X86_VCAST_F2X, "> 0"}, + applyIfCPUFeatureAnd = {"avx512dq", "true", "avx10_2", "false"}) + @IR(counts = {IRNode.X86_VCAST_F2X_AVX10, "> 0"}, + applyIfCPUFeature = {"avx10_2", "true"}) public long[] convertFloatToLong() { long[] res = new long[SIZE]; for (int i = 0; i < SIZE; i++) { @@ -313,8 +321,12 @@ public long[] convertFloatToLong() { } @Test - @IR(applyIfCPUFeatureOr = {"sve", "true", "avx", "true", "rvv", "true"}, - counts = {IRNode.VECTOR_CAST_D2I, IRNode.VECTOR_SIZE + "min(max_double, max_int)", ">0"}) + @IR(applyIfCPUFeatureOr = {"sve", "true", "avx", "true", "avx10_2", "true", "rvv", "true"}, + counts = {IRNode.VECTOR_CAST_D2I, IRNode.VECTOR_SIZE + "min(max_double, max_int)", "> 0"}) + @IR(counts = {IRNode.X86_VCAST_D2X, "> 0"}, + applyIfCPUFeatureAnd = {"avx", "true", "avx10_2", "false"}) + @IR(counts = {IRNode.X86_VCAST_D2X_AVX10, "> 0"}, + applyIfCPUFeature = {"avx10_2", "true"}) public int[] convertDoubleToInt() { int[] res = new int[SIZE]; for (int i = 0; i < SIZE; i++) { @@ -324,8 +336,12 @@ public int[] convertDoubleToInt() { } @Test - @IR(applyIfCPUFeatureOr = {"asimd", "true", "avx512dq", "true", "rvv", "true"}, - counts = {IRNode.VECTOR_CAST_D2L, IRNode.VECTOR_SIZE + "min(max_double, max_long)", ">0"}) + @IR(applyIfCPUFeatureOr = {"asimd", "true", "avx512dq", "true", "avx10_2", "true", "rvv", "true"}, + counts = {IRNode.VECTOR_CAST_D2L, IRNode.VECTOR_SIZE + "min(max_double, max_long)", "> 0"}) + @IR(counts = {IRNode.X86_VCAST_D2X, "> 0"}, + applyIfCPUFeatureAnd = {"avx512dq", "true", "avx10_2", "false"}) + @IR(counts = {IRNode.X86_VCAST_D2X_AVX10, "> 0"}, + applyIfCPUFeature = {"avx10_2", "true"}) public long[] convertDoubleToLong() { long[] res = new long[SIZE]; for (int i = 0; i < SIZE; i++) { @@ -336,9 +352,15 @@ public long[] convertDoubleToLong() { // ---------------- Convert F/D to Subword-I ---------------- @Test - @IR(applyIfCPUFeatureOr = {"asimd", "true", "avx2", "true", "rvv", "true"}, + @IR(applyIfCPUFeatureOr = {"asimd", "true", "avx2", "true", "avx10_2", "true", "rvv", "true"}, + applyIfOr = {"AlignVector", "false", "UseCompactObjectHeaders", "false"}, + counts = {IRNode.VECTOR_CAST_F2S, IRNode.VECTOR_SIZE + "min(max_float, max_short)", "> 0"}) + @IR(counts = {IRNode.X86_VCAST_F2X, "> 0"}, applyIfOr = {"AlignVector", "false", "UseCompactObjectHeaders", "false"}, - counts = {IRNode.VECTOR_CAST_F2S, IRNode.VECTOR_SIZE + "min(max_float, max_short)", ">0"}) + applyIfCPUFeatureAnd = {"avx2", "true", "avx10_2", "false"}) + @IR(counts = {IRNode.X86_VCAST_F2X_AVX10, "> 0"}, + applyIfOr = {"AlignVector", "false", "UseCompactObjectHeaders", "false"}, + applyIfCPUFeature = {"avx10_2", "true"}) public short[] convertFloatToShort() { short[] res = new short[SIZE]; for (int i = 0; i < SIZE; i++) { @@ -355,9 +377,15 @@ public short[] convertFloatToShort() { } @Test - @IR(applyIfCPUFeatureOr = {"asimd", "true", "avx2", "true", "rvv", "true"}, + @IR(applyIfCPUFeatureOr = {"asimd", "true", "avx2", "true", "avx10_2", "true", "rvv", "true"}, + applyIfOr = {"AlignVector", "false", "UseCompactObjectHeaders", "false"}, + counts = {IRNode.VECTOR_CAST_F2S, IRNode.VECTOR_SIZE + "min(max_float, max_char)", "> 0"}) + @IR(counts = {IRNode.X86_VCAST_F2X, "> 0"}, applyIfOr = {"AlignVector", "false", "UseCompactObjectHeaders", "false"}, - counts = {IRNode.VECTOR_CAST_F2S, IRNode.VECTOR_SIZE + "min(max_float, max_char)", ">0"}) + applyIfCPUFeatureAnd = {"avx2", "true", "avx10_2", "false"}) + @IR(counts = {IRNode.X86_VCAST_F2X_AVX10, "> 0"}, + applyIfOr = {"AlignVector", "false", "UseCompactObjectHeaders", "false"}, + applyIfCPUFeature = {"avx10_2", "true"}) public char[] convertFloatToChar() { char[] res = new char[SIZE]; for (int i = 0; i < SIZE; i++) { @@ -376,7 +404,16 @@ public char[] convertFloatToChar() { @Test @IR(applyIfCPUFeatureOr = {"sve", "true", "avx", "true", "rvv", "true"}, applyIf = {"MaxVectorSize", ">=32"}, - counts = {IRNode.VECTOR_CAST_D2S, IRNode.VECTOR_SIZE + "min(max_double, max_short)", ">0"}) + counts = {IRNode.VECTOR_CAST_D2S, IRNode.VECTOR_SIZE + "min(max_double, max_short)", "> 0"}) + @IR(applyIfCPUFeatureOr = {"sve", "true", "avx", "true", "avx10_2", "true"}, + applyIf = {"MaxVectorSize", ">=16"}, + counts = {IRNode.VECTOR_CAST_D2S, IRNode.VECTOR_SIZE + "min(max_double, max_short)", "> 0"}) + @IR(counts = {IRNode.X86_VCAST_D2X, "> 0"}, + applyIf = {"MaxVectorSize", ">=16"}, + applyIfCPUFeatureAnd = {"avx", "true", "avx10_2", "false"}) + @IR(counts = {IRNode.X86_VCAST_D2X_AVX10, "> 0"}, + applyIf = {"MaxVectorSize", ">=16"}, + applyIfCPUFeature = {"avx10_2", "true"}) public short[] convertDoubleToShort() { short[] res = new short[SIZE]; for (int i = 0; i < SIZE; i++) { @@ -387,8 +424,17 @@ public short[] convertDoubleToShort() { @Test @IR(applyIfCPUFeatureOr = {"sve", "true", "avx", "true", "rvv", "true"}, - applyIf = {"MaxVectorSize", ">=32"}, - counts = {IRNode.VECTOR_CAST_D2S, IRNode.VECTOR_SIZE + "min(max_double, max_char)", ">0"}) + applyIf = {"MaxVectorSize", ">= 32"}, + counts = {IRNode.VECTOR_CAST_D2S, IRNode.VECTOR_SIZE + "min(max_double, max_char)", "> 0"}) + @IR(applyIfCPUFeatureOr = {"sve", "true", "avx", "true", "avx10_2", "true"}, + applyIf = {"MaxVectorSize", ">= 16"}, + counts = {IRNode.VECTOR_CAST_D2S, IRNode.VECTOR_SIZE + "min(max_double, max_char)", "> 0"}) + @IR(counts = {IRNode.X86_VCAST_D2X, "> 0"}, + applyIf = {"MaxVectorSize", ">=16"}, + applyIfCPUFeatureAnd = {"avx", "true", "avx10_2", "false"}) + @IR(counts = {IRNode.X86_VCAST_D2X_AVX10, "> 0"}, + applyIf = {"MaxVectorSize", ">=16"}, + applyIfCPUFeature = {"avx10_2", "true"}) public char[] convertDoubleToChar() { char[] res = new char[SIZE]; for (int i = 0; i < SIZE; i++) { From 58ef22f0a5ebe41c39f76f92f85d5f74cfac8a10 Mon Sep 17 00:00:00 2001 From: Ashay Rane Date: Wed, 17 Jun 2026 07:20:48 +0000 Subject: [PATCH 21/86] 8383611: assert(count == os::processor_count() + 1) failed: invalid enumeration! Backport-of: 2e0c8caab1d57e33f8d20a0eb29adfbddec35a74 --- src/hotspot/os/windows/os_perf_windows.cpp | 155 +++++++++++++++++++-- 1 file changed, 142 insertions(+), 13 deletions(-) diff --git a/src/hotspot/os/windows/os_perf_windows.cpp b/src/hotspot/os/windows/os_perf_windows.cpp index 9d04ae65954c..c1b8e0ac4c44 100644 --- a/src/hotspot/os/windows/os_perf_windows.cpp +++ b/src/hotspot/os/windows/os_perf_windows.cpp @@ -779,6 +779,114 @@ static OSReturn allocate_pdh_constants() { return OS_OK; } +// Look up the PDH index by reading the English (locale 009) counter name +// registry. See KB Q287159: Using PDH APIs Correctly in a Localized Language +// for details. +static OSReturn lookup_perf_index_by_english_name(const char* english_name, + DWORD* result) { + ResourceMark rm; + + DWORD type = 0; + DWORD size = 0; + + // Determine the required buffer size + if (RegQueryValueEx(HKEY_PERFORMANCE_DATA, "Counter 009", + nullptr, &type, nullptr, &size) != ERROR_SUCCESS) { + return OS_ERR; + } + + // Since registry entries in `HKEY_PERFORMANCE_DATA` are generated on the fly, + // they could change between calls, so we can't rely just on the size returned + // by the first call. Instead, Microsoft's documentation suggests running + // these calls in a loop until the return code is no longer `ERROR_MORE_DATA`. + + char* buffer; + do { + if (size == 0) { + return OS_ERR; + } + + // When `RegQueryValueEx()` returns `ERROR_MORE_DATA`, the value in the + // callback argument is undefined, so we need to create a new variable whose + // address is passed as the callback size argument. + buffer = NEW_RESOURCE_ARRAY(char, size); + + DWORD cb_size = size; + LSTATUS status = RegQueryValueEx(HKEY_PERFORMANCE_DATA, "Counter 009", + nullptr, &type, (LPBYTE)buffer, + &cb_size); + if (status == ERROR_MORE_DATA) { + // We need to increase the buffer size. Since we don't know _how much_ to + // increase it by, we use an estimate (4096) for the increment. + DWORD increment = 4096; + if (size > MAXDWORD - increment) { + return OS_ERR; + } + size += increment; + } else if (status == ERROR_SUCCESS) { + break; + } else { + // If there was some other problem fetching this registry entry, tell the + // caller that we couldn't lookup the index. + return OS_ERR; + } + } while (true); + + if (type != REG_MULTI_SZ) { + return OS_ERR; + } + + // The buffer contains indices and names in the form (\0\0)*, so + // iterate character by character to parse the name and if it matches the + // English name, then we return the integer value of the index. + for (const char* p = buffer; *p != '\0'; ) { + const char* idx_str = p; + p += strlen(p) + 1; + if (*p == '\0') { + break; + } + + const char* name = p; + p += strlen(p) + 1; + if (strcmp(name, english_name) == 0) { + errno = 0; + char* end = nullptr; + unsigned long value = strtoul(idx_str, &end, 10); + if (errno == 0 && end != idx_str && value <= MAXDWORD) { + *result = (DWORD)value; + return OS_OK; + } + } + } + + return OS_ERR; +} + +// Return the counter index of the 'Processor Information' counter, if +// available, or else the 'Processor' counter. The former is aware of the +// possibility of multiple processor groups and thus provides a more accurate +// processor count whereas the latter serves as fallback. +static DWORD get_proc_counter() { + static DWORD pdh_idx = 0; + if (pdh_idx != 0) { + return pdh_idx; + } + + // Some APIs accept English counter names whereas others accept counter names + // in the specific user's locale. We determine the locale-specific name using + // the counter index, but to find the counter index, we use the English name + // of the counter and look for it in a specific registry key. + DWORD info_idx; + if (lookup_perf_index_by_english_name("Processor Information", + &info_idx) != OS_OK) { + info_idx = PDH_PROCESSOR_IDX; + } + + // Assign to the static variable so that the value persists across calls. + pdh_idx = info_idx; + return pdh_idx; +} + /* * Enuerate the Processor PDH object and returns a buffer containing the enumerated instances. * Caller needs ResourceMark; @@ -786,8 +894,11 @@ static OSReturn allocate_pdh_constants() { * @return buffer if successful, null on failure. */ static const char* enumerate_cpu_instances() { - char* processor; //'Processor' == PDH_PROCESSOR_IDX - if (lookup_name_by_index(PDH_PROCESSOR_IDX, &processor) != OS_OK) { + // The `PdhEnumObjectItems()` function accepts a localized name of the perf + // counter. To obtain the name that is specific to the user's locale, we + // perform a reverse lookup from counter index to counter name. + char* processor; + if (lookup_name_by_index(get_proc_counter(), &processor) != OS_OK) { return nullptr; } DWORD c_size = 0; @@ -821,13 +932,17 @@ static const char* enumerate_cpu_instances() { static int count_logical_cpus(const char* instances) { assert(instances != nullptr, "invariant"); - // count logical instances. - DWORD count; - char* tmp; - for (count = 0, tmp = const_cast(instances); *tmp != '\0'; tmp = &tmp[strlen(tmp) + 1], count++); - // PDH reports an instance for each logical processor plus an instance for the total (_Total) - assert(count == os::processor_count() + 1, "invalid enumeration!"); - return count - 1; + DWORD count = 0; + for (const char* tmp = instances; *tmp != '\0'; tmp += strlen(tmp) + 1) { + // In both the 'Processor' counter and the 'Processor Information' counter, + // the output contains totals for the processor group(s). We filter those + // out by looking for the `_Total` substring. + if (strstr(tmp, "_Total") == nullptr) { + count++; + } + } + assert(count >= 1, "invalid enumeration!"); + return count; } static int number_of_logical_cpus() { @@ -847,7 +962,16 @@ static double cpu_factor() { static double cpuFactor = .0; if (numCpus == 0) { numCpus = number_of_logical_cpus(); - assert(os::processor_count() <= (int)numCpus, "invariant"); + + // If we are using the legacy 'Processor' counter, which counts processors + // only in the first processor group, then `numCpus` can undercount, in + // which case, `numCpus` will be likely smaller than `os_processor_count`. + // However, when we use the 'Processor Information' counter, we expect both + // `numCpus` and `os::processorCount` to be identical. In both cases, we + // expect to see at least one CPU. + assert(numCpus >= 1 && numCpus <= (DWORD)os::processor_count(), + "unexpected cpu count"); + cpuFactor = numCpus * 100; } return cpuFactor; @@ -861,8 +985,8 @@ static void log_error_message_on_no_PDH_artifact(const char* counter_path) { static int initialize_cpu_query_counters(MultiCounterQueryP query, DWORD pdh_counter_idx) { assert(query != nullptr, "invariant"); assert(query->counters != nullptr, "invariant"); - char* processor; //'Processor' == PDH_PROCESSOR_IDX - if (lookup_name_by_index(PDH_PROCESSOR_IDX, &processor) != OS_OK) { + char* processor; + if (lookup_name_by_index(get_proc_counter(), &processor) != OS_OK) { return OS_ERR; } char* counter_name = nullptr; @@ -880,7 +1004,11 @@ static int initialize_cpu_query_counters(MultiCounterQueryP query, DWORD pdh_cou counter_len += OBJECT_WITH_INSTANCES_COUNTER_FMT_LEN; // "\\%s(%s)\\%s" const char* instances = enumerate_cpu_instances(); DWORD index = 0; - for (char* tmp = const_cast(instances); *tmp != '\0'; tmp = &tmp[strlen(tmp) + 1], index++) { + for (char* tmp = const_cast(instances); *tmp != '\0'; tmp = &tmp[strlen(tmp) + 1]) { + // Skip totals for each processor group. + if (strstr(tmp, ",_Total") != nullptr) { + continue; + } const size_t tmp_len = strlen(tmp); char* counter_path = NEW_RESOURCE_ARRAY(char, counter_len + tmp_len + 1); const size_t jio_snprintf_result = jio_snprintf(counter_path, @@ -896,6 +1024,7 @@ static int initialize_cpu_query_counters(MultiCounterQueryP query, DWORD pdh_cou // return OS_OK to have the system continue to run without the missing counter return OS_OK; } + index++; } // Query once to initialize the counters which require at least two samples // (like the % CPU usage) to calculate correctly. From 3060b045e0f62c098cf9638d0af9f3f80ba5c92a Mon Sep 17 00:00:00 2001 From: Mohamed Issa Date: Wed, 17 Jun 2026 15:24:30 +0000 Subject: [PATCH 22/86] 8368861: [TEST] compiler/floatingpoint/ScalarFPtoIntCastTest.java expects x86 IR on non-x86 platforms Backport-of: 5a2700f231d72e2241703c1d17b308f031e8566c --- .../compiler/floatingpoint/ScalarFPtoIntCastTest.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/hotspot/jtreg/compiler/floatingpoint/ScalarFPtoIntCastTest.java b/test/hotspot/jtreg/compiler/floatingpoint/ScalarFPtoIntCastTest.java index e6d1c8752507..e8575cfe6b64 100644 --- a/test/hotspot/jtreg/compiler/floatingpoint/ScalarFPtoIntCastTest.java +++ b/test/hotspot/jtreg/compiler/floatingpoint/ScalarFPtoIntCastTest.java @@ -88,6 +88,7 @@ public ScalarFPtoIntCastTest() { @Test @IR(counts = {IRNode.CONV_F2I, "> 0"}) @IR(counts = {IRNode.X86_SCONV_F2I, "> 0"}, + applyIfPlatform = {"x64", "true"}, applyIfCPUFeature = {"avx10_2", "false"}) @IR(counts = {IRNode.X86_SCONV_F2I_AVX10, "> 0"}, applyIfCPUFeature = {"avx10_2", "true"}) @@ -103,6 +104,7 @@ public void float2int() { @Test @IR(counts = {IRNode.CONV_F2L, "> 0"}) @IR(counts = {IRNode.X86_SCONV_F2L, "> 0"}, + applyIfPlatform = {"x64", "true"}, applyIfCPUFeature = {"avx10_2", "false"}) @IR(counts = {IRNode.X86_SCONV_F2L_AVX10, "> 0"}, applyIfCPUFeature = {"avx10_2", "true"}) @@ -118,6 +120,7 @@ public void float2long() { @Test @IR(counts = {IRNode.CONV_F2I, "> 0"}) @IR(counts = {IRNode.X86_SCONV_F2I, "> 0"}, + applyIfPlatform = {"x64", "true"}, applyIfCPUFeature = {"avx10_2", "false"}) @IR(counts = {IRNode.X86_SCONV_F2I_AVX10, "> 0"}, applyIfCPUFeature = {"avx10_2", "true"}) @@ -133,6 +136,7 @@ public void float2short() { @Test @IR(counts = {IRNode.CONV_F2I, "> 0"}) @IR(counts = {IRNode.X86_SCONV_F2I, "> 0"}, + applyIfPlatform = {"x64", "true"}, applyIfCPUFeature = {"avx10_2", "false"}) @IR(counts = {IRNode.X86_SCONV_F2I_AVX10, "> 0"}, applyIfCPUFeature = {"avx10_2", "true"}) @@ -148,6 +152,7 @@ public void float2byte() { @Test @IR(counts = {IRNode.CONV_D2I, "> 0"}) @IR(counts = {IRNode.X86_SCONV_D2I, "> 0"}, + applyIfPlatform = {"x64", "true"}, applyIfCPUFeature = {"avx10_2", "false"}) @IR(counts = {IRNode.X86_SCONV_D2I_AVX10, "> 0"}, applyIfCPUFeature = {"avx10_2", "true"}) @@ -163,6 +168,7 @@ public void double2int() { @Test @IR(counts = {IRNode.CONV_D2L, "> 0"}) @IR(counts = {IRNode.X86_SCONV_D2L, "> 0"}, + applyIfPlatform = {"x64", "true"}, applyIfCPUFeature = {"avx10_2", "false"}) @IR(counts = {IRNode.X86_SCONV_D2L_AVX10, "> 0"}, applyIfCPUFeature = {"avx10_2", "true"}) @@ -178,6 +184,7 @@ public void double2long() { @Test @IR(counts = {IRNode.CONV_D2I, "> 0"}) @IR(counts = {IRNode.X86_SCONV_D2I, "> 0"}, + applyIfPlatform = {"x64", "true"}, applyIfCPUFeature = {"avx10_2", "false"}) @IR(counts = {IRNode.X86_SCONV_D2I_AVX10, "> 0"}, applyIfCPUFeature = {"avx10_2", "true"}) @@ -193,6 +200,7 @@ public void double2short() { @Test @IR(counts = {IRNode.CONV_D2I, "> 0"}) @IR(counts = {IRNode.X86_SCONV_D2I, "> 0"}, + applyIfPlatform = {"x64", "true"}, applyIfCPUFeature = {"avx10_2", "false"}) @IR(counts = {IRNode.X86_SCONV_D2I_AVX10, "> 0"}, applyIfCPUFeature = {"avx10_2", "true"}) From 5803dd3e80f4fa6d4795960f88425d4b570ac050 Mon Sep 17 00:00:00 2001 From: Roland Westrelin Date: Thu, 18 Jun 2026 07:45:32 +0000 Subject: [PATCH 23/86] 8376400: C2: folding ifs may cause incorrect execution when trap is taken Reviewed-by: chagedorn Backport-of: ca405d0eb2a0ed63dc169aceb80512bf2a523da1 --- src/hotspot/share/opto/callnode.hpp | 15 +- src/hotspot/share/opto/cfgnode.hpp | 1 + src/hotspot/share/opto/ifnode.cpp | 55 ++++ src/hotspot/share/opto/split_if.cpp | 2 +- .../rangechecks/TestFoldedIfsWrongReexec.java | 299 ++++++++++++++++++ 5 files changed, 369 insertions(+), 3 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/rangechecks/TestFoldedIfsWrongReexec.java diff --git a/src/hotspot/share/opto/callnode.hpp b/src/hotspot/share/opto/callnode.hpp index 213fbda4e896..f51543b68662 100644 --- a/src/hotspot/share/opto/callnode.hpp +++ b/src/hotspot/share/opto/callnode.hpp @@ -820,11 +820,14 @@ class CallJavaNode : public CallNode { // calls and optimized virtual calls, plus calls to wrappers for run-time // routines); generates static stub. class CallStaticJavaNode : public CallJavaNode { + // If this is an uncommon trap guarded by some condition, is it safe to change the condition to a narrower condition? + // See comment in PhaseIdealLoop::do_split_if() + bool _safe_for_fold_compare; virtual bool cmp( const Node &n ) const; virtual uint size_of() const; // Size is bigger public: CallStaticJavaNode(Compile* C, const TypeFunc* tf, address addr, ciMethod* method) - : CallJavaNode(tf, addr, method) { + : CallJavaNode(tf, addr, method), _safe_for_fold_compare(true) { init_class_id(Class_CallStaticJava); if (C->eliminate_boxing() && (method != nullptr) && method->is_boxing_method()) { init_flags(Flag_is_macro); @@ -832,7 +835,7 @@ class CallStaticJavaNode : public CallJavaNode { } } CallStaticJavaNode(const TypeFunc* tf, address addr, const char* name, const TypePtr* adr_type) - : CallJavaNode(tf, addr, nullptr) { + : CallJavaNode(tf, addr, nullptr), _safe_for_fold_compare(true) { init_class_id(Class_CallStaticJava); // This node calls a runtime stub, which often has narrow memory effects. _adr_type = adr_type; @@ -856,6 +859,14 @@ class CallStaticJavaNode : public CallJavaNode { virtual int Opcode() const; virtual Node* Ideal(PhaseGVN* phase, bool can_reshape); + void clear_safe_for_fold_compare() { + _safe_for_fold_compare = false; + } + + bool safe_for_fold_compare() const { + return _safe_for_fold_compare; + } + #ifndef PRODUCT virtual void dump_spec(outputStream *st) const; virtual void dump_compact_spec(outputStream *st) const; diff --git a/src/hotspot/share/opto/cfgnode.hpp b/src/hotspot/share/opto/cfgnode.hpp index a0e780c0e574..b17326bb511d 100644 --- a/src/hotspot/share/opto/cfgnode.hpp +++ b/src/hotspot/share/opto/cfgnode.hpp @@ -457,6 +457,7 @@ class IfNode : public MultiBranchNode { #endif bool same_condition(const Node* dom, PhaseIterGVN* igvn) const; + void mark_projections_unsafe_for_fold_compare() const; }; class RangeCheckNode : public IfNode { diff --git a/src/hotspot/share/opto/ifnode.cpp b/src/hotspot/share/opto/ifnode.cpp index 8d810e4202fa..384e5f8673de 100644 --- a/src/hotspot/share/opto/ifnode.cpp +++ b/src/hotspot/share/opto/ifnode.cpp @@ -876,6 +876,10 @@ bool IfNode::has_only_uncommon_traps(ProjNode* proj, ProjNode*& success, ProjNod return false; } + if (!dom_unc->safe_for_fold_compare()) { + return false; + } + // See merge_uncommon_traps: the reason of the uncommon trap // will be changed and the state of the dominating If will be // used. Checked that we didn't apply this transformation in a @@ -1666,6 +1670,57 @@ bool IfNode::same_condition(const Node* dom, PhaseIterGVN* igvn) const { return true; } +void IfNode::mark_projections_unsafe_for_fold_compare() const { + // With the following code pattern + // + // if (some_condition) { + // v = 0; + // } else { + // v = 1; + // } // v is Phi(0, 1) + // if (v == 0) { + // uncommon_trap(); // reexecutes the "if (v == 0) {" above, captures v as stack argument to ifeq bytecode + // } + // if (some_other_condition) { + // uncommon_trap(); // reexecutes the "if (some_other_condition) {" + // } + // + // if the second if is split thru Phi, the result is: + // + // if (some_condition) { + // uncommon_trap(); // reexecutes the "if (v == 0) {" that was removed above, captures v = 0 as stack argument to ifeq bytecode + // } + // if (some_other_condition) { + // uncommon_trap(); // reexecutes the "if (some_other_condition) {" + // } + // + // some_condition and some_other_condition could be folded into + // a single new condition that is narrower than some_condition + // (done by IfNode::fold_compares(), for instance): + // + // if (combined_narrower_condition) { + // uncommon_trap(); // reexecutes the "if (v == 0) {" that was removed, captures v = 0 as stack argument to ifeq bytecode + // } + // + // Then combined_narrower_condition is true for some input value for + // which some_condition is false. When such an input value is used + // at runtime, the trap is taken which causes "if (v == 0) {" to be + // reexecuted with v = 0 even though some_condition is wrong, causing + // the wrong branch to be executed. + // + // Mark the uncommon trap nodes to prevent such a transformation + // from happening. + IfProjNode* true_projection = proj_out(1)->as_IfProj(); + IfProjNode* false_projection = proj_out(0)->as_IfProj(); + CallStaticJavaNode* unc = true_projection->is_uncommon_trap_proj(); + if (unc != nullptr) { + unc->clear_safe_for_fold_compare(); + } + unc = false_projection->is_uncommon_trap_proj(); + if (unc != nullptr) { + unc->clear_safe_for_fold_compare(); + } +} static int subsuming_bool_test_encode(Node*); diff --git a/src/hotspot/share/opto/split_if.cpp b/src/hotspot/share/opto/split_if.cpp index bede04c6b2c5..b27b0553320c 100644 --- a/src/hotspot/share/opto/split_if.cpp +++ b/src/hotspot/share/opto/split_if.cpp @@ -578,7 +578,7 @@ void PhaseIdealLoop::handle_use( Node *use, Node *def, small_cache *cache, Node // Found an If getting its condition-code input from a Phi in the same block. // Split thru the Region. void PhaseIdealLoop::do_split_if(Node* iff, RegionNode** new_false_region, RegionNode** new_true_region) { - + iff->as_If()->mark_projections_unsafe_for_fold_compare(); C->set_major_progress(); RegionNode *region = iff->in(0)->as_Region(); Node *region_dom = idom(region); diff --git a/test/hotspot/jtreg/compiler/rangechecks/TestFoldedIfsWrongReexec.java b/test/hotspot/jtreg/compiler/rangechecks/TestFoldedIfsWrongReexec.java new file mode 100644 index 000000000000..9e77ceca0dd1 --- /dev/null +++ b/test/hotspot/jtreg/compiler/rangechecks/TestFoldedIfsWrongReexec.java @@ -0,0 +1,299 @@ +/* + * Copyright (c) 2026 IBM Corporation. All rights reserved. + * 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 8376400 + * @summary C2: folding ifs may cause incorrect execution when trap is taken + * + * @run main/othervm -XX:-TieredCompilation -XX:-UseOnStackReplacement -XX:-BackgroundCompilation + * -XX:+UnlockDiagnosticVMOptions -XX:-OptimizeUnstableIf ${test.main.class} + * @run main ${test.main.class} + * + */ + +package compiler.rangechecks; + +public class TestFoldedIfsWrongReexec { + private static int taken1; + private static int taken2; + private static int taken3; + private static int taken4; + private static int taken5; + private static int taken6; + private static int taken7; + private static int MIN_VALUE = Integer.MIN_VALUE; + + public static void main(String[] args) { + for (int i = 0; i < 20_000; i++) { + test1(12); + if (taken1 != 0) { + throw new RuntimeException("branch shouldn't have been taken"); + } + test1Helper1(16, 0); + test2(12); + if (taken2 != 0) { + throw new RuntimeException("branch shouldn't have been taken"); + } + test2Helper1(16, 0); + test3(12); + if (taken3 != 0) { + throw new RuntimeException("branch shouldn't have been taken"); + } + test3Helper1(16, 0); + test4(12, 1, 2); + if (taken4 != 0) { + throw new RuntimeException("branch shouldn't have been taken"); + } + test4Helper1(16, 0, 1, 2); + test5(12); + if (taken5 != 0) { + throw new RuntimeException("branch shouldn't have been taken"); + } + test5Helper1(16, 0); + test6(12, 1, 2); + if (taken6 != 0) { + throw new RuntimeException("branch shouldn't have been taken"); + } + test6Helper1(16, 0, 1, 2); + test7(12); + if (taken7 != 0) { + throw new RuntimeException("branch shouldn't have been taken"); + } + test7Helper1(16, 0); + test7Helper2(o1); + test7Helper2(a); + test7Helper2(b); + } + test1(0); + if (taken1 == 0) { + throw new RuntimeException("branch should have been taken"); + } + test2(0); + if (taken2 == 0) { + throw new RuntimeException("branch should have been taken"); + } + test3(0); + if (taken3 == 0) { + throw new RuntimeException("branch should have been taken"); + } + test4(0, 1, 2); + if (taken4 == 0) { + throw new RuntimeException("branch should have been taken"); + } + test5(0); + if (taken5 == 0) { + throw new RuntimeException("branch should have been taken"); + } + test6(0, 1, 2); + if (taken6 == 0) { + throw new RuntimeException("branch should have been taken"); + } + test7(0); + if (taken7 == 0) { + throw new RuntimeException("branch should have been taken"); + } + } + + private static void test1(int i) { + if (test1Helper1(i, 16) == 0) { + throw new RuntimeException("never taken"); + } + if (i + MIN_VALUE < 8 + Integer.MIN_VALUE) { + taken1++; + } + for (int j = 0; j < 10; j++) { + for (int k = 0; k < 10; k++) { + + } + } + } + + private static int test1Helper1(int i, int j) { + if (i + MIN_VALUE >= j + Integer.MIN_VALUE) { + for (int k = 0; k < 100; k++) { + } + return 0; + } + return 1; + } + + private static void test2(int i) { + if (test2Helper1(i, 16) == 42) { + throw new RuntimeException("never taken"); + } + if (i + MIN_VALUE < 8 + Integer.MIN_VALUE) { + taken2++; + } + for (int j = 0; j < 10; j++) { + for (int k = 0; k < 10; k++) { + + } + } + } + + private static int test2Helper1(int i, int j) { + if (i + MIN_VALUE >= j + Integer.MIN_VALUE) { + for (int k = 0; k < 100; k++) { + } + return 42; + } + return 0x42; + } + + private static void test3(int i) { + if (test3Helper1(i, 16) == 42L) { + throw new RuntimeException("never taken"); + } + if (i + MIN_VALUE < 8 + Integer.MIN_VALUE) { + taken3++; + } + for (int j = 0; j < 10; j++) { + for (int k = 0; k < 10; k++) { + + } + } + } + + private static long test3Helper1(int i, int j) { + if (i + MIN_VALUE >= j + Integer.MIN_VALUE) { + for (int k = 0; k < 100; k++) { + } + return 42L; + } + return 0x42L; + } + + private static void test4(int i, int x, int y) { + if (x == y) { + throw new RuntimeException("never taken"); + } + if (test4Helper1(i, 16, x, y) == y) { + throw new RuntimeException("never taken"); + } + if (i + MIN_VALUE < 8 + Integer.MIN_VALUE) { + taken4++; + } + for (int j = 0; j < 10; j++) { + for (int k = 0; k < 10; k++) { + + } + } + } + + private static int test4Helper1(int i, int j, int x, int y) { + if (i + MIN_VALUE >= j + Integer.MIN_VALUE) { + for (int k = 0; k < 100; k++) { + } + return y; + } + return x; + } + + static final Object o1 = new Object(); + static final Object o2 = new Object(); + + private static void test5(int i) { + if (test5Helper1(i, 16) == o1) { + throw new RuntimeException("never taken"); + } + if (i + MIN_VALUE < 8 + Integer.MIN_VALUE) { + taken5++; + } + for (int j = 0; j < 10; j++) { + for (int k = 0; k < 10; k++) { + + } + } + } + + private static Object test5Helper1(int i, int j) { + if (i + MIN_VALUE >= j + Integer.MIN_VALUE) { + for (int k = 0; k < 100; k++) { + } + return o1; + } + return o2; + } + + private static void test6(int i, int x, int y) { + if (x < y) { + if (test6Helper1(i, 16, x, y) < y) { + throw new RuntimeException("never taken"); + } + if (i + MIN_VALUE < 8 + Integer.MIN_VALUE) { + taken6++; + } + } + for (int j = 0; j < 10; j++) { + for (int k = 0; k < 10; k++) { + + } + } + } + + private static int test6Helper1(int i, int j, int x, int y) { + if (i + MIN_VALUE >= j + Integer.MIN_VALUE) { + for (int k = 0; k < 100; k++) { + } + return x; + } + return y; + } + + static final Object a = new A(); + static final Object b = new B(); + + private static void test7(int i) { + if (test7Helper2(test7Helper1(i, 16))) { + throw new RuntimeException("never taken"); + } + if (i + MIN_VALUE < 8 + Integer.MIN_VALUE) { + taken7++; + } + for (int j = 0; j < 10; j++) { + for (int k = 0; k < 10; k++) { + + } + } + } + + private static Object test7Helper1(int i, int j) { + if (i + MIN_VALUE >= j + Integer.MIN_VALUE) { + for (int k = 0; k < 100; k++) { + } + return a; + } + return b; + } + + private static boolean test7Helper2(Object o) { + return o instanceof A; + } + + private static class A { + } + + private static class B { + } +} From 15629b7e96847e598827cd5039434c57068099c6 Mon Sep 17 00:00:00 2001 From: Goetz Lindenmaier Date: Fri, 19 Jun 2026 06:19:08 +0000 Subject: [PATCH 24/86] 8369488: Update to use jtreg 8.1 Backport-of: 702179e7858bae1c7c13ad6eda3c4fbffdbb15db --- make/autoconf/lib-tests.m4 | 2 +- make/conf/github-actions.conf | 2 +- make/conf/jib-profiles.js | 6 +++--- test/docs/TEST.ROOT | 2 +- test/hotspot/jtreg/TEST.ROOT | 2 +- test/jaxp/TEST.ROOT | 2 +- test/jdk/TEST.ROOT | 2 +- test/langtools/TEST.ROOT | 2 +- test/lib-test/TEST.ROOT | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/make/autoconf/lib-tests.m4 b/make/autoconf/lib-tests.m4 index 23f3d443a6c7..31d480559847 100644 --- a/make/autoconf/lib-tests.m4 +++ b/make/autoconf/lib-tests.m4 @@ -28,7 +28,7 @@ ################################################################################ # Minimum supported versions -JTREG_MINIMUM_VERSION=8 +JTREG_MINIMUM_VERSION=8.1 GTEST_MINIMUM_VERSION=1.14.0 ################################################################################ diff --git a/make/conf/github-actions.conf b/make/conf/github-actions.conf index 16432a56ba28..f48def18d744 100644 --- a/make/conf/github-actions.conf +++ b/make/conf/github-actions.conf @@ -26,7 +26,7 @@ # Versions and download locations for dependencies used by GitHub Actions (GHA) GTEST_VERSION=1.14.0 -JTREG_VERSION=8+2 +JTREG_VERSION=8.1+1 LINUX_X64_BOOT_JDK_EXT=tar.gz LINUX_X64_BOOT_JDK_URL=https://github.com/adoptium/temurin25-binaries/releases/download/jdk-25.0.3%2B9/OpenJDK25U-jdk_x64_linux_hotspot_25.0.3_9.tar.gz diff --git a/make/conf/jib-profiles.js b/make/conf/jib-profiles.js index 4f2bd27d54c2..ac36fa7312e7 100644 --- a/make/conf/jib-profiles.js +++ b/make/conf/jib-profiles.js @@ -1174,9 +1174,9 @@ var getJibProfilesDependencies = function (input, common) { jtreg: { server: "jpg", product: "jtreg", - version: "8", - build_number: "2", - file: "bundles/jtreg-8+2.zip", + version: "8.1", + build_number: "1", + file: "bundles/jtreg-8.1+1.zip", environment_name: "JT_HOME", environment_path: input.get("jtreg", "home_path") + "/bin", configure_args: "--with-jtreg=" + input.get("jtreg", "home_path"), diff --git a/test/docs/TEST.ROOT b/test/docs/TEST.ROOT index bcbfd717dc0a..69e66b08b88e 100644 --- a/test/docs/TEST.ROOT +++ b/test/docs/TEST.ROOT @@ -38,7 +38,7 @@ groups=TEST.groups # Minimum jtreg version -requiredVersion=8+2 +requiredVersion=8.1+1 # Use new module options useNewOptions=true diff --git a/test/hotspot/jtreg/TEST.ROOT b/test/hotspot/jtreg/TEST.ROOT index f5b6922e7e13..df8c0183b335 100644 --- a/test/hotspot/jtreg/TEST.ROOT +++ b/test/hotspot/jtreg/TEST.ROOT @@ -104,7 +104,7 @@ requires.properties= \ jdk.static # Minimum jtreg version -requiredVersion=8+2 +requiredVersion=8.1+1 # Path to libraries in the topmost test directory. This is needed so @library # does not need ../../../ notation to reach them diff --git a/test/jaxp/TEST.ROOT b/test/jaxp/TEST.ROOT index bafa67a700e6..82504c251c09 100644 --- a/test/jaxp/TEST.ROOT +++ b/test/jaxp/TEST.ROOT @@ -23,7 +23,7 @@ modules=java.xml groups=TEST.groups # Minimum jtreg version -requiredVersion=8+2 +requiredVersion=8.1+1 # Path to libraries in the topmost test directory. This is needed so @library # does not need ../../ notation to reach them diff --git a/test/jdk/TEST.ROOT b/test/jdk/TEST.ROOT index 9d12d384399b..c7605946a5f0 100644 --- a/test/jdk/TEST.ROOT +++ b/test/jdk/TEST.ROOT @@ -121,7 +121,7 @@ requires.properties= \ jdk.static # Minimum jtreg version -requiredVersion=8+2 +requiredVersion=8.1+1 # Path to libraries in the topmost test directory. This is needed so @library # does not need ../../ notation to reach them diff --git a/test/langtools/TEST.ROOT b/test/langtools/TEST.ROOT index 1aaaa7dffe1f..14636716882c 100644 --- a/test/langtools/TEST.ROOT +++ b/test/langtools/TEST.ROOT @@ -15,7 +15,7 @@ keys=intermittent randomness needs-src needs-src-jdk_javadoc groups=TEST.groups # Minimum jtreg version -requiredVersion=8+2 +requiredVersion=8.1+1 # Use new module options useNewOptions=true diff --git a/test/lib-test/TEST.ROOT b/test/lib-test/TEST.ROOT index 5710e2e9528b..ebdf3f1a334b 100644 --- a/test/lib-test/TEST.ROOT +++ b/test/lib-test/TEST.ROOT @@ -29,7 +29,7 @@ keys=randomness # Minimum jtreg version -requiredVersion=8+2 +requiredVersion=8.1+1 # Allow querying of various System properties in @requires clauses requires.extraPropDefns = ../jtreg-ext/requires/VMProps.java From 653ff498bdbac2195e9e1e7834cd117a0011989e Mon Sep 17 00:00:00 2001 From: Goetz Lindenmaier Date: Fri, 19 Jun 2026 06:34:41 +0000 Subject: [PATCH 25/86] 8376355: Update to use jtreg 8.2.1 Backport-of: 1e99cc4880f695c12705d849d41609f176f897bd --- make/autoconf/lib-tests.m4 | 4 ++-- make/conf/github-actions.conf | 4 ++-- make/conf/jib-profiles.js | 6 +++--- test/docs/TEST.ROOT | 4 ++-- test/hotspot/jtreg/TEST.ROOT | 4 ++-- test/jaxp/TEST.ROOT | 2 +- test/jdk/TEST.ROOT | 4 ++-- test/langtools/TEST.ROOT | 2 +- test/lib-test/TEST.ROOT | 4 ++-- 9 files changed, 17 insertions(+), 17 deletions(-) diff --git a/make/autoconf/lib-tests.m4 b/make/autoconf/lib-tests.m4 index 31d480559847..faaf229eacda 100644 --- a/make/autoconf/lib-tests.m4 +++ b/make/autoconf/lib-tests.m4 @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -28,7 +28,7 @@ ################################################################################ # Minimum supported versions -JTREG_MINIMUM_VERSION=8.1 +JTREG_MINIMUM_VERSION=8.2.1 GTEST_MINIMUM_VERSION=1.14.0 ################################################################################ diff --git a/make/conf/github-actions.conf b/make/conf/github-actions.conf index f48def18d744..9c4cdfcecb0f 100644 --- a/make/conf/github-actions.conf +++ b/make/conf/github-actions.conf @@ -1,5 +1,5 @@ # -# Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved. # 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 @@ # Versions and download locations for dependencies used by GitHub Actions (GHA) GTEST_VERSION=1.14.0 -JTREG_VERSION=8.1+1 +JTREG_VERSION=8.2.1+1 LINUX_X64_BOOT_JDK_EXT=tar.gz LINUX_X64_BOOT_JDK_URL=https://github.com/adoptium/temurin25-binaries/releases/download/jdk-25.0.3%2B9/OpenJDK25U-jdk_x64_linux_hotspot_25.0.3_9.tar.gz diff --git a/make/conf/jib-profiles.js b/make/conf/jib-profiles.js index ac36fa7312e7..37cdf5315de4 100644 --- a/make/conf/jib-profiles.js +++ b/make/conf/jib-profiles.js @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -1174,9 +1174,9 @@ var getJibProfilesDependencies = function (input, common) { jtreg: { server: "jpg", product: "jtreg", - version: "8.1", + version: "8.2.1", build_number: "1", - file: "bundles/jtreg-8.1+1.zip", + file: "bundles/jtreg-8.2.1+1.zip", environment_name: "JT_HOME", environment_path: input.get("jtreg", "home_path") + "/bin", configure_args: "--with-jtreg=" + input.get("jtreg", "home_path"), diff --git a/test/docs/TEST.ROOT b/test/docs/TEST.ROOT index 69e66b08b88e..9a7e66b631c1 100644 --- a/test/docs/TEST.ROOT +++ b/test/docs/TEST.ROOT @@ -1,5 +1,5 @@ # -# Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. # 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 @@ groups=TEST.groups # Minimum jtreg version -requiredVersion=8.1+1 +requiredVersion=8.2.1+1 # Use new module options useNewOptions=true diff --git a/test/hotspot/jtreg/TEST.ROOT b/test/hotspot/jtreg/TEST.ROOT index df8c0183b335..55ba0beb4f8d 100644 --- a/test/hotspot/jtreg/TEST.ROOT +++ b/test/hotspot/jtreg/TEST.ROOT @@ -1,5 +1,5 @@ # -# Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -104,7 +104,7 @@ requires.properties= \ jdk.static # Minimum jtreg version -requiredVersion=8.1+1 +requiredVersion=8.2.1+1 # Path to libraries in the topmost test directory. This is needed so @library # does not need ../../../ notation to reach them diff --git a/test/jaxp/TEST.ROOT b/test/jaxp/TEST.ROOT index 82504c251c09..aff3b7698304 100644 --- a/test/jaxp/TEST.ROOT +++ b/test/jaxp/TEST.ROOT @@ -23,7 +23,7 @@ modules=java.xml groups=TEST.groups # Minimum jtreg version -requiredVersion=8.1+1 +requiredVersion=8.2.1+1 # Path to libraries in the topmost test directory. This is needed so @library # does not need ../../ notation to reach them diff --git a/test/jdk/TEST.ROOT b/test/jdk/TEST.ROOT index c7605946a5f0..0e51f25178d0 100644 --- a/test/jdk/TEST.ROOT +++ b/test/jdk/TEST.ROOT @@ -1,5 +1,5 @@ # -# Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This file identifies the root of the test-suite hierarchy. @@ -121,7 +121,7 @@ requires.properties= \ jdk.static # Minimum jtreg version -requiredVersion=8.1+1 +requiredVersion=8.2.1+1 # Path to libraries in the topmost test directory. This is needed so @library # does not need ../../ notation to reach them diff --git a/test/langtools/TEST.ROOT b/test/langtools/TEST.ROOT index 14636716882c..434cf91b0ec2 100644 --- a/test/langtools/TEST.ROOT +++ b/test/langtools/TEST.ROOT @@ -15,7 +15,7 @@ keys=intermittent randomness needs-src needs-src-jdk_javadoc groups=TEST.groups # Minimum jtreg version -requiredVersion=8.1+1 +requiredVersion=8.2.1+1 # Use new module options useNewOptions=true diff --git a/test/lib-test/TEST.ROOT b/test/lib-test/TEST.ROOT index ebdf3f1a334b..f23d38c1e669 100644 --- a/test/lib-test/TEST.ROOT +++ b/test/lib-test/TEST.ROOT @@ -1,5 +1,5 @@ # -# Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved. # 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,7 +29,7 @@ keys=randomness # Minimum jtreg version -requiredVersion=8.1+1 +requiredVersion=8.2.1+1 # Allow querying of various System properties in @requires clauses requires.extraPropDefns = ../jtreg-ext/requires/VMProps.java From 19fd4bc48d187b250ada0c613809f70277798a66 Mon Sep 17 00:00:00 2001 From: Goetz Lindenmaier Date: Fri, 19 Jun 2026 10:05:55 +0000 Subject: [PATCH 26/86] 8341382: EXCEPTION_ACCESS_VIOLATION in awt.dll after JDK-8185862 Reviewed-by: mbaesken Backport-of: f640edebf0074f231c1c0a24273536738eb18e28 --- .../windows/native/libawt/windows/Devices.cpp | 140 ++++++++++++------ .../windows/native/libawt/windows/Devices.h | 7 +- .../native/libawt/windows/awt_Toolkit.cpp | 104 +++++++++++-- .../windows/awt_Win32GraphicsDevice.cpp | 70 +++++++-- .../libawt/windows/awt_Win32GraphicsDevice.h | 4 +- .../libawt/windows/awt_Win32GraphicsEnv.cpp | 12 +- 6 files changed, 258 insertions(+), 79 deletions(-) diff --git a/src/java.desktop/windows/native/libawt/windows/Devices.cpp b/src/java.desktop/windows/native/libawt/windows/Devices.cpp index e275cb77a577..6f8bea161f48 100644 --- a/src/java.desktop/windows/native/libawt/windows/Devices.cpp +++ b/src/java.desktop/windows/native/libawt/windows/Devices.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, Oracle and/or its affiliates. All rights reserved. * 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,47 +113,78 @@ static BOOL IsValidMonitor(HMONITOR hMon) return TRUE; } -// Callback for CountMonitors below -static BOOL WINAPI clb_fCountMonitors(HMONITOR hMon, HDC hDC, LPRECT rRect, LPARAM lpMonitorCounter) + +// Callback for CollectMonitors below +static BOOL WINAPI clb_fCollectMonitors(HMONITOR hMon, HDC hDC, LPRECT rRect, LPARAM lpMonitorData) { - if (IsValidMonitor(hMon)) { - (*((int *)lpMonitorCounter))++; + MonitorData* pMonitorData = (MonitorData *)lpMonitorData; + + if (!IsValidMonitor(hMon)) { + return TRUE; + } + + if (pMonitorData->monitorCounter == pMonitorData->monitorLimit) { + TRY; + + int newMonitorLimit = pMonitorData->monitorLimit * 2; + HMONITOR* newMonitors = + (HMONITOR*)SAFE_SIZE_ARRAY_REALLOC( + safe_Realloc, pMonitorData->hmpMonitors, + newMonitorLimit, sizeof(HMONITOR) + ); + pMonitorData->hmpMonitors = newMonitors; + pMonitorData->monitorLimit = newMonitorLimit; + + CATCH_BAD_ALLOC_RET(FALSE); } + pMonitorData->hmpMonitors[pMonitorData->monitorCounter] = hMon; + pMonitorData->monitorCounter++; + return TRUE; } -int WINAPI CountMonitors(void) +static HMONITOR* CollectMonitors(int* numScreens) { - int monitorCounter = 0; - ::EnumDisplayMonitors(NULL, NULL, clb_fCountMonitors, (LPARAM)&monitorCounter); - return monitorCounter; -} + const int initialMonitorLimit = 4; -// Callback for CollectMonitors below -static BOOL WINAPI clb_fCollectMonitors(HMONITOR hMon, HDC hDC, LPRECT rRect, LPARAM lpMonitorData) -{ - MonitorData* pMonitorData = (MonitorData *)lpMonitorData; - if ((pMonitorData->monitorCounter < pMonitorData->monitorLimit) && (IsValidMonitor(hMon))) { - pMonitorData->hmpMonitors[pMonitorData->monitorCounter] = hMon; - pMonitorData->monitorCounter++; + *numScreens = 0; + + MonitorData data; + data.monitorCounter = 0; + data.monitorLimit = initialMonitorLimit; + + TRY; + + data.hmpMonitors = (HMONITOR*)SAFE_SIZE_ARRAY_ALLOC(safe_Malloc, + initialMonitorLimit, sizeof(HMONITOR)); + CATCH_BAD_ALLOC_RET(NULL); + + if (!::EnumDisplayMonitors(NULL, NULL, clb_fCollectMonitors, (LPARAM)&data)) { + free(data.hmpMonitors); + return NULL; } - return TRUE; + *numScreens = data.monitorCounter; + return data.hmpMonitors; } -static int WINAPI CollectMonitors(HMONITOR* hmpMonitors, int nNum) +int WINAPI CountMonitors() { - if (NULL != hmpMonitors) { - MonitorData monitorData; - monitorData.monitorCounter = 0; - monitorData.monitorLimit = nNum; - monitorData.hmpMonitors = hmpMonitors; - ::EnumDisplayMonitors(NULL, NULL, clb_fCollectMonitors, (LPARAM)&monitorData); - return monitorData.monitorCounter; - } else { - return 0; + int numScreens = 0; + HMONITOR* monHds = CollectMonitors(&numScreens); + free(monHds); + return numScreens; +} + +static BOOL AreSameMonitorInfo(LPMONITORINFOEX oldInfo, LPMONITORINFOEX newInfo) +{ + if (oldInfo == NULL || newInfo == NULL) { + return FALSE; } + + return oldInfo->dwFlags == newInfo->dwFlags + && ::lstrcmp(oldInfo->szDevice, newInfo->szDevice) == 0; } BOOL WINAPI MonitorBounds(HMONITOR hmMonitor, RECT* rpBounds) @@ -202,17 +233,26 @@ BOOL Devices::UpdateInstance(JNIEnv *env) { J2dTraceLn(J2D_TRACE_INFO, "Devices::UpdateInstance"); - int numScreens = CountMonitors(); - HMONITOR *monHds = (HMONITOR *)SAFE_SIZE_ARRAY_ALLOC(safe_Malloc, - numScreens, sizeof(HMONITOR)); - if (numScreens != CollectMonitors(monHds, numScreens)) { + int numScreens = 0; + HMONITOR *monHds = CollectMonitors(&numScreens); + if (monHds == NULL) { J2dRlsTraceLn(J2D_TRACE_ERROR, - "Devices::UpdateInstance: Failed to get all "\ + "Devices::UpdateInstance: Failed to get "\ "monitor handles."); free(monHds); return FALSE; } + if (numScreens == 0) { + CriticalSection::Lock l(arrayLock); + if (theInstance != NULL) { + J2dRlsTraceLn(J2D_TRACE_ERROR, + "Devices::UpdateInstance: No valid monitor handles."); + free(monHds); + return FALSE; + } + } + Devices *newDevices = new Devices(numScreens); // This way we know that the array will not be disposed of // at least until we replaced it with a new one. @@ -238,18 +278,26 @@ BOOL Devices::UpdateInstance(JNIEnv *env) theInstance = newDevices; if (oldDevices) { - // Invalidate the devices with indexes out of the new set of - // devices. This doesn't cover all cases when the device - // might should be invalidated (like if it's not the last device - // that was removed), but it will have to do for now. int oldNumScreens = oldDevices->GetNumDevices(); - int newNumScreens = theInstance->GetNumDevices(); - J2dTraceLn(J2D_TRACE_VERBOSE, " Invalidating removed devices"); - for (int i = newNumScreens; i < oldNumScreens; i++) { - // removed device, needs to be invalidated + J2dTraceLn(J2D_TRACE_VERBOSE, " Invalidating changed devices"); + for (int i = 0; i < oldNumScreens; i++) { + AwtWin32GraphicsDevice *oldDevice = + oldDevices->GetDevice(i, FALSE); + AwtWin32GraphicsDevice *newDevice = + theInstance->GetDevice(i, FALSE); + BOOL changed = (newDevice == NULL) + || !AreSameMonitorInfo( + (LPMONITORINFOEX) oldDevice->GetMonitorInfo(), + (LPMONITORINFOEX) newDevice->GetMonitorInfo()); + + if (!changed) { + newDevice->TransferJavaDevice(env, oldDevice); + continue; + } + J2dTraceLn1(J2D_TRACE_WARNING, - "Devices::UpdateInstance: device removed: %d", i); - oldDevices->GetDevice(i)->Invalidate(env); + "Devices::UpdateInstance: device changed: %d", i); + oldDevice->Invalidate(env); } // Now that we have a new array in place, remove this (possibly the // last) reference to the old instance. @@ -342,6 +390,12 @@ AwtWin32GraphicsDevice *Devices::GetDevice(int index, BOOL adjust) J2dTraceLn2(J2D_TRACE_INFO, "Devices::GetDevice index=%d adjust?=%d", index, adjust); + if (numDevices <= 0) { + J2dTraceLn(J2D_TRACE_WARNING, + "Devices::GetDevice: "\ + "no devices, returning NULL."); + return NULL; + } if (index < 0 || index >= numDevices) { if (!adjust) { J2dTraceLn1(J2D_TRACE_WARNING, diff --git a/src/java.desktop/windows/native/libawt/windows/Devices.h b/src/java.desktop/windows/native/libawt/windows/Devices.h index 0972ef1414e5..7e7a419453eb 100644 --- a/src/java.desktop/windows/native/libawt/windows/Devices.h +++ b/src/java.desktop/windows/native/libawt/windows/Devices.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, Oracle and/or its affiliates. All rights reserved. * 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,8 +47,11 @@ static BOOL UpdateInstance(JNIEnv *env); class InstanceAccess { public: INLINE InstanceAccess() { devices = Devices::GetInstance(); } - INLINE ~InstanceAccess() { devices->Release(); } + INLINE ~InstanceAccess() { if (devices != NULL) devices->Release(); } Devices* operator->() { return devices; } + INLINE AwtWin32GraphicsDevice* Device(int index, BOOL adjust = TRUE) { + return devices == NULL ? NULL : devices->GetDevice(index, adjust); + } private: Devices* devices; // prevent bad things like copying or getting address of diff --git a/src/java.desktop/windows/native/libawt/windows/awt_Toolkit.cpp b/src/java.desktop/windows/native/libawt/windows/awt_Toolkit.cpp index b447ad6889af..a94c96c58c5f 100644 --- a/src/java.desktop/windows/native/libawt/windows/awt_Toolkit.cpp +++ b/src/java.desktop/windows/native/libawt/windows/awt_Toolkit.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2026, Oracle and/or its affiliates. All rights reserved. * 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,7 +65,7 @@ #include #include -extern void initScreens(JNIEnv *env); +extern BOOL initScreens(JNIEnv *env); extern "C" void awt_dnd_initialize(); extern "C" void awt_dnd_uninitialize(); extern "C" void awt_clipboard_uninitialize(JNIEnv *env); @@ -157,6 +157,78 @@ extern "C" JNIEXPORT jboolean JNICALL AWTIsHeadless() { } #define IDT_AWT_MOUSECHECK 0x101 +#define IDT_AWT_DISPLAYCHANGE 0x102 + +#define AWT_DISPLAYCHANGE_RETRY_DELAY 250 +#define AWT_DISPLAYCHANGE_RETRY_LIMIT 20 + +class DisplayChangeHandler { +public: + static BOOL Handle(JNIEnv *env, HWND hWnd) { + // Reinitialize screens + if (!initScreens(env)) { + OnDisplayChangeFailed(hWnd); + return FALSE; + } + + OnDisplayChangeSucceeded(hWnd); + + // Notify Java side - call WToolkit.displayChanged() + jclass clazz = env->FindClass("sun/awt/windows/WToolkit"); + DASSERT(clazz != NULL); + if (!clazz) throw std::bad_alloc(); + env->CallStaticVoidMethod(clazz, AwtToolkit::displayChangeMID); + + return !env->ExceptionCheck(); + } + + static void Reset(HWND hWnd) { + ::KillTimer(hWnd, IDT_AWT_DISPLAYCHANGE); + retryCount = 0; + } + + static void ScheduleFromSessionChange(HWND hWnd) { + if (!recoveryPending) { + return; + } + Reset(hWnd); + Schedule(hWnd); + } + +private: + static void OnDisplayChangeFailed(HWND hWnd) { + recoveryPending = TRUE; + Schedule(hWnd); + } + + static void OnDisplayChangeSucceeded(HWND hWnd) { + recoveryPending = FALSE; + Reset(hWnd); + } + + static void Schedule(HWND hWnd) { + if (retryCount >= AWT_DISPLAYCHANGE_RETRY_LIMIT) { + Reset(hWnd); + J2dRlsTraceLn(J2D_TRACE_ERROR, + "AwtToolkit: Display change retry limit exceeded."); + return; + } + + retryCount++; + if (::SetTimer(hWnd, IDT_AWT_DISPLAYCHANGE, + AWT_DISPLAYCHANGE_RETRY_DELAY, NULL) == 0) { + Reset(hWnd); + J2dRlsTraceLn(J2D_TRACE_ERROR, + "AwtToolkit: Failed to schedule display change retry."); + } + } + + static int retryCount; + static BOOL recoveryPending; +}; + +int DisplayChangeHandler::retryCount = 0; +BOOL DisplayChangeHandler::recoveryPending = FALSE; static LPCTSTR szAwtToolkitClassName = TEXT("SunAwtToolkit"); @@ -1004,6 +1076,14 @@ LRESULT CALLBACK AwtToolkit::WndProc(HWND hWnd, UINT message, } case WM_TIMER: { + if (wParam == IDT_AWT_DISPLAYCHANGE) { + if (DisplayChangeHandler::Handle(env, hWnd)) { + GetInstance().m_displayChanged = TRUE; + ::PostMessage(HWND_BROADCAST, WM_PALETTEISCHANGING, NULL, NULL); + } + return 0; + } + // 6479820. Should check if a window is in manual resizing process: skip // sending any MouseExit/Enter events while inside resize-loop. // Note that window being in manual moving process could still @@ -1245,18 +1325,11 @@ LRESULT CALLBACK AwtToolkit::WndProc(HWND hWnd, UINT message, return tk.m_inputMethodData; } case WM_DISPLAYCHANGE: { - // Reinitialize screens - initScreens(env); - - // Notify Java side - call WToolkit.displayChanged() - jclass clazz = env->FindClass("sun/awt/windows/WToolkit"); - DASSERT(clazz != NULL); - if (!clazz) throw std::bad_alloc(); - env->CallStaticVoidMethod(clazz, AwtToolkit::displayChangeMID); - - GetInstance().m_displayChanged = TRUE; - - ::PostMessage(HWND_BROADCAST, WM_PALETTEISCHANGING, NULL, NULL); + DisplayChangeHandler::Reset(hWnd); + if (DisplayChangeHandler::Handle(env, hWnd)) { + GetInstance().m_displayChanged = TRUE; + ::PostMessage(HWND_BROADCAST, WM_PALETTEISCHANGING, NULL, NULL); + } break; } /* Session management */ @@ -1341,6 +1414,9 @@ LRESULT CALLBACK AwtToolkit::WndProc(HWND hWnd, UINT message, activate ? JNI_TRUE : JNI_FALSE, reason); + if (activate) { + DisplayChangeHandler::ScheduleFromSessionChange(hWnd); + } } break; } diff --git a/src/java.desktop/windows/native/libawt/windows/awt_Win32GraphicsDevice.cpp b/src/java.desktop/windows/native/libawt/windows/awt_Win32GraphicsDevice.cpp index 8a84a28685fd..d11c382877e2 100644 --- a/src/java.desktop/windows/native/libawt/windows/awt_Win32GraphicsDevice.cpp +++ b/src/java.desktop/windows/native/libawt/windows/awt_Win32GraphicsDevice.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -612,13 +612,52 @@ void AwtWin32GraphicsDevice::Release() } /** - * Links this native object with its java Win32GraphicsDevice. - * Need this link because the colorModel of the java device - * may be updated from native code. + * Links this native object with its java Win32GraphicsDevice peer. + * + * The link is needed for upcalls to the java peer, such as invalidate() + * and dynamic color model updates. + * + * Passing NULL intentionally clears the link. + * Clearing it here prevents stale peer links and releases + * the old JNI weak global ref. + * + * During display changes, the native device array is recreated, + * changed or removed devices invalidate their java peers. + * Unchanged monitors transfer the existing weak ref to + * the new native device by TransferJavaDevice(). */ void AwtWin32GraphicsDevice::SetJavaDevice(JNIEnv *env, jobject objPtr) { - javaDevice = env->NewWeakGlobalRef(objPtr); + jobject newJavaDevice = NULL; + if (objPtr != NULL) { + newJavaDevice = env->NewWeakGlobalRef(objPtr); + if (newJavaDevice == NULL) { + return; + } + } + + if (javaDevice != NULL) { + env->DeleteWeakGlobalRef(javaDevice); + } + javaDevice = newJavaDevice; +} + +/** + * Transfers the java Win32GraphicsDevice's link from a native device that is + * being replaced by a new native device for the same monitor. + */ +void AwtWin32GraphicsDevice::TransferJavaDevice(JNIEnv *env, + AwtWin32GraphicsDevice *device) +{ + if (device == NULL || device == this || device->javaDevice == NULL) { + return; + } + + if (javaDevice != NULL) { + env->DeleteWeakGlobalRef(javaDevice); + } + javaDevice = device->javaDevice; + device->javaDevice = NULL; } /** @@ -1398,7 +1437,10 @@ JNIEXPORT void JNICALL (JNIEnv *env, jobject thisPtr, jint screen) { Devices::InstanceAccess devices; - devices->GetDevice(screen)->SetJavaDevice(env, thisPtr); + AwtWin32GraphicsDevice *device = devices.Device(screen, FALSE); + if (device != NULL) { + device->SetJavaDevice(env, thisPtr); + } } /* @@ -1411,9 +1453,8 @@ JNIEXPORT void JNICALL (JNIEnv *env, jobject thisPtr, jint screen, jfloat scaleX, jfloat scaleY) { Devices::InstanceAccess devices; - AwtWin32GraphicsDevice *device = devices->GetDevice(screen); - - if (device != NULL ) { + AwtWin32GraphicsDevice *device = devices.Device(screen, FALSE); + if (device != NULL) { device->DisableScaleAutoRefresh(); device->SetScale(scaleX, scaleY); } @@ -1429,8 +1470,8 @@ JNIEXPORT jfloat JNICALL (JNIEnv *env, jobject thisPtr, jint screen) { Devices::InstanceAccess devices; - AwtWin32GraphicsDevice *device = devices->GetDevice(screen); - return (device == NULL) ? 1 : device->GetScaleX(); + AwtWin32GraphicsDevice *device = devices.Device(screen, FALSE); + return device == NULL ? 1 : device->GetScaleX(); } /* @@ -1443,8 +1484,8 @@ JNIEXPORT jfloat JNICALL (JNIEnv *env, jobject thisPtr, jint screen) { Devices::InstanceAccess devices; - AwtWin32GraphicsDevice *device = devices->GetDevice(screen); - return (device == NULL) ? 1 : device->GetScaleY(); + AwtWin32GraphicsDevice *device = devices.Device(screen, FALSE); + return device == NULL ? 1 : device->GetScaleY(); } /* @@ -1457,8 +1498,7 @@ Java_sun_awt_Win32GraphicsDevice_initNativeScale (JNIEnv *env, jobject thisPtr, jint screen) { Devices::InstanceAccess devices; - AwtWin32GraphicsDevice *device = devices->GetDevice(screen); - + AwtWin32GraphicsDevice *device = devices.Device(screen, FALSE); if (device != NULL) { device->InitDesktopScales(); } diff --git a/src/java.desktop/windows/native/libawt/windows/awt_Win32GraphicsDevice.h b/src/java.desktop/windows/native/libawt/windows/awt_Win32GraphicsDevice.h index 55f6c1623a8e..b3619dcc5454 100644 --- a/src/java.desktop/windows/native/libawt/windows/awt_Win32GraphicsDevice.h +++ b/src/java.desktop/windows/native/libawt/windows/awt_Win32GraphicsDevice.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, Oracle and/or its affiliates. All rights reserved. * 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,6 +54,8 @@ class AwtWin32GraphicsDevice { unsigned int *GetSystemPaletteEntries(); unsigned char *GetSystemInverseLUT(); void SetJavaDevice(JNIEnv *env, jobject objPtr); + void TransferJavaDevice(JNIEnv *env, + AwtWin32GraphicsDevice *device); HPALETTE SelectPalette(HDC hDC); void RealizePalette(HDC hDC); HPALETTE GetPalette(); diff --git a/src/java.desktop/windows/native/libawt/windows/awt_Win32GraphicsEnv.cpp b/src/java.desktop/windows/native/libawt/windows/awt_Win32GraphicsEnv.cpp index 9991427c75ed..12ab7ef622ff 100644 --- a/src/java.desktop/windows/native/libawt/windows/awt_Win32GraphicsEnv.cpp +++ b/src/java.desktop/windows/native/libawt/windows/awt_Win32GraphicsEnv.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. * 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,10 +35,12 @@ BOOL DWMIsCompositionEnabled(); -void initScreens(JNIEnv *env) { +BOOL initScreens(JNIEnv *env) { if (!Devices::UpdateInstance(env)) { - JNU_ThrowInternalError(env, "Could not update the devices array."); + J2dRlsTraceLn(J2D_TRACE_ERROR, "initScreens: Could not update the devices array."); + return FALSE; } + return TRUE; } /** @@ -144,7 +146,9 @@ Java_sun_awt_Win32GraphicsEnvironment_initDisplay(JNIEnv *env, DWMIsCompositionEnabled(); - initScreens(env); + if (!initScreens(env)) { + JNU_ThrowInternalError(env, "Could not update the devices array."); + } } /* From ff40bdf23b427be8bac496707301cdbea80dd005 Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Fri, 19 Jun 2026 10:11:01 +0000 Subject: [PATCH 27/86] 8378838: Fix issues with "dead" code elimination and serviceability agent in libjvm.so on Linux Reviewed-by: mdoerr Backport-of: 909d4e758c045a0d8cea52f6a1333839f7d6c43e --- make/autoconf/flags-cflags.m4 | 5 +---- make/autoconf/flags-ldflags.m4 | 15 +++++++-------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/make/autoconf/flags-cflags.m4 b/make/autoconf/flags-cflags.m4 index 9bea6b5062e8..a2fbae9c43c6 100644 --- a/make/autoconf/flags-cflags.m4 +++ b/make/autoconf/flags-cflags.m4 @@ -531,12 +531,9 @@ AC_DEFUN([FLAGS_SETUP_CFLAGS_HELPER], TOOLCHAIN_CFLAGS_JVM="$TOOLCHAIN_CFLAGS_JVM -fstack-protector" TOOLCHAIN_CFLAGS_JDK="-fvisibility=hidden -pipe -fstack-protector" # reduce lib size on linux in link step, this needs also special compile flags - # do this on s390x also for libjvm (where serviceability agent is not supported) if test "x$ENABLE_LINKTIME_GC" = xtrue; then TOOLCHAIN_CFLAGS_JDK="$TOOLCHAIN_CFLAGS_JDK -ffunction-sections -fdata-sections" - if test "x$OPENJDK_TARGET_CPU" = xs390x && test "x$DEBUG_LEVEL" == xrelease; then - TOOLCHAIN_CFLAGS_JVM="$TOOLCHAIN_CFLAGS_JVM -ffunction-sections -fdata-sections" - fi + TOOLCHAIN_CFLAGS_JVM="$TOOLCHAIN_CFLAGS_JVM -ffunction-sections -fdata-sections" fi # technically NOT for CXX (but since this gives *worse* performance, use # no-strict-aliasing everywhere!) diff --git a/make/autoconf/flags-ldflags.m4 b/make/autoconf/flags-ldflags.m4 index 77ea7a693c91..9d9fee79e751 100644 --- a/make/autoconf/flags-ldflags.m4 +++ b/make/autoconf/flags-ldflags.m4 @@ -1,5 +1,5 @@ # -# Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2011, 2026, Oracle and/or its affiliates. All rights reserved. # 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,17 +51,16 @@ AC_DEFUN([FLAGS_SETUP_LDFLAGS_HELPER], # add -z,now ("full relro" - more of the Global Offset Table GOT is marked read only) # add --no-as-needed to disable default --as-needed link flag on some GCC toolchains BASIC_LDFLAGS="-Wl,-z,defs -Wl,-z,relro -Wl,-z,now -Wl,--no-as-needed -Wl,--exclude-libs,ALL" + + BASIC_LDFLAGS_JVM_ONLY="" # Linux : remove unused code+data in link step if test "x$ENABLE_LINKTIME_GC" = xtrue; then - if test "x$OPENJDK_TARGET_CPU" = xs390x; then - BASIC_LDFLAGS="$BASIC_LDFLAGS -Wl,--gc-sections" - else - BASIC_LDFLAGS_JDK_ONLY="$BASIC_LDFLAGS_JDK_ONLY -Wl,--gc-sections" - fi + # keep vtables : -Wl,--undefined-glob=_ZTV* (but this seems not to work with gold ld) + # so keep at least the Metadata vtable that is used in the serviceability agent + BASIC_LDFLAGS_JVM_ONLY="$BASIC_LDFLAGS_JVM_ONLY -Wl,--gc-sections -Wl,--undefined=_ZTV8Metadata" + BASIC_LDFLAGS_JDK_ONLY="$BASIC_LDFLAGS_JDK_ONLY -Wl,--gc-sections" fi - BASIC_LDFLAGS_JVM_ONLY="" - LDFLAGS_CXX_PARTIAL_LINKING="$MACHINE_FLAG -r" elif test "x$TOOLCHAIN_TYPE" = xclang; then From 4450293b704446f6da920ee5ae4be55fb8f97c98 Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Fri, 19 Jun 2026 10:13:41 +0000 Subject: [PATCH 28/86] 8351842: Windows specific issues in combination of JEP 493 and --with-external-symbols-in-bundles=public Backport-of: 33dda887d99d39b2d003fd6521db97d45da474f0 --- make/Bundles.gmk | 28 +------------------ make/Images.gmk | 22 +++++++++------ make/hotspot/lib/CompileJvm.gmk | 12 ++++---- src/hotspot/share/prims/whitebox.cpp | 15 ++++++++-- .../NMT/CheckForProperDetailStackTrace.java | 13 +++------ .../jdk/modules/etc/JmodExcludedFiles.java | 24 ++++++++-------- test/lib/jdk/test/whitebox/WhiteBox.java | 9 ++++-- 7 files changed, 56 insertions(+), 67 deletions(-) diff --git a/make/Bundles.gmk b/make/Bundles.gmk index ba8ec0c864b0..b097f61731c4 100644 --- a/make/Bundles.gmk +++ b/make/Bundles.gmk @@ -125,13 +125,6 @@ define SetupBundleFileBody && $(TAR) cf - -$(TAR_INCLUDE_PARAM) $$($1_$$d_LIST_FILE) \ $(TAR_IGNORE_EXIT_VALUE) ) \ | ( $(CD) $(SUPPORT_OUTPUTDIR)/bundles/$1/$$($1_SUBDIR) && $(TAR) xf - )$$(NEWLINE) ) - # Rename stripped pdb files - ifeq ($(call isTargetOs, windows)+$(SHIP_DEBUG_SYMBOLS), true+public) - for f in `$(FIND) $(SUPPORT_OUTPUTDIR)/bundles/$1/$$($1_SUBDIR) -name "*.stripped.pdb"`; do \ - $(ECHO) Renaming $$$${f} to $$$${f%stripped.pdb}pdb $(LOG_INFO); \ - $(MV) $$$${f} $$$${f%stripped.pdb}pdb; \ - done - endif # Unzip any zipped debuginfo files ifeq ($$($1_UNZIP_DEBUGINFO), true) for f in `$(FIND) $(SUPPORT_OUTPUTDIR)/bundles/$1/$$($1_SUBDIR) -name "*.diz"`; do \ @@ -222,14 +215,6 @@ ifneq ($(filter product-bundles% legacy-bundles, $(MAKECMDGOALS)), ) ifeq ($(call isTargetOs, windows), true) ifeq ($(SHIP_DEBUG_SYMBOLS), ) JDK_SYMBOLS_EXCLUDE_PATTERN := %.pdb - else - ifeq ($(SHIP_DEBUG_SYMBOLS), public) - JDK_SYMBOLS_EXCLUDE_PATTERN := \ - $(filter-out \ - %.stripped.pdb, \ - $(filter %.pdb, $(ALL_JDK_FILES)) \ - ) - endif endif endif @@ -244,10 +229,7 @@ ifneq ($(filter product-bundles% legacy-bundles, $(MAKECMDGOALS)), ) ) JDK_SYMBOLS_BUNDLE_FILES := \ - $(filter-out \ - %.stripped.pdb, \ - $(call FindFiles, $(SYMBOLS_IMAGE_DIR)) \ - ) + $(call FindFiles, $(SYMBOLS_IMAGE_DIR)) TEST_DEMOS_BUNDLE_FILES := $(filter $(JDK_DEMOS_IMAGE_HOMEDIR)/demo/%, \ $(ALL_JDK_DEMOS_FILES)) @@ -267,14 +249,6 @@ ifneq ($(filter product-bundles% legacy-bundles, $(MAKECMDGOALS)), ) ifeq ($(call isTargetOs, windows), true) ifeq ($(SHIP_DEBUG_SYMBOLS), ) JRE_SYMBOLS_EXCLUDE_PATTERN := %.pdb - else - ifeq ($(SHIP_DEBUG_SYMBOLS), public) - JRE_SYMBOLS_EXCLUDE_PATTERN := \ - $(filter-out \ - %.stripped.pdb, \ - $(filter %.pdb, $(ALL_JRE_FILES)) \ - ) - endif endif endif diff --git a/make/Images.gmk b/make/Images.gmk index 22e3e43cb1f3..66ffda4f2e38 100644 --- a/make/Images.gmk +++ b/make/Images.gmk @@ -282,29 +282,33 @@ else endif CMDS_TARGET_SUBDIR := bin -# Param 1 - either JDK or JRE +# Copy debug info files into symbols bundle. +# In case of Windows and --with-external-symbols-in-bundles=public, take care to remove *.stripped.pdb files SetupCopyDebuginfo = \ $(foreach m, $(ALL_$1_MODULES), \ + $(eval dbgfiles := $(call FindDebuginfoFiles, $(SUPPORT_OUTPUTDIR)/modules_libs/$m)) \ + $(eval dbgfiles := $(if $(filter true+public,$(call isTargetOs,windows)+$(SHIP_DEBUG_SYMBOLS)), \ + $(filter-out %.stripped.pdb,$(dbgfiles)),$(dbgfiles)) \ + ) \ $(eval $(call SetupCopyFiles, COPY_$1_LIBS_DEBUGINFO_$m, \ SRC := $(SUPPORT_OUTPUTDIR)/modules_libs/$m, \ DEST := $($1_IMAGE_DIR)/$(LIBS_TARGET_SUBDIR), \ - FILES := $(call FindDebuginfoFiles, \ - $(SUPPORT_OUTPUTDIR)/modules_libs/$m), \ + FILES := $(dbgfiles), \ )) \ $(eval $1_TARGETS += $$(COPY_$1_LIBS_DEBUGINFO_$m)) \ + $(eval dbgfiles := $(call FindDebuginfoFiles, $(SUPPORT_OUTPUTDIR)/modules_cmds/$m)) \ + $(eval dbgfiles := $(if $(filter true+public,$(call isTargetOs,windows)+$(SHIP_DEBUG_SYMBOLS)), \ + $(filter-out %.stripped.pdb,$(dbgfiles)),$(dbgfiles)) \ + ) \ $(eval $(call SetupCopyFiles, COPY_$1_CMDS_DEBUGINFO_$m, \ SRC := $(SUPPORT_OUTPUTDIR)/modules_cmds/$m, \ DEST := $($1_IMAGE_DIR)/$(CMDS_TARGET_SUBDIR), \ - FILES := $(call FindDebuginfoFiles, \ - $(SUPPORT_OUTPUTDIR)/modules_cmds/$m), \ + FILES := $(dbgfiles), \ )) \ $(eval $1_TARGETS += $$(COPY_$1_CMDS_DEBUGINFO_$m)) \ ) -# No space before argument to avoid having to put $(strip ) everywhere in -# implementation above. -$(call SetupCopyDebuginfo,JDK) -$(call SetupCopyDebuginfo,JRE) +# No space before argument to avoid having to put $(strip ) everywhere in implementation above. $(call SetupCopyDebuginfo,SYMBOLS) ################################################################################ diff --git a/make/hotspot/lib/CompileJvm.gmk b/make/hotspot/lib/CompileJvm.gmk index cead9e644360..4b7bc1a365e3 100644 --- a/make/hotspot/lib/CompileJvm.gmk +++ b/make/hotspot/lib/CompileJvm.gmk @@ -149,6 +149,12 @@ JVM_STRIPFLAGS ?= $(STRIPFLAGS) # This source set is reused so save in cache. $(call FillFindCache, $(JVM_SRC_DIRS)) +ifeq ($(SHIP_DEBUG_SYMBOLS), full) + CFLAGS_SHIP_DEBUGINFO := -DSHIP_DEBUGINFO_FULL +else ifeq ($(SHIP_DEBUG_SYMBOLS), public) + CFLAGS_SHIP_DEBUGINFO := -DSHIP_DEBUGINFO_PUBLIC +endif + ifeq ($(call isTargetOs, windows), true) ifeq ($(STATIC_LIBS), true) WIN_EXPORT_FILE := $(JVM_OUTPUTDIR)/static-win-exports.def @@ -156,10 +162,6 @@ ifeq ($(call isTargetOs, windows), true) WIN_EXPORT_FILE := $(JVM_OUTPUTDIR)/win-exports.def endif - ifeq ($(SHIP_DEBUG_SYMBOLS), public) - CFLAGS_STRIPPED_DEBUGINFO := -DHAS_STRIPPED_DEBUGINFO - endif - JVM_LDFLAGS += -def:$(WIN_EXPORT_FILE) endif @@ -185,7 +187,7 @@ $(eval $(call SetupJdkLibrary, BUILD_LIBJVM, \ CFLAGS := $(JVM_CFLAGS), \ abstract_vm_version.cpp_CXXFLAGS := $(CFLAGS_VM_VERSION), \ arguments.cpp_CXXFLAGS := $(CFLAGS_VM_VERSION), \ - whitebox.cpp_CXXFLAGS := $(CFLAGS_STRIPPED_DEBUGINFO), \ + whitebox.cpp_CXXFLAGS := $(CFLAGS_SHIP_DEBUGINFO), \ DISABLED_WARNINGS_gcc := $(DISABLED_WARNINGS_gcc), \ DISABLED_WARNINGS_gcc_ad_$(HOTSPOT_TARGET_CPU_ARCH).cpp := nonnull, \ DISABLED_WARNINGS_gcc_bytecodeInterpreter.cpp := unused-label, \ diff --git a/src/hotspot/share/prims/whitebox.cpp b/src/hotspot/share/prims/whitebox.cpp index 9999b59decee..b5dbc92e40cf 100644 --- a/src/hotspot/share/prims/whitebox.cpp +++ b/src/hotspot/share/prims/whitebox.cpp @@ -509,8 +509,16 @@ WB_ENTRY(jboolean, WB_ConcurrentGCRunTo(JNIEnv* env, jobject o, jobject at)) return ConcurrentGCBreakpoints::run_to(c_name); WB_END -WB_ENTRY(jboolean, WB_HasExternalSymbolsStripped(JNIEnv* env, jobject o)) -#if defined(HAS_STRIPPED_DEBUGINFO) +WB_ENTRY(jboolean, WB_ShipDebugInfoFull(JNIEnv* env, jobject o)) +#if defined(SHIP_DEBUGINFO_FULL) + return true; +#else + return false; +#endif +WB_END + +WB_ENTRY(jboolean, WB_ShipDebugInfoPublic(JNIEnv* env, jobject o)) +#if defined(SHIP_DEBUGINFO_PUBLIC) return true; #else return false; @@ -2794,7 +2802,8 @@ static JNINativeMethod methods[] = { {CC"getVMLargePageSize", CC"()J", (void*)&WB_GetVMLargePageSize}, {CC"getHeapSpaceAlignment", CC"()J", (void*)&WB_GetHeapSpaceAlignment}, {CC"getHeapAlignment", CC"()J", (void*)&WB_GetHeapAlignment}, - {CC"hasExternalSymbolsStripped", CC"()Z", (void*)&WB_HasExternalSymbolsStripped}, + {CC"shipsFullDebugInfo", CC"()Z", (void*)&WB_ShipDebugInfoFull}, + {CC"shipsPublicDebugInfo", CC"()Z", (void*)&WB_ShipDebugInfoPublic}, {CC"countAliveClasses0", CC"(Ljava/lang/String;)I", (void*)&WB_CountAliveClasses }, {CC"getSymbolRefcount", CC"(Ljava/lang/String;)I", (void*)&WB_GetSymbolRefcount }, {CC"parseCommandLine0", diff --git a/test/hotspot/jtreg/runtime/NMT/CheckForProperDetailStackTrace.java b/test/hotspot/jtreg/runtime/NMT/CheckForProperDetailStackTrace.java index 28af06927211..66c256be3cc9 100644 --- a/test/hotspot/jtreg/runtime/NMT/CheckForProperDetailStackTrace.java +++ b/test/hotspot/jtreg/runtime/NMT/CheckForProperDetailStackTrace.java @@ -63,10 +63,9 @@ public class CheckForProperDetailStackTrace { private static final Path SRC_DIR = Paths.get(TEST_SRC, "src"); private static final Path MODS_DIR = Paths.get(TEST_CLASSES, "mods"); - // Windows has source information only in full pdbs, not in stripped pdbs - private static boolean expectSourceInformation = Platform.isLinux() || Platform.isWindows(); - - static WhiteBox wb = WhiteBox.getWhiteBox(); + // In some configurations on Windows, we could have stripped pdbs which do not have source information. + private static boolean expectSourceInformation = (Platform.isLinux() || Platform.isWindows()) && + WhiteBox.getWhiteBox().shipsFullDebugInfo(); /* The stack trace we look for by default. Note that :: has been replaced by .* to make sure it matches even if the symbol is not unmangled. @@ -145,12 +144,8 @@ public static void main(String args[]) throws Exception { throw new RuntimeException("Expected stack trace missing from output"); } - if (wb.hasExternalSymbolsStripped()) { - expectSourceInformation = false; - } - - System.out.println("Looking for source information:"); if (expectSourceInformation) { + System.out.println("Looking for source information:"); if (!stackTraceMatches(".*moduleEntry.cpp.*", output)) { output.reportDiagnosticSummary(); throw new RuntimeException("Expected source information missing from output"); diff --git a/test/jdk/jdk/modules/etc/JmodExcludedFiles.java b/test/jdk/jdk/modules/etc/JmodExcludedFiles.java index 90ca6840d52a..3929419a0803 100644 --- a/test/jdk/jdk/modules/etc/JmodExcludedFiles.java +++ b/test/jdk/jdk/modules/etc/JmodExcludedFiles.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 @@ -25,9 +25,12 @@ * @test * @bug 8159927 * @modules java.base/jdk.internal.util + * @library /test/lib * @requires jlink.packagedModules - * @run main JmodExcludedFiles - * @summary Test that JDK JMOD files do not include native debug symbols + * @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 JmodExcludedFiles + * @summary Test that JDK JMOD files do not include native debug symbols when it is not configured */ import java.nio.file.DirectoryStream; @@ -37,9 +40,11 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import jdk.internal.util.OperatingSystem; +import jdk.test.whitebox.WhiteBox; public class JmodExcludedFiles { private static String javaHome = System.getProperty("java.home"); + private static final boolean expectSymbols = WhiteBox.getWhiteBox().shipsDebugInfo(); public static void main(String[] args) throws Exception { Path jmods = Path.of(javaHome, "jmods"); @@ -76,24 +81,19 @@ boolean isNativeDebugSymbol(String name) { if (i != -1) { if (n.substring(0, i).endsWith(".dSYM")) { System.err.println("Found symbols in " + jmod + ": " + name); - return true; + return expectSymbols ? false: true; } } } if (OperatingSystem.isWindows() && name.endsWith(".pdb")) { - // on Windows we check if we should have public symbols through --with-external-symbols-in-bundles=public (JDK-8237192) - String strippedpdb = javaHome + "/bin/" + name.substring(index + 1, name.length() - 4) + ".stripped.pdb"; - if (!Files.exists(Paths.get(strippedpdb))) { - System.err.println("Found symbols in " + jmod + ": " + name + - ". No stripped pdb file " + strippedpdb + " exists."); - return true; - } + System.err.println("Found symbols in " + jmod + ": " + name); + return expectSymbols ? false: true; } if (name.endsWith(".diz") || name.endsWith(".debuginfo") || name.endsWith(".map")) { System.err.println("Found symbols in " + jmod + ": " + name); - return true; + return expectSymbols ? false: true; } } return false; diff --git a/test/lib/jdk/test/whitebox/WhiteBox.java b/test/lib/jdk/test/whitebox/WhiteBox.java index 97e12f0d8a22..3433952dd335 100644 --- a/test/lib/jdk/test/whitebox/WhiteBox.java +++ b/test/lib/jdk/test/whitebox/WhiteBox.java @@ -66,7 +66,7 @@ public synchronized static WhiteBox getWhiteBox() { // Memory private native long getObjectAddress0(Object o); - public long getObjectAddress(Object o) { + public long getObjectAddress(Object o) { Objects.requireNonNull(o); return getObjectAddress0(o); } @@ -79,7 +79,12 @@ public long getObjectAddress(Object o) { public native long getHeapAlignment(); public native long getMinimumJavaStackSize(); - public native boolean hasExternalSymbolsStripped(); + public native boolean shipsFullDebugInfo(); + public native boolean shipsPublicDebugInfo(); + + public boolean shipsDebugInfo() { + return shipsFullDebugInfo() || shipsPublicDebugInfo(); + } private native boolean isObjectInOldGen0(Object o); public boolean isObjectInOldGen(Object o) { From 29464290c2c595ae5b7f0ff3bc47dda7f0e0ec75 Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Fri, 19 Jun 2026 10:14:03 +0000 Subject: [PATCH 29/86] 8385454: Provide more NUMA related information in hsinfo/hserr files Reviewed-by: mdoerr Backport-of: 7a7ee23168584b5ff80c3cb11360b738089f11c3 --- src/hotspot/os/linux/os_linux.cpp | 95 +++++++++++++++++++++++++ src/hotspot/os/linux/os_linux.hpp | 1 + src/hotspot/share/utilities/ostream.hpp | 2 +- 3 files changed, 97 insertions(+), 1 deletion(-) diff --git a/src/hotspot/os/linux/os_linux.cpp b/src/hotspot/os/linux/os_linux.cpp index 3ef58971c2c1..d38c37141f73 100644 --- a/src/hotspot/os/linux/os_linux.cpp +++ b/src/hotspot/os/linux/os_linux.cpp @@ -2189,6 +2189,10 @@ void os::print_os_info(outputStream* st) { st->cr(); } + if (os::Linux::print_numa_info(st)) { + st->cr(); + } + VM_Version::print_platform_virtualization_info(st); os::Linux::print_steal_info(st); @@ -2592,6 +2596,97 @@ bool os::Linux::print_container_info(outputStream* st) { return true; } +#define SYS_DEVICES_NODE "/sys/devices/system/node" + +static size_t read_sysfs_file(const char* path, char* buf, size_t sz) { + FILE* f = os::fopen(path, "r"); + if (f == nullptr) return 0; + size_t n = fread(buf, 1, sz - 1, f); + fclose(f); + buf[n] = '\0'; + while (n > 0 && (buf[n-1] == '\n' || buf[n-1] == '\r')) buf[--n] = '\0'; + return n; +} + +static void print_numa_memory_info(outputStream* st, int node) { + char path[256]; + char line[256]; + long long mem_total = -1; + long long mem_free = -1; + os::snprintf_checked(path, sizeof(path), SYS_DEVICES_NODE "/node%d/meminfo", node); + FILE* f = os::fopen(path, "r"); + if (f == nullptr) { + return; + } + + while (fgets(line, sizeof(line), f) != nullptr) { + long long mval; + if (sscanf(line, "Node %*d MemTotal: %lld kB", &mval) == 1) mem_total = mval; + if (sscanf(line, "Node %*d MemFree: %lld kB", &mval) == 1) mem_free = mval; + } + fclose(f); + + if (mem_total >= 0) { st->print_cr("mem size: %lld kB", mem_total); } + if (mem_free >= 0) { st->print_cr("mem free: %lld kB", mem_free); } +} + +static void print_numa_cpu_list(outputStream* st, int node) { + char path[256]; + char buf[1024]; + os::snprintf_checked(path, sizeof(path), SYS_DEVICES_NODE "/node%d/cpulist", node); + if (read_sysfs_file(path, buf, sizeof(buf)) > 0) { + st->print_cr("cpus: %s", buf); + } else { + st->print_cr("cpus: (unavailable)"); + } +} + +bool os::Linux::print_numa_info(outputStream* st) { + if (!UseNUMA) { + // If NUMA optimizations are not enabled we don't print anything + return false; + } + + char buf[1024]; + if (read_sysfs_file("/sys/devices/system/node/online", buf, sizeof(buf)) > 0) { + st->print_cr("NUMA nodes online: %s", buf); + } else { + return false; + } + + bool first = true; + int node_count = 0; + + if (nindex_to_node() == nullptr) { + return false; + } + + for (int node: *nindex_to_node()) { + char nodepath[256]; + os::snprintf_checked(nodepath, sizeof(nodepath), SYS_DEVICES_NODE "/node%d", node); + DIR* currd = os::opendir(nodepath); + if (currd == nullptr) continue; + if (first) { + st->cr(); + first = false; + } + os::closedir(currd); + + st->print_cr("NUMA node %d", node); + StreamIndentor si(st); + print_numa_cpu_list(st, node); + print_numa_memory_info(st, node); + node_count++; + } + + if (node_count == 0) { + return false; + } + + st->print_cr("Total NUMA node count: %d", node_count); + return true; +} + void os::Linux::print_steal_info(outputStream* st) { if (has_initial_tick_info) { CPUPerfTicks pticks; diff --git a/src/hotspot/os/linux/os_linux.hpp b/src/hotspot/os/linux/os_linux.hpp index e2bd8eb3d31f..20039a4146f1 100644 --- a/src/hotspot/os/linux/os_linux.hpp +++ b/src/hotspot/os/linux/os_linux.hpp @@ -80,6 +80,7 @@ class os::Linux { static void print_proc_sys_info(outputStream* st); static bool print_ld_preload_file(outputStream* st); static void print_uptime_info(outputStream* st); + static bool print_numa_info(outputStream* st); public: struct CPUPerfTicks { diff --git a/src/hotspot/share/utilities/ostream.hpp b/src/hotspot/share/utilities/ostream.hpp index 29761f52c2cb..9408372d1aec 100644 --- a/src/hotspot/share/utilities/ostream.hpp +++ b/src/hotspot/share/utilities/ostream.hpp @@ -182,7 +182,7 @@ class StreamIndentor { NONCOPYABLE(StreamIndentor); public: - StreamIndentor(outputStream* os, int indentation) : + StreamIndentor(outputStream* os, int indentation = 2) : _stream(os), _indentation(indentation), _old_autoindent(_stream->set_autoindent(true)) { From ca10ac5e0bd11cc648aeb90e3fcf83cfb96ea88f Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Fri, 19 Jun 2026 12:40:25 +0000 Subject: [PATCH 30/86] 8373246: JDK-8351842 broke native debugging on Linux Backport-of: b5ac8f83682ddb9623a1b43bd62f309b2961a504 --- make/Bundles.gmk | 53 ++---------------------------------- make/CreateJmods.gmk | 8 ++++-- make/autoconf/jdk-options.m4 | 27 +++++++++++++----- 3 files changed, 29 insertions(+), 59 deletions(-) diff --git a/make/Bundles.gmk b/make/Bundles.gmk index b097f61731c4..d48a9c037dc9 100644 --- a/make/Bundles.gmk +++ b/make/Bundles.gmk @@ -185,77 +185,30 @@ endif ifneq ($(filter product-bundles% legacy-bundles, $(MAKECMDGOALS)), ) - SYMBOLS_EXCLUDE_PATTERN := %.debuginfo %.diz %.map - - # There may be files with spaces in the names, so use ShellFindFiles - # explicitly. + # There may be files with spaces in the names, so use ShellFindFiles explicitly. ALL_JDK_FILES := $(call ShellFindFiles, $(JDK_IMAGE_DIR)) - ifneq ($(JDK_IMAGE_DIR), $(JDK_SYMBOLS_IMAGE_DIR)) - ALL_JDK_SYMBOLS_FILES := $(call ShellFindFiles, $(JDK_SYMBOLS_IMAGE_DIR)) - else - ALL_JDK_SYMBOLS_FILES := $(ALL_JDK_FILES) - endif ifneq ($(JDK_IMAGE_DIR), $(JDK_DEMOS_IMAGE_DIR)) ALL_JDK_DEMOS_FILES := $(call ShellFindFiles, $(JDK_DEMOS_IMAGE_DIR)) else ALL_JDK_DEMOS_FILES := $(ALL_JDK_FILES) endif - # Create special filter rules when dealing with unzipped .dSYM directories on - # macosx - ifeq ($(call isTargetOs, macosx), true) - ifeq ($(ZIP_EXTERNAL_DEBUG_SYMBOLS), false) - JDK_SYMBOLS_EXCLUDE_PATTERN := $(addprefix %, \ - $(call containing, .dSYM/, $(patsubst $(JDK_IMAGE_DIR)/%, %, \ - $(ALL_JDK_SYMBOLS_FILES)))) - endif - endif - - # Create special filter rules when dealing with debug symbols on windows - ifeq ($(call isTargetOs, windows), true) - ifeq ($(SHIP_DEBUG_SYMBOLS), ) - JDK_SYMBOLS_EXCLUDE_PATTERN := %.pdb - endif - endif - JDK_BUNDLE_FILES := \ $(filter-out \ - $(JDK_SYMBOLS_EXCLUDE_PATTERN) \ $(JDK_EXTRA_EXCLUDES) \ - $(SYMBOLS_EXCLUDE_PATTERN) \ $(JDK_IMAGE_HOMEDIR)/demo/% \ , \ $(ALL_JDK_FILES) \ ) - JDK_SYMBOLS_BUNDLE_FILES := \ - $(call FindFiles, $(SYMBOLS_IMAGE_DIR)) + JDK_SYMBOLS_BUNDLE_FILES := $(call FindFiles, $(SYMBOLS_IMAGE_DIR)) TEST_DEMOS_BUNDLE_FILES := $(filter $(JDK_DEMOS_IMAGE_HOMEDIR)/demo/%, \ $(ALL_JDK_DEMOS_FILES)) ALL_JRE_FILES := $(call ShellFindFiles, $(JRE_IMAGE_DIR)) - # Create special filter rules when dealing with unzipped .dSYM directories on - # macosx - ifeq ($(OPENJDK_TARGET_OS), macosx) - ifeq ($(ZIP_EXTERNAL_DEBUG_SYMBOLS), false) - JRE_SYMBOLS_EXCLUDE_PATTERN := $(addprefix %, \ - $(call containing, .dSYM/, $(patsubst $(JRE_IMAGE_DIR)/%, %, $(ALL_JRE_FILES)))) - endif - endif - - # Create special filter rules when dealing with debug symbols on windows - ifeq ($(call isTargetOs, windows), true) - ifeq ($(SHIP_DEBUG_SYMBOLS), ) - JRE_SYMBOLS_EXCLUDE_PATTERN := %.pdb - endif - endif - - JRE_BUNDLE_FILES := $(filter-out \ - $(JRE_SYMBOLS_EXCLUDE_PATTERN) \ - $(SYMBOLS_EXCLUDE_PATTERN), \ - $(ALL_JRE_FILES)) + JRE_BUNDLE_FILES := $(ALL_JRE_FILES) ifeq ($(MACOSX_CODESIGN_MODE), hardened) # Macosx release build and code signing available. diff --git a/make/CreateJmods.gmk b/make/CreateJmods.gmk index 40bceda69a97..b252ff017326 100644 --- a/make/CreateJmods.gmk +++ b/make/CreateJmods.gmk @@ -218,10 +218,14 @@ ifeq ($(call isTargetOs, windows), true) ifeq ($(SHIP_DEBUG_SYMBOLS), ) JMOD_FLAGS += --exclude '**{_the.*,_*.marker*,*.diz,*.pdb,*.map}' else - JMOD_FLAGS += --exclude '**{_the.*,_*.marker*,*.diz,*.map}' + JMOD_FLAGS += --exclude '**{_the.*,_*.marker*,*.map}' endif else - JMOD_FLAGS += --exclude '**{_the.*,_*.marker*,*.diz,*.debuginfo,*.dSYM/**,*.dSYM}' + ifeq ($(SHIP_DEBUG_SYMBOLS), ) + JMOD_FLAGS += --exclude '**{_the.*,_*.marker*,*.diz,*.debuginfo,*.dSYM/**,*.dSYM}' + else + JMOD_FLAGS += --exclude '**{_the.*,_*.marker*}' + endif endif # Unless we are creating a very large module, use the small tool JVM options diff --git a/make/autoconf/jdk-options.m4 b/make/autoconf/jdk-options.m4 index c4d203aff3c9..6091d155bef1 100644 --- a/make/autoconf/jdk-options.m4 +++ b/make/autoconf/jdk-options.m4 @@ -327,23 +327,36 @@ AC_DEFUN_ONCE([JDKOPT_SETUP_DEBUG_SYMBOLS], AC_MSG_CHECKING([if we should add external native debug symbols to the shipped bundles]) AC_ARG_WITH([external-symbols-in-bundles], [AS_HELP_STRING([--with-external-symbols-in-bundles], - [which type of external native debug symbol information shall be shipped in product bundles (none, public, full) - (e.g. ship full/stripped pdbs on Windows) @<:@none@:>@])]) + [which type of external native debug symbol information shall be shipped with bundles/images (none, public, full). + @<:@none in release builds, full otherwise. --with-native-debug-symbols=external/zipped is a prerequisite. public is only supported on Windows@:>@])], + [], + [with_external_symbols_in_bundles=default]) if test "x$with_external_symbols_in_bundles" = x || test "x$with_external_symbols_in_bundles" = xnone ; then AC_MSG_RESULT([no]) elif test "x$with_external_symbols_in_bundles" = xfull || test "x$with_external_symbols_in_bundles" = xpublic ; then - if test "x$OPENJDK_TARGET_OS" != xwindows ; then - AC_MSG_ERROR([--with-external-symbols-in-bundles currently only works on windows!]) - elif test "x$COPY_DEBUG_SYMBOLS" != xtrue ; then - AC_MSG_ERROR([--with-external-symbols-in-bundles only works when --with-native-debug-symbols=external is used!]) - elif test "x$with_external_symbols_in_bundles" = xfull ; then + if test "x$COPY_DEBUG_SYMBOLS" != xtrue ; then + AC_MSG_ERROR([--with-external-symbols-in-bundles only works when --with-native-debug-symbols=external/zipped is used!]) + elif test "x$with_external_symbols_in_bundles" = xpublic && test "x$OPENJDK_TARGET_OS" != xwindows ; then + AC_MSG_ERROR([--with-external-symbols-in-bundles=public is only supported on Windows!]) + fi + + if test "x$with_external_symbols_in_bundles" = xfull ; then AC_MSG_RESULT([full]) SHIP_DEBUG_SYMBOLS=full else AC_MSG_RESULT([public]) SHIP_DEBUG_SYMBOLS=public fi + elif test "x$with_external_symbols_in_bundles" = xdefault ; then + if test "x$DEBUG_LEVEL" = xrelease ; then + AC_MSG_RESULT([no (default)]) + elif test "x$COPY_DEBUG_SYMBOLS" = xtrue ; then + AC_MSG_RESULT([full (default)]) + SHIP_DEBUG_SYMBOLS=full + else + AC_MSG_RESULT([no (default, native debug symbols are not external/zipped)]) + fi else AC_MSG_ERROR([$with_external_symbols_in_bundles is an unknown value for --with-external-symbols-in-bundles]) fi From 2fc458bc846f10454ce176a66de2a88a06e908cc Mon Sep 17 00:00:00 2001 From: Roman Marchenko Date: Mon, 22 Jun 2026 07:09:19 +0000 Subject: [PATCH 31/86] 8367027: java/lang/ProcessBuilder/Basic.java fails on Windows AArch64 Backport-of: f10c85fbc336f6908a4f1ecae9fb5ab52984f636 --- test/jdk/java/lang/ProcessBuilder/Basic.java | 25 ++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/test/jdk/java/lang/ProcessBuilder/Basic.java b/test/jdk/java/lang/ProcessBuilder/Basic.java index fd25b4ca1c40..87c303b2bd40 100644 --- a/test/jdk/java/lang/ProcessBuilder/Basic.java +++ b/test/jdk/java/lang/ProcessBuilder/Basic.java @@ -224,7 +224,7 @@ private static void compareLinesIgnoreCase(String lines1, String lines2) { private static String winEnvFilter(String env) { return env.replaceAll("\r", "") - .replaceAll("(?m)^(?:COMSPEC|PROMPT|PATHEXT)=.*\n",""); + .replaceAll("(?m)^(?:COMSPEC|PROMPT|PATHEXT|PROCESSOR_ARCHITECTURE)=.*\n",""); } private static String unixEnvProg() { @@ -822,6 +822,14 @@ private static String removeAixExpectedVars(String vars) { return vars.replace("AIXTHREAD_GUARDPAGES=0,", ""); } + /* Only used for Windows AArch64 -- + * Windows AArch64 adds the variable PROCESSOR_ARCHITECTURE=ARM64 to the environment. + * Remove it from the list of env variables + */ + private static String removeWindowsAArch64ExpectedVars(String vars) { + return vars.replace("PROCESSOR_ARCHITECTURE=ARM64,", ""); + } + private static String sortByLinesWindowsly(String text) { String[] lines = text.split("\n"); Arrays.sort(lines, new WindowsComparator()); @@ -1348,6 +1356,9 @@ private static void realMain(String[] args) throws Throwable { if (AIX.is()) { result = removeAixExpectedVars(result); } + if (Windows.is() && Platform.isAArch64()) { + result = removeWindowsAArch64ExpectedVars(result); + } equal(result, expected); } catch (Throwable t) { unexpected(t); } @@ -1860,6 +1871,9 @@ public void doIt(Map environ) { if (AIX.is()) { commandOutput = removeAixExpectedVars(commandOutput); } + if (Windows.is() && Platform.isAArch64()) { + commandOutput = removeWindowsAArch64ExpectedVars(commandOutput); + } equal(commandOutput, expected); if (Windows.is()) { ProcessBuilder pb = new ProcessBuilder(childArgs); @@ -1867,7 +1881,11 @@ public void doIt(Map environ) { pb.environment().put("SystemRoot", systemRoot); pb.environment().put("=ExitValue", "3"); pb.environment().put("=C:", "\\"); - equal(commandOutput(pb), expected); + commandOutput = commandOutput(pb); + if (Platform.isAArch64()) { + commandOutput = removeWindowsAArch64ExpectedVars(commandOutput); + } + equal(commandOutput, expected); } } catch (Throwable t) { unexpected(t); } @@ -1919,6 +1937,9 @@ public void doIt(Map environ) { if (AIX.is()) { commandOutput = removeAixExpectedVars(commandOutput); } + if (Windows.is() && Platform.isAArch64()) { + commandOutput = removeWindowsAArch64ExpectedVars(commandOutput); + } check(commandOutput.equals(Windows.is() ? "LC_ALL=C,SystemRoot="+systemRoot+"," : AIX.is() From a1206f00852916dffd5fd9c48eae515e28305be2 Mon Sep 17 00:00:00 2001 From: Mohamed Issa Date: Mon, 22 Jun 2026 17:29:42 +0000 Subject: [PATCH 32/86] 8386578: double_keccak stub does not set its return value on x86 Reviewed-by: mdoerr, vpaprotski --- src/hotspot/cpu/x86/stubGenerator_x86_64_sha3.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64_sha3.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_sha3.cpp index 9f13233f1d21..7aa1d72515ca 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64_sha3.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_sha3.cpp @@ -484,6 +484,8 @@ static address generate_double_keccak(StubGenerator *stubgen, MacroAssembler *_m __ cmpl(roundsLeft, 0); __ jcc(Assembler::notEqual, rounds24_loop); + __ xorq(rax, rax); // return 0 + // store the states for (int i = 0; i < 5; i++) { __ evmovdquq(Address(state0, i * 40), k5, xmm(i), true, Assembler::AVX_512bit); From 567ecce0ecde55641a9b03ea4362802d9813f86e Mon Sep 17 00:00:00 2001 From: Sruthy Jayan Date: Thu, 25 Jun 2026 13:51:01 +0000 Subject: [PATCH 33/86] 8380993: [REDO] Incorrect Interpretation of POSIX TZ Environment Variable on AIX Backport-of: 132072077aa4a15db108989e3d17e6d9249d3ba2 --- .../unix/native/libjava/TimeZone_md.c | 72 +++++++++---- .../java/util/TimeZone/AIXTzMappingTest.java | 102 ++++++++++++++++++ .../util/TimeZone/CustomTzIDCheckDST.java | 7 +- 3 files changed, 158 insertions(+), 23 deletions(-) create mode 100644 test/jdk/java/util/TimeZone/AIXTzMappingTest.java diff --git a/src/java.base/unix/native/libjava/TimeZone_md.c b/src/java.base/unix/native/libjava/TimeZone_md.c index 2f163cf27f1f..bc2ed500d604 100644 --- a/src/java.base/unix/native/libjava/TimeZone_md.c +++ b/src/java.base/unix/native/libjava/TimeZone_md.c @@ -352,33 +352,15 @@ getPlatformTimeZoneID() } static char * -mapPlatformToJavaTimezone(const char *java_home_dir, const char *tz) { +getJavaTimezoneFromPlatform(const char *tz_buf, size_t tz_len, const char *mapfilename) { FILE *tzmapf; - char mapfilename[PATH_MAX + 1]; char line[256]; int linecount = 0; - char *tz_buf = NULL; - char *temp_tz = NULL; char *javatz = NULL; - size_t tz_len = 0; - - /* On AIX, the TZ environment variable may end with a comma - * followed by modifier fields until early AIX6.1. - * This restriction has been removed from AIX7. */ - tz_buf = strdup(tz); - tz_len = strlen(tz_buf); - - /* Open tzmappings file, with buffer overrun check */ - if ((strlen(java_home_dir) + 15) > PATH_MAX) { - jio_fprintf(stderr, "Path %s/lib/tzmappings exceeds maximum path length\n", java_home_dir); - goto tzerr; - } - strcpy(mapfilename, java_home_dir); - strcat(mapfilename, "/lib/tzmappings"); if ((tzmapf = fopen(mapfilename, "r")) == NULL) { jio_fprintf(stderr, "can't open %s\n", mapfilename); - goto tzerr; + return NULL; } while (fgets(line, sizeof(line), tzmapf) != NULL) { @@ -431,10 +413,58 @@ mapPlatformToJavaTimezone(const char *java_home_dir, const char *tz) { break; } } + (void) fclose(tzmapf); + return javatz; +} + +static char * +mapPlatformToJavaTimezone(const char *java_home_dir, const char *tz) { + char mapfilename[PATH_MAX + 1]; + char *tz_buf = NULL; + char *javatz = NULL; + char *temp_tz = NULL; + size_t tz_len = 0; + + /* On AIX, the TZ environment variable may end with a comma + * followed by modifier fields until early AIX6.1. + * This restriction has been removed from AIX7. */ + + tz_buf = strdup(tz); + if (tz_buf == NULL) { + jio_fprintf(stderr, "Failed to allocate timezone buffer\n"); + goto tzerr; + } + tz_len = strlen(tz_buf); + + /* Open tzmappings file, with buffer overrun check */ + if ((strlen(java_home_dir) + 15) > PATH_MAX) { + jio_fprintf(stderr, "Path %s/lib/tzmappings exceeds maximum path length\n", java_home_dir); + goto tzerr; + } + strcpy(mapfilename, java_home_dir); + strcat(mapfilename, "/lib/tzmappings"); + + // First attempt to find the Java timezone for the full tz string + javatz = getJavaTimezoneFromPlatform(tz_buf, tz_len, mapfilename); + + // If no match was found, check for timezone with truncated value + if (javatz == NULL) { + temp_tz = strchr(tz, ','); + tz_len = (temp_tz == NULL) ? strlen(tz) : temp_tz - tz; + free((void *) tz_buf); + tz_buf = (char *)malloc(tz_len + 1); + if (tz_buf == NULL) { + jio_fprintf(stderr, "Failed to allocate timezone buffer\n"); + goto tzerr; + } + memcpy(tz_buf, tz, tz_len); + tz_buf[tz_len] = '\0'; + javatz = getJavaTimezoneFromPlatform(tz_buf, tz_len, mapfilename); + } tzerr: - if (tz_buf != NULL ) { + if (tz_buf != NULL) { free((void *) tz_buf); } diff --git a/test/jdk/java/util/TimeZone/AIXTzMappingTest.java b/test/jdk/java/util/TimeZone/AIXTzMappingTest.java new file mode 100644 index 000000000000..6324bb8581a5 --- /dev/null +++ b/test/jdk/java/util/TimeZone/AIXTzMappingTest.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * 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 8380993 + * @library /test/lib + * @summary Validates AIX timezone mapping behavior where POSIX TZ strings + * with comma-separated DST rules are truncated and mapped through tzmappings + * to the expected IANA timezone IDs. + * @requires os.family == "aix" + * @run main/othervm AIXTzMappingTest + */ + +import java.util.TimeZone; + +import jdk.test.lib.process.ProcessTools; +import jdk.test.lib.process.OutputAnalyzer; + +public class AIXTzMappingTest { + + // POSIX TZ strings that should be mapped via tzmappings + private static final String TZ_CET = "CET-1CEST,M3.5.0,M10.5.0"; + private static final String TZ_MEZ = "MEZ-1MESZ,M3.5.0,M10.5.0/3"; + + private static final String ID_PARIS = "Europe/Paris"; + private static final String ID_BERLIN = "Europe/Berlin"; + + public static void main(String[] args) throws Throwable { + if (args.length == 0) { + runWithTZ(TZ_CET, ID_PARIS); + runWithTZ(TZ_MEZ, ID_BERLIN); + } else if (args.length == 1) { + runTZTest(args[0]); + } else { + throw new RuntimeException( + "Expected 0 or 1 arguments, got " + args.length); + } + } + + private static void runWithTZ(String tz, String expectedId) + throws Throwable { + ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder( + "AIXTzMappingTest", expectedId); + + pb.environment().put("TZ", tz); + + OutputAnalyzer output = ProcessTools.executeProcess(pb); + output.shouldHaveExitValue(0); + } + + /* + * On AIX, POSIX TZ strings such as: + * CET-1CEST,M3.5.0,M10.5.0 + * MEZ-1MESZ,M3.5.0,M10.5.0/3 + * are truncated at the comma and mapped through tzmappings to + * IANA timezone IDs. + * + * This test verifies that the expected IANA timezone ID is selected. + */ + private static void runTZTest(String expectedId) { + String tzStr = System.getenv("TZ"); + + if (tzStr == null) { + throw new RuntimeException( + "Got unexpected timezone information: TZ is null"); + } + + TimeZone tz = TimeZone.getDefault(); + String tzId = tz.getID(); + + if (!expectedId.equals(tzId)) { + throw new RuntimeException( + "Expected timezone ID " + expectedId + + " but got " + tzId + + " for TZ=" + tzStr); + } + + System.out.println( + "AIX timezone mapping test passed: " + + tzId + " for TZ=" + tzStr); + } +} diff --git a/test/jdk/java/util/TimeZone/CustomTzIDCheckDST.java b/test/jdk/java/util/TimeZone/CustomTzIDCheckDST.java index 5fd48efadcc3..9a332202ab3c 100644 --- a/test/jdk/java/util/TimeZone/CustomTzIDCheckDST.java +++ b/test/jdk/java/util/TimeZone/CustomTzIDCheckDST.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2026, Oracle and/or its affiliates. All rights reserved. * 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,10 @@ * @library /test/lib * @summary This test will ensure that daylight savings rules are followed * appropriately when setting a custom timezone ID via the TZ env variable. - * @requires os.family != "windows" + * AIX is excluded because it uses a different timezone mapping mechanism + * through the tzmappings file; see AIXTzMappingTest.java for AIX-specific + * coverage. + * @requires os.family != "windows" & os.family != "aix" * @run main/othervm CustomTzIDCheckDST */ From d721341895ceafab142f6708e1f254d3a77758b5 Mon Sep 17 00:00:00 2001 From: Ashay Rane Date: Mon, 29 Jun 2026 16:09:23 +0000 Subject: [PATCH 34/86] 8365579: ml64.exe is not the right assembler for Windows aarch64 Backport-of: a62942424858178ce99cd5df0e4d484620b1631d --- make/autoconf/flags-other.m4 | 8 ++++++-- make/autoconf/toolchain.m4 | 7 +++++-- make/common/native/CompileFile.gmk | 8 +++++++- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/make/autoconf/flags-other.m4 b/make/autoconf/flags-other.m4 index f0fa82489df3..9d41cf047913 100644 --- a/make/autoconf/flags-other.m4 +++ b/make/autoconf/flags-other.m4 @@ -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 @@ -115,7 +115,11 @@ AC_DEFUN([FLAGS_SETUP_ASFLAGS], # Force preprocessor to run, just to make sure BASIC_ASFLAGS="-x assembler-with-cpp" elif test "x$TOOLCHAIN_TYPE" = xmicrosoft; then - BASIC_ASFLAGS="-nologo -c" + if test "x$OPENJDK_TARGET_CPU" = xaarch64; then + BASIC_ASFLAGS="-nologo" + else + BASIC_ASFLAGS="-nologo -c" + fi fi AC_SUBST(BASIC_ASFLAGS) diff --git a/make/autoconf/toolchain.m4 b/make/autoconf/toolchain.m4 index b7a010746862..f3ef44d382b1 100644 --- a/make/autoconf/toolchain.m4 +++ b/make/autoconf/toolchain.m4 @@ -655,8 +655,11 @@ AC_DEFUN_ONCE([TOOLCHAIN_DETECT_TOOLCHAIN_CORE], if test "x$TOOLCHAIN_TYPE" != xmicrosoft; then AS="$CC -c" else - if test "x$OPENJDK_TARGET_CPU_BITS" = "x64"; then - # On 64 bit windows, the assembler is "ml64.exe" + if test "x$OPENJDK_TARGET_CPU" = "xaarch64"; then + # On Windows aarch64, the assembler is "armasm64.exe" + UTIL_LOOKUP_TOOLCHAIN_PROGS(AS, armasm64) + elif test "x$OPENJDK_TARGET_CPU_BITS" = "x64"; then + # On Windows x64, the assembler is "ml64.exe" UTIL_LOOKUP_TOOLCHAIN_PROGS(AS, ml64) else # otherwise, the assembler is "ml.exe" diff --git a/make/common/native/CompileFile.gmk b/make/common/native/CompileFile.gmk index 9c3d39d6edf3..39b5f34a4c5b 100644 --- a/make/common/native/CompileFile.gmk +++ b/make/common/native/CompileFile.gmk @@ -155,6 +155,12 @@ define CreateCompiledNativeFileBody endif $1_FLAGS := $$($1_FLAGS) -DASSEMBLY_SRC_FILE='"$$($1_REL_ASM_SRC)"' \ -include $(TOPDIR)/make/data/autoheaders/assemblyprefix.h + else ifeq ($(TOOLCHAIN_TYPE), microsoft) + ifeq ($(OPENJDK_TARGET_CPU), aarch64) + $1_NON_ASM_EXTENSION_FLAG := + else + $1_NON_ASM_EXTENSION_FLAG := "-Ta" + endif endif else ifneq ($$(filter %.cpp %.cc %.mm, $$($1_FILENAME)), ) # Compile as a C++ or Objective-C++ file @@ -236,7 +242,7 @@ define CreateCompiledNativeFileBody # For assembler calls just create empty dependency lists $$(call ExecuteWithLog, $$@, $$(call MakeCommandRelative, \ $$($1_COMPILER) $$($1_FLAGS) \ - $(CC_OUT_OPTION)$$($1_OBJ) -Ta $$($1_SRC_FILE))) \ + $(CC_OUT_OPTION)$$($1_OBJ) $$($1_NON_ASM_EXTENSION_FLAG) $$($1_SRC_FILE))) \ | $(TR) -d '\r' | $(GREP) -v -e "Assembling:" || test "$$$$?" = "1" ; \ $(ECHO) > $$($1_DEPS_FILE) ; \ $(ECHO) > $$($1_DEPS_TARGETS_FILE) From 5aeda188703b2727f838551365eae195df42ddc2 Mon Sep 17 00:00:00 2001 From: Goetz Lindenmaier Date: Mon, 29 Jun 2026 20:30:49 +0000 Subject: [PATCH 35/86] 8357220: Introduce a BSMAttributeEntry struct Backport-of: 3d35b408e1e69d7e3953af142c5bf606691fbeb8 --- .../share/cds/aotConstantPoolResolver.cpp | 6 +- src/hotspot/share/oops/constantPool.cpp | 14 +-- src/hotspot/share/oops/constantPool.hpp | 98 ++++++++++--------- .../prims/jvmtiClassFileReconstituter.cpp | 10 +- .../share/prims/jvmtiRedefineClasses.cpp | 9 +- src/hotspot/share/runtime/vmStructs.cpp | 12 +-- .../sun/jvm/hotspot/oops/ConstantPool.java | 8 +- 7 files changed, 84 insertions(+), 73 deletions(-) diff --git a/src/hotspot/share/cds/aotConstantPoolResolver.cpp b/src/hotspot/share/cds/aotConstantPoolResolver.cpp index 3ea0c76f8702..234a2d17759d 100644 --- a/src/hotspot/share/cds/aotConstantPoolResolver.cpp +++ b/src/hotspot/share/cds/aotConstantPoolResolver.cpp @@ -408,7 +408,7 @@ bool AOTConstantPoolResolver::check_lambda_metafactory_signature(ConstantPool* c } bool AOTConstantPoolResolver::check_lambda_metafactory_methodtype_arg(ConstantPool* cp, int bsms_attribute_index, int arg_i) { - int mt_index = cp->operand_argument_index_at(bsms_attribute_index, arg_i); + int mt_index = cp->bsm_attribute_entry(bsms_attribute_index)->argument_index(arg_i); if (!cp->tag_at(mt_index).is_method_type()) { // malformed class? return false; @@ -424,7 +424,7 @@ bool AOTConstantPoolResolver::check_lambda_metafactory_methodtype_arg(ConstantPo } bool AOTConstantPoolResolver::check_lambda_metafactory_methodhandle_arg(ConstantPool* cp, int bsms_attribute_index, int arg_i) { - int mh_index = cp->operand_argument_index_at(bsms_attribute_index, arg_i); + int mh_index = cp->bsm_attribute_entry(bsms_attribute_index)->argument_index(arg_i); if (!cp->tag_at(mh_index).is_method_handle()) { // malformed class? return false; @@ -563,7 +563,7 @@ bool AOTConstantPoolResolver::is_indy_resolution_deterministic(ConstantPool* cp, } int bsms_attribute_index = cp->bootstrap_methods_attribute_index(cp_index); - int arg_count = cp->operand_argument_count_at(bsms_attribute_index); + int arg_count = cp->bsm_attribute_entry(bsms_attribute_index)->argument_count(); if (arg_count != 3) { // Malformed class? return false; diff --git a/src/hotspot/share/oops/constantPool.cpp b/src/hotspot/share/oops/constantPool.cpp index 5b0ee298ddc5..3223c56628ef 100644 --- a/src/hotspot/share/oops/constantPool.cpp +++ b/src/hotspot/share/oops/constantPool.cpp @@ -1937,18 +1937,20 @@ int ConstantPool::find_matching_entry(int pattern_i, // Compare this constant pool's bootstrap specifier at idx1 to the constant pool // cp2's bootstrap specifier at idx2. bool ConstantPool::compare_operand_to(int idx1, const constantPoolHandle& cp2, int idx2) { - int k1 = operand_bootstrap_method_ref_index_at(idx1); - int k2 = cp2->operand_bootstrap_method_ref_index_at(idx2); + BSMAttributeEntry* e1 = bsm_attribute_entry(idx1); + BSMAttributeEntry* e2 = cp2->bsm_attribute_entry(idx2); + int k1 = e1->bootstrap_method_index(); + int k2 = e2->bootstrap_method_index(); bool match = compare_entry_to(k1, cp2, k2); if (!match) { return false; } - int argc = operand_argument_count_at(idx1); - if (argc == cp2->operand_argument_count_at(idx2)) { + int argc = e1->argument_count(); + if (argc == e2->argument_count()) { for (int j = 0; j < argc; j++) { - k1 = operand_argument_index_at(idx1, j); - k2 = cp2->operand_argument_index_at(idx2, j); + k1 = e1->argument_index(j); + k2 = e2->argument_index(j); match = compare_entry_to(k1, cp2, k2); if (!match) { return false; diff --git a/src/hotspot/share/oops/constantPool.hpp b/src/hotspot/share/oops/constantPool.hpp index cc9491d7935d..be4a7a474d44 100644 --- a/src/hotspot/share/oops/constantPool.hpp +++ b/src/hotspot/share/oops/constantPool.hpp @@ -77,6 +77,43 @@ class CPKlassSlot { } }; +class BSMAttributeEntry { + friend class ConstantPool; + u2 _bootstrap_method_index; + u2 _argument_count; + + // The argument indexes are stored right after the object, in a contiguous array. + // [ bsmi_0 argc_0 arg_00 arg_01 ... arg_0N bsmi_1 argc_1 arg_10 ... arg_1N ... ] + // So in order to find the argument array, jump over ourselves. + const u2* argument_indexes() const { + return reinterpret_cast(this + 1); + } + u2* argument_indexes() { + return reinterpret_cast(this + 1); + } + // These are overlays on top of the operands array. Do not construct. + BSMAttributeEntry() = delete; + +public: + // Offsets for SA + enum { + _bsmi_offset = 0, + _argc_offset = 1, + _argv_offset = 2 + }; + + int bootstrap_method_index() const { + return _bootstrap_method_index; + } + int argument_count() const { + return _argument_count; + } + int argument_index(int n) const { + assert(checked_cast(n) < _argument_count, "oob"); + return argument_indexes()[n]; + } +}; + class ConstantPool : public Metadata { friend class VMStructs; friend class JVMCIVMStructs; @@ -519,10 +556,6 @@ class ConstantPool : public Metadata { assert(tag_at(cp_index).has_bootstrap(), "Corrupted constant pool"); return extract_low_short_from_int(*int_at_addr(cp_index)); } - int bootstrap_operand_base(int cp_index) { - int bsms_attribute_index = bootstrap_methods_attribute_index(cp_index); - return operand_offset_at(operands(), bsms_attribute_index); - } // The first part of the operands array consists of an index into the second part. // Extract a 32-bit index value from the first part. static int operand_offset_at(Array* operands, int bsms_attribute_index) { @@ -560,47 +593,26 @@ class ConstantPool : public Metadata { else return operand_offset_at(operands, nextidx); } - int bootstrap_operand_limit(int cp_index) { - int bsms_attribute_index = bootstrap_methods_attribute_index(cp_index); - return operand_limit_at(operands(), bsms_attribute_index); - } #endif //ASSERT - // Layout of InvokeDynamic and Dynamic bootstrap method specifier - // data in second part of operands array. This encodes one record in - // the BootstrapMethods attribute. The whole specifier also includes - // the name and type information from the main constant pool entry. - enum { - _indy_bsm_offset = 0, // CONSTANT_MethodHandle bsm - _indy_argc_offset = 1, // u2 argc - _indy_argv_offset = 2 // u2 argv[argc] - }; - // These functions are used in RedefineClasses for CP merge - int operand_offset_at(int bsms_attribute_index) { assert(0 <= bsms_attribute_index && bsms_attribute_index < operand_array_length(operands()), "Corrupted CP operands"); return operand_offset_at(operands(), bsms_attribute_index); } - u2 operand_bootstrap_method_ref_index_at(int bsms_attribute_index) { - int offset = operand_offset_at(bsms_attribute_index); - return operands()->at(offset + _indy_bsm_offset); - } - u2 operand_argument_count_at(int bsms_attribute_index) { - int offset = operand_offset_at(bsms_attribute_index); - u2 argc = operands()->at(offset + _indy_argc_offset); - return argc; - } - u2 operand_argument_index_at(int bsms_attribute_index, int j) { + + BSMAttributeEntry* bsm_attribute_entry(int bsms_attribute_index) { int offset = operand_offset_at(bsms_attribute_index); - return operands()->at(offset + _indy_argv_offset + j); + return reinterpret_cast(operands()->adr_at(offset)); } + int operand_next_offset_at(int bsms_attribute_index) { - int offset = operand_offset_at(bsms_attribute_index) + _indy_argv_offset - + operand_argument_count_at(bsms_attribute_index); - return offset; + BSMAttributeEntry* bsme = bsm_attribute_entry(bsms_attribute_index); + u2* argv_start = bsme->argument_indexes(); + int offset = argv_start - operands()->data(); + return offset + bsme->argument_count(); } // Compare a bootstrap specifier data in the operands arrays bool compare_operand_to(int bsms_attribute_index1, const constantPoolHandle& cp2, @@ -617,23 +629,19 @@ class ConstantPool : public Metadata { u2 bootstrap_method_ref_index_at(int cp_index) { assert(tag_at(cp_index).has_bootstrap(), "Corrupted constant pool"); - int op_base = bootstrap_operand_base(cp_index); - return operands()->at(op_base + _indy_bsm_offset); + int bsmai = bootstrap_methods_attribute_index(cp_index); + return bsm_attribute_entry(bsmai)->bootstrap_method_index(); } u2 bootstrap_argument_count_at(int cp_index) { assert(tag_at(cp_index).has_bootstrap(), "Corrupted constant pool"); - int op_base = bootstrap_operand_base(cp_index); - u2 argc = operands()->at(op_base + _indy_argc_offset); - DEBUG_ONLY(int end_offset = op_base + _indy_argv_offset + argc; - int next_offset = bootstrap_operand_limit(cp_index)); - assert(end_offset == next_offset, "matched ending"); - return argc; + int bsmai = bootstrap_methods_attribute_index(cp_index); + return bsm_attribute_entry(bsmai)->argument_count(); } u2 bootstrap_argument_index_at(int cp_index, int j) { - int op_base = bootstrap_operand_base(cp_index); - DEBUG_ONLY(int argc = operands()->at(op_base + _indy_argc_offset)); - assert((uint)j < (uint)argc, "oob"); - return operands()->at(op_base + _indy_argv_offset + j); + int bsmai = bootstrap_methods_attribute_index(cp_index); + BSMAttributeEntry* bsme = bsm_attribute_entry(bsmai); + assert((uint)j < (uint)bsme->argument_count(), "oob"); + return bsm_attribute_entry(bsmai)->argument_index(j); } // The following methods (name/signature/klass_ref_at, klass_ref_at_noresolve, diff --git a/src/hotspot/share/prims/jvmtiClassFileReconstituter.cpp b/src/hotspot/share/prims/jvmtiClassFileReconstituter.cpp index 381ad7d12fbe..a441d405f8d7 100644 --- a/src/hotspot/share/prims/jvmtiClassFileReconstituter.cpp +++ b/src/hotspot/share/prims/jvmtiClassFileReconstituter.cpp @@ -396,7 +396,7 @@ void JvmtiClassFileReconstituter::write_bootstrapmethod_attribute() { // calculate length of attribute u4 length = sizeof(u2); // num_bootstrap_methods for (int n = 0; n < num_bootstrap_methods; n++) { - u2 num_bootstrap_arguments = cpool()->operand_argument_count_at(n); + u2 num_bootstrap_arguments = cpool()->bsm_attribute_entry(n)->argument_count(); length += sizeof(u2); // bootstrap_method_ref length += sizeof(u2); // num_bootstrap_arguments length += (u4)sizeof(u2) * num_bootstrap_arguments; // bootstrap_arguments[num_bootstrap_arguments] @@ -406,12 +406,12 @@ void JvmtiClassFileReconstituter::write_bootstrapmethod_attribute() { // write attribute write_u2(checked_cast(num_bootstrap_methods)); for (int n = 0; n < num_bootstrap_methods; n++) { - u2 bootstrap_method_ref = cpool()->operand_bootstrap_method_ref_index_at(n); - u2 num_bootstrap_arguments = cpool()->operand_argument_count_at(n); - write_u2(bootstrap_method_ref); + BSMAttributeEntry* bsme = cpool()->bsm_attribute_entry(n); + u2 num_bootstrap_arguments = bsme->argument_count(); + write_u2(bsme->bootstrap_method_index()); write_u2(num_bootstrap_arguments); for (int arg = 0; arg < num_bootstrap_arguments; arg++) { - u2 bootstrap_argument = cpool()->operand_argument_index_at(n, arg); + u2 bootstrap_argument = bsme->argument_index(arg); write_u2(bootstrap_argument); } } diff --git a/src/hotspot/share/prims/jvmtiRedefineClasses.cpp b/src/hotspot/share/prims/jvmtiRedefineClasses.cpp index d5b144ec28e0..5094bab01a98 100644 --- a/src/hotspot/share/prims/jvmtiRedefineClasses.cpp +++ b/src/hotspot/share/prims/jvmtiRedefineClasses.cpp @@ -662,10 +662,11 @@ u2 VM_RedefineClasses::find_or_append_indirect_entry(const constantPoolHandle& s // Append a bootstrap specifier into the merge_cp operands that is semantically equal // to the scratch_cp operands bootstrap specifier passed by the old_bs_i index. // Recursively append new merge_cp entries referenced by the new bootstrap specifier. -void VM_RedefineClasses::append_operand(const constantPoolHandle& scratch_cp, int old_bs_i, +void VM_RedefineClasses::append_operand(const constantPoolHandle& scratch_cp, const int old_bs_i, constantPoolHandle *merge_cp_p, int *merge_cp_length_p) { - u2 old_ref_i = scratch_cp->operand_bootstrap_method_ref_index_at(old_bs_i); + BSMAttributeEntry* old_bsme = scratch_cp->bsm_attribute_entry(old_bs_i); + u2 old_ref_i = old_bsme->bootstrap_method_index(); u2 new_ref_i = find_or_append_indirect_entry(scratch_cp, old_ref_i, merge_cp_p, merge_cp_length_p); if (new_ref_i != old_ref_i) { @@ -679,14 +680,14 @@ void VM_RedefineClasses::append_operand(const constantPoolHandle& scratch_cp, in // However, the operand_offset_at(0) was set in the extend_operands() call. int new_base = (new_bs_i == 0) ? (*merge_cp_p)->operand_offset_at(0) : (*merge_cp_p)->operand_next_offset_at(new_bs_i - 1); - u2 argc = scratch_cp->operand_argument_count_at(old_bs_i); + u2 argc = old_bsme->argument_count(); ConstantPool::operand_offset_at_put(merge_ops, _operands_cur_length, new_base); merge_ops->at_put(new_base++, new_ref_i); merge_ops->at_put(new_base++, argc); for (int i = 0; i < argc; i++) { - u2 old_arg_ref_i = scratch_cp->operand_argument_index_at(old_bs_i, i); + u2 old_arg_ref_i = old_bsme->argument_index(i); u2 new_arg_ref_i = find_or_append_indirect_entry(scratch_cp, old_arg_ref_i, merge_cp_p, merge_cp_length_p); merge_ops->at_put(new_base++, new_arg_ref_i); diff --git a/src/hotspot/share/runtime/vmStructs.cpp b/src/hotspot/share/runtime/vmStructs.cpp index 879a2324e549..d355e2c08926 100644 --- a/src/hotspot/share/runtime/vmStructs.cpp +++ b/src/hotspot/share/runtime/vmStructs.cpp @@ -1502,13 +1502,13 @@ \ declare_constant(Symbol::max_symbol_length) \ \ - /***********************************************/ \ - /* ConstantPool* layout enum for InvokeDynamic */ \ - /***********************************************/ \ + /******************************************************/ \ + /* BSMAttributeEntry* - layout enum for InvokeDynamic */ \ + /******************************************************/ \ \ - declare_constant(ConstantPool::_indy_bsm_offset) \ - declare_constant(ConstantPool::_indy_argc_offset) \ - declare_constant(ConstantPool::_indy_argv_offset) \ + declare_constant(BSMAttributeEntry::_bsmi_offset) \ + declare_constant(BSMAttributeEntry::_argc_offset) \ + declare_constant(BSMAttributeEntry::_argv_offset) \ \ /***************************************/ \ /* JavaThreadStatus enum */ \ diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/ConstantPool.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/ConstantPool.java index c1df1df94295..563d9d3ac4a0 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/ConstantPool.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/ConstantPool.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 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 @@ -100,9 +100,9 @@ private static synchronized void initialize(TypeDataBase db) throws WrongTypeExc headerSize = type.getSize(); elementSize = 0; // fetch constants: - INDY_BSM_OFFSET = db.lookupIntConstant("ConstantPool::_indy_bsm_offset").intValue(); - INDY_ARGC_OFFSET = db.lookupIntConstant("ConstantPool::_indy_argc_offset").intValue(); - INDY_ARGV_OFFSET = db.lookupIntConstant("ConstantPool::_indy_argv_offset").intValue(); + INDY_BSM_OFFSET = db.lookupIntConstant("BSMAttributeEntry::_bsmi_offset").intValue(); + INDY_ARGC_OFFSET = db.lookupIntConstant("BSMAttributeEntry::_argc_offset").intValue(); + INDY_ARGV_OFFSET = db.lookupIntConstant("BSMAttributeEntry::_argv_offset").intValue(); } public ConstantPool(Address addr) { From 6fc6063d1bf9fb7e7cfca8d1185ed667a7218acb Mon Sep 17 00:00:00 2001 From: Goetz Lindenmaier Date: Mon, 29 Jun 2026 20:32:55 +0000 Subject: [PATCH 36/86] 8368073: PKCS11 HKDF can't use byte array IKM in FIPS mode Backport-of: 3183a13f666ff38c03c0628e139998803be8a719 --- .../classes/sun/security/pkcs11/P11HKDF.java | 103 ++++++++++++++---- .../pkcs11/tls/tls12/FipsModeTLS12.java | 52 +++++---- .../jdk/sun/security/pkcs11/tls/tls12/nss.cfg | 8 ++ 3 files changed, 113 insertions(+), 50 deletions(-) diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11HKDF.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11HKDF.java index e8bef222d88d..b93a8d9b98af 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11HKDF.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11HKDF.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2025, Red Hat, Inc. + * 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 @@ -167,32 +168,45 @@ private T derive(String alg, AlgorithmParameterSpec derivationSpec, checkDerivedKeyType(ki, alg); P11KeyGenerator.checkKeySize(ki.keyGenMech, outLen * 8, token); - P11Key p11BaseKey = convertKey(baseKey, (isExtract ? "IKM" : "PRK") + - " could not be converted to a token key for HKDF derivation."); - + long baseKeyID; + P11Key p11BaseKey = null; + try { + p11BaseKey = convertKey(baseKey, (isExtract ? "IKM" : "PRK") + + " could not be converted to a token key for HKDF derivation."); + baseKeyID = p11BaseKey.getKeyID(); + } catch (ProviderException pe) { + if (p11BaseKey != null) { + throw pe; + } + // special handling for FIPS mode when key cannot be imported + if (isExtract) { + baseKeyID = convertKeyToData(baseKey, pe); + } else { + throw pe; + } + } + Session session = null; long saltType = CKF_HKDF_SALT_NULL; byte[] saltBytes = null; P11Key p11SaltKey = null; - if (salt instanceof SecretKeySpec) { - saltType = CKF_HKDF_SALT_DATA; - saltBytes = salt.getEncoded(); - } else if (salt != EMPTY_KEY) { - // consolidateKeyMaterial returns a salt from the token. - saltType = CKF_HKDF_SALT_KEY; - p11SaltKey = (P11Key.P11SecretKey) salt; - assert p11SaltKey.token == token : "salt must be from the same " + - "token as service."; - } - - long derivedKeyClass = isData ? CKO_DATA : CKO_SECRET_KEY; - CK_ATTRIBUTE[] attrs = new CK_ATTRIBUTE[] { - new CK_ATTRIBUTE(CKA_CLASS, derivedKeyClass), - new CK_ATTRIBUTE(CKA_KEY_TYPE, ki.keyType), - new CK_ATTRIBUTE(CKA_VALUE_LEN, outLen) - }; - Session session = null; - long baseKeyID = p11BaseKey.getKeyID(); try { + if (salt instanceof SecretKeySpec) { + saltType = CKF_HKDF_SALT_DATA; + saltBytes = salt.getEncoded(); + } else if (salt != EMPTY_KEY) { + // consolidateKeyMaterial returns a salt from the token. + saltType = CKF_HKDF_SALT_KEY; + p11SaltKey = (P11Key.P11SecretKey) salt; + assert p11SaltKey.token == token : "salt must be from the same " + + "token as service."; + } + + long derivedKeyClass = isData ? CKO_DATA : CKO_SECRET_KEY; + CK_ATTRIBUTE[] attrs = new CK_ATTRIBUTE[] { + new CK_ATTRIBUTE(CKA_CLASS, derivedKeyClass), + new CK_ATTRIBUTE(CKA_KEY_TYPE, ki.keyType), + new CK_ATTRIBUTE(CKA_VALUE_LEN, outLen) + }; session = token.getOpSession(); CK_HKDF_PARAMS params = new CK_HKDF_PARAMS(isExtract, isExpand, svcKi.hmacMech, saltType, saltBytes, p11SaltKey != null ? @@ -230,11 +244,54 @@ private T derive(String alg, AlgorithmParameterSpec derivationSpec, if (p11SaltKey != null) { p11SaltKey.releaseKeyID(); } - p11BaseKey.releaseKeyID(); + if (p11BaseKey != null) { + p11BaseKey.releaseKeyID(); + } else { + destroyDataObject(baseKeyID); + } token.releaseSession(session); } } + private void destroyDataObject(long baseKeyID) { + try { + Session session = token.getObjSession(); + try { + token.p11.C_DestroyObject(session.id(), baseKeyID); + } finally { + token.releaseSession(session); + } + } catch (PKCS11Exception e) { + throw new ProviderException("Failed to destroy IKM data object.", e); + } + } + + private long convertKeyToData(SecretKey key, ProviderException pe) { + if (!"RAW".equalsIgnoreCase(key.getFormat())) { + throw pe; + } + byte[] keyBytes = key.getEncoded(); + if (keyBytes == null) { + throw pe; + } + CK_ATTRIBUTE[] inputAttributes = new CK_ATTRIBUTE[]{ + new CK_ATTRIBUTE(CKA_CLASS, CKO_DATA), + new CK_ATTRIBUTE(CKA_VALUE, keyBytes), + }; + try { + Session session = token.getObjSession(); + try { + return token.p11.C_CreateObject(session.id(), inputAttributes); + } finally { + token.releaseSession(session); + } + } catch (PKCS11Exception e) { + throw new ProviderException("Failed to create IKM data object.", e); + } finally { + Arrays.fill(keyBytes, (byte)0); + } + } + private static boolean canDeriveKeyInfoType(long t) { return (t == CKK_DES || t == CKK_DES3 || t == CKK_AES || t == CKK_RC4 || t == CKK_BLOWFISH || t == CKK_CHACHA20 || diff --git a/test/jdk/sun/security/pkcs11/tls/tls12/FipsModeTLS12.java b/test/jdk/sun/security/pkcs11/tls/tls12/FipsModeTLS12.java index fd5c99dd700d..523ccf812094 100644 --- a/test/jdk/sun/security/pkcs11/tls/tls12/FipsModeTLS12.java +++ b/test/jdk/sun/security/pkcs11/tls/tls12/FipsModeTLS12.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2019, Red Hat, Inc. - * 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 @@ -24,13 +24,16 @@ /* * @test - * @bug 8029661 8325164 - * @summary Test TLS 1.2 + * @bug 8029661 8325164 8368073 + * @summary Test TLS 1.2 and TLS 1.3 * @modules java.base/sun.security.internal.spec * java.base/sun.security.util * java.base/com.sun.crypto.provider * @library /test/lib ../.. - * @run main/othervm/timeout=120 -Djdk.tls.useExtendedMasterSecret=false FipsModeTLS12 + * @run main/othervm/timeout=120 -Djdk.tls.client.protocols=TLSv1.2 + * -Djdk.tls.useExtendedMasterSecret=false FipsModeTLS12 + * @comment SunPKCS11 does not support (TLS1.2) SunTlsExtendedMasterSecret yet + * @run main/othervm/timeout=120 -Djdk.tls.client.protocols=TLSv1.3 FipsModeTLS12 */ import java.io.File; @@ -51,6 +54,7 @@ import java.util.List; import javax.crypto.Cipher; +import javax.crypto.KDF; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; @@ -83,9 +87,6 @@ public final class FipsModeTLS12 extends SecmodTest { private static PublicKey publicKey; public static void main(String[] args) throws Exception { - // Re-enable TLS_RSA_* since test depends on it. - SecurityUtils.removeFromDisabledTlsAlgs("TLS_RSA_*"); - try { initialize(); } catch (Exception e) { @@ -115,11 +116,16 @@ private static boolean shouldRun() { return false; } try { - KeyGenerator.getInstance("SunTls12MasterSecret", - sunPKCS11NSSProvider); - KeyGenerator.getInstance( - "SunTls12RsaPremasterSecret", sunPKCS11NSSProvider); - KeyGenerator.getInstance("SunTls12Prf", sunPKCS11NSSProvider); + String proto = System.getProperty("jdk.tls.client.protocols"); + if ("TLSv1.3".equals(proto)) { + KDF.getInstance("HKDF-SHA256", sunPKCS11NSSProvider); + } else { + KeyGenerator.getInstance("SunTls12MasterSecret", + sunPKCS11NSSProvider); + KeyGenerator.getInstance( + "SunTls12RsaPremasterSecret", sunPKCS11NSSProvider); + KeyGenerator.getInstance("SunTls12Prf", sunPKCS11NSSProvider); + } } catch (NoSuchAlgorithmException e) { return false; } @@ -384,21 +390,9 @@ private static void runDelegatedTasks(SSLEngineResult result, private static SSLEngine[][] getSSLEnginesToTest() throws Exception { SSLEngine[][] enginesToTest = new SSLEngine[2][2]; - // TLS_RSA_WITH_AES_128_GCM_SHA256 ciphersuite is available but - // must not be chosen for the TLS connection if not supported. - // See JDK-8222937. - String[][] preferredSuites = new String[][]{ new String[] { - "TLS_RSA_WITH_AES_128_GCM_SHA256", - "TLS_RSA_WITH_AES_128_CBC_SHA256" - }, new String[] { - "TLS_RSA_WITH_AES_128_GCM_SHA256", - "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256" - }}; for (int i = 0; i < enginesToTest.length; i++) { enginesToTest[i][0] = createSSLEngine(true); enginesToTest[i][1] = createSSLEngine(false); - // All CipherSuites enabled for the client. - enginesToTest[i][1].setEnabledCipherSuites(preferredSuites[i]); } return enginesToTest; } @@ -412,23 +406,27 @@ static private SSLEngine createSSLEngine(boolean client) TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX", "SunJSSE"); tmf.init(ts); - SSLContext sslCtx = SSLContext.getInstance("TLSv1.2", "SunJSSE"); + SSLContext sslCtx = SSLContext.getInstance("TLS", "SunJSSE"); sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); ssle = sslCtx.createSSLEngine("localhost", 443); ssle.setUseClientMode(client); SSLParameters sslParameters = ssle.getSSLParameters(); // verify that FFDHE named groups are available - boolean ffdheAvailable = Arrays.stream(sslParameters.getNamedGroups()) + String[] namedGroups = sslParameters.getNamedGroups(); + boolean ffdheAvailable = Arrays.stream(namedGroups) .anyMatch(ng -> ng.startsWith("ffdhe")); if (!ffdheAvailable) { throw new RuntimeException("No FFDHE named groups available"); } // verify that ECDHE named groups are available - boolean ecdheAvailable = Arrays.stream(sslParameters.getNamedGroups()) + boolean ecdheAvailable = Arrays.stream(namedGroups) .anyMatch(ng -> ng.startsWith("secp")); if (!ecdheAvailable) { throw new RuntimeException("No ECDHE named groups available"); } + // remove XDH named groups - not available in PKCS11 + namedGroups = Arrays.stream(namedGroups).filter(s-> !s.startsWith("x")).toArray(String[]::new); + sslParameters.setNamedGroups(namedGroups); ssle.setSSLParameters(sslParameters); return ssle; diff --git a/test/jdk/sun/security/pkcs11/tls/tls12/nss.cfg b/test/jdk/sun/security/pkcs11/tls/tls12/nss.cfg index 349c783af083..1f28ddf9ac89 100644 --- a/test/jdk/sun/security/pkcs11/tls/tls12/nss.cfg +++ b/test/jdk/sun/security/pkcs11/tls/tls12/nss.cfg @@ -7,6 +7,14 @@ nssLibraryDirectory = ${pkcs11test.nss.libdir} nssModule = fips +# NSS-FIPS needs sensitive=true for key extraction. +# TLS 1.3 needs CKA_SIGN to sign the Finished message. + +attributes(*,CKO_SECRET_KEY,CKK_GENERIC_SECRET) = { + CKA_SIGN = true + CKA_SENSITIVE=true +} + # NSS needs CKA_NETSCAPE_DB for DSA and DH private keys # just put an arbitrary value in there to make it happy From eb4b36049853cc78d5eb4bb149be7e27ca973a88 Mon Sep 17 00:00:00 2001 From: Goetz Lindenmaier Date: Mon, 29 Jun 2026 20:43:24 +0000 Subject: [PATCH 37/86] 8378211: Test ChangedJarFile.java failed: missing "timestamp has changed" Backport-of: 921da0a9734d9fae2e7b0e129d2cc6949ad0b5a6 --- .../jtreg/runtime/cds/appcds/aotCache/ChangedJarFile.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/ChangedJarFile.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/ChangedJarFile.java index a717b2673475..e86defdf816b 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/ChangedJarFile.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/ChangedJarFile.java @@ -59,7 +59,7 @@ public static void main(String[] args) throws Exception { tester.productionRun(new String[] {"-XX:AOTMode=auto", "-Xlog:aot"}, new String[] {"jarHasChanged"}); out.shouldMatch("This file is not the one used while building the " + - "AOT cache: '.*app.jar', timestamp has changed, size has changed"); + "AOT cache: '.*app.jar',.* size has changed"); } static class Tester extends CDSAppTester { From 2dd96cd4af66fe96326c04202fff5deaa1272884 Mon Sep 17 00:00:00 2001 From: Goetz Lindenmaier Date: Mon, 29 Jun 2026 20:52:04 +0000 Subject: [PATCH 38/86] 8372615: Many container tests fail when running rootless on cgroup v1 Backport-of: f1a4d1bfde652cf758117b93bbd02ae8248e805e --- .../containers/docker/DockerBasicTest.java | 8 +++--- .../jtreg/containers/docker/ShareTmpDir.java | 8 +++--- .../containers/docker/TestCPUAwareness.java | 7 +++--- .../jtreg/containers/docker/TestCPUSets.java | 10 +++----- .../containers/docker/TestContainerInfo.java | 8 +++--- .../containers/docker/TestJFREvents.java | 5 ++-- .../docker/TestJFRNetworkEvents.java | 8 +++--- .../containers/docker/TestJFRWithJMX.java | 7 +++--- .../jtreg/containers/docker/TestJcmd.java | 3 ++- .../docker/TestJcmdWithSideCar.java | 8 +++--- .../containers/docker/TestLimitsUpdating.java | 9 +++---- .../docker/TestMemoryAwareness.java | 5 +--- .../docker/TestMemoryInvisibleParent.java | 5 +--- .../docker/TestMemoryWithCgroupV1.java | 7 +++--- .../docker/TestMemoryWithSubgroups.java | 5 +--- .../jtreg/containers/docker/TestMisc.java | 16 +++--------- .../jtreg/containers/docker/TestPids.java | 7 +++--- .../platform/docker/TestDockerBasic.java | 9 +++---- .../platform/docker/TestDockerCpuMetrics.java | 7 +++--- .../docker/TestDockerMemoryMetrics.java | 5 ++-- .../TestDockerMemoryMetricsSubgroup.java | 5 +--- .../docker/TestGetFreeSwapSpaceSize.java | 8 +++--- .../platform/docker/TestLimitsUpdating.java | 6 ++--- .../platform/docker/TestPidsLimit.java | 7 +++--- .../platform/docker/TestSystemMetrics.java | 5 +--- .../docker/TestUseContainerSupport.java | 5 +--- .../containers/docker/DockerTestUtils.java | 25 +++++++++++++------ 27 files changed, 83 insertions(+), 125 deletions(-) diff --git a/test/hotspot/jtreg/containers/docker/DockerBasicTest.java b/test/hotspot/jtreg/containers/docker/DockerBasicTest.java index e908f2a5bf32..403f46f6ab40 100644 --- a/test/hotspot/jtreg/containers/docker/DockerBasicTest.java +++ b/test/hotspot/jtreg/containers/docker/DockerBasicTest.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 @@ -29,6 +29,7 @@ * @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 HelloDocker @@ -45,10 +46,7 @@ public class DockerBasicTest { private static final String imageNameAndTag = Common.imageName("basic"); public static void main(String[] args) throws Exception { - if (!DockerTestUtils.canTestDocker()) { - return; - } - + DockerTestUtils.checkCanTestDocker(); DockerTestUtils.buildJdkContainerImage(imageNameAndTag); try { diff --git a/test/hotspot/jtreg/containers/docker/ShareTmpDir.java b/test/hotspot/jtreg/containers/docker/ShareTmpDir.java index 48876ca37fe2..b7f807d76a30 100644 --- a/test/hotspot/jtreg/containers/docker/ShareTmpDir.java +++ b/test/hotspot/jtreg/containers/docker/ShareTmpDir.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 @@ -30,6 +30,7 @@ * @requires container.support * @requires !vm.asan * @library /test/lib + * @modules java.base/jdk.internal.platform * @build WaitForFlagFile * @run driver ShareTmpDir */ @@ -50,10 +51,7 @@ public class ShareTmpDir { private static final String imageName = Common.imageName("sharetmpdir"); public static void main(String[] args) throws Exception { - if (!DockerTestUtils.canTestDocker()) { - return; - } - + DockerTestUtils.checkCanTestDocker(); DockerTestUtils.buildJdkContainerImage(imageName); try { diff --git a/test/hotspot/jtreg/containers/docker/TestCPUAwareness.java b/test/hotspot/jtreg/containers/docker/TestCPUAwareness.java index dc73dd1b5445..6b0a536e4d46 100644 --- a/test/hotspot/jtreg/containers/docker/TestCPUAwareness.java +++ b/test/hotspot/jtreg/containers/docker/TestCPUAwareness.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 @@ -48,9 +48,8 @@ public class TestCPUAwareness { private static final int availableCPUs = Runtime.getRuntime().availableProcessors(); public static void main(String[] args) throws Exception { - if (!DockerTestUtils.canTestDocker()) { - return; - } + DockerTestUtils.checkCanTestDocker(); + DockerTestUtils.checkCanUseResourceLimits(); System.out.println("Test Environment: detected availableCPUs = " + availableCPUs); DockerTestUtils.buildJdkContainerImage(imageName); diff --git a/test/hotspot/jtreg/containers/docker/TestCPUSets.java b/test/hotspot/jtreg/containers/docker/TestCPUSets.java index 7894172e4015..001914a66866 100644 --- a/test/hotspot/jtreg/containers/docker/TestCPUSets.java +++ b/test/hotspot/jtreg/containers/docker/TestCPUSets.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 @@ -31,6 +31,7 @@ * @requires (os.arch != "s390x") * @library /test/lib * @modules java.base/jdk.internal.misc + * java.base/jdk.internal.platform * java.management * jdk.jartool/sun.tools.jar * @build AttemptOOM jdk.test.whitebox.WhiteBox PrintContainerInfo @@ -52,11 +53,8 @@ public class TestCPUSets { private static final String imageName = Common.imageName("cpusets"); public static void main(String[] args) throws Exception { - if (!DockerTestUtils.canTestDocker()) { - return; - } - - + DockerTestUtils.checkCanTestDocker(); + DockerTestUtils.checkCanUseResourceLimits(); Common.prepareWhiteBox(); DockerTestUtils.buildJdkContainerImage(imageName); diff --git a/test/hotspot/jtreg/containers/docker/TestContainerInfo.java b/test/hotspot/jtreg/containers/docker/TestContainerInfo.java index b9b6fb65b756..a5579aa9528f 100644 --- a/test/hotspot/jtreg/containers/docker/TestContainerInfo.java +++ b/test/hotspot/jtreg/containers/docker/TestContainerInfo.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. * Copyright (c) 2024, Red Hat, Inc. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -31,6 +31,7 @@ * @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 @@ -49,10 +50,7 @@ public class TestContainerInfo { private static final String imageName = Common.imageName("container-info"); public static void main(String[] args) throws Exception { - if (!DockerTestUtils.canTestDocker()) { - return; - } - + DockerTestUtils.checkCanTestDocker(); Common.prepareWhiteBox(); DockerTestUtils.buildJdkContainerImage(imageName); diff --git a/test/hotspot/jtreg/containers/docker/TestJFREvents.java b/test/hotspot/jtreg/containers/docker/TestJFREvents.java index c8905f4e49ce..19f82acc5028 100644 --- a/test/hotspot/jtreg/containers/docker/TestJFREvents.java +++ b/test/hotspot/jtreg/containers/docker/TestJFREvents.java @@ -34,6 +34,7 @@ * @modules java.base/jdk.internal.platform * @library /test/lib * @modules java.base/jdk.internal.misc + * java.base/jdk.internal.platform * java.management * jdk.jartool/sun.tools.jar * @build JfrReporter @@ -59,9 +60,7 @@ public class TestJFREvents { public static void main(String[] args) throws Exception { System.out.println("Test Environment: detected availableCPUs = " + availableCPUs); - if (!DockerTestUtils.canTestDocker()) { - return; - } + DockerTestUtils.checkCanTestDocker(); // If cgroups is not configured, report success. Metrics metrics = Metrics.systemMetrics(); diff --git a/test/hotspot/jtreg/containers/docker/TestJFRNetworkEvents.java b/test/hotspot/jtreg/containers/docker/TestJFRNetworkEvents.java index c0dde368d1e0..2c7120c577c5 100644 --- a/test/hotspot/jtreg/containers/docker/TestJFRNetworkEvents.java +++ b/test/hotspot/jtreg/containers/docker/TestJFRNetworkEvents.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 @@ -31,6 +31,7 @@ * @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 JfrNetwork @@ -48,10 +49,7 @@ public class TestJFRNetworkEvents { public static void main(String[] args) throws Exception { System.out.println("Test Environment: detected availableCPUs = " + availableCPUs); - if (!DockerTestUtils.canTestDocker()) { - return; - } - + DockerTestUtils.checkCanTestDocker(); DockerTestUtils.buildJdkContainerImage(imageName); try { diff --git a/test/hotspot/jtreg/containers/docker/TestJFRWithJMX.java b/test/hotspot/jtreg/containers/docker/TestJFRWithJMX.java index efe1fa4ffbcc..7c26af9b27a4 100644 --- a/test/hotspot/jtreg/containers/docker/TestJFRWithJMX.java +++ b/test/hotspot/jtreg/containers/docker/TestJFRWithJMX.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2024, 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 @@ -29,6 +29,7 @@ * @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 EventProducer @@ -73,9 +74,7 @@ public class TestJFRWithJMX { static final AtomicReference ipAddr = new AtomicReference(); public static void main(String[] args) throws Exception { - if (!DockerTestUtils.canTestDocker()) { - throw new SkippedException("Docker is not supported on this host"); - } + DockerTestUtils.checkCanTestDocker(); if (DockerTestUtils.isPodman() & !Platform.isRoot()) { throw new SkippedException("test cannot be run under rootless podman configuration"); diff --git a/test/hotspot/jtreg/containers/docker/TestJcmd.java b/test/hotspot/jtreg/containers/docker/TestJcmd.java index 3f5afd31801f..5819afe8d38d 100644 --- a/test/hotspot/jtreg/containers/docker/TestJcmd.java +++ b/test/hotspot/jtreg/containers/docker/TestJcmd.java @@ -29,6 +29,7 @@ * @requires container.support * @requires vm.flagless * @modules java.base/jdk.internal.misc + * java.base/jdk.internal.platform * java.management * jdk.jartool/sun.tools.jar * @library /test/lib @@ -62,7 +63,7 @@ public class TestJcmd { public static void main(String[] args) throws Exception { - DockerTestUtils.canTestDocker(); + DockerTestUtils.checkCanTestDocker(); // podman versions below 3.3.1 hava a bug where cross-container testing with correct // permissions fails. See JDK-8273216 diff --git a/test/hotspot/jtreg/containers/docker/TestJcmdWithSideCar.java b/test/hotspot/jtreg/containers/docker/TestJcmdWithSideCar.java index 91a07012f003..28a7a20553f8 100644 --- a/test/hotspot/jtreg/containers/docker/TestJcmdWithSideCar.java +++ b/test/hotspot/jtreg/containers/docker/TestJcmdWithSideCar.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 @@ -33,6 +33,7 @@ * @requires vm.flagless * @requires !vm.asan * @modules java.base/jdk.internal.misc + * java.base/jdk.internal.platform * java.management * jdk.jartool/sun.tools.jar * @library /test/lib @@ -95,10 +96,7 @@ public class TestJcmdWithSideCar { private static final String NET_BIND_SERVICE = "--cap-add=NET_BIND_SERVICE"; public static void main(String[] args) throws Exception { - if (!DockerTestUtils.canTestDocker()) { - return; - } - + DockerTestUtils.checkCanTestDocker(); DockerTestUtils.buildJdkContainerImage(IMAGE_NAME); try { diff --git a/test/hotspot/jtreg/containers/docker/TestLimitsUpdating.java b/test/hotspot/jtreg/containers/docker/TestLimitsUpdating.java index e8cb54d7d7b9..f8712aaba1e8 100644 --- a/test/hotspot/jtreg/containers/docker/TestLimitsUpdating.java +++ b/test/hotspot/jtreg/containers/docker/TestLimitsUpdating.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2023, Red Hat, Inc. - * 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. * @@ -32,6 +32,7 @@ * @requires container.support * @requires !vm.asan * @library /test/lib + * @modules java.base/jdk.internal.platform * @build jdk.test.whitebox.WhiteBox LimitUpdateChecker * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar whitebox.jar jdk.test.whitebox.WhiteBox * @run driver TestLimitsUpdating @@ -54,10 +55,8 @@ public class TestLimitsUpdating { private static final String imageName = Common.imageName("limitsUpdating"); public static void main(String[] args) throws Exception { - if (!DockerTestUtils.canTestDocker()) { - return; - } - + DockerTestUtils.checkCanTestDocker(); + DockerTestUtils.checkCanUseResourceLimits(); Common.prepareWhiteBox(); DockerTestUtils.buildJdkContainerImage(imageName); diff --git a/test/hotspot/jtreg/containers/docker/TestMemoryAwareness.java b/test/hotspot/jtreg/containers/docker/TestMemoryAwareness.java index a5fb3f829135..6e56024726b6 100644 --- a/test/hotspot/jtreg/containers/docker/TestMemoryAwareness.java +++ b/test/hotspot/jtreg/containers/docker/TestMemoryAwareness.java @@ -59,10 +59,7 @@ private static String getHostSwap() { } public static void main(String[] args) throws Exception { - if (!DockerTestUtils.canTestDocker()) { - return; - } - + DockerTestUtils.checkCanTestDocker(); Common.prepareWhiteBox(); DockerTestUtils.buildJdkContainerImage(imageName); diff --git a/test/hotspot/jtreg/containers/docker/TestMemoryInvisibleParent.java b/test/hotspot/jtreg/containers/docker/TestMemoryInvisibleParent.java index 68331f26766d..4854f6631019 100644 --- a/test/hotspot/jtreg/containers/docker/TestMemoryInvisibleParent.java +++ b/test/hotspot/jtreg/containers/docker/TestMemoryInvisibleParent.java @@ -55,10 +55,7 @@ public static void main(String[] args) throws Exception { System.out.println("Cgroup not configured."); return; } - if (!DockerTestUtils.canTestDocker()) { - System.out.println("Unable to run docker tests."); - return; - } + DockerTestUtils.checkCanTestDocker(); ContainerRuntimeVersionTestUtils.checkContainerVersionSupported(); diff --git a/test/hotspot/jtreg/containers/docker/TestMemoryWithCgroupV1.java b/test/hotspot/jtreg/containers/docker/TestMemoryWithCgroupV1.java index 3340f9de03c3..1edc98035e48 100644 --- a/test/hotspot/jtreg/containers/docker/TestMemoryWithCgroupV1.java +++ b/test/hotspot/jtreg/containers/docker/TestMemoryWithCgroupV1.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2022, Tencent. 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 @@ -51,10 +52,8 @@ public static void main(String[] args) throws Exception { return; } if ("cgroupv1".equals(metrics.getProvider())) { - if (!DockerTestUtils.canTestDocker()) { - return; - } - + DockerTestUtils.checkCanTestDocker(); + DockerTestUtils.checkCanUseResourceLimits(); Common.prepareWhiteBox(); DockerTestUtils.buildJdkContainerImage(imageName); diff --git a/test/hotspot/jtreg/containers/docker/TestMemoryWithSubgroups.java b/test/hotspot/jtreg/containers/docker/TestMemoryWithSubgroups.java index 621aed5ee855..016bb5bf592f 100644 --- a/test/hotspot/jtreg/containers/docker/TestMemoryWithSubgroups.java +++ b/test/hotspot/jtreg/containers/docker/TestMemoryWithSubgroups.java @@ -52,10 +52,7 @@ public static void main(String[] args) throws Exception { System.out.println("Cgroup not configured."); return; } - if (!DockerTestUtils.canTestDocker()) { - System.out.println("Unable to run docker tests."); - return; - } + DockerTestUtils.checkCanTestDocker(); ContainerRuntimeVersionTestUtils.checkContainerVersionSupported(); diff --git a/test/hotspot/jtreg/containers/docker/TestMisc.java b/test/hotspot/jtreg/containers/docker/TestMisc.java index 400119dac9df..fca3cef55139 100644 --- a/test/hotspot/jtreg/containers/docker/TestMisc.java +++ b/test/hotspot/jtreg/containers/docker/TestMisc.java @@ -46,14 +46,10 @@ 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 { - if (!DockerTestUtils.canTestDocker()) { - return; - } - + DockerTestUtils.checkCanTestDocker(); Common.prepareWhiteBox(); DockerTestUtils.buildJdkContainerImage(imageName); @@ -102,14 +98,8 @@ private static void testPrintContainerInfo() throws Exception { // 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."); - } + // Test won't work on cgv1 rootless since resource limits don't work there. + DockerTestUtils.checkCanUseResourceLimits(); // 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'. diff --git a/test/hotspot/jtreg/containers/docker/TestPids.java b/test/hotspot/jtreg/containers/docker/TestPids.java index 07a0cbd295dc..61d0fb814073 100644 --- a/test/hotspot/jtreg/containers/docker/TestPids.java +++ b/test/hotspot/jtreg/containers/docker/TestPids.java @@ -31,6 +31,7 @@ * @requires !vm.asan * @library /test/lib * @modules java.base/jdk.internal.misc + * java.base/jdk.internal.platform * java.management * @build jdk.test.whitebox.WhiteBox PrintContainerInfo * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar whitebox.jar jdk.test.whitebox.WhiteBox @@ -54,10 +55,8 @@ public class TestPids { static final String warning_kernel_no_pids_support = "WARNING: Your kernel does not support pids limit capabilities"; public static void main(String[] args) throws Exception { - if (!DockerTestUtils.canTestDocker()) { - return; - } - + DockerTestUtils.checkCanTestDocker(); + DockerTestUtils.checkCanUseResourceLimits(); Common.prepareWhiteBox(); DockerTestUtils.buildJdkContainerImage(imageName); diff --git a/test/jdk/jdk/internal/platform/docker/TestDockerBasic.java b/test/jdk/jdk/internal/platform/docker/TestDockerBasic.java index 9a531d692ed4..bfc628a25fc8 100644 --- a/test/jdk/jdk/internal/platform/docker/TestDockerBasic.java +++ b/test/jdk/jdk/internal/platform/docker/TestDockerBasic.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2022, Red Hat, Inc. - * 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 @@ -30,6 +30,7 @@ * @requires container.support * @requires !vm.asan * @library /test/lib + * @modules java.base/jdk.internal.platform * @run main/timeout=360 TestDockerBasic */ @@ -42,10 +43,8 @@ public class TestDockerBasic { private static final String imageName = Common.imageName("javaDockerBasic"); public static void main(String[] args) throws Exception { - if (!DockerTestUtils.canTestDocker()) { - return; - } - + DockerTestUtils.checkCanTestDocker(); + DockerTestUtils.checkCanUseResourceLimits(); DockerTestUtils.buildJdkContainerImage(imageName); try { diff --git a/test/jdk/jdk/internal/platform/docker/TestDockerCpuMetrics.java b/test/jdk/jdk/internal/platform/docker/TestDockerCpuMetrics.java index ff039913b8fc..042996a353bf 100644 --- a/test/jdk/jdk/internal/platform/docker/TestDockerCpuMetrics.java +++ b/test/jdk/jdk/internal/platform/docker/TestDockerCpuMetrics.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2024, 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 @@ -46,9 +46,8 @@ public class TestDockerCpuMetrics { private static final String imageName = Common.imageName("metrics-cpu"); public static void main(String[] args) throws Exception { - if (!DockerTestUtils.canTestDocker()) { - return; - } + DockerTestUtils.checkCanTestDocker(); + DockerTestUtils.checkCanUseResourceLimits(); // These tests create a docker image and run this image with // varying docker cpu options. The arguments passed to the docker diff --git a/test/jdk/jdk/internal/platform/docker/TestDockerMemoryMetrics.java b/test/jdk/jdk/internal/platform/docker/TestDockerMemoryMetrics.java index 8d63e76c141e..12f90d655169 100644 --- a/test/jdk/jdk/internal/platform/docker/TestDockerMemoryMetrics.java +++ b/test/jdk/jdk/internal/platform/docker/TestDockerMemoryMetrics.java @@ -45,9 +45,8 @@ public class TestDockerMemoryMetrics { private static final String imageName = Common.imageName("metrics-memory"); public static void main(String[] args) throws Exception { - if (!DockerTestUtils.canTestDocker()) { - return; - } + DockerTestUtils.checkCanTestDocker(); + DockerTestUtils.checkCanUseResourceLimits(); // These tests create a docker image and run this image with // varying docker memory options. The arguments passed to the docker diff --git a/test/jdk/jdk/internal/platform/docker/TestDockerMemoryMetricsSubgroup.java b/test/jdk/jdk/internal/platform/docker/TestDockerMemoryMetricsSubgroup.java index 5e3963e50df3..15a3bebfbbd6 100644 --- a/test/jdk/jdk/internal/platform/docker/TestDockerMemoryMetricsSubgroup.java +++ b/test/jdk/jdk/internal/platform/docker/TestDockerMemoryMetricsSubgroup.java @@ -59,10 +59,7 @@ public static void main(String[] args) throws Exception { System.out.println("Cgroup not configured."); return; } - if (!DockerTestUtils.canTestDocker()) { - System.out.println("Unable to run docker tests."); - return; - } + DockerTestUtils.checkCanTestDocker(); ContainerRuntimeVersionTestUtils.checkContainerVersionSupported(); diff --git a/test/jdk/jdk/internal/platform/docker/TestGetFreeSwapSpaceSize.java b/test/jdk/jdk/internal/platform/docker/TestGetFreeSwapSpaceSize.java index 5a89c04796aa..6ab6a78281e4 100644 --- a/test/jdk/jdk/internal/platform/docker/TestGetFreeSwapSpaceSize.java +++ b/test/jdk/jdk/internal/platform/docker/TestGetFreeSwapSpaceSize.java @@ -29,9 +29,11 @@ * @requires container.support * @requires !vm.asan * @library /test/lib + * @modules java.base/jdk.internal.platform * @build GetFreeSwapSpaceSize * @run driver TestGetFreeSwapSpaceSize */ + import jdk.test.lib.containers.docker.Common; import jdk.test.lib.containers.docker.DockerRunOptions; import jdk.test.lib.containers.docker.DockerTestUtils; @@ -41,10 +43,8 @@ public class TestGetFreeSwapSpaceSize { private static final String imageName = Common.imageName("osbeanSwapSpace"); public static void main(String[] args) throws Exception { - if (!DockerTestUtils.canTestDocker()) { - return; - } - + DockerTestUtils.checkCanTestDocker(); + DockerTestUtils.checkCanUseResourceLimits(); DockerTestUtils.buildJdkContainerImage(imageName); try { diff --git a/test/jdk/jdk/internal/platform/docker/TestLimitsUpdating.java b/test/jdk/jdk/internal/platform/docker/TestLimitsUpdating.java index ee9e7f41ab0e..142206c210b5 100644 --- a/test/jdk/jdk/internal/platform/docker/TestLimitsUpdating.java +++ b/test/jdk/jdk/internal/platform/docker/TestLimitsUpdating.java @@ -55,10 +55,8 @@ public class TestLimitsUpdating { private static final String imageName = Common.imageName("limitsUpdatingJDK"); public static void main(String[] args) throws Exception { - if (!DockerTestUtils.canTestDocker()) { - return; - } - + DockerTestUtils.checkCanTestDocker(); + DockerTestUtils.checkCanUseResourceLimits(); DockerTestUtils.buildJdkContainerImage(imageName); try { diff --git a/test/jdk/jdk/internal/platform/docker/TestPidsLimit.java b/test/jdk/jdk/internal/platform/docker/TestPidsLimit.java index 04c172b13b8f..683eb28a069d 100644 --- a/test/jdk/jdk/internal/platform/docker/TestPidsLimit.java +++ b/test/jdk/jdk/internal/platform/docker/TestPidsLimit.java @@ -30,6 +30,7 @@ * @requires container.support * @requires !vm.asan * @library /test/lib + * @modules java.base/jdk.internal.platform * @build TestPidsLimit * @run driver TestPidsLimit */ @@ -49,10 +50,8 @@ public class TestPidsLimit { private static final int UNLIMITED_PIDS_DOCKER = -1; public static void main(String[] args) throws Exception { - if (!DockerTestUtils.canTestDocker()) { - return; - } - + DockerTestUtils.checkCanTestDocker(); + DockerTestUtils.checkCanUseResourceLimits(); DockerTestUtils.buildJdkContainerImage(imageName); try { diff --git a/test/jdk/jdk/internal/platform/docker/TestSystemMetrics.java b/test/jdk/jdk/internal/platform/docker/TestSystemMetrics.java index 49ec56634787..e6e78bd5cd8d 100644 --- a/test/jdk/jdk/internal/platform/docker/TestSystemMetrics.java +++ b/test/jdk/jdk/internal/platform/docker/TestSystemMetrics.java @@ -42,10 +42,7 @@ public class TestSystemMetrics { private static final String imageName = Common.imageName("metrics"); public static void main(String[] args) throws Exception { - if (!DockerTestUtils.canTestDocker()) { - return; - } - + DockerTestUtils.checkCanTestDocker(); DockerTestUtils.buildJdkContainerImage(imageName); try { diff --git a/test/jdk/jdk/internal/platform/docker/TestUseContainerSupport.java b/test/jdk/jdk/internal/platform/docker/TestUseContainerSupport.java index d8d300401a0c..e77783f395bc 100644 --- a/test/jdk/jdk/internal/platform/docker/TestUseContainerSupport.java +++ b/test/jdk/jdk/internal/platform/docker/TestUseContainerSupport.java @@ -42,10 +42,7 @@ public class TestUseContainerSupport { private static final String imageName = Common.imageName("useContainerSupport"); public static void main(String[] args) throws Exception { - if (!DockerTestUtils.canTestDocker()) { - return; - } - + DockerTestUtils.checkCanTestDocker(); DockerTestUtils.buildJdkContainerImage(imageName); try { diff --git a/test/lib/jdk/test/lib/containers/docker/DockerTestUtils.java b/test/lib/jdk/test/lib/containers/docker/DockerTestUtils.java index e1b368ece378..178559036b12 100644 --- a/test/lib/jdk/test/lib/containers/docker/DockerTestUtils.java +++ b/test/lib/jdk/test/lib/containers/docker/DockerTestUtils.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 @@ -38,6 +38,7 @@ import java.util.Collections; import java.util.EnumSet; import java.util.List; +import jdk.internal.platform.Metrics; import jdk.test.lib.Container; import jdk.test.lib.Utils; import jdk.test.lib.process.OutputAnalyzer; @@ -47,6 +48,7 @@ public class DockerTestUtils { private static boolean isDockerEngineAvailable = false; private static boolean wasDockerEngineChecked = false; + private static final Metrics metrics = Metrics.systemMetrics(); // Specifies how many lines to copy from child STDOUT to main test output. // Having too many lines in the main test output will result @@ -93,16 +95,12 @@ public static boolean isPodman() { } /** - * Convenience method, will check if docker engine is available and usable; - * will print the appropriate message when not available. + * Checks if the docker engine is available and usable, throws an exception if not. * - * @return true if docker engine is available * @throws Exception */ - public static boolean canTestDocker() throws Exception { - if (isDockerEngineAvailable()) { - return true; - } else { + public static void checkCanTestDocker() throws Exception { + if (!isDockerEngineAvailable()) { throw new SkippedException("Docker engine is not available on this system"); } } @@ -133,6 +131,17 @@ private static String getEngineInfo(String format) throws Exception { return execute(Container.ENGINE_COMMAND, "info", "-f", format).getStdout(); } + /** + * Checks if the engine can use resource limits, throws an exception if not. + * + * @throws Exception + */ + public static void checkCanUseResourceLimits() throws Exception { + if (isRootless() && "cgroupv1".equals(metrics.getProvider())) { + throw new SkippedException("Resource limits are not available on this system"); + } + } + /** * Determine if the engine is running in root-less mode. * From b84eb0571a714266c17833782bd4701f94bb3542 Mon Sep 17 00:00:00 2001 From: Goetz Lindenmaier Date: Mon, 29 Jun 2026 21:37:20 +0000 Subject: [PATCH 39/86] 8368514: TLS stateless session ticket decryption fails on some providers Backport-of: 3c9fd7688f4d73067db9b128c329ca7603a60578 --- .../security/ssl/SessionTicketExtension.java | 6 ++- .../FipsModeTLS.java} | 46 +++++++++++------- .../pkcs11/tls/{tls12 => fips}/cert8.db | Bin .../pkcs11/tls/{tls12 => fips}/cert9.db | Bin .../pkcs11/tls/{tls12 => fips}/key3.db | Bin .../pkcs11/tls/{tls12 => fips}/key4.db | Bin .../pkcs11/tls/{tls12 => fips}/keystore | Bin .../pkcs11/tls/{tls12 => fips}/nss.cfg | 0 .../pkcs11/tls/{tls12 => fips}/pkcs11.txt | 0 .../pkcs11/tls/{tls12 => fips}/secmod.db | Bin 10 files changed, 32 insertions(+), 20 deletions(-) rename test/jdk/sun/security/pkcs11/tls/{tls12/FipsModeTLS12.java => fips/FipsModeTLS.java} (95%) rename test/jdk/sun/security/pkcs11/tls/{tls12 => fips}/cert8.db (100%) rename test/jdk/sun/security/pkcs11/tls/{tls12 => fips}/cert9.db (100%) rename test/jdk/sun/security/pkcs11/tls/{tls12 => fips}/key3.db (100%) rename test/jdk/sun/security/pkcs11/tls/{tls12 => fips}/key4.db (100%) rename test/jdk/sun/security/pkcs11/tls/{tls12 => fips}/keystore (100%) rename test/jdk/sun/security/pkcs11/tls/{tls12 => fips}/nss.cfg (100%) rename test/jdk/sun/security/pkcs11/tls/{tls12 => fips}/pkcs11.txt (100%) rename test/jdk/sun/security/pkcs11/tls/{tls12 => fips}/secmod.db (100%) 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 c0d2bea77ca8..444af5d6daea 100644 --- a/src/java.base/share/classes/sun/security/ssl/SessionTicketExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/SessionTicketExtension.java @@ -278,8 +278,10 @@ ByteBuffer decrypt(HandshakeContext hc) { aad.putInt(keyID).put(compressed); c.updateAAD(aad); + // use getOutputSize to avoid a ShortBufferException + // from providers that require oversized buffers. See JDK-8368514. ByteBuffer out = ByteBuffer.allocate( - data.remaining() - GCM_TAG_LEN / 8); + c.getOutputSize(data.remaining())); c.doFinal(data, out); out.flip(); @@ -291,7 +293,7 @@ ByteBuffer decrypt(HandshakeContext hc) { return out; } catch (Exception e) { if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { - SSLLogger.fine("Decryption failed." + e.getMessage()); + SSLLogger.fine("Decryption failed." + e); } } diff --git a/test/jdk/sun/security/pkcs11/tls/tls12/FipsModeTLS12.java b/test/jdk/sun/security/pkcs11/tls/fips/FipsModeTLS.java similarity index 95% rename from test/jdk/sun/security/pkcs11/tls/tls12/FipsModeTLS12.java rename to test/jdk/sun/security/pkcs11/tls/fips/FipsModeTLS.java index 523ccf812094..94279ca6bc53 100644 --- a/test/jdk/sun/security/pkcs11/tls/tls12/FipsModeTLS12.java +++ b/test/jdk/sun/security/pkcs11/tls/fips/FipsModeTLS.java @@ -24,16 +24,18 @@ /* * @test - * @bug 8029661 8325164 8368073 + * @bug 8029661 8325164 8368073 8368514 * @summary Test TLS 1.2 and TLS 1.3 * @modules java.base/sun.security.internal.spec * java.base/sun.security.util * java.base/com.sun.crypto.provider * @library /test/lib ../.. * @run main/othervm/timeout=120 -Djdk.tls.client.protocols=TLSv1.2 - * -Djdk.tls.useExtendedMasterSecret=false FipsModeTLS12 - * @comment SunPKCS11 does not support (TLS1.2) SunTlsExtendedMasterSecret yet - * @run main/othervm/timeout=120 -Djdk.tls.client.protocols=TLSv1.3 FipsModeTLS12 + * -Djdk.tls.useExtendedMasterSecret=false + * -Djdk.tls.client.enableSessionTicketExtension=false FipsModeTLS + * @comment SunPKCS11 does not support (TLS1.2) SunTlsExtendedMasterSecret yet. + * Stateless resumption doesn't currently work with NSS-FIPS, see JDK-8368669 + * @run main/othervm/timeout=120 -Djdk.tls.client.protocols=TLSv1.3 FipsModeTLS */ import java.io.File; @@ -74,7 +76,7 @@ import sun.security.internal.spec.TlsPrfParameterSpec; import sun.security.internal.spec.TlsRsaPremasterSecretParameterSpec; -public final class FipsModeTLS12 extends SecmodTest { +public final class FipsModeTLS extends SecmodTest { private static final boolean enableDebug = true; @@ -101,8 +103,9 @@ public static void main(String[] args) throws Exception { // Test against JCE testTlsAuthenticationCodeGeneration(); - // Self-integrity test (complete TLS 1.2 communication) - new testTLS12SunPKCS11Communication().run(); + // Self-integrity test (complete TLS communication) + testTLSSunPKCS11Communication.initSslContext(); + testTLSSunPKCS11Communication.run(); System.out.println("Test PASS - OK"); } else { @@ -269,15 +272,18 @@ private static void testTlsAuthenticationCodeGeneration() } } - private static class testTLS12SunPKCS11Communication { + private static class testTLSSunPKCS11Communication { public static void run() throws Exception { SSLEngine[][] enginesToTest = getSSLEnginesToTest(); - + boolean firstSession = true; for (SSLEngine[] engineToTest : enginesToTest) { SSLEngine clientSSLEngine = engineToTest[0]; SSLEngine serverSSLEngine = engineToTest[1]; - + // The first connection needs to do a full handshake. + // Verify that subsequent handshakes use resumption. + clientSSLEngine.setEnableSessionCreation(firstSession); + firstSession = false; // SSLEngine code based on RedhandshakeFinished.java boolean dataDone = false; @@ -400,14 +406,6 @@ private static SSLEngine[][] getSSLEnginesToTest() throws Exception { static private SSLEngine createSSLEngine(boolean client) throws Exception { SSLEngine ssle; - KeyManagerFactory kmf = KeyManagerFactory.getInstance("PKIX", "SunJSSE"); - kmf.init(ks, passphrase); - - TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX", "SunJSSE"); - tmf.init(ts); - - SSLContext sslCtx = SSLContext.getInstance("TLS", "SunJSSE"); - sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); ssle = sslCtx.createSSLEngine("localhost", 443); ssle.setUseClientMode(client); SSLParameters sslParameters = ssle.getSSLParameters(); @@ -431,6 +429,18 @@ static private SSLEngine createSSLEngine(boolean client) return ssle; } + + private static SSLContext sslCtx; + private static void initSslContext() throws Exception { + KeyManagerFactory kmf = KeyManagerFactory.getInstance("PKIX", "SunJSSE"); + kmf.init(ks, passphrase); + + TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX", "SunJSSE"); + tmf.init(ts); + + sslCtx = SSLContext.getInstance("TLS", "SunJSSE"); + sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); + } } private static void initialize() throws Exception { diff --git a/test/jdk/sun/security/pkcs11/tls/tls12/cert8.db b/test/jdk/sun/security/pkcs11/tls/fips/cert8.db similarity index 100% rename from test/jdk/sun/security/pkcs11/tls/tls12/cert8.db rename to test/jdk/sun/security/pkcs11/tls/fips/cert8.db diff --git a/test/jdk/sun/security/pkcs11/tls/tls12/cert9.db b/test/jdk/sun/security/pkcs11/tls/fips/cert9.db similarity index 100% rename from test/jdk/sun/security/pkcs11/tls/tls12/cert9.db rename to test/jdk/sun/security/pkcs11/tls/fips/cert9.db diff --git a/test/jdk/sun/security/pkcs11/tls/tls12/key3.db b/test/jdk/sun/security/pkcs11/tls/fips/key3.db similarity index 100% rename from test/jdk/sun/security/pkcs11/tls/tls12/key3.db rename to test/jdk/sun/security/pkcs11/tls/fips/key3.db diff --git a/test/jdk/sun/security/pkcs11/tls/tls12/key4.db b/test/jdk/sun/security/pkcs11/tls/fips/key4.db similarity index 100% rename from test/jdk/sun/security/pkcs11/tls/tls12/key4.db rename to test/jdk/sun/security/pkcs11/tls/fips/key4.db diff --git a/test/jdk/sun/security/pkcs11/tls/tls12/keystore b/test/jdk/sun/security/pkcs11/tls/fips/keystore similarity index 100% rename from test/jdk/sun/security/pkcs11/tls/tls12/keystore rename to test/jdk/sun/security/pkcs11/tls/fips/keystore diff --git a/test/jdk/sun/security/pkcs11/tls/tls12/nss.cfg b/test/jdk/sun/security/pkcs11/tls/fips/nss.cfg similarity index 100% rename from test/jdk/sun/security/pkcs11/tls/tls12/nss.cfg rename to test/jdk/sun/security/pkcs11/tls/fips/nss.cfg diff --git a/test/jdk/sun/security/pkcs11/tls/tls12/pkcs11.txt b/test/jdk/sun/security/pkcs11/tls/fips/pkcs11.txt similarity index 100% rename from test/jdk/sun/security/pkcs11/tls/tls12/pkcs11.txt rename to test/jdk/sun/security/pkcs11/tls/fips/pkcs11.txt diff --git a/test/jdk/sun/security/pkcs11/tls/tls12/secmod.db b/test/jdk/sun/security/pkcs11/tls/fips/secmod.db similarity index 100% rename from test/jdk/sun/security/pkcs11/tls/tls12/secmod.db rename to test/jdk/sun/security/pkcs11/tls/fips/secmod.db From 1ddfb337fb2a23c9249d575a265a6be9708788ae Mon Sep 17 00:00:00 2001 From: Arno Zeller Date: Wed, 1 Jul 2026 07:47:58 +0000 Subject: [PATCH 40/86] 8382063: Jtreg test javax/swing/SwingWorker/TestDoneBeforeDoInBackground.java fails Backport-of: a47f3620f2cae26e2e3f3642bd26871fdd02fddb --- .../TestDoneBeforeDoInBackground.java | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/test/jdk/javax/swing/SwingWorker/TestDoneBeforeDoInBackground.java b/test/jdk/javax/swing/SwingWorker/TestDoneBeforeDoInBackground.java index 56534928f6ec..119d9efaab9e 100644 --- a/test/jdk/javax/swing/SwingWorker/TestDoneBeforeDoInBackground.java +++ b/test/jdk/javax/swing/SwingWorker/TestDoneBeforeDoInBackground.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved. * 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 @@ /* * @test * @bug 8081474 + * @library /test/lib * @summary Verifies if SwingWorker calls 'done' * before the 'doInBackground' is finished * @run main TestDoneBeforeDoInBackground @@ -34,22 +35,25 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import jdk.test.lib.Utils; + public class TestDoneBeforeDoInBackground { - private static final int WAIT_TIME = 200; + private static final long WAIT_TIME = Utils.adjustTimeout(200); private static final long CLEANUP_TIME = 1000; private static final AtomicBoolean doInBackgroundStarted = new AtomicBoolean(false); private static final AtomicBoolean doInBackgroundFinished = new AtomicBoolean(false); private static final AtomicBoolean doneFinished = new AtomicBoolean(false); private static final CountDownLatch doneLatch = new CountDownLatch(1); + private static final CountDownLatch workerStarted = new CountDownLatch(1); public static void main(String[] args) throws InterruptedException { SwingWorker worker = new SwingWorker<>() { @Override protected String doInBackground() throws Exception { try { - while (!Thread.currentThread().isInterrupted()) { + while (true) { System.out.println("Working..."); Thread.sleep(WAIT_TIME); } @@ -85,6 +89,12 @@ protected void done() { worker.addPropertyChangeListener( new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { + if (worker.getState() == SwingWorker.StateValue.STARTED) { + // Now the worker has started and we got a STARTED + // notification. It should be save to cancel now. + workerStarted.countDown(); + } + System.out.println("doInBackgroundStarted: " + doInBackgroundStarted.get() + " doInBackgroundFinished: " + @@ -121,7 +131,9 @@ public void propertyChange(PropertyChangeEvent evt) { } }); worker.execute(); - Thread.sleep(WAIT_TIME * 3); + if (!workerStarted.await(5 * WAIT_TIME, TimeUnit.MILLISECONDS)) { + throw new RuntimeException("worker didn't start in time"); + } final long start = System.currentTimeMillis(); worker.cancel(true); From de3cb2f405713f2e1b221ce5351bb57a432900e3 Mon Sep 17 00:00:00 2001 From: Goetz Lindenmaier Date: Wed, 1 Jul 2026 08:56:22 +0000 Subject: [PATCH 41/86] 8361125: Fix typo in onTradAbsence Backport-of: e9a62d79cdc43e5eb141f1d47624d0f6fe05989d --- .../share/classes/sun/security/ssl/KeyShareExtension.java | 4 ++-- .../classes/sun/security/ssl/PreSharedKeyExtension.java | 2 +- .../share/classes/sun/security/ssl/SSLExtension.java | 8 ++++---- .../sun/security/ssl/SupportedGroupsExtension.java | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) 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 2df6d26ff31c..98e4693e9170 100644 --- a/src/java.base/share/classes/sun/security/ssl/KeyShareExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/KeyShareExtension.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 @@ -46,7 +46,7 @@ final class KeyShareExtension { new CHKeyShareProducer(); static final ExtensionConsumer chOnLoadConsumer = new CHKeyShareConsumer(); - static final HandshakeAbsence chOnTradAbsence = + static final HandshakeAbsence chOnTradeAbsence = new CHKeyShareOnTradeAbsence(); static final SSLStringizer chStringizer = new CHKeyShareStringizer(); 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 76bb64a66c3c..819fdd589cb4 100644 --- a/src/java.base/share/classes/sun/security/ssl/PreSharedKeyExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/PreSharedKeyExtension.java @@ -58,7 +58,7 @@ final class PreSharedKeyExtension { new CHPreSharedKeyOnLoadAbsence(); static final HandshakeConsumer chOnTradeConsumer = new CHPreSharedKeyUpdate(); - static final HandshakeAbsence chOnTradAbsence = + static final HandshakeAbsence chOnTradeAbsence = new CHPreSharedKeyOnTradeAbsence(); static final SSLStringizer chStringizer = new CHPreSharedKeyStringizer(); 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 b28ef763796f..c7175ea7fdc4 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLExtension.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2024, 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 @@ -142,7 +142,7 @@ enum SSLExtension implements SSLStringizer { SupportedGroupsExtension.chOnLoadConsumer, null, null, - SupportedGroupsExtension.chOnTradAbsence, + SupportedGroupsExtension.chOnTradeAbsence, SupportedGroupsExtension.sgsStringizer), EE_SUPPORTED_GROUPS (0x000A, "supported_groups", SSLHandshake.ENCRYPTED_EXTENSIONS, @@ -433,7 +433,7 @@ enum SSLExtension implements SSLStringizer { KeyShareExtension.chOnLoadConsumer, null, null, - KeyShareExtension.chOnTradAbsence, + KeyShareExtension.chOnTradeAbsence, KeyShareExtension.chStringizer), SH_KEY_SHARE (0x0033, "key_share", SSLHandshake.SERVER_HELLO, @@ -486,7 +486,7 @@ enum SSLExtension implements SSLStringizer { PreSharedKeyExtension.chOnLoadConsumer, PreSharedKeyExtension.chOnLoadAbsence, PreSharedKeyExtension.chOnTradeConsumer, - PreSharedKeyExtension.chOnTradAbsence, + PreSharedKeyExtension.chOnTradeAbsence, PreSharedKeyExtension.chStringizer), SH_PRE_SHARED_KEY (0x0029, "pre_shared_key", SSLHandshake.SERVER_HELLO, 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 d6e1391d09bd..57e5f8c90932 100644 --- a/src/java.base/share/classes/sun/security/ssl/SupportedGroupsExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/SupportedGroupsExtension.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 @@ -47,7 +47,7 @@ final class SupportedGroupsExtension { new CHSupportedGroupsProducer(); static final ExtensionConsumer chOnLoadConsumer = new CHSupportedGroupsConsumer(); - static final HandshakeAbsence chOnTradAbsence = + static final HandshakeAbsence chOnTradeAbsence = new CHSupportedGroupsOnTradeAbsence(); static final SSLStringizer sgsStringizer = new SupportedGroupsStringizer(); From 3b47a4ebeeb703341cd89f65be6139784281b4c7 Mon Sep 17 00:00:00 2001 From: Goetz Lindenmaier Date: Wed, 1 Jul 2026 09:01:51 +0000 Subject: [PATCH 42/86] 8368520: TLS 1.3 KeyUpdate fails with SunPKCS11 provider Backport-of: 56baf64ada04f233fbfe4e0cd033c86183e22015 --- .../security/ssl/SSLTrafficKeyDerivation.java | 22 +++++++++---------- .../security/pkcs11/tls/fips/FipsModeTLS.java | 10 ++++++--- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/java.base/share/classes/sun/security/ssl/SSLTrafficKeyDerivation.java b/src/java.base/share/classes/sun/security/ssl/SSLTrafficKeyDerivation.java index 1db07c77160d..5cb78ed44f78 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLTrafficKeyDerivation.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLTrafficKeyDerivation.java @@ -29,13 +29,11 @@ import java.nio.ByteBuffer; import java.security.GeneralSecurityException; import java.security.ProviderException; -import java.security.spec.AlgorithmParameterSpec; import javax.crypto.KDF; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.spec.HKDFParameterSpec; import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; import javax.net.ssl.SSLHandshakeException; import sun.security.internal.spec.TlsKeyMaterialParameterSpec; import sun.security.internal.spec.TlsKeyMaterialSpec; @@ -191,26 +189,26 @@ private static byte[] createHkdfInfo( private enum KeySchedule { // Note that we use enum name as the key name. - TlsKey ("key", false), - TlsIv ("iv", true), - TlsUpdateNplus1 ("traffic upd", false); + TlsKey ("key"), + TlsIv ("iv"), + TlsUpdateNplus1 ("traffic upd"); private final byte[] label; - private final boolean isIv; - KeySchedule(String label, boolean isIv) { + KeySchedule(String label) { this.label = ("tls13 " + label).getBytes(); - this.isIv = isIv; } int getKeyLength(CipherSuite cs) { - if (this == KeySchedule.TlsUpdateNplus1) - return cs.hashAlg.hashLength; - return isIv ? cs.bulkCipher.ivSize : cs.bulkCipher.keySize; + return switch (this) { + case TlsUpdateNplus1 -> cs.hashAlg.hashLength; + case TlsIv -> cs.bulkCipher.ivSize; + case TlsKey -> cs.bulkCipher.keySize; + }; } String getAlgorithm(CipherSuite cs, String algorithm) { - return isIv ? algorithm : cs.bulkCipher.algorithm; + return this == TlsKey ? cs.bulkCipher.algorithm : algorithm; } } diff --git a/test/jdk/sun/security/pkcs11/tls/fips/FipsModeTLS.java b/test/jdk/sun/security/pkcs11/tls/fips/FipsModeTLS.java index 94279ca6bc53..764754912a11 100644 --- a/test/jdk/sun/security/pkcs11/tls/fips/FipsModeTLS.java +++ b/test/jdk/sun/security/pkcs11/tls/fips/FipsModeTLS.java @@ -24,7 +24,7 @@ /* * @test - * @bug 8029661 8325164 8368073 8368514 + * @bug 8029661 8325164 8368073 8368514 8368520 * @summary Test TLS 1.2 and TLS 1.3 * @modules java.base/sun.security.internal.spec * java.base/sun.security.util @@ -89,6 +89,9 @@ public final class FipsModeTLS extends SecmodTest { private static PublicKey publicKey; public static void main(String[] args) throws Exception { + // reduce the limit to trigger a key update later + Security.setProperty("jdk.tls.keyLimits", + "AES/GCM/NoPadding KeyUpdate 10000"); try { initialize(); } catch (Exception e) { @@ -305,10 +308,11 @@ public static void run() throws Exception { cTOs = ByteBuffer.allocateDirect(netBufferMax); sTOc = ByteBuffer.allocateDirect(netBufferMax); + // big enough to trigger a key update clientOut = ByteBuffer.wrap( - "Hi Server, I'm Client".getBytes()); + "a".repeat(16000).getBytes()); serverOut = ByteBuffer.wrap( - "Hello Client, I'm Server".getBytes()); + "b".repeat(16000).getBytes()); SSLEngineResult clientResult; SSLEngineResult serverResult; From 7ec1d5f040333c93f7286b3e662236a05241a7ff Mon Sep 17 00:00:00 2001 From: Goetz Lindenmaier Date: Wed, 1 Jul 2026 09:06:41 +0000 Subject: [PATCH 43/86] 8377991: TestLimitsUpdating.java fails with runtime exception even after JDK-8370492 is fixed Reviewed-by: sgehwolf Backport-of: e6582b2e80e31103bcda08d27fded72604f687d3 --- .../containers/docker/TestLimitsUpdating.java | 20 +++++++++++----- .../platform/docker/TestLimitsUpdating.java | 24 ++++++++++++------- 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/test/hotspot/jtreg/containers/docker/TestLimitsUpdating.java b/test/hotspot/jtreg/containers/docker/TestLimitsUpdating.java index f8712aaba1e8..069cae8a59b7 100644 --- a/test/hotspot/jtreg/containers/docker/TestLimitsUpdating.java +++ b/test/hotspot/jtreg/containers/docker/TestLimitsUpdating.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2023, Red Hat, Inc. - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -49,8 +49,12 @@ import jdk.test.lib.containers.docker.DockerRunOptions; import jdk.test.lib.containers.docker.DockerTestUtils; import jdk.test.lib.process.OutputAnalyzer; +import jtreg.SkippedException; public class TestLimitsUpdating { + private static final int CPU_PERIOD = 100_000; + private static final int INITIAL_CPU_COUNT = 1; + private static final int UPDATED_CPU_COUNT = 2; private static final String TARGET_CONTAINER = "limitsUpdatingHS_" + Runtime.getRuntime().version().major(); private static final String imageName = Common.imageName("limitsUpdating"); @@ -68,6 +72,10 @@ public static void main(String[] args) throws Exception { } private static void testLimitUpdates() throws Exception { + if (Runtime.getRuntime().availableProcessors() < UPDATED_CPU_COUNT) { + throw new SkippedException("Need at least " + UPDATED_CPU_COUNT + + " available CPUs to test CPU limit updates"); + } File sharedtmpdir = new File("test-sharedtmp"); File flag = new File(sharedtmpdir, "limitsUpdated"); // shared with LimitUpdateChecker File started = new File(sharedtmpdir, "started"); // shared with LimitUpdateChecker @@ -77,8 +85,8 @@ private static void testLimitUpdates() throws Exception { DockerRunOptions opts = new DockerRunOptions(imageName, "/jdk/bin/java", "LimitUpdateChecker"); opts.addDockerOpts("--volume", Utils.TEST_CLASSES + ":/test-classes/"); opts.addDockerOpts("--volume", sharedtmpdir.getAbsolutePath() + ":/tmp"); - opts.addDockerOpts("--cpu-period", "100000"); - opts.addDockerOpts("--cpu-quota", "200000"); + opts.addDockerOpts("--cpu-period", Integer.toString(CPU_PERIOD)); + opts.addDockerOpts("--cpu-quota", Integer.toString(INITIAL_CPU_COUNT * CPU_PERIOD)); opts.addDockerOpts("--memory", "500m"); opts.addDockerOpts("--memory-swap", "500m"); opts.addDockerOpts("--name", TARGET_CONTAINER); @@ -103,7 +111,7 @@ public void run() { Thread.sleep(100); } - final List containerCommand = getContainerUpdate(300_000, 100_000, "300m"); + final List containerCommand = getContainerUpdate(UPDATED_CPU_COUNT * CPU_PERIOD, CPU_PERIOD, "300m"); // Run the update command so as to increase resources once the container signaled it has started. Thread t2 = new Thread() { public void run() { @@ -126,8 +134,8 @@ public void run() { // Do assertions based on the output in target container OutputAnalyzer targetOut = out[0]; - targetOut.shouldContain("active_processor_count: 2"); // initial value - targetOut.shouldContain("active_processor_count: 3"); // updated value + targetOut.shouldContain("active_processor_count: 1"); // initial value + targetOut.shouldContain("active_processor_count: 2"); // updated value targetOut.shouldContain("memory_limit_in_bytes: 512000 k"); // initial value targetOut.shouldContain("memory_and_swap_limit_in_bytes: 512000 k"); // initial value targetOut.shouldContain("memory_limit_in_bytes: 307200 k"); // updated value diff --git a/test/jdk/jdk/internal/platform/docker/TestLimitsUpdating.java b/test/jdk/jdk/internal/platform/docker/TestLimitsUpdating.java index 142206c210b5..a34245002d68 100644 --- a/test/jdk/jdk/internal/platform/docker/TestLimitsUpdating.java +++ b/test/jdk/jdk/internal/platform/docker/TestLimitsUpdating.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2023, Red Hat, Inc. - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -48,9 +48,13 @@ import jdk.test.lib.containers.docker.DockerRunOptions; import jdk.test.lib.containers.docker.DockerTestUtils; import jdk.test.lib.process.OutputAnalyzer; +import jtreg.SkippedException; public class TestLimitsUpdating { private static final long M = 1024 * 1024; + private static final int CPU_PERIOD = 100_000; + private static final int INITIAL_CPU_COUNT = 1; + private static final int UPDATED_CPU_COUNT = 2; private static final String TARGET_CONTAINER = "limitsUpdatingJDK_" + Runtime.getRuntime().version().major(); private static final String imageName = Common.imageName("limitsUpdatingJDK"); @@ -67,6 +71,10 @@ public static void main(String[] args) throws Exception { } private static void testLimitUpdates() throws Exception { + if (Runtime.getRuntime().availableProcessors() < UPDATED_CPU_COUNT) { + throw new SkippedException("Need at least " + UPDATED_CPU_COUNT + + " available CPUs to test CPU limit updates"); + } File sharedtmpdir = new File("jdk-sharedtmp"); File flag = new File(sharedtmpdir, "limitsUpdated"); // shared with LimitUpdateChecker File started = new File(sharedtmpdir, "started"); // shared with LimitUpdateChecker @@ -76,8 +84,8 @@ private static void testLimitUpdates() throws Exception { DockerRunOptions opts = new DockerRunOptions(imageName, "/jdk/bin/java", "LimitUpdateChecker"); opts.addDockerOpts("--volume", Utils.TEST_CLASSES + ":/test-classes/"); opts.addDockerOpts("--volume", sharedtmpdir.getAbsolutePath() + ":/tmp"); - opts.addDockerOpts("--cpu-period", "100000"); - opts.addDockerOpts("--cpu-quota", "200000"); + opts.addDockerOpts("--cpu-period", Integer.toString(CPU_PERIOD)); + opts.addDockerOpts("--cpu-quota", Integer.toString(INITIAL_CPU_COUNT * CPU_PERIOD)); opts.addDockerOpts("--memory", "500m"); opts.addDockerOpts("--memory-swap", "500m"); opts.addDockerOpts("--name", TARGET_CONTAINER); @@ -106,7 +114,7 @@ public void run() { Thread.sleep(100); } - final List containerCommand = getContainerUpdate(300_000, 100_000, "300m"); + final List containerCommand = getContainerUpdate(UPDATED_CPU_COUNT * CPU_PERIOD, CPU_PERIOD, "300m"); // Run the update command so as to increase resources once the container signaled it has started. Thread t2 = new Thread() { public void run() { @@ -129,10 +137,10 @@ public void run() { // Do assertions based on the output in target container OutputAnalyzer targetOut = out[0]; - targetOut.shouldContain("Runtime.availableProcessors: 2"); // initial value - targetOut.shouldContain("OperatingSystemMXBean.getAvailableProcessors: 2"); // initial value - targetOut.shouldContain("Runtime.availableProcessors: 3"); // updated value - targetOut.shouldContain("OperatingSystemMXBean.getAvailableProcessors: 3"); // updated value + targetOut.shouldContain("Runtime.availableProcessors: 1"); // initial value + targetOut.shouldContain("OperatingSystemMXBean.getAvailableProcessors: 1"); // initial value + targetOut.shouldContain("Runtime.availableProcessors: 2"); // updated value + targetOut.shouldContain("OperatingSystemMXBean.getAvailableProcessors: 2"); // updated value long memoryInBytes = 500 * M; targetOut.shouldContain("Metrics.getMemoryLimit() == " + memoryInBytes); // initial value targetOut.shouldContain("OperatingSystemMXBean.getTotalMemorySize: " + memoryInBytes); // initial value From b0d5b7db38f6110bafda8ad1f791accab30dcc2f Mon Sep 17 00:00:00 2001 From: Andreas Chmielewski Date: Wed, 1 Jul 2026 14:05:37 +0000 Subject: [PATCH 44/86] 8352565: Add native method implementation of Reference.get() Reviewed-by: andrew Backport-of: 56c75453cd69e80b9411b4e1794c953998406342 --- src/hotspot/share/c1/c1_Compiler.cpp | 2 +- src/hotspot/share/c1/c1_GraphBuilder.cpp | 2 +- src/hotspot/share/c1/c1_LIRGenerator.cpp | 6 +- src/hotspot/share/c1/c1_LIRGenerator.hpp | 2 +- src/hotspot/share/classfile/vmIntrinsics.cpp | 4 +- src/hotspot/share/classfile/vmIntrinsics.hpp | 2 +- src/hotspot/share/classfile/vmSymbols.hpp | 2 +- src/hotspot/share/include/jvm.h | 3 + .../share/interpreter/abstractInterpreter.cpp | 6 +- .../share/interpreter/abstractInterpreter.hpp | 2 +- .../templateInterpreterGenerator.cpp | 5 +- .../zero/zeroInterpreterGenerator.cpp | 4 +- src/hotspot/share/opto/c2compiler.cpp | 2 +- src/hotspot/share/opto/compile.cpp | 16 +- src/hotspot/share/opto/library_call.cpp | 6 +- src/hotspot/share/opto/library_call.hpp | 2 +- src/hotspot/share/prims/jvm.cpp | 10 +- .../classes/java/lang/ref/Reference.java | 11 +- .../share/native/libjava/Reference.c | 8 +- .../jtreg/gc/TestNativeReferenceGet.java | 181 ++++++++++++++++++ 20 files changed, 236 insertions(+), 40 deletions(-) create mode 100644 test/hotspot/jtreg/gc/TestNativeReferenceGet.java diff --git a/src/hotspot/share/c1/c1_Compiler.cpp b/src/hotspot/share/c1/c1_Compiler.cpp index cce2b29d2775..e754c5057666 100644 --- a/src/hotspot/share/c1/c1_Compiler.cpp +++ b/src/hotspot/share/c1/c1_Compiler.cpp @@ -142,7 +142,7 @@ bool Compiler::is_intrinsic_supported(vmIntrinsics::ID id) { case vmIntrinsics::_arraycopy: case vmIntrinsics::_currentTimeMillis: case vmIntrinsics::_nanoTime: - case vmIntrinsics::_Reference_get: + case vmIntrinsics::_Reference_get0: // Use the intrinsic version of Reference.get() so that the value in // the referent field can be registered by the G1 pre-barrier code. // Also to prevent commoning reads from this field across safepoint diff --git a/src/hotspot/share/c1/c1_GraphBuilder.cpp b/src/hotspot/share/c1/c1_GraphBuilder.cpp index 8658bebdaeea..fa291874c85b 100644 --- a/src/hotspot/share/c1/c1_GraphBuilder.cpp +++ b/src/hotspot/share/c1/c1_GraphBuilder.cpp @@ -3341,7 +3341,7 @@ GraphBuilder::GraphBuilder(Compilation* compilation, IRScope* scope) break; } - case vmIntrinsics::_Reference_get: + case vmIntrinsics::_Reference_get0: { { // With java.lang.ref.reference.get() we must go through the diff --git a/src/hotspot/share/c1/c1_LIRGenerator.cpp b/src/hotspot/share/c1/c1_LIRGenerator.cpp index 341de0ac0c2b..850a196c898c 100644 --- a/src/hotspot/share/c1/c1_LIRGenerator.cpp +++ b/src/hotspot/share/c1/c1_LIRGenerator.cpp @@ -1185,7 +1185,7 @@ void LIRGenerator::do_Return(Return* x) { // Example: ref.get() // Combination of LoadField and g1 pre-write barrier -void LIRGenerator::do_Reference_get(Intrinsic* x) { +void LIRGenerator::do_Reference_get0(Intrinsic* x) { const int referent_offset = java_lang_ref_Reference::referent_offset(); @@ -2914,8 +2914,8 @@ void LIRGenerator::do_Intrinsic(Intrinsic* x) { case vmIntrinsics::_onSpinWait: __ on_spin_wait(); break; - case vmIntrinsics::_Reference_get: - do_Reference_get(x); + case vmIntrinsics::_Reference_get0: + do_Reference_get0(x); break; case vmIntrinsics::_updateCRC32: diff --git a/src/hotspot/share/c1/c1_LIRGenerator.hpp b/src/hotspot/share/c1/c1_LIRGenerator.hpp index e70bbd961893..ec0ea5dc047d 100644 --- a/src/hotspot/share/c1/c1_LIRGenerator.hpp +++ b/src/hotspot/share/c1/c1_LIRGenerator.hpp @@ -266,7 +266,7 @@ class LIRGenerator: public InstructionVisitor, public BlockClosure { void do_CompareAndSwap(Intrinsic* x, ValueType* type); void do_PreconditionsCheckIndex(Intrinsic* x, BasicType type); void do_FPIntrinsics(Intrinsic* x); - void do_Reference_get(Intrinsic* x); + void do_Reference_get0(Intrinsic* x); void do_update_CRC32(Intrinsic* x); void do_update_CRC32C(Intrinsic* x); void do_vectorizedMismatch(Intrinsic* x); diff --git a/src/hotspot/share/classfile/vmIntrinsics.cpp b/src/hotspot/share/classfile/vmIntrinsics.cpp index baa945cdddf1..dd28e1a898cd 100644 --- a/src/hotspot/share/classfile/vmIntrinsics.cpp +++ b/src/hotspot/share/classfile/vmIntrinsics.cpp @@ -99,7 +99,7 @@ bool vmIntrinsics::preserves_state(vmIntrinsics::ID id) { case vmIntrinsics::_dpow: case vmIntrinsics::_Preconditions_checkIndex: case vmIntrinsics::_Preconditions_checkLongIndex: - case vmIntrinsics::_Reference_get: + case vmIntrinsics::_Reference_get0: case vmIntrinsics::_Continuation_doYield: case vmIntrinsics::_updateCRC32: case vmIntrinsics::_updateBytesCRC32: @@ -244,7 +244,7 @@ bool vmIntrinsics::disabled_by_jvm_flags(vmIntrinsics::ID id) { case vmIntrinsics::_storeFence: case vmIntrinsics::_fullFence: case vmIntrinsics::_countPositives: - case vmIntrinsics::_Reference_get: + case vmIntrinsics::_Reference_get0: case vmIntrinsics::_Continuation_doYield: case vmIntrinsics::_Continuation_enterSpecial: case vmIntrinsics::_Continuation_pin: diff --git a/src/hotspot/share/classfile/vmIntrinsics.hpp b/src/hotspot/share/classfile/vmIntrinsics.hpp index eeefddfedfc4..5be372075ed6 100644 --- a/src/hotspot/share/classfile/vmIntrinsics.hpp +++ b/src/hotspot/share/classfile/vmIntrinsics.hpp @@ -461,7 +461,7 @@ class methodHandle; do_signature(vectorizedMismatch_signature, "(Ljava/lang/Object;JLjava/lang/Object;JII)I") \ \ /* java/lang/ref/Reference */ \ - do_intrinsic(_Reference_get, java_lang_ref_Reference, get_name, void_object_signature, F_R) \ + do_intrinsic(_Reference_get0, java_lang_ref_Reference, get0_name, void_object_signature, F_RN) \ do_intrinsic(_Reference_refersTo0, java_lang_ref_Reference, refersTo0_name, object_boolean_signature, F_RN) \ do_intrinsic(_PhantomReference_refersTo0, java_lang_ref_PhantomReference, refersTo0_name, object_boolean_signature, F_RN) \ do_intrinsic(_Reference_clear0, java_lang_ref_Reference, clear0_name, void_method_signature, F_RN) \ diff --git a/src/hotspot/share/classfile/vmSymbols.hpp b/src/hotspot/share/classfile/vmSymbols.hpp index dc9ce61627bf..c562e3e6ccf3 100644 --- a/src/hotspot/share/classfile/vmSymbols.hpp +++ b/src/hotspot/share/classfile/vmSymbols.hpp @@ -422,7 +422,7 @@ class SerializeClosure; template(sp_name, "sp") \ template(pc_name, "pc") \ template(cs_name, "cs") \ - template(get_name, "get") \ + template(get0_name, "get0") \ template(refersTo0_name, "refersTo0") \ template(clear0_name, "clear0") \ template(put_name, "put") \ diff --git a/src/hotspot/share/include/jvm.h b/src/hotspot/share/include/jvm.h index a01bad14ab70..94007ebd5a43 100644 --- a/src/hotspot/share/include/jvm.h +++ b/src/hotspot/share/include/jvm.h @@ -356,6 +356,9 @@ JVM_HasReferencePendingList(JNIEnv *env); JNIEXPORT void JNICALL JVM_WaitForReferencePendingList(JNIEnv *env); +JNIEXPORT jobject JNICALL +JVM_ReferenceGet(JNIEnv *env, jobject ref); + JNIEXPORT jboolean JNICALL JVM_ReferenceRefersTo(JNIEnv *env, jobject ref, jobject o); diff --git a/src/hotspot/share/interpreter/abstractInterpreter.cpp b/src/hotspot/share/interpreter/abstractInterpreter.cpp index 1de7dd824f8b..ad39169bca0b 100644 --- a/src/hotspot/share/interpreter/abstractInterpreter.cpp +++ b/src/hotspot/share/interpreter/abstractInterpreter.cpp @@ -148,7 +148,7 @@ AbstractInterpreter::MethodKind AbstractInterpreter::method_kind(const methodHan case vmIntrinsics::_fmaF: return java_lang_math_fmaF; case vmIntrinsics::_dsqrt: return java_lang_math_sqrt; case vmIntrinsics::_dsqrt_strict: return java_lang_math_sqrt_strict; - case vmIntrinsics::_Reference_get: return java_lang_ref_reference_get; + case vmIntrinsics::_Reference_get0: return java_lang_ref_reference_get0; case vmIntrinsics::_Object_init: if (m->code_size() == 1) { // We need to execute the special return bytecode to check for @@ -210,7 +210,7 @@ vmIntrinsics::ID AbstractInterpreter::method_intrinsic(MethodKind kind) { case java_lang_math_exp : return vmIntrinsics::_dexp; case java_lang_math_fmaD : return vmIntrinsics::_fmaD; case java_lang_math_fmaF : return vmIntrinsics::_fmaF; - case java_lang_ref_reference_get: return vmIntrinsics::_Reference_get; + case java_lang_ref_reference_get0: return vmIntrinsics::_Reference_get0; case java_util_zip_CRC32_update : return vmIntrinsics::_updateCRC32; case java_util_zip_CRC32_updateBytes : return vmIntrinsics::_updateBytesCRC32; @@ -320,7 +320,7 @@ void AbstractInterpreter::print_method_kind(MethodKind kind) { case java_util_zip_CRC32_updateByteBuffer : tty->print("java_util_zip_CRC32_updateByteBuffer"); break; case java_util_zip_CRC32C_updateBytes : tty->print("java_util_zip_CRC32C_updateBytes"); break; case java_util_zip_CRC32C_updateDirectByteBuffer: tty->print("java_util_zip_CRC32C_updateDirectByteByffer"); break; - case java_lang_ref_reference_get : tty->print("java_lang_ref_reference_get"); break; + case java_lang_ref_reference_get0 : tty->print("java_lang_ref_reference_get0"); break; case java_lang_Thread_currentThread : tty->print("java_lang_Thread_currentThread"); break; case java_lang_Float_float16ToFloat : tty->print("java_lang_Float_float16ToFloat"); break; case java_lang_Float_floatToFloat16 : tty->print("java_lang_Float_floatToFloat16"); break; diff --git a/src/hotspot/share/interpreter/abstractInterpreter.hpp b/src/hotspot/share/interpreter/abstractInterpreter.hpp index b6876b3a2da0..a3e93aa0a301 100644 --- a/src/hotspot/share/interpreter/abstractInterpreter.hpp +++ b/src/hotspot/share/interpreter/abstractInterpreter.hpp @@ -83,7 +83,7 @@ class AbstractInterpreter: AllStatic { java_lang_math_exp, // implementation of java.lang.Math.exp (x) java_lang_math_fmaF, // implementation of java.lang.Math.fma (x, y, z) java_lang_math_fmaD, // implementation of java.lang.Math.fma (x, y, z) - java_lang_ref_reference_get, // implementation of java.lang.ref.Reference.get() + java_lang_ref_reference_get0, // implementation of java.lang.ref.Reference.get() java_util_zip_CRC32_update, // implementation of java.util.zip.CRC32.update() java_util_zip_CRC32_updateBytes, // implementation of java.util.zip.CRC32.updateBytes() java_util_zip_CRC32_updateByteBuffer, // implementation of java.util.zip.CRC32.updateByteBuffer() diff --git a/src/hotspot/share/interpreter/templateInterpreterGenerator.cpp b/src/hotspot/share/interpreter/templateInterpreterGenerator.cpp index 533c88cce9ed..928d1ac9f9c9 100644 --- a/src/hotspot/share/interpreter/templateInterpreterGenerator.cpp +++ b/src/hotspot/share/interpreter/templateInterpreterGenerator.cpp @@ -204,7 +204,7 @@ void TemplateInterpreterGenerator::generate_all() { method_entry(java_lang_math_pow ) method_entry(java_lang_math_fmaF ) method_entry(java_lang_math_fmaD ) - method_entry(java_lang_ref_reference_get) + method_entry(java_lang_ref_reference_get0) AbstractInterpreter::initialize_method_handle_entries(); method_entry(java_util_zip_CRC32C_updateBytes) @@ -228,6 +228,7 @@ void TemplateInterpreterGenerator::generate_all() { // entries for `native` methods to use the same address in case // intrinsic is disabled. native_method_entry(java_lang_Thread_currentThread) + native_method_entry(java_lang_ref_reference_get0) native_method_entry(java_util_zip_CRC32_update) native_method_entry(java_util_zip_CRC32_updateBytes) @@ -465,7 +466,7 @@ address TemplateInterpreterGenerator::generate_intrinsic_entry(AbstractInterpret case Interpreter::java_lang_math_fmaF : entry_point = generate_math_entry(kind); break; case Interpreter::java_lang_math_sqrt_strict : entry_point = generate_math_entry(Interpreter::java_lang_math_sqrt); break; - case Interpreter::java_lang_ref_reference_get + case Interpreter::java_lang_ref_reference_get0 : entry_point = generate_Reference_get_entry(); break; case Interpreter::java_util_zip_CRC32_update : entry_point = generate_CRC32_update_entry(); break; diff --git a/src/hotspot/share/interpreter/zero/zeroInterpreterGenerator.cpp b/src/hotspot/share/interpreter/zero/zeroInterpreterGenerator.cpp index c4eeb3fa8400..8fa0835216d0 100644 --- a/src/hotspot/share/interpreter/zero/zeroInterpreterGenerator.cpp +++ b/src/hotspot/share/interpreter/zero/zeroInterpreterGenerator.cpp @@ -64,7 +64,7 @@ void ZeroInterpreterGenerator::generate_all() { method_entry(java_lang_math_exp ); method_entry(java_lang_math_fmaD ); method_entry(java_lang_math_fmaF ); - method_entry(java_lang_ref_reference_get); + method_entry(java_lang_ref_reference_get0); AbstractInterpreter::initialize_method_handle_entries(); @@ -107,7 +107,7 @@ address ZeroInterpreterGenerator::generate_method_entry( case Interpreter::java_lang_math_exp : // fall thru case Interpreter::java_lang_math_fmaD : // fall thru case Interpreter::java_lang_math_fmaF : entry_point = generate_math_entry(kind); break; - case Interpreter::java_lang_ref_reference_get + case Interpreter::java_lang_ref_reference_get0 : entry_point = generate_Reference_get_entry(); break; default: fatal("unexpected method kind: %d", kind); diff --git a/src/hotspot/share/opto/c2compiler.cpp b/src/hotspot/share/opto/c2compiler.cpp index 0c642211e1fe..6552d41cc776 100644 --- a/src/hotspot/share/opto/c2compiler.cpp +++ b/src/hotspot/share/opto/c2compiler.cpp @@ -767,7 +767,7 @@ bool C2Compiler::is_intrinsic_supported(vmIntrinsics::ID id) { case vmIntrinsics::_doubleToRawLongBits: case vmIntrinsics::_doubleToLongBits: case vmIntrinsics::_longBitsToDouble: - case vmIntrinsics::_Reference_get: + case vmIntrinsics::_Reference_get0: case vmIntrinsics::_Reference_refersTo0: case vmIntrinsics::_PhantomReference_refersTo0: case vmIntrinsics::_Reference_clear0: diff --git a/src/hotspot/share/opto/compile.cpp b/src/hotspot/share/opto/compile.cpp index 2b956dcb5d84..f6d0072f1fce 100644 --- a/src/hotspot/share/opto/compile.cpp +++ b/src/hotspot/share/opto/compile.cpp @@ -783,19 +783,9 @@ Compile::Compile(ciEnv* ci_env, ciMethod* target, int osr_bci, StartNode* s = new StartNode(root(), tf()->domain()); initial_gvn()->set_type_bottom(s); verify_start(s); - if (method()->intrinsic_id() == vmIntrinsics::_Reference_get) { - // With java.lang.ref.reference.get() we must go through the - // intrinsic - even when get() is the root - // method of the compile - so that, if necessary, the value in - // the referent field of the reference object gets recorded by - // the pre-barrier code. - cg = find_intrinsic(method(), false); - } - if (cg == nullptr) { - float past_uses = method()->interpreter_invocation_count(); - float expected_uses = past_uses; - cg = CallGenerator::for_inline(method(), expected_uses); - } + float past_uses = method()->interpreter_invocation_count(); + float expected_uses = past_uses; + cg = CallGenerator::for_inline(method(), expected_uses); } if (failing()) return; if (cg == nullptr) { diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp index f74af38387ca..358c6f6654c3 100644 --- a/src/hotspot/share/opto/library_call.cpp +++ b/src/hotspot/share/opto/library_call.cpp @@ -564,7 +564,7 @@ bool LibraryCallKit::try_to_inline(int predicate) { case vmIntrinsics::_getCallerClass: return inline_native_Reflection_getCallerClass(); - case vmIntrinsics::_Reference_get: return inline_reference_get(); + case vmIntrinsics::_Reference_get0: return inline_reference_get0(); case vmIntrinsics::_Reference_refersTo0: return inline_reference_refersTo0(false); case vmIntrinsics::_PhantomReference_refersTo0: return inline_reference_refersTo0(true); case vmIntrinsics::_Reference_clear0: return inline_reference_clear0(false); @@ -6919,9 +6919,9 @@ bool LibraryCallKit::inline_updateByteBufferAdler32() { return true; } -//----------------------------inline_reference_get---------------------------- +//----------------------------inline_reference_get0---------------------------- // public T java.lang.ref.Reference.get(); -bool LibraryCallKit::inline_reference_get() { +bool LibraryCallKit::inline_reference_get0() { const int referent_offset = java_lang_ref_Reference::referent_offset(); // Get the argument: diff --git a/src/hotspot/share/opto/library_call.hpp b/src/hotspot/share/opto/library_call.hpp index 1be08df32aea..1739758aa452 100644 --- a/src/hotspot/share/opto/library_call.hpp +++ b/src/hotspot/share/opto/library_call.hpp @@ -300,7 +300,7 @@ class LibraryCallKit : public GraphKit { bool inline_bitshuffle_methods(vmIntrinsics::ID id); bool inline_compare_unsigned(vmIntrinsics::ID id); bool inline_divmod_methods(vmIntrinsics::ID id); - bool inline_reference_get(); + bool inline_reference_get0(); bool inline_reference_refersTo0(bool is_phantom); bool inline_reference_clear0(bool is_phantom); bool inline_Class_cast(); diff --git a/src/hotspot/share/prims/jvm.cpp b/src/hotspot/share/prims/jvm.cpp index 98ec029db189..5dcfdc6a23fe 100644 --- a/src/hotspot/share/prims/jvm.cpp +++ b/src/hotspot/share/prims/jvm.cpp @@ -3053,9 +3053,17 @@ JVM_ENTRY(void, JVM_WaitForReferencePendingList(JNIEnv* env)) } JVM_END +JVM_ENTRY(jobject, JVM_ReferenceGet(JNIEnv* env, jobject ref)) + oop ref_oop = JNIHandles::resolve_non_null(ref); + // PhantomReference has its own implementation of get(). + assert(!java_lang_ref_Reference::is_phantom(ref_oop), "precondition"); + oop referent = java_lang_ref_Reference::weak_referent(ref_oop); + return JNIHandles::make_local(THREAD, referent); +JVM_END + JVM_ENTRY(jboolean, JVM_ReferenceRefersTo(JNIEnv* env, jobject ref, jobject o)) oop ref_oop = JNIHandles::resolve_non_null(ref); - // PhantomReference has it's own implementation of refersTo(). + // PhantomReference has its own implementation of refersTo(). // See: JVM_PhantomReferenceRefersTo assert(!java_lang_ref_Reference::is_phantom(ref_oop), "precondition"); oop referent = java_lang_ref_Reference::weak_referent_no_keepalive(ref_oop); diff --git a/src/java.base/share/classes/java/lang/ref/Reference.java b/src/java.base/share/classes/java/lang/ref/Reference.java index c83f197380a3..ef2e5e0d0c4f 100644 --- a/src/java.base/share/classes/java/lang/ref/Reference.java +++ b/src/java.base/share/classes/java/lang/ref/Reference.java @@ -357,11 +357,18 @@ public void runFinalization() { * {@code null} if this reference object has been cleared * @see #refersTo */ - @IntrinsicCandidate public T get() { - return this.referent; + return get0(); } + /* Implementation of get(). This method exists to avoid making get() all + * of virtual, native, and intrinsic candidate. That could have the + * undesirable effect of having the native method used instead of the + * intrinsic when devirtualization fails. + */ + @IntrinsicCandidate + private native T get0(); + /** * Tests if the referent of this reference object is {@code obj}. * Using a {@code null} {@code obj} returns {@code true} if the diff --git a/src/java.base/share/native/libjava/Reference.c b/src/java.base/share/native/libjava/Reference.c index ce5b34299ada..7fef23c2ba86 100644 --- a/src/java.base/share/native/libjava/Reference.c +++ b/src/java.base/share/native/libjava/Reference.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2020, 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 @@ -44,6 +44,12 @@ Java_java_lang_ref_Reference_waitForReferencePendingList(JNIEnv *env, jclass ign JVM_WaitForReferencePendingList(env); } +JNIEXPORT jobject JNICALL +Java_java_lang_ref_Reference_get0(JNIEnv *env, jobject ref) +{ + return JVM_ReferenceGet(env, ref); +} + JNIEXPORT jboolean JNICALL Java_java_lang_ref_Reference_refersTo0(JNIEnv *env, jobject ref, jobject o) { diff --git a/test/hotspot/jtreg/gc/TestNativeReferenceGet.java b/test/hotspot/jtreg/gc/TestNativeReferenceGet.java new file mode 100644 index 000000000000..222d250f79af --- /dev/null +++ b/test/hotspot/jtreg/gc/TestNativeReferenceGet.java @@ -0,0 +1,181 @@ +/* + * 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 gc; + +/** + * @test + * @bug 8352565 + * @summary Determine whether the native method implementation of + * Reference.get() works as expected. Disable the intrinsic implementation to + * force use of the native method. + * @library /test/lib + * @modules java.base/java.lang.ref:open + * @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 + * -XX:DisableIntrinsic=_Reference_get0 + * gc.TestNativeReferenceGet + */ + +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; +import jdk.test.whitebox.WhiteBox; + +public final class TestNativeReferenceGet { + + private static final WhiteBox WB = WhiteBox.getWhiteBox(); + + private static void gcUntilOld(Object o) { + while (!WB.isObjectInOldGen(o)) { + WB.fullGC(); + } + } + + private static final class TestObject { + public final int value; + + public TestObject(int value) { + this.value = value; + } + } + + private static final ReferenceQueue queue = + new ReferenceQueue(); + + private static final class Ref extends WeakReference { + public Ref(TestObject obj) { + super(obj, queue); + } + } + + private static final int NUM_REFS = 100; + + private static List references = null; + private static List referents = null; + + // Create all the objects used by the test, and ensure they are all in the + // old generation. + private static void setup() { + references = new ArrayList(NUM_REFS); + referents = new ArrayList(NUM_REFS); + + for (int i = 0; i < NUM_REFS; ++i) { + TestObject obj = new TestObject(i); + referents.add(obj); + references.add(new Ref(obj)); + } + + gcUntilOld(references); + gcUntilOld(referents); + for (int i = 0; i < NUM_REFS; ++i) { + gcUntilOld(references.get(i)); + gcUntilOld(referents.get(i)); + } + } + + // Discard all the strong references. + private static void dropReferents() { + // Not using List.clear() because it doesn't document null'ing elements. + for (int i = 0; i < NUM_REFS; ++i) { + referents.set(i, null); + } + } + + // Create new strong references from the weak references, by using the + // native method implementation of Reference.get() and recording the value + // in references. + private static void strengthenReferents() { + for (int i = 0; i < NUM_REFS; ++i) { + referents.set(i, references.get(i).get()); + } + } + + private static void check() { + // None of the references should have been cleared and enqueued, + // because we have strong references to all the referents. + try { + while (WB.waitForReferenceProcessing()) {} + } catch (InterruptedException e) { + throw new RuntimeException("Test interrupted"); + } + if (queue.poll() != null) { + throw new RuntimeException("Reference enqueued"); + } + + // Check details of expected state. + for (int i = 0; i < NUM_REFS; ++i) { + Ref reference = (Ref) references.get(i); + TestObject referent = reference.get(); + if (referent == null) { + throw new RuntimeException("Referent not strengthened"); + } else if (referent != referents.get(i)) { + throw new RuntimeException( + "Reference referent differs from saved referent: " + i); + } else if (referent.value != i) { + throw new RuntimeException( + "Referent " + i + " value: " + referent.value); + } + } + } + + private static void testConcurrent() { + System.out.println("Testing concurrent GC"); + try { + WB.concurrentGCAcquireControl(); + dropReferents(); + WB.concurrentGCRunTo(WB.BEFORE_MARKING_COMPLETED); + strengthenReferents(); + WB.concurrentGCRunToIdle(); + check(); + } finally { + WB.concurrentGCReleaseControl(); + } + } + + private static void testNonconcurrent() { + System.out.println("Testing nonconcurrent GC"); + // A GC between clearing and strengthening will result in test failure. + // We try to make that unlikely via this immediately preceeding GC. + WB.fullGC(); + dropReferents(); + strengthenReferents(); + WB.fullGC(); + check(); + } + + public static final void main(String[] args) { + setup(); + if (WB.supportsConcurrentGCBreakpoints()) { + testConcurrent(); + } else { + testNonconcurrent(); + } + } +} From 137b05b46ce61a79471b36a192f73534e3a17c82 Mon Sep 17 00:00:00 2001 From: Gui Cao Date: Thu, 2 Jul 2026 08:15:51 +0000 Subject: [PATCH 45/86] 8387381: RISC-V: assert failed with fastdebug build on systems with different core types Reviewed-by: fyang Backport-of: aa17cf560835706351f2ce69886b3e23049f6bb1 --- src/hotspot/os_cpu/linux_riscv/riscv_hwprobe.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/hotspot/os_cpu/linux_riscv/riscv_hwprobe.cpp b/src/hotspot/os_cpu/linux_riscv/riscv_hwprobe.cpp index a95bfb4ff96d..bb2996a548cd 100644 --- a/src/hotspot/os_cpu/linux_riscv/riscv_hwprobe.cpp +++ b/src/hotspot/os_cpu/linux_riscv/riscv_hwprobe.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2023, Rivos Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -167,13 +167,16 @@ static bool is_set(int64_t key, uint64_t value_mask) { void RiscvHwprobe::add_features_from_query_result() { assert(rw_hwprobe_completed, "hwprobe not init yet."); - if (is_valid(RISCV_HWPROBE_KEY_MVENDORID)) { + // For value-type keys, the kernel returns (uint64_t)-1 when CPUs in the + // query set disagree (different core types). Skip these as the value is + // not meaningful for the system as a whole. + if (is_valid(RISCV_HWPROBE_KEY_MVENDORID) && query[RISCV_HWPROBE_KEY_MVENDORID].value != (uint64_t)-1) { VM_Version::mvendorid.enable_feature(query[RISCV_HWPROBE_KEY_MVENDORID].value); } - if (is_valid(RISCV_HWPROBE_KEY_MARCHID)) { + if (is_valid(RISCV_HWPROBE_KEY_MARCHID) && query[RISCV_HWPROBE_KEY_MARCHID].value != (uint64_t)-1) { VM_Version::marchid.enable_feature(query[RISCV_HWPROBE_KEY_MARCHID].value); } - if (is_valid(RISCV_HWPROBE_KEY_MIMPID)) { + if (is_valid(RISCV_HWPROBE_KEY_MIMPID) && query[RISCV_HWPROBE_KEY_MIMPID].value != (uint64_t)-1) { VM_Version::mimpid.enable_feature(query[RISCV_HWPROBE_KEY_MIMPID].value); } if (is_set(RISCV_HWPROBE_KEY_BASE_BEHAVIOR, RISCV_HWPROBE_BASE_BEHAVIOR_IMA)) { From acbebd5895c551b09755368ded6e3703bdd6c92f Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Thu, 2 Jul 2026 12:00:49 +0000 Subject: [PATCH 46/86] 8379516: Adjust JVM debug helper exports Reviewed-by: mdoerr Backport-of: c64f7357a536a7577432964ea8ce723c5373a184 --- src/hotspot/share/utilities/debug.cpp | 107 +++++++++++++++++--------- 1 file changed, 69 insertions(+), 38 deletions(-) diff --git a/src/hotspot/share/utilities/debug.cpp b/src/hotspot/share/utilities/debug.cpp index bd5adc7acf9a..5304a3dd5243 100644 --- a/src/hotspot/share/utilities/debug.cpp +++ b/src/hotspot/share/utilities/debug.cpp @@ -322,20 +322,20 @@ class Command : public StackObj { int Command::level = 0; -extern "C" DEBUGEXPORT void blob(CodeBlob* cb) { +extern "C" NOINLINE void blob(CodeBlob* cb) { Command c("blob"); cb->print(); } -extern "C" DEBUGEXPORT void dump_vtable(address p) { +extern "C" NOINLINE void dump_vtable(address p) { Command c("dump_vtable"); Klass* k = (Klass*)p; k->vtable().print(); } -extern "C" DEBUGEXPORT void nm(intptr_t p) { +extern "C" NOINLINE void nm(intptr_t p) { // Actually we look through all CodeBlobs (the nm name has been kept for backwards compatibility) Command c("nm"); CodeBlob* cb = CodeCache::find_blob((address)p); @@ -347,7 +347,7 @@ extern "C" DEBUGEXPORT void nm(intptr_t p) { } -extern "C" DEBUGEXPORT void disnm(intptr_t p) { +extern "C" NOINLINE void disnm(intptr_t p) { Command c("disnm"); CodeBlob* cb = CodeCache::find_blob((address) p); if (cb != nullptr) { @@ -362,7 +362,7 @@ extern "C" DEBUGEXPORT void disnm(intptr_t p) { } -extern "C" DEBUGEXPORT void printnm(intptr_t p) { +extern "C" NOINLINE void printnm(intptr_t p) { char buffer[256]; os::snprintf_checked(buffer, sizeof(buffer), "printnm: " INTPTR_FORMAT, p); Command c(buffer); @@ -376,13 +376,13 @@ extern "C" DEBUGEXPORT void printnm(intptr_t p) { } -extern "C" DEBUGEXPORT void universe() { +extern "C" NOINLINE void universe() { Command c("universe"); Universe::print_on(tty); } -extern "C" DEBUGEXPORT void verify() { +extern "C" NOINLINE void verify() { // try to run a verify on the entire system // note: this may not be safe if we're not at a safepoint; for debugging, // this manipulates the safepoint settings to avoid assertion failures @@ -399,7 +399,7 @@ extern "C" DEBUGEXPORT void verify() { } -extern "C" DEBUGEXPORT void pp(void* p) { +extern "C" NOINLINE void pp(void* p) { Command c("pp"); FlagSetting fl(DisplayVMOutput, true); if (p == nullptr) { @@ -423,10 +423,7 @@ extern "C" DEBUGEXPORT void pp(void* p) { } } - -extern "C" DEBUGEXPORT void findpc(intptr_t x); - -extern "C" DEBUGEXPORT void ps() { // print stack +extern "C" NOINLINE void ps() { // print stack if (Thread::current_or_null() == nullptr) return; Command c("ps"); @@ -455,7 +452,7 @@ extern "C" DEBUGEXPORT void ps() { // print stack } } -extern "C" DEBUGEXPORT void pfl() { +extern "C" NOINLINE void pfl() { // print frame layout Command c("pfl"); JavaThread* p = JavaThread::active(); @@ -467,7 +464,7 @@ extern "C" DEBUGEXPORT void pfl() { } } -extern "C" DEBUGEXPORT void psf() { // print stack frames +extern "C" NOINLINE void psf() { // print stack frames { Command c("psf"); JavaThread* p = JavaThread::active(); @@ -481,19 +478,19 @@ extern "C" DEBUGEXPORT void psf() { // print stack frames } -extern "C" DEBUGEXPORT void threads() { +extern "C" NOINLINE void threads() { Command c("threads"); Threads::print(false, true); } -extern "C" DEBUGEXPORT void psd() { +extern "C" NOINLINE void psd() { Command c("psd"); SystemDictionary::print(); } -extern "C" DEBUGEXPORT void pss() { // print all stacks +extern "C" NOINLINE void pss() { // print all stacks if (Thread::current_or_null() == nullptr) return; Command c("pss"); Threads::print(true, PRODUCT_ONLY(false) NOT_PRODUCT(true)); @@ -501,7 +498,7 @@ extern "C" DEBUGEXPORT void pss() { // print all stacks // #ifndef PRODUCT -extern "C" DEBUGEXPORT void debug() { // to set things up for compiler debugging +extern "C" NOINLINE void debug() { // to set things up for compiler debugging Command c("debug"); NOT_PRODUCT(WizardMode = true;) PrintCompilation = true; @@ -510,7 +507,7 @@ extern "C" DEBUGEXPORT void debug() { // to set things up for comp } -extern "C" DEBUGEXPORT void ndebug() { // undo debug() +extern "C" NOINLINE void ndebug() { // undo debug() Command c("ndebug"); PrintCompilation = false; PrintInlining = PrintAssembly = false; @@ -518,35 +515,35 @@ extern "C" DEBUGEXPORT void ndebug() { // undo debug() } -extern "C" DEBUGEXPORT void flush() { +extern "C" NOINLINE void flush() { Command c("flush"); tty->flush(); } -extern "C" DEBUGEXPORT void events() { +extern "C" NOINLINE void events() { Command c("events"); Events::print(); } -extern "C" DEBUGEXPORT Method* findm(intptr_t pc) { +extern "C" NOINLINE Method* findm(intptr_t pc) { Command c("findm"); nmethod* nm = CodeCache::find_nmethod((address)pc); return (nm == nullptr) ? (Method*)nullptr : nm->method(); } -extern "C" DEBUGEXPORT nmethod* findnm(intptr_t addr) { +extern "C" NOINLINE nmethod* findnm(intptr_t addr) { Command c("findnm"); return CodeCache::find_nmethod((address)addr); } -extern "C" DEBUGEXPORT void find(intptr_t x) { +extern "C" NOINLINE void find(intptr_t x) { Command c("find"); os::print_location(tty, x, false); } -extern "C" DEBUGEXPORT void findpc(intptr_t x) { +extern "C" NOINLINE void findpc(intptr_t x) { Command c("findpc"); os::print_location(tty, x, true); } @@ -557,21 +554,20 @@ extern "C" DEBUGEXPORT void findpc(intptr_t x) { // call findclass("java/lang/Object", 0x3) -> find j.l.Object and disasm all of its methods // call findmethod("*ang/Object*", "wait", 0xff) -> detailed disasm of all "wait" methods in j.l.Object // call findmethod("*ang/Object*", "wait:(*J*)V", 0x1) -> list all "wait" methods in j.l.Object that have a long parameter -extern "C" DEBUGEXPORT void findclass(const char* class_name_pattern, int flags) { +extern "C" NOINLINE void findclass(const char* class_name_pattern, int flags) { Command c("findclass"); ClassPrinter::print_flags_help(tty); ClassPrinter::print_classes(class_name_pattern, flags, tty); } -extern "C" DEBUGEXPORT void findmethod(const char* class_name_pattern, - const char* method_pattern, int flags) { +extern "C" NOINLINE void findmethod(const char* class_name_pattern, const char* method_pattern, int flags) { Command c("findmethod"); ClassPrinter::print_flags_help(tty); ClassPrinter::print_methods(class_name_pattern, method_pattern, flags, tty); } // Need method pointer to find bcp -extern "C" DEBUGEXPORT void findbcp(intptr_t method, intptr_t bcp) { +extern "C" NOINLINE void findbcp(intptr_t method, intptr_t bcp) { Command c("findbcp"); Method* mh = (Method*)method; if (!mh->is_native()) { @@ -582,7 +578,7 @@ extern "C" DEBUGEXPORT void findbcp(intptr_t method, intptr_t bcp) { } // check and decode a single u5 value -extern "C" DEBUGEXPORT u4 u5decode(intptr_t addr) { +extern "C" NOINLINE u4 u5decode(intptr_t addr) { Command c("u5decode"); u1* arr = (u1*)addr; size_t off = 0, lim = 5; @@ -599,9 +595,7 @@ extern "C" DEBUGEXPORT u4 u5decode(intptr_t addr) { // there is no limit on the count of items printed; the // printing stops when an null is printed or at limit. // See documentation for UNSIGNED5::Reader::print(count). -extern "C" DEBUGEXPORT intptr_t u5p(intptr_t addr, - intptr_t limit, - int count) { +extern "C" NOINLINE intptr_t u5p(intptr_t addr, intptr_t limit, int count) { Command c("u5p"); u1* arr = (u1*)addr; if (limit && limit < addr) limit = addr; @@ -614,10 +608,10 @@ extern "C" DEBUGEXPORT intptr_t u5p(intptr_t addr, // int versions of all methods to avoid having to type type casts in the debugger -void pp(intptr_t p) { pp((void*)p); } -void pp(oop p) { pp((void*)p); } +NOINLINE void pp(intptr_t p) { pp((void*)p); } +NOINLINE void pp(oop p) { pp((void*)p); } -extern "C" DEBUGEXPORT void help() { +extern "C" NOINLINE void help() { Command c("help"); tty->print_cr("basic"); tty->print_cr(" pp(void* p) - try to make sense of p"); @@ -674,7 +668,7 @@ extern "C" DEBUGEXPORT void help() { } #ifndef PRODUCT -extern "C" DEBUGEXPORT void pns(void* sp, void* fp, void* pc) { // print native stack +extern "C" NOINLINE void pns(void* sp, void* fp, void* pc) { // print native stack Command c("pns"); static char buf[O_BUFLEN]; // Call generic frame constructor (certain arguments may be ignored) @@ -692,7 +686,7 @@ extern "C" DEBUGEXPORT void pns(void* sp, void* fp, void* pc) { // print native // WARNING: Only intended for use when debugging. Do not leave calls to // pns2() in committed source (product or debug). // -extern "C" DEBUGEXPORT void pns2() { // print native stack +extern "C" NOINLINE void pns2() { // print native stack Command c("pns2"); static char buf[O_BUFLEN]; address lastpc = nullptr; @@ -702,6 +696,43 @@ extern "C" DEBUGEXPORT void pns2() { // print native stack } #endif +// just an exported helper; to avoid link time elimination of the referenced functions +extern "C" JNIEXPORT void JVM_debug_helpers_keeper(void* p1, void* p2, void* p3, intptr_t ip, oop oh, address adr) { + blob((CodeBlob*)p1); + dump_vtable(adr); + nm(ip); + disnm(ip); + printnm(ip); + universe(); + verify(); + pp(p1); + ps(); + pfl(); + psf(); + threads(); + psd(); + pss(); + debug(); + ndebug(); + flush(); + events(); + findm(ip); + findnm(ip); + find(ip); + findpc(ip); + findclass("", 0); + findmethod("", "", 0); + findbcp(ip, ip); + u5decode(ip); + u5p(ip, ip, 0); + pp(ip); + pp(oh); + help(); +#ifndef PRODUCT + pns(p1, p2, p3); + pns2(); +#endif +} // Returns true iff the address p is readable and *(intptr_t*)p != errvalue extern "C" bool dbg_is_safe(const void* p, intptr_t errvalue) { From 7f6b120f8463adb251b98c90ce9ee0d0de9b14cb Mon Sep 17 00:00:00 2001 From: Arno Zeller Date: Mon, 6 Jul 2026 11:59:19 +0000 Subject: [PATCH 47/86] 8381840: Lots of /tmp/8173970- folders on test machines Backport-of: e29ffd108f69c467fd6041c01215e54b378af88b --- test/jdk/tools/jar/JarExtractTest.java | 64 ++++++++++++++++++++------ 1 file changed, 51 insertions(+), 13 deletions(-) diff --git a/test/jdk/tools/jar/JarExtractTest.java b/test/jdk/tools/jar/JarExtractTest.java index f1d30e678ae4..a7fea62d2208 100644 --- a/test/jdk/tools/jar/JarExtractTest.java +++ b/test/jdk/tools/jar/JarExtractTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,8 +26,11 @@ import java.io.IOException; import java.io.PrintStream; import java.nio.charset.StandardCharsets; +import java.nio.file.FileVisitResult; +import java.nio.file.FileVisitor; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -65,12 +68,12 @@ public class JarExtractTest { private static final byte[] FILE_CONTENT = "Hello world!!!".getBytes(StandardCharsets.UTF_8); // the jar that will get extracted in the tests private Path testJarPath; - private static Collection filesToDelete = new ArrayList<>(); + private static final Collection filesToDelete = new ArrayList<>(); @BeforeEach public void createTestJar() throws Exception { - final String tmpDir = Files.createTempDirectory("8173970-").toString(); - testJarPath = Path.of(tmpDir, "8173970-test.jar"); + final Path tmpDir = Files.createTempDirectory(Path.of("."), "8173970-"); + testJarPath = tmpDir.resolve("8173970-test.jar"); final JarBuilder builder = new JarBuilder(testJarPath.toString()); // d1 // |--- d2 @@ -273,22 +276,34 @@ public void testExtractToNonDirectory() throws Exception { * Tests that extracting a jar using {@code -P} flag and without any explicit destination * directory works correctly if the jar contains entries with leading slashes and/or {@code ..} * parts preserved. + * The test creates a JAR file with an entry which has a leading slash in its name and + * another entry that has ".." in its entry name. jar tool is then used to extract that JAR file + * with the "-P" option which is to preserve leading '/' (absolute path) + * and ".." (parent directory) components when extracting those entries. This test then verifies + * that after successfully extracting that JAR file, these entries are present at the expected + * paths on the filesystem. */ @Test public void testExtractNoDestDirWithPFlag() throws Exception { - // run this test only on those systems where "/tmp" directory is available and we - // can write to it + // This test requires that the entry in the JAR file have a leading slash, which + // upon extraction of that JAR file will correspond to a filesystem path. Not all + // environments may have a writable location that starts with "/". "/tmp" is one + // commonly available filesystem directory which is usually writable. Here we check the + // presence of "/tmp" directory and verify that files can be created in that directory. + // If we can't, then we skip this test. Assumptions.assumeTrue(Files.isDirectory(Path.of("/tmp")), "skipping test, since /tmp isn't a directory"); - // try and write into "/tmp" - final Path tmpDir; + final Path tempTestDir; try { - tmpDir = Files.createTempDirectory(Path.of("/tmp"), "8173970-").toAbsolutePath(); + // create a test specific directory in "/tmp" directory + tempTestDir = Files.createTempDirectory(Path.of("/tmp"), "8173970-"); } catch (IOException ioe) { Assumptions.abort("skipping test, since /tmp cannot be written to: " + ioe); + // The above Assumptions.abort(...) call makes this "return" unreachable, but we keep + // the "return" for code clarity. return; } - final String leadingSlashEntryName = tmpDir.toString() + "/foo/f1.txt"; + final String leadingSlashEntryName = tempTestDir.toString() + "/foo/f1.txt"; // create a jar which has leading slash (/) and dot-dot (..) preserved in entry names final Path jarPath = createJarWithPFlagSemantics(leadingSlashEntryName); final List cmdArgs = new ArrayList<>(); @@ -315,8 +330,9 @@ public void testExtractNoDestDirWithPFlag() throws Exception { "Unexpected content in file " + f2); } } finally { - // clean up the file that might have been extracted into "/tmp/...." directory - Files.deleteIfExists(Path.of(leadingSlashEntryName)); + // clean up the temp directory created by this test under "/tmp/...." directory + System.err.println("Deleting directory: " + tempTestDir); + deleteRecursively(tempTestDir); } } @@ -514,4 +530,26 @@ private static Path createJarWithPFlagSemantics(String leadingSlashEntryName) private static void printJarCommand(final String[] cmdArgs) { System.out.println("Running 'jar " + String.join(" ", cmdArgs) + "'"); } -} \ No newline at end of file + + private static void deleteRecursively(final Path dir) throws IOException { + Files.walkFileTree(dir, new FileVisitor<>() { + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { + return FileVisitResult.CONTINUE; + } + @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) + throws IOException { + Files.delete(file); // delete the file + return FileVisitResult.CONTINUE; + } + @Override public FileVisitResult visitFileFailed(Path file, IOException exc) { + return FileVisitResult.CONTINUE; + } + @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) + throws IOException { + Files.delete(dir); // delete the (empty) directory + return FileVisitResult.CONTINUE; + } + }); + } +} From 3e7cdc82eef2c6b88cc4d5c8f85063c75a937427 Mon Sep 17 00:00:00 2001 From: Arno Zeller Date: Mon, 6 Jul 2026 12:50:01 +0000 Subject: [PATCH 48/86] 8382222: sun/security/ssl/SSLSocketImpl/SSLSocketBruteForceClose.java fails sporadically Backport-of: a9613a324b1effe4acea72528ce276f8fdd6a7e6 --- .../security/ssl/SSLSocketImpl/SSLSocketBruteForceClose.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/jdk/sun/security/ssl/SSLSocketImpl/SSLSocketBruteForceClose.java b/test/jdk/sun/security/ssl/SSLSocketImpl/SSLSocketBruteForceClose.java index bbc8a4f8bf55..78263b8c828d 100644 --- a/test/jdk/sun/security/ssl/SSLSocketImpl/SSLSocketBruteForceClose.java +++ b/test/jdk/sun/security/ssl/SSLSocketImpl/SSLSocketBruteForceClose.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * 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 @@ protected void runServerApplication(SSLSocket socket) throws Exception { protected void configureClientSocket(SSLSocket socket) { try { socket.setSoLinger(true, 3); - socket.setSoTimeout(1000); + socket.setSoTimeout(5000); } catch (SocketException exc) { throw new RuntimeException("Could not configure client socket", exc); } From d380cc89adf8ea955ee2aa9d035bfd42540d8673 Mon Sep 17 00:00:00 2001 From: Richard Reingruber Date: Mon, 6 Jul 2026 12:50:32 +0000 Subject: [PATCH 49/86] 8384161: [PPC64] Consolidate code related to calls in nmethods that use trampoline stubs Reviewed-by: mdoerr Backport-of: 94d3aecfed043f0e8825922a17ef1d50e6bc86f7 --- src/hotspot/cpu/ppc/c1_CodeStubs_ppc.cpp | 20 +- src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp | 60 +--- src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.hpp | 10 +- src/hotspot/cpu/ppc/macroAssembler_ppc.cpp | 131 ++++---- src/hotspot/cpu/ppc/macroAssembler_ppc.hpp | 15 +- src/hotspot/cpu/ppc/ppc.ad | 337 ++------------------ src/hotspot/cpu/ppc/sharedRuntime_ppc.cpp | 57 +--- 7 files changed, 141 insertions(+), 489 deletions(-) diff --git a/src/hotspot/cpu/ppc/c1_CodeStubs_ppc.cpp b/src/hotspot/cpu/ppc/c1_CodeStubs_ppc.cpp index b1cdf38daf3a..eecdaa3f5d0e 100644 --- a/src/hotspot/cpu/ppc/c1_CodeStubs_ppc.cpp +++ b/src/hotspot/cpu/ppc/c1_CodeStubs_ppc.cpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2012, 2025 SAP SE. All rights reserved. + * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2026 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -442,15 +442,13 @@ void ArrayCopyStub::emit_code(LIR_Assembler* ce) { return; // CodeCache is full } - bool success = ce->emit_trampoline_stub_for_call(SharedRuntime::get_resolve_static_call_stub()); - if (!success) { return; } - - __ relocate(relocInfo::static_call_type); - // Note: At this point we do not have the address of the trampoline - // stub, and the entry point might be too far away for bl, so __ pc() - // serves as dummy and the bl will be patched later. - __ code()->set_insts_mark(); - __ bl(__ pc()); + AddressLiteral resolve(SharedRuntime::get_resolve_static_call_stub(), + relocInfo::static_call_type); + address call_pc = __ trampoline_call(resolve); + if (call_pc == nullptr) { + ce->bailout("const/stub overflow in call with trampoline"); + return; + } ce->add_call_info_here(info()); ce->verify_oop_map(info()); diff --git a/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp b/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp index 7dfde40364e6..29c69ac34108 100644 --- a/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp +++ b/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2012, 2025 SAP SE. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2026 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -610,67 +610,25 @@ void LIR_Assembler::align_call(LIR_Code) { // do nothing since all instructions are word aligned on ppc } - -bool LIR_Assembler::emit_trampoline_stub_for_call(address target, Register Rtoc) { - int start_offset = __ offset(); - // Put the entry point as a constant into the constant pool. - const address entry_point_toc_addr = __ address_constant(target, RelocationHolder::none); - if (entry_point_toc_addr == nullptr) { - bailout("const section overflow"); - return false; - } - const int entry_point_toc_offset = __ offset_to_method_toc(entry_point_toc_addr); - - // Emit the trampoline stub which will be related to the branch-and-link below. - address stub = __ emit_trampoline_stub(entry_point_toc_offset, start_offset, Rtoc); - if (!stub) { - bailout("no space for trampoline stub"); - return false; - } - return true; -} - - void LIR_Assembler::call(LIR_OpJavaCall* op, relocInfo::relocType rtype) { assert(rtype==relocInfo::opt_virtual_call_type || rtype==relocInfo::static_call_type, "unexpected rtype"); - bool success = emit_trampoline_stub_for_call(op->addr()); - if (!success) { return; } - - __ relocate(rtype); - // Note: At this point we do not have the address of the trampoline - // stub, and the entry point might be too far away for bl, so __ pc() - // serves as dummy and the bl will be patched later. - __ code()->set_insts_mark(); - __ bl(__ pc()); + address call_pc = __ trampoline_call(AddressLiteral(op->addr(), rtype)); + if (call_pc == nullptr) { + bailout("const/stub overflow in call with trampoline"); + return; + } add_call_info(code_offset(), op->info()); __ post_call_nop(); } - void LIR_Assembler::ic_call(LIR_OpJavaCall* op) { __ calculate_address_from_global_toc(R2_TOC, __ method_toc()); - - // Virtual call relocation will point to ic load. - address virtual_call_meta_addr = __ pc(); - // Load a clear inline cache. - AddressLiteral empty_ic((address) Universe::non_oop_word()); - bool success = __ load_const_from_method_toc(R19_inline_cache_reg, empty_ic, R2_TOC); + bool success = __ ic_call(R2_TOC, op->addr()); if (!success) { - bailout("const section overflow"); + bailout("const/stub overflow in ic_call with trampoline"); return; } - // Call to fixup routine. Fixup routine uses ScopeDesc info - // to determine who we intended to call. - __ relocate(virtual_call_Relocation::spec(virtual_call_meta_addr)); - - success = emit_trampoline_stub_for_call(op->addr(), R2_TOC); - if (!success) { return; } - - // Note: At this point we do not have the address of the trampoline - // stub, and the entry point might be too far away for bl, so __ pc() - // serves as dummy and the bl will be patched later. - __ bl(__ pc()); add_call_info(code_offset(), op->info()); __ post_call_nop(); } diff --git a/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.hpp b/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.hpp index e4de2eb5c468..79b0478bdb62 100644 --- a/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.hpp +++ b/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.hpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 2000, 2022, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2012, 2015 SAP SE. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2026 SAP SE. All rights reserved. * 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,9 @@ #ifndef CPU_PPC_C1_LIRASSEMBLER_PPC_HPP #define CPU_PPC_C1_LIRASSEMBLER_PPC_HPP +// ArrayCopyStub needs access to bailout +friend class ArrayCopyStub; + private: ////////////////////////////////////////////////////////////////////////////// @@ -56,9 +59,6 @@ public: static const ConditionRegister BOOL_RESULT; - // Emit trampoline stub for call. Call bailout() if failed. Return true on success. - bool emit_trampoline_stub_for_call(address target, Register Rtoc = noreg); - enum { _static_call_stub_size = 4 * BytesPerInstWord + MacroAssembler::b64_patchable_size, // or smaller _call_stub_size = _static_call_stub_size + MacroAssembler::trampoline_stub_size, // or smaller diff --git a/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp b/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp index 1bd74ca03d6c..55519812c7ab 100644 --- a/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp +++ b/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp @@ -1210,6 +1210,75 @@ address MacroAssembler::call_c_using_toc(const FunctionDescriptor* fd, } #endif // ABI_ELFv2 +bool MacroAssembler::ic_call(Register Rmethod_toc, + address target, + jint method_index, + bool scratch_emit, + bool fixed_size) { + AddressLiteral target_al(target, virtual_call_Relocation::spec(pc(), method_index)); + DEBUG_ONLY(int ic_load_offset = offset()); + + // Load a clear inline cache. + AddressLiteral empty_ic((address) Universe::non_oop_word()); + bool success = load_const_from_method_toc(R19_inline_cache_reg, empty_ic, Rmethod_toc, fixed_size); + if (!success) return false; + + assert(MacroAssembler::is_load_const_from_method_toc_at(addr_at(ic_load_offset)), + "should be load from TOC"); + + address call_pc = trampoline_call(target_al, Rmethod_toc, scratch_emit); + return call_pc != nullptr; +} + +address MacroAssembler::trampoline_call(AddressLiteral target, + Register Rmethod_toc, + bool scratch_emit) { + // First, emit the trampoline stub + if (!scratch_emit) { + RelocationHolder rh = trampoline_stub_Relocation::spec(pc() /* of the bl below */); + + // Put the target's entry point as a constant into the constant pool. + const address target_toc_addr = address_constant((address)target.value()); + if (target_toc_addr == nullptr) return nullptr; + + const int target_toc_offset = offset_to_method_toc(target_toc_addr); + address stub = start_a_stub(64); + if (stub == nullptr) return nullptr; + + // Annotate the stub with a relocation that points to the owning call instruction. + relocate(rh); + DEBUG_ONLY(int stub_start_offset = offset()); + + // For java_to_interp stubs we use R11_scratch1 as scratch register + // and in call trampoline stubs we use R12_scratch2. This way we + // can distinguish them (see is_NativeCallTrampolineStub_at()). + Register reg_scratch = R12_scratch2; + + if (Rmethod_toc == noreg) { + calculate_address_from_global_toc(reg_scratch, method_toc()); + Rmethod_toc = reg_scratch; + } + + ld_largeoffset_unchecked(reg_scratch, target_toc_offset, Rmethod_toc, false); + mtctr(reg_scratch); + bctr(); + + assert(target_toc_offset == NativeCallTrampolineStub_at(addr_at(stub_start_offset))->destination_toc_offset(), + "encoded offset into the constant pool must match"); + assert((uint)(offset() - stub_start_offset) <= trampoline_stub_size, "should be good size"); + assert(is_NativeCallTrampolineStub_at(addr_at(stub_start_offset)), "doesn't look like a trampoline"); + + // End the stub. + end_a_stub(); + } + + // The call will be resolved / patched later. + address call_pc = pc(); + relocate(target.rspec()); + bl(call_pc); + return call_pc; +} + void MacroAssembler::post_call_nop() { // Make inline again when loom is always enabled. if (!Continuations::enabled()) { @@ -2626,50 +2695,6 @@ void MacroAssembler::tlab_allocate( //verify_tlab(); not implemented } -address MacroAssembler::emit_trampoline_stub(int destination_toc_offset, - int insts_call_instruction_offset, Register Rtoc) { - // Start the stub. - address stub = start_a_stub(64); - if (stub == nullptr) { return nullptr; } // CodeCache full: bail out - - // Create a trampoline stub relocation which relates this trampoline stub - // with the call instruction at insts_call_instruction_offset in the - // instructions code-section. - relocate(trampoline_stub_Relocation::spec(code()->insts()->start() + insts_call_instruction_offset)); - const int stub_start_offset = offset(); - - // For java_to_interp stubs we use R11_scratch1 as scratch register - // and in call trampoline stubs we use R12_scratch2. This way we - // can distinguish them (see is_NativeCallTrampolineStub_at()). - Register reg_scratch = R12_scratch2; - - // Now, create the trampoline stub's code: - // - load the TOC - // - load the call target from the constant pool - // - call - if (Rtoc == noreg) { - calculate_address_from_global_toc(reg_scratch, method_toc()); - Rtoc = reg_scratch; - } - - ld_largeoffset_unchecked(reg_scratch, destination_toc_offset, Rtoc, false); - mtctr(reg_scratch); - bctr(); - - const address stub_start_addr = addr_at(stub_start_offset); - - // Assert that the encoded destination_toc_offset can be identified and that it is correct. - assert(destination_toc_offset == NativeCallTrampolineStub_at(stub_start_addr)->destination_toc_offset(), - "encoded offset into the constant pool must match"); - // Trampoline_stub_size should be good. - assert((uint)(offset() - stub_start_offset) <= trampoline_stub_size, "should be good size"); - assert(is_NativeCallTrampolineStub_at(stub_start_addr), "doesn't look like a trampoline"); - - // End the stub. - end_a_stub(); - return stub; -} - // "The box" is the space on the stack where we copy the object mark. void MacroAssembler::compiler_fast_lock_object(ConditionRegister flag, Register oop, Register box, Register temp, Register displaced_header, Register current_header) { @@ -3436,24 +3461,6 @@ void MacroAssembler::store_klass_gap(Register dst_oop, Register val) { } } -int MacroAssembler::instr_size_for_load_klass() { - static int computed_size = -1; - - // Not yet computed? - if (computed_size == -1) { - - // Determine by scratch emit. - ResourceMark rm; - int code_size = 16 * BytesPerInstWord; - CodeBuffer cb("load_klass scratch buffer", code_size, 0); - MacroAssembler* a = new MacroAssembler(&cb); - a->load_klass(R11_scratch1, R11_scratch1); - computed_size = a->offset(); - } - - return computed_size; -} - void MacroAssembler::decode_klass_not_null(Register dst, Register src) { assert(dst != R0, "Dst reg may not be R0, as R0 is used here."); if (src == noreg) src = dst; diff --git a/src/hotspot/cpu/ppc/macroAssembler_ppc.hpp b/src/hotspot/cpu/ppc/macroAssembler_ppc.hpp index ab7bb653d113..f81b2d8ac474 100644 --- a/src/hotspot/cpu/ppc/macroAssembler_ppc.hpp +++ b/src/hotspot/cpu/ppc/macroAssembler_ppc.hpp @@ -380,9 +380,20 @@ class MacroAssembler: public Assembler { Register toc); #endif + // CompiledIC call + bool ic_call(Register Rmethod_toc, + address target, + jint method_index = 0, + bool scratch_emit = false, + bool fixed_size = false); static int ic_check_size(); int ic_check(int end_alignment); + enum { trampoline_stub_size = 6 * 4 }; + address trampoline_call(AddressLiteral target, + Register Rmethod_toc = noreg, + bool scratch_emit = false); + protected: // It is imperative that all calls into the VM are handled via the @@ -704,9 +715,6 @@ class MacroAssembler: public Assembler { Label& slow_case // continuation point if fast allocation fails ); - enum { trampoline_stub_size = 6 * 4 }; - address emit_trampoline_stub(int destination_toc_offset, int insts_call_instruction_offset, Register Rtoc = noreg); - void compiler_fast_lock_object(ConditionRegister flag, Register oop, Register box, Register tmp1, Register tmp2, Register tmp3); @@ -810,7 +818,6 @@ class MacroAssembler: public Assembler { MacroAssembler::PreservationLevel preservation_level); void load_method_holder(Register holder, Register method); - static int instr_size_for_load_klass(); void decode_klass_not_null(Register dst, Register src = noreg); Register encode_klass_not_null(Register dst, Register src = noreg); diff --git a/src/hotspot/cpu/ppc/ppc.ad b/src/hotspot/cpu/ppc/ppc.ad index b68632f0b377..f415105396c9 100644 --- a/src/hotspot/cpu/ppc/ppc.ad +++ b/src/hotspot/cpu/ppc/ppc.ad @@ -1177,18 +1177,7 @@ int MachCallStaticJavaNode::ret_addr_offset() { } int MachCallDynamicJavaNode::ret_addr_offset() { - // Offset is 4 with postalloc expanded calls (bl is one instruction). We use - // postalloc expanded calls if we use inline caches and do not update method data. - if (UseInlineCaches) return 4; - - int vtable_index = this->_vtable_index; - if (vtable_index < 0) { - // Must be invalid_vtable_index, not nonvirtual_vtable_index. - assert(vtable_index == Method::invalid_vtable_index, "correct sentinel value"); - return 12; - } else { - return 20 + MacroAssembler::instr_size_for_load_klass(); - } + return 12; } int MachCallRuntimeNode::ret_addr_offset() { @@ -1311,9 +1300,6 @@ class CallStubImpl { public: - // Emit call stub, compiled java to interpreter. - static void emit_trampoline_stub(C2_MacroAssembler *masm, int destination_toc_offset, int insts_call_instruction_offset); - // Size of call trampoline stub. // This doesn't need to be accurate to the byte, but it // must be larger than or equal to the real size of the stub. @@ -1332,81 +1318,6 @@ class CallStubImpl { source %{ -// Emit a trampoline stub for a call to a target which is too far away. -// -// code sequences: -// -// call-site: -// branch-and-link to or -// -// Related trampoline stub for this call-site in the stub section: -// load the call target from the constant pool -// branch via CTR (LR/link still points to the call-site above) - -void CallStubImpl::emit_trampoline_stub(C2_MacroAssembler *masm, int destination_toc_offset, int insts_call_instruction_offset) { - address stub = __ emit_trampoline_stub(destination_toc_offset, insts_call_instruction_offset); - if (stub == nullptr) { - ciEnv::current()->record_out_of_memory_failure(); - } -} - -//============================================================================= - -// Emit an inline branch-and-link call and a related trampoline stub. -// -// code sequences: -// -// call-site: -// branch-and-link to or -// -// Related trampoline stub for this call-site in the stub section: -// load the call target from the constant pool -// branch via CTR (LR/link still points to the call-site above) -// - -typedef struct { - int insts_call_instruction_offset; - int ret_addr_offset; -} EmitCallOffsets; - -// Emit a branch-and-link instruction that branches to a trampoline. -// - Remember the offset of the branch-and-link instruction. -// - Add a relocation at the branch-and-link instruction. -// - Emit a branch-and-link. -// - Remember the return pc offset. -EmitCallOffsets emit_call_with_trampoline_stub(C2_MacroAssembler *masm, address entry_point, relocInfo::relocType rtype) { - EmitCallOffsets offsets = { -1, -1 }; - const int start_offset = __ offset(); - offsets.insts_call_instruction_offset = __ offset(); - - // No entry point given, use the current pc. - if (entry_point == nullptr) entry_point = __ pc(); - - // Put the entry point as a constant into the constant pool. - const address entry_point_toc_addr = __ address_constant(entry_point, RelocationHolder::none); - if (entry_point_toc_addr == nullptr) { - ciEnv::current()->record_out_of_memory_failure(); - return offsets; - } - const int entry_point_toc_offset = __ offset_to_method_toc(entry_point_toc_addr); - - // Emit the trampoline stub which will be related to the branch-and-link below. - CallStubImpl::emit_trampoline_stub(masm, entry_point_toc_offset, offsets.insts_call_instruction_offset); - if (ciEnv::current()->failing()) { return offsets; } // Code cache may be full. - __ relocate(rtype); - - // Note: At this point we do not have the address of the trampoline - // stub, and the entry point might be too far away for bl, so __ pc() - // serves as dummy and the bl will be patched later. - __ bl((address) __ pc()); - - offsets.ret_addr_offset = __ offset() - start_offset; - - return offsets; -} - -//============================================================================= - // Factory for creating loadConL* nodes for large/small constant pool. static inline jlong replicate_immF(float con) { @@ -3337,205 +3248,50 @@ encode %{ // Usage of r1 and r2 in the stubs allows to distinguish them. enc_class enc_java_static_call(method meth) %{ address entry_point = (address)$meth$$method; + address call_pc; if (!_method) { // A call to a runtime wrapper, e.g. new, new_typeArray_Java, uncommon_trap. - emit_call_with_trampoline_stub(masm, entry_point, relocInfo::runtime_call_type); - if (ciEnv::current()->failing()) { return; } // Code cache may be full. - } else { - // Remember the offset not the address. - const int start_offset = __ offset(); - - // The trampoline stub. - // No entry point given, use the current pc. - // Make sure branch fits into - if (entry_point == nullptr) entry_point = __ pc(); - - // Put the entry point as a constant into the constant pool. - const address entry_point_toc_addr = __ address_constant(entry_point, RelocationHolder::none); - if (entry_point_toc_addr == nullptr) { - ciEnv::current()->record_out_of_memory_failure(); + call_pc = __ trampoline_call(AddressLiteral(entry_point, relocInfo::runtime_call_type)); + if (call_pc == nullptr) { + ciEnv::current()->record_failure("CodeCache is full"); return; } - const int entry_point_toc_offset = __ offset_to_method_toc(entry_point_toc_addr); - - // Emit the trampoline stub which will be related to the branch-and-link below. - CallStubImpl::emit_trampoline_stub(masm, entry_point_toc_offset, start_offset); - if (ciEnv::current()->failing()) { return; } // Code cache may be full. + } else { int method_index = resolved_method_index(masm); - __ relocate(_optimized_virtual ? opt_virtual_call_Relocation::spec(method_index) - : static_call_Relocation::spec(method_index)); - - // The real call. - // Note: At this point we do not have the address of the trampoline - // stub, and the entry point might be too far away for bl, so __ pc() - // serves as dummy and the bl will be patched later. - __ set_inst_mark(); - __ bl(__ pc()); // Emits a relocation. - - // The stub for call to interpreter. - address stub = CompiledDirectCall::emit_to_interp_stub(masm); - __ clear_inst_mark(); - if (stub == nullptr) { + RelocationHolder rspec = _optimized_virtual ? opt_virtual_call_Relocation::spec(method_index) + : static_call_Relocation::spec(method_index); + call_pc = __ trampoline_call(AddressLiteral(entry_point, rspec)); + if (call_pc == nullptr) { ciEnv::current()->record_failure("CodeCache is full"); return; } - } - __ post_call_nop(); - %} - // Second node of expanded dynamic call - the call. - enc_class enc_java_dynamic_call_sched(method meth) %{ - if (!ra_->C->output()->in_scratch_emit_size()) { - // Create a call trampoline stub for the given method. - const address entry_point = !($meth$$method) ? nullptr : (address)$meth$$method; - const address entry_point_const = __ address_constant(entry_point, RelocationHolder::none); - if (entry_point_const == nullptr) { - ciEnv::current()->record_out_of_memory_failure(); + // Emit stub for static call + address stub = CompiledDirectCall::emit_to_interp_stub(masm, call_pc); + if (stub == nullptr) { + ciEnv::current()->record_failure("CodeCache is full"); return; } - const int entry_point_const_toc_offset = __ offset_to_method_toc(entry_point_const); - CallStubImpl::emit_trampoline_stub(masm, entry_point_const_toc_offset, __ offset()); - if (ra_->C->env()->failing()) { return; } // Code cache may be full. - - // Build relocation at call site with ic position as data. - assert((_load_ic_hi_node != nullptr && _load_ic_node == nullptr) || - (_load_ic_hi_node == nullptr && _load_ic_node != nullptr), - "must have one, but can't have both"); - assert((_load_ic_hi_node != nullptr && _load_ic_hi_node->_cbuf_insts_offset != -1) || - (_load_ic_node != nullptr && _load_ic_node->_cbuf_insts_offset != -1), - "must contain instruction offset"); - const int virtual_call_oop_addr_offset = _load_ic_hi_node != nullptr - ? _load_ic_hi_node->_cbuf_insts_offset - : _load_ic_node->_cbuf_insts_offset; - const address virtual_call_oop_addr = __ addr_at(virtual_call_oop_addr_offset); - assert(MacroAssembler::is_load_const_from_method_toc_at(virtual_call_oop_addr), - "should be load from TOC"); - int method_index = resolved_method_index(masm); - __ relocate(virtual_call_Relocation::spec(virtual_call_oop_addr, method_index)); } - - // At this point I do not have the address of the trampoline stub, - // and the entry point might be too far away for bl. Pc() serves - // as dummy and bl will be patched later. - __ bl((address) __ pc()); __ post_call_nop(); %} - // postalloc expand emitter for virtual calls. - enc_class postalloc_expand_java_dynamic_call_sched(method meth, iRegLdst toc) %{ - - // Create the nodes for loading the IC from the TOC. - loadConLNodesTuple loadConLNodes_IC = - loadConLNodesTuple_create(ra_, n_toc, new immLOper((jlong) Universe::non_oop_word()), - OptoReg::Name(R19_H_num), OptoReg::Name(R19_num)); - - // Create the call node. - CallDynamicJavaDirectSchedNode *call = new CallDynamicJavaDirectSchedNode(); - call->_method_handle_invoke = _method_handle_invoke; - call->_vtable_index = _vtable_index; - call->_method = _method; - call->_optimized_virtual = _optimized_virtual; - call->_tf = _tf; - call->_entry_point = _entry_point; - call->_cnt = _cnt; - call->_guaranteed_safepoint = true; - call->_oop_map = _oop_map; - call->_jvms = _jvms; - call->_jvmadj = _jvmadj; - call->_has_ea_local_in_scope = _has_ea_local_in_scope; - call->_in_rms = _in_rms; - call->_nesting = _nesting; - call->_override_symbolic_info = _override_symbolic_info; - call->_arg_escape = _arg_escape; - - // New call needs all inputs of old call. - // Req... - for (uint i = 0; i < req(); ++i) { - // The expanded node does not need toc any more. - // Add the inline cache constant here instead. This expresses the - // register of the inline cache must be live at the call. - // Else we would have to adapt JVMState by -1. - if (i == mach_constant_base_node_input()) { - call->add_req(loadConLNodes_IC._last); - } else { - call->add_req(in(i)); - } - } - // ...as well as prec - for (uint i = req(); i < len(); ++i) { - call->add_prec(in(i)); - } - - // Remember nodes loading the inline cache into r19. - call->_load_ic_hi_node = loadConLNodes_IC._large_hi; - call->_load_ic_node = loadConLNodes_IC._small; - - // Operands for new nodes. - call->_opnds[0] = _opnds[0]; - call->_opnds[1] = _opnds[1]; - - // Only the inline cache is associated with a register. - assert(Matcher::inline_cache_reg() == OptoReg::Name(R19_num), "ic reg should be R19"); - - // Push new nodes. - if (loadConLNodes_IC._large_hi) nodes->push(loadConLNodes_IC._large_hi); - if (loadConLNodes_IC._last) nodes->push(loadConLNodes_IC._last); - nodes->push(call); - %} - // Compound version of call dynamic // Toc is only passed so that it can be used in ins_encode statement. // In the code we have to use $constanttablebase. enc_class enc_java_dynamic_call(method meth, iRegLdst toc) %{ int start_offset = __ offset(); - - Register Rtoc = (ra_) ? $constanttablebase : R2_TOC; - - int vtable_index = this->_vtable_index; - if (vtable_index < 0) { - // Must be invalid_vtable_index, not nonvirtual_vtable_index. - assert(vtable_index == Method::invalid_vtable_index, "correct sentinel value"); - Register ic_reg = as_Register(Matcher::inline_cache_reg_encode()); - - // Virtual call relocation will point to ic load. - address virtual_call_meta_addr = __ pc(); - // Load a clear inline cache. - AddressLiteral empty_ic((address) Universe::non_oop_word()); - bool success = __ load_const_from_method_toc(ic_reg, empty_ic, Rtoc, /*fixed_size*/ true); - if (!success) { - ciEnv::current()->record_out_of_memory_failure(); - return; - } - // CALL to fixup routine. Fixup routine uses ScopeDesc info - // to determine who we intended to call. - __ relocate(virtual_call_Relocation::spec(virtual_call_meta_addr)); - emit_call_with_trampoline_stub(masm, (address)$meth$$method, relocInfo::none); - if (ciEnv::current()->failing()) { return; } // Code cache may be full. - assert(((MachCallDynamicJavaNode*)this)->ret_addr_offset() == __ offset() - start_offset, - "Fix constant in ret_addr_offset(), expected %d", __ offset() - start_offset); - } else { - assert(!UseInlineCaches, "expect vtable calls only if not using ICs"); - // Go thru the vtable. Get receiver klass. Receiver already - // checked for non-null. If we'll go thru a C2I adapter, the - // interpreter expects method in R19_method. - - __ load_klass(R11_scratch1, R3); - - int entry_offset = in_bytes(Klass::vtable_start_offset()) + vtable_index * vtableEntry::size_in_bytes(); - int v_off = entry_offset + in_bytes(vtableEntry::method_offset()); - __ li(R19_method, v_off); - __ ldx(R19_method/*method*/, R19_method/*method offset*/, R11_scratch1/*class*/); - // NOTE: for vtable dispatches, the vtable entry will never be - // null. However it may very well end up in handle_wrong_method - // if the method is abstract for the particular class. - __ ld(R11_scratch1, in_bytes(Method::from_compiled_offset()), R19_method); - // Call target. Either compiled code or C2I adapter. - __ mtctr(R11_scratch1); - __ bctrl(); - assert(((MachCallDynamicJavaNode*)this)->ret_addr_offset() == __ offset() - start_offset, - "Fix constant in ret_addr_offset(), expected %d", __ offset() - start_offset); + int method_index = resolved_method_index(masm); + bool scratch_emit = ra_ == nullptr; + Register Rtoc = scratch_emit ? R2_TOC : $constanttablebase; + bool success = __ ic_call(Rtoc, (address)$meth$$method, method_index, scratch_emit, true /*fixed_size*/); + if (!success) { + ciEnv::current()->record_failure("CodeCache is full"); + return; } + assert(((MachCallDynamicJavaNode*)this)->ret_addr_offset() == __ offset() - start_offset, + "Fix constant in ret_addr_offset(), expected %d", __ offset() - start_offset); __ post_call_nop(); %} @@ -13928,15 +13684,14 @@ instruct safePoint_poll(iRegPdst poll) %{ // ============================================================================ // Call Instructions -// Call Java Static Instruction - source %{ #include "runtime/continuation.hpp" %} -// Schedulable version of call static node. +// Call Java Static Instruction + instruct CallStaticJavaDirect(method meth) %{ match(CallStaticJava); effect(USE meth); @@ -13952,51 +13707,9 @@ instruct CallStaticJavaDirect(method meth) %{ // Call Java Dynamic Instruction -// Used by postalloc expand of CallDynamicJavaDirectSchedEx (actual call). -// Loading of IC was postalloc expanded. The nodes loading the IC are reachable -// via fields ins_field_load_ic_hi_node and ins_field_load_ic_node. -// The call destination must still be placed in the constant pool. -instruct CallDynamicJavaDirectSched(method meth) %{ - match(CallDynamicJava); // To get all the data fields we need ... - effect(USE meth); - predicate(false); // ... but never match. - - ins_field_load_ic_hi_node(loadConL_hiNode*); - ins_field_load_ic_node(loadConLNode*); - ins_num_consts(1 /* 1 patchable constant: call destination */); - - format %{ "BL \t// dynamic $meth ==> " %} - size((Continuations::enabled() ? 8 : 4)); - ins_encode( enc_java_dynamic_call_sched(meth) ); - ins_pipe(pipe_class_call); -%} - -// Schedulable (i.e. postalloc expanded) version of call dynamic java. -// We use postalloc expanded calls if we use inline caches -// and do not update method data. -// -// This instruction has two constants: inline cache (IC) and call destination. -// Loading the inline cache will be postalloc expanded, thus leaving a call with -// one constant. -instruct CallDynamicJavaDirectSched_Ex(method meth) %{ - match(CallDynamicJava); - effect(USE meth); - predicate(UseInlineCaches); - ins_cost(CALL_COST); - - ins_num_consts(2 /* 2 patchable constants: inline cache, call destination. */); - - format %{ "CALL,dynamic $meth \t// postalloc expanded" %} - postalloc_expand( postalloc_expand_java_dynamic_call_sched(meth, constanttablebase) ); -%} - -// Compound version of call dynamic java -// We use postalloc expanded calls if we use inline caches -// and do not update method data. instruct CallDynamicJavaDirect(method meth) %{ match(CallDynamicJava); effect(USE meth); - predicate(!UseInlineCaches); ins_cost(CALL_COST); // Enc_java_to_runtime_call needs up to 4 constants (method data oop). diff --git a/src/hotspot/cpu/ppc/sharedRuntime_ppc.cpp b/src/hotspot/cpu/ppc/sharedRuntime_ppc.cpp index 8d34f494d96b..ee729cf7d39f 100644 --- a/src/hotspot/cpu/ppc/sharedRuntime_ppc.cpp +++ b/src/hotspot/cpu/ppc/sharedRuntime_ppc.cpp @@ -1794,10 +1794,8 @@ static void gen_continuation_enter(MacroAssembler* masm, check_continuation_enter_argument(regs[pos_is_cont].first(), reg_is_cont, "isContinue"); check_continuation_enter_argument(regs[pos_is_virtual].first(), reg_is_virtual, "isVirtualThread"); - address resolve_static_call = SharedRuntime::get_resolve_static_call_stub(); - + AddressLiteral resolve(SharedRuntime::get_resolve_static_call_stub(), relocInfo::static_call_type); address start = __ pc(); - Label L_thaw, L_exit; // i2i entry used at interp_only_mode only @@ -1834,33 +1832,17 @@ static void gen_continuation_enter(MacroAssembler* masm, // Emit compiled static call. The call will be always resolved to the c2i // entry of Continuation.enter(Continuation c, boolean isContinue). - // There are special cases in SharedRuntime::resolve_static_call_C() and - // SharedRuntime::resolve_sub_helper_internal() to achieve this - // See also corresponding call below. - address c2i_call_pc = __ pc(); - int start_offset = __ offset(); - // Put the entry point as a constant into the constant pool. - const address entry_point_toc_addr = __ address_constant(resolve_static_call, RelocationHolder::none); - const int entry_point_toc_offset = __ offset_to_method_toc(entry_point_toc_addr); - guarantee(entry_point_toc_addr != nullptr, "const section overflow"); - - // Emit the trampoline stub which will be related to the branch-and-link below. - address stub = __ emit_trampoline_stub(entry_point_toc_offset, start_offset); - guarantee(stub != nullptr, "no space for trampoline stub"); - - __ relocate(relocInfo::static_call_type); - // Note: At this point we do not have the address of the trampoline - // stub, and the entry point might be too far away for bl, so __ pc() - // serves as dummy and the bl will be patched later. - __ bl(__ pc()); + address c2i_call_pc = __ trampoline_call(resolve); + guarantee(c2i_call_pc != nullptr, "CodeCache is full at gen_continuation_enter"); + + // Emit stub for static call + address stub = CompiledDirectCall::emit_to_interp_stub(masm, c2i_call_pc); + guarantee(stub != nullptr, "CodeCache is full at gen_continuation_enter"); + oop_maps->add_gc_map(__ pc() - start, map); __ post_call_nop(); __ b(L_exit); - - // static stub for the call above - stub = CompiledDirectCall::emit_to_interp_stub(masm, c2i_call_pc); - guarantee(stub != nullptr, "no space for static stub"); } // compiled entry @@ -1885,22 +1867,9 @@ static void gen_continuation_enter(MacroAssembler* masm, // SharedRuntime::find_callee_info_helper() which calls // LinkResolver::resolve_continuation_enter() which resolves the call to // Continuation.enter(Continuation c, boolean isContinue). - address call_pc = __ pc(); - int start_offset = __ offset(); - // Put the entry point as a constant into the constant pool. - const address entry_point_toc_addr = __ address_constant(resolve_static_call, RelocationHolder::none); - const int entry_point_toc_offset = __ offset_to_method_toc(entry_point_toc_addr); - guarantee(entry_point_toc_addr != nullptr, "const section overflow"); - - // Emit the trampoline stub which will be related to the branch-and-link below. - address stub = __ emit_trampoline_stub(entry_point_toc_offset, start_offset); - guarantee(stub != nullptr, "no space for trampoline stub"); - - __ relocate(relocInfo::static_call_type); - // Note: At this point we do not have the address of the trampoline - // stub, and the entry point might be too far away for bl, so __ pc() - // serves as dummy and the bl will be patched later. - __ bl(__ pc()); + address call_pc = __ trampoline_call(resolve); + guarantee(call_pc != nullptr, "CodeCache is full at gen_continuation_enter"); + oop_maps->add_gc_map(__ pc() - start, map); __ post_call_nop(); @@ -1953,8 +1922,8 @@ static void gen_continuation_enter(MacroAssembler* masm, __ blr(); // static stub for the call above - stub = CompiledDirectCall::emit_to_interp_stub(masm, call_pc); - guarantee(stub != nullptr, "no space for static stub"); + address stub = CompiledDirectCall::emit_to_interp_stub(masm, call_pc); + guarantee(stub != nullptr, "CodeCache is full at gen_continuation_enter"); } static void gen_continuation_yield(MacroAssembler* masm, From a432e7e3a946073179500d410932277537a24c23 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 6 Jul 2026 16:28:41 +0000 Subject: [PATCH 50/86] 8365426: [macos26] Graphics2D tests fail on new macOS 26 Backport-of: 7c169c9814a694126f524e8941b1035e6695900c --- 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 27a1a3a868a9..9ee44037f14d 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.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 static void main(String[] args) throws Exception { 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 void paint(Graphics g) { 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 void paint(Graphics g) { 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 Dimension getPreferredSize() { 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 @@ private static void saveImages() { 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 074560f21bf55f408247830fe544fda8716adbc2 Mon Sep 17 00:00:00 2001 From: Ashay Rane Date: Wed, 8 Jul 2026 18:02:39 +0000 Subject: [PATCH 51/86] 8384804: JMX remote bootstrap tests fail on Windows Backport-of: ea99ed616f067268ebfd5b78e428e1835a84fe08 --- test/lib/jdk/test/lib/Utils.java | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/test/lib/jdk/test/lib/Utils.java b/test/lib/jdk/test/lib/Utils.java index 2f46ed873402..8801449c9bbf 100644 --- a/test/lib/jdk/test/lib/Utils.java +++ b/test/lib/jdk/test/lib/Utils.java @@ -44,15 +44,18 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.attribute.AclEntry; +import java.nio.file.attribute.AclEntryPermission; import java.nio.file.attribute.AclEntryType; import java.nio.file.attribute.AclFileAttributeView; import java.nio.file.attribute.FileAttribute; +import java.nio.file.attribute.UserPrincipal; import java.nio.channels.SocketChannel; import java.nio.file.attribute.PosixFilePermissions; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.EnumSet; import java.util.HexFormat; import java.util.Iterator; import java.util.Map; @@ -1026,25 +1029,18 @@ public static void grantFileAccess(Path file, boolean userOnly) } else if (attr.contains("acl")) { AclFileAttributeView view = Files.getFileAttributeView(file, AclFileAttributeView.class); + UserPrincipal everyone = file.getFileSystem() + .getUserPrincipalLookupService() + .lookupPrincipalByName("Everyone"); + UserPrincipal principal = userOnly ? view.getOwner() : everyone; + EnumSet allPermissions = EnumSet.allOf(AclEntryPermission.class); + List acl = new ArrayList<>(); - for (AclEntry thisEntry : view.getAcl()) { - if (userOnly) { - if (thisEntry.principal().getName() - .equals(view.getOwner().getName())) { - acl.add(allowAccess(thisEntry)); - } else if (thisEntry.type() == AclEntryType.ALLOW) { - acl.add(revokeAccess(thisEntry)); - } else { - acl.add(thisEntry); - } - } else { - if (thisEntry.type() != AclEntryType.ALLOW) { - acl.add(allowAccess(thisEntry)); - } else { - acl.add(thisEntry); - } - } - } + acl.add(AclEntry.newBuilder() + .setType(AclEntryType.ALLOW) + .setPrincipal(principal) + .setPermissions(allPermissions) + .build()); view.setAcl(acl); } else { throw new RuntimeException("Unsupported file attributes: " + attr); From d3342feae070824f9e2b289a4821c7b897d8825b Mon Sep 17 00:00:00 2001 From: Ashay Rane Date: Wed, 8 Jul 2026 18:05:31 +0000 Subject: [PATCH 52/86] 8375352: java/net/httpclient/ConnectTimeoutWithProxy*.java tests fail on EC2 Backport-of: ea90214ce90c916dd5145c09de6960f038843326 --- .../httpclient/AbstractConnectTimeout.java | 258 ------------ .../ConnectTimeoutNoProxyAsync.java | 47 --- .../httpclient/ConnectTimeoutNoProxySync.java | 48 --- .../net/httpclient/ConnectTimeoutTest.java | 370 ++++++++++++++++++ .../ConnectTimeoutWithProxyAsync.java | 47 --- .../ConnectTimeoutWithProxySync.java | 48 --- 6 files changed, 370 insertions(+), 448 deletions(-) delete mode 100644 test/jdk/java/net/httpclient/AbstractConnectTimeout.java delete mode 100644 test/jdk/java/net/httpclient/ConnectTimeoutNoProxyAsync.java delete mode 100644 test/jdk/java/net/httpclient/ConnectTimeoutNoProxySync.java create mode 100644 test/jdk/java/net/httpclient/ConnectTimeoutTest.java delete mode 100644 test/jdk/java/net/httpclient/ConnectTimeoutWithProxyAsync.java delete mode 100644 test/jdk/java/net/httpclient/ConnectTimeoutWithProxySync.java diff --git a/test/jdk/java/net/httpclient/AbstractConnectTimeout.java b/test/jdk/java/net/httpclient/AbstractConnectTimeout.java deleted file mode 100644 index 2c4909191816..000000000000 --- a/test/jdk/java/net/httpclient/AbstractConnectTimeout.java +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Copyright (c) 2018, 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 java.net.ConnectException; -import java.net.InetSocketAddress; -import java.net.NoRouteToHostException; -import java.net.ProxySelector; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpClient.Version; -import java.net.http.HttpConnectTimeoutException; -import java.net.http.HttpRequest; -import java.net.http.HttpRequest.BodyPublishers; -import java.net.http.HttpResponse; -import java.net.http.HttpResponse.BodyHandlers; -import java.nio.channels.UnresolvedAddressException; -import java.time.Duration; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.CompletionException; -import org.testng.annotations.DataProvider; -import static java.lang.System.out; -import static java.net.http.HttpClient.Builder.NO_PROXY; -import static java.net.http.HttpClient.Version.HTTP_1_1; -import static java.net.http.HttpClient.Version.HTTP_2; -import static java.time.Duration.*; -import static java.util.concurrent.TimeUnit.NANOSECONDS; -import static org.testng.Assert.fail; - -public abstract class AbstractConnectTimeout { - - static final Duration NO_DURATION = null; - - static List> TIMEOUTS = List.of( - // connectTimeout HttpRequest timeout - Arrays.asList( NO_DURATION, ofMillis(100) ), - Arrays.asList( NO_DURATION, ofNanos(1) ), - - Arrays.asList( ofMillis(100), NO_DURATION ), - Arrays.asList( ofNanos(1), NO_DURATION ), - - Arrays.asList( ofMillis(100), ofMinutes(1) ), - Arrays.asList( ofNanos(1), ofMinutes(1) ) - ); - - static final List METHODS = List.of("GET", "POST"); - static final List VERSIONS = List.of(HTTP_2, HTTP_1_1); - static final List SCHEMES = List.of("https", "http"); - - @DataProvider(name = "variants") - public Object[][] variants() { - List l = new ArrayList<>(); - for (List timeouts : TIMEOUTS) { - Duration connectTimeout = timeouts.get(0); - Duration requestTimeout = timeouts.get(1); - for (String method: METHODS) { - for (String scheme : SCHEMES) { - for (Version requestVersion : VERSIONS) { - l.add(new Object[] {requestVersion, scheme, method, connectTimeout, requestTimeout}); - }}}} - return l.stream().toArray(Object[][]::new); - } - - static final ProxySelector EXAMPLE_DOT_COM_PROXY = ProxySelector.of( - InetSocketAddress.createUnresolved("example.com", 8080)); - - //@Test(dataProvider = "variants") - protected void timeoutNoProxySync(Version requestVersion, - String scheme, - String method, - Duration connectTimeout, - Duration requestTimeout) - throws Exception - { - timeoutSync(requestVersion, scheme, method, connectTimeout, requestTimeout, NO_PROXY); - } - - //@Test(dataProvider = "variants") - protected void timeoutWithProxySync(Version requestVersion, - String scheme, - String method, - Duration connectTimeout, - Duration requestTimeout) - throws Exception - { - timeoutSync(requestVersion, scheme, method, connectTimeout, requestTimeout, EXAMPLE_DOT_COM_PROXY); - } - - private void timeoutSync(Version requestVersion, - String scheme, - String method, - Duration connectTimeout, - Duration requestTimeout, - ProxySelector proxy) - throws Exception - { - out.printf("%ntimeoutSync(requestVersion=%s, scheme=%s, method=%s," - + " connectTimeout=%s, requestTimeout=%s, proxy=%s)%n", - requestVersion, scheme, method, connectTimeout, requestTimeout, proxy); - - HttpClient client = newClient(connectTimeout, proxy); - HttpRequest request = newRequest(scheme, requestVersion, method, requestTimeout); - - for (int i = 0; i < 2; i++) { - out.printf("iteration %d%n", i); - long startTime = System.nanoTime(); - try { - HttpResponse resp = client.send(request, BodyHandlers.ofString()); - printResponse(resp); - fail("Unexpected response: " + resp); - } catch (HttpConnectTimeoutException expected) { // blocking thread-specific exception - long elapsedTime = NANOSECONDS.toMillis(System.nanoTime() - startTime); - out.printf("Client: received in %d millis%n", elapsedTime); - assertExceptionTypeAndCause(expected.getCause()); - } catch (ConnectException e) { - long elapsedTime = NANOSECONDS.toMillis(System.nanoTime() - startTime); - out.printf("Client: received in %d millis%n", elapsedTime); - Throwable t = e.getCause().getCause(); // blocking thread-specific exception - if (!isAcceptableCause(t)) { // tolerate only NRTHE or UAE - e.printStackTrace(out); - fail("Unexpected exception:" + e); - } else { - out.printf("Caught ConnectException with " - + " cause: %s - skipping%n", t.getCause()); - } - } - } - } - - //@Test(dataProvider = "variants") - protected void timeoutNoProxyAsync(Version requestVersion, - String scheme, - String method, - Duration connectTimeout, - Duration requestTimeout) { - timeoutAsync(requestVersion, scheme, method, connectTimeout, requestTimeout, NO_PROXY); - } - - //@Test(dataProvider = "variants") - protected void timeoutWithProxyAsync(Version requestVersion, - String scheme, - String method, - Duration connectTimeout, - Duration requestTimeout) { - timeoutAsync(requestVersion, scheme, method, connectTimeout, requestTimeout, EXAMPLE_DOT_COM_PROXY); - } - - private void timeoutAsync(Version requestVersion, - String scheme, - String method, - Duration connectTimeout, - Duration requestTimeout, - ProxySelector proxy) { - out.printf("%ntimeoutAsync(requestVersion=%s, scheme=%s, method=%s, " - + "connectTimeout=%s, requestTimeout=%s, proxy=%s)%n", - requestVersion, scheme, method, connectTimeout, requestTimeout, proxy); - - HttpClient client = newClient(connectTimeout, proxy); - HttpRequest request = newRequest(scheme, requestVersion, method, requestTimeout); - for (int i = 0; i < 2; i++) { - out.printf("iteration %d%n", i); - long startTime = System.nanoTime(); - try { - HttpResponse resp = client.sendAsync(request, BodyHandlers.ofString()).join(); - printResponse(resp); - fail("Unexpected response: " + resp); - } catch (CompletionException e) { - long elapsedTime = NANOSECONDS.toMillis(System.nanoTime() - startTime); - out.printf("Client: received in %d millis%n", elapsedTime); - Throwable t = e.getCause(); - if (t instanceof ConnectException && isAcceptableCause(t.getCause())) { - // tolerate only NRTHE and UAE - out.printf("Caught ConnectException with " - + "cause: %s - skipping%n", t.getCause()); - } else { - assertExceptionTypeAndCause(t); - } - } - } - } - - static boolean isAcceptableCause(Throwable cause) { - if (cause instanceof NoRouteToHostException) return true; - if (cause instanceof UnresolvedAddressException) return true; - return false; - } - - static HttpClient newClient(Duration connectTimeout, ProxySelector proxy) { - HttpClient.Builder builder = HttpClient.newBuilder().proxy(proxy); - if (connectTimeout != NO_DURATION) - builder.connectTimeout(connectTimeout); - return builder.build(); - } - - static HttpRequest newRequest(String scheme, - Version reqVersion, - String method, - Duration requestTimeout) { - // Resolvable address. Most tested environments just ignore the TCP SYN, - // or occasionally return ICMP no route to host - URI uri = URI.create(scheme +"://example.com:81/"); - HttpRequest.Builder reqBuilder = HttpRequest.newBuilder(uri); - reqBuilder = reqBuilder.version(reqVersion); - switch (method) { - case "GET" : reqBuilder.GET(); break; - case "POST" : reqBuilder.POST(BodyPublishers.noBody()); break; - default: throw new AssertionError("Unknown method:" + method); - } - if (requestTimeout != NO_DURATION) - reqBuilder.timeout(requestTimeout); - return reqBuilder.build(); - } - - static void assertExceptionTypeAndCause(Throwable t) { - if (!(t instanceof HttpConnectTimeoutException)) { - t.printStackTrace(out); - fail("Expected HttpConnectTimeoutException, got:" + t); - } - Throwable connEx = t.getCause(); - if (!(connEx instanceof ConnectException)) { - t.printStackTrace(out); - fail("Expected ConnectException cause in:" + connEx); - } - out.printf("Caught expected HttpConnectTimeoutException with ConnectException" - + " cause: %n%s%n%s%n", t, connEx); - final String EXPECTED_MESSAGE = "HTTP connect timed out"; // impl dependent - if (!connEx.getMessage().equals(EXPECTED_MESSAGE)) - fail("Expected: \"" + EXPECTED_MESSAGE + "\", got: \"" + connEx.getMessage() + "\""); - - } - - static void printResponse(HttpResponse response) { - out.println("Unexpected response: " + response); - out.println("Headers: " + response.headers()); - out.println("Body: " + response.body()); - } -} diff --git a/test/jdk/java/net/httpclient/ConnectTimeoutNoProxyAsync.java b/test/jdk/java/net/httpclient/ConnectTimeoutNoProxyAsync.java deleted file mode 100644 index ace12cd02959..000000000000 --- a/test/jdk/java/net/httpclient/ConnectTimeoutNoProxyAsync.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. - * 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.net.http.HttpClient.Version; -import java.time.Duration; -import org.testng.annotations.Test; - -/* - * @test - * @summary Tests for connection related timeouts - * @bug 8208391 - * @run testng/othervm ConnectTimeoutNoProxyAsync - */ - -public class ConnectTimeoutNoProxyAsync extends AbstractConnectTimeout { - - @Test(dataProvider = "variants") - @Override - public void timeoutNoProxyAsync(Version requestVersion, - String scheme, - String method, - Duration connectTimeout, - Duration requestduration) - { - super.timeoutNoProxyAsync(requestVersion, scheme, method, connectTimeout, requestduration); - } -} diff --git a/test/jdk/java/net/httpclient/ConnectTimeoutNoProxySync.java b/test/jdk/java/net/httpclient/ConnectTimeoutNoProxySync.java deleted file mode 100644 index f30dea6deea0..000000000000 --- a/test/jdk/java/net/httpclient/ConnectTimeoutNoProxySync.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. - * 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.net.http.HttpClient.Version; -import java.time.Duration; -import org.testng.annotations.Test; - -/* - * @test - * @summary Tests for connection related timeouts - * @bug 8208391 - * @run testng/othervm ConnectTimeoutNoProxySync - */ - -public class ConnectTimeoutNoProxySync extends AbstractConnectTimeout { - - @Test(dataProvider = "variants") - @Override - public void timeoutNoProxySync(Version requestVersion, - String scheme, - String method, - Duration connectTimeout, - Duration requestTimeout) - throws Exception - { - super.timeoutNoProxySync(requestVersion, scheme, method, connectTimeout, requestTimeout); - } -} diff --git a/test/jdk/java/net/httpclient/ConnectTimeoutTest.java b/test/jdk/java/net/httpclient/ConnectTimeoutTest.java new file mode 100644 index 000000000000..c837071ae7c0 --- /dev/null +++ b/test/jdk/java/net/httpclient/ConnectTimeoutTest.java @@ -0,0 +1,370 @@ +/* + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. + * 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.Utils; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.io.IOException; +import java.io.PrintStream; +import java.net.ConnectException; +import java.net.InetAddress; +import java.net.Proxy; +import java.net.ProxySelector; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.SocketTimeoutException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpClient.Version; +import java.net.http.HttpConnectTimeoutException; +import java.net.http.HttpRequest; +import java.net.http.HttpRequest.BodyPublishers; +import java.net.http.HttpResponse; +import java.net.http.HttpResponse.BodyHandlers; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletionException; +import java.util.stream.Stream; + +import static java.lang.Boolean.parseBoolean; +import static java.net.http.HttpClient.Builder.NO_PROXY; +import static java.net.http.HttpClient.Version.HTTP_1_1; +import static java.net.http.HttpClient.Version.HTTP_2; +import static java.time.Duration.*; +import static java.util.concurrent.TimeUnit.NANOSECONDS; +import static org.junit.jupiter.api.Assertions.fail; + +/* + * @test id=sync + * @bug 8208391 8375352 + * @summary Verifies behavior on `connect()` timeouts + * @requires os.family != "windows" + * @library /test/lib + * @run junit/othervm ${test.main.class} + */ + +/* + * @test id=sync-proxy + * @bug 8208391 8375352 + * @summary Verifies behavior on `connect()` timeouts + * @requires os.family != "windows" + * @library /test/lib + * @run junit/othervm -Dtest.proxy=true ${test.main.class} + */ + +/* + * @test id=async + * @bug 8208391 8375352 + * @summary Verifies behavior on `connect()` timeouts + * @requires os.family != "windows" + * @library /test/lib + * @run junit/othervm -Dtest.async=true ${test.main.class} + */ + +/* + * @test id=async-proxy + * @bug 8208391 8375352 + * @summary Verifies behavior on `connect()` timeouts + * @requires os.family != "windows" + * @library /test/lib + * @run junit/othervm -Dtest.async=true -Dtest.proxy=true ${test.main.class} + */ + +class ConnectTimeoutTest { + + // This test verifies the `HttpClient` behavior on `connect()` failures. + // + // Earlier, the test was trying to connect `example.com:8080` to trigger a `connect()` failure. + // This worked, until it doesn't — `example.com:8080` started responding in certain test environments. + // + // Now we create a `ServerSocket` and exhaust all its "SYN backlog" and "Accept queue". + // The expectation is that the platform socket in this state will block on `connect()`. + // Well... It doesn't on Windows, whereas it does on Linux and macOS. + // Windows doesn't block and immediately responds with `java.net.ConnectException: Connection refused: connect`. + // Neither it is deterministic how many connections are needed to exhaust a socket admission queue. + // Hence, we took the following decisions: + // + // 1. Skip this test on Windows + // 2. Exhaust server socket admission queue by going into a loop + + private static final PrintStream LOGGER = System.out; + + private static final int BACKLOG = 1; + + /** + * A {@link ServerSocket} whose admission will be blocked by exhausting all its "SYN backlog" and "Accept queue". + */ + private static final ServerSocket SERVER_SOCKET = createServerSocket(); + + /** + * Client sockets exhausting the admission to {@link #SERVER_SOCKET}. + */ + private static final List CLIENT_SOCKETS = createClientSocketsExhaustingServerSocketAdmission(); + + private static ServerSocket createServerSocket() { + try { + LOGGER.println("Creating server socket"); + return new ServerSocket(0, BACKLOG, InetAddress.getLoopbackAddress()); + } catch (Exception exception) { + throw new RuntimeException(exception); + } + } + + private static List createClientSocketsExhaustingServerSocketAdmission() { + List sockets = new ArrayList<>(); + int maxSocketCount = BACKLOG // To fill up the backlog + + 512; // Giving some slack, should be enough to exhaust the admission queue. + int connectTimeout = Math.toIntExact(Math.addExact(500, Utils.adjustTimeout(500))); + int socketIndex = 0; + for (; socketIndex < maxSocketCount; socketIndex++) { + try { + LOGGER.printf( + "Creating client socket %s/%s to exhaust the server socket admission%n", + (socketIndex + 1), maxSocketCount); + Socket socket = new Socket(); + socket.connect(SERVER_SOCKET.getLocalSocketAddress(), connectTimeout); + sockets.add(socket); + } catch (ConnectException | SocketTimeoutException exception) { + LOGGER.printf( + "Received expected `%s` while creating client socket %s/%s%n", + exception.getClass().getName(), (socketIndex + 1), maxSocketCount); + return sockets; + } catch (IOException ioe) { + String message = String.format( + "Received unexpected exception while creating client socket %s/%s", + (socketIndex + 1), maxSocketCount); + closeSockets(SERVER_SOCKET, sockets); + throw new RuntimeException(message, ioe); + } + } + String message = String.format( + "Connected %s sockets, but still could not exhaust the socket admission", + maxSocketCount); + closeSockets(SERVER_SOCKET, sockets); + throw new RuntimeException(message); + } + + @AfterAll + public static void closeSockets() { + closeSockets(SERVER_SOCKET, CLIENT_SOCKETS); + } + + private static void closeSockets(ServerSocket serverSocket, List clientSockets) { + Throwable[] throwable = {null}; + Stream.concat(clientSockets.stream(), Stream.of(serverSocket)).forEach(closeable -> { + try { + closeable.close(); + } catch (Exception exception) { + if (throwable[0] == null) { + throwable[0] = exception; + } else { + throwable[0].addSuppressed(exception); + } + } + }); + if (throwable[0] != null) { + throwable[0].printStackTrace(System.out); + } + } + + /** + * {@link ProxySelector} always pointing to {@link #SERVER_SOCKET}. + */ + private static final ProxySelector PROXY_SELECTOR = new ProxySelector() { + + private static final List PROXIES = + List.of(new Proxy(Proxy.Type.HTTP, SERVER_SOCKET.getLocalSocketAddress())); + + @Override + public List select(URI uri) { + return PROXIES; + } + + @Override + public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { + // Do nothing + } + + }; + + private static final Duration NO_DURATION = null; + + private static List> TIMEOUTS = List.of( + // connectTimeout HttpRequest timeout + Arrays.asList( NO_DURATION, ofMillis(100) ), + Arrays.asList( NO_DURATION, ofNanos(1) ), + + Arrays.asList( ofMillis(100), NO_DURATION ), + Arrays.asList( ofNanos(1), NO_DURATION ), + + Arrays.asList( ofMillis(100), ofMinutes(1) ), + Arrays.asList( ofNanos(1), ofMinutes(1) ) + ); + + private static final List METHODS = List.of("GET", "POST"); + private static final List VERSIONS = List.of(HTTP_2, HTTP_1_1); + private static final List SCHEMES = List.of("https", "http"); + + static Object[][] variants() { + List l = new ArrayList<>(); + for (List timeouts : TIMEOUTS) { + Duration connectTimeout = timeouts.get(0); + Duration requestTimeout = timeouts.get(1); + for (String method: METHODS) { + for (String scheme : SCHEMES) { + for (Version requestVersion : VERSIONS) { + l.add(new Object[] {requestVersion, scheme, method, connectTimeout, requestTimeout}); + }}}} + return l.stream().toArray(Object[][]::new); + } + + @ParameterizedTest + @MethodSource("variants") + void test( + Version requestVersion, + String scheme, + String method, + Duration connectTimeout, + Duration requestTimeout) + throws Exception { + ProxySelector proxySelector = parseBoolean(System.getProperty("test.proxy")) ? PROXY_SELECTOR : NO_PROXY; + boolean async = parseBoolean(System.getProperty("test.async")); + if (async) { + timeoutAsync(requestVersion, scheme, method, connectTimeout, requestTimeout, proxySelector); + } else { + timeoutSync(requestVersion, scheme, method, connectTimeout, requestTimeout, proxySelector); + } + } + + private void timeoutSync(Version requestVersion, + String scheme, + String method, + Duration connectTimeout, + Duration requestTimeout, + ProxySelector proxy) + throws Exception + { + HttpClient client = newClient(connectTimeout, proxy); + HttpRequest request = newRequest(scheme, requestVersion, method, requestTimeout); + + for (int i = 0; i < 2; i++) { + LOGGER.printf("iteration %d%n", i); + long startTime = System.nanoTime(); + try { + HttpResponse resp = client.send(request, BodyHandlers.ofString()); + printResponse(resp); + fail("Unexpected response: " + resp); + } catch (HttpConnectTimeoutException expected) { // blocking thread-specific exception + long elapsedTime = NANOSECONDS.toMillis(System.nanoTime() - startTime); + LOGGER.printf("Client: received in %d millis%n", elapsedTime); + assertExceptionTypeAndCause(expected.getCause()); + } catch (ConnectException e) { + long elapsedTime = NANOSECONDS.toMillis(System.nanoTime() - startTime); + LOGGER.printf("Client: received in %d millis%n", elapsedTime); + Throwable t = e.getCause().getCause(); // blocking thread-specific exception + e.printStackTrace(LOGGER); + fail("Unexpected exception:" + e); + } + } + } + + private void timeoutAsync(Version requestVersion, + String scheme, + String method, + Duration connectTimeout, + Duration requestTimeout, + ProxySelector proxy) { + HttpClient client = newClient(connectTimeout, proxy); + HttpRequest request = newRequest(scheme, requestVersion, method, requestTimeout); + for (int i = 0; i < 2; i++) { + LOGGER.printf("iteration %d%n", i); + long startTime = System.nanoTime(); + try { + HttpResponse resp = client.sendAsync(request, BodyHandlers.ofString()).join(); + printResponse(resp); + fail("Unexpected response: " + resp); + } catch (CompletionException e) { + long elapsedTime = NANOSECONDS.toMillis(System.nanoTime() - startTime); + LOGGER.printf("Client: received in %d millis%n", elapsedTime); + Throwable t = e.getCause(); + assertExceptionTypeAndCause(t); + } + } + } + + private static HttpClient newClient(Duration connectTimeout, ProxySelector proxy) { + HttpClient.Builder builder = HttpClient.newBuilder().proxy(proxy); + if (connectTimeout != NO_DURATION) + builder.connectTimeout(connectTimeout); + return builder.build(); + } + + private static HttpRequest newRequest(String scheme, + Version reqVersion, + String method, + Duration requestTimeout) { + String hostAddress = SERVER_SOCKET.getInetAddress().getHostAddress(); + int hostPort = SERVER_SOCKET.getLocalPort(); + URI uri = URI.create(scheme + "://" + hostAddress + ':' + hostPort); + HttpRequest.Builder reqBuilder = HttpRequest.newBuilder(uri); + reqBuilder = reqBuilder.version(reqVersion); + switch (method) { + case "GET" : reqBuilder.GET(); break; + case "POST" : reqBuilder.POST(BodyPublishers.noBody()); break; + default: throw new AssertionError("Unknown method:" + method); + } + if (requestTimeout != NO_DURATION) + reqBuilder.timeout(requestTimeout); + return reqBuilder.build(); + } + + private static void assertExceptionTypeAndCause(Throwable t) { + if (!(t instanceof HttpConnectTimeoutException)) { + t.printStackTrace(LOGGER); + fail("Expected HttpConnectTimeoutException, got:" + t); + } + Throwable connEx = t.getCause(); + if (!(connEx instanceof ConnectException)) { + t.printStackTrace(LOGGER); + fail("Expected ConnectException cause in:" + connEx); + } + LOGGER.printf("Caught expected HttpConnectTimeoutException with ConnectException" + + " cause: %n%s%n%s%n", t, connEx); + final String EXPECTED_MESSAGE = "HTTP connect timed out"; // impl dependent + if (!connEx.getMessage().equals(EXPECTED_MESSAGE)) + fail("Expected: \"" + EXPECTED_MESSAGE + "\", got: \"" + connEx.getMessage() + "\""); + + } + + private static void printResponse(HttpResponse response) { + LOGGER.println("Unexpected response: " + response); + LOGGER.println("Headers: " + response.headers()); + LOGGER.println("Body: " + response.body()); + } + +} diff --git a/test/jdk/java/net/httpclient/ConnectTimeoutWithProxyAsync.java b/test/jdk/java/net/httpclient/ConnectTimeoutWithProxyAsync.java deleted file mode 100644 index a6e0c22c15a6..000000000000 --- a/test/jdk/java/net/httpclient/ConnectTimeoutWithProxyAsync.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. - * 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.net.http.HttpClient.Version; -import java.time.Duration; -import org.testng.annotations.Test; - -/* - * @test - * @summary Tests for connection related timeouts - * @bug 8208391 - * @run testng/othervm ConnectTimeoutWithProxyAsync - */ - -public class ConnectTimeoutWithProxyAsync extends AbstractConnectTimeout { - - @Test(dataProvider = "variants") - @Override - public void timeoutWithProxyAsync(Version requestVersion, - String scheme, - String method, - Duration connectTimeout, - Duration requestTimeout) - { - super.timeoutWithProxyAsync(requestVersion, scheme, method, connectTimeout, requestTimeout); - } -} diff --git a/test/jdk/java/net/httpclient/ConnectTimeoutWithProxySync.java b/test/jdk/java/net/httpclient/ConnectTimeoutWithProxySync.java deleted file mode 100644 index a61fdc48bcf8..000000000000 --- a/test/jdk/java/net/httpclient/ConnectTimeoutWithProxySync.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. - * 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.net.http.HttpClient.Version; -import java.time.Duration; -import org.testng.annotations.Test; - -/* - * @test - * @summary Tests for connection related timeouts - * @bug 8208391 - * @run testng/othervm ConnectTimeoutWithProxySync - */ - -public class ConnectTimeoutWithProxySync extends AbstractConnectTimeout { - - @Test(dataProvider = "variants") - @Override - public void timeoutWithProxySync(Version requestVersion, - String scheme, - String method, - Duration connectTimeout, - Duration requestTimeout) - throws Exception - { - super.timeoutWithProxySync(requestVersion, scheme, method, connectTimeout, requestTimeout); - } -} From 03d9056badb90368181b96e60290e29aabb4894a Mon Sep 17 00:00:00 2001 From: Arno Zeller Date: Wed, 8 Jul 2026 20:13:46 +0000 Subject: [PATCH 53/86] 8380896: Reduce runtime for MonitorVmStartTerminate.java on hosts with a lot of VMs Backport-of: d75bb86ca69d5b547c4f46217387849333afa1f3 --- .../MonitoredVm/MonitorVmStartTerminate.java | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/test/jdk/sun/jvmstat/monitor/MonitoredVm/MonitorVmStartTerminate.java b/test/jdk/sun/jvmstat/monitor/MonitoredVm/MonitorVmStartTerminate.java index f363d6a7dd55..4e4d7d0275e3 100644 --- a/test/jdk/sun/jvmstat/monitor/MonitoredVm/MonitorVmStartTerminate.java +++ b/test/jdk/sun/jvmstat/monitor/MonitoredVm/MonitorVmStartTerminate.java @@ -147,8 +147,17 @@ private void releaseStarted(Set ids) { } private void releaseStarted(Integer id) { + String monitoredArgs = readMainArgs(id); + if (monitoredArgs == null || monitoredArgs.equals("Unknown")) { + System.out.println("releaseStarted: not a test pid: " + id); + return; + } + for (JavaProcess jp : processes) { - if (hasMainArgs(id, jp.getMainArgsIdentifier())) { + if (jp.getId() != null) { + continue; + } + if (monitoredArgs.contains(jp.getMainArgsIdentifier())) { // store id for terminated identification jp.setId(id); System.out.println("RELEASED started (id=" + jp.getId() + ", args=" + jp.getMainArgsIdentifier() + ")"); @@ -176,40 +185,39 @@ private void releaseTerminated(Integer id) { } } - private boolean hasMainArgs(Integer id, String args) { - VmIdentifier vmid = null; + private String readMainArgs(Integer id) { + VmIdentifier vmid; try { vmid = new VmIdentifier("//" + id.intValue()); } catch (URISyntaxException e) { - System.out.println("hasMainArgs(" + id + "): " + e); - return false; + System.out.println("readMainArgs(" + id + "): " + e); + return null; } - // Retry a failing attempt to check arguments for a match, + // Retry a failing attempt to read arguments, // as not recognizing a test process will cause timeout and failure. for (int i = 0; i < ARGS_ATTEMPTS; i++) { try { MonitoredVm target = host.getMonitoredVm(vmid); String monitoredArgs = MonitoredVmUtil.mainArgs(target); - System.out.println("hasMainArgs(" + id + "): has main args: '" + monitoredArgs + "'"); + System.out.println("readMainArgs(" + id + "): has main args: '" + monitoredArgs + "'"); if (monitoredArgs == null || monitoredArgs.equals("Unknown")) { - System.out.println("hasMainArgs(" + id + "): retry" ); + System.out.println("readMainArgs(" + id + "): retry"); takeNap(); continue; - } else if (monitoredArgs.contains(args)) { - return true; } else { - return false; + return monitoredArgs; } } catch (MonitorException e) { // Process probably not running or not ours, e.g. // sun.jvmstat.monitor.MonitorException: Could not attach to PID // Only log if something else, to avoid filling log: - if (!e.getMessage().contains("Could not attach")) { - System.out.println("hasMainArgs(" + id + "): " + e); + String message = e.getMessage(); + if (message == null || !message.contains("Could not attach")) { + System.out.println("readMainArgs(" + id + "): " + e); } } } - return false; + return null; } } From 92590cea439d67da522aee81bf7f1e149a1802cb Mon Sep 17 00:00:00 2001 From: Ashay Rane Date: Wed, 8 Jul 2026 20:18:10 +0000 Subject: [PATCH 54/86] 8348862: runtime/ErrorHandling/CreateCoredumpOnCrash fails on Windows aarch64 Backport-of: f4209dff3ba14ccbdc0846d9bfcc62688361b6d5 --- src/hotspot/os/windows/os_windows.cpp | 52 +++++---------- src/hotspot/os/windows/os_windows.hpp | 2 + .../os/windows/safefetch_static_windows.cpp | 64 ++++++++++++++++++ .../windows_aarch64/os_windows_aarch64.cpp | 4 ++ .../safefetch_windows_aarch64.S | 65 +++++++++++++++++++ src/hotspot/share/runtime/safefetch.hpp | 4 +- .../UncaughtNativeExceptionTest.java | 2 +- .../ErrorHandling/libNativeException.c | 5 +- 8 files changed, 157 insertions(+), 41 deletions(-) create mode 100644 src/hotspot/os/windows/safefetch_static_windows.cpp create mode 100644 src/hotspot/os_cpu/windows_aarch64/safefetch_windows_aarch64.S diff --git a/src/hotspot/os/windows/os_windows.cpp b/src/hotspot/os/windows/os_windows.cpp index 4969f0ecf73d..5a255a7b82d9 100644 --- a/src/hotspot/os/windows/os_windows.cpp +++ b/src/hotspot/os/windows/os_windows.cpp @@ -2628,14 +2628,13 @@ LONG WINAPI topLevelExceptionFilter(struct _EXCEPTION_POINTERS* exceptionInfo) { DWORD exception_code = exception_record->ExceptionCode; #if defined(_M_ARM64) address pc = (address) exceptionInfo->ContextRecord->Pc; + + if (handle_safefetch(exception_code, pc, (void*)exceptionInfo->ContextRecord)) { + return EXCEPTION_CONTINUE_EXECUTION; + } #elif defined(_M_AMD64) address pc = (address) exceptionInfo->ContextRecord->Rip; -#else - #error unknown architecture -#endif - Thread* t = Thread::current_or_null_safe(); -#if defined(_M_AMD64) if ((exception_code == EXCEPTION_ACCESS_VIOLATION) && VM_Version::is_cpuinfo_segv_addr(pc)) { // Verify that OS save/restore AVX registers. @@ -2648,6 +2647,8 @@ LONG WINAPI topLevelExceptionFilter(struct _EXCEPTION_POINTERS* exceptionInfo) { VM_Version::clear_apx_test_state(); return Handle_Exception(exceptionInfo, VM_Version::cpuinfo_cont_addr_apx()); } +#else + #error unknown architecture #endif #ifdef CAN_SHOW_REGISTERS_ON_ASSERT @@ -2658,6 +2659,7 @@ LONG WINAPI topLevelExceptionFilter(struct _EXCEPTION_POINTERS* exceptionInfo) { } #endif + Thread* t = Thread::current_or_null_safe(); if (t != nullptr && t->is_Java_thread()) { JavaThread* thread = JavaThread::cast(t); bool in_java = thread->thread_state() == _thread_in_Java; @@ -2688,10 +2690,8 @@ LONG WINAPI topLevelExceptionFilter(struct _EXCEPTION_POINTERS* exceptionInfo) { // Fatal red zone violation. overflow_state->disable_stack_red_zone(); tty->print_raw_cr("An unrecoverable stack overflow has occurred."); -#if !defined(USE_VECTORED_EXCEPTION_HANDLING) report_error(t, exception_code, pc, exception_record, exceptionInfo->ContextRecord); -#endif return EXCEPTION_CONTINUE_SEARCH; } } else if (exception_code == EXCEPTION_ACCESS_VIOLATION) { @@ -2743,10 +2743,8 @@ LONG WINAPI topLevelExceptionFilter(struct _EXCEPTION_POINTERS* exceptionInfo) { } // Stack overflow or null pointer exception in native code. -#if !defined(USE_VECTORED_EXCEPTION_HANDLING) report_error(t, exception_code, pc, exception_record, exceptionInfo->ContextRecord); -#endif return EXCEPTION_CONTINUE_SEARCH; } // /EXCEPTION_ACCESS_VIOLATION // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -2821,41 +2819,21 @@ LONG WINAPI topLevelExceptionFilter(struct _EXCEPTION_POINTERS* exceptionInfo) { } } -#if !defined(USE_VECTORED_EXCEPTION_HANDLING) - if (exception_code != EXCEPTION_BREAKPOINT) { - report_error(t, exception_code, pc, exception_record, - exceptionInfo->ContextRecord); - } -#endif - return EXCEPTION_CONTINUE_SEARCH; -} + bool should_report_error = (exception_code != EXCEPTION_BREAKPOINT); -#if defined(USE_VECTORED_EXCEPTION_HANDLING) -LONG WINAPI topLevelVectoredExceptionFilter(struct _EXCEPTION_POINTERS* exceptionInfo) { - PEXCEPTION_RECORD exceptionRecord = exceptionInfo->ExceptionRecord; #if defined(_M_ARM64) - address pc = (address) exceptionInfo->ContextRecord->Pc; -#elif defined(_M_AMD64) - address pc = (address) exceptionInfo->ContextRecord->Rip; -#else - #error unknown architecture + should_report_error = should_report_error && + FAILED(exception_code) && + (exception_code != EXCEPTION_UNCAUGHT_CXX_EXCEPTION); #endif - // Fast path for code part of the code cache - if (CodeCache::low_bound() <= pc && pc < CodeCache::high_bound()) { - return topLevelExceptionFilter(exceptionInfo); - } - - // If the exception occurred in the codeCache, pass control - // to our normal exception handler. - CodeBlob* cb = CodeCache::find_blob(pc); - if (cb != nullptr) { - return topLevelExceptionFilter(exceptionInfo); + if (should_report_error) { + report_error(t, exception_code, pc, exception_record, + exceptionInfo->ContextRecord); } return EXCEPTION_CONTINUE_SEARCH; } -#endif #if defined(USE_VECTORED_EXCEPTION_HANDLING) LONG WINAPI topLevelUnhandledExceptionFilter(struct _EXCEPTION_POINTERS* exceptionInfo) { @@ -4488,7 +4466,7 @@ jint os::init_2(void) { // Setup Windows Exceptions #if defined(USE_VECTORED_EXCEPTION_HANDLING) - topLevelVectoredExceptionHandler = AddVectoredExceptionHandler(1, topLevelVectoredExceptionFilter); + topLevelVectoredExceptionHandler = AddVectoredExceptionHandler(1, topLevelExceptionFilter); previousUnhandledExceptionFilter = SetUnhandledExceptionFilter(topLevelUnhandledExceptionFilter); #endif diff --git a/src/hotspot/os/windows/os_windows.hpp b/src/hotspot/os/windows/os_windows.hpp index efb7b4149897..f1153bbbfd3d 100644 --- a/src/hotspot/os/windows/os_windows.hpp +++ b/src/hotspot/os/windows/os_windows.hpp @@ -150,6 +150,8 @@ class os::win32 { // signal support static void* install_signal_handler(int sig, signal_handler_t handler); static void* user_handler(); + + static void context_set_pc(CONTEXT* uc, address pc); }; #endif // OS_WINDOWS_OS_WINDOWS_HPP diff --git a/src/hotspot/os/windows/safefetch_static_windows.cpp b/src/hotspot/os/windows/safefetch_static_windows.cpp new file mode 100644 index 000000000000..3ea8b96b32db --- /dev/null +++ b/src/hotspot/os/windows/safefetch_static_windows.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2022 SAP SE. 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 + * 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 "os_windows.hpp" +#include "runtime/os.hpp" +#include "runtime/safefetch.hpp" +#include "utilities/globalDefinitions.hpp" + +#ifdef SAFEFETCH_METHOD_STATIC_ASSEMBLY + +// SafeFetch handling, static assembly style: +// +// SafeFetch32 and SafeFetchN are implemented via static assembly +// and live in os_cpu/xx_xx/safefetch_xx_xx.S + +extern "C" char _SafeFetch32_continuation[]; +extern "C" char _SafeFetch32_fault[]; + +#ifdef _LP64 +extern "C" char _SafeFetchN_continuation[]; +extern "C" char _SafeFetchN_fault[]; +#endif // _LP64 + +bool handle_safefetch(int exception_code, address pc, void* context) { + CONTEXT* ctx = (CONTEXT*)context; + if (exception_code == EXCEPTION_ACCESS_VIOLATION && ctx != nullptr) { + if (pc == (address)_SafeFetch32_fault) { + os::win32::context_set_pc(ctx, (address)_SafeFetch32_continuation); + return true; + } +#ifdef _LP64 + if (pc == (address)_SafeFetchN_fault) { + os::win32::context_set_pc(ctx, (address)_SafeFetchN_continuation); + return true; + } +#endif + } + return false; +} + +#endif // SAFEFETCH_METHOD_STATIC_ASSEMBLY diff --git a/src/hotspot/os_cpu/windows_aarch64/os_windows_aarch64.cpp b/src/hotspot/os_cpu/windows_aarch64/os_windows_aarch64.cpp index 01105e6d51e8..d99e0167cbd4 100644 --- a/src/hotspot/os_cpu/windows_aarch64/os_windows_aarch64.cpp +++ b/src/hotspot/os_cpu/windows_aarch64/os_windows_aarch64.cpp @@ -115,6 +115,10 @@ intptr_t* os::fetch_bcp_from_context(const void* ucVoid) { return reinterpret_cast(uc->REG_BCP); } +void os::win32::context_set_pc(CONTEXT* uc, address pc) { + uc->Pc = (intptr_t)pc; +} + bool os::win32::get_frame_at_stack_banging_point(JavaThread* thread, struct _EXCEPTION_POINTERS* exceptionInfo, address pc, frame* fr) { PEXCEPTION_RECORD exceptionRecord = exceptionInfo->ExceptionRecord; diff --git a/src/hotspot/os_cpu/windows_aarch64/safefetch_windows_aarch64.S b/src/hotspot/os_cpu/windows_aarch64/safefetch_windows_aarch64.S new file mode 100644 index 000000000000..494b68fe4cdf --- /dev/null +++ b/src/hotspot/os_cpu/windows_aarch64/safefetch_windows_aarch64.S @@ -0,0 +1,65 @@ +; +; Copyright (c) 2022 SAP SE. 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 +; 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. +; + + ; Support for int SafeFetch32(int* address, int defaultval); + ; + ; x0 : address + ; w1 : defaultval + + ; needed to align function start to 4 byte + ALIGN 4 + EXPORT _SafeFetch32_fault + EXPORT _SafeFetch32_continuation + EXPORT SafeFetch32_impl + AREA safefetch_text, CODE + +SafeFetch32_impl +_SafeFetch32_fault + ldr w0, [x0] + ret + +_SafeFetch32_continuation + mov x0, x1 + ret + + ; Support for intptr_t SafeFetchN(intptr_t* address, intptr_t defaultval); + ; + ; x0 : address + ; x1 : defaultval + + ALIGN 4 + EXPORT _SafeFetchN_fault + EXPORT _SafeFetchN_continuation + EXPORT SafeFetchN_impl + +SafeFetchN_impl +_SafeFetchN_fault + ldr x0, [x0] + ret + +_SafeFetchN_continuation + mov x0, x1 + ret + + END diff --git a/src/hotspot/share/runtime/safefetch.hpp b/src/hotspot/share/runtime/safefetch.hpp index 71a542e25e80..a1781962ec0e 100644 --- a/src/hotspot/share/runtime/safefetch.hpp +++ b/src/hotspot/share/runtime/safefetch.hpp @@ -31,8 +31,8 @@ // Safefetch allows to load a value from a location that's not known // to be valid. If the load causes a fault, the error value is returned. -#ifdef _WIN32 - // Windows uses Structured Exception Handling +#if defined(_WIN32) && !defined(_M_ARM64) + // Windows x86_64 uses Structured Exception Handling #include "safefetch_windows.hpp" #elif defined(ZERO) || defined (_AIX) // These platforms implement safefetch via Posix sigsetjmp/longjmp. diff --git a/test/hotspot/jtreg/runtime/ErrorHandling/UncaughtNativeExceptionTest.java b/test/hotspot/jtreg/runtime/ErrorHandling/UncaughtNativeExceptionTest.java index 14aca26f39f6..d432181aacbf 100644 --- a/test/hotspot/jtreg/runtime/ErrorHandling/UncaughtNativeExceptionTest.java +++ b/test/hotspot/jtreg/runtime/ErrorHandling/UncaughtNativeExceptionTest.java @@ -64,7 +64,7 @@ public void testNativeExceptionReporting() throws Exception { assertTrue(Files.exists(hsErrPath)); Pattern[] positivePatterns = { - Pattern.compile(".*Internal Error \\(0x2a\\).*") + Pattern.compile(".*Internal Error \\(0xdeadbeef\\).*") }; HsErrFileUtils.checkHsErrFileContent(hsErrFile, positivePatterns, null, true /* check end marker */, false /* verbose */); } diff --git a/test/hotspot/jtreg/runtime/ErrorHandling/libNativeException.c b/test/hotspot/jtreg/runtime/ErrorHandling/libNativeException.c index 3bf71cf9c67b..20216a2e1de0 100644 --- a/test/hotspot/jtreg/runtime/ErrorHandling/libNativeException.c +++ b/test/hotspot/jtreg/runtime/ErrorHandling/libNativeException.c @@ -25,7 +25,10 @@ #include -const DWORD EX_CODE = 42; +// Use an exception code that causes the Windows FAILED() macro to return true. +// Windows AArch64 uses vectored exception handling and therefore runs error +// reporting only for failed exception codes. +const DWORD EX_CODE = 0xdeadbeef; JNIEXPORT void JNICALL Java_UncaughtNativeExceptionTest_00024Crasher_throwException(JNIEnv* env, jclass cls) { RaiseException(EX_CODE, EXCEPTION_NONCONTINUABLE, 0, NULL); From e5ff6ce8603bdb5f8cbdbbf5fd50ec42fda47768 Mon Sep 17 00:00:00 2001 From: Ashay Rane Date: Wed, 8 Jul 2026 20:18:36 +0000 Subject: [PATCH 55/86] 8349535: Refactor ./pkcs11/Provider/MultipleLogins.sh to java test Backport-of: 2d815ac61b437835163d4014bfed16f0f3faea50 --- .../pkcs11/Provider/MultipleLogins.java | 60 +++++--- .../pkcs11/Provider/MultipleLogins.sh | 139 ------------------ 2 files changed, 37 insertions(+), 162 deletions(-) delete mode 100644 test/jdk/sun/security/pkcs11/Provider/MultipleLogins.sh diff --git a/test/jdk/sun/security/pkcs11/Provider/MultipleLogins.java b/test/jdk/sun/security/pkcs11/Provider/MultipleLogins.java index 073dd557cbda..de95186304af 100644 --- a/test/jdk/sun/security/pkcs11/Provider/MultipleLogins.java +++ b/test/jdk/sun/security/pkcs11/Provider/MultipleLogins.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 @@ -21,6 +21,17 @@ * questions. */ +/* + * @test + * @bug 8240256 8269034 + * @summary + * @library /test/lib/ /sun/security/pkcs11/ + * @modules jdk.crypto.cryptoki/sun.security.pkcs11 + * @run main/othervm + * -DCUSTOM_P11_CONFIG=${test.src}/MultipleLogins-nss.txt + * -DCUSTOM_DB_DIR=./nss/db + * MultipleLogins + */ import sun.security.pkcs11.SunPKCS11; @@ -30,6 +41,7 @@ import javax.security.auth.callback.PasswordCallback; import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.auth.login.LoginException; + import java.io.IOException; import java.lang.ref.WeakReference; import java.security.*; @@ -37,28 +49,30 @@ import jdk.test.lib.util.ForceGC; import jtreg.SkippedException; -public class MultipleLogins { - private static final String KS_TYPE = "PKCS11"; +public class MultipleLogins extends PKCS11Test { private static final int NUM_PROVIDERS = 20; private static final SunPKCS11[] providers = new SunPKCS11[NUM_PROVIDERS]; public static void main(String[] args) throws Exception { - String nssConfig = null; - try { - nssConfig = PKCS11Test.getNssConfig(); - } catch (SkippedException exc) { - System.out.println("Skipping test: " + exc.getMessage()); - } + // This bypasses the PKCS11Test settings and run the mandatory + // main method directly. This is needed to keep the custom logic of the test + new MultipleLogins().main((Provider)null); + } + + @Override + public void main(Provider p) throws Exception { + copyNssCertKeyToClassesDir(); + + String nssConfig = getNssConfig(); if (nssConfig == null) { // No test framework support yet. Ignore - System.out.println("No NSS config found. Skipping."); - return; + throw new SkippedException("No NSS config found. Skipping."); } - for (int i =0; i < NUM_PROVIDERS; i++) { + for (int i = 0; i < NUM_PROVIDERS; i++) { // loop to set up test without security manger - providers[i] = (SunPKCS11)PKCS11Test.newPKCS11Provider(); + providers[i] = (SunPKCS11)newPKCS11Provider(); } for (int i =0; i < NUM_PROVIDERS; i++) { @@ -68,7 +82,7 @@ public static void main(String[] args) throws Exception { } WeakReference[] weakRef = new WeakReference[NUM_PROVIDERS]; - for (int i =0; i < NUM_PROVIDERS; i++) { + for (int i = 0; i < NUM_PROVIDERS; i++) { weakRef[i] = new WeakReference<>(providers[i]); providers[i].logout(); @@ -95,7 +109,7 @@ public static void main(String[] args) throws Exception { } private static void test(SunPKCS11 p) throws Exception { - KeyStore ks = KeyStore.getInstance(KS_TYPE, p); + KeyStore ks = KeyStore.getInstance(PKCS11, p); p.setCallbackHandler(new PasswordCallbackHandler()); try { ks.load(null, (char[]) null); @@ -111,23 +125,23 @@ private static void test(SunPKCS11 p) throws Exception { try { ks.load(null, (char[]) null); } catch (IOException e) { - if (e.getCause() instanceof LoginException && - e.getCause().getMessage().contains("No token present")) { - // expected - } else { + if (!(e.getCause() instanceof LoginException) || + !(e.getCause().getMessage().contains("No token present"))) { + throw new RuntimeException("Token was present", e); - } + } // else expected } } public static class PasswordCallbackHandler implements CallbackHandler { public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { - if (!(callbacks[0] instanceof PasswordCallback)) { + if (callbacks[0] instanceof PasswordCallback pc) { + pc.setPassword(null); + } else { throw new UnsupportedCallbackException(callbacks[0]); } - PasswordCallback pc = (PasswordCallback)callbacks[0]; - pc.setPassword(null); + } } } diff --git a/test/jdk/sun/security/pkcs11/Provider/MultipleLogins.sh b/test/jdk/sun/security/pkcs11/Provider/MultipleLogins.sh deleted file mode 100644 index b30410d98457..000000000000 --- a/test/jdk/sun/security/pkcs11/Provider/MultipleLogins.sh +++ /dev/null @@ -1,139 +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. -# - -# @test -# @bug 8240256 8269034 -# @summary -# @library /test/lib/ -# @build jdk.test.lib.util.ForceGC -# jdk.test.lib.Platform -# jdk.test.lib.Utils -# @run shell MultipleLogins.sh - -# set a few environment variables so that the shell-script can run stand-alone -# in the source directory - -# if running by hand on windows, change TESTSRC and TESTCLASSES to "." -if [ "${TESTSRC}" = "" ] ; then - TESTSRC=`pwd` -fi -if [ "${TESTCLASSES}" = "" ] ; then - TESTCLASSES=`pwd` -fi - -if [ "${TESTCLASSPATH}" = "" ] ; then - TESTCLASSPATH=`pwd` -fi - -if [ "${COMPILEJAVA}" = "" ]; then - COMPILEJAVA="${TESTJAVA}" -fi -echo TESTSRC=${TESTSRC} -echo TESTCLASSES=${TESTCLASSES} -echo TESTJAVA=${TESTJAVA} -echo COMPILEJAVA=${COMPILEJAVA} -echo "" - -# let java test exit if platform unsupported - -OS=`uname -s` -case "$OS" in - Linux ) - FS="/" - PS=":" - CP="${FS}bin${FS}cp" - CHMOD="${FS}bin${FS}chmod" - ;; - Darwin ) - FS="/" - PS=":" - CP="${FS}bin${FS}cp" - CHMOD="${FS}bin${FS}chmod" - ;; - AIX ) - FS="/" - PS=":" - CP="${FS}bin${FS}cp" - CHMOD="${FS}bin${FS}chmod" - ;; - Windows* ) - FS="\\" - PS=";" - CP="cp" - CHMOD="chmod" - ;; - CYGWIN* ) - FS="/" - PS=";" - CP="cp" - CHMOD="chmod" - # - # javac does not like /cygdrive produced by `pwd` - # - TESTSRC=`cygpath -d ${TESTSRC}` - ;; - * ) - echo "Unrecognized system!" - exit 1; - ;; -esac - -# first make cert/key DBs writable - -${CP} ${TESTSRC}${FS}..${FS}nss${FS}db${FS}cert9.db ${TESTCLASSES} -${CHMOD} +w ${TESTCLASSES}${FS}cert9.db - -${CP} ${TESTSRC}${FS}..${FS}nss${FS}db${FS}key4.db ${TESTCLASSES} -${CHMOD} +w ${TESTCLASSES}${FS}key4.db - -${CP} ${TESTSRC}${FS}..${FS}nss${FS}db${FS}cert8.db ${TESTCLASSES} -${CHMOD} +w ${TESTCLASSES}${FS}cert8.db - -${CP} ${TESTSRC}${FS}..${FS}nss${FS}db${FS}key3.db ${TESTCLASSES} -${CHMOD} +w ${TESTCLASSES}${FS}key3.db - -# compile test -${COMPILEJAVA}${FS}bin${FS}javac ${TESTJAVACOPTS} ${TESTTOOLVMOPTS} \ - -classpath ${TESTCLASSPATH} \ - -d ${TESTCLASSES} \ - --add-modules jdk.crypto.cryptoki \ - --add-exports jdk.crypto.cryptoki/sun.security.pkcs11=ALL-UNNAMED \ - ${TESTSRC}${FS}..${FS}..${FS}..${FS}..${FS}..${FS}lib${FS}jdk${FS}test${FS}lib${FS}artifacts${FS}*.java \ - ${TESTSRC}${FS}..${FS}..${FS}..${FS}..${FS}..${FS}lib${FS}jtreg${FS}*.java \ - ${TESTSRC}${FS}MultipleLogins.java \ - ${TESTSRC}${FS}..${FS}PKCS11Test.java - -TEST_ARGS="${TESTVMOPTS} ${TESTJAVAOPTS} -classpath ${TESTCLASSPATH} \ - --add-modules jdk.crypto.cryptoki \ - --add-exports jdk.crypto.cryptoki/sun.security.pkcs11=ALL-UNNAMED \ - -DCUSTOM_DB_DIR=${TESTCLASSES} \ - -DCUSTOM_P11_CONFIG=${TESTSRC}${FS}MultipleLogins-nss.txt \ - -Dtest.src=${TESTSRC} \ - -Dtest.classes=${TESTCLASSES} \ - -Djava.security.debug=${DEBUG}" - -# run test without security manager -${TESTJAVA}${FS}bin${FS}java ${TEST_ARGS} MultipleLogins || exit 10 - -echo Done -exit 0 From 6bbae6df769195480b46f551d8ba2ec168bc6ec3 Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Fri, 10 Jul 2026 15:20:13 +0000 Subject: [PATCH 56/86] 8372757: MacOS, Accessibility: Crash in [MenuAccessibility accessibilityChildren] after JDK-8341311 Backport-of: 019df4d89c8a0fe2b27c6ec074499445ae45bc3f --- .../macosx/native/libawt_lwawt/awt/a11y/MenuAccessibility.m | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/java.desktop/macosx/native/libawt_lwawt/awt/a11y/MenuAccessibility.m b/src/java.desktop/macosx/native/libawt_lwawt/awt/a11y/MenuAccessibility.m index 90a147aa5a21..14b7ee8c6b5f 100644 --- a/src/java.desktop/macosx/native/libawt_lwawt/awt/a11y/MenuAccessibility.m +++ b/src/java.desktop/macosx/native/libawt_lwawt/awt/a11y/MenuAccessibility.m @@ -68,6 +68,11 @@ - (NSArray *)accessibilityChildren { sjm_getCurrentAccessiblePopupMenu, fAccessible, fComponent); + CHECK_EXCEPTION(); + if (axComponent == nil) { + return nil; + } + CommonComponentAccessibility *currentElement = [CommonComponentAccessibility createWithAccessible:axComponent withEnv:env withView:self->fView isCurrent:YES]; From 009a192f1fa4124b6cacc23739f20c8aa22210ee Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Fri, 10 Jul 2026 15:23:55 +0000 Subject: [PATCH 57/86] 8385817: Headless jdk still contains bin/jconsole Backport-of: c2df7329a88b079240f211e776af2d8f7030e346 --- make/modules/jdk.jconsole/Launcher.gmk | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/make/modules/jdk.jconsole/Launcher.gmk b/make/modules/jdk.jconsole/Launcher.gmk index 7cb40a1b13a1..adb3b05c400b 100644 --- a/make/modules/jdk.jconsole/Launcher.gmk +++ b/make/modules/jdk.jconsole/Launcher.gmk @@ -1,5 +1,5 @@ # -# Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2011, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -30,15 +30,15 @@ include LauncherCommon.gmk ################################################################################ ## Build jconsole ################################################################################ - -$(eval $(call SetupBuildLauncher, jconsole, \ - MAIN_CLASS := sun.tools.jconsole.JConsole, \ - JAVA_ARGS := \ - --add-opens java.base/java.io=jdk.jconsole \ - --add-modules ALL-DEFAULT \ - -Djconsole.showOutputViewer \ - -Djdk.attach.allowAttachSelf=true, \ - WINDOWS_JAVAW := true, \ -)) - +ifneq ($(ENABLE_HEADLESS_ONLY), true) + $(eval $(call SetupBuildLauncher, jconsole, \ + MAIN_CLASS := sun.tools.jconsole.JConsole, \ + JAVA_ARGS := \ + --add-opens java.base/java.io=jdk.jconsole \ + --add-modules ALL-DEFAULT \ + -Djconsole.showOutputViewer \ + -Djdk.attach.allowAttachSelf=true, \ + WINDOWS_JAVAW := true, \ + )) +endif ################################################################################ From 9f38d9a88feabf9eb711377852902346b258324e Mon Sep 17 00:00:00 2001 From: Richard Reingruber Date: Sat, 11 Jul 2026 06:35:04 +0000 Subject: [PATCH 58/86] 8385166: PPC: C2: c_return_value and return_value should not set 2nd OptoRegPair for Op_RegI Backport-of: ac85fddbbc89189dc7dd36991bea82157db699f7 --- src/hotspot/cpu/ppc/ppc.ad | 42 +++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/src/hotspot/cpu/ppc/ppc.ad b/src/hotspot/cpu/ppc/ppc.ad index f415105396c9..6b7c273ab5fe 100644 --- a/src/hotspot/cpu/ppc/ppc.ad +++ b/src/hotspot/cpu/ppc/ppc.ad @@ -3492,30 +3492,34 @@ frame %{ // 4 what apparently works and saves us some spills. return_addr(STACK 4); - // Location of native (C/C++) and interpreter return values. This - // is specified to be the same as Java. In the 32-bit VM, long - // values are actually returned from native calls in O0:O1 and - // returned to the interpreter in I0:I1. The copying to and from - // the register pairs is done by the appropriate call and epilog - // opcodes. This simplifies the register allocator. - c_return_value %{ - assert((ideal_reg >= Op_RegI && ideal_reg <= Op_RegL) || - (ideal_reg == Op_RegN && CompressedOops::base() == nullptr && CompressedOops::shift() == 0), - "only return normal values"); - // enum names from opcodes.hpp: Op_Node Op_Set Op_RegN Op_RegI Op_RegP Op_RegF Op_RegD Op_RegL - static int typeToRegLo[Op_RegL+1] = { 0, 0, R3_num, R3_num, R3_num, F1_num, F1_num, R3_num }; - static int typeToRegHi[Op_RegL+1] = { 0, 0, OptoReg::Bad, R3_H_num, R3_H_num, OptoReg::Bad, F1_H_num, R3_H_num }; - return OptoRegPair(typeToRegHi[ideal_reg], typeToRegLo[ideal_reg]); - %} - // Location of compiled Java return values. Same as C return_value %{ assert((ideal_reg >= Op_RegI && ideal_reg <= Op_RegL) || (ideal_reg == Op_RegN && CompressedOops::base() == nullptr && CompressedOops::shift() == 0), "only return normal values"); - // enum names from opcodes.hpp: Op_Node Op_Set Op_RegN Op_RegI Op_RegP Op_RegF Op_RegD Op_RegL - static int typeToRegLo[Op_RegL+1] = { 0, 0, R3_num, R3_num, R3_num, F1_num, F1_num, R3_num }; - static int typeToRegHi[Op_RegL+1] = { 0, 0, OptoReg::Bad, R3_H_num, R3_H_num, OptoReg::Bad, F1_H_num, R3_H_num }; + // enum names from opcodes.hpp + static int typeToRegLo[Op_RegL+1] = { + 0, // Op_Node + 0, // Op_Set + R3_num, // Op_RegN + R3_num, // Op_RegI + R3_num, // Op_RegP + F1_num, // Op_RegF + F1_num, // Op_RegD + R3_num, // Op_RegL + }; + + static int typeToRegHi[Op_RegL+1] = { + 0, // Op_Node + 0, // Op_Set + OptoReg::Bad, // Op_RegN + OptoReg::Bad, // Op_RegI + R3_H_num, // Op_RegP + OptoReg::Bad, // Op_RegF + F1_H_num, // Op_RegD + R3_H_num // Op_RegL + }; + return OptoRegPair(typeToRegHi[ideal_reg], typeToRegLo[ideal_reg]); %} %} From 2b36443935d30738977310a6b4a6dd049d3e9824 Mon Sep 17 00:00:00 2001 From: Roland Westrelin Date: Mon, 13 Jul 2026 07:24:49 +0000 Subject: [PATCH 59/86] 8387015: C2: crash with "named projection 2 not found" from ArrayCopyNode::finish_transform() for clone Backport-of: 4320fdeb17cc0d81bea5055d2f4e7a1bcdd5f37b --- src/hotspot/share/opto/arraycopynode.cpp | 6 ++ .../compiler/arraycopy/TestDeadCloneMem.java | 79 +++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 test/hotspot/jtreg/compiler/arraycopy/TestDeadCloneMem.java diff --git a/src/hotspot/share/opto/arraycopynode.cpp b/src/hotspot/share/opto/arraycopynode.cpp index c02aefc79437..65e5d1a04bdc 100644 --- a/src/hotspot/share/opto/arraycopynode.cpp +++ b/src/hotspot/share/opto/arraycopynode.cpp @@ -182,6 +182,12 @@ Node* ArrayCopyNode::try_clone_instance(PhaseGVN *phase, bool can_reshape, int c return nullptr; } + Node* out_mem = proj_out_or_null(TypeFunc::Memory); + if (can_reshape && out_mem == nullptr) { // dead node? + return NodeSentinel; + } + + Node* base_src = in(ArrayCopyNode::Src); Node* base_dest = in(ArrayCopyNode::Dest); Node* ctl = in(TypeFunc::Control); diff --git a/test/hotspot/jtreg/compiler/arraycopy/TestDeadCloneMem.java b/test/hotspot/jtreg/compiler/arraycopy/TestDeadCloneMem.java new file mode 100644 index 000000000000..807f45f11f7a --- /dev/null +++ b/test/hotspot/jtreg/compiler/arraycopy/TestDeadCloneMem.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2026 IBM Corporation. All rights reserved. + * 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 JDK-8387015 + * @summary C2: crash with "named projection 2 not found" from ArrayCopyNode::finish_transform() for clone + * @run main/othervm -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:CompileOnly=${test.main.class}::test1 + * -XX:CompileCommand=dontinline,${test.main.class}::notInlined -XX:+StressIGVN + * -XX:StressSeed=1324432947 ${test.main.class} + * @run main/othervm -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:CompileOnly=${test.main.class}::test1 + * -XX:CompileCommand=dontinline,${test.main.class}::notInlined -XX:+StressIGVN + * ${test.main.class} + */ + +package compiler.arraycopy; + +public class TestDeadCloneMem { + private static int field; + + public static void main(String[] args) { + int[] array = new int[10]; + array.clone(); + Object o = new Object(); + test1(42, false); + } + + private static int test1(int flag, boolean flag2) { + int len; + if (flag != 42) { + if (flag2) { + field = 42; + } + int[] array2; + if (flag != 42) { + len = -1; + array2 = new int[4]; + } else { + len = 42; + array2 = new int[100]; + } + int[] array = new int[len]; + int length = array.length; + int i = 0; + do { + synchronized (new Object()) {} + notInlined(); + array2.clone(); + i++; + } while (i < 10); + return length; + } + return 0; + } + + private static void notInlined() { + + } +} From b2e10cf51042aee5c0b62733e563a9d3d097939e Mon Sep 17 00:00:00 2001 From: Oli Gillespie Date: Mon, 13 Jul 2026 11:40:29 +0000 Subject: [PATCH 60/86] 8379129: C2 crash in LoadNode::can_split_through_phi_base during Escape Analysis (JDK 25.0.1+8) Backport-of: 8688c0b65516f0a035ae8d25790489981475d07f --- src/hotspot/share/opto/memnode.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/hotspot/share/opto/memnode.cpp b/src/hotspot/share/opto/memnode.cpp index 24b81b894cb1..9145b7f06a58 100644 --- a/src/hotspot/share/opto/memnode.cpp +++ b/src/hotspot/share/opto/memnode.cpp @@ -1585,11 +1585,15 @@ bool LoadNode::can_split_through_phi_base(PhaseGVN* phase) { intptr_t ignore = 0; Node* base = AddPNode::Ideal_base_and_offset(address, phase, ignore); + if (base == nullptr) { + return false; + } + if (base->is_CastPP()) { base = base->in(1); } - if (req() > 3 || base == nullptr || !base->is_Phi()) { + if (req() > 3 || !base->is_Phi()) { return false; } From 25379ba49f0731e6f9725c43e0d2446b9092a454 Mon Sep 17 00:00:00 2001 From: William Kemper Date: Mon, 13 Jul 2026 17:19:54 +0000 Subject: [PATCH 61/86] 8380390: Shenandoah: Missing store barrier when resetting bitmaps 8386798: Shenandoah: Missing load barrier when making assertions about mark bitmap Reviewed-by: xpeng, kdnilsen, shade Backport-of: a13dd293a811a6ba829696e68cd2150de2cb2f17 --- .../share/gc/shenandoah/shenandoahAsserts.cpp | 10 ++++++++++ .../share/gc/shenandoah/shenandoahAsserts.hpp | 7 +++++++ .../share/gc/shenandoah/shenandoahFreeSet.cpp | 12 +++++------- .../gc/shenandoah/shenandoahHeapRegion.cpp | 17 ++--------------- .../gc/shenandoah/shenandoahHeapRegion.hpp | 1 + .../gc/shenandoah/shenandoahMarkingContext.cpp | 7 +++++-- 6 files changed, 30 insertions(+), 24 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAsserts.cpp b/src/hotspot/share/gc/shenandoah/shenandoahAsserts.cpp index ad00ab2ada9a..34b4f235a00e 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahAsserts.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahAsserts.cpp @@ -30,6 +30,7 @@ #include "gc/shenandoah/shenandoahMarkingContext.inline.hpp" #include "gc/shenandoah/shenandoahUtils.hpp" #include "memory/resourceArea.hpp" +#include "runtime/orderAccess.hpp" void print_raw_memory(ShenandoahMessageBuffer &msg, void* loc) { // Be extra safe. Only access data that is guaranteed to be safe: @@ -381,6 +382,15 @@ void ShenandoahAsserts::assert_marked_strong(void *interior_loc, oop obj, const } } +void ShenandoahAsserts::assert_bitmap_clear_above_top(ShenandoahHeapRegion* region) { + ShenandoahMarkingContext* const ctx = ShenandoahHeap::heap()->marking_context(); + const HeapWord* top_bitmap = ctx->top_bitmap(region); + // Make sure that top is loaded before any of the marks from the bitmap are loaded. If another + // thread has cleared the bitmap we must not allow any stale reads. + OrderAccess::loadload(); + assert(ctx->is_bitmap_range_within_region_clear(top_bitmap, region->end()), "Bitmap above top_bitmap() must be clear"); +} + void ShenandoahAsserts::assert_in_cset(void* interior_loc, oop obj, const char* file, int line) { assert_correct(interior_loc, obj, file, line); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAsserts.hpp b/src/hotspot/share/gc/shenandoah/shenandoahAsserts.hpp index 31a99bf438cf..2d34c797566c 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahAsserts.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahAsserts.hpp @@ -30,6 +30,8 @@ #include "runtime/mutex.hpp" #include "utilities/formatBuffer.hpp" +class ShenandoahHeapRegion; + typedef FormatBuffer<8192> ShenandoahMessageBuffer; class ShenandoahAsserts { @@ -64,6 +66,7 @@ class ShenandoahAsserts { static void assert_marked(void* interior_loc, oop obj, const char* file, int line); static void assert_marked_weak(void* interior_loc, oop obj, const char* file, int line); static void assert_marked_strong(void* interior_loc, oop obj, const char* file, int line); + static void assert_bitmap_clear_above_top(ShenandoahHeapRegion* region); static void assert_in_cset(void* interior_loc, oop obj, const char* file, int line); static void assert_not_in_cset(void* interior_loc, oop obj, const char* file, int line); static void assert_not_in_cset_loc(void* interior_loc, const char* file, int line); @@ -127,6 +130,9 @@ class ShenandoahAsserts { #define shenandoah_assert_marked_strong(interior_loc, obj) \ ShenandoahAsserts::assert_marked_strong(interior_loc, obj, __FILE__, __LINE__) +#define shenandoah_assert_clear_above_top(region) \ + ShenandoahAsserts::assert_bitmap_clear_above_top(region) + #define shenandoah_assert_in_cset_if(interior_loc, obj, condition) \ if (condition) ShenandoahAsserts::assert_in_cset(interior_loc, obj, __FILE__, __LINE__) #define shenandoah_assert_in_cset_except(interior_loc, obj, exception) \ @@ -211,6 +217,7 @@ class ShenandoahAsserts { #define shenandoah_assert_marked_strong_except(interior_loc, obj, exception) #define shenandoah_assert_marked_strong(interior_loc, obj) +#define shenandoah_assert_clear_above_top(region) #define shenandoah_assert_in_cset_if(interior_loc, obj, condition) #define shenandoah_assert_in_cset_except(interior_loc, obj, exception) #define shenandoah_assert_in_cset(interior_loc, obj) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.cpp b/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.cpp index 87c4943b2383..d32e940d646f 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.cpp @@ -1,7 +1,7 @@ /* * Copyright (c) 2016, 2021, Red Hat, Inc. All rights reserved. * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -36,7 +36,6 @@ #include "gc/shenandoah/shenandoahYoungGeneration.hpp" #include "logging/logStream.hpp" #include "memory/resourceArea.hpp" -#include "runtime/orderAccess.hpp" static const char* partition_name(ShenandoahFreeSetPartitionId t) { switch (t) { @@ -1049,11 +1048,10 @@ HeapWord* ShenandoahFreeSet::try_allocate_in(ShenandoahHeapRegion* r, Shenandoah } _heap->generation_for(r->affiliation())->increment_affiliated_region_count(); -#ifdef ASSERT - ShenandoahMarkingContext* const ctx = _heap->marking_context(); - assert(ctx->top_at_mark_start(r) == r->bottom(), "Newly established allocation region starts with TAMS equal to bottom"); - assert(ctx->is_bitmap_range_within_region_clear(ctx->top_bitmap(r), r->end()), "Bitmap above top_bitmap() must be clear"); -#endif + + assert(_heap->marking_context()->top_at_mark_start(r) == r->bottom(), + "Newly established allocation region (%zu) must start with TAMS equal to bottom", r->index()); + shenandoah_assert_clear_above_top(r); log_debug(gc, free)("Using new region (%zu) for %s (" PTR_FORMAT ").", r->index(), ShenandoahAllocRequest::alloc_type_to_string(req.type()), p2i(&req)); } else { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.cpp index 2736376fe9a2..39f25194a6d7 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2013, 2020, Red Hat, Inc. All rights reserved. * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. @@ -32,7 +32,6 @@ #include "gc/shenandoah/shenandoahGeneration.hpp" #include "gc/shenandoah/shenandoahHeap.inline.hpp" #include "gc/shenandoah/shenandoahHeapRegion.hpp" -#include "gc/shenandoah/shenandoahHeapRegionSet.inline.hpp" #include "gc/shenandoah/shenandoahMarkingContext.inline.hpp" #include "gc/shenandoah/shenandoahOldGeneration.hpp" #include "gc/shenandoah/shenandoahScanRemembered.inline.hpp" @@ -40,15 +39,12 @@ #include "jfr/jfrEvents.hpp" #include "memory/allocation.hpp" #include "memory/iterator.inline.hpp" -#include "memory/resourceArea.hpp" #include "memory/universe.hpp" #include "oops/oop.inline.hpp" #include "runtime/atomic.hpp" #include "runtime/globals_extension.hpp" #include "runtime/java.hpp" -#include "runtime/mutexLocker.hpp" #include "runtime/os.hpp" -#include "runtime/safepoint.hpp" #include "utilities/powerOfTwo.hpp" size_t ShenandoahHeapRegion::RegionCount = 0; @@ -863,16 +859,7 @@ void ShenandoahHeapRegion::set_affiliation(ShenandoahAffiliation new_affiliation p2i(top()), p2i(ctx->top_at_mark_start(this)), p2i(_update_watermark), p2i(ctx->top_bitmap(this))); } -#ifdef ASSERT - { - size_t idx = this->index(); - HeapWord* top_bitmap = ctx->top_bitmap(this); - - assert(ctx->is_bitmap_range_within_region_clear(top_bitmap, _end), - "Region %zu, bitmap should be clear between top_bitmap: " PTR_FORMAT " and end: " PTR_FORMAT, idx, - p2i(top_bitmap), p2i(_end)); - } -#endif + shenandoah_assert_clear_above_top(this); if (region_affiliation == new_affiliation) { return; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.hpp index 20040eebafd8..c9a4aa5d44ce 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.hpp @@ -269,6 +269,7 @@ class ShenandoahHeapRegion { ShenandoahSharedFlag _recycling; // Used to indicate that the region is being recycled; see try_recycle*(). + // This is only read/written by a gc worker to avoid unnecessary bitmap resets bool _needs_bitmap_reset; public: diff --git a/src/hotspot/share/gc/shenandoah/shenandoahMarkingContext.cpp b/src/hotspot/share/gc/shenandoah/shenandoahMarkingContext.cpp index 40eee8c342ba..87629cefb0d4 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahMarkingContext.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahMarkingContext.cpp @@ -1,7 +1,7 @@ /* * Copyright (c) 2018, 2021, Red Hat, Inc. All rights reserved. * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,7 +27,7 @@ #include "gc/shared/markBitMap.inline.hpp" #include "gc/shenandoah/shenandoahHeap.inline.hpp" #include "gc/shenandoah/shenandoahMarkingContext.hpp" -#include "shenandoahGlobalGeneration.hpp" +#include "runtime/orderAccess.hpp" ShenandoahMarkingContext::ShenandoahMarkingContext(MemRegion heap_region, MemRegion bitmap_region, size_t num_regions) : _mark_bit_map(heap_region, bitmap_region), @@ -91,6 +91,9 @@ void ShenandoahMarkingContext::clear_bitmap(ShenandoahHeapRegion* r) { if (top_bitmap > bottom) { _mark_bit_map.clear_range_large(MemRegion(bottom, top_bitmap)); + // All bitmap writes must complete before we update top at bitmap. If these writes were reordered, + // other threads could see stale marks above top, which is not valid. + OrderAccess::storestore(); _top_bitmaps[r->index()] = bottom; } From f45ef0ce82910c980b27e832a9db6fb668717d8a Mon Sep 17 00:00:00 2001 From: Simon Tooke Date: Tue, 14 Jul 2026 14:16:27 +0000 Subject: [PATCH 62/86] 8360934: Add AVX-512 intrinsics for ML-KEM - enhancement on AVX512_VBMI Backport-of: a0e6f028a8952f61d9115f7bdf04b8a87f8ebba4 --- .../cpu/x86/stubGenerator_x86_64_kyber.cpp | 92 ++++++++++++++++++- 1 file changed, 90 insertions(+), 2 deletions(-) diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64_kyber.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_kyber.cpp index 91c005e92de4..95c0ac0c758f 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64_kyber.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_kyber.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * 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,6 +64,39 @@ static address kyberAvx512ConstsAddr(int offset) { const Register scratch = r10; +ATTRIBUTE_ALIGNED(64) static const uint8_t kyberAvx512_12To16Dup[] = { +// 0 - 63 + 0, 1, 1, 2, 3, 4, 4, 5, 6, 7, 7, 8, 9, 10, 10, 11, 12, 13, 13, 14, 15, 16, + 16, 17, 18, 19, 19, 20, 21, 22, 22, 23, 24, 25, 25, 26, 27, 28, 28, 29, 30, + 31, 31, 32, 33, 34, 34, 35, 36, 37, 37, 38, 39, 40, 40, 41, 42, 43, 43, 44, + 45, 46, 46, 47 + }; + +static address kyberAvx512_12To16DupAddr() { + return (address) kyberAvx512_12To16Dup; +} + +ATTRIBUTE_ALIGNED(64) static const uint16_t kyberAvx512_12To16Shift[] = { +// 0 - 31 + 0, 4, 0, 4, 0, 4, 0, 4, 0, 4, 0, 4, 0, 4, 0, 4, 0, 4, 0, 4, 0, 4, 0, 4, 0, + 4, 0, 4, 0, 4, 0, 4 + }; + +static address kyberAvx512_12To16ShiftAddr() { + return (address) kyberAvx512_12To16Shift; +} + +ATTRIBUTE_ALIGNED(64) static const uint64_t kyberAvx512_12To16And[] = { +// 0 - 7 + 0x0FFF0FFF0FFF0FFF, 0x0FFF0FFF0FFF0FFF, 0x0FFF0FFF0FFF0FFF, + 0x0FFF0FFF0FFF0FFF, 0x0FFF0FFF0FFF0FFF, 0x0FFF0FFF0FFF0FFF, + 0x0FFF0FFF0FFF0FFF, 0x0FFF0FFF0FFF0FFF + }; + +static address kyberAvx512_12To16AndAddr() { + return (address) kyberAvx512_12To16And; +} + ATTRIBUTE_ALIGNED(64) static const uint16_t kyberAvx512NttPerms[] = { // 0 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, @@ -822,10 +855,65 @@ address generate_kyber12To16_avx512(StubGenerator *stubgen, const Register perms = r11; - Label Loop; + Label Loop, VBMILoop; __ addptr(condensed, condensedOffs); + if (VM_Version::supports_avx512_vbmi()) { + // mask load for the first 48 bytes of each vector + __ mov64(rax, 0x0000FFFFFFFFFFFF); + __ kmovql(k1, rax); + + __ lea(perms, ExternalAddress(kyberAvx512_12To16DupAddr())); + __ evmovdqub(xmm20, Address(perms), Assembler::AVX_512bit); + + __ lea(perms, ExternalAddress(kyberAvx512_12To16ShiftAddr())); + __ evmovdquw(xmm21, Address(perms), Assembler::AVX_512bit); + + __ lea(perms, ExternalAddress(kyberAvx512_12To16AndAddr())); + __ evmovdquq(xmm22, Address(perms), Assembler::AVX_512bit); + + __ align(OptoLoopAlignment); + __ BIND(VBMILoop); + + __ evmovdqub(xmm0, k1, Address(condensed, 0), false, + Assembler::AVX_512bit); + __ evmovdqub(xmm1, k1, Address(condensed, 48), false, + Assembler::AVX_512bit); + __ evmovdqub(xmm2, k1, Address(condensed, 96), false, + Assembler::AVX_512bit); + __ evmovdqub(xmm3, k1, Address(condensed, 144), false, + Assembler::AVX_512bit); + + __ evpermb(xmm4, k0, xmm20, xmm0, false, Assembler::AVX_512bit); + __ evpermb(xmm5, k0, xmm20, xmm1, false, Assembler::AVX_512bit); + __ evpermb(xmm6, k0, xmm20, xmm2, false, Assembler::AVX_512bit); + __ evpermb(xmm7, k0, xmm20, xmm3, false, Assembler::AVX_512bit); + + __ evpsrlvw(xmm4, xmm4, xmm21, Assembler::AVX_512bit); + __ evpsrlvw(xmm5, xmm5, xmm21, Assembler::AVX_512bit); + __ evpsrlvw(xmm6, xmm6, xmm21, Assembler::AVX_512bit); + __ evpsrlvw(xmm7, xmm7, xmm21, Assembler::AVX_512bit); + + __ evpandq(xmm0, xmm22, xmm4, Assembler::AVX_512bit); + __ evpandq(xmm1, xmm22, xmm5, Assembler::AVX_512bit); + __ evpandq(xmm2, xmm22, xmm6, Assembler::AVX_512bit); + __ evpandq(xmm3, xmm22, xmm7, Assembler::AVX_512bit); + + store4regs(parsed, 0, xmm0_3, _masm); + + __ addptr(condensed, 192); + __ addptr(parsed, 256); + __ subl(parsedLength, 128); + __ jcc(Assembler::greater, VBMILoop); + + __ leave(); // required for proper stackwalking of RuntimeStub frame + __ mov64(rax, 0); // return 0 + __ ret(0); + + return start; + } + __ lea(perms, ExternalAddress(kyberAvx512_12To16PermsAddr())); load4regs(xmm24_27, perms, 0, _masm); From 52d382278d5829c82d39be10593c855076abeaed Mon Sep 17 00:00:00 2001 From: Simon Tooke Date: Tue, 14 Jul 2026 14:20:28 +0000 Subject: [PATCH 63/86] 8374755: ML-KEM's 12-bit decompression can be simplified on aarch64 Reviewed-by: aph Backport-of: 99119597aa95c1139ae2259bed5ec885a7c01269 --- .../cpu/aarch64/stubGenerator_aarch64.cpp | 83 +++---------------- .../com/sun/crypto/provider/ML_KEM.java | 22 ++--- 2 files changed, 18 insertions(+), 87 deletions(-) diff --git a/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp b/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp index a0d1e22ff969..b7f064bcd8cb 100644 --- a/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp @@ -6024,14 +6024,18 @@ class StubGenerator: public StubCodeGenerator { // static int implKyber12To16( // byte[] condensed, int index, short[] parsed, int parsedLength) {} // - // (parsedLength or (parsedLength - 48) must be divisible by 64.) + // we assume that parsed and condensed are allocated such that for + // n = (parsedLength + 63) / 64 + // n blocks of 96 bytes of input can be processed, i.e. + // index + n * 96 <= condensed.length and + // n * 64 <= parsed.length // // condensed (byte[]) = c_rarg0 // condensedIndex = c_rarg1 - // parsed (short[112 or 256]) = c_rarg2 - // parsedLength (112 or 256) = c_rarg3 + // parsed (short[]) = c_rarg2 + // parsedLength = c_rarg3 address generate_kyber12To16() { - Label L_F00, L_loop, L_end; + Label L_F00, L_loop; __ BIND(L_F00); __ emit_int64(0x0f000f000f000f00); @@ -6156,75 +6160,8 @@ class StubGenerator: public StubCodeGenerator { vs_st2_post(vs_front(vb), __ T8H, parsed); __ sub(parsedLength, parsedLength, 64); - __ cmp(parsedLength, (u1)64); - __ br(Assembler::GE, L_loop); - __ cbz(parsedLength, L_end); - - // if anything is left it should be a final 72 bytes of input - // i.e. a final 48 12-bit values. so we handle this by loading - // 48 bytes into all 16B lanes of front(vin) and only 24 - // bytes into the lower 8B lane of back(vin) - vs_ld3_post(vs_front(vin), __ T16B, condensed); - vs_ld3(vs_back(vin), __ T8B, condensed); - - // Expand vin[0] into va[0:1], and vin[1] into va[2:3] and va[4:5] - // n.b. target elements 2 and 3 of va duplicate elements 4 and - // 5 and target element 2 of vb duplicates element 4. - __ ushll(va[0], __ T8H, vin[0], __ T8B, 0); - __ ushll2(va[1], __ T8H, vin[0], __ T16B, 0); - __ ushll(va[2], __ T8H, vin[1], __ T8B, 0); - __ ushll2(va[3], __ T8H, vin[1], __ T16B, 0); - __ ushll(va[4], __ T8H, vin[1], __ T8B, 0); - __ ushll2(va[5], __ T8H, vin[1], __ T16B, 0); - - // This time expand just the lower 8 lanes - __ ushll(vb[0], __ T8H, vin[3], __ T8B, 0); - __ ushll(vb[2], __ T8H, vin[4], __ T8B, 0); - __ ushll(vb[4], __ T8H, vin[4], __ T8B, 0); - - // shift lo byte of copy 1 of the middle stripe into the high byte - __ shl(va[2], __ T8H, va[2], 8); - __ shl(va[3], __ T8H, va[3], 8); - __ shl(vb[2], __ T8H, vb[2], 8); - - // expand vin[2] into va[6:7] and lower 8 lanes of vin[5] into - // vb[6] pre-shifted by 4 to ensure top bits of the input 12-bit - // int are in bit positions [4..11]. - __ ushll(va[6], __ T8H, vin[2], __ T8B, 4); - __ ushll2(va[7], __ T8H, vin[2], __ T16B, 4); - __ ushll(vb[6], __ T8H, vin[5], __ T8B, 4); - - // mask hi 4 bits of each 1st 12-bit int in pair from copy1 and - // shift lo 4 bits of each 2nd 12-bit int in pair to bottom of - // copy2 - __ andr(va[2], __ T16B, va[2], v31); - __ andr(va[3], __ T16B, va[3], v31); - __ ushr(va[4], __ T8H, va[4], 4); - __ ushr(va[5], __ T8H, va[5], 4); - __ andr(vb[2], __ T16B, vb[2], v31); - __ ushr(vb[4], __ T8H, vb[4], 4); - - - - // sum hi 4 bits and lo 8 bits of each 1st 12-bit int in pair and - // hi 8 bits plus lo 4 bits of each 2nd 12-bit int in pair - - // n.b. ordering ensures: i) inputs are consumed before they are - // overwritten ii) order of 16-bit results across succsessive - // pairs of vectors in va and then lower half of vb reflects order - // of corresponding 12-bit inputs - __ addv(va[0], __ T8H, va[0], va[2]); - __ addv(va[2], __ T8H, va[1], va[3]); - __ addv(va[1], __ T8H, va[4], va[6]); - __ addv(va[3], __ T8H, va[5], va[7]); - __ addv(vb[0], __ T8H, vb[0], vb[2]); - __ addv(vb[1], __ T8H, vb[4], vb[6]); - - // store 48 results interleaved as shorts - vs_st2_post(vs_front(va), __ T8H, parsed); - vs_st2_post(vs_front(vs_front(vb)), __ T8H, parsed); - - __ BIND(L_end); + __ cmp(parsedLength, (u1)0); + __ br(Assembler::GT, L_loop); __ leave(); // required for proper stackwalking of RuntimeStub frame __ mov(r0, zr); // return 0 diff --git a/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM.java b/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM.java index b463f3599952..56a119893a79 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM.java @@ -1363,22 +1363,16 @@ private static void implKyber12To16Java(byte[] condensed, int index, short[] par } } - // The intrinsic implementations assume that the input and output buffers - // are such that condensed can be read in 96-byte chunks and - // parsed can be written in 64 shorts chunks except for the last chunk - // that can be either 48 or 64 shorts. In other words, - // if (i - 1) * 64 < parsedLengths <= i * 64 then - // parsed.length should be either i * 64 or (i-1) * 64 + 48 and - // condensed.length should be at least index + i * 96. + // An intrinsic implementation assumes that the input and output buffers + // are such that condensed can be read in chunks of 192 bytes and + // parsed can be written in chunks of 128 shorts, so callers should allocate + // the condensed and parsed arrays accordingly, see the assert() private void twelve2Sixteen(byte[] condensed, int index, short[] parsed, int parsedLength) { - int i = parsedLength / 64; - int remainder = parsedLength - i * 64; - if (remainder != 0) { - i++; - } - assert ((remainder == 0) || (remainder == 48)) && - (index + i * 96 <= condensed.length); + int n = (parsedLength + 127) / 128; + assert ((parsed.length >= n * 128) && + (condensed.length >= index + n * 192)); + implKyber12To16(condensed, index, parsed, parsedLength); } From 6224bfcb61484ab71f7cfb74fb000b7ec17a3f85 Mon Sep 17 00:00:00 2001 From: Kangcheng Xu Date: Wed, 15 Jul 2026 16:03:09 +0000 Subject: [PATCH 64/86] 8347901: C2 should remove unused leaf / pure runtime calls Backport-of: ed70910b0f3e1b19d915ec13ac3434407d01bc5d --- src/hotspot/share/opto/callnode.cpp | 53 +++++++- src/hotspot/share/opto/callnode.hpp | 29 ++++- src/hotspot/share/opto/classes.hpp | 2 + src/hotspot/share/opto/compile.cpp | 19 +++ src/hotspot/share/opto/divnode.cpp | 158 ++++++++++-------------- src/hotspot/share/opto/divnode.hpp | 35 +++--- src/hotspot/share/opto/graphKit.cpp | 29 +++-- src/hotspot/share/opto/graphKit.hpp | 1 + src/hotspot/share/opto/library_call.cpp | 2 +- src/hotspot/share/opto/macro.cpp | 15 +-- src/hotspot/share/opto/multnode.cpp | 13 ++ src/hotspot/share/opto/multnode.hpp | 46 +++++++ src/hotspot/share/opto/node.cpp | 12 +- src/hotspot/share/opto/node.hpp | 5 +- src/hotspot/share/opto/parse2.cpp | 8 +- 15 files changed, 282 insertions(+), 145 deletions(-) diff --git a/src/hotspot/share/opto/callnode.cpp b/src/hotspot/share/opto/callnode.cpp index 6f13ce0f809a..fc4b0c35dff3 100644 --- a/src/hotspot/share/opto/callnode.cpp +++ b/src/hotspot/share/opto/callnode.cpp @@ -935,7 +935,7 @@ Node *CallNode::result_cast() { } -void CallNode::extract_projections(CallProjections* projs, bool separate_io_proj, bool do_asserts) { +void CallNode::extract_projections(CallProjections* projs, bool separate_io_proj, bool do_asserts) const { projs->fallthrough_proj = nullptr; projs->fallthrough_catchproj = nullptr; projs->fallthrough_ioproj = nullptr; @@ -1319,6 +1319,57 @@ void CallLeafVectorNode::calling_convention( BasicType* sig_bt, VMRegPair *parm_ //============================================================================= +bool CallLeafPureNode::is_unused() const { + return proj_out_or_null(TypeFunc::Parms) == nullptr; +} + +bool CallLeafPureNode::is_dead() const { + return proj_out_or_null(TypeFunc::Control) == nullptr; +} + +/* We make a tuple of the global input state + TOP for the output values. + * We use this to delete a pure function that is not used: by replacing the call with + * such a tuple, we let output Proj's idealization pick the corresponding input of the + * pure call, so jumping over it, and effectively, removing the call from the graph. + * This avoids doing the graph surgery manually, but leaves that to IGVN + * that is specialized for doing that right. We need also tuple components for output + * values of the function to respect the return arity, and in case there is a projection + * that would pick an output (which shouldn't happen at the moment). + */ +TupleNode* CallLeafPureNode::make_tuple_of_input_state_and_top_return_values(const Compile* C) const { + // Transparently propagate input state but parameters + TupleNode* tuple = TupleNode::make( + tf()->range(), + in(TypeFunc::Control), + in(TypeFunc::I_O), + in(TypeFunc::Memory), + in(TypeFunc::FramePtr), + in(TypeFunc::ReturnAdr)); + + // And add TOPs for the return values + for (uint i = TypeFunc::Parms; i < tf()->range()->cnt(); i++) { + tuple->set_req(i, C->top()); + } + + return tuple; +} + +Node* CallLeafPureNode::Ideal(PhaseGVN* phase, bool can_reshape) { + if (is_dead()) { + return nullptr; + } + + // We need to wait until IGVN because during parsing, usages might still be missing + // and we would remove the call immediately. + if (can_reshape && is_unused()) { + // The result is not used. We remove the call by replacing it with a tuple, that + // is later disintegrated by the projections. + return make_tuple_of_input_state_and_top_return_values(phase->C); + } + + return CallRuntimeNode::Ideal(phase, can_reshape); +} + #ifndef PRODUCT void CallLeafNode::dump_spec(outputStream *st) const { st->print("# "); diff --git a/src/hotspot/share/opto/callnode.hpp b/src/hotspot/share/opto/callnode.hpp index f51543b68662..63ec9677b723 100644 --- a/src/hotspot/share/opto/callnode.hpp +++ b/src/hotspot/share/opto/callnode.hpp @@ -752,7 +752,7 @@ class CallNode : public SafePointNode { // Collect all the interesting edges from a call for use in // replacing the call by something else. Used by macro expansion // and the late inlining support. - void extract_projections(CallProjections* projs, bool separate_io_proj, bool do_asserts = true); + void extract_projections(CallProjections* projs, bool separate_io_proj, bool do_asserts = true) const; virtual uint match_edge(uint idx) const; @@ -939,6 +939,33 @@ class CallLeafNode : public CallRuntimeNode { #endif }; +/* A pure function call, they are assumed not to be safepoints, not to read or write memory, + * have no exception... They just take parameters, return a value without side effect. It is + * always correct to create some, or remove them, if the result is not used. + * + * They still have control input to allow easy lowering into other kind of calls that require + * a control, but this is more a technical than a moral constraint. + * + * Pure calls must have only control and data input and output: I/O, Memory and so on must be top. + * Nevertheless, pure calls can typically be expensive math operations so care must be taken + * when letting the node float. + */ +class CallLeafPureNode : public CallLeafNode { +protected: + bool is_unused() const; + bool is_dead() const; + TupleNode* make_tuple_of_input_state_and_top_return_values(const Compile* C) const; + +public: + CallLeafPureNode(const TypeFunc* tf, address addr, const char* name, + const TypePtr* adr_type) + : CallLeafNode(tf, addr, name, adr_type) { + init_class_id(Class_CallLeafPure); + } + int Opcode() const override; + Node* Ideal(PhaseGVN* phase, bool can_reshape) override; +}; + //------------------------------CallLeafNoFPNode------------------------------- // CallLeafNode, not using floating point or using it in the same manner as // the generated code diff --git a/src/hotspot/share/opto/classes.hpp b/src/hotspot/share/opto/classes.hpp index bc259eed2d10..587d5fad8f29 100644 --- a/src/hotspot/share/opto/classes.hpp +++ b/src/hotspot/share/opto/classes.hpp @@ -61,6 +61,7 @@ macro(CallDynamicJava) macro(CallJava) macro(CallLeaf) macro(CallLeafNoFP) +macro(CallLeafPure) macro(CallLeafVector) macro(CallRuntime) macro(CallStaticJava) @@ -372,6 +373,7 @@ macro(SubI) macro(SubL) macro(TailCall) macro(TailJump) +macro(Tuple) macro(MacroLogicV) macro(ThreadLocal) macro(Unlock) diff --git a/src/hotspot/share/opto/compile.cpp b/src/hotspot/share/opto/compile.cpp index f6d0072f1fce..1c209a6462a8 100644 --- a/src/hotspot/share/opto/compile.cpp +++ b/src/hotspot/share/opto/compile.cpp @@ -3288,6 +3288,25 @@ void Compile::final_graph_reshaping_main_switch(Node* n, Final_Reshape_Counts& f case Op_Opaque1: // Remove Opaque Nodes before matching n->subsume_by(n->in(1), this); break; + case Op_CallLeafPure: { + // If the pure call is not supported, then lower to a CallLeaf. + if (!Matcher::match_rule_supported(Op_CallLeafPure)) { + CallNode* call = n->as_Call(); + CallNode* new_call = new CallLeafNode(call->tf(), call->entry_point(), + call->_name, TypeRawPtr::BOTTOM); + new_call->init_req(TypeFunc::Control, call->in(TypeFunc::Control)); + new_call->init_req(TypeFunc::I_O, C->top()); + new_call->init_req(TypeFunc::Memory, C->top()); + new_call->init_req(TypeFunc::ReturnAdr, C->top()); + new_call->init_req(TypeFunc::FramePtr, C->top()); + for (unsigned int i = TypeFunc::Parms; i < call->tf()->domain()->cnt(); i++) { + new_call->init_req(i, call->in(i)); + } + n->subsume_by(new_call, this); + } + frc.inc_call_count(); + break; + } case Op_CallStaticJava: case Op_CallJava: case Op_CallDynamicJava: diff --git a/src/hotspot/share/opto/divnode.cpp b/src/hotspot/share/opto/divnode.cpp index a70194274a79..5dd8be877ffe 100644 --- a/src/hotspot/share/opto/divnode.cpp +++ b/src/hotspot/share/opto/divnode.cpp @@ -42,19 +42,19 @@ #include -ModFloatingNode::ModFloatingNode(Compile* C, const TypeFunc* tf, const char* name) : CallLeafNode(tf, nullptr, name, TypeRawPtr::BOTTOM) { +ModFloatingNode::ModFloatingNode(Compile* C, const TypeFunc* tf, address addr, const char* name) : CallLeafPureNode(tf, addr, name, TypeRawPtr::BOTTOM) { add_flag(Flag_is_macro); C->add_macro_node(this); } -ModDNode::ModDNode(Compile* C, Node* a, Node* b) : ModFloatingNode(C, OptoRuntime::Math_DD_D_Type(), "drem") { +ModDNode::ModDNode(Compile* C, Node* a, Node* b) : ModFloatingNode(C, OptoRuntime::Math_DD_D_Type(), CAST_FROM_FN_PTR(address, SharedRuntime::drem), "drem") { init_req(TypeFunc::Parms + 0, a); init_req(TypeFunc::Parms + 1, C->top()); init_req(TypeFunc::Parms + 2, b); init_req(TypeFunc::Parms + 3, C->top()); } -ModFNode::ModFNode(Compile* C, Node* a, Node* b) : ModFloatingNode(C, OptoRuntime::modf_Type(), "frem") { +ModFNode::ModFNode(Compile* C, Node* a, Node* b) : ModFloatingNode(C, OptoRuntime::modf_Type(), CAST_FROM_FN_PTR(address, SharedRuntime::frem), "frem") { init_req(TypeFunc::Parms + 0, a); init_req(TypeFunc::Parms + 1, b); } @@ -1516,137 +1516,109 @@ const Type* UModLNode::Value(PhaseGVN* phase) const { return unsigned_mod_value(phase, this); } -Node* ModFNode::Ideal(PhaseGVN* phase, bool can_reshape) { - if (!can_reshape) { - return nullptr; - } - PhaseIterGVN* igvn = phase->is_IterGVN(); - - bool result_is_unused = proj_out_or_null(TypeFunc::Parms) == nullptr; - bool not_dead = proj_out_or_null(TypeFunc::Control) != nullptr; - if (result_is_unused && not_dead) { - return replace_with_con(igvn, TypeF::make(0.)); - } - - // Either input is TOP ==> the result is TOP - const Type* t1 = phase->type(dividend()); - const Type* t2 = phase->type(divisor()); - if (t1 == Type::TOP || t2 == Type::TOP) { - return phase->C->top(); - } - +const Type* ModFNode::get_result_if_constant(const Type* dividend, const Type* divisor) const { // If either number is not a constant, we know nothing. - if ((t1->base() != Type::FloatCon) || (t2->base() != Type::FloatCon)) { + if ((dividend->base() != Type::FloatCon) || (divisor->base() != Type::FloatCon)) { return nullptr; // note: x%x can be either NaN or 0 } - float f1 = t1->getf(); - float f2 = t2->getf(); - jint x1 = jint_cast(f1); // note: *(int*)&f1, not just (int)f1 - jint x2 = jint_cast(f2); + float dividend_f = dividend->getf(); + float divisor_f = divisor->getf(); + jint dividend_i = jint_cast(dividend_f); // note: *(int*)&f1, not just (int)f1 + jint divisor_i = jint_cast(divisor_f); // If either is a NaN, return an input NaN - if (g_isnan(f1)) { - return replace_with_con(igvn, t1); + if (g_isnan(dividend_f)) { + return dividend; } - if (g_isnan(f2)) { - return replace_with_con(igvn, t2); + if (g_isnan(divisor_f)) { + return divisor; } // If an operand is infinity or the divisor is +/- zero, punt. - if (!g_isfinite(f1) || !g_isfinite(f2) || x2 == 0 || x2 == min_jint) { + if (!g_isfinite(dividend_f) || !g_isfinite(divisor_f) || divisor_i == 0 || divisor_i == min_jint) { return nullptr; } // We must be modulo'ing 2 float constants. // Make sure that the sign of the fmod is equal to the sign of the dividend - jint xr = jint_cast(fmod(f1, f2)); - if ((x1 ^ xr) < 0) { + jint xr = jint_cast(fmod(dividend_f, divisor_f)); + if ((dividend_i ^ xr) < 0) { xr ^= min_jint; } - return replace_with_con(igvn, TypeF::make(jfloat_cast(xr))); + return TypeF::make(jfloat_cast(xr)); } -Node* ModDNode::Ideal(PhaseGVN* phase, bool can_reshape) { - if (!can_reshape) { - return nullptr; - } - PhaseIterGVN* igvn = phase->is_IterGVN(); - - bool result_is_unused = proj_out_or_null(TypeFunc::Parms) == nullptr; - bool not_dead = proj_out_or_null(TypeFunc::Control) != nullptr; - if (result_is_unused && not_dead) { - return replace_with_con(igvn, TypeD::make(0.)); - } - - // Either input is TOP ==> the result is TOP - const Type* t1 = phase->type(dividend()); - const Type* t2 = phase->type(divisor()); - if (t1 == Type::TOP || t2 == Type::TOP) { - return nullptr; - } - +const Type* ModDNode::get_result_if_constant(const Type* dividend, const Type* divisor) const { // If either number is not a constant, we know nothing. - if ((t1->base() != Type::DoubleCon) || (t2->base() != Type::DoubleCon)) { + if ((dividend->base() != Type::DoubleCon) || (divisor->base() != Type::DoubleCon)) { return nullptr; // note: x%x can be either NaN or 0 } - double f1 = t1->getd(); - double f2 = t2->getd(); - jlong x1 = jlong_cast(f1); // note: *(long*)&f1, not just (long)f1 - jlong x2 = jlong_cast(f2); + double dividend_d = dividend->getd(); + double divisor_d = divisor->getd(); + jlong dividend_l = jlong_cast(dividend_d); // note: *(long*)&f1, not just (long)f1 + jlong divisor_l = jlong_cast(divisor_d); // If either is a NaN, return an input NaN - if (g_isnan(f1)) { - return replace_with_con(igvn, t1); + if (g_isnan(dividend_d)) { + return dividend; } - if (g_isnan(f2)) { - return replace_with_con(igvn, t2); + if (g_isnan(divisor_d)) { + return divisor; } // If an operand is infinity or the divisor is +/- zero, punt. - if (!g_isfinite(f1) || !g_isfinite(f2) || x2 == 0 || x2 == min_jlong) { + if (!g_isfinite(dividend_d) || !g_isfinite(divisor_d) || divisor_l == 0 || divisor_l == min_jlong) { return nullptr; } // We must be modulo'ing 2 double constants. // Make sure that the sign of the fmod is equal to the sign of the dividend - jlong xr = jlong_cast(fmod(f1, f2)); - if ((x1 ^ xr) < 0) { + jlong xr = jlong_cast(fmod(dividend_d, divisor_d)); + if ((dividend_l ^ xr) < 0) { xr ^= min_jlong; } - return replace_with_con(igvn, TypeD::make(jdouble_cast(xr))); + return TypeD::make(jdouble_cast(xr)); } -Node* ModFloatingNode::replace_with_con(PhaseIterGVN* phase, const Type* con) { - Compile* C = phase->C; - Node* con_node = phase->makecon(con); - CallProjections projs; - extract_projections(&projs, false, false); - phase->replace_node(projs.fallthrough_proj, in(TypeFunc::Control)); - if (projs.fallthrough_catchproj != nullptr) { - phase->replace_node(projs.fallthrough_catchproj, in(TypeFunc::Control)); - } - if (projs.fallthrough_memproj != nullptr) { - phase->replace_node(projs.fallthrough_memproj, in(TypeFunc::Memory)); - } - if (projs.catchall_memproj != nullptr) { - phase->replace_node(projs.catchall_memproj, C->top()); - } - if (projs.fallthrough_ioproj != nullptr) { - phase->replace_node(projs.fallthrough_ioproj, in(TypeFunc::I_O)); - } - assert(projs.catchall_ioproj == nullptr, "no exceptions from floating mod"); - assert(projs.catchall_catchproj == nullptr, "no exceptions from floating mod"); - if (projs.resproj != nullptr) { - phase->replace_node(projs.resproj, con_node); +Node* ModFloatingNode::Ideal(PhaseGVN* phase, bool can_reshape) { + if (can_reshape) { + PhaseIterGVN* igvn = phase->is_IterGVN(); + + // Either input is TOP ==> the result is TOP + const Type* dividend_type = phase->type(dividend()); + const Type* divisor_type = phase->type(divisor()); + if (dividend_type == Type::TOP || divisor_type == Type::TOP) { + return phase->C->top(); + } + const Type* constant_result = get_result_if_constant(dividend_type, divisor_type); + if (constant_result != nullptr) { + return make_tuple_of_input_state_and_constant_result(igvn, constant_result); + } } - phase->replace_node(this, C->top()); - C->remove_macro_node(this); - disconnect_inputs(C); - return nullptr; + + return CallLeafPureNode::Ideal(phase, can_reshape); +} + +/* Give a tuple node for ::Ideal to return, made of the input state (control to return addr) + * and the given constant result. Idealization of projections will make sure to transparently + * propagate the input state and replace the result by the said constant. + */ +TupleNode* ModFloatingNode::make_tuple_of_input_state_and_constant_result(PhaseIterGVN* phase, const Type* con) const { + Node* con_node = phase->makecon(con); + TupleNode* tuple = TupleNode::make( + tf()->range(), + in(TypeFunc::Control), + in(TypeFunc::I_O), + in(TypeFunc::Memory), + in(TypeFunc::FramePtr), + in(TypeFunc::ReturnAdr), + con_node); + + return tuple; } //============================================================================= diff --git a/src/hotspot/share/opto/divnode.hpp b/src/hotspot/share/opto/divnode.hpp index 127e2431b0b3..b13460c89f57 100644 --- a/src/hotspot/share/opto/divnode.hpp +++ b/src/hotspot/share/opto/divnode.hpp @@ -156,40 +156,45 @@ class ModLNode : public Node { }; // Base class for float and double modulus -class ModFloatingNode : public CallLeafNode { +class ModFloatingNode : public CallLeafPureNode { + TupleNode* make_tuple_of_input_state_and_constant_result(PhaseIterGVN* phase, const Type* con) const; + protected: - Node* replace_with_con(PhaseIterGVN* phase, const Type* con); + virtual Node* dividend() const = 0; + virtual Node* divisor() const = 0; + virtual const Type* get_result_if_constant(const Type* dividend, const Type* divisor) const = 0; public: - ModFloatingNode(Compile* C, const TypeFunc* tf, const char *name); + ModFloatingNode(Compile* C, const TypeFunc* tf, address addr, const char* name); + Node* Ideal(PhaseGVN* phase, bool can_reshape) override; }; // Float Modulus class ModFNode : public ModFloatingNode { private: - Node* dividend() const { return in(TypeFunc::Parms + 0); } - Node* divisor() const { return in(TypeFunc::Parms + 1); } + Node* dividend() const override { return in(TypeFunc::Parms + 0); } + Node* divisor() const override { return in(TypeFunc::Parms + 1); } + const Type* get_result_if_constant(const Type* dividend, const Type* divisor) const override; public: ModFNode(Compile* C, Node* a, Node* b); - virtual int Opcode() const; - virtual uint ideal_reg() const { return Op_RegF; } - virtual uint size_of() const { return sizeof(*this); } - virtual Node* Ideal(PhaseGVN* phase, bool can_reshape); + int Opcode() const override; + uint ideal_reg() const override { return Op_RegF; } + uint size_of() const override { return sizeof(*this); } }; // Double Modulus class ModDNode : public ModFloatingNode { private: - Node* dividend() const { return in(TypeFunc::Parms + 0); } - Node* divisor() const { return in(TypeFunc::Parms + 2); } + Node* dividend() const override { return in(TypeFunc::Parms + 0); } + Node* divisor() const override { return in(TypeFunc::Parms + 2); } + const Type* get_result_if_constant(const Type* dividend, const Type* divisor) const override; public: ModDNode(Compile* C, Node* a, Node* b); - virtual int Opcode() const; - virtual uint ideal_reg() const { return Op_RegD; } - virtual uint size_of() const { return sizeof(*this); } - virtual Node* Ideal(PhaseGVN* phase, bool can_reshape); + int Opcode() const override; + uint ideal_reg() const override { return Op_RegD; } + uint size_of() const override { return sizeof(*this); } }; //------------------------------UModINode--------------------------------------- diff --git a/src/hotspot/share/opto/graphKit.cpp b/src/hotspot/share/opto/graphKit.cpp index 20feca26ede5..1b8b7008578c 100644 --- a/src/hotspot/share/opto/graphKit.cpp +++ b/src/hotspot/share/opto/graphKit.cpp @@ -1880,14 +1880,20 @@ Node* GraphKit::set_results_for_java_call(CallJavaNode* call, bool separate_io_p // after the call, if this call has restricted memory effects. Node* GraphKit::set_predefined_input_for_runtime_call(SafePointNode* call, Node* narrow_mem) { // Set fixed predefined input arguments - Node* memory = reset_memory(); - Node* m = narrow_mem == nullptr ? memory : narrow_mem; - call->init_req( TypeFunc::Control, control() ); - call->init_req( TypeFunc::I_O, top() ); // does no i/o - call->init_req( TypeFunc::Memory, m ); // may gc ptrs - call->init_req( TypeFunc::FramePtr, frameptr() ); - call->init_req( TypeFunc::ReturnAdr, top() ); - return memory; + call->init_req(TypeFunc::Control, control()); + call->init_req(TypeFunc::I_O, top()); // does no i/o + call->init_req(TypeFunc::ReturnAdr, top()); + if (call->is_CallLeafPure()) { + call->init_req(TypeFunc::Memory, top()); + call->init_req(TypeFunc::FramePtr, top()); + return nullptr; + } else { + Node* memory = reset_memory(); + Node* m = narrow_mem == nullptr ? memory : narrow_mem; + call->init_req(TypeFunc::Memory, m); // may gc ptrs + call->init_req(TypeFunc::FramePtr, frameptr()); + return memory; + } } //-------------------set_predefined_output_for_runtime_call-------------------- @@ -1905,6 +1911,11 @@ void GraphKit::set_predefined_output_for_runtime_call(Node* call, const TypePtr* hook_mem) { // no i/o set_control(_gvn.transform( new ProjNode(call,TypeFunc::Control) )); + if (call->is_CallLeafPure()) { + // Pure function have only control (for now) and data output, in particular + // they don't touch the memory, so we don't want a memory proj that is set after. + return; + } if (keep_mem) { // First clone the existing memory state set_all_memory(keep_mem); @@ -2491,6 +2502,8 @@ Node* GraphKit::make_runtime_call(int flags, } else if (flags & RC_VECTOR){ uint num_bits = call_type->range()->field_at(TypeFunc::Parms)->is_vect()->length_in_bytes() * BitsPerByte; call = new CallLeafVectorNode(call_type, call_addr, call_name, adr_type, num_bits); + } else if (flags & RC_PURE) { + call = new CallLeafPureNode(call_type, call_addr, call_name, adr_type); } else { call = new CallLeafNode(call_type, call_addr, call_name, adr_type); } diff --git a/src/hotspot/share/opto/graphKit.hpp b/src/hotspot/share/opto/graphKit.hpp index 28773d75333e..806a211d7e25 100644 --- a/src/hotspot/share/opto/graphKit.hpp +++ b/src/hotspot/share/opto/graphKit.hpp @@ -784,6 +784,7 @@ class GraphKit : public Phase { RC_NARROW_MEM = 16, // input memory is same as output RC_UNCOMMON = 32, // freq. expected to be like uncommon trap RC_VECTOR = 64, // CallLeafVectorNode + RC_PURE = 128, // CallLeaf is pure RC_LEAF = 0 // null value: no flags set }; diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp index 358c6f6654c3..95c7dfa63022 100644 --- a/src/hotspot/share/opto/library_call.cpp +++ b/src/hotspot/share/opto/library_call.cpp @@ -1802,7 +1802,7 @@ bool LibraryCallKit::runtime_math(const TypeFunc* call_type, address funcAddr, c Node* b = (call_type == OptoRuntime::Math_DD_D_Type()) ? argument(2) : nullptr; const TypePtr* no_memory_effects = nullptr; - Node* trig = make_runtime_call(RC_LEAF, call_type, funcAddr, funcName, + Node* trig = make_runtime_call(RC_LEAF | RC_PURE, call_type, funcAddr, funcName, no_memory_effects, a, top(), b, b ? top() : nullptr); Node* value = _gvn.transform(new ProjNode(trig, TypeFunc::Parms+0)); diff --git a/src/hotspot/share/opto/macro.cpp b/src/hotspot/share/opto/macro.cpp index acf1dbabf198..aafc5d3c170c 100644 --- a/src/hotspot/share/opto/macro.cpp +++ b/src/hotspot/share/opto/macro.cpp @@ -2638,17 +2638,14 @@ bool PhaseMacroExpand::expand_macro_nodes() { switch (n->Opcode()) { case Op_ModD: case Op_ModF: { - bool is_drem = n->Opcode() == Op_ModD; CallNode* mod_macro = n->as_Call(); - CallNode* call = new CallLeafNode(mod_macro->tf(), - is_drem ? CAST_FROM_FN_PTR(address, SharedRuntime::drem) - : CAST_FROM_FN_PTR(address, SharedRuntime::frem), - is_drem ? "drem" : "frem", TypeRawPtr::BOTTOM); + CallNode* call = new CallLeafPureNode(mod_macro->tf(), mod_macro->entry_point(), + mod_macro->_name, TypeRawPtr::BOTTOM); call->init_req(TypeFunc::Control, mod_macro->in(TypeFunc::Control)); - call->init_req(TypeFunc::I_O, mod_macro->in(TypeFunc::I_O)); - call->init_req(TypeFunc::Memory, mod_macro->in(TypeFunc::Memory)); - call->init_req(TypeFunc::ReturnAdr, mod_macro->in(TypeFunc::ReturnAdr)); - call->init_req(TypeFunc::FramePtr, mod_macro->in(TypeFunc::FramePtr)); + call->init_req(TypeFunc::I_O, C->top()); + call->init_req(TypeFunc::Memory, C->top()); + call->init_req(TypeFunc::ReturnAdr, C->top()); + call->init_req(TypeFunc::FramePtr, C->top()); for (unsigned int i = 0; i < mod_macro->tf()->domain()->cnt() - TypeFunc::Parms; i++) { call->init_req(TypeFunc::Parms + i, mod_macro->in(TypeFunc::Parms + i)); } diff --git a/src/hotspot/share/opto/multnode.cpp b/src/hotspot/share/opto/multnode.cpp index 736e84315eee..f429d5daac07 100644 --- a/src/hotspot/share/opto/multnode.cpp +++ b/src/hotspot/share/opto/multnode.cpp @@ -120,6 +120,10 @@ const TypePtr *ProjNode::adr_type() const { if (bottom_type() == Type::MEMORY) { // in(0) might be a narrow MemBar; otherwise we will report TypePtr::BOTTOM Node* ctrl = in(0); + if (ctrl->Opcode() == Op_Tuple) { + // Jumping over Tuples: the i-th projection of a Tuple is the i-th input of the Tuple. + ctrl = ctrl->in(_con); + } if (ctrl == nullptr) return nullptr; // node is dead const TypePtr* adr_type = ctrl->adr_type(); #ifdef ASSERT @@ -163,6 +167,15 @@ void ProjNode::check_con() const { assert(_con < t->is_tuple()->cnt(), "ProjNode::_con must be in range"); } +//------------------------------Identity--------------------------------------- +Node* ProjNode::Identity(PhaseGVN* phase) { + if (in(0) != nullptr && in(0)->Opcode() == Op_Tuple) { + // Jumping over Tuples: the i-th projection of a Tuple is the i-th input of the Tuple. + return in(0)->in(_con); + } + return this; +} + //------------------------------Value------------------------------------------ const Type* ProjNode::Value(PhaseGVN* phase) const { if (in(0) == nullptr) return Type::TOP; diff --git a/src/hotspot/share/opto/multnode.hpp b/src/hotspot/share/opto/multnode.hpp index dff2caed38d1..834dcfdca6de 100644 --- a/src/hotspot/share/opto/multnode.hpp +++ b/src/hotspot/share/opto/multnode.hpp @@ -82,6 +82,7 @@ class ProjNode : public Node { virtual const Type *bottom_type() const; virtual const TypePtr *adr_type() const; virtual bool pinned() const; + virtual Node* Identity(PhaseGVN* phase); virtual const Type* Value(PhaseGVN* phase) const; virtual uint ideal_reg() const; virtual const RegMask &out_RegMask() const; @@ -105,4 +106,49 @@ class ProjNode : public Node { ProjNode* other_if_proj() const; }; +/* Tuples are used to avoid manual graph surgery. When a node with Proj outputs (such as a call) + * must be removed and its ouputs replaced by its input, or some other value, we can make its + * ::Ideal return a tuple of what we want for each output: the ::Identity of output Proj will + * take care to jump over the Tuple and directly pick up the right input of the Tuple. + * + * For instance, if a function call is proven to have no side effect and return the constant 0, + * we can replace it with the 6-tuple: + * (control input, IO input, memory input, frame ptr input, return addr input, Con:0) + * all the output projections will pick up the input of the now gone call, except for the result + * projection that is replaced by 0. + * + * Using TupleNode avoid manual graph surgery and leave that to our expert surgeon: IGVN. + * Since the user of a Tuple are expected to be Proj, when creating a tuple during idealization, + * the output Proj should be enqueued for IGVN immediately after, and the tuple should not survive + * after the current IGVN. + */ +class TupleNode : public MultiNode { + const TypeTuple* _tf; + + template + static void make_helper(TupleNode* tn, uint i, Node* node, NN... nn) { + tn->set_req(i, node); + make_helper(tn, i + 1, nn...); + } + + static void make_helper(TupleNode*, uint) {} + +public: + TupleNode(const TypeTuple* tf) : MultiNode(tf->cnt()), _tf(tf) {} + + int Opcode() const override; + const Type* bottom_type() const override { return _tf; } + + /* Give as many `Node*` as you want in the `nn` pack: + * TupleNode::make(tf, input1) + * TupleNode::make(tf, input1, input2, input3, input4) + */ + template + static TupleNode* make(const TypeTuple* tf, NN... nn) { + TupleNode* tn = new TupleNode(tf); + make_helper(tn, 0, nn...); + return tn; + } +}; + #endif // SHARE_OPTO_MULTNODE_HPP diff --git a/src/hotspot/share/opto/node.cpp b/src/hotspot/share/opto/node.cpp index 8f6c67c16f52..5ecc038954dd 100644 --- a/src/hotspot/share/opto/node.cpp +++ b/src/hotspot/share/opto/node.cpp @@ -2946,23 +2946,13 @@ bool Node::is_dead_loop_safe() const { bool Node::is_div_or_mod(BasicType bt) const { return Opcode() == Op_Div(bt) || Opcode() == Op_Mod(bt) || Opcode() == Op_UDiv(bt) || Opcode() == Op_UMod(bt); } -bool Node::is_pure_function() const { - switch (Opcode()) { - case Op_ModD: - case Op_ModF: - return true; - default: - return false; - } -} - // `maybe_pure_function` is assumed to be the input of `this`. This is a bit redundant, // but we already have and need maybe_pure_function in all the call sites, so // it makes it obvious that the `maybe_pure_function` is the same node as in the caller, // while it takes more thinking to realize that a locally computed in(0) must be equal to // the local in the caller. bool Node::is_data_proj_of_pure_function(const Node* maybe_pure_function) const { - return Opcode() == Op_Proj && as_Proj()->_con == TypeFunc::Parms && maybe_pure_function->is_pure_function(); + return Opcode() == Op_Proj && as_Proj()->_con == TypeFunc::Parms && maybe_pure_function->is_CallLeafPure(); } //============================================================================= diff --git a/src/hotspot/share/opto/node.hpp b/src/hotspot/share/opto/node.hpp index 2bbb10879f59..dc0ac474c4bc 100644 --- a/src/hotspot/share/opto/node.hpp +++ b/src/hotspot/share/opto/node.hpp @@ -54,6 +54,7 @@ class CallDynamicJavaNode; class CallJavaNode; class CallLeafNode; class CallLeafNoFPNode; +class CallLeafPureNode; class CallNode; class CallRuntimeNode; class CallStaticJavaNode; @@ -673,6 +674,7 @@ class Node { DEFINE_CLASS_ID(CallRuntime, Call, 1) DEFINE_CLASS_ID(CallLeaf, CallRuntime, 0) DEFINE_CLASS_ID(CallLeafNoFP, CallLeaf, 0) + DEFINE_CLASS_ID(CallLeafPure, CallLeaf, 1) DEFINE_CLASS_ID(Allocate, Call, 2) DEFINE_CLASS_ID(AllocateArray, Allocate, 0) DEFINE_CLASS_ID(AbstractLock, Call, 3) @@ -907,6 +909,7 @@ class Node { DEFINE_CLASS_QUERY(CallJava) DEFINE_CLASS_QUERY(CallLeaf) DEFINE_CLASS_QUERY(CallLeafNoFP) + DEFINE_CLASS_QUERY(CallLeafPure) DEFINE_CLASS_QUERY(CallRuntime) DEFINE_CLASS_QUERY(CallStaticJava) DEFINE_CLASS_QUERY(Catch) @@ -1289,8 +1292,6 @@ class Node { bool is_div_or_mod(BasicType bt) const; - bool is_pure_function() const; - bool is_data_proj_of_pure_function(const Node* maybe_pure_function) const; //----------------- Printing, etc diff --git a/src/hotspot/share/opto/parse2.cpp b/src/hotspot/share/opto/parse2.cpp index 1a4c3c91c4f0..04b6e49b620c 100644 --- a/src/hotspot/share/opto/parse2.cpp +++ b/src/hotspot/share/opto/parse2.cpp @@ -1097,11 +1097,11 @@ void Parse::jump_switch_ranges(Node* key_val, SwitchRange *lo, SwitchRange *hi, Node* Parse::floating_point_mod(Node* a, Node* b, BasicType type) { assert(type == BasicType::T_FLOAT || type == BasicType::T_DOUBLE, "only float and double are floating points"); - CallNode* mod = type == BasicType::T_DOUBLE ? static_cast(new ModDNode(C, a, b)) : new ModFNode(C, a, b); + CallLeafPureNode* mod = type == BasicType::T_DOUBLE ? static_cast(new ModDNode(C, a, b)) : new ModFNode(C, a, b); - Node* prev_mem = set_predefined_input_for_runtime_call(mod); - mod = _gvn.transform(mod)->as_Call(); - set_predefined_output_for_runtime_call(mod, prev_mem, TypeRawPtr::BOTTOM); + set_predefined_input_for_runtime_call(mod); + mod = _gvn.transform(mod)->as_CallLeafPure(); + set_predefined_output_for_runtime_call(mod); Node* result = _gvn.transform(new ProjNode(mod, TypeFunc::Parms + 0)); record_for_igvn(mod); return result; From a709f64404fd3d683fb69cc204215e29883da57c Mon Sep 17 00:00:00 2001 From: Ashutosh Mehra Date: Thu, 16 Jul 2026 13:59:55 +0000 Subject: [PATCH 65/86] 8386852: Lower peak throughput with AOTCache Reviewed-by: kvn Backport-of: ce93858acd423e1fa1011358cff9fc495182aca6 --- src/hotspot/share/compiler/compilationPolicy.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hotspot/share/compiler/compilationPolicy.cpp b/src/hotspot/share/compiler/compilationPolicy.cpp index 6f84dbeb40c4..4a2614fe3f95 100644 --- a/src/hotspot/share/compiler/compilationPolicy.cpp +++ b/src/hotspot/share/compiler/compilationPolicy.cpp @@ -1430,7 +1430,7 @@ CompLevel CompilationPolicy::transition_from_limited_profile(const methodHandle& // Determine if a method should be compiled with a normal entry point at a different level. CompLevel CompilationPolicy::call_event(const methodHandle& method, CompLevel cur_level, JavaThread* THREAD) { CompLevel osr_level = MIN2((CompLevel) method->highest_osr_comp_level(), common(method, cur_level, THREAD, true)); - CompLevel next_level = common(method, cur_level, THREAD, !TrainingData::have_data() && is_old(method)); + CompLevel next_level = common(method, cur_level, THREAD, is_old(method)); // If OSR method level is greater than the regular method level, the levels should be // equalized by raising the regular method level in order to avoid OSRs during each From c259a2226ba5eecf7cf7958f9c67fb3047fe3ab4 Mon Sep 17 00:00:00 2001 From: Kangcheng Xu Date: Thu, 16 Jul 2026 14:40:04 +0000 Subject: [PATCH 66/86] 8378713: C2: performance regression due to missing constant folding for Math.pow() Reviewed-by: andrew Backport-of: 7f631ea958e30246b927f3524f0fbf37334422b9 --- src/hotspot/share/opto/callnode.cpp | 175 ++++++++++++++ src/hotspot/share/opto/callnode.hpp | 17 ++ src/hotspot/share/opto/classes.hpp | 1 + src/hotspot/share/opto/library_call.cpp | 62 +---- src/hotspot/share/opto/macro.cpp | 19 +- .../intrinsics/math/PowDNodeTests.java | 218 ++++++++++++++++++ .../compiler/lib/ir_framework/IRNode.java | 11 + 7 files changed, 437 insertions(+), 66 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/intrinsics/math/PowDNodeTests.java diff --git a/src/hotspot/share/opto/callnode.cpp b/src/hotspot/share/opto/callnode.cpp index fc4b0c35dff3..cb6cc2082b38 100644 --- a/src/hotspot/share/opto/callnode.cpp +++ b/src/hotspot/share/opto/callnode.cpp @@ -42,6 +42,7 @@ #include "opto/rootnode.hpp" #include "opto/runtime.hpp" #include "runtime/sharedRuntime.hpp" +#include "runtime/stubRoutines.hpp" #include "utilities/powerOfTwo.hpp" #include "code/vmreg.hpp" @@ -1354,6 +1355,25 @@ TupleNode* CallLeafPureNode::make_tuple_of_input_state_and_top_return_values(con return tuple; } +CallLeafPureNode* CallLeafPureNode::inline_call_leaf_pure_node(Node* control) const { + Node* top = Compile::current()->top(); + if (control == nullptr) { + control = in(TypeFunc::Control); + } + + CallLeafPureNode* call = new CallLeafPureNode(tf(), entry_point(), _name, nullptr); + call->init_req(TypeFunc::Control, control); + call->init_req(TypeFunc::I_O, top); + call->init_req(TypeFunc::Memory, top); + call->init_req(TypeFunc::ReturnAdr, top); + call->init_req(TypeFunc::FramePtr, top); + for (unsigned int i = 0; i < tf()->domain()->cnt() - TypeFunc::Parms; i++) { + call->init_req(TypeFunc::Parms + i, in(TypeFunc::Parms + i)); + } + + return call; +} + Node* CallLeafPureNode::Ideal(PhaseGVN* phase, bool can_reshape) { if (is_dead()) { return nullptr; @@ -2424,3 +2444,158 @@ bool CallNode::may_modify_arraycopy_helper(const TypeOopPtr* dest_t, const TypeO return true; } + +PowDNode::PowDNode(Compile* C, Node* base, Node* exp) + : CallLeafPureNode( + OptoRuntime::Math_DD_D_Type(), + StubRoutines::dpow() != nullptr ? StubRoutines::dpow() : CAST_FROM_FN_PTR(address, SharedRuntime::dpow), + "pow", + nullptr) { + add_flag(Flag_is_macro); + C->add_macro_node(this); + + init_req(TypeFunc::Parms + 0, base); + init_req(TypeFunc::Parms + 1, C->top()); // double slot padding + init_req(TypeFunc::Parms + 2, exp); + init_req(TypeFunc::Parms + 3, C->top()); // double slot padding +} + +const Type* PowDNode::Value(PhaseGVN* phase) const { + const Type* t_base = phase->type(base()); + const Type* t_exp = phase->type(exp()); + + if (t_base == Type::TOP || t_exp == Type::TOP) { + return Type::TOP; + } + + const TypeD* base_con = t_base->isa_double_constant(); + const TypeD* exp_con = t_exp->isa_double_constant(); + const TypeD* result_t = nullptr; + + // constant folding: both inputs are constants + if (base_con != nullptr && exp_con != nullptr) { + result_t = TypeD::make(SharedRuntime::dpow(base_con->getd(), exp_con->getd())); + } + + // Special cases when only the exponent is known: + if (exp_con != nullptr) { + double e = exp_con->getd(); + + // If the second argument is positive or negative zero, then the result is 1.0. + // i.e., pow(x, +/-0.0D) => 1.0 + if (e == 0.0) { // true for both -0.0 and +0.0 + result_t = TypeD::ONE; + } + + // If the second argument is NaN, then the result is NaN. + // i.e., pow(x, NaN) => NaN + if (g_isnan(e)) { + result_t = TypeD::make(NAN); + } + } + + if (result_t != nullptr) { + // We can't simply return a TypeD here, it must be a tuple type to be compatible with call nodes. + const Type** fields = TypeTuple::fields(2); + fields[TypeFunc::Parms + 0] = result_t; + fields[TypeFunc::Parms + 1] = Type::HALF; + return TypeTuple::make(TypeFunc::Parms + 2, fields); + } + + return tf()->range(); +} + +Node* PowDNode::Ideal(PhaseGVN* phase, bool can_reshape) { + if (!can_reshape) { + return nullptr; // wait for igvn + } + + PhaseIterGVN* igvn = phase->is_IterGVN(); + Node* base = this->base(); + Node* exp = this->exp(); + + const Type* t_exp = phase->type(exp); + const TypeD* exp_con = t_exp->isa_double_constant(); + + // Special cases when only the exponent is known: + if (exp_con != nullptr) { + double e = exp_con->getd(); + + // If the second argument is 1.0, then the result is the same as the first argument. + // i.e., pow(x, 1.0) => x + if (e == 1.0) { + return make_tuple_of_input_state_and_result(igvn, base); + } + + // If the second argument is 2.0, then strength reduce to multiplications. + // i.e., pow(x, 2.0) => x * x + if (e == 2.0) { + Node* mul = igvn->transform(new MulDNode(base, base)); + return make_tuple_of_input_state_and_result(igvn, mul); + } + + // If the second argument is 0.5, the strength reduce to square roots. + // i.e., pow(x, 0.5) => sqrt(x) iff x > 0 + if (e == 0.5 && Matcher::match_rule_supported(Op_SqrtD)) { + Node* ctrl = in(TypeFunc::Control); + Node* zero = igvn->zerocon(T_DOUBLE); + + // According to the API specs, pow(-0.0, 0.5) = 0.0 and sqrt(-0.0) = -0.0. + // So pow(-0.0, 0.5) shouldn't be replaced with sqrt(-0.0). + // -0.0/+0.0 are both excluded since floating-point comparison doesn't distinguish -0.0 from +0.0. + Node* cmp = igvn->register_new_node_with_optimizer(new CmpDNode(base, zero)); + Node* test = igvn->register_new_node_with_optimizer(new BoolNode(cmp, BoolTest::le)); + + IfNode* iff = new IfNode(ctrl, test, PROB_UNLIKELY_MAG(3), COUNT_UNKNOWN); + igvn->register_new_node_with_optimizer(iff); + Node* if_slow = igvn->register_new_node_with_optimizer(new IfTrueNode(iff)); // x <= 0 + Node* if_fast = igvn->register_new_node_with_optimizer(new IfFalseNode(iff)); // x > 0 + + // slow path: call pow(x, 0.5) + Node* call = igvn->register_new_node_with_optimizer(inline_call_leaf_pure_node(if_slow)); + Node* call_ctrl = igvn->register_new_node_with_optimizer(new ProjNode(call, TypeFunc::Control)); + Node* call_result = igvn->register_new_node_with_optimizer(new ProjNode(call, TypeFunc::Parms + 0)); + + // fast path: sqrt(x) + Node* sqrt = igvn->register_new_node_with_optimizer(new SqrtDNode(igvn->C, if_fast, base)); + + // merge paths + RegionNode* region = new RegionNode(3); + igvn->register_new_node_with_optimizer(region); + region->init_req(1, call_ctrl); // slow path + region->init_req(2, if_fast); // fast path + + PhiNode* phi = new PhiNode(region, Type::DOUBLE); + igvn->register_new_node_with_optimizer(phi); + phi->init_req(1, call_result); // slow: pow() result + phi->init_req(2, sqrt); // fast: sqrt() result + + igvn->C->set_has_split_ifs(true); // Has chance for split-if optimization + + return make_tuple_of_input_state_and_result(igvn, phi, region); + } + } + + return CallLeafPureNode::Ideal(phase, can_reshape); +} + +// We can't simply have Ideal() returning a Con or MulNode since the users are still expecting a Call node, but we could +// produce a tuple that follows the same pattern so users can still get control, io, memory, etc.. +TupleNode* PowDNode::make_tuple_of_input_state_and_result(PhaseIterGVN* phase, Node* result, Node* control) { + if (control == nullptr) { + control = in(TypeFunc::Control); + } + + Compile* C = phase->C; + C->remove_macro_node(this); + TupleNode* tuple = TupleNode::make( + tf()->range(), + control, + in(TypeFunc::I_O), + in(TypeFunc::Memory), + in(TypeFunc::FramePtr), + in(TypeFunc::ReturnAdr), + result, + C->top()); + return tuple; +} diff --git a/src/hotspot/share/opto/callnode.hpp b/src/hotspot/share/opto/callnode.hpp index 63ec9677b723..e8105f3d4b09 100644 --- a/src/hotspot/share/opto/callnode.hpp +++ b/src/hotspot/share/opto/callnode.hpp @@ -964,6 +964,8 @@ class CallLeafPureNode : public CallLeafNode { } int Opcode() const override; Node* Ideal(PhaseGVN* phase, bool can_reshape) override; + + CallLeafPureNode* inline_call_leaf_pure_node(Node* control = nullptr) const; }; //------------------------------CallLeafNoFPNode------------------------------- @@ -1315,4 +1317,19 @@ class UnlockNode : public AbstractLockNode { JVMState* dbg_jvms() const { return nullptr; } #endif }; + +//------------------------------PowDNode-------------------------------------- +class PowDNode : public CallLeafPureNode { + TupleNode* make_tuple_of_input_state_and_result(PhaseIterGVN* phase, Node* result, Node* control = nullptr); + +public: + PowDNode(Compile* C, Node* base, Node* exp); + int Opcode() const override; + const Type* Value(PhaseGVN* phase) const override; + Node* Ideal(PhaseGVN* phase, bool can_reshape) override; + + Node* base() const { return in(TypeFunc::Parms + 0); } + Node* exp() const { return in(TypeFunc::Parms + 2); } +}; + #endif // SHARE_OPTO_CALLNODE_HPP diff --git a/src/hotspot/share/opto/classes.hpp b/src/hotspot/share/opto/classes.hpp index 587d5fad8f29..b7ba16e99a05 100644 --- a/src/hotspot/share/opto/classes.hpp +++ b/src/hotspot/share/opto/classes.hpp @@ -283,6 +283,7 @@ macro(OpaqueZeroTripGuard) macro(OpaqueNotNull) macro(OpaqueInitializedAssertionPredicate) macro(OpaqueTemplateAssertionPredicate) +macro(PowD) macro(ProfileBoolean) macro(OrI) macro(OrL) diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp index 95c7dfa63022..310d94d80aac 100644 --- a/src/hotspot/share/opto/library_call.cpp +++ b/src/hotspot/share/opto/library_call.cpp @@ -1817,61 +1817,17 @@ bool LibraryCallKit::runtime_math(const TypeFunc* call_type, address funcAddr, c //------------------------------inline_math_pow----------------------------- bool LibraryCallKit::inline_math_pow() { + Node* base = argument(0); Node* exp = argument(2); - const TypeD* d = _gvn.type(exp)->isa_double_constant(); - if (d != nullptr) { - if (d->getd() == 2.0) { - // Special case: pow(x, 2.0) => x * x - Node* base = argument(0); - set_result(_gvn.transform(new MulDNode(base, base))); - return true; - } else if (d->getd() == 0.5 && Matcher::match_rule_supported(Op_SqrtD)) { - // Special case: pow(x, 0.5) => sqrt(x) - Node* base = argument(0); - Node* zero = _gvn.zerocon(T_DOUBLE); - - RegionNode* region = new RegionNode(3); - Node* phi = new PhiNode(region, Type::DOUBLE); - - Node* cmp = _gvn.transform(new CmpDNode(base, zero)); - // According to the API specs, pow(-0.0, 0.5) = 0.0 and sqrt(-0.0) = -0.0. - // So pow(-0.0, 0.5) shouldn't be replaced with sqrt(-0.0). - // -0.0/+0.0 are both excluded since floating-point comparison doesn't distinguish -0.0 from +0.0. - Node* test = _gvn.transform(new BoolNode(cmp, BoolTest::le)); - - Node* if_pow = generate_slow_guard(test, nullptr); - Node* value_sqrt = _gvn.transform(new SqrtDNode(C, control(), base)); - phi->init_req(1, value_sqrt); - region->init_req(1, control()); - - if (if_pow != nullptr) { - set_control(if_pow); - address target = StubRoutines::dpow() != nullptr ? StubRoutines::dpow() : - CAST_FROM_FN_PTR(address, SharedRuntime::dpow); - const TypePtr* no_memory_effects = nullptr; - Node* trig = make_runtime_call(RC_LEAF, OptoRuntime::Math_DD_D_Type(), target, "POW", - no_memory_effects, base, top(), exp, top()); - Node* value_pow = _gvn.transform(new ProjNode(trig, TypeFunc::Parms+0)); -#ifdef ASSERT - Node* value_top = _gvn.transform(new ProjNode(trig, TypeFunc::Parms+1)); - assert(value_top == top(), "second value must be top"); -#endif - phi->init_req(2, value_pow); - region->init_req(2, _gvn.transform(new ProjNode(trig, TypeFunc::Control))); - } - - C->set_has_split_ifs(true); // Has chance for split-if optimization - set_control(_gvn.transform(region)); - record_for_igvn(region); - set_result(_gvn.transform(phi)); - return true; - } - } - - return StubRoutines::dpow() != nullptr ? - runtime_math(OptoRuntime::Math_DD_D_Type(), StubRoutines::dpow(), "dpow") : - runtime_math(OptoRuntime::Math_DD_D_Type(), CAST_FROM_FN_PTR(address, SharedRuntime::dpow), "POW"); + CallNode* pow = new PowDNode(C, base, exp); + set_predefined_input_for_runtime_call(pow); + pow = _gvn.transform(pow)->as_CallLeafPure(); + set_predefined_output_for_runtime_call(pow); + Node* result = _gvn.transform(new ProjNode(pow, TypeFunc::Parms + 0)); + record_for_igvn(pow); + set_result(result); + return true; } //------------------------------inline_math_native----------------------------- diff --git a/src/hotspot/share/opto/macro.cpp b/src/hotspot/share/opto/macro.cpp index aafc5d3c170c..a28043c8d8f5 100644 --- a/src/hotspot/share/opto/macro.cpp +++ b/src/hotspot/share/opto/macro.cpp @@ -2484,6 +2484,7 @@ void PhaseMacroExpand::eliminate_macro_nodes() { assert(n->Opcode() == Op_LoopLimit || n->Opcode() == Op_ModD || n->Opcode() == Op_ModF || + n->Opcode() == Op_PowD || n->is_OpaqueNotNull() || n->is_OpaqueInitializedAssertionPredicate() || n->Opcode() == Op_MaxL || @@ -2637,19 +2638,11 @@ bool PhaseMacroExpand::expand_macro_nodes() { default: switch (n->Opcode()) { case Op_ModD: - case Op_ModF: { - CallNode* mod_macro = n->as_Call(); - CallNode* call = new CallLeafPureNode(mod_macro->tf(), mod_macro->entry_point(), - mod_macro->_name, TypeRawPtr::BOTTOM); - call->init_req(TypeFunc::Control, mod_macro->in(TypeFunc::Control)); - call->init_req(TypeFunc::I_O, C->top()); - call->init_req(TypeFunc::Memory, C->top()); - call->init_req(TypeFunc::ReturnAdr, C->top()); - call->init_req(TypeFunc::FramePtr, C->top()); - for (unsigned int i = 0; i < mod_macro->tf()->domain()->cnt() - TypeFunc::Parms; i++) { - call->init_req(TypeFunc::Parms + i, mod_macro->in(TypeFunc::Parms + i)); - } - _igvn.replace_node(mod_macro, call); + case Op_ModF: + case Op_PowD: { + CallLeafPureNode* call_macro = n->as_CallLeafPure(); + CallLeafPureNode* call = call_macro->inline_call_leaf_pure_node(); + _igvn.replace_node(call_macro, call); transform_later(call); break; } diff --git a/test/hotspot/jtreg/compiler/intrinsics/math/PowDNodeTests.java b/test/hotspot/jtreg/compiler/intrinsics/math/PowDNodeTests.java new file mode 100644 index 000000000000..e28cc5ab3463 --- /dev/null +++ b/test/hotspot/jtreg/compiler/intrinsics/math/PowDNodeTests.java @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2026, IBM and/or its affiliates. All rights reserved. + * 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.intrinsics.math; + +import jdk.test.lib.Asserts; + +import compiler.lib.ir_framework.*; +import compiler.lib.generators.*; +import static compiler.lib.generators.Generators.*; + +import java.util.Random; + +/* + * @test + * @bug 8378713 + * @key randomness + * @summary Math.pow(base, exp) should constant propagate + * @library /test/lib / + * @run driver ${test.main.class} + */ +public class PowDNodeTests { + public static final Generator UNIFORMS = G.uniformDoubles(); // [0, 1) + + public static final double B = UNIFORMS.next() * 1000.0d; + public static final double E = UNIFORMS.next() * 1000.0d + 3.0d; // e >= 3 to avoid strength reduction code + + public static void main(String[] args) { + TestFramework.run(); + + testCorrectness(); + } + + // Test 1: pow(2.0, 10.0) -> 1024.0 + @Test + @IR(failOn = {IRNode.POW_D}) + public static double constantLiteralFolding() { + return Math.pow(2.0, 10.0); // should fold to 1024.0 + } + + // Test 2: pow(final B, final E) -> B^E + @Test + @IR(failOn = {IRNode.POW_D}) + public static double constantStaticFolding() { + return Math.pow(B, E); // should fold to B^E + } + + // Test 3: pow(b, 0.0) -> 1.0 + @Test + @IR(failOn = {IRNode.POW_D}) + @Arguments(values = {Argument.RANDOM_EACH}) + public static double expZero(double b) { + return Math.pow(b, 0.0); + } + + // Test 4: pow(b, 1.0) -> b (identity) + @Test + @IR(failOn = {IRNode.POW_D}) + @Arguments(values = {Argument.RANDOM_EACH}) + public static double expOne(double b) { + return Math.pow(b, 1.0); + } + + // Test 5: pow(b, NaN) -> NaN + @Test + @IR(failOn = {IRNode.POW_D}) + @Arguments(values = {Argument.RANDOM_EACH}) + public static double expNaN(double b) { + return Math.pow(b, Double.NaN); + } + + // Test 6: pow(b, 2.0) -> b * b + // More tests in TestPow2Opt.java + @Test + @IR(failOn = {IRNode.POW_D}) + @IR(counts = {IRNode.MUL_D, "1"}) + @Arguments(values = {Argument.RANDOM_EACH}) + public static double expTwo(double b) { + return Math.pow(b, 2.0); + } + + // Test 7: pow(b, 0.5) -> b <= 0.0 ? pow(b, 0.5) : sqrt(b) + // More tests in TestPow0Dot5Opt.java + @Test + @IR(counts = {IRNode.IF, "1"}) + @IR(counts = {IRNode.SQRT_D, "1"}) + @IR(counts = {".*CallLeaf.*pow.*", "1"}, phase = CompilePhase.BEFORE_MATCHING) + @Arguments(values = {Argument.RANDOM_EACH}) + public static double expDot5(double b) { + return Math.pow(b, 0.5); // expand to: if (b > 0) { sqrt(b) } else { call(b) } + } + + // Test 8: non-constant exponent stays as call + @Test + @IR(counts = {IRNode.POW_D, "1"}) + @Arguments(values = {Argument.RANDOM_EACH, Argument.RANDOM_EACH}) + public static double nonConstant(double b, double e) { + return Math.pow(b, e); + } + + // Test 9: late constant discovery on base (after loop opts) + @Test + @IR(counts = {IRNode.POW_D, "1"}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.POW_D}) + public static double lateBaseConstant() { + double base = 0; + for (int i = 0; i < 4; i++) { + if ((i % 2) == 0) { + base = B; + } + } + // After loop opts, base == B (constant), so pow(B, E) folds + return Math.pow(base, E); + } + + // Test 10: late constant discovery on exp (after loop opts) + @Test + @IR(counts = {IRNode.POW_D, "1"}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.POW_D}) + public static double lateExpConstant() { + double exp = 0; + for (int i = 0; i < 4; i++) { + if ((i % 2) == 0) { + exp = E; + } + } + // After loop opts, exp == E (constant), so pow(B, E) folds + return Math.pow(B, exp); + } + + // Test 11: late constant discoveries on both base and exp (after loop opts) + @Test + @IR(counts = {IRNode.POW_D, "1"}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.POW_D}) + public static double lateBothConstant() { + double base = 0, exp = 0; + for (int i = 0; i < 4; i++) { + if ((i % 2) == 0) { + base = B; + exp = E; + } + } + // After loop opts, base = B, exp == E, so pow(B, E) folds + return Math.pow(base, exp); + } + + private static void assertEQWithinOneUlp(double expected, double observed) { + if (Double.isNaN(expected) && Double.isNaN(observed)) return; + + // Math.pow() requires result must be within 1 ulp of the respective magnitude + double ulp = Math.max(Math.ulp(expected), Math.ulp(observed)); + if (Math.abs(expected - observed) > ulp) { + throw new AssertionError(String.format( + "expect = %x, observed = %x, ulp = %x", + Double.doubleToRawLongBits(expected), Double.doubleToRawLongBits(observed), Double.doubleToRawLongBits(ulp) + )); + } + } + + private static void testCorrectness() { + // No need to warm up for intrinsics + Asserts.assertEQ(1024.0d, constantLiteralFolding()); + + double BE = StrictMath.pow(B, E); + assertEQWithinOneUlp(BE, constantStaticFolding()); + assertEQWithinOneUlp(BE, lateBaseConstant()); + assertEQWithinOneUlp(BE, lateExpConstant()); + assertEQWithinOneUlp(BE, lateBothConstant()); + + Generator anyBits = G.anyBitsDouble(); + Generator largeDoubles = G.uniformDoubles(Long.MAX_VALUE, Double.MAX_VALUE); + Generator doubles = G.doubles(); + double[] values = { + Double.MIN_VALUE, Double.MIN_NORMAL, -42.0d, -1.0d, -0.0d, +0.0d, 0.5d, 1.0d, 2.0d, 123d, Double.MAX_VALUE, + Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Double.NaN, + UNIFORMS.next(), UNIFORMS.next(), + largeDoubles.next(), -largeDoubles.next(), // some sufficiently large magnitudes + anyBits.next(), anyBits.next(), // any bits with potentially more NaN representation + doubles.next(), doubles.next() // a healthy sprinkle of whatever else is possible + }; + + for (double b : values) { + // Strength reduced, so we know the bits matches exactly + Asserts.assertEQ(1.0d, expZero(b)); + Asserts.assertEQ(b, expOne(b)); + Asserts.assertEQ(b * b, expTwo(b)); + + assertEQWithinOneUlp(Double.NaN, expNaN(b)); + + // Runtime calls, so make sure the result is within 1 ulp + assertEQWithinOneUlp(StrictMath.pow(b, 0.5d), expDot5(b)); + + for (double e : values) { + assertEQWithinOneUlp(StrictMath.pow(b, e), nonConstant(b, e)); + } + } + } +} diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java index f41ccd84071b..fa7766d910c0 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java @@ -1878,6 +1878,11 @@ public class IRNode { beforeMatchingNameRegex(SQRT_HF, "SqrtHF"); } + public static final String SQRT_D = PREFIX + "SQRT_D" + POSTFIX; + static { + beforeMatchingNameRegex(SQRT_D, "SqrtD"); + } + public static final String SQRT_F = PREFIX + "SQRT_F" + POSTFIX; static { beforeMatchingNameRegex(SQRT_F, "SqrtF"); @@ -2905,6 +2910,12 @@ public class IRNode { macroNodes(MOD_D, regex); } + public static final String POW_D = PREFIX + "POW_D" + POSTFIX; + static { + String regex = START + "PowD" + MID + END; + macroNodes(POW_D, regex); + } + public static final String BLACKHOLE = PREFIX + "BLACKHOLE" + POSTFIX; static { fromBeforeRemoveUselessToFinalCode(BLACKHOLE, "Blackhole"); From eea95f35ad39c169d259bcf84120ca509af6bf09 Mon Sep 17 00:00:00 2001 From: Elif Aslan Date: Thu, 16 Jul 2026 18:06:52 +0000 Subject: [PATCH 67/86] 8387985: sun/tools/jstat shell tests fail on platforms that do not support ParallelGC Backport-of: 151516fae22ee71c12ea76a51fcf5be69f2e5dbf --- test/jdk/sun/tools/jstat/jstatGcCapacityOutput1.sh | 3 ++- test/jdk/sun/tools/jstat/jstatGcMetaCapacityOutput1.sh | 3 ++- test/jdk/sun/tools/jstat/jstatGcNewCapacityOutput1.sh | 3 ++- test/jdk/sun/tools/jstat/jstatGcNewOutput1.sh | 3 ++- test/jdk/sun/tools/jstat/jstatGcOldCapacityOutput1.sh | 5 +++-- test/jdk/sun/tools/jstat/jstatGcOldOutput1.sh | 3 ++- test/jdk/sun/tools/jstat/jstatGcOutput1.sh | 3 ++- test/jdk/sun/tools/jstat/jstatLineCounts1.sh | 3 ++- test/jdk/sun/tools/jstat/jstatLineCounts2.sh | 3 ++- test/jdk/sun/tools/jstat/jstatLineCounts3.sh | 3 ++- test/jdk/sun/tools/jstat/jstatLineCounts4.sh | 3 ++- test/jdk/sun/tools/jstat/jstatTimeStamp1.sh | 3 ++- 12 files changed, 25 insertions(+), 13 deletions(-) diff --git a/test/jdk/sun/tools/jstat/jstatGcCapacityOutput1.sh b/test/jdk/sun/tools/jstat/jstatGcCapacityOutput1.sh index c1908855ea76..6e184e9dc310 100644 --- a/test/jdk/sun/tools/jstat/jstatGcCapacityOutput1.sh +++ b/test/jdk/sun/tools/jstat/jstatGcCapacityOutput1.sh @@ -1,5 +1,5 @@ # -# Copyright (c) 2004, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. # 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 @@ # @test # @bug 4990825 +# @requires vm.gc.Parallel # @run shell jstatGcCapacityOutput1.sh # @summary Test that output of 'jstat -gccapacity 0' has expected line counts diff --git a/test/jdk/sun/tools/jstat/jstatGcMetaCapacityOutput1.sh b/test/jdk/sun/tools/jstat/jstatGcMetaCapacityOutput1.sh index b16d0e38d02c..b5caccb17681 100644 --- a/test/jdk/sun/tools/jstat/jstatGcMetaCapacityOutput1.sh +++ b/test/jdk/sun/tools/jstat/jstatGcMetaCapacityOutput1.sh @@ -1,5 +1,5 @@ # -# Copyright (c) 2013, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2013, 2026, Oracle and/or its affiliates. All rights reserved. # 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 @@ # @test # @bug 4990825 +# @requires vm.gc.Parallel # @run shell jstatGcMetaCapacityOutput1.sh # @summary Test that output of 'jstat -gcmetacapacity 0' has expected line counts diff --git a/test/jdk/sun/tools/jstat/jstatGcNewCapacityOutput1.sh b/test/jdk/sun/tools/jstat/jstatGcNewCapacityOutput1.sh index 64ce2efd455e..96f0722a9e13 100644 --- a/test/jdk/sun/tools/jstat/jstatGcNewCapacityOutput1.sh +++ b/test/jdk/sun/tools/jstat/jstatGcNewCapacityOutput1.sh @@ -1,5 +1,5 @@ # -# Copyright (c) 2004, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. # 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 @@ # @test # @bug 4990825 +# @requires vm.gc.Parallel # @run shell jstatGcNewCapacityOutput1.sh # @summary Test that output of 'jstat -gcnewcapacity 0' has expected line counts diff --git a/test/jdk/sun/tools/jstat/jstatGcNewOutput1.sh b/test/jdk/sun/tools/jstat/jstatGcNewOutput1.sh index b15ec02d2b0a..96e2db61488e 100644 --- a/test/jdk/sun/tools/jstat/jstatGcNewOutput1.sh +++ b/test/jdk/sun/tools/jstat/jstatGcNewOutput1.sh @@ -1,5 +1,5 @@ # -# Copyright (c) 2004, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. # 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 @@ # @test # @bug 4990825 +# @requires vm.gc.Parallel # @run shell jstatGcNewOutput1.sh # @summary Test that output of 'jstat -gcnew 0' has expected line counts diff --git a/test/jdk/sun/tools/jstat/jstatGcOldCapacityOutput1.sh b/test/jdk/sun/tools/jstat/jstatGcOldCapacityOutput1.sh index 1c13d6f916d3..0c5e2e198945 100644 --- a/test/jdk/sun/tools/jstat/jstatGcOldCapacityOutput1.sh +++ b/test/jdk/sun/tools/jstat/jstatGcOldCapacityOutput1.sh @@ -1,5 +1,5 @@ # -# Copyright (c) 2004, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. # 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,9 @@ # @test # @bug 4990825 +# @requires vm.gc.Parallel # @run shell jstatGcOldCapacityOutput1.sh -# @summary Test that output of 'jstat -gcoldcapcaity 0' has expected line counts +# @summary Test that output of 'jstat -gcoldcapacity 0' has expected line counts . ${TESTSRC-.}/../../jvmstat/testlibrary/utils.sh diff --git a/test/jdk/sun/tools/jstat/jstatGcOldOutput1.sh b/test/jdk/sun/tools/jstat/jstatGcOldOutput1.sh index 7f505228b12a..0f857ccb1e68 100644 --- a/test/jdk/sun/tools/jstat/jstatGcOldOutput1.sh +++ b/test/jdk/sun/tools/jstat/jstatGcOldOutput1.sh @@ -1,5 +1,5 @@ # -# Copyright (c) 2004, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. # 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 @@ # @test # @bug 4990825 +# @requires vm.gc.Parallel # @run shell jstatGcOldOutput1.sh # @summary Test that output of 'jstat -gcold 0' has expected line counts diff --git a/test/jdk/sun/tools/jstat/jstatGcOutput1.sh b/test/jdk/sun/tools/jstat/jstatGcOutput1.sh index dfffa2d1a550..5862fda3fd78 100644 --- a/test/jdk/sun/tools/jstat/jstatGcOutput1.sh +++ b/test/jdk/sun/tools/jstat/jstatGcOutput1.sh @@ -1,5 +1,5 @@ # -# Copyright (c) 2004, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. # 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 @@ # @test # @bug 4990825 +# @requires vm.gc.Parallel # @run shell jstatGcOutput1.sh # @summary Test that output of 'jstat -gc 0' has expected line counts diff --git a/test/jdk/sun/tools/jstat/jstatLineCounts1.sh b/test/jdk/sun/tools/jstat/jstatLineCounts1.sh index 97338b8e793d..ca6adce96a5c 100644 --- a/test/jdk/sun/tools/jstat/jstatLineCounts1.sh +++ b/test/jdk/sun/tools/jstat/jstatLineCounts1.sh @@ -1,5 +1,5 @@ # -# Copyright (c) 2004, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. # 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 @@ # @test # @bug 4990825 +# @requires vm.gc.Parallel # @run shell jstatLineCounts1.sh # @summary Test that output of 'jstat -gcutil 0 250 5' has expected line counts diff --git a/test/jdk/sun/tools/jstat/jstatLineCounts2.sh b/test/jdk/sun/tools/jstat/jstatLineCounts2.sh index eab19f3931e8..a668df72e0ec 100644 --- a/test/jdk/sun/tools/jstat/jstatLineCounts2.sh +++ b/test/jdk/sun/tools/jstat/jstatLineCounts2.sh @@ -1,5 +1,5 @@ # -# Copyright (c) 2004, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. # 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 @@ # @test # @bug 4990825 +# @requires vm.gc.Parallel # @run shell jstatLineCounts2.sh # @summary Test that output of 'jstat -gcutil 0' has expected line counts diff --git a/test/jdk/sun/tools/jstat/jstatLineCounts3.sh b/test/jdk/sun/tools/jstat/jstatLineCounts3.sh index 9a769a924648..bffffc8a38e3 100644 --- a/test/jdk/sun/tools/jstat/jstatLineCounts3.sh +++ b/test/jdk/sun/tools/jstat/jstatLineCounts3.sh @@ -1,5 +1,5 @@ # -# Copyright (c) 2004, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. # 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 @@ # @test # @bug 4990825 +# @requires vm.gc.Parallel # @run shell jstatLineCounts3.sh # @summary Test that output of 'jstat -gcutil -h 10 250 10' has expected line counts diff --git a/test/jdk/sun/tools/jstat/jstatLineCounts4.sh b/test/jdk/sun/tools/jstat/jstatLineCounts4.sh index 817c3b14f621..9ad1f57a5d50 100644 --- a/test/jdk/sun/tools/jstat/jstatLineCounts4.sh +++ b/test/jdk/sun/tools/jstat/jstatLineCounts4.sh @@ -1,5 +1,5 @@ # -# Copyright (c) 2004, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. # 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 @@ # @test # @bug 4990825 +# @requires vm.gc.Parallel # @run shell jstatLineCounts4.sh # @summary Test that output of 'jstat -gcutil -h 10 250 11' has expected line counts diff --git a/test/jdk/sun/tools/jstat/jstatTimeStamp1.sh b/test/jdk/sun/tools/jstat/jstatTimeStamp1.sh index db71314fcf95..4e4cb8df4266 100644 --- a/test/jdk/sun/tools/jstat/jstatTimeStamp1.sh +++ b/test/jdk/sun/tools/jstat/jstatTimeStamp1.sh @@ -1,5 +1,5 @@ # -# Copyright (c) 2004, 2020, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. # 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 @@ # @test # @bug 4990825 +# @requires vm.gc.Parallel # @run shell jstatTimeStamp1.sh # @summary Test that output of 'jstat -gcutil -t 0' has expected format From 474cc1160a75738f10956294c11171cd9b2e304e Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Wed, 22 Jul 2026 18:54:58 +0000 Subject: [PATCH 68/86] 8369020: Test compiler/intrinsics/TestLongUnsignedDivMod.java completed and timed out Backport-of: 9b59c2dc766a71f8a042a6d5b3a8d14b2835df2f --- .../jtreg/compiler/intrinsics/TestLongUnsignedDivMod.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/hotspot/jtreg/compiler/intrinsics/TestLongUnsignedDivMod.java b/test/hotspot/jtreg/compiler/intrinsics/TestLongUnsignedDivMod.java index 88fd46c43250..5c16cbf43d58 100644 --- a/test/hotspot/jtreg/compiler/intrinsics/TestLongUnsignedDivMod.java +++ b/test/hotspot/jtreg/compiler/intrinsics/TestLongUnsignedDivMod.java @@ -110,7 +110,6 @@ public TestLongUnsignedDivMod() { } @Test // needs to be run in (fast) debug mode - @Warmup(10000) @IR(counts = {IRNode.UDIV_L, ">= 1"}) // At least one UDivL node is generated if intrinsic is used public void testDivideUnsigned() { for (int i = 0; i < BUFFER_SIZE; i++) { @@ -124,7 +123,6 @@ public void testDivideUnsigned() { } @Test // needs to be run in (fast) debug mode - @Warmup(10000) @IR(counts = {IRNode.UMOD_L, ">= 1"}) // At least one UModL node is generated if intrinsic is used public void testRemainderUnsigned() { for (int i = 0; i < BUFFER_SIZE; i++) { @@ -139,7 +137,6 @@ public void testRemainderUnsigned() { @Test // needs to be run in (fast) debug mode - @Warmup(10000) @IR(applyIfPlatform = {"x64", "true"}, counts = {IRNode.UDIV_MOD_L, ">= 1"}) // At least one UDivModL node is generated if intrinsic is used public void testDivModUnsigned() { From 746b1c202f45f53b288c4eac58e9996d0bcfeba0 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Wed, 22 Jul 2026 18:55:33 +0000 Subject: [PATCH 69/86] 8370653: Fix race in CompressedClassSpaceSizeInJmapHeap.java Backport-of: dfa04f4aa5463de7812877553ea779da6467d373 --- .../CompressedClassSpaceSizeInJmapHeap.java | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/test/hotspot/jtreg/gc/metaspace/CompressedClassSpaceSizeInJmapHeap.java b/test/hotspot/jtreg/gc/metaspace/CompressedClassSpaceSizeInJmapHeap.java index 3b89bf04d180..f042ac44af48 100644 --- a/test/hotspot/jtreg/gc/metaspace/CompressedClassSpaceSizeInJmapHeap.java +++ b/test/hotspot/jtreg/gc/metaspace/CompressedClassSpaceSizeInJmapHeap.java @@ -46,6 +46,7 @@ import java.util.List; public class CompressedClassSpaceSizeInJmapHeap { + // Note that on some platforms it may require root privileges to run this test. public static void main(String[] args) throws Exception { SATestUtils.skipIfCannotAttach(); // throws SkippedException if attach not expected to work. @@ -67,8 +68,15 @@ public static void main(String[] args) throws Exception { File err = new File("CompressedClassSpaceSizeInJmapHeap.stderr.txt"); pb.redirectError(err); - run(pb); - + // If we attempt to attach to LingeredApp before it has initialized, the heap dump request will fail, so we allow 3 tries + int allowedTries = 3; + int exitValue; + do { + exitValue = run(pb); + } while ((exitValue != 0) && (allowedTries-- > 0)); + if (exitValue != 0) { + throw new Exception("jmap -heap exited with error code: " + exitValue); + } OutputAnalyzer output = new OutputAnalyzer(read(out)); output.shouldContain("CompressedClassSpaceSize = 50331648 (48.0MB)"); out.delete(); @@ -76,12 +84,9 @@ public static void main(String[] args) throws Exception { LingeredApp.stopApp(theApp); } - private static void run(ProcessBuilder pb) throws Exception { + private static int run(ProcessBuilder pb) throws Exception { OutputAnalyzer output = ProcessTools.executeProcess(pb); - int exitValue = output.getExitValue(); - if (exitValue != 0) { - throw new Exception("jmap -heap exited with error code: " + exitValue); - } + return output.getExitValue(); } private static String read(File f) throws Exception { From 52a01cf9bfa8f7005cb0fbca249fcffa9889dcd6 Mon Sep 17 00:00:00 2001 From: Christoph Langer Date: Thu, 23 Jul 2026 05:14:10 +0000 Subject: [PATCH 70/86] 8365498: jdk/jfr/event/os/TestCPULoad.java fails with Expected at least one event Backport-of: d68065e7474c07233cf1752c2a6efcaf9d35066d --- test/jdk/jdk/jfr/event/os/TestCPULoad.java | 72 ++++++---------------- 1 file changed, 18 insertions(+), 54 deletions(-) diff --git a/test/jdk/jdk/jfr/event/os/TestCPULoad.java b/test/jdk/jdk/jfr/event/os/TestCPULoad.java index 09ceb0a79b78..f3f30f15b2bd 100644 --- a/test/jdk/jdk/jfr/event/os/TestCPULoad.java +++ b/test/jdk/jdk/jfr/event/os/TestCPULoad.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,10 +23,12 @@ package jdk.jfr.event.os; -import java.util.List; +import java.time.Duration; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; -import jdk.jfr.Recording; import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordingStream; import jdk.test.lib.jfr.EventNames; import jdk.test.lib.jfr.Events; @@ -40,56 +42,18 @@ public class TestCPULoad { private final static String EVENT_NAME = EventNames.CPULoad; - public static boolean isPrime(int num) { - if (num <= 1) return false; - for (int i = 2; i <= Math.sqrt(num); i++) { - if (num % i == 0) return false; + public static void main(String... args) throws Exception { + try (RecordingStream rs = new RecordingStream()) { + BlockingQueue cpuEvent = new ArrayBlockingQueue<>(1); + rs.setReuse(false); + rs.enable(EVENT_NAME).withPeriod(Duration.ofMillis(100)); + rs.onEvent(cpuEvent::offer); + rs.startAsync(); + RecordedEvent event = cpuEvent.take(); + System.out.println(event); + Events.assertField(event, "jvmUser").atLeast(0.0f).atMost(1.0f); + Events.assertField(event, "jvmSystem").atLeast(0.0f).atMost(1.0f); + Events.assertField(event, "machineTotal").atLeast(0.0f).atMost(1.0f); } - return true; } - - public static int burnCpuCycles(int limit) { - int primeCount = 0; - for (int i = 2; i < limit; i++) { - if (isPrime(i)) { - primeCount++; - } - } - return primeCount; - } - - public static void main(String[] args) throws Throwable { - Recording recording = new Recording(); - recording.enable(EVENT_NAME); - recording.start(); - // burn some cycles to check increase of CPU related counters - int pn = burnCpuCycles(2500000); - recording.stop(); - System.out.println("Found " + pn + " primes while burning cycles"); - - List events = Events.fromRecording(recording); - if (events.isEmpty()) { - // CPU Load events are unreliable on Windows because - // the way processes are identified with perf. counters. - // See BUG 8010378. - // Workaround is to detect Windows and allow - // test to pass if events are missing. - if (isWindows()) { - return; - } - throw new AssertionError("Expected at least one event"); - } - for (RecordedEvent event : events) { - System.out.println("Event: " + event); - for (String loadName : loadNames) { - Events.assertField(event, loadName).atLeast(0.0f).atMost(1.0f); - } - } - } - - private static final String[] loadNames = {"jvmUser", "jvmSystem", "machineTotal"}; - - private static boolean isWindows() { - return System.getProperty("os.name").startsWith("Windows"); - } -} +} \ No newline at end of file From c53c4d9d83d9278d55754caf978526832004ec7a Mon Sep 17 00:00:00 2001 From: Ashay Rane Date: Thu, 23 Jul 2026 12:58:43 +0000 Subject: [PATCH 71/86] 8387625: Add "dt_socket" to `CheckedFeatures.notImplemented` for Windows/ARM64 Backport-of: 41a6eee8756ccd2ae8c511f1aacf7454aa5731db --- .../jtreg/vmTestbase/nsk/share/jdi/ArgumentHandler.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/hotspot/jtreg/vmTestbase/nsk/share/jdi/ArgumentHandler.java b/test/hotspot/jtreg/vmTestbase/nsk/share/jdi/ArgumentHandler.java index 3f924c6ac47b..41f9bb57b9e0 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/share/jdi/ArgumentHandler.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/share/jdi/ArgumentHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -605,6 +605,9 @@ class CheckedFeatures { {"windows-x64", "com.sun.jdi.CommandLineLaunch", "dt_socket"}, {"windows-x64", "com.sun.jdi.RawCommandLineLaunch", "dt_socket"}, + {"windows-aarch64", "com.sun.jdi.CommandLineLaunch", "dt_socket"}, + {"windows-aarch64", "com.sun.jdi.RawCommandLineLaunch", "dt_socket"}, + {"macosx-amd64", "com.sun.jdi.CommandLineLaunch", "dt_shmem"}, {"macosx-amd64", "com.sun.jdi.RawCommandLineLaunch", "dt_shmem"}, From d9aa7a3b04d42bbed558b44b7f8c2904303c1fe5 Mon Sep 17 00:00:00 2001 From: Vladimir Kozlov Date: Fri, 24 Jul 2026 16:16:43 +0000 Subject: [PATCH 72/86] 8388703: Launching with AOT cache fatally crashes with SIGILL (0x4) depending on available CPU instructions Reviewed-by: iklam, asmehra, heidinga --- src/hotspot/share/code/aotCodeCache.cpp | 12 +++++++----- .../appcds/aotCode/AOTCodeCompressedOopsTest.java | 4 +++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/hotspot/share/code/aotCodeCache.cpp b/src/hotspot/share/code/aotCodeCache.cpp index 9e4462ec1960..859916da9d29 100644 --- a/src/hotspot/share/code/aotCodeCache.cpp +++ b/src/hotspot/share/code/aotCodeCache.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -141,6 +141,11 @@ void AOTCodeCache::initialize() { return; // AOTCache must be specified to dump and use AOT code } + // Disable adapters caching which requires CPU features matching checks + // (implemented by JDK-8377507 and JDK-8381975) which we don't + // have in this version of code. + FLAG_SET_ERGO_IF_DEFAULT(AOTAdapterCaching, false); + // Disable stubs caching until JDK-8357398 is fixed. FLAG_SET_ERGO(AOTStubCaching, false); @@ -158,14 +163,11 @@ void AOTCodeCache::initialize() { bool is_dumping = false; bool is_using = false; if (CDSConfig::is_dumping_final_static_archive() && CDSConfig::is_dumping_aot_linked_classes()) { - FLAG_SET_ERGO_IF_DEFAULT(AOTAdapterCaching, true); - FLAG_SET_ERGO_IF_DEFAULT(AOTStubCaching, true); is_dumping = true; } else if (CDSConfig::is_using_archive() && CDSConfig::is_using_aot_linked_classes()) { - FLAG_SET_ERGO_IF_DEFAULT(AOTAdapterCaching, true); - FLAG_SET_ERGO_IF_DEFAULT(AOTStubCaching, true); is_using = true; } else { + FLAG_SET_ERGO(AOTAdapterCaching, false); log_info(aot, codecache, init)("AOT Code Cache is not used: AOT Class Linking is not used."); return; // nothing to do } diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeCompressedOopsTest.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeCompressedOopsTest.java index 4587eeae5e51..5910542cadda 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeCompressedOopsTest.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeCompressedOopsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * 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,6 +128,7 @@ public String[] vmArgs(RunMode runMode) { case RunMode.ASSEMBLY: { List args = getVMArgsForHeapConfig(zeroBaseInAsmPhase, zeroShiftInAsmPhase); args.addAll(List.of("-XX:+UnlockDiagnosticVMOptions", + "-XX:+AOTAdapterCaching", "-Xlog:aot=info", "-Xlog:aot+codecache+init=debug", "-Xlog:aot+codecache+exit=debug")); @@ -136,6 +137,7 @@ public String[] vmArgs(RunMode runMode) { case RunMode.PRODUCTION: { List args = getVMArgsForHeapConfig(zeroBaseInProdPhase, zeroShiftInProdPhase); args.addAll(List.of("-XX:+UnlockDiagnosticVMOptions", + "-XX:+AOTAdapterCaching", "-Xlog:aot=info", // we need this to parse CompressedOops settings "-Xlog:aot+codecache+init=debug", "-Xlog:aot+codecache+exit=debug")); From 21d24d201f45777d1043b8b13f6752b8a3c55687 Mon Sep 17 00:00:00 2001 From: Min Choi Date: Mon, 27 Jul 2026 19:34:45 +0000 Subject: [PATCH 73/86] 8372625: [Linux] Remove unnecessary logic for supports_fast_thread_cpu_time Reviewed-by: phh Backport-of: 683ef14bcec0e6c4825067229826ed4a53cd3d19 --- src/hotspot/os/linux/os_linux.cpp | 72 +++++-------------- src/hotspot/os/linux/os_linux.hpp | 16 +---- src/hotspot/share/runtime/cpuTimeCounters.cpp | 3 - 3 files changed, 17 insertions(+), 74 deletions(-) diff --git a/src/hotspot/os/linux/os_linux.cpp b/src/hotspot/os/linux/os_linux.cpp index d38c37141f73..c91490c10a27 100644 --- a/src/hotspot/os/linux/os_linux.cpp +++ b/src/hotspot/os/linux/os_linux.cpp @@ -162,10 +162,8 @@ physical_memory_size_type os::Linux::_physical_memory = 0; address os::Linux::_initial_thread_stack_bottom = nullptr; uintptr_t os::Linux::_initial_thread_stack_size = 0; -int (*os::Linux::_pthread_getcpuclockid)(pthread_t, clockid_t *) = nullptr; int (*os::Linux::_pthread_setname_np)(pthread_t, const char*) = nullptr; pthread_t os::Linux::_main_thread; -bool os::Linux::_supports_fast_thread_cpu_time = false; const char * os::Linux::_libc_version = nullptr; const char * os::Linux::_libpthread_version = nullptr; @@ -1529,29 +1527,6 @@ double os::elapsedVTime() { } } -void os::Linux::fast_thread_clock_init() { - clockid_t clockid; - struct timespec tp; - int (*pthread_getcpuclockid_func)(pthread_t, clockid_t *) = - (int(*)(pthread_t, clockid_t *)) dlsym(RTLD_DEFAULT, "pthread_getcpuclockid"); - - // Switch to using fast clocks for thread cpu time if - // the clock_getres() returns 0 error code. - // Note, that some kernels may support the current thread - // clock (CLOCK_THREAD_CPUTIME_ID) but not the clocks - // returned by the pthread_getcpuclockid(). - // If the fast POSIX clocks are supported then the clock_getres() - // must return at least tp.tv_sec == 0 which means a resolution - // better than 1 sec. This is extra check for reliability. - - if (pthread_getcpuclockid_func && - pthread_getcpuclockid_func(_main_thread, &clockid) == 0 && - clock_getres(clockid, &tp) == 0 && tp.tv_sec == 0) { - _supports_fast_thread_cpu_time = true; - _pthread_getcpuclockid = pthread_getcpuclockid_func; - } -} - // thread_id is kernel thread id (similar to Solaris LWP id) intx os::current_thread_id() { return os::Linux::gettid(); } int os::current_process_id() { @@ -4472,7 +4447,7 @@ OSReturn os::get_native_priority(const Thread* const thread, // For reference, please, see IEEE Std 1003.1-2004: // http://www.unix.org/single_unix_specification -jlong os::Linux::fast_thread_cpu_time(clockid_t clockid) { +jlong os::Linux::total_thread_cpu_time(clockid_t clockid) { struct timespec tp; int status = clock_gettime(clockid, &tp); assert(status == 0, "clock_gettime error: %s", os::strerror(errno)); @@ -4785,8 +4760,6 @@ jint os::init_2(void) { os::Posix::init_2(); - Linux::fast_thread_clock_init(); - if (PosixSignals::init() == JNI_ERR) { return JNI_ERR; } @@ -5213,14 +5186,14 @@ int os::open(const char *path, int oflag, int mode) { return fd; } -static jlong slow_thread_cpu_time(Thread *thread, bool user_sys_cpu_time); +static jlong user_thread_cpu_time(Thread *thread); -static jlong fast_cpu_time(Thread *thread) { +static jlong total_thread_cpu_time(Thread *thread) { clockid_t clockid; - int rc = os::Linux::pthread_getcpuclockid(thread->osthread()->pthread_id(), + int rc = pthread_getcpuclockid(thread->osthread()->pthread_id(), &clockid); if (rc == 0) { - return os::Linux::fast_thread_cpu_time(clockid); + return os::Linux::total_thread_cpu_time(clockid); } else { // It's possible to encounter a terminated native thread that failed // to detach itself from the VM - which should result in ESRCH. @@ -5237,41 +5210,31 @@ static jlong fast_cpu_time(Thread *thread) { // the fast estimate available on the platform. jlong os::current_thread_cpu_time() { - if (os::Linux::supports_fast_thread_cpu_time()) { - return os::Linux::fast_thread_cpu_time(CLOCK_THREAD_CPUTIME_ID); - } else { - // return user + sys since the cost is the same - return slow_thread_cpu_time(Thread::current(), true /* user + sys */); - } + return os::Linux::total_thread_cpu_time(CLOCK_THREAD_CPUTIME_ID); } jlong os::thread_cpu_time(Thread* thread) { - // consistent with what current_thread_cpu_time() returns - if (os::Linux::supports_fast_thread_cpu_time()) { - return fast_cpu_time(thread); - } else { - return slow_thread_cpu_time(thread, true /* user + sys */); - } + return total_thread_cpu_time(thread); } jlong os::current_thread_cpu_time(bool user_sys_cpu_time) { - if (user_sys_cpu_time && os::Linux::supports_fast_thread_cpu_time()) { - return os::Linux::fast_thread_cpu_time(CLOCK_THREAD_CPUTIME_ID); + if (user_sys_cpu_time) { + return os::Linux::total_thread_cpu_time(CLOCK_THREAD_CPUTIME_ID); } else { - return slow_thread_cpu_time(Thread::current(), user_sys_cpu_time); + return user_thread_cpu_time(Thread::current()); } } jlong os::thread_cpu_time(Thread *thread, bool user_sys_cpu_time) { - if (user_sys_cpu_time && os::Linux::supports_fast_thread_cpu_time()) { - return fast_cpu_time(thread); + if (user_sys_cpu_time) { + return total_thread_cpu_time(thread); } else { - return slow_thread_cpu_time(thread, user_sys_cpu_time); + return user_thread_cpu_time(thread); } } // -1 on error. -static jlong slow_thread_cpu_time(Thread *thread, bool user_sys_cpu_time) { +static jlong user_thread_cpu_time(Thread *thread) { pid_t tid = thread->osthread()->thread_id(); char *s; char stat[2048]; @@ -5308,11 +5271,8 @@ static jlong slow_thread_cpu_time(Thread *thread, bool user_sys_cpu_time) { &ldummy, &ldummy, &ldummy, &ldummy, &ldummy, &user_time, &sys_time); if (count != 13) return -1; - if (user_sys_cpu_time) { - return ((jlong)sys_time + (jlong)user_time) * (1000000000 / os::Posix::clock_tics_per_second()); - } else { - return (jlong)user_time * (1000000000 / os::Posix::clock_tics_per_second()); - } + + return (jlong)user_time * (1000000000 / os::Posix::clock_tics_per_second()); } void os::current_thread_cpu_time_info(jvmtiTimerInfo *info_ptr) { diff --git a/src/hotspot/os/linux/os_linux.hpp b/src/hotspot/os/linux/os_linux.hpp index 20039a4146f1..79b800659938 100644 --- a/src/hotspot/os/linux/os_linux.hpp +++ b/src/hotspot/os/linux/os_linux.hpp @@ -32,7 +32,6 @@ class os::Linux { friend class os; - static int (*_pthread_getcpuclockid)(pthread_t, clockid_t *); static int (*_pthread_setname_np)(pthread_t, const char*); static address _initial_thread_stack_bottom; @@ -41,8 +40,6 @@ class os::Linux { static const char *_libc_version; static const char *_libpthread_version; - static bool _supports_fast_thread_cpu_time; - static GrowableArray* _cpu_to_node; static GrowableArray* _nindex_to_node; @@ -146,18 +143,7 @@ class os::Linux { static bool manually_expand_stack(JavaThread * t, address addr); static void expand_stack_to(address bottom); - // fast POSIX clocks support - static void fast_thread_clock_init(void); - - static int pthread_getcpuclockid(pthread_t tid, clockid_t *clock_id) { - return _pthread_getcpuclockid ? _pthread_getcpuclockid(tid, clock_id) : -1; - } - - static bool supports_fast_thread_cpu_time() { - return _supports_fast_thread_cpu_time; - } - - static jlong fast_thread_cpu_time(clockid_t clockid); + static jlong total_thread_cpu_time(clockid_t clockid); static jlong sendfile(int out_fd, int in_fd, jlong* offset, jlong count); diff --git a/src/hotspot/share/runtime/cpuTimeCounters.cpp b/src/hotspot/share/runtime/cpuTimeCounters.cpp index 5b2e76fed7f6..1d7e75161675 100644 --- a/src/hotspot/share/runtime/cpuTimeCounters.cpp +++ b/src/hotspot/share/runtime/cpuTimeCounters.cpp @@ -121,8 +121,5 @@ ThreadTotalCPUTimeClosure::~ThreadTotalCPUTimeClosure() { } void ThreadTotalCPUTimeClosure::do_thread(Thread* thread) { - // The default code path (fast_thread_cpu_time()) asserts that - // pthread_getcpuclockid() and clock_gettime() must return 0. Thus caller - // must ensure the thread exists and has not terminated. _total += os::thread_cpu_time(thread); } From 6f795f787d26655b837d2d395a613b9adabab6ee Mon Sep 17 00:00:00 2001 From: Goetz Lindenmaier Date: Wed, 29 Jul 2026 11:46:40 +0000 Subject: [PATCH 74/86] 8387593: Using XOR mode to clear the content leaves some traces in metal Backport-of: 5428159f8856ddac220e0e970f38c1abfd9dc233 --- .../java2d/metal/MTLRenderQueue.m | 28 ++---- .../libawt_lwawt/java2d/metal/shaders.metal | 10 +- .../Graphics2D/ClearPolyLineUsingXORTest.java | 95 +++++++++++++++++++ 3 files changed, 105 insertions(+), 28 deletions(-) create mode 100644 test/jdk/java/awt/Graphics2D/ClearPolyLineUsingXORTest.java diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLRenderQueue.m b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLRenderQueue.m index 257269e647f0..8951ae8e110d 100644 --- a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLRenderQueue.m +++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/MTLRenderQueue.m @@ -161,35 +161,21 @@ void MTLRenderQueue_CheckPreviousOp(jint op) { case sun_java2d_pipe_BufferedOpCodes_DRAW_POLY: { CHECK_PREVIOUS_OP(MTL_OP_OTHER); - jint nPoints = NEXT_INT(b); - jboolean isClosed = NEXT_BOOLEAN(b); - jint transX = NEXT_INT(b); - jint transY = NEXT_INT(b); - jint *xPoints = (jint *)b; - jint *yPoints = ((jint *)b) + nPoints; if ([mtlc useXORComposite]) { commitEncodedCommands(); J2dTraceLn(J2D_TRACE_VERBOSE, "DRAW_POLY in XOR mode - Force commit earlier draw calls before DRAW_POLY."); - // draw separate (N-1) lines using N points - for(int point = 0; point < nPoints-1; point++) { - jint x1 = xPoints[point] + transX; - jint y1 = yPoints[point] + transY; - jint x2 = xPoints[point + 1] + transX; - jint y2 = yPoints[point + 1] + transY; - MTLRenderer_DrawLine(mtlc, dstOps, x1, y1, x2, y2); - } - - if (isClosed) { - MTLRenderer_DrawLine(mtlc, dstOps, xPoints[0] + transX, yPoints[0] + transY, - xPoints[nPoints-1] + transX, yPoints[nPoints-1] + transY); - } - } else { - MTLRenderer_DrawPoly(mtlc, dstOps, nPoints, isClosed, transX, transY, xPoints, yPoints); } + jint nPoints = NEXT_INT(b); + jboolean isClosed = NEXT_BOOLEAN(b); + jint transX = NEXT_INT(b); + jint transY = NEXT_INT(b); + jint *xPoints = (jint *)b; + jint *yPoints = ((jint *)b) + nPoints; + MTLRenderer_DrawPoly(mtlc, dstOps, nPoints, isClosed, transX, transY, xPoints, yPoints); SKIP_BYTES(b, nPoints * BYTES_PER_POLY_POINT); break; } diff --git a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/shaders.metal b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/shaders.metal index 722506ab2c33..8718e5cac2ff 100644 --- a/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/shaders.metal +++ b/src/java.desktop/macosx/native/libawt_lwawt/java2d/metal/shaders.metal @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -84,13 +84,11 @@ struct GradShaderInOut { struct ColShaderInOut_XOR { float4 position [[position]]; float ptSize [[point_size]]; - float2 orig_pos; half4 color; }; struct TxtShaderInOut_XOR { float4 position [[position]]; - float2 orig_pos; float2 texCoords; float2 tpCoords; }; @@ -677,7 +675,6 @@ vertex ColShaderInOut_XOR vert_col_xorMode(VertexInput in [[stage_in]], float4 pos4 = float4(in.position, 0.0, 1.0); out.position = transform.transformMatrix*pos4; out.ptSize = 1.0; - out.orig_pos = in.position; out.color = half4(uniforms.color.r, uniforms.color.g, uniforms.color.b, uniforms.color.a); return out; } @@ -685,7 +682,7 @@ vertex ColShaderInOut_XOR vert_col_xorMode(VertexInput in [[stage_in]], fragment half4 frag_col_xorMode(ColShaderInOut_XOR in [[stage_in]], texture2d renderTexture [[texture(0)]]) { - uint2 texCoord = {(unsigned int)(in.orig_pos.x), (unsigned int)(in.orig_pos.y)}; + uint2 texCoord = {(unsigned int)(in.position.x), (unsigned int)(in.position.y)}; float4 pixelColor = renderTexture.read(texCoord); half4 color = in.color; @@ -707,7 +704,6 @@ vertex TxtShaderInOut_XOR vert_txt_xorMode( TxtShaderInOut_XOR out; float4 pos4 = float4(in.position, 0.0, 1.0); out.position = transform.transformMatrix*pos4; - out.orig_pos = in.position; out.texCoords = in.texCoords; return out; } @@ -719,7 +715,7 @@ fragment half4 frag_txt_xorMode( constant TxtFrameUniforms& uniforms [[buffer(1)]], sampler textureSampler [[sampler(0)]]) { - uint2 texCoord = {(unsigned int)(vert.orig_pos.x), (unsigned int)(vert.orig_pos.y)}; + uint2 texCoord = {(unsigned int)(vert.position.x), (unsigned int)(vert.position.y)}; float4 bgColor = backgroundTexture.read(texCoord); float4 pixelColor = renderTexture.sample(textureSampler, vert.texCoords); diff --git a/test/jdk/java/awt/Graphics2D/ClearPolyLineUsingXORTest.java b/test/jdk/java/awt/Graphics2D/ClearPolyLineUsingXORTest.java new file mode 100644 index 000000000000..7afecfdee3b8 --- /dev/null +++ b/test/jdk/java/awt/Graphics2D/ClearPolyLineUsingXORTest.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * 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 + * @key headful + * @bug 8387593 + * @summary Tests that clearing a polyline using XOR mode does not + * leave any traces. Using uiScale 1 helps us to + * reproduce the issue. + * @run main/othervm -Dsun.java2d.uiScale=1 ClearPolyLineUsingXORTest + */ + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.GraphicsConfiguration; +import java.awt.GraphicsEnvironment; +import java.awt.image.BufferedImage; +import java.awt.image.VolatileImage; + +public class ClearPolyLineUsingXORTest { + private static final int SIZE = 500; + + private static BufferedImage clearUsingXOR(GraphicsConfiguration gc) { + VolatileImage vImg = gc.createCompatibleVolatileImage(SIZE, SIZE); + int attempt = 0; + while (true) { + if (++attempt > 10) { + throw new RuntimeException("Unable to use VolatileImage after " + + attempt + " attempts"); + } + + int status = vImg.validate(gc); + if (status == VolatileImage.IMAGE_INCOMPATIBLE) { + vImg = gc.createCompatibleVolatileImage(SIZE, SIZE); + } + + Graphics2D g2d = vImg.createGraphics(); + g2d.setBackground(Color.BLACK); + g2d.clearRect(0, 0, 500, 500); + + int min = 10; + int max = 210; + int mid = 110; + + int xdp[] = {min, max, min, max, min, max}; + int ydp[] = {min, min, mid, max, max, mid}; + + g2d.setXORMode(Color.GREEN); + g2d.drawPolygon(xdp, ydp, xdp.length); + g2d.drawPolygon(xdp, ydp, xdp.length); + + BufferedImage snapshot = vImg.getSnapshot(); + if (vImg.contentsLost()) { + continue; + } + return snapshot; + } + } + public static void main(String[] args) { + GraphicsConfiguration gc = + GraphicsEnvironment.getLocalGraphicsEnvironment(). + getDefaultScreenDevice().getDefaultConfiguration(); + BufferedImage bImg = clearUsingXOR(gc); + + for (int x = 0; x < SIZE; x++) { + for (int y = 0; y < SIZE; y++) { + if (bImg.getRGB(x, y) != Color.BLACK.getRGB()) { + throw new RuntimeException("Clear using XOR is not" + + " working at x: " + x + " y: " + y); + } + } + } + } +} From 33349745f276108424c15b54fa238a3f403ab32e Mon Sep 17 00:00:00 2001 From: Francisco Ferrari Bihurriet Date: Thu, 30 Jul 2026 18:10:14 +0000 Subject: [PATCH 75/86] 8352728: InternalError loading java.security due to Windows parent folder permissions Reviewed-by: andrew Backport-of: 31775fd27f247ba5d594dbf771ea7a1481422fb8 --- .../share/classes/java/security/Security.java | 37 +++-- .../ExtraFileAndIncludes.java} | 154 ++++++++++-------- .../SecurityPropFile/LinuxAnonymousFiles.java | 83 ++++++++++ .../SecurityPropFile/SecurityPropFile.file | 1 - .../SecurityPropFile/SecurityPropFile.java | 42 ----- .../WindowsParentDirPermissions.java | 84 ++++++++++ 6 files changed, 273 insertions(+), 128 deletions(-) rename test/jdk/java/security/Security/{ConfigFileTest.java => SecurityPropFile/ExtraFileAndIncludes.java} (88%) create mode 100644 test/jdk/java/security/Security/SecurityPropFile/LinuxAnonymousFiles.java delete mode 100644 test/jdk/java/security/Security/SecurityPropFile/SecurityPropFile.file delete mode 100644 test/jdk/java/security/Security/SecurityPropFile/SecurityPropFile.java create mode 100644 test/jdk/java/security/Security/SecurityPropFile/WindowsParentDirPermissions.java diff --git a/src/java.base/share/classes/java/security/Security.java b/src/java.base/share/classes/java/security/Security.java index 6969fe8a8e14..30a22b05742d 100644 --- a/src/java.base/share/classes/java/security/Security.java +++ b/src/java.base/share/classes/java/security/Security.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2026, Oracle and/or its affiliates. All rights reserved. * 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 java.nio.file.Files; import java.nio.file.InvalidPathException; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; @@ -112,7 +113,7 @@ private enum LoadingMode {OVERRIDE, APPEND} private static Path currentPath; - private static final Set activePaths = new HashSet<>(); + private static final List activePaths = new ArrayList<>(); static void loadAll() { // first load the master properties file to @@ -262,30 +263,40 @@ static void loadInclude(String propFile) { } } + private static void checkCyclicInclude(Path path) { + for (Path activePath : activePaths) { + try { + if (Files.isSameFile(path, activePath)) { + throw new InternalError( + "Cyclic include of '" + path + "'"); + } + } catch (IOException e) { + if (sdebug != null) { + sdebug.println("skipped exception when checking for " + + "cyclic inclusion of " + path + ":"); + e.printStackTrace(); + } + } + } + } + private static void loadFromPath(Path path, LoadingMode mode) throws IOException { - boolean isRegularFile = Files.isRegularFile(path); - if (isRegularFile) { - path = path.toRealPath(); - } else if (Files.isDirectory(path)) { + if (Files.isDirectory(path)) { throw new IOException("Is a directory"); - } else { - path = path.toAbsolutePath(); - } - if (activePaths.contains(path)) { - throw new InternalError("Cyclic include of '" + path + "'"); } try (InputStream is = Files.newInputStream(path)) { + checkCyclicInclude(path); reset(mode); Path previousPath = currentPath; - currentPath = isRegularFile ? path : null; + currentPath = Files.isRegularFile(path) ? path : null; activePaths.add(path); try { debugLoad(true, path); props.load(is); debugLoad(false, path); } finally { - activePaths.remove(path); + activePaths.removeLast(); currentPath = previousPath; } } diff --git a/test/jdk/java/security/Security/ConfigFileTest.java b/test/jdk/java/security/Security/SecurityPropFile/ExtraFileAndIncludes.java similarity index 88% rename from test/jdk/java/security/Security/ConfigFileTest.java rename to test/jdk/java/security/Security/SecurityPropFile/ExtraFileAndIncludes.java index caf657005e1b..4cf723f856a3 100644 --- a/test/jdk/java/security/Security/ConfigFileTest.java +++ b/test/jdk/java/security/Security/SecurityPropFile/ExtraFileAndIncludes.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2026, Oracle and/or its affiliates. All rights reserved. * 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,13 +62,13 @@ * @test * @summary Tests security properties passed through java.security, * java.security.properties or included from other properties files. - * @bug 8155246 8292297 8292177 8281658 8319332 + * @bug 4303068 8155246 8292297 8292177 8281658 8319332 * @modules java.base/sun.net.www * @library /test/lib - * @run main ConfigFileTest + * @run main ExtraFileAndIncludes */ -public class ConfigFileTest { +public class ExtraFileAndIncludes { static final String SEPARATOR_THIN = "----------------------------"; private static void printTestHeader(String testName) { @@ -91,7 +91,8 @@ public static void main(String[] args) throws Exception { } else { // Executed by the test JVM. try (FilesManager filesMgr = new FilesManager()) { - for (Method m : ConfigFileTest.class.getDeclaredMethods()) { + for (Method m : + ExtraFileAndIncludes.class.getDeclaredMethods()) { if (m.getName().startsWith("test")) { printTestHeader(m.getName()); Executor.run(m, filesMgr); @@ -120,7 +121,7 @@ static void testShowSettings(Executor ex, FilesManager filesMgr) static void testIncludeBasic(Executor ex, FilesManager filesMgr) throws Exception { PropsFile masterFile = filesMgr.newMasterFile(); - ExtraPropsFile extraFile = filesMgr.newExtraFile(); + ExtraPropsFile extraFile = filesMgr.newExtraFile(ExtraMode.FILE_URI); PropsFile file0 = filesMgr.newFile("file0.properties"); PropsFile file1 = filesMgr.newFile("dir1/file1.properties"); PropsFile file2 = filesMgr.newFile("dir1/dir2/file2.properties"); @@ -130,7 +131,7 @@ static void testIncludeBasic(Executor ex, FilesManager filesMgr) file2.addAbsoluteInclude(file1); ex.setMasterFile(masterFile); - ex.setExtraFile(extraFile, Executor.ExtraMode.FILE_URI, false); + ex.setExtraFile(extraFile, false); ex.assertSuccess(); } @@ -152,7 +153,7 @@ static void testRepeatedInclude(Executor ex, FilesManager filesMgr) static void testIncludeWithOverrideAll(Executor ex, FilesManager filesMgr) throws Exception { PropsFile masterFile = filesMgr.newMasterFile(); - ExtraPropsFile extraFile = filesMgr.newExtraFile(); + ExtraPropsFile extraFile = filesMgr.newExtraFile(ExtraMode.HTTP_SERVED); PropsFile file0 = filesMgr.newFile("file0.properties"); PropsFile file1 = filesMgr.newFile("dir1/file1.properties"); @@ -160,40 +161,40 @@ static void testIncludeWithOverrideAll(Executor ex, FilesManager filesMgr) extraFile.addAbsoluteInclude(file1); ex.setMasterFile(masterFile); - ex.setExtraFile(extraFile, Executor.ExtraMode.HTTP_SERVED, true); + ex.setExtraFile(extraFile, true); ex.assertSuccess(); } static void extraPropertiesByHelper(Executor ex, FilesManager filesMgr, - Executor.ExtraMode mode) throws Exception { - ExtraPropsFile extraFile = filesMgr.newExtraFile(); + ExtraMode mode) throws Exception { + ExtraPropsFile extraFile = filesMgr.newExtraFile(mode); PropsFile file0 = filesMgr.newFile("file0.properties"); extraFile.addRelativeInclude(file0); ex.setMasterFile(filesMgr.newMasterFile()); - ex.setExtraFile(extraFile, mode, true); + ex.setExtraFile(extraFile, true); ex.assertSuccess(); } static void testExtraPropertiesByPathAbsolute(Executor ex, FilesManager filesMgr) throws Exception { - extraPropertiesByHelper(ex, filesMgr, Executor.ExtraMode.PATH_ABS); + extraPropertiesByHelper(ex, filesMgr, ExtraMode.PATH_ABS); } static void testExtraPropertiesByPathRelative(Executor ex, FilesManager filesMgr) throws Exception { - extraPropertiesByHelper(ex, filesMgr, Executor.ExtraMode.PATH_REL); + extraPropertiesByHelper(ex, filesMgr, ExtraMode.PATH_REL); } static void specialCharsIncludes(Executor ex, FilesManager filesMgr, - char specialChar, Executor.ExtraMode extraMode, - boolean useRelativeIncludes) throws Exception { + char specialChar, ExtraMode extraMode, boolean useRelativeIncludes) + throws Exception { String suffix = specialChar + ".properties"; ExtraPropsFile extraFile; PropsFile file0, file1; try { - extraFile = filesMgr.newExtraFile("extra" + suffix); + extraFile = filesMgr.newExtraFile("extra" + suffix, extraMode); file0 = filesMgr.newFile("file0" + suffix); file1 = filesMgr.newFile("file1" + suffix); } catch (InvalidPathException ipe) { @@ -210,20 +211,18 @@ static void specialCharsIncludes(Executor ex, FilesManager filesMgr, extraFile.addAbsoluteInclude(file1); ex.setMasterFile(filesMgr.newMasterFile()); - ex.setExtraFile(extraFile, extraMode, false); + ex.setExtraFile(extraFile, false); ex.assertSuccess(); } static void testUnicodeIncludes1(Executor ex, FilesManager filesMgr) throws Exception { - specialCharsIncludes(ex, filesMgr, '\u2022', - Executor.ExtraMode.PATH_ABS, true); + specialCharsIncludes(ex, filesMgr, '\u2022', ExtraMode.PATH_ABS, true); } static void testUnicodeIncludes2(Executor ex, FilesManager filesMgr) throws Exception { - specialCharsIncludes(ex, filesMgr, '\u2022', - Executor.ExtraMode.FILE_URI, true); + specialCharsIncludes(ex, filesMgr, '\u2022', ExtraMode.FILE_URI, true); } static void testUnicodeIncludes3(Executor ex, FilesManager filesMgr) @@ -232,7 +231,7 @@ static void testUnicodeIncludes3(Executor ex, FilesManager filesMgr) // file:/tmp/extra•.properties are supported for the extra file. // However, relative includes are not allowed in these cases. specialCharsIncludes(ex, filesMgr, '\u2022', - Executor.ExtraMode.RAW_FILE_URI1, false); + ExtraMode.RAW_FILE_URI1, false); } static void testUnicodeIncludes4(Executor ex, FilesManager filesMgr) @@ -241,19 +240,17 @@ static void testUnicodeIncludes4(Executor ex, FilesManager filesMgr) // file:///tmp/extra•.properties are supported for the extra file. // However, relative includes are not allowed in these cases. specialCharsIncludes(ex, filesMgr, '\u2022', - Executor.ExtraMode.RAW_FILE_URI2, false); + ExtraMode.RAW_FILE_URI2, false); } static void testSpaceIncludes1(Executor ex, FilesManager filesMgr) throws Exception { - specialCharsIncludes(ex, filesMgr, ' ', - Executor.ExtraMode.PATH_ABS, true); + specialCharsIncludes(ex, filesMgr, ' ', ExtraMode.PATH_ABS, true); } static void testSpaceIncludes2(Executor ex, FilesManager filesMgr) throws Exception { - specialCharsIncludes(ex, filesMgr, ' ', - Executor.ExtraMode.FILE_URI, true); + specialCharsIncludes(ex, filesMgr, ' ', ExtraMode.FILE_URI, true); } static void testSpaceIncludes3(Executor ex, FilesManager filesMgr) @@ -261,8 +258,7 @@ static void testSpaceIncludes3(Executor ex, FilesManager filesMgr) // Backward compatibility check. Malformed URLs such as // file:/tmp/extra .properties are supported for the extra file. // However, relative includes are not allowed in these cases. - specialCharsIncludes(ex, filesMgr, ' ', - Executor.ExtraMode.RAW_FILE_URI1, false); + specialCharsIncludes(ex, filesMgr, ' ', ExtraMode.RAW_FILE_URI1, false); } static void testSpaceIncludes4(Executor ex, FilesManager filesMgr) @@ -270,8 +266,7 @@ static void testSpaceIncludes4(Executor ex, FilesManager filesMgr) // Backward compatibility check. Malformed URLs such as // file:///tmp/extra .properties are supported for the extra file. // However, relative includes are not allowed in these cases. - specialCharsIncludes(ex, filesMgr, ' ', - Executor.ExtraMode.RAW_FILE_URI2, false); + specialCharsIncludes(ex, filesMgr, ' ', ExtraMode.RAW_FILE_URI2, false); } static void notOverrideOnFailureHelper(Executor ex, FilesManager filesMgr, @@ -370,13 +365,13 @@ static void assertTestSecuritySetPropertyShouldNotInclude() { static void testCannotResolveRelativeFromHTTPServed(Executor ex, FilesManager filesMgr) throws Exception { - ExtraPropsFile extraFile = filesMgr.newExtraFile(); + ExtraPropsFile extraFile = filesMgr.newExtraFile(ExtraMode.HTTP_SERVED); PropsFile file0 = filesMgr.newFile("file0.properties"); extraFile.addRelativeInclude(file0); ex.setMasterFile(filesMgr.newMasterFile()); - ex.setExtraFile(extraFile, Executor.ExtraMode.HTTP_SERVED, true); + ex.setExtraFile(extraFile, true); ex.assertError("InternalError: Cannot resolve '" + file0.fileName + "' relative path when included from a non-regular " + "properties file (e.g. HTTP served file)"); @@ -394,14 +389,15 @@ static void testCannotIncludeCycles(Executor ex, FilesManager filesMgr) masterFile.addRelativeInclude(file0); ex.setMasterFile(masterFile); - ex.assertError( - "InternalError: Cyclic include of '" + masterFile.path + "'"); + ex.assertError("Cyclic include"); + ex.getOutputAnalyzer().stderrShouldMatch("\\QInternalError: Cyclic " + + "include of '\\E[^']+\\Q" + masterFile.fileName + "'\\E"); } static void testCannotIncludeURL(Executor ex, FilesManager filesMgr) throws Exception { PropsFile masterFile = filesMgr.newMasterFile(); - ExtraPropsFile extraFile = filesMgr.newExtraFile(); + ExtraPropsFile extraFile = filesMgr.newExtraFile(ExtraMode.HTTP_SERVED); masterFile.addRawProperty("include", extraFile.url.toString()); @@ -432,8 +428,7 @@ static void testMustHaveMasterFileEvenWithExtraFile(Executor ex, // Launch a JDK without a master java.security file present, but with an // extra file passed. Since the "security.overridePropertiesFile=true" // security property is missing, it should fail anyway. - ex.setExtraFile( - filesMgr.newExtraFile(), Executor.ExtraMode.FILE_URI, true); + ex.setExtraFile(filesMgr.newExtraFile(ExtraMode.FILE_URI), true); ex.assertError("InternalError: Error loading java.security file"); } } @@ -455,17 +450,24 @@ static Include of(PropsFile propsFile) { static Include of(PropsFile propsFile, String value) { return new Include(propsFile, value); } + + void assertProcessed(OutputAnalyzer oa) { + oa.shouldContain("processing include: '" + value + "'"); + oa.shouldContain("finished processing " + propsFile.displayPath); + } } protected final List includes = new ArrayList<>(); protected final PrintWriter writer; protected boolean includedFromExtra = false; + protected Path displayPath; final String fileName; final Path path; PropsFile(String fileName, Path path) throws IOException { this.fileName = fileName; this.path = path; + this.displayPath = path; this.writer = new PrintWriter(Files.newOutputStream(path, StandardOpenOption.CREATE, StandardOpenOption.APPEND), true); } @@ -513,8 +515,9 @@ void addAbsoluteInclude(PropsFile propsFile) { } void addRelativeInclude(PropsFile propsFile) { - addIncludeDefinition(Include.of(propsFile, - path.getParent().relativize(propsFile.path).toString())); + Path rel = path.getParent().relativize(propsFile.path); + addIncludeDefinition(Include.of(propsFile, rel.toString())); + propsFile.displayPath = displayPath.getParent().resolve(rel); } void assertApplied(OutputAnalyzer oa) { @@ -522,8 +525,7 @@ void assertApplied(OutputAnalyzer oa) { FilesManager.APPLIED_PROP_VALUE); for (Include include : includes) { include.propsFile.assertApplied(oa); - oa.shouldContain("processing include: '" + include.value + "'"); - oa.shouldContain("finished processing " + include.propsFile.path); + include.assertProcessed(oa); } } @@ -534,8 +536,7 @@ void assertWasOverwritten(OutputAnalyzer oa) { if (!include.propsFile.includedFromExtra) { include.propsFile.assertWasOverwritten(oa); } - oa.shouldContain("processing include: '" + include.value + "'"); - oa.shouldContain("finished processing " + include.propsFile.path); + include.assertProcessed(oa); } } @@ -556,13 +557,24 @@ void close() { } } +enum ExtraMode { + HTTP_SERVED, FILE_URI, RAW_FILE_URI1, RAW_FILE_URI2, PATH_ABS, PATH_REL +} + final class ExtraPropsFile extends PropsFile { + private static final Path CWD = Path.of(".").toAbsolutePath(); private final Map systemProps = new LinkedHashMap<>(); + private final ExtraMode mode; final URI url; - ExtraPropsFile(String fileName, URI url, Path path) throws IOException { + ExtraPropsFile(String fileName, URI url, Path path, ExtraMode mode) + throws IOException { super(fileName, path); this.url = url; + this.mode = mode; + if (mode == ExtraMode.PATH_REL) { + this.displayPath = CWD.relativize(path); + } } @Override @@ -578,14 +590,25 @@ protected void addIncludeDefinition(Include include) { super.addIncludeDefinition(include); } + String getSysPropValue() { + return switch (mode) { + case HTTP_SERVED -> url.toString(); + case FILE_URI -> path.toUri().toString(); + case RAW_FILE_URI1 -> "file:" + path; + case RAW_FILE_URI2 -> + "file://" + (path.startsWith("/") ? "" : "/") + path; + case PATH_ABS, PATH_REL -> displayPath.toString(); + }; + } + Map getSystemProperties() { return Collections.unmodifiableMap(systemProps); } } final class FilesManager implements Closeable { - private static final Path ROOT_DIR = - Path.of(ConfigFileTest.class.getSimpleName()).toAbsolutePath(); + private static final Path ROOT_DIR = Path.of( + ExtraFileAndIncludes.class.getSimpleName()).toAbsolutePath(); private static final Path PROPS_DIR = ROOT_DIR.resolve("properties"); private static final Path JDK_DIR = ROOT_DIR.resolve("jdk"); private static final Path MASTER_FILE = @@ -684,11 +707,11 @@ private PropsFile newFile(Path path, PropsFileBuilder builder) propsFile.addComment("Property to determine if this properties file " + "was parsed and not overwritten:"); propsFile.addRawProperty(fileName, APPLIED_PROP_VALUE); - propsFile.addComment(ConfigFileTest.SEPARATOR_THIN); + propsFile.addComment(ExtraFileAndIncludes.SEPARATOR_THIN); propsFile.addComment("Property to be overwritten by every properties " + "file (master, extra or included):"); propsFile.addRawProperty(LAST_FILE_PROP_NAME, fileName); - propsFile.addComment(ConfigFileTest.SEPARATOR_THIN); + propsFile.addComment(ExtraFileAndIncludes.SEPARATOR_THIN); createdFiles.add(propsFile); return propsFile; } @@ -702,16 +725,17 @@ PropsFile newMasterFile() throws IOException { return newFile(MASTER_FILE, PropsFile::new); } - ExtraPropsFile newExtraFile() throws IOException { - return newExtraFile("extra.properties"); + ExtraPropsFile newExtraFile(ExtraMode mode) throws IOException { + return newExtraFile("extra.properties", mode); } - ExtraPropsFile newExtraFile(String extraFileName) throws IOException { + ExtraPropsFile newExtraFile(String extraFileName, ExtraMode mode) + throws IOException { return (ExtraPropsFile) newFile(PROPS_DIR.resolve(extraFileName), (fileName, path) -> { URI uri = serverUri.resolve(ParseUtil.encodePath( ROOT_DIR.relativize(path).toString())); - return new ExtraPropsFile(fileName, uri, path); + return new ExtraPropsFile(fileName, uri, path, mode); }); } @@ -719,7 +743,7 @@ void reportCreatedFiles() throws IOException { for (PropsFile propsFile : createdFiles) { System.err.println(); System.err.println(propsFile.path.toString()); - System.err.println(ConfigFileTest.SEPARATOR_THIN.repeat(3)); + System.err.println(ExtraFileAndIncludes.SEPARATOR_THIN.repeat(3)); try (Stream lines = Files.lines(propsFile.path)) { long lineNumber = 1L; Iterator it = lines.iterator(); @@ -757,9 +781,6 @@ public void close() throws IOException { } final class Executor { - enum ExtraMode { - HTTP_SERVED, FILE_URI, RAW_FILE_URI1, RAW_FILE_URI2, PATH_ABS, PATH_REL - } static final String RUNNER_ARG = "runner"; static final String INITIAL_PROP_LOG_MSG = "Initial security property: "; private static final String OVERRIDING_LOG_MSG = @@ -769,7 +790,6 @@ enum ExtraMode { INITIAL_PROP_LOG_MSG + "postInitTest=shouldNotRecord", INITIAL_PROP_LOG_MSG + "include=", }; - private static final Path CWD = Path.of(".").toAbsolutePath(); private static final String JAVA_SEC_PROPS = "java.security.properties"; private static final String CLASS_PATH = Objects.requireNonNull( System.getProperty("test.classes"), "unspecified test.classes"); @@ -812,20 +832,10 @@ void setMasterFile(PropsFile masterPropsFile) { this.masterPropsFile = masterPropsFile; } - void setExtraFile(ExtraPropsFile extraPropsFile, ExtraMode mode, - boolean overrideAll) { + void setExtraFile(ExtraPropsFile extraPropsFile, boolean overrideAll) { this.extraPropsFile = extraPropsFile; expectedOverrideAll = overrideAll; - setRawExtraFile(switch (mode) { - case HTTP_SERVED -> extraPropsFile.url.toString(); - case FILE_URI -> extraPropsFile.path.toUri().toString(); - case RAW_FILE_URI1 -> "file:" + extraPropsFile.path; - case RAW_FILE_URI2 -> "file://" + - (extraPropsFile.path.startsWith("/") ? "" : "/") + - extraPropsFile.path; - case PATH_ABS -> extraPropsFile.path.toString(); - case PATH_REL -> CWD.relativize(extraPropsFile.path).toString(); - }, overrideAll); + setRawExtraFile(extraPropsFile.getSysPropValue(), overrideAll); } void setIgnoredExtraFile(String extraPropsFile, boolean overrideAll) { @@ -841,7 +851,7 @@ private void execute(boolean successExpected) throws Exception { List command = new ArrayList<>(jvmArgs); Collections.addAll(command, Utils.getTestJavaOpts()); addSystemPropertiesAsJvmArgs(command); - command.add(ConfigFileTest.class.getSimpleName()); + command.add(ExtraFileAndIncludes.class.getSimpleName()); command.add(RUNNER_ARG); oa = ProcessTools.executeProcess(new ProcessBuilder(command)); oa.shouldHaveExitValue(successExpected ? 0 : 1); diff --git a/test/jdk/java/security/Security/SecurityPropFile/LinuxAnonymousFiles.java b/test/jdk/java/security/Security/SecurityPropFile/LinuxAnonymousFiles.java new file mode 100644 index 000000000000..7ca2a7c0f8b2 --- /dev/null +++ b/test/jdk/java/security/Security/SecurityPropFile/LinuxAnonymousFiles.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2026, Red Hat, Inc. + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import jdk.test.lib.process.ProcessTools; + +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.IOException; +import java.lang.reflect.Field; +import java.nio.file.Files; +import java.nio.file.Path; + +/* + * @test + * @summary Ensures the java executable is able to load extra security + * properties files from anonymous files and pipes. + * @bug 8352728 + * @requires os.family == "linux" + * @modules java.base/java.io:+open + * @library /test/lib + * @run main LinuxAnonymousFiles + */ + +public class LinuxAnonymousFiles { + private static final String TEST_PROP = "property.name=PROPERTY_VALUE"; + + private static final class AnonymousFile implements AutoCloseable { + public final Path fdPath; + private final FileInputStream fis; + + private AnonymousFile(CharSequence content) throws Exception { + Path tmp = Files.createTempFile("anonymous-file-", ""); + Files.writeString(tmp, content + System.lineSeparator()); + fis = new FileInputStream(tmp.toFile()); + Files.delete(tmp); + // Now the file is regular but anonymous, and will be unlinked + // when we close the last file descriptor referring to it. The + // fis instance ensures we keep it alive until close() is invoked. + Field field = FileDescriptor.class.getDeclaredField("fd"); + field.setAccessible(true); + int fd = field.getInt(fis.getFD()); + fdPath = Path.of("/proc/self").toRealPath().resolve("fd/" + fd); + } + + @Override + public void close() throws IOException { + fis.close(); + } + } + + public static void main(String[] args) throws Exception { + Path java = Path.of(System.getProperty("test.jdk"), "bin", "java"); + try (AnonymousFile af = new AnonymousFile("include /dev/stdin")) { + ProcessTools.executeProcess(new ProcessBuilder(java.toString(), + "-Djava.security.debug=properties", + "-Djava.security.properties=" + af.fdPath, + "-XshowSettings:security:properties", "-version"), + TEST_PROP).shouldHaveExitValue(0).shouldContain(TEST_PROP); + } + System.out.println("TEST PASS - OK"); + } +} diff --git a/test/jdk/java/security/Security/SecurityPropFile/SecurityPropFile.file b/test/jdk/java/security/Security/SecurityPropFile/SecurityPropFile.file deleted file mode 100644 index 2b4c08c69037..000000000000 --- a/test/jdk/java/security/Security/SecurityPropFile/SecurityPropFile.file +++ /dev/null @@ -1 +0,0 @@ -policy.url.2=file:${test.src}/SecurityPropFile.policy diff --git a/test/jdk/java/security/Security/SecurityPropFile/SecurityPropFile.java b/test/jdk/java/security/Security/SecurityPropFile/SecurityPropFile.java deleted file mode 100644 index b0ba6c608549..000000000000 --- a/test/jdk/java/security/Security/SecurityPropFile/SecurityPropFile.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2001, 2024, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -/* - * @test - * @bug 4303068 - * @summary be allowed to specify the security properties file - * as a -D system property - * - * @run main/othervm -Djava.security.properties=${test.src}/SecurityPropFile.file -Djava.security.debug=properties SecurityPropFile - */ - -public class SecurityPropFile { - public static void main(String[] args) { - System.out.println(java.security.Security.getProperty - ("policy.provider")); - System.out.println(java.security.Security.getProperty - ("policy.url.1")); - System.out.println(java.security.Security.getProperty - ("policy.url.2")); - } -} diff --git a/test/jdk/java/security/Security/SecurityPropFile/WindowsParentDirPermissions.java b/test/jdk/java/security/Security/SecurityPropFile/WindowsParentDirPermissions.java new file mode 100644 index 000000000000..a41fd2f3535b --- /dev/null +++ b/test/jdk/java/security/Security/SecurityPropFile/WindowsParentDirPermissions.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2026, Red Hat, Inc. + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import jdk.test.lib.process.ProcessTools; +import jdk.test.lib.util.FileUtils; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.AclEntry; +import java.nio.file.attribute.AclEntryType; +import java.nio.file.attribute.AclFileAttributeView; +import java.util.List; + +/* + * @test + * @summary Ensures java.security is loadable in Windows, even when the user + * does not have permissions on one of the parent directories. + * @bug 8352728 + * @requires os.family == "windows" + * @library /test/lib + * @run main WindowsParentDirPermissions + */ + +public class WindowsParentDirPermissions { + private static AutoCloseable restrictedAcl(Path path) throws IOException { + AclFileAttributeView view = + Files.getFileAttributeView(path, AclFileAttributeView.class); + List originalAcl = List.copyOf(view.getAcl()); + view.setAcl(List.of(AclEntry.newBuilder().setType(AclEntryType.DENY) + .setPrincipal(Files.getOwner(path)).build())); + return () -> view.setAcl(originalAcl); + } + + public static void main(String[] args) throws Exception { + Path temp = Files.createTempDirectory("JDK-8352728-tmp-"); + try (AutoCloseable a1 = () -> FileUtils.deleteFileTreeUnchecked(temp)) { + // Copy the jdk to a different directory + Path originalJdk = Path.of(System.getProperty("test.jdk")); + Path jdk = temp.resolve("jdk-parent-dir", "jdk"); + Files.createDirectories(jdk); + FileUtils.copyDirectory(originalJdk, jdk); + + // Remove current user permissions from jdk-parent-dir + try (AutoCloseable a2 = restrictedAcl(jdk.getParent())) { + // Make sure the permissions are affecting the current user + try { + jdk.toRealPath(); + throw new jtreg.SkippedException("Must run non-elevated!"); + } catch (IOException expected) { } + + // Execute the copied jdk, ensuring java.security.Security is + // loaded (i.e. use -XshowSettings:security:properties) + ProcessTools.executeProcess(new ProcessBuilder( + List.of(jdk.resolve("bin", "java.exe").toString(), + "-Djava.security.debug=properties", + "-XshowSettings:security:properties", + "-version"))).shouldHaveExitValue(0); + } + } + System.out.println("TEST PASS - OK"); + } +} From 4e2eb850b68c7d1a00930583d950e2effb9a2c5e Mon Sep 17 00:00:00 2001 From: Goetz Lindenmaier Date: Sat, 1 Aug 2026 16:17:48 +0000 Subject: [PATCH 76/86] 8377983: (zipfs) ZipFileSystem.initCEN needlessly reads END header Backport-of: d02ac57e8469ac77cc4f53de77107a278ac5f346 --- .../classes/jdk/nio/zipfs/ZipFileSystem.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java b/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java index ab4716948902..b3db11eb1fe2 100644 --- a/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java +++ b/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -1233,7 +1233,7 @@ private void endRead() { private volatile boolean isOpen = true; private final SeekableByteChannel ch; // channel to the zipfile - final byte[] cen; // CEN & ENDHDR + final byte[] cen; // CEN private END end; private long locpos; // position of first LOC header (usually 0) @@ -1585,15 +1585,15 @@ private byte[] initCEN() throws IOException { if (locpos < 0) throw new ZipException("invalid END header (bad central directory offset)"); - // read in the CEN and END - byte[] cen = new byte[(int)(end.cenlen + ENDHDR)]; - if (readNBytesAt(cen, 0, cen.length, cenpos) != end.cenlen + ENDHDR) { + // read in the CEN + byte[] cen = new byte[(int)(end.cenlen)]; + if (readNBytesAt(cen, 0, cen.length, cenpos) != end.cenlen) { throw new ZipException("read CEN tables failed"); } // Iterate through the entries in the central directory inodes = LinkedHashMap.newLinkedHashMap(end.centot + 1); int pos = 0; - int limit = cen.length - ENDHDR; + int limit = cen.length; while (pos < limit) { if (!cenSigAt(cen, pos)) throw new ZipException("invalid CEN header (bad signature)"); @@ -1641,7 +1641,7 @@ private byte[] initCEN() throws IOException { // skip ext and comment pos += (CENHDR + nlen + elen + clen); } - if (pos + ENDHDR != cen.length) { + if (pos != cen.length) { throw new ZipException("invalid CEN header (bad header size)"); } buildNodeTree(); @@ -1671,7 +1671,7 @@ private void checkExtraFields( byte[] cen, int cenPos, long size, long csize, } // CEN Offset where this Extra field ends int extraEndOffset = startingOffset + extraFieldLen; - if (extraEndOffset > cen.length - ENDHDR) { + if (extraEndOffset > cen.length) { zerror("Invalid CEN header (extra data field size too long)"); } int currentOffset = startingOffset; From 80e832df864dac8de3ec779eea611cae18a862f3 Mon Sep 17 00:00:00 2001 From: Satyen Subramaniam Date: Sun, 2 Aug 2026 18:08:31 +0000 Subject: [PATCH 77/86] 8365893: test/jdk/java/lang/Thread/virtual/JfrEvents.java failing intermittently Backport-of: 58e7581527208dfd6dd694793e4790dcad8fc3ef --- test/jdk/java/lang/Thread/virtual/JfrEvents.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/test/jdk/java/lang/Thread/virtual/JfrEvents.java b/test/jdk/java/lang/Thread/virtual/JfrEvents.java index 0b0c2ccc7a0e..0c9678114816 100644 --- a/test/jdk/java/lang/Thread/virtual/JfrEvents.java +++ b/test/jdk/java/lang/Thread/virtual/JfrEvents.java @@ -42,6 +42,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.LockSupport; import java.util.stream.Collectors; +import java.util.stream.IntStream; import java.util.stream.Stream; import jdk.jfr.EventType; @@ -77,12 +78,13 @@ void testVirtualThreadStartAndEnd() throws Exception { // execute 100 tasks, each in their own virtual thread recording.start(); - ThreadFactory factory = Thread.ofVirtual().factory(); - try (var executor = Executors.newThreadPerTaskExecutor(factory)) { - for (int i = 0; i < 100; i++) { - executor.submit(() -> { }); + try { + List threads = IntStream.range(0, 100) + .mapToObj(_ -> Thread.startVirtualThread(() -> { })) + .toList(); + for (Thread t : threads) { + t.join(); } - Thread.sleep(1000); // give time for thread end events to be recorded } finally { recording.stop(); } From 864929f68c62a4640ab5b6680d0b5892f3a98929 Mon Sep 17 00:00:00 2001 From: Min Choi Date: Mon, 3 Aug 2026 16:05:46 +0000 Subject: [PATCH 78/86] 8372584: [Linux]: Replace reading proc to get thread user CPU time with clock_gettime Reviewed-by: phh Backport-of: 858d2e434dd4eb8aa94784bb1cd115554eec5dff --- src/hotspot/os/linux/os_linux.cpp | 93 ++++++++----------- src/hotspot/os/linux/os_linux.hpp | 2 +- .../bench/vm/runtime/ThreadMXBeanBench.java | 55 +++++++++++ 3 files changed, 96 insertions(+), 54 deletions(-) create mode 100644 test/micro/org/openjdk/bench/vm/runtime/ThreadMXBeanBench.java diff --git a/src/hotspot/os/linux/os_linux.cpp b/src/hotspot/os/linux/os_linux.cpp index c91490c10a27..e4b8c9a970a0 100644 --- a/src/hotspot/os/linux/os_linux.cpp +++ b/src/hotspot/os/linux/os_linux.cpp @@ -4447,7 +4447,7 @@ OSReturn os::get_native_priority(const Thread* const thread, // For reference, please, see IEEE Std 1003.1-2004: // http://www.unix.org/single_unix_specification -jlong os::Linux::total_thread_cpu_time(clockid_t clockid) { +jlong os::Linux::thread_cpu_time(clockid_t clockid) { struct timespec tp; int status = clock_gettime(clockid, &tp); assert(status == 0, "clock_gettime error: %s", os::strerror(errno)); @@ -5186,20 +5186,42 @@ int os::open(const char *path, int oflag, int mode) { return fd; } +// Since kernel v2.6.12 the Linux ABI has had support for encoding the clock +// types in the last three bits. Bit 2 indicates whether a cpu clock refers to a +// thread or a process. Bits 1 and 0 give the type: PROF=0, VIRT=1, SCHED=2, or +// FD=3. The clock CPUCLOCK_VIRT (0b001) reports the thread's consumed user +// time. POSIX compliant implementations of pthread_getcpuclockid return the +// clock CPUCLOCK_SCHED (0b010) which reports the thread's consumed system+user +// time (as mandated by the POSIX standard POSIX.1-2024/IEEE Std 1003.1-2024 +// §3.90). +static bool get_thread_clockid(Thread* thread, clockid_t* clockid, bool total) { + constexpr clockid_t CLOCK_TYPE_MASK = 3; + constexpr clockid_t CPUCLOCK_VIRT = 1; + + int rc = pthread_getcpuclockid(thread->osthread()->pthread_id(), clockid); + if (rc != 0) { + // It's possible to encounter a terminated native thread that failed + // to detach itself from the VM - which should result in ESRCH. + assert_status(rc == ESRCH, rc, "pthread_getcpuclockid failed"); + return false; + } + + if (!total) { + clockid_t clockid_tmp = *clockid; + clockid_tmp = (clockid_tmp & ~CLOCK_TYPE_MASK) | CPUCLOCK_VIRT; + *clockid = clockid_tmp; + } + + return true; +} + static jlong user_thread_cpu_time(Thread *thread); static jlong total_thread_cpu_time(Thread *thread) { - clockid_t clockid; - int rc = pthread_getcpuclockid(thread->osthread()->pthread_id(), - &clockid); - if (rc == 0) { - return os::Linux::total_thread_cpu_time(clockid); - } else { - // It's possible to encounter a terminated native thread that failed - // to detach itself from the VM - which should result in ESRCH. - assert_status(rc == ESRCH, rc, "pthread_getcpuclockid failed"); - return -1; - } + clockid_t clockid; + bool success = get_thread_clockid(thread, &clockid, true); + + return success ? os::Linux::thread_cpu_time(clockid) : -1; } // current_thread_cpu_time(bool) and thread_cpu_time(Thread*, bool) @@ -5210,7 +5232,7 @@ static jlong total_thread_cpu_time(Thread *thread) { // the fast estimate available on the platform. jlong os::current_thread_cpu_time() { - return os::Linux::total_thread_cpu_time(CLOCK_THREAD_CPUTIME_ID); + return os::Linux::thread_cpu_time(CLOCK_THREAD_CPUTIME_ID); } jlong os::thread_cpu_time(Thread* thread) { @@ -5219,7 +5241,7 @@ jlong os::thread_cpu_time(Thread* thread) { jlong os::current_thread_cpu_time(bool user_sys_cpu_time) { if (user_sys_cpu_time) { - return os::Linux::total_thread_cpu_time(CLOCK_THREAD_CPUTIME_ID); + return os::Linux::thread_cpu_time(CLOCK_THREAD_CPUTIME_ID); } else { return user_thread_cpu_time(Thread::current()); } @@ -5233,46 +5255,11 @@ jlong os::thread_cpu_time(Thread *thread, bool user_sys_cpu_time) { } } -// -1 on error. static jlong user_thread_cpu_time(Thread *thread) { - pid_t tid = thread->osthread()->thread_id(); - char *s; - char stat[2048]; - size_t statlen; - char proc_name[64]; - int count; - long sys_time, user_time; - char cdummy; - int idummy; - long ldummy; - FILE *fp; - - snprintf(proc_name, 64, "/proc/self/task/%d/stat", tid); - fp = os::fopen(proc_name, "r"); - if (fp == nullptr) return -1; - statlen = fread(stat, 1, 2047, fp); - stat[statlen] = '\0'; - fclose(fp); - - // Skip pid and the command string. Note that we could be dealing with - // weird command names, e.g. user could decide to rename java launcher - // to "java 1.4.2 :)", then the stat file would look like - // 1234 (java 1.4.2 :)) R ... ... - // We don't really need to know the command string, just find the last - // occurrence of ")" and then start parsing from there. See bug 4726580. - s = strrchr(stat, ')'); - if (s == nullptr) return -1; - - // Skip blank chars - do { s++; } while (s && isspace((unsigned char) *s)); - - count = sscanf(s,"%c %d %d %d %d %d %lu %lu %lu %lu %lu %lu %lu", - &cdummy, &idummy, &idummy, &idummy, &idummy, &idummy, - &ldummy, &ldummy, &ldummy, &ldummy, &ldummy, - &user_time, &sys_time); - if (count != 13) return -1; - - return (jlong)user_time * (1000000000 / os::Posix::clock_tics_per_second()); + clockid_t clockid; + bool success = get_thread_clockid(thread, &clockid, false); + + return success ? os::Linux::thread_cpu_time(clockid) : -1; } void os::current_thread_cpu_time_info(jvmtiTimerInfo *info_ptr) { diff --git a/src/hotspot/os/linux/os_linux.hpp b/src/hotspot/os/linux/os_linux.hpp index 79b800659938..7196aebf513a 100644 --- a/src/hotspot/os/linux/os_linux.hpp +++ b/src/hotspot/os/linux/os_linux.hpp @@ -143,7 +143,7 @@ class os::Linux { static bool manually_expand_stack(JavaThread * t, address addr); static void expand_stack_to(address bottom); - static jlong total_thread_cpu_time(clockid_t clockid); + static jlong thread_cpu_time(clockid_t clockid); static jlong sendfile(int out_fd, int in_fd, jlong* offset, jlong count); diff --git a/test/micro/org/openjdk/bench/vm/runtime/ThreadMXBeanBench.java b/test/micro/org/openjdk/bench/vm/runtime/ThreadMXBeanBench.java new file mode 100644 index 000000000000..f041eb89f5ab --- /dev/null +++ b/test/micro/org/openjdk/bench/vm/runtime/ThreadMXBeanBench.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. + * + * 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.vm.runtime; + +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadMXBean; +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.Threads; +import org.openjdk.jmh.annotations.Warmup; + +@State(Scope.Benchmark) +@Warmup(iterations = 2, time = 5) +@Measurement(iterations = 5, time = 5) +@BenchmarkMode(Mode.SampleTime) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@Threads(1) +@Fork(value = 10) +public class ThreadMXBeanBench { + static final ThreadMXBean mxThreadBean = ManagementFactory.getThreadMXBean(); + static long user; // To avoid dead-code elimination + + @Benchmark + public void getCurrentThreadUserTime() throws Throwable { + user = mxThreadBean.getCurrentThreadUserTime(); + } +} From ba206029e74cc71448ade9bd0f36b300443226af Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 3 Aug 2026 16:28:02 +0000 Subject: [PATCH 79/86] 7067310: 3 tests from closed/javax/sound/sampled caused BSOD on win 7 x86 8307574: ClipIsRunningAfterStop.java failed with "../nptl/pthread_mutex_lock.c:81: __pthread_mutex_lock: Assertion `mutex->__data.__owner == 0' failed." 8308395: javax/sound/sampled/Clip/ClipFlushCrash.java timed out Backport-of: 9b12c0bb190de3f7d06db71411f37f9465992a04 --- test/jdk/ProblemList.txt | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/test/jdk/ProblemList.txt b/test/jdk/ProblemList.txt index 8ca72e8cd150..12869e136d3e 100644 --- a/test/jdk/ProblemList.txt +++ b/test/jdk/ProblemList.txt @@ -640,19 +640,9 @@ sun/security/pkcs11/sslecc/ClientJSSEServerJSSE.java 8316183 linux-pp ############################################################################ # jdk_sound -javax/sound/sampled/DirectAudio/bug6372428.java 8055097 generic-all -javax/sound/sampled/Clip/bug5070081.java 8055097 generic-all -javax/sound/sampled/DataLine/LongFramePosition.java 8055097 generic-all javax/sound/sampled/Clip/Drain/ClipDrain.java 7062792 generic-all -javax/sound/sampled/Mixers/DisabledAssertionCrash.java 7067310 generic-all - -javax/sound/midi/Sequencer/Recording.java 8167580,8265485 linux-all,macosx-aarch64 -javax/sound/midi/Sequencer/Looping.java 8136897 generic-all -javax/sound/sampled/Clip/ClipIsRunningAfterStop.java 8307574 linux-x64 -javax/sound/sampled/Clip/ClipFlushCrash.java 8308395 linux-x64 - ############################################################################ # jdk_imageio From b08da4847a65331e47f2cee6cf933a2e38207c9b Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 3 Aug 2026 16:28:28 +0000 Subject: [PATCH 80/86] 8343232: PKCS#12 KeyStore support for RFC 9879: Use of Password-Based Message Authentication Code 1 (PBMAC1) Backport-of: 1781b186b51900b758dd55cc356eaaf12b28481b --- .../sun/crypto/provider/PBES2Parameters.java | 103 ++---- .../sun/crypto/provider/PBKDF2KeyImpl.java | 2 +- .../classes/sun/security/pkcs12/MacData.java | 343 ++++++++++++++---- .../sun/security/pkcs12/PBMAC1Parameters.java | 140 +++++++ .../sun/security/pkcs12/PKCS12KeyStore.java | 138 ++----- .../classes/sun/security/util/KeyUtil.java | 3 + .../classes/sun/security/util/KnownOIDs.java | 3 +- .../sun/security/util/PBKDF2Parameters.java | 212 +++++++++++ .../share/conf/security/java.security | 5 +- test/jdk/sun/security/pkcs12/PBMAC1Test.java | 223 ++++++++++++ .../security/pkcs12/ParamsPreferences.java | 8 +- 11 files changed, 901 insertions(+), 279 deletions(-) create mode 100644 src/java.base/share/classes/sun/security/pkcs12/PBMAC1Parameters.java create mode 100644 src/java.base/share/classes/sun/security/util/PBKDF2Parameters.java create mode 100644 test/jdk/sun/security/pkcs12/PBMAC1Test.java diff --git a/src/java.base/share/classes/com/sun/crypto/provider/PBES2Parameters.java b/src/java.base/share/classes/com/sun/crypto/provider/PBES2Parameters.java index 64b276a1c79a..9d33b6689d23 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/PBES2Parameters.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/PBES2Parameters.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 @@ -32,6 +32,7 @@ import java.security.spec.InvalidParameterSpecException; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.PBEParameterSpec; +import sun.security.util.PBKDF2Parameters; import sun.security.util.*; /** @@ -93,7 +94,7 @@ abstract class PBES2Parameters extends AlgorithmParametersSpi { private static final ObjectIdentifier pkcs5PBKDF2_OID = - ObjectIdentifier.of(KnownOIDs.PBKDF2WithHmacSHA1); + ObjectIdentifier.of(KnownOIDs.PBKDF2); private static final ObjectIdentifier pkcs5PBES2_OID = ObjectIdentifier.of(KnownOIDs.PBES2); private static final ObjectIdentifier aes128CBC_OID = @@ -224,77 +225,32 @@ protected void engineInit(byte[] encoded) // next DerValue as the real PBES2-params. if (kdf.getTag() == DerValue.tag_ObjectId) { pBES2_params = pBES2_params.data.getDerValue(); + if (pBES2_params.tag != DerValue.tag_Sequence) { + throw new IOException("PBE parameter parsing error: " + + "not an ASN.1 SEQUENCE tag"); + } kdf = pBES2_params.data.getDerValue(); } - String kdfAlgo = parseKDF(kdf); - - if (pBES2_params.tag != DerValue.tag_Sequence) { - throw new IOException("PBE parameter parsing error: " - + "not an ASN.1 SEQUENCE tag"); - } - String cipherAlgo = parseES(pBES2_params.data.getDerValue()); - - this.pbes2AlgorithmName = "PBEWith" + kdfAlgo + "And" + cipherAlgo; - } - - private String parseKDF(DerValue keyDerivationFunc) throws IOException { - - if (!pkcs5PBKDF2_OID.equals(keyDerivationFunc.data.getOID())) { + if (!pkcs5PBKDF2_OID.equals(kdf.data.getOID())) { throw new IOException("PBE parameter parsing error: " + "expecting the object identifier for PBKDF2"); } - if (keyDerivationFunc.tag != DerValue.tag_Sequence) { + if (kdf.tag != DerValue.tag_Sequence) { throw new IOException("PBE parameter parsing error: " + "not an ASN.1 SEQUENCE tag"); } - DerValue pBKDF2_params = keyDerivationFunc.data.getDerValue(); - if (pBKDF2_params.tag != DerValue.tag_Sequence) { - throw new IOException("PBE parameter parsing error: " - + "not an ASN.1 SEQUENCE tag"); - } - DerValue specified = pBKDF2_params.data.getDerValue(); - // the 'specified' ASN.1 CHOICE for 'salt' is supported - if (specified.tag == DerValue.tag_OctetString) { - salt = specified.getOctetString(); - } else { - // the 'otherSource' ASN.1 CHOICE for 'salt' is not supported - throw new IOException("PBE parameter parsing error: " - + "not an ASN.1 OCTET STRING tag"); - } - iCount = pBKDF2_params.data.getInteger(); + DerValue pBKDF2_params = kdf.data.getDerValue(); - // keyLength INTEGER (1..MAX) OPTIONAL, - var ksDer = pBKDF2_params.data.getOptional(DerValue.tag_Integer); - if (ksDer.isPresent()) { - keysize = ksDer.get().getInteger() * 8; // keysize (in bits) - } + var kdfParams = new PBKDF2Parameters(pBKDF2_params); + String kdfAlgo = kdfParams.getPrfAlgo(); + salt = kdfParams.getSalt(); + iCount = kdfParams.getIterationCount(); + keysize = kdfParams.getKeyLength(); - // prf AlgorithmIdentifier {{PBKDF2-PRFs}} DEFAULT algid-hmacWithSHA1 - String kdfAlgo; - var prfDer = pBKDF2_params.data.getOptional(DerValue.tag_Sequence); - if (prfDer.isPresent()) { - DerValue prf = prfDer.get(); - kdfAlgo_OID = prf.data.getOID(); - KnownOIDs o = KnownOIDs.findMatch(kdfAlgo_OID.toString()); - if (o == null || (!o.stdName().equals("HmacSHA1") && - !o.stdName().equals("HmacSHA224") && - !o.stdName().equals("HmacSHA256") && - !o.stdName().equals("HmacSHA384") && - !o.stdName().equals("HmacSHA512") && - !o.stdName().equals("HmacSHA512/224") && - !o.stdName().equals("HmacSHA512/256"))) { - throw new IOException("PBE parameter parsing error: " - + "expecting the object identifier for a HmacSHA key " - + "derivation function"); - } - kdfAlgo = o.stdName(); - prf.data.getOptional(DerValue.tag_Null); - prf.data.atEnd(); - } else { - kdfAlgo = "HmacSHA1"; - } - return kdfAlgo; + String cipherAlgo = parseES(pBES2_params.data.getDerValue()); + + this.pbes2AlgorithmName = "PBEWith" + kdfAlgo + "And" + cipherAlgo; } private String parseES(DerValue encryptionScheme) throws IOException { @@ -345,26 +301,9 @@ protected byte[] engineGetEncoded() throws IOException { DerOutputStream pBES2_params = new DerOutputStream(); - DerOutputStream keyDerivationFunc = new DerOutputStream(); - keyDerivationFunc.putOID(pkcs5PBKDF2_OID); - - DerOutputStream pBKDF2_params = new DerOutputStream(); - pBKDF2_params.putOctetString(salt); // choice: 'specified OCTET STRING' - pBKDF2_params.putInteger(iCount); - - if (keysize > 0) { - pBKDF2_params.putInteger(keysize / 8); // derived key length (in octets) - } - - DerOutputStream prf = new DerOutputStream(); - // algorithm is id-hmacWith - prf.putOID(kdfAlgo_OID); - // parameters is 'NULL' - prf.putNull(); - pBKDF2_params.write(DerValue.tag_Sequence, prf); - - keyDerivationFunc.write(DerValue.tag_Sequence, pBKDF2_params); - pBES2_params.write(DerValue.tag_Sequence, keyDerivationFunc); + // keysize encoded as octets + pBES2_params.writeBytes(PBKDF2Parameters.encode(salt, iCount, + keysize/8, kdfAlgo_OID)); DerOutputStream encryptionScheme = new DerOutputStream(); // algorithm is id-aes128-CBC or id-aes256-CBC diff --git a/src/java.base/share/classes/com/sun/crypto/provider/PBKDF2KeyImpl.java b/src/java.base/share/classes/com/sun/crypto/provider/PBKDF2KeyImpl.java index 6a0ecb6d462a..9f3e041eebc9 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/PBKDF2KeyImpl.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/PBKDF2KeyImpl.java @@ -55,7 +55,7 @@ * @author Valerie Peng * */ -final class PBKDF2KeyImpl implements javax.crypto.interfaces.PBEKey { +public final class PBKDF2KeyImpl implements javax.crypto.interfaces.PBEKey { @java.io.Serial private static final long serialVersionUID = -2234868909660948157L; diff --git a/src/java.base/share/classes/sun/security/pkcs12/MacData.java b/src/java.base/share/classes/sun/security/pkcs12/MacData.java index 9a712f28ccc5..d45b50ad7048 100644 --- a/src/java.base/share/classes/sun/security/pkcs12/MacData.java +++ b/src/java.base/share/classes/sun/security/pkcs12/MacData.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 @@ -25,32 +25,58 @@ package sun.security.pkcs12; -import java.io.*; +import java.io.IOException; import java.security.*; +import java.security.spec.InvalidKeySpecException; +import static java.util.Locale.ENGLISH; +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.PBEParameterSpec; -import sun.security.util.DerInputStream; -import sun.security.util.DerOutputStream; -import sun.security.util.DerValue; -import sun.security.x509.AlgorithmId; import sun.security.pkcs.ParsingException; +import sun.security.util.*; +import sun.security.x509.AlgorithmId; /** - * A MacData type, as defined in PKCS#12. + * The MacData type, as defined in PKCS#12. + * + * The ASN.1 definition is as follows: + * + *

+ *
+ * MacData ::= SEQUENCE {
+ *     mac        DigestInfo,
+ *     macSalt    OCTET STRING,
+ *     iterations INTEGER DEFAULT 1
+ *      -- Note: The default is for historical reasons and its use is
+ *      -- deprecated.
+ * }
+ *
+ * DigestInfo ::= SEQUENCE {
+ *     digestAlgorithm DigestAlgorithmIdentifier,
+ *     digest OCTET STRING
+ * }
+ *
+ * 
* * @author Sharon Liu */ class MacData { - private final String digestAlgorithmName; - private AlgorithmParameters digestAlgorithmParams; + private static final Debug debug = Debug.getInstance("pkcs12"); + private final String macAlgorithm; private final byte[] digest; private final byte[] macSalt; private final int iterations; - // the ASN.1 encoded contents of this class - private byte[] encoded = null; + // The following three fields are for PBMAC1. + private final int keyLength; + private final String kdfHmac; + private final String hmac; /** * Parses a PKCS#12 MAC data. @@ -70,103 +96,276 @@ class MacData { // Parse the DigestAlgorithmIdentifier. AlgorithmId digestAlgorithmId = AlgorithmId.parse(digestInfo[0]); - this.digestAlgorithmName = digestAlgorithmId.getName(); - this.digestAlgorithmParams = digestAlgorithmId.getParameters(); + String digestAlgorithmName = digestAlgorithmId.getName(); + // Get the digest. this.digest = digestInfo[1].getOctetString(); - // Get the salt. - this.macSalt = macData[1].getOctetString(); + if (digestAlgorithmName.equals("PBMAC1")) { + PBMAC1Parameters algParams; + + algParams = new PBMAC1Parameters(digestAlgorithmId + .getEncodedParams()); + + this.iterations = algParams.getKdfParams().getIterationCount(); + this.macSalt = algParams.getKdfParams().getSalt(); + this.kdfHmac = algParams.getKdfParams().getPrfAlgo(); + this.keyLength = algParams.getKdfParams().getKeyLength(); - // Iterations is optional. The default value is 1. - if (macData.length > 2) { - this.iterations = macData[2].getInteger(); + // Implementations MUST NOT accept params that omit keyLength. + if (this.keyLength == -1) { + throw new IOException("error: missing keyLength field"); + } + this.hmac = algParams.getHmac(); + this.macAlgorithm = "pbewith" + this.kdfHmac + "and" + this.hmac; } else { - this.iterations = 1; + this.kdfHmac = null; + this.hmac = null; + this.keyLength = -1; + this.macSalt = macData[1].getOctetString(); + if (macData.length > 2) { + this.iterations = macData[2].getInteger(); + } else { + this.iterations = 1; + } + // Remove "-" from digest algorithm names + this.macAlgorithm = "hmacpbe" + + digestAlgorithmName.replace("-", ""); } } - MacData(String algName, byte[] digest, byte[] salt, int iterations) - throws NoSuchAlgorithmException - { - if (algName == null) - throw new NullPointerException("the algName parameter " + - "must be non-null"); - - AlgorithmId algid = AlgorithmId.get(algName); - this.digestAlgorithmName = algid.getName(); - this.digestAlgorithmParams = algid.getParameters(); - - if (digest == null) { - throw new NullPointerException("the digest " + - "parameter must be non-null"); - } else if (digest.length == 0) { - throw new IllegalArgumentException("the digest " + - "parameter must not be empty"); + /** + * Computes a MAC on the data. + * + * This is a two-step process: first generate a key and then use the + * key to generate the MAC. PBMAC1 and non-PBMAC1 keys use different + * key factories. PBMAC1 uses a pseudorandom function (kdfHmac) + * to generate keys while non-PBMAC1 does not. The MAC is computed + * according to the specified hmac algorithm. + * + * @param macAlgorithm the algorithm used to compute the MAC + * @param password the password used to generate the key + * @param params a PBEParameterSpec object + * @param data the data on which the MAC is computed + * @param kdfHmac the pseudorandom function used to compute the key + * for PBMAC1 + * @param hmac the algorithm used to compute the MAC + * @param keyLength the length of the key generated by the pseudorandom + * function + * + * @return the computed MAC as a byte array + * + * @exception NoSuchAlgorithmException if either kdfHmac or hmac is + * unknown to the Mac or SecretKeyFactory + */ + private static byte[] calculateMac(String macAlgorithm, char[] password, + PBEParameterSpec params, byte[] data, + String kdfHmac, String hmac, int keyLength) + throws InvalidAlgorithmParameterException, InvalidKeyException, + InvalidKeySpecException, NoSuchAlgorithmException { + SecretKeyFactory skf; + SecretKey pbeKey = null; + Mac m; + + PBEKeySpec keySpec; + + /* + * The Hmac has to be extracted from the algorithm name for + * PBMAC1 algorithms. For non-PBMAC1 macAlgorithms, the name + * and Hmac are the same. + * + * The prefix used in Algorithm names is guaranteed to be lowercase. + */ + if (macAlgorithm.startsWith("pbewith")) { + m = Mac.getInstance(hmac); + int len = keyLength == -1 ? m.getMacLength()*8 : keyLength; + skf = SecretKeyFactory.getInstance("PBKDF2With" +kdfHmac); + keySpec = new PBEKeySpec(password, params.getSalt(), + params.getIterationCount(), len); } else { - this.digest = digest.clone(); + m = Mac.getInstance(macAlgorithm); + skf = SecretKeyFactory.getInstance("PBE"); + keySpec = new PBEKeySpec(password); } - this.macSalt = salt; - this.iterations = iterations; + try { + pbeKey = skf.generateSecret(keySpec); + if (macAlgorithm.startsWith("pbewith")) { + m.init(pbeKey); + } else { + m.init(pbeKey, params); + } + m.update(data); + return m.doFinal(); + } finally { + keySpec.clearPassword(); + KeyUtil.destroySecretKeys(pbeKey); + } + } - // delay the generation of ASN.1 encoding until - // getEncoded() is called - this.encoded = null; + /** + * Verify Mac on the data. + * + * Calculate Mac on the data and compare with Mac found in input stream. + * + * @param password the password used to generate the key + * @param data the data on which the MAC is computed + * + * @exception UnrecoverableKeyException if calculated Mac and + * Mac found in input stream are different + */ + void verifyMac(char[] password, byte[] data) + throws InvalidAlgorithmParameterException, InvalidKeyException, + InvalidKeySpecException, NoSuchAlgorithmException, + UnrecoverableKeyException { + + byte[] macResult = calculateMac(this.macAlgorithm, password, + new PBEParameterSpec(this.macSalt, this.iterations), + data, this.kdfHmac, this.hmac, this.keyLength); + + if (debug != null) { + debug.println("Checking keystore integrity " + + "(" + this.macAlgorithm + " iterations: " + + this.iterations + ")"); + } + if (!MessageDigest.isEqual(this.digest, macResult)) { + throw new UnrecoverableKeyException("Failed PKCS12" + + " integrity checking"); + } } - String getDigestAlgName() { - return digestAlgorithmName; - } + /* + * Gathers parameters and generates a MAC of the data + * + * @param password the password used to generate the key + * @param data the data on which the MAC is computed + * @param macAlgorithm the algorithm used to compute the MAC + * @param macIterationCount the iteration count + * @param salt the salt + * + * @exception IOException if the MAC cannot be calculated + * + * @return the computed MAC as a byte array + */ + static byte[] generateMac(char[] passwd, byte[] data, + String macAlgorithm, int macIterationCount, byte[] salt) + throws IOException, NoSuchAlgorithmException { + final PBEParameterSpec params; + String algName; + String kdfHmac; + String hmac; + + macAlgorithm = macAlgorithm.toLowerCase(ENGLISH); + // The prefix used in Algorithm names is guaranteed to be lowercase. + if (macAlgorithm.startsWith("pbewith")) { + algName = "PBMAC1"; + kdfHmac = MacData.parseKdfHmac(macAlgorithm); + hmac = MacData.parseHmac(macAlgorithm); + if (hmac == null) { + hmac = kdfHmac; + } + } else if (macAlgorithm.startsWith("hmacpbe")) { + algName = macAlgorithm.substring(7); + kdfHmac = null; + hmac = macAlgorithm; + } else { + throw new ParsingException("unexpected algorithm '" + + macAlgorithm + "'"); + } + + params = new PBEParameterSpec(salt, macIterationCount); + + try { + byte[] macResult = calculateMac(macAlgorithm, passwd, params, data, + kdfHmac, hmac, -1); - byte[] getSalt() { - return macSalt; + DerOutputStream bytes = new DerOutputStream(); + bytes.write(encode(algName, macResult, params, kdfHmac, hmac, + macResult.length)); + return bytes.toByteArray(); + } catch (InvalidKeySpecException | InvalidKeyException | + InvalidAlgorithmParameterException e) { + throw new IOException("calculateMac failed: " + e, e); + } } - int getIterations() { - return iterations; + String getMacAlgorithm() { + return this.macAlgorithm; } - byte[] getDigest() { - return digest; + int getIterations() { + return this.iterations; } /** - * Returns the ASN.1 encoding of this object. - * @return the ASN.1 encoding. - * @exception IOException if error occurs when constructing its + * Returns the ASN.1 encoding. + * @return the ASN.1 encoding + * @exception NoSuchAlgorithmException if error occurs when constructing its * ASN.1 encoding. */ - public byte[] getEncoded() throws NoSuchAlgorithmException - { - if (this.encoded != null) - return this.encoded.clone(); + static byte[] encode(String algName, byte[] digest, PBEParameterSpec p, + String kdfHmac, String hmac, int keyLength) + throws IOException, NoSuchAlgorithmException { + + final int iterations = p.getIterationCount(); + final byte[] macSalt = p.getSalt(); - DerOutputStream out = new DerOutputStream(); DerOutputStream tmp = new DerOutputStream(); + DerOutputStream out = new DerOutputStream(); - DerOutputStream tmp2 = new DerOutputStream(); - // encode encryption algorithm - AlgorithmId algid = AlgorithmId.get(digestAlgorithmName); - algid.encode(tmp2); + if (algName.equals("PBMAC1")) { + DerOutputStream tmp1 = new DerOutputStream(); + DerOutputStream tmp2 = new DerOutputStream(); - // encode digest data - tmp2.putOctetString(digest); + // id-PBMAC1 OBJECT IDENTIFIER ::= { pkcs-5 14 } + tmp2.putOID(ObjectIdentifier.of(KnownOIDs.PBMAC1)); + tmp2.writeBytes(PBMAC1Parameters.encode(macSalt, iterations, + keyLength, kdfHmac, hmac)); - tmp.write(DerValue.tag_Sequence, tmp2); + tmp1.write(DerValue.tag_Sequence, tmp2); + tmp1.putOctetString(digest); - // encode salt - tmp.putOctetString(macSalt); + tmp.write(DerValue.tag_Sequence, tmp1); + tmp.putOctetString( + new byte[]{ 'N', 'O', 'T', ' ', 'U', 'S', 'E', 'D' }); + // Unused, but must have non-zero positive value. + tmp.putInteger(1); + } else { + final AlgorithmId digestAlgorithm = AlgorithmId.get(algName); + DerOutputStream tmp2 = new DerOutputStream(); - // encode iterations - tmp.putInteger(iterations); + tmp2.write(digestAlgorithm); + tmp2.putOctetString(digest); + // wrap into a SEQUENCE + tmp.write(DerValue.tag_Sequence, tmp2); + tmp.putOctetString(macSalt); + tmp.putInteger(iterations); + } // wrap everything into a SEQUENCE out.write(DerValue.tag_Sequence, tmp); - this.encoded = out.toByteArray(); + return out.toByteArray(); + } - return this.encoded.clone(); + private static String parseKdfHmac(String text) { + int index1 = text.indexOf("with") + 4; + int index2 = text.indexOf("and"); + if (index1 == 3) { // -1 + 4 + return null; + } else if (index2 == -1) { + return text.substring(index1); + } else { + return text.substring(index1, index2); + } } + private static String parseHmac(String text) { + int index1 = text.indexOf("and") + 3; + if (index1 == 2) { // -1 + 3 + return null; + } else { + return text.substring(index1); + } + } } diff --git a/src/java.base/share/classes/sun/security/pkcs12/PBMAC1Parameters.java b/src/java.base/share/classes/sun/security/pkcs12/PBMAC1Parameters.java new file mode 100644 index 000000000000..2c3c6fa81717 --- /dev/null +++ b/src/java.base/share/classes/sun/security/pkcs12/PBMAC1Parameters.java @@ -0,0 +1,140 @@ +/* + * 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.pkcs12; + +import java.io.IOException; +import java.security.NoSuchAlgorithmException; + +import sun.security.util.*; +import sun.security.x509.AlgorithmId; + +/** + * This class implements the parameter set used with password-based + * mac scheme 1 (PBMAC1), which is defined in PKCS#5 as follows: + * + *
+ * -- PBMAC1
+ *
+ * PBMAC1Algorithms ALGORITHM-IDENTIFIER ::=
+ *   { {PBMAC1-params IDENTIFIED BY id-PBMAC1}, ...}
+ *
+ * id-PBMAC1 OBJECT IDENTIFIER ::= {pkcs-5 14}
+ *
+ * PBMAC1-params ::= SEQUENCE {
+ *   keyDerivationFunc AlgorithmIdentifier {{PBMAC1-KDFs}},
+ *   messageAuthScheme AlgorithmIdentifier {{PBMAC1-MACs}} }
+ *
+ * PBMAC1-KDFs ALGORITHM-IDENTIFIER ::=
+ *   { {PBKDF2-params IDENTIFIED BY id-PBKDF2}, ... }
+ *
+ * PBMAC1-MACs ALGORITHM-IDENTIFIER ::= { ... }
+ *
+ * -- PBKDF2
+ *
+ * See sun.security.util.PBKDF2Parameters.
+ *
+ * 
+ * + * @since 26 + */ +final class PBMAC1Parameters { + + static final ObjectIdentifier pkcs5PBKDF2_OID = + ObjectIdentifier.of(KnownOIDs.PBKDF2); + + private final String hmacAlgo; + private final PBKDF2Parameters kdfParams; + + PBMAC1Parameters(byte[] encoded) throws IOException { + DerValue pBMAC1_params = new DerValue(encoded); + if (pBMAC1_params.tag != DerValue.tag_Sequence) { + throw new IOException("PBMAC1 parameter parsing error: " + + "not an ASN.1 SEQUENCE tag"); + } + DerValue[] info = new DerInputStream(pBMAC1_params.toByteArray()) + .getSequence(2); + if (info.length != 2) { + throw new IOException("PBMAC1 parameter parsing error: " + + "expected length not 2"); + } + ObjectIdentifier OID = info[1].data.getOID(); + KnownOIDs o = KnownOIDs.findMatch(OID.toString()); + if (o == null || (!o.stdName().equals("HmacSHA1") && + !o.stdName().equals("HmacSHA224") && + !o.stdName().equals("HmacSHA256") && + !o.stdName().equals("HmacSHA384") && + !o.stdName().equals("HmacSHA512") && + !o.stdName().equals("HmacSHA512/224") && + !o.stdName().equals("HmacSHA512/256"))) { + throw new IOException("PBMAC1 parameter parsing error: " + + "expecting the object identifier for a HmacSHA key " + + "derivation function"); + } + // Hmac function used to compute the MAC + this.hmacAlgo = o.stdName(); + + //DerValue kdf = pBMAC1_params.data.getDerValue(); + DerValue kdf = info[0]; + + if (!pkcs5PBKDF2_OID.equals(kdf.data.getOID())) { + throw new IOException("PBKDF2 parameter parsing error: " + + "expecting the object identifier for PBKDF2"); + } + if (kdf.tag != DerValue.tag_Sequence) { + throw new IOException("PBKDF2 parameter parsing error: " + + "not an ASN.1 SEQUENCE tag"); + } + DerValue pBKDF2_params = kdf.data.getDerValue(); + + this.kdfParams = new PBKDF2Parameters(pBKDF2_params); + } + + /* + * Encode PBMAC1 parameters from components. + */ + static byte[] encode(byte[] salt, int iterationCount, int keyLength, + String kdfHmac, String hmac) throws NoSuchAlgorithmException { + + DerOutputStream out = new DerOutputStream(); + + // keyDerivationFunc AlgorithmIdentifier {{PBMAC1-KDFs}} + out.writeBytes(PBKDF2Parameters.encode(salt, + iterationCount, keyLength, kdfHmac)); + + // messageAuthScheme AlgorithmIdentifier {{PBMAC1-MACs}} + out.write(AlgorithmId.get(hmac)); + return new DerOutputStream().write(DerValue.tag_Sequence, out) + .toByteArray(); + } + + PBKDF2Parameters getKdfParams() { + return this.kdfParams; + } + + String getHmac() { + return this.hmacAlgo; + } +} diff --git a/src/java.base/share/classes/sun/security/pkcs12/PKCS12KeyStore.java b/src/java.base/share/classes/sun/security/pkcs12/PKCS12KeyStore.java index bee898567567..f85ba0f9c4b9 100644 --- a/src/java.base/share/classes/sun/security/pkcs12/PKCS12KeyStore.java +++ b/src/java.base/share/classes/sun/security/pkcs12/PKCS12KeyStore.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2024, 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,52 +26,36 @@ package sun.security.pkcs12; import java.io.*; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.Key; -import java.security.KeyFactory; -import java.security.KeyStore; -import java.security.KeyStoreSpi; -import java.security.KeyStoreException; -import java.security.PKCS12Attribute; -import java.security.PrivateKey; -import java.security.UnrecoverableEntryException; -import java.security.UnrecoverableKeyException; -import java.security.SecureRandom; -import java.security.Security; +import java.security.*; import java.security.cert.Certificate; +import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; -import java.security.cert.CertificateException; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.InvalidParameterSpecException; import java.security.spec.KeySpec; import java.security.spec.PKCS8EncodedKeySpec; import java.util.*; - -import static java.nio.charset.StandardCharsets.UTF_8; - -import java.security.AlgorithmParameters; -import java.security.InvalidAlgorithmParameterException; -import javax.crypto.spec.PBEParameterSpec; +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.PBEParameterSpec; import javax.crypto.spec.SecretKeySpec; -import javax.crypto.SecretKeyFactory; -import javax.crypto.SecretKey; -import javax.crypto.Cipher; -import javax.crypto.Mac; import javax.security.auth.DestroyFailedException; import javax.security.auth.x500.X500Principal; import jdk.internal.access.SharedSecrets; -import sun.security.tools.KeyStoreUtil; -import sun.security.util.*; import sun.security.pkcs.ContentInfo; -import sun.security.x509.AlgorithmId; import sun.security.pkcs.EncryptedPrivateKeyInfo; import sun.security.provider.JavaKeyStore.JKS; +import sun.security.tools.KeyStoreUtil; +import sun.security.util.*; +import sun.security.x509.AlgorithmId; import sun.security.x509.AuthorityKeyIdentifierExtension; +import static java.nio.charset.StandardCharsets.UTF_8; + /** * This class provides the keystore implementation referred to as "PKCS12". @@ -366,7 +350,7 @@ private Key internalGetKey(Entry entry, char[] password) try { cipher.init(Cipher.DECRYPT_MODE, skey, algParams); } finally { - destroyPBEKey(skey); + KeyUtil.destroySecretKeys(skey); } byte[] keyInfo = cipher.doFinal(encryptedKey); /* @@ -855,17 +839,6 @@ private SecretKey getPBEKey(char[] password) throws IOException return skey; } - /* - * Destroy the key obtained from getPBEKey(). - */ - private void destroyPBEKey(SecretKey key) { - try { - key.destroy(); - } catch (DestroyFailedException e) { - // Accept this - } - } - /* * Encrypt private key or secret key using Password-based encryption (PBE) * as defined in PKCS#5. @@ -915,7 +888,7 @@ private byte[] encryptPrivateKey(byte[] data, try { cipher.init(Cipher.ENCRYPT_MODE, skey, algParams); } finally { - destroyPBEKey(skey); + KeyUtil.destroySecretKeys(skey); } byte[] encryptedKey = cipher.doFinal(data); algid = new AlgorithmId(pbeOID, cipher.getParameters()); @@ -1265,7 +1238,8 @@ public synchronized void engineStore(OutputStream stream, char[] password) macIterationCount = defaultMacIterationCount(); } if (password != null && !macAlgorithm.equalsIgnoreCase("NONE")) { - byte[] macData = calculateMac(password, authenticatedSafe); + byte[] macData = MacData.generateMac(password, authenticatedSafe, + macAlgorithm, macIterationCount, getSalt()); pfx.write(macData); } // write PFX to output stream @@ -1480,48 +1454,6 @@ private void populateAttributes(Entry entry) { } } - /* - * Calculate MAC using HMAC algorithm (required for password integrity) - * - * Hash-based MAC algorithm combines secret key with message digest to - * create a message authentication code (MAC) - */ - private byte[] calculateMac(char[] passwd, byte[] data) - throws IOException - { - byte[] mData; - String algName = macAlgorithm.substring(7); - - try { - // Generate a random salt. - byte[] salt = getSalt(); - - // generate MAC (MAC key is generated within JCE) - Mac m = Mac.getInstance(macAlgorithm); - PBEParameterSpec params = - new PBEParameterSpec(salt, macIterationCount); - SecretKey key = getPBEKey(passwd); - try { - m.init(key, params); - } finally { - destroyPBEKey(key); - } - m.update(data); - byte[] macResult = m.doFinal(); - - // encode as MacData - MacData macData = new MacData(algName, macResult, salt, - macIterationCount); - DerOutputStream bytes = new DerOutputStream(); - bytes.write(macData.getEncoded()); - mData = bytes.toByteArray(); - } catch (Exception e) { - throw new IOException("calculateMac failed: " + e, e); - } - return mData; - } - - /* * Validate Certificate Chain */ @@ -1890,7 +1822,7 @@ private byte[] encryptContent(byte[] data, char[] password) try { cipher.init(Cipher.ENCRYPT_MODE, skey, algParams); } finally { - destroyPBEKey(skey); + KeyUtil.destroySecretKeys(skey); } encryptedData = cipher.doFinal(data); @@ -2100,7 +2032,7 @@ public synchronized void engineLoad(InputStream stream, char[] password) try { cipher.init(Cipher.DECRYPT_MODE, skey, algParams); } finally { - destroyPBEKey(skey); + KeyUtil.destroySecretKeys(skey); } loadSafeContents(new DerInputStream(cipher.doFinal(rawData))); return null; @@ -2135,39 +2067,11 @@ public synchronized void engineLoad(InputStream stream, char[] password) "MAC iteration count too large: " + ic); } - String algName = - macData.getDigestAlgName().toUpperCase(Locale.ENGLISH); - - // Change SHA-1 to SHA1 - algName = algName.replace("-", ""); - - macAlgorithm = "HmacPBE" + algName; + // Store MAC algorithm of keystore that was just loaded. + macAlgorithm = macData.getMacAlgorithm(); macIterationCount = ic; - - // generate MAC (MAC key is created within JCE) - Mac m = Mac.getInstance(macAlgorithm); - PBEParameterSpec params = - new PBEParameterSpec(macData.getSalt(), ic); - RetryWithZero.run(pass -> { - SecretKey key = getPBEKey(pass); - try { - m.init(key, params); - } finally { - destroyPBEKey(key); - } - m.update(authSafeData); - byte[] macResult = m.doFinal(); - - if (debug != null) { - debug.println("Checking keystore integrity " + - "(" + m.getAlgorithm() + " iterations: " + ic + ")"); - } - - if (!MessageDigest.isEqual(macData.getDigest(), macResult)) { - throw new UnrecoverableKeyException("Failed PKCS12" + - " integrity checking"); - } + macData.verifyMac(pass, authSafeData); return (Void) null; }, password); } catch (Exception 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 7a58ac0d4e9a..dd27b5f02d80 100644 --- a/src/java.base/share/classes/sun/security/util/KeyUtil.java +++ b/src/java.base/share/classes/sun/security/util/KeyUtil.java @@ -40,6 +40,7 @@ import javax.security.auth.DestroyFailedException; import jdk.internal.access.SharedSecrets; +import com.sun.crypto.provider.PBKDF2KeyImpl; import sun.security.jca.JCAUtil; import sun.security.x509.AlgorithmId; @@ -469,6 +470,8 @@ public static void destroySecretKeys(SecretKey... keys) { if (k instanceof SecretKeySpec sk) { SharedSecrets.getJavaxCryptoSpecAccess() .clearSecretKeySpec(sk); + } else if (k instanceof PBKDF2KeyImpl p2k) { + p2k.clear(); } else { try { k.destroy(); diff --git a/src/java.base/share/classes/sun/security/util/KnownOIDs.java b/src/java.base/share/classes/sun/security/util/KnownOIDs.java index cbb0c1e0b578..6c90801f69b3 100644 --- a/src/java.base/share/classes/sun/security/util/KnownOIDs.java +++ b/src/java.base/share/classes/sun/security/util/KnownOIDs.java @@ -208,8 +208,9 @@ public enum KnownOIDs { PBEWithMD5AndRC2("1.2.840.113549.1.5.6"), PBEWithSHA1AndDES("1.2.840.113549.1.5.10"), PBEWithSHA1AndRC2("1.2.840.113549.1.5.11"), - PBKDF2WithHmacSHA1("1.2.840.113549.1.5.12"), + PBKDF2("1.2.840.113549.1.5.12", "PBKDF2WithHmacSHA1"), PBES2("1.2.840.113549.1.5.13"), + PBMAC1("1.2.840.113549.1.5.14"), // PKCS7 1.2.840.113549.1.7.* PKCS7("1.2.840.113549.1.7"), diff --git a/src/java.base/share/classes/sun/security/util/PBKDF2Parameters.java b/src/java.base/share/classes/sun/security/util/PBKDF2Parameters.java new file mode 100644 index 000000000000..07d4c70fecb0 --- /dev/null +++ b/src/java.base/share/classes/sun/security/util/PBKDF2Parameters.java @@ -0,0 +1,212 @@ +/* + * 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 java.io.IOException; + +import sun.security.util.KnownOIDs; +import sun.security.x509.AlgorithmId; + +/** + * This class implements the parameter set used with password-based + * key derivation function 2 (PBKDF2), which is defined in PKCS#5 as follows: + * + *
+ *
+ * PBKDF2Algorithms ALGORITHM-IDENTIFIER ::=
+ *   { {PBKDF2-params IDENTIFIED BY id-PBKDF2}, ...}
+ *
+ * id-PBKDF2 OBJECT IDENTIFIER ::= {pkcs-5 12}
+ *
+ * PBKDF2-params ::= SEQUENCE {
+ *     salt CHOICE {
+ *       specified OCTET STRING,
+ *       otherSource AlgorithmIdentifier {{PBKDF2-SaltSources}}
+ *     },
+ *     iterationCount INTEGER (1..MAX),
+ *     keyLength INTEGER (1..MAX) OPTIONAL,
+ *     prf AlgorithmIdentifier {{PBKDF2-PRFs}} DEFAULT algid-hmacWithSHA1
+ * }
+ *
+ * PBKDF2-SaltSources ALGORITHM-IDENTIFIER ::= { ... }
+ *
+ * PBKDF2-PRFs ALGORITHM-IDENTIFIER ::= {
+ *     {NULL IDENTIFIED BY id-hmacWithSHA1} |
+ *     {NULL IDENTIFIED BY id-hmacWithSHA224} |
+ *     {NULL IDENTIFIED BY id-hmacWithSHA256} |
+ *     {NULL IDENTIFIED BY id-hmacWithSHA384} |
+ *     {NULL IDENTIFIED BY id-hmacWithSHA512}, ... }
+ *
+ * algid-hmacWithSHA1 AlgorithmIdentifier {{PBKDF2-PRFs}} ::=
+ *     {algorithm id-hmacWithSHA1, parameters NULL : NULL}
+ *
+ * id-hmacWithSHA1 OBJECT IDENTIFIER ::= {digestAlgorithm 7}
+ *
+ * For more information, see
+ * RFC 8018:
+ * PKCS #5: Password-Based Cryptography Specification.
+ *
+ * 
+ */ +public final class PBKDF2Parameters { + + private final byte[] salt; + + private final int iterationCount; + + // keyLength in bits, or -1 if not present + private final int keyLength; + + private final String prfAlgo; + + /** + * Initialize PBKDF2Parameters from a DER encoded + * parameter block. + * + * @param pBKDF2_params the DER encoding of the parameter block + * + * @throws IOException for parsing errors in the input stream + */ + public PBKDF2Parameters(DerValue pBKDF2_params) throws IOException { + + if (pBKDF2_params.tag != DerValue.tag_Sequence) { + throw new IOException("PBKDF2 parameter parsing error: " + + "not an ASN.1 SEQUENCE tag"); + } + DerValue specified = pBKDF2_params.data.getDerValue(); + // the 'specified' ASN.1 CHOICE for 'salt' is supported + if (specified.tag == DerValue.tag_OctetString) { + salt = specified.getOctetString(); + } else { + // the 'otherSource' ASN.1 CHOICE for 'salt' is not supported + throw new IOException("PBKDF2 parameter parsing error: " + + "not an ASN.1 OCTET STRING tag"); + } + iterationCount = pBKDF2_params.data.getInteger(); + + // keyLength INTEGER (1..MAX) OPTIONAL, + var ksDer = pBKDF2_params.data.getOptional(DerValue.tag_Integer); + if (ksDer.isPresent()) { + keyLength = ksDer.get().getInteger() * 8; // keyLength (in bits) + } else { + keyLength = -1; + } + + // prf AlgorithmIdentifier {{PBKDF2-PRFs}} DEFAULT algid-hmacWithSHA1 + var prfDer = pBKDF2_params.data.getOptional(DerValue.tag_Sequence); + if (prfDer.isPresent()) { + DerValue prf = prfDer.get(); + // the pseudorandom function (default is HmacSHA1) + ObjectIdentifier kdfAlgo_OID = prf.data.getOID(); + KnownOIDs o = KnownOIDs.findMatch(kdfAlgo_OID.toString()); + if (o == null || (!o.stdName().equals("HmacSHA1") && + !o.stdName().equals("HmacSHA224") && + !o.stdName().equals("HmacSHA256") && + !o.stdName().equals("HmacSHA384") && + !o.stdName().equals("HmacSHA512") && + !o.stdName().equals("HmacSHA512/224") && + !o.stdName().equals("HmacSHA512/256"))) { + throw new IOException("PBKDF2 parameter parsing error: " + + "expecting the object identifier for a HmacSHA " + + "pseudorandom function"); + } + prfAlgo = o.stdName(); + prf.data.getOptional(DerValue.tag_Null); + prf.data.atEnd(); + } else { + prfAlgo = "HmacSHA1"; + } + } + + public static byte[] encode(byte[] salt, int iterationCount, + int keyLength, String kdfHmac) { + ObjectIdentifier prf = + ObjectIdentifier.of(KnownOIDs.findMatch(kdfHmac)); + return PBKDF2Parameters.encode(salt, iterationCount, keyLength, prf); + } + + /* + * Encode PBKDF2 parameters from components. + * The outer algorithm ID is also encoded in addition to the parameters. + */ + public static byte[] encode(byte[] salt, int iterationCount, + int keyLength, ObjectIdentifier prf) { + assert keyLength != -1; + + DerOutputStream out = new DerOutputStream(); + DerOutputStream tmp0 = new DerOutputStream(); + + tmp0.putOctetString(salt); + tmp0.putInteger(iterationCount); + tmp0.putInteger(keyLength); + + // prf AlgorithmIdentifier {{PBKDF2-PRFs}} + tmp0.write(new AlgorithmId(prf)); + + // id-PBKDF2 OBJECT IDENTIFIER ::= {pkcs-5 12} + out.putOID(ObjectIdentifier.of(KnownOIDs.PBKDF2)); + out.write(DerValue.tag_Sequence, tmp0); + + return new DerOutputStream().write(DerValue.tag_Sequence, out) + .toByteArray(); + } + + /** + * Returns the salt. + * + * @return the salt + */ + public byte[] getSalt() { + return this.salt; + } + + /** + * Returns the iteration count. + * + * @return the iteration count + */ + public int getIterationCount() { + return this.iterationCount; + } + + /** + * Returns size of key generated by PBKDF2, or -1 if not found/set. + * + * @return size of key generated by PBKDF2, or -1 if not found/set + */ + public int getKeyLength() { + return this.keyLength; + } + + /** + * Returns name of Hmac. + * + * @return name of Hmac + */ + public String getPrfAlgo() { + return this.prfAlgo; + } +} diff --git a/src/java.base/share/conf/security/java.security b/src/java.base/share/conf/security/java.security index 497c01d0d529..b1d668abc1d8 100644 --- a/src/java.base/share/conf/security/java.security +++ b/src/java.base/share/conf/security/java.security @@ -1313,8 +1313,9 @@ jceks.key.serialFilter = java.base/java.lang.Enum;java.base/java.security.KeyRep #keystore.pkcs12.keyPbeIterationCount = 10000 # The algorithm used to calculate the optional MacData at the end of a PKCS12 -# file. This can be any HmacPBE algorithm defined in the Mac section of the -# Java Security Standard Algorithm Names Specification. When set to "NONE", +# file. This can be any HmacPBE or PBEWith algorithm defined in +# the Mac section of the Java Security Standard Algorithm Names Specification, +# for example, HmacPBESHA256 or PBEWithHmacSHA256. When set to "NONE", # no Mac is generated. The default value is "HmacPBESHA256". #keystore.pkcs12.macAlgorithm = HmacPBESHA256 diff --git a/test/jdk/sun/security/pkcs12/PBMAC1Test.java b/test/jdk/sun/security/pkcs12/PBMAC1Test.java new file mode 100644 index 000000000000..acb0f73a2d24 --- /dev/null +++ b/test/jdk/sun/security/pkcs12/PBMAC1Test.java @@ -0,0 +1,223 @@ +/* + * 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 8343232 + * @summary Verify correctness of the structure of PKCS12 PBMAC1 + * keystores created with various property values. + * Verify that keystores load correctly from an input stream. + * @modules java.base/sun.security.util + * @library /test/lib + */ +import jdk.test.lib.Asserts; +import jdk.test.lib.security.DerUtils; +import sun.security.util.KnownOIDs; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.security.KeyStore; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; + +public class PBMAC1Test { + + static final char[] PASSWORD = "1234".toCharArray(); + + public static void main(String[] args) throws Exception { + create(); + migrate(); + overflow(); + } + + // PBMAC1 inside PKCS12 + //0019:008B [2] SEQUENCE + //001C:007B [20] SEQUENCE + //001E:0057 [200] SEQUENCE + //0020:000B [2000] OID 1.2.840.113549.1.5.14 (PBMAC1) + //002B:004A [2001] SEQUENCE + //002D:003A [20010] SEQUENCE + //002F:000B [200100] OID 1.2.840.113549.1.5.12 (PBKDF2) + //003A:002D [200101] SEQUENCE + //003C:0016 [2001010] OCTET STRING (20 bytes of salt) + //0052:0004 [2001011] INTEGER 10000 + //0056:0003 [2001012] INTEGER 32 + //0059:000E [2001013] SEQUENCE + //005B:000A [20010130] OID 1.2.840.113549.2.9 (HmacSHA256) + //0065:0002 [20010131] NULL + //0067:000E [20011] SEQUENCE + //0069:000A [200110] OID 1.2.840.113549.2.9 (HmacSHA256) + //0073:0002 [200111] NULL + //0075:0022 [201] OCTET STRING (32 bytes of mac) + //0097:000A [21] OCTET STRING (8 bytes of useless salt) + //00A1:0003 [22] INTEGER 1 + static void create() throws Exception { + System.setProperty("keystore.pkcs12.macAlgorithm", "pbewithhmacsha256"); + var der = emptyP12(); + DerUtils.checkAlg(der, "2000", KnownOIDs.PBMAC1); + DerUtils.checkAlg(der, "200100", KnownOIDs.PBKDF2); + DerUtils.checkAlg(der, "20010130", KnownOIDs.HmacSHA256); + DerUtils.checkAlg(der, "200110", KnownOIDs.HmacSHA256); + DerUtils.checkInt(der, "2001011", 10000); + DerUtils.checkInt(der, "2001012", 32); + + System.setProperty("keystore.pkcs12.macAlgorithm", "PBEWITHHMACSHA512"); + der = emptyP12(); + DerUtils.checkAlg(der, "2000", KnownOIDs.PBMAC1); + DerUtils.checkAlg(der, "200100", KnownOIDs.PBKDF2); + DerUtils.checkAlg(der, "20010130", KnownOIDs.HmacSHA512); + DerUtils.checkAlg(der, "200110", KnownOIDs.HmacSHA512); + DerUtils.checkInt(der, "2001011", 10000); + DerUtils.checkInt(der, "2001012", 64); + + System.setProperty("keystore.pkcs12.macAlgorithm", "PBEWiThHmAcSHA512/224"); + der = emptyP12(); + DerUtils.checkAlg(der, "2000", KnownOIDs.PBMAC1); + DerUtils.checkAlg(der, "200100", KnownOIDs.PBKDF2); + DerUtils.checkAlg(der, "20010130", KnownOIDs.HmacSHA512$224); + DerUtils.checkAlg(der, "200110", KnownOIDs.HmacSHA512$224); + DerUtils.checkInt(der, "2001011", 10000); + DerUtils.checkInt(der, "2001012", 28); + + // As strange as I can... + System.setProperty("keystore.pkcs12.macAlgorithm", + "PBEWithHmacSHA512/224AndHmacSHA3-384"); + der = emptyP12(); + DerUtils.checkAlg(der, "2000", KnownOIDs.PBMAC1); + DerUtils.checkAlg(der, "200100", KnownOIDs.PBKDF2); + DerUtils.checkAlg(der, "20010130", KnownOIDs.HmacSHA512$224); + DerUtils.checkAlg(der, "200110", KnownOIDs.HmacSHA3_384); + DerUtils.checkInt(der, "2001011", 10000); + DerUtils.checkInt(der, "2001012", 48); + + // Bad alg names + System.setProperty("keystore.pkcs12.macAlgorithm", "PBEWithHmacSHA456"); + var reason = Asserts.assertThrows(NoSuchAlgorithmException.class, + () -> emptyP12()).getMessage(); + Asserts.assertTrue(reason.contains("Algorithm hmacsha456 not available"), reason); + } + + static void migrate() throws Exception { + // A pkcs12 file using PBEWithHmacSHA256 but key length is 8 + var sha2p12 = """ + MIGhAgEDMBEGCSqGSIb3DQEHAaAEBAIwADCBiDB5MFUGCSqGSIb3DQEFDjBIMDgGCSqGSIb3DQEF + DDArBBSV6e5xI+9AYtGHQlDI0X4pmvWLBQICJxACAQgwDAYIKoZIhvcNAgkFADAMBggqhkiG9w0C + CQUABCAaaSO6JgEh1lDo1pvAC0CF5HqgIFBvzt1+GZlgFy7xFQQITk9UIFVTRUQCAQE= + """; + var der = Base64.getMimeDecoder().decode(sha2p12); + DerUtils.checkInt(der, "2001012", 8); // key length used to be 8 + + der = loadAndStore(sha2p12); + DerUtils.checkAlg(der, "20010130", KnownOIDs.HmacSHA256); + DerUtils.checkAlg(der, "200110", KnownOIDs.HmacSHA256); + DerUtils.checkInt(der, "2001012", 32); // key length changed to 32 + } + + static void overflow() throws Exception { + + // Cannot create new + System.setProperty("keystore.pkcs12.macIterationCount", "5000001"); + System.setProperty("keystore.pkcs12.macAlgorithm", "pbewithhmacsha256"); + Asserts.assertThrows(IllegalArgumentException.class, PBMAC1Test::emptyP12); + System.clearProperty("keystore.pkcs12.macAlgorithm"); + Asserts.assertThrows(IllegalArgumentException.class, PBMAC1Test::emptyP12); + + // IC=5000001 using old algorithm + var bigICt = """ + MGYCAQMwEQYJKoZIhvcNAQcBoAQEAjAAME4wMTANBglghkgBZQMEAgEFAAQgyLBK5h9/E/2o7l2A + eALbI1otiS8kT3C41Ef3T38OMjUEFIic7isrAJNr+3+8fUbnMtmB0qytAgNMS0E= + """; + + // IC=5000000 using old algorithm + var smallICt = """ + MGYCAQMwEQYJKoZIhvcNAQcBoAQEAjAAME4wMTANBglghkgBZQMEAgEFAAQgR61YZLW6H81rkGTk + XfuU138mkIugdoQBhuNsnvWuBtQEFJ0wmMlpoUiji8PlvwCrmMbqWW4XAgNMS0A= + """; + + // IC=5000001 using PBMAC1 + var bigICp = """ + MIGiAgEDMBEGCSqGSIb3DQEHAaAEBAIwADCBiTB6MFYGCSqGSIb3DQEFDjBJMDkGCSqGSIb3DQEF + DDAsBBQFNf/gHCO5jNT429D6Q5gxTKHqVAIDTEtBAgEgMAwGCCqGSIb3DQIJBQAwDAYIKoZIhvcN + AgkFAAQgwEVMcyMPQXJSXUIbWqNWjMArtnXDlNUGnKD+19B7QFkECE5PVCBVU0VEAgEB + """; + + // IC=5000000 using PBMAC1 + var smallICp = """ + MIGiAgEDMBEGCSqGSIb3DQEHAaAEBAIwADCBiTB6MFYGCSqGSIb3DQEFDjBJMDkGCSqGSIb3DQEF + DDAsBBS/ZFfC7swsDHvaCXwyQkuMrZ7dbgIDTEtAAgEgMAwGCCqGSIb3DQIJBQAwDAYIKoZIhvcN + AgkFAAQgCRvE7LDbzkcYOVv/7iBv0KB3DoUkwnpTI0nsonVfv9UECE5PVCBVU0VEAgEB"""; + + loadAndStore(smallICp); + loadAndStore(smallICt); + + Asserts.assertTrue(Asserts.assertThrows(IOException.class, () -> loadAndStore(bigICp)) + .getMessage().contains("MAC iteration count too large: 5000001")); + Asserts.assertTrue(Asserts.assertThrows(IOException.class, () -> loadAndStore(bigICt)) + .getMessage().contains("MAC iteration count too large: 5000001")); + + // Incorrect Salt + var incorrectSalt = """ + MIGdAgEDMBEGCSqGSIb3DQEHAaAEBAIwADCBhDB1MFEGCSqGSIb3DQEFDjBEMDYGCSqGSIb3DQEF + DDApBBSakVhBLltKvqUj6EAxvWqJi+gc7AICJxACASAwCgYIKoZIhvcNAgkwCgYIKoZIhvcNAgkE + IG+euEHE8iN/2C7txbCjCJ9mU4TgEsHPsC9L3Rxa7malBAhOT1QgVVNFRAIBAQ=="""; + Asserts.assertTrue(Asserts.assertThrows(IOException.class, () -> loadAndStore(incorrectSalt)) + .getMessage().contains("Integrity check failed")); + + // Incorrect Iteration Count + var incorrectIC = """ + MIGdAgEDMBEGCSqGSIb3DQEHAaAEBAIwADCBhDB1MFEGCSqGSIb3DQEFDjBEMDYGCSqGSIb3DQEF + DDApBBSZkVhBLltKvqUj6EAxvWqJi+gc7AICKBACASAwCgYIKoZIhvcNAgkwCgYIKoZIhvcNAgkE + IG+euEHE8iN/2C7txbCjCJ9mU4TgEsHPsC9L3Rxa7malBAhOT1QgVVNFRAIBAQ=="""; + Asserts.assertTrue(Asserts.assertThrows(IOException.class, () -> loadAndStore(incorrectIC)) + .getMessage().contains("Integrity check failed")); + + // Missing Key Length + var missingKeyLength = """ + MIGaAgEDMBEGCSqGSIb3DQEHAaAEBAIwADCBgTByME4GCSqGSIb3DQEFDjBBMDMGCSqGSIb3DQEF + DDAmBBSZkVhBLltKvqUj6EAxvWqJi+gc7AICJxAwCgYIKoZIhvcNAgkwCgYIKoZIhvcNAgkEIG+e + uEHE8iN/2C7txbCjCJ9mU4TgEsHPsC9L3Rxa7malBAhOT1QgVVNFRAIBAQ=="""; + Asserts.assertTrue(Asserts.assertThrows(IOException.class, () -> loadAndStore(missingKeyLength)) + .getMessage().contains("missing keyLength field")); + } + + static byte[] emptyP12() throws Exception { + var ks = KeyStore.getInstance("pkcs12"); + ks.load(null, null); + var os = new ByteArrayOutputStream(); + ks.store(os, PASSWORD); + return os.toByteArray(); + } + + static byte[] loadAndStore(String data) throws Exception { + var bytes = Base64.getMimeDecoder().decode(data); + var ks = KeyStore.getInstance("PKCS12"); + ks.load(new ByteArrayInputStream(bytes), PASSWORD); + var baos = new ByteArrayOutputStream(); + ks.store(baos, PASSWORD); + var newBytes = baos.toByteArray(); + var bais = new ByteArrayInputStream(newBytes); + ks.load(bais, PASSWORD); + return newBytes; + } +} diff --git a/test/jdk/sun/security/pkcs12/ParamsPreferences.java b/test/jdk/sun/security/pkcs12/ParamsPreferences.java index 4bedca56a786..c40bd4f4b705 100644 --- a/test/jdk/sun/security/pkcs12/ParamsPreferences.java +++ b/test/jdk/sun/security/pkcs12/ParamsPreferences.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2021, 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 @@ -35,7 +35,7 @@ /* * @test - * @bug 8076190 8242151 8153005 8266293 + * @bug 8076190 8242151 8153005 8266293 8343232 * @library /test/lib * @modules java.base/sun.security.pkcs * java.base/sun.security.util @@ -244,7 +244,7 @@ static void test(int n, Map sysProps, checkAlg(data, "110c10", EncryptedData); checkAlg(data, "110c110110", certAlg); if (certAlg == PBES2) { - checkAlg(data, "110c11011100", PBKDF2WithHmacSHA1); + checkAlg(data, "110c11011100", PBKDF2); checkAlg(data, "110c1101110130", (KnownOIDs)args[i++]); checkAlg(data, "110c11011110", (KnownOIDs)args[i++]); checkInt(data, "110c110111011", (int) args[i++]); @@ -257,7 +257,7 @@ static void test(int n, Map sysProps, KnownOIDs keyAlg = (KnownOIDs)args[i++]; checkAlg(data, "110c010c01000", keyAlg); if (keyAlg == PBES2) { - checkAlg(data, "110c010c0100100", PBKDF2WithHmacSHA1); + checkAlg(data, "110c010c0100100", PBKDF2); checkAlg(data, "110c010c010010130", (KnownOIDs)args[i++]); checkAlg(data, "110c010c0100110", (KnownOIDs)args[i++]); checkInt(data, "110c010c01001011", (int) args[i++]); From 49fc8c9a99e4395ac8f136cf6c5cc52a99ba7379 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 3 Aug 2026 16:29:00 +0000 Subject: [PATCH 81/86] 8368694: PKCS11-NSS generic keys generated by DH have leading zeroes stripped Backport-of: 914b44e277df23418736eb00c022bbd829d64e11 --- .../sun/security/pkcs11/P11KeyAgreement.java | 51 +++++++------------ .../provider/TLS/TestLeadingZeroes.java | 24 ++++++++- .../pkcs11/tls/TestLeadingZeroesP11.java | 24 ++++++++- 3 files changed, 63 insertions(+), 36 deletions(-) diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11KeyAgreement.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11KeyAgreement.java index 183135ce7e12..51d92691473b 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11KeyAgreement.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11KeyAgreement.java @@ -200,6 +200,7 @@ protected byte[] engineGenerateSecret() throws IllegalStateException { CK_ATTRIBUTE[] attributes = new CK_ATTRIBUTE[] { new CK_ATTRIBUTE(CKA_CLASS, CKO_SECRET_KEY), new CK_ATTRIBUTE(CKA_KEY_TYPE, CKK_GENERIC_SECRET), + new CK_ATTRIBUTE(CKA_VALUE_LEN, secretLen), }; attributes = token.getAttributes (O_GENERATE, CKO_SECRET_KEY, CKK_GENERIC_SECRET, attributes); @@ -213,22 +214,11 @@ protected byte[] engineGenerateSecret() throws IllegalStateException { token.p11.C_GetAttributeValue(session.id(), keyID, attributes); byte[] secret = attributes[0].getByteArray(); token.p11.C_DestroyObject(session.id(), keyID); - // Some vendors, e.g. NSS, trim off the leading 0x00 byte(s) from - // the generated secret. Thus, we need to check the secret length - // and trim/pad it so the returned value has the same length as - // the modulus size - if (secret.length == secretLen) { - return secret; - } else { - if (secret.length > secretLen) { - // Shouldn't happen; but check just in case - throw new ProviderException("generated secret is out-of-range"); - } - byte[] newSecret = new byte[secretLen]; - System.arraycopy(secret, 0, newSecret, secretLen - secret.length, - secret.length); - return newSecret; + if (secret.length != secretLen) { + // Shouldn't happen; but check just in case + throw new ProviderException("generated secret is out-of-range"); } + return secret; } catch (PKCS11Exception e) { throw new ProviderException("Could not derive key", e); } finally { @@ -321,10 +311,20 @@ private SecretKey nativeGenerateSecret(String algorithm) long privKeyID = privateKey.getKeyID(); try { session = token.getObjSession(); - CK_ATTRIBUTE[] attributes = new CK_ATTRIBUTE[] { - new CK_ATTRIBUTE(CKA_CLASS, CKO_SECRET_KEY), - new CK_ATTRIBUTE(CKA_KEY_TYPE, keyType), - }; + CK_ATTRIBUTE[] attributes; + if ("TlsPremasterSecret".equalsIgnoreCase(algorithm)) { + attributes = new CK_ATTRIBUTE[]{ + new CK_ATTRIBUTE(CKA_CLASS, CKO_SECRET_KEY), + new CK_ATTRIBUTE(CKA_KEY_TYPE, keyType), + }; + } else { + // keep the leading zeroes + attributes = new CK_ATTRIBUTE[]{ + new CK_ATTRIBUTE(CKA_CLASS, CKO_SECRET_KEY), + new CK_ATTRIBUTE(CKA_KEY_TYPE, keyType), + new CK_ATTRIBUTE(CKA_VALUE_LEN, secretLen), + }; + } attributes = token.getAttributes (O_GENERATE, CKO_SECRET_KEY, keyType, attributes); long keyID = token.p11.C_DeriveKey(session.id(), @@ -337,19 +337,6 @@ private SecretKey nativeGenerateSecret(String algorithm) int keyLen = (int)lenAttributes[0].getLong(); SecretKey key = P11Key.secretKey (session, keyID, algorithm, keyLen << 3, attributes); - if ("RAW".equals(key.getFormat()) - && algorithm.equalsIgnoreCase("TlsPremasterSecret")) { - // Workaround for Solaris bug 6318543. - // Strip leading zeroes ourselves if possible (key not sensitive). - // This should be removed once the Solaris fix is available - // as here we always retrieve the CKA_VALUE even for tokens - // that do not have that bug. - byte[] keyBytes = key.getEncoded(); - byte[] newBytes = KeyUtil.trimZeroes(keyBytes); - if (keyBytes != newBytes) { - key = new SecretKeySpec(newBytes, algorithm); - } - } return key; } catch (PKCS11Exception e) { throw new InvalidKeyException("Could not derive key", e); diff --git a/test/jdk/com/sun/crypto/provider/TLS/TestLeadingZeroes.java b/test/jdk/com/sun/crypto/provider/TLS/TestLeadingZeroes.java index 9dc22c77fd04..36f1e1b77066 100644 --- a/test/jdk/com/sun/crypto/provider/TLS/TestLeadingZeroes.java +++ b/test/jdk/com/sun/crypto/provider/TLS/TestLeadingZeroes.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2024, 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 @@ -23,7 +23,7 @@ /* * @test - * @bug 8014618 + * @bug 8014618 8368694 * @summary Need to strip leading zeros in TlsPremasterSecret of DHKeyAgreement * @author Pasi Eronen */ @@ -88,6 +88,26 @@ private void run() throws Exception { throw new Exception("First byte is not zero as expected"); } + // generate generic shared secret + aliceKeyAgree.init(alicePrivKey); + aliceKeyAgree.doPhase(bobPubKey, true); + byte[] genericSecret = + aliceKeyAgree.generateSecret("Generic").getEncoded(); + System.out.println("generic secret:\n" + HEX_FORMATTER.formatHex(genericSecret)); + + // verify that leading zero is present + if (genericSecret.length != 256) { + throw new Exception("Unexpected generic secret length"); + } + if (genericSecret[0] != 0) { + throw new Exception("First byte is not zero as expected"); + } + for (int i = 0; i < genericSecret.length; i++) { + if (genericSecret[i] != sharedSecret[i]) { + throw new Exception("Shared secrets differ"); + } + } + // now, test TLS premaster secret aliceKeyAgree.init(alicePrivKey); aliceKeyAgree.doPhase(bobPubKey, true); diff --git a/test/jdk/sun/security/pkcs11/tls/TestLeadingZeroesP11.java b/test/jdk/sun/security/pkcs11/tls/TestLeadingZeroesP11.java index 81a8c8eac9f4..e75bcc44767c 100644 --- a/test/jdk/sun/security/pkcs11/tls/TestLeadingZeroesP11.java +++ b/test/jdk/sun/security/pkcs11/tls/TestLeadingZeroesP11.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2024, 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 @@ -23,7 +23,7 @@ /* * @test - * @bug 8014618 + * @bug 8014618 8368694 * @summary Need to strip leading zeros in TlsPremasterSecret of DHKeyAgreement * @library /test/lib .. * @author Pasi Eronen @@ -87,6 +87,26 @@ public void main(Provider p) throws Exception { throw new Exception("First byte is not zero as expected"); } + // generate generic shared secret + aliceKeyAgree.init(alicePrivKey); + aliceKeyAgree.doPhase(bobPubKey, true); + byte[] genericSecret = + aliceKeyAgree.generateSecret("Generic").getEncoded(); + System.out.println("generic secret:\n" + HEX.formatHex(genericSecret)); + + // verify that leading zero is present + if (genericSecret.length != 128) { + throw new Exception("Unexpected generic secret length"); + } + if (genericSecret[0] != 0) { + throw new Exception("First byte is not zero as expected"); + } + for (int i = 0; i < genericSecret.length; i++) { + if (genericSecret[i] != sharedSecret[i]) { + throw new Exception("Shared secrets differ"); + } + } + // now, test TLS premaster secret aliceKeyAgree.init(alicePrivKey); aliceKeyAgree.doPhase(bobPubKey, true); From 1fffa46fac5be748bf3ef4cc3af3e45520daefdb Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 3 Aug 2026 16:29:22 +0000 Subject: [PATCH 82/86] 8367656: Refactor Constantpool's operand array into two Backport-of: d94c52ccf2fed3fc66d25a34254c9b581c175fa1 --- .../share/cds/aotConstantPoolResolver.cpp | 4 +- .../share/classfile/classFileParser.cpp | 76 ++--- src/hotspot/share/oops/bsmAttribute.hpp | 170 ++++++++++ .../share/oops/bsmAttribute.inline.hpp | 55 ++++ src/hotspot/share/oops/constantPool.cpp | 298 ++++++++---------- src/hotspot/share/oops/constantPool.hpp | 136 ++------ .../prims/jvmtiClassFileReconstituter.cpp | 22 +- .../share/prims/jvmtiRedefineClasses.cpp | 146 ++++----- .../share/prims/jvmtiRedefineClasses.hpp | 26 +- src/hotspot/share/runtime/vmStructs.cpp | 7 +- .../sun/jvm/hotspot/oops/ConstantPool.java | 68 ++-- .../sun/jvm/hotspot/utilities/U4Array.java | 64 ++++ 12 files changed, 613 insertions(+), 459 deletions(-) create mode 100644 src/hotspot/share/oops/bsmAttribute.hpp create mode 100644 src/hotspot/share/oops/bsmAttribute.inline.hpp create mode 100644 src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/U4Array.java diff --git a/src/hotspot/share/cds/aotConstantPoolResolver.cpp b/src/hotspot/share/cds/aotConstantPoolResolver.cpp index 234a2d17759d..635735f4494d 100644 --- a/src/hotspot/share/cds/aotConstantPoolResolver.cpp +++ b/src/hotspot/share/cds/aotConstantPoolResolver.cpp @@ -408,7 +408,7 @@ bool AOTConstantPoolResolver::check_lambda_metafactory_signature(ConstantPool* c } bool AOTConstantPoolResolver::check_lambda_metafactory_methodtype_arg(ConstantPool* cp, int bsms_attribute_index, int arg_i) { - int mt_index = cp->bsm_attribute_entry(bsms_attribute_index)->argument_index(arg_i); + int mt_index = cp->bsm_attribute_entry(bsms_attribute_index)->argument(arg_i); if (!cp->tag_at(mt_index).is_method_type()) { // malformed class? return false; @@ -424,7 +424,7 @@ bool AOTConstantPoolResolver::check_lambda_metafactory_methodtype_arg(ConstantPo } bool AOTConstantPoolResolver::check_lambda_metafactory_methodhandle_arg(ConstantPool* cp, int bsms_attribute_index, int arg_i) { - int mh_index = cp->bsm_attribute_entry(bsms_attribute_index)->argument_index(arg_i); + int mh_index = cp->bsm_attribute_entry(bsms_attribute_index)->argument(arg_i); if (!cp->tag_at(mh_index).is_method_handle()) { // malformed class? return false; diff --git a/src/hotspot/share/classfile/classFileParser.cpp b/src/hotspot/share/classfile/classFileParser.cpp index 5e28f3ec641a..c95737f4f0a9 100644 --- a/src/hotspot/share/classfile/classFileParser.cpp +++ b/src/hotspot/share/classfile/classFileParser.cpp @@ -47,6 +47,7 @@ #include "memory/resourceArea.hpp" #include "memory/universe.hpp" #include "oops/annotations.hpp" +#include "oops/bsmAttribute.inline.hpp" #include "oops/constantPool.inline.hpp" #include "oops/fieldInfo.hpp" #include "oops/fieldStreams.inline.hpp" @@ -3270,8 +3271,9 @@ void ClassFileParser::parse_classfile_bootstrap_methods_attribute(const ClassFil TRAPS) { assert(cfs != nullptr, "invariant"); assert(cp != nullptr, "invariant"); + const int cp_size = cp->length(); - const u1* const current_start = cfs->current(); + const u1* const current_before_parsing = cfs->current(); guarantee_property(attribute_byte_length >= sizeof(u2), "Invalid BootstrapMethods attribute length %u in class file %s", @@ -3280,57 +3282,40 @@ void ClassFileParser::parse_classfile_bootstrap_methods_attribute(const ClassFil cfs->guarantee_more(attribute_byte_length, CHECK); - const int attribute_array_length = cfs->get_u2_fast(); + const int num_bootstrap_methods = cfs->get_u2_fast(); - guarantee_property(_max_bootstrap_specifier_index < attribute_array_length, + guarantee_property(_max_bootstrap_specifier_index < num_bootstrap_methods, "Short length on BootstrapMethods in class file %s", CHECK); + const u4 bootstrap_methods_u2_len = (attribute_byte_length - sizeof(u2)) / sizeof(u2); - // The attribute contains a counted array of counted tuples of shorts, - // represending bootstrap specifiers: - // length*{bootstrap_method_index, argument_count*{argument_index}} - const unsigned int operand_count = (attribute_byte_length - (unsigned)sizeof(u2)) / (unsigned)sizeof(u2); - // operand_count = number of shorts in attr, except for leading length - - // The attribute is copied into a short[] array. - // The array begins with a series of short[2] pairs, one for each tuple. - const int index_size = (attribute_array_length * 2); - - Array* const operands = - MetadataFactory::new_array(_loader_data, index_size + operand_count, CHECK); - - // Eagerly assign operands so they will be deallocated with the constant + // Eagerly assign the arrays so that they will be deallocated with the constant // pool if there is an error. - cp->set_operands(operands); - - int operand_fill_index = index_size; - const int cp_size = cp->length(); - - for (int n = 0; n < attribute_array_length; n++) { - // Store a 32-bit offset into the header of the operand array. - ConstantPool::operand_offset_at_put(operands, n, operand_fill_index); + BSMAttributeEntries::InsertionIterator iter = + cp->bsm_entries().start_extension(num_bootstrap_methods, + bootstrap_methods_u2_len, + _loader_data, + CHECK); - // Read a bootstrap specifier. + for (int i = 0; i < num_bootstrap_methods; i++) { cfs->guarantee_more(sizeof(u2) * 2, CHECK); // bsm, argc - const u2 bootstrap_method_index = cfs->get_u2_fast(); - const u2 argument_count = cfs->get_u2_fast(); + u2 bootstrap_method_ref = cfs->get_u2_fast(); + u2 num_bootstrap_arguments = cfs->get_u2_fast(); guarantee_property( - valid_cp_range(bootstrap_method_index, cp_size) && - cp->tag_at(bootstrap_method_index).is_method_handle(), - "bootstrap_method_index %u has bad constant type in class file %s", - bootstrap_method_index, - CHECK); - - guarantee_property((operand_fill_index + 1 + argument_count) < operands->length(), - "Invalid BootstrapMethods num_bootstrap_methods or num_bootstrap_arguments value in class file %s", - CHECK); - - operands->at_put(operand_fill_index++, bootstrap_method_index); - operands->at_put(operand_fill_index++, argument_count); - - cfs->guarantee_more(sizeof(u2) * argument_count, CHECK); // argv[argc] - for (int j = 0; j < argument_count; j++) { + valid_cp_range(bootstrap_method_ref, cp_size) && + cp->tag_at(bootstrap_method_ref).is_method_handle(), + "bootstrap_method_index %u has bad constant type in class file %s", + bootstrap_method_ref, + CHECK); + cfs->guarantee_more(sizeof(u2) * num_bootstrap_arguments, CHECK); // argv[argc] + + BSMAttributeEntry* entry = iter.reserve_new_entry(bootstrap_method_ref, num_bootstrap_arguments); + guarantee_property(entry != nullptr, + "Invalid BootstrapMethods num_bootstrap_methods." + " The total amount of space reserved for the BootstrapMethod attribute was not sufficient", CHECK); + + for (int argi = 0; argi < num_bootstrap_arguments; argi++) { const u2 argument_index = cfs->get_u2_fast(); guarantee_property( valid_cp_range(argument_index, cp_size) && @@ -3338,10 +3323,11 @@ void ClassFileParser::parse_classfile_bootstrap_methods_attribute(const ClassFil "argument_index %u has bad constant type in class file %s", argument_index, CHECK); - operands->at_put(operand_fill_index++, argument_index); + entry->set_argument(argi, argument_index); } } - guarantee_property(current_start + attribute_byte_length == cfs->current(), + cp->bsm_entries().end_extension(iter, _loader_data, CHECK); + guarantee_property(current_before_parsing + attribute_byte_length == cfs->current(), "Bad length on BootstrapMethods in class file %s", CHECK); } diff --git a/src/hotspot/share/oops/bsmAttribute.hpp b/src/hotspot/share/oops/bsmAttribute.hpp new file mode 100644 index 000000000000..a28d2757fb07 --- /dev/null +++ b/src/hotspot/share/oops/bsmAttribute.hpp @@ -0,0 +1,170 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_OOPS_BSMATTRIBUTE_HPP +#define SHARE_OOPS_BSMATTRIBUTE_HPP + +#include "oops/array.hpp" +#include "utilities/checkedCast.hpp" +#include "utilities/globalDefinitions.hpp" + +class ClassLoaderData; + +class BSMAttributeEntry { + friend class ConstantPool; + friend class BSMAttributeEntries; + + u2 _bootstrap_method_index; + u2 _argument_count; + + // The argument indexes are stored right after the object, in a contiguous array. + // [ bsmi_0 argc_0 arg_00 arg_01 ... arg_0N bsmi_1 argc_1 arg_10 ... arg_1N ... ] + // So in order to find the argument array, jump over ourselves. + const u2* argument_indexes() const { + return reinterpret_cast(this + 1); + } + u2* argument_indexes() { + return reinterpret_cast(this + 1); + } + // These are overlays on top of the BSMAttributeEntries data array, do not construct. + BSMAttributeEntry() = delete; + NONCOPYABLE(BSMAttributeEntry); + + void copy_args_into(BSMAttributeEntry* entry) const; + +public: + // Offsets for SA + enum { + _bsmi_offset = 0, + _argc_offset = 1, + _argv_offset = 2 + }; + + int bootstrap_method_index() const { + return _bootstrap_method_index; + } + int argument_count() const { + return _argument_count; + } + int argument(int n) const { + assert(checked_cast(n) < _argument_count, "oob"); + return argument_indexes()[n]; + } + + void set_argument(int index, u2 value) { + assert(index >= 0 && index < argument_count(), "invariant"); + argument_indexes()[index] = value; + } + + // How many u2s are required to store a BSM entry with argc arguments? + static int u2s_required (u2 argc) { + return 1 /* index */ + 1 /* argc */ + argc /* argv */; + } +}; + +// The BSMAttributeEntries stores the state of the BootstrapMethods attribute. +class BSMAttributeEntries { + friend class VMStructs; + friend class JVMCIVMStructs; + +public: + class InsertionIterator { + friend BSMAttributeEntries; + BSMAttributeEntries* _insert_into; + // Current unused offset into BSMAEs offset array. + int _cur_offset; + // Current unused offset into BSMAEs bsm-data array. + int _cur_array; + public: + InsertionIterator() : _insert_into(nullptr), _cur_offset(-1), _cur_array(-1) {} + InsertionIterator(BSMAttributeEntries* insert_into, int cur_offset, int cur_array) + : _insert_into(insert_into), + _cur_offset(cur_offset), + _cur_array(cur_array) {} + InsertionIterator(const InsertionIterator&) = default; + InsertionIterator& operator=(const InsertionIterator&) = default; + + int current_offset() const { return _cur_offset; } + // Add a new BSMAE, reserving the necessary memory for filling the argument vector. + // Returns null if there isn't enough space. + inline BSMAttributeEntry* reserve_new_entry(u2 bsmi, u2 argc); + }; + +private: + // Each bootstrap method has a variable-sized array associated with it. + // We want constant-time lookup of the Nth BSM. Therefore, we use an offset table, + // such that the Nth BSM is located at _bootstrap_methods[_offsets[N]]. + Array* _offsets; + Array* _bootstrap_methods; + + // Copy the first num_entries into iter. + void copy_into(InsertionIterator& iter, int num_entries) const; + +public: + BSMAttributeEntries() : _offsets(nullptr), _bootstrap_methods(nullptr) {} + BSMAttributeEntries(Array* offsets, Array* bootstrap_methods) + : _offsets(offsets), + _bootstrap_methods(bootstrap_methods) {} + + bool is_empty() const { + return _offsets == nullptr && _bootstrap_methods == nullptr; + } + + Array*& offsets() { return _offsets; } + const Array* const& offsets() const { return _offsets; } + Array*& bootstrap_methods() { return _bootstrap_methods; } + const Array* const& bootstrap_methods() const { return _bootstrap_methods; } + + BSMAttributeEntry* entry(int bsms_attribute_index) { + return reinterpret_cast(_bootstrap_methods->adr_at(_offsets->at(bsms_attribute_index))); + } + const BSMAttributeEntry* entry(int bsms_attribute_index) const { + return reinterpret_cast(_bootstrap_methods->adr_at(_offsets->at(bsms_attribute_index))); + } + + int number_of_entries() const { + return _offsets == nullptr ? 0 : _offsets->length(); + } + + // The number of U2s the BSM data consists of. + int array_length() const { + return _bootstrap_methods == nullptr ? 0 : _bootstrap_methods->length(); + } + + void deallocate_contents(ClassLoaderData* loader_data); + + // Extend to have the space for both this BSMAEntries and other's. + // Does not copy in the other's BSMAEntrys, that must be done via the InsertionIterator. + // This starts an insertion iterator. Any call to start_extension must have a matching end_extension call. + InsertionIterator start_extension(const BSMAttributeEntries& other, ClassLoaderData* loader_data, TRAPS); + // Extend the BSMAEntries with an additional number_of_entries with a total data_size. + InsertionIterator start_extension(int number_of_entries, int data_size, ClassLoaderData* loader_data, TRAPS); + // Reallocates the underlying memory to fit the limits of the InsertionIterator precisely. + // This ends an insertion iteration. The memory is truncated to fit exactly the data used. + void end_extension(InsertionIterator& iter, ClassLoaderData* loader_data, TRAPS); + // Append all of the BSMAEs in other into this. + void append(const BSMAttributeEntries& other, ClassLoaderData* loader_data, TRAPS); +}; + +#endif // SHARE_OOPS_BSMATTRIBUTE_HPP diff --git a/src/hotspot/share/oops/bsmAttribute.inline.hpp b/src/hotspot/share/oops/bsmAttribute.inline.hpp new file mode 100644 index 000000000000..e678c280c26e --- /dev/null +++ b/src/hotspot/share/oops/bsmAttribute.inline.hpp @@ -0,0 +1,55 @@ +/* + * 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. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_OOPS_BSMATTRIBUTE_INLINE_HPP +#define SHARE_OOPS_BSMATTRIBUTE_INLINE_HPP + +#include "oops/bsmAttribute.hpp" + +inline BSMAttributeEntry* BSMAttributeEntries::InsertionIterator::reserve_new_entry(u2 bsmi, u2 argc) { + assert(_insert_into->offsets() != nullptr, "must"); + assert(_insert_into->bootstrap_methods() != nullptr, "must"); + + if (_cur_offset + 1 > _insert_into->offsets()->length() || + _cur_array + BSMAttributeEntry::u2s_required(argc) > _insert_into->bootstrap_methods()->length()) { + return nullptr; + } + _insert_into->offsets()->at_put(_cur_offset, _cur_array); + BSMAttributeEntry* e = _insert_into->entry(_cur_offset); + e->_bootstrap_method_index = bsmi; + e->_argument_count = argc; + + _cur_array += 1 + 1 + argc; + _cur_offset += 1; + return e; +} + +inline void BSMAttributeEntry::copy_args_into(BSMAttributeEntry* entry) const { + assert(entry->argument_count() == this->argument_count(), "must be same"); + for (int i = 0; i < argument_count(); i++) { + entry->set_argument(i, this->argument(i)); + } +} + +#endif // SHARE_OOPS_BSMATTRIBUTE_INLINE_HPP diff --git a/src/hotspot/share/oops/constantPool.cpp b/src/hotspot/share/oops/constantPool.cpp index 3223c56628ef..16dc8ced5693 100644 --- a/src/hotspot/share/oops/constantPool.cpp +++ b/src/hotspot/share/oops/constantPool.cpp @@ -133,8 +133,7 @@ void ConstantPool::deallocate_contents(ClassLoaderData* loader_data) { MetadataFactory::free_array(loader_data, resolved_klasses()); set_resolved_klasses(nullptr); - MetadataFactory::free_array(loader_data, operands()); - set_operands(nullptr); + bsm_entries().deallocate_contents(loader_data); release_C_heap_structures(); @@ -154,7 +153,8 @@ void ConstantPool::metaspace_pointers_do(MetaspaceClosure* it) { it->push(&_tags, MetaspaceClosure::_writable); it->push(&_cache); it->push(&_pool_holder); - it->push(&_operands); + it->push(&bsm_entries().offsets()); + it->push(&bsm_entries().bootstrap_methods()); it->push(&_resolved_klasses, MetaspaceClosure::_writable); for (int i = 0; i < length(); i++) { @@ -758,7 +758,7 @@ Method* ConstantPool::method_at_if_loaded(const constantPoolHandle& cpool, if (cpool->cache() == nullptr) return nullptr; // nothing to load yet if (!(which >= 0 && which < cpool->resolved_method_entries_length())) { // FIXME: should be an assert - log_debug(class, resolve)("bad operand %d in:", which); cpool->print(); + log_debug(class, resolve)("bad BSM %d in:", which); cpool->print(); return nullptr; } return cpool->cache()->method_if_resolved(which); @@ -1559,8 +1559,8 @@ bool ConstantPool::compare_entry_to(int index1, const constantPoolHandle& cp2, int i1 = bootstrap_methods_attribute_index(index1); int i2 = cp2->bootstrap_methods_attribute_index(index2); bool match_entry = compare_entry_to(k1, cp2, k2); - bool match_operand = compare_operand_to(i1, cp2, i2); - return (match_entry && match_operand); + bool match_bsm = compare_bootstrap_entry_to(i1, cp2, i2); + return (match_entry && match_bsm); } break; case JVM_CONSTANT_InvokeDynamic: @@ -1570,8 +1570,8 @@ bool ConstantPool::compare_entry_to(int index1, const constantPoolHandle& cp2, int i1 = bootstrap_methods_attribute_index(index1); int i2 = cp2->bootstrap_methods_attribute_index(index2); bool match_entry = compare_entry_to(k1, cp2, k2); - bool match_operand = compare_operand_to(i1, cp2, i2); - return (match_entry && match_operand); + bool match_bsm = compare_bootstrap_entry_to(i1, cp2, i2); + return (match_entry && match_bsm); } break; case JVM_CONSTANT_String: @@ -1605,140 +1605,29 @@ bool ConstantPool::compare_entry_to(int index1, const constantPoolHandle& cp2, return false; } // end compare_entry_to() - -// Resize the operands array with delta_len and delta_size. -// Used in RedefineClasses for CP merge. -void ConstantPool::resize_operands(int delta_len, int delta_size, TRAPS) { - int old_len = operand_array_length(operands()); - int new_len = old_len + delta_len; - int min_len = (delta_len > 0) ? old_len : new_len; - - int old_size = operands()->length(); - int new_size = old_size + delta_size; - int min_size = (delta_size > 0) ? old_size : new_size; - - ClassLoaderData* loader_data = pool_holder()->class_loader_data(); - Array* new_ops = MetadataFactory::new_array(loader_data, new_size, CHECK); - - // Set index in the resized array for existing elements only - for (int idx = 0; idx < min_len; idx++) { - int offset = operand_offset_at(idx); // offset in original array - operand_offset_at_put(new_ops, idx, offset + 2*delta_len); // offset in resized array - } - // Copy the bootstrap specifiers only - Copy::conjoint_memory_atomic(operands()->adr_at(2*old_len), - new_ops->adr_at(2*new_len), - (min_size - 2*min_len) * sizeof(u2)); - // Explicitly deallocate old operands array. - // Note, it is not needed for 7u backport. - if ( operands() != nullptr) { // the safety check - MetadataFactory::free_array(loader_data, operands()); - } - set_operands(new_ops); -} // end resize_operands() - - -// Extend the operands array with the length and size of the ext_cp operands. +// Extend the BSMAttributeEntries with the length and size of the ext_cp BSMAttributeEntries. // Used in RedefineClasses for CP merge. -void ConstantPool::extend_operands(const constantPoolHandle& ext_cp, TRAPS) { - int delta_len = operand_array_length(ext_cp->operands()); - if (delta_len == 0) { - return; // nothing to do - } - int delta_size = ext_cp->operands()->length(); - - assert(delta_len > 0 && delta_size > 0, "extended operands array must be bigger"); - - if (operand_array_length(operands()) == 0) { - ClassLoaderData* loader_data = pool_holder()->class_loader_data(); - Array* new_ops = MetadataFactory::new_array(loader_data, delta_size, CHECK); - // The first element index defines the offset of second part - operand_offset_at_put(new_ops, 0, 2*delta_len); // offset in new array - set_operands(new_ops); - } else { - resize_operands(delta_len, delta_size, CHECK); - } +BSMAttributeEntries::InsertionIterator +ConstantPool::start_extension(const constantPoolHandle& ext_cp, TRAPS) { + BSMAttributeEntries::InsertionIterator iter = + bsm_entries().start_extension(ext_cp->bsm_entries(), pool_holder()->class_loader_data(), + CHECK_(BSMAttributeEntries::InsertionIterator())); + return iter; +} -} // end extend_operands() +void ConstantPool::end_extension(BSMAttributeEntries::InsertionIterator iter, TRAPS) { + bsm_entries().end_extension(iter, pool_holder()->class_loader_data(), THREAD); +} -// Shrink the operands array to a smaller array with new_len length. -// Used in RedefineClasses for CP merge. -void ConstantPool::shrink_operands(int new_len, TRAPS) { - int old_len = operand_array_length(operands()); - if (new_len == old_len) { - return; // nothing to do - } - assert(new_len < old_len, "shrunken operands array must be smaller"); - - int free_base = operand_next_offset_at(new_len - 1); - int delta_len = new_len - old_len; - int delta_size = 2*delta_len + free_base - operands()->length(); - - resize_operands(delta_len, delta_size, CHECK); - -} // end shrink_operands() - - -void ConstantPool::copy_operands(const constantPoolHandle& from_cp, - const constantPoolHandle& to_cp, - TRAPS) { - - int from_oplen = operand_array_length(from_cp->operands()); - int old_oplen = operand_array_length(to_cp->operands()); - if (from_oplen != 0) { - ClassLoaderData* loader_data = to_cp->pool_holder()->class_loader_data(); - // append my operands to the target's operands array - if (old_oplen == 0) { - // Can't just reuse from_cp's operand list because of deallocation issues - int len = from_cp->operands()->length(); - Array* new_ops = MetadataFactory::new_array(loader_data, len, CHECK); - Copy::conjoint_memory_atomic( - from_cp->operands()->adr_at(0), new_ops->adr_at(0), len * sizeof(u2)); - to_cp->set_operands(new_ops); - } else { - int old_len = to_cp->operands()->length(); - int from_len = from_cp->operands()->length(); - int old_off = old_oplen * sizeof(u2); - int from_off = from_oplen * sizeof(u2); - // Use the metaspace for the destination constant pool - Array* new_operands = MetadataFactory::new_array(loader_data, old_len + from_len, CHECK); - int fillp = 0, len = 0; - // first part of dest - Copy::conjoint_memory_atomic(to_cp->operands()->adr_at(0), - new_operands->adr_at(fillp), - (len = old_off) * sizeof(u2)); - fillp += len; - // first part of src - Copy::conjoint_memory_atomic(from_cp->operands()->adr_at(0), - new_operands->adr_at(fillp), - (len = from_off) * sizeof(u2)); - fillp += len; - // second part of dest - Copy::conjoint_memory_atomic(to_cp->operands()->adr_at(old_off), - new_operands->adr_at(fillp), - (len = old_len - old_off) * sizeof(u2)); - fillp += len; - // second part of src - Copy::conjoint_memory_atomic(from_cp->operands()->adr_at(from_off), - new_operands->adr_at(fillp), - (len = from_len - from_off) * sizeof(u2)); - fillp += len; - assert(fillp == new_operands->length(), ""); - - // Adjust indexes in the first part of the copied operands array. - for (int j = 0; j < from_oplen; j++) { - int offset = operand_offset_at(new_operands, old_oplen + j); - assert(offset == operand_offset_at(from_cp->operands(), j), "correct copy"); - offset += old_len; // every new tuple is preceded by old_len extra u2's - operand_offset_at_put(new_operands, old_oplen + j, offset); - } - // replace target operands array with combined array - to_cp->set_operands(new_operands); - } - } -} // end copy_operands() +void ConstantPool::copy_bsm_entries(const constantPoolHandle& from_cp, + const constantPoolHandle& to_cp, + TRAPS) { + to_cp->bsm_entries().append(from_cp->bsm_entries(), + to_cp->pool_holder()->class_loader_data(), + THREAD); +} // Copy this constant pool's entries at start_i to end_i (inclusive) @@ -1768,7 +1657,7 @@ void ConstantPool::copy_cp_to_impl(const constantPoolHandle& from_cp, int start_ break; } } - copy_operands(from_cp, to_cp, CHECK); + copy_bsm_entries(from_cp, to_cp, THREAD); } // end copy_cp_to_impl() @@ -1892,7 +1781,7 @@ void ConstantPool::copy_entry_to(const constantPoolHandle& from_cp, int from_i, { int k1 = from_cp->bootstrap_methods_attribute_index(from_i); int k2 = from_cp->bootstrap_name_and_type_ref_index_at(from_i); - k1 += operand_array_length(to_cp->operands()); // to_cp might already have operands + k1 += to_cp->bsm_entries().array_length(); // to_cp might already have a BSM attribute to_cp->dynamic_constant_at_put(to_i, k1, k2); } break; @@ -1900,7 +1789,7 @@ void ConstantPool::copy_entry_to(const constantPoolHandle& from_cp, int from_i, { int k1 = from_cp->bootstrap_methods_attribute_index(from_i); int k2 = from_cp->bootstrap_name_and_type_ref_index_at(from_i); - k1 += operand_array_length(to_cp->operands()); // to_cp might already have operands + k1 += to_cp->bsm_entries().array_length(); // to_cp might already have a BSM attribute to_cp->invoke_dynamic_at_put(to_i, k1, k2); } break; @@ -1936,9 +1825,9 @@ int ConstantPool::find_matching_entry(int pattern_i, // Compare this constant pool's bootstrap specifier at idx1 to the constant pool // cp2's bootstrap specifier at idx2. -bool ConstantPool::compare_operand_to(int idx1, const constantPoolHandle& cp2, int idx2) { - BSMAttributeEntry* e1 = bsm_attribute_entry(idx1); - BSMAttributeEntry* e2 = cp2->bsm_attribute_entry(idx2); +bool ConstantPool::compare_bootstrap_entry_to(int idx1, const constantPoolHandle& cp2, int idx2) { + const BSMAttributeEntry* const e1 = bsm_attribute_entry(idx1); + const BSMAttributeEntry* const e2 = cp2->bsm_attribute_entry(idx2); int k1 = e1->bootstrap_method_index(); int k2 = e2->bootstrap_method_index(); bool match = compare_entry_to(k1, cp2, k2); @@ -1946,34 +1835,37 @@ bool ConstantPool::compare_operand_to(int idx1, const constantPoolHandle& cp2, i if (!match) { return false; } - int argc = e1->argument_count(); - if (argc == e2->argument_count()) { - for (int j = 0; j < argc; j++) { - k1 = e1->argument_index(j); - k2 = e2->argument_index(j); - match = compare_entry_to(k1, cp2, k2); - if (!match) { - return false; - } + + const int argc = e1->argument_count(); + if (argc != e2->argument_count()) { + return false; + } + + for (int j = 0; j < argc; j++) { + k1 = e1->argument(j); + k2 = e2->argument(j); + match = compare_entry_to(k1, cp2, k2); + if (!match) { + return false; } - return true; // got through loop; all elements equal } - return false; -} // end compare_operand_to() + + return true; // got through loop; all elements equal +} // end compare_bootstrap_entry_to() // Search constant pool search_cp for a bootstrap specifier that matches // this constant pool's bootstrap specifier data at pattern_i index. // Return the index of a matching bootstrap attribute record or (-1) if there is no match. -int ConstantPool::find_matching_operand(int pattern_i, - const constantPoolHandle& search_cp, int search_len) { - for (int i = 0; i < search_len; i++) { - bool found = compare_operand_to(pattern_i, search_cp, i); +int ConstantPool::find_matching_bsm_entry(int pattern_i, + const constantPoolHandle& search_cp, int offset_limit) { + for (int i = 0; i < offset_limit; i++) { + bool found = compare_bootstrap_entry_to(pattern_i, search_cp, i); if (found) { return i; } } return -1; // bootstrap specifier data not found; return unused index (-1) -} // end find_matching_operand() +} // end find_matching_bsm_entry() #ifndef PRODUCT @@ -2408,7 +2300,7 @@ void ConstantPool::print_value_on(outputStream* st) const { assert(is_constantPool(), "must be constantPool"); st->print("constant pool [%d]", length()); if (has_preresolution()) st->print("/preresolution"); - if (operands() != nullptr) st->print("/operands[%d]", operands()->length()); + if (!bsm_entries().is_empty()) st->print("/BSMs[%d]", bsm_entries().bootstrap_methods()->length()); print_address_on(st); if (pool_holder() != nullptr) { st->print(" for "); @@ -2443,3 +2335,87 @@ void ConstantPool::verify_on(outputStream* st) { guarantee(pool_holder()->is_klass(), "should be klass"); } } + +void BSMAttributeEntries::deallocate_contents(ClassLoaderData* loader_data) { + MetadataFactory::free_array(loader_data, this->_offsets); + MetadataFactory::free_array(loader_data, this->_bootstrap_methods); + this->_offsets = nullptr; + this->_bootstrap_methods = nullptr; +} + +void BSMAttributeEntries::copy_into(InsertionIterator& iter, int num_entries) const { + assert(num_entries + iter._cur_offset <= iter._insert_into->_offsets->length(), "must"); + for (int i = 0; i < num_entries; i++) { + const BSMAttributeEntry* e = entry(i); + BSMAttributeEntry* e_new = iter.reserve_new_entry(e->bootstrap_method_index(), e->argument_count()); + assert(e_new != nullptr, "must be"); + e->copy_args_into(e_new); + } +} + +BSMAttributeEntries::InsertionIterator +BSMAttributeEntries::start_extension(const BSMAttributeEntries& other, ClassLoaderData* loader_data, TRAPS) { + InsertionIterator iter = start_extension(other.number_of_entries(), other.array_length(), + loader_data, CHECK_(BSMAttributeEntries::InsertionIterator())); + return iter; +} + +BSMAttributeEntries::InsertionIterator +BSMAttributeEntries::start_extension(int number_of_entries, int array_length, + ClassLoaderData* loader_data, TRAPS) { + InsertionIterator extension_iterator(this, this->number_of_entries(), this->array_length()); + int new_number_of_entries = this->number_of_entries() + number_of_entries; + int new_array_length = this->array_length() + array_length; + int invalid_index = new_array_length; + + Array* new_offsets = + MetadataFactory::new_array(loader_data, new_number_of_entries, invalid_index, CHECK_(InsertionIterator())); + Array* new_array = MetadataFactory::new_array(loader_data, new_array_length, CHECK_(InsertionIterator())); + { // Copy over all the old BSMAEntry's and their respective offsets + BSMAttributeEntries carrier(new_offsets, new_array); + InsertionIterator copy_iter(&carrier, 0, 0); + copy_into(copy_iter, this->number_of_entries()); + } + // Replace content + deallocate_contents(loader_data); + _offsets = new_offsets; + _bootstrap_methods = new_array; + return extension_iterator; +} + + +void BSMAttributeEntries::append(const BSMAttributeEntries& other, ClassLoaderData* loader_data, TRAPS) { + if (other.number_of_entries() == 0) { + return; // Done! + } + InsertionIterator iter = start_extension(other, loader_data, CHECK); + other.copy_into(iter, other.number_of_entries()); + end_extension(iter, loader_data, THREAD); +} + +void BSMAttributeEntries::end_extension(InsertionIterator& iter, ClassLoaderData* loader_data, TRAPS) { + assert(iter._insert_into == this, "must be"); + assert(iter._cur_offset <= this->_offsets->length(), "must be"); + assert(iter._cur_array <= this->_bootstrap_methods->length(), "must be"); + + // Did we fill up all of the available space? If so, do nothing. + if (iter._cur_offset == this->_offsets->length() && + iter._cur_array == this->_bootstrap_methods->length()) { + return; + } + + // We used less, truncate by allocating new arrays + Array* new_offsets = + MetadataFactory::new_array(loader_data, iter._cur_offset, 0, CHECK); + Array* new_array = + MetadataFactory::new_array(loader_data, iter._cur_array, CHECK); + { // Copy over the constructed BSMAEntry's + BSMAttributeEntries carrier(new_offsets, new_array); + InsertionIterator copy_iter(&carrier, 0, 0); + copy_into(copy_iter, iter._cur_offset); + } + + deallocate_contents(loader_data); + _offsets = new_offsets; + _bootstrap_methods = new_array; +} diff --git a/src/hotspot/share/oops/constantPool.hpp b/src/hotspot/share/oops/constantPool.hpp index be4a7a474d44..101b80407f45 100644 --- a/src/hotspot/share/oops/constantPool.hpp +++ b/src/hotspot/share/oops/constantPool.hpp @@ -27,6 +27,7 @@ #include "memory/allocation.hpp" #include "oops/arrayOop.hpp" +#include "oops/bsmAttribute.inline.hpp" #include "oops/cpCache.hpp" #include "oops/objArrayOop.hpp" #include "oops/oopHandle.hpp" @@ -77,43 +78,6 @@ class CPKlassSlot { } }; -class BSMAttributeEntry { - friend class ConstantPool; - u2 _bootstrap_method_index; - u2 _argument_count; - - // The argument indexes are stored right after the object, in a contiguous array. - // [ bsmi_0 argc_0 arg_00 arg_01 ... arg_0N bsmi_1 argc_1 arg_10 ... arg_1N ... ] - // So in order to find the argument array, jump over ourselves. - const u2* argument_indexes() const { - return reinterpret_cast(this + 1); - } - u2* argument_indexes() { - return reinterpret_cast(this + 1); - } - // These are overlays on top of the operands array. Do not construct. - BSMAttributeEntry() = delete; - -public: - // Offsets for SA - enum { - _bsmi_offset = 0, - _argc_offset = 1, - _argv_offset = 2 - }; - - int bootstrap_method_index() const { - return _bootstrap_method_index; - } - int argument_count() const { - return _argument_count; - } - int argument_index(int n) const { - assert(checked_cast(n) < _argument_count, "oob"); - return argument_indexes()[n]; - } -}; - class ConstantPool : public Metadata { friend class VMStructs; friend class JVMCIVMStructs; @@ -126,7 +90,8 @@ class ConstantPool : public Metadata { Array* _tags; // the tag array describing the constant pool's contents ConstantPoolCache* _cache; // the cache holding interpreter runtime information InstanceKlass* _pool_holder; // the corresponding class - Array* _operands; // for variable-sized (InvokeDynamic) nodes, usually empty + + BSMAttributeEntries _bsm_entries; // Consider using an array of compressed klass pointers to // save space on 64-bit platforms. @@ -167,8 +132,6 @@ class ConstantPool : public Metadata { u1* tag_addr_at(int cp_index) const { return tags()->adr_at(cp_index); } - void set_operands(Array* operands) { _operands = operands; } - u2 flags() const { return _flags; } void set_flags(u2 f) { _flags = f; } @@ -208,7 +171,13 @@ class ConstantPool : public Metadata { virtual bool is_constantPool() const { return true; } Array* tags() const { return _tags; } - Array* operands() const { return _operands; } + + BSMAttributeEntries& bsm_entries() { + return _bsm_entries; + } + const BSMAttributeEntries& bsm_entries() const { + return _bsm_entries; + } bool has_preresolution() const { return (_flags & _has_preresolution) != 0; } void set_has_preresolution() { @@ -556,76 +525,21 @@ class ConstantPool : public Metadata { assert(tag_at(cp_index).has_bootstrap(), "Corrupted constant pool"); return extract_low_short_from_int(*int_at_addr(cp_index)); } - // The first part of the operands array consists of an index into the second part. - // Extract a 32-bit index value from the first part. - static int operand_offset_at(Array* operands, int bsms_attribute_index) { - int n = (bsms_attribute_index * 2); - assert(n >= 0 && n+2 <= operands->length(), "oob"); - // The first 32-bit index points to the beginning of the second part - // of the operands array. Make sure this index is in the first part. - DEBUG_ONLY(int second_part = build_int_from_shorts(operands->at(0), - operands->at(1))); - assert(second_part == 0 || n+2 <= second_part, "oob (2)"); - int offset = build_int_from_shorts(operands->at(n+0), - operands->at(n+1)); - // The offset itself must point into the second part of the array. - assert(offset == 0 || (offset >= second_part && offset <= operands->length()), "oob (3)"); - return offset; - } - static void operand_offset_at_put(Array* operands, int bsms_attribute_index, int offset) { - int n = bsms_attribute_index * 2; - assert(n >= 0 && n+2 <= operands->length(), "oob"); - operands->at_put(n+0, extract_low_short_from_int(offset)); - operands->at_put(n+1, extract_high_short_from_int(offset)); - } - static int operand_array_length(Array* operands) { - if (operands == nullptr || operands->length() == 0) return 0; - int second_part = operand_offset_at(operands, 0); - return (second_part / 2); - } - -#ifdef ASSERT - // operand tuples fit together exactly, end to end - static int operand_limit_at(Array* operands, int bsms_attribute_index) { - int nextidx = bsms_attribute_index + 1; - if (nextidx == operand_array_length(operands)) - return operands->length(); - else - return operand_offset_at(operands, nextidx); - } -#endif //ASSERT - - // These functions are used in RedefineClasses for CP merge - int operand_offset_at(int bsms_attribute_index) { - assert(0 <= bsms_attribute_index && - bsms_attribute_index < operand_array_length(operands()), - "Corrupted CP operands"); - return operand_offset_at(operands(), bsms_attribute_index); - } BSMAttributeEntry* bsm_attribute_entry(int bsms_attribute_index) { - int offset = operand_offset_at(bsms_attribute_index); - return reinterpret_cast(operands()->adr_at(offset)); - } - - int operand_next_offset_at(int bsms_attribute_index) { - BSMAttributeEntry* bsme = bsm_attribute_entry(bsms_attribute_index); - u2* argv_start = bsme->argument_indexes(); - int offset = argv_start - operands()->data(); - return offset + bsme->argument_count(); - } - // Compare a bootstrap specifier data in the operands arrays - bool compare_operand_to(int bsms_attribute_index1, const constantPoolHandle& cp2, - int bsms_attribute_index2); - // Find a bootstrap specifier data in the operands array - int find_matching_operand(int bsms_attribute_index, const constantPoolHandle& search_cp, - int operands_cur_len); - // Resize the operands array with delta_len and delta_size - void resize_operands(int delta_len, int delta_size, TRAPS); - // Extend the operands array with the length and size of the ext_cp operands - void extend_operands(const constantPoolHandle& ext_cp, TRAPS); - // Shrink the operands array to a smaller array with new_len length - void shrink_operands(int new_len, TRAPS); + return _bsm_entries.entry(bsms_attribute_index); + } + + bool compare_bootstrap_entry_to(int bsms_attribute_index1, const constantPoolHandle& cp2, + int bsms_attribute_index2); + // Find a BSM entry in search_cp that matches the BSM at bsm_attribute_index. + // Return -1 if not found. + int find_matching_bsm_entry(int bsms_attribute_index, const constantPoolHandle& search_cp, + int offset_limit); + // Extend the BSM attribute storage to fit both the current data and the BSM data in ext_cp. + // Use the returned InsertionIterator to fill out the newly allocated space. + BSMAttributeEntries::InsertionIterator start_extension(const constantPoolHandle& ext_cp, TRAPS); + void end_extension(BSMAttributeEntries::InsertionIterator iter, TRAPS); u2 bootstrap_method_ref_index_at(int cp_index) { assert(tag_at(cp_index).has_bootstrap(), "Corrupted constant pool"); @@ -641,7 +555,7 @@ class ConstantPool : public Metadata { int bsmai = bootstrap_methods_attribute_index(cp_index); BSMAttributeEntry* bsme = bsm_attribute_entry(bsmai); assert((uint)j < (uint)bsme->argument_count(), "oob"); - return bsm_attribute_entry(bsmai)->argument_index(j); + return bsm_attribute_entry(bsmai)->argument(j); } // The following methods (name/signature/klass_ref_at, klass_ref_at_noresolve, @@ -848,7 +762,7 @@ class ConstantPool : public Metadata { } static void copy_cp_to_impl(const constantPoolHandle& from_cp, int start_cpi, int end_cpi, const constantPoolHandle& to_cp, int to_cpi, TRAPS); static void copy_entry_to(const constantPoolHandle& from_cp, int from_cpi, const constantPoolHandle& to_cp, int to_cpi); - static void copy_operands(const constantPoolHandle& from_cp, const constantPoolHandle& to_cp, TRAPS); + static void copy_bsm_entries(const constantPoolHandle& from_cp, const constantPoolHandle& to_cp, TRAPS); int find_matching_entry(int pattern_i, const constantPoolHandle& search_cp); int version() const { return _saved._version; } void set_version(int version) { _saved._version = version; } diff --git a/src/hotspot/share/prims/jvmtiClassFileReconstituter.cpp b/src/hotspot/share/prims/jvmtiClassFileReconstituter.cpp index a441d405f8d7..5077a1743b9f 100644 --- a/src/hotspot/share/prims/jvmtiClassFileReconstituter.cpp +++ b/src/hotspot/share/prims/jvmtiClassFileReconstituter.cpp @@ -25,6 +25,7 @@ #include "classfile/symbolTable.hpp" #include "interpreter/bytecodeStream.hpp" #include "memory/universe.hpp" +#include "oops/bsmAttribute.inline.hpp" #include "oops/constantPool.inline.hpp" #include "oops/fieldStreams.inline.hpp" #include "oops/instanceKlass.inline.hpp" @@ -389,20 +390,13 @@ void JvmtiClassFileReconstituter::write_annotations_attribute(const char* attr_n // } bootstrap_methods[num_bootstrap_methods]; // } void JvmtiClassFileReconstituter::write_bootstrapmethod_attribute() { - Array* operands = cpool()->operands(); write_attribute_name_index("BootstrapMethods"); - int num_bootstrap_methods = ConstantPool::operand_array_length(operands); - - // calculate length of attribute - u4 length = sizeof(u2); // num_bootstrap_methods - for (int n = 0; n < num_bootstrap_methods; n++) { - u2 num_bootstrap_arguments = cpool()->bsm_attribute_entry(n)->argument_count(); - length += sizeof(u2); // bootstrap_method_ref - length += sizeof(u2); // num_bootstrap_arguments - length += (u4)sizeof(u2) * num_bootstrap_arguments; // bootstrap_arguments[num_bootstrap_arguments] - } + u4 length = sizeof(u2) + // Size of num_bootstrap_methods + // The rest of the data for the attribute is exactly the u2s in the data array. + sizeof(u2) * cpool()->bsm_entries().array_length(); write_u4(length); + int num_bootstrap_methods = cpool()->bsm_entries().number_of_entries(); // write attribute write_u2(checked_cast(num_bootstrap_methods)); for (int n = 0; n < num_bootstrap_methods; n++) { @@ -411,7 +405,7 @@ void JvmtiClassFileReconstituter::write_bootstrapmethod_attribute() { write_u2(bsme->bootstrap_method_index()); write_u2(num_bootstrap_arguments); for (int arg = 0; arg < num_bootstrap_arguments; arg++) { - u2 bootstrap_argument = bsme->argument_index(arg); + u2 bootstrap_argument = bsme->argument(arg); write_u2(bootstrap_argument); } } @@ -798,7 +792,7 @@ void JvmtiClassFileReconstituter::write_class_attributes() { if (type_anno != nullptr) { ++attr_count; // has RuntimeVisibleTypeAnnotations attribute } - if (cpool()->operands() != nullptr) { + if (!cpool()->bsm_entries().is_empty()) { ++attr_count; } if (ik()->nest_host_index() != 0) { @@ -843,7 +837,7 @@ void JvmtiClassFileReconstituter::write_class_attributes() { if (ik()->record_components() != nullptr) { write_record_attribute(); } - if (cpool()->operands() != nullptr) { + if (!cpool()->bsm_entries().is_empty()) { write_bootstrapmethod_attribute(); } if (inner_classes_length > 0) { diff --git a/src/hotspot/share/prims/jvmtiRedefineClasses.cpp b/src/hotspot/share/prims/jvmtiRedefineClasses.cpp index 5094bab01a98..42637c20b19e 100644 --- a/src/hotspot/share/prims/jvmtiRedefineClasses.cpp +++ b/src/hotspot/share/prims/jvmtiRedefineClasses.cpp @@ -44,7 +44,8 @@ #include "memory/resourceArea.hpp" #include "memory/universe.hpp" #include "oops/annotations.hpp" -#include "oops/constantPool.hpp" +#include "oops/bsmAttribute.inline.hpp" +#include "oops/constantPool.inline.hpp" #include "oops/fieldStreams.inline.hpp" #include "oops/klass.inline.hpp" #include "oops/klassVtable.hpp" @@ -572,9 +573,9 @@ void VM_RedefineClasses::append_entry(const constantPoolHandle& scratch_cp, case JVM_CONSTANT_Dynamic: // fall through case JVM_CONSTANT_InvokeDynamic: { - // Index of the bootstrap specifier in the operands array + // Index of the bootstrap specifier in the BSM array int old_bs_i = scratch_cp->bootstrap_methods_attribute_index(scratch_i); - int new_bs_i = find_or_append_operand(scratch_cp, old_bs_i, merge_cp_p, + int new_bs_i = find_or_append_bsm_entry(scratch_cp, old_bs_i, merge_cp_p, merge_cp_length_p); // The bootstrap method NameAndType_info index int old_ref_i = scratch_cp->bootstrap_name_and_type_ref_index_at(scratch_i); @@ -590,10 +591,11 @@ void VM_RedefineClasses::append_entry(const constantPoolHandle& scratch_cp, ("Dynamic entry@%d name_and_type_index change: %d to %d", *merge_cp_length_p, old_ref_i, new_ref_i); } - if (scratch_cp->tag_at(scratch_i).is_dynamic_constant()) + if (scratch_cp->tag_at(scratch_i).is_dynamic_constant()) { (*merge_cp_p)->dynamic_constant_at_put(*merge_cp_length_p, new_bs_i, new_ref_i); - else + } else { (*merge_cp_p)->invoke_dynamic_at_put(*merge_cp_length_p, new_bs_i, new_ref_i); + } if (scratch_i != *merge_cp_length_p) { // The new entry in *merge_cp_p is at a different index than // the new entry in scratch_cp so we need to map the index values. @@ -659,10 +661,10 @@ u2 VM_RedefineClasses::find_or_append_indirect_entry(const constantPoolHandle& s } // end find_or_append_indirect_entry() -// Append a bootstrap specifier into the merge_cp operands that is semantically equal -// to the scratch_cp operands bootstrap specifier passed by the old_bs_i index. +// Append a bootstrap specifier into the merge_cp BSM entries that is semantically equal +// to the scratch_cp BSM entries' bootstrap specifier passed by the old_bs_i index. // Recursively append new merge_cp entries referenced by the new bootstrap specifier. -void VM_RedefineClasses::append_operand(const constantPoolHandle& scratch_cp, const int old_bs_i, +int VM_RedefineClasses::append_bsm_entry(const constantPoolHandle& scratch_cp, const int old_bs_i, constantPoolHandle *merge_cp_p, int *merge_cp_length_p) { BSMAttributeEntry* old_bsme = scratch_cp->bsm_attribute_entry(old_bs_i); @@ -671,90 +673,82 @@ void VM_RedefineClasses::append_operand(const constantPoolHandle& scratch_cp, co merge_cp_length_p); if (new_ref_i != old_ref_i) { log_trace(redefine, class, constantpool) - ("operands entry@%d bootstrap method ref_index change: %d to %d", _operands_cur_length, old_ref_i, new_ref_i); + ("BSM attribute entry@%d bootstrap method ref_index change: %d to %d", _bsmae_iter.current_offset() - 1, old_ref_i, new_ref_i); } - Array* merge_ops = (*merge_cp_p)->operands(); - int new_bs_i = _operands_cur_length; - // We have _operands_cur_length == 0 when the merge_cp operands is empty yet. - // However, the operand_offset_at(0) was set in the extend_operands() call. - int new_base = (new_bs_i == 0) ? (*merge_cp_p)->operand_offset_at(0) - : (*merge_cp_p)->operand_next_offset_at(new_bs_i - 1); - u2 argc = old_bsme->argument_count(); - - ConstantPool::operand_offset_at_put(merge_ops, _operands_cur_length, new_base); - merge_ops->at_put(new_base++, new_ref_i); - merge_ops->at_put(new_base++, argc); - - for (int i = 0; i < argc; i++) { - u2 old_arg_ref_i = old_bsme->argument_index(i); + const int new_bs_i = _bsmae_iter.current_offset(); + BSMAttributeEntry* new_bsme = + _bsmae_iter.reserve_new_entry(new_ref_i, old_bsme->argument_count()); + assert(new_bsme != nullptr, "must be"); + for (int i = 0; i < new_bsme->argument_count(); i++) { + u2 old_arg_ref_i = old_bsme->argument(i); u2 new_arg_ref_i = find_or_append_indirect_entry(scratch_cp, old_arg_ref_i, merge_cp_p, merge_cp_length_p); - merge_ops->at_put(new_base++, new_arg_ref_i); + new_bsme->set_argument(i, new_arg_ref_i); + if (new_arg_ref_i != old_arg_ref_i) { log_trace(redefine, class, constantpool) - ("operands entry@%d bootstrap method argument ref_index change: %d to %d", - _operands_cur_length, old_arg_ref_i, new_arg_ref_i); + ("BSM attribute entry@%d bootstrap method argument ref_index change: %d to %d", + _bsmae_iter.current_offset() - 1, old_arg_ref_i, new_arg_ref_i); } } - if (old_bs_i != _operands_cur_length) { - // The bootstrap specifier in *merge_cp_p is at a different index than - // that in scratch_cp so we need to map the index values. - map_operand_index(old_bs_i, new_bs_i); - } - _operands_cur_length++; -} // end append_operand() + // This is only for the logging + map_bsm_index(old_bs_i, new_bs_i); + return new_bs_i; +} // end append_bsm_entry() -int VM_RedefineClasses::find_or_append_operand(const constantPoolHandle& scratch_cp, +int VM_RedefineClasses::find_or_append_bsm_entry(const constantPoolHandle& scratch_cp, int old_bs_i, constantPoolHandle *merge_cp_p, int *merge_cp_length_p) { + const int max_offset_in_merge = _bsmae_iter.current_offset(); int new_bs_i = old_bs_i; // bootstrap specifier index - bool match = (old_bs_i < _operands_cur_length) && - scratch_cp->compare_operand_to(old_bs_i, *merge_cp_p, old_bs_i); + // Has the old_bs_i index been used already? Check if it's the same so we know + // whether or not a remapping is required. + bool match = (old_bs_i < max_offset_in_merge) && + scratch_cp->compare_bootstrap_entry_to(old_bs_i, *merge_cp_p, old_bs_i); if (!match) { // forward reference in *merge_cp_p or not a direct match - int found_i = scratch_cp->find_matching_operand(old_bs_i, *merge_cp_p, - _operands_cur_length); + int found_i = scratch_cp->find_matching_bsm_entry(old_bs_i, *merge_cp_p, + max_offset_in_merge); if (found_i != -1) { - guarantee(found_i != old_bs_i, "compare_operand_to() and find_matching_operand() disagree"); - // found a matching operand somewhere else in *merge_cp_p so just need a mapping + guarantee(found_i != old_bs_i, "compare_bootstrap_entry_to() and find_matching_bsm_entry() disagree"); + // found a matching BSM entry somewhere else in *merge_cp_p so just need a mapping new_bs_i = found_i; - map_operand_index(old_bs_i, found_i); + map_bsm_index(old_bs_i, found_i); } else { // no match found so we have to append this bootstrap specifier to *merge_cp_p - append_operand(scratch_cp, old_bs_i, merge_cp_p, merge_cp_length_p); - new_bs_i = _operands_cur_length - 1; + new_bs_i = append_bsm_entry(scratch_cp, old_bs_i, merge_cp_p, merge_cp_length_p); } } return new_bs_i; -} // end find_or_append_operand() +} // end find_or_append_bsm_entry() -void VM_RedefineClasses::finalize_operands_merge(const constantPoolHandle& merge_cp, TRAPS) { - if (merge_cp->operands() == nullptr) { +void VM_RedefineClasses::finalize_bsm_entries_merge(const constantPoolHandle& merge_cp, TRAPS) { + if (merge_cp->bsm_entries().number_of_entries() == 0) { return; } - // Shrink the merge_cp operands - merge_cp->shrink_operands(_operands_cur_length, CHECK); + // Finished extending the BSMAEs + merge_cp->end_extension(_bsmae_iter, CHECK); if (log_is_enabled(Trace, redefine, class, constantpool)) { // don't want to loop unless we are tracing int count = 0; - for (int i = 1; i < _operands_index_map_p->length(); i++) { - int value = _operands_index_map_p->at(i); + for (int i = 1; i < _bsm_index_map_p->length(); i++) { + int value = _bsm_index_map_p->at(i); if (value != -1) { - log_trace(redefine, class, constantpool)("operands_index_map[%d]: old=%d new=%d", count, i, value); + log_trace(redefine, class, constantpool)("bsm_index_map[%d]: old=%d new=%d", count, i, value); count++; } } } // Clean-up - _operands_index_map_p = nullptr; - _operands_cur_length = 0; - _operands_index_map_count = 0; -} // end finalize_operands_merge() + _bsm_index_map_p = nullptr; + _bsm_index_map_count = 0; + _bsmae_iter = BSMAttributeEntries::InsertionIterator(); +} // end finalize_bsmentries_merge() // Symbol* comparator for qsort // The caller must have an active ResourceMark. @@ -1271,26 +1265,26 @@ u2 VM_RedefineClasses::find_new_index(int old_index) { // Find new bootstrap specifier index value for old bootstrap specifier index // value by searching the index map. Returns unused index (-1) if there is // no mapped value for the old bootstrap specifier index. -int VM_RedefineClasses::find_new_operand_index(int old_index) { - if (_operands_index_map_count == 0) { +int VM_RedefineClasses::find_new_bsm_index(int old_index) { + if (_bsm_index_map_count == 0) { // map is empty so nothing can be found return -1; } - if (old_index == -1 || old_index >= _operands_index_map_p->length()) { + if (old_index == -1 || old_index >= _bsm_index_map_p->length()) { // The old_index is out of range so it is not mapped. // This should not happen in regular constant pool merging use. return -1; } - int value = _operands_index_map_p->at(old_index); + int value = _bsm_index_map_p->at(old_index); if (value == -1) { // the old_index is not mapped return -1; } return value; -} // end find_new_operand_index() +} // end find_new_bsm_index() // The bug 6214132 caused the verification to fail. @@ -1561,22 +1555,15 @@ void VM_RedefineClasses::map_index(const constantPoolHandle& scratch_cp, // Map old_index to new_index as needed. -void VM_RedefineClasses::map_operand_index(int old_index, int new_index) { - if (find_new_operand_index(old_index) != -1) { - // old_index is already mapped - return; - } - +void VM_RedefineClasses::map_bsm_index(int old_index, int new_index) { if (old_index == new_index) { // no mapping is needed return; } - - _operands_index_map_p->at_put(old_index, new_index); - _operands_index_map_count++; - + _bsm_index_map_p->at_put(old_index, new_index); + _bsm_index_map_count++; log_trace(redefine, class, constantpool)("mapped bootstrap specifier at index %d to %d", old_index, new_index); -} // end map_index() +} // end map_bsm_index() // Merge old_cp and scratch_cp and return the results of the merge via @@ -1640,8 +1627,8 @@ bool VM_RedefineClasses::merge_constant_pools(const constantPoolHandle& old_cp, } } // end for each old_cp entry - ConstantPool::copy_operands(old_cp, merge_cp_p, CHECK_false); - merge_cp_p->extend_operands(scratch_cp, CHECK_false); + ConstantPool::copy_bsm_entries(old_cp, merge_cp_p, CHECK_false); + _bsmae_iter = merge_cp_p->start_extension(scratch_cp, CHECK_false); // We don't need to sanity check that *merge_cp_length_p is within // *merge_cp_p bounds since we have the minimum on-entry check above. @@ -1738,7 +1725,7 @@ bool VM_RedefineClasses::merge_constant_pools(const constantPoolHandle& old_cp, ("after pass 1b: merge_cp_len=%d, scratch_i=%d, index_map_len=%d", merge_cp_length_p, scratch_i, _index_map_count); } - finalize_operands_merge(merge_cp_p, CHECK_false); + finalize_bsm_entries_merge(merge_cp_p, CHECK_false); return true; } // end merge_constant_pools() @@ -1808,12 +1795,11 @@ jvmtiError VM_RedefineClasses::merge_cp_and_rewrite( _index_map_count = 0; _index_map_p = new intArray(scratch_cp->length(), scratch_cp->length(), -1); - _operands_cur_length = ConstantPool::operand_array_length(old_cp->operands()); - _operands_index_map_count = 0; - int operands_index_map_len = ConstantPool::operand_array_length(scratch_cp->operands()); - _operands_index_map_p = new intArray(operands_index_map_len, operands_index_map_len, -1); + _bsm_index_map_count = 0; + int bsm_data_len = scratch_cp->bsm_entries().array_length(); + _bsm_index_map_p = new intArray(bsm_data_len, bsm_data_len, -1); - // reference to the cp holder is needed for copy_operands() + // reference to the cp holder is needed for reallocating the BSM attribute merge_cp->set_pool_holder(scratch_class); bool result = merge_constant_pools(old_cp, scratch_cp, merge_cp, merge_cp_length, THREAD); @@ -3497,7 +3483,7 @@ void VM_RedefineClasses::set_new_constant_pool( smaller_cp->set_version(version); // attach klass to new constant pool - // reference to the cp holder is needed for copy_operands() + // reference to the cp holder is needed for reallocating the BSM attribute smaller_cp->set_pool_holder(scratch_class); smaller_cp->copy_fields(scratch_cp()); diff --git a/src/hotspot/share/prims/jvmtiRedefineClasses.hpp b/src/hotspot/share/prims/jvmtiRedefineClasses.hpp index d2eda1f3eede..3f1b555b175e 100644 --- a/src/hotspot/share/prims/jvmtiRedefineClasses.hpp +++ b/src/hotspot/share/prims/jvmtiRedefineClasses.hpp @@ -363,11 +363,16 @@ class VM_RedefineClasses: public VM_Operation { int _index_map_count; intArray * _index_map_p; - // _operands_index_map_count is just an optimization for knowing if - // _operands_index_map_p contains any entries. - int _operands_cur_length; - int _operands_index_map_count; - intArray * _operands_index_map_p; + // _bsm_index_map_count is just an optimization for knowing if + // _bsm_index_map_p contains any entries. + int _bsm_index_map_count; + intArray * _bsm_index_map_p; + + // After merge_constant_pools "Pass 0", the BSMAttribute entries of merge_cp_p will have been expanded to fit + // scratch_cp's BSMAttribute entries as well. + // However, the newly acquired space will not have been filled in yet. + // To append to this new space, the iterator is used. + BSMAttributeEntries::InsertionIterator _bsmae_iter; // ptr to _class_count scratch_classes InstanceKlass** _scratch_classes; @@ -429,17 +434,18 @@ class VM_RedefineClasses: public VM_Operation { // Support for constant pool merging (these routines are in alpha order): void append_entry(const constantPoolHandle& scratch_cp, int scratch_i, constantPoolHandle *merge_cp_p, int *merge_cp_length_p); - void append_operand(const constantPoolHandle& scratch_cp, int scratch_bootstrap_spec_index, + // Returns the index of the appended BSM + int append_bsm_entry(const constantPoolHandle& scratch_cp, int scratch_bootstrap_spec_index, constantPoolHandle *merge_cp_p, int *merge_cp_length_p); - void finalize_operands_merge(const constantPoolHandle& merge_cp, TRAPS); + void finalize_bsm_entries_merge(const constantPoolHandle& merge_cp, TRAPS); u2 find_or_append_indirect_entry(const constantPoolHandle& scratch_cp, int scratch_i, constantPoolHandle *merge_cp_p, int *merge_cp_length_p); - int find_or_append_operand(const constantPoolHandle& scratch_cp, int scratch_bootstrap_spec_index, + int find_or_append_bsm_entry(const constantPoolHandle& scratch_cp, int scratch_bootstrap_spec_index, constantPoolHandle *merge_cp_p, int *merge_cp_length_p); u2 find_new_index(int old_index); - int find_new_operand_index(int old_bootstrap_spec_index); + int find_new_bsm_index(int old_bootstrap_spec_index); void map_index(const constantPoolHandle& scratch_cp, int old_index, int new_index); - void map_operand_index(int old_bootstrap_spec_index, int new_bootstrap_spec_index); + void map_bsm_index(int old_bootstrap_spec_index, int new_bootstrap_spec_index); bool merge_constant_pools(const constantPoolHandle& old_cp, const constantPoolHandle& scratch_cp, constantPoolHandle& merge_cp_p, int& merge_cp_length_p, TRAPS); diff --git a/src/hotspot/share/runtime/vmStructs.cpp b/src/hotspot/share/runtime/vmStructs.cpp index d355e2c08926..5a5e1dc6bce7 100644 --- a/src/hotspot/share/runtime/vmStructs.cpp +++ b/src/hotspot/share/runtime/vmStructs.cpp @@ -56,6 +56,7 @@ #include "oops/array.hpp" #include "oops/arrayKlass.hpp" #include "oops/arrayOop.hpp" +#include "oops/bsmAttribute.hpp" #include "oops/constMethod.hpp" #include "oops/constantPool.hpp" #include "oops/cpCache.hpp" @@ -172,10 +173,12 @@ nonstatic_field(ArrayKlass, _dimension, int) \ volatile_nonstatic_field(ArrayKlass, _higher_dimension, ObjArrayKlass*) \ volatile_nonstatic_field(ArrayKlass, _lower_dimension, ArrayKlass*) \ + nonstatic_field(BSMAttributeEntries, _offsets, Array*) \ + nonstatic_field(BSMAttributeEntries, _bootstrap_methods, Array*) \ + nonstatic_field(ConstantPool, _bsm_entries, BSMAttributeEntries) \ nonstatic_field(ConstantPool, _tags, Array*) \ nonstatic_field(ConstantPool, _cache, ConstantPoolCache*) \ nonstatic_field(ConstantPool, _pool_holder, InstanceKlass*) \ - nonstatic_field(ConstantPool, _operands, Array*) \ nonstatic_field(ConstantPool, _resolved_klasses, Array*) \ nonstatic_field(ConstantPool, _length, int) \ nonstatic_field(ConstantPool, _minor_version, u2) \ @@ -743,6 +746,7 @@ unchecked_nonstatic_field(Array, _data, sizeof(int)) \ unchecked_nonstatic_field(Array, _data, sizeof(u1)) \ unchecked_nonstatic_field(Array, _data, sizeof(u2)) \ + unchecked_nonstatic_field(Array, _data, sizeof(u4)) \ unchecked_nonstatic_field(Array, _data, sizeof(Method*)) \ unchecked_nonstatic_field(Array, _data, sizeof(Klass*)) \ unchecked_nonstatic_field(Array, _data, sizeof(ResolvedFieldEntry)) \ @@ -974,6 +978,7 @@ declare_toplevel_type(volatile Metadata*) \ \ declare_toplevel_type(DataLayout) \ + declare_toplevel_type(BSMAttributeEntries) \ \ /********/ \ /* Oops */ \ diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/ConstantPool.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/ConstantPool.java index 563d9d3ac4a0..3a4ea5546a17 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/ConstantPool.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/ConstantPool.java @@ -88,8 +88,11 @@ public void update(Observable o, Object data) { private static synchronized void initialize(TypeDataBase db) throws WrongTypeException { Type type = db.lookupType("ConstantPool"); tags = type.getAddressField("_tags"); - operands = type.getAddressField("_operands"); cache = type.getAddressField("_cache"); + bsm_entries = type.getField("_bsm_entries").getOffset(); + Type bsmae_type = db.lookupType("BSMAttributeEntries"); + bsm_entries_offsets = bsmae_type.getAddressField("_offsets"); + bsm_entries_bootstrap_methods = bsmae_type.getAddressField("_bootstrap_methods"); poolHolder = new MetadataField(type.getAddressField("_pool_holder"), 0); length = new CIntField(type.getCIntegerField("_length"), 0); resolved_klasses = type.getAddressField("_resolved_klasses"); @@ -112,9 +115,11 @@ public ConstantPool(Address addr) { public boolean isConstantPool() { return true; } private static AddressField tags; - private static AddressField operands; private static AddressField cache; private static AddressField resolved_klasses; + private static long bsm_entries; // Offset in the constantpool where the Bsm_Entries are found + private static AddressField bsm_entries_offsets; + private static AddressField bsm_entries_bootstrap_methods; private static MetadataField poolHolder; private static CIntField length; // number of elements in oop private static CIntField majorVersion; @@ -130,10 +135,6 @@ public ConstantPool(Address addr) { private static int INDY_ARGV_OFFSET; public U1Array getTags() { return new U1Array(tags.getValue(getAddress())); } - public U2Array getOperands() { - Address addr = operands.getValue(getAddress()); - return VMObjectFactory.newObject(U2Array.class, addr); - } public ConstantPoolCache getCache() { Address addr = cache.getValue(getAddress()); return VMObjectFactory.newObject(ConstantPoolCache.class, addr); @@ -435,26 +436,23 @@ public int getMethodTypeIndexAt(int i) { return res; } + private U4Array getOffsets() { + Address a = getAddress().addOffsetTo(bsm_entries); + if (a == null) return null; + a = bsm_entries_offsets.getValue(a); + return VMObjectFactory.newObject(U4Array.class, a); + } + private U2Array getBootstrapMethods() { + Address a = getAddress().addOffsetTo(bsm_entries); + if (a == null) return null; + return VMObjectFactory.newObject(U2Array.class, bsm_entries_bootstrap_methods.getValue(a)); + } + public int getBootstrapMethodsCount() { - U2Array operands = getOperands(); + U4Array offsets = getOffsets(); int count = 0; - if (operands != null) { - // Operands array consists of two parts. First part is an array of 32-bit values which denote - // index of the bootstrap method data in the operands array. Note that elements of operands array are of type short. - // So each element of first part occupies two slots in the array. - // Second part is the bootstrap methods data. - // This layout allows us to get BSM count by getting the index of first BSM and dividing it by 2. - // - // The example below shows layout of operands array with 3 bootstrap methods. - // First part has 3 32-bit values indicating the index of the respective bootstrap methods in - // the operands array. - // The first BSM is at index 6. So the count in this case is 6/2=3. - // - // <-----first part----><-------second part-------> - // index: 0 2 4 6 i2 i3 - // operands: | 6 | i2 | i3 | bsm1 | bsm2 | bsm3 | - // - count = getOperandOffsetAt(operands, 0) / 2; + if (offsets != null) { + count = offsets.length(); } if (DEBUG) { System.err.println("ConstantPool.getBootstrapMethodsCount: count = " + count); @@ -463,12 +461,12 @@ public int getBootstrapMethodsCount() { } public int getBootstrapMethodArgsCount(int bsmIndex) { - U2Array operands = getOperands(); + U4Array offs = getOffsets(); + U2Array bsms = getBootstrapMethods(); if (Assert.ASSERTS_ENABLED) { - Assert.that(operands != null, "Operands is not present"); + Assert.that(offs != null && bsms != null, "BSM attribute is not present"); } - int bsmOffset = getOperandOffsetAt(operands, bsmIndex); - int argc = operands.at(bsmOffset + INDY_ARGC_OFFSET); + int argc = bsms.at(offs.at(bsmIndex) + INDY_ARGC_OFFSET); if (DEBUG) { System.err.println("ConstantPool.getBootstrapMethodArgsCount: bsm index = " + bsmIndex + ", args count = " + argc); } @@ -476,15 +474,16 @@ public int getBootstrapMethodArgsCount(int bsmIndex) { } public short[] getBootstrapMethodAt(int bsmIndex) { - U2Array operands = getOperands(); - if (operands == null) return null; // safety first - int basePos = getOperandOffsetAt(operands, bsmIndex); + U4Array offs = getOffsets(); + U2Array bsms = getBootstrapMethods(); + if (offs == null || bsms == null) return null; // safety first + int basePos = offs.at(bsmIndex); int argv = basePos + INDY_ARGV_OFFSET; - int argc = operands.at(basePos + INDY_ARGC_OFFSET); + int argc = getBootstrapMethodArgsCount(bsmIndex); int endPos = argv + argc; short[] values = new short[endPos - basePos]; for (int j = 0; j < values.length; j++) { - values[j] = operands.at(basePos+j); + values[j] = bsms.at(basePos+j); } return values; } @@ -773,8 +772,7 @@ private static int extractLowShortFromInt(int val) { // Return the offset of the requested Bootstrap Method in the operands array private int getOperandOffsetAt(U2Array operands, int bsmIndex) { - return VM.getVM().buildIntFromShorts(operands.at(bsmIndex * 2), - operands.at(bsmIndex * 2 + 1)); + return 0; } } diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/U4Array.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/U4Array.java new file mode 100644 index 000000000000..9836614d2c96 --- /dev/null +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/U4Array.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. + * + */ + +package sun.jvm.hotspot.utilities; + +import sun.jvm.hotspot.debugger.Address; +import sun.jvm.hotspot.runtime.VM; +import sun.jvm.hotspot.types.Type; +import sun.jvm.hotspot.types.TypeDataBase; +import sun.jvm.hotspot.types.WrongTypeException; +import sun.jvm.hotspot.utilities.Observable; +import sun.jvm.hotspot.utilities.Observer; + +public class U4Array extends GenericArray { + static { + VM.registerVMInitializedObserver(new Observer() { + public void update(Observable o, Object data) { + initialize(VM.getVM().getTypeDataBase()); + } + }); + } + + private static synchronized void initialize(TypeDataBase db) throws WrongTypeException { + elemType = db.lookupType("u4"); + Type type = db.lookupType("Array"); + dataFieldOffset = type.getAddressField("_data").getOffset(); + } + + private static long dataFieldOffset; + protected static Type elemType; + + public U4Array(Address addr) { + super(addr, dataFieldOffset); + } + + public int at(int i) { + return (int)getIntegerAt(i); + } + + public Type getElemType() { + return elemType; + } +} From 83e90e7d76e9ca804384d47a4d529e91f79d8981 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 3 Aug 2026 16:29:46 +0000 Subject: [PATCH 83/86] 8314323: Implement JEP 527: TLS 1.3 Hybrid Key Exchange Reviewed-by: stooke, simonis, andrew, sgehwolf Backport-of: 21dc41f744edd138e77970d4e25e3a7eda41621f --- .../classes/sun/security/ssl/DHasKEM.java | 254 ++++++++++ .../classes/sun/security/ssl/Hybrid.java | 474 ++++++++++++++++++ .../sun/security/ssl/HybridProvider.java | 130 +++++ .../sun/security/ssl/KAKeyDerivation.java | 127 ++++- .../sun/security/ssl/KEMKeyExchange.java | 223 ++++++++ .../sun/security/ssl/KeyShareExtension.java | 113 +++-- .../classes/sun/security/ssl/NamedGroup.java | 131 ++++- .../sun/security/ssl/SSLKeyExchange.java | 6 +- .../classes/sun/security/ssl/ServerHello.java | 51 +- .../classes/sun/security/x509/X509Key.java | 4 + .../net/ssl/SSLParameters/NamedGroups.java | 57 ++- .../javax/net/ssl/TLSCommon/NamedGroup.java | 8 +- .../net/ssl/TLSv13/ClientHelloKeyShares.java | 15 +- .../javax/net/ssl/TLSv13/HRRKeyShares.java | 25 +- .../security/pkcs11/tls/fips/FipsModeTLS.java | 8 +- .../ssl/CipherSuite/DisabledCurve.java | 49 +- .../NamedGroupsWithCipherSuite.java | 76 ++- .../ssl/CipherSuite/RestrictNamedGroup.java | 7 +- .../ssl/CipherSuite/SupportedGroups.java | 32 +- .../bench/java/security/SSLHandshake.java | 25 +- .../bench/javax/crypto/full/KEMBench.java | 110 +++- .../crypto/full/KeyPairGeneratorBench.java | 34 +- 22 files changed, 1839 insertions(+), 120 deletions(-) create mode 100644 src/java.base/share/classes/sun/security/ssl/DHasKEM.java create mode 100644 src/java.base/share/classes/sun/security/ssl/Hybrid.java create mode 100644 src/java.base/share/classes/sun/security/ssl/HybridProvider.java create mode 100644 src/java.base/share/classes/sun/security/ssl/KEMKeyExchange.java diff --git a/src/java.base/share/classes/sun/security/ssl/DHasKEM.java b/src/java.base/share/classes/sun/security/ssl/DHasKEM.java new file mode 100644 index 000000000000..763013f280c2 --- /dev/null +++ b/src/java.base/share/classes/sun/security/ssl/DHasKEM.java @@ -0,0 +1,254 @@ +/* + * 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.ssl; + +import sun.security.util.ArrayUtil; +import sun.security.util.CurveDB; +import sun.security.util.ECUtil; +import sun.security.util.NamedCurve; + +import javax.crypto.DecapsulateException; +import javax.crypto.KEM; +import javax.crypto.KEMSpi; +import javax.crypto.KeyAgreement; +import javax.crypto.SecretKey; +import java.io.IOException; +import java.math.BigInteger; +import java.security.*; +import java.security.interfaces.ECKey; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.XECKey; +import java.security.interfaces.XECPublicKey; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.security.spec.NamedParameterSpec; +import java.security.spec.XECPublicKeySpec; +import java.util.Arrays; + +/** + * The DHasKEM class presents a KEM abstraction layer over traditional + * DH-based key exchange, which can be used for either straight + * ECDH/XDH or TLS hybrid key exchanges. + * + * This class can be alongside standard full post-quantum KEMs + * when hybrid implementations are required. + */ +public class DHasKEM implements KEMSpi { + + @Override + public EncapsulatorSpi engineNewEncapsulator( + PublicKey publicKey, AlgorithmParameterSpec spec, + SecureRandom secureRandom) throws InvalidKeyException { + return new Handler(publicKey, null, secureRandom); + } + + @Override + public DecapsulatorSpi engineNewDecapsulator(PrivateKey privateKey, + AlgorithmParameterSpec spec) throws InvalidKeyException { + return new Handler(null, privateKey, null); + } + + private static final class Handler + implements KEMSpi.EncapsulatorSpi, KEMSpi.DecapsulatorSpi { + private final PublicKey pkR; + private final PrivateKey skR; + private final SecureRandom sr; + private final Params params; + + Handler(PublicKey pk, PrivateKey sk, SecureRandom sr) + throws InvalidKeyException { + this.pkR = pk; + this.skR = sk; + this.sr = sr; + this.params = paramsFromKey(pk == null ? sk : pk); + } + + @Override + public KEM.Encapsulated engineEncapsulate(int from, int to, + String algorithm) { + KeyPair kpE = params.generateKeyPair(sr); + PrivateKey skE = kpE.getPrivate(); + PublicKey pkE = kpE.getPublic(); + byte[] pkEm = params.SerializePublicKey(pkE); + try { + SecretKey dh = params.DH(algorithm, skE, pkR); + return new KEM.Encapsulated( + sub(dh, from, to), + pkEm, null); + } catch (Exception e) { + throw new ProviderException("internal error", e); + } + } + + @Override + public int engineSecretSize() { + return params.secretLen; + } + + @Override + public int engineEncapsulationSize() { + return params.publicKeyLen; + } + + @Override + public SecretKey engineDecapsulate(byte[] encapsulation, int from, + int to, String algorithm) throws DecapsulateException { + if (encapsulation.length != params.publicKeyLen) { + throw new DecapsulateException("incorrect encapsulation size"); + } + try { + PublicKey pkE = params.DeserializePublicKey(encapsulation); + SecretKey dh = params.DH(algorithm, skR, pkE); + return sub(dh, from, to); + } catch (IOException | InvalidKeyException e) { + throw new DecapsulateException("Cannot decapsulate", e); + } catch (Exception e) { + throw new ProviderException("internal error", e); + } + } + + private SecretKey sub(SecretKey key, int from, int to) { + if (from == 0 && to == params.secretLen) { + return key; + } + + // Key slicing should never happen. Otherwise, there might be + // a programming error. + throw new AssertionError( + "Unexpected key slicing: from=" + from + ", to=" + to); + } + + // This KEM is designed to be able to represent every ECDH and XDH + private Params paramsFromKey(Key k) throws InvalidKeyException { + if (k instanceof ECKey eckey) { + if (ECUtil.equals(eckey.getParams(), CurveDB.P_256)) { + return Params.P256; + } else if (ECUtil.equals(eckey.getParams(), CurveDB.P_384)) { + return Params.P384; + } else if (ECUtil.equals(eckey.getParams(), CurveDB.P_521)) { + return Params.P521; + } + } else if (k instanceof XECKey xkey + && xkey.getParams() instanceof NamedParameterSpec ns) { + if (ns.getName().equalsIgnoreCase( + NamedParameterSpec.X25519.getName())) { + return Params.X25519; + } else if (ns.getName().equalsIgnoreCase( + NamedParameterSpec.X448.getName())) { + return Params.X448; + } + } + throw new InvalidKeyException("Unsupported key"); + } + } + + private enum Params { + + P256(32, 2 * 32 + 1, + "ECDH", "EC", CurveDB.P_256), + + P384(48, 2 * 48 + 1, + "ECDH", "EC", CurveDB.P_384), + + P521(66, 2 * 66 + 1, + "ECDH", "EC", CurveDB.P_521), + + X25519(32, 32, + "XDH", "XDH", NamedParameterSpec.X25519), + + X448(56, 56, + "XDH", "XDH", NamedParameterSpec.X448); + + private final int secretLen; + private final int publicKeyLen; + private final String kaAlgorithm; + private final String keyAlgorithm; + private final AlgorithmParameterSpec spec; + + Params(int secretLen, int publicKeyLen, String kaAlgorithm, + String keyAlgorithm, AlgorithmParameterSpec spec) { + this.spec = spec; + this.secretLen = secretLen; + this.publicKeyLen = publicKeyLen; + this.kaAlgorithm = kaAlgorithm; + this.keyAlgorithm = keyAlgorithm; + } + + private boolean isEC() { + return this == P256 || this == P384 || this == P521; + } + + private KeyPair generateKeyPair(SecureRandom sr) { + try { + KeyPairGenerator g = KeyPairGenerator.getInstance(keyAlgorithm); + g.initialize(spec, sr); + return g.generateKeyPair(); + } catch (Exception e) { + throw new ProviderException("internal error", e); + } + } + + 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, publicKeyLen); + } + } + + private PublicKey DeserializePublicKey(byte[] data) throws + IOException, NoSuchAlgorithmException, + InvalidKeySpecException { + KeySpec keySpec; + if (isEC()) { + NamedCurve curve = (NamedCurve) this.spec; + keySpec = new ECPublicKeySpec( + ECUtil.decodePoint(data, curve.getCurve()), curve); + } else { + data = data.clone(); + ArrayUtil.reverse(data); + keySpec = new XECPublicKeySpec( + this.spec, new BigInteger(1, data)); + } + return KeyFactory.getInstance(keyAlgorithm). + generatePublic(keySpec); + } + + private SecretKey DH(String alg, PrivateKey skE, PublicKey pkR) + throws NoSuchAlgorithmException, InvalidKeyException { + KeyAgreement ka = KeyAgreement.getInstance(kaAlgorithm); + ka.init(skE); + ka.doPhase(pkR, true); + return ka.generateSecret(alg); + } + } +} diff --git a/src/java.base/share/classes/sun/security/ssl/Hybrid.java b/src/java.base/share/classes/sun/security/ssl/Hybrid.java new file mode 100644 index 000000000000..e3e2cfa0b238 --- /dev/null +++ b/src/java.base/share/classes/sun/security/ssl/Hybrid.java @@ -0,0 +1,474 @@ +/* + * 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.ssl; + +import sun.security.util.ArrayUtil; +import sun.security.util.CurveDB; +import sun.security.util.ECUtil; +import sun.security.util.RawKeySpec; +import sun.security.x509.X509Key; + +import javax.crypto.DecapsulateException; +import javax.crypto.KEM; +import javax.crypto.KEMSpi; +import javax.crypto.SecretKey; +import java.math.BigInteger; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.KeyFactory; +import java.security.KeyFactorySpi; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyPairGeneratorSpi; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.ProviderException; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.spec.*; +import java.util.Arrays; +import java.util.Locale; + +// The Hybrid class wraps two underlying algorithms (left and right sides) +// in a single TLS hybrid named group. +// It implements: +// - Hybrid KeyPair generation +// - Hybrid KeyFactory for decoding concatenated hybrid public keys +// - Hybrid KEM implementation for performing encapsulation and +// decapsulation over two underlying algorithms (traditional +// algorithm and post-quantum KEM algorithm) + +public class Hybrid { + + public static final NamedParameterSpec X25519_MLKEM768 = + new NamedParameterSpec("X25519MLKEM768"); + + public static final NamedParameterSpec SECP256R1_MLKEM768 = + new NamedParameterSpec("SecP256r1MLKEM768"); + + public static final NamedParameterSpec SECP384R1_MLKEM1024 = + new NamedParameterSpec("SecP384r1MLKEM1024"); + + private static AlgorithmParameterSpec getSpec(String name) { + if (name.startsWith("secp")) { + return new ECGenParameterSpec(name); + } else { + return new NamedParameterSpec(name); + } + } + + private static KeyPairGenerator getKeyPairGenerator(String name) throws + NoSuchAlgorithmException { + if (name.startsWith("secp")) { + name = "EC"; + } + return KeyPairGenerator.getInstance(name); + } + + private static KeyFactory getKeyFactory(String name) throws + NoSuchAlgorithmException { + if (name.startsWith("secp")) { + name = "EC"; + } + return KeyFactory.getInstance(name); + } + + /** + * Returns a KEM instance for each side of the hybrid algorithm. + * For traditional key exchange algorithms, we use the DH-based KEM + * implementation provided by DHasKEM class. + * For ML-KEM post-quantum algorithms, we obtain a KEM instance + * with "ML-KEM". This is done to work with 3rd-party providers that + * only have "ML-KEM" KEM algorithm. + */ + private static KEM getKEM(String name) throws NoSuchAlgorithmException { + if (name.startsWith("secp") || name.equals("X25519")) { + return KEM.getInstance("DH", HybridProvider.PROVIDER); + } else { + return KEM.getInstance("ML-KEM"); + } + } + + public static class KeyPairGeneratorImpl extends KeyPairGeneratorSpi { + private final KeyPairGenerator left; + private final KeyPairGenerator right; + private final AlgorithmParameterSpec leftSpec; + private final AlgorithmParameterSpec rightSpec; + + public KeyPairGeneratorImpl(String leftAlg, String rightAlg) + throws NoSuchAlgorithmException { + left = getKeyPairGenerator(leftAlg); + right = getKeyPairGenerator(rightAlg); + leftSpec = getSpec(leftAlg); + rightSpec = getSpec(rightAlg); + } + + @Override + public void initialize(AlgorithmParameterSpec params, + SecureRandom random) + throws InvalidAlgorithmParameterException { + left.initialize(leftSpec, random); + right.initialize(rightSpec, random); + } + + @Override + public void initialize(int keysize, SecureRandom random) { + // NO-OP (do nothing) + } + + @Override + public KeyPair generateKeyPair() { + var kp1 = left.generateKeyPair(); + var kp2 = right.generateKeyPair(); + return new KeyPair( + new PublicKeyImpl("Hybrid", kp1.getPublic(), + kp2.getPublic()), + new PrivateKeyImpl("Hybrid", kp1.getPrivate(), + kp2.getPrivate())); + } + } + + public static class KeyFactoryImpl extends KeyFactorySpi { + private final KeyFactory left; + private final KeyFactory right; + private final int leftlen; + private final String leftname; + private final String rightname; + + public KeyFactoryImpl(String left, String right) + throws NoSuchAlgorithmException { + this.left = getKeyFactory(left); + this.right = getKeyFactory(right); + this.leftlen = leftPublicLength(left); + this.leftname = left; + this.rightname = right; + } + + @Override + protected PublicKey engineGeneratePublic(KeySpec keySpec) + throws InvalidKeySpecException { + if (keySpec == null) { + throw new InvalidKeySpecException("keySpec must not be null"); + } + + if (keySpec instanceof RawKeySpec rks) { + byte[] key = rks.getKeyArr(); + if (key == null) { + throw new InvalidKeySpecException( + "RawkeySpec contains null key data"); + } + if (key.length <= leftlen) { + throw new InvalidKeySpecException( + "Hybrid key length " + key.length + + " is too short and its left key length is " + + leftlen); + } + + byte[] leftKeyBytes = Arrays.copyOfRange(key, 0, leftlen); + byte[] rightKeyBytes = Arrays.copyOfRange(key, leftlen, + key.length); + PublicKey leftKey, rightKey; + + try { + if (leftname.startsWith("secp")) { + var curve = CurveDB.lookup(leftname); + var ecSpec = new ECPublicKeySpec( + ECUtil.decodePoint(leftKeyBytes, + curve.getCurve()), curve); + leftKey = left.generatePublic(ecSpec); + } else if (leftname.startsWith("ML-KEM")) { + leftKey = left.generatePublic(new RawKeySpec( + leftKeyBytes)); + } else { + throw new InvalidKeySpecException("Unsupported left" + + " algorithm" + leftname); + } + + if (rightname.equals("X25519")) { + ArrayUtil.reverse(rightKeyBytes); + var xecSpec = new XECPublicKeySpec( + new NamedParameterSpec(rightname), + new BigInteger(1, rightKeyBytes)); + rightKey = right.generatePublic(xecSpec); + } else if (rightname.startsWith("ML-KEM")) { + rightKey = right.generatePublic(new RawKeySpec( + rightKeyBytes)); + } else { + throw new InvalidKeySpecException("Unsupported right" + + " algorithm: " + rightname); + } + + return new PublicKeyImpl("Hybrid", leftKey, rightKey); + } catch (Exception e) { + throw new InvalidKeySpecException("Failed to decode " + + "hybrid key", e); + } + } + + throw new InvalidKeySpecException( + "KeySpec type:" + + keySpec.getClass().getName() + " not supported"); + } + + private static int leftPublicLength(String name) { + return switch (name.toLowerCase(Locale.ROOT)) { + case "secp256r1" -> 65; + case "secp384r1" -> 97; + case "ml-kem-768" -> 1184; + default -> throw new IllegalArgumentException( + "Unknown named group: " + name); + }; + } + + @Override + protected PrivateKey engineGeneratePrivate(KeySpec keySpec) throws + InvalidKeySpecException { + throw new UnsupportedOperationException(); + } + + @Override + protected T engineGetKeySpec(Key key, + Class keySpec) throws InvalidKeySpecException { + throw new UnsupportedOperationException(); + } + + @Override + protected Key engineTranslateKey(Key key) throws InvalidKeyException { + throw new UnsupportedOperationException(); + } + } + + public static class KEMImpl implements KEMSpi { + private final KEM left; + private final KEM right; + + public KEMImpl(String left, String right) + throws NoSuchAlgorithmException { + this.left = getKEM(left); + this.right = getKEM(right); + } + + @Override + public EncapsulatorSpi engineNewEncapsulator(PublicKey publicKey, + AlgorithmParameterSpec spec, SecureRandom secureRandom) throws + InvalidAlgorithmParameterException, InvalidKeyException { + if (publicKey instanceof PublicKeyImpl pk) { + return new Handler(left.newEncapsulator(pk.left, secureRandom), + right.newEncapsulator(pk.right, secureRandom), + null, null); + } + throw new InvalidKeyException(); + } + + @Override + public DecapsulatorSpi engineNewDecapsulator(PrivateKey privateKey, + AlgorithmParameterSpec spec) + throws InvalidAlgorithmParameterException, InvalidKeyException { + if (privateKey instanceof PrivateKeyImpl pk) { + return new Handler(null, null, left.newDecapsulator(pk.left), + right.newDecapsulator(pk.right)); + } + throw new InvalidKeyException(); + } + } + + private static byte[] concat(byte[]... inputs) { + int outLen = 0; + for (byte[] in : inputs) { + outLen += in.length; + } + byte[] out = new byte[outLen]; + int pos = 0; + for (byte[] in : inputs) { + System.arraycopy(in, 0, out, pos, in.length); + pos += in.length; + } + return out; + } + + private record Handler(KEM.Encapsulator le, KEM.Encapsulator re, + KEM.Decapsulator ld, KEM.Decapsulator rd) + implements KEMSpi.EncapsulatorSpi, KEMSpi.DecapsulatorSpi { + @Override + public KEM.Encapsulated engineEncapsulate(int from, int to, + String algorithm) { + int expectedSecretSize = engineSecretSize(); + if (!(from == 0 && to == expectedSecretSize)) { + throw new IllegalArgumentException( + "Invalid range for encapsulation: from = " + from + + " to = " + to + ", expected total secret size = " + + expectedSecretSize); + } + + var left = le.encapsulate(); + var right = re.encapsulate(); + return new KEM.Encapsulated( + new SecretKeyImpl(left.key(), right.key()), + concat(left.encapsulation(), right.encapsulation()), + null); + } + + @Override + public int engineSecretSize() { + if (le != null) { + return le.secretSize() + re.secretSize(); + } else { + return ld.secretSize() + rd.secretSize(); + } + } + + @Override + public int engineEncapsulationSize() { + if (le != null) { + return le.encapsulationSize() + re.encapsulationSize(); + } else { + return ld.encapsulationSize() + rd.encapsulationSize(); + } + } + + @Override + public SecretKey engineDecapsulate(byte[] encapsulation, int from, + int to, String algorithm) throws DecapsulateException { + int expectedEncSize = engineEncapsulationSize(); + if (encapsulation.length != expectedEncSize) { + throw new IllegalArgumentException( + "Invalid key encapsulation message length: " + + encapsulation.length + + ", expected = " + expectedEncSize); + } + + int expectedSecretSize = engineSecretSize(); + if (!(from == 0 && to == expectedSecretSize)) { + throw new IllegalArgumentException( + "Invalid range for decapsulation: from = " + from + + " to = " + to + ", expected total secret size = " + + expectedSecretSize); + } + + var left = Arrays.copyOf(encapsulation, ld.encapsulationSize()); + var right = Arrays.copyOfRange(encapsulation, + ld.encapsulationSize(), encapsulation.length); + return new SecretKeyImpl( + ld.decapsulate(left), + rd.decapsulate(right) + ); + } + } + + // Package-private + record SecretKeyImpl(SecretKey k1, SecretKey k2) + implements SecretKey { + @Override + public String getAlgorithm() { + return "Generic"; + } + + @Override + public String getFormat() { + return null; + } + + @Override + public byte[] getEncoded() { + return null; + } + } + + /** + * Hybrid public key combines two underlying public keys (left and right). + * Public keys can be transmitted/encoded because the hybrid protocol + * requires the public component to be sent. + */ + // Package-private + record PublicKeyImpl(String algorithm, PublicKey left, + PublicKey right) implements PublicKey { + @Override + public String getAlgorithm() { + return algorithm; + } + + // getFormat() returns "RAW" as hybrid key uses RAW concatenation + // of underlying encodings. + @Override + public String getFormat() { + return "RAW"; + } + + // getEncoded() returns the concatenation of the encoded bytes of the + // left and right public keys. + @Override + public byte[] getEncoded() { + return concat(onlyKey(left), onlyKey(right)); + } + + static byte[] onlyKey(PublicKey key) { + if (key instanceof X509Key xk) { + return xk.getKeyAsBytes(); + } + + // Fallback for 3rd-party providers + if (!"X.509".equalsIgnoreCase(key.getFormat())) { + throw new ProviderException("Invalid public key encoding " + + "format"); + } + var xk = new X509Key(); + try { + xk.decode(key.getEncoded()); + } catch (InvalidKeyException e) { + throw new ProviderException("Invalid public key encoding", e); + } + return xk.getKeyAsBytes(); + } + } + + /** + * Hybrid private key combines two underlying private keys (left and right). + * It is for internal use only. The private keys should never be exported. + */ + private record PrivateKeyImpl(String algorithm, PrivateKey left, + PrivateKey right) implements PrivateKey { + + @Override + public String getAlgorithm() { + return algorithm; + } + + // getFormat() returns null because there is no standard + // format for a hybrid private key. + @Override + public String getFormat() { + return null; + } + + // getEncoded() returns an empty byte array because there is no + // standard encoding format for a hybrid private key. + @Override + public byte[] getEncoded() { + return null; + } + } +} diff --git a/src/java.base/share/classes/sun/security/ssl/HybridProvider.java b/src/java.base/share/classes/sun/security/ssl/HybridProvider.java new file mode 100644 index 000000000000..c77d6f662731 --- /dev/null +++ b/src/java.base/share/classes/sun/security/ssl/HybridProvider.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. 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.ssl; + +import java.security.Provider; +import java.security.NoSuchAlgorithmException; +import java.util.List; +import java.util.Map; + +import static sun.security.util.SecurityConstants.PROVIDER_VER; + +// This is an internal provider used in the JSSE code for DH-as-KEM +// and Hybrid KEM support. It doesn't actually get installed in the +// system's list of security providers that is searched at runtime. +// JSSE loads this provider internally. +// It registers Hybrid KeyPairGenerator, KeyFactory, and KEM +// implementations for hybrid named groups as Provider services. + +public class HybridProvider { + + public static final Provider PROVIDER = new ProviderImpl(); + + private static final class ProviderImpl extends Provider { + @java.io.Serial + private static final long serialVersionUID = 0L; + + ProviderImpl() { + super("HybridAndDHAsKEM", PROVIDER_VER, + "Hybrid and DHAsKEM provider"); + put("KEM.DH", DHasKEM.class.getName()); + + // Hybrid KeyPairGenerator/KeyFactory/KEM + + // The order of shares in the concatenation for group name + // X25519MLKEM768 has been reversed as per the current + // draft RFC. + var attrs = Map.of("name", "X25519MLKEM768", "left", "ML-KEM-768", + "right", "X25519"); + putService(new HybridService(this, "KeyPairGenerator", + "X25519MLKEM768", + "sun.security.ssl.Hybrid$KeyPairGeneratorImpl", + null, attrs)); + putService(new HybridService(this, "KEM", + "X25519MLKEM768", + "sun.security.ssl.Hybrid$KEMImpl", + null, attrs)); + putService(new HybridService(this, "KeyFactory", + "X25519MLKEM768", + "sun.security.ssl.Hybrid$KeyFactoryImpl", + null, attrs)); + + attrs = Map.of("name", "SecP256r1MLKEM768", "left", "secp256r1", + "right", "ML-KEM-768"); + putService(new HybridService(this, "KeyPairGenerator", + "SecP256r1MLKEM768", + "sun.security.ssl.Hybrid$KeyPairGeneratorImpl", + null, attrs)); + putService(new HybridService(this, "KEM", + "SecP256r1MLKEM768", + "sun.security.ssl.Hybrid$KEMImpl", + null, attrs)); + putService(new HybridService(this, "KeyFactory", + "SecP256r1MLKEM768", + "sun.security.ssl.Hybrid$KeyFactoryImpl", + null, attrs)); + + attrs = Map.of("name", "SecP384r1MLKEM1024", "left", "secp384r1", + "right", "ML-KEM-1024"); + putService(new HybridService(this, "KeyPairGenerator", + "SecP384r1MLKEM1024", + "sun.security.ssl.Hybrid$KeyPairGeneratorImpl", + null, attrs)); + putService(new HybridService(this, "KEM", + "SecP384r1MLKEM1024", + "sun.security.ssl.Hybrid$KEMImpl", + null, attrs)); + putService(new HybridService(this, "KeyFactory", + "SecP384r1MLKEM1024", + "sun.security.ssl.Hybrid$KeyFactoryImpl", + null, attrs)); + } + } + + private static class HybridService extends Provider.Service { + + HybridService(Provider p, String type, String algo, String cn, + List aliases, Map attrs) { + super(p, type, algo, cn, aliases, attrs); + } + + @Override + public Object newInstance(Object ctrParamObj) + throws NoSuchAlgorithmException { + String type = getType(); + return switch (type) { + case "KeyPairGenerator" -> new Hybrid.KeyPairGeneratorImpl( + getAttribute("left"), getAttribute("right")); + case "KeyFactory" -> new Hybrid.KeyFactoryImpl( + getAttribute("left"), getAttribute("right")); + case "KEM" -> new Hybrid.KEMImpl( + getAttribute("left"), getAttribute("right")); + default -> throw new NoSuchAlgorithmException( + "Unexpected value: " + type); + }; + } + } +} diff --git a/src/java.base/share/classes/sun/security/ssl/KAKeyDerivation.java b/src/java.base/share/classes/sun/security/ssl/KAKeyDerivation.java index 623f83f547a8..39e82b504354 100644 --- a/src/java.base/share/classes/sun/security/ssl/KAKeyDerivation.java +++ b/src/java.base/share/classes/sun/security/ssl/KAKeyDerivation.java @@ -24,7 +24,10 @@ */ package sun.security.ssl; +import sun.security.util.RawKeySpec; + import javax.crypto.KDF; +import javax.crypto.KEM; import javax.crypto.KeyAgreement; import javax.crypto.SecretKey; import javax.crypto.spec.HKDFParameterSpec; @@ -32,9 +35,11 @@ import java.io.IOException; import java.security.GeneralSecurityException; +import java.security.KeyFactory; import java.security.PrivateKey; +import java.security.Provider; import java.security.PublicKey; -import java.security.spec.AlgorithmParameterSpec; +import java.security.SecureRandom; import sun.security.util.KeyUtil; /** @@ -46,15 +51,32 @@ public class KAKeyDerivation implements SSLKeyDerivation { private final HandshakeContext context; private final PrivateKey localPrivateKey; private final PublicKey peerPublicKey; + private final byte[] keyshare; + private final Provider provider; + // Constructor called by Key Agreement KAKeyDerivation(String algorithmName, HandshakeContext context, PrivateKey localPrivateKey, PublicKey peerPublicKey) { + this(algorithmName, null, context, localPrivateKey, + peerPublicKey, null); + } + + // When the constructor called by KEM: store the client's public key or the + // encapsulated message in keyshare. + KAKeyDerivation(String algorithmName, + NamedGroup namedGroup, + HandshakeContext context, + PrivateKey localPrivateKey, + PublicKey peerPublicKey, + byte[] keyshare) { this.algorithmName = algorithmName; this.context = context; this.localPrivateKey = localPrivateKey; this.peerPublicKey = peerPublicKey; + this.keyshare = keyshare; + this.provider = (namedGroup != null) ? namedGroup.getProvider() : null; } @Override @@ -94,22 +116,15 @@ private SecretKey t12DeriveKey() throws IOException { } } - /** - * Handle the TLSv1.3 objects, which use the HKDF algorithms. - */ - private SecretKey t13DeriveKey(String type) - throws IOException { - SecretKey sharedSecret = null; + private SecretKey deriveHandshakeSecret(String label, + SecretKey sharedSecret) + throws GeneralSecurityException, IOException { SecretKey earlySecret = null; SecretKey saltSecret = null; - try { - KeyAgreement ka = KeyAgreement.getInstance(algorithmName); - ka.init(localPrivateKey); - ka.doPhase(peerPublicKey, true); - sharedSecret = ka.generateSecret("TlsPremasterSecret"); - CipherSuite.HashAlg hashAlg = context.negotiatedCipherSuite.hashAlg; - SSLKeyDerivation kd = context.handshakeKeyDerivation; + CipherSuite.HashAlg hashAlg = context.negotiatedCipherSuite.hashAlg; + SSLKeyDerivation kd = context.handshakeKeyDerivation; + try { if (kd == null) { // No PSK is in use. // If PSK is not in use, Early Secret will still be // HKDF-Extract(0, 0). @@ -129,12 +144,90 @@ private SecretKey t13DeriveKey(String type) // the handshake secret key derivation (below) as it may not // work with the "sharedSecret" obj. KDF hkdf = KDF.getInstance(hashAlg.hkdfAlgorithm); - return hkdf.deriveKey(type, HKDFParameterSpec.ofExtract() - .addSalt(saltSecret).addIKM(sharedSecret).extractOnly()); + var spec = HKDFParameterSpec.ofExtract().addSalt(saltSecret); + if (sharedSecret instanceof Hybrid.SecretKeyImpl hsk) { + spec = spec.addIKM(hsk.k1()).addIKM(hsk.k2()); + } else { + spec = spec.addIKM(sharedSecret); + } + + return hkdf.deriveKey(label, spec.extractOnly()); + } finally { + KeyUtil.destroySecretKeys(earlySecret, saltSecret); + } + } + /** + * This method is called by the server to perform KEM encapsulation. + * It uses the client's public key (sent by the client as a keyshare) + * to encapsulate a shared secret and returns the encapsulated message. + * + * Package-private, used from KeyShareExtension.SHKeyShareProducer:: + * produce(). + */ + KEM.Encapsulated encapsulate(String algorithm, SecureRandom random) + throws IOException { + SecretKey sharedSecret = null; + + if (keyshare == null) { + throw new IOException("No keyshare available for KEM " + + "encapsulation"); + } + + try { + KeyFactory kf = (provider != null) ? + KeyFactory.getInstance(algorithmName, provider) : + KeyFactory.getInstance(algorithmName); + var pk = kf.generatePublic(new RawKeySpec(keyshare)); + + KEM kem = (provider != null) ? + KEM.getInstance(algorithmName, provider) : + KEM.getInstance(algorithmName); + KEM.Encapsulator e = kem.newEncapsulator(pk, random); + KEM.Encapsulated enc = e.encapsulate(); + sharedSecret = enc.key(); + + SecretKey derived = deriveHandshakeSecret(algorithm, sharedSecret); + + return new KEM.Encapsulated(derived, enc.encapsulation(), null); + } catch (GeneralSecurityException gse) { + throw new SSLHandshakeException("Could not generate secret", gse); + } finally { + KeyUtil.destroySecretKeys(sharedSecret); + } + } + + /** + * Handle the TLSv1.3 objects, which use the HKDF algorithms. + */ + private SecretKey t13DeriveKey(String type) + throws IOException { + SecretKey sharedSecret = null; + + try { + if (keyshare != null) { + // Using KEM: called by the client after receiving the KEM + // ciphertext (keyshare) from the server in ServerHello. + // The client decapsulates it using its private key. + KEM kem = (provider != null) + ? KEM.getInstance(algorithmName, provider) + : KEM.getInstance(algorithmName); + var decapsulator = kem.newDecapsulator(localPrivateKey); + sharedSecret = decapsulator.decapsulate( + keyshare, 0, decapsulator.secretSize(), + "TlsPremasterSecret"); + } else { + // Using traditional DH-style Key Agreement + KeyAgreement ka = KeyAgreement.getInstance(algorithmName); + ka.init(localPrivateKey); + ka.doPhase(peerPublicKey, true); + sharedSecret = ka.generateSecret("TlsPremasterSecret"); + } + + return deriveHandshakeSecret(type, sharedSecret); } catch (GeneralSecurityException gse) { throw new SSLHandshakeException("Could not generate secret", gse); } finally { - KeyUtil.destroySecretKeys(sharedSecret, earlySecret, saltSecret); + KeyUtil.destroySecretKeys(sharedSecret); } } } diff --git a/src/java.base/share/classes/sun/security/ssl/KEMKeyExchange.java b/src/java.base/share/classes/sun/security/ssl/KEMKeyExchange.java new file mode 100644 index 000000000000..fb8de6cb104e --- /dev/null +++ b/src/java.base/share/classes/sun/security/ssl/KEMKeyExchange.java @@ -0,0 +1,223 @@ +/* + * 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.ssl; + +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.ProviderException; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.spec.NamedParameterSpec; +import javax.crypto.SecretKey; + +import sun.security.ssl.NamedGroup.NamedGroupSpec; +import sun.security.x509.X509Key; + +/** + * Specifics for single or hybrid Key exchanges based on KEM + */ +final class KEMKeyExchange { + + static final SSLKeyAgreementGenerator kemKAGenerator + = new KEMKAGenerator(); + + static final class KEMCredentials implements NamedGroupCredentials { + + final NamedGroup namedGroup; + // Unlike other credentials, we directly store the key share + // value here, no need to convert to a key + private final byte[] keyshare; + + KEMCredentials(byte[] keyshare, NamedGroup namedGroup) { + this.keyshare = keyshare; + this.namedGroup = namedGroup; + } + + // For KEM, server performs encapsulation and the resulting + // encapsulated message becomes the key_share value sent to + // the client. It is not a public key, so no PublicKey object + // to return. + @Override + public PublicKey getPublicKey() { + throw new UnsupportedOperationException( + "KEMCredentials stores raw keyshare, not a PublicKey"); + } + + public byte[] getKeyShare() { + return keyshare; + } + + @Override + public NamedGroup getNamedGroup() { + return namedGroup; + } + + /** + * Instantiates a KEMCredentials object + */ + static KEMCredentials valueOf(NamedGroup namedGroup, + byte[] encodedPoint) { + + if (namedGroup.spec != NamedGroupSpec.NAMED_GROUP_KEM) { + throw new RuntimeException( + "Credentials decoding: Not KEM named group"); + } + + if (encodedPoint == null || encodedPoint.length == 0) { + return null; + } + + return new KEMCredentials(encodedPoint, namedGroup); + } + } + + private static class KEMPossession implements SSLPossession { + private final NamedGroup namedGroup; + + public KEMPossession(NamedGroup ng) { + this.namedGroup = ng; + } + public NamedGroup getNamedGroup() { + return namedGroup; + } + } + + static final class KEMReceiverPossession extends KEMPossession { + + private final PrivateKey privateKey; + private final PublicKey publicKey; + + KEMReceiverPossession(NamedGroup namedGroup, SecureRandom random) { + super(namedGroup); + String algName = null; + try { + // For KEM: This receiver side (client) generates a key pair. + algName = ((NamedParameterSpec)namedGroup.keAlgParamSpec). + getName(); + Provider provider = namedGroup.getProvider(); + KeyPairGenerator kpg = (provider != null) ? + KeyPairGenerator.getInstance(algName, provider) : + KeyPairGenerator.getInstance(algName); + + kpg.initialize(namedGroup.keAlgParamSpec, random); + KeyPair kp = kpg.generateKeyPair(); + privateKey = kp.getPrivate(); + publicKey = kp.getPublic(); + } catch (GeneralSecurityException e) { + throw new RuntimeException( + "Could not generate keypair for algorithm: " + + algName, e); + } + } + + @Override + public byte[] encode() { + if (publicKey instanceof X509Key xk) { + return xk.getKeyAsBytes(); + } else if (publicKey instanceof Hybrid.PublicKeyImpl hk) { + return hk.getEncoded(); + } + throw new ProviderException("Unsupported key type: " + publicKey); + } + + // Package-private + PublicKey getPublicKey() { + return publicKey; + } + + // Package-private + PrivateKey getPrivateKey() { + return privateKey; + } + } + + static final class KEMSenderPossession extends KEMPossession { + + private SecretKey key; + private final SecureRandom random; + + KEMSenderPossession(NamedGroup namedGroup, SecureRandom random) { + super(namedGroup); + this.random = random; + } + + // Package-private + SecureRandom getRandom() { + return random; + } + + // Package-private + SecretKey getKey() { + return key; + } + + // Package-private + void setKey(SecretKey key) { + this.key = key; + } + + @Override + public byte[] encode() { + throw new UnsupportedOperationException("encode() not supported"); + } + } + + private static final class KEMKAGenerator + implements SSLKeyAgreementGenerator { + + // Prevent instantiation of this class. + private KEMKAGenerator() { + // blank + } + + @Override + public SSLKeyDerivation createKeyDerivation( + HandshakeContext context) throws IOException { + for (SSLPossession poss : context.handshakePossessions) { + if (poss instanceof KEMReceiverPossession kposs) { + NamedGroup ng = kposs.getNamedGroup(); + for (SSLCredentials cred : context.handshakeCredentials) { + if (cred instanceof KEMCredentials kcred && + ng.equals(kcred.namedGroup)) { + String name = ((NamedParameterSpec) + ng.keAlgParamSpec).getName(); + return new KAKeyDerivation(name, ng, context, + kposs.getPrivateKey(), null, + kcred.getKeyShare()); + } + } + } + } + context.conContext.fatal(Alert.HANDSHAKE_FAILURE, + "No suitable KEM key agreement " + + "parameters negotiated"); + return null; + } + } +} 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 98e4693e9170..ebf416669a99 100644 --- a/src/java.base/share/classes/sun/security/ssl/KeyShareExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/KeyShareExtension.java @@ -27,8 +27,11 @@ import java.io.IOException; import java.nio.ByteBuffer; +import java.security.AlgorithmConstraints; import java.security.CryptoPrimitive; import java.security.GeneralSecurityException; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.NamedParameterSpec; import java.text.MessageFormat; import java.util.*; import javax.net.ssl.SSLProtocolException; @@ -297,7 +300,9 @@ private static byte[] getShare(ClientHandshakeContext chc, // update the context chc.handshakePossessions.add(pos); // May need more possession types in the future. - if (pos instanceof NamedGroupPossession) { + if (pos instanceof NamedGroupPossession || + pos instanceof + KEMKeyExchange.KEMReceiverPossession) { return pos.encode(); } } @@ -358,24 +363,16 @@ public void consume(ConnectionContext context, try { SSLCredentials kaCred = ng.decodeCredentials(entry.keyExchange); - if (shc.algorithmConstraints != null && - kaCred instanceof - NamedGroupCredentials namedGroupCredentials) { - if (!shc.algorithmConstraints.permits( - EnumSet.of(CryptoPrimitive.KEY_AGREEMENT), - namedGroupCredentials.getPublicKey())) { - if (SSLLogger.isOn && - SSLLogger.isOn("ssl,handshake")) { - SSLLogger.warning( - "key share entry of " + ng + " does not " + - " comply with algorithm constraints"); - } - kaCred = null; + if (!isCredentialPermitted(shc.algorithmConstraints, + kaCred)) { + if (SSLLogger.isOn && + SSLLogger.isOn("ssl,handshake")) { + SSLLogger.warning( + "key share entry of " + ng + " does not " + + "comply with algorithm constraints"); } - } - - if (kaCred != null) { + } else { credentials.add(kaCred); } } catch (GeneralSecurityException ex) { @@ -513,7 +510,8 @@ private SHKeyShareProducer() { @Override public byte[] produce(ConnectionContext context, HandshakeMessage message) throws IOException { - // The producing happens in client side only. + // The producing happens in server side only. + ServerHandshakeContext shc = (ServerHandshakeContext)context; // In response to key_share request only @@ -571,7 +569,9 @@ public byte[] produce(ConnectionContext context, SSLPossession[] poses = ke.createPossessions(shc); for (SSLPossession pos : poses) { - if (!(pos instanceof NamedGroupPossession)) { + if (!(pos instanceof NamedGroupPossession || + pos instanceof + KEMKeyExchange.KEMSenderPossession)) { // May need more possession types in the future. continue; } @@ -579,7 +579,34 @@ public byte[] produce(ConnectionContext context, // update the context shc.handshakeKeyExchange = ke; shc.handshakePossessions.add(pos); - keyShare = new KeyShareEntry(ng.id, pos.encode()); + + // For KEM, perform encapsulation using the client’s public + // key (KEMCredentials). The resulting encapsulated message + // becomes the key_share value sent to the client. The + // shared secret derived from encapsulation is stored in + // the KEMSenderPossession for later use in the TLS key + // schedule. + + // SSLKeyExchange.createPossessions() returns at most one + // key-agreement possession or one KEMSenderPossession + // per handshake. + if (pos instanceof KEMKeyExchange.KEMSenderPossession xp) { + if (cd instanceof KEMKeyExchange.KEMCredentials kcred + && ng.equals(kcred.namedGroup)) { + String name = ((NamedParameterSpec) + ng.keAlgParamSpec).getName(); + KAKeyDerivation handshakeKD = new KAKeyDerivation( + name, ng, shc, null, null, + kcred.getKeyShare()); + var encaped = handshakeKD.encapsulate( + "TlsHandshakeSecret", xp.getRandom()); + xp.setKey(encaped.key()); + keyShare = new KeyShareEntry(ng.id, + encaped.encapsulation()); + } + } else { + keyShare = new KeyShareEntry(ng.id, pos.encode()); + } break; } @@ -663,19 +690,13 @@ public void consume(ConnectionContext context, try { SSLCredentials kaCred = ng.decodeCredentials(keyShare.keyExchange); - if (chc.algorithmConstraints != null && - kaCred instanceof - NamedGroupCredentials namedGroupCredentials) { - if (!chc.algorithmConstraints.permits( - EnumSet.of(CryptoPrimitive.KEY_AGREEMENT), - namedGroupCredentials.getPublicKey())) { - chc.conContext.fatal(Alert.INSUFFICIENT_SECURITY, - "key share entry of " + ng + " does not " + - " comply with algorithm constraints"); - } - } - if (kaCred != null) { + if (!isCredentialPermitted(chc.algorithmConstraints, + kaCred)) { + chc.conContext.fatal(Alert.INSUFFICIENT_SECURITY, + "key share entry of " + ng + " does not " + + "comply with algorithm constraints"); + } else { credentials = kaCred; } } catch (GeneralSecurityException ex) { @@ -696,6 +717,34 @@ public void consume(ConnectionContext context, } } + private static boolean isCredentialPermitted( + AlgorithmConstraints constraints, + SSLCredentials cred) { + + if (constraints == null) return true; + if (cred == null) return false; + + if (cred instanceof NamedGroupCredentials namedGroupCred) { + if (namedGroupCred instanceof KEMKeyExchange.KEMCredentials + kemCred) { + AlgorithmParameterSpec paramSpec = kemCred.getNamedGroup(). + keAlgParamSpec; + String algName = (paramSpec instanceof NamedParameterSpec nps) ? + nps.getName() : null; + return algName != null && constraints.permits( + EnumSet.of(CryptoPrimitive.KEY_AGREEMENT), + algName, + null); + } else { + return constraints.permits( + EnumSet.of(CryptoPrimitive.KEY_AGREEMENT), + namedGroupCred.getPublicKey()); + } + } + + return true; + } + /** * The absence processing if the extension is not present in * the ServerHello handshake message. 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 46280a053551..d1f0b5227c8d 100644 --- a/src/java.base/share/classes/sun/security/ssl/NamedGroup.java +++ b/src/java.base/share/classes/sun/security/ssl/NamedGroup.java @@ -214,6 +214,39 @@ enum NamedGroup { ProtocolVersion.PROTOCOLS_TO_13, PredefinedDHParameterSpecs.ffdheParams.get(8192)), + ML_KEM_512(0x0200, "MLKEM512", + NamedGroupSpec.NAMED_GROUP_KEM, + ProtocolVersion.PROTOCOLS_OF_13, + null), + + ML_KEM_768(0x0201, "MLKEM768", + NamedGroupSpec.NAMED_GROUP_KEM, + ProtocolVersion.PROTOCOLS_OF_13, + null), + + ML_KEM_1024(0x0202, "MLKEM1024", + NamedGroupSpec.NAMED_GROUP_KEM, + ProtocolVersion.PROTOCOLS_OF_13, + null), + + X25519MLKEM768(0x11ec, "X25519MLKEM768", + NamedGroupSpec.NAMED_GROUP_KEM, + ProtocolVersion.PROTOCOLS_OF_13, + Hybrid.X25519_MLKEM768, + HybridProvider.PROVIDER), + + SECP256R1MLKEM768(0x11eb, "SecP256r1MLKEM768", + NamedGroupSpec.NAMED_GROUP_KEM, + ProtocolVersion.PROTOCOLS_OF_13, + Hybrid.SECP256R1_MLKEM768, + HybridProvider.PROVIDER), + + SECP384R1MLKEM1024(0x11ed, "SecP384r1MLKEM1024", + NamedGroupSpec.NAMED_GROUP_KEM, + ProtocolVersion.PROTOCOLS_OF_13, + Hybrid.SECP384R1_MLKEM1024, + HybridProvider.PROVIDER), + // Elliptic Curves (RFC 4492) // // arbitrary prime and characteristic-2 curves @@ -234,22 +267,33 @@ enum NamedGroup { final AlgorithmParameterSpec keAlgParamSpec; final AlgorithmParameters keAlgParams; final boolean isAvailable; + final Provider defaultProvider; // performance optimization private static final Set KEY_AGREEMENT_PRIMITIVE_SET = Collections.unmodifiableSet(EnumSet.of(CryptoPrimitive.KEY_AGREEMENT)); - // Constructor used for all NamedGroup types NamedGroup(int id, String name, NamedGroupSpec namedGroupSpec, ProtocolVersion[] supportedProtocols, AlgorithmParameterSpec keAlgParamSpec) { + this(id, name, namedGroupSpec, supportedProtocols, keAlgParamSpec, + null); + } + + // Constructor used for all NamedGroup types + NamedGroup(int id, String name, + NamedGroupSpec namedGroupSpec, + ProtocolVersion[] supportedProtocols, + AlgorithmParameterSpec keAlgParamSpec, + Provider defaultProvider) { this.id = id; this.name = name; this.spec = namedGroupSpec; this.algorithm = namedGroupSpec.algorithm; this.supportedProtocols = supportedProtocols; this.keAlgParamSpec = keAlgParamSpec; + this.defaultProvider = defaultProvider; // Check if it is a supported named group. AlgorithmParameters algParams = null; @@ -266,16 +310,28 @@ enum NamedGroup { // Check the specific algorithm parameters. if (mediator) { try { - algParams = - AlgorithmParameters.getInstance(namedGroupSpec.algorithm); - algParams.init(keAlgParamSpec); + // Skip AlgorithmParameters for KEMs (not supported) + // Check KEM's availability via KeyFactory + if (namedGroupSpec == NamedGroupSpec.NAMED_GROUP_KEM) { + if (defaultProvider == null) { + KeyFactory.getInstance(name); + } else { + KeyFactory.getInstance(name, defaultProvider); + } + } else { + // ECDHE or others: use AlgorithmParameters as before + algParams = AlgorithmParameters.getInstance( + namedGroupSpec.algorithm); + algParams.init(keAlgParamSpec); + } } catch (InvalidParameterSpecException | NoSuchAlgorithmException exp) { if (namedGroupSpec != NamedGroupSpec.NAMED_GROUP_XDH) { mediator = false; if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( - "No AlgorithmParameters for " + name, exp); + "No AlgorithmParameters or KeyFactory for " + name, + exp); } } else { // Please remove the following code if the XDH/X25519/X448 @@ -307,6 +363,10 @@ enum NamedGroup { this.keAlgParams = mediator ? algParams : null; } + Provider getProvider() { + return defaultProvider; + } + // // The next set of methods search & retrieve NamedGroups. // @@ -545,6 +605,10 @@ SSLCredentials decodeCredentials( return spec.decodeCredentials(this, encoded); } + SSLPossession createPossession(boolean isClient, SecureRandom random) { + return spec.createPossession(this, isClient, random); + } + SSLPossession createPossession(SecureRandom random) { return spec.createPossession(this, random); } @@ -566,6 +630,11 @@ SSLCredentials decodeCredentials(NamedGroup ng, SSLKeyDerivation createKeyDerivation( HandshakeContext hc) throws IOException; + + default SSLPossession createPossession(NamedGroup ng, boolean isClient, + SecureRandom random) { + return createPossession(ng, random); + } } enum NamedGroupSpec implements NamedGroupScheme { @@ -578,6 +647,10 @@ enum NamedGroupSpec implements NamedGroupScheme { // Finite Field Groups (XDH) NAMED_GROUP_XDH("XDH", XDHScheme.instance), + // Post-Quantum Cryptography (PQC) KEM groups + // Currently used for hybrid named groups + NAMED_GROUP_KEM("KEM", KEMScheme.instance), + // arbitrary prime and curves (ECDHE) NAMED_GROUP_ARBITRARY("EC", null), @@ -634,6 +707,15 @@ public SSLCredentials decodeCredentials(NamedGroup ng, return null; } + public SSLPossession createPossession( + NamedGroup ng, boolean isClient, SecureRandom random) { + if (scheme != null) { + return scheme.createPossession(ng, isClient, random); + } + + return null; + } + @Override public SSLPossession createPossession( NamedGroup ng, SecureRandom random) { @@ -739,6 +821,42 @@ public SSLKeyDerivation createKeyDerivation( } } + private static class KEMScheme implements NamedGroupScheme { + private static final KEMScheme instance = new KEMScheme(); + + @Override + public byte[] encodePossessionPublicKey(NamedGroupPossession poss) { + return poss.encode(); + } + + @Override + public SSLCredentials decodeCredentials(NamedGroup ng, + byte[] encoded) throws IOException, GeneralSecurityException { + return KEMKeyExchange.KEMCredentials.valueOf(ng, encoded); + } + + @Override + public SSLPossession createPossession(NamedGroup ng, + SecureRandom random) { + // Must call createPossession with isClient + throw new UnsupportedOperationException(); + } + + @Override + public SSLPossession createPossession( + NamedGroup ng, boolean isClient, SecureRandom random) { + return isClient + ? new KEMKeyExchange.KEMReceiverPossession(ng, random) + : new KEMKeyExchange.KEMSenderPossession(ng, random); + } + + @Override + public SSLKeyDerivation createKeyDerivation( + HandshakeContext hc) throws IOException { + return KEMKeyExchange.kemKAGenerator.createKeyDerivation(hc); + } + } + static final class SupportedGroups { // the supported named groups, non-null immutable list static final String[] namedGroups; @@ -784,6 +902,9 @@ static final class SupportedGroups { } else { // default groups NamedGroup[] groups = new NamedGroup[] { + // Hybrid key agreement + X25519MLKEM768, + // Primary XDH (RFC 7748) curves X25519, diff --git a/src/java.base/share/classes/sun/security/ssl/SSLKeyExchange.java b/src/java.base/share/classes/sun/security/ssl/SSLKeyExchange.java index 22a44590ce3c..263308f0659f 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLKeyExchange.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLKeyExchange.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 @@ -570,7 +570,9 @@ static T13KeyAgreement valueOf(NamedGroup namedGroup) { @Override public SSLPossession createPossession(HandshakeContext hc) { - return namedGroup.createPossession(hc.sslContext.getSecureRandom()); + return namedGroup.createPossession( + hc instanceof ClientHandshakeContext, + hc.sslContext.getSecureRandom()); } @Override 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 d092d6c07ded..9fc364944f25 100644 --- a/src/java.base/share/classes/sun/security/ssl/ServerHello.java +++ b/src/java.base/share/classes/sun/security/ssl/ServerHello.java @@ -563,6 +563,34 @@ public byte[] produce(ConnectionContext context, clientHello); shc.serverHelloRandom = shm.serverRandom; + // For key derivation, we will either use the traditional Key + // Agreement (KA) model or the Key Encapsulation Mechanism (KEM) + // model, depending on what key exchange group is used. + // + // For KA flows, the server first receives the client's share, + // then generates its key share, and finally comes here. + // However, this is changed for KEM: the server + // must perform both actions — derive the secret and generate + // the key encapsulation message at the same time during + // encapsulation in SHKeyShareProducer. + // + // Traditional Key Agreement (KA): + // - Both peers generate a key share and exchange it. + // - Each peer computes a shared secret sometime after + // receiving the other's key share. + // + // Key Encapsulation Mechanism (KEM): + // The client publishes a public key via a KeyShareExtension, + // which the server uses to: + // + // - generate the shared secret + // - encapsulate the message which is sent to the client in + // another KeyShareExtension + // + // The derived shared secret must be stored in a + // KEMSenderPossession so it can be retrieved for handshake + // traffic secret derivation later. + // Produce extensions for ServerHello handshake message. SSLExtension[] serverHelloExtensions = shc.sslConfig.getEnabledExtensions( @@ -588,9 +616,26 @@ public byte[] produce(ConnectionContext context, "Not negotiated key shares"); } - SSLKeyDerivation handshakeKD = ke.createKeyDerivation(shc); - SecretKey handshakeSecret = handshakeKD.deriveKey( - "TlsHandshakeSecret"); + SecretKey handshakeSecret = null; + + // For KEM, the shared secret has already been generated and + // stored in the server’s possession (KEMSenderPossession) + // during encapsulation in SHKeyShareProducer. + // + // Only one key share is selected by the server, so at most one + // possession will contain the pre-derived shared secret. + for (var pos : shc.handshakePossessions) { + if (pos instanceof KEMKeyExchange.KEMSenderPossession xp) { + handshakeSecret = xp.getKey(); + break; + } + } + + if (handshakeSecret == null) { + SSLKeyDerivation handshakeKD = ke.createKeyDerivation(shc); + handshakeSecret = handshakeKD.deriveKey( + "TlsHandshakeSecret"); + } SSLTrafficKeyDerivation kdg = SSLTrafficKeyDerivation.valueOf(shc.negotiatedProtocol); diff --git a/src/java.base/share/classes/sun/security/x509/X509Key.java b/src/java.base/share/classes/sun/security/x509/X509Key.java index c83e06f651e8..1cfe3f9d95d7 100644 --- a/src/java.base/share/classes/sun/security/x509/X509Key.java +++ b/src/java.base/share/classes/sun/security/x509/X509Key.java @@ -104,6 +104,10 @@ public BitArray getKey() { return (BitArray)bitStringKey.clone(); } + public byte[] getKeyAsBytes() { + return bitStringKey.toByteArray(); + } + /** * Construct X.509 subject public key from a DER value. If * the runtime environment is configured with a specific class for diff --git a/test/jdk/javax/net/ssl/SSLParameters/NamedGroups.java b/test/jdk/javax/net/ssl/SSLParameters/NamedGroups.java index 25f73606b967..786b907b79a1 100644 --- a/test/jdk/javax/net/ssl/SSLParameters/NamedGroups.java +++ b/test/jdk/javax/net/ssl/SSLParameters/NamedGroups.java @@ -1,4 +1,5 @@ /* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. * Copyright (C) 2022, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,7 +27,7 @@ /* * @test - * @bug 8281236 + * @bug 8281236 8314323 * @summary Check TLS connection behaviors for named groups configuration * @library /javax/net/ssl/templates * @run main/othervm NamedGroups @@ -136,6 +137,60 @@ public static void main(String[] args) throws Exception { "secp256r1" }, true); + + runTest(new String[] { + "X25519MLKEM768" + }, + new String[] { + "X25519MLKEM768" + }, + false); + + runTest(new String[] { + "SecP256r1MLKEM768" + }, + new String[] { + "SecP256r1MLKEM768" + }, + false); + + runTest(new String[] { + "SecP384r1MLKEM1024" + }, + new String[] { + "SecP384r1MLKEM1024" + }, + false); + + runTest(new String[] { + "X25519MLKEM768" + }, + new String[] { + "SecP256r1MLKEM768" + }, + true); + + runTest(new String[] { + "X25519MLKEM768" + }, + new String[0], + true); + + runTest(new String[] { + "SecP256r1MLKEM768" + }, + null, + true); + + runTest(new String[] { + "X25519MLKEM768", + "x25519" + }, + new String[] { + "X25519MLKEM768", + "x25519" + }, + false); } private static void runTest(String[] serverNamedGroups, diff --git a/test/jdk/javax/net/ssl/TLSCommon/NamedGroup.java b/test/jdk/javax/net/ssl/TLSCommon/NamedGroup.java index ec89fe0d5b5e..432a2bd1b0db 100644 --- a/test/jdk/javax/net/ssl/TLSCommon/NamedGroup.java +++ b/test/jdk/javax/net/ssl/TLSCommon/NamedGroup.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 @@ -37,7 +37,11 @@ public enum NamedGroup { FFDHE3072("ffdhe3072"), FFDHE4096("ffdhe4096"), FFDHE6144("ffdhe6144"), - FFDHE8192("ffdhe8192"); + FFDHE8192("ffdhe8192"), + + X25519MLKEM768("X25519MLKEM768"), + SECP256R1MLKEM768("SecP256r1MLKEM768"), + SECP384R1MLKEM1024("SecP384r1MLKEM1024"); public final String name; diff --git a/test/jdk/javax/net/ssl/TLSv13/ClientHelloKeyShares.java b/test/jdk/javax/net/ssl/TLSv13/ClientHelloKeyShares.java index 56f37a9e4f15..5cefc2ad1608 100644 --- a/test/jdk/javax/net/ssl/TLSv13/ClientHelloKeyShares.java +++ b/test/jdk/javax/net/ssl/TLSv13/ClientHelloKeyShares.java @@ -27,16 +27,21 @@ /* * @test - * @bug 8247630 + * @bug 8247630 8314323 * @summary Use two key share entries - * @run main/othervm ClientHelloKeyShares 29 23 + * @run main/othervm ClientHelloKeyShares 4588 29 * @run main/othervm -Djdk.tls.namedGroups=secp384r1,secp521r1,x448,ffdhe2048 ClientHelloKeyShares 24 30 * @run main/othervm -Djdk.tls.namedGroups=sect163k1,sect163r1,x25519 ClientHelloKeyShares 29 * @run main/othervm -Djdk.tls.namedGroups=sect163k1,sect163r1,secp256r1 ClientHelloKeyShares 23 * @run main/othervm -Djdk.tls.namedGroups=sect163k1,sect163r1,ffdhe2048,ffdhe3072,ffdhe4096 ClientHelloKeyShares 256 * @run main/othervm -Djdk.tls.namedGroups=sect163k1,ffdhe2048,x25519,secp256r1 ClientHelloKeyShares 256 29 * @run main/othervm -Djdk.tls.namedGroups=secp256r1,secp384r1,ffdhe2048,x25519 ClientHelloKeyShares 23 256 - */ + * @run main/othervm -Djdk.tls.namedGroups=X25519MLKEM768 ClientHelloKeyShares 4588 + * @run main/othervm -Djdk.tls.namedGroups=x25519,X25519MLKEM768 ClientHelloKeyShares 29 4588 + * @run main/othervm -Djdk.tls.namedGroups=SecP256r1MLKEM768,x25519 ClientHelloKeyShares 4587 29 + * @run main/othervm -Djdk.tls.namedGroups=SecP384r1MLKEM1024,secp256r1 ClientHelloKeyShares 4589 23 + * @run main/othervm -Djdk.tls.namedGroups=X25519MLKEM768,SecP256r1MLKEM768,X25519,secp256r1 ClientHelloKeyShares 4588 29 +*/ import javax.net.ssl.*; import javax.net.ssl.SSLEngineResult.*; @@ -52,10 +57,6 @@ public class ClientHelloKeyShares { private static final int HELLO_EXT_SUPP_VERS = 43; private static final int HELLO_EXT_KEY_SHARE = 51; private static final int TLS_PROT_VER_13 = 0x0304; - private static final int NG_SECP256R1 = 0x0017; - private static final int NG_SECP384R1 = 0x0018; - private static final int NG_X25519 = 0x001D; - private static final int NG_X448 = 0x001E; public static void main(String args[]) throws Exception { // Arguments to this test are an abitrary number of integer diff --git a/test/jdk/javax/net/ssl/TLSv13/HRRKeyShares.java b/test/jdk/javax/net/ssl/TLSv13/HRRKeyShares.java index 313b2c5084b0..a7a3f3eb39b5 100644 --- a/test/jdk/javax/net/ssl/TLSv13/HRRKeyShares.java +++ b/test/jdk/javax/net/ssl/TLSv13/HRRKeyShares.java @@ -27,10 +27,12 @@ /* * @test - * @bug 8247630 + * @bug 8247630 8314323 * @summary Use two key share entries * @library /test/lib - * @run main/othervm -Djdk.tls.namedGroups=x25519,secp256r1,secp384r1 HRRKeyShares + * @run main/othervm + * -Djdk.tls.namedGroups=x25519,secp256r1,secp384r1,X25519MLKEM768,SecP256r1MLKEM768,SecP384r1MLKEM1024 + * HRRKeyShares */ import java.io.ByteArrayOutputStream; @@ -63,6 +65,10 @@ public class HRRKeyShares { private static final int NG_SECP384R1 = 0x0018; private static final int NG_X25519 = 0x001D; private static final int NG_X448 = 0x001E; + private static final int NG_X25519_MLKEM768 = 0x11EC; + private static final int NG_SECP256R1_MLKEM768 = 0x11EB; + private static final int NG_SECP384R1_MLKEM1024 = 0x11ED; + private static final int NG_GC512A = 0x0026; private static final int COMP_NONE = 0; private static final int ALERT_TYPE_FATAL = 2; @@ -224,6 +230,18 @@ public static void main(String args[]) throws Exception { System.out.println("Test 4: Bad HRR using known / unasserted x448"); hrrKeyShareTest(NG_X448, false); System.out.println(); + + System.out.println("Test 5: Good HRR exchange using X25519MLKEM768"); + hrrKeyShareTest(NG_X25519_MLKEM768, true); + System.out.println(); + + System.out.println("Test 6: Good HRR exchange using SecP256r1MLKEM768"); + hrrKeyShareTest(NG_SECP256R1_MLKEM768, true); + System.out.println(); + + System.out.println("Test 7: Good HRR exchange using SecP384r1MLKEM1024"); + hrrKeyShareTest(NG_SECP384R1_MLKEM1024, true); + System.out.println(); } private static void logResult(String str, SSLEngineResult result) { @@ -334,7 +352,8 @@ private static void hrrKeyShareTest(int hrrNamedGroup, boolean expectedPass) try { // Now we're expecting to reissue the ClientHello, this time - // with a secp384r1 share. + // with a key share for the HRR requested named + // group (hrrNamedGroup). cTOs.compact(); clientResult = engine.wrap(clientOut, cTOs); logResult("client wrap: ", clientResult); diff --git a/test/jdk/sun/security/pkcs11/tls/fips/FipsModeTLS.java b/test/jdk/sun/security/pkcs11/tls/fips/FipsModeTLS.java index 764754912a11..1ea1a5948f44 100644 --- a/test/jdk/sun/security/pkcs11/tls/fips/FipsModeTLS.java +++ b/test/jdk/sun/security/pkcs11/tls/fips/FipsModeTLS.java @@ -34,8 +34,12 @@ * -Djdk.tls.useExtendedMasterSecret=false * -Djdk.tls.client.enableSessionTicketExtension=false FipsModeTLS * @comment SunPKCS11 does not support (TLS1.2) SunTlsExtendedMasterSecret yet. - * Stateless resumption doesn't currently work with NSS-FIPS, see JDK-8368669 - * @run main/othervm/timeout=120 -Djdk.tls.client.protocols=TLSv1.3 FipsModeTLS + * Stateless resumption doesn't currently work with NSS-FIPS, see JDK-8368669. + * NSS-FIPS does not support ML-KEM, so configures the list of named groups. + * @run main/othervm/timeout=120 + * -Djdk.tls.client.protocols=TLSv1.3 + * -Djdk.tls.namedGroups=x25519,secp256r1,secp384r1,secp521r1,x448,ffdhe2048,ffdhe3072,ffdhe4096,ffdhe6144,ffdhe8192 + * FipsModeTLS */ import java.io.File; diff --git a/test/jdk/sun/security/ssl/CipherSuite/DisabledCurve.java b/test/jdk/sun/security/ssl/CipherSuite/DisabledCurve.java index 26304c5df957..a13f8570f148 100644 --- a/test/jdk/sun/security/ssl/CipherSuite/DisabledCurve.java +++ b/test/jdk/sun/security/ssl/CipherSuite/DisabledCurve.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 @@ -23,12 +23,24 @@ /* * @test - * @bug 8246330 + * @bug 8246330 8314323 * @library /javax/net/ssl/templates /test/lib * @run main/othervm -Djdk.tls.namedGroups="secp384r1" DisabledCurve DISABLE_NONE PASS * @run main/othervm -Djdk.tls.namedGroups="secp384r1" DisabledCurve secp384r1 FAIL + * @run main/othervm -Djdk.tls.namedGroups="X25519MLKEM768" + DisabledCurve DISABLE_NONE PASS + * @run main/othervm -Djdk.tls.namedGroups="X25519MLKEM768" + DisabledCurve X25519MLKEM768 FAIL + * @run main/othervm -Djdk.tls.namedGroups="SecP256r1MLKEM768" + DisabledCurve DISABLE_NONE PASS + * @run main/othervm -Djdk.tls.namedGroups="SecP256r1MLKEM768" + DisabledCurve SecP256r1MLKEM768 FAIL + * @run main/othervm -Djdk.tls.namedGroups="SecP384r1MLKEM1024" + DisabledCurve DISABLE_NONE PASS + * @run main/othervm -Djdk.tls.namedGroups="SecP384r1MLKEM1024" + DisabledCurve SecP384r1MLKEM1024 FAIL */ import java.security.Security; import java.util.Arrays; @@ -45,8 +57,10 @@ public class DisabledCurve extends SSLSocketTemplate { private static final String[][][] protocols = { { { "TLSv1.3", "TLSv1.2", "TLSv1.1", "TLSv1" }, { "TLSv1.2" } }, { { "TLSv1.2" }, { "TLSv1.3", "TLSv1.2", "TLSv1.1", "TLSv1" } }, - { { "TLSv1.2" }, { "TLSv1.2" } }, { { "TLSv1.1" }, { "TLSv1.1" } }, - { { "TLSv1" }, { "TLSv1" } } }; + { { "TLSv1.2" }, { "TLSv1.2" } }, + { { "TLSv1.1" }, { "TLSv1.1" } }, + { { "TLSv1" }, { "TLSv1" } }, + { { "TLSv1.3" }, { "TLSv1.3" } } }; @Override protected SSLContext createClientSSLContext() throws Exception { @@ -94,17 +108,36 @@ public static void main(String[] args) throws Exception { String expected = args[1]; String disabledName = ("DISABLE_NONE".equals(args[0]) ? "" : args[0]); boolean disabled = false; - if (disabledName.equals("")) { + + if (disabledName.isEmpty()) { Security.setProperty("jdk.disabled.namedCurves", ""); + Security.setProperty("jdk.certpath.disabledAlgorithms", ""); } else { disabled = true; - Security.setProperty("jdk.certpath.disabledAlgorithms", "secp384r1"); + Security.setProperty("jdk.certpath.disabledAlgorithms", disabledName); + if (!disabledName.contains("MLKEM")) { + Security.setProperty("jdk.disabled.namedCurves", disabledName); + } else { + Security.setProperty("jdk.disabled.namedCurves", ""); + } } // Re-enable TLSv1 and TLSv1.1 since test depends on it. SecurityUtils.removeFromDisabledTlsAlgs("TLSv1", "TLSv1.1"); + String namedGroups = System.getProperty("jdk.tls.namedGroups", ""); + boolean hybridGroup = namedGroups.contains("MLKEM"); + for (index = 0; index < protocols.length; index++) { + if (hybridGroup) { + String[] clientProtos = protocols[index][0]; + String[] serverProtos = protocols[index][1]; + + if (!(isTLS13(clientProtos) && isTLS13(serverProtos))) { + continue; + } + } + try { (new DisabledCurve()).run(); if (expected.equals("FAIL")) { @@ -123,4 +156,8 @@ public static void main(String[] args) throws Exception { } } + + private static boolean isTLS13(String[] protocols) { + return protocols.length == 1 && "TLSv1.3".equals(protocols[0]); + } } diff --git a/test/jdk/sun/security/ssl/CipherSuite/NamedGroupsWithCipherSuite.java b/test/jdk/sun/security/ssl/CipherSuite/NamedGroupsWithCipherSuite.java index 5732f42982c7..9080f5496835 100644 --- a/test/jdk/sun/security/ssl/CipherSuite/NamedGroupsWithCipherSuite.java +++ b/test/jdk/sun/security/ssl/CipherSuite/NamedGroupsWithCipherSuite.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020, 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 @@ -21,6 +21,8 @@ * questions. */ +import java.util.Arrays; +import java.util.List; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLServerSocket; import javax.net.ssl.SSLSocket; @@ -29,7 +31,7 @@ /* * @test - * @bug 8224650 8242929 + * @bug 8224650 8242929 8314323 * @library /javax/net/ssl/templates * /javax/net/ssl/TLSCommon * /test/lib @@ -44,17 +46,20 @@ * @run main/othervm NamedGroupsWithCipherSuite ffdhe4096 * @run main/othervm NamedGroupsWithCipherSuite ffdhe6144 * @run main/othervm NamedGroupsWithCipherSuite ffdhe8192 + * @run main/othervm NamedGroupsWithCipherSuite X25519MLKEM768 + * @run main/othervm NamedGroupsWithCipherSuite SecP256r1MLKEM768 + * @run main/othervm NamedGroupsWithCipherSuite SecP384r1MLKEM1024 */ public class NamedGroupsWithCipherSuite extends SSLSocketTemplate { - private static final Protocol[] PROTOCOLS = new Protocol[] { + private static final List PROTOCOLS = List.of( Protocol.TLSV1_3, Protocol.TLSV1_2, Protocol.TLSV1_1, Protocol.TLSV1 - }; + ); - private static final CipherSuite[] CIPHER_SUITES = new CipherSuite[] { + private static final List CIPHER_SUITES = List.of( CipherSuite.TLS_AES_128_GCM_SHA256, CipherSuite.TLS_AES_256_GCM_SHA384, CipherSuite.TLS_CHACHA20_POLY1305_SHA256, @@ -75,7 +80,23 @@ public class NamedGroupsWithCipherSuite extends SSLSocketTemplate { CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA, CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, CipherSuite.TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 - }; + ); + + private static final List HYBRID_NAMEDGROUPS = List.of( + "X25519MLKEM768", + "SecP256r1MLKEM768", + "SecP384r1MLKEM1024" + ); + + private static final List HYBRID_PROTOCOL = List.of( + Protocol.TLSV1_3 + ); + + private static final List HYBRID_CIPHER_SUITES = List.of( + CipherSuite.TLS_AES_128_GCM_SHA256, + CipherSuite.TLS_AES_256_GCM_SHA384, + CipherSuite.TLS_CHACHA20_POLY1305_SHA256 + ); private String protocol; private String cipher; @@ -151,48 +172,59 @@ public static void main(String[] args) throws Exception { // Re-enable TLSv1 and TLSv1.1 since test depends on it. SecurityUtils.removeFromDisabledTlsAlgs("TLSv1", "TLSv1.1"); - for (Protocol protocol : PROTOCOLS) { - for (CipherSuite cipherSuite : CIPHER_SUITES) { - // Named group converted to lower case just - // to satisfy Test condition + boolean hybridGroup = HYBRID_NAMEDGROUPS.contains(namedGroup); + List protocolList = hybridGroup ? + HYBRID_PROTOCOL : PROTOCOLS; + List cipherList = hybridGroup ? + HYBRID_CIPHER_SUITES : CIPHER_SUITES; + + // non-Hybrid named group converted to lower case just + // to satisfy Test condition + String normalizedGroup = hybridGroup ? + namedGroup : namedGroup.toLowerCase(); + + for (Protocol protocol : protocolList) { + for (CipherSuite cipherSuite : cipherList) { if (cipherSuite.supportedByProtocol(protocol) - && groupSupportdByCipher(namedGroup.toLowerCase(), - cipherSuite)) { + && groupSupportedByCipher(normalizedGroup, + cipherSuite)) { System.out.printf("Protocol: %s, cipher suite: %s%n", protocol, cipherSuite); - // Named group converted to lower case just - // to satisfy Test condition new NamedGroupsWithCipherSuite(protocol, - cipherSuite, namedGroup.toLowerCase()).run(); + cipherSuite, normalizedGroup).run(); } } } } - private static boolean groupSupportdByCipher(String group, + private static boolean groupSupportedByCipher(String group, CipherSuite cipherSuite) { + if (HYBRID_NAMEDGROUPS.contains(group)) { + return cipherSuite.keyExAlgorithm == null; + } + return (group.startsWith("x") - && xdhGroupSupportdByCipher(cipherSuite)) + && xdhGroupSupportedByCipher(cipherSuite)) || (group.startsWith("secp") - && ecdhGroupSupportdByCipher(cipherSuite)) + && ecdhGroupSupportedByCipher(cipherSuite)) || (group.startsWith("ffdhe") - && ffdhGroupSupportdByCipher(cipherSuite)); + && ffdhGroupSupportedByCipher(cipherSuite)); } - private static boolean xdhGroupSupportdByCipher( + private static boolean xdhGroupSupportedByCipher( CipherSuite cipherSuite) { return cipherSuite.keyExAlgorithm == null || cipherSuite.keyExAlgorithm == KeyExAlgorithm.ECDHE_RSA; } - private static boolean ecdhGroupSupportdByCipher( + private static boolean ecdhGroupSupportedByCipher( CipherSuite cipherSuite) { return cipherSuite.keyExAlgorithm == null || cipherSuite.keyExAlgorithm == KeyExAlgorithm.ECDHE_RSA || cipherSuite.keyExAlgorithm == KeyExAlgorithm.ECDHE_ECDSA; } - private static boolean ffdhGroupSupportdByCipher( + private static boolean ffdhGroupSupportedByCipher( CipherSuite cipherSuite) { return cipherSuite.keyExAlgorithm == null || cipherSuite.keyExAlgorithm == KeyExAlgorithm.DHE_DSS diff --git a/test/jdk/sun/security/ssl/CipherSuite/RestrictNamedGroup.java b/test/jdk/sun/security/ssl/CipherSuite/RestrictNamedGroup.java index c4c343bf84ee..4ff0c6e6e159 100644 --- a/test/jdk/sun/security/ssl/CipherSuite/RestrictNamedGroup.java +++ b/test/jdk/sun/security/ssl/CipherSuite/RestrictNamedGroup.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020, 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 @@ -23,7 +23,7 @@ /* * @test - * @bug 8226374 8242929 + * @bug 8226374 8242929 8314323 * @library /javax/net/ssl/templates * @summary Restrict signature algorithms and named groups * @run main/othervm RestrictNamedGroup x25519 @@ -36,6 +36,9 @@ * @run main/othervm RestrictNamedGroup ffdhe4096 * @run main/othervm RestrictNamedGroup ffdhe6144 * @run main/othervm RestrictNamedGroup ffdhe8192 + * @run main/othervm RestrictNamedGroup X25519MLKEM768 + * @run main/othervm RestrictNamedGroup SecP256r1MLKEM768 + * @run main/othervm RestrictNamedGroup SecP384r1MLKEM1024 */ import java.security.Security; diff --git a/test/jdk/sun/security/ssl/CipherSuite/SupportedGroups.java b/test/jdk/sun/security/ssl/CipherSuite/SupportedGroups.java index 88b0bf2489ae..8cf6ee2b5e62 100644 --- a/test/jdk/sun/security/ssl/CipherSuite/SupportedGroups.java +++ b/test/jdk/sun/security/ssl/CipherSuite/SupportedGroups.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021, 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 @@ -23,7 +23,7 @@ /* * @test - * @bug 8171279 + * @bug 8171279 8314323 * @library /javax/net/ssl/templates * @summary Test TLS connection with each individual supported group * @run main/othervm SupportedGroups x25519 @@ -36,6 +36,9 @@ * @run main/othervm SupportedGroups ffdhe4096 * @run main/othervm SupportedGroups ffdhe6144 * @run main/othervm SupportedGroups ffdhe8192 + * @run main/othervm SupportedGroups X25519MLKEM768 + * @run main/othervm SupportedGroups SecP256r1MLKEM768 + * @run main/othervm SupportedGroups SecP384r1MLKEM1024 */ import java.net.InetAddress; import java.util.Arrays; @@ -45,15 +48,24 @@ public class SupportedGroups extends SSLSocketTemplate { private static volatile int index; - private static final String[][][] protocols = { + private static final String[][][] protocolsForClassic = { {{"TLSv1.3"}, {"TLSv1.3"}}, {{"TLSv1.3", "TLSv1.2"}, {"TLSv1.2"}}, {{"TLSv1.2"}, {"TLSv1.3", "TLSv1.2"}}, {{"TLSv1.2"}, {"TLSv1.2"}} }; - public SupportedGroups() { + private static final String[][][] protocolsForHybrid = { + {{"TLSv1.3"}, {"TLSv1.3"}}, + {{"TLSv1.3", "TLSv1.2"}, {"TLSv1.3"}}, + {{"TLSv1.3"}, {"TLSv1.3", "TLSv1.2"}} + }; + + private final String[][][] protocols; + + public SupportedGroups(String[][][] protocols) { this.serverAddress = InetAddress.getLoopbackAddress(); + this.protocols = protocols; } // Servers are configured before clients, increment test case after. @@ -85,8 +97,18 @@ protected void configureServerSocket(SSLServerSocket serverSocket) { public static void main(String[] args) throws Exception { System.setProperty("jdk.tls.namedGroups", args[0]); + boolean hybridGroup = hybridNamedGroup(args[0]); + String[][][] protocols = hybridGroup ? + protocolsForHybrid : protocolsForClassic; + for (index = 0; index < protocols.length; index++) { - (new SupportedGroups()).run(); + (new SupportedGroups(protocols)).run(); } } + + private static boolean hybridNamedGroup(String namedGroup) { + return namedGroup.equals("X25519MLKEM768") || + namedGroup.equals("SecP256r1MLKEM768") || + namedGroup.equals("SecP384r1MLKEM1024"); + } } diff --git a/test/micro/org/openjdk/bench/java/security/SSLHandshake.java b/test/micro/org/openjdk/bench/java/security/SSLHandshake.java index d8773781b58c..b46704a01de7 100644 --- a/test/micro/org/openjdk/bench/java/security/SSLHandshake.java +++ b/test/micro/org/openjdk/bench/java/security/SSLHandshake.java @@ -44,6 +44,7 @@ import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLEngineResult.HandshakeStatus; +import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLSession; import javax.net.ssl.TrustManagerFactory; @@ -75,8 +76,15 @@ public class SSLHandshake { @Param({"true", "false"}) boolean resume; - @Param({"TLSv1.2", "TLS"}) - String tlsVersion; + @Param({ + "TLSv1.2-secp256r1", + "TLSv1.3-x25519", "TLSv1.3-secp256r1", "TLSv1.3-secp384r1", + "TLSv1.3-X25519MLKEM768", "TLSv1.3-SecP256r1MLKEM768", "TLSv1.3-SecP384r1MLKEM1024" + }) + String versionAndGroup; + + private String tlsVersion; + private String namedGroup; private static SSLContext getServerContext() { try { @@ -96,6 +104,10 @@ private static SSLContext getServerContext() { @Setup(Level.Trial) public void init() throws Exception { + String[] components = versionAndGroup.split("-", 2); + tlsVersion = components[0]; + namedGroup = components[1]; + KeyStore ts = TestCertificates.getTrustStore(); TrustManagerFactory tmf = TrustManagerFactory.getInstance( @@ -195,5 +207,14 @@ private void createSSLEngines() { */ clientEngine = sslClientCtx.createSSLEngine("client", 80); clientEngine.setUseClientMode(true); + + // Set the key exchange named group in client and server engines + SSLParameters clientParams = clientEngine.getSSLParameters(); + clientParams.setNamedGroups(new String[]{namedGroup}); + clientEngine.setSSLParameters(clientParams); + + SSLParameters serverParams = serverEngine.getSSLParameters(); + serverParams.setNamedGroups(new String[]{namedGroup}); + serverEngine.setSSLParameters(serverParams); } } diff --git a/test/micro/org/openjdk/bench/javax/crypto/full/KEMBench.java b/test/micro/org/openjdk/bench/javax/crypto/full/KEMBench.java index 3386039c62ea..dc6d2060f5e4 100644 --- a/test/micro/org/openjdk/bench/javax/crypto/full/KEMBench.java +++ b/test/micro/org/openjdk/bench/javax/crypto/full/KEMBench.java @@ -23,34 +23,69 @@ package org.openjdk.bench.javax.crypto.full; import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.OperationsPerInvocation; import org.openjdk.jmh.annotations.Param; import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.infra.Blackhole; import javax.crypto.DecapsulateException; +import java.security.GeneralSecurityException; +import java.security.InvalidAlgorithmParameterException; import javax.crypto.KEM; import java.security.InvalidKeyException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.Security; +import java.security.spec.ECGenParameterSpec; -public class KEMBench extends CryptoBase { +public abstract class KEMBench extends CryptoBase { public static final int SET_SIZE = 128; - @Param({"ML-KEM-512", "ML-KEM-768", "ML-KEM-1024" }) + @Param({}) private String algorithm; + @Param({""}) // Used when the KeyPairGenerator Alg != KEM Alg + private String kpgSpec; + private KeyPair[] keys; private byte[][] messages; private KEM kem; @Setup - public void setup() throws NoSuchAlgorithmException, InvalidKeyException { - kem = (prov == null) ? KEM.getInstance(algorithm) : KEM.getInstance(algorithm, prov); - KeyPairGenerator generator = (prov == null) ? KeyPairGenerator.getInstance(algorithm) : KeyPairGenerator.getInstance(algorithm, prov); + public void setup() throws NoSuchAlgorithmException, InvalidKeyException, + InvalidAlgorithmParameterException { + String kpgAlg; + String kpgParams; + kem = (prov == null) ? KEM.getInstance(algorithm) : + KEM.getInstance(algorithm, prov); + + // By default use the same provider for KEM and KPG + Provider kpgProv = prov; + if (kpgSpec.isEmpty()) { + kpgAlg = algorithm; + kpgParams = ""; + } else { + // The key pair generation spec is broken down from a colon- + // delimited string spec into 3 fields: + // [0] - the provider name + // [1] - the algorithm name + // [2] - the parameters (i.e. the name of the curve) + String[] kpgTok = kpgSpec.split(":"); + kpgProv = Security.getProvider(kpgTok[0]); + kpgAlg = kpgTok[1]; + kpgParams = kpgTok[2]; + } + KeyPairGenerator generator = (kpgProv == null) ? + KeyPairGenerator.getInstance(kpgAlg) : + KeyPairGenerator.getInstance(kpgAlg, kpgProv); + if (kpgParams != null && !kpgParams.isEmpty()) { + generator.initialize(new ECGenParameterSpec(kpgParams)); + } keys = new KeyPair[SET_SIZE]; for (int i = 0; i < keys.length; i++) { keys[i] = generator.generateKeyPair(); @@ -63,20 +98,79 @@ public void setup() throws NoSuchAlgorithmException, InvalidKeyException { } } + private static Provider getInternalJce() { + try { + Class dhClazz = Class.forName("sun.security.ssl.HybridProvider"); + return (Provider) dhClazz.getField("PROVIDER").get(null); + } catch (ReflectiveOperationException exc) { + throw new RuntimeException(exc); + } + } + @Benchmark @OperationsPerInvocation(SET_SIZE) public void encapsulate(Blackhole bh) throws InvalidKeyException { for (KeyPair kp : keys) { - bh.consume(kem.newEncapsulator(kp.getPublic()).encapsulate().encapsulation()); + bh.consume(kem.newEncapsulator(kp.getPublic()).encapsulate(). + encapsulation()); } } @Benchmark @OperationsPerInvocation(SET_SIZE) - public void decapsulate(Blackhole bh) throws InvalidKeyException, DecapsulateException { + public void decapsulate(Blackhole bh) throws InvalidKeyException, + DecapsulateException { for (int i = 0; i < messages.length; i++) { - bh.consume(kem.newDecapsulator(keys[i].getPrivate()).decapsulate(messages[i])); + bh.consume(kem.newDecapsulator(keys[i].getPrivate()). + decapsulate(messages[i])); } } + public static class MLKEM extends KEMBench { + @Param({"ML-KEM-512", "ML-KEM-768", "ML-KEM-1024" }) + private String algorithm; + + @Param({""}) // ML-KEM uses the same alg for KPG and KEM + private String kpgSpec; + } + + @Fork(value = 5, jvmArgs = {"-XX:+AlwaysPreTouch", "--add-opens", + "java.base/sun.security.ssl=ALL-UNNAMED"}) + public static class JSSE_DHasKEM extends KEMBench { + @Setup + public void init() { + try { + prov = getInternalJce(); + super.setup(); + } catch (GeneralSecurityException gse) { + throw new RuntimeException(gse); + } + } + + @Param({"DH"}) + private String algorithm; + + @Param({"SunEC:XDH:x25519", "SunEC:EC:secp256r1", "SunEC:EC:secp384r1"}) + private String kpgSpec; + } + + @Fork(value = 5, jvmArgs = {"-XX:+AlwaysPreTouch", "--add-opens", + "java.base/sun.security.ssl=ALL-UNNAMED"}) + public static class JSSE_Hybrid extends KEMBench { + @Setup + public void init() { + try { + prov = getInternalJce(); + super.setup(); + } catch (GeneralSecurityException gse) { + throw new RuntimeException(gse); + } + } + + @Param({"X25519MLKEM768", "SecP256r1MLKEM768", "SecP384r1MLKEM1024"}) + private String algorithm; + + @Param({""}) // ML-KEM uses the same alg for KPG and KEM + private String kpgSpec; + } } diff --git a/test/micro/org/openjdk/bench/javax/crypto/full/KeyPairGeneratorBench.java b/test/micro/org/openjdk/bench/javax/crypto/full/KeyPairGeneratorBench.java index 58daff28d880..5a3c72fb2638 100644 --- a/test/micro/org/openjdk/bench/javax/crypto/full/KeyPairGeneratorBench.java +++ b/test/micro/org/openjdk/bench/javax/crypto/full/KeyPairGeneratorBench.java @@ -22,13 +22,16 @@ */ package org.openjdk.bench.javax.crypto.full; +import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.Param; import org.openjdk.jmh.annotations.Setup; +import java.security.GeneralSecurityException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; +import java.security.Provider; public class KeyPairGeneratorBench extends CryptoBase { @@ -45,11 +48,21 @@ public void setup() throws NoSuchAlgorithmException { setupProvider(); generator = (prov == null) ? KeyPairGenerator.getInstance(algorithm) : KeyPairGenerator.getInstance(algorithm, prov); - if (keyLength > 0) { // not all key pair generators allow the use of key length + // not all key pair generators allow the use of key length + if (keyLength > 0) { generator.initialize(keyLength); } } + private static Provider getInternalJce() { + try { + Class dhClazz = Class.forName("sun.security.ssl.HybridProvider"); + return (Provider) dhClazz.getField("PROVIDER").get(null); + } catch (ReflectiveOperationException exc) { + throw new RuntimeException(exc); + } + } + @Benchmark public KeyPair generateKeyPair() { return generator.generateKeyPair(); @@ -118,4 +131,23 @@ public static class MLKEM extends KeyPairGeneratorBench { private int keyLength; } + @Fork(value = 5, jvmArgs = {"-XX:+AlwaysPreTouch", "--add-opens", + "java.base/sun.security.ssl=ALL-UNNAMED"}) + public static class JSSE_Hybrid extends KeyPairGeneratorBench { + @Setup + public void init() { + try { + prov = getInternalJce(); + super.setup(); + } catch (GeneralSecurityException gse) { + throw new RuntimeException(gse); + } + } + + @Param({"X25519MLKEM768", "SecP256r1MLKEM768", "SecP384r1MLKEM1024"}) + private String algorithm; + + @Param({"0"}) // Hybrid KPGs don't need key lengths + private int keyLength; + } } From fadd73943ab028dd7e3fb1850a943aa734bfbcb4 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 3 Aug 2026 17:05:51 +0000 Subject: [PATCH 84/86] 8379433: Throwing proper exception for invalid encapsulation length in Hybrid Backport-of: cdd64dbb5da3c6f2e316397cee71838984b323bf --- .../classes/sun/security/ssl/Hybrid.java | 4 +- .../ssl/HybridKeyExchange/TestHybrid.java | 67 +++++++++++++++++++ 2 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 test/jdk/sun/security/ssl/HybridKeyExchange/TestHybrid.java diff --git a/src/java.base/share/classes/sun/security/ssl/Hybrid.java b/src/java.base/share/classes/sun/security/ssl/Hybrid.java index e3e2cfa0b238..43634ce2f346 100644 --- a/src/java.base/share/classes/sun/security/ssl/Hybrid.java +++ b/src/java.base/share/classes/sun/security/ssl/Hybrid.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -355,7 +355,7 @@ public SecretKey engineDecapsulate(byte[] encapsulation, int from, int to, String algorithm) throws DecapsulateException { int expectedEncSize = engineEncapsulationSize(); if (encapsulation.length != expectedEncSize) { - throw new IllegalArgumentException( + throw new DecapsulateException( "Invalid key encapsulation message length: " + encapsulation.length + ", expected = " + expectedEncSize); diff --git a/test/jdk/sun/security/ssl/HybridKeyExchange/TestHybrid.java b/test/jdk/sun/security/ssl/HybridKeyExchange/TestHybrid.java new file mode 100644 index 000000000000..82314c31dcf2 --- /dev/null +++ b/test/jdk/sun/security/ssl/HybridKeyExchange/TestHybrid.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * 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 8379433 + * @summary Test expected DecapsulateException thrown by Hybrid KEM implementation + * @modules java.base/sun.security.ssl + * @run main/othervm TestHybrid +*/ +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.Provider; +import java.util.Arrays; +import javax.crypto.DecapsulateException; +import javax.crypto.KEM; + +public class TestHybrid { + + public static void main(String[] args) throws Exception { + + Class clazz = Class.forName("sun.security.ssl.HybridProvider"); + Provider p = (Provider) clazz.getField("PROVIDER").get(null); + + String alg = "X25519MLKEM768"; + KeyPairGenerator kpg = KeyPairGenerator.getInstance(alg, p); + KeyPair kp = kpg.generateKeyPair(); + + KEM kem = KEM.getInstance(alg, p); + KEM.Encapsulator e = kem.newEncapsulator(kp.getPublic()); + KEM.Decapsulator d = kem.newDecapsulator(kp.getPrivate()); + + int secretSize = e.secretSize(); + KEM.Encapsulated enc = e.encapsulate(); + byte[] ciphertext = enc.encapsulation(); + + byte[] badCiphertext = Arrays.copyOf(ciphertext, + ciphertext.length - 1); + try { + d.decapsulate(badCiphertext, 0, secretSize, "Generic"); + throw new RuntimeException( + "Expected DecapsulateException not thrown"); + } catch (DecapsulateException expected) { + System.out.println("Expected DecapsulateException thrown"); + } + } +} From 8616dd967a093ca5adf5cf53f02453896b37c2f9 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 3 Aug 2026 17:06:39 +0000 Subject: [PATCH 85/86] 8362894: PKCS12 KeyStore PBMAC1 interoperability testing Backport-of: b3acc4841f6d9c8fd484df68fd2882dab0aa1788 --- .../pkcs12/KeytoolOpensslInteropTest.java | 9 +++++++- test/jdk/sun/security/pkcs12/params/README | 2 ++ test/jdk/sun/security/pkcs12/params/os6 | 23 +++++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 test/jdk/sun/security/pkcs12/params/os6 diff --git a/test/jdk/sun/security/pkcs12/KeytoolOpensslInteropTest.java b/test/jdk/sun/security/pkcs12/KeytoolOpensslInteropTest.java index facdbc8a1876..e3c02b3541d0 100644 --- a/test/jdk/sun/security/pkcs12/KeytoolOpensslInteropTest.java +++ b/test/jdk/sun/security/pkcs12/KeytoolOpensslInteropTest.java @@ -23,7 +23,7 @@ /* * @test id=GenerateOpensslPKCS12 - * @bug 8076190 8242151 8153005 8266182 + * @bug 8076190 8242151 8153005 8266182 8362894 * @summary This is java keytool <-> openssl interop test. This test generates * some openssl keystores on the fly, java operates on it and * vice versa. @@ -138,6 +138,11 @@ private static void generateInitialKeystores(String opensslPath) "pass:changeit", "-certpbe", "AES-256-CBC", "-keypbe", "AES-256-CBC", "-macalg", "SHA512") .shouldHaveExitValue(0); + + ProcessTools.executeCommand(opensslPath, "pkcs12", "-export", "-in", + "kandc", "-out", "os6", "-name", "a", "-passout", + "pass:changeit", "-pbmac1_pbkdf2", "-macalg", "sha256") + .shouldHaveExitValue(0); } private static void testWithJavaCommands() throws Throwable { @@ -168,6 +173,8 @@ private static void testWithJavaCommands() throws Throwable { // no storepass no cert check("os5", "a", null, "changeit", true, false, true); + check("os6", "a", "changeit", "changeit", true, true, true); + // keytool // Current default pkcs12 setting diff --git a/test/jdk/sun/security/pkcs12/params/README b/test/jdk/sun/security/pkcs12/params/README index eca9e1b8d8aa..dbd1f857d632 100644 --- a/test/jdk/sun/security/pkcs12/params/README +++ b/test/jdk/sun/security/pkcs12/params/README @@ -14,6 +14,8 @@ openssl pkcs12 -export -in kandc -out os4 -name a -passout pass:changeit \ -certpbe PBE-SHA1-RC4-128 -keypbe PBE-SHA1-RC4-128 -macalg SHA224 openssl pkcs12 -export -in kandc -out os5 -name a -passout pass:changeit \ -certpbe AES-256-CBC -keypbe AES-256-CBC -macalg SHA512 +openssl pkcs12 -export -in kandc -out os6 -name a -passout pass:changeit \ + -pbmac1_pbkdf2 -macalg sha256 for a in *; do openssl base64 -in $a -out ../$a done diff --git a/test/jdk/sun/security/pkcs12/params/os6 b/test/jdk/sun/security/pkcs12/params/os6 new file mode 100644 index 000000000000..69602978d3b7 --- /dev/null +++ b/test/jdk/sun/security/pkcs12/params/os6 @@ -0,0 +1,23 @@ +MIIEOgIBAzCCA7QGCSqGSIb3DQEHAaCCA6UEggOhMIIDnTCCAmoGCSqGSIb3DQEH +BqCCAlswggJXAgEAMIICUAYJKoZIhvcNAQcBMF8GCSqGSIb3DQEFDTBSMDEGCSqG +SIb3DQEFDDAkBBBEpg+dmjxfnLMTmaHD/RjPAgIIADAMBggqhkiG9w0CCQUAMB0G +CWCGSAFlAwQBKgQQT731bM49PtePx/S4Xf6UZICCAeBeDpWGfpMn8d+wcAoHjUyg ++ceG2y75ac4UVsnVSpYCZaPHcOvUDbTAk5ylMGseLvl3x7xHmovIlShW1IBUWpTe +LhWNpa2f5yZ7t/BXB/oJFT7ol17WznHgmmCi6XbdiGq1YSV3X7SQEBw8WBWeOjGb +IURTAZCLMbGLXkSdg+2DRgP+PpM/Y29vFK2vo72s8bfYS9bGitEreyafP/jv8GxN +6SZx9+FSpTQ92Yj8qyFxvkR4fDyBnYe50KLf/bZmGMBq/d19lxNoheLGfuZ2ZM7W +Mw+wePBJsyntJfcce8iWjt6M8epVmx8SwarNkLU3UiX5XPDGJnnI/0QXEvJ2skQW +y9kCTP4DRYd2kg0tRvpsrK2DraP6xxBCviixoil1rbiQHmOhj6RKx1grGw94nvZq +JM7rZbKN3DvjSwjRn8S2QvycqGYhrQhwoQGqajmCuuBrkM6FCQUKjoWja1XCeQ3Q +8aRnQxwypB46Jrvvn4t3GghF3ZJ0X9LuimXQo9GAXf+X7eNOPpjFrIWlgICTgRN+ +v9elrcUOUKb9C24/Zws/B3nq8fvB9WY0Q9qaVZz9KUKfPjK1QwEr++5xJ5sBZgJz +kZNV0n4dxe4oCN+pE9ztpEswf4sWER92G+YDZB0IEV4wggErBgkqhkiG9w0BBwGg +ggEcBIIBGDCCARQwggEQBgsqhkiG9w0BDAoBAqCBxjCBwzBfBgkqhkiG9w0BBQ0w +UjAxBgkqhkiG9w0BBQwwJAQQvMlpTtrcoqg0XEC3z6KFEgICCAAwDAYIKoZIhvcN +AgkFADAdBglghkgBZQMEASoEEO7wolHeKZyoyII6h3l+iQ4EYEyg6yJWNUWo8ug2 +QNsXVUWmUb9nfu0+nIuhnpBwRewveSv+XMZ+C8szRQsefeMdfjzy91M/ZSHkR73K +HcKdUTVI5zNdBd61g9VNL6CvQCPZIj7AW5bsJ2cZg/GjpsepcDE4MBEGCSqGSIb3 +DQEJFDEEHgIAYTAjBgkqhkiG9w0BCRUxFgQUxCJpJWSVzAG4ZpwKuIUAgKBtWAkw +fTBtMEkGCSqGSIb3DQEFDjA8MCwGCSqGSIb3DQEFDDAfBAgMIRBR5kB3lgICCAAC +ASAwDAYIKoZIhvcNAgkFADAMBggqhkiG9w0CCQUABCDM5Ec9Anci3+OswMqEX22f +uAUrp9IqJSBF3ZY2g86utgQIDCEQUeZAd5YCAggA From 2a26511bdc07a611e946924be6d45ca25bd3cbf2 Mon Sep 17 00:00:00 2001 From: Goetz Lindenmaier Date: Tue, 4 Aug 2026 13:30:21 +0000 Subject: [PATCH 86/86] 8377992: (zipfs) Align ZipFileSystem END header validation with the ZipFile implementation Backport-of: d62b9f78ca4a35bb6c5f665172c7abce4dac56ca --- .../share/classes/java/util/zip/ZipFile.java | 16 +- .../classes/jdk/nio/zipfs/ZipFileSystem.java | 40 ++- .../util/zip/ZipFile/EndOfCenValidation.java | 277 ++---------------- .../jdk/jdk/nio/zipfs/EndOfCenValidation.java | 150 ++++++++++ test/lib/jdk/test/lib/util/ZipUtils.java | 247 ++++++++++++++++ 5 files changed, 463 insertions(+), 267 deletions(-) create mode 100644 test/jdk/jdk/nio/zipfs/EndOfCenValidation.java create mode 100644 test/lib/jdk/test/lib/util/ZipUtils.java diff --git a/src/java.base/share/classes/java/util/zip/ZipFile.java b/src/java.base/share/classes/java/util/zip/ZipFile.java index 173821818761..e28fdecde1b0 100644 --- a/src/java.base/share/classes/java/util/zip/ZipFile.java +++ b/src/java.base/share/classes/java/util/zip/ZipFile.java @@ -1706,8 +1706,10 @@ private void initCEN(final int knownTotal, final ZipCoder zipCoder) throws IOExc this.cen = null; return; // only END header present } - if (end.cenlen > end.endpos) + // Validate END header + if (end.cenlen > end.endpos) { zerror("invalid END header (bad central directory size)"); + } long cenpos = end.endpos - end.cenlen; // position of CEN table // Get position of first local file (LOC) header, taking into // account that there may be a stub prefixed to the ZIP file. @@ -1715,18 +1717,22 @@ private void initCEN(final int knownTotal, final ZipCoder zipCoder) throws IOExc if (locpos < 0) { zerror("invalid END header (bad central directory offset)"); } - // read in the CEN if (end.cenlen > MAX_CEN_SIZE) { zerror("invalid END header (central directory size too large)"); } if (end.centot < 0 || end.centot > end.cenlen / CENHDR) { zerror("invalid END header (total entries count too large)"); } - cen = this.cen = new byte[(int)end.cenlen]; - if (readFullyAt(cen, 0, cen.length, cenpos) != end.cenlen) { + // Validation ensures these are <= Integer.MAX_VALUE + int cenlen = Math.toIntExact(end.cenlen); + int centot = Math.toIntExact(end.centot); + + // read in the CEN + cen = this.cen = new byte[cenlen]; + if (readFullyAt(cen, 0, cen.length, cenpos) != cenlen) { zerror("read CEN tables failed"); } - this.total = Math.toIntExact(end.centot); + this.total = centot; } else { cen = this.cen; this.total = knownTotal; diff --git a/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java b/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java index b3db11eb1fe2..3223ff9dccd4 100644 --- a/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java +++ b/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java @@ -105,6 +105,9 @@ class ZipFileSystem extends FileSystem { private static final String COMPRESSION_METHOD_DEFLATED = "DEFLATED"; // Value specified for compressionMethod property to not compress Zip entries private static final String COMPRESSION_METHOD_STORED = "STORED"; + // CEN size is limited to the maximum array size in the JDK + // See ArraysSupport.SOFT_MAX_ARRAY_LENGTH; + private static final int MAX_CEN_SIZE = Integer.MAX_VALUE - 8; private final ZipFileSystemProvider provider; private final Path zfpath; @@ -1353,7 +1356,7 @@ private END findEND() throws IOException { // to use the end64 values end.cenlen = cenlen64; end.cenoff = cenoff64; - end.centot = (int)centot64; // assume total < 2g + end.centot = centot64; end.endpos = end64pos; return end; } @@ -1575,23 +1578,34 @@ private byte[] initCEN() throws IOException { buildNodeTree(); return null; // only END header present } - if (end.cenlen > end.endpos) - throw new ZipException("invalid END header (bad central directory size)"); + // Validate END header + if (end.cenlen > end.endpos) { + zerror("invalid END header (bad central directory size)"); + } long cenpos = end.endpos - end.cenlen; // position of CEN table - // Get position of first local file (LOC) header, taking into - // account that there may be a stub prefixed to the zip file. + // account that there may be a stub prefixed to the ZIP file. locpos = cenpos - end.cenoff; - if (locpos < 0) - throw new ZipException("invalid END header (bad central directory offset)"); + if (locpos < 0) { + zerror("invalid END header (bad central directory offset)"); + } + if (end.cenlen > MAX_CEN_SIZE) { + zerror("invalid END header (central directory size too large)"); + } + if (end.centot < 0 || end.centot > end.cenlen / CENHDR) { + zerror("invalid END header (total entries count too large)"); + } + // Validation ensures these are <= Integer.MAX_VALUE + int cenlen = Math.toIntExact(end.cenlen); + int centot = Math.toIntExact(end.centot); // read in the CEN - byte[] cen = new byte[(int)(end.cenlen)]; - if (readNBytesAt(cen, 0, cen.length, cenpos) != end.cenlen) { - throw new ZipException("read CEN tables failed"); + byte[] cen = new byte[cenlen]; + if (readNBytesAt(cen, 0, cen.length, cenpos) != cenlen) { + zerror("read CEN tables failed"); } // Iterate through the entries in the central directory - inodes = LinkedHashMap.newLinkedHashMap(end.centot + 1); + inodes = LinkedHashMap.newLinkedHashMap(centot + 1); int pos = 0; int limit = cen.length; while (pos < limit) { @@ -2666,7 +2680,7 @@ static class END { // int disknum; // int sdisknum; // int endsub; - int centot; // 4 bytes + long centot; // 4 bytes long cenlen; // 4 bytes long cenoff; // 4 bytes // int comlen; // comment length @@ -2689,7 +2703,7 @@ void write(OutputStream os, long offset, boolean forceEnd64) throws IOException xoff = ZIP64_MINVAL; hasZip64 = true; } - int count = centot; + int count = Math.toIntExact(centot); if (count >= ZIP64_MINVAL32) { count = ZIP64_MINVAL32; hasZip64 = true; diff --git a/test/jdk/java/util/zip/ZipFile/EndOfCenValidation.java b/test/jdk/java/util/zip/ZipFile/EndOfCenValidation.java index 7adcfb9c1284..7ca71c9890a7 100644 --- a/test/jdk/java/util/zip/ZipFile/EndOfCenValidation.java +++ b/test/jdk/java/util/zip/ZipFile/EndOfCenValidation.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved. * 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,33 +25,26 @@ * @bug 8272746 * @modules java.base/jdk.internal.util * @summary Verify that ZipFile rejects files with CEN sizes exceeding the implementation limit + * @library /test/lib + * @build jdk.test.lib.util.ZipUtils * @run junit/othervm EndOfCenValidation */ import jdk.internal.util.ArraysSupport; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import java.io.*; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.channels.FileChannel; -import java.nio.charset.StandardCharsets; +import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.util.Arrays; -import java.util.EnumSet; -import java.util.HexFormat; -import java.util.zip.ZipEntry; import java.util.zip.ZipException; import java.util.zip.ZipFile; -import java.util.zip.ZipOutputStream; -import static org.junit.jupiter.api.Assertions.*; +import static jdk.test.lib.util.ZipUtils.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; /** * This test augments {@link TestTooManyEntries}. It creates sparse ZIPs where @@ -65,36 +58,13 @@ public class EndOfCenValidation { // Zip files produced by this test - public static final Path CEN_TOO_LARGE_ZIP = Path.of("cen-size-too-large.zip"); - public static final Path INVALID_CEN_SIZE = Path.of("invalid-zen-size.zip"); - public static final Path BAD_CEN_OFFSET_ZIP = Path.of("bad-cen-offset.zip"); - // Some ZipFile constants for manipulating the 'End of central directory record' (END header) - private static final int ENDHDR = ZipFile.ENDHDR; // End of central directory record size - private static final int ENDSIZ = ZipFile.ENDSIZ; // Offset of CEN size field within ENDHDR - private static final int ENDOFF = ZipFile.ENDOFF; // Offset of CEN offset field within ENDHDR - // Maximum allowed CEN size allowed by ZipFile - private static final int MAX_CEN_SIZE = ArraysSupport.SOFT_MAX_ARRAY_LENGTH; - - // Expected message when CEN size does not match file size - private static final String INVALID_CEN_BAD_SIZE = "invalid END header (bad central directory size)"; - // Expected message when CEN offset is too large - private static final String INVALID_CEN_BAD_OFFSET = "invalid END header (bad central directory offset)"; - // Expected message when CEN size is too large - private static final String INVALID_CEN_SIZE_TOO_LARGE = "invalid END header (central directory size too large)"; - // Expected message when total entry count is too large - private static final String INVALID_BAD_ENTRY_COUNT = "invalid END header (total entries count too large)"; + static final Path CEN_TOO_LARGE_ZIP = Path.of("cen-size-too-large.zip"); + static final Path INVALID_CEN_SIZE = Path.of("invalid-zen-size.zip"); + static final Path BAD_CEN_OFFSET_ZIP = Path.of("bad-cen-offset.zip"); + static final Path BAD_ENTRY_COUNT_ZIP = Path.of("bad-entry-count.zip"); - // A valid ZIP file, used as a template - private byte[] zipBytes; - - /** - * Create a valid ZIP file, used as a template - * @throws IOException if an error occurs - */ - @BeforeEach - public void setup() throws IOException { - zipBytes = templateZip(); - } + // Maximum allowed CEN size allowed by ZipFile + static final int MAX_CEN_SIZE = ArraysSupport.SOFT_MAX_ARRAY_LENGTH; /** * Delete big files after test, in case the file system did not support sparse files. @@ -105,6 +75,7 @@ public void cleanup() throws IOException { Files.deleteIfExists(CEN_TOO_LARGE_ZIP); Files.deleteIfExists(INVALID_CEN_SIZE); Files.deleteIfExists(BAD_CEN_OFFSET_ZIP); + Files.deleteIfExists(BAD_ENTRY_COUNT_ZIP); } /** @@ -115,14 +86,8 @@ public void cleanup() throws IOException { @Test public void shouldRejectTooLargeCenSize() throws IOException { int size = MAX_CEN_SIZE + 1; - Path zip = zipWithModifiedEndRecord(size, true, 0, CEN_TOO_LARGE_ZIP); - - ZipException ex = assertThrows(ZipException.class, () -> { - new ZipFile(zip.toFile()); - }); - - assertEquals(INVALID_CEN_SIZE_TOO_LARGE, ex.getMessage()); + verifyRejection(zip, INVALID_CEN_SIZE_TOO_LARGE); } /** @@ -133,16 +98,9 @@ public void shouldRejectTooLargeCenSize() throws IOException { */ @Test public void shouldRejectInvalidCenSize() throws IOException { - int size = MAX_CEN_SIZE; - Path zip = zipWithModifiedEndRecord(size, false, 0, INVALID_CEN_SIZE); - - ZipException ex = assertThrows(ZipException.class, () -> { - new ZipFile(zip.toFile()); - }); - - assertEquals(INVALID_CEN_BAD_SIZE, ex.getMessage()); + verifyRejection(zip, INVALID_CEN_BAD_SIZE); } /** @@ -153,16 +111,9 @@ public void shouldRejectInvalidCenSize() throws IOException { */ @Test public void shouldRejectInvalidCenOffset() throws IOException { - int size = MAX_CEN_SIZE; - Path zip = zipWithModifiedEndRecord(size, true, 100, BAD_CEN_OFFSET_ZIP); - - ZipException ex = assertThrows(ZipException.class, () -> { - new ZipFile(zip.toFile()); - }); - - assertEquals(INVALID_CEN_BAD_OFFSET, ex.getMessage()); + verifyRejection(zip, INVALID_CEN_BAD_OFFSET); } /** @@ -181,192 +132,20 @@ public void shouldRejectInvalidCenOffset() throws IOException { Long.MAX_VALUE // Unreasonably large }) public void shouldRejectBadTotalEntries(long totalEntries) throws IOException { - /** - * A small ZIP using the ZIP64 format. - * - * ZIP created using: "echo -n hello | zip zip64.zip -" - * Hex encoded using: "cat zip64.zip | xxd -ps" - * - * The file has the following structure: - * - * 0000 LOCAL HEADER #1 04034B50 - * 0004 Extract Zip Spec 2D '4.5' - * 0005 Extract OS 00 'MS-DOS' - * 0006 General Purpose Flag 0000 - * 0008 Compression Method 0000 'Stored' - * 000A Last Mod Time 5947AB78 'Mon Oct 7 21:27:48 2024' - * 000E CRC 363A3020 - * 0012 Compressed Length FFFFFFFF - * 0016 Uncompressed Length FFFFFFFF - * 001A Filename Length 0001 - * 001C Extra Length 0014 - * 001E Filename '-' - * 001F Extra ID #0001 0001 'ZIP64' - * 0021 Length 0010 - * 0023 Uncompressed Size 0000000000000006 - * 002B Compressed Size 0000000000000006 - * 0033 PAYLOAD hello. - * - * 0039 CENTRAL HEADER #1 02014B50 - * 003D Created Zip Spec 1E '3.0' - * 003E Created OS 03 'Unix' - * 003F Extract Zip Spec 2D '4.5' - * 0040 Extract OS 00 'MS-DOS' - * 0041 General Purpose Flag 0000 - * 0043 Compression Method 0000 'Stored' - * 0045 Last Mod Time 5947AB78 'Mon Oct 7 21:27:48 2024' - * 0049 CRC 363A3020 - * 004D Compressed Length 00000006 - * 0051 Uncompressed Length FFFFFFFF - * 0055 Filename Length 0001 - * 0057 Extra Length 000C - * 0059 Comment Length 0000 - * 005B Disk Start 0000 - * 005D Int File Attributes 0001 - * [Bit 0] 1 Text Data - * 005F Ext File Attributes 11B00000 - * 0063 Local Header Offset 00000000 - * 0067 Filename '-' - * 0068 Extra ID #0001 0001 'ZIP64' - * 006A Length 0008 - * 006C Uncompressed Size 0000000000000006 - * - * 0074 ZIP64 END CENTRAL DIR 06064B50 - * RECORD - * 0078 Size of record 000000000000002C - * 0080 Created Zip Spec 1E '3.0' - * 0081 Created OS 03 'Unix' - * 0082 Extract Zip Spec 2D '4.5' - * 0083 Extract OS 00 'MS-DOS' - * 0084 Number of this disk 00000000 - * 0088 Central Dir Disk no 00000000 - * 008C Entries in this disk 0000000000000001 - * 0094 Total Entries 0000000000000001 - * 009C Size of Central Dir 000000000000003B - * 00A4 Offset to Central dir 0000000000000039 - * - * 00AC ZIP64 END CENTRAL DIR 07064B50 - * LOCATOR - * 00B0 Central Dir Disk no 00000000 - * 00B4 Offset to Central dir 0000000000000074 - * 00BC Total no of Disks 00000001 - * - * 00C0 END CENTRAL HEADER 06054B50 - * 00C4 Number of this disk 0000 - * 00C6 Central Dir Disk no 0000 - * 00C8 Entries in this disk 0001 - * 00CA Total Entries 0001 - * 00CC Size of Central Dir 0000003B - * 00D0 Offset to Central Dir FFFFFFFF - * 00D4 Comment Length 0000 - */ - - byte[] zipBytes = HexFormat.of().parseHex(""" - 504b03042d000000000078ab475920303a36ffffffffffffffff01001400 - 2d010010000600000000000000060000000000000068656c6c6f0a504b01 - 021e032d000000000078ab475920303a3606000000ffffffff01000c0000 - 00000001000000b011000000002d010008000600000000000000504b0606 - 2c000000000000001e032d00000000000000000001000000000000000100 - 0000000000003b000000000000003900000000000000504b060700000000 - 740000000000000001000000504b050600000000010001003b000000ffff - ffff0000 - """.replaceAll("\n","")); - - // Buffer to manipulate the above ZIP - ByteBuffer buf = ByteBuffer.wrap(zipBytes).order(ByteOrder.LITTLE_ENDIAN); - // Offset of the 'total entries' in the 'ZIP64 END CENTRAL DIR' record - // Update ZIP64 entry count to a value which cannot possibly fit in the small CEN - buf.putLong(0x94, totalEntries); - // The corresponding END field needs the ZIP64 magic value - buf.putShort(0xCA, (short) 0xFFFF); - // Write the ZIP to disk - Path zipFile = Path.of("bad-entry-count.zip"); - Files.write(zipFile, zipBytes); - - // Verify that the END header is rejected - ZipException ex = assertThrows(ZipException.class, () -> { - try (var zf = new ZipFile(zipFile.toFile())) { - } - }); - - assertEquals(INVALID_BAD_ENTRY_COUNT, ex.getMessage()); - } - - /** - * Create an ZIP file with a single entry, then modify the CEN size - * in the 'End of central directory record' (END header) to the given size. - * - * The CEN is optionally "inflated" with trailing zero bytes such that - * its actual size matches the one stated in the END header. - * - * The CEN offset is optiontially adjusted by the given amount - * - * The resulting ZIP is technically not valid, but it does allow us - * to test that large or invalid CEN sizes are rejected - * @param cenSize the CEN size to put in the END record - * @param inflateCen if true, zero-pad the CEN to the desired size - * @param cenOffAdjust Adjust the CEN offset field of the END record with this amount - * @throws IOException if an error occurs - */ - private Path zipWithModifiedEndRecord(int cenSize, - boolean inflateCen, - int cenOffAdjust, - Path zip) throws IOException { - - // A byte buffer for reading the END - ByteBuffer buffer = ByteBuffer.wrap(zipBytes.clone()).order(ByteOrder.LITTLE_ENDIAN); - - // Offset of the END header - int endOffset = buffer.limit() - ENDHDR; - - // Modify the CEN size - int sizeOffset = endOffset + ENDSIZ; - int currentCenSize = buffer.getInt(sizeOffset); - buffer.putInt(sizeOffset, cenSize); - - // Optionally modify the CEN offset - if (cenOffAdjust != 0) { - int offOffset = endOffset + ENDOFF; - int currentCenOff = buffer.getInt(offOffset); - buffer.putInt(offOffset, currentCenOff + cenOffAdjust); - } - - // When creating a sparse file, the file must not already exit - Files.deleteIfExists(zip); - - // Open a FileChannel for writing a sparse file - EnumSet options = EnumSet.of(StandardOpenOption.CREATE_NEW, - StandardOpenOption.WRITE, - StandardOpenOption.SPARSE); - - try (FileChannel channel = FileChannel.open(zip, options)) { - - // Write everything up to END - channel.write(buffer.slice(0, buffer.limit() - ENDHDR)); - - if (inflateCen) { - // Inject "empty bytes" to make the actual CEN size match the END - int injectBytes = cenSize - currentCenSize; - channel.position(channel.position() + injectBytes); - } - // Write the modified END - channel.write(buffer.slice(buffer.limit() - ENDHDR, ENDHDR)); - } - return zip; + Path zip = zip64WithModifiedTotalEntries(BAD_ENTRY_COUNT_ZIP, totalEntries); + verifyRejection(zip, INVALID_BAD_ENTRY_COUNT); } /** - * Produce a byte array of a ZIP with a single entry - * - * @throws IOException if an error occurs + * Verify that ZipFile rejects the ZIP file with a ZipException + * with the given message + * @param zip ZIP file to open + * @param msg exception message to expect */ - private byte[] templateZip() throws IOException { - ByteArrayOutputStream bout = new ByteArrayOutputStream(); - try (ZipOutputStream zo = new ZipOutputStream(bout)) { - ZipEntry entry = new ZipEntry("duke.txt"); - zo.putNextEntry(entry); - zo.write("duke".getBytes(StandardCharsets.UTF_8)); - } - return bout.toByteArray(); + private static void verifyRejection(Path zip, String msg) { + ZipException ex = assertThrows(ZipException.class, () -> { + new ZipFile(zip.toFile()); + }); + assertEquals(msg, ex.getMessage()); } } diff --git a/test/jdk/jdk/nio/zipfs/EndOfCenValidation.java b/test/jdk/jdk/nio/zipfs/EndOfCenValidation.java new file mode 100644 index 000000000000..ed0bdbb52eaf --- /dev/null +++ b/test/jdk/jdk/nio/zipfs/EndOfCenValidation.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved. + * 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 + * @modules java.base/jdk.internal.util + * @summary Verify that ZipFileSystem rejects files with CEN sizes exceeding the implementation limit + * @library /test/lib + * @build jdk.test.lib.util.ZipUtils + * @run junit/othervm EndOfCenValidation + */ + +import jdk.internal.util.ArraysSupport; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.io.IOException; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.zip.ZipException; + +import static jdk.test.lib.util.ZipUtils.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * This test augments {@link TestTooManyEntries}. It creates sparse ZIPs where + * the CEN size is inflated to the desired value. This helps this test run + * fast with much less resources. + * + * While the CEN in these files are zero-filled and the produced ZIPs are technically + * invalid, the CEN is never actually read by ZipFileSystem since it does + * 'End of central directory record' (END header) validation before reading the CEN. + */ +public class EndOfCenValidation { + + // Zip files produced by this test + static final Path CEN_TOO_LARGE_ZIP = Path.of("cen-size-too-large.zip"); + static final Path INVALID_CEN_SIZE = Path.of("invalid-zen-size.zip"); + static final Path BAD_CEN_OFFSET_ZIP = Path.of("bad-cen-offset.zip"); + static final Path BAD_ENTRY_COUNT_ZIP = Path.of("bad-entry-count.zip"); + + // Maximum allowed CEN size allowed by ZipFileSystem + static final int MAX_CEN_SIZE = ArraysSupport.SOFT_MAX_ARRAY_LENGTH; + + /** + * Delete big files after test, in case the file system did not support sparse files. + * @throws IOException if an error occurs + */ + @AfterEach + public void cleanup() throws IOException { + Files.deleteIfExists(CEN_TOO_LARGE_ZIP); + Files.deleteIfExists(INVALID_CEN_SIZE); + Files.deleteIfExists(BAD_CEN_OFFSET_ZIP); + Files.deleteIfExists(BAD_ENTRY_COUNT_ZIP); + } + + /** + * Validates that an 'End of central directory record' (END header) with a CEN + * length exceeding {@link #MAX_CEN_SIZE} limit is rejected + * @throws IOException if an error occurs + */ + @Test + public void shouldRejectTooLargeCenSize() throws IOException { + int size = MAX_CEN_SIZE + 1; + Path zip = zipWithModifiedEndRecord(size, true, 0, CEN_TOO_LARGE_ZIP); + verifyRejection(zip, INVALID_CEN_SIZE_TOO_LARGE); + } + + /** + * Validate that an 'End of central directory record' (END header) + * where the value of the CEN size field exceeds the position of + * the END header is rejected. + * @throws IOException if an error occurs + */ + @Test + public void shouldRejectInvalidCenSize() throws IOException { + int size = MAX_CEN_SIZE; + Path zip = zipWithModifiedEndRecord(size, false, 0, INVALID_CEN_SIZE); + verifyRejection(zip, INVALID_CEN_BAD_SIZE); + } + + /** + * Validate that an 'End of central directory record' (the END header) + * where the value of the CEN offset field is larger than the position + * of the END header minus the CEN size is rejected + * @throws IOException if an error occurs + */ + @Test + public void shouldRejectInvalidCenOffset() throws IOException { + int size = MAX_CEN_SIZE; + Path zip = zipWithModifiedEndRecord(size, true, 100, BAD_CEN_OFFSET_ZIP); + verifyRejection(zip, INVALID_CEN_BAD_OFFSET); + } + + /** + * Validate that a 'Zip64 End of Central Directory' record (the END header) + * where the value of the 'total entries' field is larger than what fits + * in the CEN size is rejected. + * + * @throws IOException if an error occurs + */ + @ParameterizedTest + @ValueSource(longs = { + -1, // Negative + Long.MIN_VALUE, // Very negative + 0x3B / 3L - 1, // Cannot fit in test ZIP's CEN + MAX_CEN_SIZE / 3 + 1, // Too large to allocate int[] entries array + Long.MAX_VALUE // Unreasonably large + }) + public void shouldRejectBadTotalEntries(long totalEntries) throws IOException { + Path zip = zip64WithModifiedTotalEntries(BAD_ENTRY_COUNT_ZIP, totalEntries); + verifyRejection(zip, INVALID_BAD_ENTRY_COUNT); + } + + /** + * Verify that ZipFileSystem.newFileSystem rejects the ZIP file with a ZipException + * with the given message + * @param zip ZIP file to open + * @param msg exception message to expect + */ + private static void verifyRejection(Path zip, String msg) { + ZipException ex = assertThrows(ZipException.class, () -> { + FileSystems.newFileSystem(zip); + }); + assertEquals(msg, ex.getMessage()); + } +} diff --git a/test/lib/jdk/test/lib/util/ZipUtils.java b/test/lib/jdk/test/lib/util/ZipUtils.java new file mode 100644 index 000000000000..a1568e51cc1b --- /dev/null +++ b/test/lib/jdk/test/lib/util/ZipUtils.java @@ -0,0 +1,247 @@ +/* + * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.test.lib.util; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.EnumSet; +import java.util.HexFormat; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + +/** + * This class consists exclusively of static utility methods that are useful + * for creating and manipulating ZIP files. + */ +public final class ZipUtils { + // Some ZipFile constants for manipulating the 'End of central directory record' (END header) + private static final int ENDHDR = ZipFile.ENDHDR; // End of central directory record size + private static final int ENDSIZ = ZipFile.ENDSIZ; // Offset of CEN size field within ENDHDR + private static final int ENDOFF = ZipFile.ENDOFF; // Offset of CEN offset field within ENDHDR + // Expected message when CEN size does not match file size + public static final String INVALID_CEN_BAD_SIZE = "invalid END header (bad central directory size)"; + // Expected message when CEN offset is too large + public static final String INVALID_CEN_BAD_OFFSET = "invalid END header (bad central directory offset)"; + // Expected message when CEN size is too large + public static final String INVALID_CEN_SIZE_TOO_LARGE = "invalid END header (central directory size too large)"; + // Expected message when total entry count is too large + public static final String INVALID_BAD_ENTRY_COUNT = "invalid END header (total entries count too large)"; + + private ZipUtils() { } + + /** + * Create an ZIP file with a single entry, then modify the CEN size + * in the 'End of central directory record' (END header) to the given size. + * + * The CEN is optionally "inflated" with trailing zero bytes such that + * its actual size matches the one stated in the END header. + * + * The CEN offset is optiontially adjusted by the given amount + * + * The resulting ZIP is technically not valid, but it does allow us + * to test that large or invalid CEN sizes are rejected + * @param cenSize the CEN size to put in the END record + * @param inflateCen if true, zero-pad the CEN to the desired size + * @param cenOffAdjust Adjust the CEN offset field of the END record with this amount + * @throws IOException if an error occurs + */ + public static Path zipWithModifiedEndRecord(int cenSize, + boolean inflateCen, + int cenOffAdjust, + Path zip) throws IOException { + // A byte buffer for reading the END + ByteBuffer buffer = ByteBuffer.wrap(templateZip()).order(ByteOrder.LITTLE_ENDIAN); + + // Offset of the END header + int endOffset = buffer.limit() - ENDHDR; + + // Modify the CEN size + int sizeOffset = endOffset + ENDSIZ; + int currentCenSize = buffer.getInt(sizeOffset); + buffer.putInt(sizeOffset, cenSize); + + // Optionally modify the CEN offset + if (cenOffAdjust != 0) { + int offOffset = endOffset + ENDOFF; + int currentCenOff = buffer.getInt(offOffset); + buffer.putInt(offOffset, currentCenOff + cenOffAdjust); + } + // When creating a sparse file, the file must not already exit + Files.deleteIfExists(zip); + + // Open a FileChannel for writing a sparse file + EnumSet options = EnumSet.of(StandardOpenOption.CREATE_NEW, + StandardOpenOption.WRITE, + StandardOpenOption.SPARSE); + + try (FileChannel channel = FileChannel.open(zip, options)) { + // Write everything up to END + channel.write(buffer.slice(0, buffer.limit() - ENDHDR)); + if (inflateCen) { + // Inject "empty bytes" to make the actual CEN size match the END + int injectBytes = cenSize - currentCenSize; + channel.position(channel.position() + injectBytes); + } + // Write the modified END + channel.write(buffer.slice(buffer.limit() - ENDHDR, ENDHDR)); + } + return zip; + } + + /** + * Create a small Zip64 ZIP file, then modify the Zip64 END header + * with a possibly very large total entry count + * + * @param zip file to write to + * @param totalEntries the number of entries wanted in the Zip64 END header + * @return the modified ZIP file + * @throws IOException if an unexpeced IO error occurs + */ + public static Path zip64WithModifiedTotalEntries(Path zip, long totalEntries) throws IOException { + /** + * A small ZIP using the ZIP64 format. + * + * ZIP created using: "echo -n hello | zip zip64.zip -" + * Hex encoded using: "cat zip64.zip | xxd -ps" + * + * The file has the following structure: + * + * 0000 LOCAL HEADER #1 04034B50 + * 0004 Extract Zip Spec 2D '4.5' + * 0005 Extract OS 00 'MS-DOS' + * 0006 General Purpose Flag 0000 + * 0008 Compression Method 0000 'Stored' + * 000A Last Mod Time 5947AB78 'Mon Oct 7 21:27:48 2024' + * 000E CRC 363A3020 + * 0012 Compressed Length FFFFFFFF + * 0016 Uncompressed Length FFFFFFFF + * 001A Filename Length 0001 + * 001C Extra Length 0014 + * 001E Filename '-' + * 001F Extra ID #0001 0001 'ZIP64' + * 0021 Length 0010 + * 0023 Uncompressed Size 0000000000000006 + * 002B Compressed Size 0000000000000006 + * 0033 PAYLOAD hello. + * + * 0039 CENTRAL HEADER #1 02014B50 + * 003D Created Zip Spec 1E '3.0' + * 003E Created OS 03 'Unix' + * 003F Extract Zip Spec 2D '4.5' + * 0040 Extract OS 00 'MS-DOS' + * 0041 General Purpose Flag 0000 + * 0043 Compression Method 0000 'Stored' + * 0045 Last Mod Time 5947AB78 'Mon Oct 7 21:27:48 2024' + * 0049 CRC 363A3020 + * 004D Compressed Length 00000006 + * 0051 Uncompressed Length FFFFFFFF + * 0055 Filename Length 0001 + * 0057 Extra Length 000C + * 0059 Comment Length 0000 + * 005B Disk Start 0000 + * 005D Int File Attributes 0001 + * [Bit 0] 1 Text Data + * 005F Ext File Attributes 11B00000 + * 0063 Local Header Offset 00000000 + * 0067 Filename '-' + * 0068 Extra ID #0001 0001 'ZIP64' + * 006A Length 0008 + * 006C Uncompressed Size 0000000000000006 + * + * 0074 ZIP64 END CENTRAL DIR 06064B50 + * RECORD + * 0078 Size of record 000000000000002C + * 0080 Created Zip Spec 1E '3.0' + * 0081 Created OS 03 'Unix' + * 0082 Extract Zip Spec 2D '4.5' + * 0083 Extract OS 00 'MS-DOS' + * 0084 Number of this disk 00000000 + * 0088 Central Dir Disk no 00000000 + * 008C Entries in this disk 0000000000000001 + * 0094 Total Entries 0000000000000001 + * 009C Size of Central Dir 000000000000003B + * 00A4 Offset to Central dir 0000000000000039 + * + * 00AC ZIP64 END CENTRAL DIR 07064B50 + * LOCATOR + * 00B0 Central Dir Disk no 00000000 + * 00B4 Offset to Central dir 0000000000000074 + * 00BC Total no of Disks 00000001 + * + * 00C0 END CENTRAL HEADER 06054B50 + * 00C4 Number of this disk 0000 + * 00C6 Central Dir Disk no 0000 + * 00C8 Entries in this disk 0001 + * 00CA Total Entries 0001 + * 00CC Size of Central Dir 0000003B + * 00D0 Offset to Central Dir FFFFFFFF + * 00D4 Comment Length 0000 + */ + + byte[] zipBytes = HexFormat.of().parseHex(""" + 504b03042d000000000078ab475920303a36ffffffffffffffff01001400 + 2d010010000600000000000000060000000000000068656c6c6f0a504b01 + 021e032d000000000078ab475920303a3606000000ffffffff01000c0000 + 00000001000000b011000000002d010008000600000000000000504b0606 + 2c000000000000001e032d00000000000000000001000000000000000100 + 0000000000003b000000000000003900000000000000504b060700000000 + 740000000000000001000000504b050600000000010001003b000000ffff + ffff0000 + """.replaceAll("\n","")); + + // Buffer to manipulate the above ZIP + ByteBuffer buf = ByteBuffer.wrap(zipBytes).order(ByteOrder.LITTLE_ENDIAN); + // Offset of the 'total entries' in the 'ZIP64 END CENTRAL DIR' record + // Update ZIP64 entry count to a value which cannot possibly fit in the small CEN + buf.putLong(0x94, totalEntries); + // The corresponding END field needs the ZIP64 magic value + buf.putShort(0xCA, (short) 0xFFFF); + // Write the ZIP to disk + Files.write(zip, zipBytes); + return zip; + } + + /** + * Produce a byte array of a ZIP with a single entry + * + * @throws IOException if an error occurs + */ + private static byte[] templateZip() throws IOException { + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + try (ZipOutputStream zo = new ZipOutputStream(bout)) { + ZipEntry entry = new ZipEntry("duke.txt"); + zo.putNextEntry(entry); + zo.write("duke".getBytes(StandardCharsets.UTF_8)); + } + return bout.toByteArray(); + } +}