diff --git a/make/autoconf/basic_tools.m4 b/make/autoconf/basic_tools.m4 index 66ef94d48a8b..db6c893c8b3c 100644 --- a/make/autoconf/basic_tools.m4 +++ b/make/autoconf/basic_tools.m4 @@ -148,7 +148,7 @@ AC_DEFUN([BASIC_CHECK_MAKE_VERSION], if test "x$OPENJDK_BUILD_OS_ENV" = "xwindows.cygwin"; then MAKE_EXPECTED_ENV='cygwin' elif test "x$OPENJDK_BUILD_OS_ENV" = "xwindows.msys2"; then - MAKE_EXPECTED_ENV='msys' + MAKE_EXPECTED_ENV='cygwin|msys' elif test "x$OPENJDK_BUILD_OS_ENV" = "xwindows.wsl1" || test "x$OPENJDK_BUILD_OS_ENV" = "xwindows.wsl2"; then if test "x$OPENJDK_BUILD_CPU" = "xaarch64"; then MAKE_EXPECTED_ENV='aarch64-.*-linux-gnu' @@ -159,7 +159,7 @@ AC_DEFUN([BASIC_CHECK_MAKE_VERSION], AC_MSG_ERROR([Unknown Windows environment]) fi MAKE_BUILT_FOR=`$MAKE_CANDIDATE --version | $GREP -i 'built for'` - IS_MAKE_CORRECT_ENV=`$ECHO $MAKE_BUILT_FOR | $GREP $MAKE_EXPECTED_ENV` + IS_MAKE_CORRECT_ENV=`$ECHO $MAKE_BUILT_FOR | $GREP -E $MAKE_EXPECTED_ENV` else # Not relevant for non-Windows IS_MAKE_CORRECT_ENV=true diff --git a/src/hotspot/share/gc/z/zRelocate.cpp b/src/hotspot/share/gc/z/zRelocate.cpp index d69475e62a33..1c2a4078904a 100644 --- a/src/hotspot/share/gc/z/zRelocate.cpp +++ b/src/hotspot/share/gc/z/zRelocate.cpp @@ -642,7 +642,7 @@ class ZRelocateWork : public StackObj { const zaddress to_addr = _forwarding->insert(from_addr, allocated_addr, &cursor); if (to_addr != allocated_addr) { // Already relocated, undo allocation - _allocator->undo_alloc_object(to_page, allocated_addr, size); + _allocator->undo_alloc_object(to_page, to_addr, size); increase_other_forwarded(size); } diff --git a/src/hotspot/share/opto/ifnode.cpp b/src/hotspot/share/opto/ifnode.cpp index 347d63ef57cf..d4c91d667592 100644 --- a/src/hotspot/share/opto/ifnode.cpp +++ b/src/hotspot/share/opto/ifnode.cpp @@ -669,9 +669,11 @@ const TypeInt* IfNode::filtered_int_type(PhaseGVN* gvn, Node* val, Node* if_proj BoolNode* bol = iff->in(1)->as_Bool(); if (bol->in(1) && bol->in(1)->is_Cmp()) { const CmpNode* cmp = bol->in(1)->as_Cmp(); - // Val is always the lhs of the comparision: val cmp2 - if (cmp->in(1) == val) { - assert(cmp->Opcode() == Op_CmpI, "signed comparison required"); + // Val is always the lhs of the comparision: val CmpI cmp2 + if (cmp->Opcode() == Op_CmpI && cmp->in(1) == val) { + // Only CmpI allowed, assumed by signed logic below. + // We could extend to CmpU in the future, and would + // have to implement unsigned range logic below. const TypeInt* cmp2_t = gvn->type(cmp->in(2))->isa_int(); if (cmp2_t != nullptr) { jint lo = cmp2_t->_lo; diff --git a/src/hotspot/share/opto/loopnode.cpp b/src/hotspot/share/opto/loopnode.cpp index 13feeb779476..7c62e313803b 100644 --- a/src/hotspot/share/opto/loopnode.cpp +++ b/src/hotspot/share/opto/loopnode.cpp @@ -3892,7 +3892,16 @@ const TypeInt* CountedLoopConverter::filtered_type_from_dominators(Node* val, No if (rtn_t == nullptr) { rtn_t = if_t; } else { - rtn_t = rtn_t->join(if_t)->is_int(); + const Type* join_t = rtn_t->join(if_t); + if (!join_t->isa_int()) { + // We may have encountered multiple if conditions, that have no + // overlap, and produce an empty/top type. Returning nullptr + // is conservative, it means we do not constrain the type, which + // will just prevent further optimiziations. + assert(join_t->empty(), "top"); + return nullptr; + } + rtn_t = join_t->is_int(); } } } diff --git a/src/java.base/share/classes/java/security/BinaryEncodable.java b/src/java.base/share/classes/java/security/BinaryEncodable.java index bd5d05ee4ecb..a1713c413ba1 100644 --- a/src/java.base/share/classes/java/security/BinaryEncodable.java +++ b/src/java.base/share/classes/java/security/BinaryEncodable.java @@ -32,15 +32,35 @@ import java.security.spec.X509EncodedKeySpec; import jdk.internal.javac.PreviewFeature; +import sun.security.internal.InternalBinaryEncodable; + /** - * This interface is implemented by security API classes that contain - * binary-encodable cryptographic material. + * This interface identifies the cryptographic objects that can be converted + * to and from binary data, and thereby encoded and decoded as PEM text. + * + *

The APIs for cryptographic objects such as public keys, private keys, + * certificates, and certificate revocation lists all provide the means to + * convert their instances to and from standardized binary representations. + * Other kinds of cryptographic objects, such as certificate requests, have + * no corresponding API but can still be expressed as standardized binary + * representations. The {@code BinaryEncodable} interface allows the + * {@link PEMEncoder} and {@link PEMDecoder} classes to operate uniformly on + * binary representations of key or certificate material. + * + *

The permitted subtype {@code PEM} is notable for supporting the encoding + * and decoding of PEM text that represents cryptographic objects for which no + * API exists. In future releases, other permitted subtypes may be added to + * support the encoding and decoding of such cryptographic objects. * - *

This sealed interface may evolve. When using {@code switch}, always include a - * {@code default} case rather than relying on the classes specified in the - * {@code permits} clause to remain fixed. An exhaustive {@code switch} may - * result in a {@link MatchException}. + *

The list of permitted subtypes shown after {@code permits} is not + * exhaustive. This means if application code switches over a + * {@code BinaryEncodable} value, the {@code switch} cannot be made exhaustive + * simply by providing a {@code case} label for every permitted subtype shown + * in the list; there also must be a {@code default} or + * {@code case BinaryEncodable} label to handle additional subtypes. This + * allows the list of permitted subtypes to change over time without causing + * pre-existing switches to fail because of an unrecognized subtype. * * @see AsymmetricKey * @see KeyPair @@ -57,5 +77,5 @@ @PreviewFeature(feature = PreviewFeature.Feature.PEM_API) public sealed interface BinaryEncodable permits AsymmetricKey, KeyPair, PKCS8EncodedKeySpec, X509EncodedKeySpec, EncryptedPrivateKeyInfo, - X509Certificate, X509CRL, PEM { + X509Certificate, X509CRL, PEM, InternalBinaryEncodable { } diff --git a/src/java.base/share/classes/java/security/PEMDecoder.java b/src/java.base/share/classes/java/security/PEMDecoder.java index f5a6a70d0f59..8ebc83f93d10 100644 --- a/src/java.base/share/classes/java/security/PEMDecoder.java +++ b/src/java.base/share/classes/java/security/PEMDecoder.java @@ -48,10 +48,10 @@ * PEM is a textual encoding used to store and transfer cryptographic * objects, such as asymmetric keys, certificates, and certificate revocation * lists (CRLs). It is defined in RFC 1421 and RFC 7468. PEM consists of - * Base64-encoded content enclosed by a type-identifying header - * and footer. + * Base64-encoded content enclosed by a header and footer that identify the + * type of the content. * - *

The {@link #decode(String)} and {@link #decode(InputStream)} methods + *

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

* *

If the PEM type has no corresponding class, {@code decode(String)} and - * {@code decode(InputStream)} will return a {@code PEM} object. + * {@code decode(InputStream)} return a {@code PEM} object. + * + *

If application code switches over the {@code BinaryEncodable} result of + * {@link #decode(String)} or {@link #decode(InputStream)}, the {@code switch} cannot + * be made exhaustive simply by providing a {@code case} label for every permitted + * subtype listed for {@code BinaryEncodable}; there also must be a {@code default} + * or {@code case BinaryEncodable} label to handle additional subtypes that + * might be added in the future. * *

The {@link #decode(String, Class)} and {@link #decode(InputStream, Class)} - * methods accept a class parameter specifying the desired {@code BinaryEncodable} - * type. These methods avoid the need for casting and are useful when multiple + * methods accept a parameter specifying the desired {@code BinaryEncodable} + * result. These methods avoid the need for casting and are useful when multiple * representations are possible. For example, if the PEM contains both public and * private keys, specifying {@code PrivateKey.class} returns only the private key. * If {@code X509EncodedKeySpec.class} is provided, the public key encoding is @@ -109,11 +116,6 @@ * for decryption, an {@link EncryptedPrivateKeyInfo} is returned. * A {@code PEMDecoder} configured for decryption can also decode unencrypted PEM. * - *

The {@code BinaryEncodable} interface may evolve. When using a decode method - * with {@code switch}, always include a {@code default} case rather than - * relying on the classes specified in the permits clause to remain fixed. - * An exhaustive {@code switch} may result in a {@link MatchException}. - * *

This class is immutable and thread-safe. * *

Example: decode a private key: @@ -136,6 +138,7 @@ * @see PEMEncoder * @see PEM * @see EncryptedPrivateKeyInfo + * @see BinaryEncodable * * @spec https://www.rfc-editor.org/info/rfc1421 * RFC 1421: Privacy Enhancement for Internet Electronic Mail diff --git a/src/java.base/share/classes/java/time/temporal/Temporal.java b/src/java.base/share/classes/java/time/temporal/Temporal.java index e3c0a6819f68..0eb8c28f5ada 100644 --- a/src/java.base/share/classes/java/time/temporal/Temporal.java +++ b/src/java.base/share/classes/java/time/temporal/Temporal.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 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 @@ -392,8 +392,8 @@ default Temporal minus(long amountToSubtract, TemporalUnit unit) { * The second is to use {@link TemporalUnit#between(Temporal, Temporal)}: *

      *   // these two lines are equivalent
-     *   temporal = start.until(end, unit);
-     *   temporal = unit.between(start, end);
+     *   amount = start.until(end, unit);
+     *   amount = unit.between(start, end);
      * 
* The choice should be made based on which makes the code more readable. *

diff --git a/src/java.base/share/classes/java/time/temporal/TemporalAmount.java b/src/java.base/share/classes/java/time/temporal/TemporalAmount.java index 0d8b8a96bb06..abc694957e10 100644 --- a/src/java.base/share/classes/java/time/temporal/TemporalAmount.java +++ b/src/java.base/share/classes/java/time/temporal/TemporalAmount.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 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 @@ -145,9 +145,9 @@ public interface TemporalAmount { *

      *   // These two lines are equivalent, but the second approach is recommended
      *   dateTime = amount.addTo(dateTime);
-     *   dateTime = dateTime.plus(adder);
+     *   dateTime = dateTime.plus(amount);
      * 
- * It is recommended to use the second approach, {@code plus(TemporalAmount)}, + * It is recommended to use the second approach, {@code plus(amount)}, * as it is a lot clearer to read in code. * * @implSpec @@ -189,7 +189,7 @@ public interface TemporalAmount { * dateTime = amount.subtractFrom(dateTime); * dateTime = dateTime.minus(amount); * - * It is recommended to use the second approach, {@code minus(TemporalAmount)}, + * It is recommended to use the second approach, {@code minus(amount)}, * as it is a lot clearer to read in code. * * @implSpec diff --git a/src/java.base/share/classes/java/time/temporal/TemporalField.java b/src/java.base/share/classes/java/time/temporal/TemporalField.java index 54a523e64c37..4d9c620699f1 100644 --- a/src/java.base/share/classes/java/time/temporal/TemporalField.java +++ b/src/java.base/share/classes/java/time/temporal/TemporalField.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 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 @@ -186,10 +186,10 @@ default String getDisplayName(Locale locale) { * The second is to use {@link TemporalAccessor#isSupported(TemporalField)}: *
      *   // these two lines are equivalent, but the second approach is recommended
-     *   temporal = thisField.isSupportedBy(temporal);
-     *   temporal = temporal.isSupported(thisField);
+     *   supported = thisField.isSupportedBy(temporal);
+     *   supported = temporal.isSupported(thisField);
      * 
- * It is recommended to use the second approach, {@code isSupported(TemporalField)}, + * It is recommended to use the second approach, {@code isSupported(thisField)}, * as it is a lot clearer to read in code. *

* Implementations should determine whether they are supported using the fields @@ -216,10 +216,10 @@ default String getDisplayName(Locale locale) { * The second is to use {@link TemporalAccessor#range(TemporalField)}: *

      *   // these two lines are equivalent, but the second approach is recommended
-     *   temporal = thisField.rangeRefinedBy(temporal);
-     *   temporal = temporal.range(thisField);
+     *   range = thisField.rangeRefinedBy(temporal);
+     *   range = temporal.range(thisField);
      * 
- * It is recommended to use the second approach, {@code range(TemporalField)}, + * It is recommended to use the second approach, {@code range(thisField)}, * as it is a lot clearer to read in code. *

* Implementations should perform any queries or calculations using the fields @@ -244,10 +244,10 @@ default String getDisplayName(Locale locale) { * (or {@link TemporalAccessor#get(TemporalField)}): *

      *   // these two lines are equivalent, but the second approach is recommended
-     *   temporal = thisField.getFrom(temporal);
-     *   temporal = temporal.getLong(thisField);
+     *   value = thisField.getFrom(temporal);
+     *   value = temporal.getLong(thisField);
      * 
- * It is recommended to use the second approach, {@code getLong(TemporalField)}, + * It is recommended to use the second approach, {@code getLong(thisField)}, * as it is a lot clearer to read in code. *

* Implementations should perform any queries or calculations using the fields @@ -281,10 +281,10 @@ default String getDisplayName(Locale locale) { * The second is to use {@link Temporal#with(TemporalField, long)}: *

      *   // these two lines are equivalent, but the second approach is recommended
-     *   temporal = thisField.adjustInto(temporal);
-     *   temporal = temporal.with(thisField);
+     *   temporal = thisField.adjustInto(temporal, newValue);
+     *   temporal = temporal.with(thisField, newValue);
      * 
- * It is recommended to use the second approach, {@code with(TemporalField)}, + * It is recommended to use the second approach, {@code with(thisField, newValue)}, * as it is a lot clearer to read in code. *

* Implementations should perform any queries or calculations using the fields diff --git a/src/java.base/share/classes/java/time/temporal/TemporalQueries.java b/src/java.base/share/classes/java/time/temporal/TemporalQueries.java index c0e2eaf59089..2181011e7cb5 100644 --- a/src/java.base/share/classes/java/time/temporal/TemporalQueries.java +++ b/src/java.base/share/classes/java/time/temporal/TemporalQueries.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 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 @@ -94,10 +94,10 @@ * The second is to use {@link TemporalAccessor#query(TemporalQuery)}: *

  *   // these two lines are equivalent, but the second approach is recommended
- *   temporal = thisQuery.queryFrom(temporal);
- *   temporal = temporal.query(thisQuery);
+ *   result = thisQuery.queryFrom(temporal);
+ *   result = temporal.query(thisQuery);
  * 
- * It is recommended to use the second approach, {@code query(TemporalQuery)}, + * It is recommended to use the second approach, {@code query(thisQuery)}, * as it is a lot clearer to read in code. *

* The most common implementations are method references, such as diff --git a/src/java.base/share/classes/java/time/temporal/TemporalQuery.java b/src/java.base/share/classes/java/time/temporal/TemporalQuery.java index 961429630623..220dec994a3d 100644 --- a/src/java.base/share/classes/java/time/temporal/TemporalQuery.java +++ b/src/java.base/share/classes/java/time/temporal/TemporalQuery.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 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 @@ -81,10 +81,10 @@ * The second is to use {@link TemporalAccessor#query(TemporalQuery)}: *

  *   // these two lines are equivalent, but the second approach is recommended
- *   temporal = thisQuery.queryFrom(temporal);
- *   temporal = temporal.query(thisQuery);
+ *   result = thisQuery.queryFrom(temporal);
+ *   result = temporal.query(thisQuery);
  * 
- * It is recommended to use the second approach, {@code query(TemporalQuery)}, + * It is recommended to use the second approach, {@code query(thisQuery)}, * as it is a lot clearer to read in code. *

* The most common implementations are method references, such as @@ -115,10 +115,10 @@ public interface TemporalQuery { * The second is to use {@link TemporalAccessor#query(TemporalQuery)}: *

      *   // these two lines are equivalent, but the second approach is recommended
-     *   temporal = thisQuery.queryFrom(temporal);
-     *   temporal = temporal.query(thisQuery);
+     *   result = thisQuery.queryFrom(temporal);
+     *   result = temporal.query(thisQuery);
      * 
- * It is recommended to use the second approach, {@code query(TemporalQuery)}, + * It is recommended to use the second approach, {@code query(thisQuery)}, * as it is a lot clearer to read in code. * * @implSpec diff --git a/src/java.base/share/classes/java/time/temporal/TemporalUnit.java b/src/java.base/share/classes/java/time/temporal/TemporalUnit.java index 9638e30c6ddc..3235fe6fcee3 100644 --- a/src/java.base/share/classes/java/time/temporal/TemporalUnit.java +++ b/src/java.base/share/classes/java/time/temporal/TemporalUnit.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 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 @@ -203,10 +203,10 @@ default boolean isSupportedBy(Temporal temporal) { * The second is to use {@link Temporal#plus(long, TemporalUnit)}: *
      *   // these two lines are equivalent, but the second approach is recommended
-     *   temporal = thisUnit.addTo(temporal);
-     *   temporal = temporal.plus(thisUnit);
+     *   temporal = thisUnit.addTo(temporal, amount);
+     *   temporal = temporal.plus(amount, thisUnit);
      * 
- * It is recommended to use the second approach, {@code plus(TemporalUnit)}, + * It is recommended to use the second approach, {@code plus(amount, thisUnit)}, * as it is a lot clearer to read in code. *

* Implementations should perform any queries or calculations using the units diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java index 137cac45ed06..f39d92aeeb40 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinTask.java @@ -418,19 +418,15 @@ else if (((a = aux) == null || a.ex == null) && for (;;) { if ((s = status) < 0) break; - else if (interrupts < 0) { - s = ABNORMAL; // interrupted and not done - break; - } else if (Thread.interrupted()) { - if (!ForkJoinPool.poolIsStopping(pool)) - interrupts = interruptible ? -1 : 1; - else { - interrupts = 1; // re-assert if cleared + if (ForkJoinPool.poolIsStopping(pool)) { try { cancel(true); - } catch (Throwable ignore) { - } + } catch (Throwable ignore) { } + } + if ((interrupts = interruptible ? -1 : 1) < 0) { + s = ABNORMAL; + break; } } else if (deadline != 0L) { diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/StackMapGenerator.java b/src/java.base/share/classes/jdk/internal/classfile/impl/StackMapGenerator.java index 71ce907efc45..3fafc9fdc8b5 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/StackMapGenerator.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/StackMapGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2024, Alibaba Group Holding Limited. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -1110,6 +1110,9 @@ void setLocalsFromArg(String name, MethodTypeDesc methodDesc, boolean isStatic, locals[localsSize++] = type; } } + if (locals != null && localsSize < locals.length) { + Arrays.fill(locals, localsSize, locals.length, Type.TOP_TYPE); + } this.localsSize = localsSize; } diff --git a/src/java.base/share/classes/sun/security/internal/InternalBinaryEncodable.java b/src/java.base/share/classes/sun/security/internal/InternalBinaryEncodable.java new file mode 100644 index 000000000000..72d0008a5058 --- /dev/null +++ b/src/java.base/share/classes/sun/security/internal/InternalBinaryEncodable.java @@ -0,0 +1,39 @@ +/* + * 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 sun.security.internal; + +import java.security.BinaryEncodable; + +/** + * This class is a non-public subtype of BinaryEncodable. This type + * allows the BinaryEncodable list of permitted subtypes to change + * over time without causing pre-existing switches to fail because of an + * unrecognized subtype. + */ + +public final class InternalBinaryEncodable implements BinaryEncodable { + private InternalBinaryEncodable() {} +} diff --git a/src/java.base/share/classes/sun/security/ssl/DHasKEM.java b/src/java.base/share/classes/sun/security/ssl/DHasKEM.java index ef5c5b82f060..9f860af101e0 100644 --- a/src/java.base/share/classes/sun/security/ssl/DHasKEM.java +++ b/src/java.base/share/classes/sun/security/ssl/DHasKEM.java @@ -268,17 +268,17 @@ private SecretKey DH(String alg, PrivateKey skE, PublicKey pkR) // RFC 8446 section 7.4.2: checks for all-zero // X25519/X448 shared secret. - if (kaAlgorithm.equals("X25519") || - kaAlgorithm.equals("X448")) { + if (this == X25519 || this == X448) { byte[] s = secret.getEncoded(); + byte data = 0; for (byte b : s) { - if (b != 0) { - return secret; - } + data |= b; + } + if (data == 0) { + // Trigger ILLEGAL_PARAMETER alert + throw new IllegalArgumentException( + "All-zero shared secret"); } - // Trigger ILLEGAL_PARAMETER alert - throw new IllegalArgumentException( - "All-zero shared secret"); } return secret; diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/CommandOutputControl.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/CommandOutputControl.java index 000db1d8a106..f818e5d966e4 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/CommandOutputControl.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/CommandOutputControl.java @@ -1555,7 +1555,9 @@ private record CachingPrintStream(PrintStream ps, Optional bufferContents() { - return buf.map(ByteArrayOutputStream::toString); + return buf.map(in -> { + return in.toString(ps.charset()); + }); } static Builder build(Charset charset) { @@ -1600,7 +1602,7 @@ CachingPrintStream create() { final PrintStream ps; if (buf.isPresent() && dumpStream != null) { - ps = new PrintStream(new TeeOutputStream(List.of(buf.get(), dumpStream)), true, dumpStream.charset()); + ps = new PrintStream(new TeeOutputStream(List.of(buf.get(), dumpStream)), true, charset); } else if (!discard) { ps = buf.map(in -> { return new PrintStream(in, false, charset); diff --git a/test/hotspot/jtreg/ProblemList-Xcomp.txt b/test/hotspot/jtreg/ProblemList-Xcomp.txt index f9a4d5ec2a97..c6b006186179 100644 --- a/test/hotspot/jtreg/ProblemList-Xcomp.txt +++ b/test/hotspot/jtreg/ProblemList-Xcomp.txt @@ -33,8 +33,6 @@ vmTestbase/vm/mlvm/mixed/stress/regression/b6969574/INDIFY_Test.java 8265295 lin serviceability/AsyncGetCallTrace/MyPackage/ASGCTBaseTest.java 8303168 linux-all -serviceability/sa/ClhsdbInspect.java 8283578 windows-x64 - vmTestbase/vm/mlvm/indy/func/jvmti/mergeCP_indy2manyDiff_a/TestDescription.java 8308367 generic-all vmTestbase/vm/mlvm/indy/func/jvmti/mergeCP_indy2manySame_a/TestDescription.java 8308367 generic-all vmTestbase/vm/mlvm/indy/func/jvmti/mergeCP_indy2none_b/TestDescription.java 8308367 generic-all diff --git a/test/hotspot/jtreg/compiler/loopopts/TestHasTruncationWrap.java b/test/hotspot/jtreg/compiler/loopopts/TestHasTruncationWrap.java new file mode 100644 index 000000000000..efc3831dc3a2 --- /dev/null +++ b/test/hotspot/jtreg/compiler/loopopts/TestHasTruncationWrap.java @@ -0,0 +1,1117 @@ +/* + * 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 id=vanilla + * @bug 8385855 + * @summary Test CountedLoopConverter::has_truncation_wrap logic that checks if + * a truncated iv (e.g. byte or char iv) is still a valid counted loop. + * @library /test/lib / + * @run main ${test.main.class} + */ + +/* + * @test id=Xcomp + * @bug 8385855 + * @library /test/lib / + * @run main ${test.main.class} -Xcomp -XX:-TieredCompilation -XX:CompileCommand=compileonly,${test.main.class}::test* + */ + +package compiler.loopopts; + +import compiler.lib.ir_framework.*; + +/** + * Tests for CountedLoopConverter::has_truncation_wrap, which deals with wrapped iv, for byte/char/short iv cases. + * We have some regression tests for JDK-8385855, as well as some IR tests that ensure that we detect counted + * loops in many cases, where we have to check that truncation does not lead to wrapping, which would mean + * the iv would not be linear, but possibly overflow the byte/char/short ranges. + * + * Note: the optimization around CountedLoopConverter::has_truncation_wrap is a bit fragile, and depends on + * the exact loop shape, and if peeling happens or not, etc. The goal of this test is not to prove that we + * recognize all truncated cases where one could in theory prove there is no wrap/overflow, but simply to + * list some examples of today's state, so we don't get further regressions in the future. + */ +public class TestHasTruncationWrap { + + public static void main(String[] args) { + TestFramework framework = new TestFramework(); + framework.addFlags(args); + framework.start(); + } + + // ------------------------- Failing cases for JDK-8385855 ------------------------------ + + // Test shape first reported in JDK-8385855, led to assert in JDK27: + // assert(cmp->Opcode() == Op_CmpI) failed: signed comparison required + public static int test0_start = 0; + public static int test0_stop = 100; + public static int[] test0_array = new int[100]; + + @Test + public static void test0() { + int start = test0_start; + int stop = test0_stop; + int[] array = test0_array; + + stop = (stop << 16) >> 16; + int v = array[start]; // dominating CmpU detected by filtered_int_type + for (int i = start; i < stop;) { + i++; + i = (i << 16) >> 16; // iv truncation + } + } + + // A second reproducer from JDK-8385855, leads to wrong result since JDK18 (JDK-8276162). + // We make use of the CmpU via Integer.compareUnsigned, introduced by JDK-8276162. + public static int test1_gold0 = 32767; // test1(-2); + public static int test1_gold1 = 3; // test1(2); + + @Run(test = "test1") + private static void run1() { + int val0 = test1(-2); + int val1 = test1( 2); + if (val0 != test1_gold0) { throw new RuntimeException("wrong value test(-2): " + test1_gold0 + " vs " + val0); } + if (val1 != test1_gold1) { throw new RuntimeException("wrong value test( 2): " + test1_gold1 + " vs " + val1); } + } + + @Test + private static int test1(int start) { + // CmpU Condition: start =u 2 + // But filtered_int_type mistakes it as a CmpI. + // Bad CmpU assumption: start >= 2 + + int i = start; + while (i < 3) { + // While condition: i <= 2 + + // char-truncation of iv: has_truncation_wrap + // We try to see if the char-truncation can be removed. + // + // Computing loop entry type: + // While condition: i <= 2 + // Bad assumption from CmpU: start >= 2 + // -> entry type i = 2 + // + // Together with the backedge type, we get the complete phi type: + // i in [1..2] + // + // The truncation below would be a no-op for input ranges [0 .. 32767]. + // Since [1..2] is a subrange: remove truncation! + // + // But: the correct CmpU assumption would only be: + // start >=u 2 + // And that allows almost all values (except 0 and 1), in particular + // it allows the whole negative int range. + // And the while condition also allows all negative ints. + // And for negative ints, the truncation is NOT a no-op. + i = (i + 1) & 0x7fff; + + // Continuing after the backedge would mean: + // i >= 1 + // Together with while condition: + // i <= 2 + // We get a backedge type: + // i in [1..2] + if (i < 1) { + break; + } + } + return i; + } + + // A third reproducer from JDK-8385855, leads to wrong result since 6u. + // We make use of the CmpU in the RangeCheck of an array access. + // To flip the condition, we just use a try/catch. + public static final int[] test2_A = new int[2]; + public static int test2_gold0 = 32767; // test2(-2); + public static int test2_gold1 = 3; // test2(2); + + @Run(test = "test2") + private static void run2() { + int val0 = test2(-2); + int val1 = test2( 2); + if (val0 != test2_gold0) { throw new RuntimeException("wrong value test(-2): " + test2_gold0 + " vs " + val0); } + if (val1 != test2_gold1) { throw new RuntimeException("wrong value test( 2): " + test2_gold1 + " vs " + val1); } + } + + @Test + static int test2(int start) { + try { + // CmpU Condition: start =u A.length = 2 + int i = start; + while (i < 3) { + // Truncating induction-variable update. + i = (i + 1) & 0x7fff; + if (i < 1) { + break; + } + } + return i; + } + } + + // ---- More general tests, Checking that truncated iv loops become CountedLoops --------- + + @DontInline + public static int opaqueSum(int i) { + return i + 1; + } + + @DontInline + public static int opaqueSum(int i, int j) { + return i + j + 1; + } + + public static int lo = 11; + public static int hi = 33; + + // testIRShort0: just a regular int loop + public static int testIRShort0_gold = testIRShort0(); + + @Run(test = "testIRShort0") + private static void runIRShort0() { + int val = testIRShort0(); + if (val != testIRShort0_gold) { throw new RuntimeException("wrong value: " + testIRShort0_gold + " vs " + val); } + } + + @Test + @IR(counts = {IRNode.COUNTED_LOOP, "> 0"}) + static int testIRShort0() { + int init = lo; + int limit = hi; + int sum = 0; + for (int i = init; i < limit; i++) { + sum = opaqueSum(sum); + } + return sum; + } + + // testIRShort0b: just a regular int loop, but with NEQ exit check. + public static int testIRShort0b_gold = testIRShort0b(); + + @Run(test = "testIRShort0b") + private static void runIRShort0b() { + int val = testIRShort0b(); + if (val != testIRShort0b_gold) { throw new RuntimeException("wrong value: " + testIRShort0b_gold + " vs " + val); } + } + + @Test + @IR(counts = {IRNode.COUNTED_LOOP, "> 0"}) + static int testIRShort0b() { + int init = lo; + int limit = hi; + int sum = 0; + for (int i = init; i != limit; i++) { + sum = opaqueSum(sum); + } + return sum; + } + + // testIRShort1: short loop, but values are trivially in short range. + public static int testIRShort1_gold = testIRShort1(); + + @Run(test = "testIRShort1") + private static void runIRShort1() { + int val = testIRShort1(); + if (val != testIRShort1_gold) { throw new RuntimeException("wrong value: " + testIRShort1_gold + " vs " + val); } + } + + @Test + @IR(counts = {IRNode.COUNTED_LOOP, "> 0"}) + static int testIRShort1() { + short init = (short)lo; + short limit = (short)hi; + int sum = 0; + for (short i = init; i < limit; i++) { + sum = opaqueSum(sum); // work to keep loop alive + } + return sum; + } + + // testIRShort1b: short loop, but values are trivially in short range. Decrement iv. + public static int testIRShort1b_gold = testIRShort1b(); + + @Run(test = "testIRShort1b") + private static void runIRShort1b() { + int val = testIRShort1b(); + if (val != testIRShort1b_gold) { throw new RuntimeException("wrong value: " + testIRShort1b_gold + " vs " + val); } + } + + @Test + @IR(counts = {IRNode.COUNTED_LOOP, "> 0"}) + static int testIRShort1b() { + short init = (short)hi; + short limit = (short)lo; + int sum = 0; + for (short i = init; i > limit; i--) { + sum = opaqueSum(sum); // work to keep loop alive + } + return sum; + } + + // testIRShort1c: short loop, but values are trivially in short range. Incr by 2. + // Not safe: lo=32766+2 would wrap past short_max. + public static int testIRShort1c_gold = testIRShort1c(); + + @Run(test = "testIRShort1c") + private static void runIRShort1c() { + int val = testIRShort1c(); + if (val != testIRShort1c_gold) { throw new RuntimeException("wrong value: " + testIRShort1c_gold + " vs " + val); } + } + + @Test + @IR(counts = {IRNode.COUNTED_LOOP, "= 0"}) + static int testIRShort1c() { + short init = (short)lo; + short limit = (short)hi; + int sum = 0; + for (short i = init; i < limit; i+=2) { + sum = opaqueSum(sum); // work to keep loop alive + } + return sum; + } + + // testIRShort1d: short loop, but values are trivially in short range. Decrement iv by 2. + // Not safe: lo=-32767-2 would wrap past short_min. + public static int testIRShort1d_gold = testIRShort1d(); + + @Run(test = "testIRShort1d") + private static void runIRShort1d() { + int val = testIRShort1d(); + if (val != testIRShort1d_gold) { throw new RuntimeException("wrong value: " + testIRShort1d_gold + " vs " + val); } + } + + @Test + @IR(counts = {IRNode.COUNTED_LOOP, "= 0"}) + static int testIRShort1d() { + short init = (short)hi; + short limit = (short)lo; + int sum = 0; + for (short i = init; i > limit; i-=2) { + sum = opaqueSum(sum); // work to keep loop alive + } + return sum; + } + + // testIRShort2: short loop, ranges proved in short range via CmpI before loop. + public static int testIRShort2_gold = testIRShort2(); + + @Run(test = "testIRShort2") + private static void runIRShort2() { + int val = testIRShort2(); + if (val != testIRShort2_gold) { throw new RuntimeException("wrong value: " + testIRShort2_gold + " vs " + val); } + } + + @Test + @IR(counts = {IRNode.COUNTED_LOOP, "> 0"}) + static int testIRShort2() { + int init = Math.max(lo, 0); // init in [0..max_int] + int limit = Math.min(hi, 100); // limit in [min_int..100] + if (init >= limit) { return -1; } // CmpI before loop + // -> init < limit <= 100 + // -> filtered_int_type return [min_int..99] + // -> and intersected with its previous type [0..max_int] + // we get init in [0..99], which is in short range. + int sum = 0; + for (int i = init; i < limit; i = (short)(i+1)) { + sum = opaqueSum(sum); // work to keep loop alive + // The backedge value of i is also far + // enough from short boundaries, because of + // the loop exit check: + // i < limit <= 100 + } + return sum; + } + + // testIRShort2b: short loop, ranges proved in short range via CmpI before loop. + // Compared to testIRShort2, the check in the loop is an NEQ. + public static int testIRShort2b_gold = testIRShort2b(); + + @Run(test = "testIRShort2b") + private static void runIRShort2b() { + int val = testIRShort2b(); + if (val != testIRShort2b_gold) { throw new RuntimeException("wrong value: " + testIRShort2b_gold + " vs " + val); } + } + + @Test + @IR(counts = {IRNode.COUNTED_LOOP, "> 0"}) + static int testIRShort2b() { + int init = Math.max(lo, 0); // init in [0..max_int] + int limit = Math.min(hi, 100); // limit in [min_int..100] + if (init >= limit) { return -1; } // CmpI before loop + // -> init < limit <= 100 + // -> filtered_int_type return [min_int..99] + // -> and intersected with its previous type [0..max_int] + // we get init in [0..99], which is in short range. + int sum = 0; + for (int i = init; i != limit; i = (short)(i+1)) { + sum = opaqueSum(sum); // work to keep loop alive + // The backedge value of i is also far + // enough from short boundaries, because of + // the loop exit check: + // i < limit <= 100 + } + return sum; + } + + // testIRShort3: short loop, and range in short range via CmpI before loop (for loop limit). + public static int testIRShort3_gold = testIRShort3(); + + @Run(test = "testIRShort3") + private static void runIRShort3() { + int val = testIRShort3(); + if (val != testIRShort3_gold) { throw new RuntimeException("wrong value: " + testIRShort3_gold + " vs " + val); } + } + + @Test + @IR(counts = {IRNode.COUNTED_LOOP, "> 0"}) + static int testIRShort3() { + int init = Math.max(lo, 0); // init in [0..max_int] + int limit = Math.min(hi, 100); // limit in [min_int..100] + int sum = 0; + // While there is no explicit CmpI before the loop, we + // actually have "i < limit" in the for loop check, which + // is also checked before entering the loop. + // So also here, we have: + // -> init < limit <= 100 + // -> filtered_int_type return [min_int..99] + // -> and intersected with its previous type [0..max_int] + // we get init in [0..99], which is in short range. + for (int i = init; i < limit; i = (short)(i+1)) { + sum = opaqueSum(sum); // work to keep loop alive + } + return sum; + } + + // testIRShort3b: short loop, and range in short range via CmpI before loop (for loop limit). + // Decr iv. + // Missed optimization opportunity: + // CountedLoopConverter::LoopStructure::is_infinite_loop + // It wrongly fires, and prevents CountedLoop detection. + // This check is increment-specific, and fails to acocunt for decrement: + // if (limit_t->hi_as_long() > incr_t->hi_as_long()) { + // I don't think this is intentional, because we have handling for positive and + // negative stride in CountedLoopConverter::has_truncation_wrap. + public static int testIRShort3b_gold = testIRShort3b(); + + @Run(test = "testIRShort3b") + private static void runIRShort3b() { + int val = testIRShort3b(); + if (val != testIRShort3b_gold) { throw new RuntimeException("wrong value: " + testIRShort3b_gold + " vs " + val); } + } + + @Test + @IR(counts = {IRNode.COUNTED_LOOP, "= 0"}) + static int testIRShort3b() { + int limit = Math.max(lo, 0); // limit in [0..max_int] + int init = Math.min(hi, 100); // init in [min_int..100] + int sum = 0; + for (int i = init; i > limit; i = (short)(i-1)) { + sum = opaqueSum(sum); // work to keep loop alive + } + return sum; + } + + // testIRShort3x: short loop, fails to be recognized as CountedLoop. + // Compared to testIRShort3, the check in the loop is an NEQ. + public static int testIRShort3x_gold = testIRShort3x(); + + @Run(test = "testIRShort3x") + private static void runIRShort3x() { + int val = testIRShort3x(); + if (val != testIRShort3x_gold) { throw new RuntimeException("wrong value: " + testIRShort3x_gold + " vs " + val); } + } + + @Test + @IR(counts = {IRNode.COUNTED_LOOP, "= 0"}) + static int testIRShort3x() { + int init = Math.max(lo, 0); // init in [0..max_int] + int limit = Math.min(hi, 100); // limit in [min_int..100] + int sum = 0; + // No useful CmpI before the loop. + // And the CmpI of the for limit is NEQ, so not useful either. + for (int i = init; i != limit; i = (short)(i+1)) { + sum = opaqueSum(sum); // work to keep loop alive + } + return sum; + } + + // testIRShort4: short loop, with a CmpI, but the limit ranges are bad. + public static int testIRShort4_gold = testIRShort4(); + + @Run(test = "testIRShort4") + private static void runIRShort4() { + int val = testIRShort4(); + if (val != testIRShort4_gold) { throw new RuntimeException("wrong value: " + testIRShort4_gold + " vs " + val); } + } + + @Test + @IR(counts = {IRNode.COUNTED_LOOP, "= 0"}) + static int testIRShort4() { + int init = Math.max(lo, 0); // init in [0..max_int] + int limit = Math.min(hi, 100_000); // limit in [min_int..100_000] + int sum = 0; + // Now, the check is not good enough: + // -> init < limit <= 100_000 + // -> filtered_int_type return [min_int..99_999] + // -> and intersected with its previous type [0..max_int] + // we get init in [0..99_999], which is NOT in short range. + for (int i = init; i < limit; i = (short)(i+1)) { + sum = opaqueSum(sum); // work to keep loop alive + // Also: the backedge range is not good because + // the exit check is not strong enough for short: + // i < limit <= 100_000 + } + return sum; + } + + // testIRShort5: short do-while-loop, and range in short range via CmpI before loop (for loop limit). + public static int testIRShort5_gold = testIRShort5(); + + @Run(test = "testIRShort5") + private static void runIRShort5() { + int val = testIRShort5(); + if (val != testIRShort5_gold) { throw new RuntimeException("wrong value: " + testIRShort5_gold + " vs " + val); } + } + + @Test + @IR(counts = {IRNode.COUNTED_LOOP, "> 0"}) + static int testIRShort5() { + int init = Math.max(lo, 0); // init in [0..max_int] + int limit = Math.min(hi, 100); // limit in [min_int..100] + if (init >= limit) { return -1; } // CmpI before loop + // -> init < limit <= 100 + // -> filtered_int_type return [min_int..99] + // -> and intersected with its previous type [0..max_int] + // we get init in [0..99], which is in short range. + int sum = 0; + int i = init; + do { + sum = opaqueSum(sum); // work to keep loop alive + i = (short)(i+1); + } while (i < limit); // exit check at the end. + return sum; + } + + // testIRShort5b: short do-while-loop, but the backedge check with NEQ is not strong enough to prevent wrapping. + // Compared to testIRShort5, the check in the loop is an NEQ. + public static int testIRShort5b_gold = testIRShort5b(); + + @Run(test = "testIRShort5b") + private static void runIRShort5b() { + int val = testIRShort5b(); + if (val != testIRShort5b_gold) { throw new RuntimeException("wrong value: " + testIRShort5b_gold + " vs " + val); } + } + + @Test + @IR(counts = {IRNode.COUNTED_LOOP, "= 0"}) + static int testIRShort5b() { + int init = Math.max(lo, 0); // init in [0..max_int] + int limit = Math.min(hi, 100); // limit in [min_int..100] + if (init >= limit) { return -1; } // CmpI before loop + // -> init < limit <= 100 + // -> filtered_int_type return [min_int..99] + // -> and intersected with its previous type [0..max_int] + // we get init in [0..99], which is in short range. + int sum = 0; + int i = init; + do { + sum = opaqueSum(sum); // work to keep loop alive + i = (short)(i+1); + } while (i != limit); // exit check at the end, but with NEQ. + return sum; + } + + // testIRShort5c: short do-while-loop. + // While the code shape looks very close to testIRShort2b, it does not behave the same. + // The while loop below is peeled once. The additional "exit check" is eliminated, + // because redundant after "init >= limit" check. + // From peeling, the new initial value is a truncated short value, and not init, so + // the "init >= limit" check is not helpful any more, as far as I can see. + // Also the backedge value is truncated to short value. But this is not enough to + // guarantee that there is no short-overflow (wrap): we do not manage to + // prove that i could never be short_max, and then overflow the short range at + // the next increment. + public static int testIRShort5c_gold = testIRShort5c(); + + @Run(test = "testIRShort5c") + private static void runIRShort5c() { + int val = testIRShort5c(); + if (val != testIRShort5c_gold) { throw new RuntimeException("wrong value: " + testIRShort5c_gold + " vs " + val); } + } + + @Test + @IR(counts = {IRNode.COUNTED_LOOP, "= 0"}) + static int testIRShort5c() { + int init = Math.max(lo, 0); // init in [0..max_int] + int limit = Math.min(hi, 100); // limit in [min_int..100] + if (init >= limit) { return -1; } // CmpI before loop + // -> init < limit <= 100 + // -> filtered_int_type return [min_int..99] + // -> and intersected with its previous type [0..max_int] + // we get init in [0..99], which is in short range. + int sum = 0; + int i = init; + if (i == limit) { return sum; } // additional "exit check" before loop. + do { + sum = opaqueSum(sum); // work to keep loop alive + i = (short)(i+1); + } while (i != limit); // exit check at the end, but with NEQ. + return sum; + } + + // testIRShort5d: short while-loop, again similar to testIRShort2b and testIRShort5c, but with while-loop form. + // No peeling, and so the entry value is init, and so the "init >= limit" check is useful, + // and used by has_truncation_wrap. With it, C2 manages to prove no short-overflow. + public static int testIRShort5d_gold = testIRShort5d(); + + @Run(test = "testIRShort5d") + private static void runIRShort5d() { + int val = testIRShort5d(); + if (val != testIRShort5d_gold) { throw new RuntimeException("wrong value: " + testIRShort5d_gold + " vs " + val); } + } + + @Test + @IR(counts = {IRNode.COUNTED_LOOP, "> 0"}) + static int testIRShort5d() { + int init = Math.max(lo, 0); // init in [0..max_int] + int limit = Math.min(hi, 100); // limit in [min_int..100] + if (init >= limit) { return -1; } // CmpI before loop + // -> init < limit <= 100 + // -> filtered_int_type return [min_int..99] + // -> and intersected with its previous type [0..max_int] + // we get init in [0..99], which is in short range. + int sum = 0; + int i = init; + while (i != limit) { + sum = opaqueSum(sum); // work to keep loop alive + i = (short)(i+1); + } + return sum; + } + + // testIRShort6: short do-while-loop, missing the CmpI before the loop. + public static int testIRShort6_gold = testIRShort6(); + + @Run(test = "testIRShort6") + private static void runIRShort6() { + int val = testIRShort6(); + if (val != testIRShort6_gold) { throw new RuntimeException("wrong value: " + testIRShort6_gold + " vs " + val); } + } + + @Test + @IR(counts = {IRNode.COUNTED_LOOP, "> 0"}) + static int testIRShort6() { + int init = Math.max(lo, 0); // init in [0..max_int] + int limit = Math.min(hi, 100); // limit in [min_int..100] + // No CmpI before the loop! + // But the loop exit check is strong enough to ignore truncation. + int sum = 0; + int i = init; + do { + sum = opaqueSum(sum); // work to keep loop alive + i = (short)(i+1); + } while (i < limit); // exit check at the end. + return sum; + } + + // testIRShort6b: short do-while-loop, missing the CmpI before the loop. + // Compared to testIRShort6, the check in the loop is an NEQ. + public static int testIRShort6b_gold = testIRShort6b(); + + @Run(test = "testIRShort6b") + private static void runIRShort6b() { + int val = testIRShort6b(); + if (val != testIRShort6b_gold) { throw new RuntimeException("wrong value: " + testIRShort6b_gold + " vs " + val); } + } + + @Test + @IR(counts = {IRNode.COUNTED_LOOP, "= 0"}) + static int testIRShort6b() { + int init = Math.max(lo, 0); // init in [0..max_int] + int limit = Math.min(hi, 100); // limit in [min_int..100] + // No CmpI before the loop! + // And the loop exit check is NOT strong enough to ignore truncation. + int sum = 0; + int i = init; + do { + sum = opaqueSum(sum); // work to keep loop alive + i = (short)(i+1); + } while (i != limit); // exit check at the end. + return sum; + } + + public static int opaqueCounter; + + @DontInline + public static void opaqueReset() { + opaqueCounter = 0; + } + + @DontInline + public static boolean opaqueCheck() { + return (opaqueCounter++) >= 100_000; + } + + // testIRShort7: with additional opaque exit check. + // Useful to verify that TestTruncationWrapFuzzer.java opaque exit checks + // do not prohibit CountedLoop detection. + // We start from testIRShort3 and testIRShort4, but add the additional opaque exit. + public static int testIRShort7_gold = testIRShort7(); + + @Run(test = "testIRShort7") + private static void runIRShort7() { + int val = testIRShort7(); + if (val != testIRShort7_gold) { throw new RuntimeException("wrong value: " + testIRShort7_gold + " vs " + val); } + } + + @Test + @IR(counts = {IRNode.COUNTED_LOOP, "> 0"}) + static int testIRShort7() { + opaqueReset(); + int init = Math.max(lo, 0); + int limit = Math.min(hi, 100); // good bounds + int sum = 0; + for (int i = init; i < limit; i = (short)(i+1)) { + sum = opaqueSum(sum, i); + if (opaqueCheck()) { break; } + } + return sum; + } + + // testIRShort7b + public static int testIRShort7b_gold = testIRShort7b(); + + @Run(test = "testIRShort7b") + private static void runIRShort7b() { + int val = testIRShort7b(); + if (val != testIRShort7b_gold) { throw new RuntimeException("wrong value: " + testIRShort7b_gold + " vs " + val); } + } + + @Test + @IR(counts = {IRNode.COUNTED_LOOP, "= 0"}) + static int testIRShort7b() { + opaqueReset(); + int init = Math.max(lo, 0); + int limit = Math.min(hi, 100_000); // bad bounds + int sum = 0; + for (int i = init; i < limit; i = (short)(i+1)) { + sum = opaqueSum(sum, i); + if (opaqueCheck()) { break; } + } + return sum; + } + + // testIRByte1: byte loop, but values are trivially in byte range. + // But: "byte i++" goes through "<< 24 >> 24" truncation with signed extension, + // and that's not recognized by TruncatedIncrement::build. + public static int testIRByte1_gold = testIRByte1(); + + @Run(test = "testIRByte1") + private static void runIRByte1() { + int val = testIRByte1(); + if (val != testIRByte1_gold) { throw new RuntimeException("wrong value: " + testIRByte1_gold + " vs " + val); } + } + + @Test + @IR(counts = {IRNode.COUNTED_LOOP, "= 0"}) + static int testIRByte1() { + byte init = (byte)lo; + byte limit = (byte)hi; + int sum = 0; + for (byte i = init; i < limit; i++) { + sum = opaqueSum(sum); // work to keep loop alive + } + return sum; + } + + // testIRByte2: byte loop, ranges proved in byte range via CmpI before loop. + // But: "byte i++" goes through "<< 24 >> 24" truncation with signed extension, + // and that's not recognized by TruncatedIncrement::build. + public static int testIRByte2_gold = testIRByte2(); + + @Run(test = "testIRByte2") + private static void runIRByte2() { + int val = testIRByte2(); + if (val != testIRByte2_gold) { throw new RuntimeException("wrong value: " + testIRByte2_gold + " vs " + val); } + } + + @Test + @IR(counts = {IRNode.COUNTED_LOOP, "= 0"}) + static int testIRByte2() { + int init = Math.max(lo, 0); // init in [0..max_int] + int limit = Math.min(hi, 100); // limit in [min_int..100] + if (init >= limit) { return -1; } // CmpI before loop + // -> init < limit <= 100 + // -> filtered_int_type return [min_int..99] + // -> and intersected with its previous type [0..max_int] + // we get init in [0..99], which is in byte range. + int sum = 0; + for (int i = init; i < limit; i = (byte)(i+1)) { + sum = opaqueSum(sum); // work to keep loop alive + // The backedge value of i is also far + // enough from byte boundaries, because of + // the loop exit check: + // i < limit <= 100 + } + return sum; + } + + // testIRByte4: byte loop, with a CmpI, but the limit ranges are bad. + // And: "byte i++" goes through "<< 24 >> 24" truncation with signed extension, + // and that's not recognized by TruncatedIncrement::build. + public static int testIRByte4_gold = testIRByte4(); + + @Run(test = "testIRByte4") + private static void runIRByte4() { + int val = testIRByte4(); + if (val != testIRByte4_gold) { throw new RuntimeException("wrong value: " + testIRByte4_gold + " vs " + val); } + } + + @Test + @IR(counts = {IRNode.COUNTED_LOOP, "= 0"}) + static int testIRByte4() { + int init = Math.max(lo, 0); // init in [0..max_int] + int limit = Math.min(hi, 1_000); // limit in [min_int..1_000] + int sum = 0; + // Now, the check is not good enough: + // -> init < limit <= 1_000 + // -> filtered_int_type return [min_int..999] + // -> and intersected with its previous type [0..max_int] + // we get init in [0..999], which is NOT in byte range. + for (int i = init; i < limit; i = (byte)(i+1)) { + sum = opaqueSum(sum); // work to keep loop alive + // Also: the backedge range is not good because + // the exit check is not strong enough for byte: + // i < limit <= 1_000 + } + return sum; + } + + // testIRChar1: char loop, but values are trivially in char range. + // But: "char i++" lowers through mask "& 0xffff", not recognized by TruncatedIncrement::build. + public static int testIRChar1_gold = testIRChar1(); + + @Run(test = "testIRChar1") + private static void runIRChar1() { + int val = testIRChar1(); + if (val != testIRChar1_gold) { throw new RuntimeException("wrong value: " + testIRChar1_gold + " vs " + val); } + } + + @Test + @IR(counts = {IRNode.COUNTED_LOOP, "= 0"}) + static int testIRChar1() { + char init = (char)lo; + char limit = (char)hi; + int sum = 0; + for (char i = init; i < limit; i++) { + sum = opaqueSum(sum); // work to keep loop alive + } + return sum; + } + + // testIRChar2: char loop, ranges proved in char range via CmpI before loop. + // But: "char i++" lowers through mask "& 0xffff", not recognized by TruncatedIncrement::build. + public static int testIRChar2_gold = testIRChar2(); + + @Run(test = "testIRChar2") + private static void runIRChar2() { + int val = testIRChar2(); + if (val != testIRChar2_gold) { throw new RuntimeException("wrong value: " + testIRChar2_gold + " vs " + val); } + } + + @Test + @IR(counts = {IRNode.COUNTED_LOOP, "= 0"}) + static int testIRChar2() { + int init = Math.max(lo, 0); // init in [0..max_int] + int limit = Math.min(hi, 100); // limit in [min_int..100] + if (init >= limit) { return -1; } // CmpI before loop + // -> init < limit <= 100 + // -> filtered_int_type return [min_int..99] + // -> and intersected with its previous type [0..max_int] + // we get init in [0..99], which is in char range. + int sum = 0; + for (int i = init; i < limit; i = (char)(i+1)) { + sum = opaqueSum(sum); // work to keep loop alive + // The backedge value of i is also far + // enough from char boundaries, because of + // the loop exit check: + // i < limit <= 100 + } + return sum; + } + + // testIRChar3: char loop, and range in char range via CmpI before loop (for loop limit). + // But: "char i++" lowers through mask "& 0xffff", not recognized by TruncatedIncrement::build. + public static int testIRChar3_gold = testIRChar3(); + + @Run(test = "testIRChar3") + private static void runIRChar3() { + int val = testIRChar3(); + if (val != testIRChar3_gold) { throw new RuntimeException("wrong value: " + testIRChar3_gold + " vs " + val); } + } + + @Test + @IR(counts = {IRNode.COUNTED_LOOP, "= 0"}) + static int testIRChar3() { + int init = Math.max(lo, 0); // init in [0..max_int] + int limit = Math.min(hi, 100); // limit in [min_int..100] + int sum = 0; + // While there is no explicit CmpI before the loop, we + // actually have "i < limit" in the for loop check, which + // is also checked before entering the loop. + // So also here, we have: + // -> init < limit <= 100 + // -> filtered_int_type return [min_int..99] + // -> and intersected with its previous type [0..max_int] + // we get init in [0..99], which is in char range. + for (int i = init; i < limit; i = (char)(i+1)) { + sum = opaqueSum(sum); // work to keep loop alive + } + return sum; + } + + // testIRChar3Mask: char loop, and range in char range via CmpI before loop (for loop limit). + public static int testIRChar3Mask_gold = testIRChar3Mask(); + + @Run(test = "testIRChar3Mask") + private static void runIRChar3Mask() { + int val = testIRChar3Mask(); + if (val != testIRChar3Mask_gold) { throw new RuntimeException("wrong value: " + testIRChar3Mask_gold + " vs " + val); } + } + + @Test + @IR(counts = {IRNode.COUNTED_LOOP, "> 0"}) + static int testIRChar3Mask() { + int init = Math.max(lo, 0); // init in [0..max_int] + int limit = Math.min(hi, 100); // limit in [min_int..100] + int sum = 0; + // While there is no explicit CmpI before the loop, we + // actually have "i < limit" in the for loop check, which + // is also checked before entering the loop. + // So also here, we have: + // -> init < limit <= 100 + // -> filtered_int_type return [min_int..99] + // -> and intersected with its previous type [0..max_int] + // we get init in [0..99], which is in char range. + for (int i = init; i < limit; i = (i+1) & 0x7fff) { // mask instead of cast + sum = opaqueSum(sum); // work to keep loop alive + } + return sum; + } + + // testIRChar4: char loop, with a CmpI, but the limit ranges are bad. + // And: "char i++" lowers through mask "& 0xffff", not recognized by TruncatedIncrement::build. + public static int testIRChar4_gold = testIRChar4(); + + @Run(test = "testIRChar4") + private static void runIRChar4() { + int val = testIRChar4(); + if (val != testIRChar4_gold) { throw new RuntimeException("wrong value: " + testIRChar4_gold + " vs " + val); } + } + + @Test + @IR(counts = {IRNode.COUNTED_LOOP, "= 0"}) + static int testIRChar4() { + int init = Math.max(lo, 0); // init in [0..max_int] + int limit = Math.min(hi, 100_000); // limit in [min_int..100_000] + int sum = 0; + // Now, the check is not good enough: + // -> init < limit <= 100_000 + // -> filtered_int_type return [min_int..99_999] + // -> and intersected with its previous type [0..max_int] + // we get init in [0..99_999], which is NOT in char range. + for (int i = init; i < limit; i = (char)(i+1)) { + sum = opaqueSum(sum); // work to keep loop alive + // Also: the backedge range is not good because + // the exit check is not strong enough for char: + // i < limit <= 100_000 + } + return sum; + } + + // testIRChar4Mask: char loop, with a CmpI, but the limit ranges are bad. + public static int testIRChar4Mask_gold = testIRChar4Mask(); + + @Run(test = "testIRChar4Mask") + private static void runIRChar4Mask() { + int val = testIRChar4Mask(); + if (val != testIRChar4Mask_gold) { throw new RuntimeException("wrong value: " + testIRChar4Mask_gold + " vs " + val); } + } + + @Test + @IR(counts = {IRNode.COUNTED_LOOP, "= 0"}) + static int testIRChar4Mask() { + int init = Math.max(lo, 0); // init in [0..max_int] + int limit = Math.min(hi, 100_000); // limit in [min_int..100_000] + int sum = 0; + // Now, the check is not good enough: + // -> init < limit <= 100_000 + // -> filtered_int_type return [min_int..99_999] + // -> and intersected with its previous type [0..max_int] + // we get init in [0..99_999], which is NOT in char range. + for (int i = init; i < limit; i = (i+1) & 0x7fff) { // mask instead of cast + sum = opaqueSum(sum); // work to keep loop alive + // Also: the backedge range is not good because + // the exit check is not strong enough for char: + // i < limit <= 100_000 + } + return sum; + } + + // testIRShift16: short loop, and range in short range via CmpI before loop (for loop limit). + public static int testIRShift16_gold = testIRShift16(); + + @Run(test = "testIRShift16") + private static void runIRShift16() { + int val = testIRShift16(); + if (val != testIRShift16_gold) { throw new RuntimeException("wrong value: " + testIRShift16_gold + " vs " + val); } + } + + @Test + @IR(counts = {IRNode.COUNTED_LOOP, "> 0"}) + static int testIRShift16() { + int init = Math.max(lo, 0); // init in [0..max_int] + int limit = Math.min(hi, 100); // limit in [min_int..100] + int sum = 0; + // While there is no explicit CmpI before the loop, we + // actually have "i < limit" in the for loop check, which + // is also checked before entering the loop. + // So also here, we have: + // -> init < limit <= 100 + // -> filtered_int_type return [min_int..99] + // -> and intersected with its previous type [0..max_int] + // we get init in [0..99], which is in short range. + for (int i = init; i < limit; i = ((i+1) << 16) >> 16) { // explicit shift instead of cast + sum = opaqueSum(sum); // work to keep loop alive + } + return sum; + } + + // testIRShift16BadBounds: short loop, with a CmpI, but the limit ranges are bad. + public static int testIRShift16BadBounds_gold = testIRShift16BadBounds(); + + @Run(test = "testIRShift16BadBounds") + private static void runIRShift16BadBounds() { + int val = testIRShift16BadBounds(); + if (val != testIRShift16BadBounds_gold) { throw new RuntimeException("wrong value: " + testIRShift16BadBounds_gold + " vs " + val); } + } + + @Test + @IR(counts = {IRNode.COUNTED_LOOP, "= 0"}) + static int testIRShift16BadBounds() { + int init = Math.max(lo, 0); // init in [0..max_int] + int limit = Math.min(hi, 100_000); // limit in [min_int..100_000] + int sum = 0; + // Now, the check is not good enough: + // -> init < limit <= 100_000 + // -> filtered_int_type return [min_int..99_999] + // -> and intersected with its previous type [0..max_int] + // we get init in [0..99_999], which is NOT in short range. + for (int i = init; i < limit; i = ((i+1) << 16) >> 16) { // explicit shift instead of cast + sum = opaqueSum(sum); // work to keep loop alive + // Also: the backedge range is not good because + // the exit check is not strong enough for short: + // i < limit <= 100_000 + } + return sum; + } + + // testIRShift8: 24-bit loop, and range in 24-bit range via CmpI before loop (for loop limit). + // Note: this shift value is strange, we probably wanted to implement byte truncation + // with shift=24, but instead we have 24-bit signed truncation. + // Note2: this pattern would have been supported by TruncatedIncrement::build, but it gets + // modified by LShiftINode::Ideal: + // RShiftI(AddI(LShiftI(Phi, 8), 256), 8) + // The same is explicitly excluded for shift 16, to preserve short/byte idioms. + public static int testIRShift8_gold = testIRShift8(); + + @Run(test = "testIRShift8") + private static void runIRShift8() { + int val = testIRShift8(); + if (val != testIRShift8_gold) { throw new RuntimeException("wrong value: " + testIRShift8_gold + " vs " + val); } + } + + @Test + @IR(counts = {IRNode.COUNTED_LOOP, "= 0"}) + static int testIRShift8() { + int init = Math.max(lo, 0); // init in [0..max_int] + int limit = Math.min(hi, 100); // limit in [min_int..100] + int sum = 0; + // While there is no explicit CmpI before the loop, we + // actually have "i < limit" in the for loop check, which + // is also checked before entering the loop. + // So also here, we have: + // -> init < limit <= 100 + // -> filtered_int_type return [min_int..99] + // -> and intersected with its previous type [0..max_int] + // we get init in [0..99], which is in 24-bit (and byte) range. + for (int i = init; i < limit; i = ((i+1) << 8) >> 8) { // explicit shift instead of cast + sum = opaqueSum(sum); // work to keep loop alive + } + return sum; + } + + // testIRShift8BadBounds: 24-bit loop, with a CmpI, but the limit ranges are bad. + // Note: same issues as for testIRShift8. + // Note2: the range argument seems a bit strange here, but it turns out that + // TruncatedIncrement::build maps shift=8 to BYTE, which just shows that + // the implementation confused the shift=24 with shift=8. + // Since we map to BYTE, 1_000 would be out of bounds, that's why this + // is still a bad bounds example. + public static int testIRShift8BadBounds_gold = testIRShift8BadBounds(); + + @Run(test = "testIRShift8BadBounds") + private static void runIRShift8BadBounds() { + int val = testIRShift8BadBounds(); + if (val != testIRShift8BadBounds_gold) { throw new RuntimeException("wrong value: " + testIRShift8BadBounds_gold + " vs " + val); } + } + + @Test + @IR(counts = {IRNode.COUNTED_LOOP, "= 0"}) + static int testIRShift8BadBounds() { + int init = Math.max(lo, 0); // init in [0..max_int] + int limit = Math.min(hi, 1_000); // limit in [min_int..1_000] + int sum = 0; + // Now, the check is not good enough: + // -> init < limit <= 1_000 + // -> filtered_int_type return [min_int..999] + // -> and intersected with its previous type [0..max_int] + // we get init in [0..999], which is NOT in byte range. + for (int i = init; i < limit; i = ((i+1) << 8) >> 8) { // explicit shift instead of cast + sum = opaqueSum(sum); // work to keep loop alive + // Also: the backedge range is not good because + // the exit check is not strong enough for byte: + // i < limit <= 1_000 + } + return sum; + } +} diff --git a/test/hotspot/jtreg/compiler/loopopts/TestTruncationWrapEmptyType.java b/test/hotspot/jtreg/compiler/loopopts/TestTruncationWrapEmptyType.java new file mode 100644 index 000000000000..5d6d49796f2c --- /dev/null +++ b/test/hotspot/jtreg/compiler/loopopts/TestTruncationWrapEmptyType.java @@ -0,0 +1,65 @@ +/* + * 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.loopopts; + +/* + * @test + * @bug 8386482 + * @summary Test case for CountedLoopConverter::filtered_type_from_dominators/ + * CountedLoopConverter::has_truncation_wrap where the type becomes + * empty / top. This used to trigger the assert: + * assert(_base == Int) failed: Not an Int + * @library /test/lib / + * @run main/othervm -Xcomp + * -XX:CompileCommand=compileonly,${test.main.class}::test + * ${test.main.class} + * @run main ${test.main.class} + */ + +public class TestTruncationWrapEmptyType { + public static void main(String[] args) { + for (int i = 0; i < 10_000; i++) { + test(1); + } + } + + static int test(int init) { + if (init < 1) { + return -1; + } + // if implies: init in [1..max_int] + + int i = init; + while (i < 0) { + // while implies: init in [min_int .. 0] + // That contradicts the "if" above: empty intersection + // + // See CountedLoopConverter::filtered_type_from_dominators: + // rtn_t = rtn_t->join(if_t)->is_int() + // -> top type in join, fails "is_int". + i = (short)(i + 1); + } + return 0; + } +} diff --git a/test/jdk/java/lang/Thread/virtual/KlassInit.java b/test/jdk/java/lang/Thread/virtual/KlassInit.java index 4f11d667a2bd..067d4c5a7f44 100644 --- a/test/jdk/java/lang/Thread/virtual/KlassInit.java +++ b/test/jdk/java/lang/Thread/virtual/KlassInit.java @@ -79,7 +79,10 @@ import java.util.stream.Stream; import java.util.stream.Collectors; +import jdk.test.lib.thread.VThreadRunner; + import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.Arguments; @@ -96,6 +99,12 @@ class KlassInit { private static final CountDownLatch finishPutStatic = new CountDownLatch(1); private static final CountDownLatch finishFailedInit = new CountDownLatch(1); + @BeforeAll + static void setup() { + // need >=2 carriers for testing pinning + VThreadRunner.ensureParallelism(2); + } + /** * Test that threads blocked waiting for klass to be initialized * on invokestatic bytecode release the carrier. diff --git a/test/jdk/java/util/concurrent/tck/ForkJoinPoolTest.java b/test/jdk/java/util/concurrent/tck/ForkJoinPoolTest.java index de1caaab2d94..85d7921f2ed8 100644 --- a/test/jdk/java/util/concurrent/tck/ForkJoinPoolTest.java +++ b/test/jdk/java/util/concurrent/tck/ForkJoinPoolTest.java @@ -663,6 +663,23 @@ public void realRun() throws Exception { } } + public void testCallerInterruptedDuringSubmit() throws InterruptedException, ExecutionException { + final Thread submitter = Thread.currentThread(); + final ExecutorService p = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(p)) { + for (int i = 0; i < 512; ++i) { // Enough repetitions such that any race condition is bound to materialize + try { + p.submit(submitter::interrupt).get(); + // If we don't get an InterruptedException, then the current thread should be interrupted + assertTrue(Thread.interrupted()); + } catch (InterruptedException e) { + // If we do get an InterruptedException, then the current thread should not be interrupted + assertTrue(!Thread.interrupted()); + } + } + } + } + /** * get of submit(callable) throws ExecutionException if callable * throws exception diff --git a/test/jdk/jdk/classfile/StackMapsTest.java b/test/jdk/jdk/classfile/StackMapsTest.java index b2f945966224..7cf6234e88f6 100644 --- a/test/jdk/jdk/classfile/StackMapsTest.java +++ b/test/jdk/jdk/classfile/StackMapsTest.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 @@ -24,7 +24,7 @@ /* * @test * @summary Testing Classfile stack maps generator. - * @bug 8305990 8320222 8320618 8335475 8338623 8338661 8343436 + * @bug 8305990 8320222 8320618 8335475 8338623 8338661 8343436 8386700 * @build testdata.* * @run junit StackMapsTest */ @@ -415,4 +415,29 @@ void testDeadCodeCountersWithCustomSMTA() { assertEquals(2, code.maxLocals()); assertEquals(2, code.maxStack()); } + + @Test + void testStaleLocals() { + byte[] bytes = ClassFile.of().build(ClassDesc.of("Repro"), clb -> clb + .withMethodBody("m", MethodTypeDesc.of(CD_void, CD_int), ACC_STATIC, cob -> { + var cond = cob.newLabel(); + var back = cob.newLabel(); + var fwd = cob.newLabel(); + cob.iconst_0() + .istore(1) // stale slot 1 holding of a long incorrectly clears slot 0 in the second round + .iload(1) + .ifeq(cond) // conditional branch triggers merge of frames + .goto_(fwd) + .labelBinding(cond) + .iload(0) // invalid stack frame when slot 0 is cleared + .pop() + .labelBinding(back) + .return_() + .labelBinding(fwd) + .lconst_0() + .lstore(0) // long overrides slots 0 and 1 + .goto_(back); // back jump with modified locals triggers second round with stale slots + })); + assertEmpty(ClassFile.of().verify(bytes)); + } } diff --git a/test/jdk/sun/security/internal/CheckIBE.java b/test/jdk/sun/security/internal/CheckIBE.java new file mode 100644 index 000000000000..802cd336ebc3 --- /dev/null +++ b/test/jdk/sun/security/internal/CheckIBE.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8383608 + * @summary check that InternalBinaryEncodable exists + * @enablePreview + * @modules java.base/sun.security.internal + * @run main CheckIBE + */ + +import javax.crypto.EncryptedPrivateKeyInfo; +import java.security.AsymmetricKey; +import java.security.BinaryEncodable; +import java.security.KeyPair; +import java.security.PEM; +import java.security.cert.X509CRL; +import java.security.cert.X509Certificate; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; + +import sun.security.internal.InternalBinaryEncodable; + +/* + * This test verifies that BinaryEncodable has the expected set of permitted + * subtypes, including InternalBinaryEncodable. If this switch stops compiling, + * update the cases to match the BinaryEncodable permits list. + */ + +public class CheckIBE { + public static void main(String[] args) { + BinaryEncodable be = new PEM("TEST", "TEST"); + + switch (be) { + case AsymmetricKey ignored -> {} + case KeyPair ignored -> {} + case PKCS8EncodedKeySpec ignored -> {} + case X509EncodedKeySpec ignored -> {} + case EncryptedPrivateKeyInfo ignored -> {} + case X509Certificate ignored -> {} + case X509CRL ignored -> {} + case PEM ignored -> {} + case InternalBinaryEncodable ignored -> {} + } + } +} diff --git a/test/jdk/sun/security/internal/ExhaustiveBE.java b/test/jdk/sun/security/internal/ExhaustiveBE.java new file mode 100644 index 000000000000..37222a0310ab --- /dev/null +++ b/test/jdk/sun/security/internal/ExhaustiveBE.java @@ -0,0 +1,65 @@ +/* + * 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 8383608 + * @summary verify switches over BinaryEncodable are not exhaustive + * @enablePreview + * @compile/fail ExhaustiveBE.java + */ + +import javax.crypto.EncryptedPrivateKeyInfo; +import java.security.AsymmetricKey; +import java.security.BinaryEncodable; +import java.security.KeyPair; +import java.security.PEM; +import java.security.cert.X509CRL; +import java.security.cert.X509Certificate; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; + +/* + * This test verifies that application code cannot exhaustively switch over + * BinaryEncodable by naming only the public permitted subtypes. Compilation + * must fail because application code needs a default case, or a + * BinaryEncodable case, to cover the internal permitted subtype + * InternalBinaryEncodable. + */ + +public class ExhaustiveBE { + public static void main(String[] args) { + BinaryEncodable be = new PEM("TEST", "TEST"); + + switch (be) { + case AsymmetricKey ignored -> {} + case KeyPair ignored -> {} + case PKCS8EncodedKeySpec ignored -> {} + case X509EncodedKeySpec ignored -> {} + case EncryptedPrivateKeyInfo ignored -> {} + case X509Certificate ignored -> {} + case X509CRL ignored -> {} + case PEM ignored -> {} + } + } +} diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java index 639bd295e57f..2dc2351be3c5 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java @@ -27,7 +27,9 @@ import java.io.IOException; import java.io.UncheckedIOException; +import java.lang.ref.Reference; import java.lang.ref.SoftReference; +import java.lang.ref.WeakReference; import java.lang.reflect.Method; import java.nio.file.Files; import java.nio.file.Path; @@ -35,11 +37,13 @@ import java.util.Collection; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; import java.util.Optional; import java.util.Properties; +import java.util.ResourceBundle; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Stream; @@ -259,9 +263,19 @@ public String buildTool() { } public static WixType getWixTypeFromVerboseJPackageOutput(Executor.Result result) { + return getWixTypeFromVerboseJPackageOutput(Locale.getDefault(), result); + } + + public static WixType getWixTypeFromVerboseJPackageOutput(Locale resultLocale, Executor.Result result) { - var summaryWixVersion = JPackageStringBundle.MAIN.cannedFormattedString( - "summary.property.win-wix-version").getValue() + ": "; + final String summaryWixVersion; + if (resultLocale.equals(Locale.getDefault())) { + summaryWixVersion = JPackageStringBundle.MAIN.cannedFormattedString( + "summary.property.win-wix-version").getValue() + ": "; + } else { + summaryWixVersion = JPackageResourceBundleCache.INSTANCE.get(resultLocale).getString( + "summary.property.win-wix-version") + ": "; + } return result.stdout().stream().filter(str -> { return str.startsWith(summaryWixVersion); @@ -646,6 +660,26 @@ private record MsiDatabaseWithTimestamp(MsiDatabase db, Instant timestamp) { } + private static final class JPackageResourceBundleCache { + + ResourceBundle get(Locale locale) { + synchronized (items) { + var value = Optional.ofNullable(items.get(locale)).map(Reference::get).orElse(null); + if (value == null) { + value = ResourceBundle.getBundle("jdk.jpackage.internal.resources.WinResources", + locale, ModuleLayer.boot().findModule("jdk.jpackage").orElseThrow()); + items.put(locale, new WeakReference<>(value)); + } + return value; + } + } + + private final Map> items = new HashMap<>(); + + static final JPackageResourceBundleCache INSTANCE = new JPackageResourceBundleCache(); + } + + static final Set CRITICAL_RUNTIME_FILES = Set.of(Path.of( "bin\\server\\jvm.dll")); diff --git a/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/util/CommandOutputControlTest.java b/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/util/CommandOutputControlTest.java index 4f67909bd47e..a64d642f633f 100644 --- a/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/util/CommandOutputControlTest.java +++ b/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/util/CommandOutputControlTest.java @@ -890,15 +890,18 @@ private static List testCharset() { for (boolean toolProvider : BOOLEAN_VALUES) { for (var redirectStderr : withAndWithout(OutputControl.REDIRECT_STDERR)) { - for (var charset : withAndWithout(OutputControl.CHARSET_UTF16LE)) { - var stdoutSink = new CharsetTestSpec.DumpOutputSink(StandardCharsets.US_ASCII, OutputStreams.STDOUT); - var stderrSink = new CharsetTestSpec.DumpOutputSink(StandardCharsets.UTF_32LE, OutputStreams.STDERR); - var outputControl = new HashSet(); - redirectStderr.ifPresent(outputControl::add); - charset.ifPresent(outputControl::add); - outputControl.add(stdoutSink); - outputControl.add(stderrSink); - testCases.add(new CharsetTestSpec(toolProvider, new CommandOutputControlSpec(outputControl))); + for (var saveOutput : withAndWithout(OutputControl.SAVE_ALL)) { + for (var charset : withAndWithout(OutputControl.CHARSET_UTF16LE)) { + var stdoutSink = new CharsetTestSpec.DumpOutputSink(StandardCharsets.US_ASCII, OutputStreams.STDOUT); + var stderrSink = new CharsetTestSpec.DumpOutputSink(StandardCharsets.UTF_32LE, OutputStreams.STDERR); + var outputControl = new HashSet(); + redirectStderr.ifPresent(outputControl::add); + saveOutput.ifPresent(outputControl::add); + charset.ifPresent(outputControl::add); + outputControl.add(stdoutSink); + outputControl.add(stderrSink); + testCases.add(new CharsetTestSpec(toolProvider, new CommandOutputControlSpec(outputControl))); + } } } } @@ -1731,37 +1734,46 @@ private CommandOutputControl.Executable createExecutable(Command command) { record CharsetTestSpec(boolean toolProvider, CommandOutputControlSpec cocSpec) { - void test() throws IOException, InterruptedException { - if (cocSpec.outputControl().stream().noneMatch(DumpOutputSink.class::isInstance)) { + CharsetTestSpec(boolean toolProvider, CommandOutputControlSpec cocSpec) { + this.toolProvider = toolProvider; + this.cocSpec = Objects.requireNonNull(cocSpec); + + // Sinks must be specified for stdout and stderr streams. + if (cocSpec.outputControl().stream().filter(DumpOutputSink.class::isInstance).count() != 2) { throw new IllegalArgumentException(); } + } + + void test() throws IOException, InterruptedException { - final var expectedString = "veni-vidi-vici"; + final var writeToStdout = "veni-vidi-vici"; + final var writeToStderr = "iciv-idiv-inev"; var coc = cocSpec.create().dumpOutput(true); CommandOutputControl.Executable exec; if (toolProvider) { - var tp = Command.createToolProvider(Stream.of(expectedString).mapMulti((str, sink) -> { - sink.accept(CommandAction.echoStdout(str)); - sink.accept(CommandAction.echoStderr(str)); - }).toList()); + var tp = Command.createToolProvider(List.of( + CommandAction.echoStdout(writeToStdout), + CommandAction.echoStderr(writeToStderr) + )); exec = coc.createExecutable(tp); } else { - var cmdline = Command.createShellCommandLine(Stream.of(expectedString).map(str -> { + Function conv = str -> { return (str + System.lineSeparator()).getBytes(coc.charset()); - }).mapMulti((bytes, sink) -> { - sink.accept(CommandAction.writeStdout(bytes)); - sink.accept(CommandAction.writeStderr(bytes)); - }).toList()); + }; + var cmdline = Command.createShellCommandLine(List.of( + CommandAction.writeStdout(conv.apply(writeToStdout)), + CommandAction.writeStderr(conv.apply(writeToStderr)) + )); exec = coc.createExecutable(new ProcessBuilder(cmdline)); } - exec.execute(); + final var execResult = exec.execute(); for (var outputContolMutator : cocSpec.outputControl()) { if (outputContolMutator instanceof DumpOutputSink sink) { - var actual = sink.lines(); + var actual = sink.lines(coc); List expected; if (cocSpec.redirectStderr()) { switch (sink.streams()) { @@ -1769,13 +1781,22 @@ void test() throws IOException, InterruptedException { expected = List.of(); } default -> { - expected = List.of(expectedString, expectedString); + expected = List.of(writeToStdout, writeToStderr); } } } else { - expected = List.of(expectedString); + switch (sink.streams()) { + case STDERR -> { + expected = List.of(writeToStderr); + } + default -> { + expected = List.of(writeToStdout); + } + } } assertEquals(expected, actual); + } else if (outputContolMutator == OutputControl.SAVE_ALL) { + assertEquals(List.of(writeToStdout, writeToStderr), execResult.content()); } } @@ -1792,8 +1813,8 @@ record DumpOutputSink(Charset charset, ByteArrayOutputStream buffer, OutputStrea this(charset, new ByteArrayOutputStream(), streams); } - List lines() { - var str = buffer.toString(charset); + List lines(CommandOutputControl coc) { + var str = buffer.toString((coc.isSaveOutput() || coc.isSaveFirstLineOfOutput()) ? coc.charset() : charset); return new BufferedReader(new StringReader(str)).lines().toList(); } diff --git a/test/jdk/tools/jpackage/windows/WinL10nTest.java b/test/jdk/tools/jpackage/windows/WinL10nTest.java index d63057adc446..6075b30a3b24 100644 --- a/test/jdk/tools/jpackage/windows/WinL10nTest.java +++ b/test/jdk/tools/jpackage/windows/WinL10nTest.java @@ -25,8 +25,11 @@ import java.io.IOException; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.List; +import java.util.Locale; import java.util.Objects; import java.util.Optional; import java.util.function.Predicate; @@ -55,64 +58,103 @@ * --jpt-run=WinL10nTest */ -public class WinL10nTest { - - public WinL10nTest(WixFileInitializer wxlFileInitializers[], - String[] expectedCultures, String expectedErrorMessage, - String userLanguage, String userCountry, - boolean enableWixUIExtension) { - this.wxlFileInitializers = wxlFileInitializers; - this.expectedCultures = expectedCultures; - this.expectedErrorMessage = expectedErrorMessage; - this.userLanguage = userLanguage; - this.userCountry = userCountry; - this.enableWixUIExtension = enableWixUIExtension; +public record WinL10nTest( + Collection wxlFileInitializers, + Collection expectedCultures, + Optional locale, + boolean enableWixUIExtension) { + + public WinL10nTest { + Objects.requireNonNull(wxlFileInitializers); + Objects.requireNonNull(expectedCultures); + Objects.requireNonNull(locale); + } + + public WinL10nTest(Collection expectedCultures, Locale locale, boolean enableWixUIExtension) { + this(List.of(), expectedCultures, Optional.of(locale), enableWixUIExtension); + } + + public WinL10nTest(Collection wxlFileInitializers, Collection expectedCultures) { + this(wxlFileInitializers, expectedCultures, Optional.empty(), false); + } + + public WinL10nTest(WinL10nTest other) { + this(other.wxlFileInitializers, other.expectedCultures, other.locale, other.enableWixUIExtension); + } + + @Override + public String toString() { + var tokens = new ArrayList(); + if (!wxlFileInitializers.isEmpty()) { + tokens.add(String.format("wxlFileInitializers=%s", wxlFileInitializers)); + } + if (!expectedCultures.isEmpty()) { + tokens.add(String.format("expectedCultures=%s", expectedCultures)); + } + locale.ifPresent(l -> { + tokens.add(String.format("locale=%s", l)); + }); + if (enableWixUIExtension) { + tokens.add("enableWixUIExtension=true"); + } + return String.join(", ", tokens); } @Parameters public static List data() { - return List.of(new Object[][]{ - {null, new String[] {"en-us"}, null, null, null, false}, - {null, new String[] {"en-us"}, null, "en", "US", false}, - {null, new String[] {"en-us"}, null, "en", "US", true}, - {null, new String[] {"de-de"}, null, "de", "DE", false}, - {null, new String[] {"de-de"}, null, "de", "DE", true}, - {null, new String[] {"ja-jp"}, null, "ja", "JP", false}, - {null, new String[] {"ja-jp"}, null, "ja", "JP", true}, - {null, new String[] {"zh-cn"}, null, "zh", "CN", false}, - {null, new String[] {"zh-cn"}, null, "zh", "CN", true}, - {new WixFileInitializer[] { + + List testCases = new ArrayList<>(); + + testCases.add(new WinL10nTest(List.of(), List.of("en-us"), Optional.empty(), false)); + for (var enableWixUIExtension : List.of(true, false)) { + testCases.add(new WinL10nTest(List.of("en-us"), Locale.of("en", "US"), enableWixUIExtension)); + testCases.add(new WinL10nTest(List.of("de-de"), Locale.of("de", "DE"), enableWixUIExtension)); + testCases.add(new WinL10nTest(List.of("ja-jp"), Locale.of("ja", "JP"), enableWixUIExtension)); + testCases.add(new WinL10nTest(List.of("zh-cn"), Locale.of("zh", "CN"), enableWixUIExtension)); + } + + testCases.add(new WinL10nTest(List.of( WixFileInitializer.create("a.wxl", "en-us") - }, new String[] {"en-us"}, null, null, null, false}, - {new WixFileInitializer[] { + ), List.of("en-us"))); + + testCases.add(new WinL10nTest(List.of( WixFileInitializer.create("a.wxl", "fr") - }, new String[] {"fr", "en-us"}, null, null, null, false}, - {new WixFileInitializer[] { + ), List.of("fr", "en-us"))); + + testCases.add(new WinL10nTest(List.of( WixFileInitializer.create("a.wxl", "fr"), WixFileInitializer.create("b.wxl", "fr") - }, new String[] {"fr", "en-us"}, null, null, null, false}, - {new WixFileInitializer[] { + ), List.of("fr", "en-us"))); + + testCases.add(new WinL10nTest(List.of( WixFileInitializer.create("a.wxl", "it"), WixFileInitializer.create("b.wxl", "fr") - }, new String[] {"it", "fr", "en-us"}, null, null, null, false}, - {new WixFileInitializer[] { + ), List.of("it", "fr", "en-us"))); + + testCases.add(new WinL10nTest(List.of( WixFileInitializer.create("c.wxl", "it"), WixFileInitializer.create("b.wxl", "fr") - }, new String[] {"fr", "it", "en-us"}, null, null, null, false}, - {new WixFileInitializer[] { + ), List.of("fr", "it", "en-us"))); + + testCases.add(new WinL10nTest(List.of( WixFileInitializer.create("a.wxl", "fr"), WixFileInitializer.create("b.wxl", "it"), WixFileInitializer.create("c.wxl", "fr"), WixFileInitializer.create("d.wxl", "it") - }, new String[] {"fr", "it", "en-us"}, null, null, null, false}, - {new WixFileInitializer[] { + ), List.of("fr", "it", "en-us"))); + + testCases.add(new WinL10nTest(List.of( WixFileInitializer.create("c.wxl", "it"), WixFileInitializer.createMalformed("b.wxl") - }, null, null, null, null, false}, - {new WixFileInitializer[] { + ), List.of())); + + testCases.add(new WinL10nTest(List.of( WixFileInitializer.create("MsiInstallerStrings_de.wxl", "de") - }, new String[] {"en-us"}, null, null, null, false} - }); + ), List.of("en-us"))); + + return testCases.stream().map(testCase -> { + return new Object[] { testCase }; + }).toList(); } private record OutputAnalizer(Executor.Result result, WixType wixType, Optional wixBuildCommandLine) { @@ -123,8 +165,8 @@ private record OutputAnalizer(Executor.Result result, WixType wixType, Optional< Objects.requireNonNull(wixBuildCommandLine); } - OutputAnalizer(Executor.Result result) { - this(result, getWixTypeFromVerboseJPackageOutput(result)); + OutputAnalizer(Locale locale, Executor.Result result) { + this(result, getWixTypeFromVerboseJPackageOutput(locale, result)); } OutputAnalizer(Executor.Result result, WixType wixType) { @@ -196,13 +238,7 @@ private static List createDefaultL10nFilesLocVerifiers( public void test() throws IOException { final Path tempRoot = TKit.createTempDirectory("tmp"); - final boolean allWxlFilesValid; - if (wxlFileInitializers != null) { - allWxlFilesValid = Stream.of(wxlFileInitializers).allMatch( - WixFileInitializer::isValid); - } else { - allWxlFilesValid = true; - } + final boolean allWxlFilesValid = wxlFileInitializers.stream().allMatch(WixFileInitializer::isValid); PackageTest test = new PackageTest() .forTypes(PackageType.WINDOWS) @@ -225,19 +261,18 @@ public void test() throws IOException { boolean withJavaOptions = false; // Set JVM default locale that is used to select primary l10n file. - if (userLanguage != null) { - withJavaOptions = true; - cmd.addArguments("-J-Duser.language=" + userLanguage); - } - if (userCountry != null) { - withJavaOptions = true; - cmd.addArguments("-J-Duser.country=" + userCountry); - } - - if (withJavaOptions) { + locale.ifPresent(l -> { + cmd.addArguments("-J-Duser.language=" + l.getLanguage()); + cmd.addArguments("-J-Duser.country=" + l.getCountry()); + // Force UTF8 encoding of the output of jpackage command. + // This is the default encoding of the output for the command executor. + // This is needed to properly handle JP and CN l10n-s. + cmd.addArguments("-J-Dstdout.encoding=UTF-8"); + cmd.addArguments("-J-Dstderr.encoding=UTF-8"); + cmd.addArguments("-J-Dfile.encoding=UTF-8"); // Use jpackage as a command to allow "-J" options come through cmd.useToolProvider(false); - } + }); // Cultures handling is affected by the WiX extensions used. // By default only WixUtilExtension is used, this flag @@ -252,14 +287,10 @@ public void test() throws IOException { }) .addBundleVerifier((cmd, result) -> { - var outputAnalizer = new OutputAnalizer(result); - - if (expectedCultures != null) { - outputAnalizer.verifyCulturesInCmdline(expectedCultures); - } + var outputAnalizer = new OutputAnalizer(locale.orElseGet(Locale::getDefault), result); - if (expectedErrorMessage != null) { - TKit.assertTextStream(expectedErrorMessage).apply(result.stderr()); + if (!expectedCultures.isEmpty()) { + outputAnalizer.verifyCulturesInCmdline(expectedCultures.toArray(String[]::new)); } if (wxlFileInitializers != null) { @@ -277,7 +308,7 @@ public void test() throws IOException { v.apply(List.of(outputAnalizer.getWixBuildCommandLine())); } } else { - Stream.of(wxlFileInitializers) + wxlFileInitializers.stream() .filter(Predicate.not(WixFileInitializer::isValid)) .forEach(v -> v.createCmdOutputVerifier( wixSrcDir).apply(result.getOutput())); @@ -287,9 +318,9 @@ public void test() throws IOException { } }); - if (wxlFileInitializers != null) { + if (!wxlFileInitializers.isEmpty()) { test.addInitializer(cmd -> { - resourceDir = TKit.createTempDirectory("resources"); + var resourceDir = TKit.createTempDirectory("resources"); cmd.addArguments("--resource-dir", resourceDir); @@ -299,21 +330,13 @@ public void test() throws IOException { }); } - if (expectedErrorMessage != null || !allWxlFilesValid) { + if (!allWxlFilesValid) { test.setExpectedExitCode(1); } test.run(); } - private final WixFileInitializer[] wxlFileInitializers; - private final String[] expectedCultures; - private final String expectedErrorMessage; - private final String userLanguage; - private final String userCountry; - private final boolean enableWixUIExtension; - private Path resourceDir; - private static class WixFileInitializer { static WixFileInitializer create(String name, String culture) { return new WixFileInitializer(name, culture); diff --git a/test/lib/jdk/test/lib/apps/LingeredApp.java b/test/lib/jdk/test/lib/apps/LingeredApp.java index 9a8395d7879c..f8ebc844bcb5 100644 --- a/test/lib/jdk/test/lib/apps/LingeredApp.java +++ b/test/lib/jdk/test/lib/apps/LingeredApp.java @@ -643,6 +643,8 @@ public static void main(String args[]) { crasher.run(); } } + // Force a GC now to reduce the risk of one happening during the loop below. + System.gc(); while (Files.exists(path)) { // Touch the lock to indicate our readiness setLastModified(theLockFileName, epoch());