From 8db1ca87846ef82e29b747d27dfc8f657cb7791a Mon Sep 17 00:00:00 2001
From: Valerie Peng
Date: Mon, 27 Apr 2026 17:06:47 +0000
Subject: [PATCH 001/169] 8380715: Enhance PKCS5 padding
Reviewed-by: jnimeh, pkumaraswamy, rhalade
---
.../com/sun/crypto/provider/PKCS5Padding.java | 36 ++++++++-------
.../sun/security/pkcs11/P11Cipher.java | 44 ++++++++++++-------
2 files changed, 48 insertions(+), 32 deletions(-)
diff --git a/src/java.base/share/classes/com/sun/crypto/provider/PKCS5Padding.java b/src/java.base/share/classes/com/sun/crypto/provider/PKCS5Padding.java
index a8ba711d658c..3aee3e633fe4 100644
--- a/src/java.base/share/classes/com/sun/crypto/provider/PKCS5Padding.java
+++ b/src/java.base/share/classes/com/sun/crypto/provider/PKCS5Padding.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2024, 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
@@ -91,25 +91,27 @@ public int unpad(byte[] in, int off, int len) {
(len == 0)) { // this can happen if input is really a padded buffer
return 0;
}
- int idx = Math.addExact(off, len);
- byte lastByte = in[idx - 1];
- int padValue = (int)lastByte & 0x0ff;
- if ((padValue < 0x01)
- || (padValue > blockSize)) {
- return -1;
- }
- int start = idx - padValue;
- if (start < off) {
- return -1;
- }
+ int last = Math.addExact(off, len);
+ int padValue = in[last - 1] & 0x0ff;
+ // check padValue >= 1; negative if fail
+ int invalid = (padValue - 1);
+ // check padValue <= blockSize; negative if fail
+ invalid |= (blockSize - padValue);
- for (int i = start; i < idx; i++) {
- if (in[i] != lastByte) {
- return -1;
- }
+ // check the last block for the padding bytes
+ for (int i = last - blockSize; i < last ; i++) {
+ // mask = 0 for the first (blockSize - padValue) bytes in the block
+ // otherwise, use 0xffff to preserve the value
+ int mask = (last - padValue - i - 1) >> 8 ;
+ int valueCheck = ((in[i] & 0x0ff) ^ padValue) & mask;
+ // again, assign negative value if fail
+ invalid |= -valueCheck;
}
- return start;
+ // convert 'invalid' to -1 when 'invalid' is not 0
+ // right shifting by 8 should be sufficient since value range is small
+ invalid >>= 8; // 0 or -1
+ return (invalid | (last - padValue));
}
/**
diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Cipher.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Cipher.java
index 39827af42e54..e349251cb10c 100644
--- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Cipher.java
+++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Cipher.java
@@ -71,9 +71,8 @@ private static interface Padding {
int setPaddingBytes(byte[] paddingBuffer, int startOff, int padLen);
// DEC: return the length of trailing padding bytes given the specified
- // padded data
- int unpad(byte[] paddedData, int len)
- throws BadPaddingException, IllegalBlockSizeException;
+ // padded data, returns -1 if invalid
+ int unpad(byte[] paddedData, int len) throws IllegalBlockSizeException;
}
private static class PKCS5Padding implements Padding {
@@ -95,23 +94,32 @@ public int setPaddingBytes(byte[] paddingBuffer, int startOff, int padLen) {
}
public int unpad(byte[] paddedData, int len)
- throws BadPaddingException, IllegalBlockSizeException {
+ throws IllegalBlockSizeException {
if ((len < 1) || (len % blockSize != 0)) {
throw new IllegalBlockSizeException
("Input length must be multiples of " + blockSize);
}
- byte padValue = paddedData[len - 1];
- if (padValue < 1 || padValue > blockSize) {
- throw new BadPaddingException("Invalid pad value!");
- }
- // sanity check padding bytes
- int padStartIndex = len - padValue;
- for (int i = padStartIndex; i < len; i++) {
- if (paddedData[i] != padValue) {
- throw new BadPaddingException("Invalid pad bytes!");
- }
+
+ int padValue = paddedData[len - 1] & 0x0ff;
+ // check padValue >= 1; negative if fail
+ int invalid = (padValue - 1);
+ // check padValue <= blockSize; negative if fail
+ invalid |= (blockSize - padValue);
+
+ // check the last block for the padding bytes
+ for (int i = len - blockSize; i < len; i++) {
+ // mask = 0 for the first (blockSize - padValue) bytes in the
+ // block. Otherwise, use 0xffff to preserve the value
+ int mask = (len - padValue - i - 1) >> 8 ;
+ int valueCheck = ((paddedData[i] & 0x0ff) ^ padValue) & mask;
+ // again, assign negative value if fail
+ invalid |= -valueCheck;
}
- return padValue;
+ // convert 'invalid' to -1 when 'invalid' is not 0
+ // right shifting by 8 should be sufficient since value range is
+ // small
+ invalid >>= 8; // 0 or -1
+ return (invalid | padValue);
}
}
@@ -970,6 +978,9 @@ private int implDoFinal(byte[] out, int outOfs, int outLen)
padBuffer.length - k);
int actualPadLen = paddingObj.unpad(padBuffer, k);
+ if (actualPadLen < 0) {
+ throw new BadPaddingException("Invalid pad value!");
+ }
k -= actualPadLen;
System.arraycopy(padBuffer, 0, out, outOfs, k);
} else {
@@ -1084,6 +1095,9 @@ private int implDoFinal(ByteBuffer outBuffer)
0, padBuffer, k, padBuffer.length - k);
int actualPadLen = paddingObj.unpad(padBuffer, k);
+ if (actualPadLen < 0) {
+ throw new BadPaddingException("Invalid pad value!");
+ }
k -= actualPadLen;
outArray = padBuffer;
outOfs = 0;
From 3895fe128e6109f85004ec5f1cf2d00657a7cb50 Mon Sep 17 00:00:00 2001
From: Mark Powers
Date: Tue, 28 Apr 2026 14:33:42 +0000
Subject: [PATCH 002/169] 8382178: Enhance TLS certificate handling
Reviewed-by: mschoene, hchao, rhalade, mullan, weijun
---
.../security/validator/CADistrustPolicy.java | 30 ++++-----
.../validator/CamerfirmaTLSPolicy.java | 33 ++--------
.../security/validator/ChunghwaTLSPolicy.java | 15 ++++-
.../security/validator/EntrustTLSPolicy.java | 30 ++-------
.../security/validator/SymantecTLSPolicy.java | 65 ++-----------------
.../share/conf/security/java.security | 17 ++---
.../distrust/Camerfirma.java | 6 +-
.../distrust/Chunghwa.java | 9 ++-
.../distrust/Entrust.java | 4 +-
.../distrust/Symantec.java | 10 +--
10 files changed, 65 insertions(+), 154 deletions(-)
diff --git a/src/java.base/share/classes/sun/security/validator/CADistrustPolicy.java b/src/java.base/share/classes/sun/security/validator/CADistrustPolicy.java
index 44c9ed2d0754..c1afe4246125 100644
--- a/src/java.base/share/classes/sun/security/validator/CADistrustPolicy.java
+++ b/src/java.base/share/classes/sun/security/validator/CADistrustPolicy.java
@@ -36,12 +36,9 @@
*/
enum CADistrustPolicy {
/**
- * Distrust TLS Server certificates anchored by a Symantec root CA and
- * issued after April 16, 2019 (with exceptions for a couple of subordinate
- * CAs, see the jdk.security.caDistrustPolicies definition in the
- * java.security file for more details). If enabled, this policy is
- * currently enforced by the PKIX and SunX509 TrustManager implementations
- * of the SunJSSE provider implementation.
+ * Distrust TLS Server certificates anchored by a Symantec root CA.
+ * If enabled, this policy is currently enforced by the PKIX and SunX509
+ * TrustManager implementations of the SunJSSE provider implementation.
*/
SYMANTEC_TLS {
void checkDistrust(String variant, X509Certificate[] chain)
@@ -54,10 +51,9 @@ void checkDistrust(String variant, X509Certificate[] chain)
},
/**
- * Distrust TLS Server certificates anchored by an Entrust root CA and
- * issued after November 11, 2024. If enabled, this policy is currently
- * enforced by the PKIX and SunX509 TrustManager implementations
- * of the SunJSSE provider implementation.
+ * Distrust TLS Server certificates anchored by an Entrust root CA.
+ * If enabled, this policy is currently enforced by the PKIX and SunX509
+ * TrustManager implementations of the SunJSSE provider implementation.
*/
ENTRUST_TLS {
void checkDistrust(String variant, X509Certificate[] chain)
@@ -70,10 +66,9 @@ void checkDistrust(String variant, X509Certificate[] chain)
},
/**
- * Distrust TLS Server certificates anchored by a CAMERFIRMA root CA and
- * issued after April 15, 2025. If enabled, this policy is currently
- * enforced by the PKIX and SunX509 TrustManager implementations
- * of the SunJSSE provider implementation.
+ * Distrust TLS Server certificates anchored by a CAMERFIRMA root CA.
+ * If enabled, this policy is currently enforced by the PKIX and SunX509
+ * TrustManager implementations of the SunJSSE provider implementation.
*/
CAMERFIRMA_TLS {
void checkDistrust(String variant, X509Certificate[] chain)
@@ -87,9 +82,10 @@ void checkDistrust(String variant, X509Certificate[] chain)
/**
* Distrust TLS Server certificates anchored by the Chunghwa ePKI root CA
- * and issued after March 17, 2026. If enabled, this policy is currently
- * enforced by the PKIX and SunX509 TrustManager implementations
- * of the SunJSSE provider implementation.
+ * and issued after March 17, 2026. After APRIL 19, 2027, certificates will
+ * be distrusted regardless of the issued date in the certificate.
+ * If enabled, this policy is currently enforced by the PKIX and SunX509
+ * TrustManager implementations of the SunJSSE provider implementation.
*/
CHUNGHWA_TLS {
void checkDistrust(String variant, X509Certificate[] chain)
diff --git a/src/java.base/share/classes/sun/security/validator/CamerfirmaTLSPolicy.java b/src/java.base/share/classes/sun/security/validator/CamerfirmaTLSPolicy.java
index 984f5a0ab47f..fb6db1126893 100644
--- a/src/java.base/share/classes/sun/security/validator/CamerfirmaTLSPolicy.java
+++ b/src/java.base/share/classes/sun/security/validator/CamerfirmaTLSPolicy.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
@@ -25,12 +25,6 @@
package sun.security.validator;
import java.security.cert.X509Certificate;
-import java.time.LocalDate;
-import java.time.Month;
-import java.time.ZoneOffset;
-import java.util.Date;
-import java.util.Map;
-import java.util.Set;
import sun.security.util.Debug;
import sun.security.x509.X509CertImpl;
@@ -53,9 +47,7 @@ final class CamerfirmaTLSPolicy {
"063E4AFAC491DFD332F3089B8542E94617D893D7FE944E10A7937EE29D9693C0";
// Any TLS Server certificate that is anchored by one of the Camerfirma
- // roots above and is issued after this date will be distrusted.
- private static final LocalDate APRIL_15_2025 =
- LocalDate.of(2025, Month.APRIL, 15);
+ // roots above will be distrusted.
/**
* This method assumes the eeCert is a TLS Server Cert and chains back to
@@ -74,11 +66,10 @@ static void checkDistrust(X509Certificate[] chain)
+ "trust anchor of TLS server certificate");
}
if (FINGERPRINT.equalsIgnoreCase(fp)) {
- Date notBefore = chain[0].getNotBefore();
- LocalDate ldNotBefore = LocalDate.ofInstant(notBefore.toInstant(),
- ZoneOffset.UTC);
- // reject if certificate is issued after April 15, 2025
- checkNotBefore(ldNotBefore, APRIL_15_2025, anchor);
+ throw new ValidatorException
+ ("TLS Server certificate anchored by a distrusted legacy " +
+ "Camerfirma root CA: "+ anchor.getSubjectX500Principal(),
+ ValidatorException.T_UNTRUSTED_CERT, anchor);
}
}
@@ -86,17 +77,5 @@ private static String fingerprint(X509Certificate cert) {
return X509CertImpl.getFingerprint("SHA-256", cert, debug);
}
- private static void checkNotBefore(LocalDate notBeforeDate,
- LocalDate distrustDate, X509Certificate anchor)
- throws ValidatorException {
- if (notBeforeDate.isAfter(distrustDate)) {
- throw new ValidatorException
- ("TLS Server certificate issued after " + distrustDate +
- " and anchored by a distrusted legacy Camerfirma root CA: "
- + anchor.getSubjectX500Principal(),
- ValidatorException.T_UNTRUSTED_CERT, anchor);
- }
- }
-
private CamerfirmaTLSPolicy() {}
}
diff --git a/src/java.base/share/classes/sun/security/validator/ChunghwaTLSPolicy.java b/src/java.base/share/classes/sun/security/validator/ChunghwaTLSPolicy.java
index 114f5043fbf9..e7f0642f8606 100644
--- a/src/java.base/share/classes/sun/security/validator/ChunghwaTLSPolicy.java
+++ b/src/java.base/share/classes/sun/security/validator/ChunghwaTLSPolicy.java
@@ -29,8 +29,6 @@
import java.time.Month;
import java.time.ZoneOffset;
import java.util.Date;
-import java.util.Map;
-import java.util.Set;
import sun.security.util.Debug;
import sun.security.x509.X509CertImpl;
@@ -55,6 +53,12 @@ final class ChunghwaTLSPolicy {
private static final LocalDate MARCH_17_2026 =
LocalDate.of(2026, Month.MARCH, 17);
+ // Any TLS Server certificate that is anchored by the Chunghwa
+ // root above is distrusted after APRIL_19_2027.
+ // APRIL_19_2027 is MARCH_17_2026 plus 398 days.
+ private static final LocalDate APRIL_19_2027 =
+ LocalDate.of(2027, Month.APRIL, 19);
+
/**
* This method assumes the eeCert is a TLS Server Cert and chains back to
* the anchor.
@@ -72,6 +76,13 @@ static void checkDistrust(X509Certificate[] chain)
+ "trust anchor of TLS server certificate");
}
if (FINGERPRINT.equalsIgnoreCase(fp)) {
+ if (LocalDate.now().isAfter(APRIL_19_2027)) {
+ throw new ValidatorException
+ ("TLS Server certificate anchored by a distrusted legacy " +
+ "Chunghwa root CA: " + anchor.getSubjectX500Principal(),
+ ValidatorException.T_UNTRUSTED_CERT, anchor);
+ }
+
Date notBefore = chain[0].getNotBefore();
LocalDate ldNotBefore = LocalDate.ofInstant(notBefore.toInstant(),
ZoneOffset.UTC);
diff --git a/src/java.base/share/classes/sun/security/validator/EntrustTLSPolicy.java b/src/java.base/share/classes/sun/security/validator/EntrustTLSPolicy.java
index 831d1088ed35..efb84ac8bc99 100644
--- a/src/java.base/share/classes/sun/security/validator/EntrustTLSPolicy.java
+++ b/src/java.base/share/classes/sun/security/validator/EntrustTLSPolicy.java
@@ -25,11 +25,6 @@
package sun.security.validator;
import java.security.cert.X509Certificate;
-import java.time.LocalDate;
-import java.time.Month;
-import java.time.ZoneOffset;
-import java.util.Date;
-import java.util.Map;
import java.util.Set;
import sun.security.util.Debug;
@@ -70,9 +65,7 @@ final class EntrustTLSPolicy {
);
// Any TLS Server certificate that is anchored by one of the Entrust
- // roots above and is issued after this date will be distrusted.
- private static final LocalDate NOVEMBER_11_2024 =
- LocalDate.of(2024, Month.NOVEMBER, 11);
+ // roots above will be distrusted.
/**
* This method assumes the eeCert is a TLS Server Cert and chains back to
@@ -91,11 +84,10 @@ static void checkDistrust(X509Certificate[] chain)
+ "trust anchor of TLS server certificate");
}
if (FINGERPRINTS.contains(fp)) {
- Date notBefore = chain[0].getNotBefore();
- LocalDate ldNotBefore = LocalDate.ofInstant(notBefore.toInstant(),
- ZoneOffset.UTC);
- // reject if certificate is issued after November 11, 2024
- checkNotBefore(ldNotBefore, NOVEMBER_11_2024, anchor);
+ throw new ValidatorException
+ ("TLS Server certificate anchored by a distrusted legacy " +
+ "Entrust root CA: "+ anchor.getSubjectX500Principal(),
+ ValidatorException.T_UNTRUSTED_CERT, anchor);
}
}
@@ -103,17 +95,5 @@ private static String fingerprint(X509Certificate cert) {
return X509CertImpl.getFingerprint("SHA-256", cert, debug);
}
- private static void checkNotBefore(LocalDate notBeforeDate,
- LocalDate distrustDate, X509Certificate anchor)
- throws ValidatorException {
- if (notBeforeDate.isAfter(distrustDate)) {
- throw new ValidatorException
- ("TLS Server certificate issued after " + distrustDate +
- " and anchored by a distrusted legacy Entrust root CA: "
- + anchor.getSubjectX500Principal(),
- ValidatorException.T_UNTRUSTED_CERT, anchor);
- }
- }
-
private EntrustTLSPolicy() {}
}
diff --git a/src/java.base/share/classes/sun/security/validator/SymantecTLSPolicy.java b/src/java.base/share/classes/sun/security/validator/SymantecTLSPolicy.java
index a481a05c752b..01c090e7eb9f 100644
--- a/src/java.base/share/classes/sun/security/validator/SymantecTLSPolicy.java
+++ b/src/java.base/share/classes/sun/security/validator/SymantecTLSPolicy.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018, 2021, 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
@@ -25,11 +25,6 @@
package sun.security.validator;
import java.security.cert.X509Certificate;
-import java.time.LocalDate;
-import java.time.Month;
-import java.time.ZoneOffset;
-import java.util.Date;
-import java.util.Map;
import java.util.Set;
import sun.security.util.Debug;
@@ -123,28 +118,8 @@ final class SymantecTLSPolicy {
"2399561127A57125DE8CEFEA610DDF2FA078B5C8067F4E828290BFB860E84B3C"
);
- private static final LocalDate DECEMBER_31_2019 =
- LocalDate.of(2019, Month.DECEMBER, 31);
- // SHA-256 certificate fingerprints of subCAs with later distrust dates
- private static final Map EXEMPT_SUBCAS = Map.of(
- // Subject DN: C=US, O=Apple Inc., OU=Certification Authority,
- // CN=Apple IST CA 2 - G1
- // Issuer DN: CN=GeoTrust Global CA, O=GeoTrust Inc., C=US
- "AC2B922ECFD5E01711772FEA8ED372DE9D1E2245FCE3F57A9CDBEC77296A424B",
- DECEMBER_31_2019,
- // Subject DN: C=US, O=Apple Inc., OU=Certification Authority,
- // CN=Apple IST CA 8 - G1
- // Issuer DN: CN=GeoTrust Primary Certification Authority - G2,
- // OU=(c) 2007 GeoTrust Inc. - For authorized use only,
- // O=GeoTrust Inc., C=US
- "A4FE7C7F15155F3F0AEF7AAA83CF6E06DEB97CA3F909DF920AC1490882D488ED",
- DECEMBER_31_2019
- );
-
// Any TLS Server certificate that is anchored by one of the Symantec
- // roots above and is issued after this date will be distrusted.
- private static final LocalDate APRIL_16_2019 =
- LocalDate.of(2019, Month.APRIL, 16);
+ // roots above will be distrusted.
/**
* This method assumes the eeCert is a TLS Server Cert and chains back to
@@ -163,26 +138,10 @@ static void checkDistrust(X509Certificate[] chain)
+ "trust anchor of TLS server certificate");
}
if (FINGERPRINTS.contains(fp)) {
- Date notBefore = chain[0].getNotBefore();
- LocalDate ldNotBefore = LocalDate.ofInstant(notBefore.toInstant(),
- ZoneOffset.UTC);
- // check if chain goes through one of the subCAs
- if (chain.length > 2) {
- X509Certificate subCA = chain[chain.length-2];
- fp = fingerprint(subCA);
- if (fp == null) {
- throw new ValidatorException("Cannot generate fingerprint "
- + "for intermediate CA of TLS server certificate");
- }
- LocalDate distrustDate = EXEMPT_SUBCAS.get(fp);
- if (distrustDate != null) {
- // reject if certificate is issued after specified date
- checkNotBefore(ldNotBefore, distrustDate, anchor);
- return; // success
- }
- }
- // reject if certificate is issued after April 16, 2019
- checkNotBefore(ldNotBefore, APRIL_16_2019, anchor);
+ throw new ValidatorException(
+ "TLS Server certificate anchored by a distrusted legacy " +
+ "Symantec root CA: " +anchor.getSubjectX500Principal(),
+ ValidatorException.T_UNTRUSTED_CERT, anchor);
}
}
@@ -190,17 +149,5 @@ private static String fingerprint(X509Certificate cert) {
return X509CertImpl.getFingerprint("SHA-256", cert, debug);
}
- private static void checkNotBefore(LocalDate notBeforeDate,
- LocalDate distrustDate, X509Certificate anchor)
- throws ValidatorException {
- if (notBeforeDate.isAfter(distrustDate)) {
- throw new ValidatorException
- ("TLS Server certificate issued after " + distrustDate +
- " and anchored by a distrusted legacy Symantec root CA: "
- + anchor.getSubjectX500Principal(),
- ValidatorException.T_UNTRUSTED_CERT, anchor);
- }
- }
-
private SymantecTLSPolicy() {}
}
diff --git a/src/java.base/share/conf/security/java.security b/src/java.base/share/conf/security/java.security
index 4289254affcf..54a7c3b915a8 100644
--- a/src/java.base/share/conf/security/java.security
+++ b/src/java.base/share/conf/security/java.security
@@ -1,4 +1,4 @@
-#
+
# This is the "master security properties file".
#
# An alternate java.security properties file may be specified
@@ -1451,23 +1451,18 @@ jdk.sasl.disabledMechanisms=
# The supported values are:
#
# SYMANTEC_TLS : Distrust TLS Server certificates anchored by a Symantec
-# root CA and issued after April 16, 2019 unless issued by one of the
-# following subordinate CAs which have a later distrust date:
-# 1. Apple IST CA 2 - G1, SHA-256 fingerprint:
-# AC2B922ECFD5E01711772FEA8ED372DE9D1E2245FCE3F57A9CDBEC77296A424B
-# Distrust after December 31, 2019.
-# 2. Apple IST CA 8 - G1, SHA-256 fingerprint:
-# A4FE7C7F15155F3F0AEF7AAA83CF6E06DEB97CA3F909DF920AC1490882D488ED
-# Distrust after December 31, 2019.
+# root CA.
#
# ENTRUST_TLS : Distrust TLS Server certificates anchored by
-# an Entrust root CA and issued after November 11, 2024.
+# an Entrust root CA.
#
# CAMERFIRMA_TLS : Distrust TLS Server certificates anchored by
-# a Camerfirma root CA and issued after April 15, 2025.
+# a Camerfirma root CA.
#
# CHUNGHWA_TLS : Distrust TLS Server certificates anchored by
# a Chunghwa root CA and issued after March 17, 2026.
+# After April 19, 2027, certificates will be distrusted regardless
+# of the date the certificate was issued.
#
# Leading and trailing whitespace surrounding each value are ignored.
# Unknown values are ignored. If the property is commented out or set to the
diff --git a/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Camerfirma.java b/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Camerfirma.java
index 8d9dfe1ef18f..98633de2a8a0 100644
--- a/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Camerfirma.java
+++ b/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Camerfirma.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
@@ -38,7 +38,7 @@
* @modules java.base/sun.security.validator
* @run main/othervm Camerfirma after policyOn invalid
* @run main/othervm Camerfirma after policyOff valid
- * @run main/othervm Camerfirma before policyOn valid
+ * @run main/othervm Camerfirma before policyOn invalid
* @run main/othervm Camerfirma before policyOff valid
*/
@@ -50,7 +50,7 @@ public class Camerfirma {
// named "-chain.pem".
private static final String ROOT_TO_TEST = "camerfirmachambersca";
- // Date after the restrictions take effect
+ // Date after the restrictions took effect when distrust was first announced
private static final ZonedDateTime DISTRUST_DATE =
LocalDate.of(2025, 04, 16).atStartOfDay(ZoneOffset.UTC);
diff --git a/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Chunghwa.java b/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Chunghwa.java
index 8512640bb015..ce94daccdcd9 100644
--- a/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Chunghwa.java
+++ b/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Chunghwa.java
@@ -42,6 +42,7 @@
* @run main/othervm Chunghwa before policyOff valid
*/
+
public class Chunghwa {
private static final String CERT_PATH = "chains" + File.separator + "chunghwa";
@@ -50,12 +51,18 @@ public class Chunghwa {
// named "-chain.pem".
private static final String ROOT_TO_TEST = "chunghwaepkirootca";
- // Date after the restrictions take effect
+ // Date after the restrictions took effect when distrust was first announced
private static final ZonedDateTime DISTRUST_DATE =
LocalDate.of(2026, 03, 18).atStartOfDay(ZoneOffset.UTC);
+ private static final LocalDate APRIL_19_2027 = LocalDate.of(2027, 04, 19);
+ private static final LocalDate TODAY = LocalDate.now();
public static void main(String[] args) throws Exception {
+ if (args[1].equals("policyOn") && TODAY.isAfter(APRIL_19_2027)) {
+ args[2] = "invalid";
+ }
+
Distrust distrust = new Distrust(args);
X509TrustManager[] tms = new X509TrustManager[]{
diff --git a/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Entrust.java b/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Entrust.java
index 9fd4b0038d1e..3f25ab4a2132 100644
--- a/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Entrust.java
+++ b/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Entrust.java
@@ -35,7 +35,7 @@
* @modules java.base/sun.security.validator
* @run main/othervm Entrust after policyOn invalid
* @run main/othervm Entrust after policyOff valid
- * @run main/othervm Entrust before policyOn valid
+ * @run main/othervm Entrust before policyOn invalid
* @run main/othervm Entrust before policyOff valid
*/
@@ -48,7 +48,7 @@ public class Entrust {
private static final String[] ROOTS_TO_TEST = new String[]{
"entrustevca", "entrustrootcaec1", "entrustrootcag2", "entrust2048ca"};
- // Date when the restrictions take effect
+ // Date after the restrictions took effect when distrust was first announced
private static final ZonedDateTime DISTRUST_DATE =
LocalDate.of(2024, 11, 12).atStartOfDay(ZoneOffset.UTC);
diff --git a/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Symantec.java b/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Symantec.java
index 4e099a8de8de..2bfb1c7f6b7a 100644
--- a/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Symantec.java
+++ b/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Symantec.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018, 2024, 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
@@ -36,7 +36,7 @@
* @modules java.base/sun.security.validator
* @run main/othervm Symantec after policyOn invalid
* @run main/othervm Symantec after policyOff valid
- * @run main/othervm Symantec before policyOn valid
+ * @run main/othervm Symantec before policyOn invalid
* @run main/othervm Symantec before policyOff valid
*/
@@ -57,7 +57,7 @@ public class Symantec {
// chain stored in a file named "-chain.pem".
private static String[] subCAsToTest = new String[]{"appleistca8g1"};
- // Date when the restrictions take effect
+ // Date after the restrictions took effect when distrust was first announced
private static final ZonedDateTime ROOTS_DISTRUST_DATE =
LocalDate.of(2019, 4, 17).atStartOfDay(ZoneOffset.UTC);
@@ -87,9 +87,5 @@ public static void main(String[] args) throws Exception {
// test code-signing chain (should be valid as restrictions don't apply)
Date validationDate = new Date(1544197375493L);
distrust.testCodeSigningChain(certPath, "verisignclass3g5ca-codesigning", validationDate);
-
- // test chains issued through subCAs
- notBefore = distrust.getNotBefore(SUBCA_DISTRUST_DATE);
- distrust.testCertificateChain(certPath, notBefore, tms, subCAsToTest);
}
}
From d996499d72c2db45239dd766e5587c3c758ecd1e Mon Sep 17 00:00:00 2001
From: Weijun Wang
Date: Thu, 30 Apr 2026 18:09:42 +0000
Subject: [PATCH 003/169] 8383076: Enhance SASL Authentication
Reviewed-by: abarashev, ascarpino, rhalade
---
.../com/sun/security/sasl/ntlm/NTLMClient.java | 11 +++++++----
.../com/sun/security/sasl/ntlm/NTLMServer.java | 11 +++++++----
2 files changed, 14 insertions(+), 8 deletions(-)
diff --git a/src/java.security.sasl/share/classes/com/sun/security/sasl/ntlm/NTLMClient.java b/src/java.security.sasl/share/classes/com/sun/security/sasl/ntlm/NTLMClient.java
index 6f3832d3b435..d79662535eef 100644
--- a/src/java.security.sasl/share/classes/com/sun/security/sasl/ntlm/NTLMClient.java
+++ b/src/java.security.sasl/share/classes/com/sun/security/sasl/ntlm/NTLMClient.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 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,6 +27,8 @@
import com.sun.security.ntlm.Client;
import com.sun.security.ntlm.NTLMException;
+import sun.security.jca.JCAUtil;
+
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
@@ -69,8 +71,9 @@
*
* com.sun.security.sasl.ntlm.random
* java.util.Random, the nonce source to be used in NTLM v2 or NTLM v1 with
- * Client Challenge. Default null, an internal java.util.Random object
- * will be used
+ * Client Challenge. Users are encouraged to provide a
+ * java.security.SecureRandom object. If not provided, then an internal
+ * SecureRandom object is used.
*
* Negotiated Properties:
*
@@ -127,7 +130,7 @@ final class NTLMClient implements SaslClient {
rtmp = (Random)props.get(NTLM_RANDOM);
hostname = (String)props.get(NTLM_HOSTNAME);
}
- this.random = rtmp != null ? rtmp : new Random();
+ this.random = rtmp != null ? rtmp : JCAUtil.getDefSecureRandom();
if (version == null) {
version = System.getProperty("ntlm.version");
diff --git a/src/java.security.sasl/share/classes/com/sun/security/sasl/ntlm/NTLMServer.java b/src/java.security.sasl/share/classes/com/sun/security/sasl/ntlm/NTLMServer.java
index 0bd34e40fd15..94a15989028a 100644
--- a/src/java.security.sasl/share/classes/com/sun/security/sasl/ntlm/NTLMServer.java
+++ b/src/java.security.sasl/share/classes/com/sun/security/sasl/ntlm/NTLMServer.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010, 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 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,6 +27,8 @@
import com.sun.security.ntlm.NTLMException;
import com.sun.security.ntlm.Server;
+import sun.security.jca.JCAUtil;
+
import java.io.IOException;
import java.util.Map;
import java.util.Random;
@@ -67,8 +69,9 @@
* String, the domain of the server, default is server name (fqdn parameter)
*
* com.sun.security.sasl.ntlm.random
- * java.util.Random, the nonce source. Default null, an internal
- * java.util.Random object will be used
+ * java.util.Random, the nonce source. Users are encouraged to provide
+ * a java.security.SecureRandom object. If not provided, then an internal
+ * SecureRandom object is used.
*
* Negotiated Properties:
*
@@ -123,7 +126,7 @@ final class NTLMServer implements SaslServer {
version = (String)props.get(NTLM_VERSION);
rtmp = (Random)props.get(NTLM_RANDOM);
}
- random = rtmp != null ? rtmp : new Random();
+ random = rtmp != null ? rtmp : JCAUtil.getDefSecureRandom();
if (version == null) {
version = System.getProperty("ntlm.version");
From 5b905b80a99e9d677a1d5bc200c24f026d57e13c Mon Sep 17 00:00:00 2001
From: Weijun Wang
Date: Fri, 8 May 2026 14:12:10 +0000
Subject: [PATCH 004/169] 8383281: Enhance MD5 Digesting
Reviewed-by: mschoene, rhalade, abarashev, hchao, mpowers
---
.../classes/com/sun/security/sasl/digest/DigestMD5Base.java | 6 +++---
.../com/sun/security/sasl/digest/DigestMD5Client.java | 6 +++---
.../com/sun/security/sasl/digest/DigestMD5Server.java | 5 +++--
3 files changed, 9 insertions(+), 8 deletions(-)
diff --git a/src/java.security.sasl/share/classes/com/sun/security/sasl/digest/DigestMD5Base.java b/src/java.security.sasl/share/classes/com/sun/security/sasl/digest/DigestMD5Base.java
index 567c71711165..547305f55fa2 100644
--- a/src/java.security.sasl/share/classes/com/sun/security/sasl/digest/DigestMD5Base.java
+++ b/src/java.security.sasl/share/classes/com/sun/security/sasl/digest/DigestMD5Base.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2000, 2025, 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
@@ -1000,7 +1000,7 @@ public byte[] unwrap(byte[] incoming, int start, int len)
}
/* First, compare MAC's before updating any of our state */
- if (!Arrays.equals(mac, expectedMac)) {
+ if (!MessageDigest.isEqual(mac, expectedMac)) {
// Discard message and do not increment sequence number
logger.log(Level.INFO, "DIGEST23:Unmatched MACs");
return EMPTY_BYTE_ARRAY;
@@ -1450,7 +1450,7 @@ public byte[] unwrap(byte[] incoming, int start, int len)
}
// First, compare MACs before updating state
- if (!Arrays.equals(mac, expectedMac)) {
+ if (!MessageDigest.isEqual(mac, expectedMac)) {
// Discard message and do not increment sequence number
logger.log(Level.INFO, "DIGEST41:Unmatched MACs");
return EMPTY_BYTE_ARRAY;
diff --git a/src/java.security.sasl/share/classes/com/sun/security/sasl/digest/DigestMD5Client.java b/src/java.security.sasl/share/classes/com/sun/security/sasl/digest/DigestMD5Client.java
index c9938ec675d7..e48192cce9e6 100644
--- a/src/java.security.sasl/share/classes/com/sun/security/sasl/digest/DigestMD5Client.java
+++ b/src/java.security.sasl/share/classes/com/sun/security/sasl/digest/DigestMD5Client.java
@@ -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
@@ -25,6 +25,7 @@
package com.sun.security.sasl.digest;
+import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -32,7 +33,6 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
-import java.util.Arrays;
import java.util.logging.Level;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -657,7 +657,7 @@ private void validateResponseValue(byte[] fromServer) throws SaslException {
byte[] expected = generateResponseValue("",
digestUri, negotiatedQop, username, negotiatedRealm,
passwd, nonce, cnonce, nonceCount, authzidBytes);
- if (!Arrays.equals(expected, fromServer)) {
+ if (!MessageDigest.isEqual(expected, fromServer)) {
/* Server's rspauth value does not match */
throw new SaslException(
"Server's rspauth value does not match what client expects");
diff --git a/src/java.security.sasl/share/classes/com/sun/security/sasl/digest/DigestMD5Server.java b/src/java.security.sasl/share/classes/com/sun/security/sasl/digest/DigestMD5Server.java
index bc508e7397be..f0d43b352237 100644
--- a/src/java.security.sasl/share/classes/com/sun/security/sasl/digest/DigestMD5Server.java
+++ b/src/java.security.sasl/share/classes/com/sun/security/sasl/digest/DigestMD5Server.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003, 2022, 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
@@ -27,6 +27,7 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.StringTokenizer;
import java.util.ArrayList;
@@ -619,7 +620,7 @@ private byte[] validateClientResponse(byte[][] responseVal)
"DIGEST-MD5: problem duplicating client response", e);
}
- if (!Arrays.equals(responseFromClient, expectedResponse)) {
+ if (!MessageDigest.isEqual(responseFromClient, expectedResponse)) {
throw new SaslException("DIGEST-MD5: digest response format " +
"violation. Mismatched response.");
}
From c8c9bcce19278f4aa5776397bc450abf2bfc9a2a Mon Sep 17 00:00:00 2001
From: Valerie Peng
Date: Wed, 13 May 2026 17:10:08 +0000
Subject: [PATCH 005/169] 8384165: Improve ciphtertext handling
Reviewed-by: mschoene, rhalade, ascarpino, semery
---
.../classes/com/sun/crypto/provider/GaloisCounterMode.java | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/src/java.base/share/classes/com/sun/crypto/provider/GaloisCounterMode.java b/src/java.base/share/classes/com/sun/crypto/provider/GaloisCounterMode.java
index 2f4df1eba03b..5c09b061a9c9 100644
--- a/src/java.base/share/classes/com/sun/crypto/provider/GaloisCounterMode.java
+++ b/src/java.base/share/classes/com/sun/crypto/provider/GaloisCounterMode.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
@@ -1603,8 +1603,9 @@ public int doFinal(ByteBuffer src, ByteBuffer dst)
NIO_ACCESS.acquireSession(dst);
try {
Unsafe.getUnsafe().setMemory(
- NIO_ACCESS.getBufferAddress(dst),
- len + dst.position(), (byte) 0);
+ NIO_ACCESS.getBufferAddress(dst)
+ + dst.position(),
+ len, (byte) 0);
} finally {
NIO_ACCESS.releaseSession(dst);
}
From 8a16825012e8a695c1fdc156b368e35e4fbf79c4 Mon Sep 17 00:00:00 2001
From: Daniel Fuchs
Date: Tue, 19 May 2026 17:37:15 +0000
Subject: [PATCH 006/169] 8383756: Enhance Network Clients
Reviewed-by: michaelm, rhalade, skoivu, djelinski
---
src/java.base/share/classes/sun/net/ftp/impl/FtpClient.java | 4 ++--
src/java.base/share/classes/sun/net/smtp/SmtpClient.java | 6 +++---
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/java.base/share/classes/sun/net/ftp/impl/FtpClient.java b/src/java.base/share/classes/sun/net/ftp/impl/FtpClient.java
index 1d6876f760e7..1951dbc925a1 100644
--- a/src/java.base/share/classes/sun/net/ftp/impl/FtpClient.java
+++ b/src/java.base/share/classes/sun/net/ftp/impl/FtpClient.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
@@ -526,7 +526,7 @@ private boolean issueCommand(String cmd) throws IOException,
// ignore...
}
}
- if (cmd.indexOf('\n') != -1) {
+ if (cmd.indexOf('\n') != -1 || cmd.indexOf('\r') != -1) {
sun.net.ftp.FtpProtocolException ex
= new sun.net.ftp.FtpProtocolException("Illegal FTP command");
ex.initCause(new IllegalArgumentException("Illegal carriage return"));
diff --git a/src/java.base/share/classes/sun/net/smtp/SmtpClient.java b/src/java.base/share/classes/sun/net/smtp/SmtpClient.java
index 494bcf199879..73968ddf1926 100644
--- a/src/java.base/share/classes/sun/net/smtp/SmtpClient.java
+++ b/src/java.base/share/classes/sun/net/smtp/SmtpClient.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1995, 2024, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1995, 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
@@ -74,7 +74,7 @@ private void toCanonical(String s) throws IOException {
}
public void to(String s) throws IOException {
- if (s.indexOf('\n') != -1) {
+ if (s.indexOf('\n') != -1 || s.indexOf('\r') != -1) {
throw new IOException("Illegal SMTP command",
new IllegalArgumentException("Illegal carriage return"));
}
@@ -120,7 +120,7 @@ else if (st == pos)
}
public void from(String s) throws IOException {
- if (s.indexOf('\n') != -1) {
+ if (s.indexOf('\n') != -1 || s.indexOf('\r') != -1) {
throw new IOException("Illegal SMTP command",
new IllegalArgumentException("Illegal carriage return"));
}
From f4a6e72b09101705b3279d07f68b94d322f78b2d Mon Sep 17 00:00:00 2001
From: Phil Race
Date: Tue, 19 May 2026 18:13:01 +0000
Subject: [PATCH 007/169] 8384445: Enhance JPEG loading
Reviewed-by: mschoene, jdv, rhalade, kizune
---
.../com/sun/imageio/plugins/jpeg/JPEGImageReader.java | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageReader.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageReader.java
index 1d6bf2dbf8a4..da815eda4e52 100644
--- a/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageReader.java
+++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageReader.java
@@ -1258,9 +1258,15 @@ private Raster readInternal(int imageIndex,
int[] sBands = param.getSourceBands();
if (sBands != null) {
- srcBands = sBands;
+ srcBands = sBands.clone();
numRasterBands = srcBands.length;
}
+ for (int b = 0; b < srcBands.length; b++) {
+ if (srcBands[b] < 0 || srcBands[b] >= numComponents) {
+ throw new IllegalArgumentException(
+ "ImageReadParam out of range source band : " + srcBands[b]);
+ }
+ }
if (!wantRaster) { // ignore dest bands for Raster
destinationBands = param.getDestinationBands();
}
From 763b8a5da4497b6fb81f65b25d8a3e931e0a49b2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Roberto=20Casta=C3=B1eda=20Lozano?=
Date: Wed, 20 May 2026 07:58:27 +0000
Subject: [PATCH 008/169] 8382378: Improve C2 loop transformations
Co-authored-by: Emanuel Peter
Co-authored-by: Quan Anh Mai
Co-authored-by: Christian Hagedorn
Reviewed-by: mschoene, rhalade, chagedorn, qamai, epeter
---
src/hotspot/share/opto/loopPredicate.cpp | 19 +-
src/hotspot/share/opto/loopTransform.cpp | 473 ++++--
src/hotspot/share/opto/loopnode.cpp | 1453 ++++++++++++++---
src/hotspot/share/opto/loopnode.hpp | 12 +-
.../c2/irTests/TestLongRangeChecks.java | 36 +-
.../irTests/TestVectorizationMultiInvar.java | 22 +-
.../compiler/lib/ir_framework/IRNode.java | 6 +
.../rangechecks/TestIndexMatching.java | 779 +++++++++
.../TestLongRangeCheckWithZeroScaling.java | 53 +
9 files changed, 2455 insertions(+), 398 deletions(-)
create mode 100644 test/hotspot/jtreg/compiler/rangechecks/TestIndexMatching.java
create mode 100644 test/hotspot/jtreg/compiler/rangechecks/TestLongRangeCheckWithZeroScaling.java
diff --git a/src/hotspot/share/opto/loopPredicate.cpp b/src/hotspot/share/opto/loopPredicate.cpp
index be099747fc01..f3c45cd497fd 100644
--- a/src/hotspot/share/opto/loopPredicate.cpp
+++ b/src/hotspot/share/opto/loopPredicate.cpp
@@ -457,10 +457,10 @@ class Invariance : public StackObj {
}
};
-//------------------------------is_range_check_if -----------------------------------
-// Returns true if the predicate of iff is in "scale*iv + offset u< load_range(ptr)" format
-// Note: this function is particularly designed for loop predication. We require load_range
-// and offset to be loop invariant computed on the fly by "invar"
+// Returns true if the predicate has the form
+// scale*iv + offset = 0.
+// Note: This is used by Loop Predication and PhaseIdealLoop::extract_long_range_check().
bool IdealLoopTree::is_range_check_if(IfProjNode* if_success_proj, PhaseIdealLoop *phase, BasicType bt, Node *iv, Node *&range,
Node *&offset, jlong &scale) const {
IfNode* iff = if_success_proj->in(0)->as_If();
@@ -518,20 +518,23 @@ bool IdealLoopTree::is_range_check_if(IfProjNode* if_success_proj, PhaseIdealLoo
return false;
}
range = cmp->in(2);
+
+ // Now we check the condition R >= 0 from the method comment.
if (range->Opcode() != Op_LoadRange) {
const TypeInteger* tinteger = phase->_igvn.type(range)->isa_integer(bt);
if (tinteger == nullptr || tinteger->empty() || tinteger->lo_as_long() < 0) {
- // Allow predication on positive values that aren't LoadRanges.
- // This allows optimization of loops where the length of the
- // array is a known value and doesn't need to be loaded back
- // from the array.
+ // R is not proven to be non-negative. Bail out.
return false;
}
} else {
+ // R = LoadRange are always non-negative.
assert(bt == T_INT, "no LoadRange for longs");
}
scale = 0;
offset = nullptr;
+ // Now check the form
+ // scale*iv + offset is_scaled_iv_plus_offset(cmp->in(1), iv, bt, &scale, &offset)) {
return false;
}
diff --git a/src/hotspot/share/opto/loopTransform.cpp b/src/hotspot/share/opto/loopTransform.cpp
index 209e4bd6bea4..20bfe53396e8 100644
--- a/src/hotspot/share/opto/loopTransform.cpp
+++ b/src/hotspot/share/opto/loopTransform.cpp
@@ -2612,8 +2612,15 @@ void PhaseIdealLoop::add_constraint(jlong stride_con, jlong scale_con, Node* off
//----------------------------------is_iv------------------------------------
// Return true if exp is the value (of type bt) of the given induction var.
-// This grammar of cases is recognized, where X is I|L according to bt:
-// VIV[iv] = iv | (CastXX VIV[iv]) | (ConvI2X VIV[iv])
+// On success, we guarantee this form:
+// if bt = T_INT (implies iv is int):
+// exp = iv
+// if bt = T_LONG and iv is long:
+// exp = iv
+// if bt = T_LONG and iv is int:
+// exp = ConvI2L(iv)
+//
+// See grammar specification in PhaseIdealLoop::is_scaled_iv_plus_offset().
bool PhaseIdealLoop::is_iv(Node* exp, Node* iv, BasicType bt) {
exp = exp->uncast();
if (exp == iv && iv->bottom_type()->isa_integer(bt)) {
@@ -2628,88 +2635,129 @@ bool PhaseIdealLoop::is_iv(Node* exp, Node* iv, BasicType bt) {
//------------------------------is_scaled_iv---------------------------------
// Return true if exp is a constant times the given induction var (of type bt).
-// The multiplication is either done in full precision (exactly of type bt),
-// or else bt is T_LONG but iv is scaled using 32-bit arithmetic followed by a ConvI2L.
-// This grammar of cases is recognized, where X is I|L according to bt:
-// SIV[iv] = VIV[iv] | (CastXX SIV[iv])
-// | (MulX VIV[iv] ConX) | (MulX ConX VIV[iv])
-// | (LShiftX VIV[iv] ConI)
-// | (ConvI2L SIV[iv]) -- a "short-scale" can occur here; note recursion
-// | (SubX 0 SIV[iv]) -- same as MulX(iv, -scale); note recursion
-// | (AddX SIV[iv] SIV[iv]) -- sum of two scaled iv; note recursion
-// | (SubX SIV[iv] SIV[iv]) -- difference of two scaled iv; note recursion
-// VIV[iv] = [either iv or its value converted; see is_iv() above]
-// On success, the constant scale value is stored back to *p_scale.
-// The value (*p_short_scale) reports if such a ConvI2L conversion was present.
-bool PhaseIdealLoop::is_scaled_iv(Node* exp, Node* iv, BasicType bt, jlong* p_scale, bool* p_short_scale, int depth) {
- BasicType exp_bt = bt;
- exp = exp->uncast(); //strip casts
- assert(exp_bt == T_INT || exp_bt == T_LONG, "unexpected int type");
- if (is_iv(exp, iv, exp_bt)) {
+// On success, we guarantee this form:
+// if bt = T_INT (implies iv is int):
+// exp = MulI( iv, p_scale) where p_scale is int
+// if bt = T_LONG and iv is long:
+// exp = MulL( iv, p_scale)
+// if bt = T_LONG and iv is int:
+// exp = MulL(ConvI2L(iv), p_scale)
+// where the constant scale value is stored back to *p_scale unless null.
+// The scaling is always done in full precision (exactly of type bt).
+// See grammar specification in PhaseIdealLoop::is_scaled_iv_plus_offset().
+bool PhaseIdealLoop::is_scaled_iv(Node* exp, Node* iv, BasicType bt, jlong* p_scale, int depth) {
+ exp = exp->uncast(); // Strip casts.
+ assert(bt == T_INT || bt == T_LONG, "unexpected int type");
+ if (is_iv(exp, iv, bt)) {
+ // if bt = T_INT (implies iv is int):
+ // exp = iv = MulI( iv, p_scale=1) where p_scale=1 is trivially an int
+ // if bt = T_LONG and iv is long:
+ // exp = iv = MulL( iv, p_scale=1)
+ // if bt = T_LONG and iv is int:
+ // exp = ConvI2L(iv) = MulL(ConvI2L(iv), p_scale=1)
if (p_scale != nullptr) {
*p_scale = 1;
}
- if (p_short_scale != nullptr) {
- *p_short_scale = false;
- }
return true;
}
- if (exp_bt == T_LONG && iv->bottom_type()->isa_int() && exp->Opcode() == Op_ConvI2L) {
- exp = exp->in(1);
- exp_bt = T_INT;
+ if (bt == T_LONG && iv->bottom_type()->isa_int() && exp->Opcode() == Op_ConvI2L) {
+ // Risk for short scaling: avoid long range-check transformations, because
+ // the current implementation (transform_long_range_checks) does not protect
+ // against overflow.
+ return false;
}
int opc = exp->Opcode();
int which = 0; // this is which subexpression we find the iv in
// Can't use is_Mul() here as it's true for AndI and AndL
- if (opc == Op_Mul(exp_bt)) {
- if ((is_iv(exp->in(which = 1), iv, exp_bt) && exp->in(2)->is_Con()) ||
- (is_iv(exp->in(which = 2), iv, exp_bt) && exp->in(1)->is_Con())) {
+ if (opc == Op_Mul(bt)) {
+ // We need to test both ways because the multiplication node may not have
+ // been canonicalized yet, for example after successful loop predication.
+ if ((is_iv(exp->in(which = 1), iv, bt) && exp->in(2)->is_Con()) ||
+ (is_iv(exp->in(which = 2), iv, bt) && exp->in(1)->is_Con())) {
Node* factor = exp->in(which == 1 ? 2 : 1); // the other argument
- jlong scale = factor->find_integer_as_long(exp_bt, 0);
+ jlong scale = factor->find_integer_as_long(bt, 0);
if (scale == 0) {
- return false; // might be top
+ return false; // find_integer_as_long may have failed, value might be top.
}
+ // if bt = T_INT (implies iv is int):
+ // exp->in(which) = iv
+ // Note: find_integer_as_long ensures that scale is int
+ // -> which = 1: exp = MulI( iv, scale) where scale is int
+ // -> which = 2: exp = MulI(scale, iv) = MulI( iv, scale) where scale is int
+ // if bt = T_LONG and iv is long:
+ // exp->in(which) = iv
+ // -> which = 1: exp = MulL( iv, scale)
+ // -> which = 2: exp = MulL(scale, iv) = MulL( iv, scale)
+ // if bt = T_LONG and iv is int:
+ // exp->in(which) = ConvI2L(iv)
+ // -> which = 1: exp = MulL(ConvI2L(iv), scale)
+ // -> which = 2: exp = MulL(scale, ConvI2L(iv)) = MulL(ConvI2L(iv), scale)
if (p_scale != nullptr) {
*p_scale = scale;
}
- if (p_short_scale != nullptr) {
- // (ConvI2L (MulI iv K)) can be 64-bit linear if iv is kept small enough...
- *p_short_scale = (exp_bt != bt && scale != 1);
- }
return true;
}
- } else if (opc == Op_LShift(exp_bt)) {
- if (is_iv(exp->in(1), iv, exp_bt) && exp->in(2)->is_Con()) {
+ } else if (opc == Op_LShift(bt)) {
+ if (is_iv(exp->in(1), iv, bt) && exp->in(2)->is_Con()) {
jint shift_amount = exp->in(2)->find_int_con(min_jint);
if (shift_amount == min_jint) {
- return false; // might be top
+ return false; // find_int_con may have failed, value might be top.
}
jlong scale;
- if (exp_bt == T_INT) {
+ if (bt == T_INT) {
+ // LShiftI(x, shift_amount) = MulI(x, LShiftI(1, shift_amount)) = MulI(x, scale)
+ // Note: scale is guaranteed to be an int
scale = java_shift_left((jint)1, (juint)shift_amount);
- } else if (exp_bt == T_LONG) {
+ } else if (bt == T_LONG) {
+ // LShiftL(x, shift_amount) = MulL(x, LShiftL(1L, shift_amount)) = MulL(x, scale)
scale = java_shift_left((jlong)1, (julong)shift_amount);
}
+ // if bt = T_INT (implies iv is int):
+ // exp->in(1) = iv
+ // exp = LShiftI( iv, shift_amount) = MulI( iv, scale) where scale is int
+ // if bt = T_LONG and iv is long:
+ // exp->in(1) = iv
+ // exp = LShiftL( iv, shift_amount) = MulL( iv, scale)
+ // if bt = T_LONG and iv is int:
+ // exp->in(1) = ConvI2L(iv)
+ // exp = LShiftL(ConvI2L(iv), shift_amount) = MulL(ConvI2L(iv), scale)
if (p_scale != nullptr) {
*p_scale = scale;
}
- if (p_short_scale != nullptr) {
- // (ConvI2L (MulI iv K)) can be 64-bit linear if iv is kept small enough...
- *p_short_scale = (exp_bt != bt && scale != 1);
- }
return true;
}
- } else if (opc == Op_Add(exp_bt)) {
+ } else if (opc == Op_Add(bt)) {
jlong scale_l = 0;
jlong scale_r = 0;
- bool short_scale_l = false;
- bool short_scale_r = false;
if (depth == 0 &&
- is_scaled_iv(exp->in(1), iv, exp_bt, &scale_l, &short_scale_l, depth + 1) &&
- is_scaled_iv(exp->in(2), iv, exp_bt, &scale_r, &short_scale_r, depth + 1)) {
- // AddX(iv*K1, iv*K2) => iv*(K1+K2)
+ is_scaled_iv(exp->in(1), iv, bt, &scale_l, depth + 1) &&
+ is_scaled_iv(exp->in(2), iv, bt, &scale_r, depth + 1)) {
+ // We know that:
+ // AddI(MulI(x, scale_l), MulI(x, scale_r)) = MulI(x, scale=AddI(scale_l, scale_r)) where scale is int
+ // AddL(MulL(x, scale_l), MulL(x, scale_r)) = MulL(x, scale=AddL(scale_l, scale_r))
+ //
+ // if bt = T_INT (implies iv is int):
+ // e1 = exp->in(1) = MulI(iv, scale_l) where scale_l is int
+ // e2 = exp->in(2) = MulI(iv, scale_r) where scale_r is int
+ // exp = AddI(e1, e2) = MulI(iv, scale=AddI(scale_l, scale_r)) where scale is int
+ // if bt = T_LONG and iv is long:
+ // e1 = exp->in(1) = MulL(iv, scale_l)
+ // e2 = exp->in(2) = MulL(iv, scale_r)
+ // exp = AddL(e1, e2) = MulL(iv, scale=AddL(scale_l, scale_r))
+ // if bt = T_LONG and iv is int:
+ // e1 = exp->in(1) = MulL(ConvI2L(iv), scale_l)
+ // e2 = exp->in(2) = MulL(ConvI2L(iv), scale_r)
+ // exp = AddL(e1, e2) = MulL(ConvI2L(iv), scale=AddL(scale_l, scale_r))
+ //
+ // For the scale computation, we could have used the same type case
+ // distinction as in LShift above. But instead, we compute in long for
+ // all cases. We need to show that this is OK if bt=T_INT.
+ // We would have computed
+ // scale = AddI(scale_l, scale_r)
+ // which is equivalent to the long addition if the long addition stays
+ // within (min_jint, max_jint].
jlong scale_sum = java_add(scale_l, scale_r);
- if (scale_sum > max_signed_integer(exp_bt) || scale_sum <= min_signed_integer(exp_bt)) {
+ if (scale_sum > max_signed_integer(bt) || scale_sum <= min_signed_integer(bt)) {
// This logic is shared by int and long. For int, the result may overflow
// as we use jlong to compute so do the check here. Long result may also
// overflow but that's fine because result wraps.
@@ -2718,41 +2766,76 @@ bool PhaseIdealLoop::is_scaled_iv(Node* exp, Node* iv, BasicType bt, jlong* p_sc
if (p_scale != nullptr) {
*p_scale = scale_sum;
}
- if (p_short_scale != nullptr) {
- *p_short_scale = short_scale_l && short_scale_r;
- }
return true;
}
- } else if (opc == Op_Sub(exp_bt)) {
- if (exp->in(1)->find_integer_as_long(exp_bt, -1) == 0) {
- jlong scale = 0;
- if (depth == 0 && is_scaled_iv(exp->in(2), iv, exp_bt, &scale, p_short_scale, depth + 1)) {
- // SubX(0, iv*K) => iv*(-K)
- if (scale == min_signed_integer(exp_bt)) {
+ } else if (opc == Op_Sub(bt)) {
+ if (exp->in(1)->find_integer_as_long(bt, -1) == 0) {
+ jlong scale_r = 0;
+ if (depth == 0 && is_scaled_iv(exp->in(2), iv, bt, &scale_r, depth + 1)) {
+ // We know that:
+ // SubI(0, MulI(x, scale_r)) = MulI(x, scale=SubI(0, scale_r)) where scale is int
+ // SubL(0, MulL(x, scale_r)) = MulL(x, scale=SubL(0, scale_r))
+ //
+ // if bt = T_INT (implies iv is int):
+ // e2 = exp->in(2) = MulI(iv, scale_r) where scale_r is int
+ // exp = SubI(0, e2) = MulI(iv, scale=SubI(0, scale_r)) where scale is int
+ // if bt = T_LONG and iv is long:
+ // e2 = exp->in(2) = MulL(iv, scale_r)
+ // exp = SubL(0, e2) = MulL(iv, scale=SubL(0, scale_r))
+ // if bt = T_LONG and iv is int:
+ // e2 = exp->in(2) = MulL(ConvI2L(iv), scale_r)
+ // exp = SubL(0, e2) = MulL(ConvI2L(iv), scale=SubL(0, scale_r))
+ //
+ // Again, we could have used bt-sensitive computations as for LShift.
+ // Instead, we compute in all cases
+ // scale = SubL(0, scale_r)
+ // How is this correct for bt=T_INT?
+ // We know that the input scale is in the int range.
+ // And so as long as the input scale != min_jint, the
+ // computation in long is equivalent, since neither the int nor
+ // the long computation would overflow.
+ if (scale_r == min_signed_integer(bt)) {
// This should work even if -K overflows, but let's not.
return false;
}
- scale = java_multiply(scale, (jlong)-1);
+ jlong scale_neg = java_multiply(scale_r, (jlong)-1);
if (p_scale != nullptr) {
- *p_scale = scale;
- }
- if (p_short_scale != nullptr) {
- // (ConvI2L (MulI iv K)) can be 64-bit linear if iv is kept small enough...
- *p_short_scale = *p_short_scale || (exp_bt != bt && scale != 1);
+ *p_scale = scale_neg;
}
return true;
}
} else {
jlong scale_l = 0;
jlong scale_r = 0;
- bool short_scale_l = false;
- bool short_scale_r = false;
if (depth == 0 &&
- is_scaled_iv(exp->in(1), iv, exp_bt, &scale_l, &short_scale_l, depth + 1) &&
- is_scaled_iv(exp->in(2), iv, exp_bt, &scale_r, &short_scale_r, depth + 1)) {
- // SubX(iv*K1, iv*K2) => iv*(K1-K2)
+ is_scaled_iv(exp->in(1), iv, bt, &scale_l, depth + 1) &&
+ is_scaled_iv(exp->in(2), iv, bt, &scale_r, depth + 1)) {
+ // We know that:
+ // SubI(MulI(x, scale_l), MulI(x, scale_r)) = MulI(x, scale=SubI(scale_l, scale_r)) where scale is int
+ // SubL(MulL(x, scale_l), MulL(x, scale_r)) = MulL(x, scale=SubL(scale_l, scale_r))
+ //
+ // if bt = T_INT (implies iv is int):
+ // e1 = exp->in(1) = MulI(iv, scale_l) where scale_l is int
+ // e2 = exp->in(2) = MulI(iv, scale_r) where scale_r is int
+ // exp = SubI(e1, e2) = MulI(iv, scale=SubI(scale_l, scale_r)) where scale is int
+ // if bt = T_LONG and iv is long:
+ // e1 = exp->in(1) = MulL(iv, scale_l)
+ // e2 = exp->in(2) = MulL(iv, scale_r)
+ // exp = SubL(e1, e2) = MulL(iv, scale=SubL(scale_l, scale_r))
+ // if bt = T_LONG and iv is int:
+ // e1 = exp->in(1) = MulL(ConvI2L(iv), scale_l)
+ // e2 = exp->in(2) = MulL(ConvI2L(iv), scale_r)
+ // exp = SubL(e1, e2) = MulL(ConvI2L(iv), scale=SubL(scale_l, scale_r))
+ //
+ // For the scale computation, we could have used the same type case
+ // distinction as in LShift above. But instead, we compute in long for
+ // all cases. We need to show that this is OK if bt=T_INT.
+ // We would have computed
+ // scale = SubI(scale_l, scale_r)
+ // which is equivalent to the long subtraction if the long subtraction
+ // stays within (min_jint, max_jint].
jlong scale_diff = java_subtract(scale_l, scale_r);
- if (scale_diff > max_signed_integer(exp_bt) || scale_diff <= min_signed_integer(exp_bt)) {
+ if (scale_diff > max_signed_integer(bt) || scale_diff <= min_signed_integer(bt)) {
// This logic is shared by int and long. For int, the result may
// overflow as we use jlong to compute so do the check here. Long
// result may also overflow but that's fine because result wraps.
@@ -2761,9 +2844,6 @@ bool PhaseIdealLoop::is_scaled_iv(Node* exp, Node* iv, BasicType bt, jlong* p_sc
if (p_scale != nullptr) {
*p_scale = scale_diff;
}
- if (p_short_scale != nullptr) {
- *p_short_scale = short_scale_l && short_scale_r;
- }
return true;
}
}
@@ -2773,27 +2853,75 @@ bool PhaseIdealLoop::is_scaled_iv(Node* exp, Node* iv, BasicType bt, jlong* p_sc
}
//-------------------------is_scaled_iv_plus_offset--------------------------
-// Return true if exp is a simple linear transform of the given induction var.
+// Return true if exp is a simple affine transform of the given induction var.
// The scale must be constant and the addition tree (if any) must be simple.
-// This grammar of cases is recognized, where X is I|L according to bt:
+// On success, we guarantee this form:
+// if bt = T_INT (implies iv is int):
+// exp = AddI(MulI( iv, p_scale), p_offset) where p_scale is int
+// if bt = T_LONG and iv is long:
+// exp = AddL(MulL( iv, p_scale), p_offset)
+// if bt = T_LONG and iv is int:
+// exp = AddL(MulL(ConvI2L(iv), p_scale), p_offset)
+// where:
+// - the constant scale value is stored back to *p_scale unless null, and
+// - the offset (perhaps a synthetic AddX or SubX node) is stored to *p_offset unless null.
+// To avoid looping, the match is depth-limited.
+//
+// This grammar of cases is recognized, where X is I (int) or L (long) according
+// to bt. CastXX operations are also accepted, but omitted for conciseness:
+//
+// is_iv():
+//
+// IV[iv,I] = iv, if iv is int
+// IV[iv,L] = iv, if iv is long
+// | ConvI2L(iv), if iv is int
+//
+// is_scaled_iv():
+//
+// SIV0[iv,X] = IV[iv,X]
+// | MulX(IV[iv,X], ConX), if get_con(ConX) != 0 (ConX is top or MulX is non canonical)
+// | MulX(ConX, IV[iv,X]), if get_con(ConX) != 0 (ConX is top or MulX is non canonical)
+// | LShiftX(IV[iv,X], ConI), if get_con(ConI) != min_jint (ConI is top or LShiftX is non canonical)
//
-// OIV[iv] = SIV[iv] | (CastXX OIV[iv])
-// | (AddX SIV[iv] E) | (AddX E SIV[iv])
-// | (SubX SIV[iv] E) | (SubX E SIV[iv])
-// SSIV[iv] = (ConvI2X SIV[iv]) -- a "short scale" might occur here
-// SIV[iv] = [a possibly scaled value of iv; see is_scaled_iv() above]
+// SIV[iv,X] = SIV0[iv,X]
+// | e:AddX(SIV0[iv,X], SIV0[iv,X]), if min_jX < scale(e) <= max_jX
+// | SubX(0, e:SIV0[iv,X]), if scale(e) != min_jX
+// | e:SubX(SIV0[iv,X], SIV0[iv,X]), if min_jX < scale(e) <= max_jX
//
-// On success, the constant scale value is stored back to *p_scale unless null.
-// Likewise, the addend (perhaps a synthetic AddX node) is stored to *p_offset.
-// Also, (*p_short_scale) reports if a ConvI2L conversion was seen after a MulI,
-// meaning bt is T_LONG but iv was scaled using 32-bit arithmetic.
-// To avoid looping, the match is depth-limited, and so may fail to match the grammar to complex expressions.
-bool PhaseIdealLoop::is_scaled_iv_plus_offset(Node* exp, Node* iv, BasicType bt, jlong* p_scale, Node** p_offset, bool* p_short_scale, int depth) {
+// is_scaled_iv_plus_offset():
+//
+// OSIV0[iv,X] = SIV[iv,X]
+// | AddX(SIV[iv,X], e)
+// | AddX(e, SIV[iv,X])
+// | SubX(SIV[iv,X], e)
+// | SubX(e1, e2:SIV[iv,X]), if scale(e2) != min_jX
+//
+// OSIV1[iv,X] = OSIV0[iv,X]
+// | AddX(OSIV0[iv,X], ConX)
+// | AddX(ConX, OSIV0[iv,X])
+//
+// OSIV[iv,X] = OSIV1[iv,X]
+// | AddX(OSIV1[iv,X], ConX)
+// | AddX(ConX, OSIV1[iv,X])
+//
+// where scale(IV[iv,X]) = 1L
+// scale(MulX(_, ConX)) = get_con(ConX)
+// scale(MulX(ConX, _)) = get_con(ConX)
+// scale(LShiftX(_, ConI)) = java_shift_left((jX)1, get_con(ConI))
+// scale(AddX(e1:SIV0, e2:SIV0)) = java_add(scale(e1), scale(e2))
+// scale(SubX(0, e:SIV0)) = java_multiply(scale(e), -1L)
+// scale(SubX(e1:SIV0, e2:SIV0)) = java_subtract(scale(e1), scale(e2))
+bool PhaseIdealLoop::is_scaled_iv_plus_offset(Node* exp, Node* iv, BasicType bt, jlong* p_scale, Node** p_offset, int depth) {
assert(bt == T_INT || bt == T_LONG, "unexpected int type");
jlong scale = 0; // to catch result from is_scaled_iv()
- BasicType exp_bt = bt;
exp = exp->uncast();
- if (is_scaled_iv(exp, iv, exp_bt, &scale, p_short_scale)) {
+ if (is_scaled_iv(exp, iv, bt, &scale)) {
+ // if bt = T_INT (implies iv is int):
+ // exp = MulI( iv, scale) = AddI(MulI( iv, scale), offset=0) where scale is int
+ // if bt = T_LONG and iv is long:
+ // exp = MulL( iv, scale) = AddL(MulL( iv, scale), offset=0)
+ // if bt = T_LONG and iv is int:
+ // exp = MulL(ConvI2L(iv), scale) = AddL(MulL(ConvI2L(iv), scale), offset=0)
if (p_scale != nullptr) {
*p_scale = scale;
}
@@ -2803,58 +2931,109 @@ bool PhaseIdealLoop::is_scaled_iv_plus_offset(Node* exp, Node* iv, BasicType bt,
}
return true;
}
- if (exp_bt != bt) {
- // We would now be matching inputs like (ConvI2L exp:(AddI (MulI iv S) E)).
- // It's hard to make 32-bit arithmetic linear if it overflows. Although we do
- // cope with overflowing multiplication by S, it would be even more work to
- // handle overflowing addition of E. So we bail out here on ConvI2L input.
- return false;
- }
int opc = exp->Opcode();
- int which = 0; // this is which subexpression we find the iv in
- Node* offset = nullptr;
- if (opc == Op_Add(exp_bt)) {
- // Check for a scaled IV in (AddX (MulX iv S) E) or (AddX E (MulX iv S)).
- if (is_scaled_iv(exp->in(which = 1), iv, bt, &scale, p_short_scale) ||
- is_scaled_iv(exp->in(which = 2), iv, bt, &scale, p_short_scale)) {
- offset = exp->in(which == 1 ? 2 : 1); // the other argument
+ int which = 0; // Input index of the subexpression we find the IV in.
+ Node* offset_other = nullptr; // Offset subexpression (the other input).
+ if (opc == Op_Add(bt)) {
+ jlong scale_which = 0; // Scale of the IV subexpression.
+ // Check for a scaled IV in AddX(MulX(iv, S), E) or AddX(E, MulX(iv, S)).
+ if (is_scaled_iv(exp->in(which = 1), iv, bt, &scale_which) ||
+ is_scaled_iv(exp->in(which = 2), iv, bt, &scale_which)) {
+ offset_other = exp->in(which == 1 ? 2 : 1); // The other argument.
+ // if bt = T_INT (implies iv is int):
+ // exp->in(which) = MulI( iv, scale_which) where scale_which is int
+ // -> which = 1: exp = AddI(MulI( iv, scale_which), offset_other) where scale_which is int
+ // -> which = 2: exp = AddI(offset_other, MulI( iv, scale_which)) = AddI(MulI( iv, scale_which), offset_other) where scale_which is int
+ // if bt = T_LONG and iv is long:
+ // exp->in(which) = MulL( iv, scale_which)
+ // -> which = 1: exp = AddL(MulL( iv, scale_which), offset_other)
+ // -> which = 2: exp = AddL(offset_other, MulL( iv, scale_which)) = AddL(MulL( iv, scale_which), offset_other)
+ // if bt = T_LONG and iv is int:
+ // exp->in(which) = MulL(ConvI2L(iv), scale_which)
+ // -> which = 1: exp = AddL(MulL(ConvI2L(iv), scale_which), offset_other)
+ // -> which = 2: exp = AddL(offset_other, MulL(ConvI2L(iv), scale_which)) = AddL(MulL(ConvI2L(iv), scale_which), offset_other)
if (p_scale != nullptr) {
- *p_scale = scale;
+ *p_scale = scale_which;
}
if (p_offset != nullptr) {
- *p_offset = offset;
+ *p_offset = offset_other;
}
return true;
}
- // Check for more addends, like (AddX (AddX (MulX iv S) E1) E2), etc.
- if (is_scaled_iv_plus_extra_offset(exp->in(1), exp->in(2), iv, bt, p_scale, p_offset, p_short_scale, depth) ||
- is_scaled_iv_plus_extra_offset(exp->in(2), exp->in(1), iv, bt, p_scale, p_offset, p_short_scale, depth)) {
+ // Check for more addends, like AddX(AddX(MulX(iv, S), E1), E2), etc.
+ if (is_scaled_iv_plus_extra_offset(exp->in(1), exp->in(2), iv, bt, p_scale, p_offset, depth) ||
+ is_scaled_iv_plus_extra_offset(exp->in(2), exp->in(1), iv, bt, p_scale, p_offset, depth)) {
+ // See proof in is_scaled_iv_plus_extra_offset.
return true;
}
- } else if (opc == Op_Sub(exp_bt)) {
- if (is_scaled_iv(exp->in(which = 1), iv, bt, &scale, p_short_scale) ||
- is_scaled_iv(exp->in(which = 2), iv, bt, &scale, p_short_scale)) {
- // Match (SubX SIV[iv] E) as if (AddX SIV[iv] (SubX 0 E)), and
- // match (SubX E SIV[iv]) as if (AddX E (SubX 0 SIV[iv])).
- offset = exp->in(which == 1 ? 2 : 1); // the other argument
+ } else if (opc == Op_Sub(bt)) {
+ jlong scale_which = 0; // Scale of the IV subexpression.
+ if (is_scaled_iv(exp->in(which = 1), iv, bt, &scale_which) ||
+ is_scaled_iv(exp->in(which = 2), iv, bt, &scale_which)) {
+ offset_other = exp->in(which == 1 ? 2 : 1); // The other argument.
+ // if bt = T_INT (implies iv is int):
+ // exp->in(which) = MulI(iv, scale_which) where scale_which is int
+ // if which=1:
+ // exp = SubI(MulI( iv, scale_which), offset_other )
+ // = AddI(MulI( iv, scale_which), offset=SubI(0, offset_other))
+ // if which=2:
+ // exp = SubI(offset_other, MulI( iv, scale_which ))
+ // = AddI(offset_other, MulI( iv, scale=SubI(0, scale_which)))
+ // = AddI(MulI( iv, scale=SubI(0, scale_which)), offset_other)
+ //
+ // if bt = T_LONG and iv is long:
+ // exp->in(which) = MulL(iv, scale_which)
+ // if which=1:
+ // exp = SubL(MulL( iv, scale_which), offset_other )
+ // = AddL(MulL( iv, scale_which), offset=SubL(0, offset_other))
+ // if which=2:
+ // exp = SubL(offset_other, MulL( iv, scale_which ))
+ // = AddL(offset_other, MulL( iv, scale=SubL(0, scale_which)))
+ // = AddL(MulL( iv, scale=SubL(0, scale_which)), offset_other)
+ //
+ // if bt = T_LONG and iv is int:
+ // exp->in(which) = MulL(ConvI2L(iv), scale_which)
+ // if which=1:
+ // exp = SubL(MulL(ConvI2L(iv), scale_which), offset_other )
+ // = AddL(MulL(ConvI2L(iv), scale_which), offset=SubL(0, offset_other))
+ // if which=2:
+ // exp = SubL(offset_other, MulL(ConvI2L(iv), scale_which ))
+ // = AddL(offset_other, MulL(ConvI2L(iv), scale=SubL(0, scale_which)))
+ // = AddL(MulL(ConvI2L(iv), scale=SubL(0, scale_which)), offset_other)
+ //
+ // Match SubX(SIV[iv], E) as if AddX(SIV[iv], SubX(0, E)), and
+ // match SubX(E, SIV[iv]) as if AddX(E, SubX(0, SIV[iv])).
+ //
+ // Let's compute the scale:
+ // which=1: scale= scale_which
+ // which=2: scale=SubX(0, scale_which)
+ // We could have used the bt case distinction for SubX(0, scale_which),
+ // but instead we compute it in long for both cases. For int, this
+ // can only lead to a wrong result (i.e. go out of int range) for
+ // min_jint, so we exclude that case.
+ jlong scale_maybe_neg = scale_which;
if (which == 2) {
// We can't handle a scale of min_jint (or min_jlong) here as -1 * min_jint = min_jint
- if (scale == min_signed_integer(bt)) {
+ if (scale_which == min_signed_integer(bt)) {
return false; // cannot negate the scale of the iv
}
- scale = java_multiply(scale, (jlong)-1);
+ // scale = SubL(0, scale_which) = MulL(scale_which, -1)
+ scale_maybe_neg = java_multiply(scale_which, (jlong)-1);
}
if (p_scale != nullptr) {
- *p_scale = scale;
+ *p_scale = scale_maybe_neg;
}
+ // Now let's compute the offset:
+ // which=1: offset=SubX(0, offset_other)
+ // which=2: offset= offset_other
if (p_offset != nullptr) {
+ Node* offset_maybe_neg = offset_other;
if (which == 1) { // must negate the extracted offset
- Node* zero = integercon(0, exp_bt);
- Node *ctrl_off = get_ctrl(offset);
- offset = SubNode::make(zero, offset, exp_bt);
- register_new_node(offset, ctrl_off);
+ Node* zero = integercon(0, bt);
+ offset_maybe_neg = SubNode::make(zero, offset_other, bt);
+ register_new_node(offset_maybe_neg, get_ctrl(offset_other));
}
- *p_offset = offset;
+ *p_offset = offset_maybe_neg;
}
return true;
}
@@ -2863,26 +3042,54 @@ bool PhaseIdealLoop::is_scaled_iv_plus_offset(Node* exp, Node* iv, BasicType bt,
}
// Helper for is_scaled_iv_plus_offset(), not called separately.
-// The caller encountered (AddX exp1 offset3) or (AddX offset3 exp1).
-// Here, exp1 is inspected to see if it is a simple linear transform of iv.
-// If so, the offset3 is combined with any other offset2 from inside exp1.
+// The caller encountered AddX(exp1, offset3) or AddX(offset3, exp1).
+// Here, exp1 is inspected to see if it is a simple affine transform of iv.
+// If so, the (constant) offset3 is combined with any other offset2 from inside exp1.
+//
+// We assume the input form is equivalent to:
+// if bt = T_INT:
+// exp = AddI(exp1, offset3)
+// if bt = T_LONG:
+// exp = AddL(exp1, offset3)
+//
+// On success, we guarantee this form:
+// if bt = T_INT (implies iv is int):
+// exp = AddI(MulI( iv, p_scale), p_offset) where p_scale is int
+// if bt = T_LONG and iv is long:
+// exp = AddL(MulL( iv, p_scale), p_offset)
+// if bt = T_LONG and iv is int:
+// exp = AddL(MulL(ConvI2L(iv), p_scale), p_offset)
+//
bool PhaseIdealLoop::is_scaled_iv_plus_extra_offset(Node* exp1, Node* offset3, Node* iv,
BasicType bt,
jlong* p_scale, Node** p_offset,
- bool* p_short_scale, int depth) {
+ int depth) {
// By the time we reach here, it is unlikely that exp1 is a simple iv*K.
- // If is a linear iv transform, it is probably an add or subtract.
+ // If it is an affine iv transform, it is probably an add or subtract.
// Let's collect the internal offset2 from it.
Node* offset2 = nullptr;
if (offset3->is_Con() &&
depth < 2 &&
is_scaled_iv_plus_offset(exp1, iv, bt, p_scale,
- &offset2, p_short_scale, depth+1)) {
+ &offset2, depth+1)) {
+ // We know that:
+ // AddI(AddI(x, offset2), offset3) = AddI(x, offset=AddI(offset2, offset3))
+ // AddL(AddL(x, offset2), offset3) = AddL(x, offset=AddL(offset2, offset3))
+ //
+ // if bt = T_INT (implies iv is int):
+ // exp1 = AddI(MulI( iv, p_scale), offset2) where p_scale is int
+ // -> exp = AddI(AddI(MulI( iv, p_scale), offset2), offset3) = AddI(MulI( iv, p_scale), offset=AddI(offset2, offset3))
+ // if bt = T_LONG and iv is long:
+ // exp1 = AddL(MulL( iv, p_scale), offset2)
+ // -> exp = AddL(AddL(MulL( iv, p_scale), offset2), offset3) = AddL(MulL( iv, p_scale), offset=AddL(offset2, offset3))
+ // if bt = T_LONG and iv is int:
+ // exp1 = AddL(MulL(ConvI2L(iv), p_scale), offset2)
+ // -> exp = AddL(AddL(MulL(ConvI2L(iv), p_scale), offset2), offset3) = AddL(MulL(ConvI2L(iv), p_scale), offset=AddL(offset2, offset3))
+ //
if (p_offset != nullptr) {
- Node* ctrl_off2 = get_ctrl(offset2);
- Node* offset = AddNode::make(offset2, offset3, bt);
- register_new_node(offset, ctrl_off2);
- *p_offset = offset;
+ Node* offset_sum = AddNode::make(offset2, offset3, bt);
+ register_new_node(offset_sum, get_ctrl(offset2));
+ *p_offset = offset_sum;
}
return true;
}
diff --git a/src/hotspot/share/opto/loopnode.cpp b/src/hotspot/share/opto/loopnode.cpp
index 6fc07d83a923..a6dca7f5b801 100644
--- a/src/hotspot/share/opto/loopnode.cpp
+++ b/src/hotspot/share/opto/loopnode.cpp
@@ -1356,11 +1356,16 @@ bool PhaseIdealLoop::try_make_short_running_loop(IdealLoopTree* loop, jint strid
return true;
}
+// This method extracts long range checks and computes the reduced iteration count (reduced_iters_limit) such that all
+// extracted long range checks can be transformed. In particular, we check that
+// - reduced_iters_limit * ABS(scale) <= original_iters_limit < max_jint
+// - we have the proper form "scale*iv + offset _body.size(); i++) {
Node* c = loop->_body.at(i);
if (c->is_IfProj() && c->in(0)->is_RangeCheck()) {
@@ -1375,6 +1380,7 @@ int PhaseIdealLoop::extract_long_range_checks(const IdealLoopTree* loop, jint st
scale != min_jlong &&
original_iters_limit / ABS(scale) >= min_iters * ABS(stride_con)) {
assert(scale == (jint)scale, "scale should be an int");
+ // Ensure: reduced_iters_limit * ABS(scale) <= original_iters_limit < max_jint
reduced_iters_limit = MIN2(reduced_iters_limit, original_iters_limit/ABS(scale));
range_checks.push(c);
}
@@ -1385,138 +1391,1056 @@ int PhaseIdealLoop::extract_long_range_checks(const IdealLoopTree* loop, jint st
return checked_cast(reduced_iters_limit);
}
-// One execution of the inner loop covers a sub-range of the entire iteration range of the loop: [A,Z), aka [A=init,
-// Z=limit). If the loop has at least one trip (which is the case here), the iteration variable i always takes A as its
-// first value, followed by A+S (S is the stride), next A+2S, etc. The limit is exclusive, so that the final value B of
-// i is never Z. It will be B=Z-1 if S=1, or B=Z+1 if S=-1.
-
-// If |S|>1 the formula for the last value B would require a floor operation, specifically B=floor((Z-sgn(S)-A)/S)*S+A,
-// which is B=Z-sgn(S)U for some U in [1,|S|]. So when S>0, i ranges as i:[A,Z) or i:[A,B=Z-U], or else (in reverse)
-// as i:(Z,A] or i:[B=Z+U,A]. It will become important to reason about this inclusive range [A,B] or [B,A].
-
-// Within the loop there may be many range checks. Each such range check (R.C.) is of the form 0 <= i*K+L < R, where K
-// is a scale factor applied to the loop iteration variable i, and L is some offset; K, L, and R are loop-invariant.
-// Because R is never negative (see below), this check can always be simplified to an unsigned check i*K+L 0), where the limit is
-// chosen to prevent various cases of 32-bit overflow (including multiplications j*K below). In the sub-loop the
-// logical value i is offset from j by a 64-bit constant C, so i ranges in i:C+[0,Z_2).
-
-// For S<0, j ranges (in reverse!) through j:[-|B_2|,0] or (-|Z_2|,0]. For either sign of S, we can say i=j+C and j
-// ranges through 32-bit ranges [A_2,B_2] or [B_2,A_2] (A_2=0 of course).
-
-// The disjoint union of all the C+[A_2,B_2] ranges from the sub-loops must be identical to the whole range [A,B].
-// Assuming S>0, the first C must be A itself, and the next C value is the previous C+B_2, plus S. If |S|=1, the next
-// C value is also the previous C+Z_2. In each sub-loop, j counts from j=A_2=0 and i counts from C+0 and exits at
-// j=B_2 (i=C+B_2), just before it gets to i=C+Z_2. Both i and j count up (from C and 0) if S>0; otherwise they count
-// down (from C and 0 again).
-
-// Returning to range checks, we see that each i*K+L 0 and K>0), Q_min=A_2*K+Q and Q_max=Q (if S<0 and K>0),
-// Q_min=B_2*K+Q and Q_max=Q if (S>0 and K<0), Q_min=Q and Q_max=A_2*K+Q (if S<0 and K<0)
-
-// Note that the first R.C. value is always Q=(S*K>0 ? Q_min : Q_max). Also Q_{min,max} = Q + {min,max}(A_2*K,B_2*K).
-// If S*K>0 then, as the loop iterations progress, each R.C. value i*K+L = j*K+Q goes up from Q=Q_min towards Q_max.
-// If S*K<0 then j*K+Q starts at Q=Q_max and goes down towards Q_min.
-
-// Case A: Some Negatives (but no overflow).
-// Number line:
-// |s64_min . . . 0 . . . s64_max|
-// | . Q_min..Q_max . 0 . . . . | s64 negative
-// | . . . . R=0 R< R< R< R< | (against R values)
-// | . . . Q_min..0..Q_max . . . | small mixed
-// | . . . . R R R< R< R< | (against R values)
-//
-// R values which are out of range (>Q_max+1) are reduced to max(0,Q_max+1). They are marked on the number line as R<.
-//
-// So, if Q_min 0 (R.C.E. steps upward)
-// j*K + s32_trunc(Q_max) R R R< R< | (against R values)
-// | . . . . 0 . Q_min..Q_max . | s64 positive
-// | . . . . R> R> R R R< | (against R values)
-//
-// R values which are out of range (Q_max+1) are reduced as marked: R> up to Q_min, R< down to Q_max+1.
-// Then the whole comparison is shifted left by Q_min, so it can take place at zero, which is a nice 32-bit value.
-//
-// So, if both Q_min, Q_max+1 >=s64 0, then use this test:
-// j*K + 0 0
-// More generally:
-// j*K + Q - Q_min R> R> R> R | (against R values)
-//
-// In this case, Q_min >s64 Q_max+1, even though the mathematical values of Q_min and Q_max+1 are correctly ordered.
-// The formulas from the previous case can be used, except that the bad upper bound Q_max is replaced by max_jlong.
-// (In fact, we could use any replacement bound from R to max_jlong inclusive, as the input to the clamp function.)
-//
-// So if Q_min >=s64 0 but Q_max+1 0
-// More generally:
-// j*K + Q - Q_min H, it returns L not H.
-//
-// All of the formulas above can be merged into a single one:
-// L_clamp = Q_min < 0 ? 0 : Q_min --whether and how far to left-shift
-// H_clamp = Q_max+1 < Q_min ? max_jlong : Q_max+1
-// = Q_max+1 < 0 && Q_min >= 0 ? max_jlong : Q_max+1
-// Q_first = Q = (S*K>0 ? Q_min : Q_max) = (C*K+L)
-// R_clamp = clamp(R, L_clamp, H_clamp) --reduced dynamic range
-// replacement R.C.:
-// j*K + Q_first - L_clamp = min_iters * ABS(stride_con) (KI-1)
+// where
+// K = scale
+// min_iters = 2
+// 0 < original_iters_limit < max_jint
+// ABS(stride_con) > 0
+// From (KI-1)
+// min_iters * ABS(stride_con) = 2 * ABS(stride_con) <= original_iters_limit / ABS(K)
+// then
+// 2 * ABS(stride_con) * ABS(K) <= original_iters_limit
+// and
+// 0 <= ABS(K) <= original_iters_limit < max_jint
+//
+// It follows that K is indeed an int32_t (KI).
+//
+// Condition (R0)
+// --------------
+// In PhaseIdealLoop::extract_long_range_check(), we check IdealLoopTree::is_range_check_if() which guarantees that
+// R >= 0
+// because R is either a LoadRange or an integer that is non-negative (R0).
+//
+// Conditions (P-lo), (P-hi), (Q-lo), (Q-hi), and (Q-lo-hi)
+// --------------------------------------------------------
+// j is to be the induction variable of an int counted loop (i.e. the inner iv).
+// From PhaseIdealLoop::extract_long_range_check() and K = scale, we reduce the iterations so that:
+// reduced_iters_limit * ABS(K) <= original_iters_limit < max_jint (Q1)
+//
+// By the fundamentals of an int counted loop, we have:
+// j_min, j_max being int32_t values
+// such that
+// SI(j_min) <= SI(j) <= SI(j_max) (Q2)
+//
+// Furthermore, due to how we construct the int counted loop, we also have
+// 0 <= SI(j_max) - SI(j_min) <= reduced_iters_limit (Q3)
+//
+// This means that:
+//
+// For K > 0, we multiply K to (Q2)
+// SI(j_min) * K <= SI(j) * K <= SI(j_max) * K
+// and to (Q3)
+// 0 <= SI(j_max) * K - SI(j_min) * K <= reduced_iters_limit * K
+// < max_jint (apply (Q1))
+// and we get
+// 0 < SI(j_max) * K - SI(j_min) * K + 1 <= max_jint (Q4)
+//
+// For K < 0, we multiply K to (Q2)
+// SI(j_min) * K >= SI(j) * K >= SI(j_max) * K
+// and to (Q3)
+// 0 <= SI(j_min) * K - SI(j_max) * K <= reduced_iters_limit * -K
+// < max_jint (apply (Q1))
+// and we get
+// 0 < SI(j_min) * K - SI(j_max) * K + 1 <= max_jint (Q5)
+//
+// Since the multiplication of int32_t values must be an int64_t value, we can choose:
+// (P-lo): P_lo such that SL(P_lo) is the smallest value of "SI(j) * K"
+// (P-hi): P_hi such that SL(P_hi) is the largest value of "SI(j) * K" plus 1.
+//
+// This means that:
+//
+// For K > 0, we have:
+// SL(P_lo) = SI(j_min) * K
+// SL(P_hi) = SI(j_max) * K + 1
+// which implies
+// SL(P_lo) = SI(j_min) * K <= SI(j) * K <= SI(j_max) * K + 1 = SL(P_hi)
+// Starting from (Q4)
+// 0 < SI(j_max) * K - SI(j_min) * K + 1 <= max_jint
+// we get
+// 0 < SL(P_hi) - SL(P_lo) <= max_jint (Q6)
+//
+// For K < 0, we have:
+// SL(P_lo) = SI(j_max) * K
+// SL(P_hi) = SI(j_min) * K + 1
+// which implies
+// SL(P_lo) = SI(j_max) * K <= SI(j) * K <= SI(j_min) * K + 1 = SL(P_hi)
+// Starting from (Q5)
+// 0 < SI(j_min) * K - SI(j_max) * K + 1 <= max_jint
+// we get
+// 0 < SL(P_hi) - SL(P_lo) <= max_jint (Q7)
+//
+// As a result, from (Q6) and (Q7), we see that
+// 0 < SL(P_hi) - SL(P_lo) <= max_jint (Q8)
+//
+// From (P-lo)
+// SL(P_lo) <= SI(j) * K
+// and (P-hi)
+// SL(P_hi) >= SI(j) * K + 1
+// and (Q8) follows
+// 0 <= SI(j) * K - SL(P_lo)
+// < SL(P_hi) - SL(P_lo)
+// <= max_jint (Q9)
+//
+// Since SI(j) and K are int32_t values, their multiplication
+// SI(j) * K
+// cannot overflow the int64_t range and therefore
+// SI(j) * K = SL(MulL(ConvI2L(j), K)) (Q10)
+//
+// Starting from (Q9)
+// 0 <= SI(j) * K - SL(P_lo) < SL(P_hi) - SL(P_lo) <= max_jint
+// it follows with (Q10)
+// 0 <= SL(MulL(ConvI2L(j), K)) - SL(P_lo) < SL(P_hi) - SL(P_lo) <= max_jint (Q11)
+//
+// From (Q9)
+// 0 <= SL(P_hi) - SL(P_lo) <= max_jint
+// it follows that SubL(P_hi, P_lo) does not overflow the signed long domain:
+// SL(SubL(P_hi, P_lo)) = SL(P_hi) - SL(P_lo) (Q12)
+// and from (Q11)
+// SubL(MulL(ConvI2L(j), K), P_lo)
+// does not overflow the signed long domain:
+// SL(SubL(MulL(ConvI2L(j), K), P_lo) = SL(MulL(ConvI2L(j), K)) - SL(P_lo) (Q13)
+//
+// From (Q11)
+// 0 <= SL(MulL(ConvI2L(j), K)) - SL(P_lo) < SL(P_hi) - SL(P_lo) <= max_jint
+// we apply (Q13)
+// 0 <= SL(SubL(MulL(ConvI2L(j), K), P_lo)) < SL(P_hi) - SL(P_lo) <= max_jint
+// and apply (Q12)
+// 0 <= SubL(MulL(ConvI2L(j), K), P_lo) < SubL(P_hi, P_lo) <= max_jint (Q14)
+//
+// Note that we can do a simple reassociation of SubL if x, y, and z are int64_t:
+// SubL(x, y) = SubL(AddL(x, z), AddL(y, z)) (Q15)
+// We apply (Q15) to both sides of (Q14)
+// 0 <= SubL( MulL(ConvI2L(j), K), P_lo)) < SubL( P_hi, P_lo) <= max_jint
+// and get
+// 0 <= SubL(AddL(MulL(ConvI2L(j), K), Q), AddL(P_lo, Q)) < SubL(AddL(P_hi, Q), AddL(P_lo, Q)) <= max_jint (Q16)
+//
+// We can insert
+// Q_lo = AddL(P_lo, Q) (Q-lo)
+// and
+// Q_hi = AddL(P_hi, Q) (Q-hi)
+// in (Q16) and get (Q-lo-hi)
+// 0 <= SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo ) < SubL(Q_hi, Q_lo) <= max_jint
+//
+// Condition (LHS)
+// ---------------
+// We ensure with
+// PhaseIdealLoop::extract_long_range_check() -> PhaseIdealLoop::is_range_check_if()
+// that we indeed have the form
+// AddL(MulL(ConvI2L(j), K), Q) (done with PhaseIdealLoop::is_scaled_iv_plus_offset())
+// for the long range check with R (R0):
+// LHS = CmpUL(AddL(MulL(ConvI2L(j), K), Q), R)
+//
+//
+// We have our conditions
+// (KI): K is an int32_t constant
+// (P-lo): P_lo: The smallest value of "j * K" (i.e. the smallest value of the inner scaled iv), an int64_t value
+// (P-hi): P_hi: The largest value of "j * K" (i.e. the largest value of the inner scaled iv) plus 1, an int64_t value
+// (Q-lo): Q_lo = AddL(P_lo, Q), an int64_t value
+// (Q-hi): Q_hi = AddL(P_hi, Q), an int64_t value
+// (R0): R is an int64_t value such that R >= 0
+// (Q-lo-hi): 0 <= SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) < SubL(Q_hi, Q_lo) <= max_jint
+// (LHS): LHS = CmpUL(AddL(MulL(ConvI2L(j), K), Q), R)
+// and define the additional variables
+// (JI): j is the inner iv, an int32_t value (actually further clamped by the possible values of the inner iv)
+// (QL): Q is an int64_t value
+// (L-clamp): L_clamp = MaxL(Q_lo, 0)
+// (H-clamp): H_clamp = Q_hi < Q_lo ? max_jlong : Q_hi
+// (R-clamp): R_clamp = MaxL(MinL(R, H_clamp), L_clamp)
+// and our target int range check as
+// RHS = CmpU(AddI(MulI(j, K), ConvL2I(SubL(Q, L_clamp))), ConvL2I(SubL(R_clamp, L_clamp)))
+//
+//
+// Now that we established our conditions and definitions, we prove that
+// (LHS < 0) == (RHS < 0)
+// which means that we can safely replace the long range check (LHS) with the int range check (RHS).
+//
+//
+// Proof
+// =====
+// Before starting with the proof, we start with some definitions and lemmas relevant for the proof.
+//
+// Definitions
+// ===========
+// Given x, y being int64_t values, we have:
+// (CmpL-SL): CmpL(x, y) = Cmp(SL(x), SL(y))
+// (CmpUL-UL): CmpUL(x, y) = Cmp(UL(x), UL(y))
+//
+// Given x, y being int64_t values with x < 0 and y >= 0:
+// (CmpUL-gt): CmpUL(x, y) > 0
+//
+// Given x being an int32_t value:
+// (CmpU-gte-zero): CmpU(x, 0) >= 0
+//
+// When the following AddL/SubL do not overflow, then:
+// (AddL-SL-no-overflow): SL(AddL(x, y)) = SL(x) + SL(y)
+// (SubL-UL-no-overflow): UL(SubL(x, y)) = UL(x) - UL(y)
+//
+// When x, y < 0, then
+// (SubL-no-overflow): SubL(x, y) does not overflow the long signed domain
+//
+// No-Overflow-Lemmas
+// ==================
+// SubL-pos-no-overflow
+// --------------------
+// When x, y >= 0, both being int64_t values, then
+// SubL(x, y)
+// does not overflow the long signed domain.
+//
+// Proof:
+// max_jlong >= SL(x) - 0
+// > SL(x) - 1
+// > ...
+// > SL(x) - max_jlong
+// > min_jlong
+//
+// We refer to this lemma with (SubL-pos-no-overflow).
+//
+// AddL-no-overflow
+// ----------------
+// When x <= 0 and y >= 0, both being int64_t values, then
+// AddL(x, y)
+// does not overflow the long signed domain.
+//
+// Proof:
+// min_jlong <= SL(x) + 0
+// < SL(x) + 1
+// < ...
+// < SL(x) + max_jlong
+// <= max_jlong
+//
+// We refer to this lemma with (AddL-no-overflow).
+//
+// Lemma 1
+// =======
+// For x being an int64_t value such that min_jint <= x <= max_jint
+// x = ConvI2L(ConvL2I(x))
+//
+// Proof:
+// If 0 <= x <= max_jint, the upper 33 bits of x are 0s.
+// ConvL2I(x) truncates 32 bits, leaving a value with the highest bit being 0.
+// ConvI2L(ConvL2I(x)) signed extends this value, filling the 32 upper bits of the result with 0.
+// As a result, x and ConvI2L(ConvL2I(x)) have the same 32 lower bits, and their 32 upper bits are all 0,
+// which means x == ConvI2L(ConvL2I(x)).
+//
+// Otherwise, min_jint <= x < 0, the upper 33 bits of x are 1s.
+// ConvL2I(x) truncates 32 bits, leaving a value with the highest bit being 1.
+// ConvI2L(ConvL2I(x)) signed extends this value, filling the 32 upper bits of the result with 1.
+// As a result, x and ConvI2L(ConvL2I(x)) have the same 32 lower bits, and their 32 upper bits are all 1, which means x == ConvI2L(ConvL2I(x))
+//
+// Lemma 2
+// =======
+// Fox x being an int32_t value
+// x = ConvL2I(ConvI2L(x))
+//
+// Proof:
+// ConvL2I(ConvI2L(x)) extends x with 32 bits, then truncates those without touching the 32 lower bits. As a result, x == ConvL2I(ConvI2L(x))
+//
+// Lemma 3
+// =======
+// For x, y being int32_t values
+// CmpU(x, y) = CmpUL(ConvI2L(x), ConvI2L(y))
+//
+// Proof:
+// This is because
+// UL(ConvI2L(0)) < UL(ConvI2L(1)) < UL(ConvI2L(2)) < ... < UL(ConvI2L(max_jint))
+// < UL(ConvI2L(min_jint)) < ... < UL(ConvI2L(-1))
+// is the same ordering as
+// UI(0) < UI(1) < UI(2) < ... < UI(max_jint) < UI(min_jint) < ... < UI(-1)
+//
+// Lemma 4
+// =======
+// For x, y being int64_t values
+// ConvL2I(AddL/SubL/MulL(x, y)) == AddI/SubI/MulI(ConvL2I(x), ConvL2I(y))
+//
+// Proof:
+// UI(ConvL2I(AddL(x, y))) == UL(AddL(x, y)) mod 2^32 (because UI(ConvL2I(v)) == UL(v) mod 2^32)
+// == ((UL(x) + UL(y)) mod 2^64) mod 2^32 (because UL(AddL(u, v)) == (UL(u) + UL(v)) mod 2^64)
+// == (UL(x) + UL(y)) mod 2^32 (because (v mod 2^64) mod 2^32 == v mod 2^32)
+// == ((UL(x) mod 2^32) + (UL(y) mod 2^32)) mod 2^32 (because (u + v) mod 2^32 == ((u mod 2^32) + (v mod 2^32)) mod 2^32)
+// == (UI(ConvL2I(x)) + UI(ConvL2I(y))) mod 2^32 (because UI(ConvL2I(v)) == UL(v) mod 2^32)
+// == UI(AddI(ConvL2I(x), ConvL2I(y))) (because UI(AddI(u, v)) == (UI(u) + UI(v)) mod 2^32)
+//
+// Which means ConvL2I(AddL(x, y)) == AddI(ConvL2I(x), ConvL2I(y)) (because UI(u) == UI(v) necessarily implies u == v)
+//
+// Lemma 5
+// =======
+// For x, y, z being int64_t values such that AddL(x, z) and AddL(y, z) do not under-/overflow the signed long domain.
+// CmpL(x, y) == CmpL(AddL(x, z), AddL(y, z))
+//
+// Proof:
+// This means that:
+// CmpL(AddL(x, z), AddL(y, z)) = Cmp(SL(AddL(x, z)), SL(AddL(y, z)) (apply (CmpL-SL))
+// = Cmp(SL(x) + SL(z), SL(y) + SL(z)) (apply (AddL-SL-no-overflow)))
+// = Cmp(S(x), S(y)) (because Cmp(u + t, v + t) == Cmp(u, v))
+// = CmpL(x, z) (apply (CmpL-SL))
+//
+// Lemma 6
+// =======
+// For x, y, z being int64_t values such that SubL(x, z) and SubL(y, z) do not under-/overflow the unsigned long domain
+// CmpUL(x, y) == CmpUL(SubL(x, z), SubL(y, z))
+//
+// Proof:
+// This means that:
+// CmpUL(SubL(x, z), SubL(y, z)) = Cmp(UL(SubL(x, z)), UL(SubL(y, z))) (apply (CmpL-SL))
+// = Cmp(U(x) - U(z), U(y) - U(z)) (apply (SubL-UL-no-overflow))
+// = Cmp(U(x), U(y)) (because Cmp(u - t, v - t) == Cmp(u, v))
+// = CmpUL(x, y) (apply (CmpL-SL))
+//
+// Lemma 7
+// =======
+// For x, y, z being int64_t values such that x < y, y >= 0, z >= 0
+// CmpUL(x, z) = CmpUL(x, MinL(y, z))
+//
+// Proof:
+// If y > z (trivial):
+// CmpUL(x, z) = CmpUL(x, MinL(y, z))
+// = CmpUL(x, z)
+//
+// If y <= z:
+// CmpUL(x, z) = CmpUL(x, MinL(y, z))
+// = CmpUL(x, y)
+//
+// We now show that for all x, the above equation is true.
+//
+// Case x >= 0:
+// From all case assumptions, we have:
+// 0 <= x < y <= z
+// We can directly convert to unsigned
+// 0 <=u x = 0
+// - 4. Q_hi < 0 < Q_lo
+// for which we all need to show that
+// (LHS < 0) == (RHS < 0)
+//
+// Case 1 and 3 can actually be improved:
+// Case 1: Q_lo, Q_hi <= 0 -> Q_lo < Q_hi <= 0
+// Case 3: Q_lo, Q_hi >= 0 -> 0 <= Q_lo < Q_hi
+//
+// Proof by Contradiction:
+// If Q_lo = Q_hi:
+// SubL(Q_hi, Q_lo) = SubL(Q_hi, Q_hi) = 0
+// which violates (Q-lo-hi)
+// 0 < SubL(Q_hi, Q_lo) <= max_jint
+//
+// If Q_lo > Q_hi:
+// Case 1: Q_hi < Q_lo <= 0
+// Case 3: 0 <= Q_hi < Q_lo
+// then
+// min_jlong <= SubL(Q_hi, Q_lo) < 0
+// in both cases which violates (Q-lo-hi)
+//
+//
+// We use the improved cases 1 and 3 in the following when proving
+// (LHS < 0) == (RHS < 0)
+// for all 4 cases for Q_hi and Q_lo:
+//
+//
+// Case 1: Q_lo < Q_hi <= 0
+// ========================
+// We can simplify the given clamp values:
+// L_clamp = MaxL(Q_lo, 0)
+// = 0
+// H_clamp = Q_hi < Q_lo ? max_jlong : Q_hi
+// = Q_hi
+// R_clamp = MaxL(MinL(R, H_clamp), L_clamp)
+// = MaxL(MinL(R, Q_hi ), 0)
+// = MaxL(Q_hi, 0) (because R >= 0, Q_hi < 0)
+// = 0 (because Q_hi < 0)
+//
+//
+// From (Q-lo-hi)
+// 0 <= SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) < SubL(Q_hi, Q_lo)
+// together with Q_lo < 0 (Case 1), we see that
+// AddL(SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), Q_lo)
+// ---------------- >= 0 ------------------ --- < 0 ---
+// and
+// AddL(SubL(Q_hi, Q_lo), Q_lo)
+// ----- > 0 ----- --- < 0 ---
+// both cannot not overflow the signed long domain (1-no-overflow) due to (AddL-no-overflow).
+//
+// The form (Q-lo-hi)
+// 0 <= SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) < SubL(Q_hi, Q_lo) <= max_jint
+// can also be written as
+// CmpL( SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), SubL(Q_hi, Q_lo)) < 0
+// with (1-no-overflow), we can apply Lemma 5 and add Q_lo to both sides
+// CmpL(AddL(SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), Q_lo), AddL(SubL(Q_hi, Q_lo), Q_lo)) < 0
+// cancel out the Q_lo terms
+// CmpL( AddL(MulL(ConvI2L(j), K), Q), Q_hi) < 0
+// and with (Case 1) see that
+// AddL(MulL(ConvI2L(j), K), Q) < Q_hi <= 0 (1a)
+//
+// From (1a), we know that
+// a = AddL(MulL(ConvI2L(j), K), Q) < 0
+// and from (R0) that
+// b = R >= 0
+// which satisfies (CmpUL-gt) and we can conclude that
+// LHS = CmpUL(AddL(MulL(ConvI2L(j), K), Q), R) > 0
+// ----------- = a ----------- --- = b ---
+//
+// Inserting the simplified L_clamp and R_clamp from above, we get for RHS
+// RHS == CmpU(AddI(MulI(j, K), ConvL2I(SubL(Q, L_clamp))), ConvL2I(SubL(R_clamp, L_clamp)))
+// == CmpU(AddI(MulI(j, K), ConvL2I(SubL(Q, 0))), ConvL2I(SubL(0, 0)))
+// == CmpU(AddI(MulI(j, K), ConvL2I( Q )), 0)
+// == CmpU(c , 0)
+// >= 0 (apply (CmpU-gte-zero))
+//
+//
+// We showed that
+// LHS > 0
+// and
+// RHS >= 0
+// and therefore proved that
+// (LHS < 0) == (RHS < 0) == false
+//
+//
+// Case 2: Q_lo < 0 < Q_hi
+// =======================
+// We can simplify the given clamp values:
+// L_clamp = MaxL(Q_lo, 0)
+// = 0
+// H_clamp = Q_hi < Q_lo ? max_jlong : Q_hi
+// = Q_hi
+// R_clamp = MaxL(MinL(R, H_clamp), L_clamp)
+// = MaxL(MinL(R, Q_hi ), 0)
+// = MinL(R, Q_hi) (because R, Q_hi >= 0)
+//
+//
+// Looking at (Case 2):
+// Q_hi > 0 > Q_lo
+// we have a constrained positive difference
+// 0 < SL(Q_hi) - SL(Q_lo)
+// <= max_jlong + (-min_jlong)
+// = (2 * max_jlong) + 1
+// = 2^64 - 1
+//
+// If
+// 0 < SL(Q_hi) - SL(Q_lo) <= max_jlong
+// then we don't have an overflow of SubL(Q_hi, Q_lo).
+//
+// If
+// max_jlong < SL(Q_hi) - SL(Q_lo) <= 2^64 - 1
+// then SubL(Q_hi, Q_lo) leads to exactly one overflow. The result is always negative:
+// SubL(Q_hi, Q_lo) = TruncL(SL(Q_hi) - SL(Q_lo)) = SL(Q_hi) - SL(Q_lo) - 2^64 < 0
+// This case is impossible as it violates (Q-lo-hi)
+// 0 < SubL(Q_hi, Q_lo)
+// We conclude that SubL(Q_hi, Q_lo) does not overflow the signed long domain (2a-no-overflow).
+//
+//
+// From (Q-lo-hi) and (2a-no-overflow), we get
+// max_jint >= SubL(Q_hi, Q_lo)
+// = SL(SubL(Q_hi, Q_lo))
+// = SL(Q_hi) - SL(Q_lo) (2a)
+//
+// Moreover, from (Case 2), we get
+// Q_hi > 0
+// SL(Q_hi) > 0 (2b)
+// and
+// Q_lo < 0
+// SL(Q_lo) < 0 (2c)
+//
+// It follows for Q_lo
+// SL(Q_lo) = SL(Q_hi) - (SL(Q_hi) - SL(Q_lo))
+// > 0 - (SL(Q_hi) - SL(Q_lo)) (apply (2b))
+// >= 0 - max_jint (apply (2a))
+// = -max_jint
+// and as a result
+// Q_lo > -max_jint (2e)
+//
+// It follows for Q_hi
+// SL(Q_hi) = (SL(Q_hi) - SL(Q_lo)) + SL(Q_lo)
+// <= max_jint + SL(Q_lo) (apply (2a))
+// < max_jint (apply (2c))
+// and as a result
+// Q_hi < max_jint (2f)
+//
+// With (2e) (2f), and (Case 2), we see that Q_lo and Q_hi fit into the 32-bit integer range:
+// -max_jint < Q_lo < 0 < Q_hi < max_jint (2g)
+//
+//
+// From (Q-lo-hi)
+// 0 <= SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) < SubL(Q_hi, Q_lo) <= max_jint
+// together with Q_lo < 0 (Case 2), we see that neither adding Q_lo to the left side
+// AddL(SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), Q_lo)
+// ---------------- >= 0 ------------------ --- < 0 ---
+// nor to the right side
+// AddL(SubL(Q_hi, Q_lo), Q_lo)
+// ----- > 0 ----- --- < 0 ---
+// can overflow the signed long domain (2b-no-overflow).
+//
+//
+// The form (Q-lo-hi)
+// 0 <= SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) < SubL(Q_hi, Q_lo) <= max_jint
+// can also be written as
+// CmpL( SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), SubL(Q_hi, Q_lo)) < 0
+// with (2b-no-overflow), we can apply Lemma 5 to add Q_lo
+// CmpL(AddL(SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), Q_lo), AddL(SubL(Q_hi, Q_lo), Q_lo)) < 0
+// cancel out the Q_lo terms
+// CmpL( AddL(MulL(ConvI2L(j), K), Q), Q_hi) < 0
+// and with (Case 2) and (2f) see that
+// AddL(MulL(ConvI2L(j), K), Q) < Q_hi < max_jint (2h)
+//
+//
+// From (Q-lo-hi)
+// 0 <= SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo)
+// we see that neither adding Q_lo to the left side
+// AddL(0, Q_lo) = Q_lo
+// nor to the right side (already shown with (2b-no-overflow))
+// AddL(SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), Q_lo)
+// can overflow the signed long domain (2c-no-overflow).
+//
+//
+// The form (Q-lo-hi)
+// 0 <= SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) < SubL(Q_hi, Q_lo) <= max_jint
+// can also be written as
+// CmpL( 0, SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo)) <= 0
+// with (2c-no-overflow), we can apply Lemma 5 to add Q_lo
+// CmpL(AddL(0, Q_lo), AddL(SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), Q_lo)) <= 0
+// cancel out the Q_lo terms and simplify
+// CmpL( Q_lo , AddL( MulL(ConvI2L(j), K), Q)) <= 0
+// and with (Case 2) and (2e) see that
+// AddL(MulL(ConvI2L(j), K), Q) >= Q_lo > -max_jint (2i)
+//
+//
+// Putting (2h) and (2i) together, we see that the AddL actually fits into the 32-bit integer range
+// -max_jint < Q_lo <= AddL(MulL(ConvI2L(j), K), Q) < Q_hi < max_jint (2j)
+//
+// With (2j) and (R0), we also conclude
+// -max_jint < MinL(Q_hi, R) < max_jint (2k)
+//
+// We notice with
+// (2h): x = AddL(MulL(ConvI2L(j), K), Q) < Q_hi
+// (Case 2): y = Q_hi > 0
+// (R0): z = R >= 0
+// that Lemma 7 can be applied
+// LHS = CmpUL( AddL(MulL(ConvI2L(j), K), Q), R)
+// ------------------ x ----------------- -- z --
+// = CmpUL( AddL(MulL(ConvI2L(j), K), Q), MinL(Q_hi, R))
+// ------------------ x ----------------- -- y -- -- z --
+// apply Lemma 1 to add ConvI2L-ConvL2I to both sides of CmpUL because they both fit into an int32_t ((2j) and (2k))
+// LHS = CmpUL(ConvI2L(ConvL2I(AddL(MulL(ConvI2L(j), K), Q))), ConvI2L(ConvL2I(MinL(Q_hi, R))))
+// apply Lemma 3 to transform to CmpU because both sides of CmpUL are int32_t values
+// = CmpU ( ConvL2I(AddL(MulL(ConvI2L(j), K), Q)) , ConvL2I(MinL(Q_hi, R))
+// apply Lemma 4 twice to move the ConvL2I inwards
+// = CmpU ( AddI(ConvL2I( MulL(ConvI2L(j), K)), ConvL2I(Q)), ConvL2I(MinL(Q_hi, R)))
+// = CmpU ( AddI(MulI( ConvL2I(ConvI2L(j)), ConvL2I(K)), ConvL2I(Q)), ConvL2I(MinL(Q_hi, R)))
+// apply Lemma 2 to remove the ConvL2I-ConvI2L due to (JI) and simplify K due to (KI)
+// = CmpU ( AddI(MulI( j, K), ConvL2I(Q)), ConvL2I(MinL(Q_hi, R)))
+// we now add a minus 0 on Q and MinL()
+// = CmpU( AddI(MulI( j, K), ConvL2I(SubL(Q, 0))), ConvL2I(SubL(MinL(Q_hi, R), 0)))
+// and eventually insert the simplified L_clamp and R_clamp values
+// = CmpU( AddI(MulI( j, K), ConvL2I(SubL(Q, L_clamp))), ConvL2I(SubL(R_clamp, L_clamp)))
+// which is equivalent to the definition of RHS
+//
+//
+// We proved that
+// LHS = RHS
+// and thus
+// (LHS < 0) == (RHS < 0)
+//
+//
+// Case 3: 0 <= Q_lo < Q_hi
+// ========================
+// We can simplify the given clamp values:
+// L_clamp = MaxL(Q_lo, 0)
+// = Q_lo
+// H_clamp = Q_hi < Q_lo ? max_jlong : Q_hi
+// = Q_hi
+// R_clamp = MaxL(MinL(R, H_clamp), L_clamp)
+// = MaxL(MinL(R, Q_hi), Q_lo)
+//
+// RHS = CmpU(AddI(MulI(j, K), ConvL2I(SubL(Q, L_clamp))), ConvL2I(SubL(R_clamp, L_clamp)))
+// = CmpU(AddI(MulI(j, K), ConvL2I(SubL(Q, Q_lo))), ConvL2I(SubL(MaxL(MinL(R, Q_hi), Q_lo), Q_lo)))
+//
+// Let's first look at both SubL of (Q-lo-hi)
+// 0 <= SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) < SubL(Q_hi, Q_lo) <= max_jint
+//
+// We see with (SubL-pos-no-overflow) and (Case 3) that
+// SubL(Q_hi, Q_lo)
+// does not overflow the signed long domain. Therefore, adding
+// Q_lo >= 0 (from Case 3)
+// to the right side
+// AddL(SubL(Q_hi, Q_lo), Q_lo) = Q_hi
+// does not overflow the signed long domain and therefore adding
+// Q_lo >= 0 (from Case 3)
+// to the left side SubL
+// AddL(SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) Q_lo) < AddL(SubL(Q_hi, Q_lo), Q_lo)
+// also does not overflow the signed long domain (3a-no-overflow).
+//
+//
+// The form (Q-lo-hi)
+// 0 <= SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) < SubL(Q_hi, Q_lo) <= max_jint
+// can also be written as
+// CmpL( SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), SubL(Q_hi, Q_lo)) < 0
+// with (3a-no-overflow), we can apply Lemma 5 and add Q_lo to both sides without overflow
+// CmpL(AddL(SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), Q_lo), AddL(SubL(Q_hi, Q_lo), Q_lo)) < 0
+// cancel out the Q_lo terms
+// CmpL( AddL(MulL(ConvI2L(j), K), Q), Q_hi) < 0
+// and see that
+// AddL(MulL(ConvI2L(j), K), Q) < Q_hi (3a)
+//
+//
+// From (Q-lo-hi)
+// 0 <= SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo)
+// we see that neither adding Q_lo to the left side
+// AddL(0, Q_lo) = Q_lo
+// nor to the right side (3a-no-overflow)
+// AddL(SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), Q_lo)
+// can overflow the signed long domain (3b-no-overflow).
+//
+//
+// The form (Q-lo-hi)
+// 0 <= SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) < SubL(Q_hi, Q_lo) <= max_jint
+// can also be written as
+// CmpL( 0, SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo)) <= 0
+// with (3b-no-overflow), we can apply Lemma 5 and add Q_lo to both sides without overflow
+// CmpL(AddL(0, Q_lo), AddL(SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), Q_lo)) <= 0
+// cancel out the Q_lo terms and simplify
+// CmpL( Q_lo , AddL( MulL(ConvI2L(j), K), Q)) <= 0
+// and seee that
+// AddL(MulL(ConvI2L(j), K), Q) >= Q_lo (3b)
+//
+//
+// Putting (3a) and (3b) together, we see that
+// 0 <= Q_lo <= AddL(MulL(ConvI2L(j), K), Q) < Q_hi (3c)
+//
+// Looking at the definition of LHS
+// LHS = CmpUL(AddL(MulL(ConvI2L(j), K), Q), R)
+// we have 3 cases for R with respect to Q_lo and Q_hi:
+// 3-R1: 0 <= R <= Q_lo
+// 3-R2: 0 <= Q_lo < R < Q_hi
+// 3-R3: 0 <= Q_lo < Q_hi <= R
+//
+// We need to prove for all three cases that (LHS < 0) == (RHS < 0)
+//
+// 3-R1: 0 <= R <= Q_lo
+// --------------------
+// Combining (3-R1) and (3c), we have
+// 0 <= R <= Q_lo <= AddL(MulL(ConvI2L(j), K), Q) < Q_hi
+// and conclude
+// LHS = CmpUL(AddL(MulL(ConvI2L(j), K), Q), R)
+// >= 0
+//
+// From the definition of RHS
+// RHS = CmpU(AddI(MulI(j, K), ConvL2I(SubL(Q, Q_lo))), ConvL2I(SubL(MaxL(MinL(R, Q_hi), Q_lo), Q_lo)))
+// we know from (3-R1) that R < Q_hi:
+// RHS = CmpU(AddI(MulI(j, K), ConvL2I(SubL(Q, Q_lo))), ConvL2I(SubL(MaxL( R , Q_lo), Q_lo)))
+// and R <= Q_lo:
+// RHS = CmpU(AddI(MulI(j, K), ConvL2I(SubL(Q, Q_lo))), ConvL2I(SubL( Q_lo , Q_lo)))
+// = CmpU(AddI(MulI(j, K), ConvL2I(SubL(Q, Q_lo))), 0)
+// = CmpU(c , 0)
+// >= 0 (apply (CmpU-gte-zero))
+//
+//
+// We showed that
+// LHS >= 0
+// and
+// RHS >= 0
+// and therefore proved that
+// (LHS < 0) == (RHS < 0) == false
+//
+//
+// 3-R2: 0 <= Q_lo < R < Q_hi
+// --------------------------
+// From (Q-lo-hi)
+// 0 <= SubL(Q_hi, Q_lo) <= max_jint
+// and (3-R2), we see that subtracting Q_lo from R
+// 0 < SubL(R, Q_lo) < SubL(Q_hi, Q_lo) <= max_jint (3d)
+// does not overflow the unsigned long domain (3-R2a-no-overflow).
+//
+// Further, we know from (3c)
+// 0 <= Q_lo <= AddL(MulL(ConvI2L(j), K), Q)
+// that subtracting Q_lo
+// 0 <= SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) <= AddL(MulL(ConvI2L(j), K), Q)
+// does also not overflow the unsigned long domain (3-R2b-no-overflow).
+//
+// We can now apply several lemmas and transformations to show the equivalence of LHS and RHS
+// LHS = CmpUL( AddL(MulL(ConvI2L(j), K), Q), R)
+// apply Lemma 6 to add SubL to both sides because both SubL do not overflow ((3-R2a-no-overflow) and (3-R2b-no-overflow))
+// LHS = CmpUL( SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), SubL(R, Q_lo))
+// apply Lemma 1 to add ConvI2L-ConvL2I because both sides of CmpUL are int32_t values ((Q-lo-hi) + (3d))
+// LHS = CmpUL(ConvI2L(ConvL2I(SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo)), ConvI2L( ConvL2I(SubL(R, Q_lo)))))
+// apply Lemma 3 to transform to CmpU because both sides of CmpUL are int32_t values
+// LHS = CmpU (ConvL2I( SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo)), ConvL2I(SubL(R, Q_lo)))
+// apply Lemma 4 twice to move the ConvL2I inwards
+// LHS = CmpU (AddI(ConvL2I( MulL(ConvI2L(j), K )), ConvL2I(SubL(Q, Q_lo))), ConvL2I(SubL(R, Q_lo)))
+// = CmpU (AddI(MulI(ConvL2I( ConvI2L(j)), ConvL2I(K)), ConvL2I(SubL(Q, Q_lo))), ConvL2I(SubL(R, Q_lo)))
+// apply Lemma 2 to remove the ConvL2I-ConvI2L due to (JI) and simplify K due to (KI)
+// LHS = CmpU (AddI(MulI( j, K), ConvL2I(SubL(Q, Q_lo))), ConvL2I(SubL(R, Q_lo)))
+// and space it differently
+// LHS = CmpU (AddI(MulI(j, K), ConvL2I(SubL(Q, Q_lo))), ConvL2I(SubL( R , Q_lo)))
+// apply R > Q_lo (3-R2) to add an equivalent MaxL for R
+// LHS = CmpU (AddI(MulI(j, K), ConvL2I(SubL(Q, Q_lo))), ConvL2I(SubL(MaxL( R , Q_lo), Q_lo)))
+// apply R < Q_hi (3-R2) to add an equivlanet MinL for R
+// LHS = CmpU (AddI(MulI(j, K), ConvL2I(SubL(Q, Q_lo))), ConvL2I(SubL(MaxL(MinL(R, Q_hi), Q_lo), Q_lo)))
+// which is equivalent to the definition of RHS
+//
+//
+// We proved that
+// LHS = RHS
+// and thus
+// (LHS < 0) == (RHS < 0)
+//
+//
+// 3-R3: 0 <= Q_lo < Q_hi <= R
+// --------------------------
+// From (3-R3) and (3c), we know that
+// 0 <= AddL(MulL(ConvI2L(j), K), Q) < Q_hi <= R
+// 0 <= AddL(MulL(ConvI2L(j), K), Q) < R
+// and therefore
+// LHS = CmpUL(AddL(MulL(ConvI2L(j), K), Q), R)
+// < 0
+//
+//
+// From the definition of
+// RHS = CmpU(AddI(MulI(j, K), ConvL2I(SubL(Q, Q_lo))), ConvL2I(SubL(MaxL(MinL(R, Q_hi), Q_lo), Q_lo)))
+// we know from (3-R3) that R >= Q_hi:
+// RHS = CmpU(AddI(MulI(j, K), ConvL2I(SubL(Q, Q_lo))), ConvL2I(SubL(MaxL( Q_hi, Q_lo), Q_lo)))
+// and Q_lo < Q_hi:
+// RHS = CmpU(AddI(MulI(j, K), ConvL2I(SubL(Q, Q_lo))), ConvL2I(SubL( Q_hi, Q_lo)))
+// apply Lemma 2 to j due to (JI)
+// RHS = CmpU(AddI(MulI(ConvL2I(ConvI2L(j)), K), ConvL2I(SubL(Q, Q_lo))), ConvL2I(SubL(Q_hi, Q_lo)))
+// apply Lemma 4 twice to move the ConvL2I outwards
+// RHS = CmpU(AddI(ConvL2I(MulL(ConvI2L(j), K)), ConvL2I(SubL(Q, Q_lo))), ConvL2I(SubL(Q_hi, Q_lo)))
+// CmpU(ConvL2I(AddL(MulL(ConvI2L(j), K), SubL(Q, Q_lo))), ConvL2I(SubL(Q_hi, Q_lo)))
+// ------- x -------- -- y -- -- z --
+// reassociated AddL/SubL
+// AddL(x, SubL(y, z)) = SubL(AddL(x, y), z)
+// and space it differently as
+// RHS = CmpU (ConvL2I( SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo)), ConvL2I( SubL(Q_hi, Q_lo)))
+// ------- x -------- -- y -- -- z --
+// apply Lemma 3 to transform to CmpUL form because both sides are int32_t values due to (Q-lo-hi)
+// RHS = CmpUL(ConvI2L(ConvL2I(SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo))), ConvI2L(ConvL2I(SubL(Q_hi, Q_lo))))
+// apply Lemma 1 to remove both ConvI2L-ConvL2I because the inner values are int32_t due to (Q-lo-hi)
+// RHS = CmpUL( SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), SubL(Q_hi, Q_lo))
+// and see with (Q-lo-hi) that
+// RHS < 0
+//
+// We showed that
+// LHS < 0
+// and
+// RHS < 0
+// and therefore proved that
+// (LHS < 0) == (RHS < 0) == true
+//
+//
+// Case 4: Q_hi < 0 < Q_lo
+// =======================
+// We can simplify the given clamp values:
+// L_clamp = MaxL(Q_lo, 0)
+// = Q_lo
+// H_clamp = Q_hi < Q_lo ? max_jlong : Q_hi
+// = max_jlong
+// R_clamp = MaxL(MinL(R, H_clamp), L_clamp)
+// = MaxL(MinL(R, max_jlong), Q_lo)
+// = MaxL(R, Q_lo) (because R <= max_jlong (R0))
+//
+// And insert them into the definition of
+// RHS = CmpU(AddI(MulI( j, K), ConvL2I(SubL(Q, L_clamp))), ConvL2I(SubL(R_clamp, L_clamp)))
+// = CmpU(AddI(MulI( j, K), ConvL2I(SubL(Q, Q_lo)), ConvL2I(SubL(MaxL(R, Q_lo), Q_lo))))
+// apply Lemma 2 to add ConvL2I-ConvI2L due to (JI)
+// RHS = CmpU(AddI(MulI(ConvL2I(ConvI2L(j)), K), ConvL2I(SubL(Q, Q_lo)), ConvL2I(SubL(MaxL(R, Q_lo), Q_lo)))
+// apply Lemma 4 twice to move the ConvL2I outwards:
+// RHS = CmpU(AddI(ConvL2I(MulL(ConvI2L(j), K)), ConvL2I(SubL(Q, Q_lo)), ConvL2I(SubL(MaxL(R, Q_lo), Q_lo)))
+// = CmpU(ConvL2I(AddL(MulL(ConvI2L(j), K), SubL(Q, Q_lo))), ConvL2I(SubL(MaxL(R, Q_lo), Q_lo)))
+// ------- x -------- -- y -- -- z --
+// reassociate AddL/SubL
+// AddL(x, SubL(y, z)) = SubL(AddL(x, y), z)
+// and space it differently as
+// RHS = CmpU(ConvL2I( SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo)), C onvL2I(SubL(MaxL(R, Q_lo), Q_lo)))
+// ------- x -------- -- y -- -- z --
+// apply Lemma 3 to transform to CmpUL form because both sides are int32_t values with ConvL2I
+// RHS = CmpUL(ConvI2L(ConvL2I(SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo))), ConvI2L(ConvL2I(SubL(MaxL(R, Q_lo), Q_lo))))
+// apply Lemma 1 to remove the left ConvI2L-ConvL2I because the inner value is int32_t due to (Q-lo-hi)
+// RHS = CmpUL( SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), ConvI2L(ConvL2I(SubL(MaxL(R, Q_lo), Q_lo))))
+//
+//
+// To remove the right-side ConvI2L-ConvL2I, we first need to show that
+// SubL(MaxL(R, Q_lo), Q_lo)
+// is an int32_t value
+//
+// SubL is an int32_t
+// ------------------
+// (Case 4)
+// Q_hi < 0 < Q_lo
+// is equivalent to
+// 0 u 0
+// does not overflow the unsigned long domain and thus
+// UL(Q_hi) - UL(Q_lo) = UL(SubL(Q_hi, Q_lo)) (4b)
+//
+// It also follows that
+// Q_hi u> max_jlong
+// UL(Q_hi) > max_jlong (4c)
+//
+// From (R0)
+// 0 <= R <= max_jlong
+// and (Case 4)
+// 0 <= Q_lo <= max_jlong
+// we see that
+// 0 <= MaxL(R, Q_lo) <= max_jlong (4d)
+//
+// We notice that
+// 0 < Q_lo <= MaxL(R, Q_lo)
+// which means that
+// 0 <= SubL(MaxL(R, Q_lo), Q_lo)
+// does not overflow the unsigned long domain (4b-no-overflow)
+//
+//
+// With (4b-no-overflow), we get
+// 0 <= SubL(MaxL(R, Q_lo), Q_lo)
+// = UL(SubL(MaxL(R, Q_lo), Q_lo))
+// = UL(MaxL(R, Q_lo)) - UL(Q_lo)
+// <= max_jlong - UL(Q_lo) (apply (4d))
+// < UL(Q_hi) - UL(Q_lo) (apply (4c))
+// = UL(SubL(Q_hi, Q_lo)) (apply (4b))
+// <= UL(max_jint) (apply (Q-lo-hi))
+// = max_jint
+// and thus showed that
+// 0 <= SubL(MaxL(R, Q_lo), Q_lo) < max_jint (4e)
+// indeed fits into an int32_t.
+//
+//
+// Thus, we can apply Lemma 1 to remove the second ConvI2L-ConvL2I as well
+// RHS = CmpUL(SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), ConvI2L(ConvL2I(SubL(MaxL(R, Q_lo), Q_lo))))
+// RHS = CmpUL(SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), SubL(MaxL(R, Q_lo), Q_lo))
+//
+// We now want to remove the SubL on both sides with Lemma 6. We already showed with (4e) that the right SubL does
+// not overflow. Which leaves us by showing that the left SubL does not overflow.
+//
+//
+// No Overflow of Left SubL
+// ------------------------
+// Consider the subtraction
+// UL(x - Q_lo)
+// which only overflows if
+// x = Q_lo
+// 4-R2: R < Q_lo
+//
+// We need to prove for both cases that (LHS < 0) == (RHS < 0)
+//
+// 4-R1: R >= Q_lo
+// ---------------
+// Starting from (4g)
+// RHS = CmpUL(AddL(MulL(ConvI2L(j), K), Q), MaxL(R, Q_lo))
+// = CmpUL(AddL(MulL(ConvI2L(j), K), Q), R)
+// = LHS
+//
+// which implies (LHS < 0) == (RHS < 0)
+//
+// 4-R2: R < Q_lo
+// ---------------
+// Starting from (4g)
+// RHS = CmpUL(AddL(MulL(ConvI2L(j), K), Q), MaxL(R, Q_lo))
+// = CmpUL(AddL(MulL(ConvI2L(j), K), Q), Q_lo)
+//
+// From (4c-no-overflow), we know, that
+// SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo)
+// does not overflow and with (Q-lo-hi)
+// 0 < SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) < max_jint
+// it follows that
+// 0 =u Q_lo (4h)
+// must hold and therefore
+// RHS = CmpUL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) >= 0
+//
+// From (4-R2) and (R0) we know
+// 0 <= R < Q_lo
+// which is the same as
+// R 0
+//
+// We showed that
+// LHS > 0
+// and
+// RHS >= 0
+// and therefore proved that
+// (LHS < 0) == (RHS < 0) == false
+//
+// We proved that the transformation is valid under the stated pre-conditions.
void PhaseIdealLoop::transform_long_range_checks(int stride_con, const Node_List &range_checks, Node* outer_phi,
Node* inner_iters_actual_int, Node* inner_phi,
Node* iv_add, LoopNode* inner_head) {
@@ -1536,150 +2460,195 @@ void PhaseIdealLoop::transform_long_range_checks(int stride_con, const Node_List
// could be shared and have already been taken care of
continue;
}
- bool short_scale = false;
- bool ok = is_scaled_iv_plus_offset(rc_cmp->in(1), iv_add, T_LONG, &scale, &offset, &short_scale);
+ bool ok = is_scaled_iv_plus_offset(rc_cmp->in(1), iv_add, T_LONG, &scale, &offset);
assert(ok, "inconsistent: was tested before");
+ // Because is_scaled_iv_plus_offset is successful, we know that
+ // rc_cmp->in(1) is semantically equivalent to either:
+ // AddL(MulL(iv_add, scale), offset), if iv_add is long; or
+ // AddL(MulL(ConvI2L(iv_add), scale), offset), if iv_add is int.
+ // See the equivalence proof in PhaseIdealLoop::is_scaled_iv_plus_offset and
+ // functions called therein.
Node* range = rc_cmp->in(2);
Node* c = rc->in(0);
Node* entry_control = inner_head->in(LoopNode::EntryControl);
Node* R = range;
Node* K = longcon(scale);
-
Node* L = offset;
-
- if (short_scale) {
- // This converts:
- // (int)i*K + L u64 (long)max_jint and still is C, int_zero, inner_iters_actual_int, int_stride);
- register_new_node(B_2, entry_control);
- B_2 = new SubINode(B_2, int_stride);
- register_new_node(B_2, entry_control);
- B_2 = new ConvI2LNode(B_2);
- register_new_node(B_2, entry_control);
-
- Node* Q_max = new MulLNode(B_2, K);
- register_new_node(Q_max, entry_control);
- Q_max = new AddLNode(Q_max, Q_first);
- register_new_node(Q_max, entry_control);
-
+ // Last iteration, j = last, where
+ // last = ((iter_count - 1) * stride + init)
+ // = ( iter_count * stride + init) - stride
+ // = LoopLimitNode(init, limit, stride ) - stride
+ // = LoopLimitNode(0, inner_iters_actual_int, int_stride) - stride
+ // which is used to get
+ // Q_last = (j * K) + Q
+ // = (last * K) + Q
+ // = (LoopLimitNode(0, inner_iters_actual_int, int_stride) - stride * K) + Q (def. of last)
+ // = (LoopLimitNode(0, inner_iters_actual_int, int_stride) - stride * K) + (C * K) + L (def. of Q)
+ // = (LoopLimitNode(0, inner_iters_actual_int, int_stride) - stride * K) + Q_first (def. of Q_first)
+ Node* last = new LoopLimitNode(this->C, int_zero, inner_iters_actual_int, int_stride);
+ register_new_node(last, entry_control);
+ last = new SubINode(last, int_stride);
+ register_new_node(last, entry_control);
+ last = new ConvI2LNode(last);
+ register_new_node(last, entry_control);
+
+ Node* Q_last = new MulLNode(last, K);
+ register_new_node(Q_last, entry_control);
+ Q_last = new AddLNode(Q_last, Q_first);
+ register_new_node(Q_last, entry_control);
+
+ // We define Q_lo and Q_hi now from Q_first, Q_last, P_lo, and P_hi:
+ //
+ // If Q_first < Q_last (which is the case if scale * stride_con > 0):
+ // Q_lo = P_lo + Q (def. Q_lo)
+ // = 0 * K + Q (def. P_lo)
+ // = Q
+ // = Q_first
+ // and
+ // Q_hi = P_hi + Q (def. Q_hi)
+ // = last * K + 1 + Q (def. P_hi)
+ // = last * K + Q + 1
+ // = Q_last + 1
+ // If Q_first > Q_last (which is the case if scale * stride_con < 0):
+ // Q_lo = P_lo + Q (def. Q_lo)
+ // = last * K + Q (def. P_lo)
+ // = Q_last
+ // and
+ // Q_hi = P_hi + Q (def. Q_hi)
+ // = 0 * K + 1 + Q (def. P_hi)
+ // = 0 * K + Q + 1
+ // = Q_first + 1
+ Node* Q_lo = Q_first;
+ Node* Q_hi = Q_last;
if (scale * stride_con < 0) {
- swap(Q_min, Q_max);
+ swap(Q_lo, Q_hi);
}
- // Now, mathematically, Q_max > Q_min, and they are close enough so that (Q_max-Q_min) fits in 32 bits.
- // L_clamp = Q_min < 0 ? 0 : Q_min
- Node* Q_min_cmp = new CmpLNode(Q_min, long_zero);
- register_new_node(Q_min_cmp, entry_control);
- Node* Q_min_bool = new BoolNode(Q_min_cmp, BoolTest::lt);
- register_new_node(Q_min_bool, entry_control);
- Node* L_clamp = new CMoveLNode(Q_min_bool, Q_min, long_zero, TypeLong::LONG);
- register_new_node(L_clamp, entry_control);
- // (This could also be coded bitwise as L_clamp = Q_min & ~(Q_min>>63).)
+ // Q_lo is now correct. Add plus one to get the correct Q_hi value as defined above as well:
+ Q_hi = new AddLNode(Q_hi, long_one);
+ register_new_node(Q_hi, entry_control);
- Node* Q_max_plus_one = new AddLNode(Q_max, long_one);
- register_new_node(Q_max_plus_one, entry_control);
+ // Let's next define all clamp values:
+ //
+ // First, we compute
+ // L_clamp = Q_lo < 0 ? 0 : Q_lo
+ Node* Q_lo_cmp = new CmpLNode(Q_lo, long_zero);
+ register_new_node(Q_lo_cmp, entry_control);
+ Node* Q_lo_bool = new BoolNode(Q_lo_cmp, BoolTest::lt);
+ register_new_node(Q_lo_bool, entry_control);
+ Node* L_clamp = new CMoveLNode(Q_lo_bool, Q_lo, long_zero, TypeLong::LONG);
+ register_new_node(L_clamp, entry_control);
+ // (This could also be coded bitwise as L_clamp = Q_lo & ~(Q_lo>>63).)
- // H_clamp = Q_max+1 < Q_min ? max_jlong : Q_max+1
- // (Because Q_min and Q_max are close, the overflow check could also be encoded as Q_max+1 < 0 & Q_min >= 0.)
+ // Next, we compute
+ // H_clamp = Q_hi< Q_lo ? max_jlong : Q_hi
Node* max_jlong_long = longcon(max_jlong);
- Node* Q_max_cmp = new CmpLNode(Q_max_plus_one, Q_min);
- register_new_node(Q_max_cmp, entry_control);
- Node* Q_max_bool = new BoolNode(Q_max_cmp, BoolTest::lt);
- register_new_node(Q_max_bool, entry_control);
- Node* H_clamp = new CMoveLNode(Q_max_bool, Q_max_plus_one, max_jlong_long, TypeLong::LONG);
+ Node* Q_hi_cmp = new CmpLNode(Q_hi, Q_lo);
+ register_new_node(Q_hi_cmp, entry_control);
+ Node* Q_hi_bool = new BoolNode(Q_hi_cmp, BoolTest::lt);
+ register_new_node(Q_hi_bool, entry_control);
+ Node* H_clamp = new CMoveLNode(Q_hi_bool, Q_hi, max_jlong_long, TypeLong::LONG);
register_new_node(H_clamp, entry_control);
- // (This could also be coded bitwise as H_clamp = ((Q_max+1)<<1 | M)>>>1 where M = (Q_max+1)>>63 & ~Q_min>>63.)
-
- // R_2 = clamp(R, L_clamp, H_clamp) - L_clamp
- // that is: R_2 = clamp(R, L_clamp=0, H_clamp=Q_max) if Q_min < 0
- // or else: R_2 = clamp(R, L_clamp, H_clamp) - Q_min if Q_min >= 0
- // and also: R_2 = clamp(R, L_clamp, Q_max+1) - L_clamp if Q_min < Q_max+1 (no overflow)
- // or else: R_2 = clamp(R, L_clamp, *no limit*)- L_clamp if Q_max+1 < Q_min (overflow)
- Node* R_2 = clamp(R, L_clamp, H_clamp);
- R_2 = new SubLNode(R_2, L_clamp);
- register_new_node(R_2, entry_control);
- R_2 = new ConvL2INode(R_2, TypeInt::POS);
- register_new_node(R_2, entry_control);
-
- // L_2 = Q_first - L_clamp
- // We are subtracting L_clamp from both sides of the 0, then Q_first == 0 and the R.C. expression at -L_clamp and steps upward to Q_max-L_clamp.
- // If S*K<0, then Q_first != 0 and the R.C. expression starts high and steps downward to Q_min-L_clamp.
- Node* L_2 = new SubLNode(Q_first, L_clamp);
- register_new_node(L_2, entry_control);
- L_2 = new ConvL2INode(L_2, TypeInt::INT);
- register_new_node(L_2, entry_control);
-
- // Transform the range check using the computed values L_2/R_2
- // from: i*K + L >>1 where M = (Q_hi)>>63 & ~Q_lo>>63.)
+
+ // Finally, we compute
+ // R_clamp = MaxL(MinL(R, H_clamp), L_clamp)
+ Node* R_clamp = clamp(R, L_clamp, H_clamp);
+
+ // Now we have everything together to transform the long range check:
+ // (i * K) + L (scale));
Node* scaled_iv = new MulINode(inner_phi, K);
register_new_node(scaled_iv, c);
- Node* scaled_iv_plus_offset = new AddINode(scaled_iv, L_2);
+ Node* scaled_iv_plus_offset = new AddINode(scaled_iv, L2);
register_new_node(scaled_iv_plus_offset, c);
- Node* new_rc_cmp = new CmpUNode(scaled_iv_plus_offset, R_2);
+ Node* new_rc_cmp = new CmpUNode(scaled_iv_plus_offset, R2);
register_new_node(new_rc_cmp, c);
_igvn.replace_input_of(rc_bol, 1, new_rc_cmp);
}
}
-Node* PhaseIdealLoop::clamp(Node* R, Node* L, Node* H) {
- Node* min = MinMaxNode::signed_min(R, H, TypeLong::LONG, _igvn);
+Node* PhaseIdealLoop::clamp(Node* R, Node* L_clamp, Node* H_clamp) {
+ Node* min = MinMaxNode::signed_min(R, H_clamp, TypeLong::LONG, _igvn);
set_subtree_ctrl(min, true);
- Node* max = MinMaxNode::signed_max(L, min, TypeLong::LONG, _igvn);
+ Node* max = MinMaxNode::signed_max(L_clamp, min, TypeLong::LONG, _igvn);
set_subtree_ctrl(max, true);
return max;
}
diff --git a/src/hotspot/share/opto/loopnode.hpp b/src/hotspot/share/opto/loopnode.hpp
index a6f0ddd8b6b8..70ee7fc45f35 100644
--- a/src/hotspot/share/opto/loopnode.hpp
+++ b/src/hotspot/share/opto/loopnode.hpp
@@ -1569,12 +1569,12 @@ class PhaseIdealLoop : public PhaseTransform {
void do_unroll( IdealLoopTree *loop, Node_List &old_new, bool adjust_min_trip );
// Return true if exp is a constant times an induction var
- bool is_scaled_iv(Node* exp, Node* iv, BasicType bt, jlong* p_scale, bool* p_short_scale, int depth = 0);
+ bool is_scaled_iv(Node* exp, Node* iv, BasicType bt, jlong* p_scale, int depth = 0);
bool is_iv(Node* exp, Node* iv, BasicType bt);
- // Return true if exp is a scaled induction var plus (or minus) constant
- bool is_scaled_iv_plus_offset(Node* exp, Node* iv, BasicType bt, jlong* p_scale, Node** p_offset, bool* p_short_scale = nullptr, int depth = 0);
+ // Return true if exp is a scaled induction var plus (or minus) an offset
+ bool is_scaled_iv_plus_offset(Node* exp, Node* iv, BasicType bt, jlong* p_scale, Node** p_offset, int depth = 0);
bool is_scaled_iv_plus_offset(Node* exp, Node* iv, int* p_scale, Node** p_offset) {
jlong long_scale;
if (is_scaled_iv_plus_offset(exp, iv, T_INT, &long_scale, p_offset)) {
@@ -1587,10 +1587,10 @@ class PhaseIdealLoop : public PhaseTransform {
return false;
}
// Helper for finding more complex matches to is_scaled_iv_plus_offset.
- bool is_scaled_iv_plus_extra_offset(Node* exp1, Node* offset2, Node* iv,
+ bool is_scaled_iv_plus_extra_offset(Node* exp1, Node* offset3, Node* iv,
BasicType bt,
jlong* p_scale, Node** p_offset,
- bool* p_short_scale, int depth);
+ int depth);
// Create a new if above the uncommon_trap_if_pattern for the predicate to be promoted
IfTrueNode* create_new_if_for_predicate(const ParsePredicateSuccessProj* parse_predicate_proj, Node* new_entry,
@@ -2040,7 +2040,7 @@ class PhaseIdealLoop : public PhaseTransform {
void try_sink_out_of_loop(Node* n);
- Node* clamp(Node* R, Node* L, Node* H);
+ Node* clamp(Node* R, Node* L_clamp, Node* H_clamp);
bool safe_for_if_replacement(const Node* dom) const;
diff --git a/test/hotspot/jtreg/compiler/c2/irTests/TestLongRangeChecks.java b/test/hotspot/jtreg/compiler/c2/irTests/TestLongRangeChecks.java
index 9a06d12b15f7..3e350e83091a 100644
--- a/test/hotspot/jtreg/compiler/c2/irTests/TestLongRangeChecks.java
+++ b/test/hotspot/jtreg/compiler/c2/irTests/TestLongRangeChecks.java
@@ -89,9 +89,14 @@ private void testStridePosScalePosInIntLoop1_runner() {
}
@Test
- @IR(applyIf = { "ShortRunningLongLoop", "false" }, counts = { IRNode.LOOP, "1" })
@IR(applyIf = { "ShortRunningLongLoop", "true" }, failOn = IRNode.LOOP)
- @IR(failOn = { IRNode.COUNTED_LOOP})
+ // This is a negative test: the range check cannot be currently optimized
+ // because the index expression scale * i + offset contains short scaling
+ // (see PhaseIdealLoop::is_scaled_iv) and is thus not a candidate for long
+ // range check transformations. The two rules below verify that range check
+ // optimization does NOT take place.
+ @IR(applyIf = { "ShortRunningLongLoop", "false" }, failOn = IRNode.LOOP)
+ @IR(counts = { IRNode.COUNTED_LOOP, "1"})
public static void testStridePosScalePosInIntLoop2(int start, int stop, long length, long offset) {
final int scale = 2;
final int stride = 1;
@@ -143,9 +148,14 @@ private void testStrideNegScaleNegInIntLoop1_runner() {
}
@Test
- @IR(applyIf = { "ShortRunningLongLoop", "false" }, counts = { IRNode.LOOP, "1" })
@IR(applyIf = { "ShortRunningLongLoop", "true" }, failOn = IRNode.LOOP)
- @IR(failOn = { IRNode.COUNTED_LOOP})
+ // This is a negative test: the range check cannot be currently optimized
+ // because the index expression scale * i + offset contains short scaling
+ // (see PhaseIdealLoop::is_scaled_iv) and is thus not a candidate for long
+ // range check transformations. The two rules below verify that range check
+ // optimization does NOT take place.
+ @IR(applyIf = { "ShortRunningLongLoop", "false" }, failOn = IRNode.LOOP)
+ @IR(counts = { IRNode.COUNTED_LOOP, "1"})
public static void testStrideNegScaleNegInIntLoop2(int start, int stop, long length, long offset) {
final int scale = -2;
final int stride = 1;
@@ -195,9 +205,14 @@ private void testStrideNegScalePosInIntLoop1_runner() {
}
@Test
- @IR(applyIf = { "ShortRunningLongLoop", "false" }, counts = { IRNode.LOOP, "1" })
@IR(applyIf = { "ShortRunningLongLoop", "true" }, failOn = IRNode.LOOP)
- @IR(failOn = { IRNode.COUNTED_LOOP})
+ // This is a negative test: the range check cannot be currently optimized
+ // because the index expression scale * i + offset contains short scaling
+ // (see PhaseIdealLoop::is_scaled_iv) and is thus not a candidate for long
+ // range check transformations. The two rules below verify that range check
+ // optimization does NOT take place.
+ @IR(applyIf = { "ShortRunningLongLoop", "false" }, failOn = IRNode.LOOP)
+ @IR(counts = { IRNode.COUNTED_LOOP, "1"})
public static void testStrideNegScalePosInIntLoop2(int start, int stop, long length, long offset) {
final int scale = 2;
final int stride = 1;
@@ -246,9 +261,14 @@ private void testStridePosScaleNegInIntLoop1_runner() {
}
@Test
- @IR(applyIf = { "ShortRunningLongLoop", "false" }, counts = { IRNode.LOOP, "1" })
@IR(applyIf = { "ShortRunningLongLoop", "true" }, failOn = IRNode.LOOP)
- @IR(failOn = { IRNode.COUNTED_LOOP})
+ // This is a negative test: the range check cannot be currently optimized
+ // because the index expression scale * i + offset contains short scaling
+ // (see PhaseIdealLoop::is_scaled_iv) and is thus not a candidate for long
+ // range check transformations. The two rules below verify that range check
+ // optimization does NOT take place.
+ @IR(applyIf = { "ShortRunningLongLoop", "false" }, failOn = IRNode.LOOP)
+ @IR(counts = { IRNode.COUNTED_LOOP, "1"})
public static void testStridePosScaleNegInIntLoop2(int start, int stop, long length, long offset) {
final int scale = -2;
final int stride = 1;
diff --git a/test/hotspot/jtreg/compiler/c2/irTests/TestVectorizationMultiInvar.java b/test/hotspot/jtreg/compiler/c2/irTests/TestVectorizationMultiInvar.java
index eb4b611e8ebe..4239dd469566 100644
--- a/test/hotspot/jtreg/compiler/c2/irTests/TestVectorizationMultiInvar.java
+++ b/test/hotspot/jtreg/compiler/c2/irTests/TestVectorizationMultiInvar.java
@@ -64,7 +64,7 @@ public static void main(String[] args) {
@IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" })
public static void testByteLong1(byte[] dest, long[] src) {
for (int i = 0; i < src.length; i++) {
- long j = Objects.checkIndex(i * 8, (long)(src.length * 8));
+ long j = Objects.checkIndex(i * 8L, (long)(src.length * 8));
UNSAFE.putLongUnaligned(dest, baseOffset + j, src[i]);
}
}
@@ -75,6 +75,26 @@ public static void testByteLong1_runner() {
testByteLong1(byteArray, longArray);
}
+ // This is a negative version of the test above where C2 currently fails to
+ // perform range check elimination, which inhibits vectorization.
+ // The reason is that the i * 8 index expression is not accepted as a valid
+ // index for long range check transformations, see
+ // PhaseIdealLoop::is_scaled_iv_plus_offset.
+ @Test
+ @IR(failOn = { IRNode.LOAD_VECTOR_L, IRNode.STORE_VECTOR })
+ public static void testNegativeByteLong1(byte[] dest, long[] src) {
+ for (int i = 0; i < src.length; i++) {
+ long j = Objects.checkIndex(i * 8, (long)(src.length * 8));
+ UNSAFE.putLongUnaligned(dest, baseOffset + j, src[i]);
+ }
+ }
+
+ @Run(test = "testNegativeByteLong1")
+ public static void testNegativeByteLong1_runner() {
+ baseOffset = UNSAFE.ARRAY_BYTE_BASE_OFFSET;
+ testNegativeByteLong1(byteArray, longArray);
+ }
+
@Test
@IR(counts = { IRNode.LOAD_VECTOR_B, ">=1", IRNode.STORE_VECTOR, ">=1" })
public static void testLoopNest1(byte[] dest, byte[] src,
diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java
index 87ad9f029f7c..9822c6e07545 100644
--- a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java
+++ b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java
@@ -490,6 +490,12 @@ public static void allocateArrayOfNodes(String irNodePlaceholder, String allocat
superWordNodes(AND_REDUCTION_V, "AndReductionV");
}
+ public static final String ANY_LOOP = PREFIX + "ANY_LOOP" + POSTFIX;
+ static {
+ String regex = START + "(Counted|LongCounted|OuterStripMined)?Loop\\b" + MID + END;
+ fromBeforeCountedLoops(ANY_LOOP, regex);
+ }
+
public static final String CALL = PREFIX + "CALL" + POSTFIX;
static {
beforeMatchingNameRegex(CALL, "Call.*Java");
diff --git a/test/hotspot/jtreg/compiler/rangechecks/TestIndexMatching.java b/test/hotspot/jtreg/compiler/rangechecks/TestIndexMatching.java
new file mode 100644
index 000000000000..72b4cc7e5e97
--- /dev/null
+++ b/test/hotspot/jtreg/compiler/rangechecks/TestIndexMatching.java
@@ -0,0 +1,779 @@
+/*
+ * 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.
+ */
+
+package compiler.rangechecks;
+
+import compiler.lib.ir_framework.*;
+import java.util.Objects;
+
+/**
+ * @test
+ * @bug 8382378
+ * @summary Tests that valid index expressions in range checks are matched for
+ * range check optimizations, and that invalid expressions are
+ * rejected. These tests should be kept updated with the grammar-based
+ * specification provided as a comment on
+ * PhaseIdealLoop::is_scaled_iv_plus_offset().
+ * @library /test/lib /
+ * @run driver ${test.main.class}
+ */
+
+public class TestIndexMatching {
+
+ static int intVar0 = 0;
+ static int intVar3 = 3;
+ static int intVar100 = 100;
+ static int intVar1000 = 1000;
+ static int intVar10000 = 10000;
+
+ static long longVar0 = 0L;
+ static long longVar3 = 3L;
+ static long longVar100 = 100L;
+ static long longVar1000 = 1000L;
+ static long longVar10000 = 10000L;
+
+ public static void main(String[] args) {
+ TestFramework.run();
+ }
+
+ // Positive tests: expect the index expressions to be matched and range
+ // check optimizations to trigger. Use C2-opaque range check limits to
+ // increase the likelihood that loop elimination is indeed caused by range
+ // check optimizations and not accidentally by other transformations.
+
+ // Int indices using int induction variables
+
+ // iv
+ @Test
+ @IR(failOn = {IRNode.ANY_LOOP})
+ static void testIntIndexIntIV() {
+ for (int iv = 0; iv <= intVar100; iv++) {
+ Objects.checkIndex(iv, intVar10000);
+ }
+ }
+
+ // AddI(iv, e)
+ @Test
+ @IR(failOn = {IRNode.ANY_LOOP})
+ static void testIntIndexIntIVWithAddedVarOffset() {
+ for (int iv = 0; iv <= intVar100; iv++) {
+ Objects.checkIndex(iv + intVar0, intVar10000);
+ }
+ }
+
+ // SubI(iv, e)
+ @Test
+ @IR(failOn = {IRNode.ANY_LOOP})
+ static void testIntIndexIntIVWithSubtractedVarOffset() {
+ for (int iv = 0; iv <= intVar100; iv++) {
+ Objects.checkIndex(iv - intVar0, intVar10000);
+ }
+ }
+
+ // SubI(e, iv)
+ @Test
+ @IR(failOn = {IRNode.ANY_LOOP})
+ static void testIntIndexIntIVSubtractedFromVarOffset() {
+ for (int iv = 0; iv <= intVar100; iv++) {
+ Objects.checkIndex(intVar1000 - iv, intVar10000);
+ }
+ }
+
+ // AddI(iv, ConI)
+ @Test
+ @IR(failOn = {IRNode.ANY_LOOP})
+ static void testIntIndexIntIVWithAddedConstOffset() {
+ for (int iv = 0; iv <= intVar100; iv++) {
+ Objects.checkIndex(iv + 7, intVar10000);
+ }
+ }
+
+ // AddI((AddI(iv, e)), ConI)
+ @Test
+ @IR(failOn = {IRNode.ANY_LOOP})
+ static void testIntIndexIntIVWithAddedNestedOffset() {
+ for (int iv = 0; iv <= intVar100; iv++) {
+ Objects.checkIndex((iv + intVar0) + 7, intVar10000);
+ }
+ }
+
+ // MulI(iv, ConI)
+ @Test
+ @IR(failOn = {IRNode.ANY_LOOP})
+ static void testIntIndexIntIVWithMulScaling() {
+ for (int iv = 0; iv <= intVar100; iv++) {
+ Objects.checkIndex(iv * 11, intVar10000);
+ }
+ }
+
+ // AddI(MulI(iv, ConI), e)
+ @Test
+ @IR(failOn = {IRNode.ANY_LOOP})
+ static void testIntIndexIntIVWithMulScalingAndAddedVarOffset() {
+ for (int iv = 0; iv <= intVar100; iv++) {
+ Objects.checkIndex((iv * 11) + intVar0, intVar10000);
+ }
+ }
+
+ // AddI(AddI(MulI(iv, ConI), e), ConI)
+ @Test
+ @IR(failOn = {IRNode.ANY_LOOP})
+ static void testIntIndexIntIVWithMulScalingAndAddedNestedOffset() {
+ for (int iv = 0; iv <= intVar100; iv++) {
+ Objects.checkIndex(((iv * 11) + intVar0) + 7, intVar10000);
+ }
+ }
+
+ // LShiftI(iv, ConI)
+ @Test
+ @IR(failOn = {IRNode.ANY_LOOP})
+ static void testIntIndexIntIVWithShiftScaling() {
+ for (int iv = 0; iv <= intVar100; iv++) {
+ Objects.checkIndex(iv << 2, intVar10000);
+ }
+ }
+
+ // SubI(0, MulI(iv, ConI))
+ @Test
+ @IR(failOn = {IRNode.ANY_LOOP})
+ static void testIntIndexIntIVWithNegatedMulScaling() {
+ for (int iv = -intVar100; iv <= 0; iv++) {
+ Objects.checkIndex(0 - (iv * 11), intVar10000);
+ }
+ }
+
+ // AddI(MulI(iv, ConI), MulI(iv, ConI))
+ @Test
+ @IR(failOn = {IRNode.ANY_LOOP})
+ static void testIntIndexIntIVWithAddOfMulsScaling() {
+ for (int iv = 0; iv <= intVar100; iv++) {
+ Objects.checkIndex((iv * 3) + (iv * 7), intVar10000);
+ }
+ }
+
+ // SubI(LShiftI(iv, ConI), MulI(iv, ConI))
+ @Test
+ @IR(failOn = {IRNode.ANY_LOOP})
+ static void testIntIndexIntIVWithSubOfShiftAndMulScaling() {
+ for (int iv = 0; iv <= intVar100; iv++) {
+ Objects.checkIndex((iv << 4) - (iv * 13), intVar10000);
+ }
+ }
+
+ // Long indices using long induction variables
+
+ // iv
+ @Test
+ @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING)
+ @IR(failOn = {IRNode.ANY_LOOP})
+ static void testLongIndexLongIV() {
+ for (long iv = 0L; iv <= longVar100; iv++) {
+ Objects.checkIndex(iv, longVar10000);
+ }
+ }
+
+ // AddL(iv, e)
+ @Test
+ @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING)
+ @IR(failOn = {IRNode.ANY_LOOP})
+ static void testLongIndexLongIVWithAddedVarOffset() {
+ for (long iv = 0L; iv <= longVar100; iv++) {
+ Objects.checkIndex(iv + longVar0, longVar10000);
+ }
+ }
+
+ // SubL(iv, e)
+ @Test
+ @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING)
+ @IR(failOn = {IRNode.ANY_LOOP})
+ static void testLongIndexLongIVWithSubtractedVarOffset() {
+ for (long iv = 0L; iv <= longVar100; iv++) {
+ Objects.checkIndex(iv - longVar0, longVar10000);
+ }
+ }
+
+ // SubL(e, iv)
+ @Test
+ @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING)
+ @IR(failOn = {IRNode.ANY_LOOP})
+ static void testLongIndexLongIVSubtractedFromVarOffset() {
+ for (long iv = 0L; iv <= longVar100; iv++) {
+ Objects.checkIndex(longVar1000 - iv, longVar10000);
+ }
+ }
+
+ // AddL(iv, ConL)
+ @Test
+ @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING)
+ @IR(failOn = {IRNode.ANY_LOOP})
+ static void testLongIndexLongIVWithAddedConstOffset() {
+ for (long iv = 0L; iv <= longVar100; iv++) {
+ Objects.checkIndex(iv + 1L, longVar10000);
+ }
+ }
+
+ // AddL((AddL(iv, e)), ConL)
+ @Test
+ @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING)
+ @IR(failOn = {IRNode.ANY_LOOP})
+ static void testLongIndexLongIVWithAddedNestedOffset() {
+ for (long iv = 0L; iv <= longVar100; iv++) {
+ Objects.checkIndex((iv + longVar0) + 7L, longVar10000);
+ }
+ }
+
+ // MulL(iv, ConL)
+ @Test
+ @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING)
+ @IR(failOn = {IRNode.ANY_LOOP})
+ static void testLongIndexLongIVWithMulScaling() {
+ for (long iv = 0L; iv <= longVar100; iv++) {
+ Objects.checkIndex(iv * 11L, longVar10000);
+ }
+ }
+
+ // AddL(MulL(iv, ConL), e)
+ @Test
+ @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING)
+ @IR(failOn = {IRNode.ANY_LOOP})
+ static void testLongIndexLongIVWithMulScalingAndAddedVarOffset() {
+ for (long iv = 0L; iv <= longVar100; iv++) {
+ Objects.checkIndex((iv * 11L) + longVar0, longVar10000);
+ }
+ }
+
+ // AddL(AddL(MulL(iv, ConL), e), ConL)
+ @Test
+ @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING)
+ @IR(failOn = {IRNode.ANY_LOOP})
+ static void testLongIndexLongIVWithMulScalingAndAddedNestedOffset() {
+ for (long iv = 0L; iv <= longVar100; iv++) {
+ Objects.checkIndex(((iv * 11L) + longVar0) + 7L, longVar10000);
+ }
+ }
+
+ // LShiftL(iv, ConI)
+ @Test
+ @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING)
+ @IR(failOn = {IRNode.ANY_LOOP})
+ static void testLongIndexLongIVWithShiftScaling() {
+ for (long iv = 0L; iv <= longVar100; iv++) {
+ Objects.checkIndex(iv << 2, longVar10000);
+ }
+ }
+
+ // SubL(0L, MulL(iv, ConL))
+ @Test
+ @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING)
+ @IR(failOn = {IRNode.ANY_LOOP})
+ static void testLongIndexLongIVWithNegatedMulScaling() {
+ for (long iv = -longVar100; iv <= 0; iv++) {
+ Objects.checkIndex(0L - (iv * 11L), longVar10000);
+ }
+ }
+
+ // Long indices using int induction variables
+
+ // ConvI2L(iv)
+ @Test
+ @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING)
+ @IR(failOn = {IRNode.ANY_LOOP})
+ static void testLongIndexIntIV() {
+ for (int iv = 0; iv <= intVar100; iv++) {
+ Objects.checkIndex((long)iv, longVar10000);
+ }
+ }
+
+ // AddL(ConvI2L(iv), e)
+ @Test
+ @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING)
+ @IR(failOn = {IRNode.ANY_LOOP})
+ static void testLongIndexIntIVWithAddedVarOffset() {
+ for (int iv = 0; iv <= intVar100; iv++) {
+ Objects.checkIndex((long)iv + longVar0, longVar10000);
+ }
+ }
+
+ // AddL(ConvI2L(iv), ConL)
+ @Test
+ @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING)
+ @IR(failOn = {IRNode.ANY_LOOP})
+ static void testLongIndexIntIVWithAddedConstOffset() {
+ for (int iv = 0; iv <= intVar100; iv++) {
+ Objects.checkIndex((long)iv + 1L, longVar10000);
+ }
+ }
+
+ // MulL(ConvI2L(iv), ConL)
+ @Test
+ @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING)
+ @IR(failOn = {IRNode.ANY_LOOP})
+ static void testLongIndexIntIVWithMulScaling() {
+ for (int iv = 0; iv <= intVar100; iv++) {
+ Objects.checkIndex((long)iv * 11L, longVar10000);
+ }
+ }
+
+ // AddL(MulL(ConvI2L(iv), ConL), e)
+ @Test
+ @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING)
+ @IR(failOn = {IRNode.ANY_LOOP})
+ static void testLongIndexIntIVWithMulScalingAndAddedVarOffset() {
+ for (int iv = 0; iv <= intVar100; iv++) {
+ Objects.checkIndex(((long)iv * 11L) + longVar0, longVar10000);
+ }
+ }
+
+ // LShiftL(ConvI2L(iv), ConI)
+ @Test
+ @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING)
+ @IR(failOn = {IRNode.ANY_LOOP})
+ static void testLongIndexIntIVWithShiftScaling() {
+ for (int iv = 0; iv <= intVar100; iv++) {
+ Objects.checkIndex((long)iv << 2, longVar10000);
+ }
+ }
+
+ // SubL(0L, MulL(ConvI2L(iv), ConL))
+ @Test
+ @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING)
+ @IR(failOn = {IRNode.ANY_LOOP})
+ static void testLongIndexIntIVWithNegatedMulScaling() {
+ for (int iv = -intVar100; iv <= 0; iv++) {
+ Objects.checkIndex(0L - ((long)iv * 11L), longVar10000);
+ }
+ }
+
+ // ConvI2L(MulI(iv, ConI))
+ // Range check optimizations still trigger because the short scaling is
+ // optimized away during IGVN.
+ @Test
+ @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING)
+ @IR(failOn = {IRNode.ANY_LOOP})
+ static void testLongIndexIntIVWithShortMulScaling() {
+ for (int iv = 0; iv <= intVar100; iv++) {
+ Objects.checkIndex((long)(iv * 1), longVar10000);
+ }
+ }
+
+ // ConvI2L(LShiftI(iv, ConI))
+ // Range check optimizations still trigger because the short scaling is
+ // optimized away during IGVN.
+ @Test
+ @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING)
+ @IR(failOn = {IRNode.ANY_LOOP})
+ static void testLongIndexIntIVWithShortShiftScaling() {
+ for (int iv = 0; iv <= intVar100; iv++) {
+ Objects.checkIndex((long)(iv << 0), longVar10000);
+ }
+ }
+
+ // ConvI2L(AddI(MulI(iv, ConI), MulI(iv, ConI)))
+ // Range check optimizations still trigger because the short scaling is
+ // optimized away during IGVN.
+ @Test
+ @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING)
+ @IR(failOn = {IRNode.ANY_LOOP})
+ static void testLongIndexIntIVWithShortAddOfMulsScaling() {
+ for (int iv = 0; iv <= intVar100; iv++) {
+ Objects.checkIndex((long)((iv * 3) + (iv * -2)), longVar10000);
+ }
+ }
+
+ // ConvI2L(SubI(0, MulI(iv, ConI)))
+ // Range check optimizations still trigger because the short scaling is
+ // optimized away during IGVN.
+ @Test
+ @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING)
+ @IR(failOn = {IRNode.ANY_LOOP})
+ static void testLongIndexIntIVWithShortNegatedSubScaling() {
+ for (int iv = 0; iv <= intVar100; iv++) {
+ Objects.checkIndex((long)(0 - (iv * -1)), longVar10000);
+ }
+ }
+
+ // ConvI2L(SubI(MulI(iv, ConI), MulI(iv, ConI)))
+ // Range check optimizations still trigger because the short scaling is
+ // optimized away during IGVN.
+ @Test
+ @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING)
+ @IR(failOn = {IRNode.ANY_LOOP})
+ static void testLongIndexIntIVWithShortSubOfMulsScaling() {
+ for (int iv = 0; iv <= intVar100; iv++) {
+ Objects.checkIndex((long)((iv * 3) - (iv * 2)), longVar10000);
+ }
+ }
+
+ // AddL(MulL(ConvI2L(iv), ConL), MulL(ConvI2L(iv), ConL))
+ @Test
+ @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING)
+ @IR(failOn = {IRNode.ANY_LOOP})
+ static void testLongIndexIntIVWithAddOfMulsScaling() {
+ for (int iv = 0; iv <= intVar100; iv++) {
+ Objects.checkIndex(((long)iv * 3L) + ((long)iv * 7L), longVar10000);
+ }
+ }
+
+ // SubL(LShiftL(ConvI2L(iv), ConI), ConvI2L(iv))
+ @Test
+ @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING)
+ @IR(failOn = {IRNode.ANY_LOOP})
+ static void testLongIndexIntIVWithSubOfShiftScaling() {
+ for (int iv = 0; iv <= intVar100; iv++) {
+ Objects.checkIndex(((long)iv << 3) - iv, longVar10000);
+ }
+ }
+
+ // ConvI2L(AddI(MulI(iv, ConI), LShiftI(iv, ConI)))
+ // Range check optimizations still trigger because the short scaling is
+ // optimized away during IGVN.
+ @Test
+ @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING)
+ @IR(failOn = {IRNode.ANY_LOOP})
+ static void testLongIndexIntIVWithShortAddOfMulAndShiftScaling() {
+ for (int iv = 0; iv <= intVar100; iv++) {
+ Objects.checkIndex((long)((iv * -15) + (iv << 4)), longVar10000);
+ }
+ }
+
+ // AddL(AddL(ConvI2L(AddI(MulI(iv, ConI), MulI(iv, ConI))), e), ConL)
+ // Range check optimizations still trigger because the short scaling is
+ // optimized away during IGVN.
+ @Test
+ @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING)
+ @IR(failOn = {IRNode.ANY_LOOP})
+ static void testLongIndexIntIVWithShortAddOfMulsScalingAndAddedNestedOffset() {
+ for (int iv = 0; iv <= intVar100; iv++) {
+ Objects.checkIndex(((long)((iv * 3) + (iv * -2)) + longVar0) + 7L, longVar10000);
+ }
+ }
+
+ // Negative tests: expect the index expressions to be rejected and the loop
+ // to not be eliminated.
+
+ // Int indices using int induction variables
+
+ // MulI(iv, LoadI(..))
+ @Test
+ @IR(counts = {IRNode.COUNTED_LOOP, ">0"})
+ static void testIntIndexIntIVWithVarMulScaling() {
+ for (int iv = 0; iv <= intVar100; iv++) {
+ Objects.checkIndex(iv * intVar3, 10000);
+ }
+ }
+
+ // LShiftI(iv, LoadI(..))
+ @Test
+ @IR(counts = {IRNode.COUNTED_LOOP, ">0"})
+ static void testIntIndexIntIVWithVarShiftScaling() {
+ for (int iv = 0; iv <= intVar100; iv++) {
+ Objects.checkIndex(iv << intVar0, 10000);
+ }
+ }
+
+ // MulI(AddI(iv, LoadI(..)), ConI)
+ @Test
+ @IR(counts = {IRNode.COUNTED_LOOP, ">0"})
+ static void testIntIndexIntIVWithNonTrivialMulScaling() {
+ for (int iv = 0; iv <= intVar100; iv++) {
+ Objects.checkIndex((iv + intVar0) * 11, 10000);
+ }
+ }
+
+ // LShiftI(AddI(iv, LoadI(..)), LoadI(..))
+ @Test
+ @IR(counts = {IRNode.COUNTED_LOOP, ">0"})
+ static void testIntIndexIntIVWithNonTrivialShiftScaling() {
+ for (int iv = 0; iv <= intVar100; iv++) {
+ Objects.checkIndex((iv + intVar0) << intVar0, 10000);
+ }
+ }
+
+ // RShiftI(iv, ConI)
+ @Test
+ @IR(counts = {IRNode.COUNTED_LOOP, ">0"})
+ static void testIntIndexIntIVWithUnsupportedRightShift() {
+ for (int iv = 0; iv <= intVar100; iv++) {
+ Objects.checkIndex(iv >> 1, 10000);
+ }
+ }
+
+ // OrI(iv, ConI)
+ @Test
+ @IR(counts = {IRNode.COUNTED_LOOP, ">0"})
+ static void testIntIndexIntIVWithUnsupportedOr() {
+ for (int iv = 0; iv <= intVar100; iv++) {
+ Objects.checkIndex(iv | 1024, 10000);
+ }
+ }
+
+ // XorI(iv, ConI)
+ @Test
+ @IR(counts = {IRNode.COUNTED_LOOP, ">0"})
+ static void testIntIndexIntIVWithUnsupportedXor() {
+ for (int iv = 0; iv <= intVar100; iv++) {
+ Objects.checkIndex(iv ^ 1024, 10000);
+ }
+ }
+
+ // SubI(0, LShiftI(iv, ConI)) where scale(LShiftI(..)) == min_jint
+ @Test
+ @IR(counts = {IRNode.COUNTED_LOOP, ">0"})
+ static void testIntIndexIntIVWithNegatedMinJIntShiftScaling() {
+ for (int iv = 0; iv <= intVar100; iv += 2) {
+ Objects.checkIndex(0 - (iv << 31), 10000);
+ }
+ }
+
+ // Long indices using long induction variables
+
+ // MulL(iv, LoadL(..))
+ @Test
+ @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING)
+ @IR(counts = {IRNode.COUNTED_LOOP, ">0"})
+ static void testLongIndexLongIVWithVarMulScaling() {
+ for (long iv = 0L; iv <= longVar100; iv++) {
+ Objects.checkIndex(iv * longVar3, 10000L);
+ }
+ }
+
+ // LShiftL(iv, LoadI(..))
+ @Test
+ @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING)
+ @IR(counts = {IRNode.COUNTED_LOOP, ">0"})
+ static void testLongIndexLongIVWithVarShiftScaling() {
+ for (long iv = 0L; iv <= longVar100; iv++) {
+ Objects.checkIndex(iv << intVar0, 10000L);
+ }
+ }
+
+ // MulL(AddL(iv, LoadL(..)), ConL)
+ @Test
+ @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING)
+ @IR(counts = {IRNode.COUNTED_LOOP, ">0"})
+ static void testLongIndexLongIVWithNonTrivialMulScaling() {
+ for (long iv = 0L; iv <= longVar100; iv++) {
+ Objects.checkIndex((iv + longVar0) * 11L, 10000L);
+ }
+ }
+
+ // LShiftL(AddL(iv, LoadL(..)), LoadI(..))
+ @Test
+ @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING)
+ @IR(counts = {IRNode.COUNTED_LOOP, ">0"})
+ static void testLongIndexLongIVWithNonTrivialShiftScaling() {
+ for (long iv = 0L; iv <= longVar100; iv++) {
+ Objects.checkIndex((iv + longVar0) << intVar0, 10000L);
+ }
+ }
+
+ // RShiftL(iv, ConI)
+ @Test
+ @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING)
+ @IR(counts = {IRNode.COUNTED_LOOP, ">0"})
+ static void testLongIndexLongIVWithUnsupportedRightShift() {
+ for (long iv = 0L; iv <= longVar100; iv++) {
+ Objects.checkIndex(iv >> 1, 10000L);
+ }
+ }
+
+ // OrL(iv, ConL)
+ @Test
+ @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING)
+ @IR(counts = {IRNode.COUNTED_LOOP, ">0"})
+ static void testLongIndexLongIVWithUnsupportedOr() {
+ for (long iv = 0L; iv <= longVar100; iv++) {
+ Objects.checkIndex(iv | 1024L, 10000L);
+ }
+ }
+
+ // XorL(iv, ConL)
+ @Test
+ @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING)
+ @IR(counts = {IRNode.COUNTED_LOOP, ">0"})
+ static void testLongIndexLongIVWithUnsupportedXor() {
+ for (long iv = 0L; iv <= longVar100; iv++) {
+ Objects.checkIndex(iv ^ 1024L, 10000L);
+ }
+ }
+
+ // SubL(0L, LShiftL(iv, ConI)) where scale(LShiftL(..)) == min_jlong
+ @Test
+ @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING)
+ @IR(counts = {IRNode.COUNTED_LOOP, ">0"})
+ static void testLongIndexLongIVWithNegatedMinJLongShiftScaling() {
+ for (long iv = 0L; iv <= longVar100; iv += 2L) {
+ Objects.checkIndex(0L - (iv << 63), 10000L);
+ }
+ }
+
+ // Long indices using int induction variables
+
+ // ConvI2L(MulI(iv, ConI))
+ @Test
+ @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING)
+ @IR(counts = {IRNode.COUNTED_LOOP, ">0"})
+ static void testLongIndexIntIVWithNonUnitShortMulScaling() {
+ for (int iv = 0; iv <= intVar100; iv++) {
+ Objects.checkIndex((long)(iv * 11), 10000L);
+ }
+ }
+
+ // ConvI2L(LShiftI(iv, ConI))
+ @Test
+ @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING)
+ @IR(counts = {IRNode.COUNTED_LOOP, ">0"})
+ static void testLongIndexIntIVWithNonUnitShortShiftScaling() {
+ for (int iv = 0; iv <= intVar100; iv++) {
+ Objects.checkIndex((long)(iv << 2), 10000L);
+ }
+ }
+
+ // ConvI2L(SubI(0, MulI(iv, ConI)))
+ @Test
+ @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING)
+ @IR(counts = {IRNode.COUNTED_LOOP, ">0"})
+ static void testLongIndexIntIVWithNonUnitShortNegatedSubScaling() {
+ for (int iv = -100; iv <= 0; iv++) {
+ Objects.checkIndex((long)(0 - (iv * 3)),
+ // Using a variable range check length is
+ // required here for the index expression not to
+ // be optimized into something else that matches.
+ longVar10000);
+ }
+ }
+
+ // ConvI2L(SubI(0, LShiftI(iv, ConI))) where scale(LShiftI(..)) == min_jint
+ @Test
+ @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING)
+ @IR(counts = {IRNode.COUNTED_LOOP, ">0"})
+ static void testLongIndexIntIVWithShortNegatedSubMinJIntShiftScaling() {
+ for (int iv = 0; iv <= intVar100; iv += 2) {
+ Objects.checkIndex((long)(0 - (iv << 31)), 10000L);
+ }
+ }
+
+ // ConvI2L(MulI(iv, LoadI(..)))
+ @Test
+ @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING)
+ @IR(counts = {IRNode.COUNTED_LOOP, ">0"})
+ static void testLongIndexIntIVWithShortVarMulScaling() {
+ for (int iv = 0; iv <= intVar100; iv++) {
+ Objects.checkIndex((long)(iv * intVar3), 10000L);
+ }
+ }
+
+ // ConvI2L(LShiftI(iv, LoadI(..)))
+ @Test
+ @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING)
+ @IR(counts = {IRNode.COUNTED_LOOP, ">0"})
+ static void testLongIndexIntIVWithShortVarShiftScaling() {
+ for (int iv = 0; iv <= intVar100; iv++) {
+ Objects.checkIndex((long)(iv << intVar3), 10000L);
+ }
+ }
+
+ // MulL(AddL(ConvI2L(iv), LoadL(..)), ConL)
+ @Test
+ @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING)
+ @IR(counts = {IRNode.COUNTED_LOOP, ">0"})
+ static void testLongIndexIntIVWithMulOfAddScaling() {
+ for (int iv = 0; iv <= intVar100; iv++) {
+ Objects.checkIndex(((long)iv + longVar0) * 11L, 10000L);
+ }
+ }
+
+ // RShiftL(ConvI2L(iv), ConI)
+ @Test
+ @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING)
+ @IR(counts = {IRNode.COUNTED_LOOP, ">0"})
+ static void testLongIndexIntIVWithUnsupportedRightShift() {
+ for (int iv = 0; iv <= intVar100; iv++) {
+ Objects.checkIndex((long)iv >> 1, 10000L);
+ }
+ }
+
+ // ConvI2L(AddI(MulI(iv, ConI), MulI(iv, ConI)))
+ @Test
+ @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING)
+ @IR(counts = {IRNode.COUNTED_LOOP, ">0"})
+ static void testLongIndexIntIVWithNonUnitShortAddOfMulsScaling() {
+ for (int iv = 0; iv <= intVar100; iv++) {
+ Objects.checkIndex((long)((iv * 3) + (iv * 5)), 10000L);
+ }
+ }
+
+ // ConvI2L(AddI(MulI(iv, ConI), LShiftI(iv, ConI)))
+ @Test
+ @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING)
+ @IR(counts = {IRNode.COUNTED_LOOP, ">0"})
+ static void testLongIndexIntIVWithNonUnitShortAddOfMulAndShiftScaling() {
+ for (int iv = 0; iv <= intVar100; iv++) {
+ Objects.checkIndex((long)((iv * -15) + (iv << 5)), 10000L);
+ }
+ }
+
+ // ConvI2L(SubI(MulI(iv, ConI), MulI(iv, ConI)))
+ @Test
+ @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING)
+ @IR(counts = {IRNode.COUNTED_LOOP, ">0"})
+ static void testLongIndexIntIVWithNonUnitShortSubOfMulsScaling() {
+ for (int iv = 0; iv <= intVar100; iv++) {
+ Objects.checkIndex((long)((iv * 7) - (iv * 2)), 10000L);
+ }
+ }
+
+ // ConvI2L(SubI(LShiftI(iv, ConI), LShiftI(iv, ConI)))
+ @Test
+ @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING)
+ @IR(counts = {IRNode.COUNTED_LOOP, ">0"})
+ static void testLongIndexIntIVWithShortSubOfShiftsScaling() {
+ for (int iv = 0; iv <= intVar100; iv++) {
+ Objects.checkIndex((long)((iv << 33) - (iv << 0)), longVar10000);
+ }
+ }
+
+ // ConvI2L(SubI(LShiftI(iv, ConI), LShiftI(iv, ConI)))
+ @Test
+ @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING)
+ @IR(counts = {IRNode.COUNTED_LOOP, ">0"})
+ static void testLongIndexIntIVWithNonUnitShortSubOfShiftsScaling() {
+ for (int iv = 0; iv <= intVar100; iv++) {
+ Objects.checkIndex((long)((iv << 5) - (iv << 1)), 10000L);
+ }
+ }
+
+ // ConvI2L(SubI(0, LShiftI(iv, ConI)))
+ @Test
+ @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING)
+ @IR(counts = {IRNode.COUNTED_LOOP, ">0"})
+ static void testLongIndexIntIVWithNonUnitShortNegatedSubShiftScaling() {
+ for (int iv = -100; iv <= 0; iv++) {
+ Objects.checkIndex((long)(0 - (iv << 2)),
+ // Using a variable range check length is
+ // required here for the index expression not to
+ // be optimized into something else that matches.
+ longVar10000);
+ }
+ }
+}
diff --git a/test/hotspot/jtreg/compiler/rangechecks/TestLongRangeCheckWithZeroScaling.java b/test/hotspot/jtreg/compiler/rangechecks/TestLongRangeCheckWithZeroScaling.java
new file mode 100644
index 000000000000..f170929a2e01
--- /dev/null
+++ b/test/hotspot/jtreg/compiler/rangechecks/TestLongRangeCheckWithZeroScaling.java
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+
+package compiler.rangechecks;
+
+import java.util.Objects;
+
+/**
+ * @test
+ * @bug 8382378
+ * @summary Test that C2 can handle long range check indices scaled by zero.
+ * @run main ${test.main.class}
+ * @run main/othervm -Xcomp -XX:-TieredCompilation
+ * -XX:CompileCommand=compileonly,${test.main.class}::test*
+ * ${test.main.class}
+ */
+
+public class TestLongRangeCheckWithZeroScaling {
+
+ static void testCheckCancellingSubtractionOfScaledIndices() {
+ for (int i = 0; i < 100; i++) {
+ long index = ((long)(i << 10)) - (((long)i) << 10);
+ Objects.checkIndex(index, 100);
+ }
+ }
+
+ public static void main(String[] args) {
+ Objects.checkIndex(0, 1); // Ensure java.util.Objects is loaded.
+ for (int i = 0; i < 10_000; i++) {
+ testCheckCancellingSubtractionOfScaledIndices();
+ }
+ }
+}
From ce0dad268e99b110e46354769ed7c9f7d0fffd18 Mon Sep 17 00:00:00 2001
From: Daniel Fuchs
Date: Wed, 20 May 2026 17:54:50 +0000
Subject: [PATCH 009/169] 8382407: Enhance LDAP Lookups
Reviewed-by: rhalade, aefimov, jpai
---
.../sun/jndi/ldap/LdapBindingEnumeration.java | 4 +-
.../classes/com/sun/jndi/ldap/LdapCtx.java | 14 +-
.../sun/jndi/ldap/LdapReferralContext.java | 4 +-
.../sun/jndi/ldap/LdapSearchEnumeration.java | 4 +-
.../share/classes/com/sun/jndi/ldap/Obj.java | 10 +-
.../naming/internal/NamingManagerHelper.java | 29 ++--
.../internal/ObjectFactoriesFilter.java | 136 +++++++++++++++++-
.../share/classes/module-info.java | 24 +++-
8 files changed, 202 insertions(+), 23 deletions(-)
diff --git a/src/java.naming/share/classes/com/sun/jndi/ldap/LdapBindingEnumeration.java b/src/java.naming/share/classes/com/sun/jndi/ldap/LdapBindingEnumeration.java
index 5259cb638014..936ec08a6d13 100644
--- a/src/java.naming/share/classes/com/sun/jndi/ldap/LdapBindingEnumeration.java
+++ b/src/java.naming/share/classes/com/sun/jndi/ldap/LdapBindingEnumeration.java
@@ -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
@@ -65,7 +65,7 @@ final class LdapBindingEnumeration
try {
obj = NamingManagerHelper.getDirObjectInstance(obj, cn, homeCtx,
- homeCtx.envprops, attrs, ObjectFactoriesFilter::checkLdapFilter);
+ homeCtx.envprops, attrs, ObjectFactoriesFilter.ldapFactoryFilter());
} catch (NamingException e) {
throw e;
diff --git a/src/java.naming/share/classes/com/sun/jndi/ldap/LdapCtx.java b/src/java.naming/share/classes/com/sun/jndi/ldap/LdapCtx.java
index 636d8f5fe2b6..90bf789de1ff 100644
--- a/src/java.naming/share/classes/com/sun/jndi/ldap/LdapCtx.java
+++ b/src/java.naming/share/classes/com/sun/jndi/ldap/LdapCtx.java
@@ -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
@@ -388,6 +388,16 @@ public LdapCtx(String dn, String host, int port_number,
initEnv();
}
+ /**
+ * Whether this LDAP provider implementation trusts serial data.
+ * See {@linkplain java.naming/ java.naming module info} for more info.
+ *
+ * @return true if this LDAP provider implementation trusts serial data
+ */
+ public static boolean trustSerialData() {
+ return Obj.trustSerialData();
+ }
+
public LdapContext newInstance(Control[] reqCtls) throws NamingException {
LdapContext clone = new LdapCtx(this, currentDN);
@@ -1113,7 +1123,7 @@ protected Object c_lookup(Name name, Continuation cont)
try {
return NamingManagerHelper.getDirObjectInstance(obj, name, this,
- envprops, attrs, ObjectFactoriesFilter::checkLdapFilter);
+ envprops, attrs, ObjectFactoriesFilter.ldapFactoryFilter());
} catch (NamingException e) {
throw cont.fillInException(e);
diff --git a/src/java.naming/share/classes/com/sun/jndi/ldap/LdapReferralContext.java b/src/java.naming/share/classes/com/sun/jndi/ldap/LdapReferralContext.java
index f3f937029803..095cc4185e79 100644
--- a/src/java.naming/share/classes/com/sun/jndi/ldap/LdapReferralContext.java
+++ b/src/java.naming/share/classes/com/sun/jndi/ldap/LdapReferralContext.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1999, 2022, 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
@@ -118,7 +118,7 @@ final class LdapReferralContext implements DirContext, LdapContext {
Object obj;
try {
obj = NamingManagerHelper.getObjectInstance(ref, null, null,
- env, ObjectFactoriesFilter::checkLdapFilter);
+ env, ObjectFactoriesFilter.ldapFactoryFilter());
} catch (NamingException e) {
if (handleReferrals == LdapClient.LDAP_REF_THROW) {
diff --git a/src/java.naming/share/classes/com/sun/jndi/ldap/LdapSearchEnumeration.java b/src/java.naming/share/classes/com/sun/jndi/ldap/LdapSearchEnumeration.java
index 6f1981df1028..714481d151c2 100644
--- a/src/java.naming/share/classes/com/sun/jndi/ldap/LdapSearchEnumeration.java
+++ b/src/java.naming/share/classes/com/sun/jndi/ldap/LdapSearchEnumeration.java
@@ -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
@@ -124,7 +124,7 @@ protected SearchResult createItem(String dn, Attributes attrs,
// rcn is either relative to homeCtx or a fully qualified DN
obj = NamingManagerHelper.getDirObjectInstance(
obj, rcn, (relative ? homeCtx : null),
- homeCtx.envprops, attrs, ObjectFactoriesFilter::checkLdapFilter);
+ homeCtx.envprops, attrs, ObjectFactoriesFilter.ldapFactoryFilter());
} catch (NamingException e) {
throw e;
} catch (Exception e) {
diff --git a/src/java.naming/share/classes/com/sun/jndi/ldap/Obj.java b/src/java.naming/share/classes/com/sun/jndi/ldap/Obj.java
index 5e34f9543021..6065273a829e 100644
--- a/src/java.naming/share/classes/com/sun/jndi/ldap/Obj.java
+++ b/src/java.naming/share/classes/com/sun/jndi/ldap/Obj.java
@@ -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
@@ -119,6 +119,14 @@ final class Obj {
static final int SER_OBJECT = 3; // auxiliary serialized object class
static final int MAR_OBJECT = 4; // auxiliary marshalled object class
+ /**
+ * Whether deserialization of serial data is allowed
+ * @return true if deserialization of serial data is allowed
+ */
+ static boolean trustSerialData() {
+ return trustSerialData;
+ }
+
/**
* Encode an object in LDAP attributes.
* Supports binding Referenceable or Reference, Serializable,
diff --git a/src/java.naming/share/classes/com/sun/naming/internal/NamingManagerHelper.java b/src/java.naming/share/classes/com/sun/naming/internal/NamingManagerHelper.java
index 856a1b0a4446..43eeb788e400 100644
--- a/src/java.naming/share/classes/com/sun/naming/internal/NamingManagerHelper.java
+++ b/src/java.naming/share/classes/com/sun/naming/internal/NamingManagerHelper.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
@@ -88,7 +88,7 @@ public static Object getObjectInstance(Object refInfo, Name name, Context nameCt
// if reference has no factory, check for addresses
// containing URLs
- answer = processURLAddrs(ref, name, nameCtx, environment);
+ answer = processURLAddrs(ref, name, nameCtx, environment, factoryFilter);
if (answer != null) {
return answer;
}
@@ -156,7 +156,7 @@ public static Object getDirObjectInstance(Object refInfo, Name name, Context nam
// containing URLs
// ignore name & attrs params; not used in URL factory
// RMI references from '
- answer = processURLAddrs(ref, name, nameCtx, environment);
+ answer = processURLAddrs(ref, name, nameCtx, environment, factoryFilter);
if (answer != null) {
return answer;
}
@@ -239,7 +239,8 @@ private static Object createObjectFromFactories(Object obj, Name name,
* invoking a factory.
*/
static Object processURLAddrs(Reference ref, Name name, Context nameCtx,
- Hashtable,?> environment)
+ Hashtable,?> environment,
+ Predicate> factoryFilter)
throws NamingException {
for (int i = 0; i < ref.size(); i++) {
@@ -248,7 +249,7 @@ static Object processURLAddrs(Reference ref, Name name, Context nameCtx,
addr.getType().equalsIgnoreCase("URL")) {
String url = (String)addr.getContent();
- Object answer = processURL(url, name, nameCtx, environment);
+ Object answer = processURL(url, name, nameCtx, environment, factoryFilter);
if (answer != null) {
return answer;
}
@@ -258,7 +259,8 @@ static Object processURLAddrs(Reference ref, Name name, Context nameCtx,
}
private static Object processURL(Object refInfo, Name name,
- Context nameCtx, Hashtable,?> environment)
+ Context nameCtx, Hashtable,?> environment,
+ Predicate> factoryFilter)
throws NamingException {
Object answer;
@@ -269,7 +271,7 @@ private static Object processURL(Object refInfo, Name name,
String scheme = getURLScheme(url);
if (scheme != null) {
answer = getURLObject(scheme, refInfo, name, nameCtx,
- environment);
+ environment, factoryFilter);
if (answer != null) {
return answer;
}
@@ -285,7 +287,7 @@ private static Object processURL(Object refInfo, Name name,
String scheme = getURLScheme(urls[i]);
if (scheme != null) {
answer = getURLObject(scheme, refInfo, name, nameCtx,
- environment);
+ environment, factoryFilter);
if (answer != null)
return answer;
}
@@ -327,11 +329,16 @@ private static String getURLScheme(String str) {
* @param nameCtx Context whose provider resource file will be searched
* for package prefix values (or null if none)
* @param environment Environment properties for creating the context
+ * @param factoryFilter an object factory filter. If the factoryFilter is
+ * an instance of {@link ObjectFactoriesFilter.ContextFactoryFilter}
+ * it will be used to check whether the selected URL Context Factory
+ * is allowed to process a URL Reference of the given URL scheme.
* @see javax.naming.InitialContext
*/
private static Object getURLObject(String scheme, Object urlInfo,
Name name, Context nameCtx,
- Hashtable,?> environment)
+ Hashtable,?> environment,
+ Predicate> factoryFilter)
throws NamingException {
// e.g. "ftpURLContextFactory"
@@ -342,6 +349,10 @@ private static Object getURLObject(String scheme, Object urlInfo,
if (factory == null)
return null;
+ if (!ObjectFactoriesFilter.checkURLContextFactory(scheme, factory, factoryFilter)) {
+ return null;
+ }
+
// Found object factory
try {
return factory.getObjectInstance(urlInfo, name, nameCtx, environment);
diff --git a/src/java.naming/share/classes/com/sun/naming/internal/ObjectFactoriesFilter.java b/src/java.naming/share/classes/com/sun/naming/internal/ObjectFactoriesFilter.java
index c7d50778d7bf..5c5267f2275f 100644
--- a/src/java.naming/share/classes/com/sun/naming/internal/ObjectFactoriesFilter.java
+++ b/src/java.naming/share/classes/com/sun/naming/internal/ObjectFactoriesFilter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020, 2024, 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
@@ -25,12 +25,19 @@
package com.sun.naming.internal;
+import com.sun.jndi.ldap.LdapCtx;
import sun.security.util.SecurityProperties;
import javax.naming.Reference;
+import javax.naming.spi.ObjectFactory;
import java.io.ObjectInputFilter;
import java.io.ObjectInputFilter.FilterInfo;
import java.io.ObjectInputFilter.Status;
+import java.util.List;
+import java.util.Locale;
+import java.util.function.BiPredicate;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
/**
* This class implements the filter that validates object factories classes instantiated
@@ -47,6 +54,133 @@
*/
public final class ObjectFactoriesFilter {
+ /**
+ * Defines an Object Factory Filter that can be used to {@linkplain
+ * #test(Class) validate usage of regular ObjectFactory classes} as
+ * well as {@linkplain #checkURLContextFactory(String, Class) validate
+ * usage of URL Context Factory classes}.
+ */
+ public static final class ContextFactoryFilter implements Predicate> {
+ private final Predicate> factoryFilter;
+ private final BiPredicate> urlFactoryFilter;
+
+ /**
+ * Construct a new {@code ContextFactoryFilter} from the given
+ * {@code factoryFilter} and {@code urlFactoryFilter}
+ * @param factoryFilter A predicate that tells whether a given
+ * object factory class can be used to reconstruct an object
+ * from a given Reference.
+ * This is typically {@link #checkLdapFilter(Class)
+ * ObjectFactoriesFilter::checkLdapFilter}
+ * @param urlFactoryFilter A bi-predicate that tells whether
+ * a URL reference of a given scheme should be processed
+ * using the selected URL Context Factory.
+ * This is typically {@link #checkURLContextFactory(String, Class)
+ * ObjectFactoriesFilter::checkURLContextFactory}
+ */
+ private ContextFactoryFilter(Predicate> factoryFilter,
+ BiPredicate> urlFactoryFilter) {
+ this.factoryFilter = factoryFilter;
+ this.urlFactoryFilter = urlFactoryFilter;
+ }
+
+ /**
+ * Checks whether a URL reference of the given scheme should be processed
+ * using the selected URL Context Factory.
+ * @param scheme the URL scheme
+ * @param urlContextFactoryClass the URL Context Factory class
+ * @return true if the URL Reference can be processed
+ */
+ public boolean checkURLContextFactory(String scheme, Class> urlContextFactoryClass) {
+ return urlFactoryFilter.test(scheme, urlContextFactoryClass);
+ }
+
+ @Override
+ public boolean test(Class> factoryClass) {
+ return factoryFilter.test(factoryClass);
+ }
+
+ @Override
+ public ContextFactoryFilter negate() {
+ return new ContextFactoryFilter(factoryFilter.negate(), urlFactoryFilter.negate());
+ }
+
+ private static final ContextFactoryFilter LDAP_CONTEXT_FILTER =
+ new ContextFactoryFilter(ObjectFactoriesFilter::checkLdapFilter,
+ ObjectFactoriesFilter::ldapFollowsURL);
+ }
+
+ private static final class URLSchemes {
+ private static final String LDAP_DISABLED_URL_SCHEMES_PROP =
+ "jdk.jndi.ldap.disabledURLSchemes";
+ private static final String DEFAULT_LDAP_DISABLED_URL_SCHEMES =
+ LdapCtx.trustSerialData() ? "" : "rmi";
+ private static final String LDAP_DISABLED_URL_SCHEMES =
+ System.getProperty(LDAP_DISABLED_URL_SCHEMES_PROP,
+ DEFAULT_LDAP_DISABLED_URL_SCHEMES);
+ static final List LDAP_DISABLED_URL_SCHEMES_LIST;
+
+ static {
+ String[] protocols = LDAP_DISABLED_URL_SCHEMES.split(",");
+ LDAP_DISABLED_URL_SCHEMES_LIST = Stream.of(protocols)
+ .filter(Predicate.not(String::isEmpty))
+ .map(s -> s.toLowerCase(Locale.ROOT))
+ .toList();
+ }
+ }
+
+ /**
+ * Returns a {@code ContextFactoryFilter} used to check whether Reference
+ * objects retrieved from an LDAP context should be processed.
+ * The returned {@code ContextFactoryFilter} should be used to check
+ * whether a given object factory class can be used to reconstruct an object
+ * from a Reference, or whether a URL Reference of a given scheme can be
+ * processed using a given URL Context Factory class.
+ *
+ * @return a {@code ContextFactoryFilter} used to check whether Reference
+ * objects retrieved from an LDAP context should be processed.
+ */
+ public static ContextFactoryFilter ldapFactoryFilter() {
+ return ContextFactoryFilter.LDAP_CONTEXT_FILTER;
+ }
+
+ /**
+ * Checks whether URL References of the given scheme should be
+ * processed with the given URL Context Factory class, when the
+ * URL Reference was retrieved from LDAP.
+ * @param scheme the URL scheme
+ * @param urlContextFactory the URL Context Factory class
+ * @return true if the URL Reference should be processed by the
+ * given urlContextFactory class.
+ */
+ public static boolean ldapFollowsURL(String scheme, Class> urlContextFactory) {
+ // do not follow URLs if they are disabled;
+ for (String p : URLSchemes.LDAP_DISABLED_URL_SCHEMES_LIST) {
+ if (p.equalsIgnoreCase(scheme)) return false;
+ }
+ return true;
+ }
+
+ /**
+ * This method is called from NamingManagerHelper to verify whether
+ * a URL Reference of the given URL scheme can be processed by the
+ * given ObjectFactory.
+ * @param scheme the URL scheme
+ * @param urlContextFactory the URL Context Factory selected for that scheme
+ * @param factoryFilter the factory filter. If the Reference was retrieved from
+ * LDAP this is typically an instance of ContextFactoryFilter.
+ * @return true if a URL Reference of the given URL scheme is allowed to be processed
+ * by the given ObjectFactory.
+ */
+ public static boolean checkURLContextFactory(String scheme,
+ ObjectFactory urlContextFactory,
+ Predicate> factoryFilter) {
+ if (factoryFilter instanceof ContextFactoryFilter ucf) {
+ return ucf.checkURLContextFactory(scheme, urlContextFactory.getClass());
+ }
+ return true;
+ }
+
/**
* Checks if serial filter configured with {@code "jdk.jndi.object.factoriesFilter"}
* system property value allows instantiation of the specified objects factory class.
diff --git a/src/java.naming/share/classes/module-info.java b/src/java.naming/share/classes/module-info.java
index 5a731000194c..a31e808588d8 100644
--- a/src/java.naming/share/classes/module-info.java
+++ b/src/java.naming/share/classes/module-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 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
@@ -89,18 +89,34 @@
*
The following implementation specific system properties are supported by the
* default LDAP Naming Service Provider implementation in the JDK:
*
{@systemProperty com.sun.jndi.ldap.object.trustSerialData}:
* The value of this system property is the string representation of a boolean value
* that controls the deserialization of java objects from the {@code javaSerializedData} LDAP
* attribute, reconstruction of RMI references from the {@code javaRemoteLocation} LDAP attribute, and
* reconstruction of {@linkplain javax.naming.BinaryRefAddr binary reference addresses} from
- * the {@code javaReferenceAddress} LDAP attribute.
+ * the {@code javaReferenceAddress} LDAP attribute. It also controls whether RMI URL References
+ * obtained from LDAP will be followed.
* To allow the deserialization or reconstruction of java objects from {@code javaSerializedData},
* {@code javaRemoteLocation} or {@code javaReferenceAddress} attributes, the system property value
* can be set to {@code true} (case insensitive).
* If the property is not specified the deserialization of java objects
* from the {@code javaSerializedData}, the {@code javaRemoteLocation}, or {@code javaReferenceAddress}
- * attributes is not allowed.
+ * attributes is not allowed. In addition URL References obtained from LDAP that use the
+ * {@code rmi} scheme will not be followed by default, unless the default value of the {@link
+ * ##disabledURLSchemes jdk.jndi.ldap.disabledURLSchemes} system property is overridden on the
+ * java command line to allow it.
+ *
+ *
{@systemProperty jdk.jndi.ldap.disabledURLSchemes}:
+ * The value of this system property is a comma separated list of URL schemes for which
+ * use of a URL Context Factory is disabled when processing {@linkplain javax.naming.StringRefAddr
+ * string reference addresses} in the built-in JNDI/LDAP provider. If the value of that property
+ * is empty then all schemes are allowed.
+ * The default value of that property is empty if {@linkplain ##trustSerialData
+ * deserialization of java objects} is allowed, and {@code "rmi"} if deserialization of java
+ * objects is not allowed: in that latter case {@linkplain javax.naming.StringRefAddr string reference
+ * addresses} of {@linkplain javax.naming.StringRefAddr#getType() type} {@code "URL"} whose {@linkplain
+ * javax.naming.StringRefAddr#getContent() content} is an URL string with scheme {@code "rmi"}
+ * will not be followed.
*
*
{@systemProperty jdk.jndi.object.factoriesFilter}:
* The value of this system property defines a filter used by
From 599168ac348323ec1bd79069a9323cff4f428998 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Hannes=20Walln=C3=B6fer?=
Date: Thu, 21 May 2026 14:33:06 +0000
Subject: [PATCH 010/169] 8383530: Enhance Javadoc Searching
Reviewed-by: rhalade, nbenalla
---
.../doclets/formats/html/resources/script.js.template | 6 +++---
.../doclets/formats/html/resources/search.js.template | 4 ++--
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/script.js.template b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/script.js.template
index e00b8682811f..9dfc7ea45e19 100644
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/script.js.template
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/script.js.template
@@ -242,13 +242,13 @@ function isInput(elem) {
function switchCopyLabel(button, span) {
var copied = span.getAttribute("data-copied");
button.classList.add("visible");
- var initialLabel = span.innerHTML;
- span.innerHTML = copied;
+ var initialLabel = span.textContent;
+ span.textContent = copied;
setTimeout(function() {
button.classList.remove("visible");
setTimeout(function() {
if (initialLabel !== copied) {
- span.innerHTML = initialLabel;
+ span.textContent = initialLabel;
}
}, 100);
}, 1900);
diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/search.js.template b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/search.js.template
index 04b7ac7e3717..c00e4c0df39c 100644
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/search.js.template
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/search.js.template
@@ -449,7 +449,7 @@ window.addEventListener("load", () => {
if (redirect.checked) {
href += "&r=1";
}
- searchLink.innerHTML = href;
+ searchLink.textContent = href;
copy.onmouseenter();
}
function copyLink(e) {
@@ -670,7 +670,7 @@ window.addEventListener("load", () => {
const descSpan = document.createElement("span");
descSpan.className = "search-result-desc";
- descSpan.innerHTML = desc;
+ descSpan.textContent = desc;
link.appendChild(labelSpan);
link.appendChild(descSpan);
From c0987ae2ceb549ae5f206a929a3a329abd51fcf2 Mon Sep 17 00:00:00 2001
From: Volkan Yazici
Date: Tue, 26 May 2026 06:06:49 +0000
Subject: [PATCH 011/169] 8383439: Enhance HTTP Client Response Handling
Reviewed-by: michaelm, dfuchs, rhalade, djelinski
---
.../internal/net/http/ResponseContent.java | 69 +++++++++----------
.../net/httpserver/ChunkedInputStream.java | 30 ++++++--
2 files changed, 56 insertions(+), 43 deletions(-)
diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/ResponseContent.java b/src/java.net.http/share/classes/jdk/internal/net/http/ResponseContent.java
index 8c2c095ba0fa..e6933a3c2aae 100644
--- a/src/java.net.http/share/classes/jdk/internal/net/http/ResponseContent.java
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/ResponseContent.java
@@ -26,6 +26,7 @@
package jdk.internal.net.http;
import java.io.IOException;
+import java.net.ProtocolException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
@@ -141,7 +142,7 @@ class ChunkedBodyParser implements BodyParser {
volatile boolean cr = false; // tryReadChunkLength has found CR
volatile int chunkext = 0; // number of bytes already read in the chunk extension
volatile int digits = 0; // number of chunkLength bytes already read
- volatile int bytesToConsume; // number of bytes that still need to be consumed before proceeding
+ volatile int crlfBytesToConsume; // number of CRLF bytes that still need to be consumed
volatile ChunkState state = ChunkState.READING_LENGTH; // current state
volatile AbstractSubscription sub;
ChunkedBodyParser(Consumer onComplete) {
@@ -252,8 +253,8 @@ public void accept(ByteBuffer b) {
private int tryReadChunkLen(ByteBuffer chunkbuf) throws IOException {
assert state == ChunkState.READING_LENGTH;
while (chunkbuf.hasRemaining()) {
- if (chunkext + digits >= MAX_CHUNK_HEADER_SIZE) {
- throw new IOException("Chunk header size too long: " + (chunkext + digits));
+ if (chunkext >= MAX_CHUNK_HEADER_SIZE - digits) {
+ throw new IOException("Chunk header size too long: " + (chunkext + (long) digits));
}
int c = chunkbuf.get();
if (cr) {
@@ -265,8 +266,8 @@ private int tryReadChunkLen(ByteBuffer chunkbuf) throws IOException {
}
if (c == CR) {
cr = true;
- if (digits == 0 && debug.on()) {
- debug.log("tryReadChunkLen: invalid chunk header? No digits in chunkLen?");
+ if (digits == 0) {
+ throw new ProtocolException("Chunk header doesn't contain any digits");
}
} else if (cr == false && chunkext > 0) {
// we have seen a non digit character after the chunk length.
@@ -291,41 +292,33 @@ private int tryReadChunkLen(ByteBuffer chunkbuf) throws IOException {
}
} else {
digits++;
- partialChunklen = partialChunklen * 16 + digit;
+ long nextPartialChunklen = partialChunklen * 16L + digit;
+ if (nextPartialChunklen > Integer.MAX_VALUE) {
+ throw new ProtocolException("chunk length too big: " + nextPartialChunklen);
+ }
+ partialChunklen = (int) nextPartialChunklen;
}
}
}
return -1;
}
-
- // try to consume as many bytes as specified by bytesToConsume.
- // returns the number of bytes that still need to be consumed.
- // In practice this method is only called to consume one CRLF pair
- // with bytesToConsume set to 2, so it will only return 0 (if completed),
- // 1, or 2 (if chunkbuf doesn't have the 2 chars).
- private int tryConsumeBytes(ByteBuffer chunkbuf) throws IOException {
- int n = bytesToConsume;
- if (n > 0) {
- int e = Math.min(chunkbuf.remaining(), n);
-
- // verifies some assertions
- // this methods is called only to consume CRLF
- if (Utils.ASSERTIONSENABLED) {
- assert n <= 2 && e <= 2;
- ByteBuffer tmp = chunkbuf.slice();
- // if n == 2 assert that we will first consume CR
- assert (n == 2 && e > 0) ? tmp.get() == CR : true;
- // if n == 1 || n == 2 && e == 2 assert that we then consume LF
- assert (n == 1 || e == 2) ? tmp.get() == LF : true;
+ private boolean tryConsumeCRLF(ByteBuffer buffer) throws ProtocolException {
+ assert crlfBytesToConsume >= 0 && crlfBytesToConsume <= 2;
+ for (; crlfBytesToConsume > 0 && buffer.remaining() > 0; crlfBytesToConsume--) {
+ byte b = buffer.get();
+ if (crlfBytesToConsume == 1) { // 1 byte to go (implies already consumed CR), check LF
+ if (b != LF) {
+ throw new ProtocolException("LF expected");
+ }
+ } else { // 2 bytes to go, check CR
+ assert crlfBytesToConsume == 2;
+ if (b != CR) {
+ throw new ProtocolException("CR expected");
+ }
}
-
- chunkbuf.position(chunkbuf.position() + e);
- n -= e;
- bytesToConsume = n;
}
- assert n >= 0;
- return n;
+ return crlfBytesToConsume == 0;
}
/**
@@ -338,7 +331,7 @@ private int tryConsumeBytes(ByteBuffer chunkbuf) throws IOException {
*/
ByteBuffer tryReadOneHunk(ByteBuffer chunk) throws IOException {
int unfulfilled = bytesremaining;
- int toconsume = bytesToConsume;
+ int toconsume = crlfBytesToConsume;
ChunkState st = state;
if (st == ChunkState.READING_LENGTH && chunklen == -1) {
if (debug.on()) debug.log(() -> "Trying to read chunk len"
@@ -349,7 +342,7 @@ ByteBuffer tryReadOneHunk(ByteBuffer chunk) throws IOException {
if (debug.on()) debug.log("Got chunk len %d", clen);
cr = false; partialChunklen = 0;
unfulfilled = bytesremaining = clen;
- if (clen == 0) toconsume = bytesToConsume = 2; // that was the last chunk
+ if (clen == 0) toconsume = crlfBytesToConsume = 2; // that was the last chunk
else st = state = ChunkState.READING_DATA; // read the data
}
@@ -357,12 +350,12 @@ ByteBuffer tryReadOneHunk(ByteBuffer chunk) throws IOException {
if (debug.on())
debug.log("Trying to consume bytes: %d (remaining in buffer: %s)",
toconsume, chunk.remaining());
- if (tryConsumeBytes(chunk) > 0) {
+ if (!tryConsumeCRLF(chunk)) {
return READMORE;
}
}
- toconsume = bytesToConsume;
+ toconsume = crlfBytesToConsume;
assert toconsume == 0;
@@ -394,7 +387,7 @@ ByteBuffer tryReadOneHunk(ByteBuffer chunk) throws IOException {
debug.log( "Returning chunk bytes: %d", bytes2return);
returnBuffer = Utils.sliceWithLimitedCapacity(chunk, bytes2return).asReadOnlyBuffer();
unfulfilled = bytesremaining -= bytes2return;
- if (unfulfilled == 0) bytesToConsume = 2;
+ if (unfulfilled == 0) crlfBytesToConsume = 2;
}
assert unfulfilled >= 0;
@@ -408,7 +401,7 @@ ByteBuffer tryReadOneHunk(ByteBuffer chunk) throws IOException {
// then we will come back here later - skipping the block
// that reads data because remaining==0, and finding
// that the two bytes are now consumed.
- if (tryConsumeBytes(chunk) == 0) {
+ if (tryConsumeCRLF(chunk)) {
// we're done for this chunk! reset all states and
// prepare to read the next chunk.
chunklen = -1;
diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/ChunkedInputStream.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/ChunkedInputStream.java
index 0c003378d77c..057f15b93371 100644
--- a/src/jdk.httpserver/share/classes/sun/net/httpserver/ChunkedInputStream.java
+++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/ChunkedInputStream.java
@@ -26,11 +26,11 @@
package sun.net.httpserver;
import java.io.*;
-import java.net.*;
-import com.sun.net.httpserver.*;
-import com.sun.net.httpserver.spi.*;
class ChunkedInputStream extends LeftOverInputStream {
+
+ private IOException readFailure = null;
+
ChunkedInputStream(ExchangeImpl t, InputStream src) {
super (t, src);
}
@@ -50,6 +50,9 @@ class ChunkedInputStream extends LeftOverInputStream {
private int numeric(char[] arr, int nchars) throws IOException {
assert arr.length >= nchars;
+ if (nchars == 0) {
+ throw new IOException("missing chunk length");
+ }
int len = 0;
for (int i=0; i Integer.MAX_VALUE) {
+ throw new IOException("chunk length too big: " + nextLen);
+ }
+ len = (int) nextLen;
}
return len;
}
@@ -111,14 +118,27 @@ private int readChunkHeader() throws IOException {
}
protected int readImpl(byte[] b, int off, int len) throws IOException {
+ // Chunk reader is stateful, don't retry after a failure
+ if (readFailure != null) {
+ throw readFailure;
+ }
+ try {
+ return readImpl0(b, off, len);
+ } catch (IOException ioe) {
+ readFailure = ioe;
+ throw ioe;
+ }
+ }
+
+ private int readImpl0(byte[] b, int off, int len) throws IOException {
if (eof) {
return -1;
}
if (needToReadHeader) {
remaining = readChunkHeader();
if (remaining == 0) {
- eof = true;
consumeCRLF();
+ eof = true;
t.getServerImpl().requestCompleted(t.getConnection());
return -1;
}
From 943540d4ff36d39a453c4bc677caa22e595fdb1a Mon Sep 17 00:00:00 2001
From: Joe Wang
Date: Thu, 28 May 2026 18:27:43 +0000
Subject: [PATCH 012/169] 8383301: Enhance WebRowSets
Reviewed-by: aefimov, rhalade, lancea, jlu, naoto
---
make/modules/java.sql.rowset/Java.gmk | 1 +
.../rowset/RowSetResourceBundle.properties | 8 +-
.../classes/com/sun/rowset/WebRowSetImpl.java | 34 +-
.../rowset/internal/WebRowSetXmlReader.java | 168 +++++-----
.../rowset/internal/WebRowSetXmlWriter.java | 40 ++-
.../sun/rowset/internal/XmlErrorHandler.java | 36 +-
.../internal/XmlReaderContentHandler.java | 175 +++++++---
.../com/sun/rowset/internal/XmlResolver.java | 38 ++-
.../webrowset/CommonWebRowSetTests.java | 309 +++++++++++++++++-
test/jdk/javax/sql/xml/COFFEE_ROWS.xml | 32 +-
.../jdk/javax/sql/xml/DELETED_COFFEE_ROWS.xml | 52 +--
.../javax/sql/xml/INSERTED_COFFEE_ROWS.xml | 32 +-
.../sql/xml/MODFIED_DELETED_COFFEE_ROWS.xml | 56 ++--
.../jdk/javax/sql/xml/UPDATED_COFFEE_ROWS.xml | 40 ++-
.../sql/xml/UPDATED_INSERTED_COFFEE_ROWS.xml | 36 +-
15 files changed, 702 insertions(+), 355 deletions(-)
diff --git a/make/modules/java.sql.rowset/Java.gmk b/make/modules/java.sql.rowset/Java.gmk
index ecfe3e6e6412..9326a59220d1 100644
--- a/make/modules/java.sql.rowset/Java.gmk
+++ b/make/modules/java.sql.rowset/Java.gmk
@@ -29,6 +29,7 @@ DISABLED_WARNINGS_java += dangling-doc-comments
DOCLINT += -Xdoclint:all/protected \
'-Xdoclint/package:java.*,javax.*'
+COPY += .xsd
CLEAN_FILES += $(wildcard \
$(TOPDIR)/src/java.sql.rowset/share/classes/com/sun/rowset/*.properties \
$(TOPDIR)/src/java.sql.rowset/share/classes/javax/sql/rowset/*.properties)
diff --git a/src/java.sql.rowset/share/classes/com/sun/rowset/RowSetResourceBundle.properties b/src/java.sql.rowset/share/classes/com/sun/rowset/RowSetResourceBundle.properties
index a3b810fb8f13..ecb246e24463 100644
--- a/src/java.sql.rowset/share/classes/com/sun/rowset/RowSetResourceBundle.properties
+++ b/src/java.sql.rowset/share/classes/com/sun/rowset/RowSetResourceBundle.properties
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2005, 2024, 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
@@ -137,9 +137,11 @@ syncrsimpl.syncnotpos = Synchronization is not possible
syncrsimpl.valtores = Value to be resolved can either be in the database or in cachedrowset
#WebRowSetXmlReader exception
-wrsxmlreader.invalidcp = End of RowSet reached. Invalid cursor position
wrsxmlreader.readxml = readXML : {0}
-wrsxmlreader.parseerr = ** Parsing Error : {0} , line : {1} , uri : {2}
+wrsxmlreader.stdschema = readXML : Cannot locate standard WebRowSet schema: {0}.
+wrsxmlreader.schemaloc = readXML : Malformed xsi:schemaLocation: {0}.
+wrsxmlreader.schema = readXML : Non-standard schema specified for namespace {0} : {1}.
+wrsxmlreader.entity = readXML : external entity {0} other than the standard schema is not allowed.
#WebRowSetXmlWriter exceptions
wrsxmlwriter.ioex = IOException : {0}
diff --git a/src/java.sql.rowset/share/classes/com/sun/rowset/WebRowSetImpl.java b/src/java.sql.rowset/share/classes/com/sun/rowset/WebRowSetImpl.java
index a72ed6cdf43d..e638e956c185 100644
--- a/src/java.sql.rowset/share/classes/com/sun/rowset/WebRowSetImpl.java
+++ b/src/java.sql.rowset/share/classes/com/sun/rowset/WebRowSetImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003, 2021, 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
@@ -179,27 +179,23 @@ public void readXml(java.io.Reader reader) throws SQLException {
// This will change to a XmlReader, which over-rides the default
// Xml that is used when a WRS is instantiated.
//WebRowSetXmlReader xmlReader = getXmlReader();
- try {
- if (reader != null) {
- xmlReader.readXML(this, reader);
-
- // Position is before the first row
- // The cursor position is to be stored while serializng
- // and deserializing the WebRowSet Object.
- if(curPosBfrWrite == 0) {
- this.beforeFirst();
- }
+ if (reader != null) {
+ xmlReader.readXML(this, reader);
- // Return the position back to place prior to callin writeXml
- else {
- this.absolute(curPosBfrWrite);
- }
+ // Position is before the first row
+ // The cursor position is to be stored while serializing
+ // and deserializing the WebRowSet Object.
+ if(curPosBfrWrite == 0) {
+ this.beforeFirst();
+ }
- } else {
- throw new SQLException(resBundle.handleGetObject("webrowsetimpl.invalidrd").toString());
+ // Return the position back to place prior to callin writeXml
+ else {
+ this.absolute(curPosBfrWrite);
}
- } catch (Exception e) {
- throw new SQLException(e.getMessage());
+
+ } else {
+ throw new SQLException(resBundle.handleGetObject("webrowsetimpl.invalidrd").toString());
}
}
diff --git a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/WebRowSetXmlReader.java b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/WebRowSetXmlReader.java
index a0d99e4fbcf4..2c105bd4ae88 100644
--- a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/WebRowSetXmlReader.java
+++ b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/WebRowSetXmlReader.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003, 2010, 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
@@ -25,18 +25,23 @@
package com.sun.rowset.internal;
+import java.net.URL;
import java.sql.*;
import javax.sql.*;
import java.io.*;
import org.xml.sax.*;
import org.xml.sax.helpers.*;
+
+import javax.xml.XMLConstants;
import javax.xml.parsers.*;
import com.sun.rowset.*;
import java.text.MessageFormat;
import javax.sql.rowset.*;
import javax.sql.rowset.spi.*;
+import javax.xml.validation.Schema;
+import javax.xml.validation.SchemaFactory;
/**
* An implementation of the XmlReader interface, which
@@ -45,7 +50,9 @@
* as its parser.
*/
public class WebRowSetXmlReader implements XmlReader, Serializable {
-
+ // standard schema
+ static final String WEBROWSET_XSD = "webrowset.xsd";
+ private static final String VALIDATION_PROPERTY = "jdk.sql.rowset.webrowsetValidation";
private JdbcRowSetResourceBundle resBundle;
@@ -81,56 +88,7 @@ public WebRowSetXmlReader(){
* @see XmlReaderContentHandler
*/
public void readXML(WebRowSet caller, java.io.Reader reader) throws SQLException {
- try {
- // Crimson Parser(as in J2SE 1.4.1 is NOT able to handle
- // Reader(s)(FileReader).
- //
- // But getting the file as a Stream works fine. So we are going to take
- // the reader but send it as a InputStream to the parser. Note that this
- // functionality needs to work against any parser
- // Crimson(J2SE 1.4.x) / Xerces(J2SE 1.5.x).
- InputSource is = new InputSource(reader);
- DefaultHandler dh = new XmlErrorHandler();
- XmlReaderContentHandler hndr = new XmlReaderContentHandler((RowSet)caller);
- SAXParserFactory factory = SAXParserFactory.newInstance();
- factory.setNamespaceAware(true);
- factory.setValidating(true);
- SAXParser parser = factory.newSAXParser() ;
-
- parser.setProperty(
- "http://java.sun.com/xml/jaxp/properties/schemaLanguage", "http://www.w3.org/2001/XMLSchema");
-
- XMLReader reader1 = parser.getXMLReader() ;
- reader1.setEntityResolver(new XmlResolver());
- reader1.setContentHandler(hndr);
-
- reader1.setErrorHandler(dh);
-
- reader1.parse(is);
-
- } catch (SAXParseException err) {
- System.out.println (MessageFormat.format(resBundle.handleGetObject("wrsxmlreader.parseerr").toString(), new Object[]{ err.getMessage (), err.getLineNumber(), err.getSystemId()}));
- err.printStackTrace();
- throw new SQLException(err.getMessage());
-
- } catch (SAXException e) {
- Exception x = e;
- if (e.getException () != null)
- x = e.getException();
- x.printStackTrace ();
- throw new SQLException(x.getMessage());
-
- }
-
- // Will be here if trying to write beyond the RowSet limits
-
- catch (ArrayIndexOutOfBoundsException aie) {
- throw new SQLException(resBundle.handleGetObject("wrsxmlreader.invalidcp").toString());
- }
- catch (Throwable e) {
- throw new SQLException(MessageFormat.format(resBundle.handleGetObject("wrsxmlreader.readxml").toString() , e.getMessage()));
- }
-
+ readXml(caller, new InputSource(reader));
}
@@ -160,52 +118,76 @@ public void readXML(WebRowSet caller, java.io.Reader reader) throws SQLException
* @see XmlReaderContentHandler
*/
public void readXML(WebRowSet caller, java.io.InputStream iStream) throws SQLException {
- try {
- InputSource is = new InputSource(iStream);
- DefaultHandler dh = new XmlErrorHandler();
-
- XmlReaderContentHandler hndr = new XmlReaderContentHandler((RowSet)caller);
- SAXParserFactory factory = SAXParserFactory.newInstance();
- factory.setNamespaceAware(true);
- factory.setValidating(true);
-
- SAXParser parser = factory.newSAXParser() ;
-
- parser.setProperty("http://java.sun.com/xml/jaxp/properties/schemaLanguage",
- "http://www.w3.org/2001/XMLSchema");
-
- XMLReader reader1 = parser.getXMLReader() ;
- reader1.setEntityResolver(new XmlResolver());
- reader1.setContentHandler(hndr);
-
- reader1.setErrorHandler(dh);
-
- reader1.parse(is);
-
- } catch (SAXParseException err) {
- System.out.println (MessageFormat.format(resBundle.handleGetObject("wrsxmlreader.parseerr").toString(), new Object[]{err.getLineNumber(), err.getSystemId() }));
- System.out.println(" " + err.getMessage ());
- err.printStackTrace();
- throw new SQLException(err.getMessage());
-
- } catch (SAXException e) {
- Exception x = e;
- if (e.getException () != null)
- x = e.getException();
- x.printStackTrace ();
- throw new SQLException(x.getMessage());
+ readXml(caller, new InputSource(iStream));
+ }
+ /**
+ * Parses the supplied XML input into the given {@code WebRowSet}.
+ *
+ * The parser validates the XML against the standard WebRowSet schema by default.
+ * Validation can be disabled by setting {@systemProperty jdk.sql.rowset.webrowsetValidation}
+ * to {@code false} so that the parser can read legacy WebRowSet XML.
+ *
+ * @param caller the rowset to populate
+ * @param is the SAX input source to parse
+ * @throws SQLException if parsing or rowset population fails
+ */
+ private void readXml(WebRowSet caller, InputSource is) throws SQLException {
+ try {
+ boolean validating = validationEnabled();
+
+ SAXParserFactory spf = SAXParserFactory.newDefaultNSInstance();
+ spf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
+ if (validating) {
+ spf.setSchema(getSchema());
+ }
+
+ XMLReader reader = spf.newSAXParser().getXMLReader();
+ reader.setEntityResolver(new XmlResolver());
+ reader.setContentHandler(new XmlReaderContentHandler(caller, validating));
+ reader.setErrorHandler(new XmlErrorHandler());
+ reader.parse(is);
+ } catch (SAXException | ParserConfigurationException | IOException err) {
+ throw new SQLException(MessageFormat.format(resBundle.handleGetObject("wrsxmlreader.readxml").toString(),
+ err.getMessage()), err);
}
+ }
- // Will be here if trying to write beyond the RowSet limits
-
- catch (ArrayIndexOutOfBoundsException aie) {
- throw new SQLException(resBundle.handleGetObject("wrsxmlreader.invalidcp").toString());
+ /**
+ * Returns the standard WebRowSet schema bundled in the
+ * {@code java.sql.rowset} module.
+ *
+ * A new schema is created for each call so parser instances do not share
+ * validator implementation state. This can be revisited once the bundled
+ * schema implementation is safe to share.
+ *
+ * @return the WebRowSet schema
+ * @throws SAXException if the schema cannot be located or parsed
+ */
+ private Schema getSchema() throws SAXException {
+ URL stdSchema = WebRowSet.class.getResource(WEBROWSET_XSD);
+ if (stdSchema == null) {
+ throw new SAXException(MessageFormat.format(resBundle.handleGetObject(
+ "wrsxmlreader.stdschema").toString(), WEBROWSET_XSD));
}
- catch (Throwable e) {
- throw new SQLException(MessageFormat.format(resBundle.handleGetObject("wrsxmlreader.readxml").toString() , e.getMessage()));
- }
+ SchemaFactory sf = SchemaFactory.newDefaultInstance();
+ sf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
+ return sf.newSchema(stdSchema);
+ }
+
+ /**
+ * Returns whether WebRowSet XML validation is enabled.
+ *
+ * Validation is enabled by default. It is disabled only when the
+ * {@systemProperty jdk.sql.rowset.webrowsetValidation} is set to
+ * {@code false}, case-insensitive.
+ *
+ * @return {@code true} if XML should be schema-validated
+ */
+ private static boolean validationEnabled() {
+ return !"false".equalsIgnoreCase(
+ System.getProperty(VALIDATION_PROPERTY, "true").trim());
}
/**
diff --git a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/WebRowSetXmlWriter.java b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/WebRowSetXmlWriter.java
index 3e419be005a0..426afbf551ac 100644
--- a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/WebRowSetXmlWriter.java
+++ b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/WebRowSetXmlWriter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003, 2022, 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
@@ -148,6 +148,7 @@ private void writeRowSet(WebRowSet caller) throws SQLException {
writeData(caller);
endHeader();
+ writer.flush();
} catch (java.io.IOException ex) {
throw new SQLException(MessageFormat.format(resBundle.handleGetObject("wrsxmlwriter.ioex").toString(), ex.getMessage()));
@@ -321,12 +322,19 @@ private void writeData(WebRowSet caller) throws java.io.IOException {
caller.beforeFirst();
caller.setShowDeleted(true);
while (caller.next()) {
- if (caller.rowDeleted() && caller.rowInserted()) {
+ boolean rowDeleted = caller.rowDeleted();
+ boolean rowInserted = caller.rowInserted();
+ boolean rowUpdated = caller.rowUpdated();
+ boolean writeUpdateValues = rowDeleted || rowUpdated;
+
+ if (rowDeleted && rowInserted) {
beginSection("modifyRow");
- } else if (caller.rowDeleted()) {
+ } else if (rowDeleted) {
beginSection("deleteRow");
- } else if (caller.rowInserted()) {
+ } else if (rowInserted) {
beginSection("insertRow");
+ } else if (rowUpdated) {
+ beginSection("modifyRow");
} else {
beginSection("currentRow");
}
@@ -338,13 +346,17 @@ private void writeData(WebRowSet caller) throws java.io.IOException {
beginTag("columnValue");
writeValue(i, (RowSet)rs);
endTag("columnValue");
- beginTag("updateRow");
+ beginTag("updateValue");
writeValue(i, caller);
- endTag("updateRow");
+ endTag("updateValue");
} else {
beginTag("columnValue");
writeValue(i, caller);
endTag("columnValue");
+ if (writeUpdateValues) {
+ beginTag("updateValue");
+ endTag("updateValue");
+ }
}
}
@@ -482,7 +494,6 @@ private void endSection(String tag) throws java.io.IOException {
} else {
;
}
- writer.flush();
}
private void endSection() throws java.io.IOException {
@@ -492,7 +503,6 @@ private void endSection() throws java.io.IOException {
String beginTag = getTag();
writer.write("" + beginTag + ">\n");
- writer.flush();
}
private void beginTag(String tag) throws java.io.IOException {
@@ -513,7 +523,6 @@ private void endTag(String tag) throws java.io.IOException {
} else {
;
}
- writer.flush();
}
private void emptyTag(String tag) throws java.io.IOException {
@@ -550,8 +559,6 @@ private void writeStringData(String s) throws java.io.IOException {
private void writeString(String s) throws java.io.IOException {
if (s != null) {
writer.write(s);
- } else {
- writeNull();
}
}
@@ -595,9 +602,14 @@ private void writeIndent(int tabs) throws java.io.IOException {
}
private void propString(String tag, String s) throws java.io.IOException {
- beginTag(tag);
- writeString(s);
- endTag(tag);
+ if (s == null) {
+ writeIndent(stack.size());
+ writer.write("<" + tag + "/>\n");
+ } else {
+ beginTag(tag);
+ writeString(s);
+ endTag(tag);
+ }
}
private void propInteger(String tag, int i) throws java.io.IOException {
diff --git a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlErrorHandler.java b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlErrorHandler.java
index 099446c6865c..8bbafd0999c7 100644
--- a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlErrorHandler.java
+++ b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlErrorHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003, 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
@@ -25,12 +25,10 @@
package com.sun.rowset.internal;
-import org.xml.sax.*;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
-import com.sun.rowset.*;
-import javax.sql.rowset.*;
-
/**
* An implementation of the DefaultHandler interface, which
@@ -38,22 +36,16 @@
* This is the ErrorHandler which helps WebRowSetXmlReader
* to handle any errors while reading the xml data.
*/
-
-
public class XmlErrorHandler extends DefaultHandler {
- public int errorCounter = 0;
-
- public void error(SAXParseException e) throws SAXException {
- errorCounter++;
-
- }
-
- public void fatalError(SAXParseException e) throws SAXException {
- errorCounter++;
-
- }
-
- public void warning(SAXParseException exception) throws SAXException {
-
- }
+ @Override
+ public void error(SAXParseException e) throws SAXException {
+ throw e;
+ }
+
+ @Override
+ public void warning(SAXParseException e) throws SAXException {
+ // to ensure consistent handling of all XML parsing and validation diagnostics
+ // warnings are not ignored, though unlikely to happen
+ throw e;
+ }
}
diff --git a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlReaderContentHandler.java b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlReaderContentHandler.java
index 73bb39eba985..c70a7bc142a2 100644
--- a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlReaderContentHandler.java
+++ b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlReaderContentHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003, 2024, 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
@@ -370,16 +370,21 @@ public class XmlReaderContentHandler extends DefaultHandler {
*/
private static final int MetaNullTag = 19;
- private String [] data = {"currentRow", "columnValue", "insertRow", "deleteRow", "insdel", "updateRow", "null" , "emptyString"};
+ private String [] data = {"currentRow", "columnValue", "insertRow", "deleteRow",
+ "modifyRow", "updateValue", "null" , "emptyString"};
private static final int RowTag = 0;
private static final int ColTag = 1;
private static final int InsTag = 2;
private static final int DelTag = 3;
- private static final int InsDelTag = 4;
+ private static final int ModTag = 4;
private static final int UpdTag = 5;
private static final int NullTag = 6;
private static final int EmptyStringTag = 7;
+ private static final int InsDelTag = 8;
+ private static final int UpdRowTag = 9;
+
+ private static final String XSI_NS = "http://www.w3.org/2001/XMLSchema-instance";
/**
* A constant indicating the state of this XmlReaderContentHandler
@@ -419,6 +424,9 @@ public class XmlReaderContentHandler extends DefaultHandler {
private JdbcRowSetResourceBundle resBundle;
+ private boolean sawRoot;
+ private final boolean validating;
+
/**
* Constructs a new XmlReaderContentHandler object that will
* assist the SAX parser in reading a WebRowSet object in the
@@ -431,6 +439,20 @@ public class XmlReaderContentHandler extends DefaultHandler {
* @param r the RowSet object in XML format that will be read
*/
public XmlReaderContentHandler(RowSet r) {
+ this(r, true);
+ }
+
+ /**
+ * Constructs a new {@code XmlReaderContentHandler} with a validation flag.
+ * When validation is disabled, the handler accepts legacy WebRowSet XML tags
+ * in addition to the standard tags.
+ *
+ * @param r the {@code RowSet} object in XML format that will be read
+ * @param validating the flag indicating whether schema validation is on
+ */
+ XmlReaderContentHandler(RowSet r, boolean validating) {
+ this.validating = validating;
+
// keep the rowset we've been given
rs = (WebRowSetImpl)r;
@@ -481,21 +503,27 @@ private void initMaps() {
items = properties.length;
for (i=0;i();
items = colDef.length;
for (i=0;i();
items = data.length;
for (i=0;iThe XML parser is configured with the standard WebRowSet schema
+ * explicitly through the JAXP Validation API, so schema validation does
+ * not depend on xsi:schemaLocation being present in the XML instance
+ * document. This method therefore performs only an additional consistency
+ * check on the schema hint provided by the document.
+ *
+ * @param attributes the attributes of the root webRowSet element
+ * @throws SAXException if a non-standard schema location is specified
+ */
+ private void checkSchemaLocation(Attributes attributes) throws SAXException {
+ String schemaLocation = attributes.getValue(XSI_NS, "schemaLocation");
+
+ // schemaLocation is optional when schema is supplied explicitly
+ if (schemaLocation == null || schemaLocation.isBlank()) {
+ return;
+ }
+
+ String[] tokens = schemaLocation.trim().split("\\s+");
+ if (tokens.length % 2 != 0) {
+ throw new SAXException(MessageFormat.format(
+ resBundle.handleGetObject("wrsxmlreader.schemaloc").toString(), schemaLocation));
+ }
+
+ for (int i = 0; i < tokens.length; i += 2) {
+ String ns = tokens[i];
+ String xsd = tokens[i + 1];
+
+ if (!isWebRowSetSchema(xsd)) {
+ throw new SAXException(MessageFormat.format(
+ resBundle.handleGetObject("wrsxmlreader.schema").toString(), ns, xsd));
+ }
+ }
+ }
+
+ private static boolean isWebRowSetSchema(String xsd) {
+ return xsd.equals(WebRowSetXmlReader.WEBROWSET_XSD)
+ || xsd.endsWith("/" + WebRowSetXmlReader.WEBROWSET_XSD);
+ }
+
/**
* Sets the value for the given element if name is one of
* the array elements in the fields properties,
@@ -742,6 +819,8 @@ public void endElement(String uri, String lName, String qName) throws SAXExcepti
}
break;
case RowTag:
+ case ModTag:
+ case InsDelTag:
try {
rs.insertRow();
rs.moveToCurrentRow();
@@ -751,6 +830,8 @@ public void endElement(String uri, String lName, String qName) throws SAXExcepti
// rowInserted flagging
rs.setOriginalRow();
+ // Legacy currentRow elements can carry update tags when
+ // schema validation is disabled.
applyUpdates();
} catch (SQLException ex) {
throw new SAXException(MessageFormat.format(resBundle.handleGetObject("xmlrch.errconstr").toString(), ex.getMessage()));
@@ -762,6 +843,8 @@ public void endElement(String uri, String lName, String qName) throws SAXExcepti
rs.moveToCurrentRow();
rs.next();
rs.setOriginalRow();
+ // Legacy deleteRow elements may omit update tags when
+ // schema validation is disabled.
applyUpdates();
rs.deleteRow();
} catch (SQLException ex) {
@@ -779,33 +862,20 @@ public void endElement(String uri, String lName, String qName) throws SAXExcepti
}
break;
- case InsDelTag:
- try {
- rs.insertRow();
- rs.moveToCurrentRow();
- rs.next();
- rs.setOriginalRow();
- applyUpdates();
- } catch (SQLException ex) {
- throw new SAXException(MessageFormat.format(resBundle.handleGetObject("xmlrch.errinsdel").toString() , ex.getMessage()));
- }
- break;
-
case UpdTag:
- try {
- if(getNullValue())
- {
- insertValue(null);
- setNullValue(false);
- } else if(getEmptyStringValue()) {
- insertValue("");
- setEmptyStringValue(false);
- } else {
- updates.add(upd);
- }
- } catch(SQLException ex) {
- throw new SAXException(MessageFormat.format(resBundle.handleGetObject("xmlrch.errupdate").toString() , ex.getMessage()));
- }
+ case UpdRowTag:
+ // Empty updateValue/updateRow placeholders are ignored when
+ // they carry no value.
+ if(getNullValue())
+ {
+ addUpdate(null);
+ setNullValue(false);
+ } else if(getEmptyStringValue()) {
+ addUpdate("");
+ setEmptyStringValue(false);
+ } else if(!tempUpdate.isEmpty()) {
+ addUpdate(tempUpdate);
+ }
break;
default:
@@ -825,10 +895,7 @@ private void applyUpdates() throws SAXException {
while (i.hasNext()) {
upd = (Object [])i.next();
idx = ((Integer)upd[0]).intValue();
-
- if(!(lastval.equals(upd[1]))){
- insertValue((String)(upd[1]));
- }
+ insertValue((String)(upd[1]));
}
rs.updateRow();
@@ -841,6 +908,19 @@ private void applyUpdates() throws SAXException {
}
+ /**
+ * Queues an update value for the current column to be applied when the
+ * row element is complete.
+ *
+ * @param value the update value, can be {@code null}
+ */
+ private void addUpdate(String value) {
+ upd = new Object[2];
+ upd[0] = Integer.valueOf(idx);
+ upd[1] = value;
+ updates.add(upd);
+ }
+
/**
* Sets a property, metadata, or data value with the characters in
* the given array of characters, starting with the array element
@@ -1027,10 +1107,12 @@ private java.sql.Timestamp getTimestampValue(String s) {
private void setPropertyValue(String s) throws SQLException {
// find out if we are going to be dealing with a null
boolean nullValue = getNullValue();
+ // Empty nullable properties preserve the legacy behavior while
+ // remaining schema-valid.
switch(getTag()) {
case CommandTag:
- if (nullValue)
+ if (nullValue || s == null || s.isEmpty())
; //rs.setCommand(null);
else
rs.setCommand(s);
@@ -1042,7 +1124,7 @@ private void setPropertyValue(String s) throws SQLException {
rs.setConcurrency(getIntegerValue(s));
break;
case DatasourceTag:
- if (nullValue)
+ if (nullValue || s == null || s.isEmpty())
rs.setDataSourceName(null);
else
rs.setDataSourceName(s);
@@ -1129,14 +1211,14 @@ private void setPropertyValue(String s) throws SQLException {
rs.setShowDeleted(getBooleanValue(s));
break;
case TableNameTag:
- if (nullValue)
+ if (nullValue || s == null || s.isEmpty())
//rs.setTableName(null);
;
else
rs.setTableName(s);
break;
case UrlTag:
- if (nullValue)
+ if (nullValue || s == null || s.isEmpty())
rs.setUrl(null);
else
rs.setUrl(s);
@@ -1305,8 +1387,7 @@ private void setDataValue(char[] ch, int start, int len) throws SQLException {
tempStr = tempStr.concat(columnValue);
break;
case UpdTag:
- upd = new Object[2];
-
+ case UpdRowTag:
/**
* This has been added for handling of special characters. When special
* characters are encountered the characters function gets called for
@@ -1316,12 +1397,6 @@ private void setDataValue(char[] ch, int start, int len) throws SQLException {
**/
tempUpdate = tempUpdate.concat(new String(ch,start,len));
- upd[0] = Integer.valueOf(idx);
- upd[1] = tempUpdate;
- //updates.add(upd);
-
- lastval = (String)upd[1];
- //insertValue(ch, start, len);
break;
case InsTag:
@@ -1330,7 +1405,7 @@ private void setDataValue(char[] ch, int start, int len) throws SQLException {
private void insertValue(String s) throws SQLException {
- if (getNullValue()) {
+ if (s == null || getNullValue()) {
rs.updateNull(idx);
return;
}
diff --git a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlResolver.java b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlResolver.java
index a3fa6afc9a2b..856d7d734150 100644
--- a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlResolver.java
+++ b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003, 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
@@ -25,32 +25,40 @@
package com.sun.rowset.internal;
+import com.sun.rowset.JdbcRowSetResourceBundle;
import org.xml.sax.*;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
+import javax.sql.rowset.WebRowSet;
+import java.io.IOException;
+import java.io.StringReader;
+import java.text.MessageFormat;
+
/**
* An implementation of the EntityResolver interface, which
* reads and parses an XML formatted WebRowSet object.
* This is an implementation of org.xml.sax
- *
*/
public class XmlResolver implements EntityResolver {
+ //The standard WebRowSet XML Schema as defined in WebRowSet
+ public static final String STD_SCHEMA_ID = "http://xmlns.jcp.org/xml/ns//jdbc/webrowset.xsd";
- public InputSource resolveEntity(String publicId, String systemId) {
- String schemaName = systemId.substring(systemId.lastIndexOf('/'));
-
- if(systemId.startsWith("http://java.sun.com/xml/ns/jdbc")) {
- return new InputSource(this.getClass().getResourceAsStream(schemaName));
-
- } else {
- // use the default behaviour
- return null;
- }
-
-
+ @Override
+ public InputSource resolveEntity(String publicId, String systemId)
+ throws SAXException, IOException {
+ // accepts the standard schema without resolving it
+ // the schema is explicitly provided via the validation API
+ if (WebRowSet.SCHEMA_SYSTEM_ID.equals(systemId) ||
+ STD_SCHEMA_ID.equals(systemId)) {
+ return new InputSource(new StringReader(""));
+ }
- }
+ // reports error upon any external entity other than the standard schema
+ throw new SAXException(MessageFormat.format(
+ JdbcRowSetResourceBundle.getJdbcRowSetResourceBundle().handleGetObject(
+ "wrsxmlreader.entity").toString(), systemId));
+ }
}
diff --git a/test/jdk/javax/sql/test/rowset/webrowset/CommonWebRowSetTests.java b/test/jdk/javax/sql/test/rowset/webrowset/CommonWebRowSetTests.java
index 023adc8254b0..d8f1ea4ad584 100644
--- a/test/jdk/javax/sql/test/rowset/webrowset/CommonWebRowSetTests.java
+++ b/test/jdk/javax/sql/test/rowset/webrowset/CommonWebRowSetTests.java
@@ -22,29 +22,27 @@
*/
package test.rowset.webrowset;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileReader;
-import java.io.InputStreamReader;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.io.OutputStreamWriter;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import test.rowset.cachedrowset.CommonCachedRowSetTests;
+
+import javax.sql.rowset.WebRowSet;
+import java.io.*;
import java.math.BigDecimal;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
import java.util.Arrays;
-import javax.sql.rowset.WebRowSet;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
-import org.junit.jupiter.api.Disabled;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.MethodSource;
-
-import test.rowset.cachedrowset.CommonCachedRowSetTests;
public abstract class CommonWebRowSetTests extends CommonCachedRowSetTests {
@@ -61,7 +59,39 @@ public abstract class CommonWebRowSetTests extends CommonCachedRowSetTests {
= XMLFILEPATH + "INSERTED_COFFEE_ROWS.xml";
protected final String UPDATED_INSERTED_COFFEE_ROWS_XML
= XMLFILEPATH + "UPDATED_INSERTED_COFFEE_ROWS.xml";
+ private static final String ROWSET_VALIDATION_PROPERTY
+ = "jdk.sql.rowset.webrowsetValidation";
+
+ @FunctionalInterface
+ private interface ThrowingRunnable {
+ void run() throws Exception;
+ }
+ /**
+ * Runs the given action with the WebRowSet validation system property set
+ * to the requested value, then restores the previous value.
+ *
+ * @param value the temporary property value, or {@code null} to clear it
+ * @param action the action to run while the property is set
+ */
+ private static synchronized void withRowSetValidation(String value,
+ ThrowingRunnable action) throws Exception {
+ String previous = System.getProperty(ROWSET_VALIDATION_PROPERTY);
+ try {
+ if (value == null) {
+ System.clearProperty(ROWSET_VALIDATION_PROPERTY);
+ } else {
+ System.setProperty(ROWSET_VALIDATION_PROPERTY, value);
+ }
+ action.run();
+ } finally {
+ if (previous == null) {
+ System.clearProperty(ROWSET_VALIDATION_PROPERTY);
+ } else {
+ System.setProperty(ROWSET_VALIDATION_PROPERTY, previous);
+ }
+ }
+ }
/*
* Utility method to write a WebRowSet XML file via an OutputStream
@@ -101,7 +131,7 @@ protected WebRowSet readWebRowSetWithOInputStream(ByteArrayOutputStream baos) th
}
/*
- * Utility method to write a WebRowSet XML file via an Writer
+ * Utility method to write a WebRowSet XML file via a Writer
*/
protected ByteArrayOutputStream writeWebRowSetWithOutputStreamWithWriter(WebRowSet rs) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -111,7 +141,7 @@ protected ByteArrayOutputStream writeWebRowSetWithOutputStreamWithWriter(WebRowS
}
/*
- * Utility method to write a WebRowSet XML file via an Writer and populating
+ * Utility method to write a WebRowSet XML file via a Writer and populating
* the WebRowSet via a ResultSet
*/
protected ByteArrayOutputStream writeWebRowSetWithOutputStreamWithWriter(ResultSet rs) throws Exception {
@@ -418,4 +448,249 @@ public void WebRowSetTest0012(WebRowSet wrs1) throws Exception {
wrs1.close();
}
+ /**
+ * Verifies that an updated row written as XML can be read back with schema
+ * validation enabled.
+ */
+ @ParameterizedTest(autoCloseArguments = false)
+ @MethodSource("rowSetType")
+ public void testUpdatedRows(WebRowSet wrs) throws Exception {
+ try {
+ readCoffeeRows(wrs);
+ wrs.absolute(3);
+ wrs.updateInt(5, 21);
+ wrs.updateInt(6, 69);
+ wrs.updateRow();
+
+ ByteArrayOutputStream baos =
+ writeWebRowSetWithOutputStreamWithWriter(wrs);
+ withRowSetValidation(null, () -> {
+ // Clearing the property ensures validation is enabled;
+ // verifies the writer emits schema-compliant
+ // modifyRow/updateValue XML.
+ try (WebRowSet wrs1 =
+ readWebRowSetWithOInputStreamWithReader(baos)) {
+ assertEquals(COFFEES_ROWS, wrs1.size());
+ assertArrayEquals(COFFEES_PRIMARY_KEYS, getPrimaryKeys(wrs1));
+
+ assertTrue(findRowByPrimaryKey(wrs1, 3, 1));
+ assertTrue(wrs1.rowUpdated());
+ assertEquals(21, wrs1.getInt(5));
+ assertEquals(69, wrs1.getInt(6));
+ }
+ });
+ } finally {
+ wrs.close();
+ }
+ }
+
+ /**
+ * Verifies that a deleted row written as XML can be read back with schema
+ * validation enabled.
+ */
+ @ParameterizedTest(autoCloseArguments = false)
+ @MethodSource("rowSetType")
+ public void testDeletedRows(WebRowSet wrs)
+ throws Exception {
+ try {
+ readCoffeeRows(wrs);
+ assertTrue(deleteRowByPrimaryKey(wrs, 2, 1));
+
+ ByteArrayOutputStream baos =
+ writeWebRowSetWithOutputStreamWithWriter(wrs);
+ withRowSetValidation(null, () -> {
+ // Clearing the property ensures validation is enabled; parsing
+ // fails if the writer omits schema-required empty updateValue
+ // elements.
+ try (WebRowSet wrs1 =
+ readWebRowSetWithOInputStreamWithReader(baos)) {
+ Object[] expectedRows = {1, 3, 4, 5};
+ assertEquals(COFFEES_ROWS, wrs1.size());
+ assertArrayEquals(expectedRows, getPrimaryKeys(wrs1));
+
+ wrs1.setShowDeleted(true);
+ assertTrue(findRowByPrimaryKey(wrs1, 2, 1));
+ assertTrue(wrs1.rowDeleted());
+ assertArrayEquals(COFFEES_PRIMARY_KEYS, getPrimaryKeys(wrs1));
+ }
+ });
+ } finally {
+ wrs.close();
+ }
+ }
+
+ /**
+ * Verifies that null properties are written as schema-compliant empty
+ * elements and read back as null.
+ */
+ @ParameterizedTest(autoCloseArguments = false)
+ @MethodSource("rowSetType")
+ public void testNullProperties(WebRowSet wrs) throws Exception {
+ try {
+ readCoffeeRows(wrs);
+ assertNull(wrs.getCommand());
+ assertNull(wrs.getDataSourceName());
+ assertNull(wrs.getUrl());
+
+ ByteArrayOutputStream baos =
+ writeWebRowSetWithOutputStreamWithWriter(wrs);
+ withRowSetValidation(null, () -> {
+ // Reading with validation enabled fails if the writer emits the
+ // legacy invalid form for nullable properties.
+ try (WebRowSet wrs1 =
+ readWebRowSetWithOInputStreamWithReader(baos)) {
+ assertEquals(COFFEES_ROWS, wrs1.size());
+ assertArrayEquals(COFFEES_PRIMARY_KEYS, getPrimaryKeys(wrs1));
+ assertNull(wrs1.getCommand());
+ assertNull(wrs1.getDataSourceName());
+ assertNull(wrs1.getUrl());
+ }
+ });
+ } finally {
+ wrs.close();
+ }
+ }
+
+ /**
+ * Verifies that legacy XML is rejected since schema validation is enabled
+ * by default.
+ */
+ @ParameterizedTest(autoCloseArguments = false)
+ @MethodSource("rowSetType")
+ public void testLegacyXmlWithValidation(WebRowSet wrs)
+ throws Exception {
+ String legacyXml = getLegacyXml();
+ withRowSetValidation(null, () -> {
+ try {
+ assertThrows(SQLException.class,
+ () -> wrs.readXml(new StringReader(legacyXml)));
+ } finally {
+ wrs.close();
+ }
+ });
+ }
+
+ /**
+ * Verifies that legacy XML is accepted when schema validation is explicitly
+ * disabled.
+ */
+ @ParameterizedTest(autoCloseArguments = false)
+ @MethodSource("rowSetType")
+ public void testLegacyXmlWithoutValidation(WebRowSet wrs)
+ throws Exception {
+ String legacyXml = getLegacyXml();
+ withRowSetValidation("false", () -> {
+ try {
+ wrs.readXml(new StringReader(legacyXml));
+ assertEquals(COFFEES_ROWS, wrs.size());
+ assertArrayEquals(COFFEES_PRIMARY_KEYS, getPrimaryKeys(wrs));
+
+ wrs.beforeFirst();
+ boolean foundUpdatedRow = false;
+ while (wrs.next()) {
+ if (wrs.getInt(1) == 3) {
+ foundUpdatedRow = true;
+ assertTrue(wrs.rowUpdated());
+ assertEquals(21, wrs.getInt(5));
+ assertEquals(69, wrs.getInt(6));
+ }
+ }
+ assertTrue(foundUpdatedRow);
+ } finally {
+ wrs.close();
+ }
+ });
+ }
+
+ /**
+ * Verifies that legacy null elements are rejected since schema validation
+ * is enabled by default.
+ */
+ @ParameterizedTest(autoCloseArguments = false)
+ @MethodSource("rowSetType")
+ public void testLegacyNullXmlWithValidation(WebRowSet wrs)
+ throws Exception {
+ String legacyXml = getLegacyNullXml();
+ withRowSetValidation(null, () -> {
+ try {
+ // Legacy property values are not valid against the
+ // standard schema.
+ assertThrows(SQLException.class,
+ () -> wrs.readXml(new StringReader(legacyXml)));
+ } finally {
+ wrs.close();
+ }
+ });
+ }
+
+ /**
+ * Verifies that legacy null elements are accepted when schema validation is
+ * explicitly disabled.
+ */
+ @ParameterizedTest(autoCloseArguments = false)
+ @MethodSource("rowSetType")
+ public void testLegacyNullXmlWithoutValidation(WebRowSet wrs)
+ throws Exception {
+ String legacyXml = getLegacyNullXml();
+ withRowSetValidation("false", () -> {
+ try {
+ wrs.readXml(new StringReader(legacyXml));
+ assertEquals(COFFEES_ROWS, wrs.size());
+ assertArrayEquals(COFFEES_PRIMARY_KEYS, getPrimaryKeys(wrs));
+
+ // With validation disabled, preserve the old reader behavior
+ // for nullable properties and metadata represented by .
+ assertNull(wrs.getCommand());
+ assertNull(wrs.getDataSourceName());
+ assertNull(wrs.getUrl());
+
+ ResultSetMetaData md = wrs.getMetaData();
+ for (int i = 1; i <= md.getColumnCount(); i++) {
+ assertEquals("", md.getColumnLabel(i));
+ assertEquals("", md.getColumnTypeName(i));
+ }
+ } finally {
+ wrs.close();
+ }
+ });
+ }
+
+ /**
+ * Builds a legacy document from the schema-compliant UPDATED_COFFEE_ROWS_XML
+ * file. Older WebRowSet XML used currentRow/updateRow for modified rows,
+ * while the webrowset schema requires modifyRow/updateValue.
+ */
+ private String getLegacyXml() throws Exception {
+ return Files.readString(Path.of(UPDATED_COFFEE_ROWS_XML))
+ .replace("", "")
+ .replace("", "")
+ .replace("updateValue", "updateRow");
+ }
+
+ /**
+ * Builds a legacy document from the schema-compliant COFFEE_ROWS_XML file.
+ * Older WebRowSet XML used nested {@code } elements for nullable
+ * string properties and metadata, while the webrowset schema requires
+ * simple string content.
+ */
+ private String getLegacyNullXml() throws Exception {
+ return Files.readString(Path.of(COFFEE_ROWS_XML))
+ .replace("", "")
+ .replace("", "")
+ .replace("", "")
+ .replace("",
+ "")
+ .replace("",
+ "");
+ }
+
+ /**
+ * Reads COFFEE_ROWS_XML and populates the given WebRowSet.
+ */
+ private void readCoffeeRows(WebRowSet wrs) throws Exception {
+ try (FileInputStream fis = new FileInputStream(COFFEE_ROWS_XML)) {
+ wrs.readXml(fis);
+ }
+ }
+
}
diff --git a/test/jdk/javax/sql/xml/COFFEE_ROWS.xml b/test/jdk/javax/sql/xml/COFFEE_ROWS.xml
index e42eb4405b0a..7337206243e9 100644
--- a/test/jdk/javax/sql/xml/COFFEE_ROWS.xml
+++ b/test/jdk/javax/sql/xml/COFFEE_ROWS.xml
@@ -2,9 +2,9 @@
-
+ 1008
-
+ true10000
@@ -20,7 +20,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
ResultSet.TYPE_SCROLL_INSENSITIVEfalseCOFFEES
-
+ com.sun.rowset.providers.RIOptimisticProviderOracle Corporation
@@ -40,7 +40,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
falsefalse0
-
+ COF_NAME0
@@ -48,7 +48,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
4
-
+ 2
@@ -59,7 +59,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
falsefalse0
-
+ SUP_ID0
@@ -67,7 +67,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
12
-
+ 3
@@ -78,7 +78,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
falsefalse0
-
+ PRICE0
@@ -86,7 +86,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
4
-
+ 4
@@ -97,7 +97,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
falsefalse0
-
+ SALES10
@@ -105,7 +105,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
2
-
+ 5
@@ -116,7 +116,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
falsefalse0
-
+ TOTAL0
@@ -124,7 +124,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
4
-
+ 6
@@ -135,15 +135,15 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
falsefalse0
-
-
+
+ 004
-
+
diff --git a/test/jdk/javax/sql/xml/DELETED_COFFEE_ROWS.xml b/test/jdk/javax/sql/xml/DELETED_COFFEE_ROWS.xml
index 6fd38ff4e44e..c256461a8695 100644
--- a/test/jdk/javax/sql/xml/DELETED_COFFEE_ROWS.xml
+++ b/test/jdk/javax/sql/xml/DELETED_COFFEE_ROWS.xml
@@ -4,7 +4,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
SELECT * FROM COFFEES1008
-
+ true10000
@@ -40,7 +40,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
falsefalse0
-
+ COF_NAME0
@@ -48,7 +48,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
4
-
+ 2
@@ -59,7 +59,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
falsefalse0
-
+ SUP_ID0
@@ -67,7 +67,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
12
-
+ 3
@@ -78,7 +78,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
falsefalse0
-
+ PRICE0
@@ -86,7 +86,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
4
-
+ 4
@@ -97,7 +97,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
falsefalse0
-
+ SALES10
@@ -105,7 +105,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
2
-
+ 5
@@ -116,7 +116,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
falsefalse0
-
+ TOTAL0
@@ -124,7 +124,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
4
-
+ 6
@@ -135,15 +135,15 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
falsefalse0
-
-
+
+ 004
-
+
@@ -156,12 +156,12 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
0
- 2
- French_Roast
- 49
- 8.99
- 0
- 0
+ 2
+ French_Roast
+ 49
+ 8.99
+ 0
+ 03
@@ -172,12 +172,12 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
0
- 4
- Colombian_Decaf
- 101
- 8.99
- 0
- 0
+ 4
+ Colombian_Decaf
+ 101
+ 8.99
+ 0
+ 05
diff --git a/test/jdk/javax/sql/xml/INSERTED_COFFEE_ROWS.xml b/test/jdk/javax/sql/xml/INSERTED_COFFEE_ROWS.xml
index a700bab5c6c5..8b6a0112f3bc 100644
--- a/test/jdk/javax/sql/xml/INSERTED_COFFEE_ROWS.xml
+++ b/test/jdk/javax/sql/xml/INSERTED_COFFEE_ROWS.xml
@@ -2,9 +2,9 @@
-
+ 1008
-
+ true10000
@@ -20,7 +20,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
ResultSet.TYPE_SCROLL_INSENSITIVEfalseCOFFEES
-
+ com.sun.rowset.providers.RIOptimisticProviderOracle Corporation
@@ -40,7 +40,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
falsefalse0
-
+ COF_NAME0
@@ -48,7 +48,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
4
-
+ 2
@@ -59,7 +59,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
falsefalse0
-
+ SUP_ID0
@@ -67,7 +67,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
12
-
+ 3
@@ -78,7 +78,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
falsefalse0
-
+ PRICE0
@@ -86,7 +86,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
4
-
+ 4
@@ -97,7 +97,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
falsefalse0
-
+ SALES10
@@ -105,7 +105,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
2
-
+ 5
@@ -116,7 +116,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
falsefalse0
-
+ TOTAL0
@@ -124,7 +124,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
4
-
+ 6
@@ -135,15 +135,15 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
falsefalse0
-
-
+
+ 004
-
+
diff --git a/test/jdk/javax/sql/xml/MODFIED_DELETED_COFFEE_ROWS.xml b/test/jdk/javax/sql/xml/MODFIED_DELETED_COFFEE_ROWS.xml
index 2c71a5ffd3ad..d6256bd7cce0 100644
--- a/test/jdk/javax/sql/xml/MODFIED_DELETED_COFFEE_ROWS.xml
+++ b/test/jdk/javax/sql/xml/MODFIED_DELETED_COFFEE_ROWS.xml
@@ -2,9 +2,9 @@
-
+ 1008
-
+ true10000
@@ -20,7 +20,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
ResultSet.TYPE_SCROLL_INSENSITIVEfalseCOFFEES
-
+ com.sun.rowset.providers.RIOptimisticProviderOracle Corporation
@@ -40,7 +40,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
falsefalse0
-
+ COF_NAME0
@@ -48,7 +48,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
4
-
+ 2
@@ -59,7 +59,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
falsefalse0
-
+ SUP_ID0
@@ -67,7 +67,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
12
-
+ 3
@@ -78,7 +78,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
falsefalse0
-
+ PRICE0
@@ -86,7 +86,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
4
-
+ 4
@@ -97,7 +97,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
falsefalse0
-
+ SALES10
@@ -105,7 +105,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
2
-
+ 5
@@ -116,7 +116,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
falsefalse0
-
+ TOTAL0
@@ -124,7 +124,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
4
-
+ 6
@@ -135,15 +135,15 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
falsefalse0
-
-
+
+ 004
-
+
@@ -156,12 +156,12 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
0
- 2
- French_Roast
- 49
- 8.99
- 0
- 0
+ 2
+ French_Roast
+ 49
+ 8.99
+ 0
+ 03
@@ -172,12 +172,12 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
0
- 4
- Colombian_Decaf
- 101
- 8.99
- 0
- 0
+ 4
+ Colombian_Decaf
+ 101
+ 8.99
+ 0
+ 05
diff --git a/test/jdk/javax/sql/xml/UPDATED_COFFEE_ROWS.xml b/test/jdk/javax/sql/xml/UPDATED_COFFEE_ROWS.xml
index 449ba120b175..8d497e87d817 100644
--- a/test/jdk/javax/sql/xml/UPDATED_COFFEE_ROWS.xml
+++ b/test/jdk/javax/sql/xml/UPDATED_COFFEE_ROWS.xml
@@ -4,7 +4,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
SELECT * FROM COFFEES1008
-
+ true10000
@@ -40,7 +40,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
falsefalse0
-
+ COF_NAME0
@@ -48,7 +48,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
4
-
+ 2
@@ -59,7 +59,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
falsefalse0
-
+ SUP_ID0
@@ -67,7 +67,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
12
-
+ 3
@@ -78,7 +78,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
falsefalse0
-
+ PRICE0
@@ -86,7 +86,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
4
-
+ 4
@@ -97,7 +97,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
falsefalse0
-
+ SALES10
@@ -105,7 +105,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
2
-
+ 5
@@ -116,7 +116,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
falsefalse0
-
+ TOTAL0
@@ -124,7 +124,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
4
-
+ 6
@@ -135,15 +135,15 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
falsefalse0
-
-
+
+ 004
-
+
@@ -163,16 +163,20 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
00
-
+ 3
+ 3Espresso
+ Espresso150
+ 1509.99
+ 9.990
- 21
+ 210
- 69
-
+ 69
+
4Colombian_Decaf
diff --git a/test/jdk/javax/sql/xml/UPDATED_INSERTED_COFFEE_ROWS.xml b/test/jdk/javax/sql/xml/UPDATED_INSERTED_COFFEE_ROWS.xml
index 0cfc3c9fc732..9be10b56c6b9 100644
--- a/test/jdk/javax/sql/xml/UPDATED_INSERTED_COFFEE_ROWS.xml
+++ b/test/jdk/javax/sql/xml/UPDATED_INSERTED_COFFEE_ROWS.xml
@@ -2,9 +2,9 @@
-
+ 1008
-
+ true10000
@@ -20,7 +20,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
ResultSet.TYPE_SCROLL_INSENSITIVEfalseCOFFEES
-
+ com.sun.rowset.providers.RIOptimisticProviderOracle Corporation
@@ -40,7 +40,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
falsefalse0
-
+ COF_NAME0
@@ -48,7 +48,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
4
-
+ 2
@@ -59,7 +59,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
falsefalse0
-
+ SUP_ID0
@@ -67,7 +67,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
12
-
+ 3
@@ -78,7 +78,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
falsefalse0
-
+ PRICE0
@@ -86,7 +86,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
4
-
+ 4
@@ -97,7 +97,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
falsefalse0
-
+ SALES10
@@ -105,7 +105,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
2
-
+ 5
@@ -116,7 +116,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
falsefalse0
-
+ TOTAL0
@@ -124,7 +124,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
4
-
+ 6
@@ -135,15 +135,15 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
falsefalse0
-
-
+
+ 004
-
+
@@ -192,10 +192,10 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j
Mocha499.99
- 12.99
+ 12.992035
- 125
+ 125
From 3c9a28d5a035181779e90659d49561a27537eae4 Mon Sep 17 00:00:00 2001
From: Artur Barashev
Date: Fri, 29 May 2026 14:29:07 +0000
Subject: [PATCH 013/169] 8384021: Enhance Certificate Constraint Checking
Reviewed-by: jnibedita, ahgross, mullan, weijun, rhalade
---
.../sun/security/util/HostnameChecker.java | 20 ++-
.../sun/security/x509/IPAddressName.java | 147 ++++++++++++------
.../HostnameChecker/TestHostnameChecker.java | 8 +-
.../x509/IPAddressName/ConstrainsTest.java | 98 ++++++++----
4 files changed, 183 insertions(+), 90 deletions(-)
diff --git a/src/java.base/share/classes/sun/security/util/HostnameChecker.java b/src/java.base/share/classes/sun/security/util/HostnameChecker.java
index b5a6e48e5707..a9fafd43acf7 100644
--- a/src/java.base/share/classes/sun/security/util/HostnameChecker.java
+++ b/src/java.base/share/classes/sun/security/util/HostnameChecker.java
@@ -125,7 +125,7 @@ private static boolean isIpAddress(String name) {
/**
* Check if the certificate allows use of the given IP address.
- *
+ *
* From RFC2818:
* In some cases, the URI is specified as an IP address rather than a
* hostname. In this case, the iPAddress subjectAltName must be present
@@ -140,26 +140,30 @@ private static void matchIP(String expectedIP, X509Certificate cert)
}
for (List> next : subjAltNames) {
// For IP address, it needs to be exact match
- if (((Integer)next.get(0)).intValue() == ALTNAME_IP) {
+ if ((int) next.get(0) == ALTNAME_IP) {
String ipAddress = (String)next.get(1);
+
if (expectedIP.equalsIgnoreCase(ipAddress)) {
return;
} else {
- // compare InetAddress objects in order to ensure
- // equality between a long IPv6 address and its
- // abbreviated form.
+ // Compare InetAddress objects if literal check failed.
+ // InetAddress.getByName() normalizes IP address, thus
+ // the following is insured:
+ // - Equality between a long IPv6 address and its
+ // abbreviated form.
+ // - Equality between IPv4-Mapped IPv6 address and its
+ // regular IPv4 form.
try {
if (InetAddress.getByName(expectedIP).equals(
InetAddress.getByName(ipAddress))) {
return;
}
- } catch (UnknownHostException e) {}
+ } catch (UnknownHostException _) {}
}
}
}
throw new CertificateException("No subject alternative " +
- "names matching " + "IP address " +
- expectedIP + " found");
+ "names matching IP address " + expectedIP + " found");
}
/**
diff --git a/src/java.base/share/classes/sun/security/x509/IPAddressName.java b/src/java.base/share/classes/sun/security/x509/IPAddressName.java
index f372d349a573..7a26fe3c4a23 100644
--- a/src/java.base/share/classes/sun/security/x509/IPAddressName.java
+++ b/src/java.base/share/classes/sun/security/x509/IPAddressName.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
@@ -29,6 +29,7 @@
import java.lang.Integer;
import java.net.InetAddress;
import java.util.Arrays;
+import sun.net.util.IPAddressUtil;
import sun.security.util.HexDumpEncoder;
import sun.security.util.BitArray;
import sun.security.util.DerOutputStream;
@@ -69,11 +70,12 @@
*/
public class IPAddressName implements GeneralNameInterface {
private byte[] address;
- private final boolean isIPv4;
private String name;
/**
* Create the IPAddressName object from the passed encoded Der value.
+ *
+ * IPv4-Mapped IPv6 addresses/subnets normalized to IPv4.
*
* @param derValue the encoded DER IPAddressName.
* @exception IOException on error.
@@ -84,24 +86,44 @@ public IPAddressName(DerValue derValue) throws IOException {
/**
* Create the IPAddressName object with the specified octets.
+ *
+ * A valid address must consist of 4 bytes of address and
+ * optional 4 bytes of mask, or 16 bytes of address and
+ * optional 16 bytes of mask.
+ *
+ * IPv4-Mapped IPv6 addresses/subnets normalized to IPv4.
*
* @param address the IP address
* @throws IOException if address is not a valid IPv4 or IPv6 address
*/
public IPAddressName(byte[] address) throws IOException {
- /*
- * A valid address must consist of 4 bytes of address and
- * optional 4 bytes of 4 bytes of mask, or 16 bytes of address
- * and optional 16 bytes of mask.
- */
- if (address.length == 4 || address.length == 8) {
- isIPv4 = true;
- } else if (address.length == 16 || address.length == 32) {
- isIPv4 = false;
- } else {
- throw new IOException("Invalid IPAddressName");
- }
- this.address = address;
+ // Treat IPv4-Mapped IPv6 address as a regular IPv4.
+ this.address = switch (address.length) {
+ case 4, 8 -> address; // IPv4 address/subnet
+ case 16 -> {
+ // Single IPv6 address.
+ byte[] newAddr = IPAddressUtil.convertFromIPv4MappedAddress(
+ address);
+ yield newAddr != null ? newAddr : address;
+ }
+ case 32 -> {
+ // IPv6 subnet.
+ byte[] newAddr = IPAddressUtil.convertFromIPv4MappedAddress(
+ Arrays.copyOfRange(address, 0, 16));
+
+ if (newAddr == null) {
+ yield address;
+ }
+
+ byte[] ret = new byte[8];
+ System.arraycopy(newAddr, 0, ret, 0, 4);
+ System.arraycopy(ipv4MaskFromIpv6Mask(
+ Arrays.copyOfRange(address, 16, 32)),
+ 0, ret, 4, 4);
+ yield ret;
+ }
+ default -> throw new IOException("Invalid IPAddressName");
+ };
}
/**
@@ -118,6 +140,8 @@ public IPAddressName(byte[] address) throws IOException {
* of the address. If /n is used, n is a decimal number indicating how many
* of the leftmost contiguous bits of the address comprise the prefix for
* this subnet. Internally, a mask value is created using the prefix length.
+ *
+ * IPv4-Mapped IPv6 addresses/subnets normalized to IPv4.
*
* @param name String form of IPAddressName
* @throws IOException if name can not be converted to a valid IPv4 or IPv6
@@ -137,11 +161,9 @@ public IPAddressName(String name) throws IOException {
// Parse name into byte-value address components and optional
// prefix
parseIPv6(name);
- isIPv4 = false;
} else if (name.indexOf('.') >= 0) {
//name is IPv4: uses dots as value separators
parseIPv4(name);
- isIPv4 = true;
} else {
throw new IOException("Invalid IPAddress: " + name);
}
@@ -150,7 +172,7 @@ public IPAddressName(String name) throws IOException {
/**
* Parse an IPv4 address.
*
- * @param name IPv4 address with optional mask values
+ * @param name IPv4 address with an optional mask in dotted IP notation.
* @throws IOException on error
*/
private void parseIPv4(String name) throws IOException {
@@ -178,41 +200,76 @@ private void parseIPv4(String name) throws IOException {
/**
* Parse an IPv6 address.
*
- * @param name String IPv6 address with optional /
- * If / is present, address[] array will
- * be 32 bytes long, otherwise 16.
+ * @param name IPv6 address with an optional mask in CIDR notation.
* @throws IOException on error
*/
- private static final int MASKSIZE = 16;
private void parseIPv6(String name) throws IOException {
-
int slashNdx = name.indexOf('/');
+
if (slashNdx == -1) {
address = InetAddress.getByName(name).getAddress();
} else {
- address = new byte[32];
byte[] base = InetAddress.getByName
(name.substring(0, slashNdx)).getAddress();
- System.arraycopy(base, 0, address, 0, 16);
-
- // append a mask corresponding to the num of prefix bits specified
- int prefixLen = Integer.parseInt(name.substring(slashNdx+1));
- if (prefixLen < 0 || prefixLen > 128) {
- throw new IOException("IPv6Address prefix length (" +
- prefixLen + ") in out of valid range [0,128]");
+ byte[] mask = ipv6MaskFromString(name.substring(slashNdx + 1));
+
+ if (base.length == 16) {
+ // IPv6 address.
+ address = new byte[32];
+ System.arraycopy(base, 0, address, 0, 16);
+ System.arraycopy(mask, 0, address, 16, 16);
+ } else if (base.length == 4) {
+ // IPv4-Mapped IPv6 address.
+ address = new byte[8];
+ System.arraycopy(base, 0, address, 0, 4);
+ System.arraycopy(ipv4MaskFromIpv6Mask(mask), 0, address, 4, 4);
+ } else {
+ throw new IOException("Invalid IPAddress subnet: " + name);
}
+ }
+ }
+
+ private static byte[] ipv6MaskFromString(String ipv6Mask)
+ throws IOException {
+ int prefixLen;
+
+ try {
+ prefixLen = Integer.parseInt(ipv6Mask);
+ } catch (NumberFormatException e) {
+ throw new IOException(e);
+ }
+
+ if (prefixLen < 0 || prefixLen > 128) {
+ throw new IOException("IPv6Address prefix length ("
+ + prefixLen + ") is out of valid range [0,128]");
+ }
- // create new bit array initialized to zeros
- BitArray bitArray = new BitArray(MASKSIZE * 8);
+ // create new bit array initialized to zeros
+ BitArray bitArray = new BitArray(128);
- // set all most significant bits up to prefix length
- for (int i = 0; i < prefixLen; i++)
- bitArray.set(i, true);
- byte[] maskArray = bitArray.toByteArray();
+ // set all most significant bits up to prefix length
+ for (int i = 0; i < prefixLen; i++) {
+ bitArray.set(i, true);
+ }
+
+ return bitArray.toByteArray();
+ }
+
+ private static byte[] ipv4MaskFromIpv6Mask(byte[] ipv6Mask)
+ throws IOException {
+ if (ipv6Mask == null || ipv6Mask.length != 16) {
+ throw new IOException("IPv6 mask must be 16 bytes");
+ }
- // copy mask bytes into mask portion of address
- System.arraycopy(maskArray, 0, address, MASKSIZE, MASKSIZE);
+ // For a mapped IPv4 subnet, the first 96 bits must be set.
+ for (int i = 0; i < 12; i++) {
+ if ((ipv6Mask[i] & 0xFF) != 0xFF) {
+ throw new IOException("Not an IPv4-mapped IPv6 mask: first 96"
+ + " bits are not set");
+ }
}
+
+ return Arrays.copyOfRange(ipv6Mask, 12, 16);
}
/**
@@ -256,8 +313,8 @@ public String getName() throws IOException {
if (name != null)
return name;
- if (isIPv4) {
- //IPv4 address or subdomain
+ if (address.length == 4 || address.length == 8) {
+ //IPv4 address or subnet
byte[] host = new byte[4];
System.arraycopy(address, 0, host, 0, 4);
name = InetAddress.getByAddress(host).getHostAddress();
@@ -268,14 +325,14 @@ public String getName() throws IOException {
InetAddress.getByAddress(mask).getHostAddress();
}
} else {
- //IPv6 address or subdomain
+ //IPv6 address or subnet
byte[] host = new byte[16];
System.arraycopy(address, 0, host, 0, 16);
name = InetAddress.getByAddress(host).getHostAddress();
if (address.length == 32) {
- // IPv6 subdomain: display prefix length
+ // IPv6 subnet: display prefix length
- // copy subdomain into new array and convert to BitArray
+ // copy subnet into new array and convert to BitArray
byte[] maskBytes = new byte[16];
System.arraycopy(address, 16, maskBytes, 0, 16);
BitArray ba = new BitArray(16*8, maskBytes);
@@ -289,7 +346,7 @@ public String getName() throws IOException {
// Verify remaining bits 0
for (; i < 16*8; i++) {
if (ba.get(i)) {
- throw new IOException("Invalid IPv6 subdomain - set " +
+ throw new IOException("Invalid IPv6 subnet - set " +
"bit " + i + " not contiguous");
}
}
diff --git a/test/jdk/sun/security/util/HostnameChecker/TestHostnameChecker.java b/test/jdk/sun/security/util/HostnameChecker/TestHostnameChecker.java
index 63eb4057f8e6..64f7c5113f67 100644
--- a/test/jdk/sun/security/util/HostnameChecker/TestHostnameChecker.java
+++ b/test/jdk/sun/security/util/HostnameChecker/TestHostnameChecker.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2002, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2002, 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
@@ -196,6 +196,7 @@ public static void main(String[] args) throws Exception {
check(checker, "altfoo1.com", cert3, true);
check(checker, "altfoo2.com", cert3, true);
check(checker, "5.6.7.8", cert3, true);
+ check(checker, "::ffff:5.6.7.8", cert3, true);
check(checker, "foo.bar.com", cert4, true);
check(checker, "altfoo.bar.com", cert4, false);
check(checker, "2001:db8:3c4d:15::1a2f:1a2b", cert5, true);
@@ -221,6 +222,7 @@ public static void main(String[] args) throws Exception {
check(checker, "altfoo1.com", cert3, true);
check(checker, "altfoo2.com", cert3, true);
check(checker, "5.6.7.8", cert3, true);
+ check(checker, "::ffff:5.6.7.8", cert3, true);
check(checker, "foo.bar.com", cert4, true);
check(checker, "altfoo.bar.com", cert4, false);
}
@@ -239,11 +241,11 @@ private static void check(HostnameChecker checker, String name,
throws Exception {
try {
checker.match(name, cert);
- if (expectedResult == false) {
+ if (!expectedResult) {
throw new Exception("Passed invalid test: " + name);
}
} catch (CertificateException e) {
- if (expectedResult == true) {
+ if (expectedResult) {
throw new Exception("Failed valid test: " + name, e);
}
}
diff --git a/test/jdk/sun/security/x509/IPAddressName/ConstrainsTest.java b/test/jdk/sun/security/x509/IPAddressName/ConstrainsTest.java
index 63772eef3351..6ca664fb1e6b 100644
--- a/test/jdk/sun/security/x509/IPAddressName/ConstrainsTest.java
+++ b/test/jdk/sun/security/x509/IPAddressName/ConstrainsTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022, 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
@@ -21,58 +21,88 @@
* questions.
*/
-import org.testng.annotations.DataProvider;
-import org.testng.annotations.Test;
-import sun.security.x509.GeneralNameInterface;
-import sun.security.x509.IPAddressName;
+import static jdk.test.lib.Asserts.assertEquals;
+import static sun.security.x509.GeneralNameInterface.NAME_MATCH;
+import static sun.security.x509.GeneralNameInterface.NAME_NARROWS;
+import static sun.security.x509.GeneralNameInterface.NAME_SAME_TYPE;
+import static sun.security.x509.GeneralNameInterface.NAME_WIDENS;
import java.io.IOException;
-
-import static org.testng.Assert.assertEquals;
+import java.util.List;
+import sun.security.x509.IPAddressName;
/*
* @test
* @summary Verify IPAddressName.constrains
* @bug 8267617
+ * @library /test/lib
* @modules java.base/sun.security.x509
- * @run testng ConstrainsTest
*/
+
public class ConstrainsTest {
IPAddressName ipv4Addr = new IPAddressName("127.0.0.1");
IPAddressName ipv4Mask = new IPAddressName("127.0.0.0/255.0.0.0");
- IPAddressName ipv6Addr = new IPAddressName("::1");
- IPAddressName ipv6Mask = new IPAddressName("::/0");
+ IPAddressName ipv6Addr = new IPAddressName("2001:db8::1");
+ IPAddressName ipv6Mask = new IPAddressName("2001:db8::/124");
+
+ IPAddressName ipv4Addr2 = new IPAddressName("128.0.0.1");
+ IPAddressName ipv4NarrowMask = new IPAddressName("127.0.0.0/255.255.0.0");
+ IPAddressName ipv4DisjointMask = new IPAddressName("128.0.0.0/255.0.0.0");
+
+ IPAddressName ipv6Addr2 = new IPAddressName("2001:db8:1::1");
+ IPAddressName ipv6NarrowMask = new IPAddressName("2001:db8::/125");
+ IPAddressName ipv6DisjointMask = new IPAddressName("2001:db8:1::/124");
public ConstrainsTest() throws IOException {
}
- @DataProvider(name = "names")
- public Object[][] names() {
- Object[][] data = {
- {ipv4Addr, ipv4Addr, GeneralNameInterface.NAME_MATCH},
- {ipv4Addr, ipv4Mask, GeneralNameInterface.NAME_WIDENS},
- {ipv4Addr, ipv6Addr, GeneralNameInterface.NAME_SAME_TYPE},
- {ipv4Addr, ipv6Mask, GeneralNameInterface.NAME_SAME_TYPE},
- {ipv4Mask, ipv4Addr, GeneralNameInterface.NAME_NARROWS},
- {ipv4Mask, ipv4Mask, GeneralNameInterface.NAME_MATCH},
- {ipv4Mask, ipv6Addr, GeneralNameInterface.NAME_SAME_TYPE},
- {ipv4Mask, ipv6Mask, GeneralNameInterface.NAME_SAME_TYPE},
- {ipv6Addr, ipv4Addr, GeneralNameInterface.NAME_SAME_TYPE},
- {ipv6Addr, ipv4Mask, GeneralNameInterface.NAME_SAME_TYPE},
- {ipv6Addr, ipv6Addr, GeneralNameInterface.NAME_MATCH},
- {ipv6Addr, ipv6Mask, GeneralNameInterface.NAME_WIDENS},
- {ipv6Mask, ipv4Addr, GeneralNameInterface.NAME_SAME_TYPE},
- {ipv6Mask, ipv4Mask, GeneralNameInterface.NAME_SAME_TYPE},
- {ipv6Mask, ipv6Addr, GeneralNameInterface.NAME_NARROWS},
- {ipv6Mask, ipv6Mask, GeneralNameInterface.NAME_MATCH},
- };
- return data;
+ private Object[][] names() {
+ return new Object[][]{
+ // Basic matching
+ {ipv4Addr, ipv4Addr, NAME_MATCH},
+ {ipv4Addr, ipv4Mask, NAME_WIDENS},
+ {ipv4Addr, ipv6Addr, NAME_SAME_TYPE},
+ {ipv4Addr, ipv6Mask, NAME_SAME_TYPE},
+ {ipv4Mask, ipv4Addr, NAME_NARROWS},
+ {ipv4Mask, ipv4Mask, NAME_MATCH},
+ {ipv4Mask, ipv6Addr, NAME_SAME_TYPE},
+ {ipv4Mask, ipv6Mask, NAME_SAME_TYPE},
+ {ipv6Addr, ipv4Addr, NAME_SAME_TYPE},
+ {ipv6Addr, ipv4Mask, NAME_SAME_TYPE},
+ {ipv6Addr, ipv6Addr, NAME_MATCH},
+ {ipv6Addr, ipv6Mask, NAME_WIDENS},
+ {ipv6Mask, ipv4Addr, NAME_SAME_TYPE},
+ {ipv6Mask, ipv4Mask, NAME_SAME_TYPE},
+ {ipv6Mask, ipv6Addr, NAME_NARROWS},
+ {ipv6Mask, ipv6Mask, NAME_MATCH},
+
+ // Same-family host mismatches
+ {ipv4Addr, ipv4Addr2, NAME_SAME_TYPE},
+ {ipv6Addr, ipv6Addr2, NAME_SAME_TYPE},
+
+ // Host to subnet match, nothing matches
+ {ipv4Addr2, ipv4Mask, NAME_SAME_TYPE},
+ {ipv6Addr2, ipv6Mask, NAME_SAME_TYPE},
+
+ // Subnet to subnet match: narrow/widen/disjoint
+ {ipv4Mask, ipv4NarrowMask, NAME_NARROWS},
+ {ipv4NarrowMask, ipv4Mask, NAME_WIDENS},
+ {ipv4Mask, ipv4DisjointMask, NAME_SAME_TYPE},
+
+ {ipv6Mask, ipv6NarrowMask, NAME_NARROWS},
+ {ipv6NarrowMask, ipv6Mask, NAME_WIDENS},
+ {ipv6Mask, ipv6DisjointMask, NAME_SAME_TYPE},
+ };
}
- @Test(dataProvider = "names")
- public void testNameContains(IPAddressName addr1, IPAddressName addr2, int result) throws IOException {
- assertEquals(addr1.constrains(addr2), result);
+ public static void main(String[] args) throws Exception {
+ List.of(new ConstrainsTest().names()).forEach(v -> testNameContains(
+ (IPAddressName) v[0], (IPAddressName) v[1], (int) v[2]));
}
+ private static void testNameContains(IPAddressName addr1,
+ IPAddressName addr2, int result) {
+ assertEquals(addr1.constrains(addr2), result);
+ }
}
From 4820d3f389d1563673c777fa581db60990446c61 Mon Sep 17 00:00:00 2001
From: Emanuel Peter
Date: Mon, 1 Jun 2026 13:17:15 +0000
Subject: [PATCH 014/169] 8383689: Enhance Vector Processing
Reviewed-by: mschoene, vlivanov, rhalade, kvn, qamai
---
src/hotspot/cpu/aarch64/matcher_aarch64.hpp | 5 -
src/hotspot/cpu/arm/matcher_arm.hpp | 5 -
src/hotspot/cpu/ppc/matcher_ppc.hpp | 5 -
src/hotspot/cpu/riscv/matcher_riscv.hpp | 5 -
src/hotspot/cpu/s390/matcher_s390.hpp | 5 -
src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp | 106 --------------
src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp | 9 --
src/hotspot/cpu/x86/matcher_x86.hpp | 13 --
src/hotspot/cpu/x86/x86.ad | 129 +-----------------
src/hotspot/share/opto/vectorIntrinsics.cpp | 68 +++++----
src/hotspot/share/opto/vectornode.hpp | 35 +++--
11 files changed, 73 insertions(+), 312 deletions(-)
diff --git a/src/hotspot/cpu/aarch64/matcher_aarch64.hpp b/src/hotspot/cpu/aarch64/matcher_aarch64.hpp
index 42bb61f6889a..ede5a4814950 100644
--- a/src/hotspot/cpu/aarch64/matcher_aarch64.hpp
+++ b/src/hotspot/cpu/aarch64/matcher_aarch64.hpp
@@ -130,11 +130,6 @@
return true;
}
- // Does target support predicated operation emulation.
- static bool supports_vector_predicate_op_emulation(int vopc, int vlen, BasicType bt) {
- return false;
- }
-
// Does the CPU supports vector variable rotate instructions?
static constexpr bool supports_vector_variable_rotates(void) {
return false;
diff --git a/src/hotspot/cpu/arm/matcher_arm.hpp b/src/hotspot/cpu/arm/matcher_arm.hpp
index a3139d91f302..7980838c5cca 100644
--- a/src/hotspot/cpu/arm/matcher_arm.hpp
+++ b/src/hotspot/cpu/arm/matcher_arm.hpp
@@ -122,11 +122,6 @@
return VM_Version::has_simd();
}
- // Does target support predicated operation emulation.
- static bool supports_vector_predicate_op_emulation(int vopc, int vlen, BasicType bt) {
- return false;
- }
-
// Does the CPU supports vector variable rotate instructions?
static constexpr bool supports_vector_variable_rotates(void) {
return false; // not supported
diff --git a/src/hotspot/cpu/ppc/matcher_ppc.hpp b/src/hotspot/cpu/ppc/matcher_ppc.hpp
index 209d317a0335..356682bb760b 100644
--- a/src/hotspot/cpu/ppc/matcher_ppc.hpp
+++ b/src/hotspot/cpu/ppc/matcher_ppc.hpp
@@ -130,11 +130,6 @@
return false;
}
- // Does target support predicated operation emulation.
- static bool supports_vector_predicate_op_emulation(int vopc, int vlen, BasicType bt) {
- return false;
- }
-
// Does the CPU supports vector variable rotate instructions?
static constexpr bool supports_vector_variable_rotates(void) {
return false;
diff --git a/src/hotspot/cpu/riscv/matcher_riscv.hpp b/src/hotspot/cpu/riscv/matcher_riscv.hpp
index bb3d2d015f2d..570bfbf711ad 100644
--- a/src/hotspot/cpu/riscv/matcher_riscv.hpp
+++ b/src/hotspot/cpu/riscv/matcher_riscv.hpp
@@ -129,11 +129,6 @@
return UseRVV;
}
- // Does target support predicated operation emulation.
- static bool supports_vector_predicate_op_emulation(int vopc, int vlen, BasicType bt) {
- return false;
- }
-
// Does the CPU supports vector variable rotate instructions?
static bool supports_vector_variable_rotates(void) {
return UseZvbb;
diff --git a/src/hotspot/cpu/s390/matcher_s390.hpp b/src/hotspot/cpu/s390/matcher_s390.hpp
index 188d46438aca..7e006bdc6c7b 100644
--- a/src/hotspot/cpu/s390/matcher_s390.hpp
+++ b/src/hotspot/cpu/s390/matcher_s390.hpp
@@ -125,11 +125,6 @@
return false;
}
- // Does target support predicated operation emulation.
- static bool supports_vector_predicate_op_emulation(int vopc, int vlen, BasicType bt) {
- return false;
- }
-
// Does the CPU supports vector variable rotate instructions?
static constexpr bool supports_vector_variable_rotates(void) {
return false;
diff --git a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp
index dcce0aa9c00b..b4ca8648b326 100644
--- a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp
+++ b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp
@@ -1430,112 +1430,6 @@ void C2_MacroAssembler::vinsert(BasicType typ, XMMRegister dst, XMMRegister src,
}
}
-void C2_MacroAssembler::vgather8b_masked(BasicType elem_bt, XMMRegister dst,
- Register base, Register idx_base,
- Register mask, Register mask_idx,
- Register rtmp, int vlen_enc) {
- vpxor(dst, dst, dst, vlen_enc);
- if (elem_bt == T_SHORT) {
- for (int i = 0; i < 4; i++) {
- // dst[i] = mask[i] ? src[idx_base[i]] : 0
- Label skip_load;
- btq(mask, mask_idx);
- jccb(Assembler::carryClear, skip_load);
- movl(rtmp, Address(idx_base, i * 4));
- pinsrw(dst, Address(base, rtmp, Address::times_2), i);
- bind(skip_load);
- incq(mask_idx);
- }
- } else {
- assert(elem_bt == T_BYTE, "only handles short and byte");
- for (int i = 0; i < 8; i++) {
- // dst[i] = mask[i] ? src[idx_base[i]] : 0
- Label skip_load;
- btq(mask, mask_idx);
- jccb(Assembler::carryClear, skip_load);
- movl(rtmp, Address(idx_base, i * 4));
- pinsrb(dst, Address(base, rtmp), i);
- bind(skip_load);
- incq(mask_idx);
- }
- }
-}
-
-void C2_MacroAssembler::vgather8b(BasicType elem_bt, XMMRegister dst,
- Register base, Register idx_base,
- Register rtmp, int vlen_enc) {
- vpxor(dst, dst, dst, vlen_enc);
- if (elem_bt == T_SHORT) {
- for (int i = 0; i < 4; i++) {
- // dst[i] = src[idx_base[i]]
- movl(rtmp, Address(idx_base, i * 4));
- pinsrw(dst, Address(base, rtmp, Address::times_2), i);
- }
- } else {
- assert(elem_bt == T_BYTE, "only handles short and byte");
- for (int i = 0; i < 8; i++) {
- // dst[i] = src[idx_base[i]]
- movl(rtmp, Address(idx_base, i * 4));
- pinsrb(dst, Address(base, rtmp), i);
- }
- }
-}
-
-/*
- * Gather using hybrid algorithm, first partially unroll scalar loop
- * to accumulate values from gather indices into a quad-word(64bit) slice.
- * A slice may hold 8 bytes or 4 short values. This is followed by a vector
- * permutation to place the slice into appropriate vector lane
- * locations in destination vector. Following pseudo code describes the
- * algorithm in detail:
- *
- * DST_VEC = ZERO_VEC
- * PERM_INDEX = {0, 1, 2, 3, 4, 5, 6, 7, 8..}
- * TWO_VEC = {2, 2, 2, 2, 2, 2, 2, 2, 2..}
- * FOREACH_ITER:
- * TMP_VEC_64 = PICK_SUB_WORDS_FROM_GATHER_INDICES
- * TEMP_PERM_VEC = PERMUTE TMP_VEC_64 PERM_INDEX
- * DST_VEC = DST_VEC OR TEMP_PERM_VEC
- * PERM_INDEX = PERM_INDEX - TWO_VEC
- *
- * With each iteration, doubleword permute indices (0,1) corresponding
- * to gathered quadword gets right shifted by two lane positions.
- *
- */
-void C2_MacroAssembler::vgather_subword(BasicType elem_ty, XMMRegister dst,
- Register base, Register idx_base,
- Register mask, XMMRegister xtmp1,
- XMMRegister xtmp2, XMMRegister temp_dst,
- Register rtmp, Register mask_idx,
- Register length, int vector_len, int vlen_enc) {
- Label GATHER8_LOOP;
- assert(is_signed_subword_type(elem_ty), "only handles short and byte");
- movl(length, vector_len);
- vpxor(xtmp1, xtmp1, xtmp1, vlen_enc); // xtmp1 = {0, ...}
- vpxor(dst, dst, dst, vlen_enc); // dst = {0, ...}
- vallones(xtmp2, vlen_enc);
- vpsubd(xtmp2, xtmp1, xtmp2, vlen_enc);
- vpslld(xtmp2, xtmp2, 1, vlen_enc); // xtmp2 = {2, 2, ...}
- load_iota_indices(xtmp1, vector_len * type2aelembytes(elem_ty), T_INT); // xtmp1 = {0, 1, 2, ...}
-
- bind(GATHER8_LOOP);
- // TMP_VEC_64(temp_dst) = PICK_SUB_WORDS_FROM_GATHER_INDICES
- if (mask == noreg) {
- vgather8b(elem_ty, temp_dst, base, idx_base, rtmp, vlen_enc);
- } else {
- vgather8b_masked(elem_ty, temp_dst, base, idx_base, mask, mask_idx, rtmp, vlen_enc);
- }
- // TEMP_PERM_VEC(temp_dst) = PERMUTE TMP_VEC_64(temp_dst) PERM_INDEX(xtmp1)
- vpermd(temp_dst, xtmp1, temp_dst, vlen_enc == Assembler::AVX_512bit ? vlen_enc : Assembler::AVX_256bit);
- // PERM_INDEX(xtmp1) = PERM_INDEX(xtmp1) - TWO_VEC(xtmp2)
- vpsubd(xtmp1, xtmp1, xtmp2, vlen_enc);
- // DST_VEC = DST_VEC OR TEMP_PERM_VEC
- vpor(dst, dst, temp_dst, vlen_enc);
- addptr(idx_base, 32 >> (type2aelembytes(elem_ty) - 1));
- subl(length, 8 >> (type2aelembytes(elem_ty) - 1));
- jcc(Assembler::notEqual, GATHER8_LOOP);
-}
-
void C2_MacroAssembler::vgather(BasicType typ, XMMRegister dst, Register base, XMMRegister idx, XMMRegister mask, int vector_len) {
switch(typ) {
case T_INT:
diff --git a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp
index a6471cbd3fdb..761879cbc9ca 100644
--- a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp
+++ b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp
@@ -509,15 +509,6 @@
void efp16sh(int opcode, XMMRegister dst, XMMRegister src1, XMMRegister src2);
- void vgather_subword(BasicType elem_ty, XMMRegister dst, Register base, Register idx_base, Register mask,
- XMMRegister xtmp1, XMMRegister xtmp2, XMMRegister xtmp3, Register rtmp,
- Register midx, Register length, int vector_len, int vlen_enc);
-
- void vgather8b_masked(BasicType elem_bt, XMMRegister dst, Register base, Register idx_base,
- Register mask, Register midx, Register rtmp, int vlen_enc);
- void vgather8b(BasicType elem_bt, XMMRegister dst, Register base, Register idx_base,
- Register rtmp, int vlen_enc);
-
void vector_saturating_op(int opc, BasicType elem_bt, XMMRegister dst, XMMRegister src1, XMMRegister src2, bool is_unsigned, int vlen_enc);
void vector_saturating_op(int opc, BasicType elem_bt, XMMRegister dst, XMMRegister src1, Address src2, bool is_unsigned, int vlen_enc);
diff --git a/src/hotspot/cpu/x86/matcher_x86.hpp b/src/hotspot/cpu/x86/matcher_x86.hpp
index eda8a05d974a..5b012768cb9d 100644
--- a/src/hotspot/cpu/x86/matcher_x86.hpp
+++ b/src/hotspot/cpu/x86/matcher_x86.hpp
@@ -115,16 +115,6 @@
return (UseAVX >= 2);
}
- // Does target support predicated operation emulation.
- static bool supports_vector_predicate_op_emulation(int vopc, int vlen, BasicType bt) {
- switch(vopc) {
- case Op_LoadVectorGatherMasked:
- return is_subword_type(bt) && VM_Version::supports_avx2();
- default:
- return false;
- }
- }
-
// Does the CPU supports vector variable rotate instructions?
static constexpr bool supports_vector_variable_rotates(void) {
return true;
@@ -185,9 +175,6 @@
return 7;
case Op_MulVL:
return VM_Version::supports_avx512vldq() ? 0 : 6;
- case Op_LoadVectorGather:
- case Op_LoadVectorGatherMasked:
- return is_subword_type(ety) ? 50 : 0;
case Op_VectorCastF2X: // fall through
case Op_VectorCastD2X:
return is_floating_point_type(ety) ? 0 : (is_subword_type(ety) ? 35 : 30);
diff --git a/src/hotspot/cpu/x86/x86.ad b/src/hotspot/cpu/x86/x86.ad
index 62c30a668d7f..fbd01ef59814 100644
--- a/src/hotspot/cpu/x86/x86.ad
+++ b/src/hotspot/cpu/x86/x86.ad
@@ -3507,16 +3507,6 @@ bool Matcher::match_rule_supported_vector(int opcode, int vlen, BasicType bt) {
}
break;
case Op_LoadVectorGatherMasked:
- if (!is_subword_type(bt) && size_in_bits < 512 && !VM_Version::supports_avx512vl()) {
- return false;
- }
- if (is_subword_type(bt) &&
- ((size_in_bits > 256 && !VM_Version::supports_avx512bw()) ||
- (size_in_bits < 64) ||
- (bt == T_SHORT && !VM_Version::supports_bmi2()))) {
- return false;
- }
- break;
case Op_StoreVectorScatterMasked:
case Op_StoreVectorScatter:
if (is_subword_type(bt)) {
@@ -3526,10 +3516,9 @@ bool Matcher::match_rule_supported_vector(int opcode, int vlen, BasicType bt) {
}
// fallthrough
case Op_LoadVectorGather:
- if (!is_subword_type(bt) && size_in_bits == 64) {
+ if (is_subword_type(bt)) {
return false;
- }
- if (is_subword_type(bt) && size_in_bits < 64) {
+ } else if (size_in_bits == 64) {
return false;
}
break;
@@ -18379,7 +18368,7 @@ instruct storeV(memory mem, vec src) %{
// ---------------------------------------- Gather ------------------------------------
-// Gather BYTE, SHORT, INT, LONG, FLOAT, DOUBLE
+// Gather INT, LONG, FLOAT, DOUBLE
instruct gather(legVec dst, memory mem, legVec idx, rRegP tmp, legVec mask) %{
predicate(!VM_Version::supports_avx512vl() && !is_subword_type(Matcher::vector_element_basic_type(n)) &&
@@ -18436,118 +18425,6 @@ instruct evgather_masked(vec dst, memory mem, vec idx, kReg mask, kReg ktmp, rRe
ins_pipe( pipe_slow );
%}
-instruct vgather_subwordLE8B(vec dst, memory mem, rRegP idx_base, rRegP tmp, rRegI rtmp) %{
- predicate(is_subword_type(Matcher::vector_element_basic_type(n)) && Matcher::vector_length_in_bytes(n) <= 8);
- match(Set dst (LoadVectorGather mem idx_base));
- effect(TEMP tmp, TEMP rtmp);
- format %{ "vector_gatherLE8 $dst, $mem, $idx_base\t! using $tmp and $rtmp as TEMP" %}
- ins_encode %{
- int vlen_enc = vector_length_encoding(this);
- BasicType elem_bt = Matcher::vector_element_basic_type(this);
- __ lea($tmp$$Register, $mem$$Address);
- __ vgather8b(elem_bt, $dst$$XMMRegister, $tmp$$Register, $idx_base$$Register, $rtmp$$Register, vlen_enc);
- %}
- ins_pipe( pipe_slow );
-%}
-
-instruct vgather_subwordGT8B(vec dst, memory mem, rRegP idx_base, rRegP tmp, rRegP idx_base_temp,
- vec xtmp1, vec xtmp2, vec xtmp3, rRegI rtmp, rRegI length, rFlagsReg cr) %{
- predicate(is_subword_type(Matcher::vector_element_basic_type(n)) && Matcher::vector_length_in_bytes(n) > 8);
- match(Set dst (LoadVectorGather mem idx_base));
- effect(TEMP_DEF dst, TEMP tmp, TEMP idx_base_temp, TEMP xtmp1, TEMP xtmp2, TEMP xtmp3, TEMP rtmp, TEMP length, KILL cr);
- format %{ "vector_gatherGT8 $dst, $mem, $idx_base\t! using $tmp, $idx_base_temp, $xtmp1, $xtmp2, $xtmp3, $rtmp and $length as TEMP" %}
- ins_encode %{
- int vlen_enc = vector_length_encoding(this);
- int vector_len = Matcher::vector_length(this);
- BasicType elem_bt = Matcher::vector_element_basic_type(this);
- __ lea($tmp$$Register, $mem$$Address);
- __ movptr($idx_base_temp$$Register, $idx_base$$Register);
- __ vgather_subword(elem_bt, $dst$$XMMRegister, $tmp$$Register, $idx_base_temp$$Register, noreg, $xtmp1$$XMMRegister,
- $xtmp2$$XMMRegister, $xtmp3$$XMMRegister, $rtmp$$Register, noreg, $length$$Register, vector_len, vlen_enc);
- %}
- ins_pipe( pipe_slow );
-%}
-
-instruct vgather_masked_subwordLE8B_avx3(vec dst, memory mem, rRegP idx_base, kReg mask, rRegL mask_idx, rRegP tmp, rRegI rtmp, rRegL rtmp2, rFlagsReg cr) %{
- predicate(VM_Version::supports_avx512bw() && is_subword_type(Matcher::vector_element_basic_type(n)) && Matcher::vector_length_in_bytes(n) <= 8);
- match(Set dst (LoadVectorGatherMasked mem (Binary idx_base mask)));
- effect(TEMP mask_idx, TEMP tmp, TEMP rtmp, TEMP rtmp2, KILL cr);
- format %{ "vector_masked_gatherLE8 $dst, $mem, $idx_base, $mask\t! using $mask_idx, $tmp, $rtmp and $rtmp2 as TEMP" %}
- ins_encode %{
- int vlen_enc = vector_length_encoding(this);
- BasicType elem_bt = Matcher::vector_element_basic_type(this);
- __ xorq($mask_idx$$Register, $mask_idx$$Register);
- __ lea($tmp$$Register, $mem$$Address);
- __ kmovql($rtmp2$$Register, $mask$$KRegister);
- __ vgather8b_masked(elem_bt, $dst$$XMMRegister, $tmp$$Register, $idx_base$$Register, $rtmp2$$Register, $mask_idx$$Register, $rtmp$$Register, vlen_enc);
- %}
- ins_pipe( pipe_slow );
-%}
-
-instruct vgather_masked_subwordGT8B_avx3(vec dst, memory mem, rRegP idx_base, kReg mask, rRegP tmp, rRegP idx_base_temp,
- vec xtmp1, vec xtmp2, vec xtmp3, rRegI rtmp, rRegL rtmp2, rRegL mask_idx, rRegI length, rFlagsReg cr) %{
- predicate(VM_Version::supports_avx512bw() && is_subword_type(Matcher::vector_element_basic_type(n)) && Matcher::vector_length_in_bytes(n) > 8);
- match(Set dst (LoadVectorGatherMasked mem (Binary idx_base mask)));
- effect(TEMP_DEF dst, TEMP tmp, TEMP idx_base_temp, TEMP xtmp1, TEMP xtmp2, TEMP xtmp3, TEMP rtmp, TEMP rtmp2, TEMP mask_idx, TEMP length, KILL cr);
- format %{ "vector_gatherGT8_masked $dst, $mem, $idx_base, $mask\t! using $tmp, $idx_base_temp, $xtmp1, $xtmp2, $xtmp3, $rtmp, $rtmp2, $mask_idx and $length as TEMP" %}
- ins_encode %{
- int vlen_enc = vector_length_encoding(this);
- int vector_len = Matcher::vector_length(this);
- BasicType elem_bt = Matcher::vector_element_basic_type(this);
- __ xorq($mask_idx$$Register, $mask_idx$$Register);
- __ lea($tmp$$Register, $mem$$Address);
- __ movptr($idx_base_temp$$Register, $idx_base$$Register);
- __ kmovql($rtmp2$$Register, $mask$$KRegister);
- __ vgather_subword(elem_bt, $dst$$XMMRegister, $tmp$$Register, $idx_base_temp$$Register, $rtmp2$$Register, $xtmp1$$XMMRegister,
- $xtmp2$$XMMRegister, $xtmp3$$XMMRegister, $rtmp$$Register, $mask_idx$$Register, $length$$Register, vector_len, vlen_enc);
- %}
- ins_pipe( pipe_slow );
-%}
-
-instruct vgather_masked_subwordLE8B_avx2(vec dst, memory mem, rRegP idx_base, vec mask, rRegI mask_idx, rRegP tmp, rRegI rtmp, rRegI rtmp2, rFlagsReg cr) %{
- predicate(!VM_Version::supports_avx512vlbw() && is_subword_type(Matcher::vector_element_basic_type(n)) && Matcher::vector_length_in_bytes(n) <= 8);
- match(Set dst (LoadVectorGatherMasked mem (Binary idx_base mask)));
- effect(TEMP mask_idx, TEMP tmp, TEMP rtmp, TEMP rtmp2, KILL cr);
- format %{ "vector_masked_gatherLE8 $dst, $mem, $idx_base, $mask\t! using $mask_idx, $tmp, $rtmp and $rtmp2 as TEMP" %}
- ins_encode %{
- int vlen_enc = vector_length_encoding(this);
- BasicType elem_bt = Matcher::vector_element_basic_type(this);
- __ lea($tmp$$Register, $mem$$Address);
- __ vpmovmskb($rtmp2$$Register, $mask$$XMMRegister, vlen_enc);
- if (elem_bt == T_SHORT) {
- __ movl($mask_idx$$Register, 0x55555555);
- __ pextl($rtmp2$$Register, $rtmp2$$Register, $mask_idx$$Register);
- }
- __ xorl($mask_idx$$Register, $mask_idx$$Register);
- __ vgather8b_masked(elem_bt, $dst$$XMMRegister, $tmp$$Register, $idx_base$$Register, $rtmp2$$Register, $mask_idx$$Register, $rtmp$$Register, vlen_enc);
- %}
- ins_pipe( pipe_slow );
-%}
-
-instruct vgather_masked_subwordGT8B_avx2(vec dst, memory mem, rRegP idx_base, vec mask, rRegP tmp, rRegP idx_base_temp,
- vec xtmp1, vec xtmp2, vec xtmp3, rRegI rtmp, rRegI rtmp2, rRegI mask_idx, rRegI length, rFlagsReg cr) %{
- predicate(!VM_Version::supports_avx512vlbw() && is_subword_type(Matcher::vector_element_basic_type(n)) && Matcher::vector_length_in_bytes(n) > 8);
- match(Set dst (LoadVectorGatherMasked mem (Binary idx_base mask)));
- effect(TEMP_DEF dst, TEMP tmp, TEMP idx_base_temp, TEMP xtmp1, TEMP xtmp2, TEMP xtmp3, TEMP rtmp, TEMP rtmp2, TEMP mask_idx, TEMP length, KILL cr);
- format %{ "vector_gatherGT8_masked $dst, $mem, $idx_base, $mask\t! using $tmp, $idx_base_temp, $xtmp1, $xtmp2, $xtmp3, $rtmp, $rtmp2, $mask_idx and $length as TEMP" %}
- ins_encode %{
- int vlen_enc = vector_length_encoding(this);
- int vector_len = Matcher::vector_length(this);
- BasicType elem_bt = Matcher::vector_element_basic_type(this);
- __ lea($tmp$$Register, $mem$$Address);
- __ movptr($idx_base_temp$$Register, $idx_base$$Register);
- __ vpmovmskb($rtmp2$$Register, $mask$$XMMRegister, vlen_enc);
- if (elem_bt == T_SHORT) {
- __ movl($mask_idx$$Register, 0x55555555);
- __ pextl($rtmp2$$Register, $rtmp2$$Register, $mask_idx$$Register);
- }
- __ xorl($mask_idx$$Register, $mask_idx$$Register);
- __ vgather_subword(elem_bt, $dst$$XMMRegister, $tmp$$Register, $idx_base_temp$$Register, $rtmp2$$Register, $xtmp1$$XMMRegister,
- $xtmp2$$XMMRegister, $xtmp3$$XMMRegister, $rtmp$$Register, $mask_idx$$Register, $length$$Register, vector_len, vlen_enc);
- %}
- ins_pipe( pipe_slow );
-%}
-
// ====================Scatter=======================================
// Scatter INT, LONG, FLOAT, DOUBLE
diff --git a/src/hotspot/share/opto/vectorIntrinsics.cpp b/src/hotspot/share/opto/vectorIntrinsics.cpp
index 0b5bfd5e8d2e..3427769f05cb 100644
--- a/src/hotspot/share/opto/vectorIntrinsics.cpp
+++ b/src/hotspot/share/opto/vectorIntrinsics.cpp
@@ -279,7 +279,6 @@ bool LibraryCallKit::arch_supports_vector(int sopc, int num_elem, BasicType type
is_supported = Matcher::match_rule_supported_vector_masked(sopc, num_elem, type);
}
}
- is_supported |= Matcher::supports_vector_predicate_op_emulation(sopc, num_elem, type);
if (!is_supported) {
non_product_log_if_needed("Rejected vector mask predicate using (%s,%s,%d) because architecture does not support it",
@@ -1366,31 +1365,55 @@ bool LibraryCallKit::inline_vector_gather_scatter(bool is_scatter) {
}
// Check that the vector holding indices is supported by architecture
- // For sub-word gathers expander receive index array.
- if (!is_subword_type(elem_bt) && !arch_supports_vector(Op_LoadVector, idx_num_elem, T_INT, VecMaskNotUsed)) {
+ if (!arch_supports_vector(Op_LoadVector, idx_num_elem, T_INT, VecMaskNotUsed)) {
log_if_needed(" ** not supported: arity=%d op=%s/loadindex vlen=%d etype=int is_masked_op=%d",
is_scatter, is_scatter ? "scatter" : "gather",
idx_num_elem, is_masked_op ? 1 : 0);
return false; // not supported
}
+ // Currently, subword is not supported.
+ // Note: we used to have an x64 subword gather implementation before, but it was buggy.
+ // JDK-8318650 had introduced the subword gather implementation:
+ // - We loaded from indexMap once to perform checkIndex
+ // - And then we loaded again from indexMap in the LoadVectorGather(Masked)
+ // backend implementation. But if a second thread mutated the indexMap,
+ // we would not have checked the index again, and possibly performed out
+ // of bounds accesses.
+ // - The reason for using indexMap directly was that subword gather requires
+ // an int-index per subword element, and so the indices vector would have
+ // to be larger than the output vector, or we would need multiple index
+ // vectors. Just loading from indexMap seemed like an elegant solution,
+ // but unfortunetely, it leads to TOCTOU bugs.
+ //
+ // JDK-8383689 removed subword gather again.
+ //
+ // In the future, we could consider re-implementing subword gather. We would
+ // have to ensure to only load the indices once, and perform checkIndex and
+ // indexing from that same index vector. We may also want to change the API,
+ // so that one can provide an index vector, rather than a indexMap array.
+ // Since vectors are immutable, the indexCheck and indexing would trivially
+ // be consuming the same indices. And the API user could compute arbitrary
+ // indices, and would not have to round-trip them via an indexMap array.
+ // However: so far, no hardware we are aware of can actually perform
+ // vectorized subword gather/scatter. So the implementation would actually
+ // need to be a scalar emulation. The JDK-8318650 performed scalar loads
+ // from the indexMap. A re-implementation would instead have to extract
+ // elements from the index vector. The performance impact has not yet been
+ // investigated.
+ if (is_subword_type(elem_bt)) {
+ log_if_needed(" ** not supported: subword type etype=%s",
+ type2name(elem_bt));
+ return false; // not supported
+ }
+
Node* base = argument(6);
Node* offset = ConvL2X(argument(7));
// Save state and restore on bailout
SavedState old_state(this);
- Node* addr = nullptr;
- if (!is_subword_type(elem_bt)) {
- addr = make_unsafe_address(base, offset, elem_bt, true);
- } else {
- assert(!is_scatter, "Only supports gather operation for subword types now");
- uint header = arrayOopDesc::base_offset_in_bytes(elem_bt);
- assert(offset->is_Con() && offset->bottom_type()->is_long()->get_con() == header,
- "offset must be the array base offset");
- Node* index = argument(15);
- addr = array_element_address(base, index, elem_bt);
- }
+ Node* addr = make_unsafe_address(base, offset, elem_bt, true);
const TypeAryPtr* arr_type = gvn().type(addr)->isa_aryptr();
@@ -1417,18 +1440,13 @@ bool LibraryCallKit::inline_vector_gather_scatter(bool is_scatter) {
}
// Get the indexes for gather/scatter.
- Node* indexes = nullptr;
+ // Note: we have already loaded the indices from indexMap into a vector,
+ // and performed checkIndex on it. We must now use that same vector,
+ // and not reload from the indexMap again.
const TypeInstPtr* vbox_idx_type = TypeInstPtr::make_exact(TypePtr::NotNull, vbox_idx_klass);
- if (is_subword_type(elem_bt)) {
- Node* indexMap = argument(16);
- Node* indexM = argument(17);
- indexes = array_element_address(indexMap, indexM, T_INT);
- } else {
- // Get the first index vector.
- indexes = unbox_vector(argument(9), vbox_idx_type, T_INT, idx_num_elem);
- if (indexes == nullptr) {
- return false;
- }
+ Node* indexes = unbox_vector(argument(9), vbox_idx_type, T_INT, idx_num_elem);
+ if (indexes == nullptr) {
+ return false;
}
// Get the vector mask value.
diff --git a/src/hotspot/share/opto/vectornode.hpp b/src/hotspot/share/opto/vectornode.hpp
index 4657050af6ef..6ed37b70798b 100644
--- a/src/hotspot/share/opto/vectornode.hpp
+++ b/src/hotspot/share/opto/vectornode.hpp
@@ -1214,15 +1214,19 @@ class LoadVectorNode : public LoadNode {
#endif
};
-// Load Vector from memory via index map
+// Gather:
+// indices: int-vector of indices
+// adr: memory address (e.g. array base plus offset)
+// output[i] = adr[indices[i]]
+//
+// Note: assumes that indices are all in-bounds, performs no index checking.
class LoadVectorGatherNode : public LoadVectorNode {
public:
LoadVectorGatherNode(Node* c, Node* mem, Node* adr, const TypePtr* at, const TypeVect* vt, Node* indices)
: LoadVectorNode(c, mem, adr, at, vt) {
init_class_id(Class_LoadVectorGather);
add_req(indices);
- DEBUG_ONLY(bool is_subword = is_subword_type(vt->element_basic_type()));
- assert(is_subword || indices->bottom_type()->is_vect(), "indices must be in vector");
+ assert(indices->bottom_type()->is_vect(), "indices must be in vector");
assert(req() == MemNode::ValueIn + 1, "match_edge expects that index input is in MemNode::ValueIn");
}
@@ -1277,8 +1281,13 @@ class StoreVectorNode : public StoreNode {
#endif
};
-// Store Vector into memory via index map
- class StoreVectorScatterNode : public StoreVectorNode {
+// Scatter:
+// indices: int-vector of indices
+// adr: memory address (e.g. array base plus offset)
+// adr[indices[i]] = val[i]
+//
+// Note: assumes that indices are all in-bounds, performs no index checking.
+class StoreVectorScatterNode : public StoreVectorNode {
public:
enum { Indices = 4 };
StoreVectorScatterNode(Node* c, Node* mem, Node* adr, const TypePtr* at, Node* val, Node* indices)
@@ -1338,7 +1347,12 @@ class LoadVectorMaskedNode : public LoadVectorNode {
}
};
-// Load Vector from memory via index map under the influence of a predicate register(mask).
+// Masked Gather:
+// indices: int-vector of indices
+// adr: memory address (e.g. array base plus offset)
+// output[i] = mask[i] ? adr[indices[i]] : 0
+//
+// Note: assumes that indices are all in-bounds, performs no index checking.
class LoadVectorGatherMaskedNode : public LoadVectorNode {
public:
LoadVectorGatherMaskedNode(Node* c, Node* mem, Node* adr, const TypePtr* at, const TypeVect* vt, Node* indices, Node* mask)
@@ -1347,7 +1361,7 @@ class LoadVectorGatherMaskedNode : public LoadVectorNode {
add_req(indices);
add_req(mask);
assert(req() == MemNode::ValueIn + 2, "match_edge expects that last input is in MemNode::ValueIn+1");
- assert(is_subword_type(vt->element_basic_type()) || indices->bottom_type()->is_vect(), "indices must be in vector");
+ assert(indices->bottom_type()->is_vect(), "indices must be in vector");
}
virtual int Opcode() const;
@@ -1360,7 +1374,12 @@ class LoadVectorGatherMaskedNode : public LoadVectorNode {
}
};
-// Store Vector into memory via index map under the influence of a predicate register(mask).
+// Masked Scatter:
+// indices: int-vector of indices
+// adr: memory address (e.g. array base plus offset)
+// if (mask[i]) { adr[indices[i]] = val[i] }
+//
+// Note: assumes that indices are all in-bounds, performs no index checking.
class StoreVectorScatterMaskedNode : public StoreVectorNode {
public:
enum { Indices = 4,
From db055baa2ac01615b0df5f2fe93c44eccfdbc6a4 Mon Sep 17 00:00:00 2001
From: Volkan Yazici
Date: Tue, 2 Jun 2026 09:58:51 +0000
Subject: [PATCH 015/169] 8385260: Enhance SOCKS5 support
Reviewed-by: aefimov, rhalade, djelinski, alanb, dfuchs
---
.../classes/java/net/SocksSocketImpl.java | 41 +++++++++++++++----
1 file changed, 32 insertions(+), 9 deletions(-)
diff --git a/src/java.base/share/classes/java/net/SocksSocketImpl.java b/src/java.base/share/classes/java/net/SocksSocketImpl.java
index 71794d7a0a66..5f8b974e12b6 100644
--- a/src/java.base/share/classes/java/net/SocksSocketImpl.java
+++ b/src/java.base/share/classes/java/net/SocksSocketImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2000, 2025, 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
@@ -143,13 +143,27 @@ private boolean authenticate(byte method, InputStream in,
if (userName == null)
return false;
out.write(1);
- out.write(userName.length());
- out.write(userName.getBytes(StandardCharsets.ISO_8859_1));
- if (password != null) {
- out.write(password.length());
- out.write(password.getBytes(StandardCharsets.ISO_8859_1));
- } else
+
+ var userNameBytes = userName.getBytes(StandardCharsets.ISO_8859_1);
+ // RFC 1929 allows usernames of at most 255 bytes
+ if (userNameBytes.length > 0xFF) {
+ throw new SocketException("SOCKS: username is too long");
+ }
+ out.write(userNameBytes.length);
+ out.write(userNameBytes);
+
+ if (password == null) {
out.write(0);
+ } else {
+ var passwordBytes = password.getBytes(StandardCharsets.ISO_8859_1);
+ // RFC 1929 allows password of at most 255 bytes
+ if (passwordBytes.length > 0xFF) {
+ throw new SocketException("SOCKS: password is too long");
+ }
+ out.write(passwordBytes.length);
+ out.write(passwordBytes);
+ }
+
out.flush();
byte[] data = new byte[2];
int i = readSocksReply(in, data, deadlineMillis);
@@ -241,6 +255,15 @@ protected void connect(SocketAddress endpoint, int timeout) throws IOException {
if (!(endpoint instanceof InetSocketAddress epoint))
throw new IllegalArgumentException("Unsupported address type");
+ byte[] domainNameBytes = null;
+ if (epoint.isUnresolved()) {
+ domainNameBytes = epoint.getHostName().getBytes(StandardCharsets.ISO_8859_1);
+ // RFC 1928 allows FQDNs of at most 255 bytes
+ if (domainNameBytes.length > 0xFF) {
+ throw new SocketException("SOCKS: host name is too long");
+ }
+ }
+
if (server == null) {
// This is the general case
// server is not null only when the socket was created with a
@@ -366,8 +389,8 @@ protected void connect(SocketAddress endpoint, int timeout) throws IOException {
/* Test for IPV4/IPV6/Unresolved */
if (epoint.isUnresolved()) {
out.write(DOMAIN_NAME);
- out.write(epoint.getHostName().length());
- out.write(epoint.getHostName().getBytes(StandardCharsets.ISO_8859_1));
+ out.write(domainNameBytes.length);
+ out.write(domainNameBytes);
out.write((epoint.getPort() >> 8) & 0xff);
out.write((epoint.getPort() >> 0) & 0xff);
} else if (epoint.getAddress() instanceof Inet6Address) {
From c251c01efd05fdfffe1e107b122d5f18129c5efb Mon Sep 17 00:00:00 2001
From: Alexander Zuev
Date: Tue, 2 Jun 2026 21:37:31 +0000
Subject: [PATCH 016/169] 8384455: Enhance DataFlavor
Reviewed-by: rhalade, azvegint, skoivu, prr
---
.../java/awt/datatransfer/DataFlavor.java | 17 ++---------------
1 file changed, 2 insertions(+), 15 deletions(-)
diff --git a/src/java.datatransfer/share/classes/java/awt/datatransfer/DataFlavor.java b/src/java.datatransfer/share/classes/java/awt/datatransfer/DataFlavor.java
index cfb89118ff71..5eb12e4b8731 100644
--- a/src/java.datatransfer/share/classes/java/awt/datatransfer/DataFlavor.java
+++ b/src/java.datatransfer/share/classes/java/awt/datatransfer/DataFlavor.java
@@ -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
@@ -33,7 +33,6 @@
import java.io.InputStreamReader;
import java.io.ObjectInput;
import java.io.ObjectOutput;
-import java.io.OptionalDataException;
import java.io.Reader;
import java.io.Serial;
import java.io.StringReader;
@@ -1320,19 +1319,7 @@ public synchronized void readExternal(ObjectInput is) throws IOException , Class
}
}
- try {
- representationClass = (Class)is.readObject();
- } catch (OptionalDataException ode) {
- if (!ode.eof || ode.length != 0) {
- throw ode;
- }
- // Ensure backward compatibility.
- // Old versions didn't write the representation class to the stream.
- if (rcn != null) {
- representationClass =
- DataFlavor.tryToLoadClass(rcn, getClass().getClassLoader());
- }
- }
+ representationClass = (Class)is.readObject();
}
/**
From 64d86d84aa5e23b4981fa0a667a6cd2bc83cfc45 Mon Sep 17 00:00:00 2001
From: Artur Barashev
Date: Wed, 3 Jun 2026 18:27:31 +0000
Subject: [PATCH 017/169] 8383679: Enhance Certificate Name Checks
Reviewed-by: ahgross, jnibedita, mullan, weijun, rhalade
---
.../sun/security/x509/NameConstraintsExtension.java | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/src/java.base/share/classes/sun/security/x509/NameConstraintsExtension.java b/src/java.base/share/classes/sun/security/x509/NameConstraintsExtension.java
index 5b34a7267967..07989fea25b5 100644
--- a/src/java.base/share/classes/sun/security/x509/NameConstraintsExtension.java
+++ b/src/java.base/share/classes/sun/security/x509/NameConstraintsExtension.java
@@ -449,7 +449,14 @@ public boolean verify(X509Certificate cert) throws IOException {
}
} else {
if (!hasNameType(altNames, GeneralNameInterface.NAME_DNS)) {
- altNames.add(new GeneralName(new DNSName(cn)));
+ // Replacing wildcard character '*' with 'z' to check
+ // the domain name template validity.
+ new DNSName(cn.replace('*', 'z'));
+ // If the above line doesn't throw, use Der constructor
+ // which performs no format checks: we want to accept
+ // DNS name with any wildcard here.
+ altNames.add(new GeneralName(new DNSName(new DerValue(
+ DerValue.tag_IA5String, cn))));
}
}
} catch (IOException ioe) {
From e7322d8822d31072e290a6915ac7be0d2b945481 Mon Sep 17 00:00:00 2001
From: Joe Wang
Date: Thu, 4 Jun 2026 00:52:02 +0000
Subject: [PATCH 018/169] 8383699: Enhance XPath Expressions
Reviewed-by: aefimov, rhalade, lancea, naoto, jlu
---
.../apache/xpath/internal/XPathContext.java | 32 +++++++++++++++++--
.../functions/FuncSystemProperty.java | 27 ++++++++++++++--
.../xpath/internal/jaxp/XPathFactoryImpl.java | 6 ++--
.../xpath/internal/jaxp/XPathImplUtil.java | 5 +--
.../internal/res/XPATHErrorResources.java | 9 ++++--
.../jdk/xml/internal/JdkConstants.java | 7 ++++
.../jdk/xml/internal/JdkXmlFeatures.java | 15 +++++++--
src/java.xml/share/classes/module-info.java | 32 ++++++++++++++++---
8 files changed, 115 insertions(+), 18 deletions(-)
diff --git a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/XPathContext.java b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/XPathContext.java
index 1ab774f69f63..27669d89567b 100644
--- a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/XPathContext.java
+++ b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/XPathContext.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2026, Oracle and/or its affiliates. All rights reserved.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
@@ -49,6 +49,8 @@
import javax.xml.transform.ErrorListener;
import javax.xml.transform.SourceLocator;
import javax.xml.transform.URIResolver;
+import jdk.xml.internal.JdkXmlConfig;
+import jdk.xml.internal.JdkXmlFeatures;
import org.xml.sax.XMLReader;
/**
@@ -56,7 +58,7 @@
*
*
This class extends DTMManager but does not directly implement it.
* @xsl.usage advanced
- * @LastModified: Jan 2019
+ * @LastModified: June 2026
*/
public class XPathContext extends DTMManager // implements ExpressionContext
{
@@ -94,6 +96,11 @@ public class XPathContext extends DTMManager // implements ExpressionContext
private boolean m_overrideDefaultParser;
+ /**
+ * JDK XML feature settings
+ */
+ private JdkXmlFeatures m_jdkXmlFeatures;
+
/**
* Though XPathContext context extends
* the DTMManager, it really is a proxy for this object, which
@@ -128,6 +135,27 @@ public boolean isSecureProcessing()
return m_isSecureProcessing;
}
+ /**
+ * Sets the JDK XML feature settings for this XPath evaluation.
+ */
+ public void setJdkXmlFeatures(JdkXmlFeatures features)
+ {
+ m_jdkXmlFeatures = features;
+ }
+
+ /**
+ * {@return the JDK XML feature settings for this XPath evaluation}
+ *
+ * The settings are initialized from the global configuration if needed.
+ */
+ public JdkXmlFeatures getJdkXmlFeatures()
+ {
+ if (m_jdkXmlFeatures == null) {
+ m_jdkXmlFeatures = JdkXmlConfig.getInstance(false).getXMLFeatures(true);
+ }
+ return m_jdkXmlFeatures;
+ }
+
/**
* Get an instance of a DTM, loaded with the content from the
* specified source. If the unique flag is true, a new instance will
diff --git a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/functions/FuncSystemProperty.java b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/functions/FuncSystemProperty.java
index 0beccb5132bf..b6f8c87d61e2 100644
--- a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/functions/FuncSystemProperty.java
+++ b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/functions/FuncSystemProperty.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 2026, Oracle and/or its affiliates. All rights reserved.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
@@ -20,19 +20,22 @@
package com.sun.org.apache.xpath.internal.functions;
+import com.sun.org.apache.xalan.internal.res.XSLMessages;
import com.sun.org.apache.xpath.internal.XPathContext;
import com.sun.org.apache.xpath.internal.objects.XObject;
import com.sun.org.apache.xpath.internal.objects.XString;
import com.sun.org.apache.xpath.internal.res.XPATHErrorResources;
-import java.io.BufferedInputStream;
import java.io.InputStream;
import java.util.Properties;
+import javax.xml.transform.TransformerException;
+import jdk.xml.internal.JdkConstants;
+import jdk.xml.internal.JdkXmlFeatures;
import jdk.xml.internal.SecuritySupport;
/**
* Execute the SystemProperty() function.
* @xsl.usage advanced
- * @LastModified: Nov 2024
+ * @LastModified: June 2026
*/
public class FuncSystemProperty extends FunctionOneArg
{
@@ -96,6 +99,7 @@ public XObject execute(XPathContext xctxt) throws javax.xml.transform.Transforme
new Object[]{ namespace,
fullName }); //"Don't currently do anything with namespace "+namespace+" in property: "+fullName);
+ checkSysPropAccess(xctxt, fullName);
result = System.getProperty(propName);
if (null == result)
{
@@ -106,6 +110,7 @@ public XObject execute(XPathContext xctxt) throws javax.xml.transform.Transforme
}
else
{
+ checkSysPropAccess(xctxt, fullName);
result = System.getProperty(fullName);
if (null == result)
@@ -131,6 +136,22 @@ public XObject execute(XPathContext xctxt) throws javax.xml.transform.Transforme
return new XString(result);
}
+ /**
+ * Checks whether XPath may access non-XSLT-defined system properties.
+ */
+ private void checkSysPropAccess(XPathContext xctxt, String name)
+ throws TransformerException
+ {
+ JdkXmlFeatures features = xctxt.getJdkXmlFeatures();
+
+ if (!features.getFeature(JdkXmlFeatures.XmlFeature.JDK_XPATH_SYSTEM_PROPERTY)) {
+ String fmsg = XSLMessages.createXPATHMessage(
+ XPATHErrorResources.ER_NON_XSLT_SYSTEM_PROPERTY,
+ new Object[] {name, JdkConstants.XPATH_ALLOW_NON_XSLT_SYS_PROPS});
+ throw new TransformerException(fmsg);
+ }
+ }
+
/**
* Retrieve a property bundle from XSLT_PROPERTIES
*
diff --git a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/XPathFactoryImpl.java b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/XPathFactoryImpl.java
index 2f4d2ade5453..56a09b645e4f 100644
--- a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/XPathFactoryImpl.java
+++ b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/XPathFactoryImpl.java
@@ -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.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
@@ -35,7 +35,7 @@
*
* @author Ramesh Mandava
*
- * @LastModified: June 2025
+ * @LastModified: June 2026
*/
public class XPathFactoryImpl extends XPathFactory {
@@ -185,6 +185,8 @@ public void setFeature(String name, boolean value)
if (value && _featureManager != null) {
_featureManager.setFeature(JdkXmlFeatures.XmlFeature.ENABLE_EXTENSION_FUNCTION,
JdkProperty.State.FSP, false);
+ _featureManager.setFeature(JdkXmlFeatures.XmlFeature.JDK_XPATH_SYSTEM_PROPERTY,
+ JdkProperty.State.FSP, false);
_xmlSecMgr.setSecureProcessing(value);
}
diff --git a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/XPathImplUtil.java b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/XPathImplUtil.java
index 3de72f3f68bb..585822dcbd81 100644
--- a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/XPathImplUtil.java
+++ b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/XPathImplUtil.java
@@ -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
@@ -56,7 +56,7 @@
* This class contains several utility methods used by XPathImpl and
* XPathExpressionImpl
*
- * @LastModified: June 2025
+ * @LastModified: June 2026
*/
class XPathImplUtil {
XPathFunctionResolver functionResolver;
@@ -94,6 +94,7 @@ XObject eval(Object contextItem, com.sun.org.apache.xpath.internal.XPath xpath)
xpathSupport = new com.sun.org.apache.xpath.internal.XPathContext();
}
+ xpathSupport.setJdkXmlFeatures(featureManager);
xpathSupport.setVarStack(new JAXPVariableStack(variableResolver));
XObject xobj;
diff --git a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/res/XPATHErrorResources.java b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/res/XPATHErrorResources.java
index 716a4bcf63bd..55a106aa67ed 100644
--- a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/res/XPATHErrorResources.java
+++ b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/res/XPATHErrorResources.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
@@ -31,7 +31,7 @@
* Also you need to update the count of messages(MAX_CODE)or
* the count of warnings(MAX_WARNING) [ Information purpose only]
* @xsl.usage advanced
- * @LastModified: Apr 2025
+ * @LastModified: June 2026
*/
public class XPATHErrorResources extends ListResourceBundle
{
@@ -327,6 +327,7 @@ public class XPATHErrorResources extends ListResourceBundle
public static final String ER_GETTING_UNKNOWN_PROPERTY = "ER_GETTING_UNKNOWN_PROPERTY";
public static final String ER_XPATH_GROUP_LIMIT = "ER_XPATH_GROUP_LIMIT";
public static final String ER_XPATH_OPERATOR_LIMIT = "ER_XPATH_OPERATOR_LIMIT";
+ public static final String ER_NON_XSLT_SYSTEM_PROPERTY = "ER_NON_XSLT_SYSTEM_PROPERTY";
//END: Keys needed for exception messages of JAXP 1.3 XPath API implementation
@@ -862,6 +863,10 @@ public class XPATHErrorResources extends ListResourceBundle
"JAXP0801002: the compiler encountered an XPath expression containing "
+ "''{0}'' operators that exceeds the ''{1}'' limit set by ''{2}''."},
+ { ER_NON_XSLT_SYSTEM_PROPERTY,
+ "Access to non-XSLT system property ''{0}'' is not allowed. Set "
+ + "''{1}'' to ''true'' to allow it."},
+
//END: Definitions of error keys used in exception messages of JAXP 1.3 XPath API implementation
// Warnings...
diff --git a/src/java.xml/share/classes/jdk/xml/internal/JdkConstants.java b/src/java.xml/share/classes/jdk/xml/internal/JdkConstants.java
index 927d19bc3b35..6ef32fb6d282 100644
--- a/src/java.xml/share/classes/jdk/xml/internal/JdkConstants.java
+++ b/src/java.xml/share/classes/jdk/xml/internal/JdkConstants.java
@@ -220,6 +220,13 @@ public final class JdkConstants {
*/
public static final String XPATH_TOTALOP_LIMIT = "jdk.xml.xpathTotalOpLimit";
+ /**
+ * jdk.xml.allowNonXSLTSysProps: controls whether XPath's system-property()
+ * function is permitted to access non-XSLT-defined JVM system properties.
+ */
+ public static final String XPATH_ALLOW_NON_XSLT_SYS_PROPS =
+ "jdk.xml.allowNonXSLTSysProps";
+
/**
* JDK TransformerFactory and Transformer attribute that specifies a class
* loader that will be used for extension functions class loading
diff --git a/src/java.xml/share/classes/jdk/xml/internal/JdkXmlFeatures.java b/src/java.xml/share/classes/jdk/xml/internal/JdkXmlFeatures.java
index 7016d2227ea3..fb516d17e24f 100644
--- a/src/java.xml/share/classes/jdk/xml/internal/JdkXmlFeatures.java
+++ b/src/java.xml/share/classes/jdk/xml/internal/JdkXmlFeatures.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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,6 +28,8 @@
import javax.xml.XMLConstants;
import jdk.xml.internal.JdkProperty.ImplPropMap;
import jdk.xml.internal.JdkProperty.State;
+
+import static jdk.xml.internal.JdkConstants.XPATH_ALLOW_NON_XSLT_SYS_PROPS;
import static jdk.xml.internal.JdkXmlUtils.SP_USE_CATALOG;
/**
@@ -72,7 +74,16 @@ public static enum XmlFeature {
* FSP: not enforced by FSP.
*/
JDK_OVERRIDE_PARSER(ImplPropMap.OVERRIDEPARSER, null, null, false,
- null, null, false, false, true, false);
+ null, null, false, false, true, false),
+
+ /**
+ * Feature allowNonXSLTSysProps: controls whether XPath's system-property()
+ * function is permitted to access non-XSLT-defined JVM system properties.
+ * FSP: when FSP is on, accessing non-XSLT-defined JVM system properties is disabled
+ */
+ JDK_XPATH_SYSTEM_PROPERTY(null, XPATH_ALLOW_NON_XSLT_SYS_PROPS,
+ XPATH_ALLOW_NON_XSLT_SYS_PROPS, false,
+ null, null, false, false, true, true);
private final ImplPropMap implMap;
private final String name;
diff --git a/src/java.xml/share/classes/module-info.java b/src/java.xml/share/classes/module-info.java
index a27354ff6a60..19bb39475fd9 100644
--- a/src/java.xml/share/classes/module-info.java
+++ b/src/java.xml/share/classes/module-info.java
@@ -863,7 +863,7 @@
*
9
*
*
- *
jdk.xml.extensionClassLoader
+ *
{@systemProperty jdk.xml.extensionClassLoader}
*
Sets a non-null ClassLoader instance to be used for loading XSLTC java
* extension functions.
*
@@ -877,7 +877,7 @@
*
9
*
*
- *
jdk.xml.xpathExprGrpLimit
+ *
{@systemProperty jdk.xml.xpathExprGrpLimit}
*
Limits the number of groups an XPath expression can contain.
*
*
yes
@@ -894,14 +894,14 @@
*
19
*
*
- *
jdk.xml.xpathExprOpLimit
+ *
{@systemProperty jdk.xml.xpathExprOpLimit}
*
Limits the number of operators an XPath expression can contain.
*
*
100
*
100
*
*
- *
jdk.xml.xpathTotalOpLimit
+ *
{@systemProperty jdk.xml.xpathTotalOpLimit}
*
Limits the total number of XPath operators in an XSL Stylesheet.
*
*
10000
@@ -911,6 +911,28 @@
*
*
*
+ *
{@systemProperty jdk.xml.allowNonXSLTSysProps}
+ *
controls whether XPath's {@code system-property()} function is permitted to
+ * access non-XSLT-defined JVM system properties in addition to the standard XSLT-defined
+ * properties supported by the JDK. Attempts to access non-XSLT-defined properties will
+ * fail with an XPathExpressionException during XPath expression evaluation.
+ *
+ *
yes
+ *
Boolean
+ *
+ * true or false. True allows access to non-XSLT JVM system properties; False
+ * restricts access to only XSLT-defined properties.
+ *
Determines whether extension functions in the Transform API are to be allowed.
* The extension functions in the XPath API are not affected by this property.
@@ -1263,7 +1285,7 @@
* {@code http://www.oracle.com/xml/jaxp/properties/xsltcIsStandalone}
*
*
- *
{@code jdk.xml.extensionClassLoader}
+ *
{@systemProperty jdk.xml.extensionClassLoader}
*
{@code jdk.xml.transform.extensionClassLoader}
*
*
From 1f2116007286b2332fd1d208c0ab5e1a210aebe7 Mon Sep 17 00:00:00 2001
From: Phil Race
Date: Thu, 4 Jun 2026 21:12:37 +0000
Subject: [PATCH 019/169] 8384664: Enhance Image Handling
Reviewed-by: rhalade, jdv, kizune
---
.../classes/sun/lwawt/macosx/CImage.java | 29 ++++++++++---------
.../macosx/native/libawt_lwawt/awt/CImage.m | 5 ++++
2 files changed, 21 insertions(+), 13 deletions(-)
diff --git a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CImage.java b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CImage.java
index d3f262852559..6cf49b5b9ff9 100644
--- a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CImage.java
+++ b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CImage.java
@@ -83,7 +83,6 @@ public static CImage createFromImage(final Image image, CTrayIcon.IconObserver o
}
public static final class Creator {
- CTrayIcon.IconObserver observer;
Creator() { }
@@ -131,7 +130,7 @@ public Image createImageFromName(final String name) {
return createImageUsingNativeSize(nativeCreateNSImageFromImageName(name));
}
- private static int[] imageToArray(Image image, boolean prepareImage, CTrayIcon.IconObserver observer) {
+ private static BufferedImage getAsBufferedImage(Image image, boolean prepareImage, CTrayIcon.IconObserver observer) {
if (image == null) return null;
if (prepareImage && !(image instanceof BufferedImage)) {
@@ -163,17 +162,18 @@ private static int[] imageToArray(Image image, boolean prepareImage, CTrayIcon.I
g2.drawImage(image, 0, 0, observer);
g2.dispose();
- return ((DataBufferInt)bimg.getRaster().getDataBuffer()).getData();
+ return bimg;
}
public byte[] getPlatformImageBytes(final Image image) {
- int[] buffer = imageToArray(image, false, null);
+ BufferedImage bimg = getAsBufferedImage(image, false, null);
- if (buffer == null) {
+ if (bimg == null) {
return null;
}
+ int[] buffer = ((DataBufferInt)bimg.getRaster().getDataBuffer()).getData();
- return nativeGetPlatformImageBytes(buffer, image.getWidth(null), image.getHeight(null));
+ return nativeGetPlatformImageBytes(buffer, bimg.getWidth(), bimg.getHeight());
}
/**
@@ -205,11 +205,13 @@ private CImage createFromImage(final Image image, final boolean prepareImage, CT
return createFromImages(resolutionVariants, prepareImage);
}
- int[] buffer = imageToArray(image, prepareImage, observer);
- if (buffer == null) {
+ BufferedImage bimg = getAsBufferedImage(image, prepareImage, observer);
+
+ if (bimg == null) {
return null;
}
- return new CImage(nativeCreateNSImageFromArray(buffer, image.getWidth(null), image.getHeight(null)));
+ int[] buffer = ((DataBufferInt)bimg.getRaster().getDataBuffer()).getData();
+ return new CImage(nativeCreateNSImageFromArray(buffer, bimg.getWidth(), bimg.getHeight()));
}
public CImage createFromImages(final List images) {
@@ -230,13 +232,14 @@ private CImage createFromImages(final List images, final boolean prepareI
num = 0;
for (final Image img : images) {
- buffers[num] = imageToArray(img, prepareImage, null);
- if (buffers[num] == null) {
+ BufferedImage bimg = getAsBufferedImage(img, prepareImage, null);
+ if (bimg == null) {
// Unable to process the image
continue;
}
- w[num] = img.getWidth(null);
- h[num] = img.getHeight(null);
+ buffers[num] = ((DataBufferInt)bimg.getRaster().getDataBuffer()).getData();
+ w[num] = bimg.getWidth();
+ h[num] = bimg.getHeight();
num++;
}
diff --git a/src/java.desktop/macosx/native/libawt_lwawt/awt/CImage.m b/src/java.desktop/macosx/native/libawt_lwawt/awt/CImage.m
index fa534fff2759..3c5bb10cd253 100644
--- a/src/java.desktop/macosx/native/libawt_lwawt/awt/CImage.m
+++ b/src/java.desktop/macosx/native/libawt_lwawt/awt/CImage.m
@@ -75,6 +75,11 @@
static NSBitmapImageRep* CImage_CreateImageRep(JNIEnv *env, jintArray buffer, jint width, jint height)
{
+ jlong bufferLength = (*env)->GetArrayLength(env, buffer);
+ if (((jlong)width * height) > bufferLength) {
+ return 0L;
+ }
+
NSBitmapImageRep* imageRep = [[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
pixelsWide:width
pixelsHigh:height
From 4458d9bbad5360ea9c812ce22633c5d971933da4 Mon Sep 17 00:00:00 2001
From: Alexey Semenyuk
Date: Mon, 8 Jun 2026 16:31:14 +0000
Subject: [PATCH 020/169] 8384468: Enhance jpackage Packaging
Reviewed-by: rhalade, kcr, almatvee
---
make/modules/jdk.jpackage/Lib.gmk | 3 +-
.../linux/native/applauncher/ExecCommand.c | 313 ++++++++++++++++++
.../linux/native/applauncher/ExecCommand.h | 49 +++
.../linux/native/applauncher/LinuxLauncher.c | 36 +-
.../linux/native/applauncher/LinuxPackage.c | 234 +++++++------
.../linux/native/applauncher/LinuxPackage.h | 21 +-
.../linux/native/libapplauncher/Executor.cpp | 61 ++--
.../linux/native/libapplauncher/Executor.h | 4 +-
.../libapplauncher/LinuxLauncherLib.cpp | 22 +-
.../linux/native/libapplauncher/Package.cpp | 72 +---
.../linux/native/libapplauncher/Package.h | 15 +-
.../share/native/applauncher/JvmLauncher.h | 8 +-
.../share/native/applauncher/JvmLauncherLib.c | 26 +-
13 files changed, 600 insertions(+), 264 deletions(-)
create mode 100644 src/jdk.jpackage/linux/native/applauncher/ExecCommand.c
create mode 100644 src/jdk.jpackage/linux/native/applauncher/ExecCommand.h
diff --git a/make/modules/jdk.jpackage/Lib.gmk b/make/modules/jdk.jpackage/Lib.gmk
index 86b11bdafeea..cee8309c679d 100644
--- a/make/modules/jdk.jpackage/Lib.gmk
+++ b/make/modules/jdk.jpackage/Lib.gmk
@@ -55,8 +55,8 @@ $(eval $(call SetupJdkExecutable, BUILD_JPACKAGEAPPLAUNCHER, \
EXTRA_SRC := common, \
INCLUDE_FILES := $(JPACKAGEAPPLAUNCHER_INCLUDE_FILES), \
OPTIMIZATION := SIZE, \
+ DISABLED_WARNINGS_clang_ExecCommand.c := format-nonliteral, \
DISABLED_WARNINGS_clang_JvmLauncherLib.c := format-nonliteral, \
- DISABLED_WARNINGS_clang_LinuxPackage.c := format-nonliteral, \
DISABLED_WARNINGS_clang_Log.cpp := unused-const-variable, \
CFLAGS_FILTER_OUT := -MD, \
CXXFLAGS_FILTER_OUT := -MD, \
@@ -93,6 +93,7 @@ ifeq ($(call isTargetOs, linux), true)
LINK_TYPE := C++, \
OPTIMIZATION := SIZE, \
DISABLED_WARNINGS_gcc_Log.cpp := unused-const-variable, \
+ DISABLED_WARNINGS_clang_ExecCommand.c := format-nonliteral, \
DISABLED_WARNINGS_clang_JvmLauncherLib.c := format-nonliteral, \
DISABLED_WARNINGS_clang_tstrings.cpp := format-nonliteral, \
LD_SET_ORIGIN := false, \
diff --git a/src/jdk.jpackage/linux/native/applauncher/ExecCommand.c b/src/jdk.jpackage/linux/native/applauncher/ExecCommand.c
new file mode 100644
index 000000000000..d75e1109081c
--- /dev/null
+++ b/src/jdk.jpackage/linux/native/applauncher/ExecCommand.c
@@ -0,0 +1,313 @@
+/*
+ * 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. 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.
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "ExecCommand.h"
+#include "JvmLauncher.h"
+
+
+static bool logCommandLine(const char* format, const char* const argv[]) {
+ char* formattedCommandLine = NULL;
+ int formattedCommandLineLength = 0;
+ const char* arg;
+ int i;
+ bool success = false;
+
+ for (i = 0; (arg = argv[i]) != NULL; i++) {
+ /* Count trailing whitespace */
+ formattedCommandLineLength += strlen(arg) + 1;
+ if (strchr(arg, ' ') != NULL) {
+ /* Enclose the argument into single quotes */
+ formattedCommandLineLength += 2;
+ }
+ }
+
+ if (!formattedCommandLineLength) {
+ /* Empty command line is an error */
+ goto cleanup;
+ }
+
+ formattedCommandLine = malloc(formattedCommandLineLength + 1 /* \0 */);
+ if (!formattedCommandLine) {
+ JP_LOG_ERRNO;
+ goto cleanup;
+ }
+
+ formattedCommandLine[0] = '\0';
+ for (i = 0; (arg = argv[i]) != NULL; i++) {
+ if (strchr(arg, ' ') != NULL) {
+ strcat(formattedCommandLine, "'");
+ strcat(formattedCommandLine, arg);
+ strcat(formattedCommandLine, "'");
+ } else {
+ strcat(formattedCommandLine, arg);
+ }
+ strcat(formattedCommandLine, " ");
+ }
+
+ /* Trim trailing whitespace */
+ formattedCommandLine[formattedCommandLineLength - 1] = '\0';
+
+ JP_LOG_TRACE(format, formattedCommandLine);
+
+ success = true;
+
+cleanup:
+ free(formattedCommandLine);
+
+ return success;
+}
+
+static bool invokeCallback(
+ FILE* stream,
+ ExecCommandCallbackType callback,
+ void* callbackData) {
+
+ char* strBufBegin = NULL;
+ char* strBufEnd = NULL;
+ char* strBufNextChar = NULL;
+ char* strNewBufBegin = NULL;
+ size_t strBufCapacity = 0;
+ int callbackMode = EXEC_CALLBACK_USE;
+ int c;
+ ptrdiff_t charOffset;
+ int success = false;
+ int tailCharCount = 0;
+
+ for (;;) {
+ c = fgetc(stream);
+ if (EOF == c || '\n' == c) {
+ if (EXEC_CALLBACK_USE == callbackMode
+ && strBufBegin != strBufNextChar) {
+ *strBufNextChar = 0;
+ JP_LOG_TRACE("execCommand: [%s]", strBufBegin);
+ callbackMode = (*callback)(callbackData, strBufBegin);
+ strBufNextChar = strBufBegin;
+ }
+
+ if (EOF == c) {
+ break;
+ }
+
+ continue;
+ }
+
+ if (EXEC_CALLBACK_USE != callbackMode) {
+ /* Fetch the stream */
+ for (; EOF != c; c = fgetc(stream)) {
+ tailCharCount++;
+ }
+ if (tailCharCount) {
+ JP_LOG_TRACE(
+ "execCommand: fetched %d trailing chars",
+ tailCharCount);
+ }
+ break;
+ }
+
+ if (strBufNextChar == strBufEnd) {
+ /* Double buffer size */
+ if (strBufCapacity) {
+ strBufCapacity = strBufCapacity * 2;
+ } else {
+ strBufCapacity = 1;
+ }
+
+ if (strBufNextChar && strBufBegin) {
+ charOffset = strBufNextChar - strBufBegin;
+ } else {
+ charOffset = 0;
+ }
+
+ strNewBufBegin = realloc( strBufBegin,
+ /* Account for the terminal '\0' */
+ strBufCapacity + 1);
+ if (!strNewBufBegin) {
+ JP_LOG_ERRNO;
+ goto cleanup;
+ }
+
+ strBufNextChar = strNewBufBegin + charOffset;
+ strBufEnd = strNewBufBegin + strBufCapacity;
+ strBufBegin = strNewBufBegin;
+ }
+
+ *strBufNextChar++ = (char)c;
+ }
+
+ success = (EXEC_CALLBACK_ERROR != callbackMode);
+
+cleanup:
+ if (strBufBegin) {
+ free(strBufBegin);
+ }
+
+ return success;
+}
+
+
+int execCommand(
+ const char* const argv[],
+ ExecCommandCallbackType callback,
+ void* callbackData) {
+
+ int pipefd[] = { -1, -1 };
+ pid_t cpid = -1;
+ FILE* stream = NULL;
+ int exitCode = -1;
+ int devNull = -1;
+ int savedStderr = -1;
+ int savedErrno = 0;
+ bool callbackSuccess = false;
+ int childExitCode = 1;
+ int waitpidStatus = -1;
+
+ if (!logCommandLine("execCommand: cmdline=[%s]", argv)) {
+ return -1;
+ }
+
+ if (pipe(pipefd) == -1) {
+ JP_LOG_ERRNO;
+ goto cleanup;
+ }
+
+ cpid = fork();
+ if (cpid == -1) {
+ JP_LOG_ERRNO;
+ } else if (cpid == 0) /* Child process */ {
+ /* Close unused read end */
+ closePipeEnd(pipefd, 0);
+
+ /* Save original stderr */
+ if ((savedStderr = dup(STDERR_FILENO)) == -1) {
+ JP_LOG_ERRNO;
+ goto cleanupChild;
+ }
+
+ /* Redirect stdout of the child process into the pipe's end */
+ if (dup2(pipefd[1], STDOUT_FILENO) == -1) {
+ JP_LOG_ERRNO;
+ goto cleanupChild;
+ }
+
+ devNull = open("/dev/null", O_WRONLY);
+ if (devNull == -1) {
+ JP_LOG_ERRNO;
+ goto cleanupChild;
+ }
+
+ /* Silence stderr in the child process */
+ if (dup2(devNull, STDERR_FILENO) == -1) {
+ JP_LOG_ERRNO;
+ goto cleanupChild;
+ }
+
+ childExitCode = 0;
+
+cleanupChild:
+ if (devNull != -1) {
+ close(devNull);
+ }
+
+ closePipeEnd(pipefd, 0);
+
+ if (childExitCode == 0) {
+ execvp(argv[0], (char* const*)argv);
+
+ /*
+ Normally, execvp() doesn't return.
+ If control flow reaches this point, execvp() failed.
+ Restore stderr to make JP_LOG_ERRNO macro work and report error.
+ */
+ savedErrno = errno;
+ dup2(savedStderr, STDERR_FILENO);
+ errno = savedErrno;
+ JP_LOG_ERRNO;
+
+ close(savedStderr);
+
+ childExitCode = 127; /* Command not found */
+ }
+
+ _exit(childExitCode);
+ }
+
+ /* Close unused write end */
+ closePipeEnd(pipefd, 1);
+
+ stream = fdopen(pipefd[0], "r");
+ if (!stream) {
+ JP_LOG_ERRNO;
+ goto cleanup;
+ }
+ pipefd[0] = -1;
+
+ callbackSuccess = invokeCallback(stream, callback, callbackData);
+ if (!callbackSuccess) {
+ JP_LOG_TRACE("Callback failed");
+ goto cleanup;
+ }
+
+cleanup:
+ if (stream) {
+ fclose(stream);
+ }
+
+ closePipeEnd(pipefd, 0);
+ closePipeEnd(pipefd, 1);
+
+ if (cpid > 0) {
+ while (waitpid(cpid, &waitpidStatus, 0) == -1 && errno == EINTR) {
+ }
+
+ if (WIFEXITED(waitpidStatus)) {
+ exitCode = WEXITSTATUS(waitpidStatus);
+ }
+ }
+
+ JP_LOG_TRACE("execCommand: exit=%d", exitCode);
+
+ if (exitCode == 0 && !callbackSuccess) {
+ exitCode = 1;
+ }
+
+ return exitCode;
+}
+
+
+void closePipeEnd(int* pipefd, int idx) {
+ if (pipefd[idx] >= 0) {
+ close(pipefd[idx]);
+ pipefd[idx] = -1;
+ }
+}
diff --git a/src/jdk.jpackage/linux/native/applauncher/ExecCommand.h b/src/jdk.jpackage/linux/native/applauncher/ExecCommand.h
new file mode 100644
index 000000000000..1bce66feeea4
--- /dev/null
+++ b/src/jdk.jpackage/linux/native/applauncher/ExecCommand.h
@@ -0,0 +1,49 @@
+/*
+ * 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. 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.
+ */
+
+#ifndef ExecCommand_h
+#define ExecCommand_h
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define EXEC_CALLBACK_IGNORE 0
+#define EXEC_CALLBACK_USE 1
+#define EXEC_CALLBACK_ERROR 2
+
+typedef int (*ExecCommandCallbackType)(void*, char*);
+
+int execCommand(const char* const argv[],
+ ExecCommandCallbackType callback,
+ void* callbackData);
+
+void closePipeEnd(int* pipefd, int idx);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* #ifndef ExecCommand_h */
diff --git a/src/jdk.jpackage/linux/native/applauncher/LinuxLauncher.c b/src/jdk.jpackage/linux/native/applauncher/LinuxLauncher.c
index 7db42bc2dbd6..96e6abe98c81 100644
--- a/src/jdk.jpackage/linux/native/applauncher/LinuxLauncher.c
+++ b/src/jdk.jpackage/linux/native/applauncher/LinuxLauncher.c
@@ -35,31 +35,40 @@
#include
#include "JvmLauncher.h"
#include "LinuxPackage.h"
+#include "ExecCommand.h"
#define STATUS_FAILURE 1
-typedef JvmlLauncherHandle (*JvmlLauncherAPI_CreateFunType)(int argc, char *argv[]);
+typedef JvmlLauncherHandle (*JvmlLauncherAPI_CreateFunType)(
+ const PackageDesc* pkg,
+ int argc,
+ char* argv[]);
static int appArgc;
static char **appArgv;
static JvmlLauncherData* initJvmlLauncherData(int* size) {
- char* launcherLibPath = 0;
+ JvmLauncherDesc* jvmLauncherDesc = 0;
+ PackageDesc pkg;
void* jvmLauncherLibHandle = 0;
JvmlLauncherAPI_GetAPIFunc getApi = 0;
- JvmlLauncherAPI_CreateFunType createJvmlLauncher = 0;
+ JvmlLauncherAPI_CreateFunType createJvmLauncher = 0;
JvmlLauncherAPI* api = 0;
JvmlLauncherHandle jvmLauncherHandle = 0;
JvmlLauncherData* result = 0;
- launcherLibPath = getJvmLauncherLibPath();
- if (!launcherLibPath) {
+ jvmLauncherDesc = getJvmLauncherDesc();
+ if (!jvmLauncherDesc) {
goto cleanup;
}
- jvmLauncherLibHandle = dlopen(launcherLibPath, RTLD_NOW | RTLD_LOCAL);
+ pkg.name = jvmLauncherDesc->packageName;
+ pkg.type = jvmLauncherDesc->packageType;
+
+ jvmLauncherLibHandle = dlopen( jvmLauncherDesc->jvmLauncherLibPath,
+ RTLD_NOW | RTLD_LOCAL);
if (!jvmLauncherLibHandle) {
JP_LOG_ERRMSG(dlerror());
goto cleanup;
@@ -77,13 +86,13 @@ static JvmlLauncherData* initJvmlLauncherData(int* size) {
goto cleanup;
}
- createJvmlLauncher = dlsym(jvmLauncherLibHandle, "jvmLauncherCreate");
- if (!createJvmlLauncher) {
+ createJvmLauncher = dlsym(jvmLauncherLibHandle, "jvmLauncherCreate");
+ if (!createJvmLauncher) {
JP_LOG_ERRMSG(dlerror());
goto cleanup;
}
- jvmLauncherHandle = (*createJvmlLauncher)(appArgc, appArgv);
+ jvmLauncherHandle = (*createJvmLauncher)(&pkg, appArgc, appArgv);
if (!jvmLauncherHandle) {
goto cleanup;
}
@@ -99,7 +108,7 @@ static JvmlLauncherData* initJvmlLauncherData(int* size) {
if (jvmLauncherLibHandle) {
dlclose(jvmLauncherLibHandle);
}
- free(launcherLibPath);
+ freeJvmLauncherDesc(jvmLauncherDesc);
return result;
}
@@ -170,13 +179,6 @@ static ssize_t writeFully(const int fd, const void* buf, const size_t len) {
return (ssize_t)nWritten;
}
-static void closePipeEnd(int* pipefd, int idx) {
- if (pipefd[idx] >= 0) {
- close(pipefd[idx]);
- pipefd[idx] = -1;
- }
-}
-
static void initJvmlLauncherDataPointers(void* baseAddress,
JvmlLauncherData* jvmLauncherData) {
diff --git a/src/jdk.jpackage/linux/native/applauncher/LinuxPackage.c b/src/jdk.jpackage/linux/native/applauncher/LinuxPackage.c
index d346e241035a..31d2208d7a30 100644
--- a/src/jdk.jpackage/linux/native/applauncher/LinuxPackage.c
+++ b/src/jdk.jpackage/linux/native/applauncher/LinuxPackage.c
@@ -23,16 +23,16 @@
* questions.
*/
-#include
#include
-#include
#include
#include
#include
#include
#include
+#include
#include "JvmLauncher.h"
#include "LinuxPackage.h"
+#include "ExecCommand.h"
static char* getModulePath(void) {
@@ -56,19 +56,9 @@ static char* getModulePath(void) {
}
-# define PACKAGE_TYPE_UNKNOWN 0
-# define PACKAGE_TYPE_RPM 1
-# define PACKAGE_TYPE_DEB 2
-
-typedef struct {
- char* name;
- int type;
-} PackageDesc;
-
-
static void freePackageDesc(PackageDesc* desc) {
if (desc) {
- free(desc->name);
+ free((void*)desc->name);
free(desc);
}
}
@@ -91,116 +81,73 @@ static PackageDesc* createPackageDesc(void) {
}
-static PackageDesc* initPackageDesc(PackageDesc* desc, const char* str,
- int type) {
+static bool initPackageDesc(PackageDesc* desc, const char* str, int type) {
char *newStr = strdup(str);
if (!newStr) {
JP_LOG_ERRNO;
- return 0;
+ return false;
}
- free(desc->name);
+ free((void*)desc->name);
desc->name = newStr;
desc->type = type;
- return desc;
+ return true;
}
-#define POPEN_CALLBACK_USE 1
-#define POPEN_CALLBACK_IGNORE 0
-
-typedef int (*popenCallbackType)(void*, char*);
-
-static int popenCommand(const char* cmdlineFormat, const char* arg,
- popenCallbackType callback, void* callbackData) {
- char* cmdline = 0;
- FILE *stream = 0;
- const size_t cmdlineLenth = strlen(cmdlineFormat) + strlen(arg);
- char* strBufBegin = 0;
- char* strBufEnd = 0;
- char* strBufNextChar = 0;
- char* strNewBufBegin = 0;
- size_t strBufCapacity = 0;
- int callbackMode = POPEN_CALLBACK_USE;
- int exitCode = -1;
- int c;
- ptrdiff_t char_offset;
-
- cmdline = malloc(cmdlineLenth + 1 /* \0 */);
- if (!cmdline) {
- JP_LOG_ERRNO;
- goto cleanup;
- }
+static JvmLauncherDesc* createJvmLauncherDesc(void) {
+ JvmLauncherDesc* result = 0;
-#if defined(__GNUC__) && __GNUC__ >= 5
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wformat-nonliteral"
-#endif
- if (0 > snprintf(cmdline, cmdlineLenth, cmdlineFormat, arg)) {
+ result = malloc(sizeof(JvmLauncherDesc));
+ if (!result) {
JP_LOG_ERRNO;
goto cleanup;
}
-#if defined(__GNUC__) && __GNUC__ >= 5
-#pragma GCC diagnostic pop
-#endif
- JP_LOG_TRACE("popen: (%s)", cmdline);
+ result->packageType = PACKAGE_TYPE_UNKNOWN;
+ result->packageName = NULL;
+ result->jvmLauncherLibPath = NULL;
- stream = popen(cmdline, "r");
- if (!stream) {
- JP_LOG_ERRNO;
- goto cleanup;
- }
+cleanup:
+ return result;
+}
- for (;;) {
- c = fgetc(stream);
- if((EOF == c || '\n' == c)) {
- if (POPEN_CALLBACK_USE == callbackMode
- && strBufBegin != strBufNextChar) {
- *strBufNextChar = 0;
- JP_LOG_TRACE("popen: [%s]", strBufBegin);
- callbackMode = (*callback)(callbackData, strBufBegin);
- strBufNextChar = strBufBegin;
- }
-
- if (EOF == c) {
- break;
- }
-
- continue;
- }
- if (strBufNextChar == strBufEnd) {
- /* Double buffer size */
- strBufCapacity = strBufCapacity * 2 + 1;
- char_offset = strBufNextChar - strBufBegin;
- strNewBufBegin = realloc(strBufBegin, strBufCapacity);
- if (!strNewBufBegin) {
- JP_LOG_ERRNO;
- goto cleanup;
- }
-
- strBufNextChar = strNewBufBegin + char_offset;
- strBufEnd = strNewBufBegin + strBufCapacity;
- strBufBegin = strNewBufBegin;
- }
+static bool initJvmLauncherDesc(
+ JvmLauncherDesc* desc,
+ const PackageDesc* packageDesc,
+ const char* jvmLauncherLibPath) {
- *strBufNextChar++ = (char)c;
- }
+ char* newJvmLauncherLibPath = NULL;
+ char* newPackageName = NULL;
-cleanup:
- if (stream) {
- exitCode = pclose(stream);
+ newJvmLauncherLibPath = strdup(jvmLauncherLibPath);
+ if (!newJvmLauncherLibPath) {
+ JP_LOG_ERRNO;
+ return false;
}
- if (strBufBegin) {
- free(strBufBegin);
+ if (packageDesc && packageDesc->name) {
+ newPackageName = strdup(packageDesc->name);
+ if (!newPackageName) {
+ JP_LOG_ERRNO;
+ free(newJvmLauncherLibPath);
+ return false;
+ }
}
- free(cmdline);
+ free((void*)desc->packageName);
+ free((void*)desc->jvmLauncherLibPath);
- JP_LOG_TRACE("popen: exit: %d", exitCode);
- return exitCode;
+ desc->packageName = newPackageName;
+ if (packageDesc) {
+ desc->packageType = packageDesc->type;
+ } else {
+ desc->packageType = PACKAGE_TYPE_UNKNOWN;
+ }
+ desc->jvmLauncherLibPath = newJvmLauncherLibPath;
+
+ return true;
}
@@ -221,8 +168,11 @@ static char* concat(const char *x, const char *y) {
static int initRpmPackage(void* desc, char* str) {
- initPackageDesc((PackageDesc*)desc, str, PACKAGE_TYPE_RPM);
- return POPEN_CALLBACK_IGNORE;
+ if (initPackageDesc((PackageDesc*)desc, str, PACKAGE_TYPE_RPM)) {
+ return EXEC_CALLBACK_IGNORE;
+ } else {
+ return EXEC_CALLBACK_ERROR;
+ }
}
@@ -231,8 +181,12 @@ static int initDebPackage(void* desc, char* str) {
if (colonChrPos) {
*colonChrPos = 0;
}
- initPackageDesc((PackageDesc*)desc, str, PACKAGE_TYPE_DEB);
- return POPEN_CALLBACK_IGNORE;
+
+ if (initPackageDesc((PackageDesc*)desc, str, PACKAGE_TYPE_DEB)) {
+ return EXEC_CALLBACK_IGNORE;
+ } else {
+ return EXEC_CALLBACK_ERROR;
+ }
}
@@ -248,34 +202,38 @@ static int findLauncherLib(void* launcherLibPath, char* str) {
buf = strdup(str);
if (!buf) {
JP_LOG_ERRNO;
+ return EXEC_CALLBACK_ERROR;
} else {
*(char**)launcherLibPath = buf;
}
- return POPEN_CALLBACK_IGNORE;
+ return EXEC_CALLBACK_IGNORE;
}
- return POPEN_CALLBACK_USE;
+ return EXEC_CALLBACK_USE;
}
static PackageDesc* findOwnerOfFile(const char* path) {
- int popenStatus = -1;
+ int execStatus = -1;
PackageDesc* pkg = 0;
+ const char* rpmOwner[] = {
+ "rpm", "--queryformat", "%{NAME}", "-qf", path, NULL
+ };
+ const char* debOwner[] = {
+ "dpkg", "-S", path, NULL
+ };
pkg = createPackageDesc();
if (!pkg) {
return 0;
}
- popenStatus = popenCommand(
- "rpm --queryformat '%{NAME}' -qf '%s' 2>/dev/null", path,
- initRpmPackage, pkg);
- if (popenStatus) {
+ execStatus = execCommand(rpmOwner, initRpmPackage, pkg);
+ if (execStatus) {
pkg->type = PACKAGE_TYPE_UNKNOWN;
- popenStatus = popenCommand("dpkg -S '%s' 2>/dev/null", path,
- initDebPackage, pkg);
+ execStatus = execCommand(debOwner, initDebPackage, pkg);
}
- if (popenStatus) {
+ if (execStatus) {
pkg->type = PACKAGE_TYPE_UNKNOWN;
}
@@ -292,13 +250,25 @@ static PackageDesc* findOwnerOfFile(const char* path) {
}
-char* getJvmLauncherLibPath(void) {
+void freeJvmLauncherDesc(JvmLauncherDesc* desc) {
+ if (desc) {
+ free((void*)desc->packageName);
+ free((void*)desc->jvmLauncherLibPath);
+ free(desc);
+ }
+}
+
+
+JvmLauncherDesc* getJvmLauncherDesc(void) {
char* modulePath = 0;
char* appImageDir = 0;
char* launcherLibPath = 0;
- const char* pkgQueryCmd = 0;
- int popenStatus = -1;
+ const char* pkgQueryCmd[] = {
+ NULL, NULL, NULL, NULL
+ };
PackageDesc* pkg = 0;
+ JvmLauncherDesc* result = 0;
+ bool resultReady = false;
modulePath = getModulePath();
if (!modulePath) {
@@ -314,27 +284,45 @@ char* getJvmLauncherLibPath(void) {
launcherLibPath = concat(appImageDir, "/lib" LAUNCHER_LIB_NAME);
} else {
if (PACKAGE_TYPE_RPM == pkg->type) {
- pkgQueryCmd = "rpm -ql '%s' 2>/dev/null";
+ pkgQueryCmd[0] = "rpm";
+ pkgQueryCmd[1] = "-ql";
+ pkgQueryCmd[2] = pkg->name;
} else if (PACKAGE_TYPE_DEB == pkg->type) {
- pkgQueryCmd = "dpkg -L '%s' 2>/dev/null";
+ pkgQueryCmd[0] = "dpkg";
+ pkgQueryCmd[1] = "-L";
+ pkgQueryCmd[2] = pkg->name;
} else {
/* Should never happen */
JP_LOG_ERRMSG("Internal error");
goto cleanup;
}
- popenStatus = popenCommand(pkgQueryCmd, pkg->name, findLauncherLib,
- &launcherLibPath);
- if (popenStatus) {
- free(launcherLibPath);
- launcherLibPath = NULL;
+ if (execCommand(pkgQueryCmd, findLauncherLib, &launcherLibPath)) {
goto cleanup;
}
}
+ if (!launcherLibPath) {
+ goto cleanup;
+ }
+
+ result = createJvmLauncherDesc();
+ if (result && initJvmLauncherDesc(result, pkg, launcherLibPath)) {
+ JP_LOG_TRACE("JvmLauncherDesc(%s|%d|%s)",
+ result->packageName ? result->packageName : "(null)",
+ result->packageType,
+ result->jvmLauncherLibPath);
+ resultReady = true;
+ }
+
cleanup:
free(modulePath);
freePackageDesc(pkg);
+ if (!resultReady) {
+ freeJvmLauncherDesc(result);
+ result = NULL;
+ }
+ free(launcherLibPath);
- return launcherLibPath;
+ return result;
}
diff --git a/src/jdk.jpackage/linux/native/applauncher/LinuxPackage.h b/src/jdk.jpackage/linux/native/applauncher/LinuxPackage.h
index 24ec18910666..af862502a07b 100644
--- a/src/jdk.jpackage/linux/native/applauncher/LinuxPackage.h
+++ b/src/jdk.jpackage/linux/native/applauncher/LinuxPackage.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 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
@@ -30,7 +30,24 @@
extern "C" {
#endif
-char* getJvmLauncherLibPath(void);
+#define PACKAGE_TYPE_UNKNOWN 0
+#define PACKAGE_TYPE_RPM 1
+#define PACKAGE_TYPE_DEB 2
+
+typedef struct {
+ const char* name;
+ int type;
+} PackageDesc;
+
+typedef struct {
+ const char* packageName;
+ int packageType;
+ const char* jvmLauncherLibPath;
+} JvmLauncherDesc;
+
+void freeJvmLauncherDesc(JvmLauncherDesc* desc);
+
+JvmLauncherDesc* getJvmLauncherDesc(void);
#ifdef __cplusplus
}
diff --git a/src/jdk.jpackage/linux/native/libapplauncher/Executor.cpp b/src/jdk.jpackage/linux/native/libapplauncher/Executor.cpp
index d8a1755ac88e..5caf01f193cf 100644
--- a/src/jdk.jpackage/linux/native/libapplauncher/Executor.cpp
+++ b/src/jdk.jpackage/linux/native/libapplauncher/Executor.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020, 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
@@ -23,49 +23,36 @@
* questions.
*/
-#include
-#include
#include "Executor.h"
-#include "Log.h"
#include "ErrorHandling.h"
+#include "ExecCommand.h"
-int executeCommandLineAndReadStdout(const std::string& cmd,
- CommandOutputConsumer& consumer) {
- FILE * stream = popen(cmd.c_str(), "r");
- if (!stream) {
- JP_THROW(tstrings::any() << "popen(" << cmd
- << ") failed. Error: " << lastCRTError());
+namespace {
+
+int callbackAdapter(void* callback, char* str) {
+ JP_TRY;
+ if (reinterpret_cast(callback)->accept(str)) {
+ return EXEC_CALLBACK_IGNORE;
+ } else {
+ return EXEC_CALLBACK_USE;
}
+ JP_CATCH_ALL;
+ return EXEC_CALLBACK_ERROR;
+}
- LOG_TRACE(tstrings::any() << "Reading output of [" << cmd << "] command");
+} // namespace
- try {
- bool useConsumer = true;
- std::string buf;
- for (;;) {
- const int c = fgetc(stream);
- if(c == EOF) {
- if (useConsumer && !buf.empty()) {
- LOG_TRACE(tstrings::any() << "Next line: [" << buf << "]");
- consumer.accept(buf);
- }
- break;
- }
+int executeCommandLineAndReadStdout(const tstring_array& cmd,
+ CommandOutputConsumer& consumer) {
- if (c == '\n' && useConsumer) {
- LOG_TRACE(tstrings::any() << "Next line: [" << buf << "]");
- useConsumer = !consumer.accept(buf);
- buf.clear();
- } else {
- buf.push_back(static_cast(c));
- }
- }
- return pclose(stream);
- } catch (...) {
- if (stream) {
- pclose(stream);
- }
- throw;
+ std::vector argv;
+ tstring_array::const_iterator it = cmd.begin();
+ tstring_array::const_iterator end = cmd.end();
+ for (; it != end; ++it) {
+ argv.push_back(it->c_str());
}
+ argv.push_back(NULL);
+
+ return execCommand(argv.data(), callbackAdapter, &consumer);
}
diff --git a/src/jdk.jpackage/linux/native/libapplauncher/Executor.h b/src/jdk.jpackage/linux/native/libapplauncher/Executor.h
index 44a5f0c7e26c..423289c7af03 100644
--- a/src/jdk.jpackage/linux/native/libapplauncher/Executor.h
+++ b/src/jdk.jpackage/linux/native/libapplauncher/Executor.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020, 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
@@ -38,7 +38,7 @@ class CommandOutputConsumer {
};
};
-int executeCommandLineAndReadStdout(const std::string& cmd,
+int executeCommandLineAndReadStdout(const tstring_array& cmd,
CommandOutputConsumer& consumer);
#endif // #ifndef EXECUTOR_H
diff --git a/src/jdk.jpackage/linux/native/libapplauncher/LinuxLauncherLib.cpp b/src/jdk.jpackage/linux/native/libapplauncher/LinuxLauncherLib.cpp
index 561e2a942bb0..8430f3149bb8 100644
--- a/src/jdk.jpackage/linux/native/libapplauncher/LinuxLauncherLib.cpp
+++ b/src/jdk.jpackage/linux/native/libapplauncher/LinuxLauncherLib.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 2025, 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
@@ -45,11 +45,16 @@ size_t hash(const std::string& str) {
}
Jvm* jvmLauncher;
+PackageDesc pkgDesc = {};
void launchApp() {
const tstring launcherPath = SysInfo::getProcessModulePath();
- const Package ownerPackage = Package::findOwnerOfFile(launcherPath);
+ LOG_TRACE(tstrings::any() << "PackageDesc("
+ << (pkgDesc.name ? pkgDesc.name : "(null)")
+ << "|" << pkgDesc.type << ")");
+
+ const Package ownerPackage = Package(pkgDesc);
AppLauncher appLauncher;
appLauncher.addJvmLibName(_T("lib/libjli.so"));
@@ -100,12 +105,12 @@ void launchApp() {
tistringstream iss(launchInfo);
iss.exceptions(std::ios::failbit | std::ios::badbit);
- size_t hash = 0;
- iss >> hash;
+ size_t launchInfoHash = 0;
+ iss >> launchInfoHash;
launchInfo = "";
- if (thisHash != hash) {
+ if (thisHash != launchInfoHash) {
// This launcher execution is the result of execve() call from
// within JVM.
// This means all JVM arguments are already configured in launcher
@@ -133,10 +138,15 @@ void launchApp() {
extern "C" {
-JNIEXPORT JvmlLauncherHandle jvmLauncherCreate(int argc, char *argv[]) {
+JNIEXPORT JvmlLauncherHandle jvmLauncherCreate(
+ const PackageDesc* pkg,
+ int argc,
+ char* argv[]) {
+
SysInfo::argc = argc;
SysInfo::argv = argv;
jvmLauncher = 0;
+ pkgDesc = *pkg;
app::launch(std::nothrow, launchApp);
JvmlLauncherHandle jlh = 0;
diff --git a/src/jdk.jpackage/linux/native/libapplauncher/Package.cpp b/src/jdk.jpackage/linux/native/libapplauncher/Package.cpp
index 3665d7e14a00..fe99a723c4a5 100644
--- a/src/jdk.jpackage/linux/native/libapplauncher/Package.cpp
+++ b/src/jdk.jpackage/linux/native/libapplauncher/Package.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020, 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
@@ -33,67 +33,9 @@ Package::Package(): type(Unknown) {
}
-namespace {
-class FirstLineConsumer : public CommandOutputConsumer {
-public:
- FirstLineConsumer(): processed(false) {
- }
-
- virtual bool accept(const std::string& line) {
- if (!processed) {
- value = line;
- processed = true;
- }
- return processed;
- };
-
- std::string getValue() const {
- if (!processed) {
- JP_THROW("No output captured");
- }
- return value;
- }
-
-private:
- bool processed;
- std::string value;
-};
-
-
-std::string findOwnerOfFile(const std::nothrow_t&, const std::string& cmdline,
- const std::string& path) {
- try {
- FirstLineConsumer consumer;
- int exitCode = executeCommandLineAndReadStdout(
- cmdline + " \'" + path + "\' 2>/dev/null", consumer);
- if (exitCode == 0) {
- return consumer.getValue();
- }
- } catch (...) {
- }
- return "";
-}
-
-} // namespace
-
-Package Package::findOwnerOfFile(const std::string& path) {
- Package result;
- result.theName = ::findOwnerOfFile(std::nothrow,
- "rpm --queryformat '%{NAME}' -qf", path);
- if (!result.theName.empty()) {
- result.type = RPM;
- } else {
- tstring_array components = tstrings::split(::findOwnerOfFile(
- std::nothrow, "dpkg -S", path), ":");
- if (!components.empty()) {
- result.theName = components.front();
- if (!result.theName.empty()) {
- result.type = DEB;
- }
- }
- }
-
- return result;
+Package::Package(const PackageDesc& desc):
+ type(static_cast(desc.type)),
+ theName(desc.name ? desc.name : "") {
}
@@ -119,7 +61,7 @@ class AppLauncherInitializer : public CommandOutputConsumer {
return !appDir.empty() && !runtimeDir.empty();
};
- void apply(AppLauncher& launcher) {
+ void apply(AppLauncher& launcher) const {
launcher.setDefaultRuntimePath(runtimeDir);
launcher.setAppDir(appDir);
}
@@ -136,10 +78,10 @@ void Package::initAppLauncher(AppLauncher& appLauncher) const {
int exitCode = -1;
if (type == RPM) {
exitCode = executeCommandLineAndReadStdout(
- "rpm -ql \'" + theName + "\'", consumer);
+ { "rpm", "-ql", theName }, consumer);
} else if (type == DEB) {
exitCode = executeCommandLineAndReadStdout(
- "dpkg -L \'" + theName + "\'", consumer);
+ { "dpkg", "-L", theName }, consumer);
}
if (exitCode == 0) {
diff --git a/src/jdk.jpackage/linux/native/libapplauncher/Package.h b/src/jdk.jpackage/linux/native/libapplauncher/Package.h
index 4c25d2c7bc1d..7cb7f26db734 100644
--- a/src/jdk.jpackage/linux/native/libapplauncher/Package.h
+++ b/src/jdk.jpackage/linux/native/libapplauncher/Package.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020, 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
@@ -28,6 +28,7 @@
#include "tstrings.h"
+#include "LinuxPackage.h"
class AppLauncher;
@@ -37,16 +38,20 @@ class Package {
public:
Package();
- std::string name() const {
+ explicit Package(const PackageDesc& desc);
+
+ const std::string& name() const {
return theName;
}
void initAppLauncher(AppLauncher& appLauncher) const;
- static Package findOwnerOfFile(const std::string& path);
-
private:
- enum Type { Unknown, RPM, DEB };
+ enum Type {
+ Unknown = PACKAGE_TYPE_UNKNOWN,
+ RPM = PACKAGE_TYPE_RPM,
+ DEB = PACKAGE_TYPE_DEB
+ };
Type type;
std::string theName;
diff --git a/src/jdk.jpackage/share/native/applauncher/JvmLauncher.h b/src/jdk.jpackage/share/native/applauncher/JvmLauncher.h
index ab8a9d792a42..4bcaa7f08465 100644
--- a/src/jdk.jpackage/share/native/applauncher/JvmLauncher.h
+++ b/src/jdk.jpackage/share/native/applauncher/JvmLauncher.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020, 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
@@ -86,11 +86,11 @@ JvmlLauncherData* jvmLauncherCreateJvmlLauncherData(JvmlLauncherAPI* api,
JvmlLauncherHandle h, int* size);
int jvmLauncherStartJvm(JvmlLauncherData* jvmArgs, void* JLI_Launch);
-void jvmLauncherLog(const char* format, ...);
+void jvmLauncherLog(const char* file, int line, const char* format, ...);
-#define JP_LOG_ERRMSG(msg) do { jvmLauncherLog((msg)); } while (0)
+#define JP_LOG_ERRMSG(msg) do { jvmLauncherLog(__FILE__, __LINE__, (msg)); } while (0)
#define JP_LOG_ERRNO JP_LOG_ERRMSG(strerror(errno))
-#define JP_LOG_TRACE jvmLauncherLog
+#define JP_LOG_TRACE(format, ...) jvmLauncherLog(__FILE__, __LINE__, format, ##__VA_ARGS__)
#ifdef __cplusplus
diff --git a/src/jdk.jpackage/share/native/applauncher/JvmLauncherLib.c b/src/jdk.jpackage/share/native/applauncher/JvmLauncherLib.c
index 6d3c9f3dd546..fb765ca99025 100644
--- a/src/jdk.jpackage/share/native/applauncher/JvmLauncherLib.c
+++ b/src/jdk.jpackage/share/native/applauncher/JvmLauncherLib.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 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
@@ -98,6 +98,27 @@ static void dumpJvmlLauncherData(const JvmlLauncherData* jvmArgs) {
}
+static const char* filename(const char* path) {
+ const char* slash = strrchr(path, '/');
+#ifdef _WIN32
+ const char* backslash = strrchr(path, '\\');
+#else
+ const char* backslash = NULL;
+#endif
+ const char* sep;
+
+ if (slash == NULL) {
+ sep = backslash;
+ } else if (backslash == NULL) {
+ sep = slash;
+ } else {
+ sep = slash > backslash ? slash : backslash;
+ }
+
+ return sep ? sep + 1 : path;
+}
+
+
int jvmLauncherStartJvm(JvmlLauncherData* jvmArgs, void* JLI_Launch) {
int i;
int exitCode;
@@ -135,7 +156,7 @@ int jvmLauncherStartJvm(JvmlLauncherData* jvmArgs, void* JLI_Launch) {
}
-void jvmLauncherLog(const char* format, ...) {
+void jvmLauncherLog(const char* file, int line, const char* format, ...) {
const char *withLog = getenv("JPACKAGE_DEBUG");
if (!withLog || strcmp(withLog, "true")) {
return;
@@ -151,6 +172,7 @@ void jvmLauncherLog(const char* format, ...) {
#ifdef LINUX
fprintf(stderr, "[%d]: ", getpid());
#endif
+ fprintf(stderr, "%s:%d: ", filename(file), line);
vfprintf(stderr, format, args);
fprintf(stderr, "\n");
#if defined(__GNUC__) && __GNUC__ >= 5
From cd3d04b6dcbb24fac0a4fc6078862150a810f033 Mon Sep 17 00:00:00 2001
From: Serguei Spitsyn
Date: Mon, 8 Jun 2026 22:32:33 +0000
Subject: [PATCH 021/169] 8385249: Enhance diagnostic sessions
Reviewed-by: fmatte, rhalade, kevinw
---
.../linux/native/libattach/VirtualMachineImpl.c | 13 +++++++++----
.../macosx/native/libattach/VirtualMachineImpl.c | 15 ++++++++++-----
2 files changed, 19 insertions(+), 9 deletions(-)
diff --git a/src/jdk.attach/linux/native/libattach/VirtualMachineImpl.c b/src/jdk.attach/linux/native/libattach/VirtualMachineImpl.c
index df4fc54cc966..53d3f743a912 100644
--- a/src/jdk.attach/linux/native/libattach/VirtualMachineImpl.c
+++ b/src/jdk.attach/linux/native/libattach/VirtualMachineImpl.c
@@ -141,13 +141,14 @@ JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_checkPermissions
memset(&sb, 0, sizeof(struct stat));
/*
- * Check that the path is owned by the effective uid/gid of this
- * process. Also check that group/other access is not allowed.
+ * Check that the path itself (not following symbolic links) is
+ * owned by the effective uid/gid of this process and that it is
+ * a socket. Also check that group/other access is not allowed.
*/
uid = geteuid();
gid = getegid();
- res = stat(p, &sb);
+ res = lstat(p, &sb);
if (res != 0) {
/* save errno */
res = errno;
@@ -156,7 +157,11 @@ JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_checkPermissions
if (res == 0) {
char msg[100];
jboolean isError = JNI_FALSE;
- if (sb.st_uid != uid && uid != ROOT_UID) {
+ if (!S_ISSOCK(sb.st_mode)) {
+ snprintf(msg, sizeof(msg),
+ "file is expected to be a socket: S_ISSOCK(sb.st_mode): %d", S_ISSOCK(sb.st_mode));
+ isError = JNI_TRUE;
+ } else if (sb.st_uid != uid && uid != ROOT_UID) {
snprintf(msg, sizeof(msg),
"file should be owned by the current user (which is %d) but is owned by %d", uid, sb.st_uid);
isError = JNI_TRUE;
diff --git a/src/jdk.attach/macosx/native/libattach/VirtualMachineImpl.c b/src/jdk.attach/macosx/native/libattach/VirtualMachineImpl.c
index 5b2579eba39c..e99d872ad1ac 100644
--- a/src/jdk.attach/macosx/native/libattach/VirtualMachineImpl.c
+++ b/src/jdk.attach/macosx/native/libattach/VirtualMachineImpl.c
@@ -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
@@ -180,13 +180,14 @@ JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_checkPermissions
memset(&sb, 0, sizeof(struct stat));
/*
- * Check that the path is owned by the effective uid/gid of this
- * process. Also check that group/other access is not allowed.
+ * Check that the path itself (not following symbolic links) is
+ * owned by the effective uid/gid of this process and that it is
+ * a socket. Also check that group/other access is not allowed.
*/
uid = geteuid();
gid = getegid();
- res = stat(p, &sb);
+ res = lstat(p, &sb);
if (res != 0) {
/* save errno */
res = errno;
@@ -195,7 +196,11 @@ JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_checkPermissions
if (res == 0) {
char msg[100];
jboolean isError = JNI_FALSE;
- if (sb.st_uid != uid && uid != ROOT_UID) {
+ if (!S_ISSOCK(sb.st_mode)) {
+ snprintf(msg, sizeof(msg),
+ "file is expected to be a socket: S_ISSOCK(sb.st_mode): %d", S_ISSOCK(sb.st_mode));
+ isError = JNI_TRUE;
+ } else if (sb.st_uid != uid && uid != ROOT_UID) {
snprintf(msg, sizeof(msg),
"file should be owned by the current user (which is %d) but is owned by %d", uid, sb.st_uid);
isError = JNI_TRUE;
From c1e81159189112ce1123ce92bb756a6c56ca13d6 Mon Sep 17 00:00:00 2001
From: Kevin Walls
Date: Wed, 10 Jun 2026 15:07:16 +0000
Subject: [PATCH 022/169] 8384930: Enhance jstatd
Reviewed-by: dfuchs, skoivu
---
.../classes/sun/tools/jstatd/Jstatd.java | 7 +++----
.../sun/tools/jstatd/RemoteHostImpl.java | 19 ++++++++++++++++---
2 files changed, 19 insertions(+), 7 deletions(-)
diff --git a/src/jdk.jstatd/share/classes/sun/tools/jstatd/Jstatd.java b/src/jdk.jstatd/share/classes/sun/tools/jstatd/Jstatd.java
index cfd1212a67ab..3d6d4ff12752 100644
--- a/src/jdk.jstatd/share/classes/sun/tools/jstatd/Jstatd.java
+++ b/src/jdk.jstatd/share/classes/sun/tools/jstatd/Jstatd.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2004, 2024, 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
@@ -149,10 +149,9 @@ public static void main(String[] args) {
name.append("/").append(rminame);
try {
- remoteHost = new RemoteHostImpl(rmiPort);
ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(rmiFilterPattern);
- RemoteHost stub = (RemoteHost) UnicastRemoteObject.exportObject(
- remoteHost, rmiPort, filter);
+ remoteHost = new RemoteHostImpl(rmiPort, filter);
+ RemoteHost stub = (RemoteHost) UnicastRemoteObject.exportObject(remoteHost, rmiPort, filter);
bind(name.toString(), stub);
System.out.println("jstatd started (bound to " + name.toString() + ")");
System.out.flush();
diff --git a/src/jdk.jstatd/share/classes/sun/tools/jstatd/RemoteHostImpl.java b/src/jdk.jstatd/share/classes/sun/tools/jstatd/RemoteHostImpl.java
index 5db7834e8f1e..53ca1da7f05c 100644
--- a/src/jdk.jstatd/share/classes/sun/tools/jstatd/RemoteHostImpl.java
+++ b/src/jdk.jstatd/share/classes/sun/tools/jstatd/RemoteHostImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2004, 2022, 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
@@ -26,6 +26,7 @@
package sun.tools.jstatd;
import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
import java.nio.*;
import java.io.*;
import java.net.*;
@@ -52,13 +53,20 @@ public class RemoteHostImpl implements RemoteHost, HostListener {
private Set activeVms;
private static RemoteVm rvm;
private final int rmiPort;
+ private final ObjectInputFilter filter;
+ private final Map exportedVMs = new ConcurrentHashMap<>();
public RemoteHostImpl() throws MonitorException {
this(0);
}
public RemoteHostImpl(int rmiPort) throws MonitorException {
+ this(0, null);
+ }
+
+ public RemoteHostImpl(int rmiPort, ObjectInputFilter filter) throws MonitorException {
this.rmiPort = rmiPort;
+ this.filter = filter;
try {
monitoredHost = MonitoredHost.getMonitoredHost("localhost");
} catch (URISyntaxException e) { }
@@ -78,7 +86,8 @@ public RemoteVm attachVm(int lvmid) throws RemoteException, MonitorException {
VmIdentifier vmid = new VmIdentifier(vmidStr);
MonitoredVm mvm = monitoredHost.getMonitoredVm(vmid);
rvm = new RemoteVmImpl((BufferedMonitoredVm)mvm);
- stub = (RemoteVm) UnicastRemoteObject.exportObject(rvm, rmiPort);
+ stub = (RemoteVm) UnicastRemoteObject.exportObject(rvm, rmiPort, filter);
+ exportedVMs.put(stub, (RemoteVmImpl) rvm);
}
catch (URISyntaxException e) {
throw new RuntimeException("Malformed VmIdentifier URI: "
@@ -88,7 +97,11 @@ public RemoteVm attachVm(int lvmid) throws RemoteException, MonitorException {
}
public void detachVm(RemoteVm rvm) throws RemoteException {
- rvm.detach();
+ // Do not invoke on caller-supplied Object.
+ RemoteVm v = exportedVMs.remove(rvm);
+ if (v != null) {
+ v.detach();
+ }
}
public int[] activeVms() throws MonitorException {
From 84ecb95e01c1ef7804c82d65cc199609bd466488 Mon Sep 17 00:00:00 2001
From: Phil Race
Date: Tue, 16 Jun 2026 21:34:47 +0000
Subject: [PATCH 023/169] 8386187: Enhance font rendering
Reviewed-by: psadhukhan, rhalade, jdv
---
.../share/native/libfontmanager/freetypeScaler.c | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/src/java.desktop/share/native/libfontmanager/freetypeScaler.c b/src/java.desktop/share/native/libfontmanager/freetypeScaler.c
index 3bd2b3d07ae5..b294d8f396d4 100644
--- a/src/java.desktop/share/native/libfontmanager/freetypeScaler.c
+++ b/src/java.desktop/share/native/libfontmanager/freetypeScaler.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2007, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2007, 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
@@ -864,10 +864,12 @@ static void CopyGrey4ToGrey8(const void* srcImage, int srcRowBytes,
srcRow += srcRowBytes;
dstRow += dstRowBytes;
- for (i = 0; i < width; i++) {
+ for (i = 0; i < width; i += 2) {
srcValue = *src8++;
- *dstByte++ = Grey4ToAlpha255(srcValue & 0x0f);
*dstByte++ = Grey4ToAlpha255(srcValue >> 4);
+ if (i < width-1) {
+ *dstByte++ = Grey4ToAlpha255(srcValue & 0x0f);
+ }
}
}
}
From be5205ef2667942a172ae9ed1c1fe00f2d56d7ff Mon Sep 17 00:00:00 2001
From: Michael McMahon
Date: Mon, 22 Jun 2026 14:47:58 +0000
Subject: [PATCH 024/169] 8383745: Enhance HTTP Server
Reviewed-by: dfuchs, rhalade, djelinski
---
.../sun/net/httpserver/simpleserver/FileServerHandler.java | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/FileServerHandler.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/FileServerHandler.java
index 08ea357b7f4b..1da06ed9397c 100644
--- a/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/FileServerHandler.java
+++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/FileServerHandler.java
@@ -25,6 +25,7 @@
package sun.net.httpserver.simpleserver;
+import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -221,7 +222,8 @@ private Path mapToPath(HttpExchange exchange, Path root) {
// resolve each path segment against the root
Path path = root;
for (var segment : pathSegment) {
- if (!URIPathSegment.isSupported(segment)) {
+ if (!URIPathSegment.isSupported(segment) || segment.contains(File.separator)) {
+ // checking for separator rules out anomalies like UNCs on Windows
return null; // stop resolution, null results in 404 response
}
path = path.resolve(segment);
From a5142bd728911f1baf414eaf360a39893066b7d6 Mon Sep 17 00:00:00 2001
From: Jayathirth D V
Date: Tue, 23 Jun 2026 06:39:10 +0000
Subject: [PATCH 025/169] 8386552: Enhance GIF loading
Reviewed-by: rhalade, prr, psadhukhan
---
.../sun/imageio/plugins/gif/GIFImageReader.java | 17 +++++++----------
1 file changed, 7 insertions(+), 10 deletions(-)
diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFImageReader.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFImageReader.java
index 69c1bc63f3f6..e584769f3e8f 100644
--- a/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFImageReader.java
+++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFImageReader.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2000, 2022, 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
@@ -35,6 +35,7 @@
import java.awt.image.MultiPixelPackedSampleModel;
import java.awt.image.PixelInterleavedSampleModel;
import java.awt.image.SampleModel;
+import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteOrder;
@@ -650,7 +651,8 @@ private int locateImage(int imageIndex) throws IIOException {
// Read blocks of 1-255 bytes, stop at a 0-length block
private byte[] concatenateBlocks() throws IOException {
- byte[] data = new byte[0];
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream(255);
+ byte[] data = new byte[255];
while (true) {
int length = stream.readUnsignedByte();
if (length == 0) {
@@ -660,15 +662,10 @@ private byte[] concatenateBlocks() throws IOException {
stream.skipBytes(length);
continue;
}
- byte[] subBlockData =
- ReaderUtil.staggeredReadByteStream(stream, length);
- byte[] newData = new byte[data.length + length];
- System.arraycopy(data, 0, newData, 0, data.length);
- System.arraycopy(subBlockData, 0, newData,
- data.length, length);
- data = newData;
+ stream.readFully(data, 0, length);
+ buffer.write(data, 0, length);
}
- return data;
+ return buffer.toByteArray();
}
// Stream must be positioned at start of metadata for 'currIndex'
From 5d6bd8f24d933a3a525d6199e177eb7b71b18e75 Mon Sep 17 00:00:00 2001
From: Kevin Walls
Date: Tue, 23 Jun 2026 10:07:47 +0000
Subject: [PATCH 026/169] 8387075: RemoteHostImpl constructor correction
(jstatd)
Reviewed-by: dfuchs
---
.../share/classes/sun/tools/jstatd/RemoteHostImpl.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/jdk.jstatd/share/classes/sun/tools/jstatd/RemoteHostImpl.java b/src/jdk.jstatd/share/classes/sun/tools/jstatd/RemoteHostImpl.java
index 53ca1da7f05c..9f050fbbb99c 100644
--- a/src/jdk.jstatd/share/classes/sun/tools/jstatd/RemoteHostImpl.java
+++ b/src/jdk.jstatd/share/classes/sun/tools/jstatd/RemoteHostImpl.java
@@ -61,7 +61,7 @@ public RemoteHostImpl() throws MonitorException {
}
public RemoteHostImpl(int rmiPort) throws MonitorException {
- this(0, null);
+ this(rmiPort, null);
}
public RemoteHostImpl(int rmiPort, ObjectInputFilter filter) throws MonitorException {
From 01bf14b163282e2f1df5198e7d393f1473256a67 Mon Sep 17 00:00:00 2001
From: Jan Lahoda
Date: Wed, 24 Jun 2026 04:59:50 +0000
Subject: [PATCH 027/169] 8384736: Enhance JShell
Reviewed-by: rhalade, dbalek, asotona
---
.../execution/JdiDefaultExecutionControl.java | 69 +++++++---
.../jdk/jshell/execution/JdiInitiator.java | 3 +-
.../execution/RemoteExecutionControl.java | 40 +++++-
.../execution/StreamingExecutionControl.java | 55 +++++---
.../jdk/jshell/RemoteAgentHandshakeTest.java | 126 ++++++++++++++++++
5 files changed, 255 insertions(+), 38 deletions(-)
create mode 100644 test/langtools/jdk/jshell/RemoteAgentHandshakeTest.java
diff --git a/src/jdk.jshell/share/classes/jdk/jshell/execution/JdiDefaultExecutionControl.java b/src/jdk.jshell/share/classes/jdk/jshell/execution/JdiDefaultExecutionControl.java
index 1df6dc5191b3..b4489a8071cb 100644
--- a/src/jdk.jshell/share/classes/jdk/jshell/execution/JdiDefaultExecutionControl.java
+++ b/src/jdk.jshell/share/classes/jdk/jshell/execution/JdiDefaultExecutionControl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.function.Consumer;
import com.sun.jdi.BooleanValue;
import com.sun.jdi.ClassNotLoadedException;
import com.sun.jdi.Field;
@@ -48,8 +47,12 @@
import com.sun.jdi.VMDisconnectedException;
import com.sun.jdi.VirtualMachine;
import java.io.PrintStream;
+import java.security.SecureRandom;
import java.time.Duration;
+import java.util.Arrays;
+import java.util.Base64;
import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
import jdk.jshell.JShellConsole;
import jdk.jshell.execution.JdiDefaultExecutionControl.JdiStarter.TargetDescription;
import jdk.jshell.spi.ExecutionControl;
@@ -103,6 +106,7 @@ public class JdiDefaultExecutionControl extends JdiExecutionControl {
* @throws IOException if there are errors in set-up
*/
static ExecutionControl create(ExecutionEnv env, Map parameters, String remoteAgent, int timeout, JdiStarter starter) throws IOException {
+ Process processToStop = null;
try (final ServerSocket listener = new ServerSocket(0, 1, InetAddress.getLoopbackAddress())) {
// timeout on I/O-socket
listener.setSoTimeout(timeout);
@@ -146,15 +150,30 @@ public void closeDown() {
};
// Set-up the JDI connection
- TargetDescription target = starter.start(augmentedEnv, parameters, port);
+ TargetDescription target = starter.start(augmentedEnv, parameters, -1);
VirtualMachine vm = target.vm();
- Process process = target.process();
+ Process process = processToStop = target.process();
+ byte[] secret;
+
+ OutputStream stdout = process.getOutputStream();
+
+ //send the port number:
+ stdout.write(("" + port + "\n").getBytes());
+ stdout.flush();
+
+ //generate a secret that will help us validate the correct remote is attached:
+ secret = new byte[64];
+ new SecureRandom().nextBytes(secret);
+ stdout.write((Base64.getEncoder().encodeToString(secret) + "\n").getBytes());
+ stdout.flush();
+
+ CompletableFuture agentStopped = new CompletableFuture<>();
- List> deathListeners = new ArrayList<>();
Util.detectJdiExitEvent(vm, s -> {
- for (Consumer h : deathListeners) {
- h.accept(s);
- }
+ agentStopped.complete(s);
+ });
+ process.onExit().thenRun(() -> {
+ agentStopped.complete("Remote process died.");
});
// Set-up the commands/reslts on the socket. Piggy-back snippet
@@ -162,6 +181,17 @@ public void closeDown() {
Socket socket = listener.accept();
// out before in -- match remote creation so we don't hang
OutputStream out = socket.getOutputStream();
+ InputStream in = socket.getInputStream();
+
+ socket.setSoTimeout(timeout);
+
+ //verify the secret:
+ byte[] receivedSecret = in.readNBytes(secret.length);
+
+ if (!Arrays.equals(secret, receivedSecret)) {
+ throw new IOException("Incorrect secret received.");
+ }
+
Map outputs = new HashMap<>();
outputs.put("out", env.userOut());
outputs.put("err", env.userErr());
@@ -176,10 +206,17 @@ public void closeDown() {
outputs.put("consoleInput", consoleOutput);
input.put("consoleOutput", consoleOutput.sinkInput);
}
- return remoteInputOutput(socket.getInputStream(), out, outputs, input,
+ ExecutionControl result = remoteInputOutput(in, out, outputs, input,
(objIn, objOut) -> new JdiDefaultExecutionControl(env,
- objOut, objIn, vm, process, remoteAgent, deathListeners));
- }
+ objOut, objIn, vm, process, remoteAgent,
+ agentStopped));
+ processToStop = null;
+ return result;
+ } finally {
+ if (processToStop != null) {
+ processToStop.destroy();
+ }
+}
}
/**
@@ -191,7 +228,7 @@ public void closeDown() {
private JdiDefaultExecutionControl(ExecutionEnv env,
ObjectOutput cmdout, ObjectInput cmdin,
VirtualMachine vm, Process process, String remoteAgent,
- List> deathListeners) {
+ CompletableFuture agentStopped) {
super(cmdout, cmdin);
this.vm = vm;
this.process = process;
@@ -199,8 +236,10 @@ private JdiDefaultExecutionControl(ExecutionEnv env,
// We have now succeeded in establishing the connection.
// If there is an exit now it propagates all the way up
// and the VM should be disposed of.
- deathListeners.add(s -> env.closeDown());
- deathListeners.add(s -> disposeVM());
+ agentStopped.thenAccept(s -> {
+ env.closeDown();
+ disposeVM();
+ });
}
/**
@@ -356,7 +395,7 @@ public interface JdiStarter {
*
* @param env the execution context
* @param parameters additional execution parameters
- * @param port the port to which the remote process should connect
+ * @param port the value is always {@code -1}
* @return a description of the started external process
* @throws RuntimeException if the process cannot be started
* @throws Error if the process cannot be started
diff --git a/src/jdk.jshell/share/classes/jdk/jshell/execution/JdiInitiator.java b/src/jdk.jshell/share/classes/jdk/jshell/execution/JdiInitiator.java
index fd908745d81d..13497f91edce 100644
--- a/src/jdk.jshell/share/classes/jdk/jshell/execution/JdiInitiator.java
+++ b/src/jdk.jshell/share/classes/jdk/jshell/execution/JdiInitiator.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2016, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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
@@ -216,6 +216,7 @@ protected void runListenProcess(String jdiAddress,
ProcessBuilder pb = new ProcessBuilder(args);
pb.redirectError(crashErrorFile);
pb.redirectOutput(crashOutputFile);
+ pb.redirectInput(ProcessBuilder.Redirect.PIPE);
process = pb.start();
setupVM.processStarted(process);
diff --git a/src/jdk.jshell/share/classes/jdk/jshell/execution/RemoteExecutionControl.java b/src/jdk.jshell/share/classes/jdk/jshell/execution/RemoteExecutionControl.java
index 3add8dd7a2b3..75c6e17846cd 100644
--- a/src/jdk.jshell/share/classes/jdk/jshell/execution/RemoteExecutionControl.java
+++ b/src/jdk.jshell/share/classes/jdk/jshell/execution/RemoteExecutionControl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2014, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 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
@@ -24,11 +24,15 @@
*/
package jdk.jshell.execution;
+import java.io.IOException;
import java.io.InputStream;
+import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
+import java.io.Reader;
import java.lang.reflect.Method;
import java.net.Socket;
+import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
@@ -45,6 +49,12 @@
* {@link DirectExecutionControl} which adds stop support controlled by
* an external process. Designed to work with {@link JdiDefaultExecutionControl}.
*
+ *
The initial handshake with {@link JdiDefaultExecutionControl} is as follows:
+ * the first command line argument is {@code -1}. The port number to which the
+ * agent should connect is passed on the first line on stdin. On the second line
+ * of stdin, an Base64-encoded byte array is sent, which should be sent to
+ * the socket immediately after the connection is established.
+ *
* @author Jan Lahoda
* @author Robert Field
* @since 9
@@ -61,9 +71,24 @@ public class RemoteExecutionControl extends DirectExecutionControl implements Ex
*/
public static void main(String[] args) throws Exception {
String loopBack = null;
- Socket socket = new Socket(loopBack, Integer.parseInt(args[0]));
+ int commandLinePort = Integer.parseInt(args[0]);
+ int port;
+ byte[] secret;
+ if (commandLinePort == (-1)) {
+ Reader r = new InputStreamReader(System.in);
+ port = Integer.parseInt(readLine(r));
+ secret = Base64.getDecoder().decode(readLine(r));
+ } else {
+ port = commandLinePort;
+ secret = null;
+ }
+ Socket socket = new Socket(loopBack, port);
InputStream inStream = socket.getInputStream();
OutputStream outStream = socket.getOutputStream();
+ if (secret != null) {
+ outStream.write(secret);
+ outStream.flush();
+ }
Map> outputs = new HashMap<>();
outputs.put("out", st -> System.setOut(new PrintStream(st, true, System.out.charset())));
outputs.put("err", st -> System.setErr(new PrintStream(st, true, System.err.charset())));
@@ -74,6 +99,17 @@ public static void main(String[] args) throws Exception {
forwardExecutionControlAndIO(new RemoteExecutionControl(), inStream, outStream, outputs, input);
}
+ private static String readLine(Reader r) throws IOException {
+ StringBuilder line = new StringBuilder();
+ int read;
+
+ while ((read = r.read()) != (-1) && read != '\n') {
+ line.append((char)read);
+ }
+
+ return line.toString();
+ }
+
// These three variables are used by the main JShell process in interrupting
// the running process. Access is via JDI, so the reference is not visible
// to code inspection.
diff --git a/src/jdk.jshell/share/classes/jdk/jshell/execution/StreamingExecutionControl.java b/src/jdk.jshell/share/classes/jdk/jshell/execution/StreamingExecutionControl.java
index d4df40dac9a7..5f5372b780fc 100644
--- a/src/jdk.jshell/share/classes/jdk/jshell/execution/StreamingExecutionControl.java
+++ b/src/jdk.jshell/share/classes/jdk/jshell/execution/StreamingExecutionControl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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,6 +27,8 @@
import java.io.EOFException;
import java.io.IOException;
import java.io.ObjectInput;
+import java.io.ObjectInputFilter;
+import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import jdk.jshell.JShellException;
import jdk.jshell.spi.ExecutionControl;
@@ -45,16 +47,23 @@ public class StreamingExecutionControl implements ExecutionControl {
private final ObjectOutput out;
private final ObjectInput in;
+ private final ObjectInputFilterImpl filter = new ObjectInputFilterImpl();
/**
* Creates an instance.
*
* @param out the output for commands
- * @param in the input for command responses
+ * @param in the input for command responses; must be an {@link ObjectInputStream}
+ * @throws IllegalArgumentException if {@code in} is not an {@linkplain ObjectInputStream}
*/
public StreamingExecutionControl(ObjectOutput out, ObjectInput in) {
this.out = out;
this.in = in;
+ if (in instanceof ObjectInputStream ois) {
+ ois.setObjectInputFilter(filter);
+ } else {
+ throw new IllegalArgumentException("The 'in' parameter must be an ObjectInputStream.");
+ }
}
@Override
@@ -162,27 +171,16 @@ public void stop()
}
/**
- * @throws ExecutionControl.UserException {@inheritDoc}
- * @throws ExecutionControl.ResolutionException {@inheritDoc}
- * @throws ExecutionControl.StoppedException {@inheritDoc}
- * @throws ExecutionControl.EngineTerminationException {@inheritDoc}
- * @throws ExecutionControl.NotImplementedException {@inheritDoc}
+ * {@inheritDoc}
+ *
+ * @apiNote This method always throws {@link NotImplementedException}.
+ *
+ * @throws ExecutionControl.NotImplementedException always thrown.
*/
@Override
public Object extensionCommand(String command, Object arg)
- throws RunException, EngineTerminationException, InternalException {
- try {
- writeCommand(command);
- out.writeObject(arg);
- out.flush();
- // Retrieve and report results from the remote agent.
- readAndReportExecutionResult();
- Object result = in.readObject();
- return result;
- } catch (IOException | ClassNotFoundException ex) {
- throw new EngineTerminationException("Exception transmitting remote extensionCommand: "
- + command + " -- " + ex);
- }
+ throws NotImplementedException {
+ throw new NotImplementedException("extensionCommand not implemented.");
}
/**
@@ -379,4 +377,21 @@ private ResolutionException readResolutionException() throws IOException, ClassN
StackTraceElement[] elems = (StackTraceElement[]) in.readObject();
return new ResolutionException(id, elems);
}
+
+ private static final class ObjectInputFilterImpl implements ObjectInputFilter {
+
+ @Override
+ public ObjectInputFilter.Status checkInput(ObjectInputFilter.FilterInfo filterInfo) {
+ if (filterInfo.serialClass() instanceof Class> clazz) {
+ while (clazz.isArray()) {
+ clazz = clazz.componentType();
+ }
+ return clazz == StackTraceElement.class ||
+ clazz == boolean.class ? Status.ALLOWED
+ : Status.REJECTED;
+ }
+
+ return Status.ALLOWED;
+ }
+ }
}
diff --git a/test/langtools/jdk/jshell/RemoteAgentHandshakeTest.java b/test/langtools/jdk/jshell/RemoteAgentHandshakeTest.java
new file mode 100644
index 000000000000..0f0ba0c9a963
--- /dev/null
+++ b/test/langtools/jdk/jshell/RemoteAgentHandshakeTest.java
@@ -0,0 +1,126 @@
+/*
+ * 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 8384736
+ * @summary Verify the correct handshake is used for remote agents.
+ * @run junit RemoteAgentHandshakeTest
+ */
+
+import java.time.Duration;
+import java.util.*;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import jdk.jfr.consumer.RecordingStream;
+import jdk.jshell.JShell;
+import jdk.jshell.execution.JdiExecutionControlProvider;
+import jdk.jshell.execution.RemoteExecutionControl;
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class RemoteAgentHandshakeTest {
+
+ @Test
+ public void testCustomAgentOldHandshake() throws Exception {
+ assertThrows(IllegalStateException.class, () -> {
+ try (JShell jshell =
+ JShell.builder()
+ .executionEngine(new JdiExecutionControlProvider(),
+ Map.of(JdiExecutionControlProvider.PARAM_REMOTE_AGENT,
+ OldHandShake.class.getName()))
+ .build()) {
+ jshell.eval("1");
+ }
+ });
+ }
+
+ @Test
+ public void testCustomAgentNewHandshake() throws Exception {
+ try (JShell jshell =
+ JShell.builder()
+ .executionEngine(new JdiExecutionControlProvider(),
+ Map.of(JdiExecutionControlProvider.PARAM_REMOTE_AGENT,
+ NewHandShake.class.getName()))
+ .build()) {
+ jshell.eval("1");
+ }
+ }
+
+ @Test
+ public void testDefaultAgentUsesNewHandshake() throws Exception {
+ try (var rs = new RecordingStream()) {
+ rs.enable("jdk.ProcessStart");
+ rs.enable("jdk.CPULoad").withPeriod(Duration.ofMillis(10));
+
+ CompletableFuture command = new CompletableFuture<>();
+
+ rs.onEvent("jdk.ProcessStart", evt -> {
+ String commandLine = evt.getValue("command");
+
+ if (commandLine.contains("RemoteExecutionControl")) {
+ command.complete(commandLine);
+ }
+ });
+
+ CountDownLatch running = new CountDownLatch(1);
+
+ rs.onEvent("jdk.CPULoad", evt -> {
+ running.countDown();
+ });
+
+ rs.startAsync();
+
+ running.await();
+
+ try (JShell jshell = JShell.create()) {
+ jshell.eval("1");
+ }
+
+ String commandLine = command.get(10, TimeUnit.SECONDS);
+ if (!commandLine.endsWith(" -1")) {
+ throw new AssertionError("Does not contain a correct port: '" + commandLine + "'");
+ }
+ }
+ }
+
+ public static class OldHandShake extends RemoteExecutionControl {
+ public static void main(String[] args) throws Exception {
+ if (Integer.parseInt(args[0]) <= 0) {
+ throw new AssertionError("Unexpected port number!");
+ }
+
+ RemoteExecutionControl.main(args);
+ }
+ }
+
+ public static class NewHandShake extends RemoteExecutionControl {
+ public static void main(String[] args) throws Exception {
+ if (Integer.parseInt(args[0]) != (-1)) {
+ throw new AssertionError("Unexpected port number!");
+ }
+
+ RemoteExecutionControl.main(args);
+ }
+ }
+}
From 70b65c2d2c7cae3d8cd054eae54554a121462a44 Mon Sep 17 00:00:00 2001
From: Jaikiran Pai
Date: Wed, 24 Jun 2026 10:25:50 +0000
Subject: [PATCH 028/169] 8383047: Enhance LDAP logins
Reviewed-by: rhalade, aefimov, djelinski, vyazici
---
.../security/auth/module/JndiLoginModule.java | 157 ++++++++++--------
1 file changed, 87 insertions(+), 70 deletions(-)
diff --git a/src/jdk.security.auth/share/classes/com/sun/security/auth/module/JndiLoginModule.java b/src/jdk.security.auth/share/classes/com/sun/security/auth/module/JndiLoginModule.java
index 4694134e3880..12a687f83d16 100644
--- a/src/jdk.security.auth/share/classes/com/sun/security/auth/module/JndiLoginModule.java
+++ b/src/jdk.security.auth/share/classes/com/sun/security/auth/module/JndiLoginModule.java
@@ -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
@@ -25,12 +25,23 @@
package com.sun.security.auth.module;
-import javax.security.auth.*;
-import javax.security.auth.callback.*;
-import javax.security.auth.login.*;
-import javax.security.auth.spi.*;
-import javax.naming.*;
-import javax.naming.directory.*;
+import javax.naming.InitialContext;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.SearchResult;
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.FailedLoginException;
+import javax.security.auth.login.LoginException;
+import javax.security.auth.spi.LoginModule;
import java.util.Map;
import java.util.LinkedList;
@@ -200,9 +211,8 @@ public class JndiLoginModule implements LoginModule {
private static final String CRYPT = "{crypt}";
private static final String USER_PWD = "userPassword";
- private static final String USER_UID = "uidNumber";
- private static final String USER_GID = "gidNumber";
- private static final String GROUP_ID = "gidNumber";
+ private static final String UID_NUMBER = "uidNumber";
+ private static final String GID_NUMBER = "gidNumber";
private static final String NAME = "javax.security.auth.login.name";
private static final String PWD = "javax.security.auth.login.password";
@@ -381,7 +391,7 @@ public boolean login() throws LoginException {
*/
public boolean commit() throws LoginException {
- if (succeeded == false) {
+ if (!succeeded) {
return false;
} else {
if (subject.isReadOnly()) {
@@ -389,11 +399,11 @@ public boolean commit() throws LoginException {
throw new LoginException ("Subject is Readonly");
}
// add Principals to the Subject
- if (!subject.getPrincipals().contains(userPrincipal))
+ if (userPrincipal != null && !subject.getPrincipals().contains(userPrincipal))
subject.getPrincipals().add(userPrincipal);
- if (!subject.getPrincipals().contains(UIDPrincipal))
+ if (UIDPrincipal != null && !subject.getPrincipals().contains(UIDPrincipal))
subject.getPrincipals().add(UIDPrincipal);
- if (!subject.getPrincipals().contains(GIDPrincipal))
+ if (GIDPrincipal != null && !subject.getPrincipals().contains(GIDPrincipal))
subject.getPrincipals().add(GIDPrincipal);
for (int i = 0; i < supplementaryGroups.size(); i++) {
if (!subject.getPrincipals().contains
@@ -436,9 +446,9 @@ public boolean abort() throws LoginException {
System.out.println("\t\t[JndiLoginModule]: " +
"aborted authentication failed");
- if (succeeded == false) {
+ if (!succeeded) {
return false;
- } else if (succeeded == true && commitSucceeded == false) {
+ } else if (!commitSucceeded) {
// Clean out state
succeeded = false;
@@ -510,34 +520,25 @@ public boolean logout() throws LoginException {
* @param getPasswdFromSharedState boolean that tells this method whether
* to retrieve the password from the sharedState.
*/
- private void attemptAuthentication(boolean getPasswdFromSharedState)
- throws LoginException {
-
+ private void attemptAuthentication(boolean getPasswdFromSharedState) throws LoginException {
String encryptedPassword = null;
-
// first get the username and password
getUsernamePassword(getPasswdFromSharedState);
-
+ if (username == null) {
+ throw new FailedLoginException("No username provided");
+ }
try {
-
// get the user's passwd entry from the user provider URL
InitialContext iCtx = new InitialContext();
ctx = (DirContext)iCtx.lookup(userProvider);
-
- /*
- SearchControls controls = new SearchControls
- (SearchControls.ONELEVEL_SCOPE,
- 0,
- 5000,
- new String[] { USER_PWD },
- false,
- false);
- */
-
- SearchControls controls = new SearchControls();
- NamingEnumeration ne = ctx.search("",
- "(uid=" + username + ")",
- controls);
+ // Search for the user with the given "uid" and obtain all their attributes.
+ // Then verify the user's password attribute matches with the one set on
+ // this LoginModule
+ NamingEnumeration ne = ctx.search("", new BasicAttributes("uid", username));
+ // Create an exception instance beforehand, to use it in the case of a failed login due
+ // to a missing user or due to an incorrect credential. It's OK for it to go unused
+ // in the case of a successful authentication.
+ final FailedLoginException failedLogin = new FailedLoginException("Login failed");
if (ne.hasMore()) {
SearchResult result = ne.next();
Attributes attributes = result.getAttributes();
@@ -559,14 +560,17 @@ private void attemptAuthentication(boolean getPasswdFromSharedState)
// authentication for LDAP is recommended only for secure
// channels. For nonsecure channels, SSL is recommended.
+ final boolean passwordMatches;
Attribute pwd = attributes.get(USER_PWD);
- String encryptedPwd = new String((byte[])pwd.get(), UTF_8);
- encryptedPassword = encryptedPwd.substring(CRYPT.length());
-
- // check the password
- if (verifyPassword
- (encryptedPassword, new String(password)) == true) {
-
+ if (pwd == null) {
+ passwordMatches = false;
+ } else {
+ String encryptedPwd = new String((byte[]) pwd.get(), UTF_8);
+ encryptedPassword = encryptedPwd.substring(CRYPT.length());
+ // check the password
+ passwordMatches = verifyPassword(encryptedPassword, new String(password));
+ }
+ if (passwordMatches) {
// authentication succeeded
if (debug)
System.out.println("\t\t[JndiLoginModule] " +
@@ -577,7 +581,7 @@ private void attemptAuthentication(boolean getPasswdFromSharedState)
if (debug)
System.out.println("\t\t[JndiLoginModule] " +
"attemptAuthentication() failed");
- throw new FailedLoginException("Login incorrect");
+ throw failedLogin;
}
// save input as shared state only if
@@ -592,27 +596,36 @@ private void attemptAuthentication(boolean getPasswdFromSharedState)
// create the user principal
userPrincipal = new UnixPrincipal(username);
- // get the UID
- Attribute uid = attributes.get(USER_UID);
- String uidNumber = (String)uid.get();
- UIDPrincipal = new UnixNumericUserPrincipal(uidNumber);
- if (debug && uidNumber != null) {
- System.out.println("\t\t[JndiLoginModule] " +
- "user: '" + username + "' has UID: " +
+ // get the uidNumber
+ Attribute uid = attributes.get(UID_NUMBER);
+ if (uid == null) {
+ UIDPrincipal = null;
+ } else {
+ String uidNumber = (String) uid.get();
+ UIDPrincipal = uidNumber == null
+ ? null
+ : new UnixNumericUserPrincipal(uidNumber);
+ if (debug && UIDPrincipal != null) {
+ System.out.println("\t\t[JndiLoginModule] " +
+ "user: '" + username + "' has uidNumber: " +
uidNumber);
+ }
}
-
// get the GID
- Attribute gid = attributes.get(USER_GID);
- String gidNumber = (String)gid.get();
- GIDPrincipal = new UnixNumericGroupPrincipal
- (gidNumber, true);
- if (debug && gidNumber != null) {
- System.out.println("\t\t[JndiLoginModule] " +
- "user: '" + username + "' has GID: " +
+ Attribute gid = attributes.get(GID_NUMBER);
+ if (gid == null) {
+ GIDPrincipal = null;
+ } else {
+ String gidNumber = (String) gid.get();
+ GIDPrincipal = gidNumber == null
+ ? null
+ : new UnixNumericGroupPrincipal(gidNumber, true);
+ if (debug && GIDPrincipal != null) {
+ System.out.println("\t\t[JndiLoginModule] " +
+ "user: '" + username + "' has gidNumber: " +
gidNumber);
+ }
}
-
// get the supplementary groups from the group provider URL
ctx = (DirContext)iCtx.lookup(groupProvider);
ne = ctx.search("", new BasicAttributes("memberUid", username));
@@ -621,13 +634,19 @@ private void attemptAuthentication(boolean getPasswdFromSharedState)
result = ne.next();
attributes = result.getAttributes();
- gid = attributes.get(GROUP_ID);
- String suppGid = (String)gid.get();
- if (!gidNumber.equals(suppGid)) {
+ gid = attributes.get(GID_NUMBER);
+ if (gid == null) {
+ continue;
+ }
+ String suppGid = (String) gid.get();
+ if (suppGid == null) {
+ continue;
+ }
+ if (GIDPrincipal == null || !GIDPrincipal.getName().equals(suppGid)) {
UnixNumericGroupPrincipal suppPrincipal =
new UnixNumericGroupPrincipal(suppGid, false);
supplementaryGroups.add(suppPrincipal);
- if (debug && suppGid != null) {
+ if (debug) {
System.out.println("\t\t[JndiLoginModule] " +
"user: '" + username +
"' has Supplementary Group: " +
@@ -641,17 +660,15 @@ private void attemptAuthentication(boolean getPasswdFromSharedState)
if (debug) {
System.out.println("\t\t[JndiLoginModule]: User not found");
}
- throw new FailedLoginException("User not found");
+ throw failedLogin;
}
} catch (NamingException ne) {
- // bad username
if (debug) {
- System.out.println("\t\t[JndiLoginModule]: User not found");
+ System.out.println("\t\t[JndiLoginModule]: exception when finding user");
ne.printStackTrace();
}
- throw new FailedLoginException("User not found");
+ throw new FailedLoginException("Login failed");
}
-
// authentication succeeded
}
From da7c28fa49a8e9486c53a41961a4286b770696a3 Mon Sep 17 00:00:00 2001
From: Justin Lu
Date: Wed, 24 Jun 2026 16:31:37 +0000
Subject: [PATCH 029/169] 8386783: WebRowSet empty elements read value of
previous element
Reviewed-by: naoto, joehw, lancea
---
.../rowset/RowSetResourceBundle.properties | 4 ----
.../rowset/internal/WebRowSetXmlReader.java | 8 ++++++--
.../internal/XmlReaderContentHandler.java | 19 ++++++++++++++-----
.../com/sun/rowset/internal/XmlResolver.java | 10 +++++-----
.../webrowset/CommonWebRowSetTests.java | 6 +++++-
5 files changed, 30 insertions(+), 17 deletions(-)
diff --git a/src/java.sql.rowset/share/classes/com/sun/rowset/RowSetResourceBundle.properties b/src/java.sql.rowset/share/classes/com/sun/rowset/RowSetResourceBundle.properties
index ecb246e24463..54ae42cf991a 100644
--- a/src/java.sql.rowset/share/classes/com/sun/rowset/RowSetResourceBundle.properties
+++ b/src/java.sql.rowset/share/classes/com/sun/rowset/RowSetResourceBundle.properties
@@ -138,10 +138,6 @@ syncrsimpl.valtores = Value to be resolved can either be in the database or in c
#WebRowSetXmlReader exception
wrsxmlreader.readxml = readXML : {0}
-wrsxmlreader.stdschema = readXML : Cannot locate standard WebRowSet schema: {0}.
-wrsxmlreader.schemaloc = readXML : Malformed xsi:schemaLocation: {0}.
-wrsxmlreader.schema = readXML : Non-standard schema specified for namespace {0} : {1}.
-wrsxmlreader.entity = readXML : external entity {0} other than the standard schema is not allowed.
#WebRowSetXmlWriter exceptions
wrsxmlwriter.ioex = IOException : {0}
diff --git a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/WebRowSetXmlReader.java b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/WebRowSetXmlReader.java
index 2c105bd4ae88..67d0db98c7f1 100644
--- a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/WebRowSetXmlReader.java
+++ b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/WebRowSetXmlReader.java
@@ -54,6 +54,11 @@ public class WebRowSetXmlReader implements XmlReader, Serializable {
static final String WEBROWSET_XSD = "webrowset.xsd";
private static final String VALIDATION_PROPERTY = "jdk.sql.rowset.webrowsetValidation";
+ // Error messages
+ private static final String STD_SCHEMA_MESSAGE =
+ "readXML : Cannot locate standard WebRowSet schema: %s.".formatted(WEBROWSET_XSD);
+
+
private JdbcRowSetResourceBundle resBundle;
public WebRowSetXmlReader(){
@@ -167,8 +172,7 @@ private void readXml(WebRowSet caller, InputSource is) throws SQLException {
private Schema getSchema() throws SAXException {
URL stdSchema = WebRowSet.class.getResource(WEBROWSET_XSD);
if (stdSchema == null) {
- throw new SAXException(MessageFormat.format(resBundle.handleGetObject(
- "wrsxmlreader.stdschema").toString(), WEBROWSET_XSD));
+ throw new SAXException(STD_SCHEMA_MESSAGE);
}
SchemaFactory sf = SchemaFactory.newDefaultInstance();
diff --git a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlReaderContentHandler.java b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlReaderContentHandler.java
index c70a7bc142a2..8e1222a889ff 100644
--- a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlReaderContentHandler.java
+++ b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlReaderContentHandler.java
@@ -84,7 +84,7 @@ public class XmlReaderContentHandler extends DefaultHandler {
private String propertyValue;
private String metaDataValue;
- private int tag;
+ private int tag = -1;
private int state;
private WebRowSetImpl rs;
@@ -386,6 +386,10 @@ public class XmlReaderContentHandler extends DefaultHandler {
private static final String XSI_NS = "http://www.w3.org/2001/XMLSchema-instance";
+ // Error messages
+ private static final String SCHEMA_LOC_MESSAGE = "readXML : Malformed xsi:schemaLocation: %s.";
+ private static final String SCHEMA_MESSAGE = "readXML : Non-standard schema specified for namespace %s : %s.";
+
/**
* A constant indicating the state of this XmlReaderContentHandler
* object in which it has not yet been called by the SAX parser and therefore
@@ -660,8 +664,7 @@ private void checkSchemaLocation(Attributes attributes) throws SAXException {
String[] tokens = schemaLocation.trim().split("\\s+");
if (tokens.length % 2 != 0) {
- throw new SAXException(MessageFormat.format(
- resBundle.handleGetObject("wrsxmlreader.schemaloc").toString(), schemaLocation));
+ throw new SAXException(SCHEMA_LOC_MESSAGE.formatted(schemaLocation));
}
for (int i = 0; i < tokens.length; i += 2) {
@@ -669,8 +672,7 @@ private void checkSchemaLocation(Attributes attributes) throws SAXException {
String xsd = tokens[i + 1];
if (!isWebRowSetSchema(xsd)) {
- throw new SAXException(MessageFormat.format(
- resBundle.handleGetObject("wrsxmlreader.schema").toString(), ns, xsd));
+ throw new SAXException(SCHEMA_MESSAGE.formatted(ns, xsd));
}
}
}
@@ -941,6 +943,13 @@ public void characters(char[] ch, int start, int length) throws SAXException {
try {
switch (getState()) {
case PROPERTIES:
+
+ // If tag is -1, startElement has not registered a valid tag
+ // Thus, we are in whitespace between elements, and should exit
+ if (tag == -1) {
+ break;
+ }
+
propertyValue = new String(ch, start, length);
/**
diff --git a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlResolver.java b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlResolver.java
index 856d7d734150..96edbfa1f195 100644
--- a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlResolver.java
+++ b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlResolver.java
@@ -25,7 +25,6 @@
package com.sun.rowset.internal;
-import com.sun.rowset.JdbcRowSetResourceBundle;
import org.xml.sax.*;
import org.xml.sax.EntityResolver;
@@ -34,7 +33,6 @@
import javax.sql.rowset.WebRowSet;
import java.io.IOException;
import java.io.StringReader;
-import java.text.MessageFormat;
/**
* An implementation of the EntityResolver interface, which
@@ -44,6 +42,10 @@
public class XmlResolver implements EntityResolver {
//The standard WebRowSet XML Schema as defined in WebRowSet
public static final String STD_SCHEMA_ID = "http://xmlns.jcp.org/xml/ns//jdbc/webrowset.xsd";
+ // Error messages
+ private static final String ENTITY_MESSAGE =
+ "readXML : external entity %s other than the standard schema is not allowed.";
+
@Override
public InputSource resolveEntity(String publicId, String systemId)
@@ -57,8 +59,6 @@ public InputSource resolveEntity(String publicId, String systemId)
}
// reports error upon any external entity other than the standard schema
- throw new SAXException(MessageFormat.format(
- JdbcRowSetResourceBundle.getJdbcRowSetResourceBundle().handleGetObject(
- "wrsxmlreader.entity").toString(), systemId));
+ throw new SAXException(ENTITY_MESSAGE.formatted(systemId));
}
}
diff --git a/test/jdk/javax/sql/test/rowset/webrowset/CommonWebRowSetTests.java b/test/jdk/javax/sql/test/rowset/webrowset/CommonWebRowSetTests.java
index d8f1ea4ad584..79839c223240 100644
--- a/test/jdk/javax/sql/test/rowset/webrowset/CommonWebRowSetTests.java
+++ b/test/jdk/javax/sql/test/rowset/webrowset/CommonWebRowSetTests.java
@@ -578,10 +578,14 @@ public void testLegacyXmlWithValidation(WebRowSet wrs)
@MethodSource("rowSetType")
public void testLegacyXmlWithoutValidation(WebRowSet wrs)
throws Exception {
- String legacyXml = getLegacyXml();
+ String legacyXml = getLegacyXml()
+ // Exercise the path where an empty element follows a value bearing
+ // element. This is for JDK-8386783
+ .replace("jdbc:derby://localhost:1527/testDB;create=true", "");
withRowSetValidation("false", () -> {
try {
wrs.readXml(new StringReader(legacyXml));
+ assertNull(wrs.getUrl());
assertEquals(COFFEES_ROWS, wrs.size());
assertArrayEquals(COFFEES_PRIMARY_KEYS, getPrimaryKeys(wrs));
From 7d3ef9a8c0e825089f37206234fe51d9bfd7d426 Mon Sep 17 00:00:00 2001
From: Joe Wang
Date: Thu, 25 Jun 2026 16:47:04 +0000
Subject: [PATCH 030/169] 8384031: Enhance XML Processing
Reviewed-by: rhalade, naoto, lancea, jlu, aefimov
---
.../impl/xs/SchemaNamespaceSupport.java | 7 +-
.../internal/util/NamespaceSupport.java | 185 +++++++++---------
.../MultipleScopeNamespaceSupport.java | 52 ++---
3 files changed, 118 insertions(+), 126 deletions(-)
diff --git a/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/impl/xs/SchemaNamespaceSupport.java b/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/impl/xs/SchemaNamespaceSupport.java
index aca31d1e7704..79577c3ef509 100644
--- a/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/impl/xs/SchemaNamespaceSupport.java
+++ b/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/impl/xs/SchemaNamespaceSupport.java
@@ -1,6 +1,5 @@
/*
- * reserved comment block
- * DO NOT REMOVE OR ALTER!
+ * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
@@ -32,6 +31,7 @@
*
* @author Neil Graham, IBM
*
+ * @LastModified: June 2026
*/
public class SchemaNamespaceSupport
extends NamespaceSupport {
@@ -50,6 +50,7 @@ public SchemaNamespaceSupport(SchemaNamespaceSupport nSupport) {
if (fContext.length <= fCurrentContext)
fContext = new int[fCurrentContext+1];
System.arraycopy(nSupport.fContext, 0, fContext, 0, fCurrentContext+1);
+ rebuildNamespaceTable();
} // end constructor
/**
@@ -76,6 +77,7 @@ public void setEffectiveContext (String [] namespaceDecls) {
System.arraycopy(namespaceDecls, 0, fNamespace, fNamespaceSize,
namespaceDecls.length);
fNamespaceSize = newSize;
+ rebuildNamespaceTable();
} // setEffectiveContext(String):void
/**
@@ -108,6 +110,7 @@ public void makeGlobal() {
if (fCurrentContext >= 3) {
fCurrentContext = 3;
fNamespaceSize = fContext[3];
+ rebuildNamespaceTable();
}
} // makeGlobal
} // class NamespaceSupport
diff --git a/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/util/NamespaceSupport.java b/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/util/NamespaceSupport.java
index 05eba5b2560c..30cfb3d7db49 100644
--- a/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/util/NamespaceSupport.java
+++ b/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/util/NamespaceSupport.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 2026, Oracle and/or its affiliates. All rights reserved.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
@@ -23,6 +23,7 @@
import com.sun.org.apache.xerces.internal.xni.NamespaceContext;
import java.util.ArrayList;
import java.util.Enumeration;
+import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
@@ -35,10 +36,17 @@
*
* @author Andy Clark, IBM
*
- * @LastModified: Oct 2017
+ * @LastModified: June 2026
*/
public class NamespaceSupport implements NamespaceContext {
+ //
+ // Constants
+ //
+
+ /** No previous active namespace declaration exists for the prefix. */
+ protected static final int NO_PREVIOUS_DECLARATION = -1;
+
//
// Data
//
@@ -57,6 +65,17 @@ public class NamespaceSupport implements NamespaceContext {
/** The top of the namespace information array. */
protected int fNamespaceSize;
+ /**
+ * Previous active declaration for the prefix at the same tuple index in
+ * {@link #fNamespace}, or {@link #NO_PREVIOUS_DECLARATION} if none exists.
+ * This allows {@link #popContext()} to restore {@link #fNamespaceTable}
+ * to the previous active binding without searching ancestor contexts.
+ */
+ protected int[] fNamespacePrev = new int[16];
+
+ /** Active namespace declaration indexes, keyed by prefix identity. */
+ protected IdentityHashMap fNamespaceTable = new IdentityHashMap<>();
+
// NOTE: The constructor depends on the initial context size
// being at least 1. -Ac
@@ -111,14 +130,19 @@ public void reset() {
// reset namespace and context info
fNamespaceSize = 0;
fCurrentContext = 0;
+ fNamespaceTable.clear();
// bind "xml" prefix to the XML uri
+ fNamespacePrev[0] = NO_PREVIOUS_DECLARATION;
fNamespace[fNamespaceSize++] = XMLSymbols.PREFIX_XML;
fNamespace[fNamespaceSize++] = NamespaceContext.XML_URI;
+ fNamespaceTable.put(XMLSymbols.PREFIX_XML, 0);
// bind "xmlns" prefix to the XMLNS uri
+ fNamespacePrev[1] = NO_PREVIOUS_DECLARATION;
fNamespace[fNamespaceSize++] = XMLSymbols.PREFIX_XMLNS;
fNamespace[fNamespaceSize++] = NamespaceContext.XMLNS_URI;
+ fNamespaceTable.put(XMLSymbols.PREFIX_XMLNS, 2);
fContext[fCurrentContext] = fNamespaceSize;
//++fCurrentContext;
@@ -148,8 +172,19 @@ public void pushContext() {
* @see com.sun.org.apache.xerces.internal.xni.NamespaceContext#popContext()
*/
public void popContext() {
- fNamespaceSize = fContext[fCurrentContext--];
- //System.out.println("Calling popContext, fCurrentContext = " + fCurrentContext);
+ final int context = fContext[fCurrentContext];
+ for (int prefixIndex = fNamespaceSize - 2; prefixIndex >= context; prefixIndex -= 2) {
+ final String prefix = fNamespace[prefixIndex];
+ final int prev = fNamespacePrev[prefixIndex >> 1];
+ if (prev == NO_PREVIOUS_DECLARATION) {
+ fNamespaceTable.remove(prefix);
+ }
+ else {
+ fNamespaceTable.put(prefix, prev);
+ }
+ }
+ fNamespaceSize = context;
+ fCurrentContext--;
} // popContext()
/**
@@ -161,18 +196,14 @@ public boolean declarePrefix(String prefix, String uri) {
return false;
}
+ final int activeIndex = fNamespaceTable.getOrDefault(prefix, NO_PREVIOUS_DECLARATION);
+
// see if prefix already exists in current context
- for (int i = fNamespaceSize; i > fContext[fCurrentContext]; i -= 2) {
- if (fNamespace[i - 2] == prefix) {
- // REVISIT: [Q] Should the new binding override the
- // previously declared binding or should it
- // it be ignored? -Ac
- // NOTE: The SAX2 "NamespaceSupport" helper allows
- // re-bindings with the new binding overwriting
- // the previous binding. -Ac
- fNamespace[i - 1] = uri;
- return true;
- }
+ if (activeIndex >= fContext[fCurrentContext]) {
+ // A duplicate declaration in the current context overwrites the
+ // existing binding in place, preserving the active table entry.
+ fNamespace[activeIndex + 1] = uri;
+ return true;
}
// resize array, if needed
@@ -180,9 +211,15 @@ public boolean declarePrefix(String prefix, String uri) {
String[] namespacearray = new String[fNamespaceSize * 2];
System.arraycopy(fNamespace, 0, namespacearray, 0, fNamespaceSize);
fNamespace = namespacearray;
+
+ int[] namespacePrev = new int[namespacearray.length >> 1];
+ System.arraycopy(fNamespacePrev, 0, namespacePrev, 0, fNamespacePrev.length);
+ fNamespacePrev = namespacePrev;
}
// bind prefix to uri in current context
+ fNamespacePrev[fNamespaceSize >> 1] = activeIndex;
+ fNamespaceTable.put(prefix, fNamespaceSize);
fNamespace[fNamespaceSize++] = prefix;
fNamespace[fNamespaceSize++] = uri;
@@ -190,20 +227,30 @@ public boolean declarePrefix(String prefix, String uri) {
} // declarePrefix(String,String):boolean
+ /**
+ * Rebuilds the active namespace table from {@link #fNamespace}. Subclasses
+ * that directly replace or truncate namespace tuples must call this method.
+ */
+ protected void rebuildNamespaceTable() {
+ if (fNamespacePrev.length < (fNamespace.length >> 1)) {
+ fNamespacePrev = new int[fNamespace.length >> 1];
+ }
+ fNamespaceTable.clear();
+ for (int i = 0; i < fNamespaceSize; i += 2) {
+ final String prefix = fNamespace[i];
+ fNamespacePrev[i >> 1] = fNamespaceTable.getOrDefault(prefix, NO_PREVIOUS_DECLARATION);
+ fNamespaceTable.put(prefix, i);
+ }
+ }
+
/**
* @see com.sun.org.apache.xerces.internal.xni.NamespaceContext#getURI(String)
*/
public String getURI(String prefix) {
// find prefix in current context
- for (int i = fNamespaceSize; i > 0; i -= 2) {
- if (fNamespace[i - 2] == prefix) {
- return fNamespace[i - 1];
- }
- }
-
- // prefix not found
- return null;
+ final int active = fNamespaceTable.getOrDefault(prefix, NO_PREVIOUS_DECLARATION);
+ return active != NO_PREVIOUS_DECLARATION ? fNamespace[active + 1] : null;
} // getURI(String):String
@@ -241,71 +288,46 @@ public String getDeclaredPrefixAt(int index) {
} // getDeclaredPrefixAt(int):String
public Iterator getPrefixes(){
- int count = 0;
- if (fPrefixes.length < (fNamespace.length/2)) {
- // resize prefix array
- String[] prefixes = new String[fNamespaceSize];
- fPrefixes = prefixes;
- }
- String prefix = null;
- boolean unique = true;
- for (int i = 2; i < (fNamespaceSize-2); i += 2) {
- prefix = fNamespace[i + 2];
- for (int k=0;k getAllPrefixes() {
- int count = 0;
- if (fPrefixes.length < (fNamespace.length/2)) {
- // resize prefix array
- String[] prefixes = new String[fNamespaceSize];
- fPrefixes = prefixes;
- }
- String prefix = null;
- boolean unique = true;
- for (int i = 2; i < (fNamespaceSize-2); i += 2) {
- prefix = fNamespace[i + 2];
- for (int k=0;k getPrefixes(String uri){
- int count = 0;
- String prefix = null;
- boolean unique = true;
List prefixList = new ArrayList<>();
- for (int i = fNamespaceSize; i >0 ; i -= 2) {
- if(fNamespace[i-1] == uri){
- if(!prefixList.contains(fNamespace[i-2]))
- prefixList.add(fNamespace[i-2]);
+ for (int i = fNamespaceSize; i > 0; i -= 2) {
+ final int prefixIndex = i - 2;
+ if (fNamespace[prefixIndex + 1] == uri) {
+ final String prefix = fNamespace[prefixIndex];
+ if (fNamespaceTable.getOrDefault(prefix, NO_PREVIOUS_DECLARATION) == prefixIndex) {
+ prefixList.add(prefix);
+ }
}
}
return prefixList;
}
+ protected int collectPrefixes(int start, int end) {
+ if (fPrefixes.length < (fNamespace.length / 2)) {
+ // resize prefix array
+ fPrefixes = new String[fNamespace.length / 2];
+ }
+ int count = 0;
+ for (int i = start; i < end; i += 2) {
+ if (fNamespacePrev[i >> 1] < start) {
+ fPrefixes[count++] = fNamespace[i];
+ }
+ }
+ return count;
+ }
+
/*
* non-NamespaceContext methods
*/
@@ -321,14 +343,7 @@ public List getPrefixes(String uri){
public boolean containsPrefix(String prefix) {
// find prefix in context
- for (int i = fNamespaceSize; i > 0; i -= 2) {
- if (fNamespace[i - 2] == prefix) {
- return true;
- }
- }
-
- // prefix not found
- return false;
+ return fNamespaceTable.containsKey(prefix);
}
/**
@@ -340,16 +355,8 @@ public boolean containsPrefix(String prefix) {
* @return true if the given prefix exists in the current context
*/
public boolean containsPrefixInCurrentContext(String prefix) {
-
// find prefix in current context
- for (int i = fContext[fCurrentContext]; i < fNamespaceSize; i += 2) {
- if (fNamespace[i] == prefix) {
- return true;
- }
- }
-
- // prefix not found
- return false;
+ return fNamespaceTable.getOrDefault(prefix, NO_PREVIOUS_DECLARATION) >= fContext[fCurrentContext];
}
protected final class IteratorPrefixes implements Iterator {
diff --git a/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/xinclude/MultipleScopeNamespaceSupport.java b/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/xinclude/MultipleScopeNamespaceSupport.java
index 7b506b72fa66..3d29a7466fc5 100644
--- a/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/xinclude/MultipleScopeNamespaceSupport.java
+++ b/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/xinclude/MultipleScopeNamespaceSupport.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 2026, Oracle and/or its affiliates. All rights reserved.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
@@ -37,7 +37,7 @@
*
* @author Peter McCracken, IBM
*
- * @LastModified: Oct 2017
+ * @LastModified: June 2026
*/
public class MultipleScopeNamespaceSupport extends NamespaceSupport {
@@ -66,29 +66,7 @@ public MultipleScopeNamespaceSupport(NamespaceContext context) {
* @see com.sun.org.apache.xerces.internal.xni.NamespaceContext#getAllPrefixes()
*/
public Enumeration getAllPrefixes() {
- int count = 0;
- if (fPrefixes.length < (fNamespace.length / 2)) {
- // resize prefix array
- String[] prefixes = new String[fNamespaceSize];
- fPrefixes = prefixes;
- }
- String prefix = null;
- boolean unique = true;
- for (int i = fContext[fScope[fCurrentScope]];
- i <= (fNamespaceSize - 2);
- i += 2) {
- prefix = fNamespace[i];
- for (int k = 0; k < count; k++) {
- if (fPrefixes[k] == prefix) {
- unique = false;
- break;
- }
- }
- if (unique) {
- fPrefixes[count++] = prefix;
- }
- unique = true;
- }
+ int count = collectPrefixes(fContext[fScope[fCurrentScope]], fNamespaceSize);
return new Prefixes(fPrefixes, count);
}
@@ -131,10 +109,10 @@ public String getPrefix(String uri, int start, int end) {
return XMLSymbols.PREFIX_XMLNS;
}
- // find uri in current context
+ // find uri in the requested context range within the current scope
for (int i = start; i > end; i -= 2) {
if (fNamespace[i - 1] == uri) {
- if (getURI(fNamespace[i - 2]) == uri)
+ if (getURI(fNamespace[i - 2], start, end) == uri)
return fNamespace[i - 2];
}
}
@@ -152,15 +130,18 @@ public String getURI(String prefix, int start, int end) {
return NamespaceContext.XMLNS_URI;
}
- // find prefix in current context
- for (int i = start; i > end; i -= 2) {
- if (fNamespace[i - 2] == prefix) {
- return fNamespace[i - 1];
- }
- }
+ int prefixIndex = getPrefixIndex(prefix, start, end);
+ return prefixIndex != NO_PREVIOUS_DECLARATION
+ ? fNamespace[prefixIndex + 1] : null;
+ }
- // prefix not found
- return null;
+ private int getPrefixIndex(String prefix, int start, int end) {
+ final Integer active = fNamespaceTable.get(prefix);
+ int prefixIndex = active != null ? active : NO_PREVIOUS_DECLARATION;
+ while (prefixIndex >= start) {
+ prefixIndex = fNamespacePrev[prefixIndex >> 1];
+ }
+ return prefixIndex >= end ? prefixIndex : NO_PREVIOUS_DECLARATION;
}
/**
@@ -170,6 +151,7 @@ public String getURI(String prefix, int start, int end) {
public void reset() {
fCurrentContext = fScope[fCurrentScope];
fNamespaceSize = fContext[fCurrentContext];
+ rebuildNamespaceTable();
}
/**
From 9f4e850777aa60db3ebbcdfaa91fe58af53d87b8 Mon Sep 17 00:00:00 2001
From: Jan Lahoda
Date: Fri, 26 Jun 2026 02:37:41 +0000
Subject: [PATCH 031/169] 8387235: JShell tests failing
Reviewed-by: dbalek, asotona
---
.../jdk/jshell/execution/JdiDefaultExecutionControl.java | 2 ++
test/langtools/jdk/jshell/RemoteAgentHandshakeTest.java | 2 ++
test/langtools/jdk/jshell/UserJdiUserRemoteTest.java | 5 +++--
3 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/src/jdk.jshell/share/classes/jdk/jshell/execution/JdiDefaultExecutionControl.java b/src/jdk.jshell/share/classes/jdk/jshell/execution/JdiDefaultExecutionControl.java
index b4489a8071cb..49521748b3b8 100644
--- a/src/jdk.jshell/share/classes/jdk/jshell/execution/JdiDefaultExecutionControl.java
+++ b/src/jdk.jshell/share/classes/jdk/jshell/execution/JdiDefaultExecutionControl.java
@@ -192,6 +192,8 @@ public void closeDown() {
throw new IOException("Incorrect secret received.");
}
+ socket.setSoTimeout(0);
+
Map outputs = new HashMap<>();
outputs.put("out", env.userOut());
outputs.put("err", env.userErr());
diff --git a/test/langtools/jdk/jshell/RemoteAgentHandshakeTest.java b/test/langtools/jdk/jshell/RemoteAgentHandshakeTest.java
index 0f0ba0c9a963..bb44ff275bb4 100644
--- a/test/langtools/jdk/jshell/RemoteAgentHandshakeTest.java
+++ b/test/langtools/jdk/jshell/RemoteAgentHandshakeTest.java
@@ -49,6 +49,7 @@ public void testCustomAgentOldHandshake() throws Exception {
.executionEngine(new JdiExecutionControlProvider(),
Map.of(JdiExecutionControlProvider.PARAM_REMOTE_AGENT,
OldHandShake.class.getName()))
+ .remoteVMOptions("-classpath", System.getProperty("test.classes"))
.build()) {
jshell.eval("1");
}
@@ -62,6 +63,7 @@ public void testCustomAgentNewHandshake() throws Exception {
.executionEngine(new JdiExecutionControlProvider(),
Map.of(JdiExecutionControlProvider.PARAM_REMOTE_AGENT,
NewHandShake.class.getName()))
+ .remoteVMOptions("-classpath", System.getProperty("test.classes"))
.build()) {
jshell.eval("1");
}
diff --git a/test/langtools/jdk/jshell/UserJdiUserRemoteTest.java b/test/langtools/jdk/jshell/UserJdiUserRemoteTest.java
index 094c705bce64..28edb4c26b05 100644
--- a/test/langtools/jdk/jshell/UserJdiUserRemoteTest.java
+++ b/test/langtools/jdk/jshell/UserJdiUserRemoteTest.java
@@ -37,6 +37,7 @@
import jdk.jshell.spi.ExecutionControl;
import jdk.jshell.spi.ExecutionControl.ExecutionControlException;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -63,8 +64,8 @@ public void testVarValue() {
@Test
public void testExtension() throws ExecutionControlException {
assertEval("42;");
- Object res = currentEC.extensionCommand("FROG", "test");
- assertEquals("ribbit", res);
+ Assertions.assertThrows(ExecutionControl.NotImplementedException.class,
+ () -> currentEC.extensionCommand("FROG", "test"));
}
@Test
From e0b15f85dfeeb7ff740fd3fc4eda8d3b48ba208d Mon Sep 17 00:00:00 2001
From: Volkan Yazici
Date: Fri, 26 Jun 2026 06:58:11 +0000
Subject: [PATCH 032/169] 8386033: Enhance QUIC connections
Reviewed-by: rhalade, aefimov, skoivu, dfuchs, djelinski
---
.../net/http/quic/QuicConnectionImpl.java | 21 +++++++++++++------
.../net/http/quic/frames/AckFrame.java | 4 ++--
.../net/http/quic/frames/StreamFrame.java | 9 +++++++-
.../http/quic/packets/QuicPacketDecoder.java | 20 +++++++++---------
4 files changed, 35 insertions(+), 19 deletions(-)
diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicConnectionImpl.java b/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicConnectionImpl.java
index ed14fc49f19d..f225afe6d0b1 100644
--- a/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicConnectionImpl.java
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicConnectionImpl.java
@@ -1122,12 +1122,16 @@ public void increaseProcessedData(long diff) {
}
private long bumpMaxData() {
- long newMaxData = processedData + desiredBufferSize;
+ var processedData = this.processedData;
+ long newMaxData = processedData >= MAX_VL_INTEGER - desiredBufferSize
+ ? MAX_VL_INTEGER
+ : processedData + desiredBufferSize;
long maxData = this.maxData;
- if (newMaxData - maxData < (desiredBufferSize / 5)) {
- return 0;
- }
- while (maxData < newMaxData) {
+ while (maxData < newMaxData) { // CAS-loop on `maxData`
+ if (newMaxData != MAX_VL_INTEGER && // Do not skip the final clamp-to-max bump
+ newMaxData - maxData < (desiredBufferSize / 5)) { // Skip small ordinary bumps
+ break;
+ }
if (MAX_RCV_DATA.compareAndSet(this, maxData, newMaxData))
return newMaxData;
maxData = this.maxData;
@@ -1136,7 +1140,12 @@ private long bumpMaxData() {
}
public boolean needSendMaxData() {
- return maxData - processedData < desiredBufferSize/2;
+ long maxData = this.maxData;
+ // `maxData` is the value that was already sent to the peer.
+ // `bumpMaxData()` clamps `maxData` at `MAX_VL_INTEGER`.
+ // Once `maxData == MAX_VL_INTEGER`, there is no larger legal value to advertise.
+ return maxData < MAX_VL_INTEGER &&
+ maxData - processedData < (desiredBufferSize / 2);
}
String logTag() { return logTag.get(); }
diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/quic/frames/AckFrame.java b/src/java.net.http/share/classes/jdk/internal/net/http/quic/frames/AckFrame.java
index 09d05f910661..338e690ce0e3 100644
--- a/src/java.net.http/share/classes/jdk/internal/net/http/quic/frames/AckFrame.java
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/quic/frames/AckFrame.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 2025, 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
@@ -84,7 +84,7 @@ public final class AckFrame extends QuicFrame {
null, type,
QuicTransportErrors.FRAME_ENCODING_ERROR);
}
- var ackRanges = new ArrayList(ackRangeCount + 1);
+ var ackRanges = new ArrayList();
AckRange first = AckRange.of(0, firstAckRange);
ackRanges.addFirst(first);
for (int i=1; i <= ackRangeCount; i++) {
diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/quic/frames/StreamFrame.java b/src/java.net.http/share/classes/jdk/internal/net/http/quic/frames/StreamFrame.java
index b597e53c06c3..693eb059815c 100644
--- a/src/java.net.http/share/classes/jdk/internal/net/http/quic/frames/StreamFrame.java
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/quic/frames/StreamFrame.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 2025, 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
@@ -29,7 +29,9 @@
import java.util.Objects;
import jdk.internal.net.http.common.Utils;
+import jdk.internal.net.http.quic.VariableLengthEncoder;
import jdk.internal.net.http.quic.packets.QuicPacketEncoder;
+import jdk.internal.net.quic.QuicTransportErrors;
import jdk.internal.net.quic.QuicTransportException;
/**
@@ -69,6 +71,11 @@ public final class StreamFrame extends QuicFrame {
} else {
length = -1;
}
+ if (length > VariableLengthEncoder.MAX_ENCODED_INTEGER - offset) {
+ throw new QuicTransportException(
+ "Maximum stream frame offset exceeded",
+ null, type, QuicTransportErrors.FRAME_ENCODING_ERROR);
+ }
if (length == -1) {
int remaining = buffer.remaining();
streamData = Utils.sliceOrCopy(buffer, buffer.position(), remaining);
diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/quic/packets/QuicPacketDecoder.java b/src/java.net.http/share/classes/jdk/internal/net/http/quic/packets/QuicPacketDecoder.java
index 233a1a35778b..fc4984a30a2e 100644
--- a/src/java.net.http/share/classes/jdk/internal/net/http/quic/packets/QuicPacketDecoder.java
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/quic/packets/QuicPacketDecoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 2023, 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
@@ -888,26 +888,24 @@ private static final class IncomingInitialPacket
final int size;
final int length;
- final int tokenLength;
final long packetNumber;
final byte[] token;
final List frames;
IncomingInitialPacket(QuicConnectionId sourceId,
QuicConnectionId destinationId, int version,
- int tokenLength, byte[] token, int length,
+ byte[] token, int length,
long packetNumber, List frames, int size) {
super(sourceId, destinationId, version);
this.size = size;
this.length = length;
- this.tokenLength = tokenLength;
this.token = token;
this.packetNumber = packetNumber;
this.frames = List.copyOf(frames);
}
@Override
- public int tokenLength() { return tokenLength; }
+ public int tokenLength() { return token != null ? token.length : 0; }
@Override
public byte[] token() { return token; }
@@ -968,7 +966,7 @@ static IncomingInitialPacket decode(PacketReader reader, CodingContext context)
}
// Get number of bytes needed to store the length of the token
- var tokenLength = (int) reader.readTokenLength();
+ var tokenLength = reader.readTokenLength();
if (debug.on()) {
debug.log("IncomingInitialPacket.decode(token-length(%d), %s)",
tokenLength, reader);
@@ -1051,7 +1049,7 @@ static IncomingInitialPacket decode(PacketReader reader, CodingContext context)
assert size == reader.bytesRead() : size - reader.bytesRead();
return new IncomingInitialPacket(sourceID, destinationID,
- version, tokenLength, token, (int)packetLength, packetNumber, frames, size);
+ version, token, (int)packetLength, packetNumber, frames, size);
}
}
@@ -1572,16 +1570,18 @@ public long readPacketLength() {
return packetLength;
}
- public long readTokenLength() {
+ private long readTokenLength() {
return readVariableLength();
}
- public byte[] readToken(int tokenLength) {
+ public byte[] readToken(long tokenLength) {
// Check to ensure that tokenLength is within valid range
if (tokenLength < 0 || tokenLength > buffer.remaining()) {
throw new BufferUnderflowException();
}
- byte[] token = tokenLength > 0 ? new byte[tokenLength] : null;
+ // `int` conversion is safe due to the `> buffer.remaining()` check above.
+ int tokenLengthInt = Math.toIntExact(tokenLength);
+ byte[] token = tokenLength > 0 ? new byte[tokenLengthInt] : null;
if (tokenLength > 0) {
buffer.get(token);
}
From 17e41a6f7b6aa979f37212776b3225934f68da05 Mon Sep 17 00:00:00 2001
From: Hai-May Chao
Date: Fri, 26 Jun 2026 17:37:56 +0000
Subject: [PATCH 033/169] 8384771: Enhance TLS handshakes
Reviewed-by: mschoene, rhalade, weijun, jnimeh
---
.../classes/sun/security/x509/URIName.java | 24 ++++++++++++-------
1 file changed, 16 insertions(+), 8 deletions(-)
diff --git a/src/java.base/share/classes/sun/security/x509/URIName.java b/src/java.base/share/classes/sun/security/x509/URIName.java
index c6d0d1790569..4a021ccbea59 100644
--- a/src/java.base/share/classes/sun/security/x509/URIName.java
+++ b/src/java.base/share/classes/sun/security/x509/URIName.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2022, 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
@@ -29,6 +29,7 @@
import java.net.URI;
import java.net.URISyntaxException;
+import sun.net.util.IPAddressUtil;
import sun.security.util.*;
/**
@@ -131,14 +132,21 @@ public URIName(String name) throws IOException {
try {
hostDNS = new DNSName(host);
} catch (IOException ioe) {
- // Not a valid DNSName; see if it is a valid IPv4
- // IPAddressName
- try {
- hostIP = new IPAddressName(host);
- } catch (Exception ioe2) {
+ // Not a valid DNSName; only accept host as an
+ // IPv4 literal.
+ byte[] ipV4 = IPAddressUtil.textToNumericFormatV4(host);
+ if (ipV4 != null) {
+ try {
+ hostIP = new IPAddressName(ipV4);
+ } catch (IOException ioe2) {
+ throw new IOException("invalid URI name (host " +
+ "portion is not a valid DNSName or IPv4 address" +
+ "):" + name, ioe2);
+ }
+ } else {
throw new IOException("invalid URI name (host " +
- "portion is not a valid DNSName, IPv4 address," +
- " or IPv6 address):" + name);
+ "portion is not a valid DNSName or IPv4 address" +
+ "):" + name);
}
}
}
From 098ed73f1b8a7a78c472ee04ba731bd0af86157d Mon Sep 17 00:00:00 2001
From: Alexander Zuev
Date: Sat, 27 Jun 2026 04:23:15 +0000
Subject: [PATCH 034/169] 8385476: Enhance MIDI file handling
Reviewed-by: mschoene, psadhukhan, rhalade, prr
---
.../media/sound/StandardMidiFileReader.java | 57 ++++++++++++++-----
1 file changed, 44 insertions(+), 13 deletions(-)
diff --git a/src/java.desktop/share/classes/com/sun/media/sound/StandardMidiFileReader.java b/src/java.desktop/share/classes/com/sun/media/sound/StandardMidiFileReader.java
index 0581b0ce3ca6..7f89ec1e422e 100644
--- a/src/java.desktop/share/classes/com/sun/media/sound/StandardMidiFileReader.java
+++ b/src/java.desktop/share/classes/com/sun/media/sound/StandardMidiFileReader.java
@@ -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
@@ -26,6 +26,7 @@
package com.sun.media.sound;
import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.File;
@@ -229,6 +230,10 @@ final class SMFParser {
private static final boolean DEBUG = false;
+ private static final int MAX_ALLOWED_ALLOC = 1024*1024; // Maximum allowed memory preallocation
+
+ private static final int TRACK_BUFFER_SIZE = 10*1024; // Read large tracks in 10 KB chunks
+
int tracks; // number of tracks
DataInputStream stream; // the stream to read from
@@ -286,19 +291,45 @@ boolean nextTrack() throws IOException, InvalidMidiDataException {
}
}
// now read track in a byte array
- try {
- trackData = new byte[trackLength];
- } catch (final OutOfMemoryError oom) {
- throw new IOException("Track length too big", oom);
- }
- try {
- // $$fb 2003-08-20: fix for 4910986: MIDI file parser breaks up on http connection
- stream.readFully(trackData);
- } catch (EOFException eof) {
- if (!STRICT_PARSER) {
- return false;
+ if (trackLength <= MAX_ALLOWED_ALLOC) {
+ try {
+ trackData = new byte[trackLength];
+ } catch (final OutOfMemoryError oom) {
+ throw new IOException("Track length too big", oom);
+ }
+ try {
+ // $$fb 2003-08-20: fix for 4910986: MIDI file parser breaks up on http connection
+ stream.readFully(trackData);
+ } catch (EOFException eof) {
+ if (!STRICT_PARSER) {
+ return false;
+ }
+ throw new EOFException("invalid MIDI file");
+ }
+ } else {
+ try {
+ byte[] buffer = new byte[TRACK_BUFFER_SIZE];
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ long bytesRead = 0;
+
+ while (bytesRead < trackLength) {
+ int bytesToRead = (int) Math.min(buffer.length, trackLength - bytesRead);
+ try {
+ stream.readFully(buffer, 0, bytesToRead);
+ } catch (EOFException eof) {
+ if (!STRICT_PARSER) {
+ return false;
+ }
+ throw new EOFException("invalid MIDI file");
+ }
+
+ out.write(buffer, 0, bytesToRead);
+ bytesRead += bytesToRead;
+ }
+ trackData = out.toByteArray();
+ } catch (final OutOfMemoryError oom) {
+ throw new IOException("Track length too big", oom);
}
- throw new EOFException("invalid MIDI file");
}
pos = 0;
return true;
From c6000549c7b1035c0e1ae42993e24c4f1c10a3ed Mon Sep 17 00:00:00 2001
From: Daniel Fuchs
Date: Wed, 1 Jul 2026 14:37:58 +0000
Subject: [PATCH 035/169] 8385209: Enhance HTTP client caching
Reviewed-by: rhalade, michaelm, djelinski
---
.../net/http/AuthenticationFilter.java | 104 ++++++++++++------
.../jdk/internal/net/http/HttpClientImpl.java | 2 +-
.../jdk/internal/net/http/RedirectFilter.java | 47 +++-----
3 files changed, 85 insertions(+), 68 deletions(-)
diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/AuthenticationFilter.java b/src/java.net.http/share/classes/jdk/internal/net/http/AuthenticationFilter.java
index 278e2bf23b14..05f0a4cb7c2d 100644
--- a/src/java.net.http/share/classes/jdk/internal/net/http/AuthenticationFilter.java
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/AuthenticationFilter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2015, 2024, 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
@@ -49,7 +49,7 @@
* Implementation of Http Basic authentication.
*/
class AuthenticationFilter implements HeaderFilter {
- volatile MultiExchange> exchange;
+
private static final Base64.Encoder encoder = Base64.getEncoder();
static final int DEFAULT_RETRY_LIMIT = 3;
@@ -67,12 +67,12 @@ class AuthenticationFilter implements HeaderFilter {
// A public no-arg constructor is required by FilterFactory
public AuthenticationFilter() {}
- private PasswordAuthentication getCredentials(String header,
+ private PasswordAuthentication getCredentials(HttpClientImpl client,
+ String header,
boolean proxy,
HttpRequestImpl req)
throws IOException
{
- HttpClientImpl client = exchange.client();
java.net.Authenticator auth =
client.authenticator()
.orElseThrow(() -> new IOException("No authenticator set"));
@@ -144,33 +144,51 @@ private URI getProxyURI(HttpRequestImpl r) {
}
@Override
- public void request(HttpRequestImpl r, MultiExchange> e) throws IOException {
+ public void request(HttpRequestImpl r, MultiExchange> exchange) throws IOException {
// use preemptive authentication if an entry exists.
- Cache cache = getCache(e);
- this.exchange = e;
+ Cache cache = getCache(exchange.client());
+ AuthInfo serverAuth = exchange.serverauth;
+ AuthInfo proxyAuth = exchange.proxyauth;
+
+ if (serverAuth != null && !sameOriginAndScheme(serverAuth.requestor, r.uri())) {
+ serverAuth = exchange.serverauth = null;
+ }
+ if (proxyAuth != null && !sameOriginAndScheme(proxyAuth.requestor, getProxyURI(r))) {
+ proxyAuth = exchange.proxyauth = null;
+ }
// Proxy
- if (exchange.proxyauth == null) {
+ if (proxyAuth == null) {
URI proxyURI = getProxyURI(r);
if (proxyURI != null) {
CacheEntry ca = cache.get(proxyURI, true);
if (ca != null) {
- exchange.proxyauth = new AuthInfo(true, ca.scheme, null, ca, ca.isUTF8);
+ exchange.proxyauth = new AuthInfo(true, ca.scheme, proxyURI, null, ca, ca.isUTF8);
addBasicCredentials(r, true, ca.value, ca.isUTF8);
}
}
}
// Server
- if (exchange.serverauth == null) {
- CacheEntry ca = cache.get(r.uri(), false);
+ if (serverAuth == null) {
+ URI requestor = r.uri();
+ CacheEntry ca = cache.get(requestor, false);
if (ca != null) {
- exchange.serverauth = new AuthInfo(true, ca.scheme, null, ca, ca.isUTF8);
+ exchange.serverauth = new AuthInfo(true, ca.scheme, requestor, null, ca, ca.isUTF8);
addBasicCredentials(r, false, ca.value, ca.isUTF8);
}
}
}
+ private static boolean sameOriginAndScheme(URI uri, URI other) {
+ // in this context we don't need to use equalsIgnoreCase.
+ if (uri == other) return true;
+ if (uri == null || other == null) return false;
+ if (!Objects.equals(uri.getScheme(), other.getScheme())) return false;
+ if (!Objects.equals(uri.getRawAuthority(), other.getRawAuthority())) return false;
+ return true;
+ }
+
// TODO: refactor into per auth scheme class
private static void addBasicCredentials(HttpRequestImpl r,
boolean proxy,
@@ -202,6 +220,7 @@ private static void addBasicCredentials(HttpRequestImpl r,
static class AuthInfo {
final boolean fromcache;
final String scheme;
+ final URI requestor;
int retries;
PasswordAuthentication credentials; // used in request
CacheEntry cacheEntry; // if used
@@ -209,19 +228,24 @@ static class AuthInfo {
AuthInfo(boolean fromcache,
String scheme,
- PasswordAuthentication credentials, boolean isUTF8) {
+ URI requestor,
+ PasswordAuthentication credentials,
+ boolean isUTF8) {
this.fromcache = fromcache;
this.scheme = scheme;
this.credentials = credentials;
+ this.requestor = requestor;
this.retries = 1;
this.isUTF8 = isUTF8;
}
AuthInfo(boolean fromcache,
String scheme,
+ URI requestor,
PasswordAuthentication credentials,
- CacheEntry ca, boolean isUTF8) {
- this(fromcache, scheme, credentials, isUTF8);
+ CacheEntry ca,
+ boolean isUTF8) {
+ this(fromcache, scheme, requestor, credentials, isUTF8);
assert credentials == null || (ca != null && ca.value == null);
cacheEntry = ca;
}
@@ -230,7 +254,9 @@ AuthInfo retryWithCredentials(PasswordAuthentication pw, boolean isUTF8) {
// If the info was already in the cache we need to create a new
// instance with fromCache==false so that it's put back in the
// cache if authentication succeeds
- AuthInfo res = fromcache ? new AuthInfo(false, scheme, pw, isUTF8) : this;
+ AuthInfo res = fromcache
+ ? new AuthInfo(false, scheme, requestor, pw, isUTF8)
+ : this;
res.credentials = Objects.requireNonNull(pw);
res.retries = retries;
return res;
@@ -239,7 +265,9 @@ AuthInfo retryWithCredentials(PasswordAuthentication pw, boolean isUTF8) {
@Override
public HttpRequestImpl response(Response r) throws IOException {
- Cache cache = getCache(exchange);
+ MultiExchange> exchange = r.exchange.multi;
+ HttpClientImpl client = exchange.client();
+ Cache cache = getCache(client);
int status = r.statusCode();
HttpHeaders hdrs = r.headers();
HttpRequestImpl req = r.request();
@@ -253,28 +281,37 @@ public HttpRequestImpl response(Response r) throws IOException {
}
if (status != PROXY_UNAUTHORIZED) {
- if (exchange.proxyauth != null && !exchange.proxyauth.fromcache) {
- AuthInfo au = exchange.proxyauth;
+ AuthInfo au = exchange.proxyauth;
+ if (au != null && !au.fromcache) {
URI proxyURI = getProxyURI(req);
if (proxyURI != null) {
- exchange.proxyauth = null;
- cache.store(au.scheme, proxyURI, true, au.credentials, au.isUTF8);
+ if (sameOriginAndScheme(au.requestor, proxyURI)) {
+ exchange.proxyauth = null;
+ cache.store(au.scheme, au.requestor, true, au.credentials, au.isUTF8);
+ }
}
}
if (status != UNAUTHORIZED) {
+ au = exchange.serverauth;
// check if any authentication succeeded for first time
- if (exchange.serverauth != null && !exchange.serverauth.fromcache) {
- AuthInfo au = exchange.serverauth;
- cache.store(au.scheme, req.uri(), false, au.credentials, au.isUTF8);
+ if (au != null && !au.fromcache) {
+ URI requestor = req.uri();
+ if (sameOriginAndScheme(au.requestor, requestor)) {
+ cache.store(au.scheme, au.requestor, false, au.credentials, au.isUTF8);
+ }
}
+ assert status != PROXY_UNAUTHORIZED && status != UNAUTHORIZED;
return null;
}
+ assert status == UNAUTHORIZED;
}
+ assert status == PROXY_UNAUTHORIZED || status == UNAUTHORIZED;
+
boolean proxy = status == PROXY_UNAUTHORIZED;
String authname = proxy ? "Proxy-Authenticate" : "WWW-Authenticate";
List authvals = hdrs.allValues(authname);
- if (authvals.isEmpty() && exchange.client().authenticator().isPresent()) {
+ if (authvals.isEmpty() && client.authenticator().isPresent()) {
throw new IOException(authname + " header missing for response code " + status);
}
String authval = null;
@@ -310,16 +347,17 @@ public HttpRequestImpl response(Response r) throws IOException {
}
AuthInfo au = proxy ? exchange.proxyauth : exchange.serverauth;
- if (au == null) {
+ URI requestor = proxy ? getProxyURI(req) : req.uri();
+ if (au == null || !sameOriginAndScheme(au.requestor, requestor)) {
// if no authenticator, let the user deal with 407/401
- if (exchange.client().authenticator().isEmpty()) return null;
+ if (client.authenticator().isEmpty()) return null;
- PasswordAuthentication pw = getCredentials(authval, proxy, req);
+ PasswordAuthentication pw = getCredentials(client, authval, proxy, req);
if (pw == null) {
throw new IOException("No credentials provided");
}
// No authentication in request. Get credentials from user
- au = new AuthInfo(false, "Basic", pw, isUTF8);
+ au = new AuthInfo(false, "Basic",requestor, pw, isUTF8);
if (proxy) {
exchange.proxyauth = au;
} else {
@@ -332,16 +370,17 @@ public HttpRequestImpl response(Response r) throws IOException {
throw new IOException("too many authentication attempts. Limit: " +
retry_limit);
} else {
+ assert sameOriginAndScheme(au.requestor, requestor);
// we sent credentials, but they were rejected
if (au.fromcache) {
cache.remove(au.cacheEntry);
}
// if no authenticator, let the user deal with 407/401
- if (exchange.client().authenticator().isEmpty()) return null;
+ if (client.authenticator().isEmpty()) return null;
// try again
- PasswordAuthentication pw = getCredentials(authval, proxy, req);
+ PasswordAuthentication pw = getCredentials(client, authval, proxy, req);
if (pw == null) {
throw new IOException("No credentials provided");
}
@@ -362,8 +401,7 @@ public HttpRequestImpl response(Response r) throws IOException {
// be garbage collected when no longer referenced.
static final WeakHashMap caches = new WeakHashMap<>();
- static synchronized Cache getCache(MultiExchange> exchange) {
- HttpClientImpl client = exchange.client();
+ static synchronized Cache getCache(HttpClientImpl client) {
Cache c = caches.get(client);
if (c == null) {
c = new Cache();
diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/HttpClientImpl.java b/src/java.net.http/share/classes/jdk/internal/net/http/HttpClientImpl.java
index aacfb27e99ad..1fdc5ffc7994 100644
--- a/src/java.net.http/share/classes/jdk/internal/net/http/HttpClientImpl.java
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/HttpClientImpl.java
@@ -1839,8 +1839,8 @@ public String toString() {
}
private void initFilters() {
- addFilter(AuthenticationFilter.class);
addFilter(RedirectFilter.class);
+ addFilter(AuthenticationFilter.class);
if (this.cookieHandler != null) {
addFilter(CookieFilter.class);
}
diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/RedirectFilter.java b/src/java.net.http/share/classes/jdk/internal/net/http/RedirectFilter.java
index a3c588a8f922..a88860232136 100644
--- a/src/java.net.http/share/classes/jdk/internal/net/http/RedirectFilter.java
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/RedirectFilter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2015, 2023, 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
@@ -28,23 +28,15 @@
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URI;
-import java.net.http.HttpClient;
+import java.net.http.HttpClient.Redirect;
import java.net.http.HttpHeaders;
-import java.util.concurrent.locks.ReentrantLock;
import jdk.internal.net.http.common.Log;
import jdk.internal.net.http.common.Utils;
class RedirectFilter implements HeaderFilter {
- private final ReentrantLock stateLock = new ReentrantLock();
- HttpRequestImpl request;
- HttpClientImpl client;
- HttpClient.Redirect policy;
- String method;
- MultiExchange> exchange;
static final int DEFAULT_MAX_REDIRECTS = 5;
- URI uri;
/*
* NOT_MODIFIED status code results from a conditional GET where
@@ -62,28 +54,11 @@ public RedirectFilter() {}
@Override
public void request(HttpRequestImpl r, MultiExchange> e) throws IOException {
- stateLock.lock();
- try {
- this.request = r;
- this.client = e.client();
- this.policy = client.followRedirects();
-
- this.method = r.method();
- this.uri = r.uri();
- this.exchange = e;
- } finally {
- stateLock.unlock();
- }
}
@Override
public HttpRequestImpl response(Response r) throws IOException {
- stateLock.lock();
- try {
- return handleResponse(r);
- } finally {
- stateLock.unlock();
- }
+ return handleResponse(r);
}
private static String redirectedMethod(int statusCode, String orig) {
@@ -120,7 +95,9 @@ private static boolean isRedirecting(int statusCode) {
*/
private HttpRequestImpl handleResponse(Response r) {
int rcode = r.statusCode();
- if (rcode == 200 || policy == HttpClient.Redirect.NEVER) {
+ MultiExchange> exchange = r.exchange.multi;
+ Redirect policy = exchange.client.followRedirects();
+ if (rcode == 200 || policy == Redirect.NEVER) {
return null;
}
@@ -128,10 +105,12 @@ private HttpRequestImpl handleResponse(Response r) {
return null;
if (isRedirecting(rcode)) {
- URI redir = getRedirectedURI(r.headers());
- String newMethod = redirectedMethod(rcode, method);
+ HttpRequestImpl request = r.request();
+ URI from = request.uri();
+ URI redir = getRedirectedURI(from, r.headers());
+ String newMethod = redirectedMethod(rcode, request.method());
Log.logTrace("response code: {0}, redirected URI: {1}", rcode, redir);
- if (canRedirect(redir) && ++exchange.numberOfRedirects < max_redirects) {
+ if (canRedirect(from, redir, policy) && ++exchange.numberOfRedirects < max_redirects) {
Log.logTrace("redirect to: {0} with method: {1}", redir, newMethod);
return HttpRequestImpl.newInstanceForRedirection(redir, newMethod, request, rcode != 303);
} else {
@@ -142,7 +121,7 @@ private HttpRequestImpl handleResponse(Response r) {
return null;
}
- private URI getRedirectedURI(HttpHeaders headers) {
+ private URI getRedirectedURI(URI uri, HttpHeaders headers) {
URI redirectedURI;
redirectedURI = headers.firstValue("Location")
.map(URI::create)
@@ -155,7 +134,7 @@ private URI getRedirectedURI(HttpHeaders headers) {
return redirectedURI;
}
- private boolean canRedirect(URI redir) {
+ private boolean canRedirect(URI uri, URI redir, Redirect policy) {
String newScheme = redir.getScheme();
String oldScheme = uri.getScheme();
return switch (policy) {
From 4e627bb421da3dd8c4d3b29dde26fbeb2f23d576 Mon Sep 17 00:00:00 2001
From: Weijun Wang
Date: Tue, 7 Jul 2026 14:31:41 +0000
Subject: [PATCH 036/169] 8387152: Enhance krb5 tokens
Reviewed-by: rhalade, valeriep, semery
---
.../sun/security/jgss/krb5/CipherHelper.java | 246 ++++--------------
.../sun/security/jgss/krb5/Krb5Context.java | 132 ++--------
.../security/jgss/krb5/MessageToken_v2.java | 34 ++-
.../sun/security/jgss/krb5/WrapToken_v2.java | 4 +-
4 files changed, 87 insertions(+), 329 deletions(-)
diff --git a/src/java.security.jgss/share/classes/sun/security/jgss/krb5/CipherHelper.java b/src/java.security.jgss/share/classes/sun/security/jgss/krb5/CipherHelper.java
index 20bf6a147533..54f74353100c 100644
--- a/src/java.security.jgss/share/classes/sun/security/jgss/krb5/CipherHelper.java
+++ b/src/java.security.jgss/share/classes/sun/security/jgss/krb5/CipherHelper.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2004, 2024, 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
@@ -39,6 +39,7 @@
import java.security.MessageDigest;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
+
import sun.security.krb5.*;
import sun.security.krb5.internal.crypto.Aes128Sha2;
import sun.security.krb5.internal.crypto.Aes256Sha2;
@@ -47,6 +48,7 @@
import sun.security.krb5.internal.crypto.Aes256;
import sun.security.krb5.internal.crypto.ArcFourHmac;
import sun.security.krb5.internal.crypto.EType;
+import sun.security.util.ByteArrays;
class CipherHelper {
@@ -587,34 +589,43 @@ void decryptData(WrapToken token, byte[] ciphertext, int cStart, int cLen,
// decrypt data in the new GSS tokens
void decryptData(WrapToken_v2 token, byte[] ciphertext, int cStart,
int cLen, byte[] plaintext, int pStart, int key_usage)
- throws GSSException {
-
- /*
- Krb5Token.debug("decryptData : ciphertext = " +
- Krb5Token.getHexBytes(ciphertext));
- */
+ throws GSSException {
+ byte[] decrypted;
switch (etype) {
case EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96:
- aes128Decrypt(token, ciphertext, cStart, cLen,
- plaintext, pStart, key_usage);
- break;
+ decrypted = aes128Decrypt(ciphertext, cStart, cLen, key_usage);
+ break;
case EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96:
- aes256Decrypt(token, ciphertext, cStart, cLen,
- plaintext, pStart, key_usage);
- break;
+ decrypted = aes256Decrypt(ciphertext, cStart, cLen, key_usage);
+ break;
case EncryptedData.ETYPE_AES128_CTS_HMAC_SHA256_128:
- aes128Sha2Decrypt(token, ciphertext, cStart, cLen,
- plaintext, pStart, key_usage);
- break;
+ decrypted = aes128Sha2Decrypt(ciphertext, cStart, cLen, key_usage);
+ break;
case EncryptedData.ETYPE_AES256_CTS_HMAC_SHA384_192:
- aes256Sha2Decrypt(token, ciphertext, cStart, cLen,
- plaintext, pStart, key_usage);
- break;
+ decrypted = aes256Sha2Decrypt(ciphertext, cStart, cLen, key_usage);
+ break;
default:
- throw new GSSException(GSSException.FAILURE, -1,
+ throw new GSSException(GSSException.FAILURE, -1,
"Unsupported etype: " + etype);
- }
+ }
+ byte[] outerHeader = token.getTokenHeader().clone();
+ outerHeader[MessageToken_v2.TOKEN_RRC_POS] = 0;
+ outerHeader[MessageToken_v2.TOKEN_RRC_POS + 1] = 0;
+ int len = decrypted.length - WrapToken_v2.CONFOUNDER_SIZE -
+ WrapToken_v2.TOKEN_HEADER_SIZE - token.getEc();
+ if (len < 0) {
+ throw new GSSException(GSSException.DEFECTIVE_TOKEN);
+ }
+ if (!ByteArrays.isEqual(
+ outerHeader,
+ 0, WrapToken_v2.TOKEN_HEADER_SIZE,
+ decrypted,
+ decrypted.length - WrapToken_v2.TOKEN_HEADER_SIZE, decrypted.length)) {
+ throw new GSSException(GSSException.DEFECTIVE_TOKEN);
+ }
+ System.arraycopy(decrypted, WrapToken_v2.CONFOUNDER_SIZE,
+ plaintext, pStart, len);
}
void decryptData(WrapToken token, InputStream cipherStream, int cLen,
@@ -667,44 +678,6 @@ void decryptData(WrapToken token, InputStream cipherStream, int cLen,
}
}
- void decryptData(WrapToken_v2 token, InputStream cipherStream, int cLen,
- byte[] plaintext, int pStart, int key_usage)
- throws GSSException, IOException {
-
- // Read encrypted data from stream
- byte[] ciphertext = new byte[cLen];
- try {
- Krb5Token.readFully(cipherStream, ciphertext, 0, cLen);
- } catch (IOException e) {
- GSSException ge = new GSSException(
- GSSException.DEFECTIVE_TOKEN, -1,
- "Cannot read complete token");
- ge.initCause(e);
- throw ge;
- }
- switch (etype) {
- case EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96:
- aes128Decrypt(token, ciphertext, 0, cLen,
- plaintext, pStart, key_usage);
- break;
- case EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96:
- aes256Decrypt(token, ciphertext, 0, cLen,
- plaintext, pStart, key_usage);
- break;
- case EncryptedData.ETYPE_AES128_CTS_HMAC_SHA256_128:
- aes128Sha2Decrypt(token, ciphertext, 0, cLen,
- plaintext, pStart, key_usage);
- break;
- case EncryptedData.ETYPE_AES256_CTS_HMAC_SHA384_192:
- aes256Sha2Decrypt(token, ciphertext, 0, cLen,
- plaintext, pStart, key_usage);
- break;
- default:
- throw new GSSException(GSSException.FAILURE, -1,
- "Unsupported etype: " + etype);
- }
- }
-
void encryptData(WrapToken token, byte[] confounder, byte[] plaintext,
int start, int len, byte[] padding, OutputStream os)
throws GSSException, IOException {
@@ -824,45 +797,6 @@ void encryptData(WrapToken token, byte[] confounder, byte[] plaintext,
}
}
- /*
- * Encrypt data in the new GSS tokens
- *
- * Wrap Tokens (with confidentiality)
- * { Encrypt(16-byte confounder | plaintext | 16-byte token_header) |
- * 12-byte HMAC }
- * where HMAC is on {16-byte confounder | plaintext | 16-byte token_header}
- * HMAC is not encrypted; it is appended at the end.
- */
- int encryptData(WrapToken_v2 token, byte[] confounder, byte[] tokenHeader,
- byte[] plaintext, int pStart, int pLen, byte[] ciphertext, int cStart,
- int key_usage) throws GSSException {
-
- byte[] ctext;
- switch (etype) {
- case EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96:
- ctext = aes128Encrypt(confounder, tokenHeader,
- plaintext, pStart, pLen, key_usage);
- break;
- case EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96:
- ctext = aes256Encrypt(confounder, tokenHeader,
- plaintext, pStart, pLen, key_usage);
- break;
- case EncryptedData.ETYPE_AES128_CTS_HMAC_SHA256_128:
- ctext = aes128Sha2Encrypt(confounder, tokenHeader,
- plaintext, pStart, pLen, key_usage);
- break;
- case EncryptedData.ETYPE_AES256_CTS_HMAC_SHA384_192:
- ctext = aes256Sha2Encrypt(confounder, tokenHeader,
- plaintext, pStart, pLen, key_usage);
- break;
- default:
- throw new GSSException(GSSException.FAILURE, -1,
- "Unsupported etype: " + etype);
- }
- System.arraycopy(ctext, 0, ciphertext, cStart, ctext.length);
- return ctext.length;
- }
-
// --------------------- DES methods
/**
@@ -1408,14 +1342,11 @@ private byte[] aes128Sha2Encrypt(byte[] confounder, byte[] tokenHeader,
}
}
- private void aes128Decrypt(WrapToken_v2 token, byte[] ciphertext,
- int cStart, int cLen, byte[] plaintext, int pStart, int key_usage)
- throws GSSException {
-
- byte[] ptext;
+ private byte[] aes128Decrypt(byte[] ciphertext,
+ int cStart, int cLen, int key_usage) throws GSSException {
try {
- ptext = Aes128.decryptRaw(keybytes, key_usage,
+ return Aes128.decryptRaw(keybytes, key_usage,
ZERO_IV_AES, ciphertext, cStart, cLen);
} catch (GeneralSecurityException e) {
GSSException ge = new GSSException(GSSException.FAILURE, -1,
@@ -1423,36 +1354,13 @@ private void aes128Decrypt(WrapToken_v2 token, byte[] ciphertext,
ge.initCause(e);
throw ge;
}
-
- /*
- Krb5Token.debug("\naes128Decrypt in: " +
- Krb5Token.getHexBytes(ciphertext, cStart, cLen));
- Krb5Token.debug("\naes128Decrypt plain: " +
- Krb5Token.getHexBytes(ptext));
- Krb5Token.debug("\naes128Decrypt ptext: " +
- Krb5Token.getHexBytes(ptext));
- */
-
- // Strip out confounder and token header
- int len = ptext.length - WrapToken_v2.CONFOUNDER_SIZE -
- WrapToken_v2.TOKEN_HEADER_SIZE;
- System.arraycopy(ptext, WrapToken_v2.CONFOUNDER_SIZE,
- plaintext, pStart, len);
-
- /*
- Krb5Token.debug("\naes128Decrypt plaintext: " +
- Krb5Token.getHexBytes(plaintext, pStart, len));
- */
}
- private void aes128Sha2Decrypt(WrapToken_v2 token, byte[] ciphertext,
- int cStart, int cLen, byte[] plaintext, int pStart, int key_usage)
- throws GSSException {
-
- byte[] ptext;
+ private byte[] aes128Sha2Decrypt(byte[] ciphertext,
+ int cStart, int cLen, int key_usage) throws GSSException {
try {
- ptext = Aes128Sha2.decryptRaw(keybytes, key_usage,
+ return Aes128Sha2.decryptRaw(keybytes, key_usage,
ZERO_IV_AES, ciphertext, cStart, cLen);
} catch (GeneralSecurityException e) {
GSSException ge = new GSSException(GSSException.FAILURE, -1,
@@ -1460,26 +1368,6 @@ private void aes128Sha2Decrypt(WrapToken_v2 token, byte[] ciphertext,
ge.initCause(e);
throw ge;
}
-
- /*
- Krb5Token.debug("\naes128Sha2Decrypt in: " +
- Krb5Token.getHexBytes(ciphertext, cStart, cLen));
- Krb5Token.debug("\naes128Sha2Decrypt plain: " +
- Krb5Token.getHexBytes(ptext));
- Krb5Token.debug("\naes128Sha2Decrypt ptext: " +
- Krb5Token.getHexBytes(ptext));
- */
-
- // Strip out confounder and token header
- int len = ptext.length - WrapToken_v2.CONFOUNDER_SIZE -
- WrapToken_v2.TOKEN_HEADER_SIZE;
- System.arraycopy(ptext, WrapToken_v2.CONFOUNDER_SIZE,
- plaintext, pStart, len);
-
- /*
- Krb5Token.debug("\naes128Sha2Decrypt plaintext: " +
- Krb5Token.getHexBytes(plaintext, pStart, len));
- */
}
private byte[] aes256Encrypt(byte[] confounder, byte[] tokenHeader,
@@ -1546,13 +1434,11 @@ private byte[] aes256Sha2Encrypt(byte[] confounder, byte[] tokenHeader,
}
}
- private void aes256Decrypt(WrapToken_v2 token, byte[] ciphertext,
- int cStart, int cLen, byte[] plaintext, int pStart, int key_usage)
- throws GSSException {
+ private byte[] aes256Decrypt(byte[] ciphertext,
+ int cStart, int cLen, int key_usage) throws GSSException {
- byte[] ptext;
try {
- ptext = Aes256.decryptRaw(keybytes, key_usage,
+ return Aes256.decryptRaw(keybytes, key_usage,
ZERO_IV_AES, ciphertext, cStart, cLen);
} catch (GeneralSecurityException e) {
GSSException ge = new GSSException(GSSException.FAILURE, -1,
@@ -1560,36 +1446,13 @@ private void aes256Decrypt(WrapToken_v2 token, byte[] ciphertext,
ge.initCause(e);
throw ge;
}
-
- /*
- Krb5Token.debug("\naes256Decrypt in: " +
- Krb5Token.getHexBytes(ciphertext, cStart, cLen));
- Krb5Token.debug("\naes256Decrypt plain: " +
- Krb5Token.getHexBytes(ptext));
- Krb5Token.debug("\naes256Decrypt ptext: " +
- Krb5Token.getHexBytes(ptext));
- */
-
- // Strip out confounder and token header
- int len = ptext.length - WrapToken_v2.CONFOUNDER_SIZE -
- WrapToken_v2.TOKEN_HEADER_SIZE;
- System.arraycopy(ptext, WrapToken_v2.CONFOUNDER_SIZE,
- plaintext, pStart, len);
-
- /*
- Krb5Token.debug("\naes128Decrypt plaintext: " +
- Krb5Token.getHexBytes(plaintext, pStart, len));
- */
-
}
- private void aes256Sha2Decrypt(WrapToken_v2 token, byte[] ciphertext,
- int cStart, int cLen, byte[] plaintext, int pStart, int key_usage)
- throws GSSException {
+ private byte[] aes256Sha2Decrypt(byte[] ciphertext,
+ int cStart, int cLen, int key_usage) throws GSSException {
- byte[] ptext;
try {
- ptext = Aes256Sha2.decryptRaw(keybytes, key_usage,
+ return Aes256Sha2.decryptRaw(keybytes, key_usage,
ZERO_IV_AES, ciphertext, cStart, cLen);
} catch (GeneralSecurityException e) {
GSSException ge = new GSSException(GSSException.FAILURE, -1,
@@ -1597,27 +1460,6 @@ private void aes256Sha2Decrypt(WrapToken_v2 token, byte[] ciphertext,
ge.initCause(e);
throw ge;
}
-
- /*
- Krb5Token.debug("\naes256Sha2Decrypt in: " +
- Krb5Token.getHexBytes(ciphertext, cStart, cLen));
- Krb5Token.debug("\naes256Sha2Decrypt plain: " +
- Krb5Token.getHexBytes(ptext));
- Krb5Token.debug("\naes256Sha2Decrypt ptext: " +
- Krb5Token.getHexBytes(ptext));
- */
-
- // Strip out confounder and token header
- int len = ptext.length - WrapToken_v2.CONFOUNDER_SIZE -
- WrapToken_v2.TOKEN_HEADER_SIZE;
- System.arraycopy(ptext, WrapToken_v2.CONFOUNDER_SIZE,
- plaintext, pStart, len);
-
- /*
- Krb5Token.debug("\naes256Sha2Decrypt plaintext: " +
- Krb5Token.getHexBytes(plaintext, pStart, len));
- */
-
}
/**
diff --git a/src/java.security.jgss/share/classes/sun/security/jgss/krb5/Krb5Context.java b/src/java.security.jgss/share/classes/sun/security/jgss/krb5/Krb5Context.java
index b118c9ee2157..d6a5de796721 100644
--- a/src/java.security.jgss/share/classes/sun/security/jgss/krb5/Krb5Context.java
+++ b/src/java.security.jgss/share/classes/sun/security/jgss/krb5/Krb5Context.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2000, 2025, 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
@@ -904,39 +904,6 @@ public final byte[] wrap(byte[] inBuf, int offset, int len,
}
}
- public final int wrap(byte[] inBuf, int inOffset, int len,
- byte[] outBuf, int outOffset,
- MessageProp msgProp) throws GSSException {
-
- if (state != STATE_DONE)
- throw new GSSException(GSSException.NO_CONTEXT, -1,
- "Wrap called in invalid state!");
-
- int retVal = 0;
- try {
- if (cipherHelper.getProto() == 0) {
- WrapToken token =
- new WrapToken(this, msgProp, inBuf, inOffset, len);
- retVal = token.encode(outBuf, outOffset);
- } else if (cipherHelper.getProto() == 1) {
- WrapToken_v2 token =
- new WrapToken_v2(this, msgProp, inBuf, inOffset, len);
- retVal = token.encode(outBuf, outOffset);
- }
- if (DEBUG != null) {
- DEBUG.println("Krb5Context.wrap: token=["
- + getHexBytes(outBuf, outOffset, retVal)
- + "]");
- }
- return retVal;
- } catch (IOException e) {
- GSSException gssException =
- new GSSException(GSSException.FAILURE, -1, e.getMessage());
- gssException.initCause(e);
- throw gssException;
- }
- }
-
public final void wrap(byte[] inBuf, int offset, int len,
OutputStream os, MessageProp msgProp)
throws GSSException {
@@ -994,79 +961,35 @@ public final void wrap(InputStream is, OutputStream os,
public final byte[] unwrap(byte[] inBuf, int offset, int len,
MessageProp msgProp)
- throws GSSException {
-
- if (DEBUG != null) {
- DEBUG.println("Krb5Context.unwrap: token=["
- + getHexBytes(inBuf, offset, len)
- + "]");
- }
-
- if (state != STATE_DONE) {
- throw new GSSException(GSSException.NO_CONTEXT, -1,
- " Unwrap called in invalid state!");
- }
-
- byte[] data = null;
- if (cipherHelper.getProto() == 0) {
- WrapToken token =
- new WrapToken(this, inBuf, offset, len, msgProp);
- data = token.getData();
- setSequencingAndReplayProps(token, msgProp);
- } else if (cipherHelper.getProto() == 1) {
- WrapToken_v2 token =
- new WrapToken_v2(this, inBuf, offset, len, msgProp);
- data = token.getData();
- setSequencingAndReplayProps(token, msgProp);
- }
+ throws GSSException {
- return data;
+ if (DEBUG != null) {
+ DEBUG.println("Krb5Context.unwrap: token=["
+ + getHexBytes(inBuf, offset, len)
+ + "]");
}
- public final int unwrap(byte[] inBuf, int inOffset, int len,
- byte[] outBuf, int outOffset,
- MessageProp msgProp) throws GSSException {
-
- if (state != STATE_DONE)
+ if (state != STATE_DONE) {
throw new GSSException(GSSException.NO_CONTEXT, -1,
- "Unwrap called in invalid state!");
+ " Unwrap called in invalid state!");
+ }
+ byte[] data = null;
if (cipherHelper.getProto() == 0) {
WrapToken token =
- new WrapToken(this, inBuf, inOffset, len, msgProp);
- len = token.getData(outBuf, outOffset);
+ new WrapToken(this, inBuf, offset, len, msgProp);
+ data = token.getData();
setSequencingAndReplayProps(token, msgProp);
} else if (cipherHelper.getProto() == 1) {
WrapToken_v2 token =
- new WrapToken_v2(this, inBuf, inOffset, len, msgProp);
- len = token.getData(outBuf, outOffset);
+ new WrapToken_v2(this, inBuf, offset, len, msgProp);
+ data = token.getData();
setSequencingAndReplayProps(token, msgProp);
}
- return len;
- }
- public final int unwrap(InputStream is,
- byte[] outBuf, int outOffset,
- MessageProp msgProp) throws GSSException {
-
- if (state != STATE_DONE)
- throw new GSSException(GSSException.NO_CONTEXT, -1,
- "Unwrap called in invalid state!");
-
- int len = 0;
- if (cipherHelper.getProto() == 0) {
- WrapToken token = new WrapToken(this, is, msgProp);
- len = token.getData(outBuf, outOffset);
- setSequencingAndReplayProps(token, msgProp);
- } else if (cipherHelper.getProto() == 1) {
- WrapToken_v2 token = new WrapToken_v2(this, is, msgProp);
- len = token.getData(outBuf, outOffset);
- setSequencingAndReplayProps(token, msgProp);
- }
- return len;
+ return data;
}
-
public final void unwrap(InputStream is, OutputStream os,
MessageProp msgProp) throws GSSException {
@@ -1120,31 +1043,6 @@ public final byte[] getMIC(byte[] inMsg, int offset, int len,
}
}
- private int getMIC(byte[] inMsg, int offset, int len,
- byte[] outBuf, int outOffset,
- MessageProp msgProp)
- throws GSSException {
-
- int retVal = 0;
- try {
- if (cipherHelper.getProto() == 0) {
- MicToken token =
- new MicToken(this, msgProp, inMsg, offset, len);
- retVal = token.encode(outBuf, outOffset);
- } else if (cipherHelper.getProto() == 1) {
- MicToken_v2 token =
- new MicToken_v2(this, msgProp, inMsg, offset, len);
- retVal = token.encode(outBuf, outOffset);
- }
- return retVal;
- } catch (IOException e) {
- GSSException gssException =
- new GSSException(GSSException.FAILURE, -1, e.getMessage());
- gssException.initCause(e);
- throw gssException;
- }
- }
-
/*
* Checksum calculation requires a byte[]. Hence, might as well pass
* a byte[] into the MicToken constructor. However, writing the
diff --git a/src/java.security.jgss/share/classes/sun/security/jgss/krb5/MessageToken_v2.java b/src/java.security.jgss/share/classes/sun/security/jgss/krb5/MessageToken_v2.java
index 784e4803149b..5eb2dcd265b1 100644
--- a/src/java.security.jgss/share/classes/sun/security/jgss/krb5/MessageToken_v2.java
+++ b/src/java.security.jgss/share/classes/sun/security/jgss/krb5/MessageToken_v2.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2004, 2022, 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
@@ -102,7 +102,9 @@ abstract class MessageToken_v2 extends Krb5Token {
private static final int TOKEN_ID_POS = 0;
private static final int TOKEN_FLAG_POS = 2;
private static final int TOKEN_EC_POS = 4;
- private static final int TOKEN_RRC_POS = 6;
+
+ // Accessed by CipherHelper to normalize RRC
+ public static final int TOKEN_RRC_POS = 6;
/**
* The size of the random confounder used in a WrapToken.
@@ -235,7 +237,14 @@ abstract class MessageToken_v2 extends Krb5Token {
}
if (tokenId == Krb5Token.WRAP_ID_v2) {
- rotate();
+ // According to RFC 4121, we should rotate by RRC. However, Windows
+ // rotates by RRC + EC in DCE-style tokens, and MIT krb5 follows it.
+ // (https://github.com/krb5/krb5/blob/07818f1/src/lib/gssapi/krb5/k5sealv3iov.c#L119)
+ // Non-DCE-style tokens observed from Windows shows EC = 0, and normal
+ // MIT krb5 build hardcoded EC = 0 for non-DCE-style tokens.
+ // (https://github.com/krb5/krb5/blob/07818f1/src/lib/gssapi/krb5/k5sealv3.c#L130),
+ // In this case, adding EC does not change the effective rotation.
+ rotate(prop.getPrivacy() ? (rrc + ec) : rrc);
}
if (tokenId == Krb5Token.MIC_ID_v2 ||
@@ -374,13 +383,13 @@ public final boolean verifySign(byte[] data, int offset, int len)
* Our implementation does not do any rotates when sending, only
* when receiving, we rotate left as per the RRC count, to revert it.
*/
- private void rotate() {
- if (rrc % tokenDataLen != 0) {
- rrc = rrc % tokenDataLen;
+ private void rotate(int rotateNum) {
+ rotateNum = rotateNum % tokenDataLen;
+ if (rotateNum != 0) {
byte[] newBytes = new byte[tokenDataLen];
- System.arraycopy(tokenData, rrc, newBytes, 0, tokenDataLen-rrc);
- System.arraycopy(tokenData, 0, newBytes, tokenDataLen-rrc, rrc);
+ System.arraycopy(tokenData, rotateNum, newBytes, 0, tokenDataLen-rotateNum);
+ System.arraycopy(tokenData, 0, newBytes, tokenDataLen-rotateNum, rotateNum);
tokenData = newBytes;
}
@@ -490,6 +499,15 @@ protected final byte[] getTokenHeader() {
return (tokenHeader.getBytes());
}
+ /**
+ * Returns the EC value for the message token.
+ * @return the EC value
+ * @see CipherHelper#decryptData(WrapToken_v2, byte[], int, int, byte[], int, int)
+ */
+ public final int getEc() {
+ return ec;
+ }
+
// ******************************************* //
// I N N E R C L A S S E S F O L L O W
// ******************************************* //
diff --git a/src/java.security.jgss/share/classes/sun/security/jgss/krb5/WrapToken_v2.java b/src/java.security.jgss/share/classes/sun/security/jgss/krb5/WrapToken_v2.java
index f25dfb464b1b..96f1c78f21ee 100644
--- a/src/java.security.jgss/share/classes/sun/security/jgss/krb5/WrapToken_v2.java
+++ b/src/java.security.jgss/share/classes/sun/security/jgss/krb5/WrapToken_v2.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2004, 2022, 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
@@ -130,7 +130,7 @@ public int getData(byte[] dataBuf, int dataBufOffset)
cipherHelper.decryptData(this, tokenData, 0, tokenDataLen,
dataBuf, dataBufOffset, getKeyUsage());
- return tokenDataLen - CONFOUNDER_SIZE -
+ return tokenDataLen - CONFOUNDER_SIZE - getEc() -
TOKEN_HEADER_SIZE - cipherHelper.getChecksumLength();
} else {
From 42c4f841c8188b51a2e02e8616f7705be26cac2d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Jeli=C5=84ski?=
Date: Wed, 8 Jul 2026 08:45:59 +0000
Subject: [PATCH 037/169] 8386515: Enhance HTTP server parsing
Reviewed-by: rhalade, dfuchs, michaelm, vyazici
---
.../classes/sun/net/httpserver/Request.java | 80 ++++++++-----------
.../sun/net/httpserver/ServerImpl.java | 9 ++-
2 files changed, 41 insertions(+), 48 deletions(-)
diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/Request.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/Request.java
index 75eeb29f015b..ee05ed29d6a0 100644
--- a/src/jdk.httpserver/share/classes/sun/net/httpserver/Request.java
+++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/Request.java
@@ -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
@@ -79,7 +79,7 @@ public OutputStream outputStream() {
*/
public String readLine() throws IOException {
- boolean gotCR = false, gotLF = false;
+ boolean gotLF = false;
pos = 0; lineBuf = new StringBuffer();
long lsize = 32;
@@ -106,34 +106,18 @@ public String readLine() throws IOException {
if (c == -1) {
return null;
}
- if (gotCR) {
- if (c == LF) {
- gotLF = true;
- } else {
- gotCR = false;
- consume(CR);
- if (firstClearRequest && offset == 0) {
- if (c < FIRST_CHAR) {
- throw new ProtocolException("Unexpected start of request line");
- }
- offset++;
- }
- consume(c);
- lsize = lsize + 2;
- }
+ if (c == CR || c == LF) {
+ checkCRLF(c);
+ gotLF = true;
} else {
- if (c == CR) {
- gotCR = true;
- } else {
- if (firstClearRequest && offset == 0) {
- if (c < FIRST_CHAR) {
- throw new ProtocolException("Unexpected start of request line");
- }
- offset++;
+ if (firstClearRequest && offset == 0) {
+ if (c < FIRST_CHAR) {
+ throw new ProtocolException("Unexpected start of request line");
}
- consume(c);
- lsize = lsize + 1;
+ offset++;
}
+ consume(c);
+ lsize = lsize + 1;
}
if (maxReqHeaderSize > 0 && lsize > maxReqHeaderSize) {
throw new IOException("Maximum header (" +
@@ -161,7 +145,16 @@ public String requestLine() {
}
Headers hdrs = null;
- @SuppressWarnings("fallthrough")
+
+ void checkCRLF(int firstc) throws IOException {
+ if (firstc == CR) {
+ int c = is.read();
+ if (c != LF) {
+ throw new ProtocolException("Invalid CR in request header");
+ }
+ }
+ }
+
Headers headers() throws IOException {
if (hdrs != null) {
return hdrs;
@@ -175,20 +168,15 @@ Headers headers() throws IOException {
// check for empty headers
if (firstc == CR || firstc == LF) {
- int c = is.read();
- if (c == CR || c == LF) {
- return hdrs;
- }
- s[0] = (char)firstc;
- len = 1;
- firstc = c;
+ checkCRLF(firstc);
+ return hdrs;
}
long hsize = startLine.length() + 32L;
while (firstc != LF && firstc != CR && firstc >= 0) {
int keyend = -1;
int c;
- boolean inKey = firstc > ' ';
+ boolean inKey = firstc != ' ' && firstc != '\t';
s[len++] = (char) firstc;
hsize = hsize + 1;
parseloop:{
@@ -201,26 +189,24 @@ Headers headers() throws IOException {
: Long.MAX_VALUE;
while ((c = is.read()) >= 0) {
switch (c) {
- /*fallthrough*/
case ':':
- if (inKey && len > 0)
+ if (inKey && len > 0) {
keyend = len;
- inKey = false;
+ inKey = false;
+ }
break;
case '\t':
c = ' ';
- case ' ':
- inKey = false;
break;
case CR:
case LF:
- firstc = is.read();
- if (c == CR && firstc == LF) {
- firstc = is.read();
- if (firstc == CR)
- firstc = is.read();
+ checkCRLF(c);
+ if (inKey) {
+ throw new ProtocolException("Missing field separator");
}
- if (firstc == LF || firstc == CR || firstc > ' ')
+ firstc = is.read();
+ checkCRLF(firstc);
+ if (firstc != ' ' && firstc != '\t')
break parseloop;
/* continuation */
c = ' ';
diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java
index f0a8efe1a6b1..cfc02c3910f7 100644
--- a/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java
+++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java
@@ -790,7 +790,14 @@ public void run() {
start = space+1;
String version = requestLine.substring(start);
- Headers headers = req.headers();
+ Headers headers;
+ try {
+ headers = req.headers();
+ } catch (ProtocolException pe) {
+ reject(Code.HTTP_BAD_REQUEST, requestLine,
+ "Failed to parse headers: " + pe.getMessage());
+ return;
+ }
/* check key for illegal characters */
for (var k : headers.keySet()) {
if (!isValidName(k)) {
From 8dd230157ffcd59ac9528876bd44a822f864e057 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Jeli=C5=84ski?=
Date: Wed, 8 Jul 2026 12:46:45 +0000
Subject: [PATCH 038/169] 8386535: Improve HTTP headers
Reviewed-by: dfuchs, rhalade, michaelm, vyazici
---
.../classes/sun/net/httpserver/Request.java | 33 ++++++++-----------
1 file changed, 14 insertions(+), 19 deletions(-)
diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/Request.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/Request.java
index ee05ed29d6a0..c0e6131456b3 100644
--- a/src/jdk.httpserver/share/classes/sun/net/httpserver/Request.java
+++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/Request.java
@@ -37,7 +37,6 @@
*/
class Request {
- static final int BUF_LEN = 2048;
static final byte CR = 13;
static final byte LF = 10;
static final byte FIRST_CHAR = 32;
@@ -60,11 +59,6 @@ class Request {
} while ("".equals(startLine));
}
-
- char[] buf = new char[BUF_LEN];
- int pos;
- StringBuffer lineBuf;
-
public InputStream inputStream() {
return is;
}
@@ -80,7 +74,7 @@ public OutputStream outputStream() {
public String readLine() throws IOException {
boolean gotLF = false;
- pos = 0; lineBuf = new StringBuffer();
+ StringBuilder lineBuf = new StringBuilder();
long lsize = 32;
// For the first request that comes on a clear connection
@@ -116,7 +110,8 @@ public String readLine() throws IOException {
}
offset++;
}
- consume(c);
+ checkCtl(c);
+ lineBuf.append((char)c);
lsize = lsize + 1;
}
if (maxReqHeaderSize > 0 && lsize > maxReqHeaderSize) {
@@ -125,16 +120,7 @@ public String readLine() throws IOException {
ServerConfig.getMaxReqHeaderSize() + ".");
}
}
- lineBuf.append(buf, 0, pos);
- return new String(lineBuf);
- }
-
- private void consume(int c) throws IOException {
- if (pos == BUF_LEN) {
- lineBuf.append(buf);
- pos = 0;
- }
- buf[pos++] = (char)c;
+ return lineBuf.toString();
}
/**
@@ -155,6 +141,13 @@ void checkCRLF(int firstc) throws IOException {
}
}
+ void checkCtl(int c) throws IOException {
+ if (c == 0x7f ||
+ c < ' ' && c != CR && c!= LF && c != '\t') {
+ throw new ProtocolException("Invalid character in request header");
+ }
+ }
+
Headers headers() throws IOException {
if (hdrs != null) {
return hdrs;
@@ -177,6 +170,7 @@ Headers headers() throws IOException {
int keyend = -1;
int c;
boolean inKey = firstc != ' ' && firstc != '\t';
+ checkCtl(firstc);
s[len++] = (char) firstc;
hsize = hsize + 1;
parseloop:{
@@ -188,6 +182,7 @@ Headers headers() throws IOException {
? maxReqHeaderSize - hsize - 32
: Long.MAX_VALUE;
while ((c = is.read()) >= 0) {
+ checkCtl(c);
switch (c) {
case ':':
if (inKey && len > 0) {
@@ -241,7 +236,7 @@ Headers headers() throws IOException {
}
String v;
if (keyend >= len)
- v = new String();
+ v = "";
else
v = String.copyValueOf(s, keyend, len - keyend);
From 72a42f15b597bfefb1091fe9a0e360de4812a58c Mon Sep 17 00:00:00 2001
From: Weijun Wang
Date: Wed, 8 Jul 2026 16:22:26 +0000
Subject: [PATCH 039/169] 8387217: Enhance krb5 client
Reviewed-by: rhalade, semery, valeriep
---
.../sun/security/krb5/KrbAsReqBuilder.java | 4 +--
.../security/krb5/internal/ETypeInfo2.java | 5 ++-
.../security/krb5/internal/KDCReqBody.java | 2 +-
.../sun/security/krb5/internal/PAData.java | 31 +++++++++++++------
test/lib/jdk/test/lib/security/DerUtils.java | 14 +++++----
5 files changed, 36 insertions(+), 20 deletions(-)
diff --git a/src/java.security.jgss/share/classes/sun/security/krb5/KrbAsReqBuilder.java b/src/java.security.jgss/share/classes/sun/security/krb5/KrbAsReqBuilder.java
index 0d1375756874..c65a92e6b62d 100644
--- a/src/java.security.jgss/share/classes/sun/security/krb5/KrbAsReqBuilder.java
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/KrbAsReqBuilder.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 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
@@ -357,7 +357,7 @@ private KrbAsReqBuilder send() throws KrbException, IOException {
preAuthFailedOnce = true;
KRBError kerr = ke.getError();
int paEType = PAData.getPreferredEType(kerr.getPA(),
- EType.getDefaults("default_tkt_enctypes")[0]);
+ req.getMessage().reqBody.eType);
if (password == null) {
EncryptionKey[] ks = Krb5Util.keysFromJavaxKeyTab(ktab, cname);
pakey = EncryptionKey.findKey(paEType, ks);
diff --git a/src/java.security.jgss/share/classes/sun/security/krb5/internal/ETypeInfo2.java b/src/java.security.jgss/share/classes/sun/security/krb5/internal/ETypeInfo2.java
index c1abbaa3f52f..c9a6d096743a 100644
--- a/src/java.security.jgss/share/classes/sun/security/krb5/internal/ETypeInfo2.java
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/internal/ETypeInfo2.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2005, 2009, 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
@@ -165,4 +165,7 @@ public byte[] getParams() {
return ((s2kparams == null) ? null : s2kparams.clone());
}
+ public boolean hasParams() {
+ return s2kparams != null;
+ }
}
diff --git a/src/java.security.jgss/share/classes/sun/security/krb5/internal/KDCReqBody.java b/src/java.security.jgss/share/classes/sun/security/krb5/internal/KDCReqBody.java
index 8579dfdac412..d0354095a5be 100644
--- a/src/java.security.jgss/share/classes/sun/security/krb5/internal/KDCReqBody.java
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/internal/KDCReqBody.java
@@ -77,9 +77,9 @@ public class KDCReqBody {
public KerberosTime till;
public KerberosTime rtime; //optional
public HostAddresses addresses; //optional
+ public int[] eType = null; //a sequence; not optional
private int nonce;
- private int[] eType = null; //a sequence; not optional
private EncryptedData encAuthorizationData; //optional
private Ticket[] additionalTickets; //optional
diff --git a/src/java.security.jgss/share/classes/sun/security/krb5/internal/PAData.java b/src/java.security.jgss/share/classes/sun/security/krb5/internal/PAData.java
index bcbae770505d..4b95bfc6b09e 100644
--- a/src/java.security.jgss/share/classes/sun/security/krb5/internal/PAData.java
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/internal/PAData.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2017, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 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
@@ -188,13 +188,13 @@ public static PAData[] parseSequence(DerInputStream data,
*
* (This is useful when PA-DATAs from KRB-ERROR and AS-REP are combined).
*
- * @return the etype, or defaultEType if not enough info
+ * @return the etype, or requestedETypes[0] if not enough info
* @throws Asn1Exception|IOException if there is an encoding error
*/
- public static int getPreferredEType(PAData[] pas, int defaultEType)
+ public static int getPreferredEType(PAData[] pas, int[] requestedETypes)
throws IOException, Asn1Exception {
- if (pas == null) return defaultEType;
+ if (pas == null) return requestedETypes[0];
DerValue d = null, d2 = null;
for (PAData p: pas) {
@@ -209,23 +209,34 @@ public static int getPreferredEType(PAData[] pas, int defaultEType)
}
}
if (d2 != null) {
+ ArrayList offered = new ArrayList<>();
while (d2.data.available() > 0) {
DerValue value = d2.data.getDerValue();
ETypeInfo2 tmp = new ETypeInfo2(value);
- if (EType.isNewer(tmp.getEType()) || tmp.getParams() == null) {
+ if (EType.isNewer(tmp.getEType()) || !tmp.hasParams()) {
// we don't support non-null s2kparams for old etypes
- return tmp.getEType();
+ offered.add(tmp.getEType());
+ }
+ }
+ for (int etype : requestedETypes) {
+ if (offered.contains(etype)) {
+ return etype;
}
}
}
if (d != null) {
+ ArrayList offered = new ArrayList<>();
while (d.data.available() > 0) {
DerValue value = d.data.getDerValue();
- ETypeInfo tmp = new ETypeInfo(value);
- return tmp.getEType();
+ offered.add(new ETypeInfo(value).getEType());
+ }
+ for (int etype : requestedETypes) {
+ if (offered.contains(etype)) {
+ return etype;
+ }
}
}
- return defaultEType;
+ return requestedETypes[0];
}
/**
@@ -280,7 +291,7 @@ public static SaltAndParams getSaltAndParams(int eType, PAData[] pas)
DerValue value = d2.data.getDerValue();
ETypeInfo2 tmp = new ETypeInfo2(value);
if (tmp.getEType() == eType &&
- (EType.isNewer(eType) || tmp.getParams() == null)) {
+ (EType.isNewer(eType) || !tmp.hasParams())) {
// we don't support non-null s2kparams for old etypes
return new SaltAndParams(tmp.getSalt(), tmp.getParams());
}
diff --git a/test/lib/jdk/test/lib/security/DerUtils.java b/test/lib/jdk/test/lib/security/DerUtils.java
index 2323065b5952..ed59dabf7d41 100644
--- a/test/lib/jdk/test/lib/security/DerUtils.java
+++ b/test/lib/jdk/test/lib/security/DerUtils.java
@@ -30,6 +30,7 @@
import sun.security.util.ObjectIdentifier;
import java.io.IOException;
+import java.util.Locale;
public class DerUtils {
/**
@@ -37,9 +38,10 @@ public class DerUtils {
*
* The location of the inner DerValue is expressed as a string, in which
* each character is a step from the outer DerValue into the inner one.
- * If it's a number n, the n'th element (starting from 0) of a sequence
- * is the next step. If it's 'c', the content of an OctetString parsed
- * as a DerValue is the next step. Note that n cannot be bigger than 9.
+ * If it's a number n (in uppercase HEX, from 0 to F), the n'th element
+ * of a sequence is the next step. If it's 'c', the content of an OCTET
+ * STRING parsed as a DerValue is the next step. Note that n cannot be
+ * bigger than 15 (HEX "F").
*
* Attention: do not reuse the return value. DerValue is mutable and
* reading it advances a pointer inside.
@@ -80,7 +82,7 @@ public static DerValue innerDerValue(byte[] data, String location)
} else {
DerInputStream ins = v.getData();
// skip n DerValue in the sequence
- for (int i = 0; i < step - '0'; i++) {
+ for (int i = 0; i < Integer.parseInt(step + "", 16); i++) {
ins.getDerValue();
}
if (ins.available() > 0) {
@@ -183,8 +185,8 @@ private static DerOutputStream modify0(byte[] data, String now, String target,
} else {
for (int i = 0; ; i++) {
// We only support locations of one digit now
- if (i > 9) throw new IllegalStateException("Too big " + i);
- String pos = now + i;
+ if (i > 15) throw new IllegalStateException("Too big " + i);
+ String pos = now + Integer.toHexString(i).toUpperCase(Locale.ROOT);
var sub = DerUtils.innerDerValue(data, pos); // current value
if (sub == null) break; // at the end
if (target.equals(pos)) { // the one we want to change
From dff917e5657efac503974da4b4ff5184ced6f1c1 Mon Sep 17 00:00:00 2001
From: Prasanta Sadhukhan
Date: Sun, 12 Jul 2026 04:15:05 +0000
Subject: [PATCH 040/169] 8386013: Enhance EventListenerList
Reviewed-by: kizune, rhalade, prr, skoivu
---
.../classes/javax/swing/event/EventListenerList.java | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/src/java.desktop/share/classes/javax/swing/event/EventListenerList.java b/src/java.desktop/share/classes/javax/swing/event/EventListenerList.java
index 234a58de4ece..c894f434e2fa 100644
--- a/src/java.desktop/share/classes/javax/swing/event/EventListenerList.java
+++ b/src/java.desktop/share/classes/javax/swing/event/EventListenerList.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2024, 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
@@ -301,9 +301,12 @@ private void readObject(ObjectInputStream s)
ClassLoader cl = Thread.currentThread().getContextClassLoader();
EventListener l = (EventListener)s.readObject();
String name = (String) listenerTypeOrNull;
- @SuppressWarnings("unchecked")
- Class tmp = (Class)Class.forName(name, true, cl);
- add(tmp, l);
+ Class> c = Class.forName(name, false, cl);
+ if (EventListener.class.isAssignableFrom(c)) {
+ @SuppressWarnings("unchecked")
+ Class tmp = (Class) c;
+ add(tmp, l);
+ }
}
}
From 192660424689742b94698b99ad4b6221711f24f5 Mon Sep 17 00:00:00 2001
From: Artur Barashev
Date: Mon, 13 Jul 2026 13:43:38 +0000
Subject: [PATCH 041/169] 8386923: Enhance TLS Extensions
Reviewed-by: ahgross, jnibedita, pkumaraswamy, rhalade, hchao, jnimeh, weijun, mullan
---
.../share/classes/sun/security/ssl/SSLExtensions.java | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/src/java.base/share/classes/sun/security/ssl/SSLExtensions.java b/src/java.base/share/classes/sun/security/ssl/SSLExtensions.java
index c325890a3b66..5c596932dc27 100644
--- a/src/java.base/share/classes/sun/security/ssl/SSLExtensions.java
+++ b/src/java.base/share/classes/sun/security/ssl/SSLExtensions.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018, 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
@@ -68,8 +68,17 @@ final class SSLExtensions {
}
encodedLength = len + 2; // 2: the length of the extensions.
+ Set extIdSet = new HashSet<>(); // To check for duplicates.
+
while (len > 0) {
int extId = Record.getInt16(m);
+
+ if (!extIdSet.add(extId)) {
+ throw hm.handshakeContext.conContext.fatal(
+ Alert.ILLEGAL_PARAMETER,
+ "Duplicate extension with ID: " + extId);
+ }
+
int extLen = Record.getInt16(m);
if (extLen > m.remaining()) {
throw hm.handshakeContext.conContext.fatal(
From b0413bda33fe360d61afa14c302221da77a27086 Mon Sep 17 00:00:00 2001
From: Michael McMahon
Date: Tue, 14 Jul 2026 17:09:32 +0000
Subject: [PATCH 042/169] 8384872: Enhance HTTP Client
Reviewed-by: rhalade, dfuchs, vyazici, skoivu
---
.../jdk/internal/net/http/CheckFilename.java | 76 +++++++++++++++++++
.../net/http/ResponseBodyHandlers.java | 2 +-
2 files changed, 77 insertions(+), 1 deletion(-)
create mode 100644 src/java.net.http/share/classes/jdk/internal/net/http/CheckFilename.java
diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/CheckFilename.java b/src/java.net.http/share/classes/jdk/internal/net/http/CheckFilename.java
new file mode 100644
index 000000000000..fc2b3c428f8a
--- /dev/null
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/CheckFilename.java
@@ -0,0 +1,76 @@
+/*
+ * 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. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.internal.net.http;
+
+import java.util.Set;
+import java.util.List;
+import java.util.Locale;
+import jdk.internal.util.OperatingSystem;
+
+/**
+ * Checks for disallowed filenames that may be provided by a server
+ * which we do not want to write to. On Windows we disallow any filename
+ * that looks like a DOS legacy device. No restrictions on other platforms.
+ */
+final class CheckFilename {
+
+ private CheckFilename() {
+ throw new InternalError();
+ }
+
+ private static final boolean isWindows = OperatingSystem.isWindows();
+
+ /**
+ * Reserved names documented at:
+ * https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file
+ * and other Microsoft api documentation
+ */
+ private static final Set WINDOWS_RESERVED_NAMES =
+ Set.of("AUX", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6",
+ "COM7", "COM8", "COM9", "COM¹", "COM²", "COM³", "CON", "CONIN$",
+ "CONOUT$", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8",
+ "LPT9", "LPT¹", "LPT²", "LPT³", "NUL", "PRN");
+
+ /**
+ * name must be the final component of the pathname only.
+ * Possibly including an extension.
+ */
+ public static boolean isAllowed(String name) {
+ if (!isWindows)
+ return true;
+
+ // Microsoft documents reserved device names followed by extensions as equivalent to the device name.
+ // `CON.txt`, `NUL.tar.gz`, `COM1.txt`, etc. should not be allowed.
+ int dot = name.indexOf('.');
+ if (dot != -1) {
+ name = name.substring(0, dot);
+ }
+ name = name.toUpperCase(Locale.ROOT)
+ .stripTrailing();
+
+ return !WINDOWS_RESERVED_NAMES.contains(name);
+ }
+}
diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/ResponseBodyHandlers.java b/src/java.net.http/share/classes/jdk/internal/net/http/ResponseBodyHandlers.java
index a74c632c2185..d1f5b21e054f 100644
--- a/src/java.net.http/share/classes/jdk/internal/net/http/ResponseBodyHandlers.java
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/ResponseBodyHandlers.java
@@ -320,7 +320,7 @@ public BodySubscriber apply(ResponseInfo responseInfo) {
filenameParam = filenameParam.trim();
- if (PROHIBITED.contains(filenameParam)) {
+ if (PROHIBITED.contains(filenameParam) || !CheckFilename.isAllowed(filenameParam)) {
throw unchecked(responseInfo,
"Prohibited Content-Disposition filename parameter:"
+ filenameParam);
From 5fcdf381b411cd375590d7b2de345792675ee6b7 Mon Sep 17 00:00:00 2001
From: Stuart Marks
Date: Wed, 15 Jul 2026 16:33:23 +0000
Subject: [PATCH 043/169] 8384654: Enhance Template Handling
Reviewed-by: ahgross, rhalade, joehw, naoto, jlu
---
.../xsltc/compiler/util/ErrorMessages.java | 5 ++++
.../xsltc/compiler/util/ErrorMsg.java | 3 ++-
.../internal/xsltc/trax/TemplatesImpl.java | 11 +++++++-
.../jdk/xml/internal/JdkConstants.java | 5 ++++
.../jdk/xml/internal/JdkXmlFeatures.java | 22 ++++++++++++----
src/java.xml/share/classes/module-info.java | 26 +++++++++++++++++--
6 files changed, 63 insertions(+), 9 deletions(-)
diff --git a/src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/compiler/util/ErrorMessages.java b/src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/compiler/util/ErrorMessages.java
index a3875969bb7e..4c5f249326f8 100644
--- a/src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/compiler/util/ErrorMessages.java
+++ b/src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/compiler/util/ErrorMessages.java
@@ -1037,6 +1037,11 @@ public Object[][] getContents()
"smaller templates."
},
+ {ErrorMsg.DESERIALIZE_TRANSLET_ERR,
+ "Support for deserializing TemplatesImpl is disabled. This can be "
+ + "overridden by setting the ''jdk.xml.enableTemplatesImplDeserialization'' "
+ + "property to ''true''."},
+
{ErrorMsg.XPATH_GROUP_LIMIT,
"JAXP0801001: the compiler encountered an XPath expression containing "
+ "''{0}'' groups that exceeds the ''{1}'' limit set by ''{2}''."},
diff --git a/src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/compiler/util/ErrorMsg.java b/src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/compiler/util/ErrorMsg.java
index 8ef4c9c86b60..ad63a44c57a7 100644
--- a/src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/compiler/util/ErrorMsg.java
+++ b/src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/compiler/util/ErrorMsg.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2026, Oracle and/or its affiliates. All rights reserved.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
@@ -174,6 +174,7 @@ public final class ErrorMsg {
public static final String XPATH_GROUP_LIMIT = "XPATH_GROUP_LIMIT";
public static final String XPATH_OPERATOR_LIMIT = "XPATH_OPERATOR_LIMIT";
public static final String XPATH_TOTAL_OPERATOR_LIMIT = "XPATH_TOTAL_OPERATOR_LIMIT";
+ public static final String DESERIALIZE_TRANSLET_ERR = "DESERIALIZE_TEMPLATES_ERR";
// All error messages are localized and are stored in resource bundles.
// This array and the following 4 strings are read from that bundle.
diff --git a/src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/trax/TemplatesImpl.java b/src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/trax/TemplatesImpl.java
index 71aad15b09f6..cef94e450e90 100644
--- a/src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/trax/TemplatesImpl.java
+++ b/src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/trax/TemplatesImpl.java
@@ -58,6 +58,7 @@
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.URIResolver;
import jdk.xml.internal.JdkConstants;
+import jdk.xml.internal.JdkXmlFeatures;
import jdk.xml.internal.XMLSecurityManager;
@@ -261,6 +262,14 @@ public TemplatesImpl() { }
private void readObject(ObjectInputStream is)
throws IOException, ClassNotFoundException
{
+ TransformerFactoryImpl tfactory = new TransformerFactoryImpl();
+
+ if (!tfactory.getFeature(
+ JdkXmlFeatures.XmlFeature.ENABLE_TEMPLATESIMPL_DESERIALIZATION.apiProperty())) {
+ ErrorMsg err = new ErrorMsg(ErrorMsg.DESERIALIZE_TRANSLET_ERR);
+ throw new UnsupportedOperationException(err.toString());
+ }
+
// We have to read serialized fields first.
ObjectInputStream.GetField gf = is.readFields();
_name = (String)gf.get("_name", null);
@@ -275,7 +284,7 @@ private void readObject(ObjectInputStream is)
_uriResolver = (URIResolver) is.readObject();
}
- _tfactory = new TransformerFactoryImpl();
+ _tfactory = tfactory;
}
diff --git a/src/java.xml/share/classes/jdk/xml/internal/JdkConstants.java b/src/java.xml/share/classes/jdk/xml/internal/JdkConstants.java
index 6ef32fb6d282..be0e7ec021a3 100644
--- a/src/java.xml/share/classes/jdk/xml/internal/JdkConstants.java
+++ b/src/java.xml/share/classes/jdk/xml/internal/JdkConstants.java
@@ -435,4 +435,9 @@ public final class JdkConstants {
public static final int CDATA_CHUNK_SIZE_DEFAULT
= SecuritySupport.getJAXPSystemProperty(Integer.class, CDATA_CHUNK_SIZE, "0");
+ /**
+ * Enable TemplatesImpl deserialization.
+ */
+ public static final String SP_ENABLE_TEMPLATESIMPL_DESERIALIZATION =
+ "jdk.xml.enableTemplatesImplDeserialization";
}
diff --git a/src/java.xml/share/classes/jdk/xml/internal/JdkXmlFeatures.java b/src/java.xml/share/classes/jdk/xml/internal/JdkXmlFeatures.java
index fb516d17e24f..13e69ac10c77 100644
--- a/src/java.xml/share/classes/jdk/xml/internal/JdkXmlFeatures.java
+++ b/src/java.xml/share/classes/jdk/xml/internal/JdkXmlFeatures.java
@@ -31,6 +31,7 @@
import static jdk.xml.internal.JdkConstants.XPATH_ALLOW_NON_XSLT_SYS_PROPS;
import static jdk.xml.internal.JdkXmlUtils.SP_USE_CATALOG;
+import static jdk.xml.internal.JdkConstants.SP_ENABLE_TEMPLATESIMPL_DESERIALIZATION;
/**
* This class manages JDK's XML Features. Previously added features and properties
@@ -83,7 +84,16 @@ public static enum XmlFeature {
*/
JDK_XPATH_SYSTEM_PROPERTY(null, XPATH_ALLOW_NON_XSLT_SYS_PROPS,
XPATH_ALLOW_NON_XSLT_SYS_PROPS, false,
- null, null, false, false, true, true);
+ null, null, false, false, true, true),
+
+ /*
+ * Feature TemplatesImplDeserialization
+ * FSP: TemplatesImpl deserialization is enforced by FSP. When FSP is on,
+ * TemplatesImpl deserialization is disabled.
+ */
+ ENABLE_TEMPLATESIMPL_DESERIALIZATION(null,
+ SP_ENABLE_TEMPLATESIMPL_DESERIALIZATION, SP_ENABLE_TEMPLATESIMPL_DESERIALIZATION,
+ false, null, null, false, false, true, true);
private final ImplPropMap implMap;
private final String name;
@@ -103,16 +113,18 @@ public static enum XmlFeature {
* parameters.
* @param name the name of the feature
* @param nameSP the name of the System Property
+ * @param differ
* @param nameOld the name of the corresponding legacy property
* @param nameOldSP the system property of the legacy property
- * @param value the value of the feature
- * @param hasSystem a flag to indicate whether the feature is supported
+ * @param valueDefault the default value of the feature
+ * @param valueEnforced the FSP-enforced value of the feature
+ * @param hasSystem a flag indicating whether a system property is supported for the feature
* @param enforced a flag indicating whether the feature is
* FSP (Feature_Secure_Processing) enforced
* with a System property
*/
XmlFeature(ImplPropMap implMap, String name, String nameSP, boolean differ,
- String nameOld, String nameOldSP, boolean value, boolean valueEnforced,
+ String nameOld, String nameOldSP, boolean valueDefault, boolean valueEnforced,
boolean hasSystem, boolean enforced) {
this.implMap = implMap;
if (implMap != null) {
@@ -127,7 +139,7 @@ public static enum XmlFeature {
this.nameOldSP = nameOldSP;
}
this.differ = differ;
- this.valueDefault = value;
+ this.valueDefault = valueDefault;
this.valueEnforced = valueEnforced;
this.hasSystem = hasSystem;
this.enforced = enforced;
diff --git a/src/java.xml/share/classes/module-info.java b/src/java.xml/share/classes/module-info.java
index 19bb39475fd9..fb2f2fe602d7 100644
--- a/src/java.xml/share/classes/module-info.java
+++ b/src/java.xml/share/classes/module-info.java
@@ -1061,6 +1061,24 @@
*
* DOM
* SAX
@@ -1207,7 +1225,11 @@
* they are set to deny or ignore, other properties that regulate a part or an
* aspect of DTD shall have no effect.
*
- * [8] In the current release, the state of the {@code jdk.xml.resource.access}
+ * [8] This property can be set only via a system property or in the
+ * JAXP Configuration File. Setting this property
+ * on a transformer factory or on a {@code Transformer} instance has no effect.
+ *
+ * [9] In the current release, the state of the {@code jdk.xml.resource.access}
* property does not change when {@link javax.xml.XMLConstants#FEATURE_SECURE_PROCESSING FEATURE_SECURE_PROCESSING}
* (FSP) is enabled. In future releases, it will transition to a restrictive setting
* when FSP is enabled.
From b54d299b081e3e579e62eb512e297d164b1322d1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Jeli=C5=84ski?=
Date: Thu, 16 Jul 2026 19:05:13 +0000
Subject: [PATCH 044/169] 8386525: Enhance HTTP Server Handler
Reviewed-by: rhalade, michaelm, dfuchs
---
.../sun/net/httpserver/ServerConfig.java | 8 +++++++-
.../sun/net/httpserver/ServerImpl.java | 20 +++++++++++++++++++
.../ContextPathMatcherPathPrefixTest.java | 3 +--
test/jdk/com/sun/net/httpserver/Test10.java | 14 +++++++++----
test/jdk/com/sun/net/httpserver/Test3.java | 10 +++++-----
test/jdk/com/sun/net/httpserver/Test4.java | 10 +++++-----
test/jdk/com/sun/net/httpserver/Test5.java | 10 +++++-----
.../com/sun/net/httpserver/bugs/B6361557.java | 13 ++++++++++--
.../com/sun/net/httpserver/bugs/B6393710.java | 4 +++-
.../com/sun/net/httpserver/bugs/B6433018.java | 3 ++-
.../httpserver/bugs/TruncatedRequestBody.java | 6 ++++--
11 files changed, 73 insertions(+), 28 deletions(-)
diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerConfig.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerConfig.java
index 3eda0eb1a484..3bc836186cda 100644
--- a/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerConfig.java
+++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerConfig.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2005, 2024, 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
@@ -71,6 +71,7 @@ class ServerConfig {
// the value of the TCP_NODELAY socket-level option
private static final boolean noDelay;
+ private static final boolean hostValidationDisabled;
static {
@@ -116,6 +117,7 @@ class ServerConfig {
debug = Boolean.getBoolean("sun.net.httpserver.debug");
noDelay = Boolean.getBoolean("sun.net.httpserver.nodelay");
+ hostValidationDisabled = Boolean.getBoolean("sun.net.httpserver.disableHostValidation");
}
@@ -219,4 +221,8 @@ static long getReqRspTimerScheduleMillis() {
static boolean noDelay() {
return noDelay;
}
+
+ static boolean isHostValidationDisabled() {
+ return hostValidationDisabled;
+ }
}
diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java
index cfc02c3910f7..6339e41a3f45 100644
--- a/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java
+++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java
@@ -110,6 +110,7 @@ class ServerImpl {
static final long REQ_RSP_TIMER_SCHEDULE = ServerConfig.getReqRspTimerScheduleMillis();
static final long MAX_REQ_TIME = getTimeMillis(ServerConfig.getMaxReqTime());
static final long MAX_RSP_TIME = getTimeMillis(ServerConfig.getMaxRspTime());
+ static final boolean HOST_VALIDATION_DISABLED = ServerConfig.isHostValidationDisabled();;
static final boolean reqRspTimeoutEnabled = MAX_REQ_TIME != -1 || MAX_RSP_TIME != -1;
// the maximum idle duration for a connection which is currently idle but has served
// some request in the past
@@ -847,6 +848,25 @@ public void run() {
requestCompleted(connection);
}
}
+ /* check if there's a single Host header */
+ if (!HOST_VALIDATION_DISABLED) {
+ List hostValueList = headers.get("Host");
+ if (hostValueList == null || hostValueList.isEmpty()) {
+ if (!version.equalsIgnoreCase("http/1.0")) {
+ reject(400, requestLine, "Missing Host header");
+ return;
+ }
+ } else if (hostValueList.size() > 1) {
+ reject(400, requestLine, "Multiple Host headers");
+ return;
+ } else {
+ String host = hostValueList.get(0);
+ if (host.indexOf(',') != -1) {
+ reject(400, requestLine, "Multiple Host header values");
+ return;
+ }
+ }
+ }
ctx = contexts.findContext(protocol, uri.getPath());
if (ctx == null) {
reject(Code.HTTP_NOT_FOUND,
diff --git a/test/jdk/com/sun/net/httpserver/ContextPathMatcherPathPrefixTest.java b/test/jdk/com/sun/net/httpserver/ContextPathMatcherPathPrefixTest.java
index 6b2606365858..3d302793fedf 100644
--- a/test/jdk/com/sun/net/httpserver/ContextPathMatcherPathPrefixTest.java
+++ b/test/jdk/com/sun/net/httpserver/ContextPathMatcherPathPrefixTest.java
@@ -222,8 +222,7 @@ private void expect0(int statusCode, String requestLinePrefix) throws IOExceptio
// Write the request
byte[] requestBytes = String
- // `Connection: close` is required for `BufferedReader::readLine` to work.
- .format("%s HTTP/1.1\r\nConnection: close\r\n\r\n", requestLinePrefix)
+ .format("%s HTTP/1.0\r\n\r\n", requestLinePrefix)
.getBytes(CHARSET);
outputStream.write(requestBytes);
outputStream.flush();
diff --git a/test/jdk/com/sun/net/httpserver/Test10.java b/test/jdk/com/sun/net/httpserver/Test10.java
index a256acbca118..5eb8b1a7cd3f 100644
--- a/test/jdk/com/sun/net/httpserver/Test10.java
+++ b/test/jdk/com/sun/net/httpserver/Test10.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 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,9 +25,11 @@
* @test
* @bug 7005016
* @summary pit jdk7 b121 sqe test jhttp/HttpServer150013 failing
- * @run main/othervm -Dsun.net.httpserver.clockTick=1000 -Dsun.net.httpserver.idleInterval=3 Test10
* @run main/othervm -Dsun.net.httpserver.clockTick=1000 -Dsun.net.httpserver.idleInterval=3
- * -Djava.net.preferIPv6Addresses Test10
+ * -Dsun.net.httpserver.disableHostValidation=true Test10
+ * @run main/othervm -Dsun.net.httpserver.clockTick=1000 -Dsun.net.httpserver.idleInterval=3
+ * -Djava.net.preferIPv6Addresses
+ * -Dsun.net.httpserver.disableHostValidation=true Test10
*/
import com.sun.net.httpserver.*;
@@ -42,6 +44,7 @@
*/
public class Test10 extends Test {
+ static volatile boolean handlerInvoked;
public static void main (String[] args) throws Exception {
System.out.print ("Test10: ");
Handler handler = new Handler();
@@ -66,10 +69,10 @@ public static void main (String[] args) throws Exception {
}
static class Handler implements HttpHandler {
- volatile int invocation = 0;
public void handle (HttpExchange t)
throws IOException
{
+ handlerInvoked = true;
InputStream is = t.getRequestBody();
while (is.read() != -1);
Headers map = t.getRequestHeaders();
@@ -89,6 +92,9 @@ public static void doClient (int port) throws Exception {
int c;
byte[] b = new byte [1024];
while ((c=is.read(b)) != -1) ;
+ if (!handlerInvoked) {
+ throw new RuntimeException("Handler not invoked");
+ }
is.close();
socket.close();
}
diff --git a/test/jdk/com/sun/net/httpserver/Test3.java b/test/jdk/com/sun/net/httpserver/Test3.java
index bbb307dc9f27..50c364c9cc40 100644
--- a/test/jdk/com/sun/net/httpserver/Test3.java
+++ b/test/jdk/com/sun/net/httpserver/Test3.java
@@ -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
@@ -125,13 +125,13 @@ static void checkBody (InputStream is, String cmp) throws IOException {
"HTTP/1.1 200 OK.*Xtest: 3.*";
public static void doClient (int port) throws Exception {
- String s = "GET /test/1.html HTTP/1.1\r\nContent-length: 20\r\n"+
+ String s = "GET /test/1.html HTTP/1.1\r\nHost: localhost\r\nContent-length: 20\r\n"+
"\r\n" +body1 +
- "GET /test/2.html HTTP/1.1\r\nContent-length: 30\r\n"+
+ "GET /test/2.html HTTP/1.1\r\nHost: localhost\r\nContent-length: 30\r\n"+
"\r\n"+ body2 +
- "GET /test/3.html HTTP/1.1\r\nContent-length: 10\r\n"+
+ "GET /test/3.html HTTP/1.1\r\nHost: localhost\r\nContent-length: 10\r\n"+
"\r\n"+ body3 +
- "GET /test/4.html HTTP/1.1\r\nContent-length: 10\r\n"+
+ "GET /test/4.html HTTP/1.1\r\nHost: localhost\r\nContent-length: 10\r\n"+
"\r\n"+body4;
Socket socket = new Socket (InetAddress.getLoopbackAddress(), port);
diff --git a/test/jdk/com/sun/net/httpserver/Test4.java b/test/jdk/com/sun/net/httpserver/Test4.java
index ba2f5b4ec825..033425fdad75 100644
--- a/test/jdk/com/sun/net/httpserver/Test4.java
+++ b/test/jdk/com/sun/net/httpserver/Test4.java
@@ -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
@@ -124,13 +124,13 @@ static void checkBody (InputStream is, String cmp) throws IOException {
"HTTP/1.1 200 OK.*Xtest: 3.*";
public static void doClient (int port) throws Exception {
- String s = "GET /test/1.html HTTP/1.1\r\nContent-length: 20\r\n"+
+ String s = "GET /test/1.html HTTP/1.1\r\nHost: localhost\r\nContent-length: 20\r\n"+
"\r\n" +body1 +
- "GET /test/2.html HTTP/1.1\r\nContent-length: 30\r\n"+
+ "GET /test/2.html HTTP/1.1\r\nHost: localhost\r\nContent-length: 30\r\n"+
"\r\n"+ body2 +
- "GET /test/3.html HTTP/1.1\r\nContent-length: 10\r\n"+
+ "GET /test/3.html HTTP/1.1\r\nHost: localhost\r\nContent-length: 10\r\n"+
"\r\n"+ body3 +
- "GET /test/4.html HTTP/1.1\r\nContent-length: 10\r\n"+
+ "GET /test/4.html HTTP/1.1\r\nHost: localhost\r\nContent-length: 10\r\n"+
"\r\n"+body4;
Socket socket = new Socket (InetAddress.getLoopbackAddress(), port);
diff --git a/test/jdk/com/sun/net/httpserver/Test5.java b/test/jdk/com/sun/net/httpserver/Test5.java
index 68b7d7b357bc..91d18a7d934d 100644
--- a/test/jdk/com/sun/net/httpserver/Test5.java
+++ b/test/jdk/com/sun/net/httpserver/Test5.java
@@ -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
@@ -123,13 +123,13 @@ static void checkBody (InputStream is, String cmp) throws IOException {
"HTTP/1.1 200 OK.*Xtest: 3.*";
public static void doClient (int port) throws Exception {
- String s = "GET /test/1.html HTTP/1.1\r\nContent-length: 20\r\n"+
+ String s = "GET /test/1.html HTTP/1.1\r\nHost: localhost\r\nContent-length: 20\r\n"+
"\r\n" +body1 +
- "GET /test/2.html HTTP/1.1\r\nContent-length: 30\r\n"+
+ "GET /test/2.html HTTP/1.1\r\nHost: localhost\r\nContent-length: 30\r\n"+
"\r\n"+ body2 +
- "GET /test/3.html HTTP/1.1\r\nContent-length: 10\r\n"+
+ "GET /test/3.html HTTP/1.1\r\nHost: localhost\r\nContent-length: 10\r\n"+
"\r\n"+ body3 +
- "GET /test/4.html HTTP/1.1\r\nContent-length: 10\r\n"+
+ "GET /test/4.html HTTP/1.1\r\nHost: localhost\r\nContent-length: 10\r\n"+
"\r\n"+body4;
Socket socket = new Socket (InetAddress.getLoopbackAddress(), port);
diff --git a/test/jdk/com/sun/net/httpserver/bugs/B6361557.java b/test/jdk/com/sun/net/httpserver/bugs/B6361557.java
index a92bb9adfdc5..a8c274e9b100 100644
--- a/test/jdk/com/sun/net/httpserver/bugs/B6361557.java
+++ b/test/jdk/com/sun/net/httpserver/bugs/B6361557.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2006, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2006, 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,6 +36,8 @@
import java.nio.*;
import java.nio.channels.*;
import java.net.*;
+import java.util.concurrent.atomic.AtomicInteger;
+
import static com.sun.net.httpserver.HttpExchange.RSPBODY_EMPTY;
/**
@@ -49,12 +51,14 @@ public class B6361557 {
public static boolean error = false;
static final int NUM = 1000;
+ static final AtomicInteger count = new AtomicInteger(0);
static class Handler implements HttpHandler {
int invocation = 1;
public void handle (HttpExchange t)
throws IOException
{
+ count.getAndIncrement();
InputStream is = t.getRequestBody();
Headers map = t.getRequestHeaders();
Headers rmap = t.getResponseHeaders();
@@ -65,7 +69,9 @@ public void handle (HttpExchange t)
}
}
- final static String request = "GET /test/foo.html HTTP/1.1\r\nContent-length: 0\r\n\r\n";
+ final static String request = "GET /test/foo.html HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
+ "Content-length: 0\r\n\r\n";
final static ByteBuffer requestBuf = ByteBuffer.wrap(request.getBytes());
public static void main (String[] args) throws Exception {
@@ -127,6 +133,9 @@ public static void main (String[] args) throws Exception {
break;
}
}
+ if (count.get() != NUM) {
+ throw new RuntimeException("Unexpected number of handled requests: " +count.get());
+ }
server.stop(0);
selector.close();
executor.shutdown ();
diff --git a/test/jdk/com/sun/net/httpserver/bugs/B6393710.java b/test/jdk/com/sun/net/httpserver/bugs/B6393710.java
index 68a3bac259f3..7d7206eaa817 100644
--- a/test/jdk/com/sun/net/httpserver/bugs/B6393710.java
+++ b/test/jdk/com/sun/net/httpserver/bugs/B6393710.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2006, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2006, 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
@@ -58,11 +58,13 @@ public class B6393710 {
*/
static String cmd =
"POST /test/foo HTTP/1.1"+CRLF+
+ "Host: localhost"+CRLF+
"Content-Length: 22"+CRLF+
"Pragma: no-cache"+CRLF+
"Cache-Control: no-cache"+CRLF+ CRLF+
""+
"POST /test/foo HTTP/1.1"+CRLF+
+ "Host: localhost"+CRLF+
"Content-Length: 22"+CRLF+
"Pragma: no-cache"+CRLF+
"Authorization: Basic ZnJlZDpmcmVkcGFzc3dvcmQ="+CRLF+
diff --git a/test/jdk/com/sun/net/httpserver/bugs/B6433018.java b/test/jdk/com/sun/net/httpserver/bugs/B6433018.java
index 1f6876bf8727..ad8c863cd5f9 100644
--- a/test/jdk/com/sun/net/httpserver/bugs/B6433018.java
+++ b/test/jdk/com/sun/net/httpserver/bugs/B6433018.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2006, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2006, 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
@@ -46,6 +46,7 @@ public class B6433018 {
static final String cmd =
"POST /test/item HTTP/1.1"+CRLF+
+ "Host: araku:9999"+CRLF+
"Keep-Alive: 300"+CRLF+
"Proxy-Connection: keep-alive"+CRLF+
"Content-Type: text/xml"+CRLF+
diff --git a/test/jdk/com/sun/net/httpserver/bugs/TruncatedRequestBody.java b/test/jdk/com/sun/net/httpserver/bugs/TruncatedRequestBody.java
index 95a3d1787df8..1ea1e62be8bf 100644
--- a/test/jdk/com/sun/net/httpserver/bugs/TruncatedRequestBody.java
+++ b/test/jdk/com/sun/net/httpserver/bugs/TruncatedRequestBody.java
@@ -112,7 +112,8 @@ public static void main(String[] args) throws IOException, InterruptedException
// Test 1: fixed length
Socket sock = new Socket(loopback, port);
- String s1 = "POST /foo HTTP/1.1\r\nContent-length: 200000\r\n"
+ String s1 = "POST /foo HTTP/1.1\r\nHost: localhost\r\n" +
+ "Content-length: 200000\r\n"
+ "\r\nfoo bar99";
OutputStream os = sock.getOutputStream();
@@ -123,7 +124,8 @@ public static void main(String[] args) throws IOException, InterruptedException
// Test 2: chunked
- String s2 = "POST /foo HTTP/1.1\r\nTransfer-encoding: chunked\r\n\r\n" +
+ String s2 = "POST /foo HTTP/1.1\r\nHost: localhost\r\n" +
+ "Transfer-encoding: chunked\r\n\r\n" +
"100\r\nFoo bar";
sock = new Socket(loopback, port);
os = sock.getOutputStream();
From bf0cc850a441e467706969d5da502d7938d59c99 Mon Sep 17 00:00:00 2001
From: Weijun Wang
Date: Fri, 17 Jul 2026 16:24:55 +0000
Subject: [PATCH 045/169] 8387238: Enhance Kerberos validation
Reviewed-by: rhalade, valeriep, semery
---
.../share/classes/sun/security/krb5/KrbApReq.java | 2 +-
.../share/classes/sun/security/krb5/KrbAsRep.java | 7 +++++--
.../share/classes/sun/security/krb5/KrbAsReqBuilder.java | 2 +-
.../classes/sun/security/krb5/internal/crypto/EType.java | 6 +++---
4 files changed, 10 insertions(+), 7 deletions(-)
diff --git a/src/java.security.jgss/share/classes/sun/security/krb5/KrbApReq.java b/src/java.security.jgss/share/classes/sun/security/krb5/KrbApReq.java
index 8682619096d9..7d32bf9a9f64 100644
--- a/src/java.security.jgss/share/classes/sun/security/krb5/KrbApReq.java
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/KrbApReq.java
@@ -510,7 +510,7 @@ private void createMessage(APOptions apOptions,
// Check that key is one of the permitted types
private static void checkPermittedEType(int target) throws KrbException {
int[] etypes = EType.getDefaults("permitted_enctypes");
- if (!EType.isSupported(target, etypes)) {
+ if (!EType.isOneOf(target, etypes)) {
throw new KrbException(EType.toString(target) +
" encryption type not in permitted_enctypes list");
}
diff --git a/src/java.security.jgss/share/classes/sun/security/krb5/KrbAsRep.java b/src/java.security.jgss/share/classes/sun/security/krb5/KrbAsRep.java
index 2b4bd4da2668..2393284ebf2c 100644
--- a/src/java.security.jgss/share/classes/sun/security/krb5/KrbAsRep.java
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/KrbAsRep.java
@@ -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
@@ -53,11 +53,14 @@ final class KrbAsRep extends KrbKdcRep {
// message, created by initiator after calling
// the decrypt() method
- KrbAsRep(byte[] ibuf) throws
+ KrbAsRep(byte[] ibuf, int[] requestedETypes) throws
KrbException, Asn1Exception, IOException {
DerValue encoding = new DerValue(ibuf);
try {
rep = new ASRep(encoding);
+ if (!EType.isOneOf(rep.encPart.getEType(), requestedETypes)) {
+ throw new KrbException("AS-REP enc_part not using requested etype");
+ }
} catch (Asn1Exception e) {
rep = null;
KRBError err = new KRBError(encoding);
diff --git a/src/java.security.jgss/share/classes/sun/security/krb5/KrbAsReqBuilder.java b/src/java.security.jgss/share/classes/sun/security/krb5/KrbAsReqBuilder.java
index c65a92e6b62d..5bf3bc503dd4 100644
--- a/src/java.security.jgss/share/classes/sun/security/krb5/KrbAsReqBuilder.java
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/KrbAsReqBuilder.java
@@ -344,7 +344,7 @@ private KrbAsReqBuilder send() throws KrbException, IOException {
}
try {
req = build(pakey, referralsState);
- rep = new KrbAsRep(comm.send(req));
+ rep = new KrbAsRep(comm.send(req), req.getMessage().reqBody.eType);
return this;
} catch (KrbException ke) {
if (!preAuthFailedOnce && (
diff --git a/src/java.security.jgss/share/classes/sun/security/krb5/internal/crypto/EType.java b/src/java.security.jgss/share/classes/sun/security/krb5/internal/crypto/EType.java
index ee385affb9e8..874a68fa6d9b 100644
--- a/src/java.security.jgss/share/classes/sun/security/krb5/internal/crypto/EType.java
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/internal/crypto/EType.java
@@ -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
@@ -293,7 +293,7 @@ public static int[] getDefaults(String configName, EncryptionKey[] keys)
}
}
- public static boolean isSupported(int eTypeConst, int[] config) {
+ public static boolean isOneOf(int eTypeConst, int[] config) {
for (int i = 0; i < config.length; i++) {
if (eTypeConst == config[i]) {
return true;
@@ -303,7 +303,7 @@ public static boolean isSupported(int eTypeConst, int[] config) {
}
public static boolean isSupported(int eTypeConst) {
- return isSupported(eTypeConst, supportedETypes);
+ return isOneOf(eTypeConst, supportedETypes);
}
/**
From 3365a373ba1b95d547720cb34369b72f63def412 Mon Sep 17 00:00:00 2001
From: Brent Christian
Date: Fri, 17 Jul 2026 20:44:30 +0000
Subject: [PATCH 046/169] 8384746: Enhance Preferences
Reviewed-by: rhalade, jpai, naoto
---
.../unix/native/libprefs/FileSystemPreferences.c | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/src/java.prefs/unix/native/libprefs/FileSystemPreferences.c b/src/java.prefs/unix/native/libprefs/FileSystemPreferences.c
index 4dc4137da2a7..542438ef3086 100644
--- a/src/java.prefs/unix/native/libprefs/FileSystemPreferences.c
+++ b/src/java.prefs/unix/native/libprefs/FileSystemPreferences.c
@@ -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
@@ -67,7 +67,6 @@ Java_java_util_prefs_FileSystemPreferences_lockFile0(JNIEnv *env,
int fd, rc;
int result[2] = {0, 0};
jintArray javaResult = NULL;
- int old_umask;
struct flock fl;
if (!fname)
@@ -86,10 +85,12 @@ Java_java_util_prefs_FileSystemPreferences_lockFile0(JNIEnv *env,
fd = open(fname, O_RDONLY, 0);
result[1] = errno;
} else {
- old_umask = umask(0);
fd = open(fname, O_WRONLY|O_CREAT, permission);
result[1] = errno;
- umask(old_umask);
+ if (fd >= 0) {
+ /* lock file created; set correct permissions */
+ fchmod(fd, permission); /* assume will succeed, ignore retval & errno */
+ }
}
if (fd < 0) {
From 85b119e6a78accb5f0c6676f0b6500059deea8d1 Mon Sep 17 00:00:00 2001
From: Xueming Shen
Date: Sat, 25 Jul 2026 00:53:12 +0000
Subject: [PATCH 047/169] 8384688: Enhance jmod handling
Reviewed-by: lancea, alanb
---
.../classes/jdk/internal/jmod/JmodFile.java | 53 ++++++++++++++++++-
1 file changed, 51 insertions(+), 2 deletions(-)
diff --git a/src/java.base/share/classes/jdk/internal/jmod/JmodFile.java b/src/java.base/share/classes/jdk/internal/jmod/JmodFile.java
index 987d68f0e66b..74906d2b308f 100644
--- a/src/java.base/share/classes/jdk/internal/jmod/JmodFile.java
+++ b/src/java.base/share/classes/jdk/internal/jmod/JmodFile.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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,7 +113,10 @@ private Entry(ZipEntry e) {
this.zipEntry = e;
this.section = section(name.substring(0, i));
- this.name = name.substring(i+1);
+ this.name = name.substring(i + 1);
+ if (invalidEntryName(this.name)) {
+ throw new RuntimeException("invalid jmod entry: " + name);
+ }
}
/**
@@ -168,6 +171,52 @@ static Section section(String name) {
return NAME_TO_SECTION.get(name);
}
+ private static boolean invalidEntryName(String name) {
+ // JMOD entries are stored as section/name ZIP entries using '/'
+ // as the separator. Reject absolute paths, path names that contain
+ // '\' or '.' or '..' elements, and Windows drive-letter path elements.
+ if (name.startsWith("/") || name.indexOf('\\') >= 0) {
+ return true;
+ }
+ return hasDotOrDotDot(name) || hasWinDrive(name);
+ }
+
+ private static boolean hasDotOrDotDot(String name) {
+ int index = 0;
+ while (index < name.length()) {
+ int start = index;
+ while (index < name.length() && name.charAt(index) != '/') {
+ index++;
+ }
+ if (name.charAt(start) == '.') {
+ int length = index - start;
+ if (length == 1 || (length == 2 && name.charAt(start + 1) == '.')) {
+ return true;
+ }
+ }
+ index++;
+ }
+ return false;
+ }
+
+ private static boolean isWinDriveLetter(char c) {
+ return ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z'));
+ }
+
+ private static boolean hasWinDrive(String name) {
+ int i = 0;
+ while (i + 1 < name.length()) {
+ if (name.charAt(i + 1) == ':' && isWinDriveLetter(name.charAt(i))) {
+ return true;
+ }
+ i = name.indexOf('/', i);
+ if (i < 0) {
+ return false;
+ }
+ i++;
+ }
+ return false;
+ }
}
private final Path file;
From e185170dd1a1706d2af1e93ac9c337f0200151e2 Mon Sep 17 00:00:00 2001
From: Volkan Yazici
Date: Wed, 26 Aug 2026 17:41:31 +0000
Subject: [PATCH 048/169] 8391087:
com/sun/net/httpserver/property/DrainAmountPropertyTest.java fails with
AssertionError
Reviewed-by: djelinski, dfuchs
---
.../sun/net/httpserver/property/DrainAmountPropertyTest.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/test/jdk/com/sun/net/httpserver/property/DrainAmountPropertyTest.java b/test/jdk/com/sun/net/httpserver/property/DrainAmountPropertyTest.java
index 0b060178b6c9..e3368b0304ab 100644
--- a/test/jdk/com/sun/net/httpserver/property/DrainAmountPropertyTest.java
+++ b/test/jdk/com/sun/net/httpserver/property/DrainAmountPropertyTest.java
@@ -170,7 +170,7 @@ private static void sendRequest(
throws IOException {
LOGGER.info("Sending request (target=%s, bodyLength=%s)".formatted(requestTarget, requestBodyLength));
var socketOutput = clientSocket.getOutputStream();
- var request = "%s %s HTTP/1.1%s\r\n\r\n".formatted(
+ var request = "%s %s HTTP/1.1%s\r\nHost: localhost\r\n\r\n".formatted(
requestMethod, requestTarget,
requestBodyLength == null ? "" : "\r\nContent-Length: " + requestBodyLength);
socketOutput.write(request.getBytes(US_ASCII));
From 624e4263a16082fe6d217d87eb5f91ef42c2c0c0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Manuel=20H=C3=A4ssig?=
Date: Mon, 7 Sep 2026 12:26:22 +0000
Subject: [PATCH 049/169] 8390400: Enhance C2 comparisons
Reviewed-by: skoivu, rhalade, thartmann, dlong
---
src/hotspot/cpu/x86/x86.ad | 20 ++++++++++++++------
1 file changed, 14 insertions(+), 6 deletions(-)
diff --git a/src/hotspot/cpu/x86/x86.ad b/src/hotspot/cpu/x86/x86.ad
index fbd01ef59814..383947fa7849 100644
--- a/src/hotspot/cpu/x86/x86.ad
+++ b/src/hotspot/cpu/x86/x86.ad
@@ -10687,10 +10687,12 @@ instruct compareAndExchangeB(
effect(KILL cr);
format %{ "cmpxchgb $mem_ptr,$newval\t# "
- "If rax == $mem_ptr then store $newval into $mem_ptr\n\t" %}
+ "If rax == $mem_ptr then store $newval into $mem_ptr, "
+ "else store $mem_ptr into rax; rax -> byte\n\t" %}
ins_encode %{
__ lock();
__ cmpxchgb($newval$$Register, $mem_ptr$$Address);
+ __ narrow_subword_type($oldval$$Register, T_BYTE);
%}
ins_pipe( pipe_cmpxchg );
%}
@@ -10704,10 +10706,12 @@ instruct compareAndExchangeS(
effect(KILL cr);
format %{ "cmpxchgw $mem_ptr,$newval\t# "
- "If rax == $mem_ptr then store $newval into $mem_ptr\n\t" %}
+ "If rax == $mem_ptr then store $newval into $mem_ptr, "
+ "else store $mem_ptr into rax; rax -> short\n\t" %}
ins_encode %{
__ lock();
__ cmpxchgw($newval$$Register, $mem_ptr$$Address);
+ __ narrow_subword_type($oldval$$Register, T_SHORT);
%}
ins_pipe( pipe_cmpxchg );
%}
@@ -10721,7 +10725,8 @@ instruct compareAndExchangeI(
effect(KILL cr);
format %{ "cmpxchgl $mem_ptr,$newval\t# "
- "If rax == $mem_ptr then store $newval into $mem_ptr\n\t" %}
+ "If rax == $mem_ptr then store $newval into $mem_ptr, "
+ "else store $mem_ptr into rax\n\t" %}
ins_encode %{
__ lock();
__ cmpxchgl($newval$$Register, $mem_ptr$$Address);
@@ -10738,7 +10743,8 @@ instruct compareAndExchangeL(
effect(KILL cr);
format %{ "cmpxchgq $mem_ptr,$newval\t# "
- "If rax == $mem_ptr then store $newval into $mem_ptr\n\t" %}
+ "If rax == $mem_ptr then store $newval into $mem_ptr, "
+ "else store $mem_ptr into rax\n\t" %}
ins_encode %{
__ lock();
__ cmpxchgq($newval$$Register, $mem_ptr$$Address);
@@ -10755,7 +10761,8 @@ instruct compareAndExchangeN(
effect(KILL cr);
format %{ "cmpxchgl $mem_ptr,$newval\t# "
- "If rax == $mem_ptr then store $newval into $mem_ptr\n\t" %}
+ "If rax == $mem_ptr then store $newval into $mem_ptr, "
+ "else store $mem_ptr into rax\n\t" %}
ins_encode %{
__ lock();
__ cmpxchgl($newval$$Register, $mem_ptr$$Address);
@@ -10773,7 +10780,8 @@ instruct compareAndExchangeP(
effect(KILL cr);
format %{ "cmpxchgq $mem_ptr,$newval\t# "
- "If rax == $mem_ptr then store $newval into $mem_ptr\n\t" %}
+ "If rax == $mem_ptr then store $newval into $mem_ptr, "
+ "else store $mem_ptr into rax\n\t" %}
ins_encode %{
__ lock();
__ cmpxchgq($newval$$Register, $mem_ptr$$Address);
From 98c4d2f7b92f2bef85c624adf07306e41ba4622c Mon Sep 17 00:00:00 2001
From: Amit Kumar
Date: Thu, 15 Oct 2026 04:40:51 +0000
Subject: [PATCH 050/169] 8388189: [s390x] Implement post-call NOPs
Reviewed-by: hdhiman, rrich
---
src/hotspot/cpu/ppc/macroAssembler_ppc.cpp | 2 +-
src/hotspot/cpu/s390/assembler_s390.hpp | 9 ++++
.../continuationFreezeThaw_s390.inline.hpp | 2 +-
src/hotspot/cpu/s390/frame_s390.hpp | 12 ++++-
src/hotspot/cpu/s390/frame_s390.inline.hpp | 26 ++++++----
src/hotspot/cpu/s390/macroAssembler_s390.cpp | 10 ++--
src/hotspot/cpu/s390/nativeInst_s390.cpp | 20 ++++++++
src/hotspot/cpu/s390/nativeInst_s390.hpp | 50 ++++++++++++++++---
.../linux_s390/javaThread_linux_s390.cpp | 6 ++-
.../os_cpu/linux_s390/os_linux_s390.cpp | 16 +++---
10 files changed, 118 insertions(+), 35 deletions(-)
diff --git a/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp b/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp
index e3294587f0d8..e2151a6f9545 100644
--- a/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp
+++ b/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp
@@ -1278,7 +1278,7 @@ void MacroAssembler::post_call_nop() {
relocate(post_call_nop_Relocation::spec());
InlineSkippedInstructionsCounter skipCounter(this);
Assembler::emit_int32(Assembler::CMPLI_OPCODE | Assembler::opp_u_field(1, 9, 9));
- assert(is_post_call_nop(*(int*)(pc() - 4)), "post call not not found");
+ assert(is_post_call_nop(*(int*)(pc() - 4)), "post call nop not found");
}
int MacroAssembler::ic_check_size() {
diff --git a/src/hotspot/cpu/s390/assembler_s390.hpp b/src/hotspot/cpu/s390/assembler_s390.hpp
index 11c65ec91bc2..f3407d3af1cf 100644
--- a/src/hotspot/cpu/s390/assembler_s390.hpp
+++ b/src/hotspot/cpu/s390/assembler_s390.hpp
@@ -3281,6 +3281,15 @@ class Assembler : public AbstractAssembler {
static bool is_z_nop(address x) {
return is_z_nop(* (short *) x);
}
+ // A post-call NOP is encoded as CLFI SP, imm32 (RIL-a format, 6 bytes).
+ // Byte 0 = 0xC2 (opcode1), byte 1 = 0xFF (R1=SP=15 in bits 39..36,
+ // opcode2=0xF in bits 35..32). The 4-byte imm32 carries the payload.
+ // Result goes only to the condition code, which is caller-scratch after a call.
+ static bool is_post_call_nop(address x) {
+ // Check the first 2 bytes for the CLFI SP opcode signature (0xC2 0xFF).
+ // Only 2 bytes are read to avoid a page-boundary out-of-bounds access.
+ return (*(unsigned short*) x) == (unsigned short)((0xc2 << 8) | 0xff);
+ }
static bool is_z_illtrap(address x) {
return *(uint8_t*)x == 0u;
}
diff --git a/src/hotspot/cpu/s390/continuationFreezeThaw_s390.inline.hpp b/src/hotspot/cpu/s390/continuationFreezeThaw_s390.inline.hpp
index 725d3623128a..a7d186c0517e 100644
--- a/src/hotspot/cpu/s390/continuationFreezeThaw_s390.inline.hpp
+++ b/src/hotspot/cpu/s390/continuationFreezeThaw_s390.inline.hpp
@@ -331,7 +331,7 @@ inline void Thaw::patch_caller_links(intptr_t* sp, intptr_t* bottom) {
callers_sp = _cont.entryFP();
} else {
assert(!Interpreter::contains(pc), "sp:" PTR_FORMAT " pc:" PTR_FORMAT, p2i(sp), p2i(pc));
- CodeBlob* cb = CodeCache::find_blob(pc);
+ CodeBlob* cb = CodeCache::find_blob_fast(pc);
callers_sp = sp + cb->frame_size();
}
// set the back link
diff --git a/src/hotspot/cpu/s390/frame_s390.hpp b/src/hotspot/cpu/s390/frame_s390.hpp
index 2946ed60d3e7..9256e9ae0d3b 100644
--- a/src/hotspot/cpu/s390/frame_s390.hpp
+++ b/src/hotspot/cpu/s390/frame_s390.hpp
@@ -489,10 +489,19 @@
// Mark a frame as not fully initialized. Must not be used for frames in the valid back chain.
void mark_not_fully_initialized() const { DEBUG_ONLY(own_abi()->callers_sp = NOT_FULLY_INITIALIZED;) }
+ enum class kind {
+ unknown, // The frame's pc is not necessarily in the CodeCache.
+ // CodeCache::find_blob_fast() can yield wrong results here and must not be used.
+ code_blob, // The frame's pc is known to be in the CodeCache but not likely in an nmethod.
+ // CodeCache::find_blob_fast() is correct but not faster.
+ nmethod // This frame is likely from an nmethod.
+ // CodeCache lookup is optimized via NativePostCallNop payload.
+ };
+
private:
// Initialize frame members (_pc and _sp must be given)
- inline void setup();
+ inline void setup(kind knd);
public:
@@ -500,6 +509,7 @@
inline frame(intptr_t* sp, intptr_t* fp, address pc);
// To be used, if sp was not extended to match callee's calling convention.
inline frame(intptr_t* sp, address pc, intptr_t* unextended_sp = nullptr, intptr_t* fp = nullptr, CodeBlob* cb = nullptr);
+ inline frame(intptr_t* sp, address pc, kind knd);
inline frame(intptr_t* sp, intptr_t* unextended_sp, intptr_t* fp, address pc, CodeBlob* cb, const ImmutableOopMap* oop_map, bool on_heap);
inline frame(intptr_t* sp, intptr_t* unextended_sp, intptr_t* fp, address pc, CodeBlob* cb, const ImmutableOopMap* oop_map = nullptr);
diff --git a/src/hotspot/cpu/s390/frame_s390.inline.hpp b/src/hotspot/cpu/s390/frame_s390.inline.hpp
index 7acdd01a9c91..08f86de40e21 100644
--- a/src/hotspot/cpu/s390/frame_s390.inline.hpp
+++ b/src/hotspot/cpu/s390/frame_s390.inline.hpp
@@ -35,14 +35,14 @@
// Inline functions for z/Architecture frames:
// Initialize frame members (_sp must be given)
-inline void frame::setup() {
+inline void frame::setup(kind knd) {
if (_pc == nullptr) {
_pc = (address)own_abi()->return_pc;
assert(_pc != nullptr, "must have PC");
}
if (_cb == nullptr) {
- _cb = CodeCache::find_blob(_pc);
+ _cb = (knd == kind::nmethod) ? CodeCache::find_blob_fast(_pc) : CodeCache::find_blob(_pc);
}
if (_unextended_sp == nullptr) {
@@ -99,20 +99,26 @@ inline frame::frame() : _sp(nullptr), _pc(nullptr), _cb(nullptr), _oop_map(nullp
inline frame::frame(intptr_t* sp, address pc, intptr_t* unextended_sp, intptr_t* fp, CodeBlob* cb)
: _sp(sp), _pc(pc), _cb(cb), _oop_map(nullptr),
_on_heap(false), DEBUG_ONLY(_frame_index(-1) COMMA) _unextended_sp(unextended_sp), _fp(fp) {
- setup();
+ setup(kind::nmethod);
}
-inline frame::frame(intptr_t* sp) : frame(sp, nullptr) {}
+inline frame::frame(intptr_t* sp) : frame(sp, nullptr, kind::nmethod) {}
inline frame::frame(intptr_t* sp, intptr_t* fp, address pc)
: _sp(sp), _pc(pc), _cb(nullptr), _oop_map(nullptr), _deopt_state(unknown),
_on_heap(false), DEBUG_ONLY(_frame_index(-1) COMMA) _unextended_sp(nullptr), _fp(fp) {
- setup();
+ setup(kind::nmethod);
+}
+
+inline frame::frame(intptr_t* sp, address pc, kind knd)
+ : _sp(sp), _pc(pc), _cb(nullptr), _oop_map(nullptr),
+ _on_heap(false), DEBUG_ONLY(_frame_index(-1) COMMA) _unextended_sp(sp), _fp(nullptr) {
+ setup(knd);
}
inline frame::frame(intptr_t* sp, intptr_t* unextended_sp, intptr_t* fp, address pc, CodeBlob* cb, const ImmutableOopMap* oop_map)
:_sp(sp), _pc(pc), _cb(cb), _oop_map(oop_map), _on_heap(false), DEBUG_ONLY(_frame_index(-1) COMMA) _unextended_sp(unextended_sp), _fp(fp) {
- setup();
+ setup(kind::nmethod);
}
inline frame::frame(intptr_t* sp, intptr_t* unextended_sp, intptr_t* fp, address pc, CodeBlob* cb, const ImmutableOopMap* oop_map, bool on_heap)
@@ -120,7 +126,7 @@ inline frame::frame(intptr_t* sp, intptr_t* unextended_sp, intptr_t* fp, address
// In thaw, non-heap frames use this constructor to pass oop_map. I don't know why.
assert(_on_heap || _cb != nullptr, "these frames are always heap frames");
if (cb != nullptr) {
- setup();
+ setup(kind::nmethod);
}
}
@@ -129,7 +135,7 @@ inline frame::frame(intptr_t* sp, intptr_t* unextended_sp, intptr_t* fp, address
inline frame::frame(void* sp, void* pc, void* unextended_sp)
: _sp((intptr_t*)sp), _pc((address)pc), _cb(nullptr), _oop_map(nullptr),
_on_heap(false), DEBUG_ONLY(_frame_index(-1) COMMA) _unextended_sp((intptr_t*)unextended_sp) {
- setup();
+ setup(kind::unknown);
}
#endif
@@ -405,7 +411,7 @@ inline frame frame::sender_raw(RegisterMap* map) const {
// Must be native-compiled frame, i.e. the marshaling code for native
// methods that exists in the core system.
- return frame(sender_sp(), sender_pc());
+ return frame(sender_sp(), sender_pc(), kind::code_blob);
}
inline frame frame::sender_for_compiled_frame(RegisterMap *map) const {
@@ -452,7 +458,7 @@ inline frame frame::sender_for_compiled_frame(RegisterMap *map) const {
return Continuation::continuation_bottom_sender(map->thread(), *this, sender_sp);
}
}
- return frame(sender_sp, sender_pc);
+ return frame(sender_sp, sender_pc, kind::nmethod);
}
template
diff --git a/src/hotspot/cpu/s390/macroAssembler_s390.cpp b/src/hotspot/cpu/s390/macroAssembler_s390.cpp
index b3e1b223bf0e..c4688c0546b1 100644
--- a/src/hotspot/cpu/s390/macroAssembler_s390.cpp
+++ b/src/hotspot/cpu/s390/macroAssembler_s390.cpp
@@ -6928,10 +6928,12 @@ void MacroAssembler::post_call_nop() {
if (!Continuations::enabled()) {
return;
}
- nop();
- // TODO:
- // 1. https://bugs.openjdk.org/browse/JDK-8300002
- // 2. https://bugs.openjdk.org/browse/JDK-8290965
+ // We use CLFI SP, imm32 to encode post call nops.
+ // Refer to NativePostCallNop for details.
+ relocate(post_call_nop_Relocation::spec());
+ InlineSkippedInstructionsCounter skipCounter(this);
+ z_clfi(Z_SP, 0);
+ assert(is_post_call_nop(pc() - 6), "post call nop not found");
}
void MacroAssembler::push_cont_fastpath() {
diff --git a/src/hotspot/cpu/s390/nativeInst_s390.cpp b/src/hotspot/cpu/s390/nativeInst_s390.cpp
index 3520e9a34935..0a49826adf2f 100644
--- a/src/hotspot/cpu/s390/nativeInst_s390.cpp
+++ b/src/hotspot/cpu/s390/nativeInst_s390.cpp
@@ -634,6 +634,26 @@ void NativeGeneralJump::replace_mt_safe(address instr_addr, address code_buffer)
void NativeDeoptInstruction::verify() {
}
+bool NativePostCallNop::patch(int32_t oopmap_slot, int32_t cb_offset) {
+ if (((oopmap_slot & 0xff) != oopmap_slot) || ((cb_offset & 0xffffff) != cb_offset)) {
+ return false; // cannot encode
+ }
+ uint32_t data = ((uint32_t)oopmap_slot << 24) | (uint32_t)cb_offset;
+ assert(data != 0, "must be");
+ // Write the 4-byte payload into the imm32 field of the CLFI instruction
+ // (bytes 2..5). s390 is big-endian so a direct 32-bit write is correct.
+ *(uint32_t*) addr_at(displacement_offset) = data;
+ ICache::invalidate_range(addr_at(0), instruction_size);
+
+ int32_t oopmap_slot_dec, cb_offset_dec;
+ assert(check(), "pcn not recognized after patch");
+ assert(decode(oopmap_slot_dec, cb_offset_dec), "encoding failed");
+ assert(oopmap_slot == oopmap_slot_dec, "oopmap slot encoding is wrong");
+ assert(cb_offset == cb_offset_dec, "cb offset encoding is wrong");
+
+ return true; // successfully encoded
+}
+
void NativePostCallNop::make_deopt() {
NativeDeoptInstruction::insert(addr_at(0));
}
diff --git a/src/hotspot/cpu/s390/nativeInst_s390.hpp b/src/hotspot/cpu/s390/nativeInst_s390.hpp
index 0ba97830bb70..adc0395d4889 100644
--- a/src/hotspot/cpu/s390/nativeInst_s390.hpp
+++ b/src/hotspot/cpu/s390/nativeInst_s390.hpp
@@ -653,16 +653,50 @@ class NativeGeneralJump: public NativeInstruction {
};
class NativePostCallNop: public NativeInstruction {
+
+ // Post-call NOPs are encoded as CLFI R0, imm32 (RIL-a format, 6 bytes):
+ //
+ // Bit |0 7|8 11|12 15|16 47|
+ // +---------+------+------+--------------------------+
+ // | 0xC2 | R1 | 0xF | I2 (imm32) |
+ // +---------+------+------+--------------------------+
+ // | opcode1 | 0x0 |op2=F | oopmap_slot[31:24] |
+ // | |(R0) | | | cb_offset[23:0] |
+ //
+ // Byte 0 = 0xC2 (opcode1, from CLFI_ZOPC bits 47..40)
+ // Byte 1 = 0x0F (R1=R0=0x0 in bits 39..36; opcode2=0xF in bits 35..32)
+ // Bytes 2..5 = imm32 payload
+ //
+ // CLFI sets only the condition code — no GPR is written.
+ // The R1 field is fixed to R0 (encoding 0), allowing check() to
+ // recognize the instruction by testing only the first two bytes.
+
public:
- enum z_specific_constants {
- // The check reads a 2-byte nop instruction. Since s390 nop is 2 bytes (BCR instruction),
- // we can safely read it in a single stage without risk of out-of-bounds access.
- // The nop instruction is checked by is_nop() which reads a short (2 bytes).
- first_check_size = 2
+
+ enum z_pcn_constants {
+ // Only the first 2 bytes are needed to identify the instruction.
+ // This avoids an out-of-bounds read if the return address is at a
+ // page boundary (same rationale as x86 first_check_size).
+ first_check_size = 2,
+ instruction_size = 6, // CLFI is a 6-byte RIL-a instruction
+ displacement_offset = 2 // imm32 payload starts at byte 2
};
- bool check() const { return is_nop(); }
- bool decode(int32_t& oopmap_slot, int32_t& cb_offset) const { return false; }
- bool patch(int32_t oopmap_slot, int32_t cb_offset) { Unimplemented(); return false; }
+
+ bool check() const { return Assembler::is_post_call_nop(addr_at(0)); }
+
+ bool decode(int32_t& oopmap_slot, int32_t& cb_offset) const {
+ // s390 is big-endian; read the 4-byte imm32 at byte offset 2.
+ // Use uint32_t to avoid signed integer arithmetic on the raw bit pattern.
+ uint32_t data = *(uint32_t*) addr_at(displacement_offset);
+ if (data == 0) {
+ return false; // no payload encoded yet
+ }
+ cb_offset = (int32_t)(data & 0xffffff);
+ oopmap_slot = (int32_t)(data >> 24); // top 8 bits, no masking needed
+ return true;
+ }
+
+ bool patch(int32_t oopmap_slot, int32_t cb_offset);
void make_deopt();
};
diff --git a/src/hotspot/os_cpu/linux_s390/javaThread_linux_s390.cpp b/src/hotspot/os_cpu/linux_s390/javaThread_linux_s390.cpp
index 6643187170ab..f63bdb62dff5 100644
--- a/src/hotspot/os_cpu/linux_s390/javaThread_linux_s390.cpp
+++ b/src/hotspot/os_cpu/linux_s390/javaThread_linux_s390.cpp
@@ -47,7 +47,8 @@ frame JavaThread::pd_last_frame() {
}
#endif
- return frame(sp, pc);
+ // Likely the frame of a RuntimeStub (e.g. after a call to the VM).
+ return frame(sp, pc, frame::kind::code_blob);
}
bool JavaThread::pd_get_top_frame_for_profiling(frame* fr_addr, void* ucontext, bool isInJava) {
@@ -71,7 +72,8 @@ bool JavaThread::pd_get_top_frame_for_profiling(frame* fr_addr, void* ucontext,
return false;
}
- frame ret_frame((intptr_t*)uc->uc_mcontext.gregs[15/*Z_SP*/], pc);
+ // pc is from ucontext — could be anywhere (native, compiled, unknown).
+ frame ret_frame((intptr_t*)uc->uc_mcontext.gregs[15/*Z_SP*/], pc, frame::kind::unknown);
if (ret_frame.fp() == nullptr) {
// The found frame does not have a valid frame pointer.
diff --git a/src/hotspot/os_cpu/linux_s390/os_linux_s390.cpp b/src/hotspot/os_cpu/linux_s390/os_linux_s390.cpp
index cbb06d2d07d8..17aa7fd84841 100644
--- a/src/hotspot/os_cpu/linux_s390/os_linux_s390.cpp
+++ b/src/hotspot/os_cpu/linux_s390/os_linux_s390.cpp
@@ -145,14 +145,14 @@ frame os::fetch_frame_from_context(const void* ucVoid) {
// compiled frame stack bang
return fetch_compiled_frame_from_context(ucVoid);
}
- return frame(sp, epc);
+ return frame(sp, epc, frame::kind::unknown);
}
frame os::fetch_compiled_frame_from_context(const void* ucVoid) {
const ucontext_t* uc = (const ucontext_t*)ucVoid;
intptr_t* sp = os::Linux::ucontext_get_sp(uc);
address lr = ucontext_get_lr(uc);
- return frame(sp, lr);
+ return frame(sp, lr, frame::kind::unknown);
}
intptr_t* os::fetch_bcp_from_context(const void* ucVoid) {
@@ -171,23 +171,23 @@ frame os::get_sender_for_C_frame(frame* fr) {
// of gpr10.
if ((Interpreter::code() != nullptr && Interpreter::contains(fr->pc())) ||
(CodeCache::contains(fr->pc()) && !StubRoutines::contains(fr->pc()))) {
- return frame(fr->sender_sp(), fr->sender_pc());
+ return frame(fr->sender_sp(), fr->sender_pc(), frame::kind::unknown);
} else {
if (StubRoutines::contains(fr->pc())) {
StubCodeDesc* desc = StubCodeDesc::desc_for(fr->pc());
if (desc && !strcmp(desc->name(),"call_stub")) {
- return frame(fr->sender_sp(), fr->callstub_sender_pc());
+ return frame(fr->sender_sp(), fr->callstub_sender_pc(), frame::kind::unknown);
} else {
- return frame(fr->sender_sp(), fr->sender_pc());
+ return frame(fr->sender_sp(), fr->sender_pc(), frame::kind::unknown);
}
} else {
intptr_t* sender_sp = fr->sender_sp();
address sender_fp = (address)*sender_sp;
ptrdiff_t entry_len = sender_fp - (address)sender_sp;
if (entry_len < frame::z_abi_160_size) {
- return frame(sender_sp, fr->sender_pc());
+ return frame(sender_sp, fr->sender_pc(), frame::kind::unknown);
} else {
- return frame(sender_sp, fr->native_sender_pc());
+ return frame(sender_sp, fr->native_sender_pc(), frame::kind::unknown);
}
}
}
@@ -200,7 +200,7 @@ frame os::current_frame() {
assert (csp != nullptr, "sp should not be null");
// Pass a dummy pc. This way we don't have to load it from the
// stack, since we don't know in which slot we can find it.
- frame topframe(csp, (address)0x8);
+ frame topframe(csp, (address)0x8, frame::kind::unknown);
if (os::is_first_C_frame(&topframe)) {
// Stack is not walkable.
return frame();
From ca6e6b55dae1dff51babbd63eb34e3bef4bcc8ac Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Jeli=C5=84ski?=
Date: Thu, 15 Oct 2026 06:28:34 +0000
Subject: [PATCH 051/169] 8394512: Remove support for pre-Vista SDKs
Reviewed-by: alanb
---
src/java.base/windows/native/libnio/ch/Net.c | 31 ++-----------------
...ndowsAsynchronousServerSocketChannelImpl.c | 27 ++--------------
.../ch/WindowsAsynchronousSocketChannelImpl.c | 24 ++------------
3 files changed, 9 insertions(+), 73 deletions(-)
diff --git a/src/java.base/windows/native/libnio/ch/Net.c b/src/java.base/windows/native/libnio/ch/Net.c
index aff73441cfd7..d9300ee2028d 100644
--- a/src/java.base/windows/native/libnio/ch/Net.c
+++ b/src/java.base/windows/native/libnio/ch/Net.c
@@ -37,31 +37,6 @@
#include "sun_nio_ch_Net.h"
#include "sun_nio_ch_PollArrayWrapper.h"
-/**
- * Definitions to allow for building with older SDK include files.
- */
-
-#ifndef MCAST_BLOCK_SOURCE
-
-#define MCAST_BLOCK_SOURCE 43
-#define MCAST_UNBLOCK_SOURCE 44
-#define MCAST_JOIN_SOURCE_GROUP 45
-#define MCAST_LEAVE_SOURCE_GROUP 46
-
-#endif /* MCAST_BLOCK_SOURCE */
-
-struct my_ip_mreq_source {
- IN_ADDR imr_multiaddr;
- IN_ADDR imr_sourceaddr;
- IN_ADDR imr_interface;
-};
-
-struct my_group_source_req {
- ULONG gsr_interface;
- SOCKADDR_STORAGE gsr_group;
- SOCKADDR_STORAGE gsr_source;
-};
-
/**
* Copy IPv6 address as jbytearray to target
*/
@@ -455,7 +430,7 @@ Java_sun_nio_ch_Net_joinOrDrop4(JNIEnv *env, jobject this, jboolean join, jobjec
jint group, jint interf, jint source)
{
struct ip_mreq mreq;
- struct my_ip_mreq_source mreq_source;
+ struct ip_mreq_source mreq_source;
int opt, n, optlen;
void* optval;
@@ -487,7 +462,7 @@ JNIEXPORT jint JNICALL
Java_sun_nio_ch_Net_blockOrUnblock4(JNIEnv *env, jobject this, jboolean block, jobject fdo,
jint group, jint interf, jint source)
{
- struct my_ip_mreq_source mreq_source;
+ struct ip_mreq_source mreq_source;
int n;
int opt = (block) ? IP_BLOCK_SOURCE : IP_UNBLOCK_SOURCE;
@@ -518,7 +493,7 @@ static int setGroupSourceReqOption(JNIEnv* env,
jint index,
jbyteArray source)
{
- struct my_group_source_req req;
+ struct group_source_req req;
struct sockaddr_in6* sin6;
req.gsr_interface = (ULONG)index;
diff --git a/src/java.base/windows/native/libnio/ch/WindowsAsynchronousServerSocketChannelImpl.c b/src/java.base/windows/native/libnio/ch/WindowsAsynchronousServerSocketChannelImpl.c
index 2c6de95ac65f..90737969c4a5 100644
--- a/src/java.base/windows/native/libnio/ch/WindowsAsynchronousServerSocketChannelImpl.c
+++ b/src/java.base/windows/native/libnio/ch/WindowsAsynchronousServerSocketChannelImpl.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2008, 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,6 +25,7 @@
#include
#include
+#include
#include "jni.h"
#include "jni_util.h"
@@ -36,29 +37,7 @@
#include "sun_nio_ch_WindowsAsynchronousServerSocketChannelImpl.h"
-#ifndef WSAID_ACCEPTEX
-#define WSAID_ACCEPTEX {0xb5367df1,0xcbac,0x11cf,{0x95,0xca,0x00,0x80,0x5f,0x48,0xa1,0x92}}
-#endif
-
-#ifndef SO_UPDATE_ACCEPT_CONTEXT
-#define SO_UPDATE_ACCEPT_CONTEXT 0x700B
-#endif
-
-
-typedef BOOL (*AcceptEx_t)
-(
- SOCKET sListenSocket,
- SOCKET sAcceptSocket,
- PVOID lpOutputBuffer,
- DWORD dwReceiveDataLength,
- DWORD dwLocalAddressLength,
- DWORD dwRemoteAddressLength,
- LPDWORD lpdwBytesReceived,
- LPOVERLAPPED lpOverlapped
-);
-
-
-static AcceptEx_t AcceptEx_func;
+static LPFN_ACCEPTEX AcceptEx_func;
JNIEXPORT void JNICALL
diff --git a/src/java.base/windows/native/libnio/ch/WindowsAsynchronousSocketChannelImpl.c b/src/java.base/windows/native/libnio/ch/WindowsAsynchronousSocketChannelImpl.c
index b325c34c9d85..b91dde31e3e4 100644
--- a/src/java.base/windows/native/libnio/ch/WindowsAsynchronousSocketChannelImpl.c
+++ b/src/java.base/windows/native/libnio/ch/WindowsAsynchronousSocketChannelImpl.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2008, 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,6 +25,7 @@
#include
#include
+#include
#include
#include "jni.h"
@@ -36,26 +37,7 @@
#include "sun_nio_ch_WindowsAsynchronousSocketChannelImpl.h"
-#ifndef WSAID_CONNECTEX
-#define WSAID_CONNECTEX {0x25a207b9,0xddf3,0x4660,{0x8e,0xe9,0x76,0xe5,0x8c,0x74,0x06,0x3e}}
-#endif
-
-#ifndef SO_UPDATE_CONNECT_CONTEXT
-#define SO_UPDATE_CONNECT_CONTEXT 0x7010
-#endif
-
-typedef BOOL (PASCAL *ConnectEx_t)
-(
- SOCKET s,
- const struct sockaddr* name,
- int namelen,
- PVOID lpSendBuffer,
- DWORD dwSendDataLength,
- LPDWORD lpdwBytesSent,
- LPOVERLAPPED lpOverlapped
-);
-
-static ConnectEx_t ConnectEx_func;
+static LPFN_CONNECTEX ConnectEx_func;
JNIEXPORT void JNICALL
From 00f4e1dfe37abdca1c5ded6726dca9c4d168b240 Mon Sep 17 00:00:00 2001
From: Emanuel Peter
Date: Thu, 15 Oct 2026 07:23:14 +0000
Subject: [PATCH 052/169] 8393645: [REDO] C2 VerifyIterativeGVN: fix many
missing optimization/notification cases for arithmetic nodes
Reviewed-by: qamai, chagedorn
---
src/hotspot/share/opto/addnode.cpp | 4 +
src/hotspot/share/opto/loopopts.cpp | 2 +
src/hotspot/share/opto/node.cpp | 4 +
src/hotspot/share/opto/phaseX.cpp | 553 ++++++++----------
src/hotspot/share/opto/superword.cpp | 6 +
.../c2/igvn/TestMissingAndOrIdealization.java | 66 +++
.../TestMissingNestedMulIdealization.java | 64 ++
...stMissingVariableTimesConIdealization.java | 75 +++
.../loopopts/superword/TestReductions.java | 13 +-
9 files changed, 467 insertions(+), 320 deletions(-)
create mode 100644 test/hotspot/jtreg/compiler/c2/igvn/TestMissingAndOrIdealization.java
create mode 100644 test/hotspot/jtreg/compiler/c2/igvn/TestMissingNestedMulIdealization.java
create mode 100644 test/hotspot/jtreg/compiler/c2/igvn/TestMissingVariableTimesConIdealization.java
diff --git a/src/hotspot/share/opto/addnode.cpp b/src/hotspot/share/opto/addnode.cpp
index aa523fe9be80..4a6659db8794 100644
--- a/src/hotspot/share/opto/addnode.cpp
+++ b/src/hotspot/share/opto/addnode.cpp
@@ -1610,6 +1610,8 @@ Node* MaxLNode::Ideal(PhaseGVN* phase, bool can_reshape) {
}
if (can_reshape) {
return fold_subI_no_underflow_pattern(this, phase);
+ } else {
+ phase->record_for_igvn(this);
}
return nullptr;
}
@@ -1642,6 +1644,8 @@ Node* MinLNode::Ideal(PhaseGVN* phase, bool can_reshape) {
}
if (can_reshape) {
return fold_subI_no_underflow_pattern(this, phase);
+ } else {
+ phase->record_for_igvn(this);
}
return nullptr;
}
diff --git a/src/hotspot/share/opto/loopopts.cpp b/src/hotspot/share/opto/loopopts.cpp
index 77ccf3b31bdd..d5332dbef541 100644
--- a/src/hotspot/share/opto/loopopts.cpp
+++ b/src/hotspot/share/opto/loopopts.cpp
@@ -612,6 +612,8 @@ Node* PhaseIdealLoop::remix_address_expressions(Node* n) {
n_op == Op_MulD) {
if (n2_loop == n_loop) {
assert(n1_loop != n_loop, "");
+ // IGVN may want to swap the edges back later, to commute (canonicalize) the operation.
+ _igvn.rehash_node_delayed(n);
n->swap_edges(1, 2);
}
}
diff --git a/src/hotspot/share/opto/node.cpp b/src/hotspot/share/opto/node.cpp
index d16edc38e239..163b9ee89783 100644
--- a/src/hotspot/share/opto/node.cpp
+++ b/src/hotspot/share/opto/node.cpp
@@ -1238,6 +1238,10 @@ bool Node::has_special_unique_user() const {
} else if (op == Op_LoadUS && n->Opcode() == Op_LShiftI) {
// Condition for RShiftI(LShiftI(LoadUS(...), 16), 16) => LoadS(...), see RShiftINode::Ideal
return true;
+ } else if ((op == Op_LoadB || op == Op_LoadS) && n->Opcode() == Op_AndI) {
+ // AndINode::Ideal turns AndI(LoadB/S) into AndI(LoadUB/US), if the LoadB
+ // only has a single use.
+ return true;
} else if (op == Op_AddL) {
// Condition for convL2I(addL(x,y)) ==> addI(convL2I(x),convL2I(y))
return n->Opcode() == Op_ConvL2I && n->in(1) == this;
diff --git a/src/hotspot/share/opto/phaseX.cpp b/src/hotspot/share/opto/phaseX.cpp
index ffd9ccf19161..51f83f816ffb 100644
--- a/src/hotspot/share/opto/phaseX.cpp
+++ b/src/hotspot/share/opto/phaseX.cpp
@@ -440,22 +440,34 @@ PhaseRenumberLive::PhaseRenumberLive(PhaseGVN* gvn,
assert(worklist.is_subset_of(_useful), "only useful nodes should still be in the worklist");
+ // While we don't need to rehash, some optimizations are sensitive to the
+ // relative ordering of node IDs, for example AddNode::commute wants to keep
+ // in1 < in2. Therefore, we keep the relative ordering stable.
+ uint next_idx = 0;
+ for (int old_idx = 0; old_idx < _old2new_map.length(); old_idx++) {
+ if (_useful.member_set().test(old_idx)) {
+ _old2new_map.at_put(old_idx, next_idx++);
+ }
+ }
+ assert(next_idx == _useful.size(), "must map all useful nodes");
+
// Iterate over the set of live nodes.
for (uint current_idx = 0; current_idx < _useful.size(); current_idx++) {
Node* n = _useful.at(current_idx);
- const Type* type = gvn->type_or_null(n);
- _new_type_array.map(current_idx, type);
+ uint old_idx = n->_idx;
+ int new_idx = _old2new_map.at(old_idx);
+ assert(new_idx >= 0, "every useful node must have new idx");
- assert(_old2new_map.at(n->_idx) == -1, "already seen");
- _old2new_map.at_put(n->_idx, current_idx);
+ const Type* type = gvn->type_or_null(n);
+ _new_type_array.map(new_idx, type);
if (old_node_note_array != nullptr) {
- Node_Notes* nn = C->locate_node_notes(old_node_note_array, n->_idx);
- C->set_node_notes_at(current_idx, nn);
+ Node_Notes* nn = C->locate_node_notes(old_node_note_array, old_idx);
+ C->set_node_notes_at(new_idx, nn);
}
- n->set_idx(current_idx); // Update node ID.
+ n->set_idx(new_idx); // Update node ID.
if (update_embedded_ids(n) < 0) {
_delayed.push(n); // has embedded IDs; handle later
@@ -1391,6 +1403,51 @@ void PhaseIterGVN::verify_Ideal_for(Node* n, bool can_reshape, bool deep_revisit
// because there are known cases where Ideal can optimize after IGVN.
// Some may be expected and cannot be fixed, and others should be fixed.
switch (n->Opcode()) {
+ // BoolNode::Ideal canonicalizes integer comparisons, but only if "!is_counted_loop_exit_test".
+ // We only optimize if:
+ // Cmp -> Bool -> If
+ // And not if:
+ // Cmp -> Bool -> CountedLoopEnd
+ // In loop unswitching, we turn a CountedLoopEnd into an If.
+ // We would now have to notify the input of the modified node,
+ // which we don't do so far.
+ // One simple hack would be to just push the Bool node on
+ // the worklist in that optimization, in an ad-hoc fashon.
+ // But more principled would probably be to do this as part
+ // of replace_input_of, which calls set_req_X, which already
+ // has some notification logic, for example using
+ // Node::has_special_unique_user. There are also other places
+ // the graph could be updated and lose the CountedLoopEnd node,
+ // and in those places we also use Node::has_special_unique_user,
+ // and other related code.
+ // I would suggest we refactor and extend that notification
+ // code in a future RFE.
+ //
+ // Found with:
+ // -XX:VerifyIterativeGVN=1110
+ // compiler/loopopts/TestBaseCountedEndLoopUnswitchCandidate.java
+ case Op_Bool:
+ return;
+
+ // I have encountered some issues with AddHF.
+ // It could be due to issues arising from JDK-8388873, we should
+ // check again after that is fixed.
+ //
+ // Found on aarch64 with:
+ // -XX:-TieredCompilation -XX:VerifyIterativeGVN=1110
+ // compiler/c2/irTests/TestFloat16ScalarOperations.java
+ case Op_AddHF:
+ return;
+
+ // In XorI/LNode::Ideal, we optimize Xor(x, -1), but
+ // only if is_used_in_only_arithmetic: so we would
+ // need notification if the Xor only has arithmetic
+ // nodes. I think this also calls for a generalization
+ // of Node::has_special_unique_user.
+ case Op_XorI:
+ case Op_XorL:
+ return;
+
// RegionNode::Ideal does "Skip around the useless IF diamond".
// 245 IfTrue === 244
// 258 If === 245 257
@@ -1410,182 +1467,6 @@ void PhaseIterGVN::verify_Ideal_for(Node* n, bool can_reshape, bool deep_revisit
case Op_Region:
return;
- // In AddNode::Ideal, we call "commute", which swaps the inputs so
- // that smaller idx are first. Tracking it back, it led me to
- // PhaseIdealLoop::remix_address_expressions which swapped the edges.
- //
- // Example:
- // Before PhaseIdealLoop::remix_address_expressions
- // 154 AddI === _ 12 144
- // After PhaseIdealLoop::remix_address_expressions
- // 154 AddI === _ 144 12
- // After AddNode::Ideal
- // 154 AddI === _ 12 144
- //
- // I suspect that the node should be added to the IGVN worklist after
- // PhaseIdealLoop::remix_address_expressions
- //
- // This is the only case I looked at, there may be others. Found like this:
- // java -XX:VerifyIterativeGVN=0100 -Xbatch --version
- //
- // The following hit the same logic in PhaseIdealLoop::remix_address_expressions.
- //
- // Note: currently all of these fail also for other reasons, for example
- // because of "commute" doing the reordering with the phi below. Once
- // that is resolved, we can come back to this issue here.
- //
- // case Op_AddD:
- // case Op_AddI:
- // case Op_AddL:
- // case Op_AddF:
- // case Op_MulI:
- // case Op_MulL:
- // case Op_MulF:
- // case Op_MulD:
- // if (n->in(1)->_idx > n->in(2)->_idx) {
- // // Expect "commute" to revert this case.
- // return false;
- // }
- // break; // keep verifying
-
- // AddFPNode::Ideal calls "commute", which can reorder the inputs for this:
- // Check for tight loop increments: Loop-phi of Add of loop-phi
- // It wants to take the phi into in(1):
- // 471 Phi === 435 38 390
- // 390 AddF === _ 471 391
- //
- // Other Associative operators are also affected equally.
- //
- // Investigate why this does not happen earlier during IGVN.
- //
- // Found with:
- // test/hotspot/jtreg/compiler/loopopts/superword/ReductionPerf.java
- // -XX:VerifyIterativeGVN=1110
- case Op_AddD:
- //case Op_AddI: // Also affected for other reasons, see case further down.
- //case Op_AddL: // Also affected for other reasons, see case further down.
- case Op_AddF:
- case Op_MulI:
- case Op_MulL:
- case Op_MulF:
- case Op_MulD:
- case Op_MinF:
- case Op_MinD:
- case Op_MaxF:
- case Op_MaxD:
- // XorINode::Ideal
- // Found with:
- // compiler/intrinsics/chacha/TestChaCha20.java
- // -XX:VerifyIterativeGVN=1110
- case Op_XorI:
- case Op_XorL:
- // It seems we may have similar issues with the HF cases.
- // Found with aarch64:
- // compiler/vectorization/TestFloat16VectorOperations.java
- // -XX:VerifyIterativeGVN=1110
- case Op_AddHF:
- case Op_MulHF:
- case Op_MaxHF:
- case Op_MinHF:
- return;
-
- // In MulNode::Ideal the edges can be swapped to help value numbering:
- //
- // // We are OK if right is a constant, or right is a load and
- // // left is a non-constant.
- // if( !(t2->singleton() ||
- // (in(2)->is_Load() && !(t1->singleton() || in(1)->is_Load())) ) ) {
- // if( t1->singleton() || // Left input is a constant?
- // // Otherwise, sort inputs (commutativity) to help value numbering.
- // (in(1)->_idx > in(2)->_idx) ) {
- // swap_edges(1, 2);
- //
- // Why was this not done earlier during IGVN?
- //
- // Found with:
- // test/hotspot/jtreg/gc/stress/gcbasher/TestGCBasherWithG1.java
- // -XX:VerifyIterativeGVN=1110
- case Op_AndI:
- // Same for AndL.
- // Found with:
- // compiler/intrinsics/bigInteger/MontgomeryMultiplyTest.java
- // -XX:VerifyIterativeGVN=1110
- case Op_AndL:
- return;
-
- // SubLNode::Ideal does transform like:
- // Convert "c1 - (y+c0)" into "(c1-c0) - y"
- //
- // In IGVN before verification:
- // 8423 ConvI2L === _ 3519 [[ 8424 ]] #long:-2
- // 8422 ConvI2L === _ 8399 [[ 8424 ]] #long:3..256:www
- // 8424 AddL === _ 8422 8423 [[ 8383 ]] !orig=[8382]
- // 8016 ConL === 0 [[ 8383 ]] #long:0
- // 8383 SubL === _ 8016 8424 [[ 8156 ]] !orig=[8154]
- //
- // And then in verification:
- // 8338 ConL === 0 [[ 8339 8424 ]] #long:-2 <----- Was constant folded.
- // 8422 ConvI2L === _ 8399 [[ 8424 ]] #long:3..256:www
- // 8424 AddL === _ 8422 8338 [[ 8383 ]] !orig=[8382]
- // 8016 ConL === 0 [[ 8383 ]] #long:0
- // 8383 SubL === _ 8016 8424 [[ 8156 ]] !orig=[8154]
- //
- // So the form changed from:
- // c1 - (y + [8423 ConvI2L])
- // to
- // c1 - (y + -2)
- // but the SubL was not added to the IGVN worklist. Investigate why.
- // There could be other issues too.
- //
- // There seems to be a related AddL IGVN optimization that triggers
- // the same SubL optimization, so investigate that too.
- //
- // Found with:
- // java -XX:VerifyIterativeGVN=0100 -Xcomp --version
- case Op_SubL:
- return;
-
- // SubINode::Ideal does
- // Convert "x - (y+c0)" into "(x-y) - c0" AND
- // Convert "c1 - (y+c0)" into "(c1-c0) - y"
- //
- // Investigate why this does not yet happen during IGVN.
- //
- // Found with:
- // test/hotspot/jtreg/compiler/c2/IVTest.java
- // -XX:VerifyIterativeGVN=1110
- case Op_SubI:
- return;
-
- // AddNode::IdealIL does transform like:
- // Convert x + (con - y) into "(x - y) + con"
- //
- // In IGVN before verification:
- // 8382 ConvI2L
- // 8381 ConvI2L === _ 791 [[ 8383 ]] #long:0
- // 8383 SubL === _ 8381 8382
- // 8168 ConvI2L
- // 8156 AddL === _ 8168 8383 [[ 8158 ]]
- //
- // And then in verification:
- // 8424 AddL
- // 8016 ConL === 0 [[ 8383 ]] #long:0 <--- Was constant folded.
- // 8383 SubL === _ 8016 8424
- // 8168 ConvI2L
- // 8156 AddL === _ 8168 8383 [[ 8158 ]]
- //
- // So the form changed from:
- // x + (ConvI2L(0) - [8382 ConvI2L])
- // to
- // x + (0 - [8424 AddL])
- // but the AddL was not added to the IGVN worklist. Investigate why.
- // There could be other issues, too. For example with "commute", see above.
- //
- // Found with:
- // java -XX:VerifyIterativeGVN=0100 -Xcomp --version
- case Op_AddL:
- return;
-
// SubTypeCheckNode::Ideal calls SubTypeCheckNode::verify_helper, which does
// Node* cmp = phase->transform(new CmpPNode(subklass, in(SuperKlass)));
// record_for_cleanup(cmp, phase);
@@ -1683,20 +1564,6 @@ void PhaseIterGVN::verify_Ideal_for(Node* n, bool can_reshape, bool deep_revisit
case Op_ConvI2L:
return;
- // AddNode::IdealIL can do this transform (and similar other ones):
- // Convert "a*b+a*c into a*(b+c)
- // The example had AddI(MulI(a, b), MulI(a, c)). Why did this not happen
- // during IGVN? There was a mutation for one of the MulI, and only
- // after that the pattern was as needed for the optimization. The MulI
- // was added to the IGVN worklist, but not the AddI. This probably
- // can be fixed by adding the correct pattern in add_users_of_use_to_worklist.
- //
- // Found with:
- // test/hotspot/jtreg/compiler/loopopts/superword/ReductionPerf.java
- // -XX:VerifyIterativeGVN=1110
- case Op_AddI:
- return;
-
// ArrayCopyNode::Ideal
// calls ArrayCopyNode::prepare_array_copy
// calls Compile::conv_I2X_index -> is called with sizetype = intcon(0), I think that
@@ -1743,67 +1610,6 @@ void PhaseIterGVN::verify_Ideal_for(Node* n, bool can_reshape, bool deep_revisit
case Op_CastII:
return;
- // MaxLNode::Ideal
- // calls AddNode::Ideal
- // calls commute -> decides to swap edges
- //
- // Another notification issue, because we check inputs of inputs?
- // MaxL -> Phi -> Loop
- // MaxL -> Phi -> MaxL
- //
- // Found with:
- // compiler/c2/irTests/TestIfMinMax.java
- // -XX:VerifyIterativeGVN=1110
- case Op_MaxL:
- case Op_MinL:
- return;
-
- // OrINode::Ideal
- // calls AddNode::Ideal
- // calls commute -> left is Load, right not -> commute.
- //
- // Not sure why notification does not work here, seems like
- // the depth is only 1, so it should work. Needs investigation.
- //
- // Found with:
- // compiler/codegen/TestCharVect2.java#id0
- // -XX:VerifyIterativeGVN=1110
- case Op_OrI:
- case Op_OrL:
- return;
-
- // Bool -> constant folded to 1.
- // Issue with notification?
- //
- // Found with:
- // compiler/c2/irTests/TestVectorizationMismatchedAccess.java
- // -XX:VerifyIterativeGVN=1110
- case Op_Bool:
- return;
-
- // LShiftLNode::Ideal
- // Looks at pattern: "(x + x) << c0", converts it to "x << (c0 + 1)"
- // Probably a notification issue.
- //
- // Found with:
- // compiler/conversions/TestMoveConvI2LOrCastIIThruAddIs.java
- // -ea -esa -XX:CompileThreshold=100 -XX:+UnlockExperimentalVMOptions -server -XX:-TieredCompilation -XX:+IgnoreUnrecognizedVMOptions -XX:VerifyIterativeGVN=1110
- case Op_LShiftL:
- return;
-
- // LShiftINode::Ideal
- // pattern: ((x + con1) << con2) -> x << con2 + con1 << con2
- // Could be issue with notification of inputs of inputs
- //
- // Side-note: should cases like these not be shared between
- // LShiftI and LShiftL?
- //
- // Found with:
- // compiler/escapeAnalysis/Test6689060.java
- // -XX:+IgnoreUnrecognizedVMOptions -XX:VerifyIterativeGVN=1110 -ea -esa -XX:CompileThreshold=100 -XX:+UnlockExperimentalVMOptions -server -XX:-TieredCompilation -XX:+IgnoreUnrecognizedVMOptions -XX:VerifyIterativeGVN=1110
- case Op_LShiftI:
- return;
-
// AddPNode::Ideal seems to do set_req without removing lock first.
// Found with various vector tests tier1-tier3.
case Op_AddP:
@@ -1853,13 +1659,6 @@ void PhaseIterGVN::verify_Ideal_for(Node* n, bool can_reshape, bool deep_revisit
// java -XX:VerifyIterativeGVN=1110 -Xcomp --version
case Op_CmpP:
return;
-
- // MinINode::Ideal
- // Did not investigate, but there are some patterns that might
- // need more notification.
- case Op_MinI:
- case Op_MaxI: // preemptively removed it as well.
- return;
}
if (n->is_Store()) {
@@ -2065,24 +1864,6 @@ void PhaseIterGVN::verify_Identity_for(Node* n) {
case Op_CheckCastPP:
return;
- // In SubNode::Identity, we do:
- // Convert "(X+Y) - Y" into X and "(X+Y) - X" into Y
- // In the example, the AddI had an input replaced, the AddI is
- // added to the IGVN worklist, but the SubI is one link further
- // down and is not added. I checked add_users_of_use_to_worklist
- // where I would expect the SubI would be added, and I cannot
- // find the pattern, only this one:
- // If changed AddI/SubI inputs, check CmpU for range check optimization.
- //
- // Fix this "notification" issue and check if there are any other
- // issues.
- //
- // Found with:
- // java -XX:VerifyIterativeGVN=1000 -Xcomp --version
- case Op_SubI:
- case Op_SubL:
- return;
-
// PhiNode::Identity checks for patterns like:
// r = (x != con) ? x : con;
// that can be constant folded to "x".
@@ -2096,22 +1877,6 @@ void PhaseIterGVN::verify_Identity_for(Node* n) {
// -XX:VerifyIterativeGVN=1110
case Op_Phi:
return;
-
- // ConvI2LNode::Identity does
- // convert I2L(L2I(x)) => x
- //
- // Investigate why this did not already happen during IGVN.
- //
- // Found with:
- // compiler/loopopts/superword/TestDependencyOffsets.java#vanilla-A
- // -XX:VerifyIterativeGVN=1110
- case Op_ConvI2L:
- return;
-
- // AbsINode::Identity
- // Not investigated yet.
- case Op_AbsI:
- return;
}
if (n->is_Load()) {
@@ -2545,8 +2310,18 @@ void PhaseIterGVN::add_users_of_use_to_worklist(Node* n, Node* use, Unique_Node_
}
}
- int use_op = use->Opcode();
- if(use->is_Cmp()) { // Enable CMP/BOOL optimization
+ // AndLNode::Ideal folds GraphKit::mark_word_test patterns. Give it a chance to run.
+ if (n->is_Load() && use->is_Phi()) {
+ for (DUIterator_Fast imax, i = use->fast_outs(imax); i < imax; i++) {
+ Node* u = use->fast_out(i);
+ if (u->Opcode() == Op_AndL) {
+ worklist.push(u);
+ }
+ }
+ }
+
+ const int use_op = use->Opcode();
+ if (use->is_Cmp()) { // Enable CMP/BOOL optimization
add_users_to_worklist0(use, worklist); // Put Bool on worklist
for (DUIterator_Fast jmax, j = use->fast_outs(jmax); j < jmax; j++) {
Node* bol = use->fast_out(j);
@@ -2692,6 +2467,18 @@ void PhaseIterGVN::add_users_of_use_to_worklist(Node* n, Node* use, Unique_Node_
return u->Opcode() == Op_AndI || u->Opcode() == Op_AndL;
});
}
+ // OrI/LNode::Ideal converts shift/or patterns into rotates, which
+ // needs to trigger when x and y common.
+ // e.g. (x << 14) | (y >>> 18)
+ if (use_op == Op_LShiftI || use_op == Op_URShiftI) {
+ add_users_to_worklist_if(worklist, use, [](Node* u) {
+ return u->Opcode() == Op_OrI;
+ });
+ } else if (use_op == Op_LShiftL || use_op == Op_URShiftL) {
+ add_users_to_worklist_if(worklist, use, [](Node* u) {
+ return u->Opcode() == Op_OrL;
+ });
+ }
// If changed LShift inputs, check CompressBits and ExpandBits users for
// compress(x, 1 << n), compress(x, -1 << n),
// expand(x, 1 << n), expand(x, -1 << n) optimizations.
@@ -2701,6 +2488,20 @@ void PhaseIterGVN::add_users_of_use_to_worklist(Node* n, Node* use, Unique_Node_
u->in(2) == use;
});
}
+ // AddNode::Ideal_collapse_variable_times_con optimizes 3-hop pattern:
+ // e.g. ((x << c1) + (x << c2)) + x -> c * x
+ // pattern: n -> use=shift -> add1 -> add2
+ if (use_op == Op_LShiftI || use_op == Op_LShiftL) {
+ const int add_op = (use_op == Op_LShiftI) ? Op_AddI : Op_AddL;
+ for (DUIterator_Fast imax, i = use->fast_outs(imax); i < imax; i++) {
+ Node* add1 = use->fast_out(i);
+ if (add1->Opcode() == add_op) {
+ add_users_to_worklist_if(worklist, add1, [&](Node* add2) {
+ return add2->Opcode() == add_op;
+ });
+ }
+ }
+ }
// If changed ExpandBits inputs, check CompressBits users for
// compress(expand(x, m), m) optimization.
if (use_op == Op_ExpandBits) {
@@ -2721,6 +2522,33 @@ void PhaseIterGVN::add_users_of_use_to_worklist(Node* n, Node* use, Unique_Node_
return u->Opcode() == Op_CmpU;
});
}
+ // LShiftNode::IdealIL:
+ // (x + c1) << c2 -> (x << c2) + c3
+ // (c1 - x) << c2 -> c3 - (x << c2)
+ if (use_op == Op_AddI || use_op == Op_AddL ||
+ use_op == Op_SubI || use_op == Op_SubL) {
+ add_users_to_worklist_if(worklist, use, [](Node* u) {
+ return u->Opcode() == Op_LShiftI || u->Opcode() == Op_LShiftL;
+ });
+ }
+ // AddNode::Ideal reassociates constants to the outside, e.g.:
+ // (x + C) + y -> (x + y) + C
+ // (x | C) | y -> (x | y) | C
+ if (use->is_Add()) {
+ add_users_to_worklist_if(worklist, use, [&](Node* u) {
+ return u->Opcode() == use_op;
+ });
+ }
+ // If changed AddI/AddL inputs, check for add users:
+ // e.g. x - (y + n) -> x - (y + C) -> (x - y) + (-C)
+ // before mutation after mutation optimized by
+ // AddNode::Ideal
+ if (use_op == Op_AddI || use_op == Op_AddL) {
+ int sub_op = (use_op == Op_AddI) ? Op_SubI : Op_SubL;
+ add_users_to_worklist_if(worklist, use, [&](Node* u) {
+ return u->Opcode() == sub_op;
+ });
+ }
// If changed AddI/AddL inputs, check URShift users for
// "((X << z) + Y) >>> z" optimization in URShift{I,L}Node::Ideal.
if (use_op == Op_AddI || use_op == Op_AddL) {
@@ -2749,10 +2577,20 @@ void PhaseIterGVN::add_users_of_use_to_worklist(Node* n, Node* use, Unique_Node_
}
}
// If changed AndI/AndL inputs, check RShift/URShift users for "(x & mask) >> shift" optimization opportunity
+ // LShiftNode::IdealIL optimizes patterns like: LShift(And(RShift(x, c), Y), c)
if (use_op == Op_AndI || use_op == Op_AndL) {
add_users_to_worklist_if(worklist, use, [](Node* u) {
return u->Opcode() == Op_RShiftI || u->Opcode() == Op_RShiftL ||
- u->Opcode() == Op_URShiftI || u->Opcode() == Op_URShiftL;
+ u->Opcode() == Op_URShiftI || u->Opcode() == Op_URShiftL ||
+ u->Opcode() == Op_LShiftI || u->Opcode() == Op_LShiftL;
+ });
+ }
+ // MulNode::AndIL_sum_and_mask can optimize:
+ // AndX(AddX(x, y), mask) -> AndX(x, mask)
+ if (use_op == Op_AddI || use_op == Op_AddL) {
+ const int and_op = (use_op == Op_AddI) ? Op_AndI : Op_AndL;
+ add_users_to_worklist_if(worklist, use, [&](Node* u) {
+ return u->Opcode() == and_op;
});
}
// If changed AndL inputs, check ConvL2I users for
@@ -2769,15 +2607,20 @@ void PhaseIterGVN::add_users_of_use_to_worklist(Node* n, Node* use, Unique_Node_
// ConvI2F->ConvF2I->ConvI2F
// Note: there may be other 3-nodes conversion chains that would require to be added here, but these
// are the only ones that are known to trigger missed optimizations otherwise
+ //
+ // ConvI2LNode::Identity optimizes the following 2-hop optimization, once x is proven to be in int range:
+ // ConvI2L(ConvL2I(x)) -> x
if (use_op == Op_ConvL2D ||
use_op == Op_ConvI2F ||
use_op == Op_ConvL2F ||
- use_op == Op_ConvF2I) {
- add_users_to_worklist_if(worklist, use, [=](Node* u) {
+ use_op == Op_ConvF2I ||
+ use_op == Op_ConvL2I) {
+ add_users_to_worklist_if(worklist, use, [&](Node* u) {
return (use_op == Op_ConvL2D && u->Opcode() == Op_ConvD2L) ||
(use_op == Op_ConvI2F && u->Opcode() == Op_ConvF2I) ||
(use_op == Op_ConvL2F && u->Opcode() == Op_ConvF2L) ||
- (use_op == Op_ConvF2I && u->Opcode() == Op_ConvI2F);
+ (use_op == Op_ConvF2I && u->Opcode() == Op_ConvI2F) ||
+ (use_op == Op_ConvL2I && u->Opcode() == Op_ConvI2L);
});
}
// ConvD2F::Ideal matches ConvD2F(SqrtD(ConvF2D(x))) => SqrtF(x).
@@ -2796,7 +2639,7 @@ void PhaseIterGVN::add_users_of_use_to_worklist(Node* n, Node* use, Unique_Node_
// address expression flattening.
if (use_op == Op_AddP) {
bool offset_changed = n == use->in(AddPNode::Offset);
- add_users_to_worklist_if(worklist, use, [=](Node* u) {
+ add_users_to_worklist_if(worklist, use, [&](Node* u) {
return u->is_Mem() ||
(offset_changed && u->is_AddP() && u->in(AddPNode::Offset)->is_Con());
});
@@ -2809,13 +2652,22 @@ void PhaseIterGVN::add_users_of_use_to_worklist(Node* n, Node* use, Unique_Node_
});
}
// Check for Max/Min(A, Max/Min(B, C)) where A == B or A == C
- if (use->is_MinMax()) {
- add_users_to_worklist_if(worklist, use, [](Node* u) { return u->is_MinMax(); });
+ // MinMaxNode::IdealI also optimizes these cases (if no overflow):
+ // MinI(AddI(x, -1), x) -> AddI(x, -1)
+ if (use->is_MinMax() || use->Opcode() == Op_AddI) {
+ add_users_to_worklist_if(worklist, use, [](Node* u) {
+ return u->is_MinMax();
+ });
}
// Check for A | (B | C) and (B | C) | A where A == B or A == C.
if (use_op == Op_OrI || use_op == Op_OrL) {
add_users_to_worklist_if(worklist, use, [&](Node* u) { return u->Opcode() == use->Opcode(); });
}
+ // MulNode::Ideal idealizes (x | c1) & c2 -> (x & c2) | (c1 & c2)
+ if (use_op == Op_OrI || use_op == Op_OrL) {
+ const int and_op = use_op == Op_OrI ? Op_AndI : Op_AndL;
+ add_users_to_worklist_if(worklist, use, [&](Node* u) { return u->Opcode() == and_op; });
+ }
auto enqueue_init_mem_projs = [&](ProjNode* proj) {
add_users_to_worklist0(proj, worklist);
};
@@ -2976,9 +2828,92 @@ void PhaseIterGVN::add_users_of_use_to_worklist(Node* n, Node* use, Unique_Node_
// If changed Sub inputs, check Add for identity.
// e.g., (x - y) + y -> x; x + (y - x) -> y.
+ // SubI/LNode::Ideal also optimize this:
+ // (x - y) - x -> -y
if (use_op == Op_SubI || use_op == Op_SubL) {
const int add_op = (use_op == Op_SubI) ? Op_AddI : Op_AddL;
- add_users_to_worklist_if(worklist, use, [=](Node* u) { return u->Opcode() == add_op; });
+ add_users_to_worklist_if(worklist, use, [&](Node* u) {
+ return u->Opcode() == add_op || u->Opcode() == use_op;
+ });
+ }
+
+ // If changed Mul inputs, check Add/Sub for common-factor reassociation.
+ // e.g., (a * b) + (a * c) -> a * (b + c).
+ // Additionally, check for nested Mul reassociation.
+ // e.g. (x * c1) * c2 -> x * (c1 * c2)
+ if (use_op == Op_MulI || use_op == Op_MulL) {
+ const int add_op = (use_op == Op_MulI) ? Op_AddI : Op_AddL;
+ const int sub_op = (use_op == Op_MulI) ? Op_SubI : Op_SubL;
+ add_users_to_worklist_if(worklist, use, [&](Node* u) {
+ return u->Opcode() == add_op || u->Opcode() == sub_op || u->Opcode() == use_op;
+ });
+ }
+
+ // MulNode::Ideal distributes constant multiplication:
+ // e.g., (x + c1) * c2 -> x * c2 + (c1 * c2)
+ if (use_op == Op_AddI || use_op == Op_AddL) {
+ const int mul_op = (use_op == Op_AddI) ? Op_MulI : Op_MulL;
+ add_users_to_worklist_if(worklist, use, [&](Node* u) { return u->Opcode() == mul_op; });
+ }
+
+ // We may have a loop-phi that is about to close the AddI recurrence,
+ // and we would have to ensure AddNode::commute is called for the AddI.
+ // e.g. use = Phi(x, n) -> use = Phi(x, u) -> use = Phi(x, u)
+ // u = AddI(y, use) u = AddI(y, use) -> u = AddI(use, y)
+ // current state, After mutation, After commute,
+ // before mutation before commute canonical loop incr
+ //
+ // Alternatively, we may have a region-phi, that is about to turn
+ // into a loop-phi, and where the AddI recurrence is already closed,
+ // and the AddI has its inputs sorted by idx. In this case, we also
+ // need AddNode::commute to canonicalize the AddI, so that the phi
+ // input is on the left now.
+ // e.g. use = Phi(region, u) -> use = Phi(loop, u) -> use = Phi(loop, u)
+ // u = AddI(y, use) u = AddI(y, use) -> u = AddI(use, y)
+ // current state, loop-phi, After commute,
+ // region-phi before commute canonical loop incr
+ if (use->is_Phi() &&
+ use->req() == 3 &&
+ (n == use->in(0) || // possibly: region-phi -> loop-phi
+ n == use->in(LoopNode::LoopBackControl))) { // possibly: closing AddI recurrence
+ add_users_to_worklist_if(worklist, use, [](Node* u) {
+ return u->is_Add();
+ });
+ }
+
+ // AndLNode::Ideal optimizes GraphKit::mark_word_test patterns
+ // Pattern: AndL(LoadL(x=mark word), y)
+ // Pattern: AndL(Phi(LoadL(x=mark word), z), y)
+ if (use->is_Load()) {
+ for (DUIterator_Fast i2max, i2 = use->fast_outs(i2max); i2 < i2max; i2++) {
+ Node* u = use->fast_out(i2);
+ if (u->Opcode() == Op_AndL) {
+ // n -> Load -> AndL
+ worklist.push(u);
+ } else if (u->is_Phi()) {
+ // n -> Load -> Phi -> AndL
+ add_users_to_worklist_if(worklist, u, [](Node* u2) {
+ return u2->Opcode() == Op_AndL;
+ });
+ }
+ }
+ }
+
+ // PhiNode::Ideal can optimize:
+ // Pattern: Phi(condition, x + y, x) -> x + Phi(condition, y, 0)
+ if (use_op == Op_AddI) {
+ add_users_to_worklist_if(worklist, use, [](Node* u) {
+ return u->is_Phi();
+ });
+ }
+
+ // AndVNode::Ideal and OrVNode::Ideal have 2-hop operations for
+ // repeated and/or with the same value:
+ // Pattern: AndV(AndV(a, b), a) -> AndV(a, b)
+ if (use_op == Op_AndV || use_op == Op_OrV) {
+ add_users_to_worklist_if(worklist, use, [&](Node* u) {
+ return u->Opcode() == use_op;
+ });
}
// (Phi (ValueType ValueType)) is transformed into (ValueType (Phi ) (Phi )...)
// and is applied if there are EncodeP/DecodeN or casts between a Phi and InlineType nodes
diff --git a/src/hotspot/share/opto/superword.cpp b/src/hotspot/share/opto/superword.cpp
index 8acdf2b80002..62fa32c7045b 100644
--- a/src/hotspot/share/opto/superword.cpp
+++ b/src/hotspot/share/opto/superword.cpp
@@ -1081,10 +1081,12 @@ SuperWord::PairOrderStatus SuperWord::order_inputs_of_uses_to_match_def_pair(Nod
if (is_marked_reduction(use1) && is_marked_reduction(use2)) {
Node* use1_in2 = use1->in(2);
if (use1_in2->is_Phi() || is_marked_reduction(use1_in2)) {
+ igvn().rehash_node_delayed(use1);
use1->swap_edges(1, 2);
}
Node* use2_in2 = use2->in(2);
if (use2_in2->is_Phi() || is_marked_reduction(use2_in2)) {
+ igvn().rehash_node_delayed(use2);
use2->swap_edges(1, 2);
}
return PairOrderStatus::Ordered;
@@ -1100,14 +1102,17 @@ SuperWord::PairOrderStatus SuperWord::order_inputs_of_uses_to_match_def_pair(Nod
if (i1 != i2) {
if ((i1 == (3-i2)) && (use2->is_Add() || use2->is_Mul())) {
// 2. Commutative: swap edges, and hope the other position matches too.
+ igvn().rehash_node_delayed(use2);
use2->swap_edges(i1, i2);
} else if (VectorNode::is_muladds2i(use2) && use1 != use2) {
// 3.a/b: MulAddS2I.
if (i1 == 5 - i2) { // ((i1 == 3 && i2 == 2) || (i1 == 2 && i2 == 3) || (i1 == 1 && i2 == 4) || (i1 == 4 && i2 == 1))
+ igvn().rehash_node_delayed(use2);
use2->swap_edges(1, 2);
use2->swap_edges(3, 4);
}
if (i1 == 3 - i2 || i1 == 7 - i2) { // ((i1 == 1 && i2 == 2) || (i1 == 2 && i2 == 1) || (i1 == 3 && i2 == 4) || (i1 == 4 && i2 == 3))
+ igvn().rehash_node_delayed(use2);
use2->swap_edges(2, 3);
use2->swap_edges(1, 4);
}
@@ -1118,6 +1123,7 @@ SuperWord::PairOrderStatus SuperWord::order_inputs_of_uses_to_match_def_pair(Nod
}
} else if (i1 == i2 && VectorNode::is_muladds2i(use2) && use1 != use2) {
// 3.c: MulAddS2I.
+ igvn().rehash_node_delayed(use2);
use2->swap_edges(1, 3);
use2->swap_edges(2, 4);
return PairOrderStatus::Unknown;
diff --git a/test/hotspot/jtreg/compiler/c2/igvn/TestMissingAndOrIdealization.java b/test/hotspot/jtreg/compiler/c2/igvn/TestMissingAndOrIdealization.java
new file mode 100644
index 000000000000..02c7805ac3ec
--- /dev/null
+++ b/test/hotspot/jtreg/compiler/c2/igvn/TestMissingAndOrIdealization.java
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ */
+
+package compiler.c2.igvn;
+
+/*
+ * @test
+ * @bug 8393645 8393641 8393586
+ * @summary C2 IGVN should idealize: (x | c1) & c2 -> (x & c2) | (c1 & c2)
+ * @library /test/lib /
+ * @run main/othervm -XX:-TieredCompilation -Xcomp
+ * -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions -XX:VerifyIterativeGVN=1110
+ * -XX:CompileOnly=${test.main.class}::test*
+ * -XX:+StressIGVN
+ * ${test.main.class}
+ * @run main ${test.main.class}
+ */
+
+public class TestMissingAndOrIdealization {
+ public static void main(String[] args) {
+ testI(0, 0.0, false);
+ testL(0, 0.0, false);
+ }
+
+ // Failed to optimize AndI(OrI(x, c1), c2).
+ // Optimization from MulNode::Ideal
+ static int testI(int x, double i, boolean b) {
+ while (i < 4) {
+ x |= b ? -126 : -30;
+ x &= 32;
+ i++;
+ }
+ return x;
+ }
+
+ // Failed to optimize AndL(OrL(x, c1), c2).
+ // Optimization from MulNode::Ideal
+ static long testL(long x, double i, boolean b) {
+ while (i < 4) {
+ x |= b ? -126 : -30;
+ x &= 32;
+ i++;
+ }
+ return x;
+ }
+}
diff --git a/test/hotspot/jtreg/compiler/c2/igvn/TestMissingNestedMulIdealization.java b/test/hotspot/jtreg/compiler/c2/igvn/TestMissingNestedMulIdealization.java
new file mode 100644
index 000000000000..77aa48e4ed65
--- /dev/null
+++ b/test/hotspot/jtreg/compiler/c2/igvn/TestMissingNestedMulIdealization.java
@@ -0,0 +1,64 @@
+/*
+ * 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.
+ */
+
+package compiler.c2.igvn;
+
+/*
+ * @test
+ * @bug 8393645 8393608 8393644
+ * @summary C2 IGVN should idealize: (x * c1) * c2 -> x * (c1 * c2)
+ * @library /test/lib /
+ * @run main/othervm -XX:-TieredCompilation -Xcomp
+ * -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions -XX:VerifyIterativeGVN=1110
+ * -XX:CompileOnly=${test.main.class}::test*
+ * -XX:+StressIGVN
+ * ${test.main.class}
+ * @run main ${test.main.class}
+ */
+
+// The test is somewhat intermittent, more stable: -XX:RepeatCompilation=50
+public class TestMissingNestedMulIdealization {
+
+ public static void main(String[] args) {
+ testI(0);
+ testL(0);
+ }
+
+ // Failed to optimize MulI(MulI(x, c1), c2).
+ // Optimization from MulNode::Ideal
+ static int testI(int x) {
+ for (int i = 0; i < 17; i++) {
+ x *= ~i;
+ }
+ return x;
+ }
+
+ // Failed to optimize MulL(MulL(x, c1), c2).
+ // Optimization from MulNode::Ideal
+ static long testL(long x) {
+ for (long i = 0; i < 17; i++) {
+ x *= ~i;
+ }
+ return x;
+ }
+}
diff --git a/test/hotspot/jtreg/compiler/c2/igvn/TestMissingVariableTimesConIdealization.java b/test/hotspot/jtreg/compiler/c2/igvn/TestMissingVariableTimesConIdealization.java
new file mode 100644
index 000000000000..f0f0f6b41f88
--- /dev/null
+++ b/test/hotspot/jtreg/compiler/c2/igvn/TestMissingVariableTimesConIdealization.java
@@ -0,0 +1,75 @@
+/*
+ * 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.
+ */
+
+package compiler.c2.igvn;
+
+/*
+ * @test
+ * @bug 8393645 8393610 8393640
+ * @summary Missing notification pattern for AddNode::Ideal_collapse_variable_times_con:
+ * ((x << c1) + (x << c2)) + x -> c * x
+ * @library /test/lib /
+ * @run main/othervm -XX:-TieredCompilation -Xbatch
+ * -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions -XX:VerifyIterativeGVN=1110
+ * -XX:CompileOnly=${test.main.class}::test*
+ * -XX:+StressIGVN
+ * ${test.main.class}
+ * @run main/othervm --enable-preview
+ * -XX:-TieredCompilation -Xcomp
+ * -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions -XX:VerifyIterativeGVN=1110
+ * -XX:CompileOnly=${test.main.class}::test*
+ * -XX:+StressIGVN
+ * ${test.main.class}
+ * @run main ${test.main.class}
+ */
+
+// Test was intermittent, reproduces better with -XX:RepeatCompilation=50
+public class TestMissingVariableTimesConIdealization {
+ static short sFld;
+ static int iFld;
+ static long lFld;
+
+ public static void main(String[] args) {
+ for (int i = 0; i < 10000; i++) {
+ test1I();
+ test1L();
+ }
+ test2I();
+ }
+
+ // ((x << 2) + (x << 1)) + x -> 7 * x
+ static void test1I() {
+ iFld = iFld - Byte.valueOf((byte) -6) * iFld;
+ }
+
+ // ((x << 2) + (x << 1)) + x -> 7 * x
+ static void test1L() {
+ lFld = lFld - Byte.valueOf((byte) -6) * lFld;
+ }
+
+ // ((x << 14) + (x << 2)) + x -> 16389 * x
+ // Only reproduced with --enable-preview.
+ static int test2I() {
+ return sFld + Short.valueOf((short) 16388) * sFld;
+ }
+}
diff --git a/test/hotspot/jtreg/compiler/loopopts/superword/TestReductions.java b/test/hotspot/jtreg/compiler/loopopts/superword/TestReductions.java
index 599e95bd6aba..d7b4278ede90 100644
--- a/test/hotspot/jtreg/compiler/loopopts/superword/TestReductions.java
+++ b/test/hotspot/jtreg/compiler/loopopts/superword/TestReductions.java
@@ -118,8 +118,7 @@ public static void main(String[] args) {
switch (args[0]) {
case "P0" -> { framework.addFlags("-XX:+UnlockDiagnosticVMOptions", "-XX:AutoVectorizationOverrideProfitability=0"); }
case "P1" -> { framework.addFlags("-XX:+UnlockDiagnosticVMOptions", "-XX:AutoVectorizationOverrideProfitability=1"); }
- // Note: increasing the node count limit also helps in some cases.
- case "P2" -> { framework.addFlags("-XX:+UnlockDiagnosticVMOptions", "-XX:AutoVectorizationOverrideProfitability=2", "-XX:LoopUnrollLimit=1000"); }
+ case "P2" -> { framework.addFlags("-XX:+UnlockDiagnosticVMOptions", "-XX:AutoVectorizationOverrideProfitability=2"); }
default -> { throw new RuntimeException("Test argument not recognized: " + args[0]); }
};
framework.start();
@@ -2116,15 +2115,7 @@ private static long longAddBig() {
IRNode.MUL_REDUCTION_VL, "> 0",
IRNode.MUL_VL, "> 0"},
applyIfCPUFeatureOr = {"avx512dq", "true", "sve", "true"},
- applyIfAnd = {"AutoVectorizationOverrideProfitability", "> 0",
- "LoopUnrollLimit", ">= 1000"})
- @IR(failOn = IRNode.LOAD_VECTOR_L,
- applyIfCPUFeature = {"avx512dq", "true"},
- applyIfAnd = {"AutoVectorizationOverrideProfitability", "> 0",
- "LoopUnrollLimit", "< 1000"})
- // Increasing the body limit seems to help. Filed for investigation: JDK-8370685
- // If you can eliminate this exception for LoopUnrollLimit, please remove
- // the flag completely from the test, also the "addFlags" at the top.
+ applyIf = {"AutoVectorizationOverrideProfitability", "> 0"})
@IR(failOn = IRNode.LOAD_VECTOR_L,
applyIfCPUFeatureAnd = {"asimd", "true", "sve", "false"})
// MulVL is not implemented on NEON, so we also not have the reduction.
From 019a9d04a8d17aa284fc4abfdd30dfd20426f037 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Lund=C3=A9n?=
Date: Thu, 15 Oct 2026 08:06:51 +0000
Subject: [PATCH 053/169] 8337821: C2 hits compiler MemLimit in
PhaseChaitin::Split
Reviewed-by: kvn, rcastanedalo
---
src/hotspot/share/opto/c2_globals.hpp | 5 ++
src/hotspot/share/opto/reg_split.cpp | 7 ++-
test/hotspot/jtreg/ProblemList.txt | 2 -
.../TestFloat16RearrangeInlining.java | 59 +++++++++++++++++++
.../VectorReplicateLongSpecialImmTest.java | 1 -
.../jtreg/vmTestbase/jit/t/t105/t105.java | 4 +-
6 files changed, 72 insertions(+), 6 deletions(-)
create mode 100644 test/hotspot/jtreg/compiler/vectorapi/TestFloat16RearrangeInlining.java
diff --git a/src/hotspot/share/opto/c2_globals.hpp b/src/hotspot/share/opto/c2_globals.hpp
index 083a3c94d480..3379da006347 100644
--- a/src/hotspot/share/opto/c2_globals.hpp
+++ b/src/hotspot/share/opto/c2_globals.hpp
@@ -288,6 +288,11 @@
"Maximum allowed edges in the interference graphs") \
range(0, max_juint) \
\
+ product(uint, RegSplitLimit, 20*M, DIAGNOSTIC, \
+ "Maximum product of spill and CFG block counts during Split " \
+ "in the Register Allocator") \
+ range(0, max_juint) \
+ \
develop(bool, UseUniqueSubclasses, true, \
"Narrow an abstract reference to the unique concrete subclass") \
\
diff --git a/src/hotspot/share/opto/reg_split.cpp b/src/hotspot/share/opto/reg_split.cpp
index 96f87fe6947d..7b2597d74146 100644
--- a/src/hotspot/share/opto/reg_split.cpp
+++ b/src/hotspot/share/opto/reg_split.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2000, 2025, 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
@@ -534,6 +534,11 @@ uint PhaseChaitin::Split(uint maxlrg, ResourceArea* split_arena) {
}
}
+ if (static_cast(spill_cnt) * _cfg.number_of_blocks() > RegSplitLimit) {
+ C->record_method_not_compilable("PhaseChaitin::Split requires too much memory");
+ return 0;
+ }
+
// Create side arrays for propagating reaching defs info.
// Each block needs a node pointer for each spilling live range for the
// Def which is live into the block. Phi nodes handle multiple input
diff --git a/test/hotspot/jtreg/ProblemList.txt b/test/hotspot/jtreg/ProblemList.txt
index 9e4d24efc3bb..189d9b4aa0ff 100644
--- a/test/hotspot/jtreg/ProblemList.txt
+++ b/test/hotspot/jtreg/ProblemList.txt
@@ -69,8 +69,6 @@ compiler/c2/aarch64/TestStaticCallStub.java 8359963 generic-aarch64
compiler/escapeAnalysis/TestBCEscapeAnalyzerOverflow.java 8387392 windows-aarch64
-compiler/vectorapi/VectorExpressionFuzzer.java 8337281 linux-aarch64,macosx-aarch64
-
compiler/igvn/TestIncorrectDeadPathTypeNode.java 8392354 generic-all
#############################################################################
diff --git a/test/hotspot/jtreg/compiler/vectorapi/TestFloat16RearrangeInlining.java b/test/hotspot/jtreg/compiler/vectorapi/TestFloat16RearrangeInlining.java
new file mode 100644
index 000000000000..18ed88dd4c7a
--- /dev/null
+++ b/test/hotspot/jtreg/compiler/vectorapi/TestFloat16RearrangeInlining.java
@@ -0,0 +1,59 @@
+/*
+ * 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 8337821 8393397
+ * @summary Test that C2's register allocator gracefully handles pathological
+ * input resulting from extreme inlining. See 8393397 for the
+ * inlining issue (observed on aarch64).
+ * @modules jdk.incubator.vector
+ * @library /test/lib /
+ * @run main/othervm -Xbatch
+ * -esa
+ * -XX:CompileCommand=CompileOnly,jdk.incubator.vector.Float16Vector::lambda$rearrangeTemplate$0
+ * compiler.vectorapi.TestFloat16RearrangeInlining
+ * @run main compiler.vectorapi.TestFloat16RearrangeInlining
+ */
+
+package compiler.vectorapi;
+
+import jdk.incubator.vector.*;
+
+public class TestFloat16RearrangeInlining {
+
+ public static void main(String[] args) {
+ Float16Vector vector512 = Float16Vector.zero(Float16Vector.SPECIES_512);
+ VectorShuffle shuffle512 =
+ VectorShuffle.iota(Float16Vector.SPECIES_512, 0, 1, false);
+ for (int i = 0; i < 1_500; i++) {
+ vector512.rearrange(shuffle512);
+ }
+ Float16Vector vector256 = Float16Vector.zero(Float16Vector.SPECIES_256);
+ VectorShuffle shuffle256 =
+ VectorShuffle.iota(Float16Vector.SPECIES_256, 0, 1, false);
+ for (int i = 0; i < 20_000; i++) {
+ vector256.rearrange(shuffle256);
+ }
+ }
+}
diff --git a/test/hotspot/jtreg/compiler/vectorapi/VectorReplicateLongSpecialImmTest.java b/test/hotspot/jtreg/compiler/vectorapi/VectorReplicateLongSpecialImmTest.java
index f9cb7d163d02..c288fc2a4d6d 100644
--- a/test/hotspot/jtreg/compiler/vectorapi/VectorReplicateLongSpecialImmTest.java
+++ b/test/hotspot/jtreg/compiler/vectorapi/VectorReplicateLongSpecialImmTest.java
@@ -39,7 +39,6 @@
* @run testng/othervm -XX:UseSVE=0 -XX:-TieredCompilation
* -XX:CompileThreshold=100
* -XX:+IgnoreUnrecognizedVMOptions
- * -XX:CompileCommand=MemLimit,*.*,0
* -XX:CompileTaskTimeout=0
* compiler.vectorapi.VectorReplicateLongSpecialImmTest
*/
diff --git a/test/hotspot/jtreg/vmTestbase/jit/t/t105/t105.java b/test/hotspot/jtreg/vmTestbase/jit/t/t105/t105.java
index b27975260bde..ba54f72c8005 100644
--- a/test/hotspot/jtreg/vmTestbase/jit/t/t105/t105.java
+++ b/test/hotspot/jtreg/vmTestbase/jit/t/t105/t105.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2008, 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
@@ -31,7 +31,7 @@
* @library /vmTestbase
* /test/lib
* @run main/othervm -XX:-OmitStackTraceInFastThrow jit.t.t105.t105
- * @run main/othervm -XX:CompileCommand=MemLimit,*.*,0 -XX:-OmitStackTraceInFastThrow -Xbatch -XX:Tier0BackedgeNotifyFreqLog=0 -XX:Tier2BackedgeNotifyFreqLog=0 -XX:Tier3BackedgeNotifyFreqLog=0 -XX:Tier2BackEdgeThreshold=1 -XX:Tier3BackEdgeThreshold=1 -XX:Tier4BackEdgeThreshold=1 jit.t.t105.t105
+ * @run main/othervm -XX:-OmitStackTraceInFastThrow -Xbatch -XX:Tier0BackedgeNotifyFreqLog=0 -XX:Tier2BackedgeNotifyFreqLog=0 -XX:Tier3BackedgeNotifyFreqLog=0 -XX:Tier2BackEdgeThreshold=1 -XX:Tier3BackEdgeThreshold=1 -XX:Tier4BackEdgeThreshold=1 jit.t.t105.t105
*
* This test must be run with OmitStackTraceInFastThrow disabled to avoid preallocated
* exceptions. They don't have the detailed message that this test relies on.
From f7917fbd76af5d44ffd68ec75923654cc48bd599 Mon Sep 17 00:00:00 2001
From: Dingli Zhang
Date: Thu, 15 Oct 2026 10:44:10 +0000
Subject: [PATCH 054/169] 8394622: [BACKOUT] RISC-V: Use movptr2 for C2 pointer
constants
Reviewed-by: gcao, fyang
---
src/hotspot/cpu/riscv/macroAssembler_riscv.cpp | 8 ++++----
src/hotspot/cpu/riscv/macroAssembler_riscv.hpp | 4 ++--
src/hotspot/cpu/riscv/riscv.ad | 4 ++--
3 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp b/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp
index 733464b8b269..fbbfc87ae861 100644
--- a/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp
+++ b/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp
@@ -3792,7 +3792,7 @@ void MacroAssembler::cmp_klass_bne(Register obj, Register klass,
}
// Move an oop into a register.
-void MacroAssembler::movoop(Register dst, jobject obj, Register tmp) {
+void MacroAssembler::movoop(Register dst, jobject obj) {
int oop_index;
if (obj == nullptr) {
oop_index = oop_recorder()->allocate_oop_index(obj);
@@ -3808,7 +3808,7 @@ void MacroAssembler::movoop(Register dst, jobject obj, Register tmp) {
RelocationHolder rspec = oop_Relocation::spec(oop_index);
if (BarrierSet::barrier_set()->barrier_set_assembler()->supports_instruction_patching()) {
- movptr(dst, Address((address)obj, rspec), tmp);
+ movptr(dst, Address((address)obj, rspec));
} else {
address dummy = address(uintptr_t(pc()) & -wordSize); // A nearby aligned address
ld(dst, Address(dummy, rspec));
@@ -3816,7 +3816,7 @@ void MacroAssembler::movoop(Register dst, jobject obj, Register tmp) {
}
// Move a metadata address into a register.
-void MacroAssembler::mov_metadata(Register dst, Metadata* obj, Register tmp) {
+void MacroAssembler::mov_metadata(Register dst, Metadata* obj) {
assert((uintptr_t)obj < (1ull << 48), "48-bit overflow in metadata");
int oop_index;
if (obj == nullptr) {
@@ -3825,7 +3825,7 @@ void MacroAssembler::mov_metadata(Register dst, Metadata* obj, Register tmp) {
oop_index = oop_recorder()->find_index(obj);
}
RelocationHolder rspec = metadata_Relocation::spec(oop_index);
- movptr(dst, Address((address)obj, rspec), tmp);
+ movptr(dst, Address((address)obj, rspec));
}
void MacroAssembler::payload_offset(Register value_klass, Register offset) {
diff --git a/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp b/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp
index 7a65ce5a27e5..838491511921 100644
--- a/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp
+++ b/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp
@@ -191,8 +191,8 @@ class MacroAssembler: public Assembler {
void resolve_jobject(Register value, Register tmp1, Register tmp2);
void resolve_global_jobject(Register value, Register tmp1, Register tmp2);
- void movoop(Register dst, jobject obj, Register tmp = noreg);
- void mov_metadata(Register dst, Metadata* obj, Register tmp = noreg);
+ void movoop(Register dst, jobject obj);
+ void mov_metadata(Register dst, Metadata* obj);
void bang_stack_size(Register size, Register tmp);
void set_narrow_oop(Register dst, jobject obj);
void set_narrow_klass(Register dst, Klass* k);
diff --git a/src/hotspot/cpu/riscv/riscv.ad b/src/hotspot/cpu/riscv/riscv.ad
index 8dfc989846e5..615378b7f4a0 100644
--- a/src/hotspot/cpu/riscv/riscv.ad
+++ b/src/hotspot/cpu/riscv/riscv.ad
@@ -2372,9 +2372,9 @@ encode %{
} else {
relocInfo::relocType rtype = $src->constant_reloc();
if (rtype == relocInfo::oop_type) {
- __ movoop(dst_reg, (jobject)con, t0);
+ __ movoop(dst_reg, (jobject)con);
} else if (rtype == relocInfo::metadata_type) {
- __ mov_metadata(dst_reg, (Metadata*)con, t0);
+ __ mov_metadata(dst_reg, (Metadata*)con);
} else {
assert(rtype == relocInfo::none || rtype == relocInfo::external_word_type, "unexpected reloc type");
// Load fake address constants as a plain immediate. They are not real
From 2990eca07a7889750baf5213147f62f8e01f91fa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Jeli=C5=84ski?=
Date: Thu, 15 Oct 2026 11:04:06 +0000
Subject: [PATCH 055/169] 8394574: Clean up net_util
Reviewed-by: alanb
---
src/java.base/share/native/libnet/net_util.h | 10 +--
.../unix/native/libnet/net_util_md.h | 11 ---
.../windows/native/libnet/Inet4AddressImpl.c | 3 +
.../windows/native/libnet/Inet6AddressImpl.c | 3 +
.../windows/native/libnet/NetworkInterface.c | 5 +-
.../native/libnet/ResolverConfigurationImpl.c | 2 +
.../windows/native/libnet/net_util_md.c | 5 +-
.../windows/native/libnet/net_util_md.h | 77 +------------------
.../native/libnio/ch/DatagramChannelImpl.c | 3 +-
src/java.base/windows/native/libnio/ch/Net.c | 1 +
10 files changed, 19 insertions(+), 101 deletions(-)
diff --git a/src/java.base/share/native/libnet/net_util.h b/src/java.base/share/native/libnet/net_util.h
index 92b812b18686..a739cb3d2ccf 100644
--- a/src/java.base/share/native/libnet/net_util.h
+++ b/src/java.base/share/native/libnet/net_util.h
@@ -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
@@ -82,14 +82,6 @@ extern jfieldID ni_addrsID;
extern jfieldID ni_descID;
extern jmethodID ni_ctrID;
-/* DatagramPacket fields */
-extern jfieldID dp_addressID;
-extern jfieldID dp_portID;
-extern jfieldID dp_bufID;
-extern jfieldID dp_offsetID;
-extern jfieldID dp_lengthID;
-extern jfieldID dp_bufLengthID;
-
/* Inet6Address fields */
extern jclass ia6_class;
extern jfieldID ia6_holder6ID;
diff --git a/src/java.base/unix/native/libnet/net_util_md.h b/src/java.base/unix/native/libnet/net_util_md.h
index 639cf00515fa..18c563f98a4e 100644
--- a/src/java.base/unix/native/libnet/net_util_md.h
+++ b/src/java.base/unix/native/libnet/net_util_md.h
@@ -54,17 +54,6 @@
#endif
#endif
-/*
- * On 64-bit JDKs we use a much larger stack and heap buffer.
- */
-#ifdef _LP64
-#define MAX_BUFFER_LEN 65536
-#define MAX_HEAP_BUFFER_LEN 131072
-#else
-#define MAX_BUFFER_LEN 8192
-#define MAX_HEAP_BUFFER_LEN 65536
-#endif
-
typedef union {
struct sockaddr sa;
struct sockaddr_in sa4;
diff --git a/src/java.base/windows/native/libnet/Inet4AddressImpl.c b/src/java.base/windows/native/libnet/Inet4AddressImpl.c
index 91b307375006..8ea34cfbd4b6 100644
--- a/src/java.base/windows/native/libnet/Inet4AddressImpl.c
+++ b/src/java.base/windows/native/libnet/Inet4AddressImpl.c
@@ -24,6 +24,9 @@
*/
#include
#include
+#include
+#include
+#include
#include "net_util.h"
diff --git a/src/java.base/windows/native/libnet/Inet6AddressImpl.c b/src/java.base/windows/native/libnet/Inet6AddressImpl.c
index 812693299ba2..029d2c588e90 100644
--- a/src/java.base/windows/native/libnet/Inet6AddressImpl.c
+++ b/src/java.base/windows/native/libnet/Inet6AddressImpl.c
@@ -24,6 +24,9 @@
*/
#include
#include
+#include
+#include
+#include
#include "net_util.h"
diff --git a/src/java.base/windows/native/libnet/NetworkInterface.c b/src/java.base/windows/native/libnet/NetworkInterface.c
index 838921567c3b..b166c1cc4cff 100644
--- a/src/java.base/windows/native/libnet/NetworkInterface.c
+++ b/src/java.base/windows/native/libnet/NetworkInterface.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2000, 2023, 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
@@ -22,6 +22,9 @@
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
+#include
+#include
+#include
#include "net_util.h"
#include "NetworkInterface.h"
diff --git a/src/java.base/windows/native/libnet/ResolverConfigurationImpl.c b/src/java.base/windows/native/libnet/ResolverConfigurationImpl.c
index f3bc27f67ca3..f6c240a1c0b6 100644
--- a/src/java.base/windows/native/libnet/ResolverConfigurationImpl.c
+++ b/src/java.base/windows/native/libnet/ResolverConfigurationImpl.c
@@ -28,6 +28,8 @@
#include
#include
#include
+#include
+#include
#include "net_util.h"
#include "jni_util.h"
diff --git a/src/java.base/windows/native/libnet/net_util_md.c b/src/java.base/windows/native/libnet/net_util_md.c
index 5abdd8d4c2e3..6320c6b81121 100644
--- a/src/java.base/windows/native/libnet/net_util_md.c
+++ b/src/java.base/windows/native/libnet/net_util_md.c
@@ -22,6 +22,8 @@
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
+#include
+#include
#include "net_util.h"
#include "java_net_InetAddress.h"
@@ -37,9 +39,6 @@
#define IPTOS_PREC_MASK 0xe0
#endif
-/* true if SO_RCVTIMEO is supported */
-jboolean isRcvTimeoutSupported = JNI_TRUE;
-
/*
* Table of Windows Sockets errors, the specific exception we
* throw for the error, and the error text.
diff --git a/src/java.base/windows/native/libnet/net_util_md.h b/src/java.base/windows/native/libnet/net_util_md.h
index 255988c461e3..6a661e70bdda 100644
--- a/src/java.base/windows/native/libnet/net_util_md.h
+++ b/src/java.base/windows/native/libnet/net_util_md.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2022, 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
@@ -24,32 +24,6 @@
*/
#include
#include
-#include
-#include
-#include
-
-/* used to disable connection reset messages on Windows XP */
-#ifndef SIO_UDP_CONNRESET
-#define SIO_UDP_CONNRESET _WSAIOW(IOC_VENDOR,12)
-#endif
-
-#ifndef IN6_IS_ADDR_ANY
-#define IN6_IS_ADDR_ANY(a) \
- (((a)->s6_words[0] == 0) && ((a)->s6_words[1] == 0) && \
- ((a)->s6_words[2] == 0) && ((a)->s6_words[3] == 0) && \
- ((a)->s6_words[4] == 0) && ((a)->s6_words[5] == 0) && \
- ((a)->s6_words[6] == 0) && ((a)->s6_words[7] == 0))
-#endif
-
-#ifndef IPV6_V6ONLY
-#define IPV6_V6ONLY 27 /* Treat wildcard bind as AF_INET6-only. */
-#endif
-
-#define MAX_BUFFER_LEN 2048
-#define MAX_HEAP_BUFFER_LEN 65536
-
-/* true if SO_RCVTIMEO is supported by underlying provider */
-extern jboolean isRcvTimeoutSupported;
typedef union {
struct sockaddr sa;
@@ -57,24 +31,6 @@ typedef union {
struct sockaddr_in6 sa6;
} SOCKETADDRESS;
-#define SOCKETADDRESS_COPY(DST,SRC) { \
- if ((SRC)->sa_family == AF_INET6) { \
- memcpy ((DST), (SRC), sizeof (struct sockaddr_in6)); \
- } else { \
- memcpy ((DST), (SRC), sizeof (struct sockaddr_in)); \
- } \
-}
-
-#define SET_PORT(X,Y) { \
- if ((X)->sa.sa_family == AF_INET) { \
- (X)->sa4.sin_port = (Y); \
- } else { \
- (X)->sa6.sin6_port = (Y); \
- } \
-}
-
-#define GET_PORT(X) ((X)->sa.sa_family == AF_INET ? (X)->sa4.sin_port : (X)->sa6.sin6_port)
-
/**
* With dual socket implementation the
* IPv4 addresseses might be mapped as IPv6.
@@ -116,34 +72,3 @@ JNIEXPORT int JNICALL NET_WinBind(int s, SOCKETADDRESS *sa, int len,
JNIEXPORT jint JNICALL NET_EnableFastTcpLoopbackConnect(int fd);
-/* XP versions of the native routines */
-
-JNIEXPORT jobject JNICALL Java_java_net_NetworkInterface_getByName0_XP
- (JNIEnv *env, jclass cls, jstring name);
-
-JNIEXPORT jobject JNICALL Java_java_net_NetworkInterface_getByIndex0_XP
- (JNIEnv *env, jclass cls, jint index);
-
-JNIEXPORT jobject JNICALL Java_java_net_NetworkInterface_getByInetAddress0_XP
- (JNIEnv *env, jclass cls, jobject iaObj);
-
-JNIEXPORT jobjectArray JNICALL Java_java_net_NetworkInterface_getAll_XP
- (JNIEnv *env, jclass cls);
-
-JNIEXPORT jboolean JNICALL Java_java_net_NetworkInterface_supportsMulticast0_XP
- (JNIEnv *env, jclass cls, jstring name, jint index);
-
-JNIEXPORT jboolean JNICALL Java_java_net_NetworkInterface_isUp0_XP
- (JNIEnv *env, jclass cls, jstring name, jint index);
-
-JNIEXPORT jboolean JNICALL Java_java_net_NetworkInterface_isP2P0_XP
- (JNIEnv *env, jclass cls, jstring name, jint index);
-
-JNIEXPORT jbyteArray JNICALL Java_java_net_NetworkInterface_getMacAddr0_XP
- (JNIEnv *env, jclass cls, jstring name, jint index);
-
-JNIEXPORT jint JNICALL Java_java_net_NetworkInterface_getMTU0_XP
- (JNIEnv *env, jclass class, jstring name, jint index);
-
-JNIEXPORT jboolean JNICALL Java_java_net_NetworkInterface_isLoopback0_XP
- (JNIEnv *env, jclass cls, jstring name, jint index);
diff --git a/src/java.base/windows/native/libnio/ch/DatagramChannelImpl.c b/src/java.base/windows/native/libnio/ch/DatagramChannelImpl.c
index 54a4133a3b22..ce3144d5c045 100644
--- a/src/java.base/windows/native/libnio/ch/DatagramChannelImpl.c
+++ b/src/java.base/windows/native/libnio/ch/DatagramChannelImpl.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2001, 2023, 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
@@ -24,6 +24,7 @@
*/
#include
+#include
#include "jni.h"
#include "jni_util.h"
diff --git a/src/java.base/windows/native/libnio/ch/Net.c b/src/java.base/windows/native/libnio/ch/Net.c
index d9300ee2028d..36c0e4fa738f 100644
--- a/src/java.base/windows/native/libnio/ch/Net.c
+++ b/src/java.base/windows/native/libnio/ch/Net.c
@@ -25,6 +25,7 @@
#include
#include
+#include
#include "jni.h"
#include "jni_util.h"
From 47b09aa1912d594d23134a51ed8893287f25d4a3 Mon Sep 17 00:00:00 2001
From: Christian Hagedorn
Date: Thu, 15 Oct 2026 12:28:52 +0000
Subject: [PATCH 056/169] 8388433: [IR Framework] Introduce @Skip to skip
failing tests instead of commenting them out
Reviewed-by: mchevalier
---
.../jtreg/compiler/lib/ir_framework/README.md | 6 +
.../jtreg/compiler/lib/ir_framework/Skip.java | 50 +++
.../lib/ir_framework/test/DeclaredTest.java | 8 +-
.../lib/ir_framework/test/TestVM.java | 58 +++-
.../examples/SkipTestExample.java | 128 ++++++++
.../ir_framework/tests/TestBadFormat.java | 38 +++
.../tests/TestNoMainExecution.java | 2 +-
.../ir_framework/tests/TestSkip.java | 303 ++++++++++++++++++
8 files changed, 587 insertions(+), 6 deletions(-)
create mode 100644 test/hotspot/jtreg/compiler/lib/ir_framework/Skip.java
create mode 100644 test/hotspot/jtreg/testlibrary_tests/ir_framework/examples/SkipTestExample.java
create mode 100644 test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestSkip.java
diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/README.md b/test/hotspot/jtreg/compiler/lib/ir_framework/README.md
index 5560869d80f1..77b11277b3c1 100644
--- a/test/hotspot/jtreg/compiler/lib/ir_framework/README.md
+++ b/test/hotspot/jtreg/compiler/lib/ir_framework/README.md
@@ -60,6 +60,11 @@ A custom run test gives full control over the invocation of the `@Test` annotate
More information on checked tests with a precise definition can be found in the Javadocs of [Run](./Run.java). Concrete examples on how to specify a custom run test can be found in [CustomRunTestsExample](../../../testlibrary_tests/ir_framework/examples/CustomRunTestExample.java).
+#### Skipping a Test Completely When Encountering Problems
+When a test execution is failing due to a VM problem (crash, wrong execution etc.) without having an immediate fix, it is preferred to specify [@Skip](./Skip.java) at the `@Test`-method to disable the test over commenting the test out.
+
+Specifying `@Skip` allows the IR Framework to still execute the skipped test on command by using the property flag `-DIgnoreSkip=true`. This can be useful when trying to quickly verify if a disabled test is still failing or not.
+
### 2.2 IR Verification
The main feature of this framework is to perform a simple but yet powerful regex-based C2 IR matching on the output of `-XX:+PrintIdeal`, `-XX:+PrintOptoAssembly` and/or on specific compile phases emitted by the compile command `-XX:CompileCommand=PrintIdealPhase` which supports the same set of compile phases as the Ideal Graph Visualizer (IGV).
@@ -187,6 +192,7 @@ The framework provides various stress and debug flags. They should mainly be use
- `-DWaitForCompilationTimeout=20`: Change the default waiting time (default: 10s) for a compilation of a `@Test` annotated method with compilation level [WAIT\_FOR\_COMPILATION](./CompLevel.java).
- `-DIgnoreCompilerControls=true`: Ignore all compiler controls applied in the framework. This includes any compiler control annotations (`@DontCompile`, `@DontInline`, `@ForceCompile`, `@ForceInline`, `@ForceCompileStaticInitializer`), the exclusion of `@Run` and `@Check` methods from compilation, and the directive to not inline `@Test` annotated methods.
- `-DPreferCommandLineFlags=true`: Prefer flags set via the command line over flags specified by the tests.
+- `-DIgnoreSkip=true`: Additionally run all skipped tests (i.e. tests with a `@Skip` annotation).
## 3. Test Framework Execution
This section gives an overview of how the framework is executing a JTreg test that calls the framework from within its `main()` method.
diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/Skip.java b/test/hotspot/jtreg/compiler/lib/ir_framework/Skip.java
new file mode 100644
index 000000000000..fefd2ab2f214
--- /dev/null
+++ b/test/hotspot/jtreg/compiler/lib/ir_framework/Skip.java
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+package compiler.lib.ir_framework;
+
+import compiler.lib.ir_framework.shared.TestFormatException;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This annotation skips the execution of a {@link Test @Test}-annotated method completely. This is useful when the
+ * {@link Test @Test}-method causes the VM to misbehave (crash, wrong execution etc.) with no immediately available fix.
+ *
+ *
+ * It is preferable to use {@link Skip @Skip} over commenting a test out because one can still execute the skipped
+ * test by passing the property flag {@code -DIgnoreSkip=true} (see {@link ir_framework.examples.SkipTestExample}).
+ * This can be useful when trying to quickly verify if a disabled test is still failing or not.
+ *
+ *
+ * The {@link Skip @Skip} annotation can only be used at {@link Test @Test}-annotated methods. If the
+ * {@link Test @Test}-method is part of a custom run test, then the {@link Run @Run}-method is also not executed.
+ * A special case is a {@link Run @Run}-annotated method with multiple associated {@link Test @Test}-methods. In this
+ * case, you must either skip all or none to limit the impact. Partially skipping tests results in a
+ * {@link TestFormatException}. When only needing to disable one of the multiple associated tests, consider moving it
+ * to a separate custom run test with a 1-to-1 {@link Run @Run}-{@link Test @Test}-relation for minimum impact.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Skip {
+}
diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/test/DeclaredTest.java b/test/hotspot/jtreg/compiler/lib/ir_framework/test/DeclaredTest.java
index 81143b6c2540..f72a3c3f9265 100644
--- a/test/hotspot/jtreg/compiler/lib/ir_framework/test/DeclaredTest.java
+++ b/test/hotspot/jtreg/compiler/lib/ir_framework/test/DeclaredTest.java
@@ -37,16 +37,18 @@ public class DeclaredTest {
private final CompLevel compLevel;
private final int warmupIterations;
private final boolean allowNotCompilable;
+ private final boolean hasSkipAnno;
private Method attachedMethod;
public DeclaredTest(Method testMethod, ArgumentsProvider argumentsProvider, CompLevel compLevel, int warmupIterations,
- boolean allowNotCompilable) {
+ boolean hasSkipAnno, boolean allowNotCompilable) {
// Make sure we can also call non-public or public methods in package private classes
testMethod.setAccessible(true);
this.testMethod = testMethod;
this.argumentsProvider = argumentsProvider;
this.compLevel = compLevel;
this.warmupIterations = warmupIterations;
+ this.hasSkipAnno = hasSkipAnno;
this.allowNotCompilable = allowNotCompilable;
this.attachedMethod = null;
}
@@ -71,6 +73,10 @@ public Object[] getArguments(Object invocationTarget, int invocationCounter) {
return argumentsProvider.getArguments(invocationTarget, invocationCounter);
}
+ public boolean hasSkipAnno() {
+ return hasSkipAnno;
+ }
+
public void setAttachedMethod(Method m) {
attachedMethod = m;
}
diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/test/TestVM.java b/test/hotspot/jtreg/compiler/lib/ir_framework/test/TestVM.java
index be189cd6f245..32e6ae5faa32 100644
--- a/test/hotspot/jtreg/compiler/lib/ir_framework/test/TestVM.java
+++ b/test/hotspot/jtreg/compiler/lib/ir_framework/test/TestVM.java
@@ -97,6 +97,8 @@ assertions from main() of your test!
static final boolean EXCLUDE_RANDOM = Boolean.getBoolean("ExcludeRandom");
private static final String TEST_LIST = SystemProperty.getTestList();
private static final String EXCLUDE_LIST = SystemProperty.getExcludeList();
+ // We ignore @Skip when running with -DIgnoreSkip=true
+ private static final boolean IGNORE_SKIP = Boolean.getBoolean("IgnoreSkip");
private static final boolean DUMP_REPLAY = Boolean.getBoolean("DumpReplay");
private static final boolean GC_AFTER = Boolean.getBoolean("GCAfter");
private static final boolean SHUFFLE_TESTS = Boolean.parseBoolean(System.getProperty("ShuffleTests", "true"));
@@ -312,11 +314,13 @@ private void addBaseTests() {
* A test is excluded from execution if:
* - -DTest does not list the method
* - -DExclude lists the method
+ * - The method specifies a (temporary) @Skip annotation.
*/
private boolean shouldExcludeTest(Method testMethod) {
String testName = testMethod.getName();
return isNotOnTestList(testName) ||
- isOnExcludeList(testName);
+ isOnExcludeList(testName) ||
+ shouldSkip(testMethod);
}
private boolean isNotOnTestList(String testName) {
@@ -327,6 +331,14 @@ private boolean isOnExcludeList(String testName) {
return excludeList.contains(testName);
}
+ private boolean shouldSkip(Method method) {
+ return !IGNORE_SKIP && hasSkipAnnotation(method);
+ }
+
+ private boolean hasSkipAnnotation(Method method) {
+ return getAnnotation(method, Skip.class) != null;
+ }
+
/**
* Generate replay compilation files.
*/
@@ -560,6 +572,8 @@ private void setupDeclaredTests() {
"Found @IR annotation on non-@Test method " + m);
TestFormat.checkNoThrow(!m.isAnnotationPresent(Warmup.class) || getAnnotation(m, Run.class) != null,
"Found @Warmup annotation on non-@Test or non-@Run method " + m);
+ TestFormat.checkNoThrow(!m.isAnnotationPresent(Skip.class) ,
+ "Found @Skip annotation on non-@Test method " + m);
}
} catch (TestFormatException e) {
// Failure logged. Continue and report later.
@@ -582,16 +596,22 @@ private void addDeclaredTest(Method m) {
// Don't inline test methods by default. Do not apply this when -DIgnoreCompilerControls=true is set.
WHITE_BOX.testSetDontInlineMethod(m, true);
}
+
CompLevel compLevel = restrictCompLevel(testAnno.compLevel());
if (FLIP_C1_C2) {
compLevel = restrictCompLevel(compLevel.flipCompLevel());
}
+
if (EXCLUDE_RANDOM) {
compLevel = compLevel.excludeCompilationRandomly(m);
}
+
boolean allowNotCompilable = testAnno.allowNotCompilable() || ALLOW_METHOD_NOT_COMPILABLE;
ArgumentsProvider argumentsProvider = ArgumentsProviderBuilder.build(m, setupMethodMap);
- DeclaredTest test = new DeclaredTest(m, argumentsProvider, compLevel, warmupIterations, allowNotCompilable);
+ Skip skip = getAnnotation(m, Skip.class);
+ boolean hasSkip = skip != null;
+ DeclaredTest test = new DeclaredTest(m, argumentsProvider, compLevel, warmupIterations,
+ hasSkip, allowNotCompilable);
declaredTests.put(m, test);
testMethodMap.put(m.getName(), m);
}
@@ -762,9 +782,13 @@ private void addCustomRunTest(Method m, Run runAnno) {
// Logged, continue.
}
}
- if (tests.isEmpty()) {
- return; // There was a format violation. Return.
+
+ if (tests.size() != runAnno.test().length) {
+ // At least one test is invalid. Return and report the already recorded format violation(s) later.
+ return;
}
+
+ checkNoneOrAllAssociatedTestsSkipped(m, tests);
dontCompileAndDontInlineMethod(m);
CustomRunTest customRunTest = new CustomRunTest(m, getAnnotation(m, Warmup.class), runAnno, tests, shouldExcludeTest);
allTests.add(customRunTest);
@@ -809,6 +833,32 @@ private void checkRunMethod(Method m, Run runAnno) {
"Cannot set @Warmup at @Run method " + m + " when used with RunMode.STANDALONE. The @Run method is only invoked once.");
}
+ private void checkNoneOrAllAssociatedTestsSkipped(Method runMethod, List tests) {
+ List testMethodsWithoutSkip = testMethodsWithoutSkipAnnotation(tests);
+ if (testMethodsWithoutSkip.isEmpty() ||
+ testMethodsWithoutSkip.size() == tests.size()) {
+ // All with @Skip or none.
+ return;
+ }
+
+ // Skipping some tests but not all is a format violation.
+ reportPartiallySkippingTests(runMethod, testMethodsWithoutSkip);
+ }
+
+ private List testMethodsWithoutSkipAnnotation(List tests) {
+ return tests.stream().filter(test -> !test.hasSkipAnno()).map(DeclaredTest::getTestMethod).toList();
+ }
+
+ private void reportPartiallySkippingTests(Method runMethod, List testMethodsWithoutSkip) {
+ StringBuilder builder = new StringBuilder();
+ builder.append("@Run-methods with multiple tests can only either skip none or all methods with @Skip:")
+ .append(System.lineSeparator())
+ .append(" - @Run-method: ").append(runMethod);
+ testMethodsWithoutSkip.forEach(method -> builder.append(System.lineSeparator())
+ .append(" - @Test without @Skip: ").append(method));
+ TestFormat.fail(builder.toString());
+ }
+
private static T getAnnotation(AnnotatedElement element, Class c) {
T[] annos = element.getAnnotationsByType(c);
TestFormat.check(annos.length < 2, element + " has duplicated annotations");
diff --git a/test/hotspot/jtreg/testlibrary_tests/ir_framework/examples/SkipTestExample.java b/test/hotspot/jtreg/testlibrary_tests/ir_framework/examples/SkipTestExample.java
new file mode 100644
index 000000000000..4c8cfb266967
--- /dev/null
+++ b/test/hotspot/jtreg/testlibrary_tests/ir_framework/examples/SkipTestExample.java
@@ -0,0 +1,128 @@
+/*
+ * 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.
+ */
+
+package ir_framework.examples;
+
+import compiler.lib.ir_framework.*;
+import compiler.lib.ir_framework.driver.TestVMException;
+import compiler.lib.ir_framework.shared.TestFormatException;
+
+import jdk.test.lib.Asserts;
+
+/*
+ * @test
+ * @summary Example to show how to skip individual IR tests over commenting them out or problemlisting.
+ * @library /test/lib /
+ * @run driver ${test.main.class}
+ */
+
+/**
+ * When a general jtreg test is failing, there are several options to mitigate that problem including uncommenting the
+ * problematic part of the test out or just problemlisting.
+ *
+ *
+ * The IR Framework introduces another way: Use {@link Skip @Skip} at a {@link Test @Test}-annotated method to exclude
+ * this test from being executed.
+ *
+ *
+ * The IR Framework provides the following additional flag to run those skipped tests on the fly without requiring
+ * any additional test or IR Framework changes (also see README.md):
+ *
+ *
-DIgnoreSkip=true: The IR Framework executes all tests - including {@link Skip @Skip}-annotated
+ * tests.
+ *
+ *
+ *
+ * Below you find some examples.
+ *
+ * @see Skip
+ */
+public class SkipTestExample {
+ public static void main(String[] args) {
+ header("SkipTestExample Tests:");
+ TestFramework.run();
+ try {
+ header("SkipTestExample Tests - -DIgnoreSkip=true:");
+ TestFramework.runWithFlags("-DIgnoreSkip=true");
+ Asserts.fail("Should report TestVMException");
+ } catch (TestVMException e) {
+ // Expected, uncomment to see the output:
+ //throw e;
+ }
+
+ try {
+ header("Violation Tests:");
+ TestFramework.run(Violation.class);
+ Asserts.fail("Should report TestFormatException");
+ } catch (TestFormatException e) {
+ // Expected, uncomment to see the output:
+ //throw e;
+ }
+ }
+
+ private static void header(String header) {
+ System.out.println(header);
+ System.out.println("=".repeat(header.length()));
+ }
+
+ // Skipped test that would otherwise fail.
+ // - -DIgnoreSkip=true: This test will fail and is reported as such.
+ @Test
+ @Skip
+ public void testWithSkip() {
+ throw new RuntimeException("skipped");
+ }
+
+ @Test
+ @Skip
+ public void testForRunner() {
+ }
+
+ // testWithSkip2() is skipped and thus the IR Framework is not executing this @Run method.
+ // - -DIgnoreSkip=true: This test will fail and is reported as such.
+ @Run(test = "testForRunner")
+ public void runWithSkip() {
+ testForRunner();
+ throw new RuntimeException("skipped");
+ }
+}
+
+class Violation {
+ @Test
+ @Skip
+ public void testSkip() {}
+
+ @Test
+ public void testNotSkipped() {}
+
+ // Even though not all tests that are called from this @Run method are skipped, there is no way to modify the
+ // code to call testSkip() but avoid calling testNotSkipped(). Therefore, the entire @Run method should be excluded
+ // which in turn could be undesirable. Therefore, the IR Framework rejects such partially skipped tests with a
+ // TestFormatException by requiring either all @Test methods to be skipped or none.
+ @Run(test = {"testSkip",
+ "testNotSkipped"})
+ public void runMultipleTests() {
+ testSkip();
+ testNotSkipped();
+ }
+}
diff --git a/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestBadFormat.java b/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestBadFormat.java
index effb18611962..3df645be17ee 100644
--- a/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestBadFormat.java
+++ b/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestBadFormat.java
@@ -58,6 +58,7 @@ public static void main(String[] args) {
expectTestFormatException(BadRunTests.class);
expectTestFormatException(BadSetupTest.class);
expectTestFormatException(BadCheckTest.class);
+ expectTestFormatException(BadSkip.class);
expectTestFormatException(BadIRAnnotationBeforeFlagVM.class);
expectTestFormatException(BadIRAnnotations.class);
expectTestFormatException(BadIRAnnotationsAfterTestVM.class);
@@ -777,6 +778,43 @@ public void testInvalidRunWithArgAnnotation() {}
public void invalidRunWithArgAnnotation(TestInfo info) {}
}
+class BadSkip {
+ @NoFail
+ @Test
+ public void test() {}
+
+ @NoFail
+ @Test
+ public void test2() {}
+
+ @Skip
+ public void noTest() {}
+
+ @Run(test = "test")
+ @Skip
+ public void noTest2() {}
+
+ @Check(test = "test2")
+ @Skip
+ public void noTest3() {}
+
+ @Setup
+ @Skip
+ public void noTest4() {}
+
+ @NoFail
+ @Test
+ @Skip
+ public static void testSkipOnlyOneTestWithRunMultiple1() {}
+
+ @FailCount(0) // Combined with runTestSkipOnlyOneTestWithRunMultiple() below
+ @Test
+ public static void testSkipOnlyOneTestWithRunMultiple2() {}
+
+ @Run(test = {"testSkipOnlyOneTestWithRunMultiple1",
+ "testSkipOnlyOneTestWithRunMultiple2"})
+ public static void runTestSkipOnlyOneTestWithRunMultiple() {}
+}
class BadIRAnnotationBeforeFlagVM {
diff --git a/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestNoMainExecution.java b/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestNoMainExecution.java
index 8e9410cdb8d9..156d8d82184d 100644
--- a/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestNoMainExecution.java
+++ b/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestNoMainExecution.java
@@ -30,7 +30,7 @@
* @run driver ${test.main.class}
*/
-package testlibrary_tests.ir_framework.tests;
+package ir_framework.tests;
import compiler.lib.ir_framework.*;
import jdk.test.lib.Asserts;
diff --git a/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestSkip.java b/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestSkip.java
new file mode 100644
index 000000000000..509f899bd694
--- /dev/null
+++ b/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestSkip.java
@@ -0,0 +1,303 @@
+/*
+ * 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.
+ */
+
+package ir_framework.tests;
+
+import compiler.lib.ir_framework.*;
+import compiler.lib.ir_framework.driver.TestVMException;
+import compiler.lib.ir_framework.driver.irmatching.IRViolationException;
+import compiler.lib.ir_framework.shared.TestFormatException;
+import jdk.internal.misc.Unsafe;
+import jdk.test.lib.Asserts;
+
+/*
+ * @test
+ * @requires vm.debug == true & vm.compMode != "Xint" & vm.compiler1.enabled & vm.compiler2.enabled & vm.flagless
+ * @summary Test different custom run tests.
+ * @modules java.base/jdk.internal.misc:+open
+ * @library /test/lib /testlibrary_tests /
+ * @run driver ${test.main.class}
+ */
+
+public class TestSkip {
+ public static void main(String[] args) {
+ runGood();
+ runMix();
+ runViolation();
+ runVMCrash();
+ runIRVerification();
+ }
+
+ private static void runGood() {
+ TestFramework.run(SkipBasics.class);
+ }
+
+ private static void runMix() {
+ try {
+ TestFramework.run(Mix.class);
+ Asserts.fail("Should have thrown");
+ } catch (TestVMException e) {
+ Asserts.assertTrue(e.getExceptionInfo().contains("testFailNoSkip() no @Skip"));
+ }
+
+ TestFramework testFramework = new TestFramework(Mix.class);
+ testFramework.addFlags("-DIgnoreSkip=true");
+ try {
+ testFramework.start();
+ Asserts.fail("Should have thrown");
+ } catch (TestVMException e) {
+ Asserts.assertTrue(e.getExceptionInfo().contains("testFailNoSkip() no @Skip"));
+ Asserts.assertTrue(e.getExceptionInfo().contains("testFailWithSkip() with @Skip"));
+ Asserts.assertTrue(e.getExceptionInfo().contains("testWithSingleTest() with @Skip"));
+ Asserts.assertTrue(e.getExceptionInfo().contains("testWithMultipleTestsFail() with @Skip"));
+ }
+ }
+
+ private static void runViolation() {
+ runViolation(new TestFramework(Violation.class));
+ runViolation(new TestFramework(Violation.class).addFlags("-DIgnoreSkip=true"));
+ }
+
+ private static void runViolation(TestFramework testFramework) {
+ try {
+ testFramework.start();
+ Asserts.fail("Should throw");
+ } catch (TestFormatException e) {
+ String message = e.getMessage();
+ Asserts.assertTrue(message.contains("Violations (2)"));
+ Asserts.assertTrue(message.contains("@Run-methods with multiple tests can only either skip none or all " +
+ "methods with @Skip"));
+ Asserts.assertTrue(message.contains("run1()"));
+ Asserts.assertTrue(message.contains("two()"));
+ Asserts.assertTrue(message.contains("run2()"));
+ Asserts.assertTrue(message.contains("missing"));
+ }
+ }
+
+ private static void runVMCrash() {
+ new TestFramework(VMCrash.class)
+ .addFlags("--add-exports", "java.base/jdk.internal.misc=ALL-UNNAMED")
+ .start();
+
+ try {
+ new TestFramework(VMCrash.class)
+ .addFlags("--add-exports", "java.base/jdk.internal.misc=ALL-UNNAMED", "-DIgnoreSkip=true")
+ .start();
+ Asserts.fail("Should have thrown");
+ } catch (TestVMException e) {
+ // Expected.
+ Asserts.assertTrue(e.getExceptionInfo().contains("A fatal error has been detected"));
+ }
+ }
+
+ private static void runIRVerification() {
+ TestFramework.run(IRVerification.class);
+
+ try {
+ new TestFramework(IRVerification.class)
+ .addFlags("-DIgnoreSkip=true")
+ .start();
+ Asserts.fail("Should have thrown");
+ } catch (IRViolationException e) {
+ // Expected.
+ String message = e.getExceptionInfo();
+ Asserts.assertTrue(message.contains("Failed IR Rules (3) of Methods (3)"));
+ Asserts.assertTrue(message.contains("Method \"ir_framework.tests." +
+ "TestSkip$IRVerification::testFailingIrCustomRunTest"));
+ Asserts.assertTrue(message.contains("Method \"ir_framework.tests." +
+ "TestSkip$IRVerification::testFailingIrCheckedTest"));
+ Asserts.assertTrue(message.contains("Method \"ir_framework.tests." +
+ "TestSkip$IRVerification::testFailingIrBaseTest"));
+ }
+ }
+
+ static class SkipBasics {
+ @Skip
+ @Test
+ public static void testSkip() {
+ throw new RuntimeException("testSkip() with @Skip");
+ }
+
+ @Skip
+ @Test
+ public static void testWithSingleTest() {
+ throw new RuntimeException("testSkipWithRun() with @Skip");
+ }
+
+ @Run(test = "testWithSingleTest")
+ public static void runWithSingleTest() {
+ try {
+ testWithSingleTest();
+ } catch (RuntimeException e) {
+ throw new RuntimeException("From @Run", e);
+ }
+ }
+
+ @Skip
+ @Test
+ public static void testWithMultipleTestsAllSkipFail1() {
+ throw new RuntimeException("testWithMultipleTestsAllSkipFail1() with @Skip");
+ }
+
+ @Skip
+ @Test
+ public static void testWithMultipleTestsAllSkipFail2() {
+ throw new RuntimeException("testWithMultipleTestsAllSkipFail2() with @Skip");
+ }
+
+ @Run(test = {"testWithMultipleTestsAllSkipFail1",
+ "testWithMultipleTestsAllSkipFail2"})
+ public static void runWithMultipleTestsAllSkipFail() {
+ testWithMultipleTestsAllSkipFail1();
+ testWithMultipleTestsAllSkipFail2();
+ }
+ }
+
+ static class IRVerification {
+ static int iFld;
+
+ @Skip
+ @Test
+ @IR(failOn = IRNode.STORE_I)
+ public static void testFailingIrBaseTest() {
+ iFld = 34;
+ }
+
+ @Skip
+ @Test
+ @IR(failOn = IRNode.STORE_I)
+ public static void testFailingIrCheckedTest() {
+ iFld = 34;
+ }
+
+ @Check(test = "testFailingIrCheckedTest")
+ public static void checkTest() {}
+
+ @Skip
+ @Test
+ @IR(failOn = IRNode.STORE_I)
+ public static void testFailingIrCustomRunTest() {
+ iFld = 34;
+ }
+
+ @Run(test = "testFailingIrCustomRunTest")
+ public static void runCustomRunTest() {
+ testFailingIrCustomRunTest();
+ }
+ }
+
+ static class Mix {
+ @Skip
+ @Test
+ public static void testFailWithSkip() {
+ throw new RuntimeException("testFailWithSkip() with @Skip");
+ }
+
+ @Test
+ public static void testFailNoSkip() {
+ throw new RuntimeException("testFailNoSkip() no @Skip");
+ }
+
+ @Test
+ public static void testSuccessNoSkip() {
+ }
+
+ @Test
+ @Skip
+ public static void testSuccessWithSkip() {
+ }
+
+ @Skip
+ @Test
+ public static void testWithSingleTest() {
+ throw new RuntimeException("testWithSingleTest() with @Skip");
+ }
+
+ @Run(test = "testWithSingleTest")
+ public static void runWithSingleTest() {
+ testWithSingleTest();
+ }
+
+ // If one is skipped,
+ @Skip
+ @Test
+ public static void testWithMultipleTestsFail() {
+ throw new RuntimeException("testWithMultipleTestsFail() with @Skip");
+ }
+
+ @Skip
+ @Test
+ public static void testWithMultipleTestsSuccess() {
+ }
+
+ @Run(test = {"testWithMultipleTestsFail",
+ "testWithMultipleTestsSuccess"})
+ public static void runWithMultipleTestsOneFail() {
+ testWithMultipleTestsFail();
+ testWithMultipleTestsSuccess();
+ }
+ }
+
+ static class Violation {
+ Violation() {
+ throw new RuntimeException("should not be constructed");
+ }
+
+ @Test
+ @Skip
+ void one() {}
+
+ // Forbidden, should also be reported with -DIgnoreSkip=true
+ @Test
+ void two() {}
+
+ @Run(test = {"one", "two"})
+ void run1() {
+ one();
+ two();
+ }
+
+ @Test
+ @Skip
+ void skipped1() {}
+
+ @Test
+ @Skip
+ void skipped2() {}
+
+ @Run(test = {"skipped1", "skipped2", "missing"})
+ void run2() {
+ one();
+ two();
+ }
+ }
+
+ static class VMCrash {
+ @Test
+ @Skip
+ public void testCrashVM() {
+ Unsafe unsafe = Unsafe.getUnsafe();
+ unsafe.putAddress(0, 0); // Crash
+ }
+ }
+}
From c110aa0a8f4a39684c28b48cc9a0a272ef72c28c Mon Sep 17 00:00:00 2001
From: Rudraroop Ray
Date: Thu, 15 Oct 2026 13:10:46 +0000
Subject: [PATCH 057/169] 8393745: Shenandoah: Purge SHENANDOAH_CENSUS_NOISE
support
Reviewed-by: shade, wkemper
---
.../shenandoahGenerationalHeuristics.cpp | 1 +
.../heuristics/shenandoahGlobalHeuristics.cpp | 1 +
.../gc/shenandoah/shenandoahAgeCensus.cpp | 86 +----------------
.../gc/shenandoah/shenandoahAgeCensus.hpp | 96 +------------------
.../shenandoah/shenandoahAgeCensus.inline.hpp | 26 +----
.../gc/shenandoah/shenandoahGeneration.cpp | 1 +
.../shenandoahGenerationalFullGC.cpp | 1 +
.../gc/shenandoah/shenandoahHeapRegion.cpp | 4 -
.../gc/shenandoah/shenandoahHeapRegion.hpp | 8 +-
.../gc/shenandoah/shenandoahMark.inline.hpp | 3 +-
.../shenandoah/test_shenandoahAgeCensus.cpp | 3 +-
11 files changed, 12 insertions(+), 218 deletions(-)
diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.cpp
index 31f32836ceb3..c941d024e2ff 100644
--- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.cpp
+++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.cpp
@@ -24,6 +24,7 @@
*/
#include "gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.hpp"
+#include "gc/shenandoah/shenandoahAgeCensus.hpp"
#include "gc/shenandoah/shenandoahAllocRate.inline.hpp"
#include "gc/shenandoah/shenandoahCollectionSet.hpp"
#include "gc/shenandoah/shenandoahCollectorPolicy.hpp"
diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.cpp
index 5c07fcb6b59f..21ac29895538 100644
--- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.cpp
+++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.cpp
@@ -24,6 +24,7 @@
*/
#include "gc/shenandoah/heuristics/shenandoahGlobalHeuristics.hpp"
+#include "gc/shenandoah/shenandoahAgeCensus.hpp"
#include "gc/shenandoah/shenandoahAsserts.hpp"
#include "gc/shenandoah/shenandoahGenerationalHeap.inline.hpp"
#include "gc/shenandoah/shenandoahGlobalGeneration.hpp"
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp
index 3c23dd0bf233..d90100953920 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp
@@ -44,24 +44,17 @@ ShenandoahAgeCensus::ShenandoahAgeCensus(uint max_workers)
}
_global_age_tables = NEW_C_HEAP_ARRAY(AgeTable*, MAX_SNAPSHOTS, mtGC);
- CENSUS_NOISE(_global_noise = NEW_C_HEAP_ARRAY(ShenandoahNoiseStats, MAX_SNAPSHOTS, mtGC);)
_tenuring_threshold = NEW_C_HEAP_ARRAY(uint, MAX_SNAPSHOTS, mtGC);
- CENSUS_NOISE(_skipped = 0);
- NOT_PRODUCT(_counted = 0);
- NOT_PRODUCT(_total = 0);
for (int i = 0; i < MAX_SNAPSHOTS; i++) {
// Note that we don't now get perfdata from age_table
_global_age_tables[i] = new AgeTable(false);
- CENSUS_NOISE(_global_noise[i].clear();)
// Sentinel value
_tenuring_threshold[i] = MAX_COHORTS;
}
_local_age_tables = NEW_C_HEAP_ARRAY(AgeTable*, _max_workers, mtGC);
- CENSUS_NOISE(_local_noise = NEW_C_HEAP_ARRAY(ShenandoahNoiseStats, max_workers, mtGC);)
for (uint i = 0; i < _max_workers; i++) {
_local_age_tables[i] = new AgeTable(false);
- CENSUS_NOISE(_local_noise[i].clear();)
}
_epoch = MAX_SNAPSHOTS - 1; // see prepare_for_census_update()
}
@@ -72,34 +65,14 @@ ShenandoahAgeCensus::~ShenandoahAgeCensus() {
}
FREE_C_HEAP_ARRAY(_global_age_tables);
FREE_C_HEAP_ARRAY(_tenuring_threshold);
- CENSUS_NOISE(FREE_C_HEAP_ARRAY(_global_noise));
if (_local_age_tables) {
for (uint i = 0; i < _max_workers; i++) {
delete _local_age_tables[i];
}
FREE_C_HEAP_ARRAY(_local_age_tables);
- CENSUS_NOISE(FREE_C_HEAP_ARRAY(_local_noise));
}
}
-#ifdef SHENANDOAH_CENSUS_NOISE
-void ShenandoahAgeCensus::add_skipped(size_t size, uint worker_id) {
- _local_noise[worker_id].skipped += size;
-}
-
-void ShenandoahAgeCensus::add_aged(size_t size, uint worker_id) {
- _local_noise[worker_id].aged += size;
-}
-
-void ShenandoahAgeCensus::add_clamped(size_t size, uint worker_id) {
- _local_noise[worker_id].clamped += size;
-}
-
-void ShenandoahAgeCensus::add_young(size_t size, uint worker_id) {
- _local_noise[worker_id].young += size;
-}
-#endif // SHENANDOAH_CENSUS_NOISE
-
// Prepare for a new census update, by clearing appropriate global slots.
void ShenandoahAgeCensus::prepare_for_census_update() {
assert(_epoch < MAX_SNAPSHOTS, "Out of bounds");
@@ -107,7 +80,6 @@ void ShenandoahAgeCensus::prepare_for_census_update() {
_epoch=0;
}
_global_age_tables[_epoch]->clear();
- CENSUS_NOISE(_global_noise[_epoch].clear();)
}
// Update the census data from appropriate sources,
@@ -115,7 +87,6 @@ void ShenandoahAgeCensus::prepare_for_census_update() {
void ShenandoahAgeCensus::update_census(size_t age0_pop) {
prepare_for_census_update();
assert(_global_age_tables[_epoch]->is_clear(), "Dirty decks");
- CENSUS_NOISE(assert(_global_noise[_epoch].is_clear(), "Dirty decks");)
// Seed cohort 0 with population that may have been missed during
// regular census.
@@ -124,19 +95,11 @@ void ShenandoahAgeCensus::update_census(size_t age0_pop) {
// Merge data from local age tables into the global age table for the epoch,
// clearing the local tables.
for (uint i = 0; i < _max_workers; i++) {
- // age stats
_global_age_tables[_epoch]->merge(_local_age_tables[i]);
_local_age_tables[i]->clear(); // clear for next census
- // Merge noise stats
- CENSUS_NOISE(_global_noise[_epoch].merge(_local_noise[i]);)
- CENSUS_NOISE(_local_noise[i].clear();)
}
update_tenuring_threshold();
-
- // used for checking reasonableness of census coverage, non-product
- // only.
- NOT_PRODUCT(update_total();)
}
size_t ShenandoahAgeCensus::get_tenurable_bytes(const uint tenuring_threshold) const {
@@ -155,7 +118,6 @@ void ShenandoahAgeCensus::reset_global() {
assert(_epoch < MAX_SNAPSHOTS, "Out of bounds");
for (uint i = 0; i < MAX_SNAPSHOTS; i++) {
_global_age_tables[i]->clear();
- CENSUS_NOISE(_global_noise[i].clear();)
}
_epoch = MAX_SNAPSHOTS;
assert(_epoch < MAX_SNAPSHOTS, "Error");
@@ -165,7 +127,6 @@ void ShenandoahAgeCensus::reset_global() {
void ShenandoahAgeCensus::reset_local() {
for (uint i = 0; i < _max_workers; i++) {
_local_age_tables[i]->clear();
- CENSUS_NOISE(_local_noise[i].clear();)
}
}
@@ -174,9 +135,7 @@ void ShenandoahAgeCensus::reset_local() {
bool ShenandoahAgeCensus::is_clear_global() {
assert(_epoch < MAX_SNAPSHOTS, "Out of bounds");
for (uint i = 0; i < MAX_SNAPSHOTS; i++) {
- bool clear = _global_age_tables[i]->is_clear();
- CENSUS_NOISE(clear |= _global_noise[i].is_clear();)
- if (!clear) {
+ if (!_global_age_tables[i]->is_clear()) {
return false;
}
}
@@ -186,35 +145,12 @@ bool ShenandoahAgeCensus::is_clear_global() {
// Is local census information clear?
bool ShenandoahAgeCensus::is_clear_local() {
for (uint i = 0; i < _max_workers; i++) {
- bool clear = _local_age_tables[i]->is_clear();
- CENSUS_NOISE(clear |= _local_noise[i].is_clear();)
- if (!clear) {
+ if (!_local_age_tables[i]->is_clear()) {
return false;
}
}
return true;
}
-
-size_t ShenandoahAgeCensus::get_all_ages(uint snap) {
- assert(snap < MAX_SNAPSHOTS, "Out of bounds");
- size_t pop = 0;
- const AgeTable* pv = _global_age_tables[snap];
- for (uint i = 0; i < MAX_COHORTS; i++) {
- pop += pv->sizes[i];
- }
- return pop;
-}
-
-size_t ShenandoahAgeCensus::get_skipped(uint snap) {
- assert(snap < MAX_SNAPSHOTS, "Out of bounds");
- return _global_noise[snap].skipped;
-}
-
-void ShenandoahAgeCensus::update_total() {
- _counted = get_all_ages(_epoch);
- _skipped = get_skipped(_epoch);
- _total = _counted + _skipped;
-}
#endif // !PRODUCT
void ShenandoahAgeCensus::update_tenuring_threshold() {
@@ -332,7 +268,6 @@ void ShenandoahAgeCensus::print() {
const uint tt = tenuring_threshold();
- size_t total= 0;
for (uint i = 1; i < MAX_COHORTS; i++) {
const size_t prev_pop = prev_pv->sizes[i-1]; // (i-1) OK because i >= 1
const size_t cur_pop = cur_pv->sizes[i];
@@ -342,26 +277,9 @@ void ShenandoahAgeCensus::print() {
ls.print_cr(" - age %3u: prev %10zu bytes, curr %10zu bytes, mortality %.2f ",
i, prev_pop * oopSize, cur_pop * oopSize, mr);
}
- total += cur_pop;
if (i == tt) {
// Underline the cohort for tenuring threshold (if < MAX_COHORTS)
ls.print_cr("----------------------------------------------------------------------------");
}
}
- CENSUS_NOISE(_global_noise[cur_epoch].print(ls, total);)
-}
-
-#ifdef SHENANDOAH_CENSUS_NOISE
-void ShenandoahNoiseStats::print(LogStream& ls, const size_t total) {
- if (total > 0) {
- const float f_skipped = (float)skipped/(float)total;
- const float f_aged = (float)aged/(float)total;
- const float f_clamped = (float)clamped/(float)total;
- const float f_young = (float)young/(float)total;
- ls.print_cr("Skipped: %10zu (%.2f), R-Aged: %10zu (%.2f), "
- "Clamped: %10zu (%.2f), R-Young: %10zu (%.2f)",
- skipped*oopSize, f_skipped, aged*oopSize, f_aged,
- clamped*oopSize, f_clamped, young*oopSize, f_young);
- }
}
-#endif // SHENANDOAH_CENSUS_NOISE
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp
index 2d79eb559609..6577715f44d3 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp
@@ -27,55 +27,6 @@
#include "gc/shared/ageTable.hpp"
-#ifndef PRODUCT
-// Enable noise instrumentation
-#define SHENANDOAH_CENSUS_NOISE 1
-#endif // PRODUCT
-
-#ifdef SHENANDOAH_CENSUS_NOISE
-
-#define CENSUS_NOISE(x) x
-#define NO_CENSUS_NOISE(x)
-
-class LogStream;
-
-struct ShenandoahNoiseStats {
- size_t skipped; // Volume of objects skipped
- size_t aged; // Volume of objects from aged regions
- size_t clamped; // Volume of objects whose ages were clamped
- size_t young; // Volume of (rejuvenated) objects of retrograde age
-
- ShenandoahNoiseStats() {
- clear();
- }
-
- void clear() {
- skipped = 0;
- aged = 0;
- clamped = 0;
- young = 0;
- }
-
-#ifndef PRODUCT
- bool is_clear() {
- return (skipped + aged + clamped + young) == 0;
- }
-#endif // !PRODUCT
-
- void merge(ShenandoahNoiseStats& other) {
- skipped += other.skipped;
- aged += other.aged;
- clamped += other.clamped;
- young += other.young;
- }
-
- void print(LogStream& ls, size_t total);
-};
-#else // SHENANDOAH_CENSUS_NOISE
-#define CENSUS_NOISE(x)
-#define NO_CENSUS_NOISE(x) x
-#endif // SHENANDOAH_CENSUS_NOISE
-
// A class for tracking a sequence of cohort population vectors (or,
// interchangeably, age tables) for up to C=MAX_COHORTS age cohorts, where a cohort
// represents the set of objects allocated during a specific inter-GC epoch.
@@ -102,19 +53,6 @@ class ShenandoahAgeCensus: public CHeapObj {
AgeTable** _global_age_tables; // Global age tables used for adapting tenuring threshold, one per snapshot
AgeTable** _local_age_tables; // Local scratch age tables to track object ages, one per worker
-#ifdef SHENANDOAH_CENSUS_NOISE
- ShenandoahNoiseStats* _global_noise; // Noise stats, one per snapshot
- ShenandoahNoiseStats* _local_noise; // Local scratch table for noise stats, one per worker
-
- size_t _skipped; // net size of objects encountered, but skipped during census,
- // because their age was indeterminate
-#endif // SHENANDOAH_CENSUS_NOISE
-
-#ifndef PRODUCT
- size_t _counted; // net size of objects counted in census
- size_t _total; // net size of objects encountered (counted or skipped) in census
-#endif
-
uint _epoch; // Current epoch (modulo max age)
uint* _tenuring_threshold; // An array of the last N tenuring threshold values we
// computed.
@@ -156,20 +94,6 @@ class ShenandoahAgeCensus: public CHeapObj {
// to force is_tenurable() to be true for every age during WB.fullGC tests.
void set_always_tenure(bool always_tenure) { _always_tenure = always_tenure; }
-#ifndef PRODUCT
- // Return the sum of size of objects of all ages recorded in the
- // census at snapshot indexed by snap.
- size_t get_all_ages(uint snap);
-
- // Return the size of all objects that were encountered, but skipped,
- // during the census, because their age was indeterminate.
- size_t get_skipped(uint snap);
-
- // Update the total size of objects counted or skipped at the census for
- // the most recent epoch.
- void update_total();
-#endif // !PRODUCT
-
public:
enum {
MAX_COHORTS = AgeTable::table_size, // = markWord::max_age + 1
@@ -204,20 +128,8 @@ class ShenandoahAgeCensus: public CHeapObj {
bool is_always_tenure() const { return _always_tenure; }
// Update the local age table for worker_id by size for
- // given obj_age, region_age, and region_youth
- CENSUS_NOISE(inline void add(uint obj_age, uint region_age, uint region_youth, size_t size, uint worker_id);)
- NO_CENSUS_NOISE(inline void add(uint obj_age, uint region_age, size_t size, uint worker_id);)
-
-#ifdef SHENANDOAH_CENSUS_NOISE
- // Update the local skip table for worker_id by size
- void add_skipped(size_t size, uint worker_id);
- // Update the local aged region volume table for worker_id by size
- void add_aged(size_t size, uint worker_id);
- // Update the local clamped object volume table for worker_id by size
- void add_clamped(size_t size, uint worker_id);
- // Update the local (rejuvenated) object volume (retrograde age) for worker_id by size
- void add_young(size_t size, uint worker_id);
-#endif // SHENANDOAH_CENSUS_NOISE
+ // given obj_age and region_age
+ inline void add(uint obj_age, uint region_age, size_t size, uint worker_id);
// Update the census data, and compute the new tenuring threshold.
// This method should be called at the end of each marking cycle to update
@@ -245,10 +157,6 @@ class ShenandoahAgeCensus: public CHeapObj {
// Check whether census information is clear
bool is_clear_global();
bool is_clear_local();
-
- // Return the net size of objects encountered (counted or skipped) in census
- // at most recent epoch.
- size_t get_total() const { return _total; }
#endif // !PRODUCT
// Print the age census information
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.inline.hpp
index 299b9a3f8b7a..704915375fa3 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.inline.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.inline.hpp
@@ -27,35 +27,11 @@
#include "gc/shenandoah/shenandoahAgeCensus.hpp"
-CENSUS_NOISE(void ShenandoahAgeCensus::add(uint obj_age, uint region_age, uint region_youth, size_t size, uint worker_id) {)
-NO_CENSUS_NOISE(void ShenandoahAgeCensus::add(uint obj_age, uint region_age, size_t size, uint worker_id) {)
+void ShenandoahAgeCensus::add(uint obj_age, uint region_age, size_t size, uint worker_id) {
if (obj_age <= markWord::max_age) {
assert(obj_age < MAX_COHORTS && region_age < MAX_COHORTS, "Should have been tenured");
-#ifdef SHENANDOAH_CENSUS_NOISE
- // Region ageing is stochastic and non-monotonic; this vitiates mortality
- // demographics in ways that might defeat our algorithms. Marking may be a
- // time when we might be able to correct this, but we currently do not do
- // this. Like skipped statistics further below, we want to track the
- // impact of this noise to see if this may be worthwhile. JDK-.
- uint age = obj_age;
- if (region_age > 0) {
- add_aged(size, worker_id); // this tracking is coarse for now
- age += region_age;
- if (age >= MAX_COHORTS) {
- age = (uint)(MAX_COHORTS - 1); // clamp
- add_clamped(size, worker_id);
- }
- }
- if (region_youth > 0) { // track object volume with retrograde age
- add_young(size, worker_id);
- }
-#else // SHENANDOAH_CENSUS_NOISE
uint age = MIN2(obj_age + region_age, (uint)(MAX_COHORTS - 1)); // clamp
-#endif // SHENANDOAH_CENSUS_NOISE
get_local_age_table(worker_id)->add(age, size);
- } else {
- // update skipped statistics
- CENSUS_NOISE(add_skipped(size, worker_id);)
}
}
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp
index fb99825bef93..6b1d94d64348 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp
@@ -25,6 +25,7 @@
#include "gc/shenandoah/heuristics/shenandoahHeuristics.hpp"
#include "gc/shenandoah/shenandoahAffiliation.hpp"
+#include "gc/shenandoah/shenandoahAgeCensus.hpp"
#include "gc/shenandoah/shenandoahCollectorPolicy.hpp"
#include "gc/shenandoah/shenandoahFreeSet.hpp"
#include "gc/shenandoah/shenandoahGeneration.hpp"
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalFullGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalFullGC.cpp
index 46230c9654f4..4d178f4b0494 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalFullGC.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalFullGC.cpp
@@ -26,6 +26,7 @@
#include "gc/shared/fullGCForwarding.inline.hpp"
#include "gc/shared/preservedMarks.inline.hpp"
#include "gc/shenandoah/shenandoahAffiliation.hpp"
+#include "gc/shenandoah/shenandoahAgeCensus.hpp"
#include "gc/shenandoah/shenandoahForwarding.inline.hpp"
#include "gc/shenandoah/shenandoahGeneration.hpp"
#include "gc/shenandoah/shenandoahGenerationalFullGC.hpp"
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.cpp
index 3aac7f21efaf..289bf95ecf57 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.cpp
@@ -74,9 +74,6 @@ ShenandoahHeapRegion::ShenandoahHeapRegion(HeapWord* start, size_t index, bool c
_mixed_candidate_garbage_words(0),
_update_watermark(start),
_age(0),
-#ifdef SHENANDOAH_CENSUS_NOISE
- _youth(0),
-#endif // SHENANDOAH_CENSUS_NOISE
_needs_bitmap_reset(false)
{
@@ -320,7 +317,6 @@ void ShenandoahHeapRegion::make_trash_immediate() {
void ShenandoahHeapRegion::make_empty() {
reset_age();
- CENSUS_NOISE(clear_youth();)
switch (state()) {
case _trash:
set_state(_empty_committed);
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.hpp
index 86e3afae81e3..0a4c90575d09 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.hpp
@@ -29,7 +29,6 @@
#include "gc/shared/gc_globals.hpp"
#include "gc/shared/spaceDecorator.hpp"
#include "gc/shenandoah/shenandoahAffiliation.hpp"
-#include "gc/shenandoah/shenandoahAgeCensus.hpp"
#include "gc/shenandoah/shenandoahAllocRequest.hpp"
#include "gc/shenandoah/shenandoahAsserts.hpp"
#include "gc/shenandoah/shenandoahHeap.hpp"
@@ -271,7 +270,6 @@ class ShenandoahHeapRegion {
uint _age;
bool _promoted_in_place;
- CENSUS_NOISE(uint _youth;) // tracks epochs of retrograde ageing (rejuvenation)
ShenandoahSharedFlag _recycling; // Used to indicate that the region is being recycled; see try_recycle*().
@@ -505,9 +503,8 @@ class ShenandoahHeapRegion {
void set_affiliation(ShenandoahAffiliation new_affiliation);
- // Region ageing and rejuvenation
+ // Region ageing
uint age() const { return _age; }
- CENSUS_NOISE(uint youth() const { return _youth; })
void increment_age() {
const uint max_age = markWord::max_age;
@@ -519,12 +516,9 @@ class ShenandoahHeapRegion {
void reset_age() {
assert(get_top_before_promote() == nullptr, "Cannot reset age on region (%zu) scheduled for promotion", index());
- CENSUS_NOISE(_youth += _age;)
_age = 0;
}
- CENSUS_NOISE(void clear_youth() { _youth = 0; })
-
inline bool need_bitmap_reset() const {
return _needs_bitmap_reset;
}
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp
index 7a3a5e89b11d..3df384dcb243 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp
@@ -163,8 +163,7 @@ void ShenandoahMark::count_liveness(ShenandoahLiveData* live_data, oop obj, Klas
assert(region->is_young(), "Only for young objects");
const uint age = ShenandoahForwarding::age(obj);
ShenandoahAgeCensus* const census = ShenandoahGenerationalHeap::heap()->age_census();
- CENSUS_NOISE(census->add(age, region->age(), region->youth(), size, worker_id);)
- NO_CENSUS_NOISE(census->add(age, region->age(), size, worker_id);)
+ census->add(age, region->age(), size, worker_id);
}
if (!region->is_humongous_start()) {
diff --git a/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp b/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp
index 9189e5af456e..394e7037e9ad 100644
--- a/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp
+++ b/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp
@@ -41,8 +41,7 @@ class ShenandoahAgeCensusTest : public ::testing::Test {
}
static void add_population(ShenandoahAgeCensus& census, const uint age, const size_t population_words) {
- CENSUS_NOISE(census.add(age, 0, 0, population_words, 0));
- NO_CENSUS_NOISE(census.add(age, 0, population_words, 0));
+ census.add(age, 0, population_words, 0);
}
void update(ShenandoahAgeCensus& census, size_t cohorts) const {
From 196fc94db84275ca0d02e5e6e8ac7ea445d74c4a Mon Sep 17 00:00:00 2001
From: Hannes Greule
Date: Thu, 15 Oct 2026 15:27:19 +0000
Subject: [PATCH 058/169] 8364407: [REDO] Consolidate Identity of self-inverse
operations
Reviewed-by: mhaessig, chagedorn
---
src/hotspot/share/opto/subnode.cpp | 32 ++-
src/hotspot/share/opto/subnode.hpp | 2 +
.../c2/gvn/InvolutionIdentityTests.java | 241 ++++++++++++++++++
.../compiler/lib/ir_framework/IRNode.java | 10 +
4 files changed, 277 insertions(+), 8 deletions(-)
create mode 100644 test/hotspot/jtreg/compiler/c2/gvn/InvolutionIdentityTests.java
diff --git a/src/hotspot/share/opto/subnode.cpp b/src/hotspot/share/opto/subnode.cpp
index 69be1c2b8947..18e82113f1b1 100644
--- a/src/hotspot/share/opto/subnode.cpp
+++ b/src/hotspot/share/opto/subnode.cpp
@@ -2222,16 +2222,32 @@ const Type* ReverseLNode::Value(PhaseGVN* phase) const {
return bottom_type();
}
-Node* ReverseINode::Identity(PhaseGVN* phase) {
- if (in(1)->Opcode() == Op_ReverseI) {
- return in(1)->in(1);
+static Node* simplify_involution(PhaseGVN* phase, Node* involution) {
+ if (involution->in(1)->Opcode() == involution->Opcode()) {
+ Node* original = involution->in(1)->in(1);
+ const TypeInt* type = phase->type(original)->isa_int();
+ // Operations on sub-int types might not be "real" involutions for values outside their type range,
+ // for example, a ReverseBytesS node with an input larger than short.
+ // Make sure not to drop potential truncations.
+ if (type == nullptr || involution->bottom_type()->is_int()->contains(type)) {
+ return original;
+ }
}
- return this;
+ return involution;
+}
+
+Node* NegNode::Identity(PhaseGVN* phase) {
+ return simplify_involution(phase, this);
+}
+
+Node* ReverseBytesNode::Identity(PhaseGVN* phase) {
+ return simplify_involution(phase, this);
+}
+
+Node* ReverseINode::Identity(PhaseGVN* phase) {
+ return simplify_involution(phase, this);
}
Node* ReverseLNode::Identity(PhaseGVN* phase) {
- if (in(1)->Opcode() == Op_ReverseL) {
- return in(1)->in(1);
- }
- return this;
+ return simplify_involution(phase, this);
}
diff --git a/src/hotspot/share/opto/subnode.hpp b/src/hotspot/share/opto/subnode.hpp
index 3b3877f35daf..f3f1f9a265d1 100644
--- a/src/hotspot/share/opto/subnode.hpp
+++ b/src/hotspot/share/opto/subnode.hpp
@@ -473,6 +473,7 @@ class NegNode : public Node {
NegNode(Node* in1) : Node(nullptr, in1) {
init_class_id(Class_Neg);
}
+ virtual Node* Identity(PhaseGVN* phase);
};
//------------------------------NegINode---------------------------------------
@@ -603,6 +604,7 @@ class ReverseBytesNode : public Node {
public:
ReverseBytesNode(Node* in) : Node(nullptr, in) {}
virtual const Type* Value(PhaseGVN* phase) const;
+ virtual Node* Identity(PhaseGVN* phase);
};
//-------------------------------ReverseBytesINode--------------------------------
// reverse bytes of an integer
diff --git a/test/hotspot/jtreg/compiler/c2/gvn/InvolutionIdentityTests.java b/test/hotspot/jtreg/compiler/c2/gvn/InvolutionIdentityTests.java
new file mode 100644
index 000000000000..bc64df850c48
--- /dev/null
+++ b/test/hotspot/jtreg/compiler/c2/gvn/InvolutionIdentityTests.java
@@ -0,0 +1,241 @@
+/*
+ * 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.
+ */
+package compiler.c2.gvn;
+
+import compiler.lib.generators.Generator;
+import compiler.lib.generators.Generators;
+import compiler.lib.generators.RestrictableGenerator;
+import compiler.lib.ir_framework.DontCompile;
+import compiler.lib.ir_framework.IR;
+import compiler.lib.ir_framework.IRNode;
+import compiler.lib.ir_framework.Run;
+import compiler.lib.ir_framework.Test;
+import compiler.lib.ir_framework.TestFramework;
+import jdk.test.lib.Asserts;
+
+/*
+ * @test
+ * @bug 8350988 8364407
+ * @summary Test that Identity simplifications of Involution nodes are being performed as expected.
+ * @key randomness
+ * @library /test/lib /
+ * @compile ReverseBytesConstantsHelper.jasm
+ * @run driver ${test.main.class}
+ */
+public class InvolutionIdentityTests {
+
+ public static final RestrictableGenerator GEN_CHAR = Generators.G.safeRestrict(Generators.G.ints(), Character.MIN_VALUE, Character.MAX_VALUE);
+ public static final RestrictableGenerator GEN_SHORT = Generators.G.safeRestrict(Generators.G.ints(), Short.MIN_VALUE, Short.MAX_VALUE);
+ public static final RestrictableGenerator GEN_LONG = Generators.G.longs();
+ public static final RestrictableGenerator GEN_INT = Generators.G.ints();
+ public static final Generator GEN_FLOAT = Generators.G.floats();
+ public static final Generator GEN_DOUBLE = Generators.G.doubles();
+
+ public static void main(String[] args) {
+ TestFramework.runWithFlags("-XX:CompileCommand=inline,compiler.c2.gvn.ReverseBytesConstantsHelper::*");
+ }
+
+ @Run(test = {
+ "testI1", "testI2",
+ "testL1", "testL2",
+ "testS1", "testS2",
+ "testUS1", "testUS2",
+ "testF1",
+ "testD1"
+ })
+ public void runMethod() {
+ int ai = GEN_INT.next();
+
+ int mini = Integer.MIN_VALUE;
+ int maxi = Integer.MAX_VALUE;
+
+ assertResultI(0);
+ assertResultI(ai);
+ assertResultI(mini);
+ assertResultI(maxi);
+
+ long al = GEN_LONG.next();
+
+ long minl = Long.MIN_VALUE;
+ long maxl = Long.MAX_VALUE;
+
+ assertResultL(0);
+ assertResultL(al);
+ assertResultL(minl);
+ assertResultL(maxl);
+
+ short as = GEN_SHORT.next().shortValue();
+
+ short mins = Short.MIN_VALUE;
+ short maxs = Short.MAX_VALUE;
+
+ assertResultS((short) 0);
+ assertResultS(as);
+ assertResultS(mins);
+ assertResultS(maxs);
+
+ char ac = (char) GEN_CHAR.next().intValue();
+
+ char minc = Character.MIN_VALUE;
+ char maxc = Character.MAX_VALUE;
+
+ assertResultUS((char) 0);
+ assertResultUS(ac);
+ assertResultUS(minc);
+ assertResultUS(maxc);
+
+ // short and char variants but passing an int
+ assertResultTruncating(0);
+ assertResultTruncating(ai);
+ assertResultTruncating(mini);
+ assertResultTruncating(maxi);
+
+ float af = GEN_FLOAT.next();
+ float inf = Float.POSITIVE_INFINITY;
+ float nanf = Float.NaN;
+
+ assertResultF(0f);
+ assertResultF(-0f);
+ assertResultF(af);
+ assertResultF(inf);
+ assertResultF(nanf);
+
+ double ad = GEN_DOUBLE.next();
+ double ind = Double.POSITIVE_INFINITY;
+ double nand = Double.NaN;
+
+ assertResultD(0d);
+ assertResultD(-0d);
+ assertResultD(ad);
+ assertResultD(ind);
+ assertResultD(nand);
+ }
+
+ @DontCompile
+ public void assertResultI(int a) {
+ Asserts.assertEQ(Integer.reverseBytes(Integer.reverseBytes(a)), testI1(a));
+ Asserts.assertEQ(Integer.reverse(Integer.reverse(a)) , testI2(a));
+ }
+
+ @DontCompile
+ public void assertResultL(long a) {
+ Asserts.assertEQ(Long.reverseBytes(Long.reverseBytes(a)), testL1(a));
+ Asserts.assertEQ(Long.reverse(Long.reverse(a)) , testL2(a));
+ }
+
+ @DontCompile
+ public void assertResultS(short a) {
+ Asserts.assertEQ(Short.reverseBytes(Short.reverseBytes(a)), testS1(a));
+ }
+
+ @DontCompile
+ public void assertResultUS(char a) {
+ Asserts.assertEQ(Character.reverseBytes(Character.reverseBytes(a)), testUS1(a));
+ }
+
+ @DontCompile
+ public void assertResultF(float a) {
+ Asserts.assertEQ(Float.floatToRawIntBits(-(-a)), Float.floatToRawIntBits(testF1(a)));
+ }
+
+ @DontCompile
+ public void assertResultD(double a) {
+ Asserts.assertEQ(Double.doubleToRawLongBits(-(-a)), Double.doubleToRawLongBits(testD1(a)));
+ }
+
+ @DontCompile
+ public void assertResultTruncating(int a) {
+ Asserts.assertEQ((int) Short.reverseBytes( ReverseBytesConstantsHelper.reverseBytesShort(a)), testS2( a));
+ Asserts.assertEQ((int) Character.reverseBytes(ReverseBytesConstantsHelper.reverseBytesChar( a)), testUS2(a));
+ }
+
+ @Test
+ @IR(failOn = {IRNode.REVERSE_BYTES_I})
+ public int testI1(int x) {
+ return Integer.reverseBytes(Integer.reverseBytes(x));
+ }
+
+ @Test
+ @IR(failOn = {IRNode.REVERSE_I})
+ public int testI2(int x) {
+ return Integer.reverse(Integer.reverse(x));
+ }
+
+ @Test
+ @IR(failOn = {IRNode.REVERSE_BYTES_L})
+ public long testL1(long x) {
+ return Long.reverseBytes(Long.reverseBytes(x));
+ }
+
+ @Test
+ @IR(failOn = {IRNode.REVERSE_L})
+ public long testL2(long x) {
+ return Long.reverse(Long.reverse(x));
+ }
+
+ @Test
+ @IR(failOn = {IRNode.REVERSE_BYTES_S})
+ public short testS1(short x) {
+ // explicit cast to restrict the input to TypeInt::SHORT
+ return Short.reverseBytes(Short.reverseBytes((short) (int) x));
+ }
+
+ @Test
+ @IR(failOn = {IRNode.REVERSE_BYTES_US})
+ public char testUS1(char x) {
+ // explicit cast to restrict the input to TypeInt::CHAR
+ return Character.reverseBytes(Character.reverseBytes((char) (int) x));
+ }
+
+ @Test
+ @IR(counts = {IRNode.REVERSE_BYTES_S, "2"},
+ applyIfPlatform = {"riscv64", "true"},
+ applyIfCPUFeature = { "zbb", "true" })
+ @IR(counts = {IRNode.REVERSE_BYTES_S, "2"},
+ applyIfPlatform = {"riscv64", "false"})
+ public int testS2(int x) {
+ return Short.reverseBytes(ReverseBytesConstantsHelper.reverseBytesShort(x));
+ }
+
+ @Test
+ @IR(counts = {IRNode.REVERSE_BYTES_US, "2"},
+ applyIfPlatform = {"riscv64", "true"},
+ applyIfCPUFeature = { "zbb", "true" })
+ @IR(counts = {IRNode.REVERSE_BYTES_US, "2"},
+ applyIfPlatform = {"riscv64", "false"})
+ public int testUS2(int x) {
+ return Character.reverseBytes(ReverseBytesConstantsHelper.reverseBytesChar(x));
+ }
+
+ @Test
+ @IR(failOn = {IRNode.NEG_F})
+ public float testF1(float x) {
+ return -(-x);
+ }
+
+ @Test
+ @IR(failOn = {IRNode.NEG_D})
+ public double testD1(double x) {
+ return -(-x);
+ }
+}
diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java
index 87ad9f029f7c..147300d0416b 100644
--- a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java
+++ b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java
@@ -1705,6 +1705,16 @@ public static void anyLoadOfNodes(String irNodePlaceholder, String fieldHolder)
superWordNodes(UMAX_REDUCTION_V, "UMaxReductionV");
}
+ public static final String NEG_F = PREFIX + "NEG_F" + POSTFIX;
+ static {
+ beforeMatchingNameRegex(NEG_F, "NegF");
+ }
+
+ public static final String NEG_D = PREFIX + "NEG_D" + POSTFIX;
+ static {
+ beforeMatchingNameRegex(NEG_D, "NegD");
+ }
+
public static final String NEG_VF = VECTOR_PREFIX + "NEG_VF" + POSTFIX;
static {
vectorNode(NEG_VF, "NegVF", TYPE_FLOAT);
From b7653ecd5e98cad27e357c79f2ac3fe0026a91e3 Mon Sep 17 00:00:00 2001
From: Frederic Parain
Date: Thu, 15 Oct 2026 17:45:50 +0000
Subject: [PATCH 059/169] 8394654: [Valhalla] Missing return after uncommon
trap in LibraryCallKit::inline_unsafe_flat_access()
Reviewed-by: shade, qamai, kvn
---
src/hotspot/share/opto/library_call.cpp | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp
index 6b3f61342a9b..be0c21e823ec 100644
--- a/src/hotspot/share/opto/library_call.cpp
+++ b/src/hotspot/share/opto/library_call.cpp
@@ -2816,6 +2816,7 @@ bool LibraryCallKit::inline_unsafe_flat_access(bool is_store, AccessKind kind) {
if (new_base->is_top()) {
uncommon_trap(Deoptimization::Reason_intrinsic,
Deoptimization::Action_none);
+ return true;
}
replace_in_map(base, new_base);
base = new_base;
From decb49e1f3d05eeae7ef48e43623e3795b21d266 Mon Sep 17 00:00:00 2001
From: Dan Smith
Date: Thu, 15 Oct 2026 18:22:48 +0000
Subject: [PATCH 060/169] 8388161: value-objects-jls: 12.6: address
finalization of value objects
Reviewed-by: vromero, liach
---
.../share/classes/java/lang/Object.java | 17 +++++++----------
1 file changed, 7 insertions(+), 10 deletions(-)
diff --git a/src/java.base/share/classes/java/lang/Object.java b/src/java.base/share/classes/java/lang/Object.java
index 5fee4809a700..6cfe3b5b3ba7 100644
--- a/src/java.base/share/classes/java/lang/Object.java
+++ b/src/java.base/share/classes/java/lang/Object.java
@@ -47,11 +47,11 @@
* It is not possible to synchronize on a value object. An attempt to {@code
* synchronize} on a value object causes {@link IdentityException} to be thrown.
*
- * The {@link #finalize()} method of a value class will never be invoked by
- * the garbage collector.
+ * Value objects are never finalized, and it is an error to declare a
+ * {@code finalize} method in a value class.
*
* A {@linkplain java.lang.ref.Reference Reference object} can only refer to an
- * object with identity. Creating a reference object with a value object as
+ * object with identity. Creating a Reference object with a value object as
* the referent throws {@code IdentityException}.
*
*
@@ -598,12 +598,9 @@ public final void wait(long timeoutMillis, int nanos) throws InterruptedExceptio
* system resources or to perform other cleanup.
*
*
- * The {@code finalize} method of a value class is never directly
- * invoked by the garbage collector. This includes the case where an
- * abstract value class declares a {@code finalize} method and the
- * class is extended by an identity class; the garbage collector never
- * directly invokes the {@code finalize} method declared by the
- * abstract value class.
+ * A value class cannot override the {@code finalize} method, and
+ * the garbage collector never invokes {@code finalize} on a value
+ * object.
*