From f083faf2c3f0ab96c703567e32c8ae6392c2b969 Mon Sep 17 00:00:00 2001 From: SendaoYan Date: Wed, 4 Mar 2026 05:56:15 +0000 Subject: [PATCH 001/168] 8371503: RETAIN_IMAGE_AFTER_TEST do not work for some tests Backport-of: 34f241317ecd7473cfb6dcc2e6e5cf3a40299e2c --- test/hotspot/jtreg/containers/docker/DockerBasicTest.java | 4 +--- test/hotspot/jtreg/containers/docker/ShareTmpDir.java | 4 +--- test/hotspot/jtreg/containers/docker/TestCPUAwareness.java | 4 +--- test/hotspot/jtreg/containers/docker/TestLimitsUpdating.java | 4 +--- test/hotspot/jtreg/containers/docker/TestMemoryAwareness.java | 4 +--- test/hotspot/jtreg/containers/docker/TestPids.java | 4 +--- .../jdk/internal/platform/docker/TestDockerMemoryMetrics.java | 4 +--- .../internal/platform/docker/TestGetFreeSwapSpaceSize.java | 4 +--- test/jdk/jdk/internal/platform/docker/TestLimitsUpdating.java | 4 +--- test/jdk/jdk/internal/platform/docker/TestPidsLimit.java | 4 +--- test/lib/jdk/test/lib/containers/docker/DockerTestUtils.java | 2 ++ 11 files changed, 12 insertions(+), 30 deletions(-) diff --git a/test/hotspot/jtreg/containers/docker/DockerBasicTest.java b/test/hotspot/jtreg/containers/docker/DockerBasicTest.java index 8e2c0b6a85a9..e908f2a5bf32 100644 --- a/test/hotspot/jtreg/containers/docker/DockerBasicTest.java +++ b/test/hotspot/jtreg/containers/docker/DockerBasicTest.java @@ -56,9 +56,7 @@ public static void main(String[] args) throws Exception { testHelloDocker(); testJavaVersionWithCgMounts(); } finally { - if (!DockerTestUtils.RETAIN_IMAGE_AFTER_TEST) { - DockerTestUtils.removeDockerImage(imageNameAndTag); - } + DockerTestUtils.removeDockerImage(imageNameAndTag); } } diff --git a/test/hotspot/jtreg/containers/docker/ShareTmpDir.java b/test/hotspot/jtreg/containers/docker/ShareTmpDir.java index 9a4748563bd7..48876ca37fe2 100644 --- a/test/hotspot/jtreg/containers/docker/ShareTmpDir.java +++ b/test/hotspot/jtreg/containers/docker/ShareTmpDir.java @@ -59,9 +59,7 @@ public static void main(String[] args) throws Exception { try { test(); } finally { - if (!DockerTestUtils.RETAIN_IMAGE_AFTER_TEST) { - DockerTestUtils.removeDockerImage(imageName); - } + DockerTestUtils.removeDockerImage(imageName); } } diff --git a/test/hotspot/jtreg/containers/docker/TestCPUAwareness.java b/test/hotspot/jtreg/containers/docker/TestCPUAwareness.java index 99220201f666..dc73dd1b5445 100644 --- a/test/hotspot/jtreg/containers/docker/TestCPUAwareness.java +++ b/test/hotspot/jtreg/containers/docker/TestCPUAwareness.java @@ -86,9 +86,7 @@ public static void main(String[] args) throws Exception { } } finally { - if (!DockerTestUtils.RETAIN_IMAGE_AFTER_TEST) { - DockerTestUtils.removeDockerImage(imageName); - } + DockerTestUtils.removeDockerImage(imageName); } } diff --git a/test/hotspot/jtreg/containers/docker/TestLimitsUpdating.java b/test/hotspot/jtreg/containers/docker/TestLimitsUpdating.java index 7b05669085c9..e8cb54d7d7b9 100644 --- a/test/hotspot/jtreg/containers/docker/TestLimitsUpdating.java +++ b/test/hotspot/jtreg/containers/docker/TestLimitsUpdating.java @@ -64,9 +64,7 @@ public static void main(String[] args) throws Exception { try { testLimitUpdates(); } finally { - if (!DockerTestUtils.RETAIN_IMAGE_AFTER_TEST) { - DockerTestUtils.removeDockerImage(imageName); - } + DockerTestUtils.removeDockerImage(imageName); } } diff --git a/test/hotspot/jtreg/containers/docker/TestMemoryAwareness.java b/test/hotspot/jtreg/containers/docker/TestMemoryAwareness.java index 06a874e008ae..a5fb3f829135 100644 --- a/test/hotspot/jtreg/containers/docker/TestMemoryAwareness.java +++ b/test/hotspot/jtreg/containers/docker/TestMemoryAwareness.java @@ -105,9 +105,7 @@ public static void main(String[] args) throws Exception { testMetricsSwapExceedingPhysical(); testContainerMemExceedsPhysical(); } finally { - if (!DockerTestUtils.RETAIN_IMAGE_AFTER_TEST) { - DockerTestUtils.removeDockerImage(imageName); - } + DockerTestUtils.removeDockerImage(imageName); } } diff --git a/test/hotspot/jtreg/containers/docker/TestPids.java b/test/hotspot/jtreg/containers/docker/TestPids.java index 62bd70dc61fb..07a0cbd295dc 100644 --- a/test/hotspot/jtreg/containers/docker/TestPids.java +++ b/test/hotspot/jtreg/containers/docker/TestPids.java @@ -64,9 +64,7 @@ public static void main(String[] args) throws Exception { try { testPids(); } finally { - if (!DockerTestUtils.RETAIN_IMAGE_AFTER_TEST) { - DockerTestUtils.removeDockerImage(imageName); - } + DockerTestUtils.removeDockerImage(imageName); } } diff --git a/test/jdk/jdk/internal/platform/docker/TestDockerMemoryMetrics.java b/test/jdk/jdk/internal/platform/docker/TestDockerMemoryMetrics.java index 2afb5ed93b1e..8d63e76c141e 100644 --- a/test/jdk/jdk/internal/platform/docker/TestDockerMemoryMetrics.java +++ b/test/jdk/jdk/internal/platform/docker/TestDockerMemoryMetrics.java @@ -82,9 +82,7 @@ public static void main(String[] args) throws Exception { testMemorySoftLimit("500m","200m"); } finally { - if (!DockerTestUtils.RETAIN_IMAGE_AFTER_TEST) { - DockerTestUtils.removeDockerImage(imageName); - } + DockerTestUtils.removeDockerImage(imageName); } } diff --git a/test/jdk/jdk/internal/platform/docker/TestGetFreeSwapSpaceSize.java b/test/jdk/jdk/internal/platform/docker/TestGetFreeSwapSpaceSize.java index b9d031f03090..5a89c04796aa 100644 --- a/test/jdk/jdk/internal/platform/docker/TestGetFreeSwapSpaceSize.java +++ b/test/jdk/jdk/internal/platform/docker/TestGetFreeSwapSpaceSize.java @@ -53,9 +53,7 @@ public static void main(String[] args) throws Exception { "150M", Integer.toString(0) ); } finally { - if (!DockerTestUtils.RETAIN_IMAGE_AFTER_TEST) { - DockerTestUtils.removeDockerImage(imageName); - } + DockerTestUtils.removeDockerImage(imageName); } } diff --git a/test/jdk/jdk/internal/platform/docker/TestLimitsUpdating.java b/test/jdk/jdk/internal/platform/docker/TestLimitsUpdating.java index 31e90e8802a9..ee9e7f41ab0e 100644 --- a/test/jdk/jdk/internal/platform/docker/TestLimitsUpdating.java +++ b/test/jdk/jdk/internal/platform/docker/TestLimitsUpdating.java @@ -64,9 +64,7 @@ public static void main(String[] args) throws Exception { try { testLimitUpdates(); } finally { - if (!DockerTestUtils.RETAIN_IMAGE_AFTER_TEST) { - DockerTestUtils.removeDockerImage(imageName); - } + DockerTestUtils.removeDockerImage(imageName); } } diff --git a/test/jdk/jdk/internal/platform/docker/TestPidsLimit.java b/test/jdk/jdk/internal/platform/docker/TestPidsLimit.java index 6b19bb475f13..04c172b13b8f 100644 --- a/test/jdk/jdk/internal/platform/docker/TestPidsLimit.java +++ b/test/jdk/jdk/internal/platform/docker/TestPidsLimit.java @@ -60,9 +60,7 @@ public static void main(String[] args) throws Exception { testPidsLimit("2000"); testPidsLimit("Unlimited"); } finally { - if (!DockerTestUtils.RETAIN_IMAGE_AFTER_TEST) { - DockerTestUtils.removeDockerImage(imageName); - } + DockerTestUtils.removeDockerImage(imageName); } } diff --git a/test/lib/jdk/test/lib/containers/docker/DockerTestUtils.java b/test/lib/jdk/test/lib/containers/docker/DockerTestUtils.java index 422671d65b70..e1b368ece378 100644 --- a/test/lib/jdk/test/lib/containers/docker/DockerTestUtils.java +++ b/test/lib/jdk/test/lib/containers/docker/DockerTestUtils.java @@ -281,7 +281,9 @@ public static OutputAnalyzer dockerRunJava(DockerRunOptions opts) throws Excepti * @throws Exception */ public static void removeDockerImage(String imageNameAndTag) throws Exception { + if(!DockerTestUtils.RETAIN_IMAGE_AFTER_TEST) { execute(Container.ENGINE_COMMAND, "rmi", "--force", imageNameAndTag); + } } From ed1b2c629c4548df8ab9d71bc6b1f372b058e716 Mon Sep 17 00:00:00 2001 From: Goetz Lindenmaier Date: Wed, 4 Mar 2026 10:22:21 +0000 Subject: [PATCH 002/168] 8378774: Bump update version for OpenJDK: jdk-25.0.4 Reviewed-by: mbaesken --- .jcheck/conf | 2 +- make/conf/version-numbers.conf | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.jcheck/conf b/.jcheck/conf index 27adc61e5340..84e19c1d09ba 100644 --- a/.jcheck/conf +++ b/.jcheck/conf @@ -1,7 +1,7 @@ [general] project=jdk-updates jbs=JDK -version=25.0.3 +version=25.0.4 [checks] error=author,committer,reviewers,merge,issues,executable,symlink,message,hg-tag,whitespace,problemlists,copyright diff --git a/make/conf/version-numbers.conf b/make/conf/version-numbers.conf index f82c7822485d..46b6c430f542 100644 --- a/make/conf/version-numbers.conf +++ b/make/conf/version-numbers.conf @@ -1,5 +1,5 @@ # -# Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2011, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -28,12 +28,12 @@ DEFAULT_VERSION_FEATURE=25 DEFAULT_VERSION_INTERIM=0 -DEFAULT_VERSION_UPDATE=3 +DEFAULT_VERSION_UPDATE=4 DEFAULT_VERSION_PATCH=0 DEFAULT_VERSION_EXTRA1=0 DEFAULT_VERSION_EXTRA2=0 DEFAULT_VERSION_EXTRA3=0 -DEFAULT_VERSION_DATE=2026-04-21 +DEFAULT_VERSION_DATE=2026-07-21 DEFAULT_VERSION_CLASSFILE_MAJOR=69 # "`$EXPR $DEFAULT_VERSION_FEATURE + 44`" DEFAULT_VERSION_CLASSFILE_MINOR=0 DEFAULT_VERSION_DOCS_API_SINCE=11 From 7878bc9b5d6697bd810a805c82865c11ccdbf7be Mon Sep 17 00:00:00 2001 From: Fei Yang Date: Sun, 8 Mar 2026 01:35:38 +0000 Subject: [PATCH 003/168] 8378888: jdk/incubator/vector/Float16OperationsBenchmark.java uses wrong package name Backport-of: b7d0cb5fb36965874f0950ab882dc517b002509f --- .../bench/jdk/incubator/vector/Float16OperationsBenchmark.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/micro/org/openjdk/bench/jdk/incubator/vector/Float16OperationsBenchmark.java b/test/micro/org/openjdk/bench/jdk/incubator/vector/Float16OperationsBenchmark.java index ebbfbb01cc61..cbfe99589240 100644 --- a/test/micro/org/openjdk/bench/jdk/incubator/vector/Float16OperationsBenchmark.java +++ b/test/micro/org/openjdk/bench/jdk/incubator/vector/Float16OperationsBenchmark.java @@ -20,7 +20,7 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package org.openjdk.bench.java.lang; +package org.openjdk.bench.jdk.incubator.vector; import java.util.stream.IntStream; import java.util.concurrent.TimeUnit; From 18507ad50a2dcfb307bd16e5c0a1fe3b041abca9 Mon Sep 17 00:00:00 2001 From: Fei Yang Date: Sun, 8 Mar 2026 01:37:14 +0000 Subject: [PATCH 004/168] 8378810: Enable missing FFM test via jtreg requires for RISC-V Backport-of: 8009a714ba81af8b6a7b422f510ae5d6509a73a7 --- .../jtreg/gc/shenandoah/compiler/TestLinkToNativeRBP.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/hotspot/jtreg/gc/shenandoah/compiler/TestLinkToNativeRBP.java b/test/hotspot/jtreg/gc/shenandoah/compiler/TestLinkToNativeRBP.java index 96440ba15ae7..d999df2f8862 100644 --- a/test/hotspot/jtreg/gc/shenandoah/compiler/TestLinkToNativeRBP.java +++ b/test/hotspot/jtreg/gc/shenandoah/compiler/TestLinkToNativeRBP.java @@ -27,7 +27,8 @@ * @summary guarantee(loc != NULL) failed: missing saved register with native invoke * * @requires vm.flavor == "server" - * @requires ((os.arch == "amd64" | os.arch == "x86_64") & sun.arch.data.model == "64") | os.arch == "aarch64" | os.arch == "ppc64" | os.arch == "ppc64le" + * @requires ((os.arch == "amd64" | os.arch == "x86_64") & sun.arch.data.model == "64") | + os.arch == "aarch64" | os.arch == "ppc64" | os.arch == "ppc64le" | os.arch == "riscv64" * @requires vm.gc.Shenandoah * * @run main/othervm --enable-native-access=ALL-UNNAMED -XX:+UnlockDiagnosticVMOptions From a27ea41aae1ea5d0ccb6af2406fa975c6389026c Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Mon, 9 Mar 2026 07:37:00 +0000 Subject: [PATCH 005/168] 8376402: Dependencies::print_statistics() and AbstractClassHierarchyWalker::print_statistics() are not called from PRODUCT code Backport-of: 0e2e66be2423335002a53d887df35d2348a3ec9f --- src/hotspot/share/code/dependencies.cpp | 6 ++++-- src/hotspot/share/code/dependencies.hpp | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/hotspot/share/code/dependencies.cpp b/src/hotspot/share/code/dependencies.cpp index 7f925388eb03..324b72f051da 100644 --- a/src/hotspot/share/code/dependencies.cpp +++ b/src/hotspot/share/code/dependencies.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -1125,7 +1125,7 @@ class AbstractClassHierarchyWalker { Klass* find_witness(InstanceKlass* context_type, KlassDepChange* changes = nullptr); static void init(); - static void print_statistics(); + NOT_PRODUCT(static void print_statistics();) }; PerfCounter* AbstractClassHierarchyWalker::_perf_find_witness_anywhere_calls_count = nullptr; @@ -2278,6 +2278,7 @@ bool KlassDepChange::involves_context(Klass* k) { return is_contained; } +#ifndef PRODUCT void Dependencies::print_statistics() { AbstractClassHierarchyWalker::print_statistics(); } @@ -2303,6 +2304,7 @@ void AbstractClassHierarchyWalker::print_statistics() { } } } +#endif CallSiteDepChange::CallSiteDepChange(Handle call_site, Handle method_handle) : _call_site(call_site), diff --git a/src/hotspot/share/code/dependencies.hpp b/src/hotspot/share/code/dependencies.hpp index d11c51a66dc6..582a08183f9c 100644 --- a/src/hotspot/share/code/dependencies.hpp +++ b/src/hotspot/share/code/dependencies.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -649,7 +649,7 @@ class Dependencies: public ResourceObj { }; friend class Dependencies::DepStream; - static void print_statistics(); + NOT_PRODUCT(static void print_statistics();) }; From 44949450bb298e59a31a509d6d5178f080f0f933 Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Mon, 9 Mar 2026 07:44:21 +0000 Subject: [PATCH 006/168] 8374998: Failing os::write - remove bad file Backport-of: 4c9103f7b6c91b0f237859516ef72bb9ee27157e --- src/hotspot/os/posix/perfMemory_posix.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/hotspot/os/posix/perfMemory_posix.cpp b/src/hotspot/os/posix/perfMemory_posix.cpp index e3483781794e..3deb7efeb9bc 100644 --- a/src/hotspot/os/posix/perfMemory_posix.cpp +++ b/src/hotspot/os/posix/perfMemory_posix.cpp @@ -111,6 +111,10 @@ static void save_memory_to_file(char* addr, size_t size) { result = ::close(fd); if (result == OS_ERR) { warning("Could not close %s: %s\n", destfile, os::strerror(errno)); + } else { + if (!successful_write) { + remove(destfile); + } } } FREE_C_HEAP_ARRAY(char, destfile); @@ -953,6 +957,7 @@ static int create_sharedmem_file(const char* dirname, const char* filename, size warning("Insufficient space for shared memory file: %s/%s\n", dirname, filename); } result = OS_ERR; + remove(filename); break; } } From c0e1c14e77e8e051551e3515f56742e0cfd8de20 Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Mon, 9 Mar 2026 07:55:43 +0000 Subject: [PATCH 007/168] 8374727: Audio configuration Platform class - use nio for getting endianness of the underlying platform Backport-of: 074038438f5b8b91e9390430b4fa58ff53e5df26 --- .../libjsound/PLATFORM_API_MacOSX_PCM.cpp | 7 ++- .../classes/com/sun/media/sound/Platform.java | 17 ++------ .../share/native/libjsound/Platform.c | 43 ------------------- .../share/native/libjsound/Utilities.c | 11 +---- .../share/native/libjsound/Utilities.h | 6 +-- 5 files changed, 8 insertions(+), 76 deletions(-) delete mode 100644 src/java.desktop/share/native/libjsound/Platform.c diff --git a/src/java.desktop/macosx/native/libjsound/PLATFORM_API_MacOSX_PCM.cpp b/src/java.desktop/macosx/native/libjsound/PLATFORM_API_MacOSX_PCM.cpp index 441a71f5c50d..bae16cb0a9c0 100644 --- a/src/java.desktop/macosx/native/libjsound/PLATFORM_API_MacOSX_PCM.cpp +++ b/src/java.desktop/macosx/native/libjsound/PLATFORM_API_MacOSX_PCM.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -162,8 +162,7 @@ void DAUDIO_GetFormats(INT32 mixerIndex, INT32 deviceID, int isSource, void* cre sampleRate, // sample rate DAUDIO_PCM, // only accept PCM bits == 8 ? FALSE : TRUE, // signed - bits == 8 ? FALSE // little-endian for 8bit - : UTIL_IsBigEndianPlatform()); + FALSE); // all supported macOS versions run on LE } } // add default format @@ -175,7 +174,7 @@ void DAUDIO_GetFormats(INT32 mixerIndex, INT32 deviceID, int isSource, void* cre defSampleRate, // sample rate DAUDIO_PCM, // PCM TRUE, // signed - UTIL_IsBigEndianPlatform()); // native endianness + FALSE); // native endianness; all supported macOS versions run on LE } TRACE0("< Date: Mon, 9 Mar 2026 08:43:45 +0000 Subject: [PATCH 008/168] 8378561: Mark gc/shenandoah/compiler/TestLinkToNativeRBP.java as /native Backport-of: d7f4365b296d120521e16666e2ce2177a8d2c44d --- .../jtreg/gc/shenandoah/compiler/TestLinkToNativeRBP.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/hotspot/jtreg/gc/shenandoah/compiler/TestLinkToNativeRBP.java b/test/hotspot/jtreg/gc/shenandoah/compiler/TestLinkToNativeRBP.java index d999df2f8862..ff853d174b12 100644 --- a/test/hotspot/jtreg/gc/shenandoah/compiler/TestLinkToNativeRBP.java +++ b/test/hotspot/jtreg/gc/shenandoah/compiler/TestLinkToNativeRBP.java @@ -31,7 +31,7 @@ os.arch == "aarch64" | os.arch == "ppc64" | os.arch == "ppc64le" | os.arch == "riscv64" * @requires vm.gc.Shenandoah * - * @run main/othervm --enable-native-access=ALL-UNNAMED -XX:+UnlockDiagnosticVMOptions + * @run main/native/othervm --enable-native-access=ALL-UNNAMED -XX:+UnlockDiagnosticVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=aggressive TestLinkToNativeRBP * */ From 2836b162c5181bacea19dd7d81697c2e280615da Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Tue, 10 Mar 2026 14:39:47 +0000 Subject: [PATCH 009/168] 8368159: Significant performance overhead when started with jdwp agent and unattached debugger Backport-of: 17244c699ad20fafe7448678a53266ce6bf017e5 --- src/hotspot/share/prims/jvmtiExport.cpp | 17 +++++++++++++++++ src/hotspot/share/prims/jvmtiExport.hpp | 3 ++- .../share/runtime/continuationFreezeThaw.cpp | 9 ++++----- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/hotspot/share/prims/jvmtiExport.cpp b/src/hotspot/share/prims/jvmtiExport.cpp index 13822f73f772..c767a0eeac25 100644 --- a/src/hotspot/share/prims/jvmtiExport.cpp +++ b/src/hotspot/share/prims/jvmtiExport.cpp @@ -1726,6 +1726,23 @@ void JvmtiExport::post_vthread_unmount(jobject vthread) { } } +bool JvmtiExport::has_frame_pops(JavaThread* thread) { + if (!can_post_frame_pop()) { + return false; + } + JvmtiThreadState *state = get_jvmti_thread_state(thread); + if (state == nullptr) { + return false; + } + JvmtiEnvThreadStateIterator it(state); + for (JvmtiEnvThreadState* ets = it.first(); ets != nullptr; ets = it.next(ets)) { + if (ets->has_frame_pops()) { + return true; + } + } + return false; +} + void JvmtiExport::continuation_yield_cleanup(JavaThread* thread, jint continuation_frame_count) { if (JvmtiEnv::get_phase() < JVMTI_PHASE_PRIMORDIAL) { return; diff --git a/src/hotspot/share/prims/jvmtiExport.hpp b/src/hotspot/share/prims/jvmtiExport.hpp index e47cd3d6363d..2f03b130288e 100644 --- a/src/hotspot/share/prims/jvmtiExport.hpp +++ b/src/hotspot/share/prims/jvmtiExport.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -373,6 +373,7 @@ class JvmtiExport : public AllStatic { JVMTI_ONLY(return _should_post_class_file_load_hook); NOT_JVMTI(return false;) } + static bool has_frame_pops(JavaThread* thread) NOT_JVMTI_RETURN_(false); static bool is_early_phase() NOT_JVMTI_RETURN_(false); static bool has_early_class_hook_env() NOT_JVMTI_RETURN_(false); static bool has_early_vmstart_env() NOT_JVMTI_RETURN_(false); diff --git a/src/hotspot/share/runtime/continuationFreezeThaw.cpp b/src/hotspot/share/runtime/continuationFreezeThaw.cpp index a928b0443eef..821b32f21ed2 100644 --- a/src/hotspot/share/runtime/continuationFreezeThaw.cpp +++ b/src/hotspot/share/runtime/continuationFreezeThaw.cpp @@ -1623,15 +1623,14 @@ static int num_java_frames(ContinuationWrapper& cont) { } static void invalidate_jvmti_stack(JavaThread* thread) { - if (thread->is_interp_only_mode()) { - JvmtiThreadState *state = thread->jvmti_thread_state(); - if (state != nullptr) - state->invalidate_cur_stack_depth(); + JvmtiThreadState *state = thread->jvmti_thread_state(); + if (state != nullptr) { + state->invalidate_cur_stack_depth(); } } static void jvmti_yield_cleanup(JavaThread* thread, ContinuationWrapper& cont) { - if (JvmtiExport::can_post_frame_pop()) { + if (JvmtiExport::has_frame_pops(thread)) { int num_frames = num_java_frames(cont); ContinuationWrapper::SafepointOp so(Thread::current(), cont); From 367d27a89bbe81fad0eb004aea3872d6f56258dc Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Tue, 10 Mar 2026 14:42:44 +0000 Subject: [PATCH 010/168] 8373650: Test "javax/swing/JMenuItem/6458123/ManualBug6458123.java" fails because the check icons are not aligned properly as expected Backport-of: e45f5656bc90421c9acb0cbf87164162039ddf81 --- .../plaf/windows/WindowsIconFactory.java | 44 +++++++++++++++++-- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsIconFactory.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsIconFactory.java index 915a361a3a1d..91c2cbcd61d9 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsIconFactory.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsIconFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 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 @@ -905,10 +905,46 @@ public void paintIcon(Component c, Graphics g, int x, int y) { XPStyle xp = XPStyle.getXP(); if (xp != null) { Skin skin = xp.getSkin(c, part); - if (icon == null || icon.getIconHeight() <= 16) { - skin.paintSkin(g, x + OFFSET, y + OFFSET, state); + if (WindowsGraphicsUtils.isLeftToRight(c)) { + if (icon == null || icon.getIconHeight() <= 16) { + skin.paintSkin(g, x + OFFSET, y + OFFSET, state); + } else { + skin.paintSkin(g, x + OFFSET, y + icon.getIconHeight() / 2, state); + } } else { - skin.paintSkin(g, x + OFFSET, y + icon.getIconHeight() / 2, state); + if (icon == null) { + skin.paintSkin(g, x + 4 * OFFSET, y + OFFSET, state); + } else { + int ycoord = (icon.getIconHeight() <= 16) + ? y + OFFSET + : (y + icon.getIconHeight() / 2); + if (icon.getIconWidth() <= 8) { + skin.paintSkin(g, x + OFFSET, ycoord, state); + } else if (icon.getIconWidth() <= 16) { + if (menuItem.getText().isEmpty()) { + skin.paintSkin(g, + (menuItem.getAccelerator() != null) + ? (x + 2 * OFFSET) : (x + 3 * OFFSET), + ycoord, state); + } else { + skin.paintSkin(g, + (type == JRadioButtonMenuItem.class) + ? (x + 4 * OFFSET) : (x + 3 * OFFSET), + ycoord, state); + } + } else { + if (menuItem.getText().isEmpty() + || menuItem.getAccelerator() != null) { + skin.paintSkin(g, + (type == JRadioButtonMenuItem.class) + ? (x + 3 * OFFSET) : (x + 4 * OFFSET), + ycoord, state); + } else { + skin.paintSkin(g, x + 7 * OFFSET, + ycoord, state); + } + } + } } } } From 2161b142f70c99970f5a8d381deea4b289e620d1 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Tue, 10 Mar 2026 14:43:06 +0000 Subject: [PATCH 011/168] 8297191: [macos] Printing a page range with starting page > 1 results in missing pages Backport-of: 5ba91fed345b078a67ad6bead1d8893bd9289f58 --- .../classes/sun/lwawt/macosx/CPrinterJob.java | 24 +++++-------------- .../native/libawt_lwawt/awt/CPrinterJob.m | 8 +++---- .../native/libawt_lwawt/awt/PrinterView.h | 6 ++--- .../native/libawt_lwawt/awt/PrinterView.m | 13 +++++----- .../java/awt/print/PrinterJob/PageRanges.java | 4 ++-- 5 files changed, 21 insertions(+), 34 deletions(-) diff --git a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPrinterJob.java b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPrinterJob.java index 1ca94eb3f51c..ac37526e25a4 100644 --- a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPrinterJob.java +++ b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPrinterJob.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -322,20 +322,9 @@ public void print(PrintRequestAttributeSet attributes) throws PrinterException { validateDestination(destinationAttr); } - /* Get the range of pages we are to print. If the - * last page to print is unknown, then we print to - * the end of the document. Note that firstPage - * and lastPage are 0 based page indices. - */ - + // Note that firstPage is 0 based page index. int firstPage = getFirstPage(); - int lastPage = getLastPage(); - if(lastPage == Pageable.UNKNOWN_NUMBER_OF_PAGES) { - int totalPages = mDocument.getNumberOfPages(); - if (totalPages != Pageable.UNKNOWN_NUMBER_OF_PAGES) { - lastPage = mDocument.getNumberOfPages() - 1; - } - } + int totalPages = mDocument.getNumberOfPages(); try { synchronized (this) { @@ -360,7 +349,7 @@ public void print(PrintRequestAttributeSet attributes) throws PrinterException { try { // Fire off the print rendering loop on the AppKit thread, and don't have // it wait and block this thread. - if (printLoop(false, firstPage, lastPage)) { + if (printLoop(false, firstPage, totalPages)) { // Start a secondary loop on EDT until printing operation is finished or cancelled printingLoop.enter(); } @@ -374,7 +363,7 @@ public void print(PrintRequestAttributeSet attributes) throws PrinterException { onEventThread = false; try { - printLoop(true, firstPage, lastPage); + printLoop(true, firstPage, totalPages); } catch (Exception e) { e.printStackTrace(); } @@ -384,7 +373,6 @@ public void print(PrintRequestAttributeSet attributes) throws PrinterException { } if (++loopi < prMembers.length) { firstPage = prMembers[loopi][0]-1; - lastPage = prMembers[loopi][1] -1; } } while (loopi < prMembers.length); } finally { @@ -634,7 +622,7 @@ private long getNSPrintInfo() { } } - private native boolean printLoop(boolean waitUntilDone, int firstPage, int lastPage) throws PrinterException; + private native boolean printLoop(boolean waitUntilDone, int firstPage, int totalPages) throws PrinterException; private PageFormat getPageFormat(int pageIndex) { // This is called from the native side. diff --git a/src/java.desktop/macosx/native/libawt_lwawt/awt/CPrinterJob.m b/src/java.desktop/macosx/native/libawt_lwawt/awt/CPrinterJob.m index 9333aa8676bd..92d58f806ae1 100644 --- a/src/java.desktop/macosx/native/libawt_lwawt/awt/CPrinterJob.m +++ b/src/java.desktop/macosx/native/libawt_lwawt/awt/CPrinterJob.m @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -656,7 +656,7 @@ static void javaPrinterJobToNSPrintInfo(JNIEnv* env, jobject srcPrinterJob, jobj * Signature: ()V */ JNIEXPORT jboolean JNICALL Java_sun_lwawt_macosx_CPrinterJob_printLoop - (JNIEnv *env, jobject jthis, jboolean blocks, jint firstPage, jint lastPage) + (JNIEnv *env, jobject jthis, jboolean blocks, jint firstPage, jint totalPages) { AWT_ASSERT_NOT_APPKIT_THREAD; @@ -672,14 +672,14 @@ static void javaPrinterJobToNSPrintInfo(JNIEnv* env, jobject srcPrinterJob, jobj JNI_COCOA_ENTER(env); // Get the first page's PageFormat for setting things up (This introduces // and is a facet of the same problem in Radar 2818593/2708932). - jobject page = (*env)->CallObjectMethod(env, jthis, jm_getPageFormat, 0); // AWT_THREADING Safe (!appKit) + jobject page = (*env)->CallObjectMethod(env, jthis, jm_getPageFormat, firstPage); // AWT_THREADING Safe (!appKit) CHECK_EXCEPTION(); if (page != NULL) { jobject pageFormatArea = (*env)->CallObjectMethod(env, jthis, jm_getPageFormatArea, page); // AWT_THREADING Safe (!appKit) CHECK_EXCEPTION(); PrinterView* printerView = [[PrinterView alloc] initWithFrame:JavaToNSRect(env, pageFormatArea) withEnv:env withPrinterJob:jthis]; - [printerView setFirstPage:firstPage lastPage:lastPage]; + [printerView setTotalPages:totalPages]; GET_NSPRINTINFO_METHOD_RETURN(NO) NSPrintInfo* printInfo = (NSPrintInfo*)jlong_to_ptr((*env)->CallLongMethod(env, jthis, sjm_getNSPrintInfo)); // AWT_THREADING Safe (known object) diff --git a/src/java.desktop/macosx/native/libawt_lwawt/awt/PrinterView.h b/src/java.desktop/macosx/native/libawt_lwawt/awt/PrinterView.h index 43472bee9208..95a8055cdb0c 100644 --- a/src/java.desktop/macosx/native/libawt_lwawt/awt/PrinterView.h +++ b/src/java.desktop/macosx/native/libawt_lwawt/awt/PrinterView.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -32,12 +32,12 @@ jobject fCurPainter; jobject fCurPeekGraphics; - jint fFirstPage, fLastPage; + jint fTotalPages; } - (id)initWithFrame:(NSRect)aRect withEnv:(JNIEnv*)env withPrinterJob:(jobject)printerJob; -- (void)setFirstPage:(jint)firstPage lastPage:(jint)lastPage; +- (void)setTotalPages:(jint)totalPages; - (void)releaseReferences:(JNIEnv*)env; diff --git a/src/java.desktop/macosx/native/libawt_lwawt/awt/PrinterView.m b/src/java.desktop/macosx/native/libawt_lwawt/awt/PrinterView.m index f39ca25a08f7..8a80df6ee0bf 100644 --- a/src/java.desktop/macosx/native/libawt_lwawt/awt/PrinterView.m +++ b/src/java.desktop/macosx/native/libawt_lwawt/awt/PrinterView.m @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -72,9 +72,8 @@ - (void)releaseReferences:(JNIEnv*)env } } -- (void)setFirstPage:(jint)firstPage lastPage:(jint)lastPage { - fFirstPage = firstPage; - fLastPage = lastPage; +- (void)setTotalPages:(jint)totalPages { + fTotalPages = totalPages; } - (void)drawRect:(NSRect)aRect @@ -139,15 +138,15 @@ - (BOOL)knowsPageRange:(NSRangePointer)aRange return NO; } - aRange->location = fFirstPage + 1; + aRange->location = 1; - if (fLastPage == java_awt_print_Pageable_UNKNOWN_NUMBER_OF_PAGES) + if (fTotalPages == java_awt_print_Pageable_UNKNOWN_NUMBER_OF_PAGES) { aRange->length = NSIntegerMax; } else { - aRange->length = (fLastPage + 1) - fFirstPage; + aRange->length = fTotalPages; } return YES; diff --git a/test/jdk/java/awt/print/PrinterJob/PageRanges.java b/test/jdk/java/awt/print/PrinterJob/PageRanges.java index e80330bae6ce..aea60516f782 100644 --- a/test/jdk/java/awt/print/PrinterJob/PageRanges.java +++ b/test/jdk/java/awt/print/PrinterJob/PageRanges.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,7 +23,7 @@ /* * @test - * @bug 6575331 + * @bug 6575331 8297191 * @key printer * @summary The specified pages should be printed. * @library /java/awt/regtesthelpers From cc0debb3d977cfd6cf8101047c27bbe83517d9d4 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Tue, 10 Mar 2026 14:43:30 +0000 Subject: [PATCH 012/168] 8363949: Incorrect jtreg header in MonitorWithDeadObjectTest.java Backport-of: c4fbfa21030c9a0e8a3e0eed1b0a0988eba08ddb --- .../jtreg/runtime/Monitor/MonitorWithDeadObjectTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/hotspot/jtreg/runtime/Monitor/MonitorWithDeadObjectTest.java b/test/hotspot/jtreg/runtime/Monitor/MonitorWithDeadObjectTest.java index 31aeaccf07a4..7f9b44a4a76b 100644 --- a/test/hotspot/jtreg/runtime/Monitor/MonitorWithDeadObjectTest.java +++ b/test/hotspot/jtreg/runtime/Monitor/MonitorWithDeadObjectTest.java @@ -32,20 +32,20 @@ */ /* - * @requires os.family != "windows" & os.family != "aix" * @test id=DetachThread + * @requires os.family != "windows" & os.family != "aix" * @run main/othervm/native MonitorWithDeadObjectTest 0 */ /* - * @requires os.family != "windows" & os.family != "aix" * @test id=DumpThreadsBeforeDetach + * @requires os.family != "windows" & os.family != "aix" * @run main/othervm/native MonitorWithDeadObjectTest 1 */ /* - * @requires os.family != "windows" & os.family != "aix" * @test id=DumpThreadsAfterDetach + * @requires os.family != "windows" & os.family != "aix" * @run main/othervm/native MonitorWithDeadObjectTest 2 */ From 17641faed605a4fcb6e401d89a791d0f6b2d18a9 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Tue, 10 Mar 2026 14:45:58 +0000 Subject: [PATCH 013/168] 8374506: Incorrect positioning of arrow icon in parent JMenu in Windows L&F Backport-of: 9a10cceeafa5d332aa571f0d62acf50032a597d4 --- .../swing/plaf/windows/WindowsMenuItemUI.java | 11 ++- .../LargeMenuTextArrowIconPosition.java | 92 +++++++++++++++++++ 2 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 test/jdk/javax/swing/JMenuItem/LargeMenuTextArrowIconPosition.java diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsMenuItemUI.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsMenuItemUI.java index 117d3b5fd08d..4eea860d8555 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsMenuItemUI.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsMenuItemUI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -255,6 +255,15 @@ static void paintMenuItem(WindowsMenuItemUIAccessor accessor, Graphics g, SwingUtilities3.paintAccText(g, lh, lr, disabledForeground, acceleratorSelectionForeground, acceleratorForeground); + if (lh.getCheckIcon() != null && lh.useCheckAndArrow()) { + Rectangle rect = lr.getArrowRect(); + if (menuItem.getComponentOrientation().isLeftToRight()) { + rect.x += lh.getAfterCheckIconGap(); + } else { + rect.x -= lh.getAfterCheckIconGap(); + } + lr.setArrowRect(rect); + } SwingUtilities3.paintArrowIcon(g, lh, lr, foreground); // Restore original graphics font and color diff --git a/test/jdk/javax/swing/JMenuItem/LargeMenuTextArrowIconPosition.java b/test/jdk/javax/swing/JMenuItem/LargeMenuTextArrowIconPosition.java new file mode 100644 index 000000000000..72512560cffa --- /dev/null +++ b/test/jdk/javax/swing/JMenuItem/LargeMenuTextArrowIconPosition.java @@ -0,0 +1,92 @@ +/* + * 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 8374506 + * @summary Verify if arrow icon positioning is correct in + * parent JMenu in Windows L&F + * @requires (os.family == "windows") + * @library /java/awt/regtesthelpers + * @build PassFailJFrame + * @run main/manual LargeMenuTextArrowIconPosition + */ + +import java.awt.BorderLayout; +import javax.swing.JCheckBoxMenuItem; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; +import javax.swing.UIManager; + +public class LargeMenuTextArrowIconPosition { + + private static final String INSTRUCTIONS = """ + A frame will be shown with a label. + Right click on the label. + + Check the arrow icon at the end of + "Really long Menu-Text" text. + If it overlaps with the menu text, + press Fail else press Pass."""; + + public static void main(String[] args) throws Throwable { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + + PassFailJFrame.builder() + .instructions(INSTRUCTIONS) + .columns(40) + .testUI(LargeMenuTextArrowIconPosition::createTestUI) + .build() + .awaitAndCheck(); + } + + private static JFrame createTestUI() { + + JFrame frame = new JFrame("LargeMenuTextArrowIcon"); + frame.setSize(300, 150); + frame.setLayout(new BorderLayout()); + + JPopupMenu popupMenu = new JPopupMenu(); + popupMenu.add(new JCheckBoxMenuItem("CheckBox On", true)); + popupMenu.add(new JCheckBoxMenuItem("CheckBox Icon On", + UIManager.getIcon("FileView.floppyDriveIcon"), true)); + popupMenu.add(new JCheckBoxMenuItem("CheckBox Icon Off", + UIManager.getIcon("FileView.floppyDriveIcon"), false)); + + JMenu menu = new JMenu("Really long Menu-Text"); + menu.add(new JMenuItem("Sub-MenuItem")); + menu.add(new JCheckBoxMenuItem("Sub-CheckBox On", true)); + + popupMenu.add(menu); + + JLabel lbl = new JLabel("Right click to invoke popupMenu"); + lbl.setComponentPopupMenu(popupMenu); + frame.add(lbl, BorderLayout.CENTER); + + return frame; + } + +} From 8790103083d901ec427e394ba13cd9e0a9be8502 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Tue, 10 Mar 2026 14:49:35 +0000 Subject: [PATCH 014/168] 8365625: Can't change accelerator colors in Windows L&F Backport-of: a1302e5fbc1e1b41bc0b334c2502e487fa42209f --- .../windows/WindowsCheckBoxMenuItemUI.java | 17 +- .../swing/plaf/windows/WindowsMenuItemUI.java | 46 ++--- .../swing/plaf/windows/WindowsMenuUI.java | 20 +- .../windows/WindowsRadioButtonMenuItemUI.java | 17 +- .../MenuItem/MenuItemAcceleratorColor.java | 193 ++++++++++++++++++ 5 files changed, 236 insertions(+), 57 deletions(-) create mode 100644 test/jdk/com/sun/java/swing/plaf/windows/MenuItem/MenuItemAcceleratorColor.java diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsCheckBoxMenuItemUI.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsCheckBoxMenuItemUI.java index 02054575d77d..2ce47e380667 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsCheckBoxMenuItemUI.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsCheckBoxMenuItemUI.java @@ -75,19 +75,20 @@ protected void paintBackground(Graphics g, JMenuItem menuItem, super.paintBackground(g, menuItem, bgColor); } - /** - * Paint MenuItem. - */ + @Override protected void paintMenuItem(Graphics g, JComponent c, Icon checkIcon, Icon arrowIcon, Color background, Color foreground, int defaultTextIconGap) { if (WindowsMenuItemUI.isVistaPainting()) { - WindowsMenuItemUI.paintMenuItem(accessor, g, c, checkIcon, - arrowIcon, background, foreground, - disabledForeground, acceleratorSelectionForeground, - acceleratorForeground, defaultTextIconGap, - menuItem, getPropertyPrefix()); + WindowsMenuItemUI.paintMenuItem(accessor, g, c, + checkIcon, arrowIcon, + background, foreground, + disabledForeground, + acceleratorSelectionForeground, + acceleratorForeground, + defaultTextIconGap, + menuItem, getPropertyPrefix()); return; } super.paintMenuItem(g, c, checkIcon, arrowIcon, background, diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsMenuItemUI.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsMenuItemUI.java index 4eea860d8555..5206d5575478 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsMenuItemUI.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsMenuItemUI.java @@ -29,16 +29,11 @@ import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; -import java.awt.Insets; import java.awt.Rectangle; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; -import java.util.Enumeration; -import javax.swing.AbstractButton; -import javax.swing.ButtonGroup; import javax.swing.ButtonModel; -import javax.swing.DefaultButtonModel; import javax.swing.Icon; import javax.swing.JComponent; import javax.swing.JMenu; @@ -132,27 +127,6 @@ public void propertyChange(PropertyChangeEvent e) { menuItem.addPropertyChangeListener(changeListener); } - protected void installDefaults() { - super.installDefaults(); - String prefix = getPropertyPrefix(); - - if (acceleratorSelectionForeground == null || - acceleratorSelectionForeground instanceof UIResource) { - acceleratorSelectionForeground = - UIManager.getColor(prefix + ".acceleratorSelectionForeground"); - } - if (acceleratorForeground == null || - acceleratorForeground instanceof UIResource) { - acceleratorForeground = - UIManager.getColor(prefix + ".acceleratorForeground"); - } - if (disabledForeground == null || - disabledForeground instanceof UIResource) { - disabledForeground = - UIManager.getColor(prefix + ".disabledForeground"); - } - } - /** * {@inheritDoc} */ @@ -165,15 +139,19 @@ protected void uninstallListeners() { changeListener = null; } + @Override protected void paintMenuItem(Graphics g, JComponent c, Icon checkIcon, Icon arrowIcon, Color background, Color foreground, int defaultTextIconGap) { if (WindowsMenuItemUI.isVistaPainting()) { - WindowsMenuItemUI.paintMenuItem(accessor, g, c, checkIcon, - arrowIcon, background, foreground, - disabledForeground, acceleratorSelectionForeground, - acceleratorForeground, defaultTextIconGap, menuItem, + WindowsMenuItemUI.paintMenuItem(accessor, g, c, + checkIcon, arrowIcon, + background, foreground, + disabledForeground, + acceleratorSelectionForeground, + acceleratorForeground, + defaultTextIconGap, menuItem, getPropertyPrefix()); return; } @@ -182,12 +160,16 @@ protected void paintMenuItem(Graphics g, JComponent c, } static void paintMenuItem(WindowsMenuItemUIAccessor accessor, Graphics g, - JComponent c, Icon checkIcon, Icon arrowIcon, + JComponent c, + Icon checkIcon, Icon arrowIcon, Color background, Color foreground, Color disabledForeground, Color acceleratorSelectionForeground, Color acceleratorForeground, - int defaultTextIconGap, JMenuItem menuItem, String prefix) { + int defaultTextIconGap, JMenuItem menuItem, + String prefix) { + assert c == menuItem : "menuItem passed as 'c' must be the same"; + // Save original graphics font and color Font holdf = g.getFont(); Color holdc = g.getColor(); diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsMenuUI.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsMenuUI.java index 1476c6fc152b..8208c77cdb82 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsMenuUI.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsMenuUI.java @@ -130,18 +130,20 @@ protected void installDefaults() { hotTrackingOn = (obj instanceof Boolean) ? (Boolean)obj : true; } - /** - * Paint MenuItem. - */ + @Override protected void paintMenuItem(Graphics g, JComponent c, - Icon checkIcon, Icon arrowIcon, - Color background, Color foreground, - int defaultTextIconGap) { + Icon checkIcon, Icon arrowIcon, + Color background, Color foreground, + int defaultTextIconGap) { + assert c == menuItem : "menuItem passed as 'c' must be the same"; if (WindowsMenuItemUI.isVistaPainting()) { - WindowsMenuItemUI.paintMenuItem(accessor, g, c, checkIcon, arrowIcon, + WindowsMenuItemUI.paintMenuItem(accessor, g, c, + checkIcon, arrowIcon, background, foreground, - disabledForeground, acceleratorSelectionForeground, - acceleratorForeground, defaultTextIconGap, menuItem, + disabledForeground, + acceleratorSelectionForeground, + acceleratorForeground, + defaultTextIconGap, menuItem, getPropertyPrefix()); return; } diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsRadioButtonMenuItemUI.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsRadioButtonMenuItemUI.java index 628a4be16378..c9d314a3a88a 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsRadioButtonMenuItemUI.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsRadioButtonMenuItemUI.java @@ -75,19 +75,20 @@ protected void paintBackground(Graphics g, JMenuItem menuItem, super.paintBackground(g, menuItem, bgColor); } - /** - * Paint MenuItem. - */ + @Override protected void paintMenuItem(Graphics g, JComponent c, Icon checkIcon, Icon arrowIcon, Color background, Color foreground, int defaultTextIconGap) { if (WindowsMenuItemUI.isVistaPainting()) { - WindowsMenuItemUI.paintMenuItem(accessor, g, c, checkIcon, - arrowIcon, background, foreground, - disabledForeground, acceleratorSelectionForeground, - acceleratorForeground, defaultTextIconGap, - menuItem, getPropertyPrefix()); + WindowsMenuItemUI.paintMenuItem(accessor, g, c, + checkIcon, arrowIcon, + background, foreground, + disabledForeground, + acceleratorSelectionForeground, + acceleratorForeground, + defaultTextIconGap, + menuItem, getPropertyPrefix()); return; } super.paintMenuItem(g, c, checkIcon, arrowIcon, background, diff --git a/test/jdk/com/sun/java/swing/plaf/windows/MenuItem/MenuItemAcceleratorColor.java b/test/jdk/com/sun/java/swing/plaf/windows/MenuItem/MenuItemAcceleratorColor.java new file mode 100644 index 000000000000..f098be4fdbd6 --- /dev/null +++ b/test/jdk/com/sun/java/swing/plaf/windows/MenuItem/MenuItemAcceleratorColor.java @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; + +import javax.swing.Box; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.KeyStroke; +import javax.swing.UIManager; + +import static javax.swing.BorderFactory.createEmptyBorder; + +/* + * @test id=windows + * @bug 8348760 8365375 8365389 8365625 + * @requires (os.family == "windows") + * @summary Verify that Windows Look & Feel allows changing + * accelerator colors + * @library /java/awt/regtesthelpers + * @build PassFailJFrame + * @run main/manual MenuItemAcceleratorColor + */ + +/* + * @test id=classic + * @bug 8348760 8365375 8365389 8365625 + * @requires (os.family == "windows") + * @summary Verify that Windows Classic Look & Feel allows changing + * accelerator colors + * @library /java/awt/regtesthelpers + * @build PassFailJFrame + * @run main/manual MenuItemAcceleratorColor classic + */ +public final class MenuItemAcceleratorColor { + private static final String INSTRUCTIONS = + "Click the Menu to open it.\n" + + "\n" + + "Verify that the first and the last menu items render " + + "their accelerators using the default colors, the color " + + "should match that of the menu item itself in regular and " + + "selected states.\n" + + "\n" + + "Verify that the second menu item renders its accelerator " + + "with green and that the color changes to red when selected.\n" + + "\n" + + "Verify that the third menu item renders its accelerator " + + "with magenta and yellow correspondingly.\n" + + "\n" + + "Verify that only the fifth menu item renders its accelerator " + + "with blue; both the fourth and sixth should render their " + + "accelerator with a shade of gray.\n" + + "\n" + + "If the above conditions are satisfied, press the Pass button; " + + "otherwise, press the Fail button."; + + public static void main(String[] args) throws Exception { + UIManager.setLookAndFeel((args.length > 0 && "classic".equals(args[0])) + ? "com.sun.java.swing.plaf.windows.WindowsClassicLookAndFeel" + : "com.sun.java.swing.plaf.windows.WindowsLookAndFeel"); + + PassFailJFrame.builder() + .instructions(INSTRUCTIONS) + .rows(20) + .columns(60) + .testUI(MenuItemAcceleratorColor::createUI) + .build() + .awaitAndCheck(); + } + + private static Box createInfoPanel() { + Box box = Box.createVerticalBox(); + box.add(new JLabel("Look and Feel: " + + UIManager.getLookAndFeel() + .getName())); + box.add(new JLabel("Java version: " + + System.getProperty("java.runtime.version"))); + return box; + } + + private static JFrame createUI() { + JPanel content = new JPanel(new BorderLayout()); + content.setBorder(createEmptyBorder(8, 8, 8, 8)); + content.add(createInfoPanel(), + BorderLayout.SOUTH); + + JFrame frame = new JFrame("Accelerator colors in Windows L&F"); + frame.setJMenuBar(createMenuBar()); + frame.add(content, BorderLayout.CENTER); + frame.setSize(350, 370); + return frame; + } + + private static JMenuBar createMenuBar() { + JMenuItem first = new JMenuItem("First menu item"); + first.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F, + InputEvent.CTRL_DOWN_MASK)); + + // Modify colors for accelerator rendering + Color acceleratorForeground = UIManager.getColor("MenuItem.acceleratorForeground"); + Color acceleratorSelectionForeground = UIManager.getColor("MenuItem.acceleratorSelectionForeground"); + UIManager.put("MenuItem.acceleratorForeground", Color.GREEN); + UIManager.put("MenuItem.acceleratorSelectionForeground", Color.RED); + + JMenuItem second = new JMenuItem("Second menu item"); + second.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, + InputEvent.SHIFT_DOWN_MASK + | InputEvent.CTRL_DOWN_MASK)); + + UIManager.put("MenuItem.acceleratorForeground", Color.MAGENTA); + UIManager.put("MenuItem.acceleratorSelectionForeground", Color.YELLOW); + JMenuItem third = new JMenuItem("Third menu item"); + third.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_T, + InputEvent.ALT_DOWN_MASK)); + + // Restore colors + UIManager.put("MenuItem.acceleratorForeground", acceleratorForeground); + UIManager.put("MenuItem.acceleratorSelectionForeground", acceleratorSelectionForeground); + + + // Disabled foreground + JMenuItem fourth = new JMenuItem("Fourth menu item"); + fourth.setEnabled(false); + fourth.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F, + InputEvent.CTRL_DOWN_MASK)); + + Color disabledForeground = UIManager.getColor("MenuItem.disabledForeground"); + UIManager.put("MenuItem.disabledForeground", Color.BLUE); + + JMenuItem fifth = new JMenuItem("Fifth menu item"); + fifth.setEnabled(false); + fifth.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F, + InputEvent.CTRL_DOWN_MASK + | InputEvent.SHIFT_DOWN_MASK)); + + // Restore disabled foreground + UIManager.put("MenuItem.disabledForeground", disabledForeground); + + JMenuItem sixth = new JMenuItem("Sixth menu item"); + sixth.setEnabled(false); + sixth.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X, + InputEvent.CTRL_DOWN_MASK + | InputEvent.ALT_DOWN_MASK)); + + + JMenuItem quit = new JMenuItem("Quit"); + quit.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, + InputEvent.CTRL_DOWN_MASK)); + + JMenu menu = new JMenu("Menu"); + menu.add(first); + menu.add(second); + menu.add(third); + menu.addSeparator(); + menu.add(fourth); + menu.add(fifth); + menu.add(sixth); + menu.addSeparator(); + menu.add(quit); + + JMenuBar menuBar = new JMenuBar(); + menuBar.add(menu); + + return menuBar; + } +} From 119dbe1e3a055479f7e658918d876d19b90fdd58 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Tue, 10 Mar 2026 14:53:54 +0000 Subject: [PATCH 015/168] 8376151: Test javax/swing/JFileChooser/4966171/bug4966171.java is failing with OOME Backport-of: 12570be64ae2114587e6de4ef79f79be961023b9 --- test/jdk/javax/swing/JFileChooser/4966171/bug4966171.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/jdk/javax/swing/JFileChooser/4966171/bug4966171.java b/test/jdk/javax/swing/JFileChooser/4966171/bug4966171.java index f3b3ba684e6d..27f26d570bed 100644 --- a/test/jdk/javax/swing/JFileChooser/4966171/bug4966171.java +++ b/test/jdk/javax/swing/JFileChooser/4966171/bug4966171.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -50,8 +50,8 @@ public static void main(String[] args) throws Exception { } private static void test() { - // Will run the test no more than 10 seconds per L&F - long endtime = System.nanoTime() + TimeUnit.SECONDS.toNanos(10); + // Will run the test no more than 5 seconds per L&F + long endtime = System.nanoTime() + TimeUnit.SECONDS.toNanos(5); while (System.nanoTime() < endtime) { try { var byteOut = new ByteArrayOutputStream(); From b40fbd7ff2e0b78e2359ddba5ef0943bcd904574 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Tue, 10 Mar 2026 14:54:34 +0000 Subject: [PATCH 016/168] 8365379: SU3.applyInsets may produce wrong results Backport-of: 4b544f93ad0e2beae4c80e060cae727d143151ac --- .../com/sun/java/swing/SwingUtilities3.java | 10 ++- .../swing/plaf/basic/BasicMenuItemUI.java | 6 +- .../swing/plaf/synth/SynthGraphicsUtils.java | 14 +--- .../SwingUtilities3/ApplyInsetsTest.java | 65 +++++++++++++++++++ 4 files changed, 76 insertions(+), 19 deletions(-) create mode 100644 test/jdk/com/sun/java/swing/SwingUtilities3/ApplyInsetsTest.java diff --git a/src/java.desktop/share/classes/com/sun/java/swing/SwingUtilities3.java b/src/java.desktop/share/classes/com/sun/java/swing/SwingUtilities3.java index dffc0c8f2cd9..91bf0edc5479 100644 --- a/src/java.desktop/share/classes/com/sun/java/swing/SwingUtilities3.java +++ b/src/java.desktop/share/classes/com/sun/java/swing/SwingUtilities3.java @@ -152,11 +152,15 @@ public static RepaintManager getDelegateRepaintManager(Component } public static void applyInsets(Rectangle rect, Insets insets) { + applyInsets(rect, insets, true); + } + + public static void applyInsets(Rectangle rect, Insets insets, boolean leftToRight) { if (insets != null) { - rect.x += insets.left; + rect.x += leftToRight ? insets.left : insets.right; rect.y += insets.top; - rect.width -= (insets.right + rect.x); - rect.height -= (insets.bottom + rect.y); + rect.width -= (insets.left + insets.right); + rect.height -= (insets.top + insets.bottom); } } diff --git a/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicMenuItemUI.java b/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicMenuItemUI.java index d361906b291f..348d58bab212 100644 --- a/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicMenuItemUI.java +++ b/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicMenuItemUI.java @@ -682,7 +682,7 @@ protected void paintMenuItem(Graphics g, JComponent c, g.setFont(mi.getFont()); Rectangle viewRect = new Rectangle(0, 0, mi.getWidth(), mi.getHeight()); - applyInsets(viewRect, mi.getInsets()); + SwingUtilities3.applyInsets(viewRect, mi.getInsets()); MenuItemLayoutHelper lh = new MenuItemLayoutHelper(mi, checkIcon, arrowIcon, viewRect, defaultTextIconGap, acceleratorDelimiter, @@ -741,10 +741,6 @@ private void paintArrowIcon(Graphics g, MenuItemLayoutHelper lh, SwingUtilities3.paintArrowIcon(g, lh, lr, foreground); } - private void applyInsets(Rectangle rect, Insets insets) { - SwingUtilities3.applyInsets(rect, insets); - } - /** * Draws the background of the menu item. * diff --git a/src/java.desktop/share/classes/javax/swing/plaf/synth/SynthGraphicsUtils.java b/src/java.desktop/share/classes/javax/swing/plaf/synth/SynthGraphicsUtils.java index 95a9aed981a3..0a0b25f9383b 100644 --- a/src/java.desktop/share/classes/javax/swing/plaf/synth/SynthGraphicsUtils.java +++ b/src/java.desktop/share/classes/javax/swing/plaf/synth/SynthGraphicsUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -41,6 +41,7 @@ import javax.swing.plaf.basic.BasicHTML; import javax.swing.text.View; +import com.sun.java.swing.SwingUtilities3; import sun.swing.MenuItemLayoutHelper; import sun.swing.SwingUtilities2; @@ -552,15 +553,6 @@ static Dimension getPreferredMenuItemSize(SynthContext context, return result; } - static void applyInsets(Rectangle rect, Insets insets, boolean leftToRight) { - if (insets != null) { - rect.x += (leftToRight ? insets.left : insets.right); - rect.y += insets.top; - rect.width -= (leftToRight ? insets.right : insets.left) + rect.x; - rect.height -= (insets.bottom + rect.y); - } - } - static void paint(SynthContext context, SynthContext accContext, Graphics g, Icon checkIcon, Icon arrowIcon, String acceleratorDelimiter, int defaultTextIconGap, String propertyPrefix) { @@ -570,7 +562,7 @@ static void paint(SynthContext context, SynthContext accContext, Graphics g, Rectangle viewRect = new Rectangle(0, 0, mi.getWidth(), mi.getHeight()); boolean leftToRight = SynthLookAndFeel.isLeftToRight(mi); - applyInsets(viewRect, mi.getInsets(), leftToRight); + SwingUtilities3.applyInsets(viewRect, mi.getInsets(), leftToRight); SynthMenuItemLayoutHelper lh = new SynthMenuItemLayoutHelper( context, accContext, mi, checkIcon, arrowIcon, viewRect, diff --git a/test/jdk/com/sun/java/swing/SwingUtilities3/ApplyInsetsTest.java b/test/jdk/com/sun/java/swing/SwingUtilities3/ApplyInsetsTest.java new file mode 100644 index 000000000000..047eaff66d4a --- /dev/null +++ b/test/jdk/com/sun/java/swing/SwingUtilities3/ApplyInsetsTest.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.awt.Insets; +import java.awt.Rectangle; + +import com.sun.java.swing.SwingUtilities3; + +/* + * @test + * @bug 8365379 + * @summary Verify SwingUtilities3 insets return correct result + * independent of initial values + * @modules java.desktop/com.sun.java.swing + * @run main ApplyInsetsTest + */ + +public class ApplyInsetsTest { + public static void main(String[] args) { + Rectangle rect = new Rectangle(10, 20, 60, 60); + Insets insets = new Insets(5, 10, 15, 20); + Rectangle expected = + new Rectangle(rect.x + insets.left, + rect.y + insets.top, + rect.width - (insets.left + insets.right), + rect.height - (insets.top + insets.bottom)); + + SwingUtilities3.applyInsets(rect, insets); + if (!rect.equals(expected)) { + throw new RuntimeException("Test failed: expected " + expected + + " but got " + rect); + } + + // Right to left test + rect.setRect(10, 20, 60, 60); + expected.x = rect.x + insets.right; + SwingUtilities3.applyInsets(rect, insets, false); + if (!rect.equals(expected)) { + throw new RuntimeException("Right to left test failed: expected " + + expected + " but got " + rect); + } + + System.out.println("Test passed."); + } +} From c9ef994276e96ba1fc406dd7e1f17de6a141460f Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Tue, 10 Mar 2026 14:58:25 +0000 Subject: [PATCH 017/168] 8325482: Test that distinct seeds produce distinct traces for compiler stress flags Reviewed-by: phh Backport-of: d25b9befe0a462b9785502806ad14e0a5f6b4320 --- .../jtreg/compiler/debug/TestStress.java | 12 +- .../debug/TestStressDistinctSeed.java | 118 ++++++++++++++++++ 2 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 test/hotspot/jtreg/compiler/debug/TestStressDistinctSeed.java diff --git a/test/hotspot/jtreg/compiler/debug/TestStress.java b/test/hotspot/jtreg/compiler/debug/TestStress.java index 6678d09e6499..7bb020e188de 100644 --- a/test/hotspot/jtreg/compiler/debug/TestStress.java +++ b/test/hotspot/jtreg/compiler/debug/TestStress.java @@ -71,7 +71,17 @@ static String macroExpansionTrace(int stressSeed) throws Exception { static void sum(int n) { int acc = 0; - for (int i = 0; i < n; i++) acc += i; + int[] arr1 = new int[n]; + int[] arr2 = new int[n]; + int[] arr3 = new int[n]; + int[] arr4 = new int[n]; + for (int i = 0; i < n; i++) { + acc += i; + arr1[i] = i; + arr2[i] = acc; + arr3[i] = i * n; + arr4[i] = acc * n; + } System.out.println(acc); } diff --git a/test/hotspot/jtreg/compiler/debug/TestStressDistinctSeed.java b/test/hotspot/jtreg/compiler/debug/TestStressDistinctSeed.java new file mode 100644 index 000000000000..c91326b1fc17 --- /dev/null +++ b/test/hotspot/jtreg/compiler/debug/TestStressDistinctSeed.java @@ -0,0 +1,118 @@ +/* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* This code is free software; you can redistribute it and/or modify it +* under the terms of the GNU General Public License version 2 only, as +* published by the Free Software Foundation. +* +* This code is distributed in the hope that it will be useful, but WITHOUT +* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +* version 2 for more details (a copy is included in the LICENSE file that +* accompanied this code). +* +* You should have received a copy of the GNU General Public License version +* 2 along with this work; if not, write to the Free Software Foundation, +* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +* +* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +* or visit www.oracle.com if you need additional information or have any +* questions. +*/ + +package compiler.debug; + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; +import jdk.test.lib.Asserts; +import java.util.Set; +import java.util.HashSet; +import java.util.Arrays; + +/* + * @test + * @key stress randomness + * @requires vm.debug == true & vm.compiler2.enabled & vm.flagless + * @summary Tests that stress compilations with the N different seeds yield different + * IGVN, CCP, and macro expansion traces. + * @library /test/lib / + * @run driver compiler.debug.TestStressDistinctSeed + */ + +public class TestStressDistinctSeed { + + private static int counter = 0; + + static String phaseTrace(String stressOption, String traceOption, + int stressSeed) throws Exception { + String className = TestStressDistinctSeed.class.getName(); + String[] procArgs = { + "-Xcomp", "-XX:-TieredCompilation", "-XX:-Inline", "-XX:+CICountNative", + "-XX:CompileOnly=" + className + "::sum", "-XX:" + traceOption, + "-XX:+" + stressOption, "-XX:StressSeed=" + stressSeed, + className, "5" }; + ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(procArgs); + OutputAnalyzer out = new OutputAnalyzer(pb.start()); + out.shouldHaveExitValue(0); + return out.getStdout(); + } + + static String igvnTrace(int stressSeed) throws Exception { + return phaseTrace("StressIGVN", "+TraceIterativeGVN", stressSeed); + } + + static String ccpTrace(int stressSeed) throws Exception { + return phaseTrace("StressCCP", "+TracePhaseCCP", stressSeed); + } + + static String macroExpansionTrace(int stressSeed) throws Exception { + return phaseTrace("StressMacroExpansion", + "CompileCommand=PrintIdealPhase,*::*,AFTER_MACRO_EXPANSION_STEP", + stressSeed); + } + + static void sum(int n) { + int[] arr1 = new int[n]; + for (int i = 0; i < n; i++) { + synchronized (TestStressDistinctSeed.class) { + counter += i; + arr1[i] = counter; + } + } + System.out.println(counter); + } + + public static void main(String[] args) throws Exception { + Set igvnTraceSet = new HashSet<>(); + Set ccpTraceSet = new HashSet<>(); + Set macroExpansionTraceSet = new HashSet<>(); + String igvnTraceOutput, ccpTraceOutput, macroExpansionTraceOutput; + if (args.length == 0) { + for (int s = 0; s < 5; s++) { + igvnTraceOutput = igvnTrace(s); + ccpTraceOutput = ccpTrace(s); + macroExpansionTraceOutput = macroExpansionTrace(s); + // Test same seed produce same result to test that different traces come from different seed and + // not indeterminism with the test. + Asserts.assertEQ(igvnTraceOutput, igvnTrace(s), + "got different IGVN traces for the same seed"); + Asserts.assertEQ(ccpTraceOutput, ccpTrace(s), + "got different CCP traces for the same seed"); + Asserts.assertEQ(macroExpansionTraceOutput, macroExpansionTrace(s), + "got different macro expansion traces for the same seed"); + + igvnTraceSet.add(igvnTraceOutput); + ccpTraceSet.add(ccpTraceOutput); + macroExpansionTraceSet.add(macroExpansionTraceOutput); + } + Asserts.assertGT(igvnTraceSet.size(), 1, + "got same IGVN traces for 5 different seeds"); + Asserts.assertGT(ccpTraceSet.size(), 1, + "got same CCP traces for 5 different seeds"); + Asserts.assertGT(macroExpansionTraceSet.size(), 1, + "got same macro expansion traces for 5 different seeds"); + } else if (args.length > 0) { + sum(Integer.parseInt(args[0])); + } + } +} From 652b24a4459839b123ff1323f98dd0fb06c92feb Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Tue, 10 Mar 2026 14:59:10 +0000 Subject: [PATCH 018/168] 8068293: [TEST_BUG] Test closed/com/sun/java/swing/plaf/motif/InternalFrame/4150591/bug4150591.java fails with GTKLookAndFeel Backport-of: 26eed3b61e4987a2998f941d7d26790493850612 --- test/jdk/javax/swing/plaf/motif/bug4150591.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/jdk/javax/swing/plaf/motif/bug4150591.java b/test/jdk/javax/swing/plaf/motif/bug4150591.java index 66c668a441c3..f3614908cfff 100644 --- a/test/jdk/javax/swing/plaf/motif/bug4150591.java +++ b/test/jdk/javax/swing/plaf/motif/bug4150591.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,6 +23,7 @@ import com.sun.java.swing.plaf.motif.MotifInternalFrameTitlePane; import javax.swing.JInternalFrame; +import javax.swing.UIManager; /* * @test @@ -36,7 +37,8 @@ */ public class bug4150591 { - public static void main(String[] args) { + public static void main(String[] args) throws Exception { + UIManager.setLookAndFeel("com.sun.java.swing.plaf.motif.MotifLookAndFeel"); MotifInternalFrameTitlePane mtp = new MotifInternalFrameTitlePane(new JInternalFrame()); } } From 0988694146b9a63ab360536b94ba87938498ed28 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Tue, 10 Mar 2026 14:59:34 +0000 Subject: [PATCH 019/168] 8351010: Test java/io/File/GetXSpace.java failed: / usable space 56380809216 > free space 14912244940 Backport-of: 509105761492ced0ecdc91aae464dcd016e2a4d7 --- test/jdk/java/io/File/GetXSpace.java | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/test/jdk/java/io/File/GetXSpace.java b/test/jdk/java/io/File/GetXSpace.java index 246835d03db2..e61880edb2c7 100644 --- a/test/jdk/java/io/File/GetXSpace.java +++ b/test/jdk/java/io/File/GetXSpace.java @@ -176,11 +176,11 @@ private static void compare(Space s) { long fs = f.getFreeSpace(); long us = f.getUsableSpace(); - // Verify inequalities us <= fs <= ts (JDK-8349092) + // Verify inequalities us <= ts and fs <= ts (JDK-8349092) if (fs > ts) throw new RuntimeException(f + " free space " + fs + " > total space " + ts); - if (us > fs) - throw new RuntimeException(f + " usable space " + us + " > free space " + fs); + if (us > ts) + throw new RuntimeException(f + " usable space " + us + " > total space " + ts); out.format("%s (%d):%n", s.name(), s.size()); String fmt = " %-4s total = %12d free = %12d usable = %12d%n"; @@ -269,15 +269,9 @@ private static void compare(Space s) { pass(); } - // usable space <= free space - if (us > s.free()) { - // free and usable change dynamically - System.err.println("Warning: us > s.free()"); - if (1.0 - Math.abs((double)s.free()/(double)us) > 0.01) { - fail(s.name() + " usable vs. free space", us, ">", s.free()); - } else { - pass(); - } + // usable space <= total space + if (us > s.total()) { + fail(s.name() + " usable vs. total space", us, ">", s.total()); } else { pass(); } From 902d17c627e172d7d5514a7debc1619437f14ee6 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Tue, 10 Mar 2026 15:00:05 +0000 Subject: [PATCH 020/168] 8373690: Unexpected Keystore message using jdk.crypto.disabledAlgorithms Backport-of: e92726c352f2d9e9ccb074441d9c09eef781a492 --- .../share/classes/java/security/KeyStore.java | 16 ++++-- .../security/KeyStore/DisabledKnownType.java | 53 +++++++++++++++++++ 2 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 test/jdk/java/security/KeyStore/DisabledKnownType.java diff --git a/src/java.base/share/classes/java/security/KeyStore.java b/src/java.base/share/classes/java/security/KeyStore.java index f477110a46c3..561758f09379 100644 --- a/src/java.base/share/classes/java/security/KeyStore.java +++ b/src/java.base/share/classes/java/security/KeyStore.java @@ -1799,6 +1799,7 @@ private static final KeyStore getInstance(File file, char[] password, } KeyStore keystore = null; + String matched = null; try (DataInputStream dataStream = new DataInputStream( @@ -1822,8 +1823,10 @@ private static final KeyStore getInstance(File file, char[] password, if (CryptoAlgorithmConstraints.permits( "KEYSTORE", ksAlgo)) { keystore = new KeyStore(impl, p, ksAlgo); - break; + } else { + matched = ksAlgo; } + break; } } catch (NoSuchAlgorithmException e) { // ignore @@ -1853,9 +1856,14 @@ private static final KeyStore getInstance(File file, char[] password, return keystore; } } - - throw new KeyStoreException("Unrecognized keystore format. " - + "Please load it with a specified type"); + if (matched == null) { + throw new KeyStoreException("Unrecognized keystore format. " + + "Please load it with a specified type"); + } else { + throw new KeyStoreException("Keystore format " + + matched + + " disabled by jdk.crypto.disabledAlgorithms property"); + } } /** diff --git a/test/jdk/java/security/KeyStore/DisabledKnownType.java b/test/jdk/java/security/KeyStore/DisabledKnownType.java new file mode 100644 index 000000000000..70b7228f4c2e --- /dev/null +++ b/test/jdk/java/security/KeyStore/DisabledKnownType.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8373690 + * @summary verify that the exception message indicates the keystore type + * when the type is disabled instead of being unrecognized + * @run main/othervm -Djdk.crypto.disabledAlgorithms=KeyStore.PKCS12 DisabledKnownType + */ + +import java.security.KeyStore; +import java.security.KeyStoreException; + +public class DisabledKnownType { + public static void main(String[] args) throws Exception { + String cacertsPath = System.getProperty("java.home") + + "/lib/security/cacerts"; + try { + KeyStore ks = KeyStore.getInstance(new java.io.File(cacertsPath), + "changeit".toCharArray()); + throw new RuntimeException("Expected KeyStoreException not thrown"); + } catch (KeyStoreException kse) { + if (kse.getMessage().contains("PKCS12")) { + System.out.println("Passed: expected ex thrown: " + kse); + } else { + // pass it up + throw kse; + } + } + } +} + From f2b678f95a46bb8b5484206810b226f1bbb4267d Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Tue, 10 Mar 2026 18:51:21 +0000 Subject: [PATCH 021/168] 8370939: C2: SIGSEGV in SafePointNode::verify_input when processing MH call from Compile::process_late_inline_calls_no_inline() Backport-of: 2735140147b159d3a3238804f221db4f835ef744 --- src/hotspot/share/opto/callGenerator.cpp | 3 +- src/hotspot/share/opto/callnode.cpp | 1 - src/hotspot/share/opto/callnode.hpp | 7 +- src/hotspot/share/opto/compile.cpp | 4 +- src/hotspot/share/opto/compile.hpp | 9 +- .../inlining/TestLateMHClonedCallNode.java | 110 ++++++++++++++++++ 6 files changed, 122 insertions(+), 12 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/inlining/TestLateMHClonedCallNode.java diff --git a/src/hotspot/share/opto/callGenerator.cpp b/src/hotspot/share/opto/callGenerator.cpp index e0ad2f00ab57..fa78fa73e37c 100644 --- a/src/hotspot/share/opto/callGenerator.cpp +++ b/src/hotspot/share/opto/callGenerator.cpp @@ -424,7 +424,6 @@ bool LateInlineMHCallGenerator::do_late_inline_check(Compile* C, JVMState* jvms) } assert(!cg->is_late_inline() || cg->is_mh_late_inline() || AlwaysIncrementalInline || StressIncrementalInlining, "we're doing late inlining"); _inline_cg = cg; - C->dec_number_of_mh_late_inlines(); return true; } else { // Method handle call which has a constant appendix argument should be either inlined or replaced with a direct call @@ -436,7 +435,7 @@ bool LateInlineMHCallGenerator::do_late_inline_check(Compile* C, JVMState* jvms) CallGenerator* CallGenerator::for_mh_late_inline(ciMethod* caller, ciMethod* callee, bool input_not_const) { assert(IncrementalInlineMH, "required"); - Compile::current()->inc_number_of_mh_late_inlines(); + Compile::current()->mark_has_mh_late_inlines(); CallGenerator* cg = new LateInlineMHCallGenerator(caller, callee, input_not_const); return cg; } diff --git a/src/hotspot/share/opto/callnode.cpp b/src/hotspot/share/opto/callnode.cpp index f0fa73f11ce7..6f13ce0f809a 100644 --- a/src/hotspot/share/opto/callnode.cpp +++ b/src/hotspot/share/opto/callnode.cpp @@ -1145,7 +1145,6 @@ Node* CallStaticJavaNode::Ideal(PhaseGVN* phase, bool can_reshape) { assert(callee->has_member_arg(), "wrong type of call?"); if (in(TypeFunc::Parms + callee->arg_size() - 1)->Opcode() == Op_ConP) { register_for_late_inline(); - phase->C->inc_number_of_mh_late_inlines(); } } } else { diff --git a/src/hotspot/share/opto/callnode.hpp b/src/hotspot/share/opto/callnode.hpp index 2a95cb3b1f24..213fbda4e896 100644 --- a/src/hotspot/share/opto/callnode.hpp +++ b/src/hotspot/share/opto/callnode.hpp @@ -729,9 +729,10 @@ class CallNode : public SafePointNode { // for some macro nodes whose expansion does not have a safepoint on the fast path. virtual bool guaranteed_safepoint() { return true; } // For macro nodes, the JVMState gets modified during expansion. If calls - // use MachConstantBase, it gets modified during matching. So when cloning - // the node the JVMState must be deep cloned. Default is to shallow clone. - virtual bool needs_deep_clone_jvms(Compile* C) { return C->needs_deep_clone_jvms(); } + // use MachConstantBase, it gets modified during matching. If the call is + // late inlined, it also needs the full JVMState. So when cloning the + // node the JVMState must be deep cloned. Default is to shallow clone. + virtual bool needs_deep_clone_jvms(Compile* C) { return _generator != nullptr || C->needs_deep_clone_jvms(); } // Returns true if the call may modify n virtual bool may_modify(const TypeOopPtr* t_oop, PhaseValues* phase); diff --git a/src/hotspot/share/opto/compile.cpp b/src/hotspot/share/opto/compile.cpp index 8dc493956ae8..2b956dcb5d84 100644 --- a/src/hotspot/share/opto/compile.cpp +++ b/src/hotspot/share/opto/compile.cpp @@ -687,7 +687,7 @@ Compile::Compile(ciEnv* ci_env, ciMethod* target, int osr_bci, _boxing_late_inlines(comp_arena(), 2, 0, nullptr), _vector_reboxing_late_inlines(comp_arena(), 2, 0, nullptr), _late_inlines_pos(0), - _number_of_mh_late_inlines(0), + _has_mh_late_inlines(false), _oom(false), _replay_inline_data(nullptr), _inline_printer(this), @@ -957,7 +957,7 @@ Compile::Compile(ciEnv* ci_env, _igvn_worklist(nullptr), _types(nullptr), _node_hash(nullptr), - _number_of_mh_late_inlines(0), + _has_mh_late_inlines(false), _oom(false), _replay_inline_data(nullptr), _inline_printer(this), diff --git a/src/hotspot/share/opto/compile.hpp b/src/hotspot/share/opto/compile.hpp index 9b6d8db05b0e..134b930ada36 100644 --- a/src/hotspot/share/opto/compile.hpp +++ b/src/hotspot/share/opto/compile.hpp @@ -471,7 +471,9 @@ class Compile : public Phase { GrowableArray _vector_reboxing_late_inlines; // same but for vector reboxing operations int _late_inlines_pos; // Where in the queue should the next late inlining candidate go (emulate depth first inlining) - uint _number_of_mh_late_inlines; // number of method handle late inlining still pending + bool _has_mh_late_inlines; // Can there still be a method handle late inlining pending? + // false: there can't be one + // true: we've enqueued one at some point so there may still be one // "MemLimit" directive was specified and the memory limit was hit during compilation bool _oom; @@ -1087,9 +1089,8 @@ class Compile : public Phase { } } - void inc_number_of_mh_late_inlines() { _number_of_mh_late_inlines++; } - void dec_number_of_mh_late_inlines() { assert(_number_of_mh_late_inlines > 0, "_number_of_mh_late_inlines < 0 !"); _number_of_mh_late_inlines--; } - bool has_mh_late_inlines() const { return _number_of_mh_late_inlines > 0; } + void mark_has_mh_late_inlines() { _has_mh_late_inlines = true; } + bool has_mh_late_inlines() const { return _has_mh_late_inlines; } bool inline_incrementally_one(); void inline_incrementally_cleanup(PhaseIterGVN& igvn); diff --git a/test/hotspot/jtreg/compiler/inlining/TestLateMHClonedCallNode.java b/test/hotspot/jtreg/compiler/inlining/TestLateMHClonedCallNode.java new file mode 100644 index 000000000000..417fe60ebae5 --- /dev/null +++ b/test/hotspot/jtreg/compiler/inlining/TestLateMHClonedCallNode.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2025 IBM Corporation. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8370939 + * @summary C2: SIGSEGV in SafePointNode::verify_input when processing MH call from Compile::process_late_inline_calls_no_inline() + * @run main/othervm -XX:-BackgroundCompilation -XX:CompileOnly=TestLateMHClonedCallNode::test1 + * -XX:CompileOnly=TestLateMHClonedCallNode::test2 TestLateMHClonedCallNode + * @run main TestLateMHClonedCallNode + */ + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; + +public class TestLateMHClonedCallNode { + private static int field; + + public static void main(String[] args) throws Throwable { + for (int i = 0; i < 20_000; i++) { + test1(true); + test1(false); + test2(true); + test2(false); + } + } + + private static int test1(boolean flag) throws Throwable { + return inlined1(flag); + } + + private static int inlined1(boolean flag) throws Throwable { + MethodHandle mh = mh1; + for (int i = 0; i < 3; ++i) { + if (i > 1) { + mh = mh2; + } + } + int res = 0; + for (int i = 0; i < 2; i++) { + if (!flag) { + field = 42; + } + res += (int) mh.invokeExact(); + } + return res; + } + + private static int test2(boolean flag) throws Throwable { + int res = (int)unknownMh.invokeExact(); + return inlined2(flag); + } + + private static int inlined2(boolean flag) throws Throwable { + MethodHandle mh = mh1; + for (int i = 0; i < 3; ++i) { + if (i > 1) { + mh = mh2; + } + } + int res = 0; + for (int i = 0; i < 2; i++) { + if (!flag) { + field = 42; + } + res += (int) mh.invokeExact(); + } + return res; + } + + static final MethodHandle mh1; + static final MethodHandle mh2; + static MethodHandle unknownMh; + + static { + try { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + mh1 = lookup.findStatic(TestLateMHClonedCallNode.class, "method1", MethodType.methodType(int.class)); + mh2 = lookup.findStatic(TestLateMHClonedCallNode.class, "method2", MethodType.methodType(int.class)); + unknownMh = mh1; + } catch (NoSuchMethodException | IllegalAccessException e) { + e.printStackTrace(); + throw new RuntimeException("Method handle lookup failed"); + } + } + + static int method1() { return 0; } + static int method2() { return 42; } +} From bbd7c76e114fb619b382ae47006f9f03a4eff8be Mon Sep 17 00:00:00 2001 From: Ozan Cetin Date: Wed, 11 Mar 2026 14:23:22 +0000 Subject: [PATCH 022/168] 8370502: C2: segfault while adding node to IGVN worklist Backport-of: 7c6c34e150cf01cec5d166f6cbb8a649c75b0627 --- src/hotspot/share/opto/macro.cpp | 22 ++++---- .../c2/TestUnlockNodeNullMemprof.java | 53 +++++++++++++++++++ 2 files changed, 63 insertions(+), 12 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/c2/TestUnlockNodeNullMemprof.java diff --git a/src/hotspot/share/opto/macro.cpp b/src/hotspot/share/opto/macro.cpp index 99fedcdf8802..acf1dbabf198 100644 --- a/src/hotspot/share/opto/macro.cpp +++ b/src/hotspot/share/opto/macro.cpp @@ -2312,12 +2312,7 @@ void PhaseMacroExpand::expand_unlock_node(UnlockNode *unlock) { // No need for a null check on unlock // Make the merge point - Node *region; - Node *mem_phi; - - region = new RegionNode(3); - // create a Phi for the memory state - mem_phi = new PhiNode( region, Type::MEMORY, TypeRawPtr::BOTTOM); + Node* region = new RegionNode(3); FastUnlockNode *funlock = new FastUnlockNode( ctrl, obj, box ); funlock = transform_later( funlock )->as_FastUnlock(); @@ -2346,12 +2341,15 @@ void PhaseMacroExpand::expand_unlock_node(UnlockNode *unlock) { transform_later(region); _igvn.replace_node(_callprojs.fallthrough_proj, region); - Node *memproj = transform_later(new ProjNode(call, TypeFunc::Memory) ); - mem_phi->init_req(1, memproj ); - mem_phi->init_req(2, mem); - transform_later(mem_phi); - - _igvn.replace_node(_callprojs.fallthrough_memproj, mem_phi); + if (_callprojs.fallthrough_memproj != nullptr) { + // create a Phi for the memory state + Node* mem_phi = new PhiNode( region, Type::MEMORY, TypeRawPtr::BOTTOM); + Node* memproj = transform_later(new ProjNode(call, TypeFunc::Memory)); + mem_phi->init_req(1, memproj); + mem_phi->init_req(2, mem); + transform_later(mem_phi); + _igvn.replace_node(_callprojs.fallthrough_memproj, mem_phi); + } } void PhaseMacroExpand::expand_subtypecheck_node(SubTypeCheckNode *check) { diff --git a/test/hotspot/jtreg/compiler/c2/TestUnlockNodeNullMemprof.java b/test/hotspot/jtreg/compiler/c2/TestUnlockNodeNullMemprof.java new file mode 100644 index 000000000000..f86dc495fd29 --- /dev/null +++ b/test/hotspot/jtreg/compiler/c2/TestUnlockNodeNullMemprof.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8370502 + * @summary Do not segfault while adding node to IGVN worklist + * + * @run main/othervm -Xbatch ${test.main.class} + */ + +package compiler.c2; + +public class TestUnlockNodeNullMemprof { + public static void main(String[] args) { + int[] a = new int[0]; // test only valid when size is 0. + for (int i = 0; i < Integer.valueOf(10000); i++) // test only valid with boxed loop limit + try { + test(a); + } catch (ArrayIndexOutOfBoundsException e) { + } + } + + static void test(int[] a) { + for (int i = 0; i < 1;) { + a[i] = 0; + synchronized (TestUnlockNodeNullMemprof.class) { + } + for (int j = 0; Integer.valueOf(j) < 1;) + j = 0; + } + } +} From 969ea616e6b1829b6223cfa5558a23194a967e6c Mon Sep 17 00:00:00 2001 From: William Kemper Date: Thu, 12 Mar 2026 00:35:22 +0000 Subject: [PATCH 023/168] 8367708: GenShen: Reduce total evacuation burden Backport-of: c597384ad64c7107fba4e970aa435a141276b2fd --- .../heuristics/shenandoahOldHeuristics.cpp | 2 +- .../share/gc/shenandoah/shenandoahGeneration.cpp | 10 +++++----- .../shenandoahGenerationalEvacuationTask.cpp | 8 +++++++- .../share/gc/shenandoah/shenandoah_globals.hpp | 16 +++++++++++++++- 4 files changed, 28 insertions(+), 8 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.cpp index 2d0bbfd5e4a3..2361a50e76dc 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.cpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.cpp @@ -412,7 +412,7 @@ void ShenandoahOldHeuristics::prepare_for_old_collections() { size_t defrag_count = 0; size_t total_uncollected_old_regions = _last_old_region - _last_old_collection_candidate; - if (cand_idx > _last_old_collection_candidate) { + if ((ShenandoahGenerationalHumongousReserve > 0) && (cand_idx > _last_old_collection_candidate)) { // Above, we have added into the set of mixed-evacuation candidates all old-gen regions for which the live memory // that they contain is below a particular old-garbage threshold. Regions that were not selected for the collection // set hold enough live memory that it is not considered efficient (by "garbage-first standards") to compact these diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp index 7b3839dc1980..2fd1cebfa775 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp @@ -535,6 +535,8 @@ size_t ShenandoahGeneration::select_aged_regions(size_t old_available) { const size_t old_garbage_threshold = (ShenandoahHeapRegion::region_size_bytes() * ShenandoahOldGarbageThreshold) / 100; + const size_t pip_used_threshold = (ShenandoahHeapRegion::region_size_bytes() * ShenandoahGenerationalMinPIPUsage) / 100; + size_t old_consumed = 0; size_t promo_potential = 0; size_t candidates = 0; @@ -557,10 +559,8 @@ size_t ShenandoahGeneration::select_aged_regions(size_t old_available) { continue; } if (heap->is_tenurable(r)) { - if ((r->garbage() < old_garbage_threshold)) { - // This tenure-worthy region has too little garbage, so we do not want to expend the copying effort to - // reclaim the garbage; instead this region may be eligible for promotion-in-place to the - // old generation. + if ((r->garbage() < old_garbage_threshold) && (r->used() > pip_used_threshold)) { + // We prefer to promote this region in place because is has a small amount of garbage and a large usage. HeapWord* tams = ctx->top_at_mark_start(r); HeapWord* original_top = r->top(); if (!heap->is_concurrent_old_mark_in_progress() && tams == original_top) { @@ -586,7 +586,7 @@ size_t ShenandoahGeneration::select_aged_regions(size_t old_available) { // Else, we do not promote this region (either in place or by copy) because it has received new allocations. // During evacuation, we exclude from promotion regions for which age > tenure threshold, garbage < garbage-threshold, - // and get_top_before_promote() != tams + // used > pip_used_threshold, and get_top_before_promote() != tams } else { // Record this promotion-eligible candidate region. After sorting and selecting the best candidates below, // we may still decide to exclude this promotion-eligible region from the current collection set. If this diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.cpp index b538f7b1417e..3f0db4fb8eab 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.cpp @@ -150,7 +150,13 @@ void ShenandoahGenerationalEvacuationTask::maybe_promote_region(ShenandoahHeapRe // more garbage than ShenandoahOldGarbageThreshold, we'll promote by evacuation. If there is room for evacuation // in this cycle, the region will be in the collection set. If there is not room, the region will be promoted // by evacuation in some future GC cycle. - promote_humongous(r); + + // We do not promote primitive arrays because there's no performance penalty keeping them in young. When/if they + // become garbage, reclaiming the memory from young is much quicker and more efficient than reclaiming them from old. + oop obj = cast_to_oop(r->bottom()); + if (!obj->is_typeArray()) { + promote_humongous(r); + } } else if (r->is_regular() && (r->get_top_before_promote() != nullptr)) { // Likewise, we cannot put promote-in-place regions into the collection set because that would also trigger // the LRB to copy on reference fetch. diff --git a/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp b/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp index 6811c042e2ab..dc49de9a7629 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp @@ -34,6 +34,20 @@ range, \ constraint) \ \ + product(uintx, ShenandoahGenerationalMinPIPUsage, 30, EXPERIMENTAL, \ + "(Generational mode only) What percent of a heap region " \ + "should be used before we consider promoting a region in " \ + "place? Regions with less than this amount of used will " \ + "promoted by evacuation. A benefit of promoting in place " \ + "is that less work is required by the GC at the time the " \ + "region is promoted. A disadvantage of promoting in place " \ + "is that this introduces fragmentation of old-gen memory, " \ + "with old-gen regions scattered throughout the heap. Regions " \ + "that have been promoted in place may need to be evacuated at " \ + "a later time in order to compact old-gen memory to enable " \ + "future humongous allocations.") \ + range(0,100) \ + \ product(uintx, ShenandoahGenerationalHumongousReserve, 0, EXPERIMENTAL, \ "(Generational mode only) What percent of the heap should be " \ "reserved for humongous objects if possible. Old-generation " \ @@ -169,7 +183,7 @@ "collector accepts. In percents of heap region size.") \ range(0,100) \ \ - product(uintx, ShenandoahOldGarbageThreshold, 15, EXPERIMENTAL, \ + product(uintx, ShenandoahOldGarbageThreshold, 25, EXPERIMENTAL, \ "How much garbage an old region has to contain before it would " \ "be taken for collection.") \ range(0,100) \ From 08a14fd8b53b8cda92933339ba539fb5ff54fd12 Mon Sep 17 00:00:00 2001 From: Fei Yang Date: Thu, 12 Mar 2026 11:15:12 +0000 Subject: [PATCH 024/168] 8379464: Enable missing stack walking test via jtreg requires for RISC-V Backport-of: 58bf76adfd93303bdd78862ae3677b514754d34d --- .../jtreg/runtime/ErrorHandling/StackWalkNativeToJava.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/hotspot/jtreg/runtime/ErrorHandling/StackWalkNativeToJava.java b/test/hotspot/jtreg/runtime/ErrorHandling/StackWalkNativeToJava.java index 7d7deffdb6e3..7f25f20e8943 100644 --- a/test/hotspot/jtreg/runtime/ErrorHandling/StackWalkNativeToJava.java +++ b/test/hotspot/jtreg/runtime/ErrorHandling/StackWalkNativeToJava.java @@ -33,7 +33,7 @@ * @test StackWalkNativeToJava * @bug 8316309 * @summary Check that walking the stack works fine when going from C++ frame to Java frame. - * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64" + * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64" | os.arch=="riscv64" * @requires os.family != "windows" * @requires vm.flagless * @library /test/lib From 79f0f9ded514c763ad085abb5b156a81bf633c16 Mon Sep 17 00:00:00 2001 From: William Kemper Date: Thu, 12 Mar 2026 15:41:55 +0000 Subject: [PATCH 025/168] 8367450: Shenandoah: Log the composition of the collection set Backport-of: 4be4826ddb51c155eec3fe2923d891357f8d753b --- .../shenandoahGenerationalHeuristics.cpp | 74 ++--------- .../shenandoahGenerationalHeuristics.hpp | 3 +- .../heuristics/shenandoahGlobalHeuristics.cpp | 2 - .../heuristics/shenandoahHeuristics.cpp | 22 +--- .../heuristics/shenandoahYoungHeuristics.cpp | 2 - .../gc/shenandoah/shenandoahCollectionSet.cpp | 35 +++++ .../gc/shenandoah/shenandoahCollectionSet.hpp | 21 ++- .../shenandoahCollectionSet.inline.hpp | 8 +- .../gc/shenandoah/shenandoahEvacInfo.hpp | 120 ------------------ .../share/gc/shenandoah/shenandoahTrace.cpp | 36 +++--- .../share/gc/shenandoah/shenandoahTrace.hpp | 10 +- 11 files changed, 89 insertions(+), 244 deletions(-) delete mode 100644 src/hotspot/share/gc/shenandoah/shenandoahEvacInfo.hpp diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.cpp index dfae90402424..c7067b2e5abf 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.cpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.cpp @@ -26,7 +26,6 @@ #include "gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.hpp" #include "gc/shenandoah/shenandoahCollectionSet.hpp" #include "gc/shenandoah/shenandoahCollectorPolicy.hpp" -#include "gc/shenandoah/shenandoahEvacInfo.hpp" #include "gc/shenandoah/shenandoahGeneration.hpp" #include "gc/shenandoah/shenandoahGenerationalHeap.inline.hpp" #include "gc/shenandoah/shenandoahHeapRegion.inline.hpp" @@ -185,59 +184,16 @@ void ShenandoahGenerationalHeuristics::choose_collection_set(ShenandoahCollectio heap->shenandoah_policy()->record_mixed_cycle(); } - size_t cset_percent = (total_garbage == 0) ? 0 : (collection_set->garbage() * 100 / total_garbage); - size_t collectable_garbage = collection_set->garbage() + immediate_garbage; - size_t collectable_garbage_percent = (total_garbage == 0) ? 0 : (collectable_garbage * 100 / total_garbage); + collection_set->summarize(total_garbage, immediate_garbage, immediate_regions); - log_info(gc, ergo)("Collectable Garbage: %zu%s (%zu%%), " - "Immediate: %zu%s (%zu%%), %zu regions, " - "CSet: %zu%s (%zu%%), %zu regions", - - byte_size_in_proper_unit(collectable_garbage), - proper_unit_for_byte_size(collectable_garbage), - collectable_garbage_percent, - - byte_size_in_proper_unit(immediate_garbage), - proper_unit_for_byte_size(immediate_garbage), - immediate_percent, - immediate_regions, - - byte_size_in_proper_unit(collection_set->garbage()), - proper_unit_for_byte_size(collection_set->garbage()), - cset_percent, - collection_set->count()); - - if (collection_set->garbage() > 0) { - size_t young_evac_bytes = collection_set->get_young_bytes_reserved_for_evacuation(); - size_t promote_evac_bytes = collection_set->get_young_bytes_to_be_promoted(); - size_t old_evac_bytes = collection_set->get_old_bytes_reserved_for_evacuation(); - size_t total_evac_bytes = young_evac_bytes + promote_evac_bytes + old_evac_bytes; - log_info(gc, ergo)("Evacuation Targets: YOUNG: %zu%s, " - "PROMOTE: %zu%s, " - "OLD: %zu%s, " - "TOTAL: %zu%s", - byte_size_in_proper_unit(young_evac_bytes), proper_unit_for_byte_size(young_evac_bytes), - byte_size_in_proper_unit(promote_evac_bytes), proper_unit_for_byte_size(promote_evac_bytes), - byte_size_in_proper_unit(old_evac_bytes), proper_unit_for_byte_size(old_evac_bytes), - byte_size_in_proper_unit(total_evac_bytes), proper_unit_for_byte_size(total_evac_bytes)); - - ShenandoahEvacuationInformation evacInfo; - evacInfo.set_collection_set_regions(collection_set->count()); - evacInfo.set_collection_set_used_before(collection_set->used()); - evacInfo.set_collection_set_used_after(collection_set->live()); - evacInfo.set_collected_old(old_evac_bytes); - evacInfo.set_collected_promoted(promote_evac_bytes); - evacInfo.set_collected_young(young_evac_bytes); - evacInfo.set_regions_promoted_humongous(humongous_regions_promoted); - evacInfo.set_regions_promoted_regular(regular_regions_promoted_in_place); - evacInfo.set_regular_promoted_garbage(regular_regions_promoted_garbage); - evacInfo.set_regular_promoted_free(regular_regions_promoted_free); - evacInfo.set_regions_immediate(immediate_regions); - evacInfo.set_immediate_size(immediate_garbage); - evacInfo.set_free_regions(free_regions); - - ShenandoahTracer().report_evacuation_info(&evacInfo); - } + ShenandoahTracer::report_evacuation_info(collection_set, + free_regions, + humongous_regions_promoted, + regular_regions_promoted_in_place, + regular_regions_promoted_garbage, + regular_regions_promoted_free, + immediate_regions, + immediate_garbage); } @@ -268,15 +224,3 @@ size_t ShenandoahGenerationalHeuristics::add_preselected_regions_to_collection_s return cur_young_garbage; } -void ShenandoahGenerationalHeuristics::log_cset_composition(ShenandoahCollectionSet* cset) const { - size_t collected_old = cset->get_old_bytes_reserved_for_evacuation(); - size_t collected_promoted = cset->get_young_bytes_to_be_promoted(); - size_t collected_young = cset->get_young_bytes_reserved_for_evacuation(); - - log_info(gc, ergo)( - "Chosen CSet evacuates young: %zu%s (of which at least: %zu%s are to be promoted), " - "old: %zu%s", - byte_size_in_proper_unit(collected_young), proper_unit_for_byte_size(collected_young), - byte_size_in_proper_unit(collected_promoted), proper_unit_for_byte_size(collected_promoted), - byte_size_in_proper_unit(collected_old), proper_unit_for_byte_size(collected_old)); -} diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.hpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.hpp index 6708c63f0425..31c016bb4b7f 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.hpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.hpp @@ -51,9 +51,8 @@ class ShenandoahGenerationalHeuristics : public ShenandoahAdaptiveHeuristics { size_t add_preselected_regions_to_collection_set(ShenandoahCollectionSet* cset, const RegionData* data, size_t size) const; - - void log_cset_composition(ShenandoahCollectionSet* cset) const; }; #endif //SHARE_GC_SHENANDOAH_HEURISTICS_SHENANDOAHGENERATIONALHEURISTICS_HPP + diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.cpp index 331bd0405752..93f9b18ad9f5 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.cpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.cpp @@ -42,8 +42,6 @@ void ShenandoahGlobalHeuristics::choose_collection_set_from_regiondata(Shenandoa QuickSort::sort(data, (int) size, compare_by_garbage); choose_global_collection_set(cset, data, size, actual_free, 0 /* cur_young_garbage */); - - log_cset_composition(cset); } diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.cpp index b151a75e6e7e..c8a0c3dc5183 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.cpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.cpp @@ -153,27 +153,7 @@ void ShenandoahHeuristics::choose_collection_set(ShenandoahCollectionSet* collec choose_collection_set_from_regiondata(collection_set, candidates, cand_idx, immediate_garbage + free); } - size_t cset_percent = (total_garbage == 0) ? 0 : (collection_set->garbage() * 100 / total_garbage); - size_t collectable_garbage = collection_set->garbage() + immediate_garbage; - size_t collectable_garbage_percent = (total_garbage == 0) ? 0 : (collectable_garbage * 100 / total_garbage); - - log_info(gc, ergo)("Collectable Garbage: %zu%s (%zu%%), " - "Immediate: %zu%s (%zu%%), %zu regions, " - "CSet: %zu%s (%zu%%), %zu regions", - - byte_size_in_proper_unit(collectable_garbage), - proper_unit_for_byte_size(collectable_garbage), - collectable_garbage_percent, - - byte_size_in_proper_unit(immediate_garbage), - proper_unit_for_byte_size(immediate_garbage), - immediate_percent, - immediate_regions, - - byte_size_in_proper_unit(collection_set->garbage()), - proper_unit_for_byte_size(collection_set->garbage()), - cset_percent, - collection_set->count()); + collection_set->summarize(total_garbage, immediate_garbage, immediate_regions); } void ShenandoahHeuristics::record_cycle_start() { diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahYoungHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahYoungHeuristics.cpp index d236be8c9e60..15d1058d7cd5 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahYoungHeuristics.cpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahYoungHeuristics.cpp @@ -55,8 +55,6 @@ void ShenandoahYoungHeuristics::choose_collection_set_from_regiondata(Shenandoah size_t cur_young_garbage = add_preselected_regions_to_collection_set(cset, data, size); choose_young_collection_set(cset, data, size, actual_free, cur_young_garbage); - - log_cset_composition(cset); } void ShenandoahYoungHeuristics::choose_young_collection_set(ShenandoahCollectionSet* cset, diff --git a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.cpp b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.cpp index 35faa40af771..19a9367d6882 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.cpp @@ -200,3 +200,38 @@ void ShenandoahCollectionSet::print_on(outputStream* out) const { } assert(regions == count(), "Must match"); } + +void ShenandoahCollectionSet::summarize(size_t total_garbage, size_t immediate_garbage, size_t immediate_regions) const { + const LogTarget(Info, gc, ergo) lt; + LogStream ls(lt); + if (lt.is_enabled()) { + const size_t cset_percent = (total_garbage == 0) ? 0 : (garbage() * 100 / total_garbage); + const size_t collectable_garbage = garbage() + immediate_garbage; + const size_t collectable_garbage_percent = (total_garbage == 0) ? 0 : (collectable_garbage * 100 / total_garbage); + const size_t immediate_percent = (total_garbage == 0) ? 0 : (immediate_garbage * 100 / total_garbage); + + ls.print_cr("Collectable Garbage: " PROPERFMT " (%zu%%), " + "Immediate: " PROPERFMT " (%zu%%), %zu regions, " + "CSet: " PROPERFMT " (%zu%%), %zu regions", + PROPERFMTARGS(collectable_garbage), + collectable_garbage_percent, + + PROPERFMTARGS(immediate_garbage), + immediate_percent, + immediate_regions, + + PROPERFMTARGS(garbage()), + cset_percent, + count()); + + if (garbage() > 0) { + const size_t young_evac_bytes = get_young_bytes_reserved_for_evacuation(); + const size_t promote_evac_bytes = get_young_bytes_to_be_promoted(); + const size_t old_evac_bytes = get_old_bytes_reserved_for_evacuation(); + const size_t total_evac_bytes = young_evac_bytes + promote_evac_bytes + old_evac_bytes; + ls.print_cr("Evacuation Targets: " + "YOUNG: " PROPERFMT ", " "PROMOTE: " PROPERFMT ", " "OLD: " PROPERFMT ", " "TOTAL: " PROPERFMT, + PROPERFMTARGS(young_evac_bytes), PROPERFMTARGS(promote_evac_bytes), PROPERFMTARGS(old_evac_bytes), PROPERFMTARGS(total_evac_bytes)); + } + } +} diff --git a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.hpp b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.hpp index 4f9f6fc20522..d4a590a3d89a 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.hpp @@ -103,17 +103,26 @@ class ShenandoahCollectionSet : public CHeapObj { inline bool is_in(oop obj) const; inline bool is_in_loc(void* loc) const; + // Prints a detailed accounting of all regions in the collection set when gc+cset=debug void print_on(outputStream* out) const; - // It is not known how many of these bytes will be promoted. - inline size_t get_young_bytes_reserved_for_evacuation(); - inline size_t get_old_bytes_reserved_for_evacuation(); + // Prints a summary of the collection set when gc+ergo=info + void summarize(size_t total_garbage, size_t immediate_garbage, size_t immediate_regions) const; - inline size_t get_young_bytes_to_be_promoted(); + // Returns the amount of live bytes in young regions in the collection set. It is not known how many of these bytes will be promoted. + inline size_t get_young_bytes_reserved_for_evacuation() const; - size_t get_young_available_bytes_collected() { return _young_available_bytes_collected; } + // Returns the amount of live bytes in old regions in the collection set. + inline size_t get_old_bytes_reserved_for_evacuation() const; - inline size_t get_old_garbage(); + // Returns the amount of live bytes in young regions with an age above the tenuring threshold. + inline size_t get_young_bytes_to_be_promoted() const; + + // Returns the amount of free bytes in young regions in the collection set. + size_t get_young_available_bytes_collected() const { return _young_available_bytes_collected; } + + // Returns the amount of garbage in old regions in the collection set. + inline size_t get_old_garbage() const; bool is_preselected(size_t region_idx) { assert(_preselected_regions != nullptr, "Missing etsablish after abandon"); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.inline.hpp index 791e9c73b28e..4adcec4fbb55 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.inline.hpp @@ -54,19 +54,19 @@ bool ShenandoahCollectionSet::is_in_loc(void* p) const { return _biased_cset_map[index] == 1; } -size_t ShenandoahCollectionSet::get_old_bytes_reserved_for_evacuation() { +size_t ShenandoahCollectionSet::get_old_bytes_reserved_for_evacuation() const { return _old_bytes_to_evacuate; } -size_t ShenandoahCollectionSet::get_young_bytes_reserved_for_evacuation() { +size_t ShenandoahCollectionSet::get_young_bytes_reserved_for_evacuation() const { return _young_bytes_to_evacuate - _young_bytes_to_promote; } -size_t ShenandoahCollectionSet::get_young_bytes_to_be_promoted() { +size_t ShenandoahCollectionSet::get_young_bytes_to_be_promoted() const { return _young_bytes_to_promote; } -size_t ShenandoahCollectionSet::get_old_garbage() { +size_t ShenandoahCollectionSet::get_old_garbage() const { return _old_garbage; } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahEvacInfo.hpp b/src/hotspot/share/gc/shenandoah/shenandoahEvacInfo.hpp deleted file mode 100644 index 8069fd13afa6..000000000000 --- a/src/hotspot/share/gc/shenandoah/shenandoahEvacInfo.hpp +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - * - */ - -#ifndef SHARE_GC_SHENANDOAH_SHENANDOAHEVACINFO_HPP -#define SHARE_GC_SHENANDOAH_SHENANDOAHEVACINFO_HPP - -#include "memory/allocation.hpp" - -class ShenandoahEvacuationInformation : public StackObj { - // Values for ShenandoahEvacuationInformation jfr event, sizes stored as bytes - size_t _collection_set_regions; - size_t _collection_set_used_before; - size_t _collection_set_used_after; - size_t _collected_old; - size_t _collected_promoted; - size_t _collected_young; - size_t _free_regions; - size_t _regions_promoted_humongous; - size_t _regions_promoted_regular; - size_t _regular_promoted_garbage; - size_t _regular_promoted_free; - size_t _regions_immediate; - size_t _immediate_size; - -public: - ShenandoahEvacuationInformation() : - _collection_set_regions(0), _collection_set_used_before(0), _collection_set_used_after(0), - _collected_old(0), _collected_promoted(0), _collected_young(0), _free_regions(0), - _regions_promoted_humongous(0), _regions_promoted_regular(0), _regular_promoted_garbage(0), - _regular_promoted_free(0), _regions_immediate(0), _immediate_size(0) { } - - void set_collection_set_regions(size_t collection_set_regions) { - _collection_set_regions = collection_set_regions; - } - - void set_collection_set_used_before(size_t used) { - _collection_set_used_before = used; - } - - void set_collection_set_used_after(size_t used) { - _collection_set_used_after = used; - } - - void set_collected_old(size_t collected) { - _collected_old = collected; - } - - void set_collected_promoted(size_t collected) { - _collected_promoted = collected; - } - - void set_collected_young(size_t collected) { - _collected_young = collected; - } - - void set_free_regions(size_t freed) { - _free_regions = freed; - } - - void set_regions_promoted_humongous(size_t humongous) { - _regions_promoted_humongous = humongous; - } - - void set_regions_promoted_regular(size_t regular) { - _regions_promoted_regular = regular; - } - - void set_regular_promoted_garbage(size_t garbage) { - _regular_promoted_garbage = garbage; - } - - void set_regular_promoted_free(size_t free) { - _regular_promoted_free = free; - } - - void set_regions_immediate(size_t immediate) { - _regions_immediate = immediate; - } - - void set_immediate_size(size_t size) { - _immediate_size = size; - } - - size_t collection_set_regions() { return _collection_set_regions; } - size_t collection_set_used_before() { return _collection_set_used_before; } - size_t collection_set_used_after() { return _collection_set_used_after; } - size_t collected_old() { return _collected_old; } - size_t collected_promoted() { return _collected_promoted; } - size_t collected_young() { return _collected_young; } - size_t regions_promoted_humongous() { return _regions_promoted_humongous; } - size_t regions_promoted_regular() { return _regions_promoted_regular; } - size_t regular_promoted_garbage() { return _regular_promoted_garbage; } - size_t regular_promoted_free() { return _regular_promoted_free; } - size_t free_regions() { return _free_regions; } - size_t regions_immediate() { return _regions_immediate; } - size_t immediate_size() { return _immediate_size; } -}; - -#endif // SHARE_GC_SHENANDOAH_SHENANDOAHEVACINFO_HPP diff --git a/src/hotspot/share/gc/shenandoah/shenandoahTrace.cpp b/src/hotspot/share/gc/shenandoah/shenandoahTrace.cpp index dd153718c9f1..a786f8ae216b 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahTrace.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahTrace.cpp @@ -22,31 +22,31 @@ * */ -#include "gc/shenandoah/shenandoahEvacInfo.hpp" +#include "gc/shenandoah/shenandoahCollectionSet.inline.hpp" #include "gc/shenandoah/shenandoahTrace.hpp" #include "jfr/jfrEvents.hpp" -void ShenandoahTracer::report_evacuation_info(ShenandoahEvacuationInformation* info) { - send_evacuation_info_event(info); -} +void ShenandoahTracer::report_evacuation_info(const ShenandoahCollectionSet* cset, + size_t free_regions, size_t regions_promoted_humongous, size_t regions_promoted_regular, + size_t regular_promoted_garbage, size_t regular_promoted_free, size_t regions_immediate, + size_t immediate_size) { -void ShenandoahTracer::send_evacuation_info_event(ShenandoahEvacuationInformation* info) { EventShenandoahEvacuationInformation e; if (e.should_commit()) { e.set_gcId(GCId::current()); - e.set_cSetRegions(info->collection_set_regions()); - e.set_cSetUsedBefore(info->collection_set_used_before()); - e.set_cSetUsedAfter(info->collection_set_used_after()); - e.set_collectedOld(info->collected_old()); - e.set_collectedPromoted(info->collected_promoted()); - e.set_collectedYoung(info->collected_young()); - e.set_regionsPromotedHumongous(info->regions_promoted_humongous()); - e.set_regionsPromotedRegular(info->regions_promoted_regular()); - e.set_regularPromotedGarbage(info->regular_promoted_garbage()); - e.set_regularPromotedFree(info->regular_promoted_free()); - e.set_freeRegions(info->free_regions()); - e.set_regionsImmediate(info->regions_immediate()); - e.set_immediateBytes(info->immediate_size()); + e.set_cSetRegions(cset->count()); + e.set_cSetUsedBefore(cset->used()); + e.set_cSetUsedAfter(cset->live()); + e.set_collectedOld(cset->get_old_bytes_reserved_for_evacuation()); + e.set_collectedPromoted(cset->get_young_bytes_to_be_promoted()); + e.set_collectedYoung(cset->get_young_bytes_reserved_for_evacuation()); + e.set_regionsPromotedHumongous(regions_promoted_humongous); + e.set_regionsPromotedRegular(regions_promoted_regular); + e.set_regularPromotedGarbage(regular_promoted_garbage); + e.set_regularPromotedFree(regular_promoted_free); + e.set_freeRegions(free_regions); + e.set_regionsImmediate(regions_immediate); + e.set_immediateBytes(immediate_size); e.commit(); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahTrace.hpp b/src/hotspot/share/gc/shenandoah/shenandoahTrace.hpp index a5351f4ef281..116968103dea 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahTrace.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahTrace.hpp @@ -28,15 +28,17 @@ #include "gc/shared/gcTrace.hpp" #include "memory/allocation.hpp" -class ShenandoahEvacuationInformation; +class ShenandoahCollectionSet; class ShenandoahTracer : public GCTracer, public CHeapObj { public: ShenandoahTracer() : GCTracer(Shenandoah) {} - void report_evacuation_info(ShenandoahEvacuationInformation* info); -private: - void send_evacuation_info_event(ShenandoahEvacuationInformation* info); + // Sends a JFR event (if enabled) summarizing the composition of the collection set + static void report_evacuation_info(const ShenandoahCollectionSet* cset, + size_t free_regions, size_t regions_promoted_humongous, size_t regions_promoted_regular, + size_t regular_promoted_garbage, size_t regular_promoted_free, size_t regions_immediate, + size_t immediate_size); }; #endif From d5c666713837421bb641b8d532d0f2ba9c8e1d6a Mon Sep 17 00:00:00 2001 From: SendaoYan Date: Mon, 16 Mar 2026 09:04:30 +0000 Subject: [PATCH 026/168] 8368524: Tests are skipped and shown as passed in test/jdk/sun/security/pkcs11/Cipher/KeyWrap Backport-of: 9292244aef5b24d37105dbef9768db7ac423f366 --- .../pkcs11/Cipher/KeyWrap/NISTWrapKAT.java | 18 ++++++++++-------- .../pkcs11/Cipher/KeyWrap/TestGeneral.java | 1 + 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/test/jdk/sun/security/pkcs11/Cipher/KeyWrap/NISTWrapKAT.java b/test/jdk/sun/security/pkcs11/Cipher/KeyWrap/NISTWrapKAT.java index e8f637e0b0c7..b570a0dd0301 100644 --- a/test/jdk/sun/security/pkcs11/Cipher/KeyWrap/NISTWrapKAT.java +++ b/test/jdk/sun/security/pkcs11/Cipher/KeyWrap/NISTWrapKAT.java @@ -83,7 +83,7 @@ public class NISTWrapKAT extends PKCS11Test { private static String KEK2 = "5840DF6E29B02AF1AB493B705BF16EA1AE8338F4DCC176A8"; - private static final List skippedList = new ArrayList <>(); + private static final List skippedAlgoList = new ArrayList <>(); private static byte[] toBytes(String hex, int hexLen) { if (hexLen < hex.length()) { @@ -274,8 +274,8 @@ public void testKeyWrap(String algo, String key, int keyLen, dataLen + "-byte key with " + 8*keyLen + "-bit KEK"); int allowed = Cipher.getMaxAllowedKeyLength("AES"); if (keyLen > allowed) { - System.out.println("=> skip, exceeds max allowed size " + allowed); - skippedList.add(algo + " Cipher with wrapping " + + System.err.println("Skip, exceeds max allowed size " + allowed); + skippedAlgoList.add(algo + " Cipher with wrapping " + dataLen + "-byte key with " + 8 * keyLen + "-bit KEK exceeds max allowed size " + allowed); return; @@ -344,8 +344,8 @@ public void testEnc(String algo, String key, int keyLen, String data, dataLen + "-byte data with " + 8*keyLen + "-bit KEK"); int allowed = Cipher.getMaxAllowedKeyLength("AES"); if (keyLen > allowed) { - System.out.println("=> skip, exceeds max allowed size " + allowed); - skippedList.add(algo + " Cipher with enc " + + System.err.println("Skip, exceeds max allowed size " + allowed); + skippedAlgoList.add(algo + " Cipher with enc " + dataLen + "-byte data with " + 8 * keyLen + "-bit KEK exceeds max allowed size " + allowed); return; @@ -416,7 +416,9 @@ public void main(Provider p) throws Exception { for (Object[] td : testDatum) { String algo = (String) td[0]; if (p.getService("Cipher", algo) == null) { - skippedList.add("No support for " + algo); + System.err.println("Skip, due to no support: " + algo); + skippedAlgoList.add("No support for " + algo); + continue; } testKeyWrap(algo, (String) td[1], (int) td[2], (String) td[3], (int) td[4], (String) td[5], p); @@ -424,9 +426,9 @@ public void main(Provider p) throws Exception { (int) td[4], (String) td[5], p); } - if (!skippedList.isEmpty()) { + if (!skippedAlgoList.isEmpty()) { throw new SkippedException("One or more tests skipped " - + skippedList); + + skippedAlgoList); } else { System.out.println("All Tests Passed"); } diff --git a/test/jdk/sun/security/pkcs11/Cipher/KeyWrap/TestGeneral.java b/test/jdk/sun/security/pkcs11/Cipher/KeyWrap/TestGeneral.java index f5e4494fc59b..d7cdfc6c04c5 100644 --- a/test/jdk/sun/security/pkcs11/Cipher/KeyWrap/TestGeneral.java +++ b/test/jdk/sun/security/pkcs11/Cipher/KeyWrap/TestGeneral.java @@ -273,6 +273,7 @@ public void main(Provider p) throws Exception { for (String a : algos) { if (p.getService("Cipher", a) == null) { skippedList.add(a); + continue; } System.out.println("Testing " + a); From 0f33a678d9c52681c0d3c8fc582a91e7ee5dfb15 Mon Sep 17 00:00:00 2001 From: SendaoYan Date: Mon, 16 Mar 2026 09:26:41 +0000 Subject: [PATCH 027/168] 8377167: javax/imageio/ReadAbortTest.java throw NPE when x11 unavailable Backport-of: d93bd18d67555ba998735196576c337249f4932b --- test/jdk/javax/imageio/ReadAbortTest.java | 11 +++++++---- test/jdk/javax/imageio/WriteAbortTest.java | 15 +++++++++------ 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/test/jdk/javax/imageio/ReadAbortTest.java b/test/jdk/javax/imageio/ReadAbortTest.java index 7476dbe0de85..bda17aa42046 100644 --- a/test/jdk/javax/imageio/ReadAbortTest.java +++ b/test/jdk/javax/imageio/ReadAbortTest.java @@ -30,16 +30,17 @@ * calling IIOReadProgressListener.readAborted() for all readers. * @run main ReadAbortTest */ +import java.awt.Color; +import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.io.File; +import java.nio.file.Files; import java.util.Iterator; + import javax.imageio.ImageIO; import javax.imageio.ImageReader; import javax.imageio.event.IIOReadProgressListener; import javax.imageio.stream.ImageInputStream; -import java.awt.Color; -import java.awt.Graphics2D; -import java.nio.file.Files; public class ReadAbortTest implements IIOReadProgressListener { @@ -103,7 +104,9 @@ public ReadAbortTest(String format) throws Exception { } catch (Exception e) { throw e; } finally { - Files.delete(file.toPath()); + if (file != null && file.exists()) { + Files.delete(file.toPath()); + } } } diff --git a/test/jdk/javax/imageio/WriteAbortTest.java b/test/jdk/javax/imageio/WriteAbortTest.java index 624ce16c94e4..43abd703d7da 100644 --- a/test/jdk/javax/imageio/WriteAbortTest.java +++ b/test/jdk/javax/imageio/WriteAbortTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -30,15 +30,16 @@ * calling IIOWriteProgressListener.readAborted() for all readers. * @run main WriteAbortTest */ -import java.awt.image.BufferedImage; -import java.io.File; -import javax.imageio.ImageIO; -import javax.imageio.stream.ImageInputStream; import java.awt.Color; import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.io.File; import java.nio.file.Files; + +import javax.imageio.ImageIO; import javax.imageio.ImageWriter; import javax.imageio.event.IIOWriteProgressListener; +import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageOutputStream; public class WriteAbortTest implements IIOWriteProgressListener { @@ -98,7 +99,9 @@ public WriteAbortTest(String format) throws Exception { + format); } } finally { - Files.delete(file.toPath()); + if (file != null && file.exists()) { + Files.delete(file.toPath()); + } } } From 5b7517943928373456cbf3af0db7ec1041d825e8 Mon Sep 17 00:00:00 2001 From: Richard Reingruber Date: Mon, 16 Mar 2026 09:29:21 +0000 Subject: [PATCH 028/168] 8374769: PPC: MASM::pop_cont_fastpath() should reset _cont_fastpath if SP == _cont_fastpath Backport-of: 5664d9148401934cd26308dc4493f4a5656e89bd --- src/hotspot/cpu/ppc/macroAssembler_ppc.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp b/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp index 396a50427f85..33faa555d4fb 100644 --- a/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp +++ b/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp @@ -4752,7 +4752,7 @@ void MacroAssembler::push_cont_fastpath() { Label done; ld_ptr(R0, JavaThread::cont_fastpath_offset(), R16_thread); cmpld(CR0, R1_SP, R0); - ble(CR0, done); + ble(CR0, done); // if (SP <= _cont_fastpath) goto done; st_ptr(R1_SP, JavaThread::cont_fastpath_offset(), R16_thread); bind(done); } @@ -4763,7 +4763,7 @@ void MacroAssembler::pop_cont_fastpath() { Label done; ld_ptr(R0, JavaThread::cont_fastpath_offset(), R16_thread); cmpld(CR0, R1_SP, R0); - ble(CR0, done); + blt(CR0, done); // if (SP < _cont_fastpath) goto done; li(R0, 0); st_ptr(R0, JavaThread::cont_fastpath_offset(), R16_thread); bind(done); From 2fb3e9c698c555618d82ca62a09ac2cbf054ee38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mar=C3=ADa=20Arias=20de=20Reyna=20Dom=C3=ADnguez?= Date: Mon, 16 Mar 2026 09:34:16 +0000 Subject: [PATCH 029/168] 8377932: AOT cache is not rejected when JAR file has changed Backport-of: 3a09cbd28df36ca85d69583ac37058fa2f1ff5f3 --- src/hotspot/share/cds/aotClassLocation.cpp | 12 +- .../cds/appcds/aotCache/ChangedJarFile.java | 110 ++++++++++++++++++ 2 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/aotCache/ChangedJarFile.java diff --git a/src/hotspot/share/cds/aotClassLocation.cpp b/src/hotspot/share/cds/aotClassLocation.cpp index b662c5a1b478..ed23f255173b 100644 --- a/src/hotspot/share/cds/aotClassLocation.cpp +++ b/src/hotspot/share/cds/aotClassLocation.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -432,7 +432,8 @@ bool AOTClassLocation::check(const char* runtime_path, bool has_aot_linked_class bool size_differs = _filesize != st.st_size; bool time_differs = _check_time && (_timestamp != st.st_mtime); if (size_differs || time_differs) { - aot_log_warning(aot)("This file is not the one used while building the shared archive file: '%s'%s%s", + aot_log_warning(aot)("This file is not the one used while building the %s: '%s'%s%s", + CDSConfig::type_of_archive_being_loaded(), runtime_path, time_differs ? ", timestamp has changed" : "", size_differs ? ", size has changed" : ""); @@ -454,6 +455,13 @@ void AOTClassLocationConfig::dumptime_init(JavaThread* current) { java_lang_Throwable::print(current->pending_exception(), tty); vm_exit_during_initialization("AOTClassLocationConfig::dumptime_init_helper() failed unexpectedly"); } + + if (CDSConfig::is_dumping_final_static_archive()) { + // The _max_used_index is usually updated by ClassLoader::record_result(). However, + // when dumping the final archive, the classes are loaded from their images in + // the AOT config file, so we don't go through ClassLoader::record_result(). + dumptime_update_max_used_index(runtime()->_max_used_index); // Same value as recorded in the training run. + } } void AOTClassLocationConfig::dumptime_init_helper(TRAPS) { diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/ChangedJarFile.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/ChangedJarFile.java new file mode 100644 index 000000000000..a717b2673475 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/ChangedJarFile.java @@ -0,0 +1,110 @@ +/* + * 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 + * @summary AOT cache should be rejected if JAR file(s) in the classpath have changed + * @bug 8377932 + * @requires vm.cds.supports.aot.class.linking + * @library /test/lib + * @build ChangedJarFile + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar MyTestApp OtherClass + * @run driver ChangedJarFile AOT + */ + +import jdk.jfr.Event; +import jdk.test.lib.cds.CDSAppTester; +import jdk.test.lib.helpers.ClassFileInstaller; +import jdk.test.lib.process.OutputAnalyzer; + +public class ChangedJarFile { + static final String appJar = ClassFileInstaller.getJarPath("app.jar"); + static final String mainClass = MyTestApp.class.getName(); + + public static void main(String[] args) throws Exception { + // Train and run with unchanged JAR file (which has OtherClass.class) + Tester tester = new Tester(); + tester.run(args); + + // Run again with changed JAR file (which doesn't have OtherClass.class anymore) + ClassFileInstaller.writeJar(appJar, "MyTestApp"); + + // First disable AOT cache to verify test login + tester.productionRun(new String[] {"-XX:AOTMode=off"}, + new String[] {"jarHasChanged"}); + + // Now see if the AOT cache will be automatically disabled + OutputAnalyzer out = + tester.productionRun(new String[] {"-XX:AOTMode=auto", "-Xlog:aot"}, + new String[] {"jarHasChanged"}); + out.shouldMatch("This file is not the one used while building the " + + "AOT cache: '.*app.jar', timestamp has changed, size has changed"); + } + + static class Tester extends CDSAppTester { + public Tester() { + super(mainClass); + } + + @Override + public String classpath(RunMode runMode) { + return appJar; + } + + @Override + public String[] appCommandLine(RunMode runMode) { + return new String[] { mainClass }; + } + + @Override + public void checkExecution(OutputAnalyzer out, RunMode runMode) { + + } + } + + +} + +class MyTestApp { + public static void main(String args[]) { + boolean jarHasChanged = (args.length != 0); + + System.out.println("JAR has changed = " + (jarHasChanged)); + Class c = null; + try { + c = Class.forName("OtherClass"); + System.out.println("Other class = " + c); + } catch (Throwable t) { + if (!jarHasChanged) { + throw new RuntimeException("OtherClass should have been loaded because JAR has not been changed yet", t); + } + } + + if (jarHasChanged && c != null) { + throw new RuntimeException("OtherClass should not be in JAR file"); + } + } +} + +class OtherClass {} From fe129fc7368c062930e09a7fe69d3595c4eb0c21 Mon Sep 17 00:00:00 2001 From: Goetz Lindenmaier Date: Mon, 16 Mar 2026 09:57:57 +0000 Subject: [PATCH 030/168] 8357086: os::xxx functions returning memory size should return size_t Reviewed-by: rrich Backport-of: d5d94db12a6d82a6fe9da18b5f8ce3733a6ee7e7 --- src/hotspot/os/aix/os_aix.cpp | 37 +++--- src/hotspot/os/aix/os_aix.hpp | 8 +- src/hotspot/os/bsd/os_bsd.cpp | 55 +++++---- src/hotspot/os/bsd/os_bsd.hpp | 8 +- .../os/linux/cgroupSubsystem_linux.cpp | 12 +- src/hotspot/os/linux/cgroupUtil_linux.cpp | 2 +- src/hotspot/os/linux/os_linux.cpp | 105 +++++++++++------- src/hotspot/os/linux/os_linux.hpp | 8 +- src/hotspot/os/windows/os_windows.cpp | 56 +++++++--- src/hotspot/os/windows/os_windows.hpp | 8 +- src/hotspot/share/compiler/compileBroker.cpp | 4 +- src/hotspot/share/gc/shared/gcInitLogger.cpp | 5 +- src/hotspot/share/gc/z/zLargePages.cpp | 3 +- src/hotspot/share/jfr/jni/jfrJniMethod.cpp | 9 +- .../share/jfr/periodic/jfrPeriodic.cpp | 17 ++- .../share/prims/jvmtiRedefineClasses.cpp | 20 ++-- src/hotspot/share/prims/whitebox.cpp | 9 +- src/hotspot/share/runtime/arguments.cpp | 7 +- src/hotspot/share/runtime/os.cpp | 23 ++-- src/hotspot/share/runtime/os.hpp | 12 +- src/hotspot/share/services/heapDumper.cpp | 5 +- src/hotspot/share/services/management.cpp | 2 +- 22 files changed, 254 insertions(+), 161 deletions(-) diff --git a/src/hotspot/os/aix/os_aix.cpp b/src/hotspot/os/aix/os_aix.cpp index 50a219f0824e..d6df2cf8fbad 100644 --- a/src/hotspot/os/aix/os_aix.cpp +++ b/src/hotspot/os/aix/os_aix.cpp @@ -169,7 +169,7 @@ static void vmembk_print_on(outputStream* os); //////////////////////////////////////////////////////////////////////////////// // global variables (for a description see os_aix.hpp) -julong os::Aix::_physical_memory = 0; +size_t os::Aix::_physical_memory = 0; pthread_t os::Aix::_main_thread = ((pthread_t)0); @@ -254,40 +254,43 @@ static bool is_close_to_brk(address a) { return false; } -julong os::free_memory() { - return Aix::available_memory(); +bool os::free_memory(size_t& value) { + return Aix::available_memory(value); } -julong os::available_memory() { - return Aix::available_memory(); +bool os::available_memory(size_t& value) { + return Aix::available_memory(value); } -julong os::Aix::available_memory() { +bool os::Aix::available_memory(size_t& value) { os::Aix::meminfo_t mi; if (os::Aix::get_meminfo(&mi)) { - return mi.real_free; + value = static_cast(mi.real_free); + return true; } else { - return ULONG_MAX; + return false; } } -jlong os::total_swap_space() { +bool os::total_swap_space(size_t& value) { perfstat_memory_total_t memory_info; if (libperfstat::perfstat_memory_total(nullptr, &memory_info, sizeof(perfstat_memory_total_t), 1) == -1) { - return -1; + return false; } - return (jlong)(memory_info.pgsp_total * 4 * K); + value = static_cast(memory_info.pgsp_total * 4 * K); + return true; } -jlong os::free_swap_space() { +bool os::free_swap_space(size_t& value) { perfstat_memory_total_t memory_info; if (libperfstat::perfstat_memory_total(nullptr, &memory_info, sizeof(perfstat_memory_total_t), 1) == -1) { - return -1; + return false; } - return (jlong)(memory_info.pgsp_free * 4 * K); + value = static_cast(memory_info.pgsp_free * 4 * K); + return true; } -julong os::physical_memory() { +size_t os::physical_memory() { return Aix::physical_memory(); } @@ -326,7 +329,7 @@ void os::Aix::initialize_system_info() { if (!os::Aix::get_meminfo(&mi)) { assert(false, "os::Aix::get_meminfo failed."); } - _physical_memory = (julong) mi.real_total; + _physical_memory = static_cast(mi.real_total); } // Helper function for tracing page sizes. @@ -2267,7 +2270,7 @@ jint os::init_2(void) { os::Posix::init_2(); trcVerbose("processor count: %d", os::_processor_count); - trcVerbose("physical memory: %lu", Aix::_physical_memory); + trcVerbose("physical memory: %zu", Aix::_physical_memory); // Initially build up the loaded dll map. LoadedLibraries::reload(); diff --git a/src/hotspot/os/aix/os_aix.hpp b/src/hotspot/os/aix/os_aix.hpp index d17c022e4113..1530f2adb763 100644 --- a/src/hotspot/os/aix/os_aix.hpp +++ b/src/hotspot/os/aix/os_aix.hpp @@ -35,7 +35,7 @@ class os::Aix { private: - static julong _physical_memory; + static size_t _physical_memory; static pthread_t _main_thread; // 0 = uninitialized, otherwise 16 bit number: @@ -54,9 +54,9 @@ class os::Aix { // 1 - EXTSHM=ON static int _extshm; - static julong available_memory(); - static julong free_memory(); - static julong physical_memory() { return _physical_memory; } + static bool available_memory(size_t& value); + static bool free_memory(size_t& value); + static size_t physical_memory() { return _physical_memory; } static void initialize_system_info(); // OS recognitions (AIX OS level) call this before calling Aix::os_version(). diff --git a/src/hotspot/os/bsd/os_bsd.cpp b/src/hotspot/os/bsd/os_bsd.cpp index 868725fdb671..6de85f54bb94 100644 --- a/src/hotspot/os/bsd/os_bsd.cpp +++ b/src/hotspot/os/bsd/os_bsd.cpp @@ -114,7 +114,7 @@ //////////////////////////////////////////////////////////////////////////////// // global variables -julong os::Bsd::_physical_memory = 0; +size_t os::Bsd::_physical_memory = 0; #ifdef __APPLE__ mach_timebase_info_data_t os::Bsd::_timebase_info = {0, 0}; @@ -133,19 +133,19 @@ static volatile int processor_id_next = 0; //////////////////////////////////////////////////////////////////////////////// // utility functions -julong os::available_memory() { - return Bsd::available_memory(); +bool os::available_memory(size_t& value) { + return Bsd::available_memory(value); } -julong os::free_memory() { - return Bsd::available_memory(); +bool os::free_memory(size_t& value) { + return Bsd::available_memory(value); } // Available here means free. Note that this number is of no much use. As an estimate // for future memory pressure it is far too conservative, since MacOS will use a lot // of unused memory for caches, and return it willingly in case of needs. -julong os::Bsd::available_memory() { - uint64_t available = physical_memory() >> 2; +bool os::Bsd::available_memory(size_t& value) { + uint64_t available = static_cast(physical_memory() >> 2); #ifdef __APPLE__ mach_msg_type_number_t count = HOST_VM_INFO64_COUNT; vm_statistics64_data_t vmstat; @@ -156,9 +156,12 @@ julong os::Bsd::available_memory() { if (kerr == KERN_SUCCESS) { // free_count is just a lowerbound, other page categories can be freed too and make memory available available = (vmstat.free_count + vmstat.inactive_count + vmstat.purgeable_count) * os::vm_page_size(); + } else { + return false; } #endif - return available; + value = static_cast(available); + return true; } // for more info see : @@ -177,33 +180,35 @@ void os::Bsd::print_uptime_info(outputStream* st) { } } -jlong os::total_swap_space() { +bool os::total_swap_space(size_t& value) { #if defined(__APPLE__) struct xsw_usage vmusage; size_t size = sizeof(vmusage); if (sysctlbyname("vm.swapusage", &vmusage, &size, nullptr, 0) != 0) { - return -1; + return false; } - return (jlong)vmusage.xsu_total; + value = static_cast(vmusage.xsu_total); + return true; #else - return -1; + return false; #endif } -jlong os::free_swap_space() { +bool os::free_swap_space(size_t& value) { #if defined(__APPLE__) struct xsw_usage vmusage; size_t size = sizeof(vmusage); if (sysctlbyname("vm.swapusage", &vmusage, &size, nullptr, 0) != 0) { - return -1; + return false; } - return (jlong)vmusage.xsu_avail; + value = static_cast(vmusage.xsu_avail); + return true; #else - return -1; + return false; #endif } -julong os::physical_memory() { +size_t os::physical_memory() { return Bsd::physical_memory(); } @@ -281,7 +286,7 @@ void os::Bsd::initialize_system_info() { len = sizeof(mem_val); if (sysctl(mib, 2, &mem_val, &len, nullptr, 0) != -1) { assert(len == sizeof(mem_val), "unexpected data size"); - _physical_memory = mem_val; + _physical_memory = static_cast(mem_val); } else { _physical_memory = 256 * 1024 * 1024; // fallback (XXXBSD?) } @@ -292,7 +297,7 @@ void os::Bsd::initialize_system_info() { // datasize rlimit restricts us anyway. struct rlimit limits; getrlimit(RLIMIT_DATA, &limits); - _physical_memory = MIN2(_physical_memory, (julong)limits.rlim_cur); + _physical_memory = MIN2(_physical_memory, static_cast(limits.rlim_cur)); } #endif } @@ -1465,11 +1470,13 @@ void os::print_memory_info(outputStream* st) { st->print("Memory:"); st->print(" %zuk page", os::vm_page_size()>>10); - - st->print(", physical " UINT64_FORMAT "k", - os::physical_memory() >> 10); - st->print("(" UINT64_FORMAT "k free)", - os::available_memory() >> 10); + size_t phys_mem = os::physical_memory(); + st->print(", physical %zuk", + phys_mem >> 10); + size_t avail_mem = 0; + (void)os::available_memory(avail_mem); + st->print("(%zuk free)", + avail_mem >> 10); if((sysctlbyname("vm.swapusage", &swap_usage, &size, nullptr, 0) == 0) || (errno == ENOMEM)) { if (size >= offset_of(xsw_usage, xsu_used)) { diff --git a/src/hotspot/os/bsd/os_bsd.hpp b/src/hotspot/os/bsd/os_bsd.hpp index 72de9ca59719..173cc5a40ad1 100644 --- a/src/hotspot/os/bsd/os_bsd.hpp +++ b/src/hotspot/os/bsd/os_bsd.hpp @@ -42,12 +42,12 @@ class os::Bsd { protected: - static julong _physical_memory; + static size_t _physical_memory; static pthread_t _main_thread; - static julong available_memory(); - static julong free_memory(); - static julong physical_memory() { return _physical_memory; } + static bool available_memory(size_t& value); + static bool free_memory(size_t& value); + static size_t physical_memory() { return _physical_memory; } static void initialize_system_info(); static void rebuild_cpu_to_node_map(); diff --git a/src/hotspot/os/linux/cgroupSubsystem_linux.cpp b/src/hotspot/os/linux/cgroupSubsystem_linux.cpp index a9cabc873356..3186d97ec614 100644 --- a/src/hotspot/os/linux/cgroupSubsystem_linux.cpp +++ b/src/hotspot/os/linux/cgroupSubsystem_linux.cpp @@ -670,8 +670,8 @@ jlong CgroupSubsystem::memory_limit_in_bytes() { if (!memory_limit->should_check_metric()) { return memory_limit->value(); } - jlong phys_mem = os::Linux::physical_memory(); - log_trace(os, container)("total physical memory: " JLONG_FORMAT, phys_mem); + julong phys_mem = static_cast(os::Linux::physical_memory()); + log_trace(os, container)("total physical memory: " JULONG_FORMAT, phys_mem); jlong mem_limit = contrl->controller()->read_memory_limit_in_bytes(phys_mem); // Update cached metric to avoid re-reading container settings too often memory_limit->set_value(mem_limit, OSCONTAINER_CACHE_TIMEOUT); @@ -841,19 +841,19 @@ jlong CgroupController::limit_from_str(char* limit_str) { // CgroupSubsystem implementations jlong CgroupSubsystem::memory_and_swap_limit_in_bytes() { - julong phys_mem = os::Linux::physical_memory(); + julong phys_mem = static_cast(os::Linux::physical_memory()); julong host_swap = os::Linux::host_swap(); return memory_controller()->controller()->memory_and_swap_limit_in_bytes(phys_mem, host_swap); } jlong CgroupSubsystem::memory_and_swap_usage_in_bytes() { - julong phys_mem = os::Linux::physical_memory(); + julong phys_mem = static_cast(os::Linux::physical_memory()); julong host_swap = os::Linux::host_swap(); return memory_controller()->controller()->memory_and_swap_usage_in_bytes(phys_mem, host_swap); } jlong CgroupSubsystem::memory_soft_limit_in_bytes() { - julong phys_mem = os::Linux::physical_memory(); + julong phys_mem = static_cast(os::Linux::physical_memory()); return memory_controller()->controller()->memory_soft_limit_in_bytes(phys_mem); } @@ -894,6 +894,6 @@ jlong CgroupSubsystem::cpu_usage_in_micros() { } void CgroupSubsystem::print_version_specific_info(outputStream* st) { - julong phys_mem = os::Linux::physical_memory(); + julong phys_mem = static_cast(os::Linux::physical_memory()); memory_controller()->controller()->print_version_specific_info(st, phys_mem); } diff --git a/src/hotspot/os/linux/cgroupUtil_linux.cpp b/src/hotspot/os/linux/cgroupUtil_linux.cpp index b52ef87dcaee..72dda36504d3 100644 --- a/src/hotspot/os/linux/cgroupUtil_linux.cpp +++ b/src/hotspot/os/linux/cgroupUtil_linux.cpp @@ -65,7 +65,7 @@ void CgroupUtil::adjust_controller(CgroupMemoryController* mem) { char* cg_path = os::strdup(orig); char* last_slash; assert(cg_path[0] == '/', "cgroup path must start with '/'"); - julong phys_mem = os::Linux::physical_memory(); + julong phys_mem = static_cast(os::Linux::physical_memory()); char* limit_cg_path = nullptr; jlong limit = mem->read_memory_limit_in_bytes(phys_mem); jlong lowest_limit = limit < 0 ? phys_mem : limit; diff --git a/src/hotspot/os/linux/os_linux.cpp b/src/hotspot/os/linux/os_linux.cpp index fc0578f4d9b3..a30fc9bdd358 100644 --- a/src/hotspot/os/linux/os_linux.cpp +++ b/src/hotspot/os/linux/os_linux.cpp @@ -157,7 +157,7 @@ enum CoredumpFilterBit { //////////////////////////////////////////////////////////////////////////////// // global variables -julong os::Linux::_physical_memory = 0; +size_t os::Linux::_physical_memory = 0; address os::Linux::_initial_thread_stack_bottom = nullptr; uintptr_t os::Linux::_initial_thread_stack_size = 0; @@ -232,15 +232,16 @@ julong os::Linux::available_memory_in_container() { return avail_mem; } -julong os::available_memory() { - return Linux::available_memory(); +bool os::available_memory(size_t& value) { + return Linux::available_memory(value); } -julong os::Linux::available_memory() { +bool os::Linux::available_memory(size_t& value) { julong avail_mem = available_memory_in_container(); if (avail_mem != static_cast(-1L)) { log_trace(os)("available container memory: " JULONG_FORMAT, avail_mem); - return avail_mem; + value = static_cast(avail_mem); + return true; } FILE *fp = os::fopen("/proc/meminfo", "r"); @@ -255,66 +256,88 @@ julong os::Linux::available_memory() { fclose(fp); } if (avail_mem == static_cast(-1L)) { - avail_mem = free_memory(); + size_t free_mem = 0; + if (!free_memory(free_mem)) { + return false; + } + avail_mem = static_cast(free_mem); } log_trace(os)("available memory: " JULONG_FORMAT, avail_mem); - return avail_mem; + value = static_cast(avail_mem); + return true; } -julong os::free_memory() { - return Linux::free_memory(); +bool os::free_memory(size_t& value) { + return Linux::free_memory(value); } -julong os::Linux::free_memory() { +bool os::Linux::free_memory(size_t& value) { // values in struct sysinfo are "unsigned long" struct sysinfo si; julong free_mem = available_memory_in_container(); if (free_mem != static_cast(-1L)) { log_trace(os)("free container memory: " JULONG_FORMAT, free_mem); - return free_mem; + value = static_cast(free_mem); + return true; } - sysinfo(&si); + int ret = sysinfo(&si); + if (ret != 0) { + return false; + } free_mem = (julong)si.freeram * si.mem_unit; log_trace(os)("free memory: " JULONG_FORMAT, free_mem); - return free_mem; + value = static_cast(free_mem); + return true; } -jlong os::total_swap_space() { +bool os::total_swap_space(size_t& value) { if (OSContainer::is_containerized()) { - if (OSContainer::memory_limit_in_bytes() > 0) { - return (jlong)(OSContainer::memory_and_swap_limit_in_bytes() - OSContainer::memory_limit_in_bytes()); + jlong memory_and_swap_limit_in_bytes = OSContainer::memory_and_swap_limit_in_bytes(); + jlong memory_limit_in_bytes = OSContainer::memory_limit_in_bytes(); + if (memory_limit_in_bytes > 0 && memory_and_swap_limit_in_bytes > 0) { + value = static_cast(memory_and_swap_limit_in_bytes - memory_limit_in_bytes); + return true; } - } + } // fallback to the host swap space if the container did return the unbound value of -1 struct sysinfo si; int ret = sysinfo(&si); if (ret != 0) { - return -1; + assert(false, "sysinfo failed in total_swap_space(): %s", os::strerror(errno)); + return false; } - return (jlong)(si.totalswap * si.mem_unit); + value = static_cast(si.totalswap * si.mem_unit); + return true; } -static jlong host_free_swap() { +static bool host_free_swap_f(size_t& value) { struct sysinfo si; int ret = sysinfo(&si); if (ret != 0) { - return -1; + assert(false, "sysinfo failed in host_free_swap_f(): %s", os::strerror(errno)); + return false; } - return (jlong)(si.freeswap * si.mem_unit); + value = static_cast(si.freeswap * si.mem_unit); + return true; } -jlong os::free_swap_space() { +bool os::free_swap_space(size_t& value) { // os::total_swap_space() might return the containerized limit which might be // less than host_free_swap(). The upper bound of free swap needs to be the lower of the two. - jlong host_free_swap_val = MIN2(os::total_swap_space(), host_free_swap()); - assert(host_free_swap_val >= 0, "sysinfo failed?"); + size_t total_swap_space = 0; + size_t host_free_swap = 0; + if (!os::total_swap_space(total_swap_space) || !host_free_swap_f(host_free_swap)) { + return false; + } + size_t host_free_swap_val = MIN2(total_swap_space, host_free_swap); if (OSContainer::is_containerized()) { jlong mem_swap_limit = OSContainer::memory_and_swap_limit_in_bytes(); jlong mem_limit = OSContainer::memory_limit_in_bytes(); if (mem_swap_limit >= 0 && mem_limit >= 0) { jlong delta_limit = mem_swap_limit - mem_limit; if (delta_limit <= 0) { - return 0; + value = 0; + return true; } jlong mem_swap_usage = OSContainer::memory_and_swap_usage_in_bytes(); jlong mem_usage = OSContainer::memory_usage_in_bytes(); @@ -322,30 +345,31 @@ jlong os::free_swap_space() { jlong delta_usage = mem_swap_usage - mem_usage; if (delta_usage >= 0) { jlong free_swap = delta_limit - delta_usage; - return free_swap >= 0 ? free_swap : 0; + value = free_swap >= 0 ? static_cast(free_swap) : 0; + return true; } } } // unlimited or not supported. Fall through to return host value log_trace(os,container)("os::free_swap_space: container_swap_limit=" JLONG_FORMAT - " container_mem_limit=" JLONG_FORMAT " returning host value: " JLONG_FORMAT, + " container_mem_limit=" JLONG_FORMAT " returning host value: %zu", mem_swap_limit, mem_limit, host_free_swap_val); } - return host_free_swap_val; + value = host_free_swap_val; + return true; } -julong os::physical_memory() { - jlong phys_mem = 0; +size_t os::physical_memory() { if (OSContainer::is_containerized()) { jlong mem_limit; if ((mem_limit = OSContainer::memory_limit_in_bytes()) > 0) { log_trace(os)("total container memory: " JLONG_FORMAT, mem_limit); - return mem_limit; + return static_cast(mem_limit); } } - phys_mem = Linux::physical_memory(); - log_trace(os)("total system memory: " JLONG_FORMAT, phys_mem); + size_t phys_mem = Linux::physical_memory(); + log_trace(os)("total system memory: %zu", phys_mem); return phys_mem; } @@ -527,7 +551,7 @@ void os::Linux::initialize_system_info() { fclose(fp); } } - _physical_memory = (julong)sysconf(_SC_PHYS_PAGES) * (julong)sysconf(_SC_PAGESIZE); + _physical_memory = static_cast(sysconf(_SC_PHYS_PAGES)) * static_cast(sysconf(_SC_PAGESIZE)); assert(processor_count() > 0, "linux error"); } @@ -2593,10 +2617,13 @@ void os::print_memory_info(outputStream* st) { struct sysinfo si; int ret = sysinfo(&si); assert(ret == 0, "sysinfo failed: %s", os::strerror(errno)); - st->print(", physical " UINT64_FORMAT "k", - os::physical_memory() >> 10); - st->print("(" UINT64_FORMAT "k free)", - os::available_memory() >> 10); + size_t phys_mem = physical_memory(); + st->print(", physical %zuk", + phys_mem >> 10); + size_t avail_mem = 0; + (void)os::available_memory(avail_mem); + st->print("(%zuk free)", + avail_mem >> 10); if (ret == 0) { st->print(", swap " UINT64_FORMAT "k", ((jlong)si.totalswap * si.mem_unit) >> 10); diff --git a/src/hotspot/os/linux/os_linux.hpp b/src/hotspot/os/linux/os_linux.hpp index 4e208a11300e..497d383200df 100644 --- a/src/hotspot/os/linux/os_linux.hpp +++ b/src/hotspot/os/linux/os_linux.hpp @@ -50,11 +50,11 @@ class os::Linux { protected: - static julong _physical_memory; + static size_t _physical_memory; static pthread_t _main_thread; - static julong available_memory(); - static julong free_memory(); + static bool available_memory(size_t& value); + static bool free_memory(size_t& value); static void initialize_system_info(); @@ -117,7 +117,7 @@ class os::Linux { static address initial_thread_stack_bottom(void) { return _initial_thread_stack_bottom; } static uintptr_t initial_thread_stack_size(void) { return _initial_thread_stack_size; } - static julong physical_memory() { return _physical_memory; } + static size_t physical_memory() { return _physical_memory; } static julong host_swap(); static intptr_t* ucontext_get_sp(const ucontext_t* uc); diff --git a/src/hotspot/os/windows/os_windows.cpp b/src/hotspot/os/windows/os_windows.cpp index e602acbfae3d..8e0ce4871b69 100644 --- a/src/hotspot/os/windows/os_windows.cpp +++ b/src/hotspot/os/windows/os_windows.cpp @@ -848,39 +848,56 @@ jlong os::elapsed_frequency() { } -julong os::available_memory() { - return win32::available_memory(); +bool os::available_memory(size_t& value) { + return win32::available_memory(value); } -julong os::free_memory() { - return win32::available_memory(); +bool os::free_memory(size_t& value) { + return win32::available_memory(value); } -julong os::win32::available_memory() { +bool os::win32::available_memory(size_t& value) { // Use GlobalMemoryStatusEx() because GlobalMemoryStatus() may return incorrect // value if total memory is larger than 4GB MEMORYSTATUSEX ms; ms.dwLength = sizeof(ms); - GlobalMemoryStatusEx(&ms); - - return (julong)ms.ullAvailPhys; + BOOL res = GlobalMemoryStatusEx(&ms); + if (res == TRUE) { + value = static_cast(ms.ullAvailPhys); + return true; + } else { + assert(false, "GlobalMemoryStatusEx failed in os::win32::available_memory(): %lu", ::GetLastError()); + return false; + } } -jlong os::total_swap_space() { +bool os::total_swap_space(size_t& value) { MEMORYSTATUSEX ms; ms.dwLength = sizeof(ms); - GlobalMemoryStatusEx(&ms); - return (jlong) ms.ullTotalPageFile; + BOOL res = GlobalMemoryStatusEx(&ms); + if (res == TRUE) { + value = static_cast(ms.ullTotalPageFile); + return true; + } else { + assert(false, "GlobalMemoryStatusEx failed in os::total_swap_space(): %lu", ::GetLastError()); + return false; + } } -jlong os::free_swap_space() { +bool os::free_swap_space(size_t& value) { MEMORYSTATUSEX ms; ms.dwLength = sizeof(ms); - GlobalMemoryStatusEx(&ms); - return (jlong) ms.ullAvailPageFile; + BOOL res = GlobalMemoryStatusEx(&ms); + if (res == TRUE) { + value = static_cast(ms.ullAvailPageFile); + return true; + } else { + assert(false, "GlobalMemoryStatusEx failed in os::free_swap_space(): %lu", ::GetLastError()); + return false; + } } -julong os::physical_memory() { +size_t os::physical_memory() { return win32::physical_memory(); } @@ -3898,7 +3915,7 @@ int os::current_process_id() { int os::win32::_processor_type = 0; // Processor level is not available on non-NT systems, use vm_version instead int os::win32::_processor_level = 0; -julong os::win32::_physical_memory = 0; +size_t os::win32::_physical_memory = 0; bool os::win32::_is_windows_server = false; @@ -4128,8 +4145,11 @@ void os::win32::initialize_system_info() { // also returns dwAvailPhys (free physical memory bytes), dwTotalVirtual, dwAvailVirtual, // dwMemoryLoad (% of memory in use) - GlobalMemoryStatusEx(&ms); - _physical_memory = ms.ullTotalPhys; + BOOL res = GlobalMemoryStatusEx(&ms); + if (res != TRUE) { + assert(false, "GlobalMemoryStatusEx failed in os::win32::initialize_system_info(): %lu", ::GetLastError()); + } + _physical_memory = static_cast(ms.ullTotalPhys); if (FLAG_IS_DEFAULT(MaxRAM)) { // Adjust MaxRAM according to the maximum virtual address space available. diff --git a/src/hotspot/os/windows/os_windows.hpp b/src/hotspot/os/windows/os_windows.hpp index 1aba43fb3d25..1426dc8be930 100644 --- a/src/hotspot/os/windows/os_windows.hpp +++ b/src/hotspot/os/windows/os_windows.hpp @@ -40,7 +40,7 @@ class os::win32 { protected: static int _processor_type; static int _processor_level; - static julong _physical_memory; + static size_t _physical_memory; static bool _is_windows_server; static bool _has_exit_bug; static bool _processor_group_warning_displayed; @@ -102,9 +102,9 @@ class os::win32 { static int processor_level() { return _processor_level; } - static julong available_memory(); - static julong free_memory(); - static julong physical_memory() { return _physical_memory; } + static bool available_memory(size_t& value); + static bool free_memory(size_t& value); + static size_t physical_memory() { return _physical_memory; } // load dll from Windows system directory or Windows directory static HINSTANCE load_Windows_dll(const char* name, char *ebuf, int ebuflen); diff --git a/src/hotspot/share/compiler/compileBroker.cpp b/src/hotspot/share/compiler/compileBroker.cpp index f8711e8785a8..75b5d11e1c07 100644 --- a/src/hotspot/share/compiler/compileBroker.cpp +++ b/src/hotspot/share/compiler/compileBroker.cpp @@ -1058,7 +1058,9 @@ void CompileBroker::possibly_add_compiler_threads(JavaThread* THREAD) { if (new_c2_count <= old_c2_count && new_c1_count <= old_c1_count) return; // Now, we do the more expensive operations. - julong free_memory = os::free_memory(); + size_t free_memory = 0; + // Return value ignored - defaulting to 0 on failure. + (void)os::free_memory(free_memory); // If SegmentedCodeCache is off, both values refer to the single heap (with type CodeBlobType::All). size_t available_cc_np = CodeCache::unallocated_capacity(CodeBlobType::MethodNonProfiled), available_cc_p = CodeCache::unallocated_capacity(CodeBlobType::MethodProfiled); diff --git a/src/hotspot/share/gc/shared/gcInitLogger.cpp b/src/hotspot/share/gc/shared/gcInitLogger.cpp index 91bebf726c12..763c265b65ea 100644 --- a/src/hotspot/share/gc/shared/gcInitLogger.cpp +++ b/src/hotspot/share/gc/shared/gcInitLogger.cpp @@ -62,9 +62,8 @@ void GCInitLogger::print_cpu() { } void GCInitLogger::print_memory() { - julong memory = os::physical_memory(); - log_info_p(gc, init)("Memory: " JULONG_FORMAT "%s", - byte_size_in_proper_unit(memory), proper_unit_for_byte_size(memory)); + size_t memory = os::physical_memory(); + log_info_p(gc, init)("Memory: " PROPERFMT, PROPERFMTARGS(memory)); } void GCInitLogger::print_large_pages() { diff --git a/src/hotspot/share/gc/z/zLargePages.cpp b/src/hotspot/share/gc/z/zLargePages.cpp index 56c94a75713c..639c9b0a04fb 100644 --- a/src/hotspot/share/gc/z/zLargePages.cpp +++ b/src/hotspot/share/gc/z/zLargePages.cpp @@ -31,7 +31,8 @@ bool ZLargePages::_os_enforced_transparent_mode; void ZLargePages::initialize() { pd_initialize(); - log_info_p(gc, init)("Memory: " JULONG_FORMAT "M", os::physical_memory() / M); + const size_t memory = os::physical_memory(); + log_info_p(gc, init)("Memory: " PROPERFMT, PROPERFMTARGS(memory)); log_info_p(gc, init)("Large Page Support: %s", to_string()); } diff --git a/src/hotspot/share/jfr/jni/jfrJniMethod.cpp b/src/hotspot/share/jfr/jni/jfrJniMethod.cpp index 0b4ab5320648..6a2ba7bc2456 100644 --- a/src/hotspot/share/jfr/jni/jfrJniMethod.cpp +++ b/src/hotspot/share/jfr/jni/jfrJniMethod.cpp @@ -411,9 +411,9 @@ JVM_ENTRY_NO_ENV(jlong, jfr_host_total_memory(JNIEnv* env, jclass jvm)) #ifdef LINUX // We want the host memory, not the container limit. // os::physical_memory() would return the container limit. - return os::Linux::physical_memory(); + return static_cast(os::Linux::physical_memory()); #else - return os::physical_memory(); + return static_cast(os::physical_memory()); #endif JVM_END @@ -422,7 +422,10 @@ JVM_ENTRY_NO_ENV(jlong, jfr_host_total_swap_memory(JNIEnv* env, jclass jvm)) // We want the host swap memory, not the container value. return os::Linux::host_swap(); #else - return os::total_swap_space(); + size_t total_swap_space = 0; + // Return value ignored - defaulting to 0 on failure. + (void)os::total_swap_space(total_swap_space); + return static_cast(total_swap_space); #endif JVM_END diff --git a/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp b/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp index 00d41a10bf0e..56b0a2f82d3b 100644 --- a/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp +++ b/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp @@ -528,17 +528,26 @@ TRACE_REQUEST_FUNC(ThreadAllocationStatistics) { * the total memory reported is the amount of memory configured for the guest OS by the hypervisor. */ TRACE_REQUEST_FUNC(PhysicalMemory) { - u8 totalPhysicalMemory = os::physical_memory(); + u8 totalPhysicalMemory = static_cast(os::physical_memory()); EventPhysicalMemory event; event.set_totalSize(totalPhysicalMemory); - event.set_usedSize(totalPhysicalMemory - os::available_memory()); + size_t avail_mem = 0; + // Return value ignored - defaulting to 0 on failure. + (void)os::available_memory(avail_mem); + event.set_usedSize(totalPhysicalMemory - static_cast(avail_mem)); event.commit(); } TRACE_REQUEST_FUNC(SwapSpace) { EventSwapSpace event; - event.set_totalSize(os::total_swap_space()); - event.set_freeSize(os::free_swap_space()); + size_t total_swap_space = 0; + // Return value ignored - defaulting to 0 on failure. + (void)os::total_swap_space(total_swap_space); + event.set_totalSize(static_cast(total_swap_space)); + size_t free_swap_space = 0; + // Return value ignored - defaulting to 0 on failure. + (void)os::free_swap_space(free_swap_space); + event.set_freeSize(static_cast(free_swap_space)); event.commit(); } diff --git a/src/hotspot/share/prims/jvmtiRedefineClasses.cpp b/src/hotspot/share/prims/jvmtiRedefineClasses.cpp index 4257d7e997b9..12009e3e8e4d 100644 --- a/src/hotspot/share/prims/jvmtiRedefineClasses.cpp +++ b/src/hotspot/share/prims/jvmtiRedefineClasses.cpp @@ -1356,10 +1356,12 @@ jvmtiError VM_RedefineClasses::load_new_class_versions() { // constant pools HandleMark hm(current); InstanceKlass* the_class = get_ik(_class_defs[i].klass); - + size_t avail_mem = 0; + // Return value ignored - defaulting to 0 on failure. + (void)os::available_memory(avail_mem); log_debug(redefine, class, load) - ("loading name=%s kind=%d (avail_mem=" UINT64_FORMAT "K)", - the_class->external_name(), _class_load_kind, os::available_memory() >> 10); + ("loading name=%s kind=%d (avail_mem=%zuK)", + the_class->external_name(), _class_load_kind, avail_mem >> 10); ClassFileStream st((u1*)_class_defs[i].class_bytes, _class_defs[i].class_byte_count, @@ -1523,9 +1525,10 @@ jvmtiError VM_RedefineClasses::load_new_class_versions() { return JVMTI_ERROR_INTERNAL; } } - + // Return value ignored - defaulting to 0 on failure. + (void)os::available_memory(avail_mem); log_debug(redefine, class, load) - ("loaded name=%s (avail_mem=" UINT64_FORMAT "K)", the_class->external_name(), os::available_memory() >> 10); + ("loaded name=%s (avail_mem=%zuK)", the_class->external_name(), avail_mem >> 10); } return JVMTI_ERROR_NONE; @@ -4422,9 +4425,12 @@ void VM_RedefineClasses::redefine_single_class(Thread* current, jclass the_jclas ResourceMark rm(current); // increment the classRedefinedCount field in the_class and in any // direct and indirect subclasses of the_class + size_t avail_mem = 0; + // Return value ignored - defaulting to 0 on failure. + (void)os::available_memory(avail_mem); log_info(redefine, class, load) - ("redefined name=%s, count=%d (avail_mem=" UINT64_FORMAT "K)", - the_class->external_name(), java_lang_Class::classRedefinedCount(the_class->java_mirror()), os::available_memory() >> 10); + ("redefined name=%s, count=%d (avail_mem=%zuK)", + the_class->external_name(), java_lang_Class::classRedefinedCount(the_class->java_mirror()), avail_mem >> 10); Events::log_redefinition(current, "redefined class name=%s, count=%d", the_class->external_name(), java_lang_Class::classRedefinedCount(the_class->java_mirror())); diff --git a/src/hotspot/share/prims/whitebox.cpp b/src/hotspot/share/prims/whitebox.cpp index f9f37d84ddef..2013e33ed188 100644 --- a/src/hotspot/share/prims/whitebox.cpp +++ b/src/hotspot/share/prims/whitebox.cpp @@ -2544,13 +2544,16 @@ WB_END // Physical memory of the host machine (including containers) WB_ENTRY(jlong, WB_HostPhysicalMemory(JNIEnv* env, jobject o)) - LINUX_ONLY(return os::Linux::physical_memory();) - return os::physical_memory(); + LINUX_ONLY(return static_cast(os::Linux::physical_memory());) + return static_cast(os::physical_memory()); WB_END // Available memory of the host machine (container-aware) WB_ENTRY(jlong, WB_HostAvailableMemory(JNIEnv* env, jobject o)) - return os::available_memory(); + size_t avail_mem = 0; + // Return value ignored - defaulting to 0 on failure. + (void)os::available_memory(avail_mem); + return static_cast(avail_mem); WB_END // Physical swap of the host machine (including containers), Linux only. diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp index 47398ef48128..196e4f215c20 100644 --- a/src/hotspot/share/runtime/arguments.cpp +++ b/src/hotspot/share/runtime/arguments.cpp @@ -1503,13 +1503,13 @@ void Arguments::set_heap_size() { !FLAG_IS_DEFAULT(MaxRAM)); if (override_coop_limit) { if (FLAG_IS_DEFAULT(MaxRAM)) { - phys_mem = os::physical_memory(); + phys_mem = static_cast(os::physical_memory()); FLAG_SET_ERGO(MaxRAM, (uint64_t)phys_mem); } else { phys_mem = (julong)MaxRAM; } } else { - phys_mem = FLAG_IS_DEFAULT(MaxRAM) ? MIN2(os::physical_memory(), (julong)MaxRAM) + phys_mem = FLAG_IS_DEFAULT(MaxRAM) ? MIN2(static_cast(os::physical_memory()), (julong)MaxRAM) : (julong)MaxRAM; } @@ -1631,7 +1631,8 @@ jint Arguments::set_aggressive_heap_flags() { // Thus, we need to make sure we're using a julong for intermediate // calculations. julong initHeapSize; - julong total_memory = os::physical_memory(); + size_t phys_mem = os::physical_memory(); + julong total_memory = static_cast(phys_mem); if (total_memory < (julong) 256 * M) { jio_fprintf(defaultStream::error_stream(), diff --git a/src/hotspot/share/runtime/os.cpp b/src/hotspot/share/runtime/os.cpp index 1e1f8e90be62..0ed754e027c4 100644 --- a/src/hotspot/share/runtime/os.cpp +++ b/src/hotspot/share/runtime/os.cpp @@ -1183,9 +1183,10 @@ void os::print_summary_info(outputStream* st, char* buf, size_t buflen) { #endif // PRODUCT get_summary_cpu_info(buf, buflen); st->print("%s, ", buf); - size_t mem = physical_memory()/G; + size_t phys_mem = physical_memory(); + size_t mem = phys_mem/G; if (mem == 0) { // for low memory systems - mem = physical_memory()/M; + mem = phys_mem/M; st->print("%d cores, %zuM, ", processor_count(), mem); } else { st->print("%d cores, %zuG, ", processor_count(), mem); @@ -1940,10 +1941,10 @@ bool os::is_server_class_machine() { // We allow some part (1/8?) of the memory to be "missing", // based on the sizes of DIMMs, and maybe graphics cards. const julong missing_memory = 256UL * M; - + size_t phys_mem = os::physical_memory(); /* Is this a server class machine? */ if ((os::active_processor_count() >= (int)server_processors) && - (os::physical_memory() >= (server_memory - missing_memory))) { + (phys_mem >= (server_memory - missing_memory))) { const unsigned int logical_processors = VM_Version::logical_processors_per_package(); if (logical_processors > 1) { @@ -2202,16 +2203,24 @@ static void assert_nonempty_range(const char* addr, size_t bytes) { p2i(addr), p2i(addr) + bytes); } -julong os::used_memory() { +bool os::used_memory(size_t& value) { #ifdef LINUX if (OSContainer::is_containerized()) { jlong mem_usage = OSContainer::memory_usage_in_bytes(); if (mem_usage > 0) { - return mem_usage; + value = static_cast(mem_usage); + return true; + } else { + return false; } } #endif - return os::physical_memory() - os::available_memory(); + size_t avail_mem = 0; + // Return value ignored - defaulting to 0 on failure. + (void)os::available_memory(avail_mem); + size_t phys_mem = os::physical_memory(); + value = phys_mem - avail_mem; + return true; } diff --git a/src/hotspot/share/runtime/os.hpp b/src/hotspot/share/runtime/os.hpp index bb07abad6b14..6739e97efc50 100644 --- a/src/hotspot/share/runtime/os.hpp +++ b/src/hotspot/share/runtime/os.hpp @@ -337,14 +337,14 @@ class os: AllStatic { // For example, on Linux, "available" memory (`MemAvailable` in `/proc/meminfo`) is greater // than "free" memory (`MemFree` in `/proc/meminfo`) because Linux can free memory // aggressively (e.g. clear caches) so that it becomes available. - static julong available_memory(); - static julong used_memory(); - static julong free_memory(); + [[nodiscard]] static bool available_memory(size_t& value); + [[nodiscard]] static bool used_memory(size_t& value); + [[nodiscard]] static bool free_memory(size_t& value); - static jlong total_swap_space(); - static jlong free_swap_space(); + [[nodiscard]] static bool total_swap_space(size_t& value); + [[nodiscard]] static bool free_swap_space(size_t& value); - static julong physical_memory(); + static size_t physical_memory(); static bool has_allocatable_memory_limit(size_t* limit); static bool is_server_class_machine(); static size_t rss(); diff --git a/src/hotspot/share/services/heapDumper.cpp b/src/hotspot/share/services/heapDumper.cpp index 731a26f7cf1e..1caa3ce354c6 100644 --- a/src/hotspot/share/services/heapDumper.cpp +++ b/src/hotspot/share/services/heapDumper.cpp @@ -2612,7 +2612,10 @@ int HeapDumper::dump(const char* path, outputStream* out, int compression, bool // (DumpWriter buffer, DumperClassCacheTable, GZipCompressor buffers). // For the OOM handling we may already be limited in memory. // Lets ensure we have at least 20MB per thread. - julong max_threads = os::free_memory() / (20 * M); + size_t free_memory = 0; + // Return value ignored - defaulting to 0 on failure. + (void)os::free_memory(free_memory); + julong max_threads = free_memory / (20 * M); if (num_dump_threads > max_threads) { num_dump_threads = MAX2(1, (uint)max_threads); } diff --git a/src/hotspot/share/services/management.cpp b/src/hotspot/share/services/management.cpp index 070d4daa3fb5..c414938debb1 100644 --- a/src/hotspot/share/services/management.cpp +++ b/src/hotspot/share/services/management.cpp @@ -975,7 +975,7 @@ static jlong get_long_attribute(jmmLongAttribute att) { return ClassLoadingService::class_method_data_size(); case JMM_OS_MEM_TOTAL_PHYSICAL_BYTES: - return os::physical_memory(); + return static_cast(os::physical_memory()); default: return -1; From 2829159100043e3e0619d827eaf8370b0727b099 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 16 Mar 2026 14:53:36 +0000 Subject: [PATCH 031/168] 8376233: Clean up code in Desktop native peer Backport-of: c73f05bec95c3ef0d8b6235b67478352db9a48a9 --- .../macosx/native/libawt_lwawt/awt/CDesktopPeer.m | 5 ++++- .../windows/native/libawt/windows/awt_Desktop.cpp | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/java.desktop/macosx/native/libawt_lwawt/awt/CDesktopPeer.m b/src/java.desktop/macosx/native/libawt_lwawt/awt/CDesktopPeer.m index e1841c9398c6..460749c363dd 100644 --- a/src/java.desktop/macosx/native/libawt_lwawt/awt/CDesktopPeer.m +++ b/src/java.desktop/macosx/native/libawt_lwawt/awt/CDesktopPeer.m @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -120,6 +120,7 @@ if (appURI == nil || [[urlToOpen absoluteString] containsString:[appURI absoluteString]] || [[defaultTerminalApp absoluteString] containsString:[appURI absoluteString]]) { + [urlToOpen release]; return -1; } // Additionally set forPrinting=TRUE for print @@ -129,6 +130,7 @@ } else if (action == sun_lwawt_macosx_CDesktopPeer_EDIT) { if (appURI == nil || [[urlToOpen absoluteString] containsString:[appURI absoluteString]]) { + [urlToOpen release]; return -1; } // for EDIT: if (defaultApp = TerminalApp) then set appURI = DefaultTextEditor @@ -156,6 +158,7 @@ dispatch_semaphore_wait(semaphore, timeout); + [urlToOpen release]; JNI_COCOA_EXIT(env); return status; } diff --git a/src/java.desktop/windows/native/libawt/windows/awt_Desktop.cpp b/src/java.desktop/windows/native/libawt/windows/awt_Desktop.cpp index ebb43b2f0789..ba69fa75f734 100644 --- a/src/java.desktop/windows/native/libawt/windows/awt_Desktop.cpp +++ b/src/java.desktop/windows/native/libawt/windows/awt_Desktop.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -92,6 +92,8 @@ JNIEXPORT jstring JNICALL Java_sun_awt_windows_WDesktopPeer_ShellExecute if (wcscmp(verb_c, L"open") == 0) { BOOL isExecutable = SaferiIsExecutableFileType(fileOrUri_c, FALSE); if (isExecutable) { + JNU_ReleaseStringPlatformChars(env, fileOrUri_j, fileOrUri_c); + JNU_ReleaseStringPlatformChars(env, verb_j, verb_c); return env->NewStringUTF("Unsupported URI content"); } } From cc0e99089ba9a029c1ebde3de3d241aa12a90642 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 16 Mar 2026 15:20:25 +0000 Subject: [PATCH 032/168] 8364315: Remove unused xml files from test/jaxp/javax/xml/jaxp/functional/javax/xml/transform/xmlfiles Backport-of: b8acbc3ed8675ad4cc4b9dea69ee1e87c2a2ca45 --- .../javax/xml/transform/xmlfiles/lexical.xml | 24 ------------------- .../xml/transform/xmlfiles/out/doctypeGF.out | 21 ---------------- .../javax/xml/transform/xmlfiles/publish2.xml | 23 ------------------ .../org/xml/sax/xmlfiles/out/DTDHandlerGF.out | 2 -- 4 files changed, 70 deletions(-) delete mode 100644 test/jaxp/javax/xml/jaxp/functional/javax/xml/transform/xmlfiles/lexical.xml delete mode 100644 test/jaxp/javax/xml/jaxp/functional/javax/xml/transform/xmlfiles/out/doctypeGF.out delete mode 100644 test/jaxp/javax/xml/jaxp/functional/javax/xml/transform/xmlfiles/publish2.xml delete mode 100644 test/jaxp/javax/xml/jaxp/functional/org/xml/sax/xmlfiles/out/DTDHandlerGF.out diff --git a/test/jaxp/javax/xml/jaxp/functional/javax/xml/transform/xmlfiles/lexical.xml b/test/jaxp/javax/xml/jaxp/functional/javax/xml/transform/xmlfiles/lexical.xml deleted file mode 100644 index e7ea712cdd96..000000000000 --- a/test/jaxp/javax/xml/jaxp/functional/javax/xml/transform/xmlfiles/lexical.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - Publishers of the Music of New York Women Composers - The Publishers <![CDATA[<?xml>]]> - - - ACA - info@composers.com - http://www.composers.com/ -
- 170 West 74th St. - NY - NY - 10023 -
- 212-362-8900 - 212-874-8605 - - &familytree; -
-
- diff --git a/test/jaxp/javax/xml/jaxp/functional/javax/xml/transform/xmlfiles/out/doctypeGF.out b/test/jaxp/javax/xml/jaxp/functional/javax/xml/transform/xmlfiles/out/doctypeGF.out deleted file mode 100644 index d4a9d98475f3..000000000000 --- a/test/jaxp/javax/xml/jaxp/functional/javax/xml/transform/xmlfiles/out/doctypeGF.out +++ /dev/null @@ -1,21 +0,0 @@ - - - - Publishers of the Music of New York Women Composers - The Publishers - - ACA - info@composers.com - http://www.composers.com/ -
- 170 West 74th St. - NY - NY - 10023 -
- 212-362-8900 - 212-874-8605 - - -
-
diff --git a/test/jaxp/javax/xml/jaxp/functional/javax/xml/transform/xmlfiles/publish2.xml b/test/jaxp/javax/xml/jaxp/functional/javax/xml/transform/xmlfiles/publish2.xml deleted file mode 100644 index 789983f79f31..000000000000 --- a/test/jaxp/javax/xml/jaxp/functional/javax/xml/transform/xmlfiles/publish2.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - Publishers of the Music of New York Women Composers - The Publishers - - ACA - info@composers.com - http://www.composers.com/ -
- 170 West 74th St. - NY - NY - 10023 -
- 212-362-8900 - 212-874-8605 - - &familytree; -
-
- diff --git a/test/jaxp/javax/xml/jaxp/functional/org/xml/sax/xmlfiles/out/DTDHandlerGF.out b/test/jaxp/javax/xml/jaxp/functional/org/xml/sax/xmlfiles/out/DTDHandlerGF.out deleted file mode 100644 index f4b4241dd397..000000000000 --- a/test/jaxp/javax/xml/jaxp/functional/org/xml/sax/xmlfiles/out/DTDHandlerGF.out +++ /dev/null @@ -1,2 +0,0 @@ -In unparsedEntityDecl... name:logo publicId:null systemId:http://sc11152338.us.oracle.com:8080/xmlsqe/jaxp/web/testfiles/JAXPREP/images/tool.gif notationName:gif -In notationDecl... name:gif publicId:null systemId:http://sardinia/ From c42de314665eee4f43e468af253f9bc9160fdc58 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 16 Mar 2026 15:21:01 +0000 Subject: [PATCH 033/168] 8225787: java/awt/Window/GetScreenLocation/GetScreenLocationTest.java fails on Ubuntu 8203004: UnixMultiResolutionSplashTest.java fails on Ubuntu16.04 Backport-of: 9c0f41e9973726df0544bf0c7f06a7eb214b849f --- test/jdk/ProblemList.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/jdk/ProblemList.txt b/test/jdk/ProblemList.txt index 8f8553452ced..6048af5d4254 100644 --- a/test/jdk/ProblemList.txt +++ b/test/jdk/ProblemList.txt @@ -456,7 +456,6 @@ java/awt/Focus/NonFocusableBlockedOwnerTest/NonFocusableBlockedOwnerTest.java 71 java/awt/Focus/TranserFocusToWindow/TranserFocusToWindow.java 6848810 macosx-all,linux-all java/awt/FileDialog/ModalFocus/FileDialogModalFocusTest.java 8194751 linux-all java/awt/image/VolatileImage/BitmaskVolatileImage.java 8133102 linux-all -java/awt/SplashScreen/MultiResolutionSplash/unix/UnixMultiResolutionSplashTest.java 8203004 linux-all java/awt/ScrollPane/ScrollPaneEventType.java 8296516 macosx-all java/awt/Robot/AcceptExtraMouseButtons/AcceptExtraMouseButtons.java 7107528 linux-all,macosx-all java/awt/Mouse/MouseDragEvent/MouseDraggedTest.java 8080676 linux-all @@ -494,7 +493,6 @@ java/awt/KeyboardFocusmanager/ConsumeNextMnemonicKeyTypedTest/ConsumeForModalDia java/awt/KeyboardFocusmanager/TypeAhead/MenuItemActivatedTest/MenuItemActivatedTest.java 8302787 windows-all java/awt/KeyboardFocusmanager/ConsumeNextMnemonicKeyTypedTest/ConsumeNextMnemonicKeyTypedTest.java 8321303 linux-all -java/awt/Window/GetScreenLocation/GetScreenLocationTest.java 8225787 linux-x64 java/awt/Dialog/MakeWindowAlwaysOnTop/MakeWindowAlwaysOnTop.java 8266243 macosx-aarch64 java/awt/Dialog/ChoiceModalDialogTest.java 8161475 macosx-all java/awt/Dialog/FileDialogUserFilterTest.java 8001142 generic-all From ec06e8c90174a3190b7d91541135a24cd27dbc0f Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 16 Mar 2026 15:21:39 +0000 Subject: [PATCH 034/168] 8355339: Test java/io/File/GetCanonicalPath.java failed: The specified network name is no longer available Backport-of: aabf699dd0f066efe6654db24b520068b256d855 --- .../windows/native/libjava/canonicalize_md.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/java.base/windows/native/libjava/canonicalize_md.c b/src/java.base/windows/native/libjava/canonicalize_md.c index ecfdf63d0916..7e567c7fbb41 100644 --- a/src/java.base/windows/native/libjava/canonicalize_md.c +++ b/src/java.base/windows/native/libjava/canonicalize_md.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -41,7 +41,7 @@ /* We should also include jdk_util.h here, for the prototype of JDK_Canonicalize. This isn't possible though because canonicalize_md.c is as well used in different contexts within Oracle. - */ +*/ #include "io_util_md.h" /* Copy bytes to dst, not going past dend; return dst + number of bytes copied, @@ -139,7 +139,8 @@ lastErrorReportable() || (errval == ERROR_ACCESS_DENIED) || (errval == ERROR_NETWORK_UNREACHABLE) || (errval == ERROR_NETWORK_ACCESS_DENIED) - || (errval == ERROR_NO_MORE_FILES)) { + || (errval == ERROR_NO_MORE_FILES) + || (errval == ERROR_NETNAME_DELETED)) { return 0; } return 1; @@ -183,7 +184,7 @@ wcanonicalize(WCHAR *orig_path, WCHAR *result, int size) /* Copy prefix, assuming path is absolute */ c = src[0]; if (((c <= L'z' && c >= L'a') || (c <= L'Z' && c >= L'A')) - && (src[1] == L':') && (src[2] == L'\\')) { + && (src[1] == L':') && (src[2] == L'\\')) { /* Drive specifier */ *src = towupper(*src); /* Canonicalize drive letter */ if (!(dst = wcp(dst, dend, L'\0', src, src + 2))) { @@ -244,9 +245,9 @@ wcanonicalize(WCHAR *orig_path, WCHAR *result, int size) continue; } else { if (!lastErrorReportable()) { - if (!(dst = wcp(dst, dend, L'\0', src, src + wcslen(src)))){ - goto err; - } + if (!(dst = wcp(dst, dend, L'\0', src, src + wcslen(src)))){ + goto err; + } break; } else { goto err; @@ -255,7 +256,7 @@ wcanonicalize(WCHAR *orig_path, WCHAR *result, int size) } if (dst >= dend) { - errno = ENAMETOOLONG; + errno = ENAMETOOLONG; goto err; } *dst = L'\0'; @@ -366,7 +367,7 @@ JDK_Canonicalize(const char *orig, char *out, int len) { // Change return value to success. ret = 0; -finish: + finish: free(wresult); free(wpath); From c8e60784e60b56c8e5f400ade72d5cfa0d4d48e9 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 16 Mar 2026 15:22:09 +0000 Subject: [PATCH 035/168] 8373718: jdk/internal/misc/VM/RuntimeArguments.java test fails in Virtual threads mode Backport-of: 4d0ad0a4a391286c683ebb8c8d711ea0be68c31a --- test/jdk/jdk/internal/misc/VM/RuntimeArguments.java | 1 + 1 file changed, 1 insertion(+) diff --git a/test/jdk/jdk/internal/misc/VM/RuntimeArguments.java b/test/jdk/jdk/internal/misc/VM/RuntimeArguments.java index dbcb30255a8d..b86593d84ba7 100644 --- a/test/jdk/jdk/internal/misc/VM/RuntimeArguments.java +++ b/test/jdk/jdk/internal/misc/VM/RuntimeArguments.java @@ -24,6 +24,7 @@ /** * @test * @requires vm.flagless + * @requires test.thread.factory == null * @library /test/lib * @modules java.base/jdk.internal.misc * jdk.zipfs From 09103d3fd74da92f3eb97b2e02f68cac57718ef0 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 16 Mar 2026 15:25:33 +0000 Subject: [PATCH 036/168] 8298823: [macos] java/awt/Mouse/EnterExitEvents/DragWindowTest.java continues to fail with "No MouseReleased event on label!" Backport-of: fa6e884105ac247b3b83a5a2329f9c18888bd7d0 --- test/jdk/ProblemList.txt | 1 - .../Mouse/EnterExitEvents/DragWindowTest.java | 113 +++++++----------- 2 files changed, 45 insertions(+), 69 deletions(-) diff --git a/test/jdk/ProblemList.txt b/test/jdk/ProblemList.txt index 6048af5d4254..52cda55446bf 100644 --- a/test/jdk/ProblemList.txt +++ b/test/jdk/ProblemList.txt @@ -188,7 +188,6 @@ java/awt/Mixing/AWT_Mixing/JTextFieldOverlapping.java 8158801 windows-all java/awt/Mixing/AWT_Mixing/JToggleButtonInGlassPaneOverlapping.java 8158801 windows-all java/awt/Mixing/AWT_Mixing/JToggleButtonOverlapping.java 8158801 windows-all java/awt/Mixing/NonOpaqueInternalFrame.java 7124549 macosx-all -java/awt/Mouse/EnterExitEvents/DragWindowTest.java 8298823 macosx-all java/awt/Focus/ActualFocusedWindowTest/ActualFocusedWindowRetaining.java 6829264 generic-all java/awt/datatransfer/DragImage/MultiResolutionDragImageTest.java 8080982 generic-all java/awt/datatransfer/SystemFlavorMap/AddFlavorTest.java 8079268 linux-all diff --git a/test/jdk/java/awt/Mouse/EnterExitEvents/DragWindowTest.java b/test/jdk/java/awt/Mouse/EnterExitEvents/DragWindowTest.java index 4f789668c4a5..2a77f294298d 100644 --- a/test/jdk/java/awt/Mouse/EnterExitEvents/DragWindowTest.java +++ b/test/jdk/java/awt/Mouse/EnterExitEvents/DragWindowTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,11 +26,8 @@ * @key headful * @bug 7154048 * @summary Window created under a mouse does not receive mouse enter event. - * Mouse Entered/Exited events are wrongly generated during dragging the window - * from one component to another - * @library ../../regtesthelpers - * @build Util - * @author alexandr.scherbatiy area=awt.event + * Mouse Entered/Exited events are wrongly generated during dragging the + * window from one component to another * @run main DragWindowTest */ @@ -50,76 +47,67 @@ import javax.swing.JPanel; import javax.swing.SwingUtilities; -import java.util.concurrent.Callable; - -import test.java.awt.regtesthelpers.Util; - public class DragWindowTest { - private static volatile int dragWindowMouseEnteredCount = 0; - private static volatile int dragWindowMouseReleasedCount = 0; private static volatile int buttonMouseEnteredCount = 0; private static volatile int labelMouseReleasedCount = 0; + + private static volatile Point pointToClick; + private static volatile Point pointToDrag; + private static MyDragWindow dragWindow; private static JLabel label; private static JButton button; + private static JFrame frame; public static void main(String[] args) throws Exception { + try { + Robot robot = new Robot(); + robot.setAutoDelay(100); - Robot robot = new Robot(); - robot.setAutoDelay(100); - - SwingUtilities.invokeAndWait(new Runnable() { + SwingUtilities.invokeAndWait(DragWindowTest::createAndShowGUI); - @Override - public void run() { - createAndShowGUI(); - } - }); + robot.delay(250); + robot.waitForIdle(); - robot.delay(250); - robot.waitForIdle(); + SwingUtilities.invokeAndWait(() -> { + pointToClick = getCenterPoint(label); + pointToDrag = getCenterPoint(button); + }); - Point pointToClick = Util.invokeOnEDT(new Callable() { + robot.mouseMove(pointToClick.x, pointToClick.y); + robot.mousePress(InputEvent.BUTTON1_DOWN_MASK); + robot.waitForIdle(); + robot.delay(250); - @Override - public Point call() throws Exception { - return getCenterPoint(label); + if (dragWindowMouseEnteredCount != 1) { + throw new RuntimeException("No MouseEntered event on Drag Window!"); } - }); + // Reset entered count to check if mouse entered starting from here + buttonMouseEnteredCount = 0; + robot.mouseMove(pointToDrag.x, pointToDrag.y); + robot.waitForIdle(); + robot.delay(250); - robot.mouseMove(pointToClick.x, pointToClick.y); - robot.mousePress(InputEvent.BUTTON1_MASK); - robot.waitForIdle(); - - if (dragWindowMouseEnteredCount != 1) { - throw new RuntimeException("No MouseEntered event on Drag Window!"); - } - - Point pointToDrag = Util.invokeOnEDT(new Callable() { - - @Override - public Point call() throws Exception { - button.addMouseListener(new ButtonMouseListener()); - return getCenterPoint(button); + if (buttonMouseEnteredCount != 0) { + throw new RuntimeException("Extra MouseEntered event on button!"); } - }); - - robot.mouseMove(pointToDrag.x, pointToDrag.y); - robot.waitForIdle(); - - if (buttonMouseEnteredCount != 0) { - throw new RuntimeException("Extra MouseEntered event on button!"); - } - robot.mouseRelease(InputEvent.BUTTON1_MASK); - robot.waitForIdle(); + robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK); + robot.waitForIdle(); + robot.delay(250); - if (labelMouseReleasedCount != 1) { - throw new RuntimeException("No MouseReleased event on label!"); + if (labelMouseReleasedCount != 1) { + throw new RuntimeException("No MouseReleased event on label!"); + } + } finally { + SwingUtilities.invokeAndWait(() -> { + if (frame != null) { + frame.dispose(); + } + }); } - } private static Point getCenterPoint(Component comp) { @@ -129,8 +117,7 @@ private static Point getCenterPoint(Component comp) { } private static void createAndShowGUI() { - - JFrame frame = new JFrame("Main Frame"); + frame = new JFrame("DragWindowTest"); frame.setSize(300, 200); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); @@ -142,6 +129,7 @@ private static void createAndShowGUI() { button = new JButton("Button"); Panel panel = new Panel(new BorderLayout()); + button.addMouseListener(new ButtonMouseListener()); panel.add(label, BorderLayout.NORTH); panel.add(button, BorderLayout.CENTER); @@ -149,7 +137,6 @@ private static void createAndShowGUI() { frame.getContentPane().add(panel); frame.setLocationRelativeTo(null); frame.setVisible(true); - } private static Point getAbsoluteLocation(MouseEvent e) { @@ -157,7 +144,6 @@ private static Point getAbsoluteLocation(MouseEvent e) { } static class MyDragWindow extends Window { - static int d = 30; public MyDragWindow(Window parent, Point location) { @@ -176,8 +162,6 @@ void dragTo(Point point) { } static class LabelMouseListener extends MouseAdapter { - - Point origin; Window parent; public LabelMouseListener(Window parent) { @@ -210,20 +194,13 @@ public void mouseDragged(MouseEvent e) { } static class DragWindowMouseListener extends MouseAdapter { - @Override public void mouseEntered(MouseEvent e) { dragWindowMouseEnteredCount++; } - - @Override - public void mouseReleased(MouseEvent e) { - dragWindowMouseReleasedCount++; - } } static class ButtonMouseListener extends MouseAdapter { - @Override public void mouseEntered(MouseEvent e) { buttonMouseEnteredCount++; From 61eba7c09d1dc2ae9cd4b86aab13eddccc5cabdb Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 16 Mar 2026 15:31:42 +0000 Subject: [PATCH 037/168] =?UTF-8?q?8373847:=20Test=20javax/swing/JMenuItem?= =?UTF-8?q?/MenuItemTest/bug6197830.java=20failed=20because=20The=20test?= =?UTF-8?q?=20case=20automatically=20fails=20when=20clicking=20any=20items?= =?UTF-8?q?=20in=20the=20=E2=80=9CNothing=E2=80=9D=20menu=20in=20all=20fou?= =?UTF-8?q?r=20windows=20(Left-to-right)-Menu=20Item=20Test=20and=20(Right?= =?UTF-8?q?-to-left)-Menu=20Item=20Test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backport-of: 05d2f7f4080f5cc6d3eef97878806e28773d6f70 --- test/jdk/javax/swing/JMenuItem/MenuItemTest/bug6197830.java | 1 + 1 file changed, 1 insertion(+) diff --git a/test/jdk/javax/swing/JMenuItem/MenuItemTest/bug6197830.java b/test/jdk/javax/swing/JMenuItem/MenuItemTest/bug6197830.java index afb1c7bb33f9..5a30e202d9da 100644 --- a/test/jdk/javax/swing/JMenuItem/MenuItemTest/bug6197830.java +++ b/test/jdk/javax/swing/JMenuItem/MenuItemTest/bug6197830.java @@ -49,6 +49,7 @@ public static void main(String[] args) throws Exception { .columns(35) .testUI(bug6197830::createTestUI) .positionTestUIBottomRowCentered() + .logArea() .build() .awaitAndCheck(); } From c125ee6f6a1df8f54ccf07227bfca2c349b83fba Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 16 Mar 2026 15:32:32 +0000 Subject: [PATCH 038/168] 8370370: Add still more cases to WorstCaseTests Backport-of: 8151251fa683459e57430abf8e3583c444315746 --- test/jdk/java/lang/Math/WorstCaseTests.java | 39 +++++++++++++++++-- .../java/lang/StrictMath/CubeRootTests.java | 3 +- .../java/lang/StrictMath/HyperbolicTests.java | 14 ++++++- test/jdk/java/lang/StrictMath/Log10Tests.java | 5 ++- test/jdk/java/lang/StrictMath/Log1pTests.java | 3 +- test/jdk/java/lang/StrictMath/TrigTests.java | 6 ++- 6 files changed, 61 insertions(+), 9 deletions(-) diff --git a/test/jdk/java/lang/Math/WorstCaseTests.java b/test/jdk/java/lang/Math/WorstCaseTests.java index a479c6a34446..664958da1588 100644 --- a/test/jdk/java/lang/Math/WorstCaseTests.java +++ b/test/jdk/java/lang/Math/WorstCaseTests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -32,7 +32,7 @@ */ /** - * This test contains two distinct kinds of worst-case inputs: + * This test contains three distinct kinds of worst-case inputs: * * 1) Exact numerical results that are nearly half-way between * representable numbers or very close to a representable @@ -43,6 +43,9 @@ * 2) Worst-case errors as observed empirically across different * implementations that are not correctly rounded. * + * 3) Worst-case values found in the Julia environment, which uses a + * fork of FDLIBM. + * * For the first category, the "Table Maker's Dilemma" results from * Jean-Michel Muller and Vincent Lefèvre, are used. * See https://perso.ens-lyon.fr/jean-michel.muller/TMD.html for original @@ -70,6 +73,8 @@ * Extended, and Quadruple Precision" by Brian Gladman, Vincenzo * Innocente and Paul Zimmermann. * + * For the third category, see the preprint https://arxiv.org/abs/2509.05666 + * * From https://openlibm.org/, "The OpenLibm code derives from the * FreeBSD msun and OpenBSD libm implementations, which in turn derive * from FDLIBM 5.3." Java's StrictMath libraries use the FDLIBM 5.3 @@ -143,6 +148,8 @@ private static int testWorstExp() { // Worst-case observed error for OpenLibm {+0x1.2e8f20cf3cbe7p+8, 0x1.6a2a59cc78bf7p436}, + // Julia worst-case observed error + {-0x1.6251620687bf3p9, 0x0.c980224219398p-1022}, // Other worst-case observed errors {-0x1.49f33ad2c1c58p+9, 0x1.f3ccc815431b5p-953}, {+0x1.fce66609f7428p+5, 0x1.b59724cb0bc4cp91}, @@ -188,7 +195,9 @@ private static int testWorstLog() { {+0x1.DE7CD6751029Ap16, +0x1.76E7E5D7B6EABp+3}, // Worst-case observed error for OpenLibm - {+0x1.48ae5a67204f5p+0, 0x1.ffd10abffc3fep-3}, + {+0x1.48ae5a67204f5p+0, +0x1.ffd10abffc3fep-3}, + // Julia worst-case observed error + {+0x1.14fad2c09e275p0, +0x1.42a13ec2691dbp-4}, // Other worst-case observed errors {+0x1.1211bef8f68e9p+0, +0x1.175caeca67f84p-4}, {+0x1.008000db2e8bep+0, +0x1.ff83959f5cc1fp-10}, @@ -238,6 +247,8 @@ private static int testWorstSin() { // Worst-case observed error for OpenLibm {+0x1.4d84db080b9fdp+21, +0x1.6e21c4ff6aec3p-1}, + // Worst-case observed error for Julia + {+0x1.5a8e729e7934p102, +0x1.6deadddde6752p-1}, // Other worst-case observed errors {-0x1.f8b791cafcdefp+4, -0x1.073ca87470df9p-3 }, {-0x1.0e16eb809a35dp+944, +0x1.b5e361ed01dacp-2}, @@ -285,7 +296,10 @@ private static int testWorstAsin() { {+0x1.E264357EA0E29p-1, +0x1.3AA301F6EBB1Dp+0}, // Worst-case observed error for OpenLibm - {-0x1.004d1c5a9400bp-1, -0x1.0c6e322e8a28bp-1}, + {-0x1.004d1c5a9400bp-1, -0x1.0c6e322e8a28bp-1}, + // Julia worst-case observed error + {-0x1.012d405d9408ep-1, -0x1.0d7142df4968fp-1}, + // Other worst-case observed errors {-0x1.0000045b2c904p-3, -0x1.00abe5252746cp-3}, {+0x1.6c042a6378102p-1, +0x1.94eda53f72c5ap-1}, @@ -334,6 +348,8 @@ private static int testWorstCos() { // Worst-case observed error for OpenLibm {-0x1.34e729fd08086p+21, +0x1.6a6a0d6a17f0fp-1}, + // Julia worst-case observed error + {-0x1.4e4cb79b5b5a2p930, 0x1.70f851fbdea52p-1}, // Other worst-case observed errors {-0x1.7120161c92674p+0, +0x1.0741fb7683849p-3}, {-0x1.d19ebc5567dcdp+311, -0x1.b5d2f45f68958p-2}, @@ -374,6 +390,8 @@ private static int testWorstAcos() { // Worst-case observed error for OpenLibm {-0x1.0068b067c6feep-1, +0x1.0c335e2f0726fp1}, + // Julia worst-case observed error + {-0x1.0b7c63033d6cp-1, +0x1.0f6c7f5db3b93p1}, // Other worst-case observed errors {+0x1.dffffb3488a4p-1, 0x1.6bf3a4a4f4dcbp-2}, {+0x1.6c05eb219ec46p-1, 0x1.8f4f472807261p-1}, @@ -418,6 +436,9 @@ private static int testWorstTan() { // Worst-case observed error for OpenLibm, outside of 1 ulp error // {0x1.3f9605aaeb51bp+21, -0x1.9678ee5d64934p-1}, // 1.02 + + // Worst-case observed error for Julia, outside of 1 ulp error + // {0x1.e608f1390d9fp293, -0x1.9942a10545924p-1}, // 1.04 }; for(double[] testCase: testCases) { @@ -456,6 +477,8 @@ private static int testWorstAtan() { // Worst-case observed error {0x1.62ff6a1682c25p-1, +0x1.3666b15c8756ap-1}, + // Julia worst-case observed error + {0x1.66340e55ce1adp-1, +0x1.388f4792eaa82p-1}, // Other worst-case observed errors {+0x1.f9004c4fef9eap-4, 0x1.f67727f5618f2p-4}, {-0x1.ffff8020d3d1dp-7, -0x1.fff4d5e4886c7p-7}, @@ -554,6 +577,10 @@ private static int testWorstSinh() { {+0x1.E07E71BFCF06Fp+5, +0x1.91EC4412C344Fp+85}, {+0x1.54CD1FEA7663Ap+7, +0x1.C90810D354618p+244}, {+0x1.D6479EBA7C971p+8, +0x1.62A88613629B5p+677}, + + // Julia worst-case observed error, 1.9 ulps; + // added to hyperbolics testing in StrictMath. + // {-0x1.633c654fee2bap9, -0x1.fdf25fc26e7cp1023}, }; for(double[] testCase: testCases) { @@ -582,6 +609,10 @@ private static int testWorstCosh() { {+0x1.A6031CD5F93BAp-1, +0x1.5BFF041B260FDp+0}, {+0x1.104B648F113A1p+0, +0x1.9EFDCA62B7009p+0}, {+0x1.EA5F2F2E4B0C5p+1, +0x17.10DB0CD0FED5p+0}, + + // Julia worst-case observed error, 1.9 ulps; + // added to hyperbolics testing in StrictMath. + // {-0x1.633c654fee2bap9, 0x1.fdf25fc26e7cp1023}, }; for(double[] testCase: testCases) { diff --git a/test/jdk/java/lang/StrictMath/CubeRootTests.java b/test/jdk/java/lang/StrictMath/CubeRootTests.java index 474a77609845..6275b8a8b44f 100644 --- a/test/jdk/java/lang/StrictMath/CubeRootTests.java +++ b/test/jdk/java/lang/StrictMath/CubeRootTests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -471,6 +471,7 @@ static int testCubeRoot() { {0x0.0000ffffffap-1022, 0x1.ffffffcp-347}, {0x0.0000ffffffff8p-1022, 0x1.ffffffffaaaabp-347}, {0x0.0fffffffffffbp-1022, 0x1.fffffffffffcbp-343}, + {-0x1.0edb6c7fa500fp-531, -0x1.04dc0b189b6cep-177}, // next down from Julia value // Empirical worst-case points in other libraries with // larger worst-case errors than FDLIBM diff --git a/test/jdk/java/lang/StrictMath/HyperbolicTests.java b/test/jdk/java/lang/StrictMath/HyperbolicTests.java index 68e6a27124c9..1f570ce9efdc 100644 --- a/test/jdk/java/lang/StrictMath/HyperbolicTests.java +++ b/test/jdk/java/lang/StrictMath/HyperbolicTests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -340,6 +340,10 @@ private static int testSinh() { {0x1.fffffffffff68p4, 0x1.1f43fcc4b5b83p45}, {0x1.fffffffffffd4p4, 0x1.1f43fcc4b6316p45}, {0x1.0p5, 0x1.1f43fcc4b662cp45}, + + // Julia worst-case input + {-0x1.633c654fee2bap9, -0x1.fdf25fc26e7cp1023}, + // Empirical worst-case points in other libraries with // larger worst-case errors than FDLIBM {-0x1.633c654fee2bap+9, -0x1.fdf25fc26e7cp1023}, @@ -386,6 +390,10 @@ private static int testCosh() { {0x1.0p4, 0x1.0f2ebd0a8005cp22}, {0x1.fffffffffffd4p4, 0x1.1f43fcc4b6316p45}, {0x1.0p5, 0x1.1f43fcc4b662cp45}, + + // Julia worst-case input + {-0x1.633c654fee2bap9, 0x1.fdf25fc26e7cp1023}, + // Empirical worst-case points in other libraries with // larger worst-case errors than FDLIBM {-0x1.633c654fee2bap+9, 0x1.fdf25fc26e7cp1023}, @@ -462,6 +470,10 @@ private static int testTanh() { {0x1.fffffffffffe1p0, 0x1.ed9505e1bc3cfp-1}, {0x1.ffffffffffed8p1, 0x1.ffa81708a0b4p-1}, {0x1.fffffffffff92p1, 0x1.ffa81708a0b41p-1}, + + // Julia worst-case input + {0x1.0108b83c4bbc8p-1, 0x1.dad53a45da5b0p-2}, + // Empirical worst-case points in other libraries with // larger worst-case errors than FDLIBM {-0x1.c41e527b70f43p-3, -0x1.bcea047cc736cp-3}, diff --git a/test/jdk/java/lang/StrictMath/Log10Tests.java b/test/jdk/java/lang/StrictMath/Log10Tests.java index 6cd8427c438a..537d13d9cb40 100644 --- a/test/jdk/java/lang/StrictMath/Log10Tests.java +++ b/test/jdk/java/lang/StrictMath/Log10Tests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -722,6 +722,9 @@ static int testLog10() { {0x1.3fffffffffebdp25, 0x1.e7d9a8edb47a2p2}, {0x1.4p25, 0x1.e7d9a8edb47bfp2}, + // Julia worst-case + {0x1.10f12374877e3p0, 0x1.c7f8d2e32f5e9p-6}, + // Empirical worst-case points in other libraries with // larger worst-case errors than FDLIBM {0x1.de02157073b31p-1, -0x1.e8cfabf160ec6p-6}, diff --git a/test/jdk/java/lang/StrictMath/Log1pTests.java b/test/jdk/java/lang/StrictMath/Log1pTests.java index b8a8592812f1..c31dc946136b 100644 --- a/test/jdk/java/lang/StrictMath/Log1pTests.java +++ b/test/jdk/java/lang/StrictMath/Log1pTests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -209,6 +209,7 @@ static int testLog1p() { {0x1.7688bb5394bd3p325, 0x1.c34e8276daa48p7}, {0x1.d42aea2878b45p328, 0x1.c7e96ee5c7f87p7}, {0x1.249ad2594989p332, 0x1.cc845b54b54a6p7}, + {0x1.300240b87b096p-4, 0x1.25417bd05ba95p-4}, // nextUp from Julia value // Empirical worst-case points in other libraries with // larger worst-case errors than FDLIBM diff --git a/test/jdk/java/lang/StrictMath/TrigTests.java b/test/jdk/java/lang/StrictMath/TrigTests.java index c4f7dab547f6..e758762a97d2 100644 --- a/test/jdk/java/lang/StrictMath/TrigTests.java +++ b/test/jdk/java/lang/StrictMath/TrigTests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -271,6 +271,10 @@ private static int testAgainstTranslitTan() { {0x1.000000000001cp300, -0x1.b30fc9f73002cp-1}, {0x1.0000000000013p500, -0x1.c4e46751be12cp-1}, {0x1.00000000000ep1023, -0x1.d52c4ec04f108p-2}, + + // Julia worst-case input + {0x1.e608f1390d9fp293, -0x1.9942a10545925p-1}, + // Empirical worst-case points in other libraries with // larger worst-case errors than FDLIBM {+0x1.371a47b7e4eb2p+11, 0x1.9ded57c9ff46ap-1}, From 4eb445662c1d696d285b216922b9ac593b729501 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 16 Mar 2026 15:33:01 +0000 Subject: [PATCH 039/168] 8371792: Refactor barrier loop tests out of TestIfMinMax Backport-of: a655ea48453a321fb7cadc6ffb6111276497a929 --- .../compiler/c2/irTests/TestIfMinMax.java | 38 +------- .../gcbarriers/TestMinMaxLongLoopBarrier.java | 86 +++++++++++++++++++ 2 files changed, 87 insertions(+), 37 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/gcbarriers/TestMinMaxLongLoopBarrier.java diff --git a/test/hotspot/jtreg/compiler/c2/irTests/TestIfMinMax.java b/test/hotspot/jtreg/compiler/c2/irTests/TestIfMinMax.java index fdc0a83fb8bc..bfcc775efeb4 100644 --- a/test/hotspot/jtreg/compiler/c2/irTests/TestIfMinMax.java +++ b/test/hotspot/jtreg/compiler/c2/irTests/TestIfMinMax.java @@ -30,7 +30,7 @@ /* * @test - * @bug 8324655 8329797 8331090 + * @bug 8324655 8331090 * @key randomness * @summary Test that if expressions are properly folded into min/max nodes * @library /test/lib / @@ -139,42 +139,6 @@ public long testMaxL2E(long a, long b) { return a <= b ? b : a; } - public class Dummy { - long l; - public Dummy(long l) { this.l = l; } - } - - @Setup - Object[] setupDummyArray() { - Dummy[] arr = new Dummy[512]; - for (int i = 0; i < 512; i++) { - arr[i] = new Dummy(RANDOM.nextLong()); - } - return new Object[] { arr }; - } - - @Test - @Arguments(setup = "setupDummyArray") - @IR(failOn = { IRNode.MAX_L }) - public long testMaxLAndBarrierInLoop(Dummy[] arr) { - long result = 0; - for (int i = 0; i < arr.length; ++i) { - result += Math.max(arr[i].l, 1); - } - return result; - } - - @Test - @Arguments(setup = "setupDummyArray") - @IR(failOn = { IRNode.MIN_L }) - public long testMinLAndBarrierInLoop(Dummy[] arr) { - long result = 0; - for (int i = 0; i < arr.length; ++i) { - result += Math.min(arr[i].l, 1); - } - return result; - } - @Setup static Object[] setupIntArrays() { int[] a = new int[512]; diff --git a/test/hotspot/jtreg/compiler/gcbarriers/TestMinMaxLongLoopBarrier.java b/test/hotspot/jtreg/compiler/gcbarriers/TestMinMaxLongLoopBarrier.java new file mode 100644 index 000000000000..9f2ddc0d20e5 --- /dev/null +++ b/test/hotspot/jtreg/compiler/gcbarriers/TestMinMaxLongLoopBarrier.java @@ -0,0 +1,86 @@ +/* + * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.gcbarriers; + +import compiler.lib.ir_framework.Arguments; +import compiler.lib.ir_framework.IR; +import compiler.lib.ir_framework.IRNode; +import compiler.lib.ir_framework.Setup; +import compiler.lib.ir_framework.Test; +import compiler.lib.ir_framework.TestFramework; +import jdk.test.lib.Utils; + +import java.util.Random; + +/* + * @test + * @bug 8329797 + * @key randomness + * @summary Test that MinL/MaxL nodes are removed when GC barriers in loop + * @library /test/lib / + * @run driver ${test.main.class} + */ +public class TestMinMaxLongLoopBarrier { + private static final Random RANDOM = Utils.getRandomInstance(); + + public static void main(String[] args) { + TestFramework.run(); + } + + public class Dummy { + long l; + public Dummy(long l) { this.l = l; } + } + + @Setup + Object[] setupDummyArray() { + Dummy[] arr = new Dummy[512]; + for (int i = 0; i < 512; i++) { + arr[i] = new Dummy(RANDOM.nextLong()); + } + return new Object[] { arr }; + } + + @Test + @Arguments(setup = "setupDummyArray") + @IR(failOn = { IRNode.MAX_L }) + public long testMaxLAndBarrierInLoop(Dummy[] arr) { + long result = 0; + for (int i = 0; i < arr.length; ++i) { + result += Math.max(arr[i].l, 1); + } + return result; + } + + @Test + @Arguments(setup = "setupDummyArray") + @IR(failOn = { IRNode.MIN_L }) + public long testMinLAndBarrierInLoop(Dummy[] arr) { + long result = 0; + for (int i = 0; i < arr.length; ++i) { + result += Math.min(arr[i].l, 1); + } + return result; + } +} From 78bbfd6362cdd15a2893df6d2e8b6c65ded3d025 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 16 Mar 2026 15:33:32 +0000 Subject: [PATCH 040/168] 8364927: Add @requires annotation to TestReclaimStringsLeaksMemory.java Reviewed-by: phh Backport-of: 2bfada3f58df6c041d948267368cbc4db915cac3 --- .../stress/TestReclaimStringsLeaksMemory.java | 55 ++++++++++++++++--- 1 file changed, 48 insertions(+), 7 deletions(-) diff --git a/test/hotspot/jtreg/gc/stress/TestReclaimStringsLeaksMemory.java b/test/hotspot/jtreg/gc/stress/TestReclaimStringsLeaksMemory.java index 645583ec450c..009a063f49f6 100644 --- a/test/hotspot/jtreg/gc/stress/TestReclaimStringsLeaksMemory.java +++ b/test/hotspot/jtreg/gc/stress/TestReclaimStringsLeaksMemory.java @@ -24,17 +24,58 @@ package gc.stress; /* - * @test TestReclaimStringsLeaksMemory + * @test id=Serial * @bug 8180048 - * @summary Ensure that during a Full GC interned string memory is reclaimed completely. - * @requires vm.gc == "null" + * @summary Ensure that during a Full GC interned string memory is reclaimed completely with SerialGC. + * @requires vm.gc.Serial * @requires !vm.debug * @library /test/lib * @modules java.base/jdk.internal.misc - * @run driver gc.stress.TestReclaimStringsLeaksMemory - * @run driver gc.stress.TestReclaimStringsLeaksMemory -XX:+UseSerialGC - * @run driver gc.stress.TestReclaimStringsLeaksMemory -XX:+UseParallelGC - * @run driver gc.stress.TestReclaimStringsLeaksMemory -XX:+UseG1GC + * @run driver/timeout=480 gc.stress.TestReclaimStringsLeaksMemory -XX:+UseSerialGC + */ + +/* + * @test id=Parallel + * @bug 8180048 + * @summary Ensure that during a Full GC interned string memory is reclaimed completely with ParallelGC. + * @requires vm.gc.Parallel + * @requires !vm.debug + * @library /test/lib + * @modules java.base/jdk.internal.misc + * @run driver/timeout=480 gc.stress.TestReclaimStringsLeaksMemory -XX:+UseParallelGC + */ + +/* + * @test id=G1 + * @bug 8180048 + * @summary Ensure that during a Full GC interned string memory is reclaimed completely with G1GC. + * @requires vm.gc.G1 + * @requires !vm.debug + * @library /test/lib + * @modules java.base/jdk.internal.misc + * @run driver/timeout=480 gc.stress.TestReclaimStringsLeaksMemory -XX:+UseG1GC + */ + +/* + * @test id=Shenandoah + * @bug 8180048 + * @summary Ensure that during a Full GC interned string memory is reclaimed completely with ShenandoahGC. + * @requires vm.gc.Shenandoah + * @requires !vm.debug + * @library /test/lib + * @modules java.base/jdk.internal.misc + * @run driver/timeout=480 gc.stress.TestReclaimStringsLeaksMemory -XX:+UseShenandoahGC + */ + +/* + * @test id=Z + * @bug 8180048 + * @summary Ensure that during a Full GC interned string memory is reclaimed completely with ZGC. + * @requires vm.gc.Z + * @requires !vm.debug + * @library /test/lib + * @modules java.base/jdk.internal.misc + * @run driver/timeout=480 gc.stress.TestReclaimStringsLeaksMemory -XX:+UseZGC */ import java.util.Arrays; From fdeadda47ddfa5949744417c1d425362899ac627 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 16 Mar 2026 15:34:02 +0000 Subject: [PATCH 041/168] 8068310: [TEST_BUG] Test javax/swing/JColorChooser/Test4234761.java fails with GTKL&F Backport-of: 31beb7d3b34c3516c326c9d29a267f6becb38805 --- .../jdk/javax/swing/JColorChooser/Test4234761.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/test/jdk/javax/swing/JColorChooser/Test4234761.java b/test/jdk/javax/swing/JColorChooser/Test4234761.java index c2b2d9ed7b9b..fb55ca37feb7 100644 --- a/test/jdk/javax/swing/JColorChooser/Test4234761.java +++ b/test/jdk/javax/swing/JColorChooser/Test4234761.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,10 +23,12 @@ /* * @test - * @key headful * @bug 4234761 + * @key headful * @summary RGB values sholdn't be changed in transition to HSB tab - * @author Oleg Mokhovikov + * @library /test/lib + * @build jtreg.SkippedException + * @run main Test4234761 */ import java.awt.Color; @@ -35,11 +37,17 @@ import javax.swing.JColorChooser; import javax.swing.JDialog; import javax.swing.JTabbedPane; +import javax.swing.UIManager; + +import jtreg.SkippedException; public class Test4234761 implements PropertyChangeListener { private static final Color COLOR = new Color(51, 51, 51); public static void main(String[] args) { + if (UIManager.getLookAndFeel().getName().contains("GTK")) { + throw new SkippedException("Test skipped for GTK"); + } JColorChooser chooser = new JColorChooser(COLOR); JDialog dialog = Test4177735.show(chooser); From b939f71fb652e50f582f2f815a03393612cc93e8 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 16 Mar 2026 15:34:25 +0000 Subject: [PATCH 042/168] 8377910: Minor cleanup of java/io/FileDescriptor/Sharing.java Backport-of: 7489f75dbdb1358b7f905aad2d1510b7ffc173bf --- test/jdk/java/io/FileDescriptor/Sharing.java | 76 +++++++++++--------- 1 file changed, 42 insertions(+), 34 deletions(-) diff --git a/test/jdk/java/io/FileDescriptor/Sharing.java b/test/jdk/java/io/FileDescriptor/Sharing.java index 24f4fb70b02b..e4ceb2d69065 100644 --- a/test/jdk/java/io/FileDescriptor/Sharing.java +++ b/test/jdk/java/io/FileDescriptor/Sharing.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -28,7 +28,14 @@ * @run main/othervm Sharing */ -import java.io.*; +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.io.Writer; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.util.concurrent.CountDownLatch; @@ -71,7 +78,7 @@ private static void TestFinalizer() throws Exception { // encourage gc System.gc(); // read from fis2 - when fis1 is gc'ed and finalizer is run, read will fail - System.out.print("."); + System.err.print("."); ret = fis2.read(); } } @@ -93,7 +100,7 @@ private static void TestFinalizer() throws Exception { * read from fis3 - when raf is gc'ed and finalizer is run, * fd should still be valid. */ - System.out.print("."); + System.err.print("."); ret = fis3.read(); } } finally { @@ -290,7 +297,7 @@ private static void TestCloseAll() throws Exception { FileInputStream fis = new FileInputStream(raf.getFD()); fis.close(); if (raf.getFD().valid()) { - throw new RuntimeException("FD should not be valid."); + throw new RuntimeException("FD should not be valid."); } // Test the suppressed exception handling - FileInputStream @@ -308,7 +315,7 @@ private static void TestCloseAll() throws Exception { ioe.printStackTrace(); if (ioe.getSuppressed().length != 2) { throw new RuntimeException("[FIS]Incorrect number of suppressed " + - "exceptions received : " + ioe.getSuppressed().length); + "exceptions received : " + ioe.getSuppressed().length); } } if (raf.getFD().valid()) { @@ -332,7 +339,7 @@ private static void TestCloseAll() throws Exception { ioe.printStackTrace(); if (ioe.getSuppressed().length != 2) { throw new RuntimeException("[FOS]Incorrect number of suppressed " + - "exceptions received : " + ioe.getSuppressed().length); + "exceptions received : " + ioe.getSuppressed().length); } } if (raf.getFD().valid()) { @@ -347,10 +354,8 @@ private static void TestCloseAll() throws Exception { * FileOutputStreams referencing the same native file descriptor. */ private static class OpenClose extends Thread { - private FileDescriptor fd = null; - private CountDownLatch done; - FileInputStream[] fisArray = new FileInputStream[numFiles]; - FileOutputStream[] fosArray = new FileOutputStream[numFiles]; + private final FileDescriptor fd; + private final CountDownLatch done; OpenClose(FileDescriptor filedescriptor, CountDownLatch done) { this.fd = filedescriptor; @@ -358,29 +363,32 @@ private static class OpenClose extends Thread { } public void run() { - try { - for(int i=0;i Date: Mon, 16 Mar 2026 15:34:58 +0000 Subject: [PATCH 043/168] 8369817: [TESTBUG] EmptyPath::toString is ignored Backport-of: 008d8d914cd4dd4573361390ee31120134338802 --- test/jdk/java/io/File/EmptyPath.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/jdk/java/io/File/EmptyPath.java b/test/jdk/java/io/File/EmptyPath.java index 55c4af96fe37..374a69c79591 100644 --- a/test/jdk/java/io/File/EmptyPath.java +++ b/test/jdk/java/io/File/EmptyPath.java @@ -382,9 +382,8 @@ public void toPath() { } @Test - public String toString() { + public void testToString() { assertEquals(EMPTY_STRING, f.toString()); - return EMPTY_STRING; } @Test From 48e55662311df815a77fe51a39bf3d5865adb2a7 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 16 Mar 2026 15:35:27 +0000 Subject: [PATCH 044/168] 8319326: GC: Make TestParallelRefProc use createTestJavaProcessBuilder Backport-of: 3a8a6e07f2a2cffa467815df55e746e92765903d --- .../gc/arguments/TestParallelRefProc.java | 85 +++++++++++++------ 1 file changed, 57 insertions(+), 28 deletions(-) diff --git a/test/hotspot/jtreg/gc/arguments/TestParallelRefProc.java b/test/hotspot/jtreg/gc/arguments/TestParallelRefProc.java index 6e2e3c0239eb..2cd9e9cd60b2 100644 --- a/test/hotspot/jtreg/gc/arguments/TestParallelRefProc.java +++ b/test/hotspot/jtreg/gc/arguments/TestParallelRefProc.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,13 +24,30 @@ package gc.arguments; /* - * @test TestParallelRefProc - * @summary Test defaults processing for -XX:+ParallelRefProcEnabled. + * @test id=Serial + * @summary Test defaults processing for -XX:+ParallelRefProcEnabled with Serial GC. * @library /test/lib * @library / - * @build jdk.test.whitebox.WhiteBox - * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox - * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI gc.arguments.TestParallelRefProc + * @requires vm.gc.Serial + * @run driver gc.arguments.TestParallelRefProc Serial + */ + +/* + * @test id=Parallel + * @summary Test defaults processing for -XX:+ParallelRefProcEnabled with Parallel GC. + * @library /test/lib + * @library / + * @requires vm.gc.Parallel + * @run driver gc.arguments.TestParallelRefProc Parallel + */ + +/* + * @test id=G1 + * @summary Test defaults processing for -XX:+ParallelRefProcEnabled with G1 GC. + * @library /test/lib + * @library / + * @requires vm.gc.G1 + * @run driver gc.arguments.TestParallelRefProc G1 */ import java.util.Arrays; @@ -38,34 +55,46 @@ import jdk.test.lib.process.OutputAnalyzer; -import jtreg.SkippedException; -import jdk.test.whitebox.gc.GC; - public class TestParallelRefProc { public static void main(String args[]) throws Exception { - boolean noneGCSupported = true; - if (GC.Serial.isSupported()) { - noneGCSupported = false; - testFlag(new String[] { "-XX:+UseSerialGC" }, false); + if (args.length == 0) { + throw new IllegalArgumentException("Test type must be specified as argument"); } - if (GC.Parallel.isSupported()) { - noneGCSupported = false; - testFlag(new String[] { "-XX:+UseParallelGC", "-XX:ParallelGCThreads=1" }, false); - testFlag(new String[] { "-XX:+UseParallelGC", "-XX:ParallelGCThreads=2" }, true); - testFlag(new String[] { "-XX:+UseParallelGC", "-XX:-ParallelRefProcEnabled", "-XX:ParallelGCThreads=2" }, false); - } - if (GC.G1.isSupported()) { - noneGCSupported = false; - testFlag(new String[] { "-XX:+UseG1GC", "-XX:ParallelGCThreads=1" }, false); - testFlag(new String[] { "-XX:+UseG1GC", "-XX:ParallelGCThreads=2" }, true); - testFlag(new String[] { "-XX:+UseG1GC", "-XX:-ParallelRefProcEnabled", "-XX:ParallelGCThreads=2" }, false); - } - if (noneGCSupported) { - throw new SkippedException("Skipping test because none of Serial/Parallel/G1 is supported."); + + String testType = args[0]; + + switch (testType) { + case "Serial": + testSerial(); + break; + case "Parallel": + testParallel(); + break; + case "G1": + testG1(); + break; + default: + throw new IllegalArgumentException("Unknown test type \"" + testType + "\""); } } + private static void testSerial() throws Exception { + testFlag(new String[] { "-XX:+UseSerialGC" }, false); + } + + private static void testParallel() throws Exception { + testFlag(new String[] { "-XX:+UseParallelGC", "-XX:ParallelGCThreads=1" }, false); + testFlag(new String[] { "-XX:+UseParallelGC", "-XX:ParallelGCThreads=2" }, true); + testFlag(new String[] { "-XX:+UseParallelGC", "-XX:-ParallelRefProcEnabled", "-XX:ParallelGCThreads=2" }, false); + } + + private static void testG1() throws Exception { + testFlag(new String[] { "-XX:+UseG1GC", "-XX:ParallelGCThreads=1" }, false); + testFlag(new String[] { "-XX:+UseG1GC", "-XX:ParallelGCThreads=2" }, true); + testFlag(new String[] { "-XX:+UseG1GC", "-XX:-ParallelRefProcEnabled", "-XX:ParallelGCThreads=2" }, false); + } + private static final String parallelRefProcEnabledPattern = " *bool +ParallelRefProcEnabled *= *true +\\{product\\}"; @@ -77,7 +106,7 @@ private static void testFlag(String[] args, boolean expectedTrue) throws Excepti result.addAll(Arrays.asList(args)); result.add("-XX:+PrintFlagsFinal"); result.add("-version"); - OutputAnalyzer output = GCArguments.executeLimitedTestJava(result); + OutputAnalyzer output = GCArguments.executeTestJava(result); output.shouldHaveExitValue(0); From 96ab312fdacf2e4cdce9c267a21f2733ba58caa7 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 16 Mar 2026 15:35:53 +0000 Subject: [PATCH 045/168] 8213530: Test java/awt/Modal/ToFront/DialogToFrontModeless1Test.java fails on Linux Backport-of: 5e12ff9ff64f2d7ebb501cdb19d5f013dde17be4 --- test/jdk/ProblemList.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/test/jdk/ProblemList.txt b/test/jdk/ProblemList.txt index 52cda55446bf..ef5137078411 100644 --- a/test/jdk/ProblemList.txt +++ b/test/jdk/ProblemList.txt @@ -411,7 +411,6 @@ java/awt/Mouse/EnterExitEvents/ResizingFrameTest.java 8005021 macosx-all java/awt/Mouse/EnterExitEvents/FullscreenEnterEventTest.java 8051455 macosx-all java/awt/Mouse/MouseModifiersUnitTest/MouseModifiersUnitTest_Standard.java 7124407,8302787 macosx-all,windows-all java/awt/Mouse/RemovedComponentMouseListener/RemovedComponentMouseListener.java 8157170 macosx-all -java/awt/Modal/ToFront/DialogToFrontModeless1Test.java 8213530 linux-all java/awt/Modal/ToFront/DialogToFrontNonModalTest.java 8221899 linux-all java/awt/Modal/ToBack/ToBackAppModal1Test.java 8196441 linux-all,macosx-all java/awt/Modal/ToBack/ToBackAppModal2Test.java 8196441 linux-all,macosx-all From 90f4fd1efe3731b595b6dc8d6a4d1bbc6099d01c Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 16 Mar 2026 15:36:26 +0000 Subject: [PATCH 046/168] 8368181: ProblemList java/awt/Dialog/ModalExcludedTest/ModalExcludedTest.java Backport-of: 7d3452b37eceff7309dc6b5285e3da31a3c398ec --- test/jdk/ProblemList.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/test/jdk/ProblemList.txt b/test/jdk/ProblemList.txt index ef5137078411..a905e6ff0c3f 100644 --- a/test/jdk/ProblemList.txt +++ b/test/jdk/ProblemList.txt @@ -272,6 +272,7 @@ java/awt/Clipboard/ClipboardSecurity.java 8054809 macosx-all java/awt/Clipboard/GetAltContentsTest/SystemClipboardTest.java 8234140 macosx-all java/awt/Clipboard/ImageTransferTest.java 8030710 generic-all java/awt/Clipboard/NoDataConversionFailureTest.java 8234140 macosx-all +java/awt/Dialog/ModalExcludedTest.java 7125054 macosx-all java/awt/Frame/MiscUndecorated/RepaintTest.java 8266244 macosx-aarch64 java/awt/Modal/FileDialog/FileDialogAppModal1Test.java 7186009 macosx-all java/awt/Modal/FileDialog/FileDialogAppModal2Test.java 7186009 macosx-all From b1ceddd555b165ca6b5fc704462f99963e574ff4 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 16 Mar 2026 15:36:48 +0000 Subject: [PATCH 047/168] 8368001: java/text/Format/NumberFormat/NumberRoundTrip.java timed out Backport-of: 507a6d327f1c613a130273727ee4154b5b4d7ca4 --- .../Format/NumberFormat/NumberRoundTrip.java | 268 +++++++----------- 1 file changed, 98 insertions(+), 170 deletions(-) diff --git a/test/jdk/java/text/Format/NumberFormat/NumberRoundTrip.java b/test/jdk/java/text/Format/NumberFormat/NumberRoundTrip.java index f8a57e3ae62a..be69d835c320 100644 --- a/test/jdk/java/text/Format/NumberFormat/NumberRoundTrip.java +++ b/test/jdk/java/text/Format/NumberFormat/NumberRoundTrip.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,19 +23,28 @@ /* * @test - * @summary round trip test NumberFormat + * @bug 4266589 8031145 8164791 8316696 8368001 + * @summary NumberFormat round trip testing of parsing and formatting. + * This test checks 4 factory instances per locale against ~20 numeric inputs. + * Samples ~1/4 of the available locales provided by NumberFormat. * @key randomness + * @library /test/lib + * @build jdk.test.lib.RandomFactory * @run junit NumberRoundTrip */ import java.text.DecimalFormat; import java.text.NumberFormat; -import java.text.ParseException; -import java.util.Locale; +import java.util.Arrays; +import java.util.List; +import java.util.Random; +import java.util.stream.Stream; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.fail; +import jdk.test.lib.RandomFactory; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; /** * This class tests the round-trip behavior of NumberFormat, DecimalFormat, and DigitList. @@ -44,187 +53,106 @@ * Two tests are applied: String preservation, and numeric preservation. String * preservation is exact; numeric preservation is not. However, numeric preservation * should extend to the few least-significant bits. - * //bug472 */ public class NumberRoundTrip { - static final boolean STRING_COMPARE = true; - static final boolean EXACT_NUMERIC_COMPARE = false; - static final double MAX_ERROR = 1e-14; - static double max_numeric_error = 0; - static double min_numeric_error = 1; - - String localeName, formatName; - - @Test - public void TestNumberFormatRoundTrip() { - System.out.println("Default Locale"); - localeName = "Default Locale"; - formatName = "getInstance"; - doTest(NumberFormat.getInstance()); - formatName = "getNumberInstance"; - doTest(NumberFormat.getNumberInstance()); - formatName = "getCurrencyInstance"; - doTest(NumberFormat.getCurrencyInstance()); - formatName = "getPercentInstance"; - doTest(NumberFormat.getPercentInstance()); - Locale[] loc = NumberFormat.getAvailableLocales(); - for (int i=0; i test(fmt, num)); } - public void doTest(NumberFormat fmt) { - doTest(fmt, Double.NaN); - doTest(fmt, Double.POSITIVE_INFINITY); - doTest(fmt, Double.NEGATIVE_INFINITY); - - doTest(fmt, 500); - doTest(fmt, 0); - doTest(fmt, 5555555555555555L); - doTest(fmt, 55555555555555555L); - doTest(fmt, 9223372036854775807L); - doTest(fmt, 9223372036854775808.0); - doTest(fmt, -9223372036854775808L); - doTest(fmt, -9223372036854775809.0); - - for (int i=0; i<2; ++i) { - doTest(fmt, randomDouble(1)); - doTest(fmt, randomDouble(10000)); - doTest(fmt, Math.floor(randomDouble(10000))); - doTest(fmt, randomDouble(1e50)); - doTest(fmt, randomDouble(1e-50)); - doTest(fmt, randomDouble(1e100)); - // The use of double d such that isInfinite(100d) causes the - // numeric test to fail with percent formats (bug 4266589). - // Largest double s.t. 100d < Inf: d=1.7976931348623156E306 - doTest(fmt, randomDouble(1e306)); - doTest(fmt, randomDouble(1e-323)); - doTest(fmt, randomDouble(1e-100)); + private void test(NumberFormat fmt, Number num) { + String originalFormatted = fmt.format(num); + Number parsedNum = Assertions.assertDoesNotThrow(() -> fmt.parse(originalFormatted), + "Failed parse(format(%s))".formatted(num)); + String parsedFormatted = fmt.format(parsedNum); + var equal = originalFormatted.equals(parsedFormatted); + // Try BigDecimal parsing, if not equal + if (!equal) { + var df = Assertions.assertInstanceOf(DecimalFormat.class, fmt); + df.setParseBigDecimal(true); + parsedNum = Assertions.assertDoesNotThrow(() -> fmt.parse(originalFormatted), + "Failed BigDecimal parse(format(%s))".formatted(num)); + parsedFormatted = fmt.format(parsedNum); + df.setParseBigDecimal(false); + Assertions.assertEquals(originalFormatted, parsedFormatted, + "Failed to round-trip format(parse(format(%s)))".formatted(num)); } + // Numeric mismatch to the amount of 1e-14 is tolerable + var error = proportionalError(num, parsedNum); + Assertions.assertFalse(error > 1e-14, + "Round tripping %s caused numeric error: %s".formatted(num, error)); } - /** - * Return a random value from -range..+range. - */ - public double randomDouble(double range) { - double a = Math.random(); - return (2.0 * range * a) - range; + // Regular, number, currency, and percent instance per locale + private static Stream testNumberFormatRoundTrip() { + return Stream.concat( + // Default Locale + Stream.of( + Arguments.of(NumberFormat.getInstance()), + Arguments.of(NumberFormat.getNumberInstance()), + Arguments.of(NumberFormat.getCurrencyInstance()), + Arguments.of(NumberFormat.getPercentInstance())), + // ~1000 locales returned from provider. + // Too expensive to test all locales, so sample a reasonable amount + Arrays.stream(NumberFormat.getAvailableLocales()) + .filter(_ -> RND.nextDouble() < .25) + .flatMap(loc -> Stream.of( + Arguments.of(NumberFormat.getInstance(loc)), + Arguments.of(NumberFormat.getNumberInstance(loc)), + Arguments.of(NumberFormat.getCurrencyInstance(loc)), + Arguments.of(NumberFormat.getPercentInstance(loc))) + ) + ); } - public void doTest(NumberFormat fmt, double value) { - doTest(fmt, Double.valueOf(value)); + // Fixed set of numbers to test each locale against + private static final List numbers = List.of( + Double.NaN, + Double.POSITIVE_INFINITY, + Double.NEGATIVE_INFINITY, + 500, + 0, + 5555555555555555L, + 55555555555555555L, + 9223372036854775807L, + 9223372036854775808.0, + -9223372036854775808L, + -9223372036854775809.0 + ); + + // Compute fresh batch of random numbers per locale + private Stream randomNumbers() { + return Stream.of( + randomDouble(1), + randomDouble(10000), + Math.floor(randomDouble(10000)), + randomDouble(1e50), + randomDouble(1e-50), + randomDouble(1e100), + // The use of double d such that isInfinite(100d) causes the + // numeric test to fail with percent formats (bug 4266589). + // Largest double s.t. 100d < Inf: d=1.7976931348623156E306 + randomDouble(1e306), + randomDouble(1e-323), + randomDouble(1e-100) + ); } - public void doTest(NumberFormat fmt, long value) { - doTest(fmt, Long.valueOf(value)); + // Return a random value from -range..+range. + private static double randomDouble(double range) { + return RND.nextDouble(-range, range); } - static double proportionalError(Number a, Number b) { + private static double proportionalError(Number a, Number b) { double aa = a.doubleValue(), bb = b.doubleValue(); double error = aa - bb; if (aa != 0 && bb != 0) error /= aa; return Math.abs(error); } - - public void doTest(NumberFormat fmt, Number value) { - fmt.setMaximumFractionDigits(Integer.MAX_VALUE); - String s = fmt.format(value), s2 = null; - Number n = null; - String err = ""; - try { - System.out.println(" " + value + " F> " + escape(s)); - n = fmt.parse(s); - System.out.println(" " + escape(s) + " P> " + n); - s2 = fmt.format(n); - System.out.println(" " + n + " F> " + escape(s2)); - - if (STRING_COMPARE) { - if (!s.equals(s2)) { - if (fmt instanceof DecimalFormat) { - System.out.println("Text mismatch: expected: " + s + ", got: " + s2 + " --- Try BigDecimal parsing."); - ((DecimalFormat)fmt).setParseBigDecimal(true); - n = fmt.parse(s); - System.out.println(" " + escape(s) + " P> " + n); - s2 = fmt.format(n); - System.out.println(" " + n + " F> " + escape(s2)); - ((DecimalFormat)fmt).setParseBigDecimal(false); - - if (!s.equals(s2)) { - err = "STRING ERROR(DecimalFormat): "; - } - } else { - err = "STRING ERROR(NumberFormat): "; - } - } - } - - if (EXACT_NUMERIC_COMPARE) { - if (value.doubleValue() != n.doubleValue()) { - err += "NUMERIC ERROR: "; - } - } else { - // Compute proportional error - double error = proportionalError(value, n); - - if (error > MAX_ERROR) { - err += "NUMERIC ERROR " + error + ": "; - } - - if (error > max_numeric_error) max_numeric_error = error; - if (error < min_numeric_error) min_numeric_error = error; - } - - String message = value + typeOf(value) + " F> " + - escape(s) + " P> " + - n + typeOf(n) + " F> " + - escape(s2); - if (err.length() > 0) { - fail("*** " + err + " with " + - formatName + " in " + localeName + - " " + message); - } else { - System.out.println(message); - } - } catch (ParseException e) { - fail("*** " + e.toString() + " with " + - formatName + " in " + localeName); - } - } - - static String typeOf(Number n) { - if (n instanceof Long) return " Long"; - if (n instanceof Double) return " Double"; - return " Number"; - } - - static String escape(String s) { - StringBuffer buf = new StringBuffer(); - for (int i=0; i> 12)); - buf.append(Integer.toHexString((c & 0x0F00) >> 8)); - buf.append(Integer.toHexString((c & 0x00F0) >> 4)); - buf.append(Integer.toHexString(c & 0x000F)); - } - } - return buf.toString(); - } } From c7f3f8f2b1fff51e2d6a8ac3f86c9b83db01dab5 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 16 Mar 2026 15:39:04 +0000 Subject: [PATCH 048/168] 8319540: GC: Make TestSelectDefaultGC use createTestJavaProcessBuilder Backport-of: 5f5bf1971ca622b053c4eae146298090d6944473 --- test/hotspot/jtreg/gc/arguments/TestSelectDefaultGC.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/hotspot/jtreg/gc/arguments/TestSelectDefaultGC.java b/test/hotspot/jtreg/gc/arguments/TestSelectDefaultGC.java index af3f77a8a767..a5e144bcd151 100644 --- a/test/hotspot/jtreg/gc/arguments/TestSelectDefaultGC.java +++ b/test/hotspot/jtreg/gc/arguments/TestSelectDefaultGC.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -44,7 +44,7 @@ public static void assertVMOption(OutputAnalyzer output, String option, boolean public static void testDefaultGC(boolean actAsServer) throws Exception { // Start VM without specifying GC - OutputAnalyzer output = GCArguments.executeLimitedTestJava( + OutputAnalyzer output = GCArguments.executeTestJava( "-XX:" + (actAsServer ? "+" : "-") + "AlwaysActAsServerClassMachine", "-XX:" + (actAsServer ? "-" : "+") + "NeverActAsServerClassMachine", "-XX:+PrintFlagsFinal", From 0c80640141496e31cea5e308d1bb648d8e011fc0 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Tue, 17 Mar 2026 12:44:07 +0000 Subject: [PATCH 049/168] 8347167: Reduce allocation in com.sun.net.httpserver.Headers::normalize Reviewed-by: phh Backport-of: ea19ad2ac8a1fa9d4124be9a8e05cf4c6f6231bd --- .../com/sun/net/httpserver/Headers.java | 78 +++++++++--- .../com/sun/net/httpserver/HeadersTest.java | 90 ++++++++++++- .../net/httpserver/HeaderNormalization.java | 120 ++++++++++++++++++ 3 files changed, 268 insertions(+), 20 deletions(-) create mode 100644 test/micro/org/openjdk/bench/sun/net/httpserver/HeaderNormalization.java diff --git a/src/jdk.httpserver/share/classes/com/sun/net/httpserver/Headers.java b/src/jdk.httpserver/share/classes/com/sun/net/httpserver/Headers.java index 279203d1d5ba..ef89088674f5 100644 --- a/src/jdk.httpserver/share/classes/com/sun/net/httpserver/Headers.java +++ b/src/jdk.httpserver/share/classes/com/sun/net/httpserver/Headers.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -110,29 +110,69 @@ public Headers(Map> headers) { } /** - * Normalize the key by converting to following form. - * First {@code char} upper case, rest lower case. - * key is presumed to be {@code ASCII}. + * {@return the normalized header name of the following form: the first + * character in upper-case, the rest in lower-case} + * The input header name is assumed to be encoded in ASCII. + * + * @implSpec + * This method is performance-sensitive; update with care. + * + * @param key an ASCII-encoded header name + * @throws NullPointerException on null {@code key} + * @throws IllegalArgumentException if {@code key} contains {@code \r} or {@code \n} */ - private String normalize(String key) { + private static String normalize(String key) { + + // Fast path for the empty key Objects.requireNonNull(key); - int len = key.length(); - if (len == 0) { + int l = key.length(); + if (l == 0) { return key; } - char[] b = key.toCharArray(); - if (b[0] >= 'a' && b[0] <= 'z') { - b[0] = (char)(b[0] - ('a' - 'A')); - } else if (b[0] == '\r' || b[0] == '\n') - throw new IllegalArgumentException("illegal character in key"); - - for (int i=1; i= 'A' && b[i] <= 'Z') { - b[i] = (char) (b[i] + ('a' - 'A')); - } else if (b[i] == '\r' || b[i] == '\n') - throw new IllegalArgumentException("illegal character in key"); + + // Find the first non-normalized `char` + int i = 0; + char c = key.charAt(i); + if (!(c == '\r' || c == '\n' || (c >= 'a' && c <= 'z'))) { + i++; + for (; i < l; i++) { + c = key.charAt(i); + if (c == '\r' || c == '\n' || (c >= 'A' && c <= 'Z')) { + break; + } + } + } + + // Fast path for the already normalized key + if (i == l) { + return key; } - return new String(b); + + // Upper-case the first `char` + char[] cs = key.toCharArray(); + int o = 'a' - 'A'; + if (i == 0) { + if (c == '\r' || c == '\n') { + throw new IllegalArgumentException("illegal character in key at index " + i); + } + if (c >= 'a' && c <= 'z') { + cs[0] = (char) (c - o); + } + i++; + } + + // Lower-case the secondary `char`s + for (; i < l; i++) { + c = cs[i]; + if (c >= 'A' && c <= 'Z') { + cs[i] = (char) (c + o); + } else if (c == '\r' || c == '\n') { + throw new IllegalArgumentException("illegal character in key at index " + i); + } + } + + return new String(cs); + } @Override diff --git a/test/jdk/com/sun/net/httpserver/HeadersTest.java b/test/jdk/com/sun/net/httpserver/HeadersTest.java index dffa4143c0f7..c37aba0424f3 100644 --- a/test/jdk/com/sun/net/httpserver/HeadersTest.java +++ b/test/jdk/com/sun/net/httpserver/HeadersTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -49,6 +49,9 @@ import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; +import java.util.stream.IntStream; +import java.util.stream.Stream; + import com.sun.net.httpserver.Headers; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; @@ -62,6 +65,8 @@ import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotEquals; +import static org.testng.Assert.assertNotSame; +import static org.testng.Assert.assertSame; import static org.testng.Assert.assertThrows; import static org.testng.Assert.assertTrue; @@ -288,7 +293,14 @@ public static void testPutAll() { final var list = new ArrayList(); list.add(null); assertThrows(NPE, () -> h0.putAll(Map.of("a", list))); + assertThrows(IAE, () -> h0.putAll(Map.of("a", List.of("\r")))); assertThrows(IAE, () -> h0.putAll(Map.of("a", List.of("\n")))); + assertThrows(IAE, () -> h0.putAll(Map.of("a", List.of("a\r")))); + assertThrows(IAE, () -> h0.putAll(Map.of("a", List.of("a\n")))); + assertThrows(IAE, () -> h0.putAll(Map.of("\r", List.of("a")))); + assertThrows(IAE, () -> h0.putAll(Map.of("\n", List.of("a")))); + assertThrows(IAE, () -> h0.putAll(Map.of("a\r", List.of("a")))); + assertThrows(IAE, () -> h0.putAll(Map.of("a\n", List.of("a")))); final var h1 = new Headers(); h1.put("a", List.of("1")); @@ -443,5 +455,81 @@ public static void testOfMultipleValues() { List.of(List.of("1"), List.of("1", "2", "3")).forEach(v -> assertTrue(h.containsValue(v))); } + @Test + public static void testNormalizeOnNull() { + assertThrows(NullPointerException.class, () -> normalize(null)); + } + + @DataProvider + public static Object[][] illegalKeys() { + var illegalChars = List.of('\r', '\n'); + var illegalStrings = Stream + // Insert an illegal char at every possible position of following strings + .of("Ab", "ab", "_a", "2a") + .flatMap(s -> IntStream + .range(0, s.length() + 1) + .boxed() + .flatMap(i -> illegalChars + .stream() + .map(c -> s.substring(0, i) + c + s.substring(i)))); + return Stream + .concat(illegalChars.stream().map(c -> "" + c), illegalStrings) + .map(s -> new Object[]{s}) + .toArray(Object[][]::new); + } + + @Test(dataProvider = "illegalKeys") + public static void testNormalizeOnIllegalKeys(String illegalKey) { + assertThrows(IllegalArgumentException.class, () -> normalize(illegalKey)); + } + + @DataProvider + public static Object[][] normalizedKeys() { + return new Object[][]{ + // Empty string + {""}, + // Non-alpha prefix + {"_"}, + {"0"}, + {"_xy-@"}, + {"0xy-@"}, + // Upper-case prefix + {"A"}, + {"B"}, + {"Ayz-@"}, + {"Byz-@"}, + }; + } + + @Test(dataProvider = "normalizedKeys") + public static void testNormalizeOnNormalizedKeys(String normalizedKey) { + // Verify that the fast-path is taken + assertSame(normalize(normalizedKey), normalizedKey); + } + + @DataProvider + public static Object[][] notNormalizedKeys() { + return new Object[][]{ + {"a"}, + {"b"}, + {"axy-@"}, + {"bxy-@"}, + }; + } + + @Test(dataProvider = "notNormalizedKeys") + public static void testNormalizeOnNotNormalizedKeys(String notNormalizedKey) { + var normalizedKey = normalize(notNormalizedKey); + // Verify that the fast-path is *not* taken + assertNotSame(normalizedKey, notNormalizedKey); + // Verify the result + var expectedNormalizedKey = normalizedKey.substring(0, 1).toUpperCase() + normalizedKey.substring(1); + assertEquals(normalizedKey, expectedNormalizedKey); + } + + private static String normalize(String key) { + return Headers.of(key, "foo").keySet().iterator().next(); + } + // Immutability tests in UnmodifiableHeadersTest.java } diff --git a/test/micro/org/openjdk/bench/sun/net/httpserver/HeaderNormalization.java b/test/micro/org/openjdk/bench/sun/net/httpserver/HeaderNormalization.java new file mode 100644 index 000000000000..19470f772715 --- /dev/null +++ b/test/micro/org/openjdk/bench/sun/net/httpserver/HeaderNormalization.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.sun.net.httpserver; + +import com.sun.net.httpserver.Headers; +import org.openjdk.jmh.annotations.*; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.Objects; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; + +/** + * Benchmarks {@code jdk.httpserver} header normalization. + *

+ * You can run this benchmark as follows: + *

{@code
+ * make run-test TEST="micro:HeaderNormalization" MICRO="OPTIONS=-prof gc"
+ * }
+ */ +@BenchmarkMode(Mode.AverageTime) +@Warmup(iterations = 5, time = 3, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@State(org.openjdk.jmh.annotations.Scope.Thread) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@Fork(value = 3, jvmArgs = { + "--add-exports", "jdk.httpserver/com.sun.net.httpserver=ALL-UNNAMED", + "--add-opens", "jdk.httpserver/com.sun.net.httpserver=ALL-UNNAMED", +}) +public class HeaderNormalization { + + private static final Function NORMALIZE = findNormalize(); + + private static Function findNormalize() { + var lookup = MethodHandles.lookup(); + MethodHandle handle; + try { + handle = MethodHandles + .privateLookupIn(Headers.class, lookup) + .findStatic( + Headers.class, "normalize", + MethodType.methodType(String.class, String.class)); + } catch (Exception e) { + throw new RuntimeException(e); + } + return key -> { + try { + return (String) handle.invokeExact(key); + } catch (Throwable e) { + throw new RuntimeException(e); + } + }; + } + + @Param({ + "Accept-charset", // Already normalized + "4ccept-charset", // Already normalized with a non-alpha first letter + "accept-charset", // Only the first `a` must be upper-cased + "Accept-Charset", // Only `c` must be lower-cased + "ACCEPT-CHARSET", // All secondary must be lower-cased + }) + private String key; + + @Benchmark + public String n26() { + return NORMALIZE.apply(key); + } + + @Benchmark + public String n25() { + return normalize25(key); + } + + /** + * The {@code com.sun.net.httpserver.Headers::normalize} method used in Java 25 and before. + */ + private static String normalize25(String key) { + Objects.requireNonNull(key); + int len = key.length(); + if (len == 0) { + return key; + } + char[] b = key.toCharArray(); + if (b[0] >= 'a' && b[0] <= 'z') { + b[0] = (char)(b[0] - ('a' - 'A')); + } else if (b[0] == '\r' || b[0] == '\n') + throw new IllegalArgumentException("illegal character in key"); + + for (int i=1; i= 'A' && b[i] <= 'Z') { + b[i] = (char) (b[i] + ('a' - 'A')); + } else if (b[i] == '\r' || b[i] == '\n') + throw new IllegalArgumentException("illegal character in key"); + } + return new String(b); + } + +} From 68e3ee12447fd4777905463a0919c607f8dd594c Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Tue, 17 Mar 2026 15:36:50 +0000 Subject: [PATCH 050/168] 8339526: C2: store incorrectly removed for clone() transformed to series of loads/stores Backport-of: 5a2b0ca7fea7d1a283aa90696c3989ae189148ec --- src/hotspot/share/opto/arraycopynode.cpp | 9 ++ .../TestCloneUnknownClassAtParseTime.java | 92 +++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 test/hotspot/jtreg/compiler/arraycopy/TestCloneUnknownClassAtParseTime.java diff --git a/src/hotspot/share/opto/arraycopynode.cpp b/src/hotspot/share/opto/arraycopynode.cpp index 85b6bd21aece..c02aefc79437 100644 --- a/src/hotspot/share/opto/arraycopynode.cpp +++ b/src/hotspot/share/opto/arraycopynode.cpp @@ -212,6 +212,15 @@ Node* ArrayCopyNode::try_clone_instance(PhaseGVN *phase, bool can_reshape, int c } } + const TypeInstPtr* dest_type = phase->type(base_dest)->is_instptr(); + if (dest_type->instance_klass() != ik) { + // At parse time, the exact type of the object to clone was not known. That inexact type was captured by the CheckCastPP + // of the newly allocated cloned object (in dest). The exact type is now known (in src), but the type for the cloned object + // (dest) was not updated. When copying the fields below, Store nodes may write to offsets for fields that don't exist in + // the inexact class. The stores would then be assigned an incorrect slice. + return NodeSentinel; + } + assert(ik->nof_nonstatic_fields() <= ArrayCopyLoadStoreMaxElem, "too many fields"); BarrierSetC2* bs = BarrierSet::barrier_set()->barrier_set_c2(); diff --git a/test/hotspot/jtreg/compiler/arraycopy/TestCloneUnknownClassAtParseTime.java b/test/hotspot/jtreg/compiler/arraycopy/TestCloneUnknownClassAtParseTime.java new file mode 100644 index 000000000000..1cd7cc21611c --- /dev/null +++ b/test/hotspot/jtreg/compiler/arraycopy/TestCloneUnknownClassAtParseTime.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2025 IBM Corporation. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8339526 + * @summary C2: store incorrectly removed for clone() transformed to series of loads/stores + * @run main/othervm -XX:-BackgroundCompilation compiler.arraycopy.TestCloneUnknownClassAtParseTime + * @run main compiler.arraycopy.TestCloneUnknownClassAtParseTime + */ + +package compiler.arraycopy; + +public class TestCloneUnknownClassAtParseTime { + private static volatile int volatileField; + static A field; + + public static void main(String[] args) throws CloneNotSupportedException { + A a = new A(); + for (int i = 0; i < 20_000; i++) { + B b = (B)test1(-1); + if (b.field1 != 42 || b.field2 != 42|| b.field3 != 42) { + throw new RuntimeException("Clone wrongly initialized"); + } + inlined1(42); + field = a; + inlined2(); + } + } + + private static A test1(int i) throws CloneNotSupportedException { + int[] nonEscapingArray = new int[1]; + field = new B(42, 42, 42); + + if (i > 0) { + throw new RuntimeException("never taken"); + } + inlined1(i); + + nonEscapingArray[0] = 42; + return inlined2(); + } + + private static A inlined2() throws CloneNotSupportedException { + A a = field; + return (A)a.clone(); + } + + private static void inlined1(int i) { + if (i > 0) { + volatileField = 42; + } + } + + private static class A implements Cloneable { + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } + } + + private static class B extends A { + int field1; + int field2; + int field3; + + B(int v1, int v2, int v3) { + field1 = v1; + field2 = v2; + field3 = v3; + } + } +} From 314a744ec516193f60bcda7fd5add7830ad51329 Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Tue, 17 Mar 2026 15:37:23 +0000 Subject: [PATCH 051/168] 8361699: C2: assert(can_reduce_phi(n->as_Phi())) failed: Sanity: previous reducible Phi is no longer reducible before SUT Reviewed-by: cslucas Backport-of: 6f493b4d2e7120cbe34fb70d595f7626655b47a9 --- src/hotspot/share/opto/escape.cpp | 8 +++ ...stReduceAllocationNotReducibleAnymore.java | 63 +++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 test/hotspot/jtreg/compiler/escapeAnalysis/TestReduceAllocationNotReducibleAnymore.java diff --git a/src/hotspot/share/opto/escape.cpp b/src/hotspot/share/opto/escape.cpp index 3dd43f12b2f5..d7253b065995 100644 --- a/src/hotspot/share/opto/escape.cpp +++ b/src/hotspot/share/opto/escape.cpp @@ -3129,6 +3129,14 @@ void ConnectionGraph::find_scalar_replaceable_allocs(GrowableArrayis_LocalVar()) { + Node* phi = use->ideal_node(); + if (phi->Opcode() == Op_Phi && reducible_merges.member(phi) && !can_reduce_phi(phi->as_Phi())) { + set_not_scalar_replaceable(jobj NOT_PRODUCT(COMMA "is merged in a non-reducible phi")); + reducible_merges.yank(phi); + found_nsr_alloc = true; + break; + } } } } diff --git a/test/hotspot/jtreg/compiler/escapeAnalysis/TestReduceAllocationNotReducibleAnymore.java b/test/hotspot/jtreg/compiler/escapeAnalysis/TestReduceAllocationNotReducibleAnymore.java new file mode 100644 index 000000000000..16bdd68c93c4 --- /dev/null +++ b/test/hotspot/jtreg/compiler/escapeAnalysis/TestReduceAllocationNotReducibleAnymore.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8361699 + * @summary Check that NSR state propagate correctly to initially reducible Phis + * and turns them into non-reducible Phis. + * @run main/othervm -XX:CompileCommand=compileonly,*TestReduceAllocationNotReducibleAnymore*::* + * -XX:CompileCommand=dontinline,*TestReduceAllocationNotReducibleAnymore*::* + * -Xcomp compiler.escapeAnalysis.TestReduceAllocationNotReducibleAnymore + * @run main compiler.escapeAnalysis.TestReduceAllocationNotReducibleAnymore + */ + +package compiler.escapeAnalysis; + +public class TestReduceAllocationNotReducibleAnymore { + public static void main(String[] args) { + for (int i = 0; i < 100; i++) { + test(4, null); + } + } + + static void test(int x, A a) { + Object[] objects = { new Object() }; + Object object = new Object(); + for (int i = 0; i < 150; i++) { + try { + objects[x] = object; + object = new byte[10]; + } catch (ArrayIndexOutOfBoundsException e) { + } + try { + a.foo(); + } catch (NullPointerException e) { + } + } + } + + class A { + void foo() {} + } +} From dcc66f3daa69edda56357c1fc1877a14dde3484b Mon Sep 17 00:00:00 2001 From: Mat Carter Date: Tue, 17 Mar 2026 16:26:11 +0000 Subject: [PATCH 052/168] 8369736: Add management interface for AOT cache creation Reviewed-by: adinn, kvn Backport-of: d9bc82216842bf521ccb7c451b4b411adb0cf3cc --- src/hotspot/share/include/jvm.h | 3 + src/hotspot/share/prims/jvm.cpp | 13 ++ .../classes/sun/management/VMManagement.java | 5 +- .../sun/management/VMManagementImpl.java | 5 +- .../native/libmanagement/VMManagementImpl.c | 7 ++ .../internal/HotSpotAOTCacheImpl.java | 55 ++++++++ .../internal/PlatformMBeanProviderImpl.java | 38 +++++- .../jdk/management/HotSpotAOTCacheMXBean.java | 94 ++++++++++++++ .../aotCache/HotSpotAOTCacheMXBeanTest.java | 118 ++++++++++++++++++ 9 files changed, 335 insertions(+), 3 deletions(-) create mode 100644 src/jdk.management/share/classes/com/sun/management/internal/HotSpotAOTCacheImpl.java create mode 100644 src/jdk.management/share/classes/jdk/management/HotSpotAOTCacheMXBean.java create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/aotCache/HotSpotAOTCacheMXBeanTest.java diff --git a/src/hotspot/share/include/jvm.h b/src/hotspot/share/include/jvm.h index 73f60765a702..a01bad14ab70 100644 --- a/src/hotspot/share/include/jvm.h +++ b/src/hotspot/share/include/jvm.h @@ -87,6 +87,9 @@ JVM_InternString(JNIEnv *env, jstring str); /* * java.lang.System */ +JNIEXPORT jboolean JNICALL +JVM_AOTEndRecording(JNIEnv *env); + JNIEXPORT jlong JNICALL JVM_CurrentTimeMillis(JNIEnv *env, jclass ignored); diff --git a/src/hotspot/share/prims/jvm.cpp b/src/hotspot/share/prims/jvm.cpp index c6f1172ca086..98ec029db189 100644 --- a/src/hotspot/share/prims/jvm.cpp +++ b/src/hotspot/share/prims/jvm.cpp @@ -228,6 +228,19 @@ extern void trace_class_resolution(Klass* to_class) { // java.lang.System ////////////////////////////////////////////////////////////////////// +JVM_ENTRY(jboolean, JVM_AOTEndRecording(JNIEnv *env)) +#if INCLUDE_CDS + if (CDSConfig::is_dumping_preimage_static_archive()) { + if (!MetaspaceShared::preimage_static_archive_dumped()) { + MetaspaceShared::preload_and_dump(THREAD); + return JNI_TRUE; + } + } + return JNI_FALSE; +#else + return JNI_FALSE; +#endif // INCLUDE_CDS +JVM_END JVM_LEAF(jlong, JVM_CurrentTimeMillis(JNIEnv *env, jclass ignored)) return os::javaTimeMillis(); diff --git a/src/java.management/share/classes/sun/management/VMManagement.java b/src/java.management/share/classes/sun/management/VMManagement.java index f4445f0225af..6548ae346d92 100644 --- a/src/java.management/share/classes/sun/management/VMManagement.java +++ b/src/java.management/share/classes/sun/management/VMManagement.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -48,6 +48,9 @@ public interface VMManagement { public boolean isGcNotificationSupported(); public boolean isRemoteDiagnosticCommandsSupported(); + // AOT Subsystem + public boolean endAOTRecording(); + // Class Loading Subsystem public long getTotalClassCount(); public int getLoadedClassCount(); diff --git a/src/java.management/share/classes/sun/management/VMManagementImpl.java b/src/java.management/share/classes/sun/management/VMManagementImpl.java index 041f09547d2b..e91d7955369d 100644 --- a/src/java.management/share/classes/sun/management/VMManagementImpl.java +++ b/src/java.management/share/classes/sun/management/VMManagementImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -117,6 +117,9 @@ public boolean isRemoteDiagnosticCommandsSupported() { public native boolean isThreadCpuTimeEnabled(); public native boolean isThreadAllocatedMemoryEnabled(); + // AOT Subsystem + public native boolean endAOTRecording(); + // Class Loading Subsystem public int getLoadedClassCount() { long count = getTotalClassCount() - getUnloadedClassCount(); diff --git a/src/java.management/share/native/libmanagement/VMManagementImpl.c b/src/java.management/share/native/libmanagement/VMManagementImpl.c index f1a566676dce..dc8ad3c5c19b 100644 --- a/src/java.management/share/native/libmanagement/VMManagementImpl.c +++ b/src/java.management/share/native/libmanagement/VMManagementImpl.c @@ -101,6 +101,13 @@ Java_sun_management_VMManagementImpl_getVmArguments0 return JVM_GetVmArguments(env); } +JNIEXPORT jboolean JNICALL +Java_sun_management_VMManagementImpl_endAOTRecording + (JNIEnv *env, jobject dummy) +{ + return JVM_AOTEndRecording(env); +} + JNIEXPORT jlong JNICALL Java_sun_management_VMManagementImpl_getTotalClassCount (JNIEnv *env, jobject dummy) diff --git a/src/jdk.management/share/classes/com/sun/management/internal/HotSpotAOTCacheImpl.java b/src/jdk.management/share/classes/com/sun/management/internal/HotSpotAOTCacheImpl.java new file mode 100644 index 000000000000..4bb556455ea4 --- /dev/null +++ b/src/jdk.management/share/classes/com/sun/management/internal/HotSpotAOTCacheImpl.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2025, Microsoft, Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.sun.management.internal; + +import javax.management.ObjectName; +import jdk.management.HotSpotAOTCacheMXBean; +import sun.management.Util; +import sun.management.VMManagement; + +/** + * Implementation class for the AOT Cache subsystem. + * + * ManagementFactory.getRuntimeMXBean() returns an instance + * of this class. + */ +public class HotSpotAOTCacheImpl implements HotSpotAOTCacheMXBean { + + private final VMManagement jvm; + /** + * Constructor of HotSpotAOTCacheImpl class. + */ + HotSpotAOTCacheImpl(VMManagement vm) { + this.jvm = vm; + } + + public boolean endRecording() { + return jvm.endAOTRecording(); + } + + public ObjectName getObjectName() { + return Util.newObjectName("jdk.management:type=HotSpotAOTCache"); + } +} \ No newline at end of file diff --git a/src/jdk.management/share/classes/com/sun/management/internal/PlatformMBeanProviderImpl.java b/src/jdk.management/share/classes/com/sun/management/internal/PlatformMBeanProviderImpl.java index 3a64fe6b858f..b000516e626c 100644 --- a/src/jdk.management/share/classes/com/sun/management/internal/PlatformMBeanProviderImpl.java +++ b/src/jdk.management/share/classes/com/sun/management/internal/PlatformMBeanProviderImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -39,6 +39,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import javax.management.DynamicMBean; +import jdk.management.HotSpotAOTCacheMXBean; import jdk.management.VirtualThreadSchedulerMXBean; import sun.management.ManagementFactoryHelper; import sun.management.spi.PlatformMBeanProvider; @@ -159,6 +160,41 @@ public synchronized Map nameToMBeanMa } }); + /** + * HotSpotAOTCacheMXBean. + */ + initMBeanList.add(new PlatformComponent() { + private final Set> mbeanInterfaces = + Set.of(HotSpotAOTCacheMXBean.class); + private final Set mbeanInterfaceNames = + Set.of(HotSpotAOTCacheMXBean.class.getName()); + private HotSpotAOTCacheMXBean impl; + + @Override + public Set> mbeanInterfaces() { + return mbeanInterfaces; + } + + @Override + public Set mbeanInterfaceNames() { + return mbeanInterfaceNames; + } + + @Override + public String getObjectNamePattern() { + return "jdk.management:type=HotSpotAOTCache"; + } + + @Override + public Map nameToMBeanMap() { + HotSpotAOTCacheMXBean impl = this.impl; + if (impl == null) { + this.impl = impl = new HotSpotAOTCacheImpl(ManagementFactoryHelper.getVMManagement()); + } + return Map.of("jdk.management:type=HotSpotAOTCache", impl); + } + }); + /** * VirtualThreadSchedulerMXBean. */ diff --git a/src/jdk.management/share/classes/jdk/management/HotSpotAOTCacheMXBean.java b/src/jdk.management/share/classes/jdk/management/HotSpotAOTCacheMXBean.java new file mode 100644 index 000000000000..399f02c9044b --- /dev/null +++ b/src/jdk.management/share/classes/jdk/management/HotSpotAOTCacheMXBean.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2025, Microsoft, Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.management; + +import java.lang.management.ManagementFactory; +import java.lang.management.PlatformManagedObject; +import javax.management.MBeanServer; +import javax.management.ObjectName; + +/** + * Management interface for the JDK's Ahead of Time (AOT) Cache. + * + *

The management interface is registered with the platform {@link MBeanServer + * MBeanServer}. The {@link ObjectName ObjectName} that uniquely identifies the management + * interface within the {@code MBeanServer} is {@code jdk.management:type=HotSpotAOTCache}. + * + *

Direct access to the MXBean interface can be obtained with + * {@link ManagementFactory#getPlatformMXBean(Class)}. + * + * @apiNote This interface is defined in JDK 25.0.3. + * @since 25 + */ +public interface HotSpotAOTCacheMXBean extends PlatformManagedObject { + /** + * If an AOT recording is in progress, ends the recording. This method returns + * after the AOT artifacts have been completely written. + * + *

The JVM will start recording AOT artifacts upon start-up if appropriate JVM options are + * given in the command-line. The recording will stop when the JVM exits, or when + * the {@code endRecording} method is called. Examples: + * + *

${@code java -XX:AOTCacheOutput=app.aot ....} + * + *

+ * The JVM records optimization information for the current application in the AOT cache file + * {@code app.aot}. In a future run of the application, the option {@code -XX:AOTCache=app.aot} will + * cause the JVM to use the cache to improve the application's startup and warmup performance. + *
+ * + *

${@code java -XX:AOTMode=record -XX:AOTConfiguration=app.aotconfig ....} + * + *

+ * The JVM records optimization information for the current application in the AOT configuration + * file {@code app.aotconfig}. Subsequently, an AOT cache file can be created with the command: + * + *

${@code java -XX:AOTMode=create -XX:AOTConfiguration=app.aotconfig -XX:AOTCache=app.aot ...} + *

+ * + *

For more information about creating and using the AOT artifacts, and detailed + * specification of the corresponding JVM command-line options, please refer + * to JEP 483 and JEP 514. + * + *

Currently there are no APIs to start an AOT recording. AOT recordings must be + * started using JVM command-line options such as {@code -XX:AOTCacheOutput}. + * There are also no APIs to query whether an AOT recording is in progress, or what AOT + * artifacts are being recorded. + * + *

This method enables an application to end its own AOT recording + * programatically, but that is not necessarily the best approach. Doing so + * requires changing the application’s code, which might not be + * feasible. Even when it is feasible, injecting training-specific logic + * into the application reduces the similarity between training runs and + * production runs, potentially making the AOT cache less effective. It may + * be better to arrange for an external agent to end the training run, + * thereby creating an AOT cache without interfering with the application’s + * code. + * + * @return {@code true} if a recording was in progress and has been ended + * successfully; {@code false} otherwise. + */ + public boolean endRecording(); +} \ No newline at end of file diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/HotSpotAOTCacheMXBeanTest.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/HotSpotAOTCacheMXBeanTest.java new file mode 100644 index 000000000000..fba3589f5cea --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/HotSpotAOTCacheMXBeanTest.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2025, Microsoft, Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + + +/* + * @test + * @summary Sanity test for HotSpotAOTCache MXBean + * @requires vm.cds.write.archived.java.heap + * @library /test/jdk/lib/testlibrary /test/lib + * @build HotSpotAOTCacheMXBeanTest + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar HotSpotAOTCacheMXBeanApp + * @run driver HotSpotAOTCacheMXBeanTest + */ + +import java.io.IOException; +import java.lang.management.ManagementFactory; +import javax.management.MBeanServer; +import jdk.management.HotSpotAOTCacheMXBean; +import jdk.test.lib.cds.CDSAppTester; +import jdk.test.lib.helpers.ClassFileInstaller; +import jdk.test.lib.process.OutputAnalyzer; + +public class HotSpotAOTCacheMXBeanTest { + static final String appJar = ClassFileInstaller.getJarPath("app.jar"); + static final String mainClass = "HotSpotAOTCacheMXBeanApp"; + public static void main(String[] args) throws Exception { + Tester tester = new Tester(); + tester.runAOTWorkflow(); + } + + static class Tester extends CDSAppTester { + public Tester() { + super(mainClass); + } + + @Override + public String classpath(RunMode runMode) { + return appJar; + } + + @Override + public String[] vmArgs(RunMode runMode) { + return new String[] { + "-Xlog:cds+class=trace", + "--add-modules=jdk.management" + }; + } + + @Override + public String[] appCommandLine(RunMode runMode) { + return new String[] { + mainClass, runMode.name() + }; + } + + @Override + public void checkExecution(OutputAnalyzer out, RunMode runMode) { + var name = runMode.name(); + if (runMode.isApplicationExecuted()) { + if(runMode == RunMode.TRAINING) { + out.shouldContain("Hello Leyden " + name); + out.shouldContain("Successfully stopped recording"); + } else if (runMode == RunMode.ASSEMBLY) { + out.shouldNotContain("Hello Leyden "); + } else if (runMode == RunMode.PRODUCTION) { + out.shouldContain("Hello Leyden " + name); + out.shouldContain("Failed to stop recording"); + } + out.shouldNotContain("HotSpotAOTCacheMXBean is not available"); + out.shouldNotContain("IOException occurred!"); + } + } + } +} + +class HotSpotAOTCacheMXBeanApp { + public static void main(String[] args) { + System.out.println("Hello Leyden " + args[0]); + try { + MBeanServer server = ManagementFactory.getPlatformMBeanServer(); + HotSpotAOTCacheMXBean aotBean = ManagementFactory.newPlatformMXBeanProxy(server, + "jdk.management:type=HotSpotAOTCache", + HotSpotAOTCacheMXBean.class); + if (aotBean == null) { + System.out.println("HotSpotAOTCacheMXBean is not available"); + return; + } + if (aotBean.endRecording()) { + System.out.println("Successfully stopped recording"); + } else { + System.out.println("Failed to stop recording"); + } + } catch (IOException e) { + System.out.println("IOException occurred!"); + } + } +} \ No newline at end of file From fda0bc61af3048f6f763021c56c80a640ebe8dcd Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Tue, 17 Mar 2026 18:02:19 +0000 Subject: [PATCH 053/168] 8358772: Template-Framework Library: Primitive Types Backport-of: 6749c62b9e4261d25bea477e3c0840ab0ee9c73e --- .../compiler/lib/template_framework/Hook.java | 2 +- .../lib/template_framework/Template.java | 2 +- .../lib/template_framework/Token.java | 45 +--- .../lib/template_framework/TokenParser.java | 71 +++++++ .../library/CodeGenerationDataNameType.java | 157 ++++++++++++++ .../library/PrimitiveType.java | 151 +++++++++++++ .../examples/TestPrimitiveTypes.java | 201 ++++++++++++++++++ 7 files changed, 585 insertions(+), 44 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/TokenParser.java create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/library/CodeGenerationDataNameType.java create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/library/PrimitiveType.java create mode 100644 test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestPrimitiveTypes.java diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/Hook.java b/test/hotspot/jtreg/compiler/lib/template_framework/Hook.java index 48f7852d5098..8ee2689eb2fc 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/Hook.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/Hook.java @@ -75,7 +75,7 @@ public record Hook(String name) { * @return A {@link Token} that captures the anchoring of the scope and the list of validated {@link Token}s. */ public Token anchor(Object... tokens) { - return new HookAnchorToken(this, Token.parse(tokens)); + return new HookAnchorToken(this, TokenParser.parse(tokens)); } /** diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/Template.java b/test/hotspot/jtreg/compiler/lib/template_framework/Template.java index f01c5ccffd3d..57d06e732bb1 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/Template.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/Template.java @@ -615,7 +615,7 @@ static Template.ThreeArgs make(String arg1Name, String * @throws IllegalArgumentException if the list of tokens contains an unexpected object. */ static TemplateBody body(Object... tokens) { - return new TemplateBody(Token.parse(tokens)); + return new TemplateBody(TokenParser.parse(tokens)); } /** diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/Token.java b/test/hotspot/jtreg/compiler/lib/template_framework/Token.java index dc750c7f79f3..0e9f9b272c54 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/Token.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/Token.java @@ -23,17 +23,11 @@ package compiler.lib.template_framework; -import java.util.Arrays; -import java.util.ArrayList; -import java.util.List; - /** * The {@link Template#body} and {@link Hook#anchor} are given a list of tokens, which are either - * {@link Token}s or {@link String}s or some permitted boxed primitives. These are then parsed - * and all non-{@link Token}s are converted to {@link StringToken}s. The parsing also flattens - * {@link List}s. + * {@link Token}s or {@link String}s or some permitted boxed primitives. */ -sealed interface Token permits StringToken, +public sealed interface Token permits StringToken, TemplateToken, TemplateToken.ZeroArgs, TemplateToken.OneArg, @@ -42,37 +36,4 @@ sealed interface Token permits StringToken, HookAnchorToken, HookInsertToken, AddNameToken, - NothingToken -{ - static List parse(Object[] objects) { - if (objects == null) { - throw new IllegalArgumentException("Unexpected tokens: null"); - } - List outputList = new ArrayList<>(); - parseToken(Arrays.asList(objects), outputList); - return outputList; - } - - private static void parseList(List inputList, List outputList) { - for (Object o : inputList) { - parseToken(o, outputList); - } - } - - private static void parseToken(Object o, List outputList) { - if (o == null) { - throw new IllegalArgumentException("Unexpected token: null"); - } - switch (o) { - case Token t -> outputList.add(t); - case String s -> outputList.add(new StringToken(Renderer.format(s))); - case Integer s -> outputList.add(new StringToken(Renderer.format(s))); - case Long s -> outputList.add(new StringToken(Renderer.format(s))); - case Double s -> outputList.add(new StringToken(Renderer.format(s))); - case Float s -> outputList.add(new StringToken(Renderer.format(s))); - case Boolean s -> outputList.add(new StringToken(Renderer.format(s))); - case List l -> parseList(l, outputList); - default -> throw new IllegalArgumentException("Unexpected token: " + o); - } - } -} + NothingToken {} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/TokenParser.java b/test/hotspot/jtreg/compiler/lib/template_framework/TokenParser.java new file mode 100644 index 000000000000..0c335bd4fb89 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/TokenParser.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.lib.template_framework; + +import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; + +/** + * Helper class for {@link Token}, to keep the parsing methods package private. + * + *

+ * The {@link Template#body} and {@link Hook#anchor} are given a list of tokens, which are either + * {@link Token}s or {@link String}s or some permitted boxed primitives. These are then parsed + * and all non-{@link Token}s are converted to {@link StringToken}s. The parsing also flattens + * {@link List}s. + */ +final class TokenParser { + static List parse(Object[] objects) { + if (objects == null) { + throw new IllegalArgumentException("Unexpected tokens: null"); + } + List outputList = new ArrayList<>(); + parseToken(Arrays.asList(objects), outputList); + return outputList; + } + + private static void parseList(List inputList, List outputList) { + for (Object o : inputList) { + parseToken(o, outputList); + } + } + + private static void parseToken(Object o, List outputList) { + if (o == null) { + throw new IllegalArgumentException("Unexpected token: null"); + } + switch (o) { + case Token t -> outputList.add(t); + case String s -> outputList.add(new StringToken(Renderer.format(s))); + case Integer s -> outputList.add(new StringToken(Renderer.format(s))); + case Long s -> outputList.add(new StringToken(Renderer.format(s))); + case Double s -> outputList.add(new StringToken(Renderer.format(s))); + case Float s -> outputList.add(new StringToken(Renderer.format(s))); + case Boolean s -> outputList.add(new StringToken(Renderer.format(s))); + case List l -> parseList(l, outputList); + default -> throw new IllegalArgumentException("Unexpected token: " + o); + } + } +} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/library/CodeGenerationDataNameType.java b/test/hotspot/jtreg/compiler/lib/template_framework/library/CodeGenerationDataNameType.java new file mode 100644 index 000000000000..56f7afcbaeb9 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/library/CodeGenerationDataNameType.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.lib.template_framework.library; + +import java.util.List; + +import compiler.lib.template_framework.DataName; +import compiler.lib.template_framework.Template; + +/** + * The {@link CodeGenerationDataNameType} extends the {@link DataName.Type} with + * additional functionality for code generation. These types with their extended + * functionality can be used with many other code generation facilities in the + * library, such as generating random {@code Expression}s. + */ +public interface CodeGenerationDataNameType extends DataName.Type { + + /** + * This method provides a random constant value for the type, which can + * be used as a token inside a {@link Template}. + * + * @return A random constant value. + */ + Object con(); + + /** + * The byte {@link PrimitiveType}. + * + * @return The byte {@link PrimitiveType}. + */ + static PrimitiveType bytes() { return PrimitiveType.BYTES; } + + /** + * The short {@link PrimitiveType}. + * + * @return The short {@link PrimitiveType}. + */ + static PrimitiveType shorts() { return PrimitiveType.SHORTS; } + + /** + * The char {@link PrimitiveType}. + * + * @return The char {@link PrimitiveType}. + */ + static PrimitiveType chars() { return PrimitiveType.CHARS; } + + /** + * The int {@link PrimitiveType}. + * + * @return The int {@link PrimitiveType}. + */ + static PrimitiveType ints() { return PrimitiveType.INTS; } + + /** + * The long {@link PrimitiveType}. + * + * @return The long {@link PrimitiveType}. + */ + static PrimitiveType longs() { return PrimitiveType.LONGS; } + + /** + * The float {@link PrimitiveType}. + * + * @return The float {@link PrimitiveType}. + */ + static PrimitiveType floats() { return PrimitiveType.FLOATS; } + + /** + * The double {@link PrimitiveType}. + * + * @return The double {@link PrimitiveType}. + */ + static PrimitiveType doubles() { return PrimitiveType.DOUBLES; } + + /** + * The boolean {@link PrimitiveType}. + * + * @return The boolean {@link PrimitiveType}. + */ + static PrimitiveType booleans() { return PrimitiveType.BOOLEANS; } + + /** + * List of all {@link PrimitiveType}s. + */ + List PRIMITIVE_TYPES = List.of( + bytes(), + chars(), + shorts(), + ints(), + longs(), + floats(), + doubles(), + booleans() + ); + + /** + * List of all integral {@link PrimitiveType}s (byte, char, short, int, long). + */ + List INTEGRAL_TYPES = List.of( + bytes(), + chars(), + shorts(), + ints(), + longs() + ); + + /** + * List of all subword {@link PrimitiveType}s (byte, char, short). + */ + List SUBWORD_TYPES = List.of( + bytes(), + chars(), + shorts() + ); + + /** + * List of all floating {@link PrimitiveType}s (float, double). + */ + List FLOATING_TYPES = List.of( + floats(), + doubles() + ); + + /** + * List of all integral and floating {@link PrimitiveType}s. + */ + List INTEGRAL_AND_FLOATING_TYPES = List.of( + bytes(), + chars(), + shorts(), + ints(), + longs(), + floats(), + doubles() + ); +} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/library/PrimitiveType.java b/test/hotspot/jtreg/compiler/lib/template_framework/library/PrimitiveType.java new file mode 100644 index 000000000000..3bf6c7f62886 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/library/PrimitiveType.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.lib.template_framework.library; + +import java.util.Random; +import jdk.test.lib.Utils; + +import compiler.lib.generators.Generators; +import compiler.lib.generators.Generator; +import compiler.lib.generators.RestrictableGenerator; + +import compiler.lib.template_framework.DataName; + +/** + * The {@link PrimitiveType} models Java's primitive types, and provides a set + * of useful methods for code generation, such as the {@link #byteSize} and + * {@link #boxedTypeName}. + */ +public final class PrimitiveType implements CodeGenerationDataNameType { + private static final Random RANDOM = Utils.getRandomInstance(); + private static final RestrictableGenerator GEN_BYTE = Generators.G.safeRestrict(Generators.G.ints(), Byte.MIN_VALUE, Byte.MAX_VALUE); + private static final RestrictableGenerator GEN_CHAR = Generators.G.safeRestrict(Generators.G.ints(), Character.MIN_VALUE, Character.MAX_VALUE); + private static final RestrictableGenerator GEN_SHORT = Generators.G.safeRestrict(Generators.G.ints(), Short.MIN_VALUE, Short.MAX_VALUE); + private static final RestrictableGenerator GEN_INT = Generators.G.ints(); + private static final RestrictableGenerator GEN_LONG = Generators.G.longs(); + private static final Generator GEN_DOUBLE = Generators.G.doubles(); + private static final Generator GEN_FLOAT = Generators.G.floats(); + + private static enum Kind { BYTE, SHORT, CHAR, INT, LONG, FLOAT, DOUBLE, BOOLEAN }; + + // We have one static instance each, so we do not have duplicated instances. + static final PrimitiveType BYTES = new PrimitiveType(Kind.BYTE ); + static final PrimitiveType SHORTS = new PrimitiveType(Kind.SHORT ); + static final PrimitiveType CHARS = new PrimitiveType(Kind.CHAR ); + static final PrimitiveType INTS = new PrimitiveType(Kind.INT ); + static final PrimitiveType LONGS = new PrimitiveType(Kind.LONG ); + static final PrimitiveType FLOATS = new PrimitiveType(Kind.FLOAT ); + static final PrimitiveType DOUBLES = new PrimitiveType(Kind.DOUBLE ); + static final PrimitiveType BOOLEANS = new PrimitiveType(Kind.BOOLEAN); + + final Kind kind; + + // Private constructor so nobody can create duplicate instances. + private PrimitiveType(Kind kind) { + this.kind = kind; + } + + @Override + public boolean isSubtypeOf(DataName.Type other) { + return (other instanceof PrimitiveType pt) && pt.kind == kind; + } + + @Override + public String name() { + return switch (kind) { + case BYTE -> "byte"; + case SHORT -> "short"; + case CHAR -> "char"; + case INT -> "int"; + case LONG -> "long"; + case FLOAT -> "float"; + case DOUBLE -> "double"; + case BOOLEAN -> "boolean"; + }; + } + + @Override + public String toString() { + return name(); + } + + public Object con() { + return switch (kind) { + case BYTE -> "(byte)" + GEN_BYTE.next(); + case SHORT -> "(short)" + GEN_SHORT.next(); + case CHAR -> "(char)" + GEN_CHAR.next(); + case INT -> GEN_INT.next(); + case LONG -> GEN_LONG.next(); + case FLOAT -> GEN_FLOAT.next(); + case DOUBLE -> GEN_DOUBLE.next(); + case BOOLEAN -> RANDOM.nextBoolean(); + }; + } + + /** + * Provides the size of the type in bytes. + * + * @return Size of the type in bytes. + * @throws UnsupportedOperationException for boolean which has no defined size. + */ + public int byteSize() { + return switch (kind) { + case BYTE -> 1; + case SHORT, CHAR -> 2; + case INT, FLOAT -> 4; + case LONG, DOUBLE -> 8; + case BOOLEAN -> { throw new UnsupportedOperationException("boolean does not have a defined 'size'"); } + }; + } + + /** + * Provides the name of the boxed type. + * + * @return the name of the boxed type. + */ + public String boxedTypeName() { + return switch (kind) { + case BYTE -> "Byte"; + case SHORT -> "Short"; + case CHAR -> "Character"; + case INT -> "Integer"; + case LONG -> "Long"; + case FLOAT -> "Float"; + case DOUBLE -> "Double"; + case BOOLEAN -> "Boolean"; + }; + } + + /** + * Indicates if the type is a floating point type. + * + * @return true iff the type is a floating point type. + */ + public boolean isFloating() { + return switch (kind) { + case BYTE, SHORT, CHAR, INT, LONG, BOOLEAN -> false; + case FLOAT, DOUBLE -> true; + }; + } +} diff --git a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestPrimitiveTypes.java b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestPrimitiveTypes.java new file mode 100644 index 000000000000..5cd3f3c2a226 --- /dev/null +++ b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestPrimitiveTypes.java @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8358772 + * @summary Demonstrate the use of PrimitiveTypes form the Template Library. + * @modules java.base/jdk.internal.misc + * @library /test/lib / + * @compile ../../../compiler/lib/verify/Verify.java + * @run main template_framework.examples.TestPrimitiveTypes + */ + +package template_framework.examples; + +import java.util.List; +import java.util.Map; +import java.util.Collections; +import java.util.HashMap; + +import compiler.lib.compile_framework.*; +import compiler.lib.template_framework.Template; +import compiler.lib.template_framework.TemplateToken; +import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.dataNames; +import static compiler.lib.template_framework.Template.let; +import static compiler.lib.template_framework.Template.$; +import static compiler.lib.template_framework.Template.addDataName; +import static compiler.lib.template_framework.DataName.Mutability.MUTABLE; + +import compiler.lib.template_framework.library.Hooks; +import compiler.lib.template_framework.library.CodeGenerationDataNameType; +import compiler.lib.template_framework.library.PrimitiveType; + +/** + * This test shows the use of {@link PrimitiveType}. + */ +public class TestPrimitiveTypes { + + public static void main(String[] args) { + // Create a new CompileFramework instance. + CompileFramework comp = new CompileFramework(); + + // Add a java source file. + comp.addJavaSourceCode("p.xyz.InnerTest", generate()); + + // Compile the source file. + comp.compile(); + + // p.xyz.InnerTest.main(); + comp.invoke("p.xyz.InnerTest", "main", new Object[] {}); + } + + // Generate a Java source file as String + public static String generate() { + // Generate a list of test methods. + Map tests = new HashMap<>(); + + // The boxing tests check if we can autobox with "boxedTypeName". + var boxingTemplate = Template.make("name", "type", (String name, PrimitiveType type) -> body( + let("CON1", type.con()), + let("CON2", type.con()), + let("Boxed", type.boxedTypeName()), + """ + public static void #name() { + #type c1 = #CON1; + #type c2 = #CON2; + #Boxed b1 = c1; + #Boxed b2 = c2; + Verify.checkEQ(c1, b1); + Verify.checkEQ(c2, b2); + } + """ + )); + + for (PrimitiveType type : CodeGenerationDataNameType.PRIMITIVE_TYPES) { + String name = "test_boxing_" + type.name(); + tests.put(name, boxingTemplate.asToken(name, type)); + } + + // Integral and Float types have a size. Also test if "isFloating" is correct. + var integralFloatTemplate = Template.make("name", "type", (String name, PrimitiveType type) -> body( + let("size", type.byteSize()), + let("isFloating", type.isFloating()), + """ + public static void #name() { + // Test byteSize via creation of array. + #type[] array = new #type[1]; + MemorySegment ms = MemorySegment.ofArray(array); + if (#size != ms.byteSize()) { + throw new RuntimeException("byteSize mismatch #type"); + } + + // Test isFloating via rounding. + double value = 1.5; + #type rounded = (#type)value; + boolean isFloating = value != rounded; + if (isFloating == #isFloating) { + throw new RuntimeException("isFloating mismatch #type"); + } + } + """ + )); + + for (PrimitiveType type : CodeGenerationDataNameType.INTEGRAL_AND_FLOATING_TYPES) { + String name = "test_integral_floating_" + type.name(); + tests.put(name, integralFloatTemplate.asToken(name, type)); + } + + // Finally, test the type by creating some DataNames (variables), and sampling + // from them. There should be no cross-over between the types. + var variableTemplate = Template.make("type", (PrimitiveType type) -> body( + let("CON", type.con()), + addDataName($("var"), type, MUTABLE), + """ + #type $var = #CON; + """ + )); + + var sampleTemplate = Template.make("type", (PrimitiveType type) -> body( + let("var", dataNames(MUTABLE).exactOf(type).sample().name()), + let("CON", type.con()), + """ + #var = #CON; + """ + )); + + var namesTemplate = Template.make(() -> body( + """ + public static void test_names() { + """, + Hooks.METHOD_HOOK.anchor( + Collections.nCopies(10, + CodeGenerationDataNameType.PRIMITIVE_TYPES.stream().map(type -> + Hooks.METHOD_HOOK.insert(variableTemplate.asToken(type)) + ).toList() + ), + """ + // Now sample: + """, + Collections.nCopies(10, + CodeGenerationDataNameType.PRIMITIVE_TYPES.stream().map(sampleTemplate::asToken).toList() + ) + ), + """ + } + """ + )); + + tests.put("test_names", namesTemplate.asToken()); + + // Finally, put all the tests together in a class, and invoke all + // tests from the main method. + var template = Template.make(() -> body( + """ + package p.xyz; + + import compiler.lib.verify.*; + import java.lang.foreign.MemorySegment; + + public class InnerTest { + public static void main() { + """, + // Call all test methods from main. + tests.keySet().stream().map( + n -> List.of(n, "();\n") + ).toList(), + """ + } + """, + // Now add all the test methods. + tests.values().stream().toList(), + """ + } + """ + )); + + // Render the template to a String. + return template.render(); + } +} From 0881f7ede44173becca4ffae9224b3047625df75 Mon Sep 17 00:00:00 2001 From: Srinivas Vamsi Parasa Date: Tue, 17 Mar 2026 21:01:42 +0000 Subject: [PATCH 054/168] =?UTF-8?q?8374744:=20Enable=20dumping=20of=20APX?= =?UTF-8?q?=20EGPRs=20(R16=E2=80=93R31)=20in=20JVM=20fatal=20error=20logs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewed-by: drwhite, shade Backport-of: 3a4277db74f889d0b8350145515c1a1f4e399ec8 --- src/hotspot/cpu/x86/vm_version_x86.cpp | 16 ++- src/hotspot/cpu/x86/vm_version_x86.hpp | 9 ++ src/hotspot/os_cpu/linux_x86/os_linux_x86.cpp | 101 ++++++++++++++---- 3 files changed, 104 insertions(+), 22 deletions(-) diff --git a/src/hotspot/cpu/x86/vm_version_x86.cpp b/src/hotspot/cpu/x86/vm_version_x86.cpp index 8fc9c2f9eaa8..32675a12cb07 100644 --- a/src/hotspot/cpu/x86/vm_version_x86.cpp +++ b/src/hotspot/cpu/x86/vm_version_x86.cpp @@ -140,7 +140,7 @@ class VM_Version_StubGenerator: public StubCodeGenerator { Label detect_486, cpu486, detect_586, std_cpuid1, std_cpuid4, std_cpuid24, std_cpuid29; Label sef_cpuid, sefsl1_cpuid, ext_cpuid, ext_cpuid1, ext_cpuid5, ext_cpuid7; - Label ext_cpuid8, done, wrapup, vector_save_restore, apx_save_restore_warning; + Label ext_cpuid8, done, wrapup, vector_save_restore, apx_save_restore_warning, apx_xstate; Label legacy_setup, save_restore_except, legacy_save_restore, start_simd_check; StubCodeMark mark(this, "VM_Version", "get_cpu_info_stub"); @@ -465,6 +465,20 @@ class VM_Version_StubGenerator: public StubCodeGenerator { __ movq(Address(rsi, 0), r16); __ movq(Address(rsi, 8), r31); + // + // Query CPUID 0xD.19 for APX XSAVE offset + // Extended State Enumeration Sub-leaf 19 (APX) + // EAX = size of APX state (should be 128) + // EBX = offset in standard XSAVE format + // + __ movl(rax, 0xD); + __ movl(rcx, 19); + __ cpuid(); + __ lea(rsi, Address(rbp, in_bytes(VM_Version::apx_xstate_size_offset()))); + __ movl(Address(rsi, 0), rax); + __ lea(rsi, Address(rbp, in_bytes(VM_Version::apx_xstate_offset_offset()))); + __ movl(Address(rsi, 0), rbx); + UseAPX = save_apx; __ bind(vector_save_restore); // diff --git a/src/hotspot/cpu/x86/vm_version_x86.hpp b/src/hotspot/cpu/x86/vm_version_x86.hpp index 78ae45ef4f8c..64b9102b5cb8 100644 --- a/src/hotspot/cpu/x86/vm_version_x86.hpp +++ b/src/hotspot/cpu/x86/vm_version_x86.hpp @@ -672,6 +672,10 @@ class VM_Version : public Abstract_VM_Version { // Space to save apx registers after signal handle jlong apx_save[2]; // Save r16 and r31 + // cpuid function 0xD, subleaf 19 (APX extended state) + uint32_t apx_xstate_size; // EAX: size of APX state (128) + uint32_t apx_xstate_offset; // EBX: offset in standard XSAVE area + VM_Features feature_flags() const; // Asserts @@ -735,6 +739,11 @@ class VM_Version : public Abstract_VM_Version { static ByteSize ymm_save_offset() { return byte_offset_of(CpuidInfo, ymm_save); } static ByteSize zmm_save_offset() { return byte_offset_of(CpuidInfo, zmm_save); } static ByteSize apx_save_offset() { return byte_offset_of(CpuidInfo, apx_save); } + static ByteSize apx_xstate_offset_offset() { return byte_offset_of(CpuidInfo, apx_xstate_offset); } + static ByteSize apx_xstate_size_offset() { return byte_offset_of(CpuidInfo, apx_xstate_size); } + + static uint32_t apx_xstate_offset() { return _cpuid_info.apx_xstate_offset; } + static uint32_t apx_xstate_size() { return _cpuid_info.apx_xstate_size; } // The value used to check ymm register after signal handle static int ymm_test_value() { return 0xCAFEBABE; } diff --git a/src/hotspot/os_cpu/linux_x86/os_linux_x86.cpp b/src/hotspot/os_cpu/linux_x86/os_linux_x86.cpp index ff7fce234c4c..5a84cfef7bda 100644 --- a/src/hotspot/os_cpu/linux_x86/os_linux_x86.cpp +++ b/src/hotspot/os_cpu/linux_x86/os_linux_x86.cpp @@ -51,6 +51,7 @@ #include "utilities/debug.hpp" #include "utilities/events.hpp" #include "utilities/vmError.hpp" +#include "runtime/vm_version.hpp" // put OS-includes here # include @@ -516,6 +517,43 @@ size_t os::Posix::default_stack_size(os::ThreadType thr_type) { ///////////////////////////////////////////////////////////////////////////// // helper functions for fatal error handler +// XSAVE constants - from Intel SDM Vol. 1, Chapter 13 +#define XSAVE_HDR_OFFSET 512 +#define XFEATURE_APX (1ULL << 19) + +// XSAVE header structure +// See: Intel SDM Vol. 1, Section 13.4.2 "XSAVE Header" +// Also: Linux kernel arch/x86/include/asm/fpu/types.h +struct xstate_header { + uint64_t xfeatures; + uint64_t xcomp_bv; + uint64_t reserved[6]; +}; + +// APX extended state - R16-R31 (16 x 64-bit registers) +// See: Intel APX Architecture Specification +struct apx_state { + uint64_t regs[16]; // r16-r31 +}; + +static apx_state* get_apx_state(const ucontext_t* uc) { + uint32_t offset = VM_Version::apx_xstate_offset(); + if (offset == 0 || uc->uc_mcontext.fpregs == nullptr) { + return nullptr; + } + + char* xsave = (char*)uc->uc_mcontext.fpregs; + xstate_header* hdr = (xstate_header*)(xsave + XSAVE_HDR_OFFSET); + + // Check if APX state is present in this context + if (!(hdr->xfeatures & XFEATURE_APX)) { + return nullptr; + } + + return (apx_state*)(xsave + offset); +} + + void os::print_context(outputStream *st, const void *context) { if (context == nullptr) return; @@ -543,6 +581,14 @@ void os::print_context(outputStream *st, const void *context) { st->print(", R14=" INTPTR_FORMAT, (intptr_t)uc->uc_mcontext.gregs[REG_R14]); st->print(", R15=" INTPTR_FORMAT, (intptr_t)uc->uc_mcontext.gregs[REG_R15]); st->cr(); + // Dump APX EGPRs (R16-R31) + apx_state* apx = UseAPX ? get_apx_state(uc) : nullptr; + if (apx != nullptr) { + for (int i = 0; i < 16; i++) { + st->print("%sR%d=" INTPTR_FORMAT, (i % 4 == 0) ? "" : ", ", 16 + i, (intptr_t)apx->regs[i]); + if (i % 4 == 3) st->cr(); + } + } st->print( "RIP=" INTPTR_FORMAT, (intptr_t)uc->uc_mcontext.gregs[REG_RIP]); st->print(", EFLAGS=" INTPTR_FORMAT, (intptr_t)uc->uc_mcontext.gregs[REG_EFL]); st->print(", CSGSFS=" INTPTR_FORMAT, (intptr_t)uc->uc_mcontext.gregs[REG_CSGSFS]); @@ -584,36 +630,44 @@ void os::print_context(outputStream *st, const void *context) { } void os::print_register_info(outputStream *st, const void *context, int& continuation) { - const int register_count = AMD64_ONLY(16) NOT_AMD64(8); + if (context == nullptr) { + return; + } + const ucontext_t *uc = (const ucontext_t*)context; + apx_state* apx = UseAPX ? get_apx_state(uc) : nullptr; + + const int register_count = AMD64_ONLY(16 + (apx != nullptr ? 16 : 0)) NOT_AMD64(8); int n = continuation; assert(n >= 0 && n <= register_count, "Invalid continuation value"); - if (context == nullptr || n == register_count) { + if (n == register_count) { return; } - const ucontext_t *uc = (const ucontext_t*)context; while (n < register_count) { // Update continuation with next index before printing location continuation = n + 1; + + if (n < 16) { + // Standard registers (RAX-R15) # define CASE_PRINT_REG(n, str, id) case n: st->print(str); print_location(st, uc->uc_mcontext.gregs[REG_##id]); - switch (n) { + switch (n) { #ifdef AMD64 - CASE_PRINT_REG( 0, "RAX=", RAX); break; - CASE_PRINT_REG( 1, "RBX=", RBX); break; - CASE_PRINT_REG( 2, "RCX=", RCX); break; - CASE_PRINT_REG( 3, "RDX=", RDX); break; - CASE_PRINT_REG( 4, "RSP=", RSP); break; - CASE_PRINT_REG( 5, "RBP=", RBP); break; - CASE_PRINT_REG( 6, "RSI=", RSI); break; - CASE_PRINT_REG( 7, "RDI=", RDI); break; - CASE_PRINT_REG( 8, "R8 =", R8); break; - CASE_PRINT_REG( 9, "R9 =", R9); break; - CASE_PRINT_REG(10, "R10=", R10); break; - CASE_PRINT_REG(11, "R11=", R11); break; - CASE_PRINT_REG(12, "R12=", R12); break; - CASE_PRINT_REG(13, "R13=", R13); break; - CASE_PRINT_REG(14, "R14=", R14); break; - CASE_PRINT_REG(15, "R15=", R15); break; + CASE_PRINT_REG( 0, "RAX=", RAX); break; + CASE_PRINT_REG( 1, "RBX=", RBX); break; + CASE_PRINT_REG( 2, "RCX=", RCX); break; + CASE_PRINT_REG( 3, "RDX=", RDX); break; + CASE_PRINT_REG( 4, "RSP=", RSP); break; + CASE_PRINT_REG( 5, "RBP=", RBP); break; + CASE_PRINT_REG( 6, "RSI=", RSI); break; + CASE_PRINT_REG( 7, "RDI=", RDI); break; + CASE_PRINT_REG( 8, "R8 =", R8); break; + CASE_PRINT_REG( 9, "R9 =", R9); break; + CASE_PRINT_REG(10, "R10=", R10); break; + CASE_PRINT_REG(11, "R11=", R11); break; + CASE_PRINT_REG(12, "R12=", R12); break; + CASE_PRINT_REG(13, "R13=", R13); break; + CASE_PRINT_REG(14, "R14=", R14); break; + CASE_PRINT_REG(15, "R15=", R15); break; #else CASE_PRINT_REG(0, "EAX=", EAX); break; CASE_PRINT_REG(1, "EBX=", EBX); break; @@ -624,8 +678,13 @@ void os::print_register_info(outputStream *st, const void *context, int& continu CASE_PRINT_REG(6, "ESI=", ESI); break; CASE_PRINT_REG(7, "EDI=", EDI); break; #endif // AMD64 - } + } # undef CASE_PRINT_REG + } else { + // APX extended general purpose registers (R16-R31) + st->print("R%d=", n); + print_location(st, apx->regs[n - 16]); + } ++n; } } From aa6e7e1e6c8ba91398690139cbf099ba91ef7075 Mon Sep 17 00:00:00 2001 From: Goetz Lindenmaier Date: Wed, 18 Mar 2026 13:52:35 +0000 Subject: [PATCH 055/168] 8367485: os::physical_memory is broken in 32-bit JVMs when running on 64-bit OSes Backport-of: 2746c1a555891564963299182b3b0293eaefc901 --- src/hotspot/os/aix/os_aix.cpp | 24 ++++---- src/hotspot/os/aix/os_aix.hpp | 8 +-- src/hotspot/os/bsd/os_bsd.cpp | 34 +++++------ src/hotspot/os/bsd/os_bsd.hpp | 8 +-- src/hotspot/os/linux/os_linux.cpp | 60 +++++++++---------- src/hotspot/os/linux/os_linux.hpp | 8 +-- src/hotspot/os/windows/os_windows.cpp | 42 ++++++------- src/hotspot/os/windows/os_windows.hpp | 30 +++++----- src/hotspot/share/compiler/compileBroker.cpp | 2 +- src/hotspot/share/gc/shared/gcInitLogger.cpp | 5 +- src/hotspot/share/gc/z/zLargePages.cpp | 2 +- src/hotspot/share/jfr/jni/jfrJniMethod.cpp | 2 +- .../share/jfr/periodic/jfrPeriodic.cpp | 10 ++-- .../share/prims/jvmtiRedefineClasses.cpp | 10 ++-- src/hotspot/share/prims/whitebox.cpp | 2 +- src/hotspot/share/runtime/arguments.cpp | 2 +- src/hotspot/share/runtime/os.cpp | 28 ++++----- src/hotspot/share/runtime/os.hpp | 12 ++-- src/hotspot/share/services/heapDumper.cpp | 2 +- .../share/utilities/globalDefinitions.hpp | 6 ++ 20 files changed, 152 insertions(+), 145 deletions(-) diff --git a/src/hotspot/os/aix/os_aix.cpp b/src/hotspot/os/aix/os_aix.cpp index d6df2cf8fbad..e6d8791367ae 100644 --- a/src/hotspot/os/aix/os_aix.cpp +++ b/src/hotspot/os/aix/os_aix.cpp @@ -169,7 +169,7 @@ static void vmembk_print_on(outputStream* os); //////////////////////////////////////////////////////////////////////////////// // global variables (for a description see os_aix.hpp) -size_t os::Aix::_physical_memory = 0; +physical_memory_size_type os::Aix::_physical_memory = 0; pthread_t os::Aix::_main_thread = ((pthread_t)0); @@ -254,43 +254,43 @@ static bool is_close_to_brk(address a) { return false; } -bool os::free_memory(size_t& value) { +bool os::free_memory(physical_memory_size_type& value) { return Aix::available_memory(value); } -bool os::available_memory(size_t& value) { +bool os::available_memory(physical_memory_size_type& value) { return Aix::available_memory(value); } -bool os::Aix::available_memory(size_t& value) { +bool os::Aix::available_memory(physical_memory_size_type& value) { os::Aix::meminfo_t mi; if (os::Aix::get_meminfo(&mi)) { - value = static_cast(mi.real_free); + value = static_cast(mi.real_free); return true; } else { return false; } } -bool os::total_swap_space(size_t& value) { +bool os::total_swap_space(physical_memory_size_type& value) { perfstat_memory_total_t memory_info; if (libperfstat::perfstat_memory_total(nullptr, &memory_info, sizeof(perfstat_memory_total_t), 1) == -1) { return false; } - value = static_cast(memory_info.pgsp_total * 4 * K); + value = static_cast(memory_info.pgsp_total * 4 * K); return true; } -bool os::free_swap_space(size_t& value) { +bool os::free_swap_space(physical_memory_size_type& value) { perfstat_memory_total_t memory_info; if (libperfstat::perfstat_memory_total(nullptr, &memory_info, sizeof(perfstat_memory_total_t), 1) == -1) { return false; } - value = static_cast(memory_info.pgsp_free * 4 * K); + value = static_cast(memory_info.pgsp_free * 4 * K); return true; } -size_t os::physical_memory() { +physical_memory_size_type os::physical_memory() { return Aix::physical_memory(); } @@ -329,7 +329,7 @@ void os::Aix::initialize_system_info() { if (!os::Aix::get_meminfo(&mi)) { assert(false, "os::Aix::get_meminfo failed."); } - _physical_memory = static_cast(mi.real_total); + _physical_memory = static_cast(mi.real_total); } // Helper function for tracing page sizes. @@ -2270,7 +2270,7 @@ jint os::init_2(void) { os::Posix::init_2(); trcVerbose("processor count: %d", os::_processor_count); - trcVerbose("physical memory: %zu", Aix::_physical_memory); + trcVerbose("physical memory: " PHYS_MEM_TYPE_FORMAT, Aix::_physical_memory); // Initially build up the loaded dll map. LoadedLibraries::reload(); diff --git a/src/hotspot/os/aix/os_aix.hpp b/src/hotspot/os/aix/os_aix.hpp index 1530f2adb763..a7bac40e79b9 100644 --- a/src/hotspot/os/aix/os_aix.hpp +++ b/src/hotspot/os/aix/os_aix.hpp @@ -35,7 +35,7 @@ class os::Aix { private: - static size_t _physical_memory; + static physical_memory_size_type _physical_memory; static pthread_t _main_thread; // 0 = uninitialized, otherwise 16 bit number: @@ -54,9 +54,9 @@ class os::Aix { // 1 - EXTSHM=ON static int _extshm; - static bool available_memory(size_t& value); - static bool free_memory(size_t& value); - static size_t physical_memory() { return _physical_memory; } + static bool available_memory(physical_memory_size_type& value); + static bool free_memory(physical_memory_size_type& value); + static physical_memory_size_type physical_memory() { return _physical_memory; } static void initialize_system_info(); // OS recognitions (AIX OS level) call this before calling Aix::os_version(). diff --git a/src/hotspot/os/bsd/os_bsd.cpp b/src/hotspot/os/bsd/os_bsd.cpp index 6de85f54bb94..bfbea8497687 100644 --- a/src/hotspot/os/bsd/os_bsd.cpp +++ b/src/hotspot/os/bsd/os_bsd.cpp @@ -114,7 +114,7 @@ //////////////////////////////////////////////////////////////////////////////// // global variables -size_t os::Bsd::_physical_memory = 0; +physical_memory_size_type os::Bsd::_physical_memory = 0; #ifdef __APPLE__ mach_timebase_info_data_t os::Bsd::_timebase_info = {0, 0}; @@ -133,19 +133,19 @@ static volatile int processor_id_next = 0; //////////////////////////////////////////////////////////////////////////////// // utility functions -bool os::available_memory(size_t& value) { +bool os::available_memory(physical_memory_size_type& value) { return Bsd::available_memory(value); } -bool os::free_memory(size_t& value) { +bool os::free_memory(physical_memory_size_type& value) { return Bsd::available_memory(value); } // Available here means free. Note that this number is of no much use. As an estimate // for future memory pressure it is far too conservative, since MacOS will use a lot // of unused memory for caches, and return it willingly in case of needs. -bool os::Bsd::available_memory(size_t& value) { - uint64_t available = static_cast(physical_memory() >> 2); +bool os::Bsd::available_memory(physical_memory_size_type& value) { + physical_memory_size_type available = physical_memory() >> 2; #ifdef __APPLE__ mach_msg_type_number_t count = HOST_VM_INFO64_COUNT; vm_statistics64_data_t vmstat; @@ -160,7 +160,7 @@ bool os::Bsd::available_memory(size_t& value) { return false; } #endif - value = static_cast(available); + value = available; return true; } @@ -180,35 +180,35 @@ void os::Bsd::print_uptime_info(outputStream* st) { } } -bool os::total_swap_space(size_t& value) { +bool os::total_swap_space(physical_memory_size_type& value) { #if defined(__APPLE__) struct xsw_usage vmusage; size_t size = sizeof(vmusage); if (sysctlbyname("vm.swapusage", &vmusage, &size, nullptr, 0) != 0) { return false; } - value = static_cast(vmusage.xsu_total); + value = static_cast(vmusage.xsu_total); return true; #else return false; #endif } -bool os::free_swap_space(size_t& value) { +bool os::free_swap_space(physical_memory_size_type& value) { #if defined(__APPLE__) struct xsw_usage vmusage; size_t size = sizeof(vmusage); if (sysctlbyname("vm.swapusage", &vmusage, &size, nullptr, 0) != 0) { return false; } - value = static_cast(vmusage.xsu_avail); + value = static_cast(vmusage.xsu_avail); return true; #else return false; #endif } -size_t os::physical_memory() { +physical_memory_size_type os::physical_memory() { return Bsd::physical_memory(); } @@ -286,7 +286,7 @@ void os::Bsd::initialize_system_info() { len = sizeof(mem_val); if (sysctl(mib, 2, &mem_val, &len, nullptr, 0) != -1) { assert(len == sizeof(mem_val), "unexpected data size"); - _physical_memory = static_cast(mem_val); + _physical_memory = static_cast(mem_val); } else { _physical_memory = 256 * 1024 * 1024; // fallback (XXXBSD?) } @@ -297,7 +297,7 @@ void os::Bsd::initialize_system_info() { // datasize rlimit restricts us anyway. struct rlimit limits; getrlimit(RLIMIT_DATA, &limits); - _physical_memory = MIN2(_physical_memory, static_cast(limits.rlim_cur)); + _physical_memory = MIN2(_physical_memory, static_cast(limits.rlim_cur)); } #endif } @@ -1470,12 +1470,12 @@ void os::print_memory_info(outputStream* st) { st->print("Memory:"); st->print(" %zuk page", os::vm_page_size()>>10); - size_t phys_mem = os::physical_memory(); - st->print(", physical %zuk", + physical_memory_size_type phys_mem = os::physical_memory(); + st->print(", physical " PHYS_MEM_TYPE_FORMAT "k", phys_mem >> 10); - size_t avail_mem = 0; + physical_memory_size_type avail_mem = 0; (void)os::available_memory(avail_mem); - st->print("(%zuk free)", + st->print("(" PHYS_MEM_TYPE_FORMAT "k free)", avail_mem >> 10); if((sysctlbyname("vm.swapusage", &swap_usage, &size, nullptr, 0) == 0) || (errno == ENOMEM)) { diff --git a/src/hotspot/os/bsd/os_bsd.hpp b/src/hotspot/os/bsd/os_bsd.hpp index 173cc5a40ad1..82002917f39d 100644 --- a/src/hotspot/os/bsd/os_bsd.hpp +++ b/src/hotspot/os/bsd/os_bsd.hpp @@ -42,12 +42,12 @@ class os::Bsd { protected: - static size_t _physical_memory; + static physical_memory_size_type _physical_memory; static pthread_t _main_thread; - static bool available_memory(size_t& value); - static bool free_memory(size_t& value); - static size_t physical_memory() { return _physical_memory; } + static bool available_memory(physical_memory_size_type& value); + static bool free_memory(physical_memory_size_type& value); + static physical_memory_size_type physical_memory() { return _physical_memory; } static void initialize_system_info(); static void rebuild_cpu_to_node_map(); diff --git a/src/hotspot/os/linux/os_linux.cpp b/src/hotspot/os/linux/os_linux.cpp index a30fc9bdd358..760c44a500b7 100644 --- a/src/hotspot/os/linux/os_linux.cpp +++ b/src/hotspot/os/linux/os_linux.cpp @@ -157,7 +157,7 @@ enum CoredumpFilterBit { //////////////////////////////////////////////////////////////////////////////// // global variables -size_t os::Linux::_physical_memory = 0; +physical_memory_size_type os::Linux::_physical_memory = 0; address os::Linux::_initial_thread_stack_bottom = nullptr; uintptr_t os::Linux::_initial_thread_stack_size = 0; @@ -232,15 +232,15 @@ julong os::Linux::available_memory_in_container() { return avail_mem; } -bool os::available_memory(size_t& value) { +bool os::available_memory(physical_memory_size_type& value) { return Linux::available_memory(value); } -bool os::Linux::available_memory(size_t& value) { +bool os::Linux::available_memory(physical_memory_size_type& value) { julong avail_mem = available_memory_in_container(); if (avail_mem != static_cast(-1L)) { log_trace(os)("available container memory: " JULONG_FORMAT, avail_mem); - value = static_cast(avail_mem); + value = static_cast(avail_mem); return true; } @@ -256,28 +256,28 @@ bool os::Linux::available_memory(size_t& value) { fclose(fp); } if (avail_mem == static_cast(-1L)) { - size_t free_mem = 0; + physical_memory_size_type free_mem = 0; if (!free_memory(free_mem)) { return false; } avail_mem = static_cast(free_mem); } log_trace(os)("available memory: " JULONG_FORMAT, avail_mem); - value = static_cast(avail_mem); + value = static_cast(avail_mem); return true; } -bool os::free_memory(size_t& value) { +bool os::free_memory(physical_memory_size_type& value) { return Linux::free_memory(value); } -bool os::Linux::free_memory(size_t& value) { +bool os::Linux::free_memory(physical_memory_size_type& value) { // values in struct sysinfo are "unsigned long" struct sysinfo si; julong free_mem = available_memory_in_container(); if (free_mem != static_cast(-1L)) { log_trace(os)("free container memory: " JULONG_FORMAT, free_mem); - value = static_cast(free_mem); + value = static_cast(free_mem); return true; } @@ -287,16 +287,16 @@ bool os::Linux::free_memory(size_t& value) { } free_mem = (julong)si.freeram * si.mem_unit; log_trace(os)("free memory: " JULONG_FORMAT, free_mem); - value = static_cast(free_mem); + value = static_cast(free_mem); return true; } -bool os::total_swap_space(size_t& value) { +bool os::total_swap_space(physical_memory_size_type& value) { if (OSContainer::is_containerized()) { jlong memory_and_swap_limit_in_bytes = OSContainer::memory_and_swap_limit_in_bytes(); jlong memory_limit_in_bytes = OSContainer::memory_limit_in_bytes(); if (memory_limit_in_bytes > 0 && memory_and_swap_limit_in_bytes > 0) { - value = static_cast(memory_and_swap_limit_in_bytes - memory_limit_in_bytes); + value = static_cast(memory_and_swap_limit_in_bytes - memory_limit_in_bytes); return true; } } // fallback to the host swap space if the container did return the unbound value of -1 @@ -306,30 +306,30 @@ bool os::total_swap_space(size_t& value) { assert(false, "sysinfo failed in total_swap_space(): %s", os::strerror(errno)); return false; } - value = static_cast(si.totalswap * si.mem_unit); + value = static_cast(si.totalswap) * si.mem_unit; return true; } -static bool host_free_swap_f(size_t& value) { +static bool host_free_swap_f(physical_memory_size_type& value) { struct sysinfo si; int ret = sysinfo(&si); if (ret != 0) { assert(false, "sysinfo failed in host_free_swap_f(): %s", os::strerror(errno)); return false; } - value = static_cast(si.freeswap * si.mem_unit); + value = static_cast(si.freeswap) * si.mem_unit; return true; } -bool os::free_swap_space(size_t& value) { +bool os::free_swap_space(physical_memory_size_type& value) { // os::total_swap_space() might return the containerized limit which might be // less than host_free_swap(). The upper bound of free swap needs to be the lower of the two. - size_t total_swap_space = 0; - size_t host_free_swap = 0; + physical_memory_size_type total_swap_space = 0; + physical_memory_size_type host_free_swap = 0; if (!os::total_swap_space(total_swap_space) || !host_free_swap_f(host_free_swap)) { return false; } - size_t host_free_swap_val = MIN2(total_swap_space, host_free_swap); + physical_memory_size_type host_free_swap_val = MIN2(total_swap_space, host_free_swap); if (OSContainer::is_containerized()) { jlong mem_swap_limit = OSContainer::memory_and_swap_limit_in_bytes(); jlong mem_limit = OSContainer::memory_limit_in_bytes(); @@ -345,31 +345,31 @@ bool os::free_swap_space(size_t& value) { jlong delta_usage = mem_swap_usage - mem_usage; if (delta_usage >= 0) { jlong free_swap = delta_limit - delta_usage; - value = free_swap >= 0 ? static_cast(free_swap) : 0; + value = free_swap >= 0 ? static_cast(free_swap) : 0; return true; } } } // unlimited or not supported. Fall through to return host value log_trace(os,container)("os::free_swap_space: container_swap_limit=" JLONG_FORMAT - " container_mem_limit=" JLONG_FORMAT " returning host value: %zu", + " container_mem_limit=" JLONG_FORMAT " returning host value: " PHYS_MEM_TYPE_FORMAT, mem_swap_limit, mem_limit, host_free_swap_val); } value = host_free_swap_val; return true; } -size_t os::physical_memory() { +physical_memory_size_type os::physical_memory() { if (OSContainer::is_containerized()) { jlong mem_limit; if ((mem_limit = OSContainer::memory_limit_in_bytes()) > 0) { log_trace(os)("total container memory: " JLONG_FORMAT, mem_limit); - return static_cast(mem_limit); + return static_cast(mem_limit); } } - size_t phys_mem = Linux::physical_memory(); - log_trace(os)("total system memory: %zu", phys_mem); + physical_memory_size_type phys_mem = Linux::physical_memory(); + log_trace(os)("total system memory: " PHYS_MEM_TYPE_FORMAT, phys_mem); return phys_mem; } @@ -551,7 +551,7 @@ void os::Linux::initialize_system_info() { fclose(fp); } } - _physical_memory = static_cast(sysconf(_SC_PHYS_PAGES)) * static_cast(sysconf(_SC_PAGESIZE)); + _physical_memory = static_cast(sysconf(_SC_PHYS_PAGES)) * static_cast(sysconf(_SC_PAGESIZE)); assert(processor_count() > 0, "linux error"); } @@ -2617,12 +2617,12 @@ void os::print_memory_info(outputStream* st) { struct sysinfo si; int ret = sysinfo(&si); assert(ret == 0, "sysinfo failed: %s", os::strerror(errno)); - size_t phys_mem = physical_memory(); - st->print(", physical %zuk", + physical_memory_size_type phys_mem = physical_memory(); + st->print(", physical " PHYS_MEM_TYPE_FORMAT "k", phys_mem >> 10); - size_t avail_mem = 0; + physical_memory_size_type avail_mem = 0; (void)os::available_memory(avail_mem); - st->print("(%zuk free)", + st->print("(" PHYS_MEM_TYPE_FORMAT "k free)", avail_mem >> 10); if (ret == 0) { st->print(", swap " UINT64_FORMAT "k", diff --git a/src/hotspot/os/linux/os_linux.hpp b/src/hotspot/os/linux/os_linux.hpp index 497d383200df..e2bd8eb3d31f 100644 --- a/src/hotspot/os/linux/os_linux.hpp +++ b/src/hotspot/os/linux/os_linux.hpp @@ -50,11 +50,11 @@ class os::Linux { protected: - static size_t _physical_memory; + static physical_memory_size_type _physical_memory; static pthread_t _main_thread; - static bool available_memory(size_t& value); - static bool free_memory(size_t& value); + static bool available_memory(physical_memory_size_type& value); + static bool free_memory(physical_memory_size_type& value); static void initialize_system_info(); @@ -117,7 +117,7 @@ class os::Linux { static address initial_thread_stack_bottom(void) { return _initial_thread_stack_bottom; } static uintptr_t initial_thread_stack_size(void) { return _initial_thread_stack_size; } - static size_t physical_memory() { return _physical_memory; } + static physical_memory_size_type physical_memory() { return _physical_memory; } static julong host_swap(); static intptr_t* ucontext_get_sp(const ucontext_t* uc); diff --git a/src/hotspot/os/windows/os_windows.cpp b/src/hotspot/os/windows/os_windows.cpp index 8e0ce4871b69..f09b1f5eb7b0 100644 --- a/src/hotspot/os/windows/os_windows.cpp +++ b/src/hotspot/os/windows/os_windows.cpp @@ -848,22 +848,22 @@ jlong os::elapsed_frequency() { } -bool os::available_memory(size_t& value) { +bool os::available_memory(physical_memory_size_type& value) { return win32::available_memory(value); } -bool os::free_memory(size_t& value) { +bool os::free_memory(physical_memory_size_type& value) { return win32::available_memory(value); } -bool os::win32::available_memory(size_t& value) { +bool os::win32::available_memory(physical_memory_size_type& value) { // Use GlobalMemoryStatusEx() because GlobalMemoryStatus() may return incorrect // value if total memory is larger than 4GB MEMORYSTATUSEX ms; ms.dwLength = sizeof(ms); BOOL res = GlobalMemoryStatusEx(&ms); if (res == TRUE) { - value = static_cast(ms.ullAvailPhys); + value = static_cast(ms.ullAvailPhys); return true; } else { assert(false, "GlobalMemoryStatusEx failed in os::win32::available_memory(): %lu", ::GetLastError()); @@ -871,12 +871,12 @@ bool os::win32::available_memory(size_t& value) { } } -bool os::total_swap_space(size_t& value) { +bool os::total_swap_space(physical_memory_size_type& value) { MEMORYSTATUSEX ms; ms.dwLength = sizeof(ms); BOOL res = GlobalMemoryStatusEx(&ms); if (res == TRUE) { - value = static_cast(ms.ullTotalPageFile); + value = static_cast(ms.ullTotalPageFile); return true; } else { assert(false, "GlobalMemoryStatusEx failed in os::total_swap_space(): %lu", ::GetLastError()); @@ -884,12 +884,12 @@ bool os::total_swap_space(size_t& value) { } } -bool os::free_swap_space(size_t& value) { +bool os::free_swap_space(physical_memory_size_type& value) { MEMORYSTATUSEX ms; ms.dwLength = sizeof(ms); BOOL res = GlobalMemoryStatusEx(&ms); if (res == TRUE) { - value = static_cast(ms.ullAvailPageFile); + value = static_cast(ms.ullAvailPageFile); return true; } else { assert(false, "GlobalMemoryStatusEx failed in os::free_swap_space(): %lu", ::GetLastError()); @@ -897,7 +897,7 @@ bool os::free_swap_space(size_t& value) { } } -size_t os::physical_memory() { +physical_memory_size_type os::physical_memory() { return win32::physical_memory(); } @@ -3912,25 +3912,25 @@ int os::current_process_id() { return (_initial_pid ? _initial_pid : _getpid()); } -int os::win32::_processor_type = 0; +int os::win32::_processor_type = 0; // Processor level is not available on non-NT systems, use vm_version instead -int os::win32::_processor_level = 0; -size_t os::win32::_physical_memory = 0; +int os::win32::_processor_level = 0; +physical_memory_size_type os::win32::_physical_memory = 0; -bool os::win32::_is_windows_server = false; +bool os::win32::_is_windows_server = false; // 6573254 // Currently, the bug is observed across all the supported Windows releases, // including the latest one (as of this writing - Windows Server 2012 R2) -bool os::win32::_has_exit_bug = true; +bool os::win32::_has_exit_bug = true; -int os::win32::_major_version = 0; -int os::win32::_minor_version = 0; -int os::win32::_build_number = 0; -int os::win32::_build_minor = 0; +int os::win32::_major_version = 0; +int os::win32::_minor_version = 0; +int os::win32::_build_number = 0; +int os::win32::_build_minor = 0; -bool os::win32::_processor_group_warning_displayed = false; -bool os::win32::_job_object_processor_group_warning_displayed = false; +bool os::win32::_processor_group_warning_displayed = false; +bool os::win32::_job_object_processor_group_warning_displayed = false; void getWindowsInstallationType(char* buffer, int bufferSize) { HKEY hKey; @@ -4149,7 +4149,7 @@ void os::win32::initialize_system_info() { if (res != TRUE) { assert(false, "GlobalMemoryStatusEx failed in os::win32::initialize_system_info(): %lu", ::GetLastError()); } - _physical_memory = static_cast(ms.ullTotalPhys); + _physical_memory = static_cast(ms.ullTotalPhys); if (FLAG_IS_DEFAULT(MaxRAM)) { // Adjust MaxRAM according to the maximum virtual address space available. diff --git a/src/hotspot/os/windows/os_windows.hpp b/src/hotspot/os/windows/os_windows.hpp index 1426dc8be930..efb7b4149897 100644 --- a/src/hotspot/os/windows/os_windows.hpp +++ b/src/hotspot/os/windows/os_windows.hpp @@ -38,18 +38,18 @@ class os::win32 { friend class os; protected: - static int _processor_type; - static int _processor_level; - static size_t _physical_memory; - static bool _is_windows_server; - static bool _has_exit_bug; - static bool _processor_group_warning_displayed; - static bool _job_object_processor_group_warning_displayed; - - static int _major_version; - static int _minor_version; - static int _build_number; - static int _build_minor; + static int _processor_type; + static int _processor_level; + static physical_memory_size_type _physical_memory; + static bool _is_windows_server; + static bool _has_exit_bug; + static bool _processor_group_warning_displayed; + static bool _job_object_processor_group_warning_displayed; + + static int _major_version; + static int _minor_version; + static int _build_number; + static int _build_minor; static void print_windows_version(outputStream* st); static void print_uptime_info(outputStream* st); @@ -102,9 +102,9 @@ class os::win32 { static int processor_level() { return _processor_level; } - static bool available_memory(size_t& value); - static bool free_memory(size_t& value); - static size_t physical_memory() { return _physical_memory; } + static bool available_memory(physical_memory_size_type& value); + static bool free_memory(physical_memory_size_type& value); + static physical_memory_size_type physical_memory() { return _physical_memory; } // load dll from Windows system directory or Windows directory static HINSTANCE load_Windows_dll(const char* name, char *ebuf, int ebuflen); diff --git a/src/hotspot/share/compiler/compileBroker.cpp b/src/hotspot/share/compiler/compileBroker.cpp index 75b5d11e1c07..901c5ba9b555 100644 --- a/src/hotspot/share/compiler/compileBroker.cpp +++ b/src/hotspot/share/compiler/compileBroker.cpp @@ -1058,7 +1058,7 @@ void CompileBroker::possibly_add_compiler_threads(JavaThread* THREAD) { if (new_c2_count <= old_c2_count && new_c1_count <= old_c1_count) return; // Now, we do the more expensive operations. - size_t free_memory = 0; + physical_memory_size_type free_memory = 0; // Return value ignored - defaulting to 0 on failure. (void)os::free_memory(free_memory); // If SegmentedCodeCache is off, both values refer to the single heap (with type CodeBlobType::All). diff --git a/src/hotspot/share/gc/shared/gcInitLogger.cpp b/src/hotspot/share/gc/shared/gcInitLogger.cpp index 763c265b65ea..ba6399458603 100644 --- a/src/hotspot/share/gc/shared/gcInitLogger.cpp +++ b/src/hotspot/share/gc/shared/gcInitLogger.cpp @@ -62,8 +62,9 @@ void GCInitLogger::print_cpu() { } void GCInitLogger::print_memory() { - size_t memory = os::physical_memory(); - log_info_p(gc, init)("Memory: " PROPERFMT, PROPERFMTARGS(memory)); + physical_memory_size_type memory = os::physical_memory(); + log_info_p(gc, init)("Memory: " PHYS_MEM_TYPE_FORMAT "%s", + byte_size_in_proper_unit(memory), proper_unit_for_byte_size(memory)); } void GCInitLogger::print_large_pages() { diff --git a/src/hotspot/share/gc/z/zLargePages.cpp b/src/hotspot/share/gc/z/zLargePages.cpp index 639c9b0a04fb..c259448563bd 100644 --- a/src/hotspot/share/gc/z/zLargePages.cpp +++ b/src/hotspot/share/gc/z/zLargePages.cpp @@ -31,7 +31,7 @@ bool ZLargePages::_os_enforced_transparent_mode; void ZLargePages::initialize() { pd_initialize(); - const size_t memory = os::physical_memory(); + const size_t memory = static_cast(os::physical_memory()); log_info_p(gc, init)("Memory: " PROPERFMT, PROPERFMTARGS(memory)); log_info_p(gc, init)("Large Page Support: %s", to_string()); } diff --git a/src/hotspot/share/jfr/jni/jfrJniMethod.cpp b/src/hotspot/share/jfr/jni/jfrJniMethod.cpp index 6a2ba7bc2456..9b4ead7b17b9 100644 --- a/src/hotspot/share/jfr/jni/jfrJniMethod.cpp +++ b/src/hotspot/share/jfr/jni/jfrJniMethod.cpp @@ -422,7 +422,7 @@ JVM_ENTRY_NO_ENV(jlong, jfr_host_total_swap_memory(JNIEnv* env, jclass jvm)) // We want the host swap memory, not the container value. return os::Linux::host_swap(); #else - size_t total_swap_space = 0; + physical_memory_size_type total_swap_space = 0; // Return value ignored - defaulting to 0 on failure. (void)os::total_swap_space(total_swap_space); return static_cast(total_swap_space); diff --git a/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp b/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp index 56b0a2f82d3b..e7f12f7428f0 100644 --- a/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp +++ b/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp @@ -528,23 +528,23 @@ TRACE_REQUEST_FUNC(ThreadAllocationStatistics) { * the total memory reported is the amount of memory configured for the guest OS by the hypervisor. */ TRACE_REQUEST_FUNC(PhysicalMemory) { - u8 totalPhysicalMemory = static_cast(os::physical_memory()); + physical_memory_size_type totalPhysicalMemory = os::physical_memory(); EventPhysicalMemory event; event.set_totalSize(totalPhysicalMemory); - size_t avail_mem = 0; + physical_memory_size_type avail_mem = 0; // Return value ignored - defaulting to 0 on failure. (void)os::available_memory(avail_mem); - event.set_usedSize(totalPhysicalMemory - static_cast(avail_mem)); + event.set_usedSize(totalPhysicalMemory - avail_mem); event.commit(); } TRACE_REQUEST_FUNC(SwapSpace) { EventSwapSpace event; - size_t total_swap_space = 0; + physical_memory_size_type total_swap_space = 0; // Return value ignored - defaulting to 0 on failure. (void)os::total_swap_space(total_swap_space); event.set_totalSize(static_cast(total_swap_space)); - size_t free_swap_space = 0; + physical_memory_size_type free_swap_space = 0; // Return value ignored - defaulting to 0 on failure. (void)os::free_swap_space(free_swap_space); event.set_freeSize(static_cast(free_swap_space)); diff --git a/src/hotspot/share/prims/jvmtiRedefineClasses.cpp b/src/hotspot/share/prims/jvmtiRedefineClasses.cpp index 12009e3e8e4d..396c6db150d8 100644 --- a/src/hotspot/share/prims/jvmtiRedefineClasses.cpp +++ b/src/hotspot/share/prims/jvmtiRedefineClasses.cpp @@ -1356,11 +1356,11 @@ jvmtiError VM_RedefineClasses::load_new_class_versions() { // constant pools HandleMark hm(current); InstanceKlass* the_class = get_ik(_class_defs[i].klass); - size_t avail_mem = 0; + physical_memory_size_type avail_mem = 0; // Return value ignored - defaulting to 0 on failure. (void)os::available_memory(avail_mem); log_debug(redefine, class, load) - ("loading name=%s kind=%d (avail_mem=%zuK)", + ("loading name=%s kind=%d (avail_mem=" PHYS_MEM_TYPE_FORMAT "K)", the_class->external_name(), _class_load_kind, avail_mem >> 10); ClassFileStream st((u1*)_class_defs[i].class_bytes, @@ -1528,7 +1528,7 @@ jvmtiError VM_RedefineClasses::load_new_class_versions() { // Return value ignored - defaulting to 0 on failure. (void)os::available_memory(avail_mem); log_debug(redefine, class, load) - ("loaded name=%s (avail_mem=%zuK)", the_class->external_name(), avail_mem >> 10); + ("loaded name=%s (avail_mem=" PHYS_MEM_TYPE_FORMAT "K)", the_class->external_name(), avail_mem >> 10); } return JVMTI_ERROR_NONE; @@ -4425,11 +4425,11 @@ void VM_RedefineClasses::redefine_single_class(Thread* current, jclass the_jclas ResourceMark rm(current); // increment the classRedefinedCount field in the_class and in any // direct and indirect subclasses of the_class - size_t avail_mem = 0; + physical_memory_size_type avail_mem = 0; // Return value ignored - defaulting to 0 on failure. (void)os::available_memory(avail_mem); log_info(redefine, class, load) - ("redefined name=%s, count=%d (avail_mem=%zuK)", + ("redefined name=%s, count=%d (avail_mem=" PHYS_MEM_TYPE_FORMAT "K)", the_class->external_name(), java_lang_Class::classRedefinedCount(the_class->java_mirror()), avail_mem >> 10); Events::log_redefinition(current, "redefined class name=%s, count=%d", the_class->external_name(), diff --git a/src/hotspot/share/prims/whitebox.cpp b/src/hotspot/share/prims/whitebox.cpp index 2013e33ed188..9999b59decee 100644 --- a/src/hotspot/share/prims/whitebox.cpp +++ b/src/hotspot/share/prims/whitebox.cpp @@ -2550,7 +2550,7 @@ WB_END // Available memory of the host machine (container-aware) WB_ENTRY(jlong, WB_HostAvailableMemory(JNIEnv* env, jobject o)) - size_t avail_mem = 0; + physical_memory_size_type avail_mem = 0; // Return value ignored - defaulting to 0 on failure. (void)os::available_memory(avail_mem); return static_cast(avail_mem); diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp index 196e4f215c20..43b779c5d9a3 100644 --- a/src/hotspot/share/runtime/arguments.cpp +++ b/src/hotspot/share/runtime/arguments.cpp @@ -1631,7 +1631,7 @@ jint Arguments::set_aggressive_heap_flags() { // Thus, we need to make sure we're using a julong for intermediate // calculations. julong initHeapSize; - size_t phys_mem = os::physical_memory(); + physical_memory_size_type phys_mem = os::physical_memory(); julong total_memory = static_cast(phys_mem); if (total_memory < (julong) 256 * M) { diff --git a/src/hotspot/share/runtime/os.cpp b/src/hotspot/share/runtime/os.cpp index 0ed754e027c4..390ca92984d5 100644 --- a/src/hotspot/share/runtime/os.cpp +++ b/src/hotspot/share/runtime/os.cpp @@ -1183,13 +1183,13 @@ void os::print_summary_info(outputStream* st, char* buf, size_t buflen) { #endif // PRODUCT get_summary_cpu_info(buf, buflen); st->print("%s, ", buf); - size_t phys_mem = physical_memory(); - size_t mem = phys_mem/G; + physical_memory_size_type phys_mem = physical_memory(); + physical_memory_size_type mem = phys_mem/G; if (mem == 0) { // for low memory systems mem = phys_mem/M; - st->print("%d cores, %zuM, ", processor_count(), mem); + st->print("%d cores, " PHYS_MEM_TYPE_FORMAT "M, ", processor_count(), mem); } else { - st->print("%d cores, %zuG, ", processor_count(), mem); + st->print("%d cores, " PHYS_MEM_TYPE_FORMAT "G, ", processor_count(), mem); } get_summary_os_info(buf, buflen); st->print_raw(buf); @@ -1934,17 +1934,17 @@ bool os::is_server_class_machine() { return true; } // Then actually look at the machine - bool result = false; - const unsigned int server_processors = 2; - const julong server_memory = 2UL * G; + bool result = false; + const unsigned int server_processors = 2; + const physical_memory_size_type server_memory = 2UL * G; // We seem not to get our full complement of memory. // We allow some part (1/8?) of the memory to be "missing", // based on the sizes of DIMMs, and maybe graphics cards. - const julong missing_memory = 256UL * M; - size_t phys_mem = os::physical_memory(); + const physical_memory_size_type missing_memory = 256UL * M; + physical_memory_size_type phys_mem = os::physical_memory(); /* Is this a server class machine? */ if ((os::active_processor_count() >= (int)server_processors) && - (phys_mem >= (server_memory - missing_memory))) { + (phys_mem >= server_memory - missing_memory)) { const unsigned int logical_processors = VM_Version::logical_processors_per_package(); if (logical_processors > 1) { @@ -2203,22 +2203,22 @@ static void assert_nonempty_range(const char* addr, size_t bytes) { p2i(addr), p2i(addr) + bytes); } -bool os::used_memory(size_t& value) { +bool os::used_memory(physical_memory_size_type& value) { #ifdef LINUX if (OSContainer::is_containerized()) { jlong mem_usage = OSContainer::memory_usage_in_bytes(); if (mem_usage > 0) { - value = static_cast(mem_usage); + value = static_cast(mem_usage); return true; } else { return false; } } #endif - size_t avail_mem = 0; + physical_memory_size_type avail_mem = 0; // Return value ignored - defaulting to 0 on failure. (void)os::available_memory(avail_mem); - size_t phys_mem = os::physical_memory(); + physical_memory_size_type phys_mem = os::physical_memory(); value = phys_mem - avail_mem; return true; } diff --git a/src/hotspot/share/runtime/os.hpp b/src/hotspot/share/runtime/os.hpp index 6739e97efc50..42551edd0ba0 100644 --- a/src/hotspot/share/runtime/os.hpp +++ b/src/hotspot/share/runtime/os.hpp @@ -337,14 +337,14 @@ class os: AllStatic { // For example, on Linux, "available" memory (`MemAvailable` in `/proc/meminfo`) is greater // than "free" memory (`MemFree` in `/proc/meminfo`) because Linux can free memory // aggressively (e.g. clear caches) so that it becomes available. - [[nodiscard]] static bool available_memory(size_t& value); - [[nodiscard]] static bool used_memory(size_t& value); - [[nodiscard]] static bool free_memory(size_t& value); + [[nodiscard]] static bool available_memory(physical_memory_size_type& value); + [[nodiscard]] static bool used_memory(physical_memory_size_type& value); + [[nodiscard]] static bool free_memory(physical_memory_size_type& value); - [[nodiscard]] static bool total_swap_space(size_t& value); - [[nodiscard]] static bool free_swap_space(size_t& value); + [[nodiscard]] static bool total_swap_space(physical_memory_size_type& value); + [[nodiscard]] static bool free_swap_space(physical_memory_size_type& value); - static size_t physical_memory(); + static physical_memory_size_type physical_memory(); static bool has_allocatable_memory_limit(size_t* limit); static bool is_server_class_machine(); static size_t rss(); diff --git a/src/hotspot/share/services/heapDumper.cpp b/src/hotspot/share/services/heapDumper.cpp index 1caa3ce354c6..c9ac86063d0d 100644 --- a/src/hotspot/share/services/heapDumper.cpp +++ b/src/hotspot/share/services/heapDumper.cpp @@ -2612,7 +2612,7 @@ int HeapDumper::dump(const char* path, outputStream* out, int compression, bool // (DumpWriter buffer, DumperClassCacheTable, GZipCompressor buffers). // For the OOM handling we may already be limited in memory. // Lets ensure we have at least 20MB per thread. - size_t free_memory = 0; + physical_memory_size_type free_memory = 0; // Return value ignored - defaulting to 0 on failure. (void)os::free_memory(free_memory); julong max_threads = free_memory / (20 * M); diff --git a/src/hotspot/share/utilities/globalDefinitions.hpp b/src/hotspot/share/utilities/globalDefinitions.hpp index 4ec61babec3d..bc6944175879 100644 --- a/src/hotspot/share/utilities/globalDefinitions.hpp +++ b/src/hotspot/share/utilities/globalDefinitions.hpp @@ -134,6 +134,7 @@ class oopDesc; #define UINT64_FORMAT_X_0 "0x%016" PRIx64 #define UINT64_FORMAT_W(width) "%" #width PRIu64 #define UINT64_FORMAT_0 "%016" PRIx64 +#define PHYS_MEM_TYPE_FORMAT "%" PRIu64 // Format jlong, if necessary #ifndef JLONG_FORMAT @@ -413,6 +414,11 @@ const uintx max_uintx = (uintx)-1; typedef unsigned int uint; NEEDS_CLEANUP +// This typedef is to address the issue of running a 32-bit VM. In this case the amount +// of physical memory may not fit in size_t, so we have to have a larger type. Once 32-bit +// is deprecated, one can use size_t. +typedef uint64_t physical_memory_size_type; + //---------------------------------------------------------------------------------------------------- // Java type definitions From 866a70b4f3a588d446ff0cb6ab419d10c1bab3cb Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Wed, 18 Mar 2026 15:26:45 +0000 Subject: [PATCH 056/168] 8358600: Template-Framework Library: Template for TestFramework test class Backport-of: b85fe02be5966b72ea1a92bfb3faf088d310219a --- .../library/TestFrameworkClass.java | 119 +++++++++++++ .../examples/TestWithTestFrameworkClass.java | 163 ++++++++++++++++++ 2 files changed, 282 insertions(+) create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/library/TestFrameworkClass.java create mode 100644 test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestWithTestFrameworkClass.java diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/library/TestFrameworkClass.java b/test/hotspot/jtreg/compiler/lib/template_framework/library/TestFrameworkClass.java new file mode 100644 index 000000000000..5194b75af43d --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/library/TestFrameworkClass.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.lib.template_framework.library; + +import java.util.List; +import java.util.Set; + +import compiler.lib.ir_framework.TestFramework; +import compiler.lib.compile_framework.CompileFramework; +import compiler.lib.template_framework.Template; +import compiler.lib.template_framework.TemplateToken; +import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.let; + +/** + * This class provides a {@link #render} method that can be used to simplify generating + * source code when using the {@link TestFramework} (also known as IR Framework) to run + * a list of tests. + * + *

+ * The idea is that the user only has to generate the code for the individual tests, + * and can then pass the corresponding list of {@link TemplateToken}s to this + * provided {@link #render} method which generates the surrounding class and the main + * method that invokes the {@link TestFramework}, so that all the generated tests + * are run. + */ +public final class TestFrameworkClass { + + // Ensure there can be no instance, and we do not have to document the constructor. + private TestFrameworkClass() {} + + /** + * This method renders a list of {@code testTemplateTokens} into the body of a class + * and generates a {@code main} method which launches the {@link TestFramework} + * to run the generated tests. + * + *

+ * The generated {@code main} method is to be invoked with a {@code vmFlags} argument, + * which must be a {@link String[]}, specifying the VM flags for the Test VM, in which + * the tests will be run. Thus, one can generate the test class once, and invoke its + * {@code main} method multiple times, each time with a different set of VM flags. + * + *

+ * The internal {@link Template} sets the {@link Hooks#CLASS_HOOK} for the scope of + * all test methods. + * + * @param packageName The package name of the test class. + * @param className The name of the test class. + * @param imports A set of imports. + * @param classpath The classpath from {@link CompileFramework#getEscapedClassPathOfCompiledClasses}, + * so that the Test VM has access to the class files that are compiled from the + * generated source code. + * @param testTemplateTokens The list of tests to be generated into the test class. + * Every test must be annotated with {@code @Test}, so that + * the {@link TestFramework} can later find and run them. + * @return The generated source code of the test class as a {@link String}. + */ + public static String render(final String packageName, + final String className, + final Set imports, + final String classpath, + final List testTemplateTokens) { + var template = Template.make(() -> body( + let("packageName", packageName), + let("className", className), + let("classpath", classpath), + """ + package #packageName; + // --- IMPORTS start --- + import compiler.lib.ir_framework.*; + """, + imports.stream().map(i -> "import " + i + ";\n").toList(), + """ + // --- IMPORTS end --- + public class #className { + // --- CLASS_HOOK insertions start --- + """, + Hooks.CLASS_HOOK.anchor( + """ + // --- CLASS_HOOK insertions end --- + public static void main(String[] vmFlags) { + TestFramework framework = new TestFramework(#className.class); + framework.addFlags("-classpath", "#classpath"); + framework.addFlags(vmFlags); + framework.start(); + } + // --- LIST OF TESTS start --- + """, + testTemplateTokens + ), + """ + // --- LIST OF TESTS end --- + } + """ + )); + return template.render(); + } +} diff --git a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestWithTestFrameworkClass.java b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestWithTestFrameworkClass.java new file mode 100644 index 000000000000..813f2976ef25 --- /dev/null +++ b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestWithTestFrameworkClass.java @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary Test TestFrameworkClass.TEMPLATE which allows generating many tests and running them with the IR TestFramework. + * @modules java.base/jdk.internal.misc + * @library /test/lib / + * @compile ../../../compiler/lib/ir_framework/TestFramework.java + * @compile ../../../compiler/lib/generators/Generators.java + * @compile ../../../compiler/lib/verify/Verify.java + * @run driver template_framework.examples.TestWithTestFrameworkClass + */ + +package template_framework.examples; + +import java.util.List; +import java.util.Set; + +import compiler.lib.compile_framework.CompileFramework; + +import compiler.lib.generators.Generators; + +import compiler.lib.template_framework.Template; +import compiler.lib.template_framework.TemplateToken; +import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.let; + +import compiler.lib.template_framework.library.Hooks; +import compiler.lib.template_framework.library.TestFrameworkClass; + +/** + * This is a basic IR verification test, in combination with Generators for random input generation + * and Verify for output verification. + *

+ * The "@compile" command for JTREG is required so that the frameworks used in the Template code + * are compiled and available for the Test-VM. + *

+ * Additionally, we must set the classpath for the Test VM, so that it has access to all compiled + * classes (see {@link CompileFramework#getEscapedClassPathOfCompiledClasses}). + */ +public class TestWithTestFrameworkClass { + + public static void main(String[] args) { + // Create a new CompileFramework instance. + CompileFramework comp = new CompileFramework(); + + // Add a java source file. + comp.addJavaSourceCode("p.xyz.InnerTest", generate(comp)); + + // Compile the source file. + comp.compile(); + + // p.xyz.InnterTest.main(new String[] {}); + comp.invoke("p.xyz.InnerTest", "main", new Object[] {new String[] {}}); + + // We can also pass VM flags for the Test VM. + // p.xyz.InnterTest.main(new String[] {"-Xbatch"}); + comp.invoke("p.xyz.InnerTest", "main", new Object[] {new String[] {"-Xbatch"}}); + } + + // Generate a source Java file as String + public static String generate(CompileFramework comp) { + // A simple template that adds a comment. + var commentTemplate = Template.make(() -> body( + """ + // Comment inserted from test method to class hook. + """ + )); + + // We define a Test-Template: + // - static fields for inputs: INPUT_A and INPUT_B + // - Data generated with Generators and hashtag replacement #con1. + // - GOLD value precomputed with dedicated call to test. + // - This ensures that the GOLD value is computed in the interpreter + // most likely, since the test method is not yet compiled. + // This allows us later to compare to the results of the compiled + // code. + // The input data is cloned, so that the original INPUT_A is never + // modified and can serve as identical input in later calls to test. + // - In the Setup method, we clone the input data, since the input data + // could be modified inside the test method. + // - The test method makes use of hashtag replacements (#con2 and #op). + // - The Check method verifies the results of the test method with the + // GOLD value. + var testTemplate = Template.make("op", (String op) -> body( + let("size", Generators.G.safeRestrict(Generators.G.ints(), 10_000, 20_000).next()), + let("con1", Generators.G.ints().next()), + let("con2", Generators.G.safeRestrict(Generators.G.ints(), 1, Integer.MAX_VALUE).next()), + """ + // --- $test start --- + // $test with size=#size and op=#op + private static int[] $INPUT_A = new int[#size]; + static { + Generators.G.fill(Generators.G.ints(), $INPUT_A); + } + private static int $INPUT_B = #con1; + private static Object $GOLD = $test($INPUT_A.clone(), $INPUT_B); + + @Setup + public static Object[] $setup() { + // Must make sure to clone input arrays, if it is mutated in the test. + return new Object[] {$INPUT_A.clone(), $INPUT_B}; + } + + @Test + @Arguments(setup = "$setup") + public static Object $test(int[] a, int b) { + for (int i = 0; i < a.length; i++) { + int con = #con2; + a[i] = (a[i] * con) #op b; + } + return a; + } + + @Check(test = "$test") + public static void $check(Object result) { + Verify.checkEQ(result, $GOLD); + } + // --- $test end --- + """, + // Good to know: we can insert to the class hook, which is set for the + // TestFrameworkClass scope: + Hooks.CLASS_HOOK.insert(commentTemplate.asToken()) + )); + + // Create a test for each operator. + List ops = List.of("+", "-", "*", "&", "|"); + List testTemplateTokens = ops.stream().map(testTemplate::asToken).toList(); + + // Create the test class, which runs all testTemplateTokens. + return TestFrameworkClass.render( + // package and class name. + "p.xyz", "InnerTest", + // Set of imports. + Set.of("compiler.lib.generators.*", + "compiler.lib.verify.*"), + // classpath, so the Test VM has access to the compiled class files. + comp.getEscapedClassPathOfCompiledClasses(), + // The list of tests. + testTemplateTokens); + } +} From 9f50cd8eab71c3c0eba0971b3674360a991eeacd Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Wed, 18 Mar 2026 15:27:22 +0000 Subject: [PATCH 057/168] 8369950: TLS connection to IPv6 address fails with BCJSSE due to IllegalArgumentException Backport-of: 7da91533aaf2033cedee6e2a56fb693f26909df5 --- .../net/www/protocol/https/HttpsClient.java | 11 +- .../HttpsURLConnection/SubjectAltNameIP.java | 379 ++++++++++++++++++ 2 files changed, 388 insertions(+), 2 deletions(-) create mode 100644 test/jdk/javax/net/ssl/HttpsURLConnection/SubjectAltNameIP.java diff --git a/src/java.base/share/classes/sun/net/www/protocol/https/HttpsClient.java b/src/java.base/share/classes/sun/net/www/protocol/https/HttpsClient.java index 2f011f5805b4..9f1d7b07021e 100644 --- a/src/java.base/share/classes/sun/net/www/protocol/https/HttpsClient.java +++ b/src/java.base/share/classes/sun/net/www/protocol/https/HttpsClient.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -44,6 +44,7 @@ import java.util.StringTokenizer; import javax.net.ssl.*; +import sun.net.util.IPAddressUtil; import sun.net.www.http.HttpClient; import sun.net.www.protocol.http.AuthCacheImpl; import sun.net.www.protocol.http.HttpURLConnection; @@ -471,7 +472,13 @@ public void afterConnect() throws IOException, UnknownHostException { SSLParameters parameters = s.getSSLParameters(); parameters.setEndpointIdentificationAlgorithm("HTTPS"); // host has been set previously for SSLSocketImpl - if (!(s instanceof SSLSocketImpl)) { + if (!(s instanceof SSLSocketImpl) && + !IPAddressUtil.isIPv4LiteralAddress(host) && + !(host.charAt(0) == '[' && host.charAt(host.length() - 1) == ']' && + IPAddressUtil.isIPv6LiteralAddress(host.substring(1, host.length() - 1)) + )) { + // Fully qualified DNS hostname of the server, as per section 3, RFC 6066 + // Literal IPv4 and IPv6 addresses are not permitted in "HostName". parameters.setServerNames(List.of(new SNIHostName(host))); } s.setSSLParameters(parameters); diff --git a/test/jdk/javax/net/ssl/HttpsURLConnection/SubjectAltNameIP.java b/test/jdk/javax/net/ssl/HttpsURLConnection/SubjectAltNameIP.java new file mode 100644 index 000000000000..2def2f69d6e9 --- /dev/null +++ b/test/jdk/javax/net/ssl/HttpsURLConnection/SubjectAltNameIP.java @@ -0,0 +1,379 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8369950 + * @summary Test that the HttpsURLConnection does not set IP address literals for + * SNI hostname during TLS handshake + * @library /test/lib + * @modules java.base/sun.net.util + * @comment Insert -Djavax.net.debug=all into the following lines to enable SSL debugging + * @run main/othervm SubjectAltNameIP 127.0.0.1 + * @run main/othervm SubjectAltNameIP [::1] + */ + +import javax.net.ssl.HandshakeCompletedListener; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLServerSocket; +import javax.net.ssl.SSLServerSocketFactory; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.net.InetAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.URI; +import java.util.concurrent.CountDownLatch; +import java.util.function.Consumer; + +import jdk.test.lib.Asserts; +import jdk.test.lib.net.IPSupport; +import jdk.test.lib.net.SimpleSSLContext; +import jtreg.SkippedException; +import sun.net.util.IPAddressUtil; + +public class SubjectAltNameIP { + + // Is the server ready to serve? + private final CountDownLatch serverReady = new CountDownLatch(1); + + // Use any free port by default. + volatile int serverPort = 0; + + // Stores an exception thrown by server in a separate thread. + volatile Exception serverException = null; + + // SSLSocket object created by HttpsClient internally. + SSLSocket clientSSLSocket = null; + + // The hostname the server socket is bound to. + String hostName; + + static final byte[] requestEnd = new byte[] {'\r', '\n', '\r', '\n' }; + + // Read until the end of the request. + void readOneRequest(InputStream is) throws IOException { + int requestEndCount = 0, r; + while ((r = is.read()) != -1) { + if (r == requestEnd[requestEndCount]) { + requestEndCount++; + if (requestEndCount == 4) { + break; + } + } else { + requestEndCount = 0; + } + } + } + + /* + * Define the server side of the test. + * + * If the server prematurely exits, serverReady will be set to true + * to avoid infinite hangs. + */ + void doServerSide() throws Exception { + SSLServerSocketFactory sslssf = + new SimpleSSLContext().get().getServerSocketFactory(); + SSLServerSocket sslServerSocket = + (SSLServerSocket) sslssf.createServerSocket( + serverPort, 0, + InetAddress.getByName(hostName)); + sslServerSocket.setEnabledProtocols(new String[]{"TLSv1.3"}); + serverPort = sslServerSocket.getLocalPort(); + + /* + * Signal the client, the server is ready to accept connection. + */ + serverReady.countDown(); + + SSLSocket sslSocket = (SSLSocket) sslServerSocket.accept(); + OutputStream sslOS = sslSocket.getOutputStream(); + BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(sslOS)); + bw.write("HTTP/1.1 200 OK\r\n\r\n"); + bw.flush(); + readOneRequest(sslSocket.getInputStream()); + sslSocket.close(); + } + + /* + * Define the client side of the test. + * + * If the server prematurely exits, serverReady will be set to true + * to avoid infinite hangs. + */ + void doClientSide() throws Exception { + + /* + * Wait for server to get started. + */ + serverReady.await(); + if (serverException != null) { + throw new RuntimeException("Server failed to start.", serverException); + } + + SSLSocketFactory sf = new SimpleSSLContext().get().getSocketFactory(); + URI uri = new URI("https://" + hostName + ":" + serverPort + "/index.html"); + HttpsURLConnection conn = (HttpsURLConnection)uri.toURL().openConnection(); + + /* + * Simulate an external JSSE implementation and store the client SSLSocket + * used internally. + */ + conn.setSSLSocketFactory(wrapSocketFactory(sf, + sslSocket -> { + Asserts.assertEquals(null, clientSSLSocket, "clientSSLSocket is"); + clientSSLSocket = sslSocket; + })); + conn.getInputStream(); + + var sniSN = clientSSLSocket.getSSLParameters().getServerNames(); + if (sniSN != null && !sniSN.isEmpty()) { + throw new RuntimeException("SNI server name '" + + sniSN.getFirst() + "' must not be set."); + } + + if (conn.getResponseCode() == -1) { + throw new RuntimeException("getResponseCode() returns -1"); + } + } + + public static void main(String[] args) throws Exception { + + if (IPAddressUtil.isIPv6LiteralAddress(args[0]) && !IPSupport.hasIPv6()) { + throw new SkippedException("Skipping test - IPv6 is not supported"); + } + /* + * Start the tests. + */ + new SubjectAltNameIP(args[0]); + } + + Thread serverThread = null; + + /* + * Primary constructor, used to drive remainder of the test. + * + * Fork off the other side, then do your work. + */ + SubjectAltNameIP(String host) throws Exception { + hostName = host; + startServer(); + doClientSide(); + + /* + * Wait for other side to close down. + */ + serverThread.join(); + + if (serverException != null) + throw serverException; + } + + void startServer() { + serverThread = new Thread(() -> { + try { + doServerSide(); + } catch (Exception e) { + /* + * Our server thread just died. + * + * Store the exception and release the client. + */ + serverException = e; + serverReady.countDown(); + } + }); + serverThread.start(); + } + + /* + * Wraps SSLSocketImpl to simulate a different JSSE implementation + */ + private static SSLSocketFactory wrapSocketFactory(final SSLSocketFactory wrap, final Consumer store) { + return new SSLSocketFactory() { + @Override + public String[] getDefaultCipherSuites() { + return wrap.getDefaultCipherSuites(); + } + @Override + public String[] getSupportedCipherSuites() { + return wrap.getSupportedCipherSuites(); + } + @Override + public Socket createSocket(Socket s, String host, int port, boolean autoClose) + throws IOException { + final SSLSocket so = + (SSLSocket) wrap.createSocket(s, host, port, autoClose); + + // store the underlying SSLSocket for later use + store.accept(so); + + return new SSLSocket() { + @Override + public void connect(SocketAddress endpoint, + int timeout) throws IOException { + so.connect(endpoint, timeout); + } + @Override + public String[] getSupportedCipherSuites() { + return so.getSupportedCipherSuites(); + } + @Override + public String[] getEnabledCipherSuites() { + return so.getEnabledCipherSuites(); + } + @Override + public void setEnabledCipherSuites(String[] suites) { + so.setEnabledCipherSuites(suites); + } + @Override + public String[] getSupportedProtocols() { + return so.getSupportedProtocols(); + } + @Override + public String[] getEnabledProtocols() { + return so.getEnabledProtocols(); + } + @Override + public void setEnabledProtocols(String[] protocols) { + so.setEnabledProtocols(protocols); + } + @Override + public SSLSession getSession() { + return so.getSession(); + } + @Override + public SSLSession getHandshakeSession() { + return so.getHandshakeSession(); + } + @Override + public void addHandshakeCompletedListener(HandshakeCompletedListener listener) { + so.addHandshakeCompletedListener(listener); + } + @Override + public void removeHandshakeCompletedListener(HandshakeCompletedListener listener) { + so.removeHandshakeCompletedListener(listener); + } + @Override + public void startHandshake() throws IOException { + so.startHandshake(); + } + @Override + public void setUseClientMode(boolean mode) { + so.setUseClientMode(mode); + } + @Override + public boolean getUseClientMode() { + return so.getUseClientMode(); + } + @Override + public void setNeedClientAuth(boolean need) { + } + @Override + public boolean getNeedClientAuth() { + return false; + } + @Override + public void setWantClientAuth(boolean want) { + } + @Override + public boolean getWantClientAuth() { + return false; + } + @Override + public void setEnableSessionCreation(boolean flag) { + so.setEnableSessionCreation(flag); + } + @Override + public boolean getEnableSessionCreation() { + return so.getEnableSessionCreation(); + } + @Override + public void close() throws IOException { + so.close(); + } + @Override + public boolean isClosed() { + return so.isClosed(); + } + @Override + public void shutdownInput() throws IOException { + so.shutdownInput(); + } + @Override + public boolean isInputShutdown() { + return so.isInputShutdown(); + } + @Override + public void shutdownOutput() throws IOException { + so.shutdownOutput(); + } + @Override + public boolean isOutputShutdown() { + return so.isOutputShutdown(); + } + @Override + public InputStream getInputStream() throws IOException { + return so.getInputStream(); + } + @Override + public OutputStream getOutputStream() throws IOException { + return so.getOutputStream(); + } + @Override + public SSLParameters getSSLParameters() { + return so.getSSLParameters(); + } + @Override + public void setSSLParameters(SSLParameters params) { + so.setSSLParameters(params); + } + }; + } + @Override + public Socket createSocket(String h, int p) { + return null; + } + @Override + public Socket createSocket(String h, int p, InetAddress ipa, int lp) { + return null; + } + @Override + public Socket createSocket(InetAddress h, int p) { + return null; + } + @Override + public Socket createSocket(InetAddress a, int p, InetAddress l, int lp) { + return null; + } + }; + } +} From 541a1942475a5cd28cf0d5946529ce137d197685 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Wed, 18 Mar 2026 15:27:51 +0000 Subject: [PATCH 058/168] 8375065: Update LCMS to 2.18 Backport-of: 3871b8899df79fa85619975bd1c7f59792a839d1 --- src/java.desktop/share/legal/lcms.md | 4 +- .../share/native/liblcms/cmsalpha.c | 4 +- .../share/native/liblcms/cmscam02.c | 41 +++++++++-------- .../share/native/liblcms/cmscgats.c | 13 ++++-- .../share/native/liblcms/cmscnvrt.c | 2 +- .../share/native/liblcms/cmserr.c | 2 +- .../share/native/liblcms/cmsgamma.c | 3 +- .../share/native/liblcms/cmsgmt.c | 2 +- .../share/native/liblcms/cmshalf.c | 2 +- .../share/native/liblcms/cmsintrp.c | 10 ++--- .../share/native/liblcms/cmsio0.c | 12 +++-- .../share/native/liblcms/cmsio1.c | 10 ++++- .../share/native/liblcms/cmslut.c | 2 +- .../share/native/liblcms/cmsmd5.c | 2 +- .../share/native/liblcms/cmsmtrx.c | 2 +- .../share/native/liblcms/cmsnamed.c | 16 +++---- .../share/native/liblcms/cmsopt.c | 16 +++++-- .../share/native/liblcms/cmspack.c | 2 +- .../share/native/liblcms/cmspcs.c | 2 +- .../share/native/liblcms/cmsplugin.c | 4 +- .../share/native/liblcms/cmsps2.c | 2 +- .../share/native/liblcms/cmssamp.c | 13 +++--- src/java.desktop/share/native/liblcms/cmssm.c | 2 +- .../share/native/liblcms/cmstypes.c | 40 ++++++++++------- .../share/native/liblcms/cmsvirt.c | 44 ++++++++++++------- .../share/native/liblcms/cmswtpnt.c | 2 +- .../share/native/liblcms/cmsxform.c | 9 +++- src/java.desktop/share/native/liblcms/lcms2.h | 6 +-- .../share/native/liblcms/lcms2_internal.h | 2 +- .../share/native/liblcms/lcms2_plugin.h | 2 +- 30 files changed, 166 insertions(+), 107 deletions(-) diff --git a/src/java.desktop/share/legal/lcms.md b/src/java.desktop/share/legal/lcms.md index d3bd7d84c8b1..20a22ea4db59 100644 --- a/src/java.desktop/share/legal/lcms.md +++ b/src/java.desktop/share/legal/lcms.md @@ -1,11 +1,11 @@ -## Little Color Management System (LCMS) v2.17 +## Little Color Management System (LCMS) v2.18 ### LCMS License

 
 MIT License
 
-Copyright (C) 1998-2025 Marti Maria Saguer
+Copyright (C) 1998-2026 Marti Maria Saguer
 
 Permission is hereby granted, free of charge, to any person obtaining
 a copy of this software and associated documentation files (the "Software"),
diff --git a/src/java.desktop/share/native/liblcms/cmsalpha.c b/src/java.desktop/share/native/liblcms/cmsalpha.c
index 2e50b65be24c..bcedbde938e8 100644
--- a/src/java.desktop/share/native/liblcms/cmsalpha.c
+++ b/src/java.desktop/share/native/liblcms/cmsalpha.c
@@ -30,7 +30,7 @@
 //---------------------------------------------------------------------------------
 //
 //  Little Color Management System
-//  Copyright (c) 1998-2024 Marti Maria Saguer
+//  Copyright (c) 1998-2026 Marti Maria Saguer
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the "Software"),
@@ -406,7 +406,7 @@ int FormatterPos(cmsUInt32Number frm)
 static
 cmsFormatterAlphaFn _cmsGetFormatterAlpha(cmsContext id, cmsUInt32Number in, cmsUInt32Number out)
 {
-static cmsFormatterAlphaFn FormattersAlpha[6][6] = {
+static const cmsFormatterAlphaFn FormattersAlpha[6][6] = {
 
        /* from 8 */  { copy8,       from8to16,   from8to16SE,   from8toHLF,   from8toFLT,    from8toDBL    },
        /* from 16*/  { from16to8,   copy16,      from16to16,    from16toHLF,  from16toFLT,   from16toDBL   },
diff --git a/src/java.desktop/share/native/liblcms/cmscam02.c b/src/java.desktop/share/native/liblcms/cmscam02.c
index 45ef4eef970c..168ef597032a 100644
--- a/src/java.desktop/share/native/liblcms/cmscam02.c
+++ b/src/java.desktop/share/native/liblcms/cmscam02.c
@@ -30,7 +30,7 @@
 //---------------------------------------------------------------------------------
 //
 //  Little Color Management System
-//  Copyright (c) 1998-2024 Marti Maria Saguer
+//  Copyright (c) 1998-2026 Marti Maria Saguer
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the "Software"),
@@ -285,27 +285,32 @@ CAM02COLOR InverseCorrelates(CAM02COLOR clr, cmsCIECAM02* pMod)
            (clr.J / 100.0),
            (1.0 / (pMod->c * pMod->z)));
 
-    p1 = e / t;
     p2 = (clr.A / pMod->Nbb) + 0.305;
-    p3 = 21.0 / 20.0;
 
-    hr = clr.h * d2r;
-
-    if (fabs(sin(hr)) >= fabs(cos(hr))) {
-        p4 = p1 / sin(hr);
-        clr.b = (p2 * (2.0 + p3) * (460.0 / 1403.0)) /
-            (p4 + (2.0 + p3) * (220.0 / 1403.0) *
-            (cos(hr) / sin(hr)) - (27.0 / 1403.0) +
-            p3 * (6300.0 / 1403.0));
-        clr.a = clr.b * (cos(hr) / sin(hr));
+    if ( t <= 0.0 ) {     // special case from spec notes, avoid divide by zero
+        clr.a = clr.b = 0.0;
     }
     else {
-        p5 = p1 / cos(hr);
-        clr.a = (p2 * (2.0 + p3) * (460.0 / 1403.0)) /
-            (p5 + (2.0 + p3) * (220.0 / 1403.0) -
-            ((27.0 / 1403.0) - p3 * (6300.0 / 1403.0)) *
-            (sin(hr) / cos(hr)));
-        clr.b = clr.a * (sin(hr) / cos(hr));
+        hr = clr.h * d2r;
+        p1 = e / t;
+        p3 = 21.0 / 20.0;
+
+        if (fabs(sin(hr)) >= fabs(cos(hr))) {
+            p4 = p1 / sin(hr);
+            clr.b = (p2 * (2.0 + p3) * (460.0 / 1403.0)) /
+                (p4 + (2.0 + p3) * (220.0 / 1403.0) *
+                (cos(hr) / sin(hr)) - (27.0 / 1403.0) +
+                p3 * (6300.0 / 1403.0));
+            clr.a = clr.b * (cos(hr) / sin(hr));
+        }
+        else {
+            p5 = p1 / cos(hr);
+            clr.a = (p2 * (2.0 + p3) * (460.0 / 1403.0)) /
+                (p5 + (2.0 + p3) * (220.0 / 1403.0) -
+                ((27.0 / 1403.0) - p3 * (6300.0 / 1403.0)) *
+                (sin(hr) / cos(hr)));
+            clr.b = clr.a * (sin(hr) / cos(hr));
+        }
     }
 
     clr.RGBpa[0] = ((460.0 / 1403.0) * p2) +
diff --git a/src/java.desktop/share/native/liblcms/cmscgats.c b/src/java.desktop/share/native/liblcms/cmscgats.c
index 3e62d064c3f6..e8a75c7355fd 100644
--- a/src/java.desktop/share/native/liblcms/cmscgats.c
+++ b/src/java.desktop/share/native/liblcms/cmscgats.c
@@ -30,7 +30,7 @@
 //---------------------------------------------------------------------------------
 //
 //  Little Color Management System
-//  Copyright (c) 1998-2024 Marti Maria Saguer
+//  Copyright (c) 1998-2026 Marti Maria Saguer
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the "Software"),
@@ -295,7 +295,7 @@ typedef struct {
         WRITEMODE as;      // How is supposed to be written
     } PROPERTY;
 
-static PROPERTY PredefinedProperties[] = {
+static const PROPERTY PredefinedProperties[] = {
 
         {"NUMBER_OF_FIELDS", WRITE_UNCOOKED},    // Required - NUMBER OF FIELDS
         {"NUMBER_OF_SETS",   WRITE_UNCOOKED},    // Required - NUMBER OF SETS
@@ -458,7 +458,7 @@ cmsBool StringAppend(string* s, char c)
         new_ptr = (char*) AllocChunk(s->it8, s->max);
         if (new_ptr == NULL) return FALSE;
 
-        if (new_ptr != NULL && s->begin != NULL)
+        if (s->begin != NULL)
             memcpy(new_ptr, s->begin, s->len);
 
         s->begin = new_ptr;
@@ -899,6 +899,11 @@ void InSymbol(cmsIT8* it8)
                     sign = -1;
                     NextCh(it8);
                 }
+                else
+                    if (it8->ch == '+') {
+                        sign = +1;
+                        NextCh(it8);
+                    }
 
                 it8->inum = 0;
                 it8->sy   = SINUM;
@@ -3206,7 +3211,7 @@ cmsBool ParseCube(cmsIT8* cube, cmsStage** Shaper, cmsStage** CLUT, char title[]
 
                 int nodes = lut_size * lut_size * lut_size;
 
-                cmsFloat32Number* lut_table = _cmsMalloc(cube->ContextID, nodes * 3 * sizeof(cmsFloat32Number));
+                cmsFloat32Number* lut_table = (cmsFloat32Number*) _cmsMalloc(cube->ContextID, nodes * 3 * sizeof(cmsFloat32Number));
                 if (lut_table == NULL) return FALSE;
 
                 for (i = 0; i < nodes; i++) {
diff --git a/src/java.desktop/share/native/liblcms/cmscnvrt.c b/src/java.desktop/share/native/liblcms/cmscnvrt.c
index 9f8619cb9dac..c66dbcbebad8 100644
--- a/src/java.desktop/share/native/liblcms/cmscnvrt.c
+++ b/src/java.desktop/share/native/liblcms/cmscnvrt.c
@@ -30,7 +30,7 @@
 //---------------------------------------------------------------------------------
 //
 //  Little Color Management System
-//  Copyright (c) 1998-2024 Marti Maria Saguer
+//  Copyright (c) 1998-2026 Marti Maria Saguer
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the "Software"),
diff --git a/src/java.desktop/share/native/liblcms/cmserr.c b/src/java.desktop/share/native/liblcms/cmserr.c
index d421c550d32d..877beb9ca6a3 100644
--- a/src/java.desktop/share/native/liblcms/cmserr.c
+++ b/src/java.desktop/share/native/liblcms/cmserr.c
@@ -30,7 +30,7 @@
 //---------------------------------------------------------------------------------
 //
 //  Little Color Management System
-//  Copyright (c) 1998-2024 Marti Maria Saguer
+//  Copyright (c) 1998-2026 Marti Maria Saguer
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the "Software"),
diff --git a/src/java.desktop/share/native/liblcms/cmsgamma.c b/src/java.desktop/share/native/liblcms/cmsgamma.c
index 773858b0c1f0..bace6ab02e2a 100644
--- a/src/java.desktop/share/native/liblcms/cmsgamma.c
+++ b/src/java.desktop/share/native/liblcms/cmsgamma.c
@@ -30,7 +30,7 @@
 //---------------------------------------------------------------------------------
 //
 //  Little Color Management System
-//  Copyright (c) 1998-2024 Marti Maria Saguer
+//  Copyright (c) 1998-2026 Marti Maria Saguer
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the "Software"),
@@ -1187,6 +1187,7 @@ cmsBool smooth2(cmsContext ContextID, cmsFloat32Number w[], cmsFloat32Number y[]
     cmsFloat32Number *c, *d, *e;
     cmsBool st;
 
+    if (m < 4 || lambda < MATRIX_DET_TOLERANCE) return FALSE;
 
     c = (cmsFloat32Number*) _cmsCalloc(ContextID, MAX_NODES_IN_CURVE, sizeof(cmsFloat32Number));
     d = (cmsFloat32Number*) _cmsCalloc(ContextID, MAX_NODES_IN_CURVE, sizeof(cmsFloat32Number));
diff --git a/src/java.desktop/share/native/liblcms/cmsgmt.c b/src/java.desktop/share/native/liblcms/cmsgmt.c
index 03ac70202a50..1b023dcc299f 100644
--- a/src/java.desktop/share/native/liblcms/cmsgmt.c
+++ b/src/java.desktop/share/native/liblcms/cmsgmt.c
@@ -30,7 +30,7 @@
 //---------------------------------------------------------------------------------
 //
 //  Little Color Management System
-//  Copyright (c) 1998-2024 Marti Maria Saguer
+//  Copyright (c) 1998-2026 Marti Maria Saguer
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the "Software"),
diff --git a/src/java.desktop/share/native/liblcms/cmshalf.c b/src/java.desktop/share/native/liblcms/cmshalf.c
index 7e5f7a3c7e03..e1fb1d554883 100644
--- a/src/java.desktop/share/native/liblcms/cmshalf.c
+++ b/src/java.desktop/share/native/liblcms/cmshalf.c
@@ -30,7 +30,7 @@
 //---------------------------------------------------------------------------------
 //
 //  Little Color Management System
-//  Copyright (c) 1998-2024 Marti Maria Saguer
+//  Copyright (c) 1998-2026 Marti Maria Saguer
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the "Software"),
diff --git a/src/java.desktop/share/native/liblcms/cmsintrp.c b/src/java.desktop/share/native/liblcms/cmsintrp.c
index 43c47429c3cd..23e59a229a9f 100644
--- a/src/java.desktop/share/native/liblcms/cmsintrp.c
+++ b/src/java.desktop/share/native/liblcms/cmsintrp.c
@@ -30,7 +30,7 @@
 //---------------------------------------------------------------------------------
 //
 //  Little Color Management System
-//  Copyright (c) 1998-2024 Marti Maria Saguer
+//  Copyright (c) 1998-2026 Marti Maria Saguer
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the "Software"),
@@ -984,9 +984,9 @@ void Eval4Inputs(CMSREGISTER const cmsUInt16Number Input[],
                                 c1 = c2 = c3 = 0;
                             }
 
-        Rest = c1 * rx + c2 * ry + c3 * rz;
+        Rest = c1 * rx + c2 * ry + c3 * rz + 0x8001;
 
-        Tmp1[OutChan] = (cmsUInt16Number)(c0 + ROUND_FIXED_TO_INT(_cmsToFixedDomain(Rest)));
+        Tmp1[OutChan] = (cmsUInt16Number)c0 + ((Rest + (Rest >> 16)) >> 16);
     }
 
 
@@ -1048,9 +1048,9 @@ void Eval4Inputs(CMSREGISTER const cmsUInt16Number Input[],
                                 c1 = c2 = c3 = 0;
                             }
 
-        Rest = c1 * rx + c2 * ry + c3 * rz;
+        Rest = c1 * rx + c2 * ry + c3 * rz + 0x8001;
 
-        Tmp2[OutChan] = (cmsUInt16Number) (c0 + ROUND_FIXED_TO_INT(_cmsToFixedDomain(Rest)));
+        Tmp2[OutChan] = (cmsUInt16Number) c0 + ((Rest + (Rest >> 16)) >> 16);
     }
 
 
diff --git a/src/java.desktop/share/native/liblcms/cmsio0.c b/src/java.desktop/share/native/liblcms/cmsio0.c
index 5258b7939d2b..5a4f09af5bcf 100644
--- a/src/java.desktop/share/native/liblcms/cmsio0.c
+++ b/src/java.desktop/share/native/liblcms/cmsio0.c
@@ -30,7 +30,7 @@
 //---------------------------------------------------------------------------------
 //
 //  Little Color Management System
-//  Copyright (c) 1998-2024 Marti Maria Saguer
+//  Copyright (c) 1998-2026 Marti Maria Saguer
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the "Software"),
@@ -685,6 +685,7 @@ void _cmsDeleteTagByPos(_cmsICCPROFILE* Icc, int i)
         // Free previous version
         if (Icc ->TagSaveAsRaw[i]) {
             _cmsFree(Icc ->ContextID, Icc ->TagPtrs[i]);
+            Icc->TagSaveAsRaw[i] = FALSE;
         }
         else {
             cmsTagTypeHandler* TypeHandler = Icc ->TagTypeHandlers[i];
@@ -1605,6 +1606,8 @@ void freeOneTag(_cmsICCPROFILE* Icc, cmsUInt32Number i)
         else
             _cmsFree(Icc->ContextID, Icc->TagPtrs[i]);
     }
+
+    Icc->TagPtrs[i] = NULL;
 }
 
 // Closes a profile freeing any involved resources
@@ -1847,8 +1850,11 @@ cmsBool CMSEXPORT cmsWriteTag(cmsHPROFILE hProfile, cmsTagSignature sig, const v
 
     if (!_cmsNewTag(Icc, sig, &i)) goto Error;
 
-    // This is not raw
-    Icc ->TagSaveAsRaw[i] = FALSE;
+    // This cannot be RAW
+    if (Icc->TagSaveAsRaw[i]) {
+        cmsSignalError(Icc->ContextID, cmsERROR_ALREADY_DEFINED, "Tag  '%x' was already saved as RAW", sig);
+        goto Error;
+    }
 
     // This is not a link
     Icc ->TagLinked[i] = (cmsTagSignature) 0;
diff --git a/src/java.desktop/share/native/liblcms/cmsio1.c b/src/java.desktop/share/native/liblcms/cmsio1.c
index 48772c7cbde9..463f1192c2a4 100644
--- a/src/java.desktop/share/native/liblcms/cmsio1.c
+++ b/src/java.desktop/share/native/liblcms/cmsio1.c
@@ -30,7 +30,7 @@
 //---------------------------------------------------------------------------------
 //
 //  Little Color Management System
-//  Copyright (c) 1998-2024 Marti Maria Saguer
+//  Copyright (c) 1998-2026 Marti Maria Saguer
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the "Software"),
@@ -1012,7 +1012,13 @@ const cmsMLU* GetInfo(cmsHPROFILE hProfile, cmsInfoType Info)
     switch (Info) {
 
     case cmsInfoDescription:
-        sig = cmsSigProfileDescriptionTag;
+        /**
+        * Add for MacOS, which uses propiertary tags for description
+        */
+        if (cmsIsTag(hProfile, cmsSigProfileDescriptionMLTag))
+            sig = cmsSigProfileDescriptionMLTag;
+        else
+            sig = cmsSigProfileDescriptionTag;
         break;
 
     case cmsInfoManufacturer:
diff --git a/src/java.desktop/share/native/liblcms/cmslut.c b/src/java.desktop/share/native/liblcms/cmslut.c
index 3cf4e8cac5a6..28220eae6673 100644
--- a/src/java.desktop/share/native/liblcms/cmslut.c
+++ b/src/java.desktop/share/native/liblcms/cmslut.c
@@ -30,7 +30,7 @@
 //---------------------------------------------------------------------------------
 //
 //  Little Color Management System
-//  Copyright (c) 1998-2024 Marti Maria Saguer
+//  Copyright (c) 1998-2026 Marti Maria Saguer
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the "Software"),
diff --git a/src/java.desktop/share/native/liblcms/cmsmd5.c b/src/java.desktop/share/native/liblcms/cmsmd5.c
index d9b9a4e52608..f18300ebace2 100644
--- a/src/java.desktop/share/native/liblcms/cmsmd5.c
+++ b/src/java.desktop/share/native/liblcms/cmsmd5.c
@@ -30,7 +30,7 @@
 //---------------------------------------------------------------------------------
 //
 //  Little Color Management System
-//  Copyright (c) 1998-2024 Marti Maria Saguer
+//  Copyright (c) 1998-2026 Marti Maria Saguer
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the "Software"),
diff --git a/src/java.desktop/share/native/liblcms/cmsmtrx.c b/src/java.desktop/share/native/liblcms/cmsmtrx.c
index 841da662a107..1db000752e3a 100644
--- a/src/java.desktop/share/native/liblcms/cmsmtrx.c
+++ b/src/java.desktop/share/native/liblcms/cmsmtrx.c
@@ -30,7 +30,7 @@
 //---------------------------------------------------------------------------------
 //
 //  Little Color Management System
-//  Copyright (c) 1998-2024 Marti Maria Saguer
+//  Copyright (c) 1998-2026 Marti Maria Saguer
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the "Software"),
diff --git a/src/java.desktop/share/native/liblcms/cmsnamed.c b/src/java.desktop/share/native/liblcms/cmsnamed.c
index 451bfe9f34d5..acdaabc3ec26 100644
--- a/src/java.desktop/share/native/liblcms/cmsnamed.c
+++ b/src/java.desktop/share/native/liblcms/cmsnamed.c
@@ -30,7 +30,7 @@
 //---------------------------------------------------------------------------------
 //
 //  Little Color Management System
-//  Copyright (c) 1998-2024 Marti Maria Saguer
+//  Copyright (c) 1998-2026 Marti Maria Saguer
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the "Software"),
@@ -303,7 +303,7 @@ cmsUInt32Number encodeUTF8(char* out, const wchar_t* in, cmsUInt32Number max_wch
     cmsUInt32Number size = 0;
     cmsUInt32Number len_w = 0;
 
-    while (*in && len_w < max_wchars)
+    while (len_w < max_wchars && *in)
     {
         if (*in >= 0xd800 && *in <= 0xdbff)
             codepoint = ((*in - 0xd800) << 10) + 0x10000;
@@ -1071,17 +1071,17 @@ cmsSEQ* CMSEXPORT cmsDupProfileSequenceDescription(const cmsSEQ* pseq)
     if (pseq == NULL)
         return NULL;
 
-    NewSeq = (cmsSEQ*) _cmsMalloc(pseq -> ContextID, sizeof(cmsSEQ));
+    NewSeq = (cmsSEQ*)_cmsMallocZero(pseq->ContextID, sizeof(cmsSEQ));
     if (NewSeq == NULL) return NULL;
 
+    NewSeq->ContextID = pseq->ContextID;
 
-    NewSeq -> seq      = (cmsPSEQDESC*) _cmsCalloc(pseq ->ContextID, pseq ->n, sizeof(cmsPSEQDESC));
-    if (NewSeq ->seq == NULL) goto Error;
+    NewSeq->seq = (cmsPSEQDESC*)_cmsCalloc(pseq->ContextID, pseq->n, sizeof(cmsPSEQDESC));
+    if (NewSeq->seq == NULL) goto Error;
 
-    NewSeq -> ContextID = pseq ->ContextID;
-    NewSeq -> n        = pseq ->n;
+    NewSeq->n = pseq->n;
 
-    for (i=0; i < pseq->n; i++) {
+    for (i = 0; i < pseq->n; i++) {
 
         memmove(&NewSeq ->seq[i].attributes, &pseq ->seq[i].attributes, sizeof(cmsUInt64Number));
 
diff --git a/src/java.desktop/share/native/liblcms/cmsopt.c b/src/java.desktop/share/native/liblcms/cmsopt.c
index 767008e68c58..9e71426a3327 100644
--- a/src/java.desktop/share/native/liblcms/cmsopt.c
+++ b/src/java.desktop/share/native/liblcms/cmsopt.c
@@ -30,7 +30,7 @@
 //---------------------------------------------------------------------------------
 //
 //  Little Color Management System
-//  Copyright (c) 1998-2024 Marti Maria Saguer
+//  Copyright (c) 1998-2026 Marti Maria Saguer
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the "Software"),
@@ -698,11 +698,16 @@ cmsBool OptimizeByResampling(cmsPipeline** Lut, cmsUInt32Number Intent, cmsUInt3
     if (ColorSpace == (cmsColorSpaceSignature)0 ||
         OutputColorSpace == (cmsColorSpaceSignature)0) return FALSE;
 
-    nGridPoints = _cmsReasonableGridpointsByColorspace(ColorSpace, *dwFlags);
-
     // For empty LUTs, 2 points are enough
     if (cmsPipelineStageCount(*Lut) == 0)
         nGridPoints = 2;
+    else
+    {
+        nGridPoints = _cmsReasonableGridpointsByColorspace(ColorSpace, *dwFlags);
+
+        // Lab16 as input cannot be optimized by a CLUT due to centering issues, thanks to Mike Chaney for discovering this.
+        if (!(*dwFlags & cmsFLAGS_FORCE_CLUT) && (ColorSpace == cmsSigLabData) && (T_BYTES(*InputFormat) == 2)) return FALSE;
+    }
 
     Src = *Lut;
 
@@ -813,6 +818,11 @@ cmsBool OptimizeByResampling(cmsPipeline** Lut, cmsUInt32Number Intent, cmsUInt3
             Dest ->OutputChannels,
             DataSetOut);
 
+        if (p16 == NULL) {
+            cmsPipelineFree(Dest);
+            return FALSE;
+        }
+
         _cmsPipelineSetOptimizationParameters(Dest, PrelinEval16, (void*) p16, PrelinOpt16free, Prelin16dup);
     }
 
diff --git a/src/java.desktop/share/native/liblcms/cmspack.c b/src/java.desktop/share/native/liblcms/cmspack.c
index d430e73051de..b740567af3b6 100644
--- a/src/java.desktop/share/native/liblcms/cmspack.c
+++ b/src/java.desktop/share/native/liblcms/cmspack.c
@@ -30,7 +30,7 @@
 //---------------------------------------------------------------------------------
 //
 //  Little Color Management System
-//  Copyright (c) 1998-2024 Marti Maria Saguer
+//  Copyright (c) 1998-2026 Marti Maria Saguer
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the "Software"),
diff --git a/src/java.desktop/share/native/liblcms/cmspcs.c b/src/java.desktop/share/native/liblcms/cmspcs.c
index 5f1b1f0d8e6d..8c33057721e9 100644
--- a/src/java.desktop/share/native/liblcms/cmspcs.c
+++ b/src/java.desktop/share/native/liblcms/cmspcs.c
@@ -30,7 +30,7 @@
 //---------------------------------------------------------------------------------
 //
 //  Little Color Management System
-//  Copyright (c) 1998-2024 Marti Maria Saguer
+//  Copyright (c) 1998-2026 Marti Maria Saguer
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the "Software"),
diff --git a/src/java.desktop/share/native/liblcms/cmsplugin.c b/src/java.desktop/share/native/liblcms/cmsplugin.c
index aaad39f52b04..a943c9f4dd96 100644
--- a/src/java.desktop/share/native/liblcms/cmsplugin.c
+++ b/src/java.desktop/share/native/liblcms/cmsplugin.c
@@ -30,7 +30,7 @@
 //---------------------------------------------------------------------------------
 //
 //  Little Color Management System
-//  Copyright (c) 1998-2024 Marti Maria Saguer
+//  Copyright (c) 1998-2026 Marti Maria Saguer
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the "Software"),
@@ -522,7 +522,7 @@ cmsBool CMSEXPORT _cmsIOPrintf(cmsIOHANDLER* io, const char* frm, ...)
     va_start(args, frm);
 
     len = vsnprintf((char*) Buffer, 2047, frm, args);
-    if (len < 0) {
+    if (len < 0 || len >= 2047) {
         va_end(args);
         return FALSE;   // Truncated, which is a fatal error for us
     }
diff --git a/src/java.desktop/share/native/liblcms/cmsps2.c b/src/java.desktop/share/native/liblcms/cmsps2.c
index 476817e9c1a2..80f7c8084ae7 100644
--- a/src/java.desktop/share/native/liblcms/cmsps2.c
+++ b/src/java.desktop/share/native/liblcms/cmsps2.c
@@ -30,7 +30,7 @@
 //---------------------------------------------------------------------------------
 //
 //  Little Color Management System
-//  Copyright (c) 1998-2024 Marti Maria Saguer
+//  Copyright (c) 1998-2026 Marti Maria Saguer
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the "Software"),
diff --git a/src/java.desktop/share/native/liblcms/cmssamp.c b/src/java.desktop/share/native/liblcms/cmssamp.c
index ca5c4a9d6931..c54a0d4ea723 100644
--- a/src/java.desktop/share/native/liblcms/cmssamp.c
+++ b/src/java.desktop/share/native/liblcms/cmssamp.c
@@ -30,7 +30,7 @@
 //---------------------------------------------------------------------------------
 //
 //  Little Color Management System
-//  Copyright (c) 1998-2024 Marti Maria Saguer
+//  Copyright (c) 1998-2026 Marti Maria Saguer
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the "Software"),
@@ -152,9 +152,12 @@ cmsBool  BlackPointAsDarkerColorant(cmsHPROFILE    hInput,
     // Convert black to Lab
     cmsDoTransform(xform, Black, &Lab, 1);
 
-    // Force it to be neutral, check for inconsistencies
-    Lab.a = Lab.b = 0;
-    if (Lab.L > 50 || Lab.L < 0) Lab.L = 0;
+    if (Lab.L > 95)
+        Lab.L = 0;  // for synthetical negative profiles
+    else if (Lab.L < 0)
+        Lab.L = 0;
+    else if (Lab.L > 50)
+        Lab.L = 50;
 
     // Free the resources
     cmsDeleteTransform(xform);
@@ -352,7 +355,7 @@ cmsFloat64Number RootOfLeastSquaresFitQuadraticCurve(int n, cmsFloat64Number x[]
     if (fabs(a) < 1.0E-10) {
 
         if (fabs(b) < 1.0E-10) return 0;
-        return cmsmin(0, cmsmax(50, -c/b ));
+        return cmsmax(0, cmsmin(50, -c/b ));
     }
     else {
 
diff --git a/src/java.desktop/share/native/liblcms/cmssm.c b/src/java.desktop/share/native/liblcms/cmssm.c
index e2a810a26695..b79cd85488bf 100644
--- a/src/java.desktop/share/native/liblcms/cmssm.c
+++ b/src/java.desktop/share/native/liblcms/cmssm.c
@@ -30,7 +30,7 @@
 //---------------------------------------------------------------------------------
 //
 //  Little Color Management System
-//  Copyright (c) 1998-2024 Marti Maria Saguer
+//  Copyright (c) 1998-2026 Marti Maria Saguer
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the "Software"),
diff --git a/src/java.desktop/share/native/liblcms/cmstypes.c b/src/java.desktop/share/native/liblcms/cmstypes.c
index 22514f882268..eab74940cd09 100644
--- a/src/java.desktop/share/native/liblcms/cmstypes.c
+++ b/src/java.desktop/share/native/liblcms/cmstypes.c
@@ -30,7 +30,7 @@
 //---------------------------------------------------------------------------------
 //
 //  Little Color Management System
-//  Copyright (c) 1998-2024 Marti Maria Saguer
+//  Copyright (c) 1998-2026 Marti Maria Saguer
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the "Software"),
@@ -4786,7 +4786,6 @@ cmsBool ReadMPEElem(struct _cms_typehandler_struct* self,
 
     return TRUE;
 
-    cmsUNUSED_PARAMETER(SizeOfTag);
     cmsUNUSED_PARAMETER(n);
 }
 
@@ -4894,9 +4893,11 @@ cmsBool Type_MPE_Write(struct _cms_typehandler_struct* self, cmsIOHANDLER* io, v
                  goto Error;
         }
 
+         Before = io ->Tell(io);
+
         if (!_cmsWriteUInt32Number(io, ElementSig)) goto Error;
         if (!_cmsWriteUInt32Number(io, 0)) goto Error;
-        Before = io ->Tell(io);
+
         if (!TypeHandler ->WritePtr(self, io, Elem, 1)) goto Error;
         if (!_cmsWriteAlignment(io)) goto Error;
 
@@ -5645,9 +5646,7 @@ void* Type_VideoSignal_Read(struct _cms_typehandler_struct* self, cmsIOHANDLER*
 {
     cmsVideoSignalType* cicp = NULL;
 
-    if (SizeOfTag != 8) return NULL;
-
-    if (!_cmsReadUInt32Number(io, NULL)) return NULL;
+    if (SizeOfTag != 4) return NULL;
 
     cicp = (cmsVideoSignalType*)_cmsCalloc(self->ContextID, 1, sizeof(cmsVideoSignalType));
     if (cicp == NULL) return NULL;
@@ -5671,7 +5670,6 @@ cmsBool Type_VideoSignal_Write(struct _cms_typehandler_struct* self, cmsIOHANDLE
 {
     cmsVideoSignalType* cicp = (cmsVideoSignalType*)Ptr;
 
-    if (!_cmsWriteUInt32Number(io, 0)) return FALSE;
     if (!_cmsWriteUInt8Number(io, cicp->ColourPrimaries)) return FALSE;
     if (!_cmsWriteUInt8Number(io, cicp->TransferCharacteristics)) return FALSE;
     if (!_cmsWriteUInt8Number(io, cicp->MatrixCoefficients)) return FALSE;
@@ -5744,11 +5742,11 @@ void Type_MHC2_Free(struct _cms_typehandler_struct* self, void* Ptr)
 
 void* Type_MHC2_Dup(struct _cms_typehandler_struct* self, const void* Ptr, cmsUInt32Number n)
 {
-    cmsMHC2Type* mhc2 = _cmsDupMem(self->ContextID, Ptr, sizeof(cmsMHC2Type));
+    cmsMHC2Type* mhc2 = (cmsMHC2Type*)_cmsDupMem(self->ContextID, Ptr, sizeof(cmsMHC2Type));
 
-    mhc2->RedCurve = _cmsDupMem(self->ContextID,   mhc2->RedCurve, mhc2->CurveEntries*sizeof(cmsFloat64Number));
-    mhc2->GreenCurve = _cmsDupMem(self->ContextID, mhc2->GreenCurve, mhc2->CurveEntries * sizeof(cmsFloat64Number));
-    mhc2->BlueCurve = _cmsDupMem(self->ContextID,  mhc2->BlueCurve, mhc2->CurveEntries * sizeof(cmsFloat64Number));
+    mhc2->RedCurve = (cmsFloat64Number*) _cmsDupMem(self->ContextID,   mhc2->RedCurve, mhc2->CurveEntries*sizeof(cmsFloat64Number));
+    mhc2->GreenCurve = (cmsFloat64Number*) _cmsDupMem(self->ContextID, mhc2->GreenCurve, mhc2->CurveEntries * sizeof(cmsFloat64Number));
+    mhc2->BlueCurve = (cmsFloat64Number*) _cmsDupMem(self->ContextID,  mhc2->BlueCurve, mhc2->CurveEntries * sizeof(cmsFloat64Number));
 
     if (mhc2->RedCurve == NULL ||
         mhc2->GreenCurve == NULL ||
@@ -5786,7 +5784,6 @@ cmsBool Type_MHC2_Write(struct _cms_typehandler_struct* self, cmsIOHANDLER* io,
     cmsUInt32Number MatrixOffset;
     cmsUInt32Number OffsetRedTable, OffsetGreenTable, OffsetBlueTable;
 
-    if (!_cmsWriteUInt32Number(io, 0)) return FALSE;
     if (!_cmsWriteUInt32Number(io, mhc2->CurveEntries)) return FALSE;
 
     if (!_cmsWrite15Fixed16Number(io, mhc2->MinLuminance)) return FALSE;
@@ -5811,10 +5808,20 @@ cmsBool Type_MHC2_Write(struct _cms_typehandler_struct* self, cmsIOHANDLER* io,
     }
 
     OffsetRedTable = io->Tell(io) - BaseOffset;
+
+    if(!_cmsWriteUInt32Number(io, cmsSigS15Fixed16ArrayType)) return FALSE;
+    if(!_cmsWriteUInt32Number(io, 0)) return FALSE;
+
     if (!WriteDoubles(io, mhc2->CurveEntries, mhc2->RedCurve)) return FALSE;
+
     OffsetGreenTable = io->Tell(io) - BaseOffset;
+    if (!_cmsWriteUInt32Number(io, cmsSigS15Fixed16ArrayType)) return FALSE;
+    if (!_cmsWriteUInt32Number(io, 0)) return FALSE;
     if (!WriteDoubles(io, mhc2->CurveEntries, mhc2->GreenCurve)) return FALSE;
+
     OffsetBlueTable = io->Tell(io) - BaseOffset;
+    if (!_cmsWriteUInt32Number(io, cmsSigS15Fixed16ArrayType)) return FALSE;
+    if (!_cmsWriteUInt32Number(io, 0)) return FALSE;
     if (!WriteDoubles(io, mhc2->CurveEntries, mhc2->BlueCurve)) return FALSE;
 
     if (!io->Seek(io, TablesOffsetPos)) return FALSE;
@@ -5858,8 +5865,6 @@ void* Type_MHC2_Read(struct _cms_typehandler_struct* self, cmsIOHANDLER* io, cms
     cmsUInt32Number MatrixOffset;
     cmsUInt32Number OffsetRedTable, OffsetGreenTable, OffsetBlueTable;
 
-    if (!_cmsReadUInt32Number(io, NULL)) return NULL;
-
     mhc2 = (cmsMHC2Type*)_cmsCalloc(self->ContextID, 1, sizeof(cmsMHC2Type));
     if (mhc2 == NULL) return NULL;
 
@@ -5890,9 +5895,10 @@ void* Type_MHC2_Read(struct _cms_typehandler_struct* self, cmsIOHANDLER* io, cms
         if (!ReadDoublesAt(io, BaseOffset + MatrixOffset, 3*4, &mhc2->XYZ2XYZmatrix[0][0])) goto Error;
     }
 
-    if (!ReadDoublesAt(io, BaseOffset + OffsetRedTable, mhc2->CurveEntries, mhc2->RedCurve)) goto Error;
-    if (!ReadDoublesAt(io, BaseOffset + OffsetGreenTable, mhc2->CurveEntries, mhc2->GreenCurve)) goto Error;
-    if (!ReadDoublesAt(io, BaseOffset + OffsetBlueTable, mhc2->CurveEntries, mhc2->BlueCurve)) goto Error;
+    // Skip sf32 tag and filler (8bytes)
+    if (!ReadDoublesAt(io, BaseOffset + OffsetRedTable + 8, mhc2->CurveEntries, mhc2->RedCurve)) goto Error;
+    if (!ReadDoublesAt(io, BaseOffset + OffsetGreenTable + 8, mhc2->CurveEntries, mhc2->GreenCurve)) goto Error;
+    if (!ReadDoublesAt(io, BaseOffset + OffsetBlueTable + 8, mhc2->CurveEntries, mhc2->BlueCurve)) goto Error;
 
     // Success
     *nItems = 1;
diff --git a/src/java.desktop/share/native/liblcms/cmsvirt.c b/src/java.desktop/share/native/liblcms/cmsvirt.c
index 1ef86dae0544..0dfc6e947a55 100644
--- a/src/java.desktop/share/native/liblcms/cmsvirt.c
+++ b/src/java.desktop/share/native/liblcms/cmsvirt.c
@@ -30,7 +30,7 @@
 //---------------------------------------------------------------------------------
 //
 //  Little Color Management System
-//  Copyright (c) 1998-2024 Marti Maria Saguer
+//  Copyright (c) 1998-2026 Marti Maria Saguer
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the "Software"),
@@ -400,7 +400,7 @@ int InkLimitingSampler(CMSREGISTER const cmsUInt16Number In[], CMSREGISTER cmsUI
     SumCMY   = (cmsFloat64Number) In[0]  + In[1] + In[2];
     SumCMYK  = SumCMY + In[3];
 
-    if (SumCMYK > InkLimit) {
+    if (SumCMYK > InkLimit && SumCMY > 0) {
 
         Ratio = 1 - ((SumCMYK - InkLimit) / SumCMY);
         if (Ratio < 0)
@@ -513,16 +513,20 @@ cmsHPROFILE CMSEXPORT cmsCreateLab2ProfileTHR(cmsContext ContextID, const cmsCIE
     cmsSetColorSpace(hProfile,  cmsSigLabData);
     cmsSetPCS(hProfile,         cmsSigLabData);
 
-    if (!SetTextTags(hProfile, L"Lab identity built-in")) return NULL;
+    if (!SetTextTags(hProfile, L"Lab identity built-in"))
+        goto Error;
 
     // An identity LUT is all we need
     LUT = cmsPipelineAlloc(ContextID, 3, 3);
-    if (LUT == NULL) goto Error;
+    if (LUT == NULL)
+        goto Error;
 
     if (!cmsPipelineInsertStage(LUT, cmsAT_BEGIN, _cmsStageAllocIdentityCLut(ContextID, 3)))
         goto Error;
 
-    if (!cmsWriteTag(hProfile, cmsSigAToB0Tag, LUT)) goto Error;
+    if (!cmsWriteTag(hProfile, cmsSigAToB0Tag, LUT))
+        goto Error;
+
     cmsPipelineFree(LUT);
 
     return hProfile;
@@ -550,8 +554,14 @@ cmsHPROFILE CMSEXPORT cmsCreateLab4ProfileTHR(cmsContext ContextID, const cmsCIE
 {
     cmsHPROFILE hProfile;
     cmsPipeline* LUT = NULL;
+    cmsCIEXYZ xyz;
 
-    hProfile = cmsCreateRGBProfileTHR(ContextID, WhitePoint == NULL ? cmsD50_xyY() : WhitePoint, NULL, NULL);
+    if (WhitePoint == NULL)
+        xyz = *cmsD50_XYZ();
+    else
+        cmsxyY2XYZ(&xyz, WhitePoint);
+
+    hProfile = cmsCreateRGBProfileTHR(ContextID, NULL, NULL, NULL);
     if (hProfile == NULL) return NULL;
 
     cmsSetProfileVersion(hProfile, 4.4);
@@ -560,6 +570,7 @@ cmsHPROFILE CMSEXPORT cmsCreateLab4ProfileTHR(cmsContext ContextID, const cmsCIE
     cmsSetColorSpace(hProfile,  cmsSigLabData);
     cmsSetPCS(hProfile,         cmsSigLabData);
 
+    if (!cmsWriteTag(hProfile, cmsSigMediaWhitePointTag, &xyz)) goto Error;
     if (!SetTextTags(hProfile, L"Lab identity built-in")) goto Error;
 
     // An empty LUTs is all we need
@@ -929,25 +940,24 @@ cmsHPROFILE CMSEXPORT cmsCreateBCHSWabstractProfileTHR(cmsContext ContextID,
 
     for (i=0; i < MAX_INPUT_DIMENSIONS; i++) Dimensions[i] = nLUTPoints;
     CLUT = cmsStageAllocCLut16bitGranular(ContextID, Dimensions, 3, 3, NULL);
-    if (CLUT == NULL) goto Error;
-
-
-    if (!cmsStageSampleCLut16bit(CLUT, bchswSampler, (void*) &bchsw, 0)) {
+    if (CLUT == NULL)
+        goto Error;
 
-        // Shouldn't reach here
+    if (!cmsStageSampleCLut16bit(CLUT, bchswSampler, (void*) &bchsw, 0))
         goto Error;
-    }
 
-    if (!cmsPipelineInsertStage(Pipeline, cmsAT_END, CLUT)) {
+    if (!cmsPipelineInsertStage(Pipeline, cmsAT_END, CLUT))
         goto Error;
-    }
 
     // Create tags
-    if (!SetTextTags(hICC, L"BCHS built-in")) return NULL;
+    if (!SetTextTags(hICC, L"BCHS built-in"))
+        goto Error;
 
-    cmsWriteTag(hICC, cmsSigMediaWhitePointTag, (void*) cmsD50_XYZ());
+    if (!cmsWriteTag(hICC, cmsSigMediaWhitePointTag, (void*)cmsD50_XYZ()))
+        goto Error;
 
-    cmsWriteTag(hICC, cmsSigAToB0Tag, (void*) Pipeline);
+    if (!cmsWriteTag(hICC, cmsSigAToB0Tag, (void*)Pipeline))
+        goto Error;
 
     // Pipeline is already on virtual profile
     cmsPipelineFree(Pipeline);
diff --git a/src/java.desktop/share/native/liblcms/cmswtpnt.c b/src/java.desktop/share/native/liblcms/cmswtpnt.c
index ebba2cd6a978..f6337765c0c7 100644
--- a/src/java.desktop/share/native/liblcms/cmswtpnt.c
+++ b/src/java.desktop/share/native/liblcms/cmswtpnt.c
@@ -30,7 +30,7 @@
 //---------------------------------------------------------------------------------
 //
 //  Little Color Management System
-//  Copyright (c) 1998-2024 Marti Maria Saguer
+//  Copyright (c) 1998-2026 Marti Maria Saguer
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the "Software"),
diff --git a/src/java.desktop/share/native/liblcms/cmsxform.c b/src/java.desktop/share/native/liblcms/cmsxform.c
index 1eb3eecbf182..b5dd302b973a 100644
--- a/src/java.desktop/share/native/liblcms/cmsxform.c
+++ b/src/java.desktop/share/native/liblcms/cmsxform.c
@@ -30,7 +30,7 @@
 //---------------------------------------------------------------------------------
 //
 //  Little Color Management System
-//  Copyright (c) 1998-2024 Marti Maria Saguer
+//  Copyright (c) 1998-2026 Marti Maria Saguer
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the "Software"),
@@ -1101,6 +1101,8 @@ cmsBool  IsProperColorSpace(cmsColorSpaceSignature Check, cmsUInt32Number dwForm
     int Space1 = (int) T_COLORSPACE(dwFormat);
     int Space2 = _cmsLCMScolorSpace(Check);
 
+    if (dwFormat == 0) return TRUE; // Bypass used by linkicc
+
     if (Space1 == PT_ANY) return (T_CHANNELS(dwFormat) == cmsChannelsOf(Check));
     if (Space1 == Space2) return TRUE;
 
@@ -1183,6 +1185,11 @@ cmsHTRANSFORM CMSEXPORT cmsCreateExtendedTransform(cmsContext ContextID,
         if (hGamutProfile == NULL) dwFlags &= ~cmsFLAGS_GAMUTCHECK;
     }
 
+    if ((dwFlags & cmsFLAGS_GAMUTCHECK) && (nGamutPCSposition <= 0 || nGamutPCSposition >= nProfiles - 1)) {
+        cmsSignalError(ContextID, cmsERROR_RANGE, "Wrong gamut PCS position '%d'", nGamutPCSposition);
+        return NULL;
+    }
+
     // On floating point transforms, inhibit cache
     if (_cmsFormatterIsFloat(InputFormat) || _cmsFormatterIsFloat(OutputFormat))
         dwFlags |= cmsFLAGS_NOCACHE;
diff --git a/src/java.desktop/share/native/liblcms/lcms2.h b/src/java.desktop/share/native/liblcms/lcms2.h
index 5ba096613088..17a523847211 100644
--- a/src/java.desktop/share/native/liblcms/lcms2.h
+++ b/src/java.desktop/share/native/liblcms/lcms2.h
@@ -30,7 +30,7 @@
 //---------------------------------------------------------------------------------
 //
 //  Little Color Management System
-//  Copyright (c) 1998-2025 Marti Maria Saguer
+//  Copyright (c) 1998-2026 Marti Maria Saguer
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the "Software"),
@@ -52,7 +52,7 @@
 //
 //---------------------------------------------------------------------------------
 //
-// Version 2.17
+// Version 2.18
 //
 
 #ifndef _lcms2_H
@@ -116,7 +116,7 @@ extern "C" {
 #endif
 
 // Version/release
-#define LCMS_VERSION        2170
+#define LCMS_VERSION        2180
 
 // I will give the chance of redefining basic types for compilers that are not fully C99 compliant
 #ifndef CMS_BASIC_TYPES_ALREADY_DEFINED
diff --git a/src/java.desktop/share/native/liblcms/lcms2_internal.h b/src/java.desktop/share/native/liblcms/lcms2_internal.h
index d14c0dd823ea..6bfe67e53501 100644
--- a/src/java.desktop/share/native/liblcms/lcms2_internal.h
+++ b/src/java.desktop/share/native/liblcms/lcms2_internal.h
@@ -30,7 +30,7 @@
 
 //
 //  Little Color Management System
-//  Copyright (c) 1998-2024 Marti Maria Saguer
+//  Copyright (c) 1998-2026 Marti Maria Saguer
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the "Software"),
diff --git a/src/java.desktop/share/native/liblcms/lcms2_plugin.h b/src/java.desktop/share/native/liblcms/lcms2_plugin.h
index bdfc76f6bf5d..85de9bc56d5f 100644
--- a/src/java.desktop/share/native/liblcms/lcms2_plugin.h
+++ b/src/java.desktop/share/native/liblcms/lcms2_plugin.h
@@ -30,7 +30,7 @@
 //---------------------------------------------------------------------------------
 //
 //  Little Color Management System
-//  Copyright (c) 1998-2024 Marti Maria Saguer
+//  Copyright (c) 1998-2026 Marti Maria Saguer
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the "Software"),

From 2008683accbe783ccb8e19157c0794a8a3af8f2c Mon Sep 17 00:00:00 2001
From: Roman Marchenko 
Date: Thu, 19 Mar 2026 15:24:09 +0000
Subject: [PATCH 059/168] 8376104: C2 crashes in PhiNode::Ideal(PhaseGVN*,
 bool) accessing NULL pointer

Backport-of: 947d5b6a0205f1d6d516521693dd29d543dada08
---
 src/hotspot/share/opto/cfgnode.cpp | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/hotspot/share/opto/cfgnode.cpp b/src/hotspot/share/opto/cfgnode.cpp
index 92f6c938dbac..cdcfe3a6b351 100644
--- a/src/hotspot/share/opto/cfgnode.cpp
+++ b/src/hotspot/share/opto/cfgnode.cpp
@@ -2580,6 +2580,10 @@ Node *PhiNode::Ideal(PhaseGVN *phase, bool can_reshape) {
     for( uint i=1; i
Date: Fri, 20 Mar 2026 06:45:58 +0000
Subject: [PATCH 060/168] 8144124: [macosx] The tabs can't be aligned when we
 pressing the key of 'R','B','L','C' or 'T'.

Backport-of: 15dd96f7a68f634124c73d78659212e7f335230e
---
 test/jdk/ProblemList.txt | 1 -
 1 file changed, 1 deletion(-)

diff --git a/test/jdk/ProblemList.txt b/test/jdk/ProblemList.txt
index a905e6ff0c3f..92f499a927c0 100644
--- a/test/jdk/ProblemList.txt
+++ b/test/jdk/ProblemList.txt
@@ -783,7 +783,6 @@ java/foreign/TestBufferStackStress.java                         8350455 macosx-a
 javax/swing/JFileChooser/6698013/bug6698013.java 8024419 macosx-all
 javax/swing/JColorChooser/8065098/bug8065098.java 8065647 macosx-all
 javax/swing/JTabbedPane/bug4499556.java 8267500 macosx-all
-javax/swing/JTabbedPane/bug4666224.java 8144124  macosx-all
 javax/swing/JTabbedPane/TestJTabbedPaneOpaqueColor.java 8345090 windows-all,linux-all
 javax/swing/SwingUtilities/TestTextPosInPrint.java 8227025 windows-all
 javax/swing/JInternalFrame/bug4134077.java 8184985 windows-all

From 1292cda71e0cd7607a57d7ddb78eb2bf6f6bcf3e Mon Sep 17 00:00:00 2001
From: Goetz Lindenmaier 
Date: Fri, 20 Mar 2026 06:46:19 +0000
Subject: [PATCH 061/168] 8064922: [macos] Test
 javax/swing/JTabbedPane/4624207/bug4624207.java fails

Backport-of: be18e7ecfd2e89a0abb168e0d9a5b69598e2199f
---
 test/jdk/ProblemList.txt                      |  1 -
 .../swing/JTabbedPane/4624207/bug4624207.java | 67 ++++++++-----------
 2 files changed, 28 insertions(+), 40 deletions(-)

diff --git a/test/jdk/ProblemList.txt b/test/jdk/ProblemList.txt
index 92f499a927c0..32318100b05f 100644
--- a/test/jdk/ProblemList.txt
+++ b/test/jdk/ProblemList.txt
@@ -679,7 +679,6 @@ javax/swing/AbstractButton/6711682/bug6711682.java 8060765 windows-all,macosx-al
 javax/swing/JFileChooser/6396844/TwentyThousandTest.java 8198003 generic-all
 javax/swing/JFileChooser/8194044/FileSystemRootTest.java 8327236 windows-all
 javax/swing/JPopupMenu/6800513/bug6800513.java 7184956 macosx-all
-javax/swing/JTabbedPane/4624207/bug4624207.java 8064922 macosx-all
 javax/swing/SwingUtilities/TestBadBreak/TestBadBreak.java 8160720 generic-all
 javax/swing/JFileChooser/bug6798062.java 8146446 windows-all
 javax/swing/JPopupMenu/4870644/bug4870644.java 8194130 macosx-all,linux-all
diff --git a/test/jdk/javax/swing/JTabbedPane/4624207/bug4624207.java b/test/jdk/javax/swing/JTabbedPane/4624207/bug4624207.java
index 10de2ab221ad..4d2fdcf030cd 100644
--- a/test/jdk/javax/swing/JTabbedPane/4624207/bug4624207.java
+++ b/test/jdk/javax/swing/JTabbedPane/4624207/bug4624207.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -25,25 +25,26 @@
  * @test
  * @key headful
  * @bug 4624207
+ * @requires (os.family != "mac")
  * @summary JTabbedPane mnemonics don't work from outside the tabbed pane
- * @author Oleg Mokhovikov
- * @library /test/lib
- * @library ../../regtesthelpers
- * @build Util jdk.test.lib.Platform
  * @run main bug4624207
  */
-import javax.swing.*;
-import javax.swing.event.ChangeEvent;
-import javax.swing.event.ChangeListener;
-import java.awt.*;
+
+import java.awt.BorderLayout;
+import java.awt.Robot;
 import java.awt.event.FocusEvent;
 import java.awt.event.FocusListener;
 import java.awt.event.KeyEvent;
 
-import jdk.test.lib.Platform;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JTabbedPane;
+import javax.swing.JTextField;
+import javax.swing.SwingUtilities;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
 
 public class bug4624207 implements ChangeListener, FocusListener {
-
     private static volatile boolean stateChanged = false;
     private static volatile boolean focusGained = false;
     private static JTextField txtField;
@@ -71,51 +72,39 @@ public static void main(String[] args) throws Exception {
             Robot robot = new Robot();
             robot.setAutoDelay(50);
 
-            SwingUtilities.invokeAndWait(new Runnable() {
-
-                public void run() {
-                    createAndShowGUI();
-                }
-            });
-
+            SwingUtilities.invokeAndWait(() -> createAndShowGUI());
             robot.waitForIdle();
-
-            SwingUtilities.invokeAndWait(new Runnable() {
-
-                public void run() {
-                    txtField.requestFocus();
-                }
-            });
-
+            SwingUtilities.invokeAndWait(() -> txtField.requestFocus());
             robot.waitForIdle();
 
             if (!focusGained) {
                 throw new RuntimeException("Couldn't gain focus for text field");
             }
 
-            SwingUtilities.invokeAndWait(new Runnable() {
-
-                public void run() {
-                    tab.addChangeListener((ChangeListener) listener);
-                    txtField.removeFocusListener((FocusListener) listener);
-                }
+            SwingUtilities.invokeAndWait(() -> {
+                tab.addChangeListener((ChangeListener) listener);
+                txtField.removeFocusListener((FocusListener) listener);
             });
 
             robot.waitForIdle();
 
-            if (Platform.isOSX()) {
-                Util.hitKeys(robot, KeyEvent.VK_CONTROL, KeyEvent.VK_ALT, KeyEvent.VK_B);
-            } else {
-                Util.hitKeys(robot, KeyEvent.VK_ALT, KeyEvent.VK_B);
-            }
+            robot.keyPress(KeyEvent.VK_ALT);
+            robot.keyPress(KeyEvent.VK_B);
+            robot.keyRelease(KeyEvent.VK_B);
+            robot.keyRelease(KeyEvent.VK_ALT);
 
             robot.waitForIdle();
 
             if (!stateChanged || tab.getSelectedIndex() != 1) {
-                throw new RuntimeException("JTabbedPane mnemonics don't work from outside the tabbed pane");
+                throw new RuntimeException("JTabbedPane mnemonics don't " +
+                        "work from outside the tabbed pane");
             }
         } finally {
-            if (frame != null) SwingUtilities.invokeAndWait(() ->  frame.dispose());
+            SwingUtilities.invokeAndWait(() -> {
+                if (frame != null) {
+                    frame.dispose();
+                }
+            });
         }
     }
 

From 7672013814cf0a7ecac18cd27f30a0a46a9df33e Mon Sep 17 00:00:00 2001
From: William Kemper 
Date: Fri, 20 Mar 2026 16:07:13 +0000
Subject: [PATCH 062/168] 8361726: Shenandoah: More detailed evacuation
 instrumentation

Backport-of: e756c0dbbb7d99df0751d71726b173e4eabcc903
---
 .../gc/shenandoah/shenandoahControlThread.cpp |  6 ++
 .../gc/shenandoah/shenandoahEvacTracker.cpp   | 80 ++++++++++++-------
 .../gc/shenandoah/shenandoahEvacTracker.hpp   | 56 ++++++++++---
 .../shenandoah/shenandoahGenerationalHeap.cpp | 17 +---
 .../shenandoah/shenandoahGenerationalHeap.hpp |  6 --
 .../share/gc/shenandoah/shenandoahHeap.cpp    | 12 ++-
 .../share/gc/shenandoah/shenandoahHeap.hpp    |  8 ++
 .../shenandoah/shenandoahThreadLocalData.cpp  |  5 +-
 .../shenandoah/shenandoahThreadLocalData.hpp  | 10 +--
 9 files changed, 130 insertions(+), 70 deletions(-)

diff --git a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp
index 9cab6807bd06..52ecd811b9c0 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp
@@ -218,6 +218,12 @@ void ShenandoahControlThread::run_service() {
           if (ShenandoahPacing) {
             heap->pacer()->print_cycle_on(&ls);
           }
+#ifdef NOT_PRODUCT
+          ShenandoahEvacuationTracker* evac_tracker = heap->evac_tracker();
+          ShenandoahCycleStats         evac_stats   = evac_tracker->flush_cycle_to_global();
+          evac_tracker->print_evacuations_on(&ls, &evac_stats.workers,
+                                                  &evac_stats.mutators);
+#endif
         }
       }
 
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.cpp b/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.cpp
index b1d474fa78da..499e1342083e 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.cpp
@@ -30,10 +30,22 @@
 #include "runtime/thread.hpp"
 #include "runtime/threadSMR.inline.hpp"
 
+ShenandoahEvacuationStats::ShenandoahEvacuations* ShenandoahEvacuationStats::get_category(
+  ShenandoahAffiliation from,
+  ShenandoahAffiliation to) {
+  if (from == YOUNG_GENERATION) {
+    if (to == YOUNG_GENERATION) {
+      return &_young;
+    }
+    assert(to == OLD_GENERATION, "If not evacuating to young, must be promotion to old");
+    return &_promotion;
+  }
+  assert(from == OLD_GENERATION, "If not evacuating from young, then must be from old");
+  return &_old;
+}
+
 ShenandoahEvacuationStats::ShenandoahEvacuationStats()
-  : _evacuations_completed(0), _bytes_completed(0),
-    _evacuations_attempted(0), _bytes_attempted(0),
-    _use_age_table(ShenandoahGenerationalCensusAtEvac || !ShenandoahGenerationalAdaptiveTenuring),
+  : _use_age_table(ShenandoahGenerationalCensusAtEvac || !ShenandoahGenerationalAdaptiveTenuring),
     _age_table(nullptr) {
   if (_use_age_table) {
     _age_table = new AgeTable(false);
@@ -45,14 +57,17 @@ AgeTable* ShenandoahEvacuationStats::age_table() const {
   return _age_table;
 }
 
-void ShenandoahEvacuationStats::begin_evacuation(size_t bytes) {
-  ++_evacuations_attempted;
-  _bytes_attempted += bytes;
+void ShenandoahEvacuationStats::begin_evacuation(size_t bytes, ShenandoahAffiliation from, ShenandoahAffiliation to) {
+  ShenandoahEvacuations* category = get_category(from, to);
+  category->_evacuations_attempted++;
+  category->_bytes_attempted += bytes;
+
 }
 
-void ShenandoahEvacuationStats::end_evacuation(size_t bytes) {
-  ++_evacuations_completed;
-  _bytes_completed += bytes;
+void ShenandoahEvacuationStats::end_evacuation(size_t bytes, ShenandoahAffiliation from, ShenandoahAffiliation to) {
+  ShenandoahEvacuations* category = get_category(from, to);
+  category->_evacuations_completed++;
+  category->_bytes_completed += bytes;
 }
 
 void ShenandoahEvacuationStats::record_age(size_t bytes, uint age) {
@@ -63,34 +78,39 @@ void ShenandoahEvacuationStats::record_age(size_t bytes, uint age) {
 }
 
 void ShenandoahEvacuationStats::accumulate(const ShenandoahEvacuationStats* other) {
-  _evacuations_completed += other->_evacuations_completed;
-  _bytes_completed += other->_bytes_completed;
-  _evacuations_attempted += other->_evacuations_attempted;
-  _bytes_attempted += other->_bytes_attempted;
+  _young.accumulate(other->_young);
+  _old.accumulate(other->_old);
+  _promotion.accumulate(other->_promotion);
+
   if (_use_age_table) {
     _age_table->merge(other->age_table());
   }
 }
 
 void ShenandoahEvacuationStats::reset() {
-  _evacuations_completed = _evacuations_attempted = 0;
-  _bytes_completed = _bytes_attempted = 0;
+  _young.reset();
+  _old.reset();
+  _promotion.reset();
+
   if (_use_age_table) {
     _age_table->clear();
   }
 }
 
-void ShenandoahEvacuationStats::print_on(outputStream* st) {
-#ifndef PRODUCT
+void ShenandoahEvacuationStats::ShenandoahEvacuations::print_on(outputStream* st) const {
   size_t abandoned_size = _bytes_attempted - _bytes_completed;
   size_t abandoned_count = _evacuations_attempted - _evacuations_completed;
-  st->print_cr("Evacuated %zu%s across %zu objects, "
-            "abandoned %zu%s across %zu objects.",
-            byte_size_in_proper_unit(_bytes_completed), proper_unit_for_byte_size(_bytes_completed),
-            _evacuations_completed,
-            byte_size_in_proper_unit(abandoned_size),   proper_unit_for_byte_size(abandoned_size),
-            abandoned_count);
-#endif
+  st->print_cr("Evacuated " PROPERFMT" across %zu objects, "
+            "abandoned " PROPERFMT " across %zu objects.",
+            PROPERFMTARGS(_bytes_completed), _evacuations_completed,
+            PROPERFMTARGS(abandoned_size), abandoned_count);
+}
+
+void ShenandoahEvacuationStats::print_on(outputStream* st) const {
+  st->print("Young: "); _young.print_on(st);
+  st->print("Promotion: "); _promotion.print_on(st);
+  st->print("Old: "); _old.print_on(st);
+
   if (_use_age_table) {
     _age_table->print_on(st);
   }
@@ -103,10 +123,10 @@ void ShenandoahEvacuationTracker::print_global_on(outputStream* st) {
 void ShenandoahEvacuationTracker::print_evacuations_on(outputStream* st,
                                                        ShenandoahEvacuationStats* workers,
                                                        ShenandoahEvacuationStats* mutators) {
-  st->print("Workers: ");
+  st->print_cr("Workers: ");
   workers->print_on(st);
   st->cr();
-  st->print("Mutators: ");
+  st->print_cr("Mutators: ");
   mutators->print_on(st);
   st->cr();
 
@@ -160,12 +180,12 @@ ShenandoahCycleStats ShenandoahEvacuationTracker::flush_cycle_to_global() {
   return {workers, mutators};
 }
 
-void ShenandoahEvacuationTracker::begin_evacuation(Thread* thread, size_t bytes) {
-  ShenandoahThreadLocalData::begin_evacuation(thread, bytes);
+void ShenandoahEvacuationTracker::begin_evacuation(Thread* thread, size_t bytes, ShenandoahAffiliation from, ShenandoahAffiliation to) {
+  ShenandoahThreadLocalData::begin_evacuation(thread, bytes, from, to);
 }
 
-void ShenandoahEvacuationTracker::end_evacuation(Thread* thread, size_t bytes) {
-  ShenandoahThreadLocalData::end_evacuation(thread, bytes);
+void ShenandoahEvacuationTracker::end_evacuation(Thread* thread, size_t bytes, ShenandoahAffiliation from, ShenandoahAffiliation to) {
+  ShenandoahThreadLocalData::end_evacuation(thread, bytes, from, to);
 }
 
 void ShenandoahEvacuationTracker::record_age(Thread* thread, size_t bytes, uint age) {
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.hpp b/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.hpp
index 7d195656b111..e5d7a7fec944 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.hpp
@@ -26,14 +26,45 @@
 #define SHARE_GC_SHENANDOAH_SHENANDOAHEVACTRACKER_HPP
 
 #include "gc/shared/ageTable.hpp"
+#include "gc/shenandoah/shenandoahAffiliation.hpp"
 #include "utilities/ostream.hpp"
 
 class ShenandoahEvacuationStats : public CHeapObj {
 private:
-  size_t _evacuations_completed;
-  size_t _bytes_completed;
-  size_t _evacuations_attempted;
-  size_t _bytes_attempted;
+  struct ShenandoahEvacuations {
+    size_t _evacuations_completed;
+    size_t _bytes_completed;
+    size_t _evacuations_attempted;
+    size_t _bytes_attempted;
+    ShenandoahEvacuations()
+      : _evacuations_completed(0)
+      , _bytes_completed(0)
+      , _evacuations_attempted(0)
+      , _bytes_attempted(0) {
+    }
+
+    void accumulate(const ShenandoahEvacuations& other) {
+      _evacuations_completed += other._evacuations_completed;
+      _bytes_completed += other._bytes_completed;
+      _evacuations_attempted += other._evacuations_attempted;
+      _bytes_attempted += other._bytes_attempted;
+    }
+
+    void reset() {
+      _evacuations_completed = 0;
+      _bytes_completed = 0;
+      _evacuations_attempted = 0;
+      _bytes_attempted = 0;
+    }
+
+    void print_on(outputStream* st) const;
+  };
+
+  ShenandoahEvacuations* get_category(ShenandoahAffiliation from, ShenandoahAffiliation to);
+
+  ShenandoahEvacuations _young;
+  ShenandoahEvacuations _old;
+  ShenandoahEvacuations _promotion;
 
   bool      _use_age_table;
   AgeTable* _age_table;
@@ -43,11 +74,14 @@ class ShenandoahEvacuationStats : public CHeapObj {
 
   AgeTable* age_table() const;
 
-  void begin_evacuation(size_t bytes);
-  void end_evacuation(size_t bytes);
+  // Record that the current thread is attempting to copy this many bytes.
+  void begin_evacuation(size_t bytes, ShenandoahAffiliation from, ShenandoahAffiliation to);
+
+  // Record that the current thread has completed copying this many bytes.
+  void end_evacuation(size_t bytes, ShenandoahAffiliation from, ShenandoahAffiliation to);
   void record_age(size_t bytes, uint age);
 
-  void print_on(outputStream* st);
+  void print_on(outputStream* st) const;
   void accumulate(const ShenandoahEvacuationStats* other);
   void reset();
 };
@@ -66,8 +100,12 @@ class ShenandoahEvacuationTracker : public CHeapObj {
 public:
   ShenandoahEvacuationTracker() = default;
 
-  void begin_evacuation(Thread* thread, size_t bytes);
-  void end_evacuation(Thread* thread, size_t bytes);
+  // Record that the given thread has begun to evacuate an object of this size.
+  void begin_evacuation(Thread* thread, size_t bytes, ShenandoahAffiliation from, ShenandoahAffiliation to);
+
+  // Multiple threads may attempt to evacuate the same object, but only the successful thread will end the evacuation.
+  // Evacuations that were begun, but not ended are considered 'abandoned'.
+  void end_evacuation(Thread* thread, size_t bytes, ShenandoahAffiliation from, ShenandoahAffiliation to);
   void record_age(Thread* thread, size_t bytes, uint age);
 
   void print_global_on(outputStream* st);
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp
index feb82dd05271..188c41aec3a6 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp
@@ -80,7 +80,6 @@ size_t ShenandoahGenerationalHeap::unsafe_max_tlab_alloc(Thread *thread) const {
 ShenandoahGenerationalHeap::ShenandoahGenerationalHeap(ShenandoahCollectorPolicy* policy) :
   ShenandoahHeap(policy),
   _age_census(nullptr),
-  _evac_tracker(new ShenandoahEvacuationTracker()),
   _min_plab_size(calculate_min_plab()),
   _max_plab_size(calculate_max_plab()),
   _regulator_thread(nullptr),
@@ -100,18 +99,6 @@ void ShenandoahGenerationalHeap::print_init_logger() const {
   logger.print_all();
 }
 
-void ShenandoahGenerationalHeap::print_tracing_info() const {
-  ShenandoahHeap::print_tracing_info();
-
-  LogTarget(Info, gc, stats) lt;
-  if (lt.is_enabled()) {
-    LogStream ls(lt);
-    ls.cr();
-    ls.cr();
-    evac_tracker()->print_global_on(&ls);
-  }
-}
-
 void ShenandoahGenerationalHeap::initialize_heuristics() {
   // Initialize global generation and heuristics even in generational mode.
   ShenandoahHeap::initialize_heuristics();
@@ -338,7 +325,7 @@ oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, Shena
   }
 
   // Copy the object:
-  NOT_PRODUCT(evac_tracker()->begin_evacuation(thread, size * HeapWordSize));
+  NOT_PRODUCT(evac_tracker()->begin_evacuation(thread, size * HeapWordSize, from_region->affiliation(), target_gen));
   Copy::aligned_disjoint_words(cast_from_oop(p), copy, size);
   oop copy_val = cast_to_oop(copy);
 
@@ -360,7 +347,7 @@ oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, Shena
     ContinuationGCSupport::relativize_stack_chunk(copy_val);
 
     // Record that the evacuation succeeded
-    NOT_PRODUCT(evac_tracker()->end_evacuation(thread, size * HeapWordSize));
+    NOT_PRODUCT(evac_tracker()->end_evacuation(thread, size * HeapWordSize, from_region->affiliation(), target_gen));
 
     if (target_gen == OLD_GENERATION) {
       old_generation()->handle_evacuation(copy, size, from_region->is_young());
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.hpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.hpp
index fb356873356d..adf9c73a2322 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.hpp
@@ -53,7 +53,6 @@ class ShenandoahGenerationalHeap : public ShenandoahHeap {
   }
 
   void print_init_logger() const override;
-  void print_tracing_info() const override;
 
   size_t unsafe_max_tlab_alloc(Thread *thread) const override;
 
@@ -64,8 +63,6 @@ class ShenandoahGenerationalHeap : public ShenandoahHeap {
   ShenandoahSharedFlag  _is_aging_cycle;
   // Age census used for adapting tenuring threshold
   ShenandoahAgeCensus* _age_census;
-  // Used primarily to look for failed evacuation attempts.
-  ShenandoahEvacuationTracker*  _evac_tracker;
 
 public:
   void set_aging_cycle(bool cond) {
@@ -83,9 +80,6 @@ class ShenandoahGenerationalHeap : public ShenandoahHeap {
 
   inline bool is_tenurable(const ShenandoahHeapRegion* r) const;
 
-  ShenandoahEvacuationTracker* evac_tracker() const {
-    return _evac_tracker;
-  }
 
   // Ages regions that haven't been used for allocations in the current cycle.
   // Resets ages for regions that have been used for allocations.
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp
index a56ada222d7e..41b848d06890 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp
@@ -574,7 +574,8 @@ ShenandoahHeap::ShenandoahHeap(ShenandoahCollectorPolicy* policy) :
   _bitmap_region_special(false),
   _aux_bitmap_region_special(false),
   _liveness_cache(nullptr),
-  _collection_set(nullptr)
+  _collection_set(nullptr),
+  _evac_tracker(new ShenandoahEvacuationTracker())
 {
   // Initialize GC mode early, many subsequent initialization procedures depend on it
   initialize_mode();
@@ -1395,6 +1396,7 @@ oop ShenandoahHeap::try_evacuate_object(oop p, Thread* thread, ShenandoahHeapReg
   }
 
   // Copy the object:
+  NOT_PRODUCT(evac_tracker()->begin_evacuation(thread, size * HeapWordSize, from_region->affiliation(), target_gen));
   Copy::aligned_disjoint_words(cast_from_oop(p), copy, size);
 
   // Try to install the new forwarding pointer.
@@ -1404,6 +1406,7 @@ oop ShenandoahHeap::try_evacuate_object(oop p, Thread* thread, ShenandoahHeapReg
     // Successfully evacuated. Our copy is now the public one!
     ContinuationGCSupport::relativize_stack_chunk(copy_val);
     shenandoah_assert_correct(nullptr, copy_val);
+    NOT_PRODUCT(evac_tracker()->end_evacuation(thread, size * HeapWordSize, from_region->affiliation(), target_gen));
     return copy_val;
   }  else {
     // Failed to evacuate. We need to deal with the object that is left behind. Since this
@@ -1633,6 +1636,13 @@ void ShenandoahHeap::print_tracing_info() const {
     ResourceMark rm;
     LogStream ls(lt);
 
+#ifdef NOT_PRODUCT
+    evac_tracker()->print_global_on(&ls);
+
+    ls.cr();
+    ls.cr();
+#endif
+
     phase_timings()->print_global_on(&ls);
 
     ls.cr();
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp
index 509ba1db9c2d..a2dd53370356 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp
@@ -557,6 +557,10 @@ class ShenandoahHeap : public CollectedHeap {
 
   ShenandoahEvacOOMHandler*  oom_evac_handler()        { return &_oom_evac_handler; }
 
+  ShenandoahEvacuationTracker* evac_tracker() const {
+    return _evac_tracker;
+  }
+
   void on_cycle_start(GCCause::Cause cause, ShenandoahGeneration* generation);
   void on_cycle_end(ShenandoahGeneration* generation);
 
@@ -789,6 +793,10 @@ class ShenandoahHeap : public CollectedHeap {
 
   oop try_evacuate_object(oop src, Thread* thread, ShenandoahHeapRegion* from_region, ShenandoahAffiliation target_gen);
 
+protected:
+  // Used primarily to look for failed evacuation attempts.
+  ShenandoahEvacuationTracker*  _evac_tracker;
+
 public:
   static address in_cset_fast_test_addr();
 
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.cpp b/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.cpp
index c444a0ba86a6..dd500462d0ff 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.cpp
@@ -44,10 +44,7 @@ ShenandoahThreadLocalData::ShenandoahThreadLocalData() :
   _plab_promoted(0),
   _plab_allows_promotion(true),
   _plab_retries_enabled(true),
-  _evacuation_stats(nullptr) {
-  if (ShenandoahHeap::heap()->mode()->is_generational()) {
-    _evacuation_stats = new ShenandoahEvacuationStats();
-  }
+  _evacuation_stats(new ShenandoahEvacuationStats()) {
 }
 
 ShenandoahThreadLocalData::~ShenandoahThreadLocalData() {
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.hpp b/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.hpp
index c1cebdf1ddef..098e20a72ec8 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.hpp
@@ -30,6 +30,7 @@
 #include "gc/shared/gcThreadLocalData.hpp"
 #include "gc/shared/plab.hpp"
 #include "gc/shenandoah/mode/shenandoahMode.hpp"
+#include "gc/shenandoah/shenandoahAffiliation.hpp"
 #include "gc/shenandoah/shenandoahBarrierSet.hpp"
 #include "gc/shenandoah/shenandoahCardTable.hpp"
 #include "gc/shenandoah/shenandoahCodeRoots.hpp"
@@ -159,12 +160,12 @@ class ShenandoahThreadLocalData {
     data(thread)->_gclab_size = v;
   }
 
-  static void begin_evacuation(Thread* thread, size_t bytes) {
-    data(thread)->_evacuation_stats->begin_evacuation(bytes);
+  static void begin_evacuation(Thread* thread, size_t bytes, ShenandoahAffiliation from, ShenandoahAffiliation to) {
+    data(thread)->_evacuation_stats->begin_evacuation(bytes, from, to);
   }
 
-  static void end_evacuation(Thread* thread, size_t bytes) {
-    data(thread)->_evacuation_stats->end_evacuation(bytes);
+  static void end_evacuation(Thread* thread, size_t bytes, ShenandoahAffiliation from, ShenandoahAffiliation to) {
+    data(thread)->_evacuation_stats->end_evacuation(bytes, from, to);
   }
 
   static void record_age(Thread* thread, size_t bytes, uint age) {
@@ -172,7 +173,6 @@ class ShenandoahThreadLocalData {
   }
 
   static ShenandoahEvacuationStats* evacuation_stats(Thread* thread) {
-    shenandoah_assert_generational();
     return data(thread)->_evacuation_stats;
   }
 

From 622fb19106ed6647807d910538a18a308f6c6cc4 Mon Sep 17 00:00:00 2001
From: Matthias Baesken 
Date: Mon, 23 Mar 2026 07:45:28 +0000
Subject: [PATCH 063/168] 8379416: AIX build fails if system (not GNU) date
 tool is in PATH

Backport-of: ef55947162c1853e453b79b66e2e971c8625a638
---
 make/autoconf/basic_tools.m4 | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/make/autoconf/basic_tools.m4 b/make/autoconf/basic_tools.m4
index 0f5691759b6d..0fa001c5c90d 100644
--- a/make/autoconf/basic_tools.m4
+++ b/make/autoconf/basic_tools.m4
@@ -1,5 +1,5 @@
 #
-# Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2011, 2026, Oracle and/or its affiliates. All rights reserved.
 # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 #
 # This code is free software; you can redistribute it and/or modify it
@@ -384,6 +384,10 @@ AC_DEFUN_ONCE([BASIC_SETUP_COMPLEX_TOOLS],
     IS_GNU_DATE=yes
   else
     AC_MSG_RESULT([no])
+    # Likely at the AIX provided version of the date utility here, which is not compatible
+    if test "x$OPENJDK_TARGET_OS" = "xaix"; then
+      AC_MSG_ERROR([gnu date from AIX toolbox is required])
+    fi
     IS_GNU_DATE=no
   fi
   AC_SUBST(IS_GNU_DATE)

From 88182afea1d1b31946466912f8800339c561e63d Mon Sep 17 00:00:00 2001
From: Roland Mesde 
Date: Mon, 23 Mar 2026 15:26:23 +0000
Subject: [PATCH 064/168] 8378201: [OGL] glXMakeContextCurrent() drops the
 buffers of the unbound drawable 8369561:
 sun/java2d/OpenGL/DrawBitmaskImage.java#id0: Incorrect color for first pixel
 (actual=ff000000)

Backport-of: e24a8f06e30a0889b1fb5689ac3d4180f90d25d4
---
 .../common/java2d/opengl/GLXSurfaceData.c     | 100 ++++++++++++-
 test/jdk/ProblemList.txt                      |   1 +
 .../sun/java2d/OpenGL/FlipCoexistTest.java    | 134 ++++++++++++++++++
 .../java2d/OpenGL/MultiWindowFillTest.java    | 122 ++++++++++++++++
 4 files changed, 352 insertions(+), 5 deletions(-)
 create mode 100644 test/jdk/sun/java2d/OpenGL/FlipCoexistTest.java
 create mode 100644 test/jdk/sun/java2d/OpenGL/MultiWindowFillTest.java

diff --git a/src/java.desktop/unix/native/common/java2d/opengl/GLXSurfaceData.c b/src/java.desktop/unix/native/common/java2d/opengl/GLXSurfaceData.c
index c48b38fa1f3e..2ed575c10c7c 100644
--- a/src/java.desktop/unix/native/common/java2d/opengl/GLXSurfaceData.c
+++ b/src/java.desktop/unix/native/common/java2d/opengl/GLXSurfaceData.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2003, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -40,6 +40,8 @@
 
 #ifndef HEADLESS
 
+#include 
+
 extern LockFunc       OGLSD_Lock;
 extern GetRasInfoFunc OGLSD_GetRasInfo;
 extern UnlockFunc     OGLSD_Unlock;
@@ -50,6 +52,74 @@ extern void
 
 jboolean surfaceCreationFailed = JNI_FALSE;
 
+/**
+ * Per-Window GLXWindow entry with reference counting.
+ * Stored in an XContext keyed by the X Window XID.
+ */
+typedef struct {
+    GLXWindow glxWindow;
+    int       refCount;
+} GLXWindowRef;
+
+static XContext glxWindowContext;
+
+/**
+ * Gets or creates a shared GLXWindow for the given X Window.
+ * All callers are synchronized by the AWT lock.
+ */
+static GLXWindow acquireGLXWindow(Window window, GLXFBConfig fbconfig)
+{
+    if (glxWindowContext == 0) {
+        glxWindowContext = XUniqueContext();
+    }
+
+    XPointer data;
+    if (XFindContext(awt_display, window, glxWindowContext, &data) == 0) {
+        GLXWindowRef *ref = (GLXWindowRef *)data;
+        ref->refCount++;
+        return ref->glxWindow;
+    }
+
+    GLXWindow glxWin = j2d_glXCreateWindow(awt_display, fbconfig, window, NULL);
+    if (glxWin == 0) {
+        return 0;
+    }
+
+    GLXWindowRef *ref = malloc(sizeof(*ref));
+    if (ref == NULL) {
+        j2d_glXDestroyWindow(awt_display, glxWin);
+        return 0;
+    }
+    ref->glxWindow = glxWin;
+    ref->refCount = 1;
+    if (XSaveContext(awt_display, window, glxWindowContext, (XPointer)ref) != 0)
+    {
+        j2d_glXDestroyWindow(awt_display, glxWin);
+        free(ref);
+        return 0;
+    }
+    return glxWin;
+}
+
+/**
+ * Decrements the reference count for the GLXWindow associated with the given
+ * X Window. Destroys it when the count reaches zero.
+ * All callers are synchronized by the AWT lock.
+ */
+static void releaseGLXWindow(Window window)
+{
+    XPointer data;
+    if (XFindContext(awt_display, window, glxWindowContext, &data) != 0) {
+        return;
+    }
+    GLXWindowRef *ref = (GLXWindowRef *)data;
+    if (--ref->refCount <= 0) {
+        j2d_glXDestroyWindow(awt_display, ref->glxWindow);
+        XDeleteContext(awt_display, window, glxWindowContext);
+        free(ref);
+    }
+}
+
 #endif /* !HEADLESS */
 
 JNIEXPORT void JNICALL
@@ -74,7 +144,7 @@ Java_sun_java2d_opengl_GLXSurfaceData_initOps(JNIEnv *env, jobject glxsd,
     // later the graphicsConfig will be used for deallocation of oglsdo
     oglsdo->graphicsConfig = gc;
 
-    GLXSDOps *glxsdo = (GLXSDOps *)malloc(sizeof(GLXSDOps));
+    GLXSDOps *glxsdo = (GLXSDOps *)calloc(1, sizeof(GLXSDOps));
 
     if (glxsdo == NULL) {
         JNU_ThrowOutOfMemoryError(env, "creating native GLX ops");
@@ -125,8 +195,13 @@ Java_sun_java2d_opengl_GLXSurfaceData_initOps(JNIEnv *env, jobject glxsd,
 void
 OGLSD_DestroyOGLSurface(JNIEnv *env, OGLSDOps *oglsdo)
 {
+    GLXSDOps *glxsdo = (GLXSDOps *)oglsdo->privOps;
     J2dTraceLn(J2D_TRACE_INFO, "OGLSD_DestroyOGLSurface");
-    // X Window is free'd later by AWT code...
+    if (glxsdo != NULL && glxsdo->drawable != 0) {
+        releaseGLXWindow(glxsdo->window);
+        glxsdo->drawable = 0;
+        oglsdo->drawableType = OGLSD_UNDEFINED;
+    }
 }
 
 /**
@@ -296,6 +371,13 @@ OGLSD_InitOGLWindow(JNIEnv *env, OGLSDOps *oglsdo)
         return JNI_FALSE;
     }
 
+    glxsdo->drawable = acquireGLXWindow(window,
+                                        glxsdo->configData->glxInfo->fbconfig);
+    if (glxsdo->drawable == 0) {
+        J2dRlsTraceLn(J2D_TRACE_ERROR, "OGLSD_InitOGLWindow: GLXWindow is 0");
+        return JNI_FALSE;
+    }
+
     XGetWindowAttributes(awt_display, window, &attr);
     oglsdo->width = attr.width;
     oglsdo->height = attr.height;
@@ -304,7 +386,6 @@ OGLSD_InitOGLWindow(JNIEnv *env, OGLSDOps *oglsdo)
     oglsdo->isOpaque = JNI_TRUE;
     oglsdo->xOffset = 0;
     oglsdo->yOffset = 0;
-    glxsdo->drawable = window;
     glxsdo->xdrawable = window;
 
     J2dTraceLn2(J2D_TRACE_VERBOSE, "  created window: w=%d h=%d",
@@ -333,7 +414,16 @@ OGLSD_SwapBuffers(JNIEnv *env, jlong window)
         return;
     }
 
-    j2d_glXSwapBuffers(awt_display, (Window)window);
+    XPointer data;
+    if (XFindContext(awt_display, (Window)window, glxWindowContext, &data) != 0)
+    {
+        J2dRlsTraceLn(J2D_TRACE_ERROR,
+                      "OGLSD_SwapBuffers: GLXWindow not found");
+        return;
+    }
+
+    GLXWindowRef *ref = (GLXWindowRef *)data;
+    j2d_glXSwapBuffers(awt_display, ref->glxWindow);
 }
 
 // needed by Mac OS X port, no-op on other platforms
diff --git a/test/jdk/ProblemList.txt b/test/jdk/ProblemList.txt
index 32318100b05f..c7d6e9ef2241 100644
--- a/test/jdk/ProblemList.txt
+++ b/test/jdk/ProblemList.txt
@@ -248,6 +248,7 @@ sun/awt/datatransfer/SuplementaryCharactersTransferTest.java 8011371 generic-all
 sun/awt/shell/ShellFolderMemoryLeak.java 8197794 windows-all
 sun/java2d/DirectX/OverriddenInsetsTest/OverriddenInsetsTest.java 8196102 generic-all
 sun/java2d/DirectX/RenderingToCachedGraphicsTest/RenderingToCachedGraphicsTest.java 8196180 windows-all,macosx-all
+sun/java2d/OpenGL/MultiWindowFillTest.java 8378506 macosx-all
 sun/java2d/OpenGL/OpaqueDest.java#id1 8367574 macosx-all
 sun/java2d/OpenGL/ScaleParamsOOB.java#id0 8377908 linux-all
 sun/java2d/SunGraphics2D/EmptyClipRenderingTest.java 8144029 macosx-all,linux-all
diff --git a/test/jdk/sun/java2d/OpenGL/FlipCoexistTest.java b/test/jdk/sun/java2d/OpenGL/FlipCoexistTest.java
new file mode 100644
index 000000000000..beb5da887b4b
--- /dev/null
+++ b/test/jdk/sun/java2d/OpenGL/FlipCoexistTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.awt.Color;
+import java.awt.Frame;
+import java.awt.Graphics;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.Robot;
+import java.awt.image.BufferStrategy;
+import java.awt.image.BufferedImage;
+import java.io.File;
+
+import javax.imageio.ImageIO;
+
+/**
+ * @test
+ * @bug 8378201
+ * @key headful
+ * @summary Verifies that WINDOW and FLIP_BACKBUFFER surfaces sharing the same X
+ *          Window render and flip correctly
+ * @run main/othervm FlipCoexistTest
+ * @run main/othervm -Dsun.java2d.opengl=True FlipCoexistTest
+ */
+public final class FlipCoexistTest {
+
+    private static final int SIZE = 200;
+    private static final int TOLERANCE = 10;
+
+    public static void main(String[] args) throws Exception {
+        Frame f = new Frame("FlipCoexistTest");
+        try {
+            f.setUndecorated(true);
+            f.setSize(SIZE, SIZE);
+            f.setLocation(100, 100);
+            f.setVisible(true);
+
+            Robot robot = new Robot();
+            robot.waitForIdle();
+            robot.delay(1000);
+
+            int w = f.getWidth();
+            int h = f.getHeight();
+
+            // Fill window RED via direct render (WINDOW surface)
+            Graphics g = f.getGraphics();
+            g.setColor(Color.RED);
+            g.fillRect(0, 0, w, h);
+            g.dispose();
+            robot.waitForIdle();
+            robot.delay(500);
+
+            // Request flip if available, blit is also useful to cover
+            f.createBufferStrategy(2);
+            BufferStrategy bs = f.getBufferStrategy();
+
+            // Render BLUE to back buffer, do not flip yet
+            Graphics bg = bs.getDrawGraphics();
+            bg.setColor(Color.BLUE);
+            bg.fillRect(0, 0, w, h);
+            bg.dispose();
+
+            // Paint small GREEN rect via direct render
+            g = f.getGraphics();
+            g.setColor(Color.GREEN);
+            g.fillRect(0, 0, 10, 10);
+            g.dispose();
+            robot.waitForIdle();
+            robot.delay(500);
+
+            // GREEN rect must be visible
+            check(robot, f, 5, 5, Color.GREEN, "small rect");
+
+            // RED must survive the context round-trip
+            check(robot, f, w / 2, h / 2, Color.RED, "survived");
+
+            // Show back buffer, BLUE must appear
+            bs.show();
+
+            robot.waitForIdle();
+            robot.delay(500);
+            check(robot, f, w / 2, h / 2, Color.BLUE, "flip");
+        } finally {
+            f.dispose();
+        }
+    }
+
+    private static void check(Robot robot, Frame frame, int x, int y, Color exp,
+                              String desc)
+    {
+        Point loc = frame.getLocationOnScreen();
+        Color c = robot.getPixelColor(loc.x + x, loc.y + y);
+        if (!isAlmostEqual(c, exp)) {
+            saveImage(robot, frame, desc);
+            throw new RuntimeException("%s: %s != %s".formatted(desc, exp, c));
+        }
+    }
+
+    private static void saveImage(Robot r, Frame f, String name) {
+        try {
+            Rectangle rect = f.getBounds();
+            BufferedImage img = r.createScreenCapture(rect);
+            ImageIO.write(img, "png", new File(name + ".png"));
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    private static boolean isAlmostEqual(Color c1, Color c2) {
+        return Math.abs(c1.getRed() - c2.getRed()) <= TOLERANCE
+                && Math.abs(c1.getGreen() - c2.getGreen()) <= TOLERANCE
+                && Math.abs(c1.getBlue() - c2.getBlue()) <= TOLERANCE;
+    }
+}
diff --git a/test/jdk/sun/java2d/OpenGL/MultiWindowFillTest.java b/test/jdk/sun/java2d/OpenGL/MultiWindowFillTest.java
new file mode 100644
index 000000000000..59c58d944d79
--- /dev/null
+++ b/test/jdk/sun/java2d/OpenGL/MultiWindowFillTest.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.awt.Color;
+import java.awt.Frame;
+import java.awt.Graphics;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.Robot;
+import java.awt.image.BufferedImage;
+import java.io.File;
+
+import javax.imageio.ImageIO;
+
+/**
+ * @test
+ * @bug 8378201
+ * @key headful
+ * @summary Verifies that window content survives a GL context switch to another
+ *          window and back
+ * @run main/othervm MultiWindowFillTest
+ * @run main/othervm -Dsun.java2d.opengl=True MultiWindowFillTest
+ */
+public final class MultiWindowFillTest {
+
+    private static final int SIZE = 100;
+    private static final int TOLERANCE = 10;
+
+    public static void main(String[] args) throws Exception {
+        Frame f1 = new Frame("f1");
+        Frame f2 = new Frame("f2");
+        try {
+            f1.setUndecorated(true);
+            f1.setSize(SIZE, SIZE);
+            f1.setLocation(100, 100);
+            f2.setUndecorated(true);
+            f2.setSize(SIZE, SIZE);
+            f2.setLocation(300, 100);
+
+            f1.setVisible(true);
+            f2.setVisible(true);
+
+            Robot robot = new Robot();
+            robot.waitForIdle();
+            robot.delay(1000);
+
+            int w = f1.getWidth();
+            int h = f1.getHeight();
+
+            // Fill both, initializes surfaces
+            fill(f1, Color.RED, w, h);
+            fill(f2, Color.BLUE, w, h);
+
+            // Touch both again
+            fill(f1, Color.RED, 2, 2);
+            fill(f2, Color.BLUE, 2, 2);
+
+            robot.waitForIdle();
+            robot.delay(1000);
+
+            check(robot, f1, w, h, Color.RED, "f1 red");
+            check(robot, f2, w, h, Color.BLUE, "f2 blue");
+        } finally {
+            f1.dispose();
+            f2.dispose();
+        }
+    }
+
+    private static void fill(Frame frame, Color c, int w, int h) {
+        Graphics g = frame.getGraphics();
+        g.setColor(c);
+        g.fillRect(0, 0, w, h);
+        g.dispose();
+    }
+
+    private static void check(Robot robot, Frame frame, int w, int h,
+                              Color exp, String desc)
+    {
+        Point loc = frame.getLocationOnScreen();
+        Color c = robot.getPixelColor(loc.x + w / 2, loc.y + h / 2);
+        if (!isAlmostEqual(c, exp)) {
+            saveImage(robot, frame, desc);
+            throw new RuntimeException("%s: %s != %s".formatted(desc, exp, c));
+        }
+    }
+
+    private static void saveImage(Robot r, Frame f, String name) {
+        try {
+            Rectangle rect = f.getBounds();
+            BufferedImage img = r.createScreenCapture(rect);
+            ImageIO.write(img, "png", new File(name + ".png"));
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    private static boolean isAlmostEqual(Color c1, Color c2) {
+        return Math.abs(c1.getRed() - c2.getRed()) <= TOLERANCE
+                && Math.abs(c1.getGreen() - c2.getGreen()) <= TOLERANCE
+                && Math.abs(c1.getBlue() - c2.getBlue()) <= TOLERANCE;
+    }
+}

From 6d19aa8ae1d99487056c861b59878a2715a39d57 Mon Sep 17 00:00:00 2001
From: Roland Mesde 
Date: Mon, 23 Mar 2026 15:27:19 +0000
Subject: [PATCH 065/168] 8373676: Test
 javax/net/ssl/HttpsURLConnection/SubjectAltNameIP.java fails on a machine
 without IPV6

Backport-of: 9e2008bf5e9a63b640eefc6cc7ec5c4f344c4266
---
 .../net/ssl/HttpsURLConnection/SubjectAltNameIP.java  | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/test/jdk/javax/net/ssl/HttpsURLConnection/SubjectAltNameIP.java b/test/jdk/javax/net/ssl/HttpsURLConnection/SubjectAltNameIP.java
index 2def2f69d6e9..cbd2089e7bdf 100644
--- a/test/jdk/javax/net/ssl/HttpsURLConnection/SubjectAltNameIP.java
+++ b/test/jdk/javax/net/ssl/HttpsURLConnection/SubjectAltNameIP.java
@@ -30,7 +30,7 @@
  * @modules java.base/sun.net.util
  * @comment Insert -Djavax.net.debug=all into the following lines to enable SSL debugging
  * @run main/othervm SubjectAltNameIP 127.0.0.1
- * @run main/othervm SubjectAltNameIP [::1]
+ * @run main/othervm SubjectAltNameIP ::1
  */
 
 import javax.net.ssl.HandshakeCompletedListener;
@@ -166,14 +166,19 @@ void doClientSide() throws Exception {
     }
 
     public static void main(String[] args) throws Exception {
+        boolean isIpv6Addr = IPAddressUtil.isIPv6LiteralAddress(args[0]);
 
-        if (IPAddressUtil.isIPv6LiteralAddress(args[0]) && !IPSupport.hasIPv6()) {
+        if (isIpv6Addr && !IPSupport.hasIPv6()) {
             throw new SkippedException("Skipping test - IPv6 is not supported");
         }
         /*
          * Start the tests.
          */
-        new SubjectAltNameIP(args[0]);
+        if (isIpv6Addr) { // use the URL notion wrapper
+            new SubjectAltNameIP("[" + args[0] + "]");
+        } else {
+            new SubjectAltNameIP(args[0]);
+        }
     }
 
     Thread serverThread = null;

From bb350a460697b831d14b4d5a06550ab4cef24939 Mon Sep 17 00:00:00 2001
From: Roland Mesde 
Date: Mon, 23 Mar 2026 15:29:53 +0000
Subject: [PATCH 066/168] 8359412: Template-Framework Library: Operations and
 Expressions

Backport-of: 0496806102bb621bdd82613d5796651d9655ea1c
---
 .../jtreg/compiler/igvn/ExpressionFuzzer.java | 349 +++++++++++++
 .../library/Expression.java                   | 474 ++++++++++++++++++
 .../library/Operations.java                   | 253 ++++++++++
 .../library/PrimitiveType.java                |  90 ++++
 .../examples/TestExpressions.java             | 116 +++++
 .../examples/TestPrimitiveTypes.java          |  47 ++
 .../tests/TestExpression.java                 | 282 +++++++++++
 7 files changed, 1611 insertions(+)
 create mode 100644 test/hotspot/jtreg/compiler/igvn/ExpressionFuzzer.java
 create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/library/Expression.java
 create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/library/Operations.java
 create mode 100644 test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestExpressions.java
 create mode 100644 test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestExpression.java

diff --git a/test/hotspot/jtreg/compiler/igvn/ExpressionFuzzer.java b/test/hotspot/jtreg/compiler/igvn/ExpressionFuzzer.java
new file mode 100644
index 000000000000..60b11e8ffbc4
--- /dev/null
+++ b/test/hotspot/jtreg/compiler/igvn/ExpressionFuzzer.java
@@ -0,0 +1,349 @@
+/*
+ * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8359412
+ * @summary Use the template framework library to generate random expressions.
+ * @modules java.base/jdk.internal.misc
+ * @library /test/lib /
+ * @compile ../lib/verify/Verify.java
+ * @run main/othervm -XX:+IgnoreUnrecognizedVMOptions -XX:CompileTaskTimeout=10000 compiler.igvn.ExpressionFuzzer
+ */
+
+package compiler.igvn;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Set;
+import java.util.Random;
+import java.util.Collections;
+import jdk.test.lib.Utils;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import compiler.lib.compile_framework.*;
+import compiler.lib.template_framework.Template;
+import compiler.lib.template_framework.TemplateToken;
+import static compiler.lib.template_framework.Template.body;
+import static compiler.lib.template_framework.Template.let;
+import static compiler.lib.template_framework.Template.$;
+import compiler.lib.template_framework.library.CodeGenerationDataNameType;
+import compiler.lib.template_framework.library.Expression;
+import compiler.lib.template_framework.library.Operations;
+import compiler.lib.template_framework.library.PrimitiveType;
+import compiler.lib.template_framework.library.TestFrameworkClass;
+import static compiler.lib.template_framework.library.CodeGenerationDataNameType.PRIMITIVE_TYPES;
+
+// We generate random Expressions from primitive type operators.
+//
+// The goal is to generate random inputs with constrained TypeInt / TypeLong ranges / KnownBits,
+// and then verify the output value, ranges and bits.
+//
+// Should this test fail and make a lot of noise in the CI, you have two choices:
+// - Problem-list this test: but other tests may also use the same broken operators.
+// - Temporarily remove the operator from {@code Operations.PRIMITIVE_OPERATIONS}.
+//
+// Future Work [FUTURE]:
+// - Constrain also the unsigned bounds
+// - Some basic IR tests to ensure that the constraints / checksum mechanics work.
+//   We may even have to add some IGVN optimizations to be able to better observe things right.
+// - Lower the CompileTaskTimeout, if possible. It is chosen conservatively (rather high) for now.
+public class ExpressionFuzzer {
+    private static final Random RANDOM = Utils.getRandomInstance();
+
+    public static record MethodArgument(String name, CodeGenerationDataNameType type) {}
+    public static record StringPair(String s0, String s1) {}
+
+    public static void main(String[] args) {
+        // Create a new CompileFramework instance.
+        CompileFramework comp = new CompileFramework();
+
+        // Add a java source file.
+        comp.addJavaSourceCode("compiler.igvn.templated.ExpressionFuzzerInnerTest", generate(comp));
+
+        // Compile the source file.
+        comp.compile();
+
+        // compiler.igvn.templated.InnterTest.main(new String[] {});
+        comp.invoke("compiler.igvn.templated.ExpressionFuzzerInnerTest", "main", new Object[] {new String[] {}});
+    }
+
+    // Generate a Java source file as String
+    public static String generate(CompileFramework comp) {
+        // Generate a list of test methods.
+        List tests = new ArrayList<>();
+
+        // We are going to use some random numbers in our tests, so import some good methods for that.
+        tests.add(PrimitiveType.generateLibraryRNG());
+
+        // Create the body for the test. We use it twice: compiled and reference.
+        // Execute the expression and catch expected Exceptions.
+        var bodyTemplate = Template.make("expression", "arguments", "checksum", (Expression expression, List arguments, String checksum) -> body(
+            """
+            try {
+            """,
+            "var val = ", expression.asToken(arguments), ";\n",
+            "return #checksum(val);\n",
+            expression.info.exceptions.stream().map(exception ->
+                "} catch (" + exception + " e) { return e;\n"
+            ).toList(),
+            """
+            } finally {
+                // Just so that javac is happy if there are no exceptions to catch.
+            }
+            """
+        ));
+
+        // Machinery for the "checksum" method.
+        //
+        // We want to do output verification. We don't just want to check if the output value is correct,
+        // but also if the signed/unsigned/KnownBits are correct of the TypeInt and TypeLong. For this,
+        // we add some comparisons. If we get the ranges/bits wrong (too tight), then the comparisons
+        // can wrongly constant fold, and we can detect that in the output array.
+        List unsignedCmp = List.of(
+            new StringPair("(val < ", ")"),
+            new StringPair("(val > ", ")"),
+            new StringPair("(val >= ", ")"),
+            new StringPair("(val <= ", ")"),
+            new StringPair("(val != ", ")"),
+            new StringPair("(val == ", ")"),
+            new StringPair("(val & ", ")") // Extract bits
+        );
+
+        List intCmp = List.of(
+            new StringPair("(val < ", ")"),
+            new StringPair("(val > ", ")"),
+            new StringPair("(val >= ", ")"),
+            new StringPair("(val <= ", ")"),
+            new StringPair("(val != ", ")"),
+            new StringPair("(val == ", ")"),
+            new StringPair("(val & ", ")"), // Extract bits
+            new StringPair("(Integer.compareUnsigned(val, ", ") > 0)"),
+            new StringPair("(Integer.compareUnsigned(val, ", ") < 0)"),
+            new StringPair("(Integer.compareUnsigned(val, ", ") >= 0)"),
+            new StringPair("(Integer.compareUnsigned(val, ", ") <= 0)")
+        );
+
+        List longCmp = List.of(
+            new StringPair("(val < ", ")"),
+            new StringPair("(val > ", ")"),
+            new StringPair("(val >= ", ")"),
+            new StringPair("(val <= ", ")"),
+            new StringPair("(val != ", ")"),
+            new StringPair("(val == ", ")"),
+            new StringPair("(val & ", ")"), // Extract bits
+            new StringPair("(Long.compareUnsigned(val, ", ") > 0)"),
+            new StringPair("(Long.compareUnsigned(val, ", ") < 0)"),
+            new StringPair("(Long.compareUnsigned(val, ", ") >= 0)"),
+            new StringPair("(Long.compareUnsigned(val, ", ") <= 0)")
+        );
+
+        var integralCmpTemplate = Template.make("type", (CodeGenerationDataNameType type) -> {
+            List cmps = switch(type.name()) {
+                case "char" -> unsignedCmp;
+                case "byte", "short", "int" -> intCmp;
+                case "long" -> longCmp;
+                default -> throw new RuntimeException("not handled: " + type.name());
+            };
+            StringPair cmp = cmps.get(RANDOM.nextInt(cmps.size()));
+            return body(
+                ", ", cmp.s0(), type.con(), cmp.s1()
+            );
+        });
+
+        // Checksum method: returns not just the value, but also does some range / bit checks.
+        //                  This gives us enhanced verification on the range / bits of the result type.
+        var checksumTemplate = Template.make("expression", "checksum", (Expression expression, String checksum) -> body(
+            let("returnType", expression.returnType),
+            """
+            @ForceInline
+            public static Object #checksum(#returnType val) {
+            """,
+            "return new Object[] {",
+            switch(expression.returnType.name()) {
+                // The integral values have signed/unsigned ranges and known bits.
+                // Return val, but also some range and bits tests to see if those
+                // ranges and bits are correct.
+                case "byte", "short", "char", "int", "long" ->
+                    List.of("val", Collections.nCopies(20, integralCmpTemplate.asToken(expression.returnType)));
+                // Float/Double have no range, just return the value:
+                case "float", "double" -> "val";
+                // Check if the boolean constant folded:
+                case "boolean" -> "val, val == true, val == false";
+                default -> throw new RuntimeException("should only be primitive types");
+            }
+            , "};\n",
+            """
+            }
+            """
+        ));
+
+        // We need to prepare some random values to pass into the test method. We generate the values
+        // once, and pass the same values into both the compiled and reference method.
+        var valueTemplate = Template.make("name", "type", (String name, CodeGenerationDataNameType type) -> body(
+            "#type #name = ",
+            (type instanceof PrimitiveType pt) ? pt.callLibraryRNG() : type.con(),
+            ";\n"
+        ));
+
+        // At the beginning of the compiled and reference test methods we receive the arguments,
+        // which have their full bottom_type (e.g. TypeInt: int). We now constrain the ranges and
+        // bits, for the types that allow it.
+        //
+        // To ensure that both the compiled and reference method use the same constraint, we put
+        // the computation in a ForceInline method.
+        var constrainArgumentMethodTemplate = Template.make("name", "type", (String name, CodeGenerationDataNameType type) -> body(
+            """
+            @ForceInline
+            public static #type constrain_#name(#type v) {
+            """,
+            switch(type.name()) {
+                // These currently have no type ranges / bits.
+                // Booleans do have an int-range, but restricting it would just make it constant, which
+                // is not very useful: we would like to keep it variable here. We already mix in variable
+                // arguments and constants in the testTemplate.
+                case "boolean", "float", "double" -> "return v;\n";
+                case "byte", "short", "char", "int", "long" -> List.of(
+                    // Sometimes constrain the signed range
+                    //   v = min(max(v, CON1), CON2)
+                    (RANDOM.nextInt(2) == 0)
+                    ? List.of("v = (#type)Math.min(Math.max(v, ", type.con(),"), ", type.con() ,");\n")
+                    : List.of(),
+                    // Sometimes constrain the bits:
+                    //   v = (v & CON1) | CON2
+                    // Note:
+                    //   and (&): forces some bits to zero
+                    //   or  (|): forces some bits to one
+                    (RANDOM.nextInt(2) == 0)
+                    ? List.of("v = (#type)((v & ", type.con(),") | ", type.con() ,");\n")
+                    : List.of(),
+                    // FUTURE: we could also constrain the unsigned bounds.
+                    "return v;\n");
+                default -> throw new RuntimeException("should only be primitive types");
+            },
+            """
+            }
+            """
+        ));
+
+        var constrainArgumentTemplate = Template.make("name", (String name) -> body(
+            """
+            #name = constrain_#name(#name);
+            """
+        ));
+
+        // The template that generates the whole test machinery needed for testing a given expression.
+        // Generates:
+        // - @Test method: generate arguments and call compiled and reference test with it.
+        //                 result verification (only if the result is known to be deterministic).
+        //
+        // - instantiate compiled and reference test methods.
+        // - instantiate argument constraint methods (constrains test method arguments types).
+        // - instantiate checksum method (summarizes value and bounds/bit checks).
+        var testTemplate = Template.make("expression", (Expression expression) -> {
+            // Fix the arguments for both the compiled and reference method.
+            // We have a mix of variable and constant inputs to the expression.
+            // The variable inputs are passed as method arguments to the test methods.
+            List methodArguments = new ArrayList<>();
+            List expressionArguments = new ArrayList<>();
+            for (CodeGenerationDataNameType type : expression.argumentTypes) {
+                switch (RANDOM.nextInt(2)) {
+                    case 0 -> {
+                        String name = $("arg" + methodArguments.size());
+                        methodArguments.add(new MethodArgument(name, type));
+                        expressionArguments.add(name);
+                    }
+                    default -> {
+                        expressionArguments.add(type.con());
+                    }
+                }
+            }
+            return body(
+                let("methodArguments",
+                    methodArguments.stream().map(ma -> ma.name).collect(Collectors.joining(", "))),
+                let("methodArgumentsWithTypes",
+                    methodArguments.stream().map(ma -> ma.type + " " + ma.name).collect(Collectors.joining(", "))),
+                """
+                @Test
+                public static void $primitiveConTest() {
+                    // In each iteration, generate new random values for the method arguments.
+                """,
+                methodArguments.stream().map(ma -> valueTemplate.asToken(ma.name, ma.type)).toList(),
+                """
+                    Object v0 = ${primitiveConTest}_compiled(#methodArguments);
+                    Object v1 = ${primitiveConTest}_reference(#methodArguments);
+                """,
+                expression.info.isResultDeterministic ? "Verify.checkEQ(v0, v1);\n" : "// could fail - don't verify.\n",
+                """
+                }
+
+                @DontInline
+                public static Object ${primitiveConTest}_compiled(#methodArgumentsWithTypes) {
+                """,
+                // The arguments now have the bottom_type. Constrain the ranges and bits.
+                methodArguments.stream().map(ma -> constrainArgumentTemplate.asToken(ma.name)).toList(),
+                // Generate the body with the expression, and calling the checksum.
+                bodyTemplate.asToken(expression, expressionArguments, $("checksum")),
+                """
+                }
+
+                @DontCompile
+                public static Object ${primitiveConTest}_reference(#methodArgumentsWithTypes) {
+                """,
+                methodArguments.stream().map(ma -> constrainArgumentTemplate.asToken(ma.name)).toList(),
+                bodyTemplate.asToken(expression, expressionArguments, $("checksum")),
+                """
+                }
+
+                """,
+                methodArguments.stream().map(ma -> constrainArgumentMethodTemplate.asToken(ma.name, ma.type)).toList(),
+                checksumTemplate.asToken(expression, $("checksum"))
+            );
+        });
+
+        // Generate expressions with the primitive types.
+        for (PrimitiveType type : PRIMITIVE_TYPES) {
+            for (int i = 0; i < 10; i++) {
+                // The depth determines roughly how many operations are going to be used in the expression.
+                int depth = RANDOM.nextInt(1, 20);
+                Expression expression = Expression.nestRandomly(type, Operations.PRIMITIVE_OPERATIONS, depth);
+                tests.add(testTemplate.asToken(expression));
+            }
+        }
+
+        // Create the test class, which runs all tests.
+        return TestFrameworkClass.render(
+            // package and class name.
+            "compiler.igvn.templated", "ExpressionFuzzerInnerTest",
+            // Set of imports.
+            Set.of("compiler.lib.verify.*",
+                   "java.util.Random",
+                   "jdk.test.lib.Utils",
+                   "compiler.lib.generators.*"),
+            // classpath, so the Test VM has access to the compiled class files.
+            comp.getEscapedClassPathOfCompiledClasses(),
+            // The list of tests.
+            tests);
+    }
+}
diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/library/Expression.java b/test/hotspot/jtreg/compiler/lib/template_framework/library/Expression.java
new file mode 100644
index 000000000000..360937c8f7fc
--- /dev/null
+++ b/test/hotspot/jtreg/compiler/lib/template_framework/library/Expression.java
@@ -0,0 +1,474 @@
+/*
+ * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package compiler.lib.template_framework.library;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Set;
+import java.util.Random;
+import jdk.test.lib.Utils;
+import java.util.stream.Stream;
+import java.util.stream.Collectors;
+
+import compiler.lib.template_framework.Template;
+import compiler.lib.template_framework.TemplateToken;
+import static compiler.lib.template_framework.Template.body;
+
+/**
+ * {@link Expression}s model Java expressions, that have a list of arguments with specified
+ * argument types, and a result with a specified result type. Once can {@link #make} a new
+ * {@link Expression} or use existing ones from {@link Operations}.
+ *
+ * 

+ * The {@link Expression}s are composable, they can be explicitly {@link #nest}ed, or randomly + * combined using {@link #nestRandomly}. + * + *

+ * Finally, they can be used in a {@link Template} as a {@link TemplateToken} by calling + * {@link #asToken} with the required arguments. + */ +public class Expression { + private static final Random RANDOM = Utils.getRandomInstance(); + + /** + * Specifies the return type of the {@link Expression}. + */ + public final CodeGenerationDataNameType returnType; + + + /** + * Specifies the types of the arguments. + */ + public final List argumentTypes; + + final List strings; + + /** + * Provides additional information about the {@link Expression}. + */ + public final Info info; + + private Expression(CodeGenerationDataNameType returnType, + List argumentTypes, + List strings, + Info info) { + if (argumentTypes.size() + 1 != strings.size()) { + throw new RuntimeException("Must have one more string than argument."); + } + this.returnType = returnType; + this.argumentTypes = List.copyOf(argumentTypes); + this.strings = List.copyOf(strings); + this.info = info; + } + + + /** + * Specifies additional information for an {@link Expression}. + */ + public static class Info { + /** + * Set of exceptions the {@link Exception} could throw when executed. + * By default, we assume that an {@link Expression} throws no exceptions. + */ + public final Set exceptions; + + /** + * Specifies if the result of the {@link Expression} is guaranteed to + * be deterministic. This allows exact result verification, for example + * by comparing compiler and interpreter results. However, there are some + * operations that do not always return the same exact result, which can + * for example happen with {@code Float.floatToRawIntBits} in combination + * with more than one {@code NaN} bit representations. + * By default, we assume that an {@link Expression} is deterministic. + */ + public final boolean isResultDeterministic; + + /** + * Create a default {@link Info}. + */ + public Info() { + this.exceptions = Set.of(); + this.isResultDeterministic = true; + } + + private Info(Set exceptions, boolean isResultDeterministic) { + this.exceptions = Set.copyOf(exceptions); + this.isResultDeterministic = isResultDeterministic; + } + + /** + * Creates a new {@link Info} with additional exceptions that the {@link Expression} could throw. + * + * @param exceptions the exceptions to be added. + * @return a new {@link Info} instance with the added exceptions. + */ + public Info withExceptions(Set exceptions) { + exceptions = Stream.concat(this.exceptions.stream(), exceptions.stream()) + .collect(Collectors.toSet()); + return new Info(exceptions, this.isResultDeterministic); + } + + /** + * Creates a new {@link Info} that specifies that the {@link Exception} may return + * indeterministic results, which prevents exact result verification. + * + * @return a new {@link Info} instance that specifies indeterministic results. + */ + public Info withNondeterministicResult() { + return new Info(this.exceptions, false); + } + + Info combineWith(Info other) { + Info info = this.withExceptions(other.exceptions); + if (!other.isResultDeterministic) { + info = info.withNondeterministicResult(); + } + return info; + } + } + + /** + * Creates a new Expression with 1 arguments. + * + * @param returnType The return type of the {@link Expression}. + * @param s0 The first string, to be placed before {@code t0}. + * @param t0 The type of the first argument. + * @param s1 The last string, finishing the {@link Expression}. + * @return the new {@link Expression}. + */ + public static Expression make(CodeGenerationDataNameType returnType, + String s0, + CodeGenerationDataNameType t0, + String s1) { + return make(returnType, s0, t0, s1, new Info()); + } + + /** + * Creates a new Expression with 1 argument. + * + * @param returnType The return type of the {@link Expression}. + * @param s0 The first string, to be placed before {@code t0}. + * @param t0 The type of the first argument. + * @param s1 The last string, finishing the {@link Expression}. + * @param info Additional information about the {@link Expression}. + * @return the new {@link Expression}. + */ + public static Expression make(CodeGenerationDataNameType returnType, + String s0, + CodeGenerationDataNameType t0, + String s1, + Info info) { + return new Expression(returnType, List.of(t0), List.of(s0, s1), info); + } + + /** + * Creates a new Expression with 2 arguments. + * + * @param returnType The return type of the {@link Expression}. + * @param s0 The first string, to be placed before {@code t0}. + * @param t0 The type of the first argument. + * @param s1 The second string, to be placed before {@code t1}. + * @param t1 The type of the second argument. + * @param s2 The last string, finishing the {@link Expression}. + * @return the new {@link Expression}. + */ + public static Expression make(CodeGenerationDataNameType returnType, + String s0, + CodeGenerationDataNameType t0, + String s1, + CodeGenerationDataNameType t1, + String s2) { + return make(returnType, s0, t0, s1, t1, s2, new Info()); + } + + /** + * Creates a new Expression with 2 arguments. + * + * @param returnType The return type of the {@link Expression}. + * @param s0 The first string, to be placed before {@code t0}. + * @param t0 The type of the first argument. + * @param s1 The second string, to be placed before {@code t1}. + * @param t1 The type of the second argument. + * @param s2 The last string, finishing the {@link Expression}. + * @param info Additional information about the {@link Expression}. + * @return the new {@link Expression}. + */ + public static Expression make(CodeGenerationDataNameType returnType, + String s0, + CodeGenerationDataNameType t0, + String s1, + CodeGenerationDataNameType t1, + String s2, + Info info) { + return new Expression(returnType, List.of(t0, t1), List.of(s0, s1, s2), info); + } + + /** + * Creates a new Expression with 3 arguments. + * + * @param returnType The return type of the {@link Expression}. + * @param s0 The first string, to be placed before {@code t0}. + * @param t0 The type of the first argument. + * @param s1 The second string, to be placed before {@code t1}. + * @param t1 The type of the second argument. + * @param s2 The third string, to be placed before {@code t2}. + * @param t2 The type of the third argument. + * @param s3 The last string, finishing the {@link Expression}. + * @return the new {@link Expression}. + */ + public static Expression make(CodeGenerationDataNameType returnType, + String s0, + CodeGenerationDataNameType t0, + String s1, + CodeGenerationDataNameType t1, + String s2, + CodeGenerationDataNameType t2, + String s3) { + return make(returnType, s0, t0, s1, t1, s2, t2, s3, new Info()); + } + + /** + * Creates a new Expression with 3 arguments. + * + * @param returnType The return type of the {@link Expression}. + * @param s0 The first string, to be placed before {@code t0}. + * @param t0 The type of the first argument. + * @param s1 The second string, to be placed before {@code t1}. + * @param t1 The type of the second argument. + * @param s2 The third string, to be placed before {@code t2}. + * @param t2 The type of the third argument. + * @param s3 The last string, finishing the {@link Expression}. + * @param info Additional information about the {@link Expression}. + * @return the new {@link Expression}. + */ + public static Expression make(CodeGenerationDataNameType returnType, + String s0, + CodeGenerationDataNameType t0, + String s1, + CodeGenerationDataNameType t1, + String s2, + CodeGenerationDataNameType t2, + String s3, + Info info) { + return new Expression(returnType, List.of(t0, t1, t2), List.of(s0, s1, s2, s3), info); + } + + /** + * Creates a new Expression with 4 arguments. + * + * @param returnType The return type of the {@link Expression}. + * @param s0 The first string, to be placed before {@code t0}. + * @param t0 The type of the first argument. + * @param s1 The second string, to be placed before {@code t1}. + * @param t1 The type of the second argument. + * @param s2 The third string, to be placed before {@code t2}. + * @param t2 The type of the third argument. + * @param s3 The fourth string, to be placed before {@code t3}. + * @param t3 The type of the fourth argument. + * @param s4 The last string, finishing the {@link Expression}. + * @return the new {@link Expression}. + */ + public static Expression make(CodeGenerationDataNameType returnType, + String s0, + CodeGenerationDataNameType t0, + String s1, + CodeGenerationDataNameType t1, + String s2, + CodeGenerationDataNameType t2, + String s3, + CodeGenerationDataNameType t3, + String s4) { + return make(returnType, s0, t0, s1, t1, s2, t2, s3, t3, s4, new Info()); + } + + /** + * Creates a new Expression with 4 arguments. + * + * @param returnType The return type of the {@link Expression}. + * @param s0 The first string, to be placed before {@code t0}. + * @param t0 The type of the first argument. + * @param s1 The second string, to be placed before {@code t1}. + * @param t1 The type of the second argument. + * @param s2 The third string, to be placed before {@code t2}. + * @param t2 The type of the third argument. + * @param s3 The fourth string, to be placed before {@code t3}. + * @param t3 The type of the fourth argument. + * @param s4 The last string, finishing the {@link Expression}. + * @param info Additional information about the {@link Expression}. + * @return the new {@link Expression}. + */ + public static Expression make(CodeGenerationDataNameType returnType, + String s0, + CodeGenerationDataNameType t0, + String s1, + CodeGenerationDataNameType t1, + String s2, + CodeGenerationDataNameType t2, + String s3, + CodeGenerationDataNameType t3, + String s4, + Info info) { + return new Expression(returnType, List.of(t0, t1, t2, t3), List.of(s0, s1, s2, s3, s4), info); + } + + /** + * Creates a {@link TemplateToken} for the use in a {@link Template} by applying the + * {@code arguments} to the {@link Expression}. It is the users responsibility to + * ensure that the argument tokens match the required {@link #argumentTypes}. + * + * @param arguments the tokens to be passed as arguments into the {@link Expression}. + * @return a {@link TemplateToken} representing the {@link Expression} with applied arguments, + * for the use in a {@link Template}. + */ + public TemplateToken asToken(List arguments) { + if (arguments.size() != argumentTypes.size()) { + throw new IllegalArgumentException("Wrong number of arguments:" + + " expected: " + argumentTypes.size() + + " but got: " + arguments.size() + + " for " + this); + } + + // List of tokens: interleave strings and arguments. + List tokens = new ArrayList<>(); + for (int i = 0; i < argumentTypes.size(); i++) { + tokens.add(strings.get(i)); + tokens.add(arguments.get(i)); + } + tokens.add(strings.getLast()); + + var template = Template.make(() -> body( + tokens + )); + return template.asToken(); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + + sb.append("Expression["); + + for (int i = 0; i < this.argumentTypes.size(); i++) { + sb.append("\""); + sb.append(this.strings.get(i)); + sb.append("\", "); + sb.append(this.argumentTypes.get(i).toString()); + sb.append(", "); + } + sb.append("\""); + sb.append(this.strings.getLast()); + sb.append("\"]"); + return sb.toString(); + } + + /** + * Create a nested {@link Expression} with a specified {@code returnType} from a + * set of {@code expressions}. + * + * @param returnType the type of the return value. + * @param expressions the list of {@link Expression}s from which we sample to create + * the nested {@link Expression}. + * @param maxNumberOfUsedExpressions the maximal number of {@link Expression}s from the + * {@code expressions} are nested. + * @return a new randomly nested {@link Expression}. + */ + public static Expression nestRandomly(CodeGenerationDataNameType returnType, + List expressions, + int maxNumberOfUsedExpressions) { + List filtered = expressions.stream().filter(e -> e.returnType.isSubtypeOf(returnType)).toList(); + + if (filtered.isEmpty()) { + throw new IllegalArgumentException("Found no exception with the specified returnType."); + } + + int r = RANDOM.nextInt(filtered.size()); + Expression expression = filtered.get(r); + + for (int i = 1; i < maxNumberOfUsedExpressions; i++) { + expression = expression.nestRandomly(expressions); + } + return expression; + } + + /** + * Nests a random {@link Expression} from {@code nestingExpressions} into a random argument of + * {@code this} {@link Expression}, ensuring compatibility of argument and return type. + * + * @param nestingExpressions list of expressions we sample from for the inner {@link Expression}. + * @return a new nested {@link Expression}. + */ + public Expression nestRandomly(List nestingExpressions) { + int argumentIndex = RANDOM.nextInt(this.argumentTypes.size()); + CodeGenerationDataNameType argumentType = this.argumentTypes.get(argumentIndex); + List filtered = nestingExpressions.stream().filter(e -> e.returnType.isSubtypeOf(argumentType)).toList(); + + if (filtered.isEmpty()) { + // Found no expression that has a matching returnType. + return this; + } + + int r = RANDOM.nextInt(filtered.size()); + Expression expression = filtered.get(r); + + return this.nest(argumentIndex, expression); + } + + /** + * Nests the {@code nestingExpression} into the specified {@code argumentIndex} of + * {@code this} {@link Expression}. + * + * @param argumentIndex the index specifying at which argument of {@code this} + * {@link Expression} we inser the {@code nestingExpression}. + * @param nestingExpression the inner {@link Expression}. + * @return a new nested {@link Expression}. + */ + public Expression nest(int argumentIndex, Expression nestingExpression) { + if (!nestingExpression.returnType.isSubtypeOf(this.argumentTypes.get(argumentIndex))) { + throw new IllegalArgumentException("Cannot nest expressions because of mismatched types."); + } + + List newArgumentTypes = new ArrayList<>(); + List newStrings = new ArrayList<>(); + // s0 t0 s1 [S0 T0 S1 T1 S2] s2 t2 s3 + for (int i = 0; i < argumentIndex; i++) { + newStrings.add(this.strings.get(i)); + newArgumentTypes.add(this.argumentTypes.get(i)); + } + newStrings.add(this.strings.get(argumentIndex) + + nestingExpression.strings.getFirst()); // concat s1 and S0 + newArgumentTypes.add(nestingExpression.argumentTypes.getFirst()); + for (int i = 1; i < nestingExpression.argumentTypes.size(); i++) { + newStrings.add(nestingExpression.strings.get(i)); + newArgumentTypes.add(nestingExpression.argumentTypes.get(i)); + } + newStrings.add(nestingExpression.strings.getLast() + + this.strings.get(argumentIndex + 1)); // concat S2 and s2 + for (int i = argumentIndex+1; i < this.argumentTypes.size(); i++) { + newArgumentTypes.add(this.argumentTypes.get(i)); + newStrings.add(this.strings.get(i+1)); + } + + return new Expression(this.returnType, newArgumentTypes, newStrings, this.info.combineWith(nestingExpression.info)); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/library/Operations.java b/test/hotspot/jtreg/compiler/lib/template_framework/library/Operations.java new file mode 100644 index 000000000000..53acf943b20d --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/library/Operations.java @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.lib.template_framework.library; + +import java.util.List; +import java.util.ArrayList; +import java.util.Set; + +import static compiler.lib.template_framework.library.PrimitiveType.BYTES; +import static compiler.lib.template_framework.library.PrimitiveType.SHORTS; +import static compiler.lib.template_framework.library.PrimitiveType.CHARS; +import static compiler.lib.template_framework.library.PrimitiveType.INTS; +import static compiler.lib.template_framework.library.PrimitiveType.LONGS; +import static compiler.lib.template_framework.library.PrimitiveType.FLOATS; +import static compiler.lib.template_framework.library.PrimitiveType.DOUBLES; +import static compiler.lib.template_framework.library.PrimitiveType.BOOLEANS; + +/** + * This class provides various lists of {@link Expression}s, that represent Java operators or library + * methods. For example, we represent arithmetic operations on primitive types. + */ +public final class Operations { + + // private constructor to avoid instantiation. + private Operations() {} + + /** + * Provides a lits of operations on {@link PrimitiveType}s, such as arithmetic, logical, + * and cast operations. + */ + public static final List PRIMITIVE_OPERATIONS = generatePrimitiveOperations(); + + private static List generatePrimitiveOperations() { + List ops = new ArrayList<>(); + + Expression.Info withArithmeticException = new Expression.Info().withExceptions(Set.of("ArithmeticException")); + Expression.Info withNondeterministicResult = new Expression.Info().withNondeterministicResult(); + + // Cast between all primitive types. Except for Boolean, we cannot cast from and to. + CodeGenerationDataNameType.INTEGRAL_AND_FLOATING_TYPES.stream().forEach(src -> { + CodeGenerationDataNameType.INTEGRAL_AND_FLOATING_TYPES.stream().forEach(dst -> { + ops.add(Expression.make(dst, "(" + dst.name() + ")(", src, ")")); + }); + }); + + // Ternary operator. + CodeGenerationDataNameType.INTEGRAL_AND_FLOATING_TYPES.stream().forEach(type -> { + ops.add(Expression.make(type, "(", BOOLEANS, "?", type, ":", type, ")")); + }); + + List.of(INTS, LONGS).stream().forEach(type -> { + // Arithmetic operators + ops.add(Expression.make(type, "(-(", type, "))")); + ops.add(Expression.make(type, "(", type, " + ", type, ")")); + ops.add(Expression.make(type, "(", type, " - ", type, ")")); + ops.add(Expression.make(type, "(", type, " * ", type, ")")); + ops.add(Expression.make(type, "(", type, " / ", type, ")", withArithmeticException)); + ops.add(Expression.make(type, "(", type, " % ", type, ")", withArithmeticException)); + + // Bitwise Operators (non short-circuit) + ops.add(Expression.make(type, "(~(", type, "))")); + ops.add(Expression.make(type, "(", type, " & ", type, ")")); + ops.add(Expression.make(type, "(", type, " | ", type, ")")); + ops.add(Expression.make(type, "(", type, " ^ ", type, ")")); + ops.add(Expression.make(type, "(", type, " << ", type, ")")); + ops.add(Expression.make(type, "(", type, " >> ", type, ")")); + ops.add(Expression.make(type, "(", type, " >>> ", type, ")")); + + // Relational / Comparison Operators + ops.add(Expression.make(BOOLEANS, "(", type, " == ", type, ")")); + ops.add(Expression.make(BOOLEANS, "(", type, " != ", type, ")")); + ops.add(Expression.make(BOOLEANS, "(", type, " > ", type, ")")); + ops.add(Expression.make(BOOLEANS, "(", type, " < ", type, ")")); + ops.add(Expression.make(BOOLEANS, "(", type, " >= ", type, ")")); + ops.add(Expression.make(BOOLEANS, "(", type, " <= ", type, ")")); + }); + + CodeGenerationDataNameType.FLOATING_TYPES.stream().forEach(type -> { + // Arithmetic operators + ops.add(Expression.make(type, "(-(", type, "))")); + ops.add(Expression.make(type, "(", type, " + ", type, ")")); + ops.add(Expression.make(type, "(", type, " - ", type, ")")); + ops.add(Expression.make(type, "(", type, " * ", type, ")")); + ops.add(Expression.make(type, "(", type, " / ", type, ")")); + ops.add(Expression.make(type, "(", type, " % ", type, ")")); + + // Relational / Comparison Operators + ops.add(Expression.make(BOOLEANS, "(", type, " == ", type, ")")); + ops.add(Expression.make(BOOLEANS, "(", type, " != ", type, ")")); + ops.add(Expression.make(BOOLEANS, "(", type, " > ", type, ")")); + ops.add(Expression.make(BOOLEANS, "(", type, " < ", type, ")")); + ops.add(Expression.make(BOOLEANS, "(", type, " >= ", type, ")")); + ops.add(Expression.make(BOOLEANS, "(", type, " <= ", type, ")")); + }); + + // ------------ byte ------------- + // Cast and ternary operator handled above. + // Arithmetic operations are not performed in byte, but rather promoted to int. + + // ------------ Byte ------------- + ops.add(Expression.make(INTS, "Byte.compare(", BYTES, ", ", BYTES, ")")); + ops.add(Expression.make(INTS, "Byte.compareUnsigned(", BYTES, ", ", BYTES, ")")); + ops.add(Expression.make(INTS, "Byte.toUnsignedInt(", BYTES, ")")); + ops.add(Expression.make(LONGS, "Byte.toUnsignedLong(", BYTES, ")")); + + // ------------ char ------------- + // Cast and ternary operator handled above. + // Arithmetic operations are not performned in char, but rather promoted to int. + + // ------------ Character ------------- + ops.add(Expression.make(INTS, "Character.compare(", CHARS, ", ", CHARS, ")")); + ops.add(Expression.make(CHARS, "Character.reverseBytes(", CHARS, ")")); + + // ------------ short ------------- + // Cast and ternary operator handled above. + // Arithmetic operations are not performned in short, but rather promoted to int. + + // ------------ Short ------------- + ops.add(Expression.make(INTS, "Short.compare(", SHORTS, ", ", SHORTS, ")")); + ops.add(Expression.make(INTS, "Short.compareUnsigned(", SHORTS, ", ", SHORTS, ")")); + ops.add(Expression.make(SHORTS, "Short.reverseBytes(", SHORTS, ")")); + ops.add(Expression.make(INTS, "Short.toUnsignedInt(", SHORTS, ")")); + ops.add(Expression.make(LONGS, "Short.toUnsignedLong(", SHORTS, ")")); + + // ------------ int ------------- + // Cast and ternary operator handled above. + // Arithmetic, Bitwise, Relational / Comparison handled above. + + // ------------ Integer ------------- + ops.add(Expression.make(INTS, "Integer.bitCount(", INTS, ")")); + ops.add(Expression.make(INTS, "Integer.compare(", INTS, ", ", INTS, ")")); + ops.add(Expression.make(INTS, "Integer.compareUnsigned(", INTS, ", ", INTS, ")")); + ops.add(Expression.make(INTS, "Integer.compress(", INTS, ", ", INTS, ")")); + ops.add(Expression.make(INTS, "Integer.divideUnsigned(", INTS, ", ", INTS, ")", withArithmeticException)); + ops.add(Expression.make(INTS, "Integer.expand(", INTS, ", ", INTS, ")")); + ops.add(Expression.make(INTS, "Integer.highestOneBit(", INTS, ")")); + ops.add(Expression.make(INTS, "Integer.lowestOneBit(", INTS, ")")); + ops.add(Expression.make(INTS, "Integer.max(", INTS, ", ", INTS, ")")); + ops.add(Expression.make(INTS, "Integer.min(", INTS, ", ", INTS, ")")); + ops.add(Expression.make(INTS, "Integer.numberOfLeadingZeros(", INTS, ")")); + ops.add(Expression.make(INTS, "Integer.numberOfTrailingZeros(", INTS, ")")); + ops.add(Expression.make(INTS, "Integer.remainderUnsigned(", INTS, ", ", INTS, ")", withArithmeticException)); + ops.add(Expression.make(INTS, "Integer.reverse(", INTS, ")")); + ops.add(Expression.make(INTS, "Integer.reverseBytes(", INTS, ")")); + ops.add(Expression.make(INTS, "Integer.rotateLeft(", INTS, ", ", INTS, ")")); + ops.add(Expression.make(INTS, "Integer.rotateRight(", INTS, ", ", INTS, ")")); + ops.add(Expression.make(INTS, "Integer.signum(", INTS, ")")); + ops.add(Expression.make(INTS, "Integer.sum(", INTS, ", ", INTS, ")")); + ops.add(Expression.make(LONGS, "Integer.toUnsignedLong(", INTS, ")")); + + // ------------ long ------------- + // Cast and ternary operator handled above. + // Arithmetic, Bitwise, Relational / Comparison handled above. + + // ------------ Long ------------- + ops.add(Expression.make(INTS, "Long.bitCount(", LONGS, ")")); + ops.add(Expression.make(INTS, "Long.compare(", LONGS, ", ", LONGS, ")")); + ops.add(Expression.make(INTS, "Long.compareUnsigned(", LONGS, ", ", LONGS, ")")); + ops.add(Expression.make(LONGS, "Long.compress(", LONGS, ", ", LONGS, ")")); + ops.add(Expression.make(LONGS, "Long.divideUnsigned(", LONGS, ", ", LONGS, ")", withArithmeticException)); + ops.add(Expression.make(LONGS, "Long.expand(", LONGS, ", ", LONGS, ")")); + ops.add(Expression.make(LONGS, "Long.highestOneBit(", LONGS, ")")); + ops.add(Expression.make(LONGS, "Long.lowestOneBit(", LONGS, ")")); + ops.add(Expression.make(LONGS, "Long.max(", LONGS, ", ", LONGS, ")")); + ops.add(Expression.make(LONGS, "Long.min(", LONGS, ", ", LONGS, ")")); + ops.add(Expression.make(INTS, "Long.numberOfLeadingZeros(", LONGS, ")")); + ops.add(Expression.make(INTS, "Long.numberOfTrailingZeros(", LONGS, ")")); + ops.add(Expression.make(LONGS, "Long.remainderUnsigned(", LONGS, ", ", LONGS, ")", withArithmeticException)); + ops.add(Expression.make(LONGS, "Long.reverse(", LONGS, ")")); + ops.add(Expression.make(LONGS, "Long.reverseBytes(", LONGS, ")")); + ops.add(Expression.make(LONGS, "Long.rotateLeft(", LONGS, ", ", INTS, ")")); + ops.add(Expression.make(LONGS, "Long.rotateRight(", LONGS, ", ", INTS, ")")); + ops.add(Expression.make(INTS, "Long.signum(", LONGS, ")")); + ops.add(Expression.make(LONGS, "Long.sum(", LONGS, ", ", LONGS, ")")); + + // ------------ float ------------- + // Cast and ternary operator handled above. + // Arithmetic and Relational / Comparison handled above. + + // ------------ Float ------------- + ops.add(Expression.make(INTS, "Float.compare(", FLOATS, ", ", FLOATS, ")")); + ops.add(Expression.make(INTS, "Float.floatToIntBits(", FLOATS, ")")); + ops.add(Expression.make(INTS, "Float.floatToRawIntBits(", FLOATS, ")", withNondeterministicResult)); + // Note: there are multiple NaN values with different bit representations. + ops.add(Expression.make(FLOATS, "Float.float16ToFloat(", SHORTS, ")")); + ops.add(Expression.make(FLOATS, "Float.intBitsToFloat(", INTS, ")")); + ops.add(Expression.make(BOOLEANS, "Float.isFinite(", FLOATS, ")")); + ops.add(Expression.make(BOOLEANS, "Float.isInfinite(", FLOATS, ")")); + ops.add(Expression.make(BOOLEANS, "Float.isNaN(", FLOATS, ")")); + ops.add(Expression.make(FLOATS, "Float.max(", FLOATS, ", ", FLOATS, ")")); + ops.add(Expression.make(FLOATS, "Float.min(", FLOATS, ", ", FLOATS, ")")); + ops.add(Expression.make(FLOATS, "Float.sum(", FLOATS, ", ", FLOATS, ")")); + + // ------------ double ------------- + // Cast and ternary operator handled above. + // Arithmetic and Relational / Comparison handled above. + + // ------------ Double ------------- + ops.add(Expression.make(INTS, "Double.compare(", DOUBLES, ", ", DOUBLES, ")")); + ops.add(Expression.make(LONGS, "Double.doubleToLongBits(", DOUBLES, ")")); + // Note: there are multiple NaN values with different bit representations. + ops.add(Expression.make(LONGS, "Double.doubleToRawLongBits(", DOUBLES, ")", withNondeterministicResult)); + ops.add(Expression.make(DOUBLES, "Double.longBitsToDouble(", LONGS, ")")); + ops.add(Expression.make(BOOLEANS, "Double.isFinite(", DOUBLES, ")")); + ops.add(Expression.make(BOOLEANS, "Double.isInfinite(", DOUBLES, ")")); + ops.add(Expression.make(BOOLEANS, "Double.isNaN(", DOUBLES, ")")); + ops.add(Expression.make(DOUBLES, "Double.max(", DOUBLES, ", ", DOUBLES, ")")); + ops.add(Expression.make(DOUBLES, "Double.min(", DOUBLES, ", ", DOUBLES, ")")); + ops.add(Expression.make(DOUBLES, "Double.sum(", DOUBLES, ", ", DOUBLES, ")")); + + // ------------ boolean ------------- + // Cast and ternary operator handled above. + // There are no boolean arithmetic operators + + // Logical operators + ops.add(Expression.make(BOOLEANS, "(!(", BOOLEANS, "))")); + ops.add(Expression.make(BOOLEANS, "(", BOOLEANS, " || ", BOOLEANS, ")")); + ops.add(Expression.make(BOOLEANS, "(", BOOLEANS, " && ", BOOLEANS, ")")); + ops.add(Expression.make(BOOLEANS, "(", BOOLEANS, " ^ ", BOOLEANS, ")")); + + // ------------ Boolean ------------- + ops.add(Expression.make(INTS, "Boolean.compare(", BOOLEANS, ", ", BOOLEANS, ")")); + ops.add(Expression.make(BOOLEANS, "Boolean.logicalAnd(", BOOLEANS, ", ", BOOLEANS, ")")); + ops.add(Expression.make(BOOLEANS, "Boolean.logicalOr(", BOOLEANS, ", ", BOOLEANS, ")")); + ops.add(Expression.make(BOOLEANS, "Boolean.logicalXor(", BOOLEANS, ", ", BOOLEANS, ")")); + + // TODO: Math and other classes. + + // Make sure the list is not modifiable. + return List.copyOf(ops); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/library/PrimitiveType.java b/test/hotspot/jtreg/compiler/lib/template_framework/library/PrimitiveType.java index 3bf6c7f62886..46a9d5bbabe6 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/library/PrimitiveType.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/library/PrimitiveType.java @@ -31,6 +31,9 @@ import compiler.lib.generators.RestrictableGenerator; import compiler.lib.template_framework.DataName; +import compiler.lib.template_framework.Template; +import compiler.lib.template_framework.TemplateToken; +import static compiler.lib.template_framework.Template.body; /** * The {@link PrimitiveType} models Java's primitive types, and provides a set @@ -148,4 +151,91 @@ public boolean isFloating() { case FLOAT, DOUBLE -> true; }; } + + /** + * Calls the corresponding pseudo random number generator from + * {@link #generateLibraryRNG}, for the given type. Accordingly, + * one must generate {@link #generateLibraryRNG} into the same + * test if one wants to use this method. + * + * Note: if you simply need a compile time constant, then please + * use {@link #con} instead. + * + * @return the token representing the method call to obtain a + * random value for the given type at runtime. + */ + public Object callLibraryRNG() { + return switch (kind) { + case BYTE -> "LibraryRNG.nextByte()"; + case SHORT -> "LibraryRNG.nextShort()"; + case CHAR -> "LibraryRNG.nextChar()"; + case INT -> "LibraryRNG.nextInt()"; + case LONG -> "LibraryRNG.nextLong()"; + case FLOAT -> "LibraryRNG.nextFloat()"; + case DOUBLE -> "LibraryRNG.nextDouble()"; + case BOOLEAN -> "LibraryRNG.nextBoolean()"; + }; + } + + /** + * Generates the {@code LibraryRNG} class, which makes a set of pseudo + * random number generators available, wrapping {@link Generators}. This + * is supposed to be used in tandem with {@link #callLibraryRNG}. + * + * Note: you must ensure that all required imports are performed: + * {@code java.util.Random} + * {@code jdk.test.lib.Utils} + * {@code compiler.lib.generators.*} + * + * @return a TemplateToken that holds all the {@code LibraryRNG} class. + */ + public static TemplateToken generateLibraryRNG() { + var template = Template.make(() -> body( + """ + public static class LibraryRNG { + private static final Random RANDOM = Utils.getRandomInstance(); + private static final RestrictableGenerator GEN_BYTE = Generators.G.safeRestrict(Generators.G.ints(), Byte.MIN_VALUE, Byte.MAX_VALUE); + private static final RestrictableGenerator GEN_CHAR = Generators.G.safeRestrict(Generators.G.ints(), Character.MIN_VALUE, Character.MAX_VALUE); + private static final RestrictableGenerator GEN_SHORT = Generators.G.safeRestrict(Generators.G.ints(), Short.MIN_VALUE, Short.MAX_VALUE); + private static final RestrictableGenerator GEN_INT = Generators.G.ints(); + private static final RestrictableGenerator GEN_LONG = Generators.G.longs(); + private static final Generator GEN_DOUBLE = Generators.G.doubles(); + private static final Generator GEN_FLOAT = Generators.G.floats(); + + public static byte nextByte() { + return GEN_BYTE.next().byteValue(); + } + + public static short nextShort() { + return GEN_SHORT.next().shortValue(); + } + + public static char nextChar() { + return (char)GEN_CHAR.next().intValue(); + } + + public static int nextInt() { + return GEN_INT.next(); + } + + public static long nextLong() { + return GEN_LONG.next(); + } + + public static float nextFloat() { + return GEN_FLOAT.next(); + } + + public static double nextDouble() { + return GEN_DOUBLE.next(); + } + + public static boolean nextBoolean() { + return RANDOM.nextBoolean(); + } + } + """ + )); + return template.asToken(); + }; } diff --git a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestExpressions.java b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestExpressions.java new file mode 100644 index 000000000000..6e11a7050540 --- /dev/null +++ b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestExpressions.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8359412 + * @summary Demonstrate the use of Expressions from the Template Library. + * @modules java.base/jdk.internal.misc + * @library /test/lib / + * @compile ../../../compiler/lib/verify/Verify.java + * @run main template_framework.examples.TestExpressions + */ + +package template_framework.examples; + +import java.util.List; +import java.util.ArrayList; +import java.util.Set; + +import compiler.lib.compile_framework.*; +import compiler.lib.template_framework.Template; +import compiler.lib.template_framework.TemplateToken; +import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.let; +import compiler.lib.template_framework.library.Expression; +import compiler.lib.template_framework.library.Operations; +import compiler.lib.template_framework.library.TestFrameworkClass; + +public class TestExpressions { + public static void main(String[] args) { + // Create a new CompileFramework instance. + CompileFramework comp = new CompileFramework(); + + // Add a java source file. + comp.addJavaSourceCode("p.xyz.InnerTest", generate(comp)); + + // Compile the source file. + comp.compile(); + + // p.xyz.InnterTest.main(new String[] {}); + comp.invoke("p.xyz.InnerTest", "main", new Object[] {new String[] {}}); + } + + // Generate a Java source file as String + public static String generate(CompileFramework comp) { + // Generate a list of test methods. + List tests = new ArrayList<>(); + + // Create a test method that executes the expression, with constant arguments. + var withConstantsTemplate = Template.make("expression", (Expression expression) -> { + // Create a token: fill the expression with a fixed set of constants. + // We then use the same token with the same constants, once compiled and once not compiled. + TemplateToken expressionToken = expression.asToken(expression.argumentTypes.stream().map(t -> t.con()).toList()); + return body( + let("returnType", expression.returnType), + """ + @Test + public static void $primitiveConTest() { + #returnType v0 = ${primitiveConTest}_compiled(); + #returnType v1 = ${primitiveConTest}_reference(); + Verify.checkEQ(v0, v1); + } + + @DontInline + public static #returnType ${primitiveConTest}_compiled() { + """, + "return ", expressionToken, ";\n", + """ + } + + @DontCompile + public static #returnType ${primitiveConTest}_reference() { + """, + "return ", expressionToken, ";\n", + """ + } + """ + ); + }); + + for (Expression operation : Operations.PRIMITIVE_OPERATIONS) { + tests.add(withConstantsTemplate.asToken(operation)); + } + + // Create the test class, which runs all tests. + return TestFrameworkClass.render( + // package and class name. + "p.xyz", "InnerTest", + // Set of imports. + Set.of("compiler.lib.verify.*"), + // classpath, so the Test VM has access to the compiled class files. + comp.getEscapedClassPathOfCompiledClasses(), + // The list of tests. + tests); + } +} diff --git a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestPrimitiveTypes.java b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestPrimitiveTypes.java index 5cd3f3c2a226..a04a5771cb49 100644 --- a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestPrimitiveTypes.java +++ b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestPrimitiveTypes.java @@ -169,6 +169,48 @@ public static void test_names() { tests.put("test_names", namesTemplate.asToken()); + // Test runtime random value generation with LibraryRNG + // Runtime random number generation of a given primitive type can be very helpful + // when writing tests that require random inputs. + var libraryRNGWithTypeTemplate = Template.make("type", (PrimitiveType type) -> body( + """ + { + // Fill an array with 1_000 random values. Every type has at least 2 values, + // so the chance that all values are the same is 2^-1_000 < 10^-300. This should + // never happen, even with a relatively weak PRNG. + #type[] a = new #type[1_000]; + for (int i = 0; i < a.length; i++) { + """, + " a[i] = ", type.callLibraryRNG(), ";\n", + """ + } + boolean allSame = true; + for (int i = 0; i < a.length; i++) { + if (a[i] != a[0]) { + allSame = false; + break; + } + } + if (allSame) { throw new RuntimeException("all values were the same for #type"); } + } + """ + )); + + var libraryRNGTemplate = Template.make(() -> body( + // Make sure we instantiate the LibraryRNG class. + PrimitiveType.generateLibraryRNG(), + // Now we can use it inside the test. + """ + public static void test_LibraryRNG() { + """, + CodeGenerationDataNameType.PRIMITIVE_TYPES.stream().map(libraryRNGWithTypeTemplate::asToken).toList(), + """ + } + """ + )); + + tests.put("test_LibraryRNG", libraryRNGTemplate.asToken()); + // Finally, put all the tests together in a class, and invoke all // tests from the main method. var template = Template.make(() -> body( @@ -178,6 +220,11 @@ public static void test_names() { import compiler.lib.verify.*; import java.lang.foreign.MemorySegment; + // Imports for LibraryRNG + import java.util.Random; + import jdk.test.lib.Utils; + import compiler.lib.generators.*; + public class InnerTest { public static void main() { """, diff --git a/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestExpression.java b/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestExpression.java new file mode 100644 index 000000000000..2dac740dd936 --- /dev/null +++ b/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestExpression.java @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8359412 + * @summary Test template generation with Expressions. + * @modules java.base/jdk.internal.misc + * @library /test/lib / + * @run main template_framework.tests.TestExpression + */ + +package template_framework.tests; + +import java.util.List; +import java.util.Set; + +import compiler.lib.template_framework.DataName; +import compiler.lib.template_framework.Template; +import compiler.lib.template_framework.TemplateToken; +import static compiler.lib.template_framework.Template.body; +import compiler.lib.template_framework.library.CodeGenerationDataNameType; +import compiler.lib.template_framework.library.Expression; + +/** + * This tests the use of the {@link Expression} from the template library. This is + * not a tutorial about how to use Expressions, rather we produce deterministic + * output to be able to compare the generated strings to expected strings. + * + * If you are interested in how to use {@link Expression}s, see {@code examples/TestExpressions.java}. + */ +public class TestExpression { + // Interface for failing tests. + interface FailingTest { + void run(); + } + + // We define our own types, so that we can check if subtyping works right. + public record MyType(String name) implements CodeGenerationDataNameType { + @Override + public Object con() { + return "<" + name() + ">"; + } + + @Override + public boolean isSubtypeOf(DataName.Type other) { + return other instanceof MyType(String n) && name().startsWith(n); + } + + @Override + public String toString() { return name(); } + } + private static final MyType myTypeA = new MyType("MyTypeA"); + private static final MyType myTypeA1 = new MyType("MyTypeA1"); + private static final MyType myTypeB = new MyType("MyTypeB"); + + public static void main(String[] args) { + // The following tests all pass, i.e. have no errors during rendering. + testAsToken(); + testNest(); + testNestRandomly(); + testInfo(); + + // The following tests should all fail, with an expected exception and message. + expectIllegalArgumentException(() -> testFailingAsToken1(), "Wrong number of arguments: expected: 2 but got: 1"); + expectIllegalArgumentException(() -> testFailingAsToken2(), "Wrong number of arguments: expected: 2 but got: 3"); + expectIllegalArgumentException(() -> testFailingNest1(), "Cannot nest expressions because of mismatched types."); + } + + public static void testAsToken() { + Expression e1 = Expression.make(myTypeA, "[", myTypeA, "]"); + Expression e2 = Expression.make(myTypeA, "[", myTypeA, ",", myTypeB, "]"); + Expression e3 = Expression.make(myTypeA, "[", myTypeA, ",", myTypeB, ",", myTypeA1, "]"); + Expression e4 = Expression.make(myTypeA, "[", myTypeA, ",", myTypeB, ",", myTypeA1, ",", myTypeA, "]"); + + var template = Template.make(() -> body( + "xx", e1.toString(), "yy\n", + "xx", e2.toString(), "yy\n", + "xx", e3.toString(), "yy\n", + "xx", e4.toString(), "yy\n", + "xx", e1.asToken(List.of("a")), "yy\n", + "xx", e2.asToken(List.of("a", "b")), "yy\n", + "xx", e3.asToken(List.of("a", "b", "c")), "yy\n", + "xx", e4.asToken(List.of("a", "b", "c", "d")), "yy\n" + )); + + String expected = + """ + xxExpression["[", MyTypeA, "]"]yy + xxExpression["[", MyTypeA, ",", MyTypeB, "]"]yy + xxExpression["[", MyTypeA, ",", MyTypeB, ",", MyTypeA1, "]"]yy + xxExpression["[", MyTypeA, ",", MyTypeB, ",", MyTypeA1, ",", MyTypeA, "]"]yy + xx[a]yy + xx[a,b]yy + xx[a,b,c]yy + xx[a,b,c,d]yy + """; + String code = template.render(); + checkEQ(code, expected); + } + + public static void testFailingAsToken1() { + Expression e1 = Expression.make(myTypeA, "[", myTypeA, ",", myTypeB, "]"); + e1.asToken(List.of("a")); + } + + public static void testFailingAsToken2() { + Expression e1 = Expression.make(myTypeA, "[", myTypeA, ",", myTypeB, "]"); + e1.asToken(List.of("a", "b", "c")); + } + + public static void testNest() { + Expression e1 = Expression.make(myTypeA, "[", myTypeA, "]"); + Expression e2 = Expression.make(myTypeA, "[", myTypeA, ",", myTypeB, "]"); + Expression e3 = Expression.make(myTypeA1, "[", myTypeA, "]"); + Expression e4 = Expression.make(myTypeA, "[", myTypeA, "x", myTypeA, "y", myTypeA, "z", myTypeA, "]"); + Expression e5 = Expression.make(myTypeA, "[", myTypeA, "u", myTypeA, "v", myTypeA, "w", myTypeA, "]"); + + Expression e1e1 = e1.nest(0, e1); + Expression e2e1 = e2.nest(0, e1); + Expression e3e1 = e3.nest(0, e1); + Expression e4e5 = e4.nest(1, e5); + + var template = Template.make(() -> body( + "xx", e1e1.toString(), "yy\n", + "xx", e2e1.toString(), "yy\n", + "xx", e3e1.toString(), "yy\n", + "xx", e4e5.toString(), "yy\n", + "xx", e1e1.asToken(List.of("a")), "yy\n", + "xx", e2e1.asToken(List.of("a", "b")), "yy\n", + "xx", e3e1.asToken(List.of("a")), "yy\n", + "xx", e4e5.asToken(List.of("a", "b", "c", "d", "e", "f", "g")), "yy\n" + )); + + String expected = + """ + xxExpression["[[", MyTypeA, "]]"]yy + xxExpression["[[", MyTypeA, "],", MyTypeB, "]"]yy + xxExpression["[[", MyTypeA, "]]"]yy + xxExpression["[", MyTypeA, "x[", MyTypeA, "u", MyTypeA, "v", MyTypeA, "w", MyTypeA, "]y", MyTypeA, "z", MyTypeA, "]"]yy + xx[[a]]yy + xx[[a],b]yy + xx[[a]]yy + xx[ax[bucvdwe]yfzg]yy + """; + String code = template.render(); + checkEQ(code, expected); + } + + public static void testNestRandomly() { + Expression e1 = Expression.make(myTypeA, "[", myTypeA, "]"); + Expression e2 = Expression.make(myTypeA, "(", myTypeA, ")"); + Expression e3 = Expression.make(myTypeB, "{", myTypeA, "}"); + Expression e4 = Expression.make(myTypeA1, "<", myTypeA, ">"); + Expression e5 = Expression.make(myTypeA, "[", myTypeB, "]"); + + Expression e1e2 = e1.nestRandomly(List.of(e2)); + Expression e1ex = e1.nestRandomly(List.of(e3, e2, e3)); + Expression e1e4 = e1.nestRandomly(List.of(e3, e4, e3)); + Expression e1ey = e1.nestRandomly(List.of(e3, e3)); + + // 5-deep nesting of e1 + Expression deep1 = Expression.nestRandomly(myTypeA, List.of(e1, e3), 5); + // Alternating pattern + Expression deep2 = Expression.nestRandomly(myTypeA, List.of(e5, e3), 5); + + var template = Template.make(() -> body( + "xx", e1e2.toString(), "yy\n", + "xx", e1ex.toString(), "yy\n", + "xx", e1e4.toString(), "yy\n", + "xx", e1ey.toString(), "yy\n", + "xx", deep1.toString(), "yy\n", + "xx", deep2.toString(), "yy\n", + "xx", e1e2.asToken(List.of("a")), "yy\n", + "xx", e1ex.asToken(List.of("a")), "yy\n", + "xx", e1e4.asToken(List.of("a")), "yy\n", + "xx", e1ey.asToken(List.of("a")), "yy\n", + "xx", deep1.asToken(List.of("a")), "yy\n", + "xx", deep2.asToken(List.of("a")), "yy\n" + )); + + String expected = + """ + xxExpression["[(", MyTypeA, ")]"]yy + xxExpression["[(", MyTypeA, ")]"]yy + xxExpression["[<", MyTypeA, ">]"]yy + xxExpression["[", MyTypeA, "]"]yy + xxExpression["[[[[[", MyTypeA, "]]]]]"]yy + xxExpression["[{[{[", MyTypeB, "]}]}]"]yy + xx[(a)]yy + xx[(a)]yy + xx[]yy + xx[a]yy + xx[[[[[a]]]]]yy + xx[{[{[a]}]}]yy + """; + String code = template.render(); + checkEQ(code, expected); + } + + public static void testFailingNest1() { + Expression e1 = Expression.make(myTypeA, "[", myTypeA, "]"); + Expression e2 = Expression.make(myTypeB, "[", myTypeA, "]"); + Expression e1e2 = e1.nest(0, e2); + } + + public static void testInfo() { + Expression e1 = Expression.make(myTypeA, "[", myTypeA, "]"); + Expression e2 = Expression.make(myTypeA, "(", myTypeA, ")"); + Expression e3 = Expression.make(myTypeA, "<", myTypeA, ">", new Expression.Info().withExceptions(Set.of("E1"))); + Expression e4 = Expression.make(myTypeA, "+", myTypeA, "-", new Expression.Info().withExceptions(Set.of("E2"))); + Expression e5 = Expression.make(myTypeA, "x", myTypeA, "y", new Expression.Info().withNondeterministicResult()); + Expression e6 = Expression.make(myTypeA, "u", myTypeA, "v", new Expression.Info().withNondeterministicResult()); + checkInfo(e1, Set.of(), true); + checkInfo(e2, Set.of(), true); + checkInfo(e3, Set.of("E1"), true); + checkInfo(e4, Set.of("E2"), true); + checkInfo(e1.nest(0, e2), Set.of(), true); + checkInfo(e2.nest(0, e1), Set.of(), true); + checkInfo(e1.nest(0, e3), Set.of("E1"), true); + checkInfo(e3.nest(0, e1), Set.of("E1"), true); + checkInfo(e3.nest(0, e4), Set.of("E1", "E2"), true); + checkInfo(e4.nest(0, e3), Set.of("E1", "E2"), true); + checkInfo(e5, Set.of(), false); + checkInfo(e6, Set.of(), false); + checkInfo(e1.nest(0, e5), Set.of(), false); + checkInfo(e5.nest(0, e1), Set.of(), false); + checkInfo(e5.nest(0, e6), Set.of(), false); + checkInfo(e4.nest(0, e3).nest(0, e5), Set.of("E1", "E2"), false); + checkInfo(e5.nest(0, e4).nest(0, e3), Set.of("E1", "E2"), false); + checkInfo(e3.nest(0, e5).nest(0, e4), Set.of("E1", "E2"), false); + } + + public static void checkInfo(Expression e, Set exceptions, boolean isResultDeterministic) { + if (!e.info.exceptions.equals(exceptions) || + e.info.isResultDeterministic != isResultDeterministic) { + throw new RuntimeException("Info not as expected."); + } + } + + public static void checkEQ(String code, String expected) { + if (!code.equals(expected)) { + System.out.println("\"" + code + "\""); + System.out.println("\"" + expected + "\""); + throw new RuntimeException("Template rendering mismatch!"); + } + } + + public static void expectIllegalArgumentException(FailingTest test, String errorPrefix) { + try { + test.run(); + System.out.println("Should have thrown IllegalArgumentException with prefix: " + errorPrefix); + throw new RuntimeException("Should have thrown!"); + } catch(IllegalArgumentException e) { + if (!e.getMessage().startsWith(errorPrefix)) { + System.out.println("Should have thrown with prefix: " + errorPrefix); + System.out.println("got: " + e.getMessage()); + throw new RuntimeException("Prefix mismatch", e); + } + } + } +} From b2e7c1c84635397d572985073d2cb2291b899686 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 23 Mar 2026 15:30:34 +0000 Subject: [PATCH 067/168] 8376031: HttpsURLConnection.getServerCertificates() throws "java.lang.IllegalStateException: connection not yet open" for the HEAD method Reviewed-by: phh Backport-of: d7523ec8d2255675547c0746d076efd7af5dd5af --- .../www/protocol/http/HttpURLConnection.java | 26 +- .../AbstractDelegateHttpsURLConnection.java | 113 +++++-- .../net/www/protocol/https/HttpsClient.java | 71 +--- .../GetServerCertificates.java | 307 ++++++++++++++++++ 4 files changed, 421 insertions(+), 96 deletions(-) create mode 100644 test/jdk/sun/net/www/protocol/https/HttpsURLConnection/GetServerCertificates.java diff --git a/src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java b/src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java index 857c2f6ad6d1..0ac1f75b86a0 100644 --- a/src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java +++ b/src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1995, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1995, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -1652,11 +1652,7 @@ private InputStream getInputStream0() throws IOException { if (method.equals("HEAD") || cl == 0 || respCode == HTTP_NOT_MODIFIED || respCode == HTTP_NO_CONTENT) { - - http.finished(); - http = null; - inputStream = new EmptyInputStream(); - connected = false; + noResponseBody(); } if (respCode == 200 || respCode == 203 || respCode == 206 || @@ -1738,6 +1734,24 @@ private InputStream getInputStream0() throws IOException { } } + /** + * This method is called when a response with no response + * body is received, and arrange for the http client to + * be returned to the pool (or released) immediately when + * possible. + * @apiNote Used by {@link sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection} + * to preserve the TLS information after receiving an empty body. + * @implSpec + * Subclasses that override this method should call the super class + * implementation. + */ + protected void noResponseBody() { + http.finished(); + http = null; + inputStream = new EmptyInputStream(); + connected = false; + } + /* * Creates a chained exception that has the same type as * original exception and with the same message. Right now, diff --git a/src/java.base/share/classes/sun/net/www/protocol/https/AbstractDelegateHttpsURLConnection.java b/src/java.base/share/classes/sun/net/www/protocol/https/AbstractDelegateHttpsURLConnection.java index 7bf8280a7ada..1415658e34d6 100644 --- a/src/java.base/share/classes/sun/net/www/protocol/https/AbstractDelegateHttpsURLConnection.java +++ b/src/java.base/share/classes/sun/net/www/protocol/https/AbstractDelegateHttpsURLConnection.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -51,6 +51,7 @@ public abstract class AbstractDelegateHttpsURLConnection extends HttpURLConnection { + private SSLSession savedSession = null; protected AbstractDelegateHttpsURLConnection(URL url, sun.net.www.protocol.http.Handler handler) throws IOException { this(url, null, handler); @@ -92,6 +93,7 @@ public void setNewClient (URL url) public void setNewClient (URL url, boolean useCache) throws IOException { int readTimeout = getReadTimeout(); + savedSession = null; http = HttpsClient.New (getSSLSocketFactory(), url, getHostnameVerifier(), @@ -184,6 +186,7 @@ public void connect() throws IOException { if (!http.isCachedConnection() && http.needsTunneling()) { doTunneling(); } + savedSession = null; ((HttpsClient)http).afterConnect(); } @@ -204,6 +207,19 @@ protected HttpClient getNewHttpClient(URL url, Proxy p, int connectTimeout, useCache, connectTimeout, this); } + @Override + protected void noResponseBody() { + savedSession = ((HttpsClient)http).getSSLSession(); + super.noResponseBody(); + } + + private SSLSession session() { + if (http instanceof HttpsClient https) { + return https.getSSLSession(); + } + return savedSession; + } + /** * Returns the cipher suite in use on this connection. */ @@ -211,11 +227,12 @@ public String getCipherSuite () { if (cachedResponse != null) { return ((SecureCacheResponse)cachedResponse).getCipherSuite(); } - if (http == null) { + + var session = session(); + if (session == null) { throw new IllegalStateException("connection not yet open"); - } else { - return ((HttpsClient)http).getCipherSuite (); } + return session.getCipherSuite(); } /** @@ -231,11 +248,12 @@ public java.security.cert.Certificate[] getLocalCertificates() { return l.toArray(new java.security.cert.Certificate[0]); } } - if (http == null) { + + var session = session(); + if (session == null) { throw new IllegalStateException("connection not yet open"); - } else { - return (((HttpsClient)http).getLocalCertificates ()); } + return session.getLocalCertificates(); } /** @@ -256,11 +274,11 @@ public java.security.cert.Certificate[] getServerCertificates() } } - if (http == null) { + var session = session(); + if (session == null) { throw new IllegalStateException("connection not yet open"); - } else { - return (((HttpsClient)http).getServerCertificates ()); } + return session.getPeerCertificates(); } /** @@ -274,11 +292,11 @@ Principal getPeerPrincipal() return ((SecureCacheResponse)cachedResponse).getPeerPrincipal(); } - if (http == null) { + var session = session(); + if (session == null) { throw new IllegalStateException("connection not yet open"); - } else { - return (((HttpsClient)http).getPeerPrincipal()); } + return getPeerPrincipal(session); } /** @@ -291,11 +309,11 @@ Principal getLocalPrincipal() return ((SecureCacheResponse)cachedResponse).getLocalPrincipal(); } - if (http == null) { + var session = session(); + if (session == null) { throw new IllegalStateException("connection not yet open"); - } else { - return (((HttpsClient)http).getLocalPrincipal()); } + return getLocalPrincipal(session); } SSLSession getSSLSession() { @@ -307,11 +325,12 @@ SSLSession getSSLSession() { } } - if (http == null) { + var session = session(); + if (session == null) { throw new IllegalStateException("connection not yet open"); } - return ((HttpsClient)http).getSSLSession(); + return session; } /* @@ -354,7 +373,7 @@ protected HttpCallerInfo getHttpCallerInfo(URL url, String proxy, int port, } HttpsClient https = (HttpsClient)http; try { - Certificate[] certs = https.getServerCertificates(); + Certificate[] certs = https.getSSLSession().getPeerCertificates(); if (certs[0] instanceof X509Certificate x509Cert) { return new HttpCallerInfo(url, proxy, port, x509Cert, authenticator); } @@ -372,7 +391,7 @@ protected HttpCallerInfo getHttpCallerInfo(URL url, Authenticator authenticator) } HttpsClient https = (HttpsClient)http; try { - Certificate[] certs = https.getServerCertificates(); + Certificate[] certs = https.getSSLSession().getPeerCertificates(); if (certs[0] instanceof X509Certificate x509Cert) { return new HttpCallerInfo(url, x509Cert, authenticator); } @@ -381,4 +400,58 @@ protected HttpCallerInfo getHttpCallerInfo(URL url, Authenticator authenticator) } return super.getHttpCallerInfo(url, authenticator); } + + @Override + public void disconnect() { + super.disconnect(); + savedSession = null; + } + + /** + * Returns the principal with which the server authenticated + * itself, or throw a SSLPeerUnverifiedException if the + * server did not authenticate. + * @param session The {@linkplain #getSSLSession() SSL session} + */ + private static Principal getPeerPrincipal(SSLSession session) + throws SSLPeerUnverifiedException + { + Principal principal; + try { + principal = session.getPeerPrincipal(); + } catch (AbstractMethodError e) { + // if the provider does not support it, fallback to peer certs. + // return the X500Principal of the end-entity cert. + java.security.cert.Certificate[] certs = + session.getPeerCertificates(); + principal = ((X509Certificate)certs[0]).getSubjectX500Principal(); + } + return principal; + } + + /** + * Returns the principal the client sent to the + * server, or null if the client did not authenticate. + * @param session The {@linkplain #getSSLSession() SSL session} + */ + private static Principal getLocalPrincipal(SSLSession session) + { + Principal principal; + try { + principal = session.getLocalPrincipal(); + } catch (AbstractMethodError e) { + principal = null; + // if the provider does not support it, fallback to local certs. + // return the X500Principal of the end-entity cert. + java.security.cert.Certificate[] certs = + session.getLocalCertificates(); + if (certs != null) { + principal = ((X509Certificate)certs[0]).getSubjectX500Principal(); + } + } + return principal; + } + + + } diff --git a/src/java.base/share/classes/sun/net/www/protocol/https/HttpsClient.java b/src/java.base/share/classes/sun/net/www/protocol/https/HttpsClient.java index 9f1d7b07021e..f5804cd83bd8 100644 --- a/src/java.base/share/classes/sun/net/www/protocol/https/HttpsClient.java +++ b/src/java.base/share/classes/sun/net/www/protocol/https/HttpsClient.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -599,75 +599,6 @@ public void closeIdleConnection() { } } - /** - * Returns the cipher suite in use on this connection. - */ - String getCipherSuite() { - return session.getCipherSuite(); - } - - /** - * Returns the certificate chain the client sent to the - * server, or null if the client did not authenticate. - */ - public java.security.cert.Certificate [] getLocalCertificates() { - return session.getLocalCertificates(); - } - - /** - * Returns the certificate chain with which the server - * authenticated itself, or throw a SSLPeerUnverifiedException - * if the server did not authenticate. - */ - java.security.cert.Certificate [] getServerCertificates() - throws SSLPeerUnverifiedException - { - return session.getPeerCertificates(); - } - - /** - * Returns the principal with which the server authenticated - * itself, or throw a SSLPeerUnverifiedException if the - * server did not authenticate. - */ - Principal getPeerPrincipal() - throws SSLPeerUnverifiedException - { - Principal principal; - try { - principal = session.getPeerPrincipal(); - } catch (AbstractMethodError e) { - // if the provider does not support it, fallback to peer certs. - // return the X500Principal of the end-entity cert. - java.security.cert.Certificate[] certs = - session.getPeerCertificates(); - principal = ((X509Certificate)certs[0]).getSubjectX500Principal(); - } - return principal; - } - - /** - * Returns the principal the client sent to the - * server, or null if the client did not authenticate. - */ - Principal getLocalPrincipal() - { - Principal principal; - try { - principal = session.getLocalPrincipal(); - } catch (AbstractMethodError e) { - principal = null; - // if the provider does not support it, fallback to local certs. - // return the X500Principal of the end-entity cert. - java.security.cert.Certificate[] certs = - session.getLocalCertificates(); - if (certs != null) { - principal = ((X509Certificate)certs[0]).getSubjectX500Principal(); - } - } - return principal; - } - /** * Returns the {@code SSLSession} in use on this connection. */ diff --git a/test/jdk/sun/net/www/protocol/https/HttpsURLConnection/GetServerCertificates.java b/test/jdk/sun/net/www/protocol/https/HttpsURLConnection/GetServerCertificates.java new file mode 100644 index 000000000000..135c2375f222 --- /dev/null +++ b/test/jdk/sun/net/www/protocol/https/HttpsURLConnection/GetServerCertificates.java @@ -0,0 +1,307 @@ +/* + * 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 8376031 + * @modules jdk.httpserver + * @library /test/lib + * @summary Ensure HttpsURLConnection::getServerCertificates does not + * throw after calling getResponseCode() if the response doesn't have + * a body. + * @run main/othervm ${test.main.class} + * @run main/othervm -Djava.net.preferIPv6Addresses=true ${test.main.class} + */ + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; + +import com.sun.net.httpserver.HttpsConfigurator; +import com.sun.net.httpserver.HttpsServer; +import jdk.test.lib.net.SimpleSSLContext; +import jdk.test.lib.net.URIBuilder; + +// Use of the static import (RSPBODY_EMPTY) depends on JDK-8331195, +// which requires a CSR approval before backporting. +// import static com.sun.net.httpserver.HttpExchange.RSPBODY_EMPTY; + + +public class GetServerCertificates { + + static final String URI_PATH = "/GetServerCertificates/"; + static final String BODY = "Go raibh maith agat"; + // Using constant (RSPBODY_EMPTY) instead of a static import that depends + // on JDK-8331195, which requires a CSR approval before backporting. + static final long RSPBODY_EMPTY = -1l; + enum TESTS { + HEAD("head", 200, "HEAD"), + NOBODY("nobody", 200, "GET", "POST"), + S204("204", 204, "GET", "POST"), + S304("304", 304, "GET", "POST"), + S200("200", 200, "GET", "POST"); + final String test; + final int code; + final List methods; + private TESTS(String test, int code, String... methods) { + this.test = test; + this.code = code; + this.methods = List.of(methods); + } + boolean isFor(String path) { + return path != null && path.endsWith("/" + test); + } + + String test() { return test; } + int code() { return code; } + List methods() { return methods; } + static Optional fromPath(String path) { + return Stream.of(values()) + .filter(test -> test.isFor(path)) + .findFirst(); + } + } + + void test(String[] args) throws Exception { + SSLContext.setDefault(SimpleSSLContext.findSSLContext()); + HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() { + @Override + public boolean verify(String hostname, SSLSession session) { + return true; + } + }); + HttpServer server = startHttpServer(); + try { + InetSocketAddress address = server.getAddress(); + URI uri = URIBuilder.newBuilder() + .scheme("https") + .host(address.getAddress()) + .port(address.getPort()) + .path(URI_PATH) + .build(); + for (var test : TESTS.values()) { + for (String method : test.methods()) { + doClient(method, uri, test); + } + } + + } finally { + server.stop(1000); + } + } + + void doClient(String method, URI baseUri, TESTS test) throws Exception { + assert baseUri.getRawQuery() == null; + assert baseUri.getRawFragment() == null; + assert test.methods().contains(method); + + String uriStr = baseUri.toString(); + if (!uriStr.endsWith("/")) uriStr = uriStr + "/"; + + URI uri = new URI(uriStr + test.test()); + assert uri.toString().endsWith("/" + test.test()); + int code = test.code(); + System.out.println("doClient(%s, %s, %s)" + .formatted(method, test.test(), test.code)); + + // first request - should create a TCP connection + HttpsURLConnection uc = (HttpsURLConnection) + uri.toURL().openConnection(Proxy.NO_PROXY); + if (!"GET".equals(method)) { + uc.setRequestMethod(method); + } + try { + uc.getServerCertificates(); + throw new AssertionError("Expected IllegalStateException not thrown"); + } catch (IllegalStateException ise) { + System.out.println("Got expected ISE: " + ise); + } + int resp = uc.getResponseCode(); + check(resp == code, "Unexpected response code. Expected %s, got %s" + .formatted(code, resp)); + + check(uc.getServerCertificates()); + if (test == TESTS.S200) { + byte[] bytes = uc.getInputStream().readAllBytes(); + String body = new String(bytes, StandardCharsets.UTF_8); + System.out.println("body: " + body); + check(BODY.equals(body), "Unexpected response body. Expected \"%s\", got \"%s\"" + .formatted(BODY, body)); + } + + // second request - should go on the same TCP connection. + // We don't have a reliable way to test that, and it could + // go on a new TCP connection if the previous connection + // was already closed. It is not an issue either way. + uc = (HttpsURLConnection) + uri.toURL().openConnection(Proxy.NO_PROXY); + if (!"GET".equals(method)) { + uc.setRequestMethod(method); + } + try { + uc.getServerCertificates(); + throw new AssertionError("Expected IllegalStateException not thrown"); + } catch (IllegalStateException ise) { + System.out.println("Got expected ISE: " + ise); + } + resp = uc.getResponseCode(); + check(resp == code, "Unexpected response code. Expected %s, got %s" + .formatted(code, resp)); + + check(uc.getServerCertificates()); + if (test == TESTS.S200) { + byte[] bytes = uc.getInputStream().readAllBytes(); + String body = new String(bytes, StandardCharsets.UTF_8); + System.out.println("body: " + body); + check(BODY.equals(body), "Unexpected response body. Expected \"%s\", got \"%s\"" + .formatted(BODY, body)); + } + + uc.disconnect(); + try { + uc.getServerCertificates(); + throw new AssertionError("Expected IllegalStateException not thrown"); + } catch (IllegalStateException ise) { + System.out.println("Got expected ISE: " + ise); + } + + // third request - forces the connection to close + // after use so that we don't find any connection in the pool + // for the next test case, assuming there was only + // one connection in the first place. + // Again there's no easy way to verify that the pool + // is empty (and it's not really necessary to bother) + uc = (HttpsURLConnection) + uri.toURL().openConnection(Proxy.NO_PROXY); + if (!"GET".equals(method)) { + uc.setRequestMethod(method); + } + uc.setRequestProperty("Connection", "close"); + try { + uc.getServerCertificates(); + throw new AssertionError("Expected IllegalStateException not thrown"); + } catch (IllegalStateException ise) { + System.out.println("Got expected ISE: " + ise); + } + resp = uc.getResponseCode(); + check(resp == code, "Unexpected response code. Expected %s, got %s" + .formatted(code, resp)); + check(uc.getServerCertificates()); + uc.disconnect(); + try { + uc.getServerCertificates(); + throw new AssertionError("Expected IllegalStateException not thrown"); + } catch (IllegalStateException ise) { + System.out.println("Got expected ISE: " + ise); + } + } + + // HTTP Server + HttpServer startHttpServer() throws IOException { + InetAddress localhost = InetAddress.getLoopbackAddress(); + HttpsServer httpServer = HttpsServer + .create(new InetSocketAddress(localhost, 0), 0); + var configurator = new HttpsConfigurator(SimpleSSLContext.findSSLContext()); + httpServer.setHttpsConfigurator(configurator); + httpServer.createContext(URI_PATH, new SimpleHandler()); + httpServer.start(); + return httpServer; + } + + static class SimpleHandler implements HttpHandler { + @Override + public void handle(HttpExchange t) throws IOException { + try { + String path = t.getRequestURI().getRawPath(); + var test = TESTS.fromPath(path); + if (!path.startsWith(URI_PATH) || !test.isPresent()) { + t.getRequestBody().close(); + t.getResponseHeaders().add("Connection", "close"); + t.sendResponseHeaders(421, RSPBODY_EMPTY); + t.close(); + return; + } + try (var is = t.getRequestBody()) { + is.readAllBytes(); + } + switch (test.get()) { + case S204, S304, NOBODY -> + t.sendResponseHeaders(test.get().code(), RSPBODY_EMPTY); + case S200 -> { + byte[] bytes = BODY.getBytes(StandardCharsets.UTF_8); + t.sendResponseHeaders(test.get().code(), bytes.length); + try (var os = t.getResponseBody()) { + os.write(bytes); + } + } + case HEAD -> { + assert t.getRequestMethod().equals("HEAD"); + byte[] bytes = BODY.getBytes(StandardCharsets.UTF_8); + t.sendResponseHeaders(test.get().code(), bytes.length); + } + } + t.close(); + } catch (Throwable error) { + error.printStackTrace(); + throw error; + } + } + } + + volatile int passed = 0, failed = 0; + boolean debug = false; + void pass() {passed++;} + void fail() {failed++;} + void fail(String msg) {System.err.println(msg); fail();} + void unexpected(Throwable t) {failed++; t.printStackTrace();} + void debug(String message) { if (debug) System.out.println(message); } + void check(boolean cond, String failMessage) {if (cond) pass(); else fail(failMessage);} + void check(java.security.cert.Certificate[] certs) { + // Use List.of to check that certs is not null and does not + // contain null. NullPointerException will be thrown here + // if that happens, which will make the test fail. + check(!List.of(certs).isEmpty(), "no certificates returned"); + } + public static void main(String[] args) throws Throwable { + Class k = new Object(){}.getClass().getEnclosingClass(); + try {k.getMethod("instanceMain",String[].class) + .invoke( k.newInstance(), (Object) args);} + catch (Throwable e) {throw e.getCause();}} + public void instanceMain(String[] args) throws Throwable { + try {test(args);} catch (Throwable t) {unexpected(t);} + System.out.printf("%nPassed = %d, failed = %d%n%n", passed, failed); + if (failed > 0) throw new AssertionError("Some tests failed");} +} From 980498750d0abcb51c075b0c326eb3c04188f1ae Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 23 Mar 2026 15:31:03 +0000 Subject: [PATCH 068/168] 8373893: Refactor networking http server tests to use JUnit Backport-of: 96e5c270b4ca0ad2b47ef3c090cbbfe4661bfc22 --- .../httpserver/BasicAuthenticatorRealm.java | 44 ++-- .../net/httpserver/CreateHttpServerTest.java | 8 +- .../sun/net/httpserver/DateFormatterTest.java | 25 ++- .../com/sun/net/httpserver/FilterTest.java | 124 +++++----- .../com/sun/net/httpserver/HeadersTest.java | 111 ++++----- .../sun/net/httpserver/HttpContextTest.java | 19 +- .../sun/net/httpserver/HttpPrincipalTest.java | 18 +- .../httpserver/HttpServerProviderTest.java | 57 ++--- .../com/sun/net/httpserver/InputNotRead.java | 7 +- .../httpserver/UnmodifiableHeadersTest.java | 70 +++--- .../BasicAuthenticatorExceptionCheck.java | 34 +-- .../simpleserver/CommandLineNegativeTest.java | 72 +++--- .../CommandLinePortNotSpecifiedTest.java | 17 +- .../simpleserver/CommandLinePositiveTest.java | 67 +++--- .../simpleserver/CustomFileSystemTest.java | 172 +++++++------- .../simpleserver/FileServerHandlerTest.java | 50 +++-- .../simpleserver/HttpHandlersTest.java | 92 ++++---- .../simpleserver/HttpsServerAlertTest.java | 12 +- .../simpleserver/HttpsServerTest.java | 30 +-- .../IdempotencyAndCommutativityTest.java | 33 +-- .../simpleserver/MapToPathTest.java | 141 ++++++------ .../simpleserver/OutputFilterTest.java | 50 +++-- .../httpserver/simpleserver/RequestTest.java | 35 +-- .../ServerMimeTypesResolutionTest.java | 25 +-- .../simpleserver/SimpleFileServerTest.java | 212 +++++++++--------- .../simpleserver/StressDirListings.java | 25 ++- .../simpleserver/ZipFileSystemTest.java | 108 ++++----- .../jwebserver/CommandLineNegativeTest.java | 72 +++--- .../CommandLinePortNotSpecifiedTest.java | 17 +- .../jwebserver/CommandLinePositiveTest.java | 67 +++--- .../jwebserver/MaxRequestTimeTest.java | 25 ++- 31 files changed, 940 insertions(+), 899 deletions(-) diff --git a/test/jdk/com/sun/net/httpserver/BasicAuthenticatorRealm.java b/test/jdk/com/sun/net/httpserver/BasicAuthenticatorRealm.java index 06248e950a54..85e4f93b5c94 100644 --- a/test/jdk/com/sun/net/httpserver/BasicAuthenticatorRealm.java +++ b/test/jdk/com/sun/net/httpserver/BasicAuthenticatorRealm.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -28,7 +28,7 @@ * with HttpURLConnection and HttpClient * @modules jdk.httpserver * @library /test/lib - * @run testng/othervm BasicAuthenticatorRealm + * @run junit/othervm BasicAuthenticatorRealm */ import com.sun.net.httpserver.BasicAuthenticator; @@ -47,12 +47,20 @@ import java.net.http.HttpResponse.BodyHandlers; import jdk.test.lib.net.URIBuilder; -import org.testng.annotations.Test; import static java.net.http.HttpClient.Builder.NO_PROXY; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.testng.Assert.assertEquals; +import org.junit.jupiter.api.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; + +/** + * The second test @Order(2) must run after the first test because it + * sets a VM wide authenticator and the first test depends on no authenticator + * being set. + */ +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class BasicAuthenticatorRealm { static final String REALM = "U\u00ffU@realm"; // non-ASCII char @@ -61,7 +69,8 @@ public class BasicAuthenticatorRealm { static final InetAddress LOOPBACK_ADDR = InetAddress.getLoopbackAddress(); @Test - public static void testURLConnection() throws Exception { + @Order(1) + public void testURLConnection() throws Exception { var server = HttpServer.create(new InetSocketAddress(LOOPBACK_ADDR, 0), 0); var handler = HttpHandlers.of(200, Headers.of(), ""); var context = server.createContext("/test", handler); @@ -73,15 +82,16 @@ public static void testURLConnection() throws Exception { server.start(); var url = uri(server).toURL(); var connection = (HttpURLConnection)url.openConnection(Proxy.NO_PROXY); - assertEquals(connection.getResponseCode(), 401); - assertEquals(connection.getHeaderField("WWW-Authenticate"), EXPECTED_AUTH_HEADER_VALUE); + assertEquals(401, connection.getResponseCode()); + assertEquals(EXPECTED_AUTH_HEADER_VALUE, connection.getHeaderField("WWW-Authenticate")); } finally { server.stop(0); } } @Test - public static void testURLConnectionAuthenticated() throws Exception { + @Order(2) + public void testURLConnectionAuthenticated() throws Exception { var server = HttpServer.create(new InetSocketAddress(LOOPBACK_ADDR, 0), 0); var handler = HttpHandlers.of(200, Headers.of(), "foo"); var context = server.createContext("/test", handler); @@ -94,15 +104,16 @@ public static void testURLConnectionAuthenticated() throws Exception { server.start(); var url = uri(server).toURL(); var connection = (HttpURLConnection)url.openConnection(Proxy.NO_PROXY); - assertEquals(connection.getResponseCode(), 200); - assertEquals(connection.getInputStream().readAllBytes(), "foo".getBytes(UTF_8)); + assertEquals(200, connection.getResponseCode()); + Assertions.assertArrayEquals("foo".getBytes(UTF_8), connection.getInputStream().readAllBytes()); } finally { server.stop(0); } } @Test - public static void testHttpClient() throws Exception { + @Order(3) + public void testHttpClient() throws Exception { var server = HttpServer.create(new InetSocketAddress(LOOPBACK_ADDR, 0), 0); var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server)).build(); @@ -115,15 +126,16 @@ public static void testHttpClient() throws Exception { try { server.start(); var response = client.send(request, BodyHandlers.ofString(UTF_8)); - assertEquals(response.statusCode(), 401); - assertEquals(response.headers().firstValue("WWW-Authenticate").orElseThrow(), EXPECTED_AUTH_HEADER_VALUE); + assertEquals(401, response.statusCode()); + assertEquals(EXPECTED_AUTH_HEADER_VALUE, response.headers().firstValue("WWW-Authenticate").orElseThrow()); } finally { server.stop(0); } } @Test - public static void testHttpClientAuthenticated() throws Exception { + @Order(4) + public void testHttpClientAuthenticated() throws Exception { var server = HttpServer.create(new InetSocketAddress(LOOPBACK_ADDR, 0), 0); var request = HttpRequest.newBuilder(uri(server)).build(); var handler = HttpHandlers.of(200, Headers.of(), "foo"); @@ -139,8 +151,8 @@ public static void testHttpClientAuthenticated() throws Exception { try { server.start(); var response = client.send(request, BodyHandlers.ofString(UTF_8)); - assertEquals(response.statusCode(), 200); - assertEquals(response.body(), "foo"); + assertEquals(200, response.statusCode()); + assertEquals("foo", response.body()); } finally { server.stop(0); } diff --git a/test/jdk/com/sun/net/httpserver/CreateHttpServerTest.java b/test/jdk/com/sun/net/httpserver/CreateHttpServerTest.java index 370bd75cf34c..1fbe4e6f3d1c 100644 --- a/test/jdk/com/sun/net/httpserver/CreateHttpServerTest.java +++ b/test/jdk/com/sun/net/httpserver/CreateHttpServerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,17 +25,17 @@ * @test * @bug 8251496 * @summary summary - * @run testng/othervm CreateHttpServerTest + * @run junit/othervm CreateHttpServerTest */ import com.sun.net.httpserver.HttpServer; -import org.testng.annotations.Test; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; -import static org.testng.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.Test; public class CreateHttpServerTest { @Test diff --git a/test/jdk/com/sun/net/httpserver/DateFormatterTest.java b/test/jdk/com/sun/net/httpserver/DateFormatterTest.java index bda3421660c0..efcc96d45b0f 100644 --- a/test/jdk/com/sun/net/httpserver/DateFormatterTest.java +++ b/test/jdk/com/sun/net/httpserver/DateFormatterTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -39,12 +39,13 @@ import java.util.regex.Pattern; import jdk.test.lib.net.URIBuilder; -import org.testng.annotations.AfterTest; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.Test; import static java.net.http.HttpResponse.BodyHandlers.ofString; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; + +import org.junit.jupiter.api.AfterAll; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; /** * @test @@ -53,19 +54,19 @@ * @modules java.net.http * @library /test/lib * @build DateFormatterTest - * @run testng/othervm DateFormatterTest + * @run junit/othervm DateFormatterTest */ public class DateFormatterTest { - private HttpServer server; + private static HttpServer server; static URI httpURI; static final Integer ITERATIONS = 10; static String format; static Pattern pattern; - @BeforeTest - public void setUp() throws IOException, URISyntaxException { + @BeforeAll + public static void setUp() throws IOException, URISyntaxException { String days = "(Mon|Tue|Wed|Thu|Fri|Sat|Sun)(,)"; String dayNo = "(\\s\\d\\d\\s)"; String month = "(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)"; @@ -85,8 +86,8 @@ public void setUp() throws IOException, URISyntaxException { server.start(); } - @AfterTest - public void cleanUp() { + @AfterAll + public static void cleanUp() { server.stop(0); } diff --git a/test/jdk/com/sun/net/httpserver/FilterTest.java b/test/jdk/com/sun/net/httpserver/FilterTest.java index 45c7ef21e8d7..a5c90d2026e6 100644 --- a/test/jdk/com/sun/net/httpserver/FilterTest.java +++ b/test/jdk/com/sun/net/httpserver/FilterTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,7 +25,7 @@ * @test * @bug 8267262 * @summary Tests for Filter static factory methods - * @run testng/othervm FilterTest + * @run junit/othervm FilterTest */ import java.io.IOException; @@ -49,11 +49,13 @@ import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; -import org.testng.annotations.BeforeTest; import static java.net.http.HttpClient.Builder.NO_PROXY; -import static org.testng.Assert.*; + +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; public class FilterTest { @@ -64,8 +66,8 @@ public class FilterTest { static final boolean ENABLE_LOGGING = true; static final Logger logger = Logger.getLogger("com.sun.net.httpserver"); - @BeforeTest - public void setup() { + @BeforeAll + public static void setup() { if (ENABLE_LOGGING) { ConsoleHandler ch = new ConsoleHandler(); logger.setLevel(Level.ALL); @@ -76,14 +78,14 @@ public void setup() { @Test public void testNull() { - expectThrows(NPE, () -> Filter.beforeHandler(null, e -> e.getResponseHeaders().set("X-Foo", "Bar"))); - expectThrows(NPE, () -> Filter.beforeHandler("Some description", null)); + assertThrows(NPE, () -> Filter.beforeHandler(null, e -> e.getResponseHeaders().set("X-Foo", "Bar"))); + assertThrows(NPE, () -> Filter.beforeHandler("Some description", null)); - expectThrows(NPE, () -> Filter.afterHandler("Some description", null)); - expectThrows(NPE, () -> Filter.afterHandler(null, HttpExchange::getResponseCode)); + assertThrows(NPE, () -> Filter.afterHandler("Some description", null)); + assertThrows(NPE, () -> Filter.afterHandler(null, HttpExchange::getResponseCode)); - expectThrows(NPE, () -> Filter.adaptRequest("Some description", null)); - expectThrows(NPE, () -> Filter.adaptRequest(null, r -> r.with("Foo", List.of("Bar")))); + assertThrows(NPE, () -> Filter.adaptRequest("Some description", null)); + assertThrows(NPE, () -> Filter.adaptRequest(null, r -> r.with("Foo", List.of("Bar")))); } @Test @@ -91,16 +93,15 @@ public void testDescription() { var desc = "Some description"; var beforeFilter = Filter.beforeHandler(desc, HttpExchange::getRequestBody); - assertEquals(desc, beforeFilter.description()); + assertEquals(beforeFilter.description(), desc); var afterFilter = Filter.afterHandler(desc, HttpExchange::getResponseCode); - assertEquals(desc, afterFilter.description()); + assertEquals(afterFilter.description(), desc); var adaptFilter = Filter.adaptRequest(desc, r -> r.with("Foo", List.of("Bar"))); - assertEquals(desc, adaptFilter.description()); + assertEquals(adaptFilter.description(), desc); } - @DataProvider public static Object[][] throwingFilters() { return new Object[][] { {Filter.beforeHandler("before RE", e -> { throw new RuntimeException(); }), IOE}, @@ -111,7 +112,8 @@ public static Object[][] throwingFilters() { }; } - @Test(dataProvider = "throwingFilters") + @ParameterizedTest + @MethodSource("throwingFilters") public void testException(Filter filter, Class exception) throws Exception { @@ -123,11 +125,11 @@ public void testException(Filter filter, Class exception) var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "")).build(); if (exception != null) { - expectThrows(exception, () -> client.send(request, HttpResponse.BodyHandlers.ofString())); + assertThrows(exception, () -> client.send(request, HttpResponse.BodyHandlers.ofString())); } else { var response = client.send(request, HttpResponse.BodyHandlers.ofString()); - assertEquals(response.statusCode(), 200); - assertEquals(response.body(), "hello world"); + assertEquals(200, response.statusCode()); + assertEquals("hello world", response.body()); } } finally { server.stop(0); @@ -146,9 +148,9 @@ public void testBeforeHandler() throws Exception { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "")).build(); var response = client.send(request, HttpResponse.BodyHandlers.ofString()); - assertEquals(response.statusCode(), 200); - assertEquals(response.headers().map().size(), 3); - assertEquals(response.headers().firstValue("x-foo").orElseThrow(), "bar"); + assertEquals(200, response.statusCode()); + assertEquals(3, response.headers().map().size()); + assertEquals("bar", response.headers().firstValue("x-foo").orElseThrow()); } finally { server.stop(0); } @@ -170,9 +172,9 @@ public void testBeforeHandlerRepeated() throws Exception { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "")).build(); var response = client.send(request, HttpResponse.BodyHandlers.ofString()); - assertEquals(response.statusCode(), 200); - assertEquals(response.headers().map().size(), 3); - assertEquals(response.headers().firstValue("x-foo").orElseThrow(), "barbar"); + assertEquals(200, response.statusCode()); + assertEquals(3, response.headers().map().size()); + assertEquals("barbar", response.headers().firstValue("x-foo").orElseThrow()); } finally { server.stop(0); } @@ -202,9 +204,9 @@ public void testBeforeHandlerSendResponse() throws Exception { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "")).build(); var response = client.send(request, HttpResponse.BodyHandlers.ofString()); - assertEquals(response.statusCode(), 200); - assertEquals(response.headers().map().size(), 3); - assertEquals(response.headers().firstValue("x-foo").orElseThrow(), "bar"); + assertEquals(200, response.statusCode()); + assertEquals(3, response.headers().map().size()); + assertEquals("bar", response.headers().firstValue("x-foo").orElseThrow()); } finally { server.stop(0); } @@ -223,8 +225,8 @@ public void testAfterHandler() throws Exception { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "")).build(); var response = client.send(request, HttpResponse.BodyHandlers.ofString()); - assertEquals(response.statusCode(), 200); - assertEquals(response.statusCode(), (int)respCode.get()); + assertEquals(200, response.statusCode()); + assertEquals((int)respCode.get(), response.statusCode()); } finally { server.stop(0); } @@ -248,8 +250,8 @@ public void testAfterHandlerRepeated() throws Exception { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "")).build(); var response = client.send(request, HttpResponse.BodyHandlers.ofString()); - assertEquals(response.statusCode(), 200); - assertEquals(attr.get(), value); + assertEquals(200, response.statusCode()); + assertEquals(value, attr.get()); } finally { server.stop(0); } @@ -280,8 +282,8 @@ public void testAfterHandlerSendResponse() throws Exception { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "")).build(); var response = client.send(request, HttpResponse.BodyHandlers.ofString()); - assertEquals(response.statusCode(), 200); - assertEquals(response.statusCode(), (int)respCode.get()); + assertEquals(200, response.statusCode()); + assertEquals((int)respCode.get(), response.statusCode()); } finally { server.stop(0); } @@ -304,10 +306,10 @@ public void testBeforeAndAfterHandler() throws Exception { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "")).build(); var response = client.send(request, HttpResponse.BodyHandlers.ofString()); - assertEquals(response.statusCode(), 200); - assertEquals(response.headers().map().size(), 3); - assertEquals(response.headers().firstValue("x-foo").orElseThrow(), "bar"); - assertEquals(response.statusCode(), (int)respCode.get()); + assertEquals(200, response.statusCode()); + assertEquals(3, response.headers().map().size()); + assertEquals("bar", response.headers().firstValue("x-foo").orElseThrow()); + assertEquals((int)respCode.get(), response.statusCode()); } finally { server.stop(0); } @@ -326,8 +328,8 @@ public void testInspectRequest() throws Exception { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "foo/bar")).build(); var response = client.send(request, HttpResponse.BodyHandlers.ofString()); - assertEquals(response.statusCode(), 200); - assertEquals(inspectedURI.get(), URI.create("/foo/bar")); + assertEquals(200, response.statusCode()); + assertEquals(URI.create("/foo/bar"), inspectedURI.get()); } finally { server.stop(0); } @@ -348,9 +350,9 @@ public void testAdaptRequest() throws Exception { }); var adaptFilter = Filter.adaptRequest("Add x-foo request header", r -> { // Confirm request state is unchanged - assertEquals(r.getRequestHeaders(), originalExchange.getRequestHeaders()); - assertEquals(r.getRequestURI(), originalExchange.getRequestURI()); - assertEquals(r.getRequestMethod(), originalExchange.getRequestMethod()); + assertEquals(originalExchange.getRequestHeaders(), r.getRequestHeaders()); + assertEquals(originalExchange.getRequestURI(), r.getRequestURI()); + assertEquals(originalExchange.getRequestMethod(), r.getRequestMethod()); return r.with("x-foo", List.of("bar")); }); var server = HttpServer.create(new InetSocketAddress(LOOPBACK_ADDR,0), 10); @@ -362,8 +364,8 @@ public void testAdaptRequest() throws Exception { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "")).build(); var response = client.send(request, HttpResponse.BodyHandlers.ofString()); - assertEquals(response.statusCode(), 200); - assertEquals(response.body(), "bar"); + assertEquals(200, response.statusCode()); + assertEquals("bar", response.body()); } finally { server.stop(0); } @@ -398,22 +400,22 @@ public void handle(HttpExchange exchange) throws IOException { static class CompareStateAndEchoHandler implements HttpHandler { @Override public void handle(HttpExchange exchange) throws IOException { - assertEquals(exchange.getLocalAddress(), originalExchange.getLocalAddress()); - assertEquals(exchange.getRemoteAddress(), originalExchange.getRemoteAddress()); - assertEquals(exchange.getProtocol(), originalExchange.getProtocol()); - assertEquals(exchange.getPrincipal(), originalExchange.getPrincipal()); - assertEquals(exchange.getHttpContext(), originalExchange.getHttpContext()); - assertEquals(exchange.getRequestMethod(), originalExchange.getRequestMethod()); - assertEquals(exchange.getRequestURI(), originalExchange.getRequestURI()); - assertEquals(exchange.getRequestBody(), originalExchange.getRequestBody()); - assertEquals(exchange.getResponseHeaders(), originalExchange.getResponseHeaders()); - assertEquals(exchange.getResponseCode(), originalExchange.getResponseCode()); - assertEquals(exchange.getResponseBody(), originalExchange.getResponseBody()); - assertEquals(exchange.getAttribute("foo"), originalExchange.getAttribute("foo")); + assertEquals(originalExchange.getLocalAddress(), exchange.getLocalAddress()); + assertEquals(originalExchange.getRemoteAddress(), exchange.getRemoteAddress()); + assertEquals(originalExchange.getProtocol(), exchange.getProtocol()); + assertEquals(originalExchange.getPrincipal(), exchange.getPrincipal()); + assertEquals(originalExchange.getHttpContext(), exchange.getHttpContext()); + assertEquals(originalExchange.getRequestMethod(), exchange.getRequestMethod()); + assertEquals(originalExchange.getRequestURI(), exchange.getRequestURI()); + assertEquals(originalExchange.getRequestBody(), exchange.getRequestBody()); + assertEquals(originalExchange.getResponseHeaders(), exchange.getResponseHeaders()); + assertEquals(originalExchange.getResponseCode(), exchange.getResponseCode()); + assertEquals(originalExchange.getResponseBody(), exchange.getResponseBody()); + assertEquals(originalExchange.getAttribute("foo"), exchange.getAttribute("foo")); assertFalse(exchange.getRequestHeaders().equals(originalExchange.getRequestHeaders())); exchange.setAttribute("foo", "barbar"); - assertEquals(exchange.getAttribute("foo"), originalExchange.getAttribute("foo")); + assertEquals(originalExchange.getAttribute("foo"), exchange.getAttribute("foo")); try (InputStream is = exchange.getRequestBody(); OutputStream os = exchange.getResponseBody()) { diff --git a/test/jdk/com/sun/net/httpserver/HeadersTest.java b/test/jdk/com/sun/net/httpserver/HeadersTest.java index c37aba0424f3..2c9ff93e1793 100644 --- a/test/jdk/com/sun/net/httpserver/HeadersTest.java +++ b/test/jdk/com/sun/net/httpserver/HeadersTest.java @@ -29,7 +29,7 @@ * jdk.httpserver/sun.net.httpserver:+open * @library /test/lib * @build jdk.test.lib.net.URIBuilder - * @run testng/othervm HeadersTest + * @run junit/othervm HeadersTest */ import java.io.IOException; @@ -57,18 +57,20 @@ import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; import jdk.test.lib.net.URIBuilder; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; import sun.net.httpserver.UnmodifiableHeaders; import static java.net.http.HttpClient.Builder.NO_PROXY; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotEquals; -import static org.testng.Assert.assertNotSame; -import static org.testng.Assert.assertSame; -import static org.testng.Assert.assertThrows; -import static org.testng.Assert.assertTrue; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; public class HeadersTest { @@ -77,13 +79,13 @@ public class HeadersTest { static final Class NPE = NullPointerException.class; @Test - public static void testDefaultConstructor() { + public void testDefaultConstructor() { var headers = new Headers(); assertTrue(headers.isEmpty()); } @Test - public static void testNull() { + public void testNull() { final Headers h = new Headers(); h.put("Foo", List.of("Bar")); @@ -157,8 +159,7 @@ public static void testNull() { assertThrows(NPE, () -> h.set("Foo", null)); } - @DataProvider - public Object[][] responseHeaders() { + public static Object[][] responseHeaders() { final var listWithNull = new LinkedList(); listWithNull.add(null); return new Object[][] { @@ -172,7 +173,8 @@ public Object[][] responseHeaders() { * Confirms HttpExchange::sendResponseHeaders throws NPE if response headers * contain a null key or value. */ - @Test(dataProvider = "responseHeaders") + @ParameterizedTest + @MethodSource("responseHeaders") public void testNullResponseHeaders(String headerKey, List headerVal) throws Exception { var handler = new Handler(headerKey, headerVal); @@ -183,7 +185,7 @@ public void testNullResponseHeaders(String headerKey, List headerVal) var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "")).build(); assertThrows(IOE, () -> client.send(request, HttpResponse.BodyHandlers.ofString())); - assertEquals(throwable.get().getClass(), NPE); + assertEquals(NPE, throwable.get().getClass()); assertTrue(Arrays.stream(throwable.get().getStackTrace()) .anyMatch(e -> e.getClassName().equals("sun.net.httpserver.HttpExchangeImpl") || e.getMethodName().equals("sendResponseHeaders"))); @@ -240,7 +242,6 @@ private static void putHeaders(Headers headers, } } - @DataProvider public static Object[][] headerPairs() { final var h1 = new Headers(); final var h2 = new Headers(); @@ -259,16 +260,16 @@ public static Object[][] headerPairs() { .toArray(Object[][]::new); } - @Test(dataProvider = "headerPairs") - public static void testEqualsAndHashCode(Headers h1, Headers h2) { - // avoid testng's asserts(Map, Map) as they don't call Headers::equals + @ParameterizedTest + @MethodSource("headerPairs") + public void testEqualsAndHashCode(Headers h1, Headers h2) { assertTrue(h1.equals(h2), "Headers differ"); - assertEquals(h1.hashCode(), h2.hashCode(), "hashCode differ for " + assertEquals(h2.hashCode(), h1.hashCode(), "hashCode differ for " + List.of(h1, h2)); } @Test - public static void testEqualsMap() { + public void testEqualsMap() { final var h = new Headers(); final var m = new HashMap>(); assertTrue(h.equals(m)); @@ -277,14 +278,14 @@ public static void testEqualsMap() { } @Test - public static void testToString() { + public void testToString() { final var h = new Headers(); h.put("Accept-Encoding", List.of("gzip, deflate")); assertTrue(h.toString().equals("Headers { {Accept-encoding=[gzip, deflate]} }")); } @Test - public static void testPutAll() { + public void testPutAll() { final var h0 = new Headers(); final var map = new HashMap>(); map.put("a", null); @@ -311,7 +312,7 @@ public static void testPutAll() { } @Test - public static void testReplaceAll() { + public void testReplaceAll() { final var h1 = new Headers(); h1.put("a", List.of("1")); h1.put("b", List.of("2")); @@ -331,7 +332,7 @@ public static void testReplaceAll() { } @Test - public static void test1ArgConstructorNull() { + public void test1ArgConstructorNull() { assertThrows(NPE, () -> new Headers(null)); { final var m = new HashMap>(); @@ -353,35 +354,35 @@ public static void test1ArgConstructorNull() { } @Test - public static void test1ArgConstructor() { + public void test1ArgConstructor() { { var h = new Headers(new Headers()); assertTrue(h.isEmpty()); } { var h = new Headers(Map.of("Foo", List.of("Bar"))); - assertEquals(h.get("Foo"), List.of("Bar")); - assertEquals(h.size(), 1); + assertEquals(List.of("Bar"), h.get("Foo")); + assertEquals(1, h.size()); } { var h1 = new Headers(new UnmodifiableHeaders(new Headers())); assertTrue(h1.isEmpty()); h1.put("Foo", List.of("Bar")); // modifiable - assertEquals(h1.get("Foo"), List.of("Bar")); - assertEquals(h1.size(), 1); + assertEquals(List.of("Bar"), h1.get("Foo")); + assertEquals(1, h1.size()); var h2 = new Headers(h1); - assertEquals(h2.get("Foo"), List.of("Bar")); - assertEquals(h2.size(), 1); + assertEquals(List.of("Bar"), h2.get("Foo")); + assertEquals(1, h2.size()); - assertEquals(h1, h2); + assertEquals(h2, h1); h1.set("Foo", "Barbar"); - assertNotEquals(h1, h2); + assertNotEquals(h2, h1); } } @Test - public static void testMutableHeaders() { + public void testMutableHeaders() { { var h = new Headers(); h.add("Foo", "Bar"); @@ -400,7 +401,7 @@ public static void testMutableHeaders() { } @Test - public static void testOfNull() { + public void testOfNull() { assertThrows(NPE, () -> Headers.of((String[])null)); assertThrows(NPE, () -> Headers.of(null, "Bar")); assertThrows(NPE, () -> Headers.of("Foo", null)); @@ -426,41 +427,40 @@ public static void testOfNull() { } @Test - public static void testOf() { + public void testOf() { final var h = Headers.of("a", "1", "b", "2"); - assertEquals(h.size(), 2); + assertEquals(2, h.size()); List.of("a", "b").forEach(n -> assertTrue(h.containsKey(n))); List.of("1", "2").forEach(v -> assertTrue(h.containsValue(List.of(v)))); } @Test - public static void testOfEmpty() { + public void testOfEmpty() { for (var h : List.of(Headers.of(), Headers.of(new String[] { }))) { - assertEquals(h.size(), 0); + assertEquals(0, h.size()); assertTrue(h.isEmpty()); } } @Test - public static void testOfNumberOfElements() { + public void testOfNumberOfElements() { assertThrows(IAE, () -> Headers.of("a")); assertThrows(IAE, () -> Headers.of("a", "1", "b")); } @Test - public static void testOfMultipleValues() { + public void testOfMultipleValues() { final var h = Headers.of("a", "1", "b", "1", "b", "2", "b", "3"); - assertEquals(h.size(), 2); + assertEquals(2, h.size()); List.of("a", "b").forEach(n -> assertTrue(h.containsKey(n))); List.of(List.of("1"), List.of("1", "2", "3")).forEach(v -> assertTrue(h.containsValue(v))); } @Test - public static void testNormalizeOnNull() { + public void testNormalizeOnNull() { assertThrows(NullPointerException.class, () -> normalize(null)); } - @DataProvider public static Object[][] illegalKeys() { var illegalChars = List.of('\r', '\n'); var illegalStrings = Stream @@ -478,12 +478,12 @@ public static Object[][] illegalKeys() { .toArray(Object[][]::new); } - @Test(dataProvider = "illegalKeys") - public static void testNormalizeOnIllegalKeys(String illegalKey) { + @ParameterizedTest + @MethodSource("illegalKeys") + public void testNormalizeOnIllegalKeys(String illegalKey) { assertThrows(IllegalArgumentException.class, () -> normalize(illegalKey)); } - @DataProvider public static Object[][] normalizedKeys() { return new Object[][]{ // Empty string @@ -501,13 +501,13 @@ public static Object[][] normalizedKeys() { }; } - @Test(dataProvider = "normalizedKeys") - public static void testNormalizeOnNormalizedKeys(String normalizedKey) { + @ParameterizedTest + @MethodSource("normalizedKeys") + public void testNormalizeOnNormalizedKeys(String normalizedKey) { // Verify that the fast-path is taken assertSame(normalize(normalizedKey), normalizedKey); } - @DataProvider public static Object[][] notNormalizedKeys() { return new Object[][]{ {"a"}, @@ -517,14 +517,15 @@ public static Object[][] notNormalizedKeys() { }; } - @Test(dataProvider = "notNormalizedKeys") - public static void testNormalizeOnNotNormalizedKeys(String notNormalizedKey) { + @ParameterizedTest + @MethodSource("notNormalizedKeys") + public void testNormalizeOnNotNormalizedKeys(String notNormalizedKey) { var normalizedKey = normalize(notNormalizedKey); // Verify that the fast-path is *not* taken assertNotSame(normalizedKey, notNormalizedKey); // Verify the result var expectedNormalizedKey = normalizedKey.substring(0, 1).toUpperCase() + normalizedKey.substring(1); - assertEquals(normalizedKey, expectedNormalizedKey); + assertEquals(expectedNormalizedKey, normalizedKey); } private static String normalize(String key) { diff --git a/test/jdk/com/sun/net/httpserver/HttpContextTest.java b/test/jdk/com/sun/net/httpserver/HttpContextTest.java index aa50f1d97579..82098e4e9936 100644 --- a/test/jdk/com/sun/net/httpserver/HttpContextTest.java +++ b/test/jdk/com/sun/net/httpserver/HttpContextTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,7 +26,7 @@ * @bug 8269692 * @summary HttpContext::createContext should throw IllegalArgumentException * if context already exists - * @run testng/othervm HttpContextTest + * @run junit/othervm HttpContextTest */ import java.io.IOException; @@ -34,22 +34,23 @@ import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; -import org.testng.annotations.Test; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertThrows; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import org.junit.jupiter.api.Test; public class HttpContextTest { static final Class IAE = IllegalArgumentException.class; @Test - public static void test() throws IOException { + public void test() throws IOException { final var server = HttpServer.create(null, 0); final var path = "/foo/"; assertThrows(IAE, () -> server.removeContext(path)); HttpContext context = server.createContext(path); - assertEquals(context.getPath(), path); + assertEquals(path, context.getPath()); assertThrows(IAE, () -> server.createContext(path)); assertThrows(IAE, () -> server.createContext(path, new Handler())); @@ -60,7 +61,7 @@ public static void test() throws IOException { assertThrows(IAE, () -> server.removeContext(path)); context = server.createContext(path, new Handler()); - assertEquals(context.getPath(), path); + assertEquals(path, context.getPath()); assertThrows(IAE, () -> server.createContext(path)); assertThrows(IAE, () -> server.createContext(path, new Handler())); server.removeContext(path); @@ -72,7 +73,7 @@ public static void test() throws IOException { * shares the prefix of an existing context. */ @Test - public static void testSubcontext() throws IOException { + public void testSubcontext() throws IOException { final var server = HttpServer.create(null, 0); server.createContext("/foo/bar/"); server.createContext("/foo/"); diff --git a/test/jdk/com/sun/net/httpserver/HttpPrincipalTest.java b/test/jdk/com/sun/net/httpserver/HttpPrincipalTest.java index 30cf0c4490c7..513a79feb608 100644 --- a/test/jdk/com/sun/net/httpserver/HttpPrincipalTest.java +++ b/test/jdk/com/sun/net/httpserver/HttpPrincipalTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,13 +25,13 @@ * @test * @bug 8251496 * @summary Tests for methods in HttpPrincipal - * @run testng/othervm HttpPrincipalTest + * @run junit/othervm HttpPrincipalTest */ import com.sun.net.httpserver.HttpPrincipal; -import org.testng.annotations.Test; -import static org.testng.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; public class HttpPrincipalTest { @@ -39,10 +39,10 @@ public class HttpPrincipalTest { public void testGetters() { var principal = new HttpPrincipal("test", "123"); - assertEquals(principal.getUsername(), "test"); - assertEquals(principal.getRealm(), "123"); - assertEquals(principal.getName(), "123:test"); - assertEquals(principal.toString(), principal.getName()); - assertEquals(("test"+"123").hashCode(), principal.hashCode()); + assertEquals("test", principal.getUsername()); + assertEquals("123", principal.getRealm()); + assertEquals("123:test", principal.getName()); + assertEquals(principal.getName(), principal.toString()); + assertEquals(principal.hashCode(), ("test"+"123").hashCode()); } } diff --git a/test/jdk/com/sun/net/httpserver/HttpServerProviderTest.java b/test/jdk/com/sun/net/httpserver/HttpServerProviderTest.java index 2912cdd40e19..db528c194889 100644 --- a/test/jdk/com/sun/net/httpserver/HttpServerProviderTest.java +++ b/test/jdk/com/sun/net/httpserver/HttpServerProviderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,19 +25,19 @@ * @test * @bug 8270286 * @summary Test for HttpServerProvider::loadProviderFromProperty - * @run testng/othervm + * @run junit/othervm * -Dcom.sun.net.httpserver.HttpServerProvider=HttpServerProviderTest$ProviderP * HttpServerProviderTest - * @run testng/othervm + * @run junit/othervm * -Dcom.sun.net.httpserver.HttpServerProvider=HttpServerProviderTest$ProviderPNPC * HttpServerProviderTest - * @run testng/othervm + * @run junit/othervm * -Dcom.sun.net.httpserver.HttpServerProvider=HttpServerProviderTest$ProviderNP * HttpServerProviderTest - * @run testng/othervm + * @run junit/othervm * -Dcom.sun.net.httpserver.HttpServerProvider=HttpServerProviderTest$ProviderT * HttpServerProviderTest - * @run testng/othervm + * @run junit/othervm * -Dcom.sun.net.httpserver.HttpServerProvider=DoesNotExist * HttpServerProviderTest */ @@ -48,10 +48,11 @@ import com.sun.net.httpserver.HttpServer; import com.sun.net.httpserver.HttpsServer; import com.sun.net.httpserver.spi.HttpServerProvider; -import org.testng.annotations.Test; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.expectThrows; + +import org.junit.jupiter.api.Assertions; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import org.junit.jupiter.api.Test; public class HttpServerProviderTest { public final static String PROPERTY_KEY = "com.sun.net.httpserver.HttpServerProvider"; @@ -70,7 +71,7 @@ public void test() throws Exception { private void testPublic() throws Exception { var n = ProviderP.class.getName(); - assertEquals(System.getProperty(PROPERTY_KEY), n); + assertEquals(n, System.getProperty(PROPERTY_KEY)); var p = HttpServerProvider.provider(); assertNull(p.createHttpServer(null, 0)); @@ -79,39 +80,39 @@ private void testPublic() throws Exception { private void testPublicNonPublicConstructor() { var n = ProviderPNPC.class.getName(); - assertEquals(System.getProperty(PROPERTY_KEY), n); + assertEquals(n, System.getProperty(PROPERTY_KEY)); - var e = expectThrows(ServiceConfigurationError.class, HttpServerProvider::provider); - assertEquals(e.getClass(), ServiceConfigurationError.class); - assertEquals(e.getCause().getClass(), IllegalAccessException.class); + var e = Assertions.assertThrows(ServiceConfigurationError.class, HttpServerProvider::provider); + assertEquals(ServiceConfigurationError.class, e.getClass()); + assertEquals(IllegalAccessException.class, e.getCause().getClass()); } private void testNonPublic() { var n = ProviderNP.class.getName(); - assertEquals(System.getProperty(PROPERTY_KEY), n); + assertEquals(n, System.getProperty(PROPERTY_KEY)); - var e = expectThrows(ServiceConfigurationError.class, HttpServerProvider::provider); - assertEquals(e.getClass(), ServiceConfigurationError.class); - assertEquals(e.getCause().getClass(), IllegalAccessException.class); + var e = Assertions.assertThrows(ServiceConfigurationError.class, HttpServerProvider::provider); + assertEquals(ServiceConfigurationError.class, e.getClass()); + assertEquals(IllegalAccessException.class, e.getCause().getClass()); } private void testThrowingConstructor() { var cn = ProviderT.class.getName(); - assertEquals(System.getProperty(PROPERTY_KEY), cn); + assertEquals(cn, System.getProperty(PROPERTY_KEY)); - var e = expectThrows(ServiceConfigurationError.class, HttpServerProvider::provider); - assertEquals(e.getClass(), ServiceConfigurationError.class); - assertEquals(e.getCause().getClass(), InvocationTargetException.class); - assertEquals(e.getCause().getCause().getMessage(), "throwing constructor"); + var e = Assertions.assertThrows(ServiceConfigurationError.class, HttpServerProvider::provider); + assertEquals(ServiceConfigurationError.class, e.getClass()); + assertEquals(InvocationTargetException.class, e.getCause().getClass()); + assertEquals("throwing constructor", e.getCause().getCause().getMessage()); } private void testBadData() { var cn = "DoesNotExist"; - assertEquals(System.getProperty(PROPERTY_KEY), cn); + assertEquals(cn, System.getProperty(PROPERTY_KEY)); - var e = expectThrows(ServiceConfigurationError.class, HttpServerProvider::provider); - assertEquals(e.getClass(), ServiceConfigurationError.class); - assertEquals(e.getCause().getClass(), ClassNotFoundException.class); + var e = Assertions.assertThrows(ServiceConfigurationError.class, HttpServerProvider::provider); + assertEquals(ServiceConfigurationError.class, e.getClass()); + assertEquals(ClassNotFoundException.class, e.getCause().getClass()); } /** diff --git a/test/jdk/com/sun/net/httpserver/InputNotRead.java b/test/jdk/com/sun/net/httpserver/InputNotRead.java index 5b67f8d8b24d..402a65fd646a 100644 --- a/test/jdk/com/sun/net/httpserver/InputNotRead.java +++ b/test/jdk/com/sun/net/httpserver/InputNotRead.java @@ -28,8 +28,8 @@ * read the request body and sends back a reply with no content, or when * the client closes its outputstream while the server tries to drains * its content. - * @run testng/othervm InputNotRead - * @run testng/othervm -Djava.net.preferIPv6Addresses=true InputNotRead + * @run junit/othervm InputNotRead + * @run junit/othervm -Djava.net.preferIPv6Addresses=true InputNotRead */ import java.io.BufferedReader; @@ -51,10 +51,11 @@ import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; -import org.testng.annotations.Test; import static java.nio.charset.StandardCharsets.*; +import org.junit.jupiter.api.Test; + public class InputNotRead { private static final int msgCode = 200; diff --git a/test/jdk/com/sun/net/httpserver/UnmodifiableHeadersTest.java b/test/jdk/com/sun/net/httpserver/UnmodifiableHeadersTest.java index 84fd5aceaa73..196cf36f010f 100644 --- a/test/jdk/com/sun/net/httpserver/UnmodifiableHeadersTest.java +++ b/test/jdk/com/sun/net/httpserver/UnmodifiableHeadersTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,7 +26,7 @@ * @bug 8251496 8333590 * @summary Test that UnmodifiableHeaders is in fact immutable * @modules jdk.httpserver/sun.net.httpserver:+open - * @run testng/othervm UnmodifiableHeadersTest + * @run junit/othervm UnmodifiableHeadersTest */ import java.io.InputStream; @@ -36,73 +36,75 @@ import java.util.AbstractMap; import java.util.List; import java.util.Map; +import java.util.stream.Stream; import com.sun.net.httpserver.Headers; import com.sun.net.httpserver.HttpContext; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpPrincipal; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; import sun.net.httpserver.UnmodifiableHeaders; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertThrows; -import static org.testng.Assert.assertTrue; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; public class UnmodifiableHeadersTest { @Test - public static void testEquality() { + public void testEquality() { var headers = new Headers(); var unmodifiableHeaders1 = new UnmodifiableHeaders(headers); - assertEquals(unmodifiableHeaders1, headers); - assertEquals(unmodifiableHeaders1.hashCode(), headers.hashCode()); - assertEquals(unmodifiableHeaders1.get("Foo"), headers.get("Foo")); + assertEquals(headers, unmodifiableHeaders1); + assertEquals(headers.hashCode(), unmodifiableHeaders1.hashCode()); + assertEquals(headers.get("Foo"), unmodifiableHeaders1.get("Foo")); headers.add("Foo", "Bar"); var unmodifiableHeaders2 = new UnmodifiableHeaders(headers); - assertEquals(unmodifiableHeaders2, headers); - assertEquals(unmodifiableHeaders2.hashCode(), headers.hashCode()); - assertEquals(unmodifiableHeaders2.get("Foo"), headers.get("Foo")); + assertEquals(headers, unmodifiableHeaders2); + assertEquals(headers.hashCode(), unmodifiableHeaders2.hashCode()); + assertEquals(headers.get("Foo"), unmodifiableHeaders2.get("Foo")); } - @DataProvider - public Object[][] headers() { + public static Stream headers() { var headers = new Headers(); headers.add("Foo", "Bar"); var exchange = new TestHttpExchange(headers); - return new Object[][] { - { exchange.getRequestHeaders() }, - { Headers.of("Foo", "Bar") }, - { Headers.of(Map.of("Foo", List.of("Bar"))) }, - }; + return Stream.of(exchange.getRequestHeaders(), + Headers.of("Foo", "Bar"), + Headers.of(Map.of("Foo", List.of("Bar")))); } - @Test(dataProvider = "headers") - public static void testUnmodifiableHeaders(Headers headers) { + @ParameterizedTest + @MethodSource("headers") + public void testUnmodifiableHeaders(Headers headers) { assertUnsupportedOperation(headers); assertUnmodifiableCollection(headers); assertUnmodifiableList(headers); } - @DataProvider - public Object[][] toStringHeaders() { + public static Stream toStringHeaders() { final Headers headers = new Headers(); headers.add("hello", "World"); - return new Object[][] { - { headers }, - { Headers.of("abc", "XYZ") }, - { Headers.of(Map.of("foo", List.of("Bar"))) }, - { Headers.of(Map.of("Hello", List.of())) }, - { Headers.of(Map.of("one", List.of("two", "THREE"))) }, - }; + return Stream.of( + headers, + Headers.of("abc", "XYZ"), + Headers.of(Map.of("foo", List.of("Bar"))), + Headers.of(Map.of("Hello", List.of())), + Headers.of(Map.of("one", List.of("two", "THREE"))) + ); } /* * Verify that the String returned by Headers.toString() contains the expected * key/value(s) */ - @Test(dataProvider = "toStringHeaders") + @ParameterizedTest + @MethodSource("toStringHeaders") public void testToString(final Headers headers) { final Headers copy = Headers.of(headers); assertNotNull(copy, "Headers.of() returned null"); diff --git a/test/jdk/com/sun/net/httpserver/bugs/BasicAuthenticatorExceptionCheck.java b/test/jdk/com/sun/net/httpserver/bugs/BasicAuthenticatorExceptionCheck.java index a07386be746b..b4a4dfda041a 100644 --- a/test/jdk/com/sun/net/httpserver/bugs/BasicAuthenticatorExceptionCheck.java +++ b/test/jdk/com/sun/net/httpserver/bugs/BasicAuthenticatorExceptionCheck.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,18 +27,20 @@ * @library /test/lib * @summary Ensure that correct exceptions are being thrown in * BasicAuthenticator constructor - * @run testng BasicAuthenticatorExceptionCheck + * @run junit BasicAuthenticatorExceptionCheck */ import java.nio.charset.Charset; import com.sun.net.httpserver.BasicAuthenticator; -import org.testng.annotations.Test; -import static org.testng.Assert.expectThrows; -import static org.testng.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + import static java.nio.charset.StandardCharsets.UTF_8; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + public class BasicAuthenticatorExceptionCheck { static final Class NPE = NullPointerException.class; @@ -62,42 +64,42 @@ public boolean checkCredentials(String username, String pw) { @Test public void testAuthenticationException() { - Throwable ex = expectThrows(NPE, () -> + Throwable ex = Assertions.assertThrows(NPE, () -> createBasicAuthenticator("/test", null)); System.out.println("Valid realm and Null charset provided - " + "NullPointerException thrown as expected: " + ex); - ex = expectThrows(NPE, () -> + ex = Assertions.assertThrows(NPE, () -> createBasicAuthenticator(null, UTF_8)); System.out.println("Null realm and valid charset provided - " + "NullPointerException thrown as expected: " + ex); - ex = expectThrows(IAE, () -> + ex = Assertions.assertThrows(IAE, () -> createBasicAuthenticator("", UTF_8)); - assertEquals(ex.getMessage(), "realm must not be empty"); + assertEquals("realm must not be empty", ex.getMessage()); System.out.println("Empty string for realm and valid charset provided - " + "IllegalArgumentException thrown as expected: " + ex); - ex = expectThrows(NPE, () -> + ex = Assertions.assertThrows(NPE, () -> createBasicAuthenticator(null)); System.out.println("Null realm provided - " + "NullPointerException thrown as expected: " + ex); - ex = expectThrows(IAE, () -> + ex = Assertions.assertThrows(IAE, () -> createBasicAuthenticator("")); - assertEquals(ex.getMessage(), "realm must not be empty"); + assertEquals("realm must not be empty", ex.getMessage()); System.out.println("Empty string for realm provided - " + "IllegalArgumentException thrown as expected: " + ex); - ex = expectThrows(IAE, () -> + ex = Assertions.assertThrows(IAE, () -> createBasicAuthenticator("\"/test\"")); - assertEquals(ex.getMessage(), "realm invalid: \"/test\""); + assertEquals("realm invalid: \"/test\"", ex.getMessage()); System.out.println("Invalid string for realm provided - " + "IllegalArgumentException thrown as expected: " + ex); - ex = expectThrows(IAE, () -> + ex = Assertions.assertThrows(IAE, () -> createBasicAuthenticator("\"")); - assertEquals(ex.getMessage(), "realm invalid: \""); + assertEquals("realm invalid: \"", ex.getMessage()); System.out.println("Invalid string for realm provided - " + "IllegalArgumentException thrown as expected: " + ex); diff --git a/test/jdk/com/sun/net/httpserver/simpleserver/CommandLineNegativeTest.java b/test/jdk/com/sun/net/httpserver/simpleserver/CommandLineNegativeTest.java index a82b0bf5a6f7..818469d00fc9 100644 --- a/test/jdk/com/sun/net/httpserver/simpleserver/CommandLineNegativeTest.java +++ b/test/jdk/com/sun/net/httpserver/simpleserver/CommandLineNegativeTest.java @@ -26,7 +26,7 @@ * @summary Negative tests for java -m jdk.httpserver command * @library /test/lib * @modules jdk.httpserver - * @run testng/othervm CommandLineNegativeTest + * @run junit/othervm CommandLineNegativeTest */ import java.io.IOException; @@ -37,13 +37,14 @@ import jdk.test.lib.process.OutputAnalyzer; import jdk.test.lib.process.ProcessTools; import jdk.test.lib.util.FileUtils; -import org.testng.SkipException; -import org.testng.annotations.AfterTest; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; import static java.lang.System.out; -import static org.testng.Assert.assertFalse; + +import org.junit.jupiter.api.AfterAll; +import static org.junit.jupiter.api.Assertions.assertFalse; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; public class CommandLineNegativeTest { @@ -55,8 +56,8 @@ public class CommandLineNegativeTest { static final Path TEST_FILE = TEST_DIR.resolve("file.txt"); static final String LOOPBACK_ADDR = InetAddress.getLoopbackAddress().getHostAddress(); - @BeforeTest - public void setup() throws IOException { + @BeforeAll + public static void setup() throws IOException { if (Files.exists(TEST_DIR)) { FileUtils.deleteFileTreeWithRetry(TEST_DIR); } @@ -64,15 +65,15 @@ public void setup() throws IOException { Files.createFile(TEST_FILE); } - @DataProvider - public Object[][] unknownOption() { + public static Object[][] unknownOption() { return new Object[][] { {"--unknownOption"}, {"null"} }; } - @Test(dataProvider = "unknownOption") + @ParameterizedTest + @MethodSource("unknownOption") public void testBadOption(String opt) throws Throwable { out.println("\n--- testUnknownOption, opt=\"%s\" ".formatted(opt)); simpleserver(JAVA, LOCALE_OPT, "-m", "jdk.httpserver", opt) @@ -80,8 +81,7 @@ public void testBadOption(String opt) throws Throwable { .shouldContain("Error: unknown option: " + opt); } - @DataProvider - public Object[][] tooManyOptionArgs() { + public static Object[][] tooManyOptionArgs() { return new Object[][] { {"-b", "localhost"}, {"-d", "/some/path"}, @@ -95,7 +95,8 @@ public Object[][] tooManyOptionArgs() { }; } - @Test(dataProvider = "tooManyOptionArgs") + @ParameterizedTest + @MethodSource("tooManyOptionArgs") public void testTooManyOptionArgs(String opt, String arg) throws Throwable { out.println("\n--- testTooManyOptionArgs, opt=\"%s\" ".formatted(opt)); simpleserver(JAVA, LOCALE_OPT, "-m", "jdk.httpserver", opt, arg, arg) @@ -103,8 +104,7 @@ public void testTooManyOptionArgs(String opt, String arg) throws Throwable { .shouldContain("Error: unknown option: " + arg); } - @DataProvider - public Object[][] noArg() { + public static Object[][] noArg() { return new Object[][] { {"-b", """ -b, --bind-address - Address to bind to. Default: %s (loopback). @@ -122,7 +122,8 @@ public Object[][] noArg() { }; } - @Test(dataProvider = "noArg") + @ParameterizedTest + @MethodSource("noArg") public void testNoArg(String opt, String msg) throws Throwable { out.println("\n--- testNoArg, opt=\"%s\" ".formatted(opt)); simpleserver(JAVA, LOCALE_OPT, "-m", "jdk.httpserver", opt) @@ -131,8 +132,7 @@ public void testNoArg(String opt, String msg) throws Throwable { .shouldContain(msg); } - @DataProvider - public Object[][] invalidValue() { + public static Object[][] invalidValue() { return new Object[][] { {"-b", "[127.0.0.1]"}, {"-b", "badhost"}, @@ -146,7 +146,8 @@ public Object[][] invalidValue() { }; } - @Test(dataProvider = "invalidValue") + @ParameterizedTest + @MethodSource("invalidValue") public void testInvalidValue(String opt, String val) throws Throwable { out.println("\n--- testInvalidValue, opt=\"%s\" ".formatted(opt)); simpleserver(JAVA, LOCALE_OPT, "-m", "jdk.httpserver", opt, val) @@ -154,10 +155,10 @@ public void testInvalidValue(String opt, String val) throws Throwable { .shouldContain("Error: invalid value given for " + opt + ": " + val); } - @DataProvider - public Object[][] portOptions() { return new Object[][] {{"-p"}, {"--port"}}; } + public static Object[][] portOptions() { return new Object[][] {{"-p"}, {"--port"}}; } - @Test(dataProvider = "portOptions") + @ParameterizedTest + @MethodSource("portOptions") public void testPortOutOfRange(String opt) throws Throwable { out.println("\n--- testPortOutOfRange, opt=\"%s\" ".formatted(opt)); simpleserver(JAVA, LOCALE_OPT, "-m", "jdk.httpserver", opt, "65536") // range 0 to 65535 @@ -165,10 +166,10 @@ public void testPortOutOfRange(String opt) throws Throwable { .shouldContain("Error: server config failed: " + "port out of range:65536"); } - @DataProvider - public Object[][] directoryOptions() { return new Object[][] {{"-d"}, {"--directory"}}; } + public static Object[][] directoryOptions() { return new Object[][] {{"-d"}, {"--directory"}}; } - @Test(dataProvider = "directoryOptions") + @ParameterizedTest + @MethodSource("directoryOptions") public void testRootNotADirectory(String opt) throws Throwable { out.println("\n--- testRootNotADirectory, opt=\"%s\" ".formatted(opt)); var file = TEST_FILE.toString(); @@ -178,7 +179,8 @@ public void testRootNotADirectory(String opt) throws Throwable { .shouldContain("Error: server config failed: " + "Path is not a directory: " + file); } - @Test(dataProvider = "directoryOptions") + @ParameterizedTest + @MethodSource("directoryOptions") public void testRootDoesNotExist(String opt) throws Throwable { out.println("\n--- testRootDoesNotExist, opt=\"%s\" ".formatted(opt)); Path root = TEST_DIR.resolve("not/existent/dir"); @@ -188,14 +190,12 @@ public void testRootDoesNotExist(String opt) throws Throwable { .shouldContain("Error: server config failed: " + "Path does not exist: " + root.toString()); } - @Test(dataProvider = "directoryOptions") + @ParameterizedTest + @MethodSource("directoryOptions") public void testRootNotReadable(String opt) throws Throwable { out.println("\n--- testRootNotReadable, opt=\"%s\" ".formatted(opt)); - if (Platform.isWindows()) { - // Not applicable to Windows. Reason: cannot revoke an owner's read - // access to a directory that was created by that owner - throw new SkipException("cannot run on Windows"); - } + Assumptions.assumeFalse(Platform.isWindows(), "cannot run on Windows"); // Not applicable to Windows. Reason: cannot revoke an owner's read + // access to a directory that was created by that owner Path root = Files.createDirectories(TEST_DIR.resolve("not/readable/dir")); try { root.toFile().setReadable(false, false); @@ -208,8 +208,8 @@ public void testRootNotReadable(String opt) throws Throwable { } } - @AfterTest - public void teardown() throws IOException { + @AfterAll + public static void teardown() throws IOException { if (Files.exists(TEST_DIR)) { FileUtils.deleteFileTreeWithRetry(TEST_DIR); } diff --git a/test/jdk/com/sun/net/httpserver/simpleserver/CommandLinePortNotSpecifiedTest.java b/test/jdk/com/sun/net/httpserver/simpleserver/CommandLinePortNotSpecifiedTest.java index 61a872a86554..c665f37abe7d 100644 --- a/test/jdk/com/sun/net/httpserver/simpleserver/CommandLinePortNotSpecifiedTest.java +++ b/test/jdk/com/sun/net/httpserver/simpleserver/CommandLinePortNotSpecifiedTest.java @@ -27,7 +27,7 @@ * @summary Tests the java -m jdk.httpserver command with port not specified * @modules jdk.httpserver * @library /test/lib - * @run testng/othervm/manual CommandLinePortNotSpecifiedTest + * @run junit/othervm/manual CommandLinePortNotSpecifiedTest */ import java.io.IOException; @@ -39,11 +39,12 @@ import jdk.test.lib.process.OutputAnalyzer; import jdk.test.lib.process.ProcessTools; import jdk.test.lib.util.FileUtils; -import org.testng.annotations.AfterTest; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.Test; import static java.lang.System.out; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + public class CommandLinePortNotSpecifiedTest { static final Path JAVA_HOME = Path.of(System.getProperty("java.home")); @@ -55,8 +56,8 @@ public class CommandLinePortNotSpecifiedTest { static final String TEST_DIR_STR = TEST_DIR.toString(); static final String LOOPBACK_ADDR = InetAddress.getLoopbackAddress().getHostAddress(); - @BeforeTest - public void setup() throws IOException { + @BeforeAll + public static void setup() throws IOException { if (Files.exists(TEST_DIR)) { FileUtils.deleteFileTreeWithRetry(TEST_DIR); } @@ -92,8 +93,8 @@ public void testPortNotSpecified() throws Throwable { .shouldContain("URL http://" + LOOPBACK_ADDR); } - @AfterTest - public void teardown() throws IOException { + @AfterAll + public static void teardown() throws IOException { if (Files.exists(TEST_DIR)) { FileUtils.deleteFileTreeWithRetry(TEST_DIR); } diff --git a/test/jdk/com/sun/net/httpserver/simpleserver/CommandLinePositiveTest.java b/test/jdk/com/sun/net/httpserver/simpleserver/CommandLinePositiveTest.java index 16d0ff67ee94..3c3ac0c215dd 100644 --- a/test/jdk/com/sun/net/httpserver/simpleserver/CommandLinePositiveTest.java +++ b/test/jdk/com/sun/net/httpserver/simpleserver/CommandLinePositiveTest.java @@ -27,7 +27,7 @@ * @library /test/lib * @build jdk.test.lib.net.IPSupport * @modules jdk.httpserver - * @run testng/othervm CommandLinePositiveTest + * @run junit/othervm CommandLinePositiveTest */ import java.io.IOException; @@ -41,12 +41,13 @@ import jdk.test.lib.process.OutputAnalyzer; import jdk.test.lib.process.ProcessTools; import jdk.test.lib.util.FileUtils; -import org.testng.annotations.AfterTest; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; import static java.lang.System.out; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + public class CommandLinePositiveTest { static final String JAVA_VERSION = System.getProperty("java.version"); @@ -84,8 +85,8 @@ public class CommandLinePositiveTest { static final String LOOPBACK_ADDR = InetAddress.getLoopbackAddress().getHostAddress(); - @BeforeTest - public void setup() throws IOException { + @BeforeAll + public static void setup() throws IOException { if (Files.exists(ROOT_DIR)) { FileUtils.deleteFileTreeWithRetry(ROOT_DIR); } @@ -105,16 +106,17 @@ static int normalExitCode() { } } - @DataProvider - public Object[][] directoryOptions() { return new Object[][] {{"-d"}, {"--directory"}}; } + public static Object[][] directoryOptions() { return new Object[][] {{"-d"}, {"--directory"}}; } - @Test(dataProvider = "directoryOptions") + @ParameterizedTest + @MethodSource("directoryOptions") public void testAbsDirectory(String opt) throws Throwable { out.printf("\n--- testAbsDirectory, opt=\"%s\"%n", opt); testDirectory(opt, ROOT_DIR_STR); } - @Test(dataProvider = "directoryOptions") + @ParameterizedTest + @MethodSource("directoryOptions") public void testRelDirectory(String opt) throws Throwable { out.printf("\n--- testRelDirectory, opt=\"%s\"%n", opt); Path rootRelDir = CWD.relativize(ROOT_DIR); @@ -129,10 +131,10 @@ private static void testDirectory(String opt, String rootDir) throws Throwable { .shouldContain("URL http://" + LOOPBACK_ADDR); } - @DataProvider - public Object[][] portOptions() { return new Object[][] {{"-p"}, {"--port"}}; } + public static Object[][] portOptions() { return new Object[][] {{"-p"}, {"--port"}}; } - @Test(dataProvider = "portOptions") + @ParameterizedTest + @MethodSource("portOptions") public void testPort(String opt) throws Throwable { out.println("\n--- testPort, opt=\"%s\" ".formatted(opt)); simpleserver(JAVA, LOCALE_OPT, "-m", "jdk.httpserver", opt, "0") @@ -142,8 +144,7 @@ public void testPort(String opt) throws Throwable { .shouldContain("URL http://" + LOOPBACK_ADDR); } - @DataProvider - public Object[][] helpOptions() { return new Object[][] {{"-h"}, {"-?"}, {"--help"}}; } + public static Object[][] helpOptions() { return new Object[][] {{"-h"}, {"-?"}, {"--help"}}; } static final String USAGE_TEXT = """ Usage: java -m jdk.httpserver [-b bind address] [-p port] [-d directory] @@ -161,7 +162,8 @@ public void testPort(String opt) throws Throwable { -version, --version - Prints version information and exits. To stop the server, press Ctrl + C.""".formatted(LOOPBACK_ADDR); - @Test(dataProvider = "helpOptions") + @ParameterizedTest + @MethodSource("helpOptions") public void testHelp(String opt) throws Throwable { out.println("\n--- testHelp, opt=\"%s\" ".formatted(opt)); simpleserver(WaitForLine.HELP_STARTUP_LINE, @@ -172,10 +174,10 @@ public void testHelp(String opt) throws Throwable { .shouldContain(OPTIONS_TEXT); } - @DataProvider - public Object[][] versionOptions() { return new Object[][] {{"-version"}, {"--version"}}; } + public static Object[][] versionOptions() { return new Object[][] {{"-version"}, {"--version"}}; } - @Test(dataProvider = "versionOptions") + @ParameterizedTest + @MethodSource("versionOptions") public void testVersion(String opt) throws Throwable { out.println("\n--- testVersion, opt=\"%s\" ".formatted(opt)); simpleserver(WaitForLine.VERSION_STARTUP_LINE, @@ -184,10 +186,10 @@ public void testVersion(String opt) throws Throwable { .shouldHaveExitValue(0); } - @DataProvider - public Object[][] bindOptions() { return new Object[][] {{"-b"}, {"--bind-address"}}; } + public static Object[][] bindOptions() { return new Object[][] {{"-b"}, {"--bind-address"}}; } - @Test(dataProvider = "bindOptions") + @ParameterizedTest + @MethodSource("bindOptions") public void testBindAllInterfaces(String opt) throws Throwable { out.println("\n--- testBindAllInterfaces, opt=\"%s\" ".formatted(opt)); simpleserver(JAVA, LOCALE_OPT, "-m", "jdk.httpserver", "-p", "0", opt, "0.0.0.0") @@ -202,7 +204,8 @@ public void testBindAllInterfaces(String opt) throws Throwable { } } - @Test(dataProvider = "bindOptions") + @ParameterizedTest + @MethodSource("bindOptions") public void testLastOneWinsBindAddress(String opt) throws Throwable { out.println("\n--- testLastOneWinsBindAddress, opt=\"%s\" ".formatted(opt)); simpleserver(JAVA, LOCALE_OPT, "-m", "jdk.httpserver", "-p", "0", opt, "123.4.5.6", opt, LOOPBACK_ADDR) @@ -212,7 +215,8 @@ public void testLastOneWinsBindAddress(String opt) throws Throwable { } - @Test(dataProvider = "directoryOptions") + @ParameterizedTest + @MethodSource("directoryOptions") public void testLastOneWinsDirectory(String opt) throws Throwable { out.println("\n--- testLastOneWinsDirectory, opt=\"%s\" ".formatted(opt)); simpleserver(JAVA, LOCALE_OPT, "-m", "jdk.httpserver", "-p", "0", opt, ROOT_DIR_STR, opt, ROOT_DIR_STR) @@ -222,10 +226,10 @@ public void testLastOneWinsDirectory(String opt) throws Throwable { .shouldContain("URL http://" + LOOPBACK_ADDR); } - @DataProvider - public Object[][] outputOptions() { return new Object[][] {{"-o"}, {"--output"}}; } + public static Object[][] outputOptions() { return new Object[][] {{"-o"}, {"--output"}}; } - @Test(dataProvider = "outputOptions") + @ParameterizedTest + @MethodSource("outputOptions") public void testLastOneWinsOutput(String opt) throws Throwable { out.println("\n--- testLastOneWinsOutput, opt=\"%s\" ".formatted(opt)); simpleserver(JAVA, LOCALE_OPT, "-m", "jdk.httpserver", "-p", "0", opt, "none", opt, "verbose") @@ -235,7 +239,8 @@ public void testLastOneWinsOutput(String opt) throws Throwable { .shouldContain("URL http://" + LOOPBACK_ADDR); } - @Test(dataProvider = "portOptions") + @ParameterizedTest + @MethodSource("portOptions") public void testLastOneWinsPort(String opt) throws Throwable { out.println("\n--- testLastOneWinsPort, opt=\"%s\" ".formatted(opt)); simpleserver(JAVA, LOCALE_OPT, "-m", "jdk.httpserver", opt, "-999", opt, "0") @@ -245,8 +250,8 @@ public void testLastOneWinsPort(String opt) throws Throwable { .shouldContain("URL http://" + LOOPBACK_ADDR); } - @AfterTest - public void teardown() throws IOException { + @AfterAll + public static void teardown() throws IOException { if (Files.exists(ROOT_DIR)) { FileUtils.deleteFileTreeWithRetry(ROOT_DIR); } diff --git a/test/jdk/com/sun/net/httpserver/simpleserver/CustomFileSystemTest.java b/test/jdk/com/sun/net/httpserver/simpleserver/CustomFileSystemTest.java index 0a46ac1ea8a5..fc289cd8c06f 100644 --- a/test/jdk/com/sun/net/httpserver/simpleserver/CustomFileSystemTest.java +++ b/test/jdk/com/sun/net/httpserver/simpleserver/CustomFileSystemTest.java @@ -27,7 +27,7 @@ * file system * @library /test/lib * @build jdk.test.lib.Platform jdk.test.lib.net.URIBuilder - * @run testng/othervm CustomFileSystemTest + * @run junit/othervm CustomFileSystemTest */ import java.io.IOException; @@ -71,16 +71,18 @@ import com.sun.net.httpserver.SimpleFileServer.OutputLevel; import jdk.test.lib.Platform; import jdk.test.lib.net.URIBuilder; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; -import org.testng.SkipException; import static java.net.http.HttpClient.Builder.NO_PROXY; import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.file.StandardOpenOption.CREATE; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; public class CustomFileSystemTest { static final InetSocketAddress LOOPBACK_ADDR = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0); @@ -88,8 +90,8 @@ public class CustomFileSystemTest { static final boolean ENABLE_LOGGING = true; static final Logger LOGGER = Logger.getLogger("com.sun.net.httpserver"); - @BeforeTest - public void setup() throws Exception { + @BeforeAll + public static void setup() throws Exception { if (ENABLE_LOGGING) { ConsoleHandler ch = new ConsoleHandler(); LOGGER.setLevel(Level.ALL); @@ -111,11 +113,11 @@ public void testFileGET() throws Exception { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "aFile.txt")).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 200); - assertEquals(response.body(), "some text"); - assertEquals(response.headers().firstValue("content-type").get(), "text/plain"); - assertEquals(response.headers().firstValue("content-length").get(), expectedLength); - assertEquals(response.headers().firstValue("last-modified").get(), lastModified); + assertEquals(200, response.statusCode()); + assertEquals("some text", response.body()); + assertEquals("text/plain", response.headers().firstValue("content-type").get()); + assertEquals(expectedLength, response.headers().firstValue("content-length").get()); + assertEquals(lastModified, response.headers().firstValue("last-modified").get()); } finally { server.stop(0); } @@ -140,11 +142,11 @@ public void testDirectoryGET() throws Exception { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "")).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 200); - assertEquals(response.headers().firstValue("content-type").get(), "text/html; charset=UTF-8"); - assertEquals(response.headers().firstValue("content-length").get(), expectedLength); - assertEquals(response.headers().firstValue("last-modified").get(), lastModified); - assertEquals(response.body(), expectedBody); + assertEquals(200, response.statusCode()); + assertEquals("text/html; charset=UTF-8", response.headers().firstValue("content-type").get()); + assertEquals(expectedLength, response.headers().firstValue("content-length").get()); + assertEquals(lastModified, response.headers().firstValue("last-modified").get()); + assertEquals(expectedBody, response.body()); } finally { server.stop(0); } @@ -164,11 +166,11 @@ public void testFileHEAD() throws Exception { var request = HttpRequest.newBuilder(uri(server, "aFile.txt")) .method("HEAD", HttpRequest.BodyPublishers.noBody()).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 200); - assertEquals(response.headers().firstValue("content-type").get(), "text/plain"); - assertEquals(response.headers().firstValue("content-length").get(), expectedLength); - assertEquals(response.headers().firstValue("last-modified").get(), lastModified); - assertEquals(response.body(), ""); + assertEquals(200, response.statusCode()); + assertEquals("text/plain", response.headers().firstValue("content-type").get()); + assertEquals(expectedLength, response.headers().firstValue("content-length").get()); + assertEquals(lastModified, response.headers().firstValue("last-modified").get()); + assertEquals("", response.body()); } finally { server.stop(0); } @@ -194,18 +196,17 @@ public void testDirectoryHEAD() throws Exception { var request = HttpRequest.newBuilder(uri(server, "")) .method("HEAD", HttpRequest.BodyPublishers.noBody()).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 200); - assertEquals(response.headers().firstValue("content-type").get(), "text/html; charset=UTF-8"); - assertEquals(response.headers().firstValue("content-length").get(), expectedLength); - assertEquals(response.headers().firstValue("last-modified").get(), lastModified); - assertEquals(response.body(), ""); + assertEquals(200, response.statusCode()); + assertEquals("text/html; charset=UTF-8", response.headers().firstValue("content-type").get()); + assertEquals(expectedLength, response.headers().firstValue("content-length").get()); + assertEquals(lastModified, response.headers().firstValue("last-modified").get()); + assertEquals("", response.body()); } finally { server.stop(0); } } - @DataProvider - public Object[][] indexFiles() { + public static Object[][] indexFiles() { var fileContent = openHTML + """

This is an index file

""" + closeHTML; @@ -221,7 +222,8 @@ public Object[][] indexFiles() { }; } - @Test(dataProvider = "indexFiles") + @ParameterizedTest + @MethodSource("indexFiles") public void testDirectoryWithIndexGET(String id, String filename, String contentType, @@ -241,11 +243,11 @@ public void testDirectoryWithIndexGET(String id, var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "")).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 200); - assertEquals(response.headers().firstValue("content-type").get(), contentType); - assertEquals(response.headers().firstValue("content-length").get(), contentLength); - assertEquals(response.headers().firstValue("last-modified").get(), lastModified); - assertEquals(response.body(), expectedBody); + assertEquals(200, response.statusCode()); + assertEquals(contentType, response.headers().firstValue("content-type").get()); + assertEquals(contentLength, response.headers().firstValue("content-length").get()); + assertEquals(lastModified, response.headers().firstValue("last-modified").get()); + assertEquals(expectedBody, response.body()); } finally { server.stop(0); if (serveIndexFile) { @@ -256,9 +258,7 @@ public void testDirectoryWithIndexGET(String id, @Test public void testNotReadableFileGET() throws Exception { - if (Platform.isWindows()) { - throw new SkipException("Not applicable on Windows"); - } + Assumptions.assumeFalse(Platform.isWindows(), "Not applicable on Windows"); var expectedBody = openHTML + """

File not found

/aFile.txt

@@ -276,9 +276,9 @@ public void testNotReadableFileGET() throws Exception { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "aFile.txt")).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 404); - assertEquals(response.headers().firstValue("content-length").get(), expectedLength); - assertEquals(response.body(), expectedBody); + assertEquals(404, response.statusCode()); + assertEquals(expectedLength, response.headers().firstValue("content-length").get()); + assertEquals(expectedBody, response.body()); } finally { server.stop(0); file.toFile().setReadable(true, false); @@ -287,9 +287,7 @@ public void testNotReadableFileGET() throws Exception { @Test public void testNotReadableSegmentGET() throws Exception { - if (Platform.isWindows()) { - throw new SkipException("Not applicable on Windows"); - } + Assumptions.assumeFalse(Platform.isWindows(), "Not applicable on Windows"); var expectedBody = openHTML + """

File not found

/dir/aFile.txt

@@ -309,9 +307,9 @@ public void testNotReadableSegmentGET() throws Exception { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "dir/aFile.txt")).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 404); - assertEquals(response.headers().firstValue("content-length").get(), expectedLength); - assertEquals(response.body(), expectedBody); + assertEquals(404, response.statusCode()); + assertEquals(expectedLength, response.headers().firstValue("content-length").get()); + assertEquals(expectedBody, response.body()); } finally { server.stop(0); dir.toFile().setReadable(true, false); @@ -333,9 +331,9 @@ public void testInvalidRequestURIGET() throws Exception { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "aFile?#.txt")).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 404); - assertEquals(response.headers().firstValue("content-length").get(), expectedLength); - assertEquals(response.body(), expectedBody); + assertEquals(404, response.statusCode()); + assertEquals(expectedLength, response.headers().firstValue("content-length").get()); + assertEquals(expectedBody, response.body()); } finally { server.stop(0); } @@ -356,9 +354,9 @@ public void testNotFoundGET() throws Exception { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "doesNotExist.txt")).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 404); - assertEquals(response.headers().firstValue("content-length").get(), expectedLength); - assertEquals(response.body(), expectedBody); + assertEquals(404, response.statusCode()); + assertEquals(expectedLength, response.headers().firstValue("content-length").get()); + assertEquals(expectedBody, response.body()); } finally { server.stop(0); } @@ -380,9 +378,9 @@ public void testNotFoundHEAD() throws Exception { var request = HttpRequest.newBuilder(uri(server, "doesNotExist.txt")) .method("HEAD", HttpRequest.BodyPublishers.noBody()).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 404); - assertEquals(response.headers().firstValue("content-length").get(), expectedLength); - assertEquals(response.body(), ""); + assertEquals(404, response.statusCode()); + assertEquals(expectedLength, response.headers().firstValue("content-length").get()); + assertEquals("", response.body()); } finally { server.stop(0); } @@ -406,9 +404,9 @@ public void testSymlinkGET() throws Exception { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "symlink")).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 404); - assertEquals(response.headers().firstValue("content-length").get(), expectedLength); - assertEquals(response.body(), expectedBody); + assertEquals(404, response.statusCode()); + assertEquals(expectedLength, response.headers().firstValue("content-length").get()); + assertEquals(expectedBody, response.body()); } finally { server.stop(0); } @@ -433,21 +431,21 @@ public void testSymlinkSegmentGET() throws Exception { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "symlink/aFile.txt")).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 404); - assertEquals(response.headers().firstValue("content-length").get(), expectedLength); - assertEquals(response.body(), expectedBody); + assertEquals(404, response.statusCode()); + assertEquals(expectedLength, response.headers().firstValue("content-length").get()); + assertEquals(expectedBody, response.body()); } finally { server.stop(0); } } - private void createSymLink(Path symlink, Path target) { + private static void createSymLink(Path symlink, Path target) { try { Files.createSymbolicLink(symlink, target); } catch (UnsupportedOperationException uoe) { - throw new SkipException("sym link creation not supported", uoe); + Assumptions.abort("sym link creation not supported"); } catch (IOException ioe) { - throw new SkipException("probably insufficient privileges to create sym links (Windows)", ioe); + Assumptions.abort("probably insufficient privileges to create sym links (Windows)"); } } @@ -470,9 +468,9 @@ public void testHiddenFileGET() throws Exception { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, fileName)).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 404); - assertEquals(response.headers().firstValue("content-length").get(), expectedLength); - assertEquals(response.body(), expectedBody); + assertEquals(404, response.statusCode()); + assertEquals(expectedLength, response.headers().firstValue("content-length").get()); + assertEquals(expectedBody, response.body()); } finally { server.stop(0); } @@ -494,15 +492,15 @@ public void testHiddenSegmentGET() throws Exception { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, ".hiddenDirectory/aFile.txt")).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 404); - assertEquals(response.headers().firstValue("content-length").get(), expectedLength); - assertEquals(response.body(), expectedBody); + assertEquals(404, response.statusCode()); + assertEquals(expectedLength, response.headers().firstValue("content-length").get()); + assertEquals(expectedBody, response.body()); } finally { server.stop(0); } } - private Path createHiddenFile(Path root) throws IOException { + private static Path createHiddenFile(Path root) throws IOException { Path file; if (Platform.isWindows()) { file = Files.createFile(root.resolve("aFile.txt")); @@ -514,7 +512,7 @@ private Path createHiddenFile(Path root) throws IOException { return file; } - private Path createFileInHiddenDirectory(Path root) throws IOException { + private static Path createFileInHiddenDirectory(Path root) throws IOException { Path dir; Path file; if (Platform.isWindows()) { @@ -549,17 +547,17 @@ public void testMovedPermanently() throws Exception { var uri = uri(server, "aDirectory"); var request = HttpRequest.newBuilder(uri).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 301); - assertEquals(response.headers().firstValue("content-length").get(), "0"); - assertEquals(response.headers().firstValue("location").get(), "/aDirectory/"); + assertEquals(301, response.statusCode()); + assertEquals("0", response.headers().firstValue("content-length").get()); + assertEquals("/aDirectory/", response.headers().firstValue("location").get()); // tests that query component is preserved during redirect var uri2 = uri(server, "aDirectory", "query"); var req2 = HttpRequest.newBuilder(uri2).build(); var res2 = client.send(req2, BodyHandlers.ofString()); - assertEquals(res2.statusCode(), 301); - assertEquals(res2.headers().firstValue("content-length").get(), "0"); - assertEquals(res2.headers().firstValue("location").get(), "/aDirectory/?query"); + assertEquals(301, res2.statusCode()); + assertEquals("0", res2.headers().firstValue("content-length").get()); + assertEquals("/aDirectory/?query", res2.headers().firstValue("location").get()); } { // tests that redirect to returned relative URI works @@ -568,10 +566,10 @@ public void testMovedPermanently() throws Exception { var uri = uri(server, "aDirectory"); var request = HttpRequest.newBuilder(uri).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 200); - assertEquals(response.body(), expectedBody); - assertEquals(response.headers().firstValue("content-type").get(), "text/html; charset=UTF-8"); - assertEquals(response.headers().firstValue("content-length").get(), expectedLength); + assertEquals(200, response.statusCode()); + assertEquals(expectedBody, response.body()); + assertEquals("text/html; charset=UTF-8", response.headers().firstValue("content-type").get()); + assertEquals(expectedLength, response.headers().firstValue("content-length").get()); } } finally { server.stop(0); @@ -588,7 +586,7 @@ public void testXss() throws Exception { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "beginDelim%3C%3EEndDelim")).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 404); + assertEquals(404, response.statusCode()); assertTrue(response.body().contains("beginDelim%3C%3EEndDelim")); assertTrue(response.body().contains("File not found")); } finally { diff --git a/test/jdk/com/sun/net/httpserver/simpleserver/FileServerHandlerTest.java b/test/jdk/com/sun/net/httpserver/simpleserver/FileServerHandlerTest.java index a5179b03f16f..85bad2153755 100644 --- a/test/jdk/com/sun/net/httpserver/simpleserver/FileServerHandlerTest.java +++ b/test/jdk/com/sun/net/httpserver/simpleserver/FileServerHandlerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,7 +24,7 @@ /* * @test * @summary Tests for FileServerHandler - * @run testng FileServerHandlerTest + * @run junit FileServerHandlerTest */ import java.io.ByteArrayInputStream; @@ -44,42 +44,44 @@ import com.sun.net.httpserver.HttpPrincipal; import com.sun.net.httpserver.HttpServer; import com.sun.net.httpserver.SimpleFileServer; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; -import static org.testng.Assert.*; + +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; public class FileServerHandlerTest { static final Path CWD = Path.of(".").toAbsolutePath(); static final Class RE = RuntimeException.class; - @DataProvider - public Object[][] notAllowedMethods() { + public static Object[][] notAllowedMethods() { var l = List.of("POST", "PUT", "DELETE", "TRACE", "OPTIONS"); return l.stream().map(s -> new Object[] { s }).toArray(Object[][]::new); } - @Test(dataProvider = "notAllowedMethods") + @ParameterizedTest + @MethodSource("notAllowedMethods") public void testNotAllowedRequestMethod(String requestMethod) throws Exception { var handler = SimpleFileServer.createFileHandler(CWD); var exchange = new MethodHttpExchange(requestMethod); handler.handle(exchange); - assertEquals(exchange.rCode, 405); - assertEquals(exchange.getResponseHeaders().getFirst("allow"), "HEAD, GET"); + assertEquals(405, exchange.rCode); + assertEquals("HEAD, GET", exchange.getResponseHeaders().getFirst("allow")); } - @DataProvider - public Object[][] notImplementedMethods() { + public static Object[][] notImplementedMethods() { var l = List.of("GARBAGE", "RUBBISH", "TRASH", "FOO", "BAR"); return l.stream().map(s -> new Object[] { s }).toArray(Object[][]::new); } - @Test(dataProvider = "notImplementedMethods") + @ParameterizedTest + @MethodSource("notImplementedMethods") public void testNotImplementedRequestMethod(String requestMethod) throws Exception { var handler = SimpleFileServer.createFileHandler(CWD); var exchange = new MethodHttpExchange(requestMethod); handler.handle(exchange); - assertEquals(exchange.rCode, 501); + assertEquals(501, exchange.rCode); } // 301 and 404 response codes tested in SimpleFileServerTest @@ -93,8 +95,8 @@ public InputStream getRequestBody() { throw new RuntimeException("getRequestBody"); } }; - var t = expectThrows(RE, () -> h.handle(exchange)); - assertEquals(t.getMessage(), "getRequestBody"); + var t = assertThrows(RE, () -> h.handle(exchange)); + assertEquals("getRequestBody", t.getMessage()); } { var exchange = new ThrowingHttpExchange("GET") { @@ -102,8 +104,8 @@ public Headers getResponseHeaders() { throw new RuntimeException("getResponseHeaders"); } }; - var t = expectThrows(RE, () -> h.handle(exchange)); - assertEquals(t.getMessage(), "getResponseHeaders"); + var t = assertThrows(RE, () -> h.handle(exchange)); + assertEquals("getResponseHeaders", t.getMessage()); } { var exchange = new ThrowingHttpExchange("GET") { @@ -111,8 +113,8 @@ public void sendResponseHeaders(int rCode, long responseLength) { throw new RuntimeException("sendResponseHeaders"); } }; - var t = expectThrows(RE, () -> h.handle(exchange)); - assertEquals(t.getMessage(), "sendResponseHeaders"); + var t = assertThrows(RE, () -> h.handle(exchange)); + assertEquals("sendResponseHeaders", t.getMessage()); } { var exchange = new ThrowingHttpExchange("GET") { @@ -120,8 +122,8 @@ public OutputStream getResponseBody() { throw new RuntimeException("getResponseBody"); } }; - var t = expectThrows(RE, () -> h.handle(exchange)); - assertEquals(t.getMessage(), "getResponseBody"); + var t = assertThrows(RE, () -> h.handle(exchange)); + assertEquals("getResponseBody", t.getMessage()); } { var exchange = new ThrowingHttpExchange("GET") { @@ -129,8 +131,8 @@ public void close() { throw new RuntimeException("close"); } }; - var t = expectThrows(RE, () -> h.handle(exchange)); - assertEquals(t.getMessage(), "close"); + var t = assertThrows(RE, () -> h.handle(exchange)); + assertEquals("close", t.getMessage()); } } diff --git a/test/jdk/com/sun/net/httpserver/simpleserver/HttpHandlersTest.java b/test/jdk/com/sun/net/httpserver/simpleserver/HttpHandlersTest.java index 85d271e44fa0..46c7085ae147 100644 --- a/test/jdk/com/sun/net/httpserver/simpleserver/HttpHandlersTest.java +++ b/test/jdk/com/sun/net/httpserver/simpleserver/HttpHandlersTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,7 +26,7 @@ * @summary Tests for HttpHandlers * @library /test/lib * @build jdk.test.lib.net.URIBuilder - * @run testng/othervm HttpHandlersTest + * @run junit/othervm HttpHandlersTest */ import java.io.IOException; @@ -44,12 +44,14 @@ import java.util.logging.Logger; import jdk.test.lib.net.URIBuilder; import com.sun.net.httpserver.*; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; import static java.net.http.HttpClient.Builder.NO_PROXY; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.testng.Assert.*; + +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; public class HttpHandlersTest { @@ -61,8 +63,8 @@ public class HttpHandlersTest { static final boolean ENABLE_LOGGING = true; static final Logger LOGGER = Logger.getLogger("com.sun.net.httpserver"); - @BeforeTest - public void setup() { + @BeforeAll + public static void setup() { if (ENABLE_LOGGING) { ConsoleHandler ch = new ConsoleHandler(); LOGGER.setLevel(Level.ALL); @@ -103,11 +105,11 @@ public void testOfNoBody() throws Exception { var request = HttpRequest.newBuilder(uri(server, "")).build(); var response = client.send(request, BodyHandlers.ofString()); assertTrue(response.headers().map().containsKey("date")); - assertEquals(response.headers().firstValue("foo").get(), "bar"); - assertEquals(response.headers().firstValue("content-length").get(), "0"); - assertEquals(response.headers().map().size(), 3); - assertEquals(response.statusCode(), 200); - assertEquals(response.body(), ""); + assertEquals("bar", response.headers().firstValue("foo").get()); + assertEquals("0", response.headers().firstValue("content-length").get()); + assertEquals(3, response.headers().map().size()); + assertEquals(200, response.statusCode()); + assertEquals("", response.body()); } finally { server.stop(0); } @@ -125,11 +127,11 @@ public void testOfWithBody() throws Exception { var request = HttpRequest.newBuilder(uri(server, "")).build(); var response = client.send(request, BodyHandlers.ofString()); assertTrue(response.headers().map().containsKey("date")); - assertEquals(response.headers().firstValue("foo").get(), "bar"); - assertEquals(response.headers().firstValue("content-length").get(), expectedLength); - assertEquals(response.headers().map().size(), 3); - assertEquals(response.statusCode(), 200); - assertEquals(response.body(), "hello world"); + assertEquals("bar", response.headers().firstValue("foo").get()); + assertEquals(expectedLength, response.headers().firstValue("content-length").get()); + assertEquals(3, response.headers().map().size()); + assertEquals(200, response.statusCode()); + assertEquals("hello world", response.body()); } finally { server.stop(0); } @@ -148,9 +150,9 @@ public void testOfHeadRequest() throws Exception { .method("HEAD", BodyPublishers.noBody()).build(); var response = client.send(request, BodyHandlers.ofString()); assertTrue(response.headers().map().containsKey("date")); - assertEquals(response.headers().firstValue("content-length").get(), expectedLength); - assertEquals(response.headers().map().size(), 2); - assertEquals(response.statusCode(), 200); + assertEquals(expectedLength, response.headers().firstValue("content-length").get()); + assertEquals(2, response.headers().map().size()); + assertEquals(200, response.statusCode()); } finally { server.stop(0); } @@ -168,22 +170,22 @@ public void testOfOverwriteHeaders() throws Exception { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "")).build(); var response = client.send(request, BodyHandlers.ofString()); - assertNotEquals(response.headers().firstValue("date").get(), "12345"); - assertEquals(response.headers().firstValue("content-length").get(), expectedLength); - assertEquals(response.headers().map().size(), 2); - assertEquals(response.statusCode(), 200); - assertEquals(response.body(), "hello world"); + assertNotEquals("12345", response.headers().firstValue("date").get()); + assertEquals(expectedLength, response.headers().firstValue("content-length").get()); + assertEquals(2, response.headers().map().size()); + assertEquals(200, response.statusCode()); + assertEquals("hello world", response.body()); } finally { server.stop(0); } } - @DataProvider - public Object[][] responseBodies() { + public static Object[][] responseBodies() { return new Object[][] { {"hello world"}, {""} }; } - @Test(dataProvider = "responseBodies") + @ParameterizedTest + @MethodSource("responseBodies") public void testOfThrowingExchange(String body) { var h = HttpHandlers.of(200, Headers.of(), body); { @@ -192,8 +194,8 @@ public InputStream getRequestBody() { throw new RuntimeException("getRequestBody"); } }; - var t = expectThrows(RE, () -> h.handle(exchange)); - assertEquals(t.getMessage(), "getRequestBody"); + var t = assertThrows(RE, () -> h.handle(exchange)); + assertEquals("getRequestBody", t.getMessage()); } { var exchange = new ThrowingHttpExchange() { @@ -201,8 +203,8 @@ public Headers getResponseHeaders() { throw new RuntimeException("getResponseHeaders"); } }; - var t = expectThrows(RE, () -> h.handle(exchange)); - assertEquals(t.getMessage(), "getResponseHeaders"); + var t = assertThrows(RE, () -> h.handle(exchange)); + assertEquals("getResponseHeaders", t.getMessage()); } { var exchange = new ThrowingHttpExchange() { @@ -210,8 +212,8 @@ public void sendResponseHeaders(int rCode, long responseLength) { throw new RuntimeException("sendResponseHeaders"); } }; - var t = expectThrows(RE, () -> h.handle(exchange)); - assertEquals(t.getMessage(), "sendResponseHeaders"); + var t = assertThrows(RE, () -> h.handle(exchange)); + assertEquals("sendResponseHeaders", t.getMessage()); } { var exchange = new ThrowingHttpExchange() { @@ -220,8 +222,8 @@ public OutputStream getResponseBody() { } }; if (!body.isEmpty()) { // getResponseBody not called if no responseBody - var t = expectThrows(RE, () -> h.handle(exchange)); - assertEquals(t.getMessage(), "getResponseBody"); + var t = assertThrows(RE, () -> h.handle(exchange)); + assertEquals("getResponseBody", t.getMessage()); } } { @@ -230,8 +232,8 @@ public void close() { throw new RuntimeException("close"); } }; - var t = expectThrows(RE, () -> h.handle(exchange)); - assertEquals(t.getMessage(), "close"); + var t = assertThrows(RE, () -> h.handle(exchange)); + assertEquals("close", t.getMessage()); } } @@ -247,8 +249,8 @@ public void testHandleOrElseTrue() throws Exception { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "")).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 200); - assertEquals(response.body(), "TestHandler-1"); + assertEquals(200, response.statusCode()); + assertEquals("TestHandler-1", response.body()); } finally { server.stop(0); } @@ -266,8 +268,8 @@ public void testHandleOrElseFalse() throws Exception { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "")).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 200); - assertEquals(response.body(), "TestHandler-2"); + assertEquals(200, response.statusCode()); + assertEquals("TestHandler-2", response.body()); } finally { server.stop(0); } @@ -287,8 +289,8 @@ public void testHandleOrElseNested() throws Exception { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "")).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 200); - assertEquals(response.body(), "TestHandler-2"); + assertEquals(200, response.statusCode()); + assertEquals("TestHandler-2", response.body()); } finally { server.stop(0); } diff --git a/test/jdk/com/sun/net/httpserver/simpleserver/HttpsServerAlertTest.java b/test/jdk/com/sun/net/httpserver/simpleserver/HttpsServerAlertTest.java index 07433bdbb2a8..ba6812ffaa5f 100644 --- a/test/jdk/com/sun/net/httpserver/simpleserver/HttpsServerAlertTest.java +++ b/test/jdk/com/sun/net/httpserver/simpleserver/HttpsServerAlertTest.java @@ -27,15 +27,13 @@ * @summary Test if HttpsServer sends the TLS alerts produced * @library /test/lib * @build jdk.test.lib.net.SimpleSSLContext - * @run testng/othervm HttpsServerAlertTest + * @run junit/othervm HttpsServerAlertTest */ import com.sun.net.httpserver.HttpsConfigurator; import com.sun.net.httpserver.HttpsParameters; import com.sun.net.httpserver.HttpsServer; import jdk.test.lib.net.SimpleSSLContext; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.Test; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLHandshakeException; @@ -50,7 +48,9 @@ import java.util.logging.Level; import java.util.logging.Logger; -import static org.testng.Assert.fail; +import static org.junit.jupiter.api.Assertions.fail; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; public class HttpsServerAlertTest { @@ -65,8 +65,8 @@ public class HttpsServerAlertTest { SSLContext.setDefault(sslContext); } - @BeforeTest - public void setup() throws IOException { + @BeforeAll + public static void setup() throws IOException { if (ENABLE_LOGGING) { ConsoleHandler ch = new ConsoleHandler(); LOGGER.setLevel(Level.ALL); diff --git a/test/jdk/com/sun/net/httpserver/simpleserver/HttpsServerTest.java b/test/jdk/com/sun/net/httpserver/simpleserver/HttpsServerTest.java index 783f159f5c1a..794067592dfd 100644 --- a/test/jdk/com/sun/net/httpserver/simpleserver/HttpsServerTest.java +++ b/test/jdk/com/sun/net/httpserver/simpleserver/HttpsServerTest.java @@ -26,7 +26,7 @@ * @summary Test for HttpsServer::create * @library /test/lib * @build jdk.test.lib.Platform jdk.test.lib.net.URIBuilder - * @run testng/othervm HttpsServerTest + * @run junit/othervm HttpsServerTest */ import java.io.IOException; @@ -51,12 +51,13 @@ import javax.net.ssl.SSLContext; import jdk.test.lib.net.SimpleSSLContext; import jdk.test.lib.net.URIBuilder; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.Test; import static java.net.http.HttpClient.Builder.NO_PROXY; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertThrows; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; public class HttpsServerTest { @@ -72,8 +73,8 @@ public class HttpsServerTest { SSLContext.setDefault(sslContext); } - @BeforeTest - public void setup() throws IOException { + @BeforeAll + public static void setup() throws IOException { if (ENABLE_LOGGING) { ConsoleHandler ch = new ConsoleHandler(); LOGGER.setLevel(Level.ALL); @@ -97,12 +98,12 @@ public void testCreate() throws IOException { final var s1 = HttpsServer.create(null, 0); assertNull(s1.getAddress()); s1.bind((LOOPBACK_ADDR), 0); - assertEquals(s1.getAddress().getAddress(), LOOPBACK_ADDR.getAddress()); + assertEquals(LOOPBACK_ADDR.getAddress(), s1.getAddress().getAddress()); final var s2 = HttpsServer.create(null, 0, "/foo/", new Handler()); assertNull(s2.getAddress()); s2.bind(LOOPBACK_ADDR, 0); - assertEquals(s2.getAddress().getAddress(), LOOPBACK_ADDR.getAddress()); + assertEquals(LOOPBACK_ADDR.getAddress(), s2.getAddress().getAddress()); s2.removeContext("/foo/"); // throws if context doesn't exist } @@ -119,11 +120,10 @@ public void testExchange() throws Exception { .build(); var request = HttpRequest.newBuilder(uri(server, "/test")).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 200); - assertEquals(response.body(), "hello world"); - assertEquals(response.headers().firstValue("content-length").get(), - Integer.toString("hello world".length())); - assertEquals(response.statusCode(), filter.responseCode.get().intValue()); + assertEquals(200, response.statusCode()); + assertEquals("hello world", response.body()); + assertEquals( Integer.toString("hello world".length()), response.headers().firstValue("content-length").get()); + assertEquals(filter.responseCode.get().intValue(), response.statusCode()); } finally { server.stop(0); } diff --git a/test/jdk/com/sun/net/httpserver/simpleserver/IdempotencyAndCommutativityTest.java b/test/jdk/com/sun/net/httpserver/simpleserver/IdempotencyAndCommutativityTest.java index d98944077dcb..2fde02efe702 100644 --- a/test/jdk/com/sun/net/httpserver/simpleserver/IdempotencyAndCommutativityTest.java +++ b/test/jdk/com/sun/net/httpserver/simpleserver/IdempotencyAndCommutativityTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -37,14 +37,15 @@ import jdk.test.lib.net.URIBuilder; import com.sun.net.httpserver.HttpServer; import com.sun.net.httpserver.SimpleFileServer; -import org.testng.annotations.AfterTest; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; import static java.net.http.HttpClient.Builder.NO_PROXY; import static java.nio.file.StandardOpenOption.CREATE; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; + +import org.junit.jupiter.api.AfterAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; /* * @test @@ -52,7 +53,7 @@ * set of binary request sequences * @library /test/lib * @build jdk.test.lib.net.URIBuilder - * @run testng/othervm IdempotencyAndCommutativityTest + * @run junit/othervm IdempotencyAndCommutativityTest */ public class IdempotencyAndCommutativityTest { @@ -69,8 +70,8 @@ public class IdempotencyAndCommutativityTest { static final boolean ENABLE_LOGGING = true; static final Logger LOGGER = Logger.getLogger("com.sun.net.httpserver"); - @BeforeTest - public void setup() throws IOException { + @BeforeAll + public static void setup() throws IOException { if (ENABLE_LOGGING) { ConsoleHandler ch = new ConsoleHandler(); LOGGER.setLevel(Level.ALL); @@ -89,8 +90,7 @@ public void setup() throws IOException { record ExchangeValues(String method, String resource, int respCode, String contentType) {} // Creates an exhaustive set of binary exchange sequences - @DataProvider - public Object[][] allBinarySequences() { + public static Object[][] allBinarySequences() { final List sequences = List.of( new ExchangeValues("GET", FILE_NAME, 200, "text/plain"), new ExchangeValues("GET", DIR_NAME, 200, "text/html; charset=UTF-8"), @@ -108,7 +108,8 @@ public Object[][] allBinarySequences() { .toArray(Object[][]::new); } - @Test(dataProvider = "allBinarySequences") + @ParameterizedTest + @MethodSource("allBinarySequences") public void testBinarySequences(ExchangeValues e1, ExchangeValues e2) throws Exception { System.out.println("---"); System.out.println(e1); @@ -122,15 +123,15 @@ private static void executeExchange(ExchangeValues e) throws Exception { .method(e.method(), HttpRequest.BodyPublishers.noBody()) .build(); var response = client.send(request, HttpResponse.BodyHandlers.ofString()); - assertEquals(response.statusCode(), e.respCode()); + assertEquals(e.respCode(), response.statusCode()); if (e.contentType != null) { - assertEquals(response.headers().firstValue("content-type").get(), e.contentType()); + assertEquals(e.contentType(), response.headers().firstValue("content-type").get()); } else { assertTrue(response.headers().firstValue("content-type").isEmpty()); } } - @AfterTest + @AfterAll public static void teardown() { server.stop(0); } diff --git a/test/jdk/com/sun/net/httpserver/simpleserver/MapToPathTest.java b/test/jdk/com/sun/net/httpserver/simpleserver/MapToPathTest.java index aa8f37acc1b4..38f0b610741e 100644 --- a/test/jdk/com/sun/net/httpserver/simpleserver/MapToPathTest.java +++ b/test/jdk/com/sun/net/httpserver/simpleserver/MapToPathTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,7 +27,7 @@ * system path * @library /test/lib * @build jdk.test.lib.Platform jdk.test.lib.net.URIBuilder - * @run testng/othervm MapToPathTest + * @run junit/othervm MapToPathTest */ import java.io.IOException; @@ -55,13 +55,14 @@ import com.sun.net.httpserver.SimpleFileServer.OutputLevel; import jdk.test.lib.net.URIBuilder; import jdk.test.lib.util.FileUtils; -import org.testng.annotations.AfterTest; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.Test; import static java.lang.System.out; import static java.net.http.HttpClient.Builder.NO_PROXY; import static java.nio.file.StandardOpenOption.CREATE; -import static org.testng.Assert.assertEquals; + +import org.junit.jupiter.api.AfterAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; public class MapToPathTest { @@ -74,8 +75,8 @@ public class MapToPathTest { static final boolean ENABLE_LOGGING = true; static final Logger LOGGER = Logger.getLogger("com.sun.net.httpserver"); - @BeforeTest - public void setup() throws IOException { + @BeforeAll + public static void setup() throws IOException { if (ENABLE_LOGGING) { ConsoleHandler ch = new ConsoleHandler(); LOGGER.setLevel(Level.ALL); @@ -88,7 +89,7 @@ public void setup() throws IOException { createDirectories(TEST_DIR); } - private void createDirectories(Path testDir) throws IOException { + private static void createDirectories(Path testDir) throws IOException { // Create directory tree: // // |-- TEST_DIR @@ -124,40 +125,40 @@ public void test() throws Exception { try { var req1 = HttpRequest.newBuilder(uri(server, "/")).build(); var res1 = client.send(req1, BodyHandlers.ofString()); - assertEquals(res1.statusCode(), 200); - assertEquals(res1.headers().firstValue("content-type").get(), "text/html; charset=UTF-8"); - assertEquals(res1.headers().firstValue("content-length").get(), Long.toString(257L)); - assertEquals(res1.headers().firstValue("last-modified").get(), getLastModified(TEST_DIR)); + assertEquals(200, res1.statusCode()); + assertEquals("text/html; charset=UTF-8", res1.headers().firstValue("content-type").get()); + assertEquals(Long.toString(257L), res1.headers().firstValue("content-length").get()); + assertEquals(getLastModified(TEST_DIR), res1.headers().firstValue("last-modified").get()); var req2 = HttpRequest.newBuilder(uri(server, "/../")).build(); var res2 = client.send(req2, BodyHandlers.ofString()); - assertEquals(res2.statusCode(), 404); // cannot escape root + assertEquals(404, res2.statusCode()); // cannot escape root var req3 = HttpRequest.newBuilder(uri(server, "/foo/bar/baz/c://")).build(); var res3 = client.send(req3, BodyHandlers.ofString()); - assertEquals(res3.statusCode(), 404); // not found + assertEquals(404, res3.statusCode()); // not found var req4 = HttpRequest.newBuilder(uri(server, "/foo/bar/baz/c:.//")).build(); var res4 = client.send(req4, BodyHandlers.ofString()); - assertEquals(res4.statusCode(), 404); // not found + assertEquals(404, res4.statusCode()); // not found var req5 = HttpRequest.newBuilder(uri(server, "/foo/bar/baz/c:..//")).build(); var res5 = client.send(req5, BodyHandlers.ofString()); - assertEquals(res5.statusCode(), 404); // not found + assertEquals(404, res5.statusCode()); // not found var req6 = HttpRequest.newBuilder(uri(server, "/foo/file:" + TEST_DIR.getParent())).build(); var res6 = client.send(req6, BodyHandlers.ofString()); - assertEquals(res6.statusCode(), 404); // not found + assertEquals(404, res6.statusCode()); // not found var req7 = HttpRequest.newBuilder(uri(server, "/foo/bar/\\..\\../")).build(); var res7 = client.send(req7, BodyHandlers.ofString()); - assertEquals(res7.statusCode(), 404); // not found + assertEquals(404, res7.statusCode()); // not found var req8 = HttpRequest.newBuilder(uri(server, "/foo")).build(); var res8 = client.send(req8, BodyHandlers.ofString()); - assertEquals(res8.statusCode(), 301); // redirect - assertEquals(res8.headers().firstValue("content-length").get(), "0"); - assertEquals(res8.headers().firstValue("location").get(), "/foo/"); + assertEquals(301, res8.statusCode()); // redirect + assertEquals("0", res8.headers().firstValue("content-length").get()); + assertEquals("/foo/", res8.headers().firstValue("location").get()); } finally { server.stop(0); } @@ -169,15 +170,15 @@ public void test() throws Exception { try { var req1 = HttpRequest.newBuilder(uri(server, "/browse/file.txt")).build(); var res1 = client.send(req1, BodyHandlers.ofString()); - assertEquals(res1.statusCode(), 200); - assertEquals(res1.body(), "testdir"); - assertEquals(res1.headers().firstValue("content-type").get(), "text/plain"); - assertEquals(res1.headers().firstValue("content-length").get(), Long.toString(7L)); - assertEquals(res1.headers().firstValue("last-modified").get(), getLastModified(TEST_DIR.resolve("file.txt"))); + assertEquals(200, res1.statusCode()); + assertEquals("testdir", res1.body()); + assertEquals("text/plain", res1.headers().firstValue("content-type").get()); + assertEquals(Long.toString(7L), res1.headers().firstValue("content-length").get()); + assertEquals(getLastModified(TEST_DIR.resolve("file.txt")), res1.headers().firstValue("last-modified").get()); var req2 = HttpRequest.newBuilder(uri(server, "/store/file.txt")).build(); var res2 = client.send(req2, BodyHandlers.ofString()); - assertEquals(res2.statusCode(), 404); // no context found + assertEquals(404, res2.statusCode()); // no context found } finally { server.stop(0); } @@ -190,29 +191,29 @@ public void test() throws Exception { try { var req1 = HttpRequest.newBuilder(uri(server, "/foo/file.txt")).build(); var res1 = client.send(req1, BodyHandlers.ofString()); - assertEquals(res1.statusCode(), 200); - assertEquals(res1.body(), "foo"); - assertEquals(res1.headers().firstValue("content-type").get(), "text/plain"); - assertEquals(res1.headers().firstValue("content-length").get(), Long.toString(3L)); - assertEquals(res1.headers().firstValue("last-modified").get(), getLastModified(TEST_DIR.resolve("foo").resolve("file.txt"))); + assertEquals(200, res1.statusCode()); + assertEquals("foo", res1.body()); + assertEquals("text/plain", res1.headers().firstValue("content-type").get()); + assertEquals(Long.toString(3L), res1.headers().firstValue("content-length").get()); + assertEquals(getLastModified(TEST_DIR.resolve("foo").resolve("file.txt")), res1.headers().firstValue("last-modified").get()); var req2 = HttpRequest.newBuilder(uri(server, "/foobar/file.txt")).build(); var res2 = client.send(req2, BodyHandlers.ofString()); - assertEquals(res2.statusCode(), 404); // no context found + assertEquals(404, res2.statusCode()); // no context found var req3 = HttpRequest.newBuilder(uri(server, "/foo/../foobar/file.txt")).build(); var res3 = client.send(req3, BodyHandlers.ofString()); - assertEquals(res3.statusCode(), 404); // cannot escape context + assertEquals(404, res3.statusCode()); // cannot escape context var req4 = HttpRequest.newBuilder(uri(server, "/foo/../..")).build(); var res4 = client.send(req4, BodyHandlers.ofString()); - assertEquals(res4.statusCode(), 404); // cannot escape root + assertEquals(404, res4.statusCode()); // cannot escape root var req5 = HttpRequest.newBuilder(uri(server, "/foo/bar")).build(); var res5 = client.send(req5, BodyHandlers.ofString()); - assertEquals(res5.statusCode(), 301); // redirect - assertEquals(res5.headers().firstValue("content-length").get(), "0"); - assertEquals(res5.headers().firstValue("location").get(), "/foo/bar/"); + assertEquals(301, res5.statusCode()); // redirect + assertEquals("0", res5.headers().firstValue("content-length").get()); + assertEquals("/foo/bar/", res5.headers().firstValue("location").get()); } finally { server.stop(0); } @@ -225,35 +226,35 @@ public void test() throws Exception { try { var req1 = HttpRequest.newBuilder(uri(server, "/foo/file.txt")).build(); var res1 = client.send(req1, BodyHandlers.ofString()); - assertEquals(res1.statusCode(), 200); - assertEquals(res1.body(), "foo"); - assertEquals(res1.headers().firstValue("content-type").get(), "text/plain"); - assertEquals(res1.headers().firstValue("content-length").get(), Long.toString(3L)); - assertEquals(res1.headers().firstValue("last-modified").get(), getLastModified(TEST_DIR.resolve("foo").resolve("file.txt"))); + assertEquals(200, res1.statusCode()); + assertEquals("foo", res1.body()); + assertEquals("text/plain", res1.headers().firstValue("content-type").get()); + assertEquals(Long.toString(3L), res1.headers().firstValue("content-length").get()); + assertEquals(getLastModified(TEST_DIR.resolve("foo").resolve("file.txt")), res1.headers().firstValue("last-modified").get()); var req2 = HttpRequest.newBuilder(uri(server, "/foobar/")).build(); var res2 = client.send(req2, BodyHandlers.ofString()); - assertEquals(res2.statusCode(), 404); // handler prevents mapping to /foo/bar + assertEquals(404, res2.statusCode()); // handler prevents mapping to /foo/bar var req3 = HttpRequest.newBuilder(uri(server, "/foobar/file.txt")).build(); var res3 = client.send(req3, BodyHandlers.ofString()); - assertEquals(res3.statusCode(), 404); // handler prevents mapping to /foo/bar/file.txt + assertEquals(404, res3.statusCode()); // handler prevents mapping to /foo/bar/file.txt var req4 = HttpRequest.newBuilder(uri(server, "/file.txt")).build(); var res4 = client.send(req4, BodyHandlers.ofString()); - assertEquals(res4.statusCode(), 404); + assertEquals(404, res4.statusCode()); var req5 = HttpRequest.newBuilder(uri(server, "/foo/bar")).build(); var res5 = client.send(req5, BodyHandlers.ofString()); - assertEquals(res5.statusCode(), 301); // redirect - assertEquals(res5.headers().firstValue("content-length").get(), "0"); - assertEquals(res5.headers().firstValue("location").get(), "/foo/bar/"); + assertEquals(301, res5.statusCode()); // redirect + assertEquals("0", res5.headers().firstValue("content-length").get()); + assertEquals("/foo/bar/", res5.headers().firstValue("location").get()); var req6 = HttpRequest.newBuilder(uri(server, "/foo")).build(); var res6 = client.send(req6, BodyHandlers.ofString()); - assertEquals(res6.statusCode(), 301); // redirect - assertEquals(res6.headers().firstValue("content-length").get(), "0"); - assertEquals(res6.headers().firstValue("location").get(), "/foo/"); + assertEquals(301, res6.statusCode()); // redirect + assertEquals("0", res6.headers().firstValue("content-length").get()); + assertEquals("/foo/", res6.headers().firstValue("location").get()); } finally { server.stop(0); } @@ -276,7 +277,7 @@ public void test() throws Exception { try { var req1 = HttpRequest.newBuilder(uri(server, "/foo/bar/c:/baz/")).build(); var res1 = client.send(req1, BodyHandlers.ofString()); - assertEquals(res1.statusCode(), 404); // not found + assertEquals(404, res1.statusCode()); // not found } finally { server.stop(0); } @@ -303,32 +304,32 @@ public void multipleContexts() throws Exception { out.println("uri.Path=" + uriPath); var req1 = HttpRequest.newBuilder(uri(server, uriPath)).build(); var res1 = client.send(req1, BodyHandlers.ofString()); - assertEquals(res1.statusCode(), 200); - assertEquals(res1.body(), "root response body"); + assertEquals(200, res1.statusCode()); + assertEquals("root response body", res1.body()); } { var req1 = HttpRequest.newBuilder(uri(server, "/foo/file.txt")).build(); var res1 = client.send(req1, BodyHandlers.ofString()); - assertEquals(res1.statusCode(), 200); - assertEquals(res1.body(), "foo"); + assertEquals(200, res1.statusCode()); + assertEquals("foo", res1.body()); var req2 = HttpRequest.newBuilder(uri(server, "/foo/bar/baz/file.txt")).build(); var res2 = client.send(req2, BodyHandlers.ofString()); - assertEquals(res2.statusCode(), 200); - assertEquals(res2.body(), "foo/bar/baz"); + assertEquals(200, res2.statusCode()); + assertEquals("foo/bar/baz", res2.body()); } { var req1 = HttpRequest.newBuilder(uri(server, "/foobar/file.txt")).build(); var res1 = client.send(req1, BodyHandlers.ofString()); - assertEquals(res1.statusCode(), 200); - assertEquals(res1.body(), "foobar"); + assertEquals(200, res1.statusCode()); + assertEquals("foobar", res1.body()); } for (String uriPath : List.of("/bar/", "/bar/t", "/bar/t/z", "/bar/index.html") ) { out.println("uri.Path=" + uriPath); var req1 = HttpRequest.newBuilder(uri(server, uriPath)).build(); var res1 = client.send(req1, BodyHandlers.ofString()); - assertEquals(res1.statusCode(), 200); - assertEquals(res1.body(), "bar response body"); + assertEquals(200, res1.statusCode()); + assertEquals("bar response body", res1.body()); } } finally { server.stop(0); @@ -347,18 +348,18 @@ public void requestWithQuery() throws Exception { out.println("uri.Query=" + query); var req = HttpRequest.newBuilder(uri(server, "", query)).build(); var res = client.send(req, BodyHandlers.ofString()); - assertEquals(res.statusCode(), 200); - assertEquals(res.headers().firstValue("content-type").get(), "text/html; charset=UTF-8"); - assertEquals(res.headers().firstValue("content-length").get(), Long.toString(257L)); - assertEquals(res.headers().firstValue("last-modified").get(), getLastModified(TEST_DIR)); + assertEquals(200, res.statusCode()); + assertEquals("text/html; charset=UTF-8", res.headers().firstValue("content-type").get()); + assertEquals(Long.toString(257L), res.headers().firstValue("content-length").get()); + assertEquals(getLastModified(TEST_DIR), res.headers().firstValue("last-modified").get()); } } finally { server.stop(0); } } - @AfterTest - public void teardown() throws IOException { + @AfterAll + public static void teardown() throws IOException { if (Files.exists(TEST_DIR)) { FileUtils.deleteFileTreeWithRetry(TEST_DIR); } diff --git a/test/jdk/com/sun/net/httpserver/simpleserver/OutputFilterTest.java b/test/jdk/com/sun/net/httpserver/simpleserver/OutputFilterTest.java index b7583ea901b3..bed4b8e2f3d8 100644 --- a/test/jdk/com/sun/net/httpserver/simpleserver/OutputFilterTest.java +++ b/test/jdk/com/sun/net/httpserver/simpleserver/OutputFilterTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,7 +27,7 @@ * @modules java.base/sun.net.www:+open * @library /test/lib * @build jdk.test.lib.net.URIBuilder - * @run testng/othervm -Djdk.httpclient.redirects.retrylimit=1 OutputFilterTest + * @run junit/othervm -Djdk.httpclient.redirects.retrylimit=1 OutputFilterTest */ import java.io.ByteArrayOutputStream; @@ -53,15 +53,17 @@ import com.sun.net.httpserver.SimpleFileServer; import com.sun.net.httpserver.SimpleFileServer.OutputLevel; import jdk.test.lib.net.URIBuilder; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; import static java.net.http.HttpClient.Builder.NO_PROXY; import static java.nio.charset.StandardCharsets.*; import static com.sun.net.httpserver.SimpleFileServer.OutputLevel.*; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertThrows; -import static org.testng.Assert.assertTrue; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; public class OutputFilterTest { static final Class NPE = NullPointerException.class; @@ -74,8 +76,8 @@ public class OutputFilterTest { static final boolean ENABLE_LOGGING = true; static final Logger logger = Logger.getLogger("com.sun.net.httpserver"); - @BeforeTest - public void setup() { + @BeforeAll + public static void setup() { if (ENABLE_LOGGING) { ConsoleHandler ch = new ConsoleHandler(); logger.setLevel(Level.ALL); @@ -94,10 +96,10 @@ public void testNull() { @Test public void testDescription() { var filter = SimpleFileServer.createOutputFilter(OUT, VERBOSE); - assertEquals(filter.description(), "HttpExchange OutputFilter (outputLevel: VERBOSE)"); + assertEquals("HttpExchange OutputFilter (outputLevel: VERBOSE)", filter.description()); filter = SimpleFileServer.createOutputFilter(OUT, INFO); - assertEquals(filter.description(), "HttpExchange OutputFilter (outputLevel: INFO)"); + assertEquals("HttpExchange OutputFilter (outputLevel: INFO)", filter.description()); } @Test @@ -120,9 +122,9 @@ public void testExchange() throws Exception { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "")).build(); var response = client.send(request, HttpResponse.BodyHandlers.ofString()); - assertEquals(response.statusCode(), 200); - assertEquals(response.headers().map().size(), 3); - assertEquals(response.body(), "hello world"); + assertEquals(200, response.statusCode()); + assertEquals(3, response.headers().map().size()); + assertEquals("hello world", response.body()); } finally { server.stop(0); baos.flush(); @@ -172,9 +174,9 @@ public void testExchangeWithoutRequestPath() throws Exception { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "")).build(); var response = client.send(request, HttpResponse.BodyHandlers.ofString()); - assertEquals(response.statusCode(), 200); - assertEquals(response.headers().map().size(), 2); - assertEquals(response.body(), "hello world"); + assertEquals(200, response.statusCode()); + assertEquals(2, response.headers().map().size()); + assertEquals("hello world", response.body()); } finally { server.stop(0); baos.flush(); @@ -207,8 +209,7 @@ public void testExchangeWithoutRequestPath() throws Exception { } } - @DataProvider - public Object[][] throwingHandler() { + public static Object[][] throwingHandler() { return new Object[][] { {VERBOSE, "Error: server exchange handling failed: IOE ThrowingHandler" + System.lineSeparator()}, {INFO, "Error: server exchange handling failed: IOE ThrowingHandler" + System.lineSeparator()}, @@ -223,7 +224,8 @@ public Object[][] throwingHandler() { * prevent retries on the client side, which would result in more than one * error message. */ - @Test(dataProvider = "throwingHandler") + @ParameterizedTest + @MethodSource("throwingHandler") public void testExchangeThrowingHandler(OutputLevel level, String expectedOutput) throws Exception { var baos = new ByteArrayOutputStream(); @@ -243,7 +245,7 @@ public void testExchangeThrowingHandler(OutputLevel level, } finally { server.stop(0); baos.flush(); - assertEquals(baos.toString(UTF_8), expectedOutput); + assertEquals(expectedOutput, baos.toString(UTF_8)); } } @@ -264,8 +266,8 @@ public void testCannotResolveRequestURI() throws Exception { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "aFile\u0000.txt")).build(); var response = client.send(request, HttpResponse.BodyHandlers.ofString()); - assertEquals(response.statusCode(), 404); - assertEquals(response.headers().map().size(), 3); + assertEquals(404, response.statusCode()); + assertEquals(3, response.headers().map().size()); } finally { server.stop(0); baos.flush(); diff --git a/test/jdk/com/sun/net/httpserver/simpleserver/RequestTest.java b/test/jdk/com/sun/net/httpserver/simpleserver/RequestTest.java index cb740f4dd2a4..5e29d7a3ddf6 100644 --- a/test/jdk/com/sun/net/httpserver/simpleserver/RequestTest.java +++ b/test/jdk/com/sun/net/httpserver/simpleserver/RequestTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,7 +24,7 @@ /* * @test * @summary Tests for Request - * @run testng RequestTest + * @run junit RequestTest */ import java.io.InputStream; @@ -35,9 +35,10 @@ import java.util.List; import java.util.Map; import com.sun.net.httpserver.*; -import org.testng.annotations.Test; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertThrows; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import org.junit.jupiter.api.Test; public class RequestTest { @@ -46,8 +47,8 @@ public void testAddToEmpty() { var headers = new Headers(); Request request = new TestHttpExchange(headers); request = request.with("Foo", List.of("Bar")); - assertEquals(request.getRequestHeaders().size(), 1); - assertEquals(request.getRequestHeaders().get("Foo"), List.of("Bar")); + assertEquals(1, request.getRequestHeaders().size()); + assertEquals(List.of("Bar"), request.getRequestHeaders().get("Foo")); assertReadOnly(request.getRequestHeaders()); } @@ -57,9 +58,9 @@ public void testAddition() { headers.add("Foo", "Bar"); Request request = new TestHttpExchange(headers); request = request.with("X-Foo", List.of("Bar")); - assertEquals(request.getRequestHeaders().size(), 2); - assertEquals(request.getRequestHeaders().get("Foo"), List.of("Bar")); - assertEquals(request.getRequestHeaders().get("X-Foo"), List.of("Bar")); + assertEquals(2, request.getRequestHeaders().size()); + assertEquals(List.of("Bar"), request.getRequestHeaders().get("Foo")); + assertEquals(List.of("Bar"), request.getRequestHeaders().get("X-Foo")); assertReadOnly(request.getRequestHeaders()); } @@ -70,8 +71,8 @@ public void testAddWithExisting() { headers.add(headerName, "Bar"); Request request = new TestHttpExchange(headers); request = request.with(headerName, List.of("blahblahblah")); - assertEquals(request.getRequestHeaders().size(), 1); - assertEquals(request.getRequestHeaders().get(headerName), List.of("Bar")); + assertEquals(1, request.getRequestHeaders().size()); + assertEquals(List.of("Bar"), request.getRequestHeaders().get(headerName)); assertReadOnly(request.getRequestHeaders()); } @@ -83,11 +84,11 @@ public void testAddSeveral() { request = request.with("Larry", List.of("a")) .with("Curly", List.of("b")) .with("Moe", List.of("c")); - assertEquals(request.getRequestHeaders().size(), 4); - assertEquals(request.getRequestHeaders().getFirst("Foo"), "Bar"); - assertEquals(request.getRequestHeaders().getFirst("Larry"), "a"); - assertEquals(request.getRequestHeaders().getFirst("Curly"), "b"); - assertEquals(request.getRequestHeaders().getFirst("Moe" ), "c"); + assertEquals(4, request.getRequestHeaders().size()); + assertEquals("Bar", request.getRequestHeaders().getFirst("Foo")); + assertEquals("a", request.getRequestHeaders().getFirst("Larry")); + assertEquals("b", request.getRequestHeaders().getFirst("Curly")); + assertEquals("c", request.getRequestHeaders().getFirst("Moe" )); assertReadOnly(request.getRequestHeaders()); } diff --git a/test/jdk/com/sun/net/httpserver/simpleserver/ServerMimeTypesResolutionTest.java b/test/jdk/com/sun/net/httpserver/simpleserver/ServerMimeTypesResolutionTest.java index baa7d17b5055..dff0eb8c1d19 100644 --- a/test/jdk/com/sun/net/httpserver/simpleserver/ServerMimeTypesResolutionTest.java +++ b/test/jdk/com/sun/net/httpserver/simpleserver/ServerMimeTypesResolutionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -39,14 +39,14 @@ import jdk.test.lib.net.URIBuilder; import com.sun.net.httpserver.HttpServer; import com.sun.net.httpserver.SimpleFileServer; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; import sun.net.www.MimeTable; import static java.net.http.HttpClient.Builder.NO_PROXY; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; /* * @test @@ -54,7 +54,7 @@ * @modules java.base/sun.net.www:+open * @library /test/lib * @build jdk.test.lib.net.URIBuilder - * @run testng/othervm ServerMimeTypesResolutionTest + * @run junit/othervm ServerMimeTypesResolutionTest */ public class ServerMimeTypesResolutionTest { @@ -70,8 +70,8 @@ public class ServerMimeTypesResolutionTest { static final boolean ENABLE_LOGGING = true; static final Logger LOGGER = Logger.getLogger("com.sun.net.httpserver"); - @BeforeTest - public void setup() throws Exception { + @BeforeAll + public static void setup() throws Exception { if (ENABLE_LOGGING) { ConsoleHandler ch = new ConsoleHandler(); LOGGER.setLevel(Level.ALL); @@ -139,7 +139,7 @@ protected static Properties deserialize(String serialized, String delimiter) { } @Test - public static void testMimeTypeHeaders() throws Exception { + public void testMimeTypeHeaders() throws Exception { final var server = SimpleFileServer.createFileServer(LOOPBACK_ADDR, root, SimpleFileServer.OutputLevel.VERBOSE); server.start(); try { @@ -163,8 +163,8 @@ private static void execute(HttpServer server, final var uri = uri(server, toFileName(extension)); final var request = HttpRequest.newBuilder(uri).build(); final var response = client.send(request, HttpResponse.BodyHandlers.ofString()); - assertEquals(response.statusCode(), 200); - assertEquals(response.headers().firstValue("content-type").get(),expectedMimeType); + assertEquals(200, response.statusCode()); + assertEquals(expectedMimeType, response.headers().firstValue("content-type").get()); } static URI uri(HttpServer server, String path) { @@ -176,7 +176,6 @@ static URI uri(HttpServer server, String path) { .buildUnchecked(); } - @DataProvider public static Object[][] commonExtensions() { Set extensions = Set.of(".aac", ".abw", ".arc", ".avi", ".azw", ".bin", ".bmp", ".bz", ".bz2", ".csh", ".css", ".csv", ".doc", ".docx",".eot", ".epub", ".gz", ".gif", ".htm", ".html", ".ico", diff --git a/test/jdk/com/sun/net/httpserver/simpleserver/SimpleFileServerTest.java b/test/jdk/com/sun/net/httpserver/simpleserver/SimpleFileServerTest.java index 167fcd3b5d34..104b4ea0cbe3 100644 --- a/test/jdk/com/sun/net/httpserver/simpleserver/SimpleFileServerTest.java +++ b/test/jdk/com/sun/net/httpserver/simpleserver/SimpleFileServerTest.java @@ -26,7 +26,7 @@ * @summary Tests for SimpleFileServer * @library /test/lib * @build jdk.test.lib.Platform jdk.test.lib.net.URIBuilder - * @run testng/othervm SimpleFileServerTest + * @run junit/othervm SimpleFileServerTest */ import java.io.IOException; @@ -52,15 +52,17 @@ import jdk.test.lib.Platform; import jdk.test.lib.net.URIBuilder; import jdk.test.lib.util.FileUtils; -import org.testng.annotations.AfterTest; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; -import org.testng.SkipException; import static java.net.http.HttpClient.Builder.NO_PROXY; import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.file.StandardOpenOption.CREATE; -import static org.testng.Assert.*; + +import org.junit.jupiter.api.AfterAll; +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; public class SimpleFileServerTest { @@ -78,8 +80,8 @@ public class SimpleFileServerTest { static final String EXPECTED_LAST_MODIFIED_OF_FAVICON = "Mon, 23 May 1995 11:11:11 GMT"; - @BeforeTest - public void setup() throws IOException { + @BeforeAll + public static void setup() throws IOException { if (ENABLE_LOGGING) { ConsoleHandler ch = new ConsoleHandler(); LOGGER.setLevel(Level.ALL); @@ -105,11 +107,11 @@ public void testFileGET() throws Exception { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "aFile.txt")).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 200); - assertEquals(response.body(), "some text"); - assertEquals(response.headers().firstValue("content-type").get(), "text/plain"); - assertEquals(response.headers().firstValue("content-length").get(), expectedLength); - assertEquals(response.headers().firstValue("last-modified").get(), lastModified); + assertEquals(200, response.statusCode()); + assertEquals("some text", response.body()); + assertEquals("text/plain", response.headers().firstValue("content-type").get()); + assertEquals(expectedLength, response.headers().firstValue("content-length").get()); + assertEquals(lastModified, response.headers().firstValue("last-modified").get()); } finally { server.stop(0); } @@ -134,11 +136,11 @@ public void testDirectoryGET() throws Exception { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "")).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 200); - assertEquals(response.headers().firstValue("content-type").get(), "text/html; charset=UTF-8"); - assertEquals(response.headers().firstValue("content-length").get(), expectedLength); - assertEquals(response.headers().firstValue("last-modified").get(), lastModified); - assertEquals(response.body(), expectedBody); + assertEquals(200, response.statusCode()); + assertEquals("text/html; charset=UTF-8", response.headers().firstValue("content-type").get()); + assertEquals(expectedLength, response.headers().firstValue("content-length").get()); + assertEquals(lastModified, response.headers().firstValue("last-modified").get()); + assertEquals(expectedBody, response.body()); } finally { server.stop(0); } @@ -155,9 +157,9 @@ public void testFavIconGET() throws Exception { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "favicon.ico")).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 200); - assertEquals(response.headers().firstValue("content-type").get(), "image/x-icon"); - assertEquals(response.headers().firstValue("last-modified").get(), EXPECTED_LAST_MODIFIED_OF_FAVICON); + assertEquals(200, response.statusCode()); + assertEquals("image/x-icon", response.headers().firstValue("content-type").get()); + assertEquals(EXPECTED_LAST_MODIFIED_OF_FAVICON, response.headers().firstValue("last-modified").get()); // expect custom (and broken) icon var file = Files.writeString(root.resolve("favicon.ico"), "broken icon", CREATE); @@ -165,19 +167,19 @@ public void testFavIconGET() throws Exception { var lastModified = getLastModified(file); var expectedLength = Long.toString(Files.size(file)); response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 200); - assertEquals(response.headers().firstValue("content-type").get(), "application/octet-stream"); - assertEquals(response.headers().firstValue("content-length").get(), expectedLength); - assertEquals(response.headers().firstValue("last-modified").get(), lastModified); + assertEquals(200, response.statusCode()); + assertEquals("application/octet-stream", response.headers().firstValue("content-type").get()); + assertEquals(expectedLength, response.headers().firstValue("content-length").get()); + assertEquals(lastModified, response.headers().firstValue("last-modified").get()); } finally { Files.delete(file); } // expect built-in icon response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 200); - assertEquals(response.headers().firstValue("content-type").get(), "image/x-icon"); - assertEquals(response.headers().firstValue("last-modified").get(), EXPECTED_LAST_MODIFIED_OF_FAVICON); + assertEquals(200, response.statusCode()); + assertEquals("image/x-icon", response.headers().firstValue("content-type").get()); + assertEquals(EXPECTED_LAST_MODIFIED_OF_FAVICON, response.headers().firstValue("last-modified").get()); } finally { server.stop(0); } @@ -194,10 +196,10 @@ public void testFavIconHEAD() throws Exception { var request = HttpRequest.newBuilder(uri(server, "favicon.ico")) .method("HEAD", BodyPublishers.noBody()).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 200); - assertEquals(response.headers().firstValue("content-type").get(), "image/x-icon"); - assertEquals(response.headers().firstValue("last-modified").get(), EXPECTED_LAST_MODIFIED_OF_FAVICON); - assertEquals(response.body(), ""); + assertEquals(200, response.statusCode()); + assertEquals("image/x-icon", response.headers().firstValue("content-type").get()); + assertEquals(EXPECTED_LAST_MODIFIED_OF_FAVICON, response.headers().firstValue("last-modified").get()); + assertEquals("", response.body()); } finally { server.stop(0); } @@ -217,11 +219,11 @@ public void testFileHEAD() throws Exception { var request = HttpRequest.newBuilder(uri(server, "aFile.txt")) .method("HEAD", BodyPublishers.noBody()).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 200); - assertEquals(response.headers().firstValue("content-type").get(), "text/plain"); - assertEquals(response.headers().firstValue("content-length").get(), expectedLength); - assertEquals(response.headers().firstValue("last-modified").get(), lastModified); - assertEquals(response.body(), ""); + assertEquals(200, response.statusCode()); + assertEquals("text/plain", response.headers().firstValue("content-type").get()); + assertEquals(expectedLength, response.headers().firstValue("content-length").get()); + assertEquals(lastModified, response.headers().firstValue("last-modified").get()); + assertEquals("", response.body()); } finally { server.stop(0); } @@ -247,18 +249,17 @@ public void testDirectoryHEAD() throws Exception { var request = HttpRequest.newBuilder(uri(server, "")) .method("HEAD", BodyPublishers.noBody()).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 200); - assertEquals(response.headers().firstValue("content-type").get(), "text/html; charset=UTF-8"); - assertEquals(response.headers().firstValue("content-length").get(), expectedLength); - assertEquals(response.headers().firstValue("last-modified").get(), lastModified); - assertEquals(response.body(), ""); + assertEquals(200, response.statusCode()); + assertEquals("text/html; charset=UTF-8", response.headers().firstValue("content-type").get()); + assertEquals(expectedLength, response.headers().firstValue("content-length").get()); + assertEquals(lastModified, response.headers().firstValue("last-modified").get()); + assertEquals("", response.body()); } finally { server.stop(0); } } - @DataProvider - public Object[][] indexFiles() { + public static Object[][] indexFiles() { var fileContent = openHTML + """

This is an index file

""" + closeHTML; @@ -274,7 +275,8 @@ public Object[][] indexFiles() { }; } - @Test(dataProvider = "indexFiles") + @ParameterizedTest + @MethodSource("indexFiles") public void testDirectoryWithIndexGET(String id, String filename, String contentType, @@ -294,11 +296,11 @@ public void testDirectoryWithIndexGET(String id, var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "")).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 200); - assertEquals(response.headers().firstValue("content-type").get(), contentType); - assertEquals(response.headers().firstValue("content-length").get(), contentLength); - assertEquals(response.headers().firstValue("last-modified").get(), lastModified); - assertEquals(response.body(), expectedBody); + assertEquals(200, response.statusCode()); + assertEquals(contentType, response.headers().firstValue("content-type").get()); + assertEquals(contentLength, response.headers().firstValue("content-length").get()); + assertEquals(lastModified, response.headers().firstValue("last-modified").get()); + assertEquals(expectedBody, response.body()); } finally { server.stop(0); if (serveIndexFile) { @@ -309,9 +311,7 @@ public void testDirectoryWithIndexGET(String id, @Test public void testNotReadableFileGET() throws Exception { - if (Platform.isWindows()) { - throw new SkipException("Not applicable on Windows"); - } + Assumptions.assumeFalse(Platform.isWindows(), "Not applicable on Windows"); var expectedBody = openHTML + """

File not found

/aFile.txt

@@ -329,9 +329,9 @@ public void testNotReadableFileGET() throws Exception { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "aFile.txt")).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 404); - assertEquals(response.headers().firstValue("content-length").get(), expectedLength); - assertEquals(response.body(), expectedBody); + assertEquals(404, response.statusCode()); + assertEquals(expectedLength, response.headers().firstValue("content-length").get()); + assertEquals(expectedBody, response.body()); } finally { server.stop(0); file.toFile().setReadable(true, false); @@ -340,9 +340,7 @@ public void testNotReadableFileGET() throws Exception { @Test public void testNotReadableSegmentGET() throws Exception { - if (Platform.isWindows()) { - throw new SkipException("Not applicable on Windows"); - } + Assumptions.assumeFalse(Platform.isWindows(), "Not applicable on Windows"); var expectedBody = openHTML + """

File not found

/dir/aFile.txt

@@ -362,9 +360,9 @@ public void testNotReadableSegmentGET() throws Exception { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "dir/aFile.txt")).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 404); - assertEquals(response.headers().firstValue("content-length").get(), expectedLength); - assertEquals(response.body(), expectedBody); + assertEquals(404, response.statusCode()); + assertEquals(expectedLength, response.headers().firstValue("content-length").get()); + assertEquals(expectedBody, response.body()); } finally { server.stop(0); dir.toFile().setReadable(true, false); @@ -386,9 +384,9 @@ public void testInvalidRequestURIGET() throws Exception { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "aFile?#.txt")).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 404); - assertEquals(response.headers().firstValue("content-length").get(), expectedLength); - assertEquals(response.body(), expectedBody); + assertEquals(404, response.statusCode()); + assertEquals(expectedLength, response.headers().firstValue("content-length").get()); + assertEquals(expectedBody, response.body()); } finally { server.stop(0); } @@ -409,9 +407,9 @@ public void testNotFoundGET() throws Exception { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "doesNotExist.txt")).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 404); - assertEquals(response.headers().firstValue("content-length").get(), expectedLength); - assertEquals(response.body(), expectedBody); + assertEquals(404, response.statusCode()); + assertEquals(expectedLength, response.headers().firstValue("content-length").get()); + assertEquals(expectedBody, response.body()); } finally { server.stop(0); } @@ -433,9 +431,9 @@ public void testNotFoundHEAD() throws Exception { var request = HttpRequest.newBuilder(uri(server, "doesNotExist.txt")) .method("HEAD", BodyPublishers.noBody()).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 404); - assertEquals(response.headers().firstValue("content-length").get(), expectedLength); - assertEquals(response.body(), ""); + assertEquals(404, response.statusCode()); + assertEquals(expectedLength, response.headers().firstValue("content-length").get()); + assertEquals("", response.body()); } finally { server.stop(0); } @@ -459,9 +457,9 @@ public void testSymlinkGET() throws Exception { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "symlink")).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 404); - assertEquals(response.headers().firstValue("content-length").get(), expectedLength); - assertEquals(response.body(), expectedBody); + assertEquals(404, response.statusCode()); + assertEquals(expectedLength, response.headers().firstValue("content-length").get()); + assertEquals(expectedBody, response.body()); } finally { server.stop(0); } @@ -486,21 +484,21 @@ public void testSymlinkSegmentGET() throws Exception { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "symlink/aFile.txt")).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 404); - assertEquals(response.headers().firstValue("content-length").get(), expectedLength); - assertEquals(response.body(), expectedBody); + assertEquals(404, response.statusCode()); + assertEquals(expectedLength, response.headers().firstValue("content-length").get()); + assertEquals(expectedBody, response.body()); } finally { server.stop(0); } } - private void createSymLink(Path symlink, Path target) { + private static void createSymLink(Path symlink, Path target) { try { Files.createSymbolicLink(symlink, target); } catch (UnsupportedOperationException uoe) { - throw new SkipException("sym link creation not supported", uoe); + Assumptions.abort("sym link creation not supported"); } catch (IOException ioe) { - throw new SkipException("probably insufficient privileges to create sym links (Windows)", ioe); + Assumptions.abort("probably insufficient privileges to create sym links (Windows)"); } } @@ -523,9 +521,9 @@ public void testHiddenFileGET() throws Exception { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, fileName)).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 404); - assertEquals(response.headers().firstValue("content-length").get(), expectedLength); - assertEquals(response.body(), expectedBody); + assertEquals(404, response.statusCode()); + assertEquals(expectedLength, response.headers().firstValue("content-length").get()); + assertEquals(expectedBody, response.body()); } finally { server.stop(0); } @@ -547,15 +545,15 @@ public void testHiddenSegmentGET() throws Exception { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, ".hiddenDirectory/aFile.txt")).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 404); - assertEquals(response.headers().firstValue("content-length").get(), expectedLength); - assertEquals(response.body(), expectedBody); + assertEquals(404, response.statusCode()); + assertEquals(expectedLength, response.headers().firstValue("content-length").get()); + assertEquals(expectedBody, response.body()); } finally { server.stop(0); } } - private Path createHiddenFile(Path root) throws IOException { + private static Path createHiddenFile(Path root) throws IOException { Path file; if (Platform.isWindows()) { file = Files.createFile(root.resolve("aFile.txt")); @@ -567,7 +565,7 @@ private Path createHiddenFile(Path root) throws IOException { return file; } - private Path createFileInHiddenDirectory(Path root) throws IOException { + private static Path createFileInHiddenDirectory(Path root) throws IOException { Path dir; Path file; if (Platform.isWindows()) { @@ -602,17 +600,17 @@ public void testMovedPermanently() throws Exception { var uri = uri(server, "aDirectory"); var request = HttpRequest.newBuilder(uri).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 301); - assertEquals(response.headers().firstValue("content-length").get(), "0"); - assertEquals(response.headers().firstValue("location").get(), "/aDirectory/"); + assertEquals(301, response.statusCode()); + assertEquals("0", response.headers().firstValue("content-length").get()); + assertEquals("/aDirectory/", response.headers().firstValue("location").get()); // tests that query component is preserved during redirect var uri2 = uri(server, "aDirectory", "query"); var req2 = HttpRequest.newBuilder(uri2).build(); var res2 = client.send(req2, BodyHandlers.ofString()); - assertEquals(res2.statusCode(), 301); - assertEquals(res2.headers().firstValue("content-length").get(), "0"); - assertEquals(res2.headers().firstValue("location").get(), "/aDirectory/?query"); + assertEquals(301, res2.statusCode()); + assertEquals("0", res2.headers().firstValue("content-length").get()); + assertEquals("/aDirectory/?query", res2.headers().firstValue("location").get()); } { // tests that redirect to returned relative URI works @@ -621,10 +619,10 @@ public void testMovedPermanently() throws Exception { var uri = uri(server, "aDirectory"); var request = HttpRequest.newBuilder(uri).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 200); - assertEquals(response.body(), expectedBody); - assertEquals(response.headers().firstValue("content-type").get(), "text/html; charset=UTF-8"); - assertEquals(response.headers().firstValue("content-length").get(), expectedLength); + assertEquals(200, response.statusCode()); + assertEquals(expectedBody, response.body()); + assertEquals("text/html; charset=UTF-8", response.headers().firstValue("content-type").get()); + assertEquals(expectedLength, response.headers().firstValue("content-length").get()); } } finally { server.stop(0); @@ -673,28 +671,26 @@ public void testIllegalPath() throws Exception { { // not a directory Path p = Files.createFile(TEST_DIR.resolve("aFile")); assert !Files.isDirectory(p); - var iae = expectThrows(IAE, () -> SimpleFileServer.createFileServer(addr, p, OutputLevel.INFO)); + var iae = assertThrows(IAE, () -> SimpleFileServer.createFileServer(addr, p, OutputLevel.INFO)); assertTrue(iae.getMessage().contains("not a directory")); } { // does not exist Path p = TEST_DIR.resolve("doesNotExist"); assert !Files.exists(p); - var iae = expectThrows(IAE, () -> SimpleFileServer.createFileServer(addr, p, OutputLevel.INFO)); + var iae = assertThrows(IAE, () -> SimpleFileServer.createFileServer(addr, p, OutputLevel.INFO)); assertTrue(iae.getMessage().contains("does not exist")); } } @Test public void testNonReadablePath() throws Exception { - if (Platform.isWindows()) { - throw new SkipException("Not applicable on Windows"); - } + Assumptions.assumeFalse(Platform.isWindows(), "Not applicable on Windows"); var addr = LOOPBACK_ADDR; Path p = Files.createDirectory(TEST_DIR.resolve("aDir")); p.toFile().setReadable(false, false); assert !Files.isReadable(p); try { - var iae = expectThrows(IAE, () -> SimpleFileServer.createFileServer(addr, p, OutputLevel.INFO)); + var iae = assertThrows(IAE, () -> SimpleFileServer.createFileServer(addr, p, OutputLevel.INFO)); assertTrue(iae.getMessage().contains("not readable")); } finally { p.toFile().setReadable(true, false); @@ -718,7 +714,7 @@ public void testXss() throws Exception { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "beginDelim%3C%3EEndDelim")).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 404); + assertEquals(404, response.statusCode()); assertTrue(response.body().contains("beginDelim%3C%3EEndDelim")); assertTrue(response.body().contains("File not found")); } finally { @@ -726,8 +722,8 @@ public void testXss() throws Exception { } } - @AfterTest - public void teardown() throws IOException { + @AfterAll + public static void teardown() throws IOException { if (Files.exists(TEST_DIR)) { FileUtils.deleteFileTreeWithRetry(TEST_DIR); } diff --git a/test/jdk/com/sun/net/httpserver/simpleserver/StressDirListings.java b/test/jdk/com/sun/net/httpserver/simpleserver/StressDirListings.java index 18a1f060c489..23442ab6b208 100644 --- a/test/jdk/com/sun/net/httpserver/simpleserver/StressDirListings.java +++ b/test/jdk/com/sun/net/httpserver/simpleserver/StressDirListings.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,7 +25,7 @@ * @test * @summary Test to stress directory listings * @library /test/lib - * @run testng/othervm/timeout=180 StressDirListings + * @run junit/othervm/timeout=180 StressDirListings */ import java.io.IOException; @@ -43,12 +43,13 @@ import com.sun.net.httpserver.SimpleFileServer; import com.sun.net.httpserver.SimpleFileServer.OutputLevel; import jdk.test.lib.net.URIBuilder; -import org.testng.annotations.AfterTest; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.Test; import static java.lang.System.out; import static java.net.http.HttpClient.Builder.NO_PROXY; -import static org.testng.Assert.assertEquals; + +import org.junit.jupiter.api.AfterAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; public class StressDirListings { @@ -66,10 +67,10 @@ public static String now() { return String.format("[%d s, %d ms, %d ns] ", secs, mill, nan); } - HttpServer simpleFileServer; + static HttpServer simpleFileServer; - @BeforeTest - public void setup() throws IOException { + @BeforeAll + public static void setup() throws IOException { out.println(now() + " creating server"); if (ENABLE_LOGGING) { ConsoleHandler ch = new ConsoleHandler(); @@ -82,8 +83,8 @@ public void setup() throws IOException { out.println(now() + " server started"); } - @AfterTest - public void teardown() { + @AfterAll + public static void teardown() { out.println(now() + " stopping server"); simpleFileServer.stop(0); out.println(now() + " server stopped"); @@ -105,7 +106,7 @@ public void testDirListings() throws Exception { var request = HttpRequest.newBuilder(uri(simpleFileServer)).build(); for (int i=0; iThis is an index file """ + closeHTML; @@ -212,7 +213,8 @@ public Object[][] indexFiles() { }; } - @Test(dataProvider = "indexFiles") + @ParameterizedTest + @MethodSource("indexFiles") public void testDirectoryWithIndexGET(String id, String filename, String contentType, @@ -232,11 +234,11 @@ public void testDirectoryWithIndexGET(String id, var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "")).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 200); - assertEquals(response.headers().firstValue("content-type").get(), contentType); - assertEquals(response.headers().firstValue("content-length").get(), contentLength); - assertEquals(response.headers().firstValue("last-modified").get(), lastModified); - assertEquals(response.body(), expectedBody); + assertEquals(200, response.statusCode()); + assertEquals(contentType, response.headers().firstValue("content-type").get()); + assertEquals(contentLength, response.headers().firstValue("content-length").get()); + assertEquals(lastModified, response.headers().firstValue("last-modified").get()); + assertEquals(expectedBody, response.body()); } finally { server.stop(0); if (serveIndexFile) { @@ -265,9 +267,9 @@ public void testInvalidRequestURIGET() throws Exception { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "aFile?#.txt")).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 404); - assertEquals(response.headers().firstValue("content-length").get(), expectedLength); - assertEquals(response.body(), expectedBody); + assertEquals(404, response.statusCode()); + assertEquals(expectedLength, response.headers().firstValue("content-length").get()); + assertEquals(expectedBody, response.body()); } finally { server.stop(0); root.getFileSystem().close(); @@ -290,9 +292,9 @@ public void testNotFoundGET() throws Exception { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "doesNotExist.txt")).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 404); - assertEquals(response.headers().firstValue("content-length").get(), expectedLength); - assertEquals(response.body(), expectedBody); + assertEquals(404, response.statusCode()); + assertEquals(expectedLength, response.headers().firstValue("content-length").get()); + assertEquals(expectedBody, response.body()); } finally { server.stop(0); root.getFileSystem().close(); @@ -317,9 +319,9 @@ public void testNotFoundHEAD() throws Exception { var request = HttpRequest.newBuilder(uri(server, "doesNotExist.txt")) .method("HEAD", BodyPublishers.noBody()).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 404); - assertEquals(response.headers().firstValue("content-length").get(), expectedLength); - assertEquals(response.body(), ""); + assertEquals(404, response.statusCode()); + assertEquals(expectedLength, response.headers().firstValue("content-length").get()); + assertEquals("", response.body()); } finally { server.stop(0); root.getFileSystem().close(); @@ -339,9 +341,9 @@ public void testMovedPermanently() throws Exception { var uri = uri(server, "aDirectory"); var request = HttpRequest.newBuilder(uri).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 301); - assertEquals(response.headers().firstValue("content-length").get(), "0"); - assertEquals(response.headers().firstValue("location").get(), "/aDirectory/"); + assertEquals(301, response.statusCode()); + assertEquals("0", response.headers().firstValue("content-length").get()); + assertEquals("/aDirectory/", response.headers().firstValue("location").get()); } finally { server.stop(0); root.getFileSystem().close(); @@ -366,7 +368,7 @@ public void testXss() throws Exception { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "beginDelim%3C%3EEndDelim")).build(); var response = client.send(request, BodyHandlers.ofString()); - assertEquals(response.statusCode(), 404); + assertEquals(404, response.statusCode()); assertTrue(response.body().contains("beginDelim%3C%3EEndDelim")); assertTrue(response.body().contains("File not found")); } finally { @@ -376,8 +378,8 @@ public void testXss() throws Exception { } } - @AfterTest - public void teardown() throws IOException { + @AfterAll + public static void teardown() throws IOException { if (Files.exists(TEST_DIR)) { FileUtils.deleteFileTreeWithRetry(TEST_DIR); } diff --git a/test/jdk/com/sun/net/httpserver/simpleserver/jwebserver/CommandLineNegativeTest.java b/test/jdk/com/sun/net/httpserver/simpleserver/jwebserver/CommandLineNegativeTest.java index da01b1542d88..22812ae01176 100644 --- a/test/jdk/com/sun/net/httpserver/simpleserver/jwebserver/CommandLineNegativeTest.java +++ b/test/jdk/com/sun/net/httpserver/simpleserver/jwebserver/CommandLineNegativeTest.java @@ -26,7 +26,7 @@ * @summary Negative tests for the jwebserver command-line tool * @library /test/lib * @modules jdk.httpserver - * @run testng/othervm CommandLineNegativeTest + * @run junit/othervm CommandLineNegativeTest */ import java.io.IOException; @@ -37,13 +37,14 @@ import jdk.test.lib.process.OutputAnalyzer; import jdk.test.lib.process.ProcessTools; import jdk.test.lib.util.FileUtils; -import org.testng.SkipException; -import org.testng.annotations.AfterTest; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; import static java.lang.System.out; -import static org.testng.Assert.assertFalse; + +import org.junit.jupiter.api.AfterAll; +import static org.junit.jupiter.api.Assertions.assertFalse; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; public class CommandLineNegativeTest { @@ -55,8 +56,8 @@ public class CommandLineNegativeTest { static final Path TEST_FILE = TEST_DIR.resolve("file.txt"); static final String LOOPBACK_ADDR = InetAddress.getLoopbackAddress().getHostAddress(); - @BeforeTest - public void setup() throws IOException { + @BeforeAll + public static void setup() throws IOException { if (Files.exists(TEST_DIR)) { FileUtils.deleteFileTreeWithRetry(TEST_DIR); } @@ -64,15 +65,15 @@ public void setup() throws IOException { Files.createFile(TEST_FILE); } - @DataProvider - public Object[][] unknownOption() { + public static Object[][] unknownOption() { return new Object[][] { {"--unknownOption"}, {"null"} }; } - @Test(dataProvider = "unknownOption") + @ParameterizedTest + @MethodSource("unknownOption") public void testBadOption(String opt) throws Throwable { out.println("\n--- testUnknownOption, opt=\"%s\" ".formatted(opt)); simpleserver(JWEBSERVER, LOCALE_OPT, opt) @@ -80,8 +81,7 @@ public void testBadOption(String opt) throws Throwable { .shouldContain("Error: unknown option: " + opt); } - @DataProvider - public Object[][] tooManyOptionArgs() { + public static Object[][] tooManyOptionArgs() { return new Object[][] { {"-b", "localhost"}, {"-d", "/some/path"}, @@ -95,7 +95,8 @@ public Object[][] tooManyOptionArgs() { }; } - @Test(dataProvider = "tooManyOptionArgs") + @ParameterizedTest + @MethodSource("tooManyOptionArgs") public void testTooManyOptionArgs(String opt, String arg) throws Throwable { out.println("\n--- testTooManyOptionArgs, opt=\"%s\" ".formatted(opt)); simpleserver(JWEBSERVER, LOCALE_OPT, opt, arg, arg) @@ -103,8 +104,7 @@ public void testTooManyOptionArgs(String opt, String arg) throws Throwable { .shouldContain("Error: unknown option: " + arg); } - @DataProvider - public Object[][] noArg() { + public static Object[][] noArg() { return new Object[][] { {"-b", """ -b, --bind-address - Address to bind to. Default: %s (loopback). @@ -122,7 +122,8 @@ public Object[][] noArg() { }; } - @Test(dataProvider = "noArg") + @ParameterizedTest + @MethodSource("noArg") public void testNoArg(String opt, String msg) throws Throwable { out.println("\n--- testNoArg, opt=\"%s\" ".formatted(opt)); simpleserver(JWEBSERVER, LOCALE_OPT, opt) @@ -131,8 +132,7 @@ public void testNoArg(String opt, String msg) throws Throwable { .shouldContain(msg); } - @DataProvider - public Object[][] invalidValue() { + public static Object[][] invalidValue() { return new Object[][] { {"-b", "[127.0.0.1]"}, {"-b", "badhost"}, @@ -146,7 +146,8 @@ public Object[][] invalidValue() { }; } - @Test(dataProvider = "invalidValue") + @ParameterizedTest + @MethodSource("invalidValue") public void testInvalidValue(String opt, String val) throws Throwable { out.println("\n--- testInvalidValue, opt=\"%s\" ".formatted(opt)); simpleserver(JWEBSERVER, LOCALE_OPT, opt, val) @@ -154,10 +155,10 @@ public void testInvalidValue(String opt, String val) throws Throwable { .shouldContain("Error: invalid value given for " + opt + ": " + val); } - @DataProvider - public Object[][] portOptions() { return new Object[][] {{"-p"}, {"--port"}}; } + public static Object[][] portOptions() { return new Object[][] {{"-p"}, {"--port"}}; } - @Test(dataProvider = "portOptions") + @ParameterizedTest + @MethodSource("portOptions") public void testPortOutOfRange(String opt) throws Throwable { out.println("\n--- testPortOutOfRange, opt=\"%s\" ".formatted(opt)); simpleserver(JWEBSERVER, LOCALE_OPT, opt, "65536") // range 0 to 65535 @@ -165,10 +166,10 @@ public void testPortOutOfRange(String opt) throws Throwable { .shouldContain("Error: server config failed: " + "port out of range:65536"); } - @DataProvider - public Object[][] directoryOptions() { return new Object[][] {{"-d"}, {"--directory"}}; } + public static Object[][] directoryOptions() { return new Object[][] {{"-d"}, {"--directory"}}; } - @Test(dataProvider = "directoryOptions") + @ParameterizedTest + @MethodSource("directoryOptions") public void testRootNotADirectory(String opt) throws Throwable { out.println("\n--- testRootNotADirectory, opt=\"%s\" ".formatted(opt)); var file = TEST_FILE.toString(); @@ -178,7 +179,8 @@ public void testRootNotADirectory(String opt) throws Throwable { .shouldContain("Error: server config failed: " + "Path is not a directory: " + file); } - @Test(dataProvider = "directoryOptions") + @ParameterizedTest + @MethodSource("directoryOptions") public void testRootDoesNotExist(String opt) throws Throwable { out.println("\n--- testRootDoesNotExist, opt=\"%s\" ".formatted(opt)); Path root = TEST_DIR.resolve("not/existent/dir"); @@ -188,14 +190,12 @@ public void testRootDoesNotExist(String opt) throws Throwable { .shouldContain("Error: server config failed: " + "Path does not exist: " + root.toString()); } - @Test(dataProvider = "directoryOptions") + @ParameterizedTest + @MethodSource("directoryOptions") public void testRootNotReadable(String opt) throws Throwable { out.println("\n--- testRootNotReadable, opt=\"%s\" ".formatted(opt)); - if (Platform.isWindows()) { - // Not applicable to Windows. Reason: cannot revoke an owner's read - // access to a directory that was created by that owner - throw new SkipException("cannot run on Windows"); - } + Assumptions.assumeFalse(Platform.isWindows(), "cannot run on Windows"); // Not applicable to Windows. Reason: cannot revoke an owner's read + // access to a directory that was created by that owner Path root = Files.createDirectories(TEST_DIR.resolve("not/readable/dir")); try { root.toFile().setReadable(false, false); @@ -208,8 +208,8 @@ public void testRootNotReadable(String opt) throws Throwable { } } - @AfterTest - public void teardown() throws IOException { + @AfterAll + public static void teardown() throws IOException { if (Files.exists(TEST_DIR)) { FileUtils.deleteFileTreeWithRetry(TEST_DIR); } diff --git a/test/jdk/com/sun/net/httpserver/simpleserver/jwebserver/CommandLinePortNotSpecifiedTest.java b/test/jdk/com/sun/net/httpserver/simpleserver/jwebserver/CommandLinePortNotSpecifiedTest.java index cd2ccbeded42..f6250d63908d 100644 --- a/test/jdk/com/sun/net/httpserver/simpleserver/jwebserver/CommandLinePortNotSpecifiedTest.java +++ b/test/jdk/com/sun/net/httpserver/simpleserver/jwebserver/CommandLinePortNotSpecifiedTest.java @@ -27,7 +27,7 @@ * @summary Tests the jwebserver tool with port not specified * @modules jdk.httpserver * @library /test/lib - * @run testng/othervm/manual CommandLinePortNotSpecifiedTest + * @run junit/othervm/manual CommandLinePortNotSpecifiedTest */ import java.io.IOException; @@ -39,11 +39,12 @@ import jdk.test.lib.process.OutputAnalyzer; import jdk.test.lib.process.ProcessTools; import jdk.test.lib.util.FileUtils; -import org.testng.annotations.AfterTest; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.Test; import static java.lang.System.out; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + public class CommandLinePortNotSpecifiedTest { static final Path JAVA_HOME = Path.of(System.getProperty("java.home")); @@ -55,8 +56,8 @@ public class CommandLinePortNotSpecifiedTest { static final String TEST_DIR_STR = TEST_DIR.toString(); static final String LOOPBACK_ADDR = InetAddress.getLoopbackAddress().getHostAddress(); - @BeforeTest - public void setup() throws IOException { + @BeforeAll + public static void setup() throws IOException { if (Files.exists(TEST_DIR)) { FileUtils.deleteFileTreeWithRetry(TEST_DIR); } @@ -92,8 +93,8 @@ public void testPortNotSpecified() throws Throwable { .shouldContain("URL http://" + LOOPBACK_ADDR); } - @AfterTest - public void teardown() throws IOException { + @AfterAll + public static void teardown() throws IOException { if (Files.exists(TEST_DIR)) { FileUtils.deleteFileTreeWithRetry(TEST_DIR); } diff --git a/test/jdk/com/sun/net/httpserver/simpleserver/jwebserver/CommandLinePositiveTest.java b/test/jdk/com/sun/net/httpserver/simpleserver/jwebserver/CommandLinePositiveTest.java index c0b0b4bc7680..8110f5d9b10c 100644 --- a/test/jdk/com/sun/net/httpserver/simpleserver/jwebserver/CommandLinePositiveTest.java +++ b/test/jdk/com/sun/net/httpserver/simpleserver/jwebserver/CommandLinePositiveTest.java @@ -27,7 +27,7 @@ * @library /test/lib * @build jdk.test.lib.net.IPSupport * @modules jdk.httpserver - * @run testng/othervm CommandLinePositiveTest + * @run junit/othervm CommandLinePositiveTest */ import java.io.IOException; @@ -41,12 +41,13 @@ import jdk.test.lib.process.OutputAnalyzer; import jdk.test.lib.process.ProcessTools; import jdk.test.lib.util.FileUtils; -import org.testng.annotations.AfterTest; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; import static java.lang.System.out; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + public class CommandLinePositiveTest { static final String JAVA_VERSION = System.getProperty("java.version"); @@ -84,8 +85,8 @@ public class CommandLinePositiveTest { static final String LOOPBACK_ADDR = InetAddress.getLoopbackAddress().getHostAddress(); - @BeforeTest - public void setup() throws IOException { + @BeforeAll + public static void setup() throws IOException { if (Files.exists(ROOT_DIR)) { FileUtils.deleteFileTreeWithRetry(ROOT_DIR); } @@ -105,16 +106,17 @@ static int normalExitCode() { } } - @DataProvider - public Object[][] directoryOptions() { return new Object[][] {{"-d"}, {"--directory"}}; } + public static Object[][] directoryOptions() { return new Object[][] {{"-d"}, {"--directory"}}; } - @Test(dataProvider = "directoryOptions") + @ParameterizedTest + @MethodSource("directoryOptions") public void testAbsDirectory(String opt) throws Throwable { out.printf("\n--- testAbsDirectory, opt=\"%s\"%n", opt); testDirectory(opt, ROOT_DIR_STR); } - @Test(dataProvider = "directoryOptions") + @ParameterizedTest + @MethodSource("directoryOptions") public void testRelDirectory(String opt) throws Throwable { out.printf("\n--- testRelDirectory, opt=\"%s\"%n", opt); Path rootRelDir = CWD.relativize(ROOT_DIR); @@ -129,10 +131,10 @@ private static void testDirectory(String opt, String rootDir) throws Throwable { .shouldContain("URL http://" + LOOPBACK_ADDR); } - @DataProvider - public Object[][] portOptions() { return new Object[][] {{"-p"}, {"--port"}}; } + public static Object[][] portOptions() { return new Object[][] {{"-p"}, {"--port"}}; } - @Test(dataProvider = "portOptions") + @ParameterizedTest + @MethodSource("portOptions") public void testPort(String opt) throws Throwable { out.println("\n--- testPort, opt=\"%s\" ".formatted(opt)); simpleserver(JWEBSERVER, LOCALE_OPT, opt, "0") @@ -142,8 +144,7 @@ public void testPort(String opt) throws Throwable { .shouldContain("URL http://" + LOOPBACK_ADDR); } - @DataProvider - public Object[][] helpOptions() { return new Object[][] {{"-h"}, {"-?"}, {"--help"}}; } + public static Object[][] helpOptions() { return new Object[][] {{"-h"}, {"-?"}, {"--help"}}; } static final String USAGE_TEXT = """ Usage: jwebserver [-b bind address] [-p port] [-d directory] @@ -161,7 +162,8 @@ public void testPort(String opt) throws Throwable { -version, --version - Prints version information and exits. To stop the server, press Ctrl + C.""".formatted(LOOPBACK_ADDR); - @Test(dataProvider = "helpOptions") + @ParameterizedTest + @MethodSource("helpOptions") public void testHelp(String opt) throws Throwable { out.println("\n--- testHelp, opt=\"%s\" ".formatted(opt)); simpleserver(WaitForLine.HELP_STARTUP_LINE, @@ -172,10 +174,10 @@ public void testHelp(String opt) throws Throwable { .shouldContain(OPTIONS_TEXT); } - @DataProvider - public Object[][] versionOptions() { return new Object[][] {{"-version"}, {"--version"}}; } + public static Object[][] versionOptions() { return new Object[][] {{"-version"}, {"--version"}}; } - @Test(dataProvider = "versionOptions") + @ParameterizedTest + @MethodSource("versionOptions") public void testVersion(String opt) throws Throwable { out.println("\n--- testVersion, opt=\"%s\" ".formatted(opt)); simpleserver(WaitForLine.VERSION_STARTUP_LINE, @@ -184,10 +186,10 @@ public void testVersion(String opt) throws Throwable { .shouldHaveExitValue(0); } - @DataProvider - public Object[][] bindOptions() { return new Object[][] {{"-b"}, {"--bind-address"}}; } + public static Object[][] bindOptions() { return new Object[][] {{"-b"}, {"--bind-address"}}; } - @Test(dataProvider = "bindOptions") + @ParameterizedTest + @MethodSource("bindOptions") public void testBindAllInterfaces(String opt) throws Throwable { out.println("\n--- testBindAllInterfaces, opt=\"%s\" ".formatted(opt)); simpleserver(JWEBSERVER, LOCALE_OPT, "-p", "0", opt, "0.0.0.0") @@ -202,7 +204,8 @@ public void testBindAllInterfaces(String opt) throws Throwable { } } - @Test(dataProvider = "bindOptions") + @ParameterizedTest + @MethodSource("bindOptions") public void testLastOneWinsBindAddress(String opt) throws Throwable { out.println("\n--- testLastOneWinsBindAddress, opt=\"%s\" ".formatted(opt)); simpleserver(JWEBSERVER, LOCALE_OPT, "-p", "0", opt, "123.4.5.6", opt, LOOPBACK_ADDR) @@ -212,7 +215,8 @@ public void testLastOneWinsBindAddress(String opt) throws Throwable { } - @Test(dataProvider = "directoryOptions") + @ParameterizedTest + @MethodSource("directoryOptions") public void testLastOneWinsDirectory(String opt) throws Throwable { out.println("\n--- testLastOneWinsDirectory, opt=\"%s\" ".formatted(opt)); simpleserver(JWEBSERVER, LOCALE_OPT, "-p", "0", opt, CWD_STR, opt, CWD_STR) @@ -222,10 +226,10 @@ public void testLastOneWinsDirectory(String opt) throws Throwable { .shouldContain("URL http://" + LOOPBACK_ADDR); } - @DataProvider - public Object[][] outputOptions() { return new Object[][] {{"-o"}, {"--output"}}; } + public static Object[][] outputOptions() { return new Object[][] {{"-o"}, {"--output"}}; } - @Test(dataProvider = "outputOptions") + @ParameterizedTest + @MethodSource("outputOptions") public void testLastOneWinsOutput(String opt) throws Throwable { out.println("\n--- testLastOneWinsOutput, opt=\"%s\" ".formatted(opt)); simpleserver(JWEBSERVER, LOCALE_OPT, "-p", "0", opt, "none", opt, "verbose") @@ -235,7 +239,8 @@ public void testLastOneWinsOutput(String opt) throws Throwable { .shouldContain("URL http://" + LOOPBACK_ADDR); } - @Test(dataProvider = "portOptions") + @ParameterizedTest + @MethodSource("portOptions") public void testLastOneWinsPort(String opt) throws Throwable { out.println("\n--- testLastOneWinsPort, opt=\"%s\" ".formatted(opt)); simpleserver(JWEBSERVER, LOCALE_OPT, opt, "-999", opt, "0") @@ -245,8 +250,8 @@ public void testLastOneWinsPort(String opt) throws Throwable { .shouldContain("URL http://" + LOOPBACK_ADDR); } - @AfterTest - public void teardown() throws IOException { + @AfterAll + public static void teardown() throws IOException { if (Files.exists(ROOT_DIR)) { FileUtils.deleteFileTreeWithRetry(ROOT_DIR); } diff --git a/test/jdk/com/sun/net/httpserver/simpleserver/jwebserver/MaxRequestTimeTest.java b/test/jdk/com/sun/net/httpserver/simpleserver/jwebserver/MaxRequestTimeTest.java index 695843de8b09..c19ca632bf3b 100644 --- a/test/jdk/com/sun/net/httpserver/simpleserver/jwebserver/MaxRequestTimeTest.java +++ b/test/jdk/com/sun/net/httpserver/simpleserver/jwebserver/MaxRequestTimeTest.java @@ -27,7 +27,7 @@ * @summary Tests the jwebserver's maximum request time * @modules jdk.httpserver * @library /test/lib - * @run testng/othervm MaxRequestTimeTest + * @run junit/othervm MaxRequestTimeTest */ import java.io.IOException; @@ -47,12 +47,13 @@ import jdk.test.lib.process.OutputAnalyzer; import jdk.test.lib.process.ProcessTools; import jdk.test.lib.util.FileUtils; -import org.testng.annotations.AfterTest; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.Test; import static java.lang.System.out; import static java.net.http.HttpClient.Builder.NO_PROXY; -import static org.testng.Assert.*; + +import org.junit.jupiter.api.AfterAll; +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; /** * This test confirms that the jwebserver does not wait indefinitely for @@ -80,8 +81,8 @@ public class MaxRequestTimeTest { private static final SSLContext sslContext = SimpleSSLContext.findSSLContext(); - @BeforeTest - public void setup() throws IOException { + @BeforeAll + public static void setup() throws IOException { if (Files.exists(TEST_DIR)) { FileUtils.deleteFileTreeWithRetry(TEST_DIR); } @@ -118,17 +119,17 @@ public void testMaxRequestTime() throws Throwable { """; - void sendHTTPRequest() throws IOException, InterruptedException { + static void sendHTTPRequest() throws IOException, InterruptedException { out.println("\n--- sendHTTPRequest"); var client = HttpClient.newBuilder() .proxy(NO_PROXY) .build(); var request = HttpRequest.newBuilder(URI.create("http://localhost:" + PORT.get() + "/")).build(); var response = client.send(request, HttpResponse.BodyHandlers.ofString()); - assertEquals(response.body(), expectedBody); + assertEquals(expectedBody, response.body()); } - void sendHTTPSRequest() throws IOException, InterruptedException { + static void sendHTTPSRequest() throws IOException, InterruptedException { out.println("\n--- sendHTTPSRequest"); var client = HttpClient.newBuilder() .sslContext(sslContext) @@ -143,8 +144,8 @@ void sendHTTPSRequest() throws IOException, InterruptedException { } } - @AfterTest - public void teardown() throws IOException { + @AfterAll + public static void teardown() throws IOException { if (Files.exists(TEST_DIR)) { FileUtils.deleteFileTreeWithRetry(TEST_DIR); } From e008ce3a7d7e34ae961c9e857a06e80dd9b60409 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 23 Mar 2026 15:31:45 +0000 Subject: [PATCH 069/168] 8344345: test/hotspot/gtest/x86/x86-asmtest.py has trailing whitespaces Backport-of: 078e71f4a3d68d298ab3c383e46d18912e1de7db --- test/hotspot/gtest/x86/x86-asmtest.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/hotspot/gtest/x86/x86-asmtest.py b/test/hotspot/gtest/x86/x86-asmtest.py index 714b7aaea403..6544e30579f7 100644 --- a/test/hotspot/gtest/x86/x86-asmtest.py +++ b/test/hotspot/gtest/x86/x86-asmtest.py @@ -172,10 +172,10 @@ class NFInstruction(Instruction): def __init__(self, name, aname, no_flag): super().__init__(name, aname) self.no_flag = no_flag - + def cstr(self): return f'__ {self._name}(' + ', '.join([op.cstr() for op in self.operands]) + (f', {str(self.no_flag).lower()}' if self.no_flag is not None else '') + ');' - + def astr(self): # JDK assembler uses 'cl' for shift instructions with one operand by default cl_str = (', cl' if self._name in shift_rot_ops and len(self.operands) == 2 else '') @@ -305,10 +305,10 @@ def __init__(self, name, aname, width, cond, reg1, reg2, reg3): self.cond = cond self.generate_operands(self.reg1, self.reg2, self.reg3) self.demote = True - + def cstr(self): return f'__ {self._name} (' + 'Assembler::Condition::' + self.cond + ', ' + ', '.join([reg.cstr() for reg in self.operands]) + ');' - + def astr(self): operands = self.operands if self.demote: @@ -326,10 +326,10 @@ def __init__(self, name, aname, width, cond, reg1, reg2, mem_base, mem_idx): self.cond = cond self.generate_operands(self.reg1, self.reg2, self.mem) self.demote = True - + def cstr(self): return f'__ {self._name} (' + 'Assembler::Condition::' + self.cond + ', ' + ', '.join([reg.cstr() for reg in self.operands]) + ');' - + def astr(self): operands = self.operands if self.demote: From 204ec6f968b11b9e9478226cb74b0daf0484373f Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 23 Mar 2026 15:33:04 +0000 Subject: [PATCH 070/168] 8361606: ConsumeNextMnemonicKeyTypedTest.java fails on Windows: character typed with VK_A: a 8321303: Intermittent open/test/jdk/java/awt/KeyboardFocusmanager/ConsumeNextMnemonicKeyTypedTest/ConsumeNextMnemonicKeyTypedTest.java failure on Linux Backport-of: 07ea907e4fc8aa8fda01d8fe64c599f9d944eef9 --- .../classes/javax/swing/plaf/basic/BasicPopupMenuUI.java | 3 ++- test/jdk/ProblemList.txt | 1 - .../ConsumeNextMnemonicKeyTypedTest.java | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicPopupMenuUI.java b/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicPopupMenuUI.java index 3869da4ee2ba..a1b6b1e0ed9e 100644 --- a/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicPopupMenuUI.java +++ b/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicPopupMenuUI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -379,6 +379,7 @@ public void menuKeyPressed(MenuKeyEvent e) { } else if (item.isEnabled()) { // we have a menu item manager.clearSelectedPath(); + sun.awt.SunToolkit.consumeNextKeyTyped(e); item.doClick(); } e.consume(); diff --git a/test/jdk/ProblemList.txt b/test/jdk/ProblemList.txt index c7d6e9ef2241..89c9048babbf 100644 --- a/test/jdk/ProblemList.txt +++ b/test/jdk/ProblemList.txt @@ -491,7 +491,6 @@ java/awt/Graphics2D/DrawString/RotTransText.java 8316878 linux-all java/awt/KeyboardFocusmanager/TypeAhead/ButtonActionKeyTest/ButtonActionKeyTest.java 8257529 windows-x64 java/awt/KeyboardFocusmanager/ConsumeNextMnemonicKeyTypedTest/ConsumeForModalDialogTest/ConsumeForModalDialogTest.java 8302787 windows-all java/awt/KeyboardFocusmanager/TypeAhead/MenuItemActivatedTest/MenuItemActivatedTest.java 8302787 windows-all -java/awt/KeyboardFocusmanager/ConsumeNextMnemonicKeyTypedTest/ConsumeNextMnemonicKeyTypedTest.java 8321303 linux-all java/awt/Dialog/MakeWindowAlwaysOnTop/MakeWindowAlwaysOnTop.java 8266243 macosx-aarch64 java/awt/Dialog/ChoiceModalDialogTest.java 8161475 macosx-all diff --git a/test/jdk/java/awt/KeyboardFocusmanager/ConsumeNextMnemonicKeyTypedTest/ConsumeNextMnemonicKeyTypedTest.java b/test/jdk/java/awt/KeyboardFocusmanager/ConsumeNextMnemonicKeyTypedTest/ConsumeNextMnemonicKeyTypedTest.java index 5b2dc2844f17..60c05fb05a67 100644 --- a/test/jdk/java/awt/KeyboardFocusmanager/ConsumeNextMnemonicKeyTypedTest/ConsumeNextMnemonicKeyTypedTest.java +++ b/test/jdk/java/awt/KeyboardFocusmanager/ConsumeNextMnemonicKeyTypedTest/ConsumeNextMnemonicKeyTypedTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2006, 2025, Oracle and/or its affiliates. All rights reserved. * 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 @key headful - @bug 6346690 + @bug 6346690 8361606 8321303 @summary Tests that key_typed is consumed after mnemonic key_pressed is handled for a menu item. @library /test/lib @build jdk.test.lib.Platform From cab51ba262ba6e1ca27fb9bc24199dd8add8a867 Mon Sep 17 00:00:00 2001 From: SendaoYan Date: Tue, 24 Mar 2026 01:44:14 +0000 Subject: [PATCH 071/168] 8377949: TestZRelocationSetEvent.java intermittent fails OOME Backport-of: cb70654943695049e75743ee957c7c51ac33ffdc --- .../jdk/jfr/event/gc/detailed/TestZRelocationSetEvent.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/jdk/jdk/jfr/event/gc/detailed/TestZRelocationSetEvent.java b/test/jdk/jdk/jfr/event/gc/detailed/TestZRelocationSetEvent.java index e64d6f83ab82..6217de6e2352 100644 --- a/test/jdk/jdk/jfr/event/gc/detailed/TestZRelocationSetEvent.java +++ b/test/jdk/jdk/jfr/event/gc/detailed/TestZRelocationSetEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,7 +25,6 @@ import java.util.List; -import static gc.testlibrary.Allocation.blackHole; import jdk.jfr.Recording; import jdk.jfr.consumer.RecordedEvent; import jdk.test.lib.jfr.EventNames; @@ -36,7 +35,7 @@ * @requires vm.hasJFR & vm.gc.Z * @requires vm.flagless * @library /test/lib /test/jdk /test/hotspot/jtreg - * @run main/othervm -XX:+UseZGC -Xmx32M jdk.jfr.event.gc.detailed.TestZRelocationSetEvent + * @run main/othervm -XX:+UseZGC -Xmx64M jdk.jfr.event.gc.detailed.TestZRelocationSetEvent */ public class TestZRelocationSetEvent { From 94657a2f84561fea349fbd3d70ec73e5d54a712f Mon Sep 17 00:00:00 2001 From: Zhaokun Xie Date: Tue, 24 Mar 2026 08:03:26 +0000 Subject: [PATCH 072/168] 8372380: Make hs_err reporting more robust for unattached threads Backport-of: 6e920fbdab17201886804bb53b59188b362f541d --- src/hotspot/share/compiler/compilationMemoryStatistic.cpp | 6 ++++-- src/hotspot/share/gc/g1/g1CollectedHeap.cpp | 3 ++- src/hotspot/share/gc/shared/gcLogPrecious.cpp | 4 +++- src/hotspot/share/utilities/vmError.cpp | 3 ++- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/hotspot/share/compiler/compilationMemoryStatistic.cpp b/src/hotspot/share/compiler/compilationMemoryStatistic.cpp index d842bcb2b6f8..49549b1142cc 100644 --- a/src/hotspot/share/compiler/compilationMemoryStatistic.cpp +++ b/src/hotspot/share/compiler/compilationMemoryStatistic.cpp @@ -1010,8 +1010,10 @@ void CompilationMemoryStatistic::print_error_report(outputStream* st) { oom_stats->print_peak_state_on(st); st->cr(); } - st->print_cr("Compiler Memory Statistic, 10 most expensive compilations:"); - print_all_by_size(st, false, false, 0, 10); + if (Thread::current_or_null_safe() != nullptr) { + st->print_cr("Compiler Memory Statistic, 10 most expensive compilations:"); + print_all_by_size(st, false, false, 0, 10); + } } void CompilationMemoryStatistic::print_final_report(outputStream* st) { diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp index e96fbeba964a..f1f1c747af0e 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp @@ -2220,7 +2220,8 @@ void G1CollectedHeap::print_heap_regions() const { } void G1CollectedHeap::print_heap_on(outputStream* st) const { - size_t heap_used = Heap_lock->owned_by_self() ? used() : used_unlocked(); + size_t heap_used = (Thread::current_or_null_safe() != nullptr && + Heap_lock->owned_by_self()) ? used() : used_unlocked(); st->print("%-20s", "garbage-first heap"); st->print(" total reserved %zuK, committed %zuK, used %zuK", _hrm.reserved().byte_size()/K, capacity()/K, heap_used/K); diff --git a/src/hotspot/share/gc/shared/gcLogPrecious.cpp b/src/hotspot/share/gc/shared/gcLogPrecious.cpp index 43bd58db1aa0..d556eed1b690 100644 --- a/src/hotspot/share/gc/shared/gcLogPrecious.cpp +++ b/src/hotspot/share/gc/shared/gcLogPrecious.cpp @@ -25,6 +25,7 @@ #include "runtime/mutex.hpp" #include "runtime/mutexLocker.hpp" #include "runtime/os.hpp" +#include "runtime/thread.hpp" #include "utilities/ostream.hpp" stringStream* GCLogPrecious::_lines = nullptr; @@ -83,7 +84,8 @@ void GCLogPrecious::print_on_error(outputStream* st) { return; } - if (!_lock->try_lock_without_rank_check()) { + if (Thread::current_or_null_safe() == nullptr || + !_lock->try_lock_without_rank_check()) { st->print_cr("\n"); return; } diff --git a/src/hotspot/share/utilities/vmError.cpp b/src/hotspot/share/utilities/vmError.cpp index 93ae435ae727..026504b7cd67 100644 --- a/src/hotspot/share/utilities/vmError.cpp +++ b/src/hotspot/share/utilities/vmError.cpp @@ -661,6 +661,7 @@ void VMError::report(outputStream* st, bool _verbose) { BEGIN if (MemTracker::enabled() && NmtVirtualMemory_lock != nullptr && + _thread != nullptr && NmtVirtualMemory_lock->owned_by_self()) { // Manually unlock to avoid reentrancy due to mallocs in detailed mode. NmtVirtualMemory_lock->unlock(); @@ -1285,7 +1286,7 @@ void VMError::report(outputStream* st, bool _verbose) { os::print_signal_handlers(st, buf, sizeof(buf)); st->cr(); - STEP_IF("Native Memory Tracking", _verbose) + STEP_IF("Native Memory Tracking", _verbose && _thread != nullptr) MemTracker::error_report(st); st->cr(); From cf531adfbf0c1fa69d27d75302f5b949362c3803 Mon Sep 17 00:00:00 2001 From: Arno Zeller Date: Tue, 24 Mar 2026 08:05:12 +0000 Subject: [PATCH 073/168] 8380428: ProblemList containers/docker/TestJcmdWithSideCar.java on linux-all Backport-of: f5fbb6a237f022b485dbc79b6ec819df65535ffe --- test/hotspot/jtreg/ProblemList.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/hotspot/jtreg/ProblemList.txt b/test/hotspot/jtreg/ProblemList.txt index 26198afba6c4..6c7994421e34 100644 --- a/test/hotspot/jtreg/ProblemList.txt +++ b/test/hotspot/jtreg/ProblemList.txt @@ -117,7 +117,7 @@ applications/jcstress/copy.java 8229852 linux-all containers/docker/TestJcmd.java 8278102 linux-all containers/docker/TestMemoryAwareness.java 8303470 linux-all containers/docker/TestJFREvents.java 8327723 linux-x64 -containers/docker/TestJcmdWithSideCar.java 8341518 linux-x64 +containers/docker/TestJcmdWithSideCar.java 8341518 linux-all ############################################################################# From 05f5a1784065c010580e4d334fa2c27ab7396f94 Mon Sep 17 00:00:00 2001 From: Sergey Nazarkin Date: Tue, 24 Mar 2026 10:50:07 +0000 Subject: [PATCH 074/168] 8363943: ARM32: Represent Registers as values Reviewed-by: bulasevich, shade Backport-of: c1230068dc4501c52999ac0bbb3a2e5933453f09 --- src/hotspot/cpu/arm/arm_32.ad | 32 +- src/hotspot/cpu/arm/assembler_arm_32.hpp | 2 +- src/hotspot/cpu/arm/c1_CodeStubs_arm.cpp | 2 +- src/hotspot/cpu/arm/c1_LIRAssembler_arm.cpp | 8 +- src/hotspot/cpu/arm/interp_masm_arm.cpp | 4 +- src/hotspot/cpu/arm/register_arm.cpp | 30 +- src/hotspot/cpu/arm/register_arm.hpp | 547 ++++++++++-------- src/hotspot/cpu/arm/sharedRuntime_arm.cpp | 10 +- src/hotspot/cpu/arm/vmreg_arm.cpp | 4 +- src/hotspot/cpu/arm/vmreg_arm.hpp | 8 +- src/hotspot/cpu/arm/vmreg_arm.inline.hpp | 8 +- .../linux_arm/macroAssembler_linux_arm_32.cpp | 6 +- 12 files changed, 376 insertions(+), 285 deletions(-) diff --git a/src/hotspot/cpu/arm/arm_32.ad b/src/hotspot/cpu/arm/arm_32.ad index 1c15d55fbc3d..be4dc825e9c7 100644 --- a/src/hotspot/cpu/arm/arm_32.ad +++ b/src/hotspot/cpu/arm/arm_32.ad @@ -62,22 +62,22 @@ register %{ // Integer/Long Registers // ---------------------------- -reg_def R_R0 (SOC, SOC, Op_RegI, 0, R(0)->as_VMReg()); -reg_def R_R1 (SOC, SOC, Op_RegI, 1, R(1)->as_VMReg()); -reg_def R_R2 (SOC, SOC, Op_RegI, 2, R(2)->as_VMReg()); -reg_def R_R3 (SOC, SOC, Op_RegI, 3, R(3)->as_VMReg()); -reg_def R_R4 (SOC, SOE, Op_RegI, 4, R(4)->as_VMReg()); -reg_def R_R5 (SOC, SOE, Op_RegI, 5, R(5)->as_VMReg()); -reg_def R_R6 (SOC, SOE, Op_RegI, 6, R(6)->as_VMReg()); -reg_def R_R7 (SOC, SOE, Op_RegI, 7, R(7)->as_VMReg()); -reg_def R_R8 (SOC, SOE, Op_RegI, 8, R(8)->as_VMReg()); -reg_def R_R9 (SOC, SOE, Op_RegI, 9, R(9)->as_VMReg()); -reg_def R_R10(NS, SOE, Op_RegI, 10, R(10)->as_VMReg()); -reg_def R_R11(NS, SOE, Op_RegI, 11, R(11)->as_VMReg()); -reg_def R_R12(SOC, SOC, Op_RegI, 12, R(12)->as_VMReg()); -reg_def R_R13(NS, NS, Op_RegI, 13, R(13)->as_VMReg()); -reg_def R_R14(SOC, SOC, Op_RegI, 14, R(14)->as_VMReg()); -reg_def R_R15(NS, NS, Op_RegI, 15, R(15)->as_VMReg()); +reg_def R_R0 (SOC, SOC, Op_RegI, 0, as_Register(0)->as_VMReg()); +reg_def R_R1 (SOC, SOC, Op_RegI, 1, as_Register(1)->as_VMReg()); +reg_def R_R2 (SOC, SOC, Op_RegI, 2, as_Register(2)->as_VMReg()); +reg_def R_R3 (SOC, SOC, Op_RegI, 3, as_Register(3)->as_VMReg()); +reg_def R_R4 (SOC, SOE, Op_RegI, 4, as_Register(4)->as_VMReg()); +reg_def R_R5 (SOC, SOE, Op_RegI, 5, as_Register(5)->as_VMReg()); +reg_def R_R6 (SOC, SOE, Op_RegI, 6, as_Register(6)->as_VMReg()); +reg_def R_R7 (SOC, SOE, Op_RegI, 7, as_Register(7)->as_VMReg()); +reg_def R_R8 (SOC, SOE, Op_RegI, 8, as_Register(8)->as_VMReg()); +reg_def R_R9 (SOC, SOE, Op_RegI, 9, as_Register(9)->as_VMReg()); +reg_def R_R10(NS, SOE, Op_RegI, 10, as_Register(10)->as_VMReg()); +reg_def R_R11(NS, SOE, Op_RegI, 11, as_Register(11)->as_VMReg()); +reg_def R_R12(SOC, SOC, Op_RegI, 12, as_Register(12)->as_VMReg()); +reg_def R_R13(NS, NS, Op_RegI, 13, as_Register(13)->as_VMReg()); +reg_def R_R14(SOC, SOC, Op_RegI, 14, as_Register(14)->as_VMReg()); +reg_def R_R15(NS, NS, Op_RegI, 15, as_Register(15)->as_VMReg()); // ---------------------------- // Float/Double Registers diff --git a/src/hotspot/cpu/arm/assembler_arm_32.hpp b/src/hotspot/cpu/arm/assembler_arm_32.hpp index ae13644ecf9e..d6524f086800 100644 --- a/src/hotspot/cpu/arm/assembler_arm_32.hpp +++ b/src/hotspot/cpu/arm/assembler_arm_32.hpp @@ -114,7 +114,7 @@ class RegisterSet { } RegisterSet(Register first, Register last) { - assert(first < last, "encoding constraint"); + assert(first->encoding() < last->encoding(), "encoding constraint"); _encoding = (1 << (last->encoding() + 1)) - (1 << first->encoding()); } diff --git a/src/hotspot/cpu/arm/c1_CodeStubs_arm.cpp b/src/hotspot/cpu/arm/c1_CodeStubs_arm.cpp index 5683bc59d5c0..60eae37e2193 100644 --- a/src/hotspot/cpu/arm/c1_CodeStubs_arm.cpp +++ b/src/hotspot/cpu/arm/c1_CodeStubs_arm.cpp @@ -181,7 +181,7 @@ void MonitorEnterStub::emit_code(LIR_Assembler* ce) { const Register lock_reg = _lock_reg->as_pointer_register(); ce->verify_reserved_argument_area_size(2); - if (obj_reg < lock_reg) { + if (obj_reg->encoding() < lock_reg->encoding()) { __ stmia(SP, RegisterSet(obj_reg) | RegisterSet(lock_reg)); } else { __ str(obj_reg, Address(SP)); diff --git a/src/hotspot/cpu/arm/c1_LIRAssembler_arm.cpp b/src/hotspot/cpu/arm/c1_LIRAssembler_arm.cpp index 66c7b916f1ff..2f7901db82e3 100644 --- a/src/hotspot/cpu/arm/c1_LIRAssembler_arm.cpp +++ b/src/hotspot/cpu/arm/c1_LIRAssembler_arm.cpp @@ -2655,11 +2655,11 @@ void LIR_Assembler::volatile_move_op(LIR_Opr src, LIR_Opr dest, BasicType type, const Register src_hi = src->as_register_hi(); assert(addr->index()->is_illegal() && addr->disp() == 0, "The address is simple already"); - if (src_lo < src_hi) { + if (src_lo->encoding() < src_hi->encoding()) { null_check_offset = __ offset(); __ stmia(addr->base()->as_register(), RegisterSet(src_lo) | RegisterSet(src_hi)); } else { - assert(src_lo < Rtemp, "Rtemp is higher than any allocatable register"); + assert(src_lo->encoding() < Rtemp->encoding(), "Rtemp is higher than any allocatable register"); __ mov(Rtemp, src_hi); null_check_offset = __ offset(); __ stmia(addr->base()->as_register(), RegisterSet(src_lo) | RegisterSet(Rtemp)); @@ -2672,10 +2672,10 @@ void LIR_Assembler::volatile_move_op(LIR_Opr src, LIR_Opr dest, BasicType type, assert(addr->index()->is_illegal() && addr->disp() == 0, "The address is simple already"); null_check_offset = __ offset(); - if (dest_lo < dest_hi) { + if (dest_lo->encoding() < dest_hi->encoding()) { __ ldmia(addr->base()->as_register(), RegisterSet(dest_lo) | RegisterSet(dest_hi)); } else { - assert(dest_lo < Rtemp, "Rtemp is higher than any allocatable register"); + assert(dest_lo->encoding() < Rtemp->encoding(), "Rtemp is higher than any allocatable register"); __ ldmia(addr->base()->as_register(), RegisterSet(dest_lo) | RegisterSet(Rtemp)); __ mov(dest_hi, Rtemp); } diff --git a/src/hotspot/cpu/arm/interp_masm_arm.cpp b/src/hotspot/cpu/arm/interp_masm_arm.cpp index e9e6187a6d18..69f3f726ab2d 100644 --- a/src/hotspot/cpu/arm/interp_masm_arm.cpp +++ b/src/hotspot/cpu/arm/interp_masm_arm.cpp @@ -409,7 +409,7 @@ void InterpreterMacroAssembler::pop_i(Register r) { void InterpreterMacroAssembler::pop_l(Register lo, Register hi) { assert_different_registers(lo, hi); - assert(lo < hi, "lo must be < hi"); + assert(lo->encoding() < hi->encoding(), "lo must be < hi"); pop(RegisterSet(lo) | RegisterSet(hi)); } @@ -459,7 +459,7 @@ void InterpreterMacroAssembler::push_i(Register r) { void InterpreterMacroAssembler::push_l(Register lo, Register hi) { assert_different_registers(lo, hi); - assert(lo < hi, "lo must be < hi"); + assert(lo->encoding() < hi->encoding(), "lo must be < hi"); push(RegisterSet(lo) | RegisterSet(hi)); } diff --git a/src/hotspot/cpu/arm/register_arm.cpp b/src/hotspot/cpu/arm/register_arm.cpp index ea3ef87e6708..296c55e2e164 100644 --- a/src/hotspot/cpu/arm/register_arm.cpp +++ b/src/hotspot/cpu/arm/register_arm.cpp @@ -25,12 +25,19 @@ #include "register_arm.hpp" #include "utilities/debug.hpp" -const int ConcreteRegisterImpl::max_gpr = ConcreteRegisterImpl::num_gpr; -const int ConcreteRegisterImpl::max_fpr = ConcreteRegisterImpl::num_fpr + - ConcreteRegisterImpl::max_gpr; +Register::RegisterImpl all_RegisterImpls [Register::number_of_registers + 1]; +FloatRegister::FloatRegisterImpl all_FloatRegisterImpls [FloatRegister::number_of_registers + 1]; +VFPSystemRegister::VFPSystemRegisterImpl all_VFPSystemRegisterImpls [VFPSystemRegister::number_of_registers + 1] { + { -1 }, //vfpsnoreg + { VFPSystemRegister::FPSID }, + { VFPSystemRegister::FPSCR }, + { VFPSystemRegister::MVFR0 }, + { VFPSystemRegister::MVFR1 } +}; -const char* RegisterImpl::name() const { - const char* names[number_of_registers] = { +const char* Register::RegisterImpl::name() const { + static const char* names[number_of_registers + 1] = { + "noreg", "r0", "r1", "r2", "r3", "r4", "r5", "r6", #if (FP_REG_NUM == 7) "fp", @@ -45,13 +52,14 @@ const char* RegisterImpl::name() const { #endif "r12", "sp", "lr", "pc" }; - return is_valid() ? names[encoding()] : "noreg"; + return names[encoding() + 1]; } -const char* FloatRegisterImpl::name() const { - const char* names[number_of_registers] = { - "s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7", - "s8", "s9", "s10", "s11", "s12", "s13", "s14", "s15", +const char* FloatRegister::FloatRegisterImpl::name() const { + static const char* names[number_of_registers + 1] = { + "fnoreg", + "s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7", + "s8", "s9", "s10", "s11", "s12", "s13", "s14", "s15", "s16", "s17", "s18", "s19", "s20", "s21", "s22", "s23", "s24", "s25", "s26", "s27", "s28", "s29", "s30", "s31" #ifdef COMPILER2 @@ -61,5 +69,5 @@ const char* FloatRegisterImpl::name() const { "s56", "s57?","s58", "s59?","s60", "s61?","s62", "s63?" #endif }; - return is_valid() ? names[encoding()] : "fnoreg"; + return names[encoding() + 1]; } diff --git a/src/hotspot/cpu/arm/register_arm.hpp b/src/hotspot/cpu/arm/register_arm.hpp index fca23d07fee5..b7286bb68a45 100644 --- a/src/hotspot/cpu/arm/register_arm.hpp +++ b/src/hotspot/cpu/arm/register_arm.hpp @@ -31,26 +31,6 @@ class VMRegImpl; typedef VMRegImpl* VMReg; -// These are declared ucontext.h -#undef R0 -#undef R1 -#undef R2 -#undef R3 -#undef R4 -#undef R5 -#undef R6 -#undef R7 -#undef R8 -#undef R9 -#undef R10 -#undef R11 -#undef R12 -#undef R13 -#undef R14 -#undef R15 - -#define R(r) ((Register)(r)) - ///////////////////////////////// // Support for different ARM ABIs // Note: default ABI is for linux @@ -94,25 +74,86 @@ typedef VMRegImpl* VMReg; #define ALIGN_WIDE_ARGUMENTS 1 #endif -#define R0 ((Register)0) -#define R1 ((Register)1) -#define R2 ((Register)2) -#define R3 ((Register)3) -#define R4 ((Register)4) -#define R5 ((Register)5) -#define R6 ((Register)6) -#define R7 ((Register)7) -#define R8 ((Register)8) -#define R9 ((Register)9) -#define R10 ((Register)10) -#define R11 ((Register)11) -#define R12 ((Register)12) -#define R13 ((Register)13) -#define R14 ((Register)14) -#define R15 ((Register)15) - - -#define FP ((Register)FP_REG_NUM) +class Register { + private: + int _encoding; + + constexpr explicit Register(int encoding) : _encoding(encoding) {} + + public: + enum { + number_of_registers = 16, + max_slots_per_register = 1 + }; + + class RegisterImpl : public AbstractRegisterImpl { + friend class Register; + + static constexpr const RegisterImpl* first(); + + public: + + // accessors and testers + int raw_encoding() const { return this - first(); } + int encoding() const { assert(is_valid(), "invalid register"); return raw_encoding(); } + bool is_valid() const { return 0 <= raw_encoding() && raw_encoding() < number_of_registers; } + + inline Register successor() const; + + VMReg as_VMReg() const; + + const char* name() const; + }; + + + inline friend constexpr Register as_Register(int encoding); + + constexpr Register() : _encoding(-1) {} //noreg + + int operator==(const Register r) const { return _encoding == r._encoding; } + int operator!=(const Register r) const { return _encoding != r._encoding; } + + const RegisterImpl* operator->() const { return RegisterImpl::first() + _encoding; } +}; + +extern Register::RegisterImpl all_RegisterImpls[Register::number_of_registers + 1] INTERNAL_VISIBILITY; + +inline constexpr const Register::RegisterImpl* Register::RegisterImpl::first() { + return all_RegisterImpls + 1; +} + +constexpr Register noreg = Register(); + +inline constexpr Register as_Register(int encoding) { + if (0 <= encoding && encoding < Register::number_of_registers) { + return Register(encoding); + } + return noreg; +} + +inline Register Register::RegisterImpl::successor() const { + assert(is_valid(), "sainty"); + return as_Register(encoding() + 1); +} + +constexpr Register R0 = as_Register( 0); +constexpr Register R1 = as_Register( 1); +constexpr Register R2 = as_Register( 2); +constexpr Register R3 = as_Register( 3); +constexpr Register R4 = as_Register( 4); +constexpr Register R5 = as_Register( 5); +constexpr Register R6 = as_Register( 6); +constexpr Register R7 = as_Register( 7); +constexpr Register R8 = as_Register( 8); +constexpr Register R9 = as_Register( 9); +constexpr Register R10 = as_Register(10); +constexpr Register R11 = as_Register(11); +constexpr Register R12 = as_Register(12); +constexpr Register R13 = as_Register(13); +constexpr Register R14 = as_Register(14); +constexpr Register R15 = as_Register(15); + +constexpr Register FP = as_Register(FP_REG_NUM); // Safe use of registers which may be FP on some platforms. // @@ -122,185 +163,170 @@ typedef VMRegImpl* VMReg; // as FP on supported ABIs (and replace R# by altFP_#_11). altFP_#_11 // must be #define to R11 if and only if # is FP_REG_NUM. #if (FP_REG_NUM == 7) -#define altFP_7_11 ((Register)11) +constexpr Register altFP_7_11 = R11; #else -#define altFP_7_11 ((Register)7) +constexpr Register altFP_7_11 = R7; #endif -#define SP R13 -#define LR R14 -#define PC R15 +constexpr Register SP = R13; +constexpr Register LR = R14; +constexpr Register PC = R15; -class RegisterImpl; -typedef RegisterImpl* Register; +class FloatRegister { + private: + int _encoding; -inline Register as_Register(int encoding) { - return (Register)(intptr_t)encoding; -} + constexpr explicit FloatRegister(int encoding) : _encoding(encoding) {} -class RegisterImpl : public AbstractRegisterImpl { public: enum { - number_of_registers = 16 + number_of_registers = NOT_COMPILER2(32) COMPILER2_PRESENT(64), + max_slots_per_register = 1 }; - Register successor() const { return as_Register(encoding() + 1); } + class FloatRegisterImpl : public AbstractRegisterImpl { + friend class FloatRegister; - inline friend Register as_Register(int encoding); + static constexpr const FloatRegisterImpl* first(); - VMReg as_VMReg(); + public: - // accessors - int encoding() const { assert(is_valid(), "invalid register"); return value(); } - const char* name() const; + // accessors and testers + int raw_encoding() const { return this - first(); } + int encoding() const { assert(is_valid(), "invalid register"); return raw_encoding(); } + bool is_valid() const { return 0 <= raw_encoding() && raw_encoding() < number_of_registers; } + inline FloatRegister successor() const; - // testers - bool is_valid() const { return 0 <= value() && value() < number_of_registers; } - -}; + VMReg as_VMReg() const; -CONSTANT_REGISTER_DECLARATION(Register, noreg, (-1)); + int hi_bits() const { + return (encoding() >> 1) & 0xf; + } + int lo_bit() const { + return encoding() & 1; + } -// Use FloatRegister as shortcut -class FloatRegisterImpl; -typedef FloatRegisterImpl* FloatRegister; + int hi_bit() const { + return encoding() >> 5; + } -inline FloatRegister as_FloatRegister(int encoding) { - return (FloatRegister)(intptr_t)encoding; -} - -class FloatRegisterImpl : public AbstractRegisterImpl { - public: - enum { - number_of_registers = NOT_COMPILER2(32) COMPILER2_PRESENT(64) + const char* name() const; }; - inline friend FloatRegister as_FloatRegister(int encoding); + inline friend constexpr FloatRegister as_FloatRegister(int encoding); - VMReg as_VMReg(); + constexpr FloatRegister() : _encoding(-1) {} // fnoreg - int encoding() const { assert(is_valid(), "invalid register"); return value(); } - bool is_valid() const { return 0 <= (intx)this && (intx)this < number_of_registers; } - FloatRegister successor() const { return as_FloatRegister(encoding() + 1); } + int operator==(const FloatRegister r) const { return _encoding == r._encoding; } + int operator!=(const FloatRegister r) const { return _encoding != r._encoding; } - const char* name() const; + const FloatRegisterImpl* operator->() const { return FloatRegisterImpl::first() + _encoding; } +}; - int hi_bits() const { - return (encoding() >> 1) & 0xf; - } +extern FloatRegister::FloatRegisterImpl all_FloatRegisterImpls[FloatRegister::number_of_registers + 1] INTERNAL_VISIBILITY; - int lo_bit() const { - return encoding() & 1; - } +inline constexpr const FloatRegister::FloatRegisterImpl* FloatRegister::FloatRegisterImpl::first() { + return all_FloatRegisterImpls + 1; +} + +constexpr FloatRegister fnoreg = FloatRegister(); - int hi_bit() const { - return encoding() >> 5; +inline constexpr FloatRegister as_FloatRegister(int encoding) { + if (0 <= encoding && encoding < FloatRegister::number_of_registers) { + return FloatRegister(encoding); } -}; + return fnoreg; +} -CONSTANT_REGISTER_DECLARATION(FloatRegister, fnoreg, (-1)); +inline FloatRegister FloatRegister::FloatRegisterImpl::successor() const { + assert(is_valid(), "sainty"); + return as_FloatRegister(encoding() + 1); +} /* * S1-S6 are named with "_reg" suffix to avoid conflict with * constants defined in sharedRuntimeTrig.cpp */ -CONSTANT_REGISTER_DECLARATION(FloatRegister, S0, ( 0)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S1_reg, ( 1)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S2_reg, ( 2)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S3_reg, ( 3)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S4_reg, ( 4)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S5_reg, ( 5)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S6_reg, ( 6)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S7, ( 7)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S8, ( 8)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S9, ( 9)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S10, (10)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S11, (11)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S12, (12)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S13, (13)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S14, (14)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S15, (15)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S16, (16)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S17, (17)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S18, (18)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S19, (19)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S20, (20)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S21, (21)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S22, (22)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S23, (23)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S24, (24)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S25, (25)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S26, (26)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S27, (27)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S28, (28)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S29, (29)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S30, (30)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, S31, (31)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, Stemp, (30)); - -CONSTANT_REGISTER_DECLARATION(FloatRegister, D0, ( 0)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D1, ( 2)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D2, ( 4)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D3, ( 6)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D4, ( 8)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D5, ( 10)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D6, ( 12)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D7, ( 14)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D8, ( 16)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D9, ( 18)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D10, ( 20)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D11, ( 22)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D12, ( 24)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D13, ( 26)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D14, ( 28)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D15, (30)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D16, (32)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D17, (34)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D18, (36)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D19, (38)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D20, (40)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D21, (42)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D22, (44)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D23, (46)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D24, (48)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D25, (50)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D26, (52)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D27, (54)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D28, (56)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D29, (58)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D30, (60)); -CONSTANT_REGISTER_DECLARATION(FloatRegister, D31, (62)); +constexpr FloatRegister S0 = as_FloatRegister( 0); +constexpr FloatRegister S1_reg = as_FloatRegister(1); +constexpr FloatRegister S2_reg = as_FloatRegister(2); +constexpr FloatRegister S3_reg = as_FloatRegister(3); +constexpr FloatRegister S4_reg = as_FloatRegister(4); +constexpr FloatRegister S5_reg = as_FloatRegister(5); +constexpr FloatRegister S6_reg = as_FloatRegister(6); +constexpr FloatRegister S7 = as_FloatRegister( 7); +constexpr FloatRegister S8 = as_FloatRegister( 8); +constexpr FloatRegister S9 = as_FloatRegister( 9); +constexpr FloatRegister S10 = as_FloatRegister(10); +constexpr FloatRegister S11 = as_FloatRegister(11); +constexpr FloatRegister S12 = as_FloatRegister(12); +constexpr FloatRegister S13 = as_FloatRegister(13); +constexpr FloatRegister S14 = as_FloatRegister(14); +constexpr FloatRegister S15 = as_FloatRegister(15); +constexpr FloatRegister S16 = as_FloatRegister(16); +constexpr FloatRegister S17 = as_FloatRegister(17); +constexpr FloatRegister S18 = as_FloatRegister(18); +constexpr FloatRegister S19 = as_FloatRegister(19); +constexpr FloatRegister S20 = as_FloatRegister(20); +constexpr FloatRegister S21 = as_FloatRegister(21); +constexpr FloatRegister S22 = as_FloatRegister(22); +constexpr FloatRegister S23 = as_FloatRegister(23); +constexpr FloatRegister S24 = as_FloatRegister(24); +constexpr FloatRegister S25 = as_FloatRegister(25); +constexpr FloatRegister S26 = as_FloatRegister(26); +constexpr FloatRegister S27 = as_FloatRegister(27); +constexpr FloatRegister S28 = as_FloatRegister(28); +constexpr FloatRegister S29 = as_FloatRegister(29); +constexpr FloatRegister S30 = as_FloatRegister(30); +constexpr FloatRegister S31 = as_FloatRegister(31); +constexpr FloatRegister Stemp = S30; + +constexpr FloatRegister D0 = as_FloatRegister( 0); +constexpr FloatRegister D1 = as_FloatRegister( 2); +constexpr FloatRegister D2 = as_FloatRegister( 4); +constexpr FloatRegister D3 = as_FloatRegister( 6); +constexpr FloatRegister D4 = as_FloatRegister( 8); +constexpr FloatRegister D5 = as_FloatRegister(10); +constexpr FloatRegister D6 = as_FloatRegister(12); +constexpr FloatRegister D7 = as_FloatRegister(14); +constexpr FloatRegister D8 = as_FloatRegister(16); +constexpr FloatRegister D9 = as_FloatRegister(18); +constexpr FloatRegister D10 = as_FloatRegister(20); +constexpr FloatRegister D11 = as_FloatRegister(22); +constexpr FloatRegister D12 = as_FloatRegister(24); +constexpr FloatRegister D13 = as_FloatRegister(26); +constexpr FloatRegister D14 = as_FloatRegister(28); +constexpr FloatRegister D15 = as_FloatRegister(30); +constexpr FloatRegister D16 = as_FloatRegister(32); +constexpr FloatRegister D17 = as_FloatRegister(34); +constexpr FloatRegister D18 = as_FloatRegister(36); +constexpr FloatRegister D19 = as_FloatRegister(38); +constexpr FloatRegister D20 = as_FloatRegister(40); +constexpr FloatRegister D21 = as_FloatRegister(42); +constexpr FloatRegister D22 = as_FloatRegister(44); +constexpr FloatRegister D23 = as_FloatRegister(46); +constexpr FloatRegister D24 = as_FloatRegister(48); +constexpr FloatRegister D25 = as_FloatRegister(50); +constexpr FloatRegister D26 = as_FloatRegister(52); +constexpr FloatRegister D27 = as_FloatRegister(54); +constexpr FloatRegister D28 = as_FloatRegister(56); +constexpr FloatRegister D29 = as_FloatRegister(58); +constexpr FloatRegister D30 = as_FloatRegister(60); +constexpr FloatRegister D31 = as_FloatRegister(62); class ConcreteRegisterImpl : public AbstractRegisterImpl { public: enum { - log_vmregs_per_word = LogBytesPerWord - LogBytesPerInt, // VMRegs are of 4-byte size -#ifdef COMPILER2 - log_bytes_per_fpr = 2, // quad vectors -#else - log_bytes_per_fpr = 2, // double vectors -#endif - log_words_per_fpr = log_bytes_per_fpr - LogBytesPerWord, - words_per_fpr = 1 << log_words_per_fpr, - log_vmregs_per_fpr = log_bytes_per_fpr - LogBytesPerInt, - log_vmregs_per_gpr = log_vmregs_per_word, - vmregs_per_gpr = 1 << log_vmregs_per_gpr, - vmregs_per_fpr = 1 << log_vmregs_per_fpr, - - num_gpr = RegisterImpl::number_of_registers << log_vmregs_per_gpr, - max_gpr0 = num_gpr, - num_fpr = FloatRegisterImpl::number_of_registers << log_vmregs_per_fpr, - max_fpr0 = max_gpr0 + num_fpr, - number_of_registers = num_gpr + num_fpr + 1+1 // APSR and FPSCR so that c2's REG_COUNT <= ConcreteRegisterImpl::number_of_registers - }; + max_gpr = Register::number_of_registers * Register::max_slots_per_register, + max_fpr = max_gpr + FloatRegister::number_of_registers * FloatRegister::max_slots_per_register, - static const int max_gpr; - static const int max_fpr; + number_of_registers = max_fpr + 1+1 // APSR and FPSCR so that c2's REG_COUNT <= ConcreteRegisterImpl::number_of_registers + }; }; typedef AbstractRegSet RegSet; @@ -328,101 +354,157 @@ inline FloatRegister AbstractRegSet::last() { -class VFPSystemRegisterImpl; -typedef VFPSystemRegisterImpl* VFPSystemRegister; -class VFPSystemRegisterImpl : public AbstractRegisterImpl { +class VFPSystemRegister { + private: + int _store_idx; + + constexpr explicit VFPSystemRegister(int store_idx) : _store_idx(store_idx) {} + + enum { + _FPSID_store_idx = 0, + _FPSCR_store_idx = 1, + _MVFR0_store_idx = 2, + _MVFR1_store_idx = 3 + }; + public: - int encoding() const { return value(); } + enum { + FPSID = 0, + FPSCR = 1, + MVFR0 = 6, + MVFR1 = 7, + number_of_registers = 4 + }; + + class VFPSystemRegisterImpl : public AbstractRegisterImpl { + friend class VFPSystemRegister; + + int _encoding; + + static constexpr const VFPSystemRegisterImpl* first(); + + public: + constexpr VFPSystemRegisterImpl(int encoding) : _encoding(encoding) {} + + int encoding() const { return _encoding; } + }; + + inline friend constexpr VFPSystemRegister as_VFPSystemRegister(int encoding); + + constexpr VFPSystemRegister() : _store_idx(-1) {} // vfpsnoreg + + int operator==(const VFPSystemRegister r) const { return _store_idx == r._store_idx; } + int operator!=(const VFPSystemRegister r) const { return _store_idx != r._store_idx; } + + const VFPSystemRegisterImpl* operator->() const { return VFPSystemRegisterImpl::first() + _store_idx; } }; -#define FPSID ((VFPSystemRegister)0) -#define FPSCR ((VFPSystemRegister)1) -#define MVFR0 ((VFPSystemRegister)0x6) -#define MVFR1 ((VFPSystemRegister)0x7) +extern VFPSystemRegister::VFPSystemRegisterImpl all_VFPSystemRegisterImpls[VFPSystemRegister::number_of_registers + 1] INTERNAL_VISIBILITY; + +inline constexpr const VFPSystemRegister::VFPSystemRegisterImpl* VFPSystemRegister::VFPSystemRegisterImpl::first() { + return all_VFPSystemRegisterImpls + 1; +} + +constexpr VFPSystemRegister vfpsnoreg = VFPSystemRegister(); + +inline constexpr VFPSystemRegister as_VFPSystemRegister(int encoding) { + switch (encoding) { + case VFPSystemRegister::FPSID: return VFPSystemRegister(VFPSystemRegister::_FPSID_store_idx); + case VFPSystemRegister::FPSCR: return VFPSystemRegister(VFPSystemRegister::_FPSCR_store_idx); + case VFPSystemRegister::MVFR0: return VFPSystemRegister(VFPSystemRegister::_MVFR0_store_idx); + case VFPSystemRegister::MVFR1: return VFPSystemRegister(VFPSystemRegister::_MVFR1_store_idx); + default: return vfpsnoreg; + } +} + +constexpr VFPSystemRegister FPSID = as_VFPSystemRegister(VFPSystemRegister::FPSID); +constexpr VFPSystemRegister FPSCR = as_VFPSystemRegister(VFPSystemRegister::FPSCR); +constexpr VFPSystemRegister MVFR0 = as_VFPSystemRegister(VFPSystemRegister::MVFR0); +constexpr VFPSystemRegister MVFR1 = as_VFPSystemRegister(VFPSystemRegister::MVFR1); /* * Register definitions shared across interpreter and compiler */ -#define Rexception_obj R4 -#define Rexception_pc R5 +constexpr Register Rexception_obj = R4; +constexpr Register Rexception_pc = R5; /* * Interpreter register definitions common to C++ and template interpreters. */ -#define Rlocals R8 -#define Rmethod R9 -#define Rthread R10 -#define Rtemp R12 +constexpr Register Rlocals = R8; +constexpr Register Rmethod = R9; +constexpr Register Rthread = R10; +constexpr Register Rtemp = R12; // Interpreter calling conventions -#define Rparams SP -#define Rsender_sp R4 +constexpr Register Rparams = SP; +constexpr Register Rsender_sp = R4; // JSR292 // Note: R5_mh is needed only during the call setup, including adapters // This does not seem to conflict with Rexception_pc // In case of issues, R3 might be OK but adapters calling the runtime would have to save it -#define R5_mh R5 // MethodHandle register, used during the call setup -#define Rmh_SP_save FP // for C1 +constexpr Register R5_mh = R5; // MethodHandle register, used during the call setup +constexpr Register Rmh_SP_save = FP; // for C1 /* * C++ Interpreter Register Defines */ -#define Rsave0 R4 -#define Rsave1 R5 -#define Rsave2 R6 -#define Rstate altFP_7_11 // R7 or R11 -#define Ricklass R8 +constexpr Register Rsave0 = R4; +constexpr Register Rsave1 = R5; +constexpr Register Rsave2 = R6; +constexpr Register Rstate = altFP_7_11; // R7 or R11 +constexpr Register Ricklass = R8; /* * TemplateTable Interpreter Register Usage */ // Temporary registers -#define R0_tmp R0 -#define R1_tmp R1 -#define R2_tmp R2 -#define R3_tmp R3 -#define R4_tmp R4 -#define R5_tmp R5 -#define R12_tmp R12 -#define LR_tmp LR +constexpr Register R0_tmp = R0; +constexpr Register R1_tmp = R1; +constexpr Register R2_tmp = R2; +constexpr Register R3_tmp = R3; +constexpr Register R4_tmp = R4; +constexpr Register R5_tmp = R5; +constexpr Register R12_tmp = R12; +constexpr Register LR_tmp = LR; -#define S0_tmp S0 -#define S1_tmp S1_reg +constexpr FloatRegister S0_tmp = S0; +constexpr FloatRegister S1_tmp = S1_reg; -#define D0_tmp D0 -#define D1_tmp D1 +constexpr FloatRegister D0_tmp = D0; +constexpr FloatRegister D1_tmp = D1; // Temporary registers saved across VM calls (according to C calling conventions) -#define Rtmp_save0 R4 -#define Rtmp_save1 R5 +constexpr Register Rtmp_save0 = R4; +constexpr Register Rtmp_save1 = R5; // Cached TOS value -#define R0_tos R0 +constexpr Register R0_tos = R0; -#define R0_tos_lo R0 -#define R1_tos_hi R1 +constexpr Register R0_tos_lo = R0; +constexpr Register R1_tos_hi = R1; -#define S0_tos S0 -#define D0_tos D0 +constexpr FloatRegister S0_tos = S0; +constexpr FloatRegister D0_tos = D0; // Dispatch table -#define RdispatchTable R6 +constexpr Register RdispatchTable = R6; // Bytecode pointer -#define Rbcp altFP_7_11 +constexpr Register Rbcp = altFP_7_11; // Pre-loaded next bytecode for the dispatch -#define R3_bytecode R3 +constexpr Register R3_bytecode = R3; // Conventions between bytecode templates and stubs -#define R2_ClassCastException_obj R2 -#define R4_ArrayIndexOutOfBounds_index R4 +constexpr Register R2_ClassCastException_obj = R2; +constexpr Register R4_ArrayIndexOutOfBounds_index = R4; // Interpreter expression stack top -#define Rstack_top SP +constexpr Register Rstack_top = SP; /* * Linux 32-bit ARM C ABI Register calling conventions @@ -445,10 +527,11 @@ class VFPSystemRegisterImpl : public AbstractRegisterImpl { * R14 (LR) Link register * R15 (PC) Program Counter */ -#define c_rarg0 R0 -#define c_rarg1 R1 -#define c_rarg2 R2 -#define c_rarg3 R3 + +constexpr Register c_rarg0 = R0; +constexpr Register c_rarg1 = R1; +constexpr Register c_rarg2 = R2; +constexpr Register c_rarg3 = R3; #define GPR_PARAMS 4 @@ -456,10 +539,10 @@ class VFPSystemRegisterImpl : public AbstractRegisterImpl { // Java ABI // XXX Is this correct? -#define j_rarg0 c_rarg0 -#define j_rarg1 c_rarg1 -#define j_rarg2 c_rarg2 -#define j_rarg3 c_rarg3 +constexpr Register j_rarg0 = c_rarg0; +constexpr Register j_rarg1 = c_rarg1; +constexpr Register j_rarg2 = c_rarg2; +constexpr Register j_rarg3 = c_rarg3; #endif // CPU_ARM_REGISTER_ARM_HPP diff --git a/src/hotspot/cpu/arm/sharedRuntime_arm.cpp b/src/hotspot/cpu/arm/sharedRuntime_arm.cpp index 8ba847e7e328..2fc317cbb286 100644 --- a/src/hotspot/cpu/arm/sharedRuntime_arm.cpp +++ b/src/hotspot/cpu/arm/sharedRuntime_arm.cpp @@ -70,7 +70,7 @@ class RegisterSaver { enum RegisterLayout { - fpu_save_size = FloatRegisterImpl::number_of_registers, + fpu_save_size = FloatRegister::number_of_registers, #ifndef __SOFTFP__ D0_offset = 0, #endif @@ -139,8 +139,8 @@ OopMap* RegisterSaver::save_live_registers(MacroAssembler* masm, if (VM_Version::has_vfp3_32()) { __ fpush(FloatRegisterSet(D16, 16)); } else { - if (FloatRegisterImpl::number_of_registers > 32) { - assert(FloatRegisterImpl::number_of_registers == 64, "nb fp registers should be 64"); + if (FloatRegister::number_of_registers > 32) { + assert(FloatRegister::number_of_registers == 64, "nb fp registers should be 64"); __ sub(SP, SP, 32 * wordSize); } } @@ -182,8 +182,8 @@ void RegisterSaver::restore_live_registers(MacroAssembler* masm, bool restore_lr if (VM_Version::has_vfp3_32()) { __ fpop(FloatRegisterSet(D16, 16)); } else { - if (FloatRegisterImpl::number_of_registers > 32) { - assert(FloatRegisterImpl::number_of_registers == 64, "nb fp registers should be 64"); + if (FloatRegister::number_of_registers > 32) { + assert(FloatRegister::number_of_registers == 64, "nb fp registers should be 64"); __ add(SP, SP, 32 * wordSize); } } diff --git a/src/hotspot/cpu/arm/vmreg_arm.cpp b/src/hotspot/cpu/arm/vmreg_arm.cpp index 4ce1dd0be20f..efaf38ef7297 100644 --- a/src/hotspot/cpu/arm/vmreg_arm.cpp +++ b/src/hotspot/cpu/arm/vmreg_arm.cpp @@ -30,14 +30,14 @@ void VMRegImpl::set_regName() { Register reg = ::as_Register(0); int i; for (i = 0; i < ConcreteRegisterImpl::max_gpr; reg = reg->successor()) { - for (int j = 0; j < (1 << ConcreteRegisterImpl::log_vmregs_per_gpr); j++) { + for (int j = 0; j < Register::max_slots_per_register; j++) { regName[i++] = reg->name(); } } #ifndef __SOFTFP__ FloatRegister freg = ::as_FloatRegister(0); for ( ; i < ConcreteRegisterImpl::max_fpr ; ) { - for (int j = 0; j < (1 << ConcreteRegisterImpl::log_vmregs_per_fpr); j++) { + for (int j = 0; j < Register::max_slots_per_register; j++) { regName[i++] = freg->name(); } freg = freg->successor(); diff --git a/src/hotspot/cpu/arm/vmreg_arm.hpp b/src/hotspot/cpu/arm/vmreg_arm.hpp index c13f443b804f..f1dfd09a1e69 100644 --- a/src/hotspot/cpu/arm/vmreg_arm.hpp +++ b/src/hotspot/cpu/arm/vmreg_arm.hpp @@ -36,20 +36,20 @@ inline Register as_Register() { assert(is_Register(), "must be"); assert(is_concrete(), "concrete register expected"); - return ::as_Register(value() >> ConcreteRegisterImpl::log_vmregs_per_gpr); + return ::as_Register(value() / Register::max_slots_per_register); } inline FloatRegister as_FloatRegister() { assert(is_FloatRegister(), "must be"); assert(is_concrete(), "concrete register expected"); - return ::as_FloatRegister((value() - ConcreteRegisterImpl::max_gpr) >> ConcreteRegisterImpl::log_vmregs_per_fpr); + return ::as_FloatRegister((value() - ConcreteRegisterImpl::max_gpr) / FloatRegister::max_slots_per_register); } inline bool is_concrete() { if (is_Register()) { - return ((value() & right_n_bits(ConcreteRegisterImpl::log_vmregs_per_gpr)) == 0); + return (value() % Register::max_slots_per_register == 0); } else if (is_FloatRegister()) { - return (((value() - ConcreteRegisterImpl::max_gpr) & right_n_bits(ConcreteRegisterImpl::log_vmregs_per_fpr)) == 0); + return (value() % FloatRegister::max_slots_per_register == 0); // Single slot } else { return false; } diff --git a/src/hotspot/cpu/arm/vmreg_arm.inline.hpp b/src/hotspot/cpu/arm/vmreg_arm.inline.hpp index f122b9ede701..3e5c18dbda0a 100644 --- a/src/hotspot/cpu/arm/vmreg_arm.inline.hpp +++ b/src/hotspot/cpu/arm/vmreg_arm.inline.hpp @@ -25,11 +25,11 @@ #ifndef CPU_ARM_VMREG_ARM_INLINE_HPP #define CPU_ARM_VMREG_ARM_INLINE_HPP -inline VMReg RegisterImpl::as_VMReg() { - return VMRegImpl::as_VMReg(encoding() << ConcreteRegisterImpl::log_vmregs_per_gpr); +inline VMReg Register::RegisterImpl::as_VMReg() const { + return VMRegImpl::as_VMReg(encoding() * Register::max_slots_per_register); } -inline VMReg FloatRegisterImpl::as_VMReg() { - return VMRegImpl::as_VMReg((encoding() << ConcreteRegisterImpl::log_vmregs_per_fpr) + ConcreteRegisterImpl::max_gpr); +inline VMReg FloatRegister::FloatRegisterImpl::as_VMReg() const { + return VMRegImpl::as_VMReg((encoding() * FloatRegister::max_slots_per_register) + ConcreteRegisterImpl::max_gpr); } #endif // CPU_ARM_VMREG_ARM_INLINE_HPP diff --git a/src/hotspot/os_cpu/linux_arm/macroAssembler_linux_arm_32.cpp b/src/hotspot/os_cpu/linux_arm/macroAssembler_linux_arm_32.cpp index e74daaa6d666..e4737191cfcd 100644 --- a/src/hotspot/os_cpu/linux_arm/macroAssembler_linux_arm_32.cpp +++ b/src/hotspot/os_cpu/linux_arm/macroAssembler_linux_arm_32.cpp @@ -246,9 +246,9 @@ void MacroAssembler::atomic_cas64(Register memval_lo, Register memval_hi, Regist Label loop; assert_different_registers(memval_lo, memval_hi, result, oldval_lo, oldval_hi, newval_lo, newval_hi, base); - assert(memval_hi == memval_lo + 1 && memval_lo < R9, "cmpxchg_long: illegal registers"); - assert(oldval_hi == oldval_lo + 1 && oldval_lo < R9, "cmpxchg_long: illegal registers"); - assert(newval_hi == newval_lo + 1 && newval_lo < R9, "cmpxchg_long: illegal registers"); + assert(memval_hi == as_Register(memval_lo->encoding() + 1) && memval_lo->encoding() < R9->encoding(), "cmpxchg_long: illegal registers"); + assert(oldval_hi == as_Register(oldval_lo->encoding() + 1) && oldval_lo->encoding() < R9->encoding(), "cmpxchg_long: illegal registers"); + assert(newval_hi == as_Register(newval_lo->encoding() + 1) && newval_lo->encoding() < R9->encoding(), "cmpxchg_long: illegal registers"); assert(result != R10, "cmpxchg_long: illegal registers"); assert(base != R10, "cmpxchg_long: illegal registers"); From 5a7d56ae0638e9be999690df8e64aa44360f9e8e Mon Sep 17 00:00:00 2001 From: Thomas Stuefe Date: Tue, 24 Mar 2026 13:14:13 +0000 Subject: [PATCH 075/168] 8380011: Path-to-gcroots search should not trigger stack overflows Backport-of: 347aae6428358e79a9463b04654f3eaf83450595 --- .../jfr/leakprofiler/chains/dfsClosure.cpp | 23 +++- .../jfr/leakprofiler/chains/dfsClosure.hpp | 3 + test/jdk/TEST.groups | 1 + .../jdk/jfr/event/oldobject/OldObjects.java | 14 ++- .../oldobject/TestDFSWithSmallStack.java | 101 ++++++++++++++++++ 5 files changed, 139 insertions(+), 3 deletions(-) create mode 100644 test/jdk/jdk/jfr/event/oldobject/TestDFSWithSmallStack.java diff --git a/src/hotspot/share/jfr/leakprofiler/chains/dfsClosure.cpp b/src/hotspot/share/jfr/leakprofiler/chains/dfsClosure.cpp index 83eee96091e0..8b5819e92c40 100644 --- a/src/hotspot/share/jfr/leakprofiler/chains/dfsClosure.cpp +++ b/src/hotspot/share/jfr/leakprofiler/chains/dfsClosure.cpp @@ -34,6 +34,7 @@ #include "memory/resourceArea.hpp" #include "oops/access.inline.hpp" #include "oops/oop.inline.hpp" +#include "runtime/os.hpp" #include "utilities/align.hpp" UnifiedOopRef DFSClosure::_reference_stack[max_dfs_depth]; @@ -67,9 +68,27 @@ void DFSClosure::find_leaks_from_root_set(EdgeStore* edge_store, rs.process(); } +static address calculate_headroom_limit() { + static constexpr size_t required_headroom = K * 64; + const Thread* const t = Thread::current_or_null(); + return t->stack_end() + required_headroom; +} + DFSClosure::DFSClosure(EdgeStore* edge_store, JFRBitSet* mark_bits, const Edge* start_edge) :_edge_store(edge_store), _mark_bits(mark_bits), _start_edge(start_edge), - _max_depth(max_dfs_depth), _depth(0), _ignore_root_set(false) { + _max_depth(max_dfs_depth), _depth(0), _ignore_root_set(false), + _headroom_limit(calculate_headroom_limit()) { +} + +bool DFSClosure::have_headroom() const { + const address sp = (address) os::current_stack_pointer(); +#ifdef ASSERT + const Thread* const t = Thread::current_or_null(); + assert(t->is_VM_thread(), "invariant"); + assert(t->is_in_full_stack(_headroom_limit), "invariant"); + assert(t->is_in_full_stack(sp), "invariant"); +#endif + return sp > _headroom_limit; } void DFSClosure::closure_impl(UnifiedOopRef reference, const oop pointee) { @@ -97,7 +116,7 @@ void DFSClosure::closure_impl(UnifiedOopRef reference, const oop pointee) { } } assert(_max_depth >= 1, "invariant"); - if (_depth < _max_depth - 1) { + if (_depth < _max_depth - 1 && have_headroom()) { _depth++; pointee->oop_iterate(this); assert(_depth > 0, "invariant"); diff --git a/src/hotspot/share/jfr/leakprofiler/chains/dfsClosure.hpp b/src/hotspot/share/jfr/leakprofiler/chains/dfsClosure.hpp index be0cd2a5d7eb..a22b5137380f 100644 --- a/src/hotspot/share/jfr/leakprofiler/chains/dfsClosure.hpp +++ b/src/hotspot/share/jfr/leakprofiler/chains/dfsClosure.hpp @@ -46,12 +46,15 @@ class DFSClosure : public BasicOopIterateClosure { size_t _max_depth; size_t _depth; bool _ignore_root_set; + const address _headroom_limit; DFSClosure(EdgeStore* edge_store, JFRBitSet* mark_bits, const Edge* start_edge); void add_chain(); void closure_impl(UnifiedOopRef reference, const oop pointee); + bool have_headroom() const; + public: virtual ReferenceIterationMode reference_iteration_mode() { return DO_FIELDS_EXCEPT_REFERENT; } diff --git a/test/jdk/TEST.groups b/test/jdk/TEST.groups index 8bb270561dc5..4a0f14baf0f1 100644 --- a/test/jdk/TEST.groups +++ b/test/jdk/TEST.groups @@ -560,6 +560,7 @@ jdk_jfr_sanity = \ jdk/jfr/event/gc/collection/TestGCWithFasttime.java \ jdk/jfr/event/gc/configuration/TestGCConfigurationEvent.java \ jdk/jfr/event/metadata/TestDefaultConfigurations.java \ + jdk/jfr/event/oldobject/TestDFSWithSmallStack.java \ jdk/jfr/startupargs/TestDumpOnExit.java \ jdk/jfr/api/consumer/recordingstream/TestBasics.java diff --git a/test/jdk/jdk/jfr/event/oldobject/OldObjects.java b/test/jdk/jdk/jfr/event/oldobject/OldObjects.java index ba90bb10a9ee..bb0ca27836ea 100644 --- a/test/jdk/jdk/jfr/event/oldobject/OldObjects.java +++ b/test/jdk/jdk/jfr/event/oldobject/OldObjects.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -276,4 +276,16 @@ public static void validateReferenceChainLimit(RecordedEvent e, int maxLength) { throw new RuntimeException("Reference chain max length not respected. Found a chain of length " + length); } } + + public static int countChains(List events) throws IOException { + int found = 0; + for (RecordedEvent e : events) { + RecordedObject ro = e.getValue("object"); + if (ro.getValue("referrer") != null) { + found++; + } + } + System.out.println("Found chains: " + found); + return found; + } } diff --git a/test/jdk/jdk/jfr/event/oldobject/TestDFSWithSmallStack.java b/test/jdk/jdk/jfr/event/oldobject/TestDFSWithSmallStack.java new file mode 100644 index 000000000000..d25a6cd5f67e --- /dev/null +++ b/test/jdk/jdk/jfr/event/oldobject/TestDFSWithSmallStack.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2026, IBM Corp. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.jfr.event.oldobject; + +import java.util.LinkedList; +import java.util.List; + +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.internal.test.WhiteBox; +import jdk.test.lib.jfr.EventNames; +import jdk.test.lib.jfr.Events; + +/** + * @test id=dfsonly + * @summary Tests that DFS works with a small stack + * @library /test/lib /test/jdk + * @requires vm.hasJFR + * @modules jdk.jfr/jdk.jfr.internal.test + * @run main/othervm -Xmx2g -XX:VMThreadStackSize=512 jdk.jfr.event.oldobject.TestDFSWithSmallStack dfsonly + */ + +/** + * @test id=bfsdfs + * @summary Tests that DFS works with a small stack + * @library /test/lib /test/jdk + * @requires vm.hasJFR + * @modules jdk.jfr/jdk.jfr.internal.test + * @run main/othervm -Xmx2g -XX:VMThreadStackSize=512 jdk.jfr.event.oldobject.TestDFSWithSmallStack bfsdfs + */ +public class TestDFSWithSmallStack { + + // Tests depth first search with a small stack. + + // An non-zero exit code, together with a missing hs-err file or possibly a missing jfr file, + // indicates a native stack overflow happened and is a fail condition for this test. + + // We build up an array of linked lists, each containing enough entries for DFS search to + // max out max_dfs_depth (but not greatly surpass it). + + private static final int TOTAL_OBJECTS = 10_000_000; + private static final int OBJECTS_PER_LIST = 5_000; + public static LinkedList[] leak; + + public static void main(String... args) throws Exception { + + switch (args[0]) { + case "dfsonly" -> WhiteBox.setSkipBFS(true); + case "bfsdfs" -> {} /* ignored */ + default -> throw new RuntimeException("Invalid argument"); + } + + WhiteBox.setWriteAllObjectSamples(true); + int count = 10; + + while (count > 0) { + try (Recording r = new Recording()) { + r.enable(EventNames.OldObjectSample).with("cutoff", "infinity"); + r.start(); + leak = new LinkedList[TOTAL_OBJECTS / OBJECTS_PER_LIST]; + for (int i = 0; i < leak.length; i++) { + leak[i] = new LinkedList(); + for (int j = 0; j < OBJECTS_PER_LIST; j++) { + leak[i].add(new Object()); + } + } + System.gc(); + r.stop(); + List events = Events.fromRecording(r); + Events.hasEvents(events); + if (OldObjects.countChains(events) >= 30) { + return; + } + System.out.println("Not enough chains found, retrying."); + } + count--; + leak = null; + } + } +} From ca9bd2851854a1dc73b51b86bae837fb563f9042 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Tue, 24 Mar 2026 15:08:46 +0000 Subject: [PATCH 076/168] 8373796: Refactor java/net/httpclient/ThrowingPublishers*.java tests to use JUnit5 Reviewed-by: phh Backport-of: c6da35d7c7076aa9643b3dbf03a285420bb1003d --- .../AbstractThrowingPublishers.java | 177 ++++++++---------- .../ThrowingPublishersCustomAfterCancel.java | 10 +- .../ThrowingPublishersCustomBeforeCancel.java | 10 +- .../ThrowingPublishersIOAfterCancel.java | 10 +- .../ThrowingPublishersIOBeforeCancel.java | 10 +- .../ThrowingPublishersInNextRequest.java | 10 +- .../ThrowingPublishersInRequest.java | 10 +- .../ThrowingPublishersInSubscribe.java | 10 +- .../httpclient/ThrowingPublishersSanity.java | 10 +- 9 files changed, 125 insertions(+), 132 deletions(-) diff --git a/test/jdk/java/net/httpclient/AbstractThrowingPublishers.java b/test/jdk/java/net/httpclient/AbstractThrowingPublishers.java index 859169dcaae8..5cabe36093f8 100644 --- a/test/jdk/java/net/httpclient/AbstractThrowingPublishers.java +++ b/test/jdk/java/net/httpclient/AbstractThrowingPublishers.java @@ -22,15 +22,6 @@ */ import jdk.test.lib.net.SimpleSSLContext; -import org.testng.ITestContext; -import org.testng.ITestResult; -import org.testng.SkipException; -import org.testng.annotations.AfterClass; -import org.testng.annotations.AfterTest; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; import javax.net.ssl.SSLContext; import java.io.IOException; @@ -47,7 +38,6 @@ import java.net.http.HttpResponse.BodyHandlers; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; -import java.util.Arrays; import java.util.EnumSet; import java.util.List; import java.util.Set; @@ -74,24 +64,31 @@ import static java.net.http.HttpClient.Version.HTTP_1_1; import static java.net.http.HttpClient.Version.HTTP_2; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; +import org.junit.jupiter.api.AfterAll; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.api.extension.TestWatcher; public abstract class AbstractThrowingPublishers implements HttpServerAdapters { - SSLContext sslContext; - HttpTestServer httpTestServer; // HTTP/1.1 [ 4 servers ] - HttpTestServer httpsTestServer; // HTTPS/1.1 - HttpTestServer http2TestServer; // HTTP/2 ( h2c ) - HttpTestServer https2TestServer; // HTTP/2 ( h2 ) - String httpURI_fixed; - String httpURI_chunk; - String httpsURI_fixed; - String httpsURI_chunk; - String http2URI_fixed; - String http2URI_chunk; - String https2URI_fixed; - String https2URI_chunk; + static SSLContext sslContext; + static HttpTestServer httpTestServer; // HTTP/1.1 [ 4 servers ] + static HttpTestServer httpsTestServer; // HTTPS/1.1 + static HttpTestServer http2TestServer; // HTTP/2 ( h2c ) + static HttpTestServer https2TestServer; // HTTP/2 ( h2 ) + static String httpURI_fixed; + static String httpURI_chunk; + static String httpsURI_fixed; + static String httpsURI_chunk; + static String http2URI_fixed; + static String http2URI_chunk; + static String https2URI_fixed; + static String https2URI_chunk; static final int ITERATION_COUNT = 1; // a shared executor helps reduce the amount of threads created by the test @@ -109,8 +106,34 @@ public static String now() { return String.format("[%d s, %d ms, %d ns] ", secs, mill, nan); } - final ReferenceTracker TRACKER = ReferenceTracker.INSTANCE; - private volatile HttpClient sharedClient; + final static class TestStopper implements TestWatcher, BeforeEachCallback { + final AtomicReference failed = new AtomicReference<>(); + TestStopper() { } + @Override + public void testFailed(ExtensionContext context, Throwable cause) { + if (stopAfterFirstFailure()) { + String msg = "Aborting due to: " + cause; + failed.compareAndSet(null, msg); + FAILURES.putIfAbsent(context.getDisplayName(), cause); + System.out.printf("%nTEST FAILED: %s%s%n\tAborting due to %s%n%n", + now(), context.getDisplayName(), cause); + System.err.printf("%nTEST FAILED: %s%s%n\tAborting due to %s%n%n", + now(), context.getDisplayName(), cause); + } + } + + @Override + public void beforeEach(ExtensionContext context) { + String msg = failed.get(); + Assumptions.assumeTrue(msg == null, msg); + } + } + + @RegisterExtension + static final TestStopper stopper = new TestStopper(); + + static final ReferenceTracker TRACKER = ReferenceTracker.INSTANCE; + private static volatile HttpClient sharedClient; static class TestExecutor implements Executor { final AtomicLong tasks = new AtomicLong(); @@ -136,34 +159,12 @@ public void execute(Runnable command) { } } - protected boolean stopAfterFirstFailure() { + protected static boolean stopAfterFirstFailure() { return Boolean.getBoolean("jdk.internal.httpclient.debug"); } - final AtomicReference skiptests = new AtomicReference<>(); - void checkSkip() { - var skip = skiptests.get(); - if (skip != null) throw skip; - } - static String name(ITestResult result) { - var params = result.getParameters(); - return result.getName() - + (params == null ? "()" : Arrays.toString(result.getParameters())); - } - - @BeforeMethod - void beforeMethod(ITestContext context) { - if (stopAfterFirstFailure() && context.getFailedTests().size() > 0) { - if (skiptests.get() == null) { - SkipException skip = new SkipException("some tests failed"); - skip.setStackTrace(new StackTraceElement[0]); - skiptests.compareAndSet(null, skip); - } - } - } - - @AfterClass - static final void printFailedTests(ITestContext context) { + @AfterAll + static final void printFailedTests() { out.println("\n========================="); try { // Exceptions should already have been added to FAILURES @@ -187,7 +188,7 @@ static final void printFailedTests(ITestContext context) { } } - private String[] uris() { + private static String[] uris() { return new String[] { httpURI_fixed, httpURI_chunk, @@ -200,8 +201,7 @@ private String[] uris() { }; } - @DataProvider(name = "sanity") - public Object[][] sanity() { + public static Object[][] sanity() { String[] uris = uris(); Object[][] result = new Object[uris.length * 2][]; //Object[][] result = new Object[uris.length][]; @@ -232,7 +232,7 @@ public void accept(Where where) { } } - private Object[][] variants(List throwers, Set whereValues) { + private static Object[][] variants(List throwers, Set whereValues) { String[] uris = uris(); Object[][] result = new Object[uris.length * 2 * throwers.size()][]; //Object[][] result = new Object[(uris.length/2) * 2 * 2][]; @@ -251,80 +251,52 @@ private Object[][] variants(List throwers, Set whereValues) { return result; } - @DataProvider(name = "subscribeProvider") - public Object[][] subscribeProvider(ITestContext context) { - if (stopAfterFirstFailure() && context.getFailedTests().size() > 0) { - return new Object[0][]; - } + public static Object[][] subscribeProvider() { return variants(List.of( new UncheckedCustomExceptionThrower(), new UncheckedIOExceptionThrower()), EnumSet.of(Where.BEFORE_SUBSCRIBE, Where.AFTER_SUBSCRIBE)); } - @DataProvider(name = "requestProvider") - public Object[][] requestProvider(ITestContext context) { - if (stopAfterFirstFailure() && context.getFailedTests().size() > 0) { - return new Object[0][]; - } + public static Object[][] requestProvider() { return variants(List.of( new UncheckedCustomExceptionThrower(), new UncheckedIOExceptionThrower()), EnumSet.of(Where.BEFORE_REQUEST, Where.AFTER_REQUEST)); } - @DataProvider(name = "nextRequestProvider") - public Object[][] nextRequestProvider(ITestContext context) { - if (stopAfterFirstFailure() && context.getFailedTests().size() > 0) { - return new Object[0][]; - } + public static Object[][] nextRequestProvider() { return variants(List.of( new UncheckedCustomExceptionThrower(), new UncheckedIOExceptionThrower()), EnumSet.of(Where.BEFORE_NEXT_REQUEST, Where.AFTER_NEXT_REQUEST)); } - @DataProvider(name = "beforeCancelProviderIO") - public Object[][] beforeCancelProviderIO(ITestContext context) { - if (stopAfterFirstFailure() && context.getFailedTests().size() > 0) { - return new Object[0][]; - } + public static Object[][] beforeCancelProviderIO() { return variants(List.of( new UncheckedIOExceptionThrower()), EnumSet.of(Where.BEFORE_CANCEL)); } - @DataProvider(name = "afterCancelProviderIO") - public Object[][] afterCancelProviderIO(ITestContext context) { - if (stopAfterFirstFailure() && context.getFailedTests().size() > 0) { - return new Object[0][]; - } + public static Object[][] afterCancelProviderIO() { return variants(List.of( new UncheckedIOExceptionThrower()), EnumSet.of(Where.AFTER_CANCEL)); } - @DataProvider(name = "beforeCancelProviderCustom") - public Object[][] beforeCancelProviderCustom(ITestContext context) { - if (stopAfterFirstFailure() && context.getFailedTests().size() > 0) { - return new Object[0][]; - } + public static Object[][] beforeCancelProviderCustom() { return variants(List.of( new UncheckedCustomExceptionThrower()), EnumSet.of(Where.BEFORE_CANCEL)); } - @DataProvider(name = "afterCancelProviderCustom") - public Object[][] afterCancelProvider(ITestContext context) { - if (stopAfterFirstFailure() && context.getFailedTests().size() > 0) { - return new Object[0][]; - } + public static Object[][] afterCancelProviderCustom() { return variants(List.of( new UncheckedCustomExceptionThrower()), EnumSet.of(Where.AFTER_CANCEL)); } - private HttpClient makeNewClient() { + private static HttpClient makeNewClient() { clientCount.incrementAndGet(); return TRACKER.track(HttpClient.newBuilder() .proxy(HttpClient.Builder.NO_PROXY) @@ -333,11 +305,11 @@ private HttpClient makeNewClient() { .build()); } - HttpClient newHttpClient(boolean share) { + static HttpClient newHttpClient(boolean share) { if (!share) return makeNewClient(); HttpClient shared = sharedClient; if (shared != null) return shared; - synchronized (this) { + synchronized (AbstractThrowingPublishers.class) { shared = sharedClient; if (shared == null) { shared = sharedClient = makeNewClient(); @@ -381,7 +353,7 @@ protected void testSanityImpl(String uri, boolean sameClient) CompletableFuture> response = client.sendAsync(req, handler); String body = response.join().body(); - assertEquals(body, Stream.of(BODY.split("\\|")).collect(Collectors.joining())); + assertEquals(Stream.of(BODY.split("\\|")).collect(Collectors.joining()), body); if (!sameClient) { // Wait for the client to be garbage collected. // we use the ReferenceTracker API rather than HttpClient::close here, @@ -425,7 +397,6 @@ private void testThrowing(String name, String uri, boolean sameClient, boolean async, Set whereValues) throws Exception { - checkSkip(); out.printf("%n%s%s%n", now(), name); try { testThrowing(uri, sameClient, publishers, finisher, thrower, async, whereValues); @@ -717,8 +688,11 @@ public void onError(Throwable throwable) { } - @BeforeTest - public void setup() throws Exception { + @BeforeAll + public static void setup() throws Exception { + System.out.println(now() + "setup"); + System.err.println(now() + "setup"); + sslContext = new SimpleSSLContext().get(); if (sslContext == null) throw new AssertionError("Unexpected null sslContext"); @@ -761,8 +735,11 @@ public void setup() throws Exception { https2TestServer.start(); } - @AfterTest - public void teardown() throws Exception { + @AfterAll + public static void teardown() throws Exception { + System.out.println(now() + "teardown"); + System.err.println(now() + "teardown"); + String sharedClientName = sharedClient == null ? null : sharedClient.toString(); sharedClient = null; diff --git a/test/jdk/java/net/httpclient/ThrowingPublishersCustomAfterCancel.java b/test/jdk/java/net/httpclient/ThrowingPublishersCustomAfterCancel.java index 796c0b8df024..2c1aa2fd9b48 100644 --- a/test/jdk/java/net/httpclient/ThrowingPublishersCustomAfterCancel.java +++ b/test/jdk/java/net/httpclient/ThrowingPublishersCustomAfterCancel.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,18 +29,20 @@ * @build jdk.test.lib.net.SimpleSSLContext * ReferenceTracker AbstractThrowingPublishers ThrowingPublishersCustomAfterCancel * jdk.httpclient.test.lib.common.HttpServerAdapters - * @run testng/othervm -Djdk.internal.httpclient.debug=true + * @run junit/othervm -Djdk.internal.httpclient.debug=true * -Djdk.httpclient.enableAllMethodRetry=true * ThrowingPublishersCustomAfterCancel */ -import org.testng.annotations.Test; import java.util.Set; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; public class ThrowingPublishersCustomAfterCancel extends AbstractThrowingPublishers { - @Test(dataProvider = "afterCancelProviderCustom") + @ParameterizedTest + @MethodSource("afterCancelProviderCustom") public void testThrowingAsString(String uri, boolean sameClient, Thrower thrower, Set whereValues) throws Exception diff --git a/test/jdk/java/net/httpclient/ThrowingPublishersCustomBeforeCancel.java b/test/jdk/java/net/httpclient/ThrowingPublishersCustomBeforeCancel.java index 06343d222b8e..ba7341854647 100644 --- a/test/jdk/java/net/httpclient/ThrowingPublishersCustomBeforeCancel.java +++ b/test/jdk/java/net/httpclient/ThrowingPublishersCustomBeforeCancel.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,18 +29,20 @@ * @build jdk.test.lib.net.SimpleSSLContext * ReferenceTracker AbstractThrowingPublishers ThrowingPublishersCustomBeforeCancel * jdk.httpclient.test.lib.common.HttpServerAdapters - * @run testng/othervm -Djdk.internal.httpclient.debug=true + * @run junit/othervm -Djdk.internal.httpclient.debug=true * -Djdk.httpclient.enableAllMethodRetry=true * ThrowingPublishersCustomBeforeCancel */ -import org.testng.annotations.Test; import java.util.Set; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; public class ThrowingPublishersCustomBeforeCancel extends AbstractThrowingPublishers { - @Test(dataProvider = "beforeCancelProviderCustom") + @ParameterizedTest + @MethodSource("beforeCancelProviderCustom") public void testThrowingAsString(String uri, boolean sameClient, Thrower thrower, Set whereValues) throws Exception diff --git a/test/jdk/java/net/httpclient/ThrowingPublishersIOAfterCancel.java b/test/jdk/java/net/httpclient/ThrowingPublishersIOAfterCancel.java index 7ed649b25463..a5e819942b44 100644 --- a/test/jdk/java/net/httpclient/ThrowingPublishersIOAfterCancel.java +++ b/test/jdk/java/net/httpclient/ThrowingPublishersIOAfterCancel.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,18 +29,20 @@ * @build jdk.test.lib.net.SimpleSSLContext * ReferenceTracker AbstractThrowingPublishers ThrowingPublishersIOAfterCancel * jdk.httpclient.test.lib.common.HttpServerAdapters - * @run testng/othervm -Djdk.internal.httpclient.debug=true + * @run junit/othervm -Djdk.internal.httpclient.debug=true * -Djdk.httpclient.enableAllMethodRetry=true * ThrowingPublishersIOAfterCancel */ -import org.testng.annotations.Test; import java.util.Set; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; public class ThrowingPublishersIOAfterCancel extends AbstractThrowingPublishers { - @Test(dataProvider = "afterCancelProviderIO") + @ParameterizedTest + @MethodSource("afterCancelProviderIO") public void testThrowingAsString(String uri, boolean sameClient, Thrower thrower, Set whereValues) throws Exception diff --git a/test/jdk/java/net/httpclient/ThrowingPublishersIOBeforeCancel.java b/test/jdk/java/net/httpclient/ThrowingPublishersIOBeforeCancel.java index 3be2eb061cc4..e68f83052bde 100644 --- a/test/jdk/java/net/httpclient/ThrowingPublishersIOBeforeCancel.java +++ b/test/jdk/java/net/httpclient/ThrowingPublishersIOBeforeCancel.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,18 +29,20 @@ * @build jdk.test.lib.net.SimpleSSLContext * ReferenceTracker AbstractThrowingPublishers ThrowingPublishersIOBeforeCancel * jdk.httpclient.test.lib.common.HttpServerAdapters - * @run testng/othervm -Djdk.internal.httpclient.debug=true + * @run junit/othervm -Djdk.internal.httpclient.debug=true * -Djdk.httpclient.enableAllMethodRetry=true * ThrowingPublishersIOBeforeCancel */ -import org.testng.annotations.Test; import java.util.Set; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; public class ThrowingPublishersIOBeforeCancel extends AbstractThrowingPublishers { - @Test(dataProvider = "beforeCancelProviderIO") + @ParameterizedTest + @MethodSource("beforeCancelProviderIO") public void testThrowingAsString(String uri, boolean sameClient, Thrower thrower, Set whereValues) throws Exception diff --git a/test/jdk/java/net/httpclient/ThrowingPublishersInNextRequest.java b/test/jdk/java/net/httpclient/ThrowingPublishersInNextRequest.java index eb07b359fc96..62d06fd60191 100644 --- a/test/jdk/java/net/httpclient/ThrowingPublishersInNextRequest.java +++ b/test/jdk/java/net/httpclient/ThrowingPublishersInNextRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,18 +29,20 @@ * @build jdk.test.lib.net.SimpleSSLContext * ReferenceTracker AbstractThrowingPublishers ThrowingPublishersInNextRequest * jdk.httpclient.test.lib.common.HttpServerAdapters - * @run testng/othervm -Djdk.internal.httpclient.debug=true + * @run junit/othervm -Djdk.internal.httpclient.debug=true * -Djdk.httpclient.enableAllMethodRetry=true * ThrowingPublishersInNextRequest */ -import org.testng.annotations.Test; import java.util.Set; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; public class ThrowingPublishersInNextRequest extends AbstractThrowingPublishers { - @Test(dataProvider = "nextRequestProvider") + @ParameterizedTest + @MethodSource("nextRequestProvider") public void testThrowingAsString(String uri, boolean sameClient, Thrower thrower, Set whereValues) throws Exception diff --git a/test/jdk/java/net/httpclient/ThrowingPublishersInRequest.java b/test/jdk/java/net/httpclient/ThrowingPublishersInRequest.java index ef05e4e0da6f..f863f3598edf 100644 --- a/test/jdk/java/net/httpclient/ThrowingPublishersInRequest.java +++ b/test/jdk/java/net/httpclient/ThrowingPublishersInRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,18 +29,20 @@ * @build jdk.test.lib.net.SimpleSSLContext * ReferenceTracker AbstractThrowingPublishers ThrowingPublishersInRequest * jdk.httpclient.test.lib.common.HttpServerAdapters - * @run testng/othervm -Djdk.internal.httpclient.debug=true + * @run junit/othervm -Djdk.internal.httpclient.debug=true * -Djdk.httpclient.enableAllMethodRetry=true * ThrowingPublishersInRequest */ -import org.testng.annotations.Test; import java.util.Set; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; public class ThrowingPublishersInRequest extends AbstractThrowingPublishers { - @Test(dataProvider = "requestProvider") + @ParameterizedTest + @MethodSource("requestProvider") public void testThrowingAsString(String uri, boolean sameClient, Thrower thrower, Set whereValues) throws Exception diff --git a/test/jdk/java/net/httpclient/ThrowingPublishersInSubscribe.java b/test/jdk/java/net/httpclient/ThrowingPublishersInSubscribe.java index f14c51670247..ce1ad89dc056 100644 --- a/test/jdk/java/net/httpclient/ThrowingPublishersInSubscribe.java +++ b/test/jdk/java/net/httpclient/ThrowingPublishersInSubscribe.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,18 +29,20 @@ * @build jdk.test.lib.net.SimpleSSLContext * ReferenceTracker AbstractThrowingPublishers ThrowingPublishersInSubscribe * jdk.httpclient.test.lib.common.HttpServerAdapters - * @run testng/othervm -Djdk.internal.httpclient.debug=true + * @run junit/othervm -Djdk.internal.httpclient.debug=true * -Djdk.httpclient.enableAllMethodRetry=true * ThrowingPublishersInSubscribe */ -import org.testng.annotations.Test; import java.util.Set; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; public class ThrowingPublishersInSubscribe extends AbstractThrowingPublishers { - @Test(dataProvider = "subscribeProvider") + @ParameterizedTest + @MethodSource("subscribeProvider") public void testThrowingAsString(String uri, boolean sameClient, Thrower thrower, Set whereValues) throws Exception diff --git a/test/jdk/java/net/httpclient/ThrowingPublishersSanity.java b/test/jdk/java/net/httpclient/ThrowingPublishersSanity.java index 305490833104..eee2f9ab7fb7 100644 --- a/test/jdk/java/net/httpclient/ThrowingPublishersSanity.java +++ b/test/jdk/java/net/httpclient/ThrowingPublishersSanity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,16 +29,18 @@ * @build jdk.test.lib.net.SimpleSSLContext * ReferenceTracker AbstractThrowingPublishers ThrowingPublishersSanity * jdk.httpclient.test.lib.common.HttpServerAdapters - * @run testng/othervm -Djdk.internal.httpclient.debug=true + * @run junit/othervm -Djdk.internal.httpclient.debug=true * -Djdk.httpclient.enableAllMethodRetry=true * ThrowingPublishersSanity */ -import org.testng.annotations.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; public class ThrowingPublishersSanity extends AbstractThrowingPublishers { - @Test(dataProvider = "sanity") + @ParameterizedTest + @MethodSource("sanity") public void testSanity(String uri, boolean sameClient) throws Exception { super.testSanityImpl(uri,sameClient); From 646e1398a18fa6e8be806e52ba4e267f6a0474d8 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Tue, 24 Mar 2026 15:09:21 +0000 Subject: [PATCH 077/168] 8367531: Template Framework: use scopes and tokens instead of misbehaving immediate-return-queries Reviewed-by: phh Backport-of: b41146cd1e5d412f69b893bfb2fd65b6206bb0d2 --- .../jtreg/compiler/igvn/ExpressionFuzzer.java | 16 +- .../lib/template_framework/AddNameToken.java | 4 + .../lib/template_framework/CodeFrame.java | 123 +- .../lib/template_framework/DataName.java | 207 +- .../compiler/lib/template_framework/Hook.java | 92 +- .../template_framework/HookAnchorToken.java | 5 +- .../template_framework/HookInsertToken.java | 6 +- .../HookIsAnchoredToken.java | 37 + .../lib/template_framework/LetToken.java | 38 + .../template_framework/NameCountToken.java | 39 + .../template_framework/NameForEachToken.java | 41 + .../template_framework/NameHasAnyToken.java | 39 + .../template_framework/NameSampleToken.java | 43 + .../lib/template_framework/NameSet.java | 1 + .../template_framework/NamesToListToken.java | 41 + .../lib/template_framework/Renderer.java | 187 +- .../{TemplateBody.java => ScopeToken.java} | 10 +- .../template_framework/ScopeTokenImpl.java | 42 + ...othingToken.java => SetFuelCostToken.java} | 5 +- .../template_framework/StructuralName.java | 147 +- .../lib/template_framework/Template.java | 510 ++-- .../lib/template_framework/TemplateFrame.java | 109 +- .../lib/template_framework/TemplateToken.java | 10 +- .../lib/template_framework/Token.java | 29 +- .../lib/template_framework/TokenParser.java | 2 +- .../library/Expression.java | 4 +- .../library/PrimitiveType.java | 4 +- .../library/TestFrameworkClass.java | 10 +- .../examples/TestAdvanced.java | 6 +- .../examples/TestExpressions.java | 4 +- .../examples/TestPrimitiveTypes.java | 31 +- .../examples/TestSimple.java | 4 +- .../examples/TestTutorial.java | 836 +++++-- .../examples/TestWithTestFrameworkClass.java | 6 +- .../tests/TestExpression.java | 8 +- .../template_framework/tests/TestFormat.java | 6 +- .../tests/TestTemplate.java | 2207 ++++++++++++++--- 37 files changed, 3939 insertions(+), 970 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/HookIsAnchoredToken.java create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/LetToken.java create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/NameCountToken.java create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/NameForEachToken.java create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/NameHasAnyToken.java create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/NameSampleToken.java create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/NamesToListToken.java rename test/hotspot/jtreg/compiler/lib/template_framework/{TemplateBody.java => ScopeToken.java} (78%) create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/ScopeTokenImpl.java rename test/hotspot/jtreg/compiler/lib/template_framework/{NothingToken.java => SetFuelCostToken.java} (89%) diff --git a/test/hotspot/jtreg/compiler/igvn/ExpressionFuzzer.java b/test/hotspot/jtreg/compiler/igvn/ExpressionFuzzer.java index 60b11e8ffbc4..40bfb2e43194 100644 --- a/test/hotspot/jtreg/compiler/igvn/ExpressionFuzzer.java +++ b/test/hotspot/jtreg/compiler/igvn/ExpressionFuzzer.java @@ -45,7 +45,7 @@ import compiler.lib.compile_framework.*; import compiler.lib.template_framework.Template; import compiler.lib.template_framework.TemplateToken; -import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.scope; import static compiler.lib.template_framework.Template.let; import static compiler.lib.template_framework.Template.$; import compiler.lib.template_framework.library.CodeGenerationDataNameType; @@ -99,7 +99,7 @@ public static String generate(CompileFramework comp) { // Create the body for the test. We use it twice: compiled and reference. // Execute the expression and catch expected Exceptions. - var bodyTemplate = Template.make("expression", "arguments", "checksum", (Expression expression, List arguments, String checksum) -> body( + var bodyTemplate = Template.make("expression", "arguments", "checksum", (Expression expression, List arguments, String checksum) -> scope( """ try { """, @@ -167,14 +167,14 @@ public static String generate(CompileFramework comp) { default -> throw new RuntimeException("not handled: " + type.name()); }; StringPair cmp = cmps.get(RANDOM.nextInt(cmps.size())); - return body( + return scope( ", ", cmp.s0(), type.con(), cmp.s1() ); }); // Checksum method: returns not just the value, but also does some range / bit checks. // This gives us enhanced verification on the range / bits of the result type. - var checksumTemplate = Template.make("expression", "checksum", (Expression expression, String checksum) -> body( + var checksumTemplate = Template.make("expression", "checksum", (Expression expression, String checksum) -> scope( let("returnType", expression.returnType), """ @ForceInline @@ -201,7 +201,7 @@ public static String generate(CompileFramework comp) { // We need to prepare some random values to pass into the test method. We generate the values // once, and pass the same values into both the compiled and reference method. - var valueTemplate = Template.make("name", "type", (String name, CodeGenerationDataNameType type) -> body( + var valueTemplate = Template.make("name", "type", (String name, CodeGenerationDataNameType type) -> scope( "#type #name = ", (type instanceof PrimitiveType pt) ? pt.callLibraryRNG() : type.con(), ";\n" @@ -213,7 +213,7 @@ public static String generate(CompileFramework comp) { // // To ensure that both the compiled and reference method use the same constraint, we put // the computation in a ForceInline method. - var constrainArgumentMethodTemplate = Template.make("name", "type", (String name, CodeGenerationDataNameType type) -> body( + var constrainArgumentMethodTemplate = Template.make("name", "type", (String name, CodeGenerationDataNameType type) -> scope( """ @ForceInline public static #type constrain_#name(#type v) { @@ -247,7 +247,7 @@ public static String generate(CompileFramework comp) { """ )); - var constrainArgumentTemplate = Template.make("name", (String name) -> body( + var constrainArgumentTemplate = Template.make("name", (String name) -> scope( """ #name = constrain_#name(#name); """ @@ -279,7 +279,7 @@ public static String generate(CompileFramework comp) { } } } - return body( + return scope( let("methodArguments", methodArguments.stream().map(ma -> ma.name).collect(Collectors.joining(", "))), let("methodArgumentsWithTypes", diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/AddNameToken.java b/test/hotspot/jtreg/compiler/lib/template_framework/AddNameToken.java index 4f1f7e569bf0..ceb1cc263fc6 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/AddNameToken.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/AddNameToken.java @@ -23,4 +23,8 @@ package compiler.lib.template_framework; +/** + * Represents the addition of the specified {@link Name} to the current scope, + * or an outer scope if the inner scope is transparent to {@link Name}s. + */ record AddNameToken(Name name) implements Token {} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/CodeFrame.java b/test/hotspot/jtreg/compiler/lib/template_framework/CodeFrame.java index 5c4ff55614fa..765e9bc42ba9 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/CodeFrame.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/CodeFrame.java @@ -29,22 +29,96 @@ import java.util.List; /** - * The {@link CodeFrame} represents a frame (i.e. scope) of code, appending {@link Code} to the {@code 'codeList'} + * The {@link CodeFrame} represents a frame (i.e. scope) of generated code by appending {@link Code} to the {@link #codeList} * as {@link Token}s are rendered, and adding names to the {@link NameSet}s with {@link Template#addStructuralName}/ - * {@link Template#addDataName}. {@link Hook}s can be added to a frame, which allows code to be inserted at that - * location later. When a {@link Hook} is {@link Hook#anchor}ed, it separates the Template into an outer and inner - * {@link CodeFrame}, ensuring that names that are added inside the inner frame are only available inside that frame. + * {@link Template#addDataName}. {@link Hook}s can be added to a code frame, which allows code to be inserted at that + * location later. * *

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

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

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

+ * Note, that the following two do the equivalent: * - * @return The number of {@link DataName}s in the filtered set. + *

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

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

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

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

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

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

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

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

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

+ * Note that if we use {@link #insert} with {@link Template#transparentScope}, then + * {@link DataName}s and {@link StructuralName}s escape from the inserted scope to the + * anchor scope, but hashtag replacements and {@link Template#setFuelCost} escape to + * the caller, i.e. from where we inserted the scope. This makes sense if we consider + * {@link DataName}s belonging to the structure of the generated code and the inserted + * scope belonging to the anchor scope. On the other hand, hashtag replacements and + * {@link Template#setFuelCost} rather belong to the code generation that happens + * within the context of a template. + * * @param name The name of the Hook, for debugging purposes only. */ public record Hook(String name) { /** - * Anchor this {@link Hook} for the scope of the provided {@code 'tokens'}. + * Anchor this {@link Hook} for the provided inner scope. * From anywhere inside this scope, even in nested Templates, code can be * {@link #insert}ed back to the location where this {@link Hook} was {@link #anchor}ed. * - * @param tokens A list of tokens, which have the same restrictions as {@link Template#body}. - * @return A {@link Token} that captures the anchoring of the scope and the list of validated {@link Token}s. + * @param innerScope An inner scope, for which the {@link Hook} is anchored. + * @return A {@link Token} that captures the anchoring and the inner scope. */ - public Token anchor(Object... tokens) { - return new HookAnchorToken(this, TokenParser.parse(tokens)); + public Token anchor(ScopeToken innerScope) { + return new HookAnchorToken(this, innerScope); } /** @@ -83,18 +108,31 @@ public Token anchor(Object... tokens) { * This could be in the same Template, or one nested further out. * * @param templateToken The Template with applied arguments to be inserted at the {@link Hook}. - * @return The {@link Token} which when used inside a {@link Template#body} performs the code insertion into the {@link Hook}. + * @return The {@link Token} which represents the code insertion into the {@link Hook}. */ public Token insert(TemplateToken templateToken) { - return new HookInsertToken(this, templateToken); + return new HookInsertToken(this, Template.transparentScope(templateToken)); + } + + /** + * Inserts a scope ({@link ScopeToken}) to the innermost location where this {@link Hook} was {@link #anchor}ed. + * This could be in the same Template, or one nested further out. + * + * @param scopeToken The scope to be inserted at the {@link Hook}. + * @return The {@link Token} which represents the code insertion into the {@link Hook}. + */ + public Token insert(ScopeToken scopeToken) { + return new HookInsertToken(this, scopeToken); } /** - * Checks if the {@link Hook} was {@link Hook#anchor}ed for the current scope or an outer scope. + * Checks if the {@link Hook} was {@link Hook#anchor}ed for the current scope or an outer scope, + * and makes the boolean result available to an inner scope. * - * @return If the {@link Hook} was {@link Hook#anchor}ed for the current scope or an outer scope. + * @param function the function that generates the inner scope given the boolean result. + * @return the token that represents the check and inner scope. */ - public boolean isAnchored() { - return Renderer.getCurrent().isAnchored(this); + public Token isAnchored(Function function) { + return new HookIsAnchoredToken(this, function); } } diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/HookAnchorToken.java b/test/hotspot/jtreg/compiler/lib/template_framework/HookAnchorToken.java index b025c5ff0415..4979365b3d0e 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/HookAnchorToken.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/HookAnchorToken.java @@ -25,4 +25,7 @@ import java.util.List; -record HookAnchorToken(Hook hook, List tokens) implements Token {} +/** + * Represents the {@link Hook#anchor} with its inner scope. + */ +record HookAnchorToken(Hook hook, ScopeToken innerScope) implements Token {} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/HookInsertToken.java b/test/hotspot/jtreg/compiler/lib/template_framework/HookInsertToken.java index de8b60bbf243..a433d472a6e7 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/HookInsertToken.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/HookInsertToken.java @@ -23,4 +23,8 @@ package compiler.lib.template_framework; -record HookInsertToken(Hook hook, TemplateToken templateToken) implements Token {} +/** + * Represents the {@link Hook#insert} with the {@link ScopeToken} of the + * scope that is to be inserted. + */ +record HookInsertToken(Hook hook, ScopeToken scopeToken) implements Token {} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/HookIsAnchoredToken.java b/test/hotspot/jtreg/compiler/lib/template_framework/HookIsAnchoredToken.java new file mode 100644 index 000000000000..5c7b92ec1fe5 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/HookIsAnchoredToken.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.lib.template_framework; + +import java.util.function.Function; + +/** + * Represents an {@link Hook#isAnchored} query with the function that creates an inner scope + * given the boolean answer. + */ +record HookIsAnchoredToken(Hook hook, Function function) implements Token { + + ScopeToken getScopeToken(boolean isAnchored) { + return function().apply(isAnchored); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/LetToken.java b/test/hotspot/jtreg/compiler/lib/template_framework/LetToken.java new file mode 100644 index 000000000000..ee18dd440b73 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/LetToken.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.lib.template_framework; + +import java.util.function.Function; + +/** + * Represents a let (aka hashtag) definition. The hashtag replacement is active for the + * scope ({@link ScopeToken}) that the {@code function} creates, but can escape that + * scope if it is transparent to hashtags. + */ +record LetToken(String key, T value, Function function) implements Token { + + ScopeToken getScopeToken() { + return function().apply(value); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/NameCountToken.java b/test/hotspot/jtreg/compiler/lib/template_framework/NameCountToken.java new file mode 100644 index 000000000000..f0344efdd082 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/NameCountToken.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.lib.template_framework; + +import java.util.function.Function; + +/** + * Represents the counting of {@link Name}s, and the function that is called + * to create an inner scope given the count. + */ +record NameCountToken( + NameSet.Predicate predicate, + Function function) implements Token { + + ScopeToken getScopeToken(int count) { + return function().apply(count); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/NameForEachToken.java b/test/hotspot/jtreg/compiler/lib/template_framework/NameForEachToken.java new file mode 100644 index 000000000000..0e629740be11 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/NameForEachToken.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.lib.template_framework; + +import java.util.function.Function; + +/** + * Represents the for-each execution of the provided function and (optional) hashtag replacement + * keys for name and type of each name. + */ +record NameForEachToken( + NameSet.Predicate predicate, + String name, + String type, + Function function) implements Token { + + ScopeToken getScopeToken(Name n) { + return function().apply((N)n); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/NameHasAnyToken.java b/test/hotspot/jtreg/compiler/lib/template_framework/NameHasAnyToken.java new file mode 100644 index 000000000000..a31990af2107 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/NameHasAnyToken.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.lib.template_framework; + +import java.util.function.Function; + +/** + * Represents the check if there is any name and the function that is to + * be called given the boolean value (true iff there are any names). + */ +record NameHasAnyToken( + NameSet.Predicate predicate, + Function function) implements Token { + + ScopeToken getScopeToken(boolean hasAny) { + return function().apply(hasAny); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/NameSampleToken.java b/test/hotspot/jtreg/compiler/lib/template_framework/NameSampleToken.java new file mode 100644 index 000000000000..0b01f00fcd9f --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/NameSampleToken.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.lib.template_framework; + +import java.util.function.Function; + +/** + * Represents the sampling of {@link Name}s, and the function that is called given + * the sampled name, as well as the (optional) hashtag replacement keys for the + * name and type of the sampled name, which are then available in the inner scope + * created by the provided function. + */ +record NameSampleToken( + NameSet.Predicate predicate, + String name, + String type, + Function function) implements Token { + + ScopeToken getScopeToken(Name n) { + return function().apply((N)n); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/NameSet.java b/test/hotspot/jtreg/compiler/lib/template_framework/NameSet.java index ef79c33d48a7..403dbdc694fe 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/NameSet.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/NameSet.java @@ -43,6 +43,7 @@ class NameSet { interface Predicate { boolean check(Name type); + String toString(); // used when sampling fails. } NameSet(NameSet parent) { diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/NamesToListToken.java b/test/hotspot/jtreg/compiler/lib/template_framework/NamesToListToken.java new file mode 100644 index 000000000000..40710a012979 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/NamesToListToken.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.lib.template_framework; + +import java.util.function.Function; +import java.util.List; + +/** + * Represents the {@code toList} on a filtered name set, including the collection of the + * names and the creation of the inner scope with the function. + */ +record NamesToListToken( + NameSet.Predicate predicate, + Function, ScopeToken> function) implements Token { + + ScopeToken getScopeToken(List names) { + List castNames = names.stream().map(n -> (N)n).toList(); + return function().apply(castNames); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/Renderer.java b/test/hotspot/jtreg/compiler/lib/template_framework/Renderer.java index 14adfc81d3fa..61ab9ab343c8 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/Renderer.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/Renderer.java @@ -76,7 +76,7 @@ static boolean isValidHashtagOrDollarName(String name) { *

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

- * Instead, the user should create a {@link TemplateToken} from the inner {@link Template}, and - * use that {@link TemplateToken} in the {@link Template#body} of the outer {@link Template}. + * Instead, the user must create a {@link TemplateToken} from the inner {@link Template}, and + * use that {@link TemplateToken} in the {@link Template#scope} of the outer {@link Template}. * This way, the inner and outer {@link Template}s get rendered together, and the inner {@link Template} * has access to the {@link Name}s and {@link Hook}s of the outer {@link Template}. * @@ -113,7 +113,7 @@ private Renderer(float fuel) { static Renderer getCurrent() { if (renderer == null) { - throw new RendererException("A Template method such as '$', 'let', 'sample', 'count' etc. was called outside a template rendering."); + throw new RendererException("A Template method such as '$', 'fuel', etc. was called outside a template rendering call."); } return renderer; } @@ -171,26 +171,6 @@ float fuel() { return currentTemplateFrame.fuel; } - void setFuelCost(float fuelCost) { - currentTemplateFrame.setFuelCost(fuelCost); - } - - Name sampleName(NameSet.Predicate predicate) { - return currentCodeFrame.sampleName(predicate); - } - - int countNames(NameSet.Predicate predicate) { - return currentCodeFrame.countNames(predicate); - } - - boolean hasAnyNames(NameSet.Predicate predicate) { - return currentCodeFrame.hasAnyNames(predicate); - } - - List listNames(NameSet.Predicate predicate) { - return currentCodeFrame.listNames(predicate); - } - /** * Formats values to {@link String} with the goal of using them in Java code. * By default, we use the overrides of {@link Object#toString}. @@ -243,12 +223,16 @@ private static String formatDouble(Double d) { } private void renderTemplateToken(TemplateToken templateToken) { + // We need a TemplateFrame in all cases, this ensures that the outermost scope of the template + // is not transparent for hashtags and setFuelCost, and also that the id of the template is + // unique. TemplateFrame templateFrame = TemplateFrame.make(currentTemplateFrame, nextTemplateFrameId++); currentTemplateFrame = templateFrame; templateToken.visitArguments((name, value) -> addHashtagReplacement(name, format(value))); - TemplateBody body = templateToken.instantiate(); - renderTokenList(body.tokens()); + + // If the ScopeToken is transparent to Names, then the Template is transparent to names. + renderScopeToken(templateToken.instantiate()); if (currentTemplateFrame != templateFrame) { throw new RuntimeException("Internal error: TemplateFrame mismatch!"); @@ -256,29 +240,76 @@ private void renderTemplateToken(TemplateToken templateToken) { currentTemplateFrame = currentTemplateFrame.parent; } + private void renderScopeToken(ScopeToken st) { + renderScopeToken(st, () -> {}); + } + + private void renderScopeToken(ScopeToken st, Runnable preamble) { + if (!(st instanceof ScopeTokenImpl(List tokens, + boolean isTransparentForNames, + boolean isTransparentForHashtags, + boolean isTransparentForSetFuelCost))) { + throw new RuntimeException("Internal error: could not unpack ScopeTokenImpl."); + } + + // We need the CodeFrame for local names. + CodeFrame outerCodeFrame = currentCodeFrame; + if (!isTransparentForNames) { + currentCodeFrame = CodeFrame.make(currentCodeFrame, false); + } + + // We need to be able to define local hashtag replacements, but still + // see the outer ones. We also need to have the same id for dollar + // replacement as the outer frame. And we need to be able to allow + // local setFuelCost definitions. + TemplateFrame innerTemplateFrame = null; + if (!isTransparentForHashtags || !isTransparentForSetFuelCost) { + innerTemplateFrame = TemplateFrame.makeInnerScope(currentTemplateFrame, + isTransparentForHashtags, + isTransparentForSetFuelCost); + currentTemplateFrame = innerTemplateFrame; + } + + // Allow definition of hashtags and variables to be placed in the nested frames. + preamble.run(); + + // Now render the nested code. + renderTokenList(tokens); + + if (!isTransparentForHashtags || !isTransparentForSetFuelCost) { + if (currentTemplateFrame != innerTemplateFrame) { + throw new RuntimeException("Internal error: TemplateFrame mismatch!"); + } + currentTemplateFrame = currentTemplateFrame.parent; + } + + // Tear down CodeFrame nesting. If no nesting happened, the code is already + // in the currentCodeFrame. + if (!isTransparentForNames) { + outerCodeFrame.addCode(currentCodeFrame.getCode()); + currentCodeFrame = outerCodeFrame; + } + } + private void renderToken(Token token) { switch (token) { case StringToken(String s) -> { renderStringWithDollarAndHashtagReplacements(s); } - case NothingToken() -> { - // Nothing. - } - case HookAnchorToken(Hook hook, List tokens) -> { + case HookAnchorToken(Hook hook, ScopeTokenImpl innerScope) -> { CodeFrame outerCodeFrame = currentCodeFrame; - // We need a CodeFrame to which the hook can insert code. That way, name - // definitions at the hook cannot escape the hookCodeFrame. - CodeFrame hookCodeFrame = CodeFrame.make(outerCodeFrame); + // We need a CodeFrame to which the hook can insert code. If the nested names + // are to be local, the CodeFrame must be non-transparent for names. + CodeFrame hookCodeFrame = CodeFrame.make(outerCodeFrame, innerScope.isTransparentForNames()); hookCodeFrame.addHook(hook); - // We need a CodeFrame where the tokens can be rendered. That way, name - // definitions from the tokens cannot escape the innerCodeFrame to the - // hookCodeFrame. - CodeFrame innerCodeFrame = CodeFrame.make(hookCodeFrame); + // We need a CodeFrame where the tokens can be rendered for code that is + // generated inside the anchor scope, but not inserted directly to the hook. + CodeFrame innerCodeFrame = CodeFrame.make(hookCodeFrame, innerScope.isTransparentForNames()); currentCodeFrame = innerCodeFrame; - renderTokenList(tokens); + renderScopeToken(innerScope); // Close the hookCodeFrame and innerCodeFrame. hookCodeFrame code comes before the // innerCodeFrame code from the tokens. @@ -286,20 +317,20 @@ case HookAnchorToken(Hook hook, List tokens) -> { currentCodeFrame.addCode(hookCodeFrame.getCode()); currentCodeFrame.addCode(innerCodeFrame.getCode()); } - case HookInsertToken(Hook hook, TemplateToken templateToken) -> { + case HookInsertToken(Hook hook, ScopeTokenImpl scopeToken) -> { // Switch to hook CodeFrame. CodeFrame callerCodeFrame = currentCodeFrame; CodeFrame hookCodeFrame = codeFrameForHook(hook); // Use a transparent nested CodeFrame. We need a CodeFrame so that the code generated - // by the TemplateToken can be collected, and hook insertions from it can still - // be made to the hookCodeFrame before the code from the TemplateToken is added to + // by the scopeToken can be collected, and hook insertions from it can still + // be made to the hookCodeFrame before the code from the scopeToken is added to // the hookCodeFrame. // But the CodeFrame must be transparent, so that its name definitions go out to - // the hookCodeFrame, and are not limited to the CodeFrame for the TemplateToken. - currentCodeFrame = CodeFrame.makeTransparentForNames(hookCodeFrame); + // the hookCodeFrame, and are not limited to the CodeFrame for the scopeToken. + currentCodeFrame = CodeFrame.make(hookCodeFrame, true); - renderTemplateToken(templateToken); + renderScopeToken(scopeToken); hookCodeFrame.addCode(currentCodeFrame.getCode()); @@ -307,18 +338,68 @@ case HookInsertToken(Hook hook, TemplateToken templateToken) -> { currentCodeFrame = callerCodeFrame; } case TemplateToken templateToken -> { - // Use a nested CodeFrame. - CodeFrame callerCodeFrame = currentCodeFrame; - currentCodeFrame = CodeFrame.make(currentCodeFrame); - renderTemplateToken(templateToken); - - callerCodeFrame.addCode(currentCodeFrame.getCode()); - currentCodeFrame = callerCodeFrame; } case AddNameToken(Name name) -> { currentCodeFrame.addName(name); } + case ScopeToken scopeToken -> { + renderScopeToken(scopeToken); + } + case NameSampleToken nameScopeToken -> { + Name name = currentCodeFrame.sampleName(nameScopeToken.predicate()); + if (name == null) { + throw new RendererException("No Name found for " + nameScopeToken.predicate().toString()); + } + ScopeToken scopeToken = nameScopeToken.getScopeToken(name); + renderScopeToken(scopeToken, () -> { + if (nameScopeToken.name() != null) { + addHashtagReplacement(nameScopeToken.name(), name.name()); + } + if (nameScopeToken.type() != null) { + addHashtagReplacement(nameScopeToken.type(), name.type()); + } + }); + } + case NameForEachToken nameForEachToken -> { + List list = currentCodeFrame.listNames(nameForEachToken.predicate()); + list.stream().forEach(name -> { + ScopeToken scopeToken = nameForEachToken.getScopeToken(name); + renderScopeToken(scopeToken, () -> { + if (nameForEachToken.name() != null) { + addHashtagReplacement(nameForEachToken.name(), name.name()); + } + if (nameForEachToken.type() != null) { + addHashtagReplacement(nameForEachToken.type(), name.type()); + } + }); + }); + } + case NamesToListToken nameToListToken -> { + List list = currentCodeFrame.listNames(nameToListToken.predicate()); + renderScopeToken(nameToListToken.getScopeToken(list)); + } + case NameCountToken nameCountToken -> { + int count = currentCodeFrame.countNames(nameCountToken.predicate()); + renderScopeToken(nameCountToken.getScopeToken(count)); + } + case NameHasAnyToken nameHasAnyToken -> { + boolean hasAny = currentCodeFrame.hasAnyNames(nameHasAnyToken.predicate()); + renderScopeToken(nameHasAnyToken.getScopeToken(hasAny)); + } + case SetFuelCostToken(float fuelCost) -> { + currentTemplateFrame.setFuelCost(fuelCost); + } + case LetToken letToken -> { + ScopeToken scopeToken = letToken.getScopeToken(); + renderScopeToken(scopeToken, () -> { + addHashtagReplacement(letToken.key(), letToken.value()); + }); + } + case HookIsAnchoredToken hookIsAnchoredToken -> { + boolean isAnchored = currentCodeFrame.codeFrameForHook(hookIsAnchoredToken.hook()) != null; + renderScopeToken(hookIsAnchoredToken.getScopeToken(isAnchored)); + } } } @@ -423,10 +504,6 @@ private void renderStringWithDollarAndHashtagReplacementsPart(final String s, fi )); } - boolean isAnchored(Hook hook) { - return currentCodeFrame.codeFrameForHook(hook) != null; - } - private CodeFrame codeFrameForHook(Hook hook) { CodeFrame codeFrame = currentCodeFrame.codeFrameForHook(hook); if (codeFrame == null) { diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/TemplateBody.java b/test/hotspot/jtreg/compiler/lib/template_framework/ScopeToken.java similarity index 78% rename from test/hotspot/jtreg/compiler/lib/template_framework/TemplateBody.java rename to test/hotspot/jtreg/compiler/lib/template_framework/ScopeToken.java index 440766b3f79f..f81215da36b8 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/TemplateBody.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/ScopeToken.java @@ -23,12 +23,8 @@ package compiler.lib.template_framework; -import java.util.List; - /** - * A Template generates a {@link TemplateBody}, which is a list of {@link Token}s, - * which are then later rendered to {@link String}s. - * - * @param tokens The list of {@link Token}s that are later rendered to {@link String}s. + * A {@link ScopeToken} represents a scope in a {@link Template}, which can be + * created with {@link Template#scope}, {@link Template#transparentScope}, and other related methods. */ -public record TemplateBody(List tokens) {} +public sealed interface ScopeToken extends Token permits ScopeTokenImpl {} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/ScopeTokenImpl.java b/test/hotspot/jtreg/compiler/lib/template_framework/ScopeTokenImpl.java new file mode 100644 index 000000000000..df95bd567226 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/ScopeTokenImpl.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.lib.template_framework; + +import java.util.List; + +/** + * Represents a scope with its tokens. Boolean flags indicate if names, + * hashtag replacements and {@link Template#setFuelCost} are local, or escape to + * outer scopes. + * + *

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

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

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

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

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

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

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

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

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

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

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

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

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

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

- * Let us look at the following example to better understand the execution order. + * A {@link TemplateBinding} allows the recursive use of Templates. With the indirection of such a binding, + * a Template can reference itself. * *

- * {@snippet lang=java : - * var testTemplate = Template.make(() -> body( - * // The lambda has just been invoked. - * // We count the DataNames and assign the count to the hashtag replacement "c1". - * let("c1", dataNames(MUTABLE).exactOf(someType).count()), - * // We want to define a DataName "v1", and create a token for it. - * addDataName($("v1"), someType, MUTABLE), - * // We count the DataNames again, but the count does NOT change compared to "c1". - * // This is because the token for "v1" is only evaluated later. - * let("c2", dataNames(MUTABLE).exactOf(someType).count()), - * // Create a nested scope. - * METHOD_HOOK.anchor( - * // We want to define a DataName "v2", which is only valid inside this - * // nested scope. - * addDataName($("v2"), someType, MUTABLE), - * // The count is still not different to "c1". - * let("c3", dataNames(MUTABLE).exactOf(someType).count()), - * // We nest a Template. This creates a TemplateToken, which is later evaluated. - * // By the time the TemplateToken is evaluated, the tokens from above will - * // be already evaluated. Hence, "v1" and "v2" are added by then, and if the - * // "otherTemplate" were to count the DataNames, the count would be increased - * // by 2 compared to "c1". - * otherTemplate.asToken() - * ), - * // After closing the scope, "v2" is no longer available. - * // The count is still the same as "c1", since "v1" is still only a token. - * let("c4", dataNames(MUTABLE).exactOf(someType).count()), - * // We nest another Template. Again, this creates a TemplateToken, which is only - * // evaluated later. By that time, the token for "v1" is evaluated, and so the - * // nested Template would observe an increment in the count. - * anotherTemplate.asToken() - * // By this point, all methods are called, and the tokens generated. - * // The lambda returns the "body", which is all of the tokens that we just - * // generated. After returning from the lambda, the tokens will be evaluated - * // one by one. - * )); - * } - + * The writer of recursive {@link Template}s must ensure that this recursion terminates. To unify the + * approach across {@link Template}s, we introduce the concept of {@link #fuel}. Templates are rendered starting + * with a limited amount of {@link #fuel} (default: 100, see {@link #DEFAULT_FUEL}), which is decreased at each + * Template nesting by a certain amount (default: 10, see {@link #DEFAULT_FUEL_COST}). The default fuel for a + * template can be changed when we {@code render()} it (e.g. {@link ZeroArgs#render(float)}) and the default + * fuel cost with {@link #setFuelCost}) when defining the {@link #scope(Object...)}. Recursive templates are + * supposed to terminate once the {@link #fuel} is depleted (i.e. reaches zero). + * + *

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

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

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

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

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

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

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

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

* {@snippet lang=java : - * var template = Template.make(() -> body( + * var template = Template.make(() -> scope( * """ * Multi-line string * """, @@ -608,14 +620,200 @@ static Template.ThreeArgs make(String arg1Name, String * )); * } * + *

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

- * The {@link Template#body} and {@link Hook#anchor} are given a list of tokens, which are either + * The {@link Template#scope} and {@link Hook#anchor} are given a list of tokens, which are either * {@link Token}s or {@link String}s or some permitted boxed primitives. These are then parsed * and all non-{@link Token}s are converted to {@link StringToken}s. The parsing also flattens * {@link List}s. diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/library/Expression.java b/test/hotspot/jtreg/compiler/lib/template_framework/library/Expression.java index 360937c8f7fc..43ab16af415b 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/library/Expression.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/library/Expression.java @@ -33,7 +33,7 @@ import compiler.lib.template_framework.Template; import compiler.lib.template_framework.TemplateToken; -import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.scope; /** * {@link Expression}s model Java expressions, that have a list of arguments with specified @@ -357,7 +357,7 @@ public TemplateToken asToken(List arguments) { } tokens.add(strings.getLast()); - var template = Template.make(() -> body( + var template = Template.make(() -> scope( tokens )); return template.asToken(); diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/library/PrimitiveType.java b/test/hotspot/jtreg/compiler/lib/template_framework/library/PrimitiveType.java index 46a9d5bbabe6..c0db3d51545c 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/library/PrimitiveType.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/library/PrimitiveType.java @@ -33,7 +33,7 @@ import compiler.lib.template_framework.DataName; import compiler.lib.template_framework.Template; import compiler.lib.template_framework.TemplateToken; -import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.scope; /** * The {@link PrimitiveType} models Java's primitive types, and provides a set @@ -190,7 +190,7 @@ public Object callLibraryRNG() { * @return a TemplateToken that holds all the {@code LibraryRNG} class. */ public static TemplateToken generateLibraryRNG() { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( """ public static class LibraryRNG { private static final Random RANDOM = Utils.getRandomInstance(); diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/library/TestFrameworkClass.java b/test/hotspot/jtreg/compiler/lib/template_framework/library/TestFrameworkClass.java index 5194b75af43d..a9db9285b78d 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/library/TestFrameworkClass.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/library/TestFrameworkClass.java @@ -30,7 +30,7 @@ import compiler.lib.compile_framework.CompileFramework; import compiler.lib.template_framework.Template; import compiler.lib.template_framework.TemplateToken; -import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.scope; import static compiler.lib.template_framework.Template.let; /** @@ -51,7 +51,7 @@ public final class TestFrameworkClass { private TestFrameworkClass() {} /** - * This method renders a list of {@code testTemplateTokens} into the body of a class + * This method renders a list of {@code testTemplateTokens} into the scope of a class * and generates a {@code main} method which launches the {@link TestFramework} * to run the generated tests. * @@ -81,7 +81,7 @@ public static String render(final String packageName, final Set imports, final String classpath, final List testTemplateTokens) { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( let("packageName", packageName), let("className", className), let("classpath", classpath), @@ -96,7 +96,7 @@ public static String render(final String packageName, public class #className { // --- CLASS_HOOK insertions start --- """, - Hooks.CLASS_HOOK.anchor( + Hooks.CLASS_HOOK.anchor(scope( """ // --- CLASS_HOOK insertions end --- public static void main(String[] vmFlags) { @@ -108,7 +108,7 @@ public static void main(String[] vmFlags) { // --- LIST OF TESTS start --- """, testTemplateTokens - ), + )), """ // --- LIST OF TESTS end --- } diff --git a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestAdvanced.java b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestAdvanced.java index c5a4528f63d4..784f1ded0655 100644 --- a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestAdvanced.java +++ b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestAdvanced.java @@ -43,7 +43,7 @@ import compiler.lib.compile_framework.*; import compiler.lib.template_framework.Template; -import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.scope; import static compiler.lib.template_framework.Template.let; /** @@ -96,7 +96,7 @@ public static String generate(CompileFramework comp) { // - The GOLD value is computed at the beginning, hopefully by the interpreter. // - The test method is eventually compiled, and the values are verified by the // check method. - var testTemplate = Template.make("typeName", "operator", "generator", (String typeName, String operator, MyGenerator generator) -> body( + var testTemplate = Template.make("typeName", "operator", "generator", (String typeName, String operator, MyGenerator generator) -> scope( let("con1", generator.next()), let("con2", generator.next()), """ @@ -116,7 +116,7 @@ public static String generate(CompileFramework comp) { )); // Template for the Class. - var classTemplate = Template.make("types", (List types) -> body( + var classTemplate = Template.make("types", (List types) -> scope( let("classpath", comp.getEscapedClassPathOfCompiledClasses()), """ package p.xyz; diff --git a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestExpressions.java b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestExpressions.java index 6e11a7050540..44ad32c9001a 100644 --- a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestExpressions.java +++ b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestExpressions.java @@ -40,7 +40,7 @@ import compiler.lib.compile_framework.*; import compiler.lib.template_framework.Template; import compiler.lib.template_framework.TemplateToken; -import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.scope; import static compiler.lib.template_framework.Template.let; import compiler.lib.template_framework.library.Expression; import compiler.lib.template_framework.library.Operations; @@ -71,7 +71,7 @@ public static String generate(CompileFramework comp) { // Create a token: fill the expression with a fixed set of constants. // We then use the same token with the same constants, once compiled and once not compiled. TemplateToken expressionToken = expression.asToken(expression.argumentTypes.stream().map(t -> t.con()).toList()); - return body( + return scope( let("returnType", expression.returnType), """ @Test diff --git a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestPrimitiveTypes.java b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestPrimitiveTypes.java index a04a5771cb49..b1f5f74e682c 100644 --- a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestPrimitiveTypes.java +++ b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestPrimitiveTypes.java @@ -41,7 +41,8 @@ import compiler.lib.compile_framework.*; import compiler.lib.template_framework.Template; import compiler.lib.template_framework.TemplateToken; -import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.scope; +import static compiler.lib.template_framework.Template.transparentScope; import static compiler.lib.template_framework.Template.dataNames; import static compiler.lib.template_framework.Template.let; import static compiler.lib.template_framework.Template.$; @@ -77,7 +78,7 @@ public static String generate() { Map tests = new HashMap<>(); // The boxing tests check if we can autobox with "boxedTypeName". - var boxingTemplate = Template.make("name", "type", (String name, PrimitiveType type) -> body( + var boxingTemplate = Template.make("name", "type", (String name, PrimitiveType type) -> scope( let("CON1", type.con()), let("CON2", type.con()), let("Boxed", type.boxedTypeName()), @@ -99,7 +100,7 @@ public static String generate() { } // Integral and Float types have a size. Also test if "isFloating" is correct. - var integralFloatTemplate = Template.make("name", "type", (String name, PrimitiveType type) -> body( + var integralFloatTemplate = Template.make("name", "type", (String name, PrimitiveType type) -> scope( let("size", type.byteSize()), let("isFloating", type.isFloating()), """ @@ -129,27 +130,31 @@ public static String generate() { // Finally, test the type by creating some DataNames (variables), and sampling // from them. There should be no cross-over between the types. - var variableTemplate = Template.make("type", (PrimitiveType type) -> body( + // IMPORTANT: since we are adding the DataName via an inserted Template, we + // must chose a "transparentScope", so that the DataName escapes. If we + // instead chose "scope", the test would fail, because it later + // finds no DataNames when we sample. + var variableTemplate = Template.make("type", (PrimitiveType type) -> transparentScope( let("CON", type.con()), - addDataName($("var"), type, MUTABLE), + addDataName($("var"), type, MUTABLE), // escapes the Template """ #type $var = #CON; """ )); - var sampleTemplate = Template.make("type", (PrimitiveType type) -> body( - let("var", dataNames(MUTABLE).exactOf(type).sample().name()), + var sampleTemplate = Template.make("type", (PrimitiveType type) -> scope( let("CON", type.con()), + dataNames(MUTABLE).exactOf(type).sampleAndLetAs("var"), """ #var = #CON; """ )); - var namesTemplate = Template.make(() -> body( + var namesTemplate = Template.make(() -> scope( """ public static void test_names() { """, - Hooks.METHOD_HOOK.anchor( + Hooks.METHOD_HOOK.anchor(scope( Collections.nCopies(10, CodeGenerationDataNameType.PRIMITIVE_TYPES.stream().map(type -> Hooks.METHOD_HOOK.insert(variableTemplate.asToken(type)) @@ -161,7 +166,7 @@ public static void test_names() { Collections.nCopies(10, CodeGenerationDataNameType.PRIMITIVE_TYPES.stream().map(sampleTemplate::asToken).toList() ) - ), + )), """ } """ @@ -172,7 +177,7 @@ public static void test_names() { // Test runtime random value generation with LibraryRNG // Runtime random number generation of a given primitive type can be very helpful // when writing tests that require random inputs. - var libraryRNGWithTypeTemplate = Template.make("type", (PrimitiveType type) -> body( + var libraryRNGWithTypeTemplate = Template.make("type", (PrimitiveType type) -> scope( """ { // Fill an array with 1_000 random values. Every type has at least 2 values, @@ -196,7 +201,7 @@ public static void test_names() { """ )); - var libraryRNGTemplate = Template.make(() -> body( + var libraryRNGTemplate = Template.make(() -> scope( // Make sure we instantiate the LibraryRNG class. PrimitiveType.generateLibraryRNG(), // Now we can use it inside the test. @@ -213,7 +218,7 @@ public static void test_LibraryRNG() { // Finally, put all the tests together in a class, and invoke all // tests from the main method. - var template = Template.make(() -> body( + var template = Template.make(() -> scope( """ package p.xyz; diff --git a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestSimple.java b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestSimple.java index e06671ca9518..c8afb34e4235 100644 --- a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestSimple.java +++ b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestSimple.java @@ -34,7 +34,7 @@ import compiler.lib.compile_framework.*; import compiler.lib.template_framework.Template; -import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.scope; public class TestSimple { @@ -61,7 +61,7 @@ public static void main(String[] args) { // Generate a source Java file as String public static String generate() { // Create a Template with two arguments. - var template = Template.make("arg1", "arg2", (Integer arg1, String arg2) -> body( + var template = Template.make("arg1", "arg2", (Integer arg1, String arg2) -> scope( """ package p.xyz; public class InnerTest { diff --git a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestTutorial.java b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestTutorial.java index faa05b29d827..ed542180bad3 100644 --- a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestTutorial.java +++ b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestTutorial.java @@ -43,7 +43,9 @@ import compiler.lib.template_framework.TemplateBinding; import compiler.lib.template_framework.DataName; import compiler.lib.template_framework.StructuralName; -import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.scope; +import static compiler.lib.template_framework.Template.transparentScope; +import static compiler.lib.template_framework.Template.hashtagScope; import static compiler.lib.template_framework.Template.let; import static compiler.lib.template_framework.Template.$; import static compiler.lib.template_framework.Template.fuel; @@ -68,13 +70,14 @@ public static void main(String[] args) { comp.addJavaSourceCode("p.xyz.InnerTest2", generateWithTemplateArguments()); comp.addJavaSourceCode("p.xyz.InnerTest3", generateWithHashtagAndDollarReplacements()); comp.addJavaSourceCode("p.xyz.InnerTest3b", generateWithHashtagAndDollarReplacements2()); + comp.addJavaSourceCode("p.xyz.InnerTest3c", generateWithHashtagAndDollarReplacements3()); comp.addJavaSourceCode("p.xyz.InnerTest4", generateWithCustomHooks()); comp.addJavaSourceCode("p.xyz.InnerTest5", generateWithLibraryHooks()); comp.addJavaSourceCode("p.xyz.InnerTest6", generateWithRecursionAndBindingsAndFuel()); comp.addJavaSourceCode("p.xyz.InnerTest7", generateWithDataNamesSimple()); comp.addJavaSourceCode("p.xyz.InnerTest8", generateWithDataNamesForFieldsAndVariables()); - comp.addJavaSourceCode("p.xyz.InnerTest9a", generateWithDataNamesAndScopes1()); - comp.addJavaSourceCode("p.xyz.InnerTest9b", generateWithDataNamesAndScopes2()); + comp.addJavaSourceCode("p.xyz.InnerTest9a", generateWithScopes1()); + comp.addJavaSourceCode("p.xyz.InnerTest9b", generateWithScopes2()); comp.addJavaSourceCode("p.xyz.InnerTest10", generateWithDataNamesForFuzzing()); comp.addJavaSourceCode("p.xyz.InnerTest11", generateWithStructuralNamesForMethods()); @@ -91,6 +94,7 @@ public static void main(String[] args) { comp.invoke("p.xyz.InnerTest2", "main", new Object[] {}); comp.invoke("p.xyz.InnerTest3", "main", new Object[] {}); comp.invoke("p.xyz.InnerTest3b", "main", new Object[] {}); + comp.invoke("p.xyz.InnerTest3c", "main", new Object[] {}); comp.invoke("p.xyz.InnerTest4", "main", new Object[] {}); comp.invoke("p.xyz.InnerTest5", "main", new Object[] {}); comp.invoke("p.xyz.InnerTest6", "main", new Object[] {}); @@ -105,9 +109,9 @@ public static void main(String[] args) { // This example shows the use of various Tokens. public static String generateWithListOfTokens() { // A Template is essentially a function / lambda that produces a - // token body, which is a list of Tokens that are concatenated. - var templateClass = Template.make(() -> body( - // The "body" method is filled by a sequence of "Tokens". + // scope, which contains a list of Tokens that are concatenated. + var templateClass = Template.make(() -> scope( + // The "scope" arguments are a sequence of "Tokens". // These can be Strings and multi-line Strings, but also // boxed primitives. """ @@ -141,14 +145,14 @@ public static void main() { // This example shows the use of Templates, with and without arguments. public static String generateWithTemplateArguments() { // A Template with no arguments. - var templateHello = Template.make(() -> body( + var templateHello = Template.make(() -> scope( """ System.out.println("Hello"); """ )); // A Template with a single Integer argument. - var templateCompare = Template.make("arg", (Integer arg) -> body( + var templateCompare = Template.make("arg", (Integer arg) -> scope( "System.out.println(", arg, ");\n", // capture arg via lambda argument "System.out.println(#arg);\n", // capture arg via hashtag replacement "System.out.println(#{arg});\n", // capture arg via hashtag replacement with brackets @@ -156,7 +160,7 @@ public static String generateWithTemplateArguments() { // argument values into Strings. However, since these are not (yet) // available, the Template Framework provides two alternative ways of // formatting Strings: - // 1) By appending to the comma-separated list of Tokens passed to body(). + // 1) By appending to the comma-separated list of Tokens passed to scope(). // Appending as a Token works whenever one has a reference to the Object // in Java code. But often, this is rather cumbersome and looks awkward, // given all the additional quotes and commands required. Hence, it @@ -180,7 +184,7 @@ public static String generateWithTemplateArguments() { // A Template that creates the body of the Class and main method, and then // uses the two Templates above inside it. - var templateClass = Template.make(() -> body( + var templateClass = Template.make(() -> scope( """ package p.xyz; @@ -204,8 +208,16 @@ public static void main() { // Note: hashtag replacements are a workaround for the missing string templates. // If we had string templates, we could just capture the typed lambda // arguments, and use them directly in the String via string templating. + // + // Important: hashtag replacements are always constrained to a single template + // and are not available in any nested templates. Hashtag replacements + // are only there to facilitate string templating within the limited + // scope of a template. You may consider it like a "local variable" + // for code generation purposes only. + // If you need to pass some value to a nested Template, consider using + // a Template argument, and capturing that Template argument. public static String generateWithHashtagAndDollarReplacements() { - var template1 = Template.make("x", (Integer x) -> body( + var template1 = Template.make("x", (Integer x) -> scope( // We have the "#x" hashtag replacement from the argument capture above. // Additionally, we can define "#con" as a hashtag replacement from let: let("con", 3 * x), @@ -219,29 +231,27 @@ public static String generateWithHashtagAndDollarReplacements() { """ )); - var template2 = Template.make("x", (Integer x) -> + var template2 = Template.make("x", (Integer x) -> scope( // Sometimes it can be helpful to not just create a hashtag replacement // with let, but also to capture the variable to use it as lambda parameter. - let("y", 11 * x, y -> - body( - """ - System.out.println("T2: #x, #y"); - """, - template1.asToken(y) - ) - ) - ); + let("y", 11 * x, y -> scope( + """ + System.out.println("T2: #x, #y"); + """, + template1.asToken(y) + )) + )); // This template generates an int variable and assigns it a value. // Together with template4, we see that each template has a unique renaming // for a $-name replacement. - var template3 = Template.make("name", "value", (String name, Integer value) -> body( + var template3 = Template.make("name", "value", (String name, Integer value) -> scope( """ int #name = #value; // Note: $var is not #name """ )); - var template4 = Template.make(() -> body( + var template4 = Template.make(() -> scope( """ // We will define the variable $var: """, @@ -252,7 +262,7 @@ public static String generateWithHashtagAndDollarReplacements() { """ )); - var templateClass = Template.make(() -> body( + var templateClass = Template.make(() -> scope( // The Template Framework API only guarantees that every Template use // has a unique ID. When using the Templates, all we need is that // variables from different Template uses do not conflict. But it can @@ -300,7 +310,7 @@ public static void main() { // "INT_CON" and "LONG_CON". public static String generateWithHashtagAndDollarReplacements2() { // Let us define some final static variables of a specific type. - var template1 = Template.make("type", (String type) -> body( + var template1 = Template.make("type", (String type) -> scope( // The type (e.g. "int") is lower case, let us create the upper case "INT_CON" from it. let("TYPE", type.toUpperCase()), """ @@ -309,7 +319,7 @@ public static String generateWithHashtagAndDollarReplacements2() { )); // Let's write a simple class to demonstrate that this works, i.e. produces compilable code. - var templateClass = Template.make(() -> body( + var templateClass = Template.make(() -> scope( """ package p.xyz; @@ -331,50 +341,221 @@ public static void main() { return templateClass.render(); } + // We already have used "scope" multiple times, but not explained it yet. + // So far, we have seen "scope" mostly in the context of Template scopes, but they + // can be used in many contexts as we will see below. They can also be used on + // their own and in the use of "let", as we will show right now. + // + // Scopes are even more relevant for DataNames and Structural names. + // See: generateWithDataNamesForFieldsAndVariables + // See: generateWithScopes1 + // See: generateWithScopes2 + public static String generateWithHashtagAndDollarReplacements3() { + + var template1 = Template.make(() -> scope( + // We can use scopes to limit the liveness of hashtag replacements. + scope( + let("x", 3), // does not escape + """ + static int v1_3 = #x; + """ + ), + scope( + let("x", 5), // does not escape + """ + static int v1_5 = #x; + """ + ), + // Using "scope" does not just limit the liveness / availability + // of hashtag replacements, but also of DataNames, StructuralNames, + // and setFuelCost. We can use "hashtagScope" to only limit hashtag + // replacements. + hashtagScope( + let("x", 7), // does not escape + """ + static int v1_7 = #x; + """ + ), + // Using "transparentScope" means the scope is transparent, and the hashtag + // replacements escape the scope. + transparentScope( + let("x", 11), // escapes the "transparentScope". + """ + static int v1_11a = #x; + """ + ), + // The hashtag replacement from the "transparentScope" escaped, and is + // still available. + """ + static int v1_11b = #x; + """ + )); + + var template2 = Template.make("x", (Integer x) -> scope( + // We can map a list of values to a list of scopes. Using a scope that is + // non-transparent for hashtag replacements means that we can reuse the same + // hashtag key when looping / streaming over multiple values. + List.of(3, 5, 7).stream().map(y -> scope( + let("y", y), // does not escape -> allows reuse of hashtag key "y". + """ + static int v2_#{x}_#{y} = #x * #y; + """ + )).toList() + )); + + var template3 = Template.make("x", (Integer x) -> scope( + // When using a "let" that captures the value in a lambda argument, we have + // to choose what kind of scope we generate. In most cases "scope" or + // "hashtagScope" are the best, because they limit the hashtag replacement + // of "y" to the same scope as the lambda argument. + let("y", x * 11, y -> scope( + """ + static int v3a_#{x} = #y; + """ + )), + // But in rare cases, we may want "y" and some nested "z" to escape. + let("y", x * 11, y -> transparentScope( + let("z", y * 2), + """ + static int v3b_#{x} = #y - #z; + """ + )), + // Because of the "transparentScope", "y" and "z" have escaped. + """ + static int v3c_#{x} = #y - #z; + """, + // Side note: We can simulate a "let" without lambda with a "let" that has a lambda. + // That is not very useful, but a similar trick can be used for other queries, that + // only provide a lambda version, and where we only want to use the hashtag replacement. + // + // Below we see the standard use of "let", where we add a hashtag replacement for "a" + // for the rest of the enclosing scope. We then also use a lambda version of "let" + // with a transparent scope, which means that "b" escapes that scope and is also + // available in the enclosing scope. In the implementation of the framework, we + // actually use a "transparentScope", so the standard "let" is really just syntactic + // sugar for the lambda "let" with "transparentScope". + let("a", -x), + let("b", -x, b -> transparentScope()), + """ + static int v3d_#{x} = #a + #b; + """ + )); + + // Let's write a simple class to demonstrate that this works, i.e. produces compilable code. + var templateClass = Template.make(() -> scope( + """ + package p.xyz; + + public class InnerTest3c { + """, + template1.asToken(), + template2.asToken(1), + template2.asToken(2), + template3.asToken(2), + """ + public static void main() { + if (v1_3 != 3 || + v1_5 != 5 || + v1_7 != 7 || + v1_11a != 11 || + v1_11b != 11 || + v2_1_3 != 3 || + v2_1_5 != 5 || + v2_1_7 != 7 || + v2_2_3 != 6 || + v2_2_5 != 10 || + v2_2_7 != 14 || + v3a_2 != 22 || + v3b_2 != -22 || + v3c_2 != -22 || + v3d_2 != -4) { + throw new RuntimeException("Wrong result!"); + } + } + } + """ + )); + + // Render templateClass to String. + return templateClass.render(); + } + // In this example, we look at the use of Hooks. They allow us to reach back, to outer // scopes. For example, we can reach out from inside a method body to a hook anchored at // the top of the class, and insert a field. + // + // When we insert to a hook, we have 3 relevant scopes: + // - Anchor scope: the scope defined at "hook.anchor(scope(...))" + // - Insertion scope: the scope that is inserted, see "hook.insert(scope(...))" + // - Caller scope: the scope we insert from. + // + // The choice of transparency of an insertion scope (the scope that is inserted) is quite + // important. A common use case is to insert a DataName. + // See: generateWithDataNamesForFieldsAndVariables + // See: generateWithScopes1 + // See: generateWithScopes2 public static String generateWithCustomHooks() { // We can define a custom hook. // Note: generally we prefer using the pre-defined CLASS_HOOK and METHOD_HOOK from the library, // whenever possible. See also the example after this one. var myHook = new Hook("MyHook"); - var template1 = Template.make("name", "value", (String name, Integer value) -> body( + var template1 = Template.make("name", "value", (String name, Integer value) -> scope( """ public static int #name = #value; """ )); - var template2 = Template.make("x", (Integer x) -> body( + var template2 = Template.make("x", (Integer x) -> scope( + """ + // Let us go back to where we anchored the hook with anchor() (see 'templateClass' below) and define a field + // named $field1 there. + """, + myHook.insert(scope( // <- insertion scope + """ + public static int $field1 = #x; + """ + // Note that we were able to use the dollar replacement "$field1" and the hashtag + // replacement "#x" inside the scope that is inserted to myHook. + )), """ - // Let us go back to where we anchored the hook with anchor() and define a field named $field there. - // Note that in the Java code we have not defined anchor() on the hook, yet. But since it's a lambda - // expression, it is not evaluated, yet! Eventually, anchor() will be evaluated before insert() in - // this example. + // We can do that by inserting a scope like above, or by inserting a template, like below. + // + // Which method is used is up to the user. General guidance is if the same code may also + // be inserted elsewhere, one should lean towards inserting templates. But in many cases + // it is nice to see the inserted code directly, and to be able to use hashtag replacements + // from the outer scope directly, without having to route them via template arguments, + // as we have to do below. """, - myHook.insert(template1.asToken($("field"), x)), + // <- caller scope + myHook.insert(template1.asToken($("field2"), x)), """ - System.out.println("$field: " + $field); - if ($field != #x) { throw new RuntimeException("Wrong value!"); } + System.out.println("$field1: " + $field1); + System.out.println("$field2: " + $field2); + if ($field1 != #x) { throw new RuntimeException("Wrong value 1!"); } + if ($field2 != #x) { throw new RuntimeException("Wrong value 2!"); } """ )); - var templateClass = Template.make(() -> body( + var templateClass = Template.make(() -> scope( """ package p.xyz; public class InnerTest4 { """, // We anchor a Hook outside the main method, but inside the Class. - // Anchoring a Hook creates a scope, spanning the braces of the - // "anchor" call. Any Hook.insert that happens inside this scope - // goes to the top of that scope. - myHook.anchor( + // Anchoring a Hook requires the definition of an inner scope, + // aka the "anchor scope", spanning the braces of the "anchor" call. + // Any Hook.insert that happens inside this scope goes to the top of + // that scope. + myHook.anchor(scope( // <- anchor scope // Any Hook.insert goes here. // - // <-------- field_X = 5 ------------------+ - // <-------- field_Y = 7 -------------+ | + // <-------- field1_X = 5 -----------------+ + // field2_X = 5 | + // | + // <-------- field1_Y = 7 ------------+ | + // field2_Y = 7 | | // | | """ public static void main() { @@ -384,7 +565,7 @@ public static void main() { """ } """ - ), // The Hook scope ends here. + )), // The Hook scope ends here. """ } """ @@ -408,46 +589,54 @@ public static void main() { // there is a class scope inside another class scope. Similarly, we can nest lambda bodies // inside method bodies, so also METHOD_HOOK can be used in such a "re-entrant" way. public static String generateWithLibraryHooks() { - var templateStaticField = Template.make("name", "value", (String name, Integer value) -> body( - """ - static { System.out.println("Defining static field #name"); } - public static int #name = #value; - """ - )); - - var templateLocalVariable = Template.make("name", "value", (String name, Integer value) -> body( - """ - System.out.println("Defining local variable #name"); - int #name = #value; - """ - )); - var templateMethodBody = Template.make(() -> body( + var templateMethodBody = Template.make(() -> scope( """ // Let's define a local variable $var and a static field $field. - """, - Hooks.CLASS_HOOK.insert(templateStaticField.asToken($("field"), 5)), - Hooks.METHOD_HOOK.insert(templateLocalVariable.asToken($("var"), 11)), - """ + // Since we are inserting them at the anchor before the code below, + // they will already be available: System.out.println("$field: " + $field); System.out.println("$var: " + $var); + """, + Hooks.CLASS_HOOK.insert(scope( + """ + static { System.out.println("Defining static field $field"); } + public static int $field = 5; + """ + )), + Hooks.METHOD_HOOK.insert(scope( + """ + System.out.println("Defining local variable $var"); + int $var = 11; + """ + )), + """ if ($field * $var != 55) { throw new RuntimeException("Wrong value!"); } """ + // Note: we have used "scope" for the "insert" scope. This is fine here as + // we are only working with code and hashtags, but not with DataNames. If + // we were to also "addDataName" inside the insert scope, we would have to + // make sure that the scope is transparent for DataNames, so that they can + // escape to the anchor scope, and can be available to the caller of the + // insertion. One might want to use "transparentScope" for the insertion scope. + // See: generateWithDataNamesForFieldsAndVariables. + // See: generateWithScopes1 + // See: generateWithScopes2 )); - var templateClass = Template.make(() -> body( + var templateClass = Template.make(() -> scope( """ package p.xyz; public class InnerTest5 { """, // Class Hook for fields. - Hooks.CLASS_HOOK.anchor( + Hooks.CLASS_HOOK.anchor(scope( """ public static void main() { """, // Method Hook for local variables, and earlier computations. - Hooks.METHOD_HOOK.anchor( + Hooks.METHOD_HOOK.anchor(scope( """ // This is the beginning of the "main" method body. System.out.println("Welcome to main!"); @@ -457,7 +646,7 @@ public static void main() { System.out.println("Going to call other..."); other(); """ - ), + )), """ } @@ -465,7 +654,7 @@ private static void other() { """, // Have a separate method hook for other, so that it can insert // its own local variables. - Hooks.METHOD_HOOK.anchor( + Hooks.METHOD_HOOK.anchor(scope( """ System.out.println("Welcome to other!"); """, @@ -473,11 +662,11 @@ private static void other() { """ System.out.println("Done with other."); """ - ), + )), """ } """ - ), + )), """ } """ @@ -493,7 +682,7 @@ private static void other() { public static String generateWithRecursionAndBindingsAndFuel() { // Binding allows the use of template1 inside of template1, via the binding indirection. var binding1 = new TemplateBinding>(); - var template1 = Template.make("depth", (Integer depth) -> body( + var template1 = Template.make("depth", (Integer depth) -> scope( let("fuel", fuel()), """ System.out.println("At depth #depth with fuel #fuel."); @@ -514,7 +703,7 @@ public static String generateWithRecursionAndBindingsAndFuel() { )); binding1.bind(template1); - var templateClass = Template.make(() -> body( + var templateClass = Template.make(() -> scope( """ package p.xyz; @@ -561,6 +750,12 @@ public static void main() { // // To get started, we show an example where all DataNames have the same type, and where // all Names are mutable. For simplicity, our type represents the primitive int type. + // + // Note: the template library contains a lot of types that model the Java types, + // such as primitive types ({@code PrimitiveType}). The following examples + // give insight into how those types work. If you are just interested in + // how to use the predefined types, then you can find other examples in + // {@code examples/TestPrimitiveTypes.java}. private record MySimpleInt() implements DataName.Type { // The type is only subtype of itself. This is relevant when sampling or weighing // DataNames, because we do not just sample from the given type, but also its subtypes. @@ -577,31 +772,25 @@ public boolean isSubtypeOf(DataName.Type other) { private static final MySimpleInt mySimpleInt = new MySimpleInt(); // In this example, we generate 3 fields, and add their names to the - // current scope. In a nested Template, we can then sample one of these - // DataNames, which gives us one of the fields. We increment that randomly - // chosen field. At the end, we print all three fields. + // current scope. We can then sample some of these DataNames, which + // gives us one of those fields each time. We increment those randomly + // chosen fields. At the end, we print all three fields. public static String generateWithDataNamesSimple() { - var templateMain = Template.make(() -> body( - // Sample a random DataName, i.e. field, and assign its name to - // the hashtag replacement "#f". - // We are picking a mutable DataName, because we are not just - // reading but also writing to the field. - let("f", dataNames(MUTABLE).exactOf(mySimpleInt).sample().name()), - """ - // Let us now sample a random field #f, and increment it. - #f += 42; - """ - )); - - var templateClass = Template.make(() -> body( + var templateClass = Template.make(() -> scope( // Let us define the names for the three fields. - // We can then sample from these names in a nested Template. // We make all DataNames mutable, and with the same weight of 1, // so that they have equal probability of being sampled. // Note: the default weight is 1, so we can also omit the weight. + // + // Also note that DataNames are only available once they are defined: + // + // Nothing defined, yet: dataNames() = {} addDataName($("f1"), mySimpleInt, MUTABLE, 1), + // Only now dataNames() contains f1: dataNames() = {f1} addDataName($("f2"), mySimpleInt, MUTABLE, 1), + // dataNames() = {f1, f2} addDataName($("f3"), mySimpleInt, MUTABLE), // omit weight, default is 1. + // dataNames() = {f1, f2, f3} """ package p.xyz; @@ -612,18 +801,35 @@ public class InnerTest7 { public static int $f3 = 0; public static void main() { - // Let us now call the nested template that samples - // a random field and increments it. + // Let us now sample a random field and assign its name to + // the hashtag replacement "a". """, - templateMain.asToken(), + dataNames(MUTABLE).exactOf(mySimpleInt).sampleAndLetAs("a"), + """ + // We can now access the field, and increment it. + #a += 42; + // If we are also interested in the type of the field, we can do: + """, + dataNames(MUTABLE).exactOf(mySimpleInt).sampleAndLetAs("b", "bType"), + """ + #b += 7; + // In some cases, we may want to capture the DataName directly, which + // requires capturing the value in a lambda that creates an inner scope: + """, + dataNames(MUTABLE).exactOf(mySimpleInt).sample((DataName dn) -> scope( + let("c", dn.name()), + """ + #c += 12; + """ + )), """ // Now, we can print all three fields, and see which - // one was incremented. + // ones were incremented. System.out.println("f1: " + $f1); System.out.println("f2: " + $f2); System.out.println("f3: " + $f3); - // We have two zeros, and one 42. - if ($f1 + $f2 + $f3 != 42) { throw new RuntimeException("wrong result!"); } + // Make sure they add up to the correct sum. + if ($f1 + $f2 + $f3 != 42 + 7 + 12) { throw new RuntimeException("wrong result!"); } } } """ @@ -662,8 +868,15 @@ public boolean isSubtypeOf(DataName.Type other) { public static String generateWithDataNamesForFieldsAndVariables() { // Define a static field. - var templateStaticField = Template.make("type", (DataName.Type type) -> body( - addDataName($("field"), type, MUTABLE), + // Note: it is very important that we use a "transparentScope" for the template here, + // so that the DataName can escape to outer scopes, so that it is available to + // everything that follows the DataName definition in the outer scope. + // (We could also use "hashtagScope", since those are also transparent for + // names. But it is not great style, because template boundaries are + // non-transparent for hashtags and setFuelCost anyway. So we might as + // well just use "transparentScope".) + var templateStaticField = Template.make("type", (DataName.Type type) -> transparentScope( + addDataName($("field"), type, MUTABLE), // escapes template because of "transparentScope" // Note: since we have overridden MyPrimitive::toString, we can use // the type directly as "#type" in the template, which then // gets hashtag replaced with "int" or "long". @@ -673,8 +886,10 @@ public static String generateWithDataNamesForFieldsAndVariables() { )); // Define a local variable. - var templateLocalVariable = Template.make("type", (DataName.Type type) -> body( - addDataName($("var"), type, MUTABLE), + // Note: it is very important that we use a "transparentScope" for the template here, + // so that the DataName can escape to outer scopes. + var templateLocalVariable = Template.make("type", (DataName.Type type) -> transparentScope( + addDataName($("var"), type, MUTABLE), // escapes template because of "transparentScope" """ #type $var = 0; """ @@ -682,8 +897,8 @@ public static String generateWithDataNamesForFieldsAndVariables() { // Sample a random field or variable, from those that are available at // the current scope. - var templateSample = Template.make("type", (DataName.Type type) -> body( - let("name", dataNames(MUTABLE).exactOf(type).sample().name()), + var templateSample = Template.make("type", (DataName.Type type) -> scope( + dataNames(MUTABLE).exactOf(type).sampleAndLetAs("name"), // Note: we could also sample from MUTABLE_OR_IMMUTABLE, we will // cover the concept of mutability in an example further down. """ @@ -692,18 +907,36 @@ public static String generateWithDataNamesForFieldsAndVariables() { )); // Check how many fields and variables are available at the current scope. - var templateStatus = Template.make(() -> body( - let("ints", dataNames(MUTABLE).exactOf(myInt).count()), - let("longs", dataNames(MUTABLE).exactOf(myLong).count()), - // Note: we could also count the MUTABLE_OR_IMMUTABLE, we will - // cover the concept of mutability in an example further down. + var templateStatus = Template.make(() -> scope( + dataNames(MUTABLE).exactOf(myInt).count(ints -> scope( + dataNames(MUTABLE).exactOf(myLong).count(longs -> scope( + // We have now captured the values as Java variables, and can + // use them inside the scope in some "let" definitions. + let("ints", ints), + let("longs", longs), + // Note: we could also count the MUTABLE_OR_IMMUTABLE, we will + // cover the concept of mutability in an example further down. + """ + System.out.println("Status: #ints ints, #longs longs."); + """ + )) + )), + // In a real code generation case, we would most likely want to + // have the count as a Java variable so that one can take conditional + // action based on the value. For that we have to capture the count + // with a lambda and inner scope as above. If we only need to have + // the count as a hashtag replacement, we can also use the following + // trick: + dataNames(MUTABLE).exactOf(myInt).count(c -> transparentScope(let("ints", c))), + dataNames(MUTABLE).exactOf(myLong).count(c -> transparentScope(let("longs", c))), + // Because of the "transparentScope", the hashtag replacements escape. """ System.out.println("Status: #ints ints, #longs longs."); """ )); // Definition of the main method body. - var templateMain = Template.make(() -> body( + var templateMain = Template.make(() -> scope( """ System.out.println("Starting inside main..."); """, @@ -736,7 +969,7 @@ public static String generateWithDataNamesForFieldsAndVariables() { // Definition of another method's body. It is in the same class // as the main method, so it has access to the same static fields. - var templateOther = Template.make(() -> body( + var templateOther = Template.make(() -> scope( """ System.out.println("Starting inside other..."); """, @@ -755,19 +988,19 @@ public static String generateWithDataNamesForFieldsAndVariables() { )); // Finally, we put it all together in a class. - var templateClass = Template.make(() -> body( + var templateClass = Template.make(() -> scope( """ package p.xyz; public class InnerTest8 { """, // Class Hook for fields. - Hooks.CLASS_HOOK.anchor( + Hooks.CLASS_HOOK.anchor(scope( """ public static void main() { """, // Method Hook for local variables. - Hooks.METHOD_HOOK.anchor( + Hooks.METHOD_HOOK.anchor(scope( """ // This is the beginning of the "main" method body. System.out.println("Welcome to main!"); @@ -777,7 +1010,7 @@ public static void main() { System.out.println("Going to call other..."); other(); """ - ), + )), """ } @@ -785,7 +1018,7 @@ private static void other() { """, // Have a separate method hook for other, where it could insert // its own local variables (but happens not to). - Hooks.METHOD_HOOK.anchor( + Hooks.METHOD_HOOK.anchor(scope( """ System.out.println("Welcome to other!"); """, @@ -793,11 +1026,11 @@ private static void other() { """ System.out.println("Done with other."); """ - ), + )), """ } """ - ), + )), """ } """ @@ -807,83 +1040,119 @@ private static void other() { return templateClass.render(); } - // Let us have a closer look at how DataNames interact with scopes created by - // Templates and Hooks. Additionally, we see how the execution order of the - // lambdas and token evaluation affects the availability of DataNames. - // - // We inject the results directly into verification inside the code, so it - // is relatively simple to see what the expected results are. - // - // For simplicity, we define a simple "list" function. It collects all - // field and variable names, and immediately returns the comma separated - // list of the names. We can use that to visualize the available names - // at any point. - public static String listNames() { - return "{" + String.join(", ", dataNames(MUTABLE).exactOf(myInt).toList() - .stream().map(DataName::name).toList()) + "}"; - } - - // Even simpler: count the available variables and return the count immediately. - public static int countNames() { - return dataNames(MUTABLE).exactOf(myInt).count(); - } - - // Having defined these helper methods, let us start with the first example. - // You should start reading this example bottom-up, starting at - // templateClass, then going to templateMain and last to templateInner. - public static String generateWithDataNamesAndScopes1() { - - var templateInner = Template.make(() -> body( - // We just got called from the templateMain. All tokens from there - // are already evaluated, so "v1" is now available: - let("l1", listNames()), - """ - if (!"{v1}".equals("#l1")) { throw new RuntimeException("l1 should have been '{v1}' but was '#l1'"); } + public static String generateWithScopes1() { + + // For the examples below, we need a convenient way of asserting the state + // of the available DataNames. + var templateVerify = Template.make("count", "hasAny", "toList", (Integer count, Boolean hasAny, String toList) -> scope( + dataNames(MUTABLE).exactOf(myInt).count(c -> transparentScope(let("count2", c))), + dataNames(MUTABLE).exactOf(myInt).hasAny(h -> transparentScope(let("hasAny2", h))), + dataNames(MUTABLE).exactOf(myInt).toList(list -> transparentScope( + let("toList2", String.join(", ", list.stream().map(DataName::name).toList())) + )), + """ + if (#count != #count2 || + #hasAny != #hasAny2 || + !"#toList".equals("#toList2")) { + throw new RuntimeException("verify failed"); + } """ )); - var templateMain = Template.make(() -> body( - // So far, no names were defined. We expect "c1" to be zero. - let("c1", countNames()), - """ - if (#c1 != 0) { throw new RuntimeException("c1 was not zero but #c1"); } - """, - // We now add a local variable "v1" to the scope of this templateMain. - // This only generates a token, and does not immediately add the name. - // The name is only added once we evaluate the tokens, and arrive at - // this particular token. + var templateMain = Template.make(() -> scope( + "// Start with nothing:\n", + templateVerify.asToken(0, false, ""), + "// Add v1:\n", addDataName("v1", myInt, MUTABLE), - // We count again with "c2". The variable "v1" is at this point still - // in token form, hence it is not yet made available while executing - // the template lambda of templateMain. - let("c2", countNames()), - """ - if (#c2 != 0) { throw new RuntimeException("c2 was not zero but #c2"); } + "int v1 = 1;\n", + "// Check that it is visible:\n", + templateVerify.asToken(1, true, "v1"), + "// Add v2:\n", + addDataName("v2", myInt, MUTABLE), + "int v2 = 2;\n", + "// Check that both are visible:\n", + templateVerify.asToken(2, true, "v1, v2"), + + "// Create a local scope:\n", + "{\n", scope( // for consistency, we model the code and template scope together. + "// Add v3:\n", + addDataName("v3", myInt, MUTABLE), + "int v3 = 3;\n", + "// Check that all are visible:\n", + templateVerify.asToken(3, true, "v1, v2, v3") + ), "}\n", + "// But after the scope, v3 is no longer available:\n", + templateVerify.asToken(2, true, "v1, v2"), + + "// Now let's create a list of variables.\n", + List.of(4, 5, 6).stream().map(i -> hashtagScope( + // The hashtagScope allows hashtag replacements to be local, + // and DataNames to escape, so we can use them afterwards. + let("i", i), + addDataName("v" + i, myInt, MUTABLE), + "int v#i = #i;\n" + )).toList(), + templateVerify.asToken(5, true, "v1, v2, v4, v5, v6"), + + "// Let's multiply all variables by a factor of 2, using forEach:\n", + dataNames(MUTABLE).exactOf(myInt).forEach(dn -> scope( + let("v", dn.name()), + "#v *= 2;\n" + )), + "// We can also capture the name (v) and type of the DataName:\n", + dataNames(MUTABLE).exactOf(myInt).forEach("v", "type", dn -> scope( + "#v *= 2;\n" + )), + "// Yet another option is using toList, but here that is more cumbersome:\n", + dataNames(MUTABLE).exactOf(myInt).toList(list -> scope( + list.stream().map(dn -> scope( + let("v", dn.name()), + "#v *= 2;\n" + )).toList() + )), + + """ + // We verify the result again. + """, + templateVerify.asToken(5, true, "v1, v2, v4, v5, v6"), + """ + if (v1 != 1 * 8 || + v2 != 2 * 8 || + v4 != 4 * 8 || + v5 != 5 * 8 || + v6 != 6 * 8) { + throw new RuntimeException("wrong value!"); + } """, - // But now we call an inner Template. This is added as a TemplateToken. - // This means it is not evaluated immediately, but only once we evaluate - // the tokens. By that time, all tokens from above are already evaluated - // and we see that "v1" is available. - templateInner.asToken() + + "// Let us copy each variable:\n", + dataNames(MUTABLE).exactOf(myInt).forEach("v", "type", dn -> hashtagScope( + // Note that we need a hashtagScope here, so that we can reuse "v" and + // "type" as hashtag replacements in each iteration, but still let the + // copied DataNames escape. + addDataName(dn.name() + "_copy", myInt, MUTABLE), + "#type #{v}_copy = #v;\n" + )), + templateVerify.asToken(10, true, "v1, v2, v4, v5, v6, v1_copy, v2_copy, v4_copy, v5_copy, v6_copy") )); - var templateClass = Template.make(() -> body( + var templateClass = Template.make(() -> scope( """ package p.xyz; public class InnerTest9a { """, - Hooks.CLASS_HOOK.anchor( + Hooks.CLASS_HOOK.anchor(scope( """ public static void main() { """, - Hooks.METHOD_HOOK.anchor( + Hooks.METHOD_HOOK.anchor(scope( templateMain.asToken() - ), + )), """ } """ - ), + )), """ } """ @@ -893,111 +1162,129 @@ public static void main() { return templateClass.render(); } - // Now that we understand this simple example, we go to a more complicated one - // where we use Hook.insert. Just as above, you should read this example - // bottom-up, starting at templateClass. - public static String generateWithDataNamesAndScopes2() { + public static String generateWithScopes2() { - var templateFields = Template.make(() -> body( - // We were just called from templateMain. But the code is not - // generated into the main scope, rather into the class scope - // out in templateClass. - // Let us now add a field "f1". - addDataName("f1", myInt, MUTABLE), - // And let's also generate the code for it. - """ - public static int f1 = 42; - """, - // But why is this DataName now available inside the scope of - // templateInner? Does that not mean that "f1" escapes this - // templateFields here? Yes it does! - // For normal template nesting, the names do not escape the - // scope of the nested template. But this here is no normal - // template nesting, rather it is an insertion into a Hook, - // and we treat those differently. We make the scope of the - // inserted templateFields transparent, so that any added - // DataNames are added to the scope of the Hook we just - // inserted into, i.e. the CLASS_HOOK. This is very important, - // if we did not make that scope transparent, we could not - // add any DataNames to the class scope anymore, and we could - // not add any fields that would be available in the class - // scope. - Hooks.METHOD_HOOK.anchor( - // We now create a separate scope. This one is not the - // template scope from above, and it is not transparent. - // Hence, "f2" will not be available outside of this - // scope. - addDataName("f2", myInt, MUTABLE), - // And let's also generate the code for it. - """ - public static int f2 = 666; - """ - // Similarly, if we called any nested Template here, - // and added DataNames inside, this would happen inside - // nested scopes that are not transparent. If one wanted - // to add names to the CLASS_HOOK from there, one would - // have to do another Hook.insert, and make sure that - // the names are added from the outermost scope of that - // inserted Template, because only that outermost scope - // is transparent to the CLASS_HOOK. - ) - )); + // In this section, we will look at some subtle facts about the behavior of + // transparent scopes around hook insertion. This is intended for expert users + // so feel free to skip it until you extensively use hook insertion. + // More info can also be found in the Javadocs of the Hook class. - var templateInner = Template.make(() -> body( - // We just got called from the templateMain. All tokens from there - // are already evaluated, so there should be some fields available. - // We can see field "f1". - let("l1", listNames()), + // Helper method to check that the expected DataNames are available. + var templateVerify = Template.make("toList", (String toList) -> scope( + dataNames(MUTABLE).exactOf(myInt).toList(list -> transparentScope( + let("toList2", String.join(", ", list.stream().map(DataName::name).toList())) + )), """ - if (!"{f1}".equals("#l1")) { throw new RuntimeException("l1 should have been '{f1}' but was '#l1'"); } + if (!"#toList".equals("#toList2")) { + throw new RuntimeException("verify failed: '#toList' vs '#toList2'."); + } """ - // Now go and have a look at templateFields, to understand how that - // field was added, and why not any others. )); - var templateMain = Template.make(() -> body( - // So far, no names were defined. We expect "c1" to be zero. - let("c1", countNames()), - """ - if (#c1 != 0) { throw new RuntimeException("c1 was not zero but #c1"); } - """, - // We would now like to add some fields to the class scope, out in the - // templateClass. This creates a token, which is only evaluated after - // the completion of the templateMain lambda. Before you go and look - // at templateFields, just assume that it does add some fields, and - // continue reading in templateMain. - Hooks.CLASS_HOOK.insert(templateFields.asToken()), - // We count again with "c2". The fields we wanted to add above are not - // yet available, because the token is not yet evaluated. Hence, we - // still only count zero names. - let("c2", countNames()), - """ - if (#c2 != 0) { throw new RuntimeException("c2 was not zero but #c2"); } - """, - // Now we call an inner Template. This also creates a token, and so it - // is not evaluated immediately. And by the time this token is evaluated - // the tokens from above are already evaluated, and so the fields should - // be available. Go have a look at templateInner now. - templateInner.asToken() + var myHook = new Hook("MyHook"); + + var templateMain = Template.make(() -> scope( + // Start with nothing: + templateVerify.asToken(""), + addDataName("v1", myInt, MUTABLE), + templateVerify.asToken("v1"), + // Non-transparent hook anchor: + myHook.anchor(scope( + templateVerify.asToken("v1"), + addDataName("v2", myInt, MUTABLE), + templateVerify.asToken("v1, v2"), + // Insert a non-transparent scope: nothing escapes. + myHook.insert(scope( + // Note that at the anchor insertion point, v2 is not yet + // available, because it is added after the anchoring. + templateVerify.asToken("v1"), + let("x3", 42), + addDataName("v3", myInt, MUTABLE), + templateVerify.asToken("v1, v3") + )), + // Note: x3 and v3 do not escape. + let("x3", 7), // we can define it again. + templateVerify.asToken("v1, v2"), + // While not letting hashtags escape may be helpful, it is probably + // not very helpful if the DataNames don't escape. For example, if + // we are inserting some variable at an outer scope, we would like + // it to be available for the rest of the scope. + // That's where a transparent scope can be helpful. + myHook.insert(transparentScope( + // At the anchoring, still only v1 is available. + templateVerify.asToken("v1"), + let("x4", 42), // escapes to caller scope + addDataName("v4", myInt, MUTABLE), // escapes to anchor scope + templateVerify.asToken("v1, v4") + )), + // x4 escapes to the caller out here, and not to the anchor scope. + "// x4: #x4\n", + // And v4 escapes to the anchor scope, which is available from here too. + // Interesting detail: the ordering in the list indicates that v1 + // is from the outermost scope of the template, v4 is located at the + // anchor scope, and v2 is located inside the anchor scope, and + // thus comes last. + templateVerify.asToken("v1, v4, v2"), + // In most practical cases we probably don't want to let the hashtag + // escape, because they just represent something local. So we can + // use a hashtagScope, so that DataNames escape, but not hashtags. + myHook.insert(hashtagScope( + // Note: both v1 and v4 are now available at the anchoring, since + // v1 was inserted outside the anchoring scope, and v4 was just + // inserted to the anchoring scope. + templateVerify.asToken("v1, v4"), + let("x5", 42), // local, does not escape. + addDataName("v5", myInt, MUTABLE), // escapes to anchor scope + templateVerify.asToken("v1, v4, v5") + )), + let("x5", 7), // we can define it again. + templateVerify.asToken("v1, v4, v5, v2") + )), + // We left the non-transparent anchoring scope which does not let anything escape + templateVerify.asToken("v1"), + + // Let us now do something that probably should never be done. But still + // we want to demonstrate it for educational purposes: transparent anchoring + // scopes. + myHook.anchor(transparentScope( + templateVerify.asToken("v1"), + // For one, this means that DataName escape the scope directly. + addDataName("v6", myInt, MUTABLE), + templateVerify.asToken("v1, v6"), + // But also if we insert to the anchoring scope, DataNames don't just + // escape from the anchoring scope, but further out to the enclosing + // scope. + myHook.insert(transparentScope( + templateVerify.asToken("v1, v6"), + addDataName("v7", myInt, MUTABLE), + templateVerify.asToken("v1, v6, v7") + )), + templateVerify.asToken("v1, v6, v7"), + let("x6", 42) // escapes the anchor scope + )), + // We left the transparent anchoring scope which lets the DataNames and + // hashtags escape. + "// x6: #x6\n", + templateVerify.asToken("v1, v6, v7") )); - var templateClass = Template.make(() -> body( + var templateClass = Template.make(() -> scope( """ package p.xyz; public class InnerTest9b { """, - Hooks.CLASS_HOOK.anchor( + Hooks.CLASS_HOOK.anchor(scope( """ public static void main() { """, - Hooks.METHOD_HOOK.anchor( + Hooks.METHOD_HOOK.anchor(scope( templateMain.asToken() - ), + )), """ } """ - ), + )), """ } """ @@ -1006,8 +1293,6 @@ public static void main() { // Render templateClass to String. return templateClass.render(); } - - // There are two more concepts to understand more deeply with DataNames. // // One is the use of mutable and immutable DataNames. @@ -1045,38 +1330,40 @@ public boolean isSubtypeOf(DataName.Type other) { private static final List myClassList = List.of(myClassA, myClassA1, myClassA2, myClassA11, myClassB); public static String generateWithDataNamesForFuzzing() { - var templateStaticField = Template.make("type", "mutable", (DataName.Type type, Boolean mutable) -> body( - addDataName($("field"), type, mutable ? MUTABLE : IMMUTABLE), + // This template is used to insert a DataName (field) into an outer scope, hence we must use + // "transparentScope" instead of "scope". + var templateStaticField = Template.make("type", "mutable", (DataName.Type type, Boolean mutable) -> transparentScope( + addDataName($("field"), type, mutable ? MUTABLE : IMMUTABLE), // Escapes the template. let("isFinal", mutable ? "" : "final"), """ public static #isFinal #type $field = new #type(); """ )); - var templateLoad = Template.make("type", (DataName.Type type) -> body( + var templateLoad = Template.make("type", (DataName.Type type) -> scope( // We only load from the field, so we do not need a mutable one, // we can load from final and non-final fields. // We want to find any field from which we can read the value and store // it in our variable v of our given type. Hence, we can take a field // of the given type or any subtype thereof. - let("field", dataNames(MUTABLE_OR_IMMUTABLE).subtypeOf(type).sample().name()), + dataNames(MUTABLE_OR_IMMUTABLE).subtypeOf(type).sampleAndLetAs("field"), """ #type $v = #field; System.out.println("#field: " + $v); """ )); - var templateStore = Template.make("type", (DataName.Type type) -> body( + var templateStore = Template.make("type", (DataName.Type type) -> scope( // We are storing to a field, so it better be non-final, i.e. mutable. // We want to store a new instance of our given type to a field. This // field must be of the given type or any supertype. - let("field", dataNames(MUTABLE).supertypeOf(type).sample().name()), + dataNames(MUTABLE).supertypeOf(type).sampleAndLetAs("field"), """ #field = new #type(); """ )); - var templateClass = Template.make(() -> body( + var templateClass = Template.make(() -> scope( """ package p.xyz; @@ -1094,7 +1381,7 @@ public static class MyClassB {} // addDataName is restricted to the scope of the templateStaticField. But // with the insertion to CLASS_HOOK, the addDataName goes through the scope // of the templateStaticField out to the scope of the CLASS_HOOK. - Hooks.CLASS_HOOK.anchor( + Hooks.CLASS_HOOK.anchor(scope( myClassList.stream().map(c -> (Object)Hooks.CLASS_HOOK.insert(templateStaticField.asToken(c, true)) ).toList(), @@ -1118,7 +1405,7 @@ public static void main() { """ } """ - ), + )), """ } """ @@ -1126,7 +1413,6 @@ public static void main() { // Render templateClass to String. return templateClass.render(); - } // "DataNames" are useful for modeling fields and variables. They hold data, @@ -1165,9 +1451,9 @@ public boolean isSubtypeOf(StructuralName.Type other) { public static String generateWithStructuralNamesForMethods() { // Define a method, which takes two ints, returns the result of op. - var templateMethod = Template.make("op", (String op) -> body( + var templateMethod = Template.make("op", (String op) -> transparentScope( // Register the method name, so we can later sample. - addStructuralName($("methodName"), myMethodType), + addStructuralName($("methodName"), myMethodType), // escapes the template because of "transparentScope" """ public static int $methodName(int a, int b) { return a #op b; @@ -1175,16 +1461,16 @@ public static String generateWithStructuralNamesForMethods() { """ )); - var templateSample = Template.make(() -> body( + var templateSample = Template.make(() -> scope( // Sample a random method, and retrieve its name. - let("methodName", structuralNames().exactOf(myMethodType).sample().name()), + structuralNames().exactOf(myMethodType).sampleAndLetAs("methodName"), """ System.out.println("Calling #methodName with inputs 7 and 11"); System.out.println(" result: " + #methodName(7, 11)); """ )); - var templateClass = Template.make(() -> body( + var templateClass = Template.make(() -> scope( """ package p.xyz; @@ -1192,7 +1478,7 @@ public class InnerTest11 { // Let us define some methods that we can sample from later. """, // We must anchor a CLASS_HOOK here, and insert the method definitions to that hook. - Hooks.CLASS_HOOK.anchor( + Hooks.CLASS_HOOK.anchor(scope( // If we directly nest the templateMethod, then the addStructuralName goes to the nested // scope, and is not available at the class scope, i.e. it is not visible // for sampleStructuralName outside of the templateMethod. @@ -1218,7 +1504,7 @@ public static void main() { } } """ - ) + )) )); // Render templateClass to String. diff --git a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestWithTestFrameworkClass.java b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestWithTestFrameworkClass.java index 813f2976ef25..01b49db2c013 100644 --- a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestWithTestFrameworkClass.java +++ b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestWithTestFrameworkClass.java @@ -43,7 +43,7 @@ import compiler.lib.template_framework.Template; import compiler.lib.template_framework.TemplateToken; -import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.scope; import static compiler.lib.template_framework.Template.let; import compiler.lib.template_framework.library.Hooks; @@ -82,7 +82,7 @@ public static void main(String[] args) { // Generate a source Java file as String public static String generate(CompileFramework comp) { // A simple template that adds a comment. - var commentTemplate = Template.make(() -> body( + var commentTemplate = Template.make(() -> scope( """ // Comment inserted from test method to class hook. """ @@ -103,7 +103,7 @@ public static String generate(CompileFramework comp) { // - The test method makes use of hashtag replacements (#con2 and #op). // - The Check method verifies the results of the test method with the // GOLD value. - var testTemplate = Template.make("op", (String op) -> body( + var testTemplate = Template.make("op", (String op) -> scope( let("size", Generators.G.safeRestrict(Generators.G.ints(), 10_000, 20_000).next()), let("con1", Generators.G.ints().next()), let("con2", Generators.G.safeRestrict(Generators.G.ints(), 1, Integer.MAX_VALUE).next()), diff --git a/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestExpression.java b/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestExpression.java index 2dac740dd936..b34538c39c16 100644 --- a/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestExpression.java +++ b/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestExpression.java @@ -38,7 +38,7 @@ import compiler.lib.template_framework.DataName; import compiler.lib.template_framework.Template; import compiler.lib.template_framework.TemplateToken; -import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.scope; import compiler.lib.template_framework.library.CodeGenerationDataNameType; import compiler.lib.template_framework.library.Expression; @@ -93,7 +93,7 @@ public static void testAsToken() { Expression e3 = Expression.make(myTypeA, "[", myTypeA, ",", myTypeB, ",", myTypeA1, "]"); Expression e4 = Expression.make(myTypeA, "[", myTypeA, ",", myTypeB, ",", myTypeA1, ",", myTypeA, "]"); - var template = Template.make(() -> body( + var template = Template.make(() -> scope( "xx", e1.toString(), "yy\n", "xx", e2.toString(), "yy\n", "xx", e3.toString(), "yy\n", @@ -141,7 +141,7 @@ public static void testNest() { Expression e3e1 = e3.nest(0, e1); Expression e4e5 = e4.nest(1, e5); - var template = Template.make(() -> body( + var template = Template.make(() -> scope( "xx", e1e1.toString(), "yy\n", "xx", e2e1.toString(), "yy\n", "xx", e3e1.toString(), "yy\n", @@ -184,7 +184,7 @@ public static void testNestRandomly() { // Alternating pattern Expression deep2 = Expression.nestRandomly(myTypeA, List.of(e5, e3), 5); - var template = Template.make(() -> body( + var template = Template.make(() -> scope( "xx", e1e2.toString(), "yy\n", "xx", e1ex.toString(), "yy\n", "xx", e1e4.toString(), "yy\n", diff --git a/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestFormat.java b/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestFormat.java index fe267a3ff638..577542e085ba 100644 --- a/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestFormat.java +++ b/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestFormat.java @@ -39,7 +39,7 @@ import compiler.lib.generators.*; import compiler.lib.verify.*; import compiler.lib.template_framework.Template; -import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.scope; import static compiler.lib.template_framework.Template.let; public class TestFormat { @@ -84,7 +84,7 @@ public static void main(String[] args) { private static String generate(List list) { // Generate 2 "get" methods, one that formats via "let" (hashtag), the other via direct token. - var template1 = Template.make("info", (FormatInfo info) -> body( + var template1 = Template.make("info", (FormatInfo info) -> scope( let("id", info.id()), let("type", info.type()), let("value", info.value()), @@ -95,7 +95,7 @@ private static String generate(List list) { )); // For each FormatInfo in list, generate the "get" methods inside InnerTest class. - var template2 = Template.make(() -> body( + var template2 = Template.make(() -> scope( """ package p.xyz; public class InnerTest { diff --git a/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestTemplate.java b/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestTemplate.java index 35d020b60802..9be74d232a76 100644 --- a/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestTemplate.java +++ b/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestTemplate.java @@ -44,7 +44,11 @@ import compiler.lib.template_framework.Hook; import compiler.lib.template_framework.TemplateBinding; import compiler.lib.template_framework.RendererException; -import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.scope; +import static compiler.lib.template_framework.Template.transparentScope; +import static compiler.lib.template_framework.Template.nameScope; +import static compiler.lib.template_framework.Template.hashtagScope; +import static compiler.lib.template_framework.Template.setFuelCostScope; import static compiler.lib.template_framework.Template.$; import static compiler.lib.template_framework.Template.let; import static compiler.lib.template_framework.Template.fuel; @@ -121,41 +125,56 @@ public static void main(String[] args) { // The following tests all pass, i.e. have no errors during rendering. testSingleLine(); testMultiLine(); - testBodyTokens(); + testBasicTokens(); testWithOneArgument(); testWithTwoArguments(); testWithThreeArguments(); - testNested(); - testHookSimple(); + testNestedTemplates(); + testHookSimple1(); + testHookSimple2(); + testHookSimple3(); testHookIsAnchored(); testHookNested(); testHookWithNestedTemplates(); testHookRecursion(); testDollar(); - testLet(); + testLet1(); + testLet2(); testDollarAndHashtagBrackets(); testSelector(); testRecursion(); testFuel(); testFuelCustom(); + testFuelAndScopes(); + testDataNames0a(); + testDataNames0b(); + testDataNames0c(); + testDataNames0d(); testDataNames1(); testDataNames2(); testDataNames3(); testDataNames4(); testDataNames5(); + testDataNames6(); + testStructuralNames0(); testStructuralNames1(); testStructuralNames2(); + testStructuralNames3(); + testStructuralNames4(); + testStructuralNames5(); + testStructuralNames6(); testListArgument(); + testNestedScopes1(); + testNestedScopes2(); + testTemplateScopes(); + testHookAndScopes1(); + testHookAndScopes2(); + testHookAndScopes3(); // The following tests should all fail, with an expected exception and message. expectRendererException(() -> testFailingNestedRendering(), "Nested render not allowed."); expectRendererException(() -> $("name"), "A Template method such as"); - expectRendererException(() -> let("x","y"), "A Template method such as"); expectRendererException(() -> fuel(), "A Template method such as"); - expectRendererException(() -> setFuelCost(1.0f), "A Template method such as"); - expectRendererException(() -> dataNames(MUTABLE_OR_IMMUTABLE).exactOf(myInt).count(), "A Template method such as"); - expectRendererException(() -> dataNames(MUTABLE_OR_IMMUTABLE).exactOf(myInt).sample(), "A Template method such as"); - expectRendererException(() -> (new Hook("abc")).isAnchored(), "A Template method such as"); expectRendererException(() -> testFailingDollarName1(), "Is not a valid '$' name: ''."); expectRendererException(() -> testFailingDollarName2(), "Is not a valid '$' name: '#abc'."); expectRendererException(() -> testFailingDollarName3(), "Is not a valid '$' name: 'abc#'."); @@ -178,20 +197,31 @@ public static void main(String[] args) { expectRendererException(() -> testFailingDollarHashtagName3(), "Is not a valid '#' replacement pattern: '#' in '#$name'."); expectRendererException(() -> testFailingDollarHashtagName4(), "Is not a valid '$' replacement pattern: '$' in '$#name'."); expectRendererException(() -> testFailingHook(), "Hook 'Hook1' was referenced but not found!"); - expectRendererException(() -> testFailingSample1(), "No variable: MUTABLE, subtypeOf(int), supertypeOf(int)."); + expectRendererException(() -> testFailingSample1a(), "No Name found for DataName.FilterdSet(MUTABLE, subtypeOf(int), supertypeOf(int))"); + expectRendererException(() -> testFailingSample1b(), "No Name found for StructuralName.FilteredSet( subtypeOf(StructuralA) supertypeOf(StructuralA))"); expectRendererException(() -> testFailingHashtag1(), "Duplicate hashtag replacement for #a"); expectRendererException(() -> testFailingHashtag2(), "Duplicate hashtag replacement for #a"); expectRendererException(() -> testFailingHashtag3(), "Duplicate hashtag replacement for #a"); expectRendererException(() -> testFailingHashtag4(), "Missing hashtag replacement for #a"); + expectRendererException(() -> testFailingHashtag5(), "Missing hashtag replacement for #a"); expectRendererException(() -> testFailingBinding1(), "Duplicate 'bind' not allowed."); expectRendererException(() -> testFailingBinding2(), "Cannot 'get' before 'bind'."); - expectIllegalArgumentException(() -> body(null), "Unexpected tokens: null"); - expectIllegalArgumentException(() -> body("x", null), "Unexpected token: null"); - expectIllegalArgumentException(() -> body(new Hook("Hook1")), "Unexpected token:"); + expectIllegalArgumentException(() -> scope(null), "Unexpected tokens: null"); + expectIllegalArgumentException(() -> scope("x", null), "Unexpected token: null"); + expectIllegalArgumentException(() -> scope(new Hook("Hook1")), "Unexpected token:"); + expectIllegalArgumentException(() -> transparentScope(null), "Unexpected tokens: null"); + expectIllegalArgumentException(() -> transparentScope("x", null), "Unexpected token: null"); + expectIllegalArgumentException(() -> transparentScope(new Hook("Hook1")), "Unexpected token:"); + expectIllegalArgumentException(() -> nameScope(null), "Unexpected tokens: null"); + expectIllegalArgumentException(() -> nameScope("x", null), "Unexpected token: null"); + expectIllegalArgumentException(() -> nameScope(new Hook("Hook1")), "Unexpected token:"); + expectIllegalArgumentException(() -> hashtagScope(null), "Unexpected tokens: null"); + expectIllegalArgumentException(() -> hashtagScope("x", null), "Unexpected token: null"); + expectIllegalArgumentException(() -> hashtagScope(new Hook("Hook1")), "Unexpected token:"); + expectIllegalArgumentException(() -> setFuelCostScope(null), "Unexpected tokens: null"); + expectIllegalArgumentException(() -> setFuelCostScope("x", null), "Unexpected token: null"); + expectIllegalArgumentException(() -> setFuelCostScope(new Hook("Hook1")), "Unexpected token:"); Hook hook1 = new Hook("Hook1"); - expectIllegalArgumentException(() -> hook1.anchor(null), "Unexpected tokens: null"); - expectIllegalArgumentException(() -> hook1.anchor("x", null), "Unexpected token: null"); - expectIllegalArgumentException(() -> hook1.anchor(hook1), "Unexpected token:"); expectIllegalArgumentException(() -> testFailingAddDataName1(), "Unexpected mutability: MUTABLE_OR_IMMUTABLE"); expectIllegalArgumentException(() -> testFailingAddDataName2(), "Unexpected weight: "); expectIllegalArgumentException(() -> testFailingAddDataName3(), "Unexpected weight: "); @@ -199,7 +229,8 @@ public static void main(String[] args) { expectIllegalArgumentException(() -> testFailingAddStructuralName1(), "Unexpected weight: "); expectIllegalArgumentException(() -> testFailingAddStructuralName2(), "Unexpected weight: "); expectIllegalArgumentException(() -> testFailingAddStructuralName3(), "Unexpected weight: "); - expectUnsupportedOperationException(() -> testFailingSample2(), "Must first call 'subtypeOf', 'supertypeOf', or 'exactOf'."); + expectUnsupportedOperationException(() -> testFailingSample2a(), "Must first call 'subtypeOf', 'supertypeOf', or 'exactOf'."); + expectUnsupportedOperationException(() -> testFailingSample2b(), "Must first call 'subtypeOf', 'supertypeOf', or 'exactOf'."); expectRendererException(() -> testFailingAddNameDuplication1(), "Duplicate name:"); expectRendererException(() -> testFailingAddNameDuplication2(), "Duplicate name:"); expectRendererException(() -> testFailingAddNameDuplication3(), "Duplicate name:"); @@ -208,16 +239,23 @@ public static void main(String[] args) { expectRendererException(() -> testFailingAddNameDuplication6(), "Duplicate name:"); expectRendererException(() -> testFailingAddNameDuplication7(), "Duplicate name:"); expectRendererException(() -> testFailingAddNameDuplication8(), "Duplicate name:"); + expectRendererException(() -> testFailingScope1(), "Duplicate hashtag replacement for #x. previous: x1, new: x2"); + expectRendererException(() -> testFailingScope2(), "Duplicate hashtag replacement for #x. previous: x1, new: x2"); + expectRendererException(() -> testFailingScope3(), "Duplicate hashtag replacement for #x. previous: a, new: b"); + expectRendererException(() -> testFailingScope4(), "Duplicate hashtag replacement for #x. previous: a, new: b"); + expectRendererException(() -> testFailingScope5(), "Duplicate name:"); + expectRendererException(() -> testFailingScope6(), "Duplicate name:"); + expectRendererException(() -> testFailingScope7(), "Duplicate name:"); } public static void testSingleLine() { - var template = Template.make(() -> body("Hello World!")); + var template = Template.make(() -> scope("Hello World!")); String code = template.render(); checkEQ(code, "Hello World!"); } public static void testMultiLine() { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( """ Code on more than a single line @@ -232,10 +270,10 @@ public static void testMultiLine() { checkEQ(code, expected); } - public static void testBodyTokens() { - // We can fill the body with Objects of different types, and they get concatenated. - // Lists get flattened into the body. - var template = Template.make(() -> body( + public static void testBasicTokens() { + // We can fill the scope with Objects of different types, and they get concatenated. + // Lists get flattened into the scope. + var template = Template.make(() -> scope( "start ", Integer.valueOf(1), 1, Long.valueOf(2), 2L, @@ -250,31 +288,31 @@ public static void testBodyTokens() { public static void testWithOneArgument() { // Capture String argument via String name. - var template1 = Template.make("a", (String a) -> body("start #a end")); + var template1 = Template.make("a", (String a) -> scope("start #a end")); checkEQ(template1.render("x"), "start x end"); checkEQ(template1.render("a"), "start a end"); checkEQ(template1.render("" ), "start end"); // Capture String argument via typed lambda argument. - var template2 = Template.make("a", (String a) -> body("start ", a, " end")); + var template2 = Template.make("a", (String a) -> scope("start ", a, " end")); checkEQ(template2.render("x"), "start x end"); checkEQ(template2.render("a"), "start a end"); checkEQ(template2.render("" ), "start end"); // Capture Integer argument via String name. - var template3 = Template.make("a", (Integer a) -> body("start #a end")); + var template3 = Template.make("a", (Integer a) -> scope("start #a end")); checkEQ(template3.render(0 ), "start 0 end"); checkEQ(template3.render(22 ), "start 22 end"); checkEQ(template3.render(444), "start 444 end"); // Capture Integer argument via templated lambda argument. - var template4 = Template.make("a", (Integer a) -> body("start ", a, " end")); + var template4 = Template.make("a", (Integer a) -> scope("start ", a, " end")); checkEQ(template4.render(0 ), "start 0 end"); checkEQ(template4.render(22 ), "start 22 end"); checkEQ(template4.render(444), "start 444 end"); // Test Strings with backslashes: - var template5 = Template.make("a", (String a) -> body("start #a " + a + " end")); + var template5 = Template.make("a", (String a) -> scope("start #a " + a + " end")); checkEQ(template5.render("/"), "start / / end"); checkEQ(template5.render("\\"), "start \\ \\ end"); checkEQ(template5.render("\\\\"), "start \\\\ \\\\ end"); @@ -282,25 +320,25 @@ public static void testWithOneArgument() { public static void testWithTwoArguments() { // Capture 2 String arguments via String names. - var template1 = Template.make("a1", "a2", (String a1, String a2) -> body("start #a1 #a2 end")); + var template1 = Template.make("a1", "a2", (String a1, String a2) -> scope("start #a1 #a2 end")); checkEQ(template1.render("x", "y"), "start x y end"); checkEQ(template1.render("a", "b"), "start a b end"); checkEQ(template1.render("", "" ), "start end"); // Capture 2 String arguments via typed lambda arguments. - var template2 = Template.make("a1", "a2", (String a1, String a2) -> body("start ", a1, " ", a2, " end")); + var template2 = Template.make("a1", "a2", (String a1, String a2) -> scope("start ", a1, " ", a2, " end")); checkEQ(template2.render("x", "y"), "start x y end"); checkEQ(template2.render("a", "b"), "start a b end"); checkEQ(template2.render("", "" ), "start end"); // Capture 2 Integer arguments via String names. - var template3 = Template.make("a1", "a2", (Integer a1, Integer a2) -> body("start #a1 #a2 end")); + var template3 = Template.make("a1", "a2", (Integer a1, Integer a2) -> scope("start #a1 #a2 end")); checkEQ(template3.render(0, 1 ), "start 0 1 end"); checkEQ(template3.render(22, 33 ), "start 22 33 end"); checkEQ(template3.render(444, 555), "start 444 555 end"); // Capture 2 Integer arguments via templated lambda arguments. - var template4 = Template.make("a1", "a2", (Integer a1, Integer a2) -> body("start ", a1, " ", a2, " end")); + var template4 = Template.make("a1", "a2", (Integer a1, Integer a2) -> scope("start ", a1, " ", a2, " end")); checkEQ(template4.render(0, 1 ), "start 0 1 end"); checkEQ(template4.render(22, 33 ), "start 22 33 end"); checkEQ(template4.render(444, 555), "start 444 555 end"); @@ -308,46 +346,46 @@ public static void testWithTwoArguments() { public static void testWithThreeArguments() { // Capture 3 String arguments via String names. - var template1 = Template.make("a1", "a2", "a3", (String a1, String a2, String a3) -> body("start #a1 #a2 #a3 end")); + var template1 = Template.make("a1", "a2", "a3", (String a1, String a2, String a3) -> scope("start #a1 #a2 #a3 end")); checkEQ(template1.render("x", "y", "z"), "start x y z end"); checkEQ(template1.render("a", "b", "c"), "start a b c end"); checkEQ(template1.render("", "", "" ), "start end"); // Capture 3 String arguments via typed lambda arguments. - var template2 = Template.make("a1", "a2", "a3", (String a1, String a2, String a3) -> body("start ", a1, " ", a2, " ", a3, " end")); + var template2 = Template.make("a1", "a2", "a3", (String a1, String a2, String a3) -> scope("start ", a1, " ", a2, " ", a3, " end")); checkEQ(template1.render("x", "y", "z"), "start x y z end"); checkEQ(template1.render("a", "b", "c"), "start a b c end"); checkEQ(template1.render("", "", "" ), "start end"); // Capture 3 Integer arguments via String names. - var template3 = Template.make("a1", "a2", "a3", (Integer a1, Integer a2, Integer a3) -> body("start #a1 #a2 #a3 end")); + var template3 = Template.make("a1", "a2", "a3", (Integer a1, Integer a2, Integer a3) -> scope("start #a1 #a2 #a3 end")); checkEQ(template3.render(0, 1 , 2 ), "start 0 1 2 end"); checkEQ(template3.render(22, 33 , 44 ), "start 22 33 44 end"); checkEQ(template3.render(444, 555, 666), "start 444 555 666 end"); // Capture 2 Integer arguments via templated lambda arguments. - var template4 = Template.make("a1", "a2", "a3", (Integer a1, Integer a2, Integer a3) -> body("start ", a1, " ", a2, " ", a3, " end")); + var template4 = Template.make("a1", "a2", "a3", (Integer a1, Integer a2, Integer a3) -> scope("start ", a1, " ", a2, " ", a3, " end")); checkEQ(template3.render(0, 1 , 2 ), "start 0 1 2 end"); checkEQ(template3.render(22, 33 , 44 ), "start 22 33 44 end"); checkEQ(template3.render(444, 555, 666), "start 444 555 666 end"); } - public static void testNested() { - var template1 = Template.make(() -> body("proton")); + public static void testNestedTemplates() { + var template1 = Template.make(() -> scope("proton")); - var template2 = Template.make("a1", "a2", (String a1, String a2) -> body( + var template2 = Template.make("a1", "a2", (String a1, String a2) -> scope( "electron #a1\n", "neutron #a2\n" )); - var template3 = Template.make("a1", "a2", (String a1, String a2) -> body( + var template3 = Template.make("a1", "a2", (String a1, String a2) -> scope( "Universe ", template1.asToken(), " {\n", template2.asToken("up", "down"), template2.asToken(a1, a2), "}\n" )); - var template4 = Template.make(() -> body( + var template4 = Template.make(() -> scope( template3.asToken("low", "high"), "{\n", template3.asToken("42", "24"), @@ -374,19 +412,45 @@ public static void testNested() { checkEQ(code, expected); } - public static void testHookSimple() { + public static void testHookSimple1() { var hook1 = new Hook("Hook1"); - var template1 = Template.make(() -> body("Hello\n")); + var template1 = Template.make(() -> scope("Hello\n")); - var template2 = Template.make(() -> body( + var template2 = Template.make(() -> scope( "{\n", - hook1.anchor( + hook1.anchor(scope( "World\n", // Note: "Hello" from the template below will be inserted // above "World" above. hook1.insert(template1.asToken()) - ), + )), + "}" + )); + + String code = template2.render(); + String expected = + """ + { + Hello + World + }"""; + checkEQ(code, expected); + } + + public static void testHookSimple2() { + var hook1 = new Hook("Hook1"); + + var template2 = Template.make(() -> scope( + "{\n", + hook1.anchor(scope( + "World\n", + // Note: "Hello" from the scope below will be inserted + // above "World" above. + hook1.insert(scope( + "Hello\n" + )) + )), "}" )); @@ -400,21 +464,59 @@ public static void testHookSimple() { checkEQ(code, expected); } + public static void testHookSimple3() { + var hook1 = new Hook("Hook1"); + + // Ensure that insert inside insert really goes first. + var template = Template.make(() -> scope( + "{\n", + hook1.anchor(scope( + "Outer Insert\n" + )), + ">Anchor\n" + )), + "}" + )); + + String code = template.render(); + String expected = + """ + { + Inner Insert + Outer Insert + Anchor + }"""; + checkEQ(code, expected); + } + public static void testHookIsAnchored() { var hook1 = new Hook("Hook1"); - var template0 = Template.make(() -> body("isAnchored: ", hook1.isAnchored(), "\n")); + var template0 = Template.make(() -> scope("t0 isAnchored: ", hook1.isAnchored(a -> scope(a)), "\n")); - var template1 = Template.make(() -> body("Hello\n", template0.asToken())); + var template1 = Template.make(() -> scope("Hello\n", template0.asToken())); - var template2 = Template.make(() -> body( + var template2 = Template.make(() -> scope( "{\n", + "t2 isAnchored: ", hook1.isAnchored(a -> scope(a)), "\n", template0.asToken(), - hook1.anchor( + hook1.anchor(scope( "World\n", + "t2 isAnchored: ", hook1.isAnchored(a -> scope(a)), "\n", template0.asToken(), - hook1.insert(template1.asToken()) - ), + hook1.insert(template1.asToken()), + hook1.insert(scope("Beautiful\n", template0.asToken())), + "t2 isAnchored: ", hook1.isAnchored(a -> scope(a)), "\n" + )), + "t2 isAnchored: ", hook1.isAnchored(a -> scope(a)), "\n", template0.asToken(), "}" )); @@ -423,12 +525,18 @@ public static void testHookIsAnchored() { String expected = """ { - isAnchored: false + t2 isAnchored: false + t0 isAnchored: false Hello - isAnchored: true + t0 isAnchored: true + Beautiful + t0 isAnchored: true World - isAnchored: true - isAnchored: false + t2 isAnchored: true + t0 isAnchored: true + t2 isAnchored: true + t2 isAnchored: false + t0 isAnchored: false }"""; checkEQ(code, expected); } @@ -436,36 +544,41 @@ public static void testHookIsAnchored() { public static void testHookNested() { var hook1 = new Hook("Hook1"); - var template1 = Template.make("a", (String a) -> body("x #a x\n")); + var template1 = Template.make("a", (String a) -> scope("x #a x\n")); // Test nested use of hooks in the same template. - var template2 = Template.make(() -> body( + var template2 = Template.make(() -> scope( "{\n", - hook1.anchor(), // empty + hook1.anchor(scope()), // empty "zero\n", - hook1.anchor( + hook1.anchor(scope( template1.asToken("one"), template1.asToken("two"), hook1.insert(template1.asToken("intoHook1a")), hook1.insert(template1.asToken("intoHook1b")), + hook1.insert(scope("y 1 y\n")), + hook1.insert(scope("y 2 y\n")), template1.asToken("three"), - hook1.anchor( + hook1.anchor(scope( template1.asToken("four"), hook1.insert(template1.asToken("intoHook1c")), + hook1.insert(scope("y 3 y\n")), template1.asToken("five") - ), + )), template1.asToken("six"), - hook1.anchor(), // empty + hook1.anchor(scope()), // empty template1.asToken("seven"), hook1.insert(template1.asToken("intoHook1d")), + hook1.insert(scope("y 4 y\n")), template1.asToken("eight"), - hook1.anchor( + hook1.anchor(scope( template1.asToken("nine"), hook1.insert(template1.asToken("intoHook1e")), + hook1.insert(scope("y 5 y\n")), template1.asToken("ten") - ), + )), template1.asToken("eleven") - ), + )), "}" )); @@ -476,17 +589,22 @@ public static void testHookNested() { zero x intoHook1a x x intoHook1b x + y 1 y + y 2 y x intoHook1d x + y 4 y x one x x two x x three x x intoHook1c x + y 3 y x four x x five x x six x x seven x x eight x x intoHook1e x + y 5 y x nine x x ten x x eleven x @@ -498,30 +616,30 @@ public static void testHookWithNestedTemplates() { var hook1 = new Hook("Hook1"); var hook2 = new Hook("Hook2"); - var template1 = Template.make("a", (String a) -> body("x #a x\n")); + var template1 = Template.make("a", (String a) -> scope("x #a x\n")); - var template2 = Template.make("b", (String b) -> body( + var template2 = Template.make("b", (String b) -> scope( "{\n", template1.asToken(b + "A"), hook1.insert(template1.asToken(b + "B")), hook2.insert(template1.asToken(b + "C")), template1.asToken(b + "D"), - hook1.anchor( + hook1.anchor(scope( template1.asToken(b + "E"), hook1.insert(template1.asToken(b + "F")), hook2.insert(template1.asToken(b + "G")), template1.asToken(b + "H"), - hook2.anchor( + hook2.anchor(scope( template1.asToken(b + "I"), hook1.insert(template1.asToken(b + "J")), hook2.insert(template1.asToken(b + "K")), template1.asToken(b + "L") - ), + )), template1.asToken(b + "M"), hook1.insert(template1.asToken(b + "N")), hook2.insert(template1.asToken(b + "O")), template1.asToken(b + "O") - ), + )), template1.asToken(b + "P"), hook1.insert(template1.asToken(b + "Q")), hook2.insert(template1.asToken(b + "R")), @@ -530,18 +648,18 @@ public static void testHookWithNestedTemplates() { )); // Test use of hooks across templates. - var template3 = Template.make(() -> body( + var template3 = Template.make(() -> scope( "{\n", "base-A\n", - hook1.anchor( + hook1.anchor(scope( "base-B\n", - hook2.anchor( + hook2.anchor(scope( "base-C\n", template2.asToken("sub-"), "base-D\n" - ), + )), "base-E\n" - ), + )), "base-F\n", "}\n" )); @@ -586,32 +704,32 @@ public static void testHookWithNestedTemplates() { public static void testHookRecursion() { var hook1 = new Hook("Hook1"); - var template1 = Template.make("a", (String a) -> body("x #a x\n")); + var template1 = Template.make("a", (String a) -> scope("x #a x\n")); - var template2 = Template.make("b", (String b) -> body( + var template2 = Template.make("b", (String b) -> scope( "<\n", template1.asToken(b + "A"), hook1.insert(template1.asToken(b + "B")), // sub-B is rendered before template2. template1.asToken(b + "C"), "inner-hook-start\n", - hook1.anchor( + hook1.anchor(scope( "inner-hook-end\n", template1.asToken(b + "E"), hook1.insert(template1.asToken(b + "E")), template1.asToken(b + "F") - ), + )), ">\n" )); // Test use of hooks across templates. - var template3 = Template.make(() -> body( + var template3 = Template.make(() -> scope( "{\n", "hook-start\n", - hook1.anchor( + hook1.anchor(scope( "hook-end\n", hook1.insert(template2.asToken("sub-")), "base-C\n" - ), + )), "base-D\n", "}\n" )); @@ -642,16 +760,16 @@ public static void testHookRecursion() { public static void testDollar() { var hook1 = new Hook("Hook1"); - var template1 = Template.make("a", (String a) -> body("x $name #a x\n")); + var template1 = Template.make("a", (String a) -> scope("x $name #a x\n")); - var template2 = Template.make("a", (String a) -> body( + var template2 = Template.make("a", (String a) -> scope( "{\n", "y $name #a y\n", template1.asToken($("name")), "}\n" )); - var template3 = Template.make(() -> body( + var template3 = Template.make(() -> scope( "{\n", "$name\n", "$name", "\n", @@ -660,11 +778,11 @@ public static void testDollar() { template1.asToken("name"), // does not capture -> literal "$name" template1.asToken("$name"), // does not capture -> literal "$name" template1.asToken($("name")), // capture replacement name "name_1" - hook1.anchor( + hook1.anchor(scope( "$name\n" - ), + )), "break\n", - hook1.anchor( + hook1.anchor(scope( "one\n", hook1.insert(template1.asToken($("name"))), "two\n", @@ -672,7 +790,7 @@ public static void testDollar() { "three\n", hook1.insert(template2.asToken($("name"))), "four\n" - ), + )), "}\n" )); @@ -704,10 +822,10 @@ public static void testDollar() { checkEQ(code, expected); } - public static void testLet() { + public static void testLet1() { var hook1 = new Hook("Hook1"); - var template1 = Template.make("a", (String a) -> body( + var template1 = Template.make("a", (String a) -> scope( "{\n", "y #a y\n", let("b", "<" + a + ">"), @@ -715,25 +833,25 @@ public static void testLet() { "}\n" )); - var template2 = Template.make("a", (Integer a) -> let("b", a * 10, b -> - body( + var template2 = Template.make("a", (Integer a) -> scope( + let("b", a * 10, b -> scope( let("c", b * 3), "abc = #a #b #c\n" - ) + )) )); - var template3 = Template.make(() -> body( + var template3 = Template.make(() -> scope( "{\n", let("x", "abc"), template1.asToken("alpha"), "break\n", "x1 = #x\n", - hook1.anchor( + hook1.anchor(transparentScope( // transparentScope allows hashtags to escape "x2 = #x\n", // leaks inside template1.asToken("beta"), let("y", "one"), "y1 = #y\n" - ), + )), "break\n", "y2 = #y\n", // leaks outside "break\n", @@ -766,8 +884,30 @@ public static void testLet() { checkEQ(code, expected); } + public static void testLet2() { + var template = Template.make(() -> scope( + "outer {\n", + let("x", "x1", x -> scope( + "x: #x ", x, ".\n" + )), + let("x", "x2"), // definition above is limited to its scope + "x: #x\n", + "} outer\n" + )); + + String code = template.render(); + String expected = + """ + outer { + x: x1 x1. + x: x2 + } outer + """; + checkEQ(code, expected); + } + public static void testDollarAndHashtagBrackets() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( let("xyz", "abc"), let("xyz_", "def"), let("xyz_klm", "ghi"), @@ -792,19 +932,19 @@ public static void testDollarAndHashtagBrackets() { } public static void testSelector() { - var template1 = Template.make("a", (String a) -> body( + var template1 = Template.make("a", (String a) -> scope( "<\n", "x #a x\n", ">\n" )); - var template2 = Template.make("a", (String a) -> body( + var template2 = Template.make("a", (String a) -> scope( "<\n", "y #a y\n", ">\n" )); - var template3 = Template.make("a", (Integer a) -> body( + var template3 = Template.make("a", (Integer a) -> scope( "[\n", "z #a z\n", // Select which template should be used: @@ -813,7 +953,7 @@ public static void testSelector() { "]\n" )); - var template4 = Template.make(() -> body( + var template4 = Template.make(() -> scope( "{\n", template3.asToken(-1), "break\n", @@ -865,7 +1005,7 @@ public static void testRecursion() { // Binding allows use of template1 inside template1, via the Binding indirection. var binding1 = new TemplateBinding>(); - var template1 = Template.make("i", (Integer i) -> body( + var template1 = Template.make("i", (Integer i) -> scope( "[ #i\n", // We cannot yet use the template1 directly, as it is being defined. // So we use binding1 instead. @@ -874,7 +1014,7 @@ public static void testRecursion() { )); binding1.bind(template1); - var template2 = Template.make(() -> body( + var template2 = Template.make(() -> scope( "{\n", // Now, we can use template1 normally, as it is already defined. template1.asToken(3), @@ -902,7 +1042,7 @@ public static void testRecursion() { } public static void testFuel() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( let("f", fuel()), "<#f>\n" @@ -910,7 +1050,7 @@ public static void testFuel() { // Binding allows use of template2 inside template2, via the Binding indirection. var binding2 = new TemplateBinding>(); - var template2 = Template.make("i", (Integer i) -> body( + var template2 = Template.make("i", (Integer i) -> scope( let("f", fuel()), "[ #i #f\n", @@ -920,7 +1060,7 @@ public static void testFuel() { )); binding2.bind(template2); - var template3 = Template.make(() -> body( + var template3 = Template.make(() -> scope( "{\n", template2.asToken(3), "}\n" @@ -948,7 +1088,7 @@ public static void testFuel() { } public static void testFuelCustom() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( setFuelCost(2.0f), let("f", fuel()), @@ -957,7 +1097,7 @@ public static void testFuelCustom() { // Binding allows use of template2 inside template2, via the Binding indirection. var binding2 = new TemplateBinding>(); - var template2 = Template.make("i", (Integer i) -> body( + var template2 = Template.make("i", (Integer i) -> scope( setFuelCost(3.0f), let("f", fuel()), @@ -968,7 +1108,7 @@ public static void testFuelCustom() { )); binding2.bind(template2); - var template3 = Template.make(() -> body( + var template3 = Template.make(() -> scope( setFuelCost(5.0f), let("f", fuel()), @@ -1002,46 +1142,277 @@ public static void testFuelCustom() { checkEQ(code, expected); } + public static void testFuelAndScopes() { + var readFuelTemplate = Template.make(() -> scope( + let("f", fuel()), + "<#f>\n" + )); + + var template = Template.make(() -> scope( + let("f", fuel()), + "{#f}\n", + readFuelTemplate.asToken(), + + "scope:\n", + setFuelCost(1.0f), + scope( + readFuelTemplate.asToken(), + setFuelCost(2.0f), + readFuelTemplate.asToken() + ), + readFuelTemplate.asToken(), + + "transparentScope:\n", + setFuelCost(4.0f), + transparentScope( + readFuelTemplate.asToken(), + setFuelCost(8.0f), + readFuelTemplate.asToken() + ), + readFuelTemplate.asToken(), + + "nameScope:\n", + setFuelCost(16.0f), + nameScope( + readFuelTemplate.asToken(), + setFuelCost(32.0f), + readFuelTemplate.asToken() + ), + readFuelTemplate.asToken(), + + "hashtagScope:\n", + setFuelCost(64.0f), + hashtagScope( + readFuelTemplate.asToken(), + setFuelCost(128.0f), + readFuelTemplate.asToken() + ), + readFuelTemplate.asToken(), + + "setFuelCostScope:\n", + setFuelCost(256.0f), + setFuelCostScope( + readFuelTemplate.asToken(), + setFuelCost(512.0f), + readFuelTemplate.asToken() + ), + readFuelTemplate.asToken() + )); + + String code = template.render(1000.0f); + String expected = + """ + {1000.0f} + <990.0f> + scope: + <999.0f> + <998.0f> + <999.0f> + transparentScope: + <996.0f> + <992.0f> + <992.0f> + nameScope: + <984.0f> + <968.0f> + <968.0f> + hashtagScope: + <936.0f> + <872.0f> + <872.0f> + setFuelCostScope: + <744.0f> + <488.0f> + <744.0f> + """; + checkEQ(code, expected); + } + + public static void testDataNames0a() { + var template = Template.make(() -> scope( + // When a DataName is added, it is immediately available afterwards. + // This may seem trivial, but it requires that either both "add" and + // "sample" happen in lambda execution, or in token evaluation. + // Otherwise, one can float above the other, and lead to unintuitive + // behavior. + addDataName("x", myInt, MUTABLE), + dataNames(MUTABLE).exactOf(myInt).sampleAndLetAs("v"), + "sample: #v." + )); + + String code = template.render(); + checkEQ(code, "sample: x."); + } + + public static void testDataNames0b() { + // Test that the scope keeps local DataNames only for the scope, but that + // we can see DataNames of outer scopes. + var template = Template.make(() -> scope( + // Outer scope DataName: + addDataName("outerInt", myInt, MUTABLE), + dataNames(MUTABLE).exactOf(myInt).sample((DataName dn) -> scope( + let("name1", dn.name()), + "sample: #name1.\n", + // We can also see the outer DataName: + dataNames(MUTABLE).exactOf(myInt).sampleAndLetAs("name2"), + "sample: #name2.\n", + // Local DataName: + addDataName("innerLong", myLong, MUTABLE), + dataNames(MUTABLE).exactOf(myLong).sampleAndLetAs("name3"), + "sample: #name3.\n" + )), + // We can still see the outer scope DataName: + dataNames(MUTABLE).exactOf(myInt).sampleAndLetAs("name4"), + "sample: #name4.\n", + // But we cannot see the DataNames that are local to the inner scope. + // So here, we will always see "outerLong", and never "innerLong". + addDataName("outerLong", myLong, MUTABLE), + dataNames(MUTABLE).exactOf(myLong).sampleAndLetAs("name5"), + "sample: #name5.\n" + )); + + String code = template.render(); + String expected = + """ + sample: outerInt. + sample: outerInt. + sample: innerLong. + sample: outerInt. + sample: outerLong. + """; + checkEQ(code, expected); + } + + public static void testDataNames0c() { + // Test that hashtag replacements that are local to inner scopes are + // only visible to inner scopes, but dollar replacements are the same + // for the whole Template. + var template = Template.make(() -> scope( + let("global", "GLOBAL"), + "g: #global. $a\n", + // Create a dummy DataName so we don't get an exception from sample. + addDataName("x", myInt, MUTABLE), + dataNames(MUTABLE).exactOf(myInt).sample((DataName dn) -> scope( + "g: #global. $b\n", + let("local", "LOCAL1"), + "l: #local. $c\n" + )), + "g: #global. $d\n", + // Open the scope again just to see if we can create the local again there. + dataNames(MUTABLE).exactOf(myInt).sample((DataName dn) -> scope( + "g: #global. $e\n", + let("local", "LOCAL2"), + "l: #local. $f\n" + )), + // We can now use the "local" hashtag replacement again, since it + // was previously only defined in an inner scope. + let("local", "LOCAL3"), + "g: #global. $g\n", + "l: #local. $h\n" + )); + + String code = template.render(); + String expected = + """ + g: GLOBAL. a_1 + g: GLOBAL. b_1 + l: LOCAL1. c_1 + g: GLOBAL. d_1 + g: GLOBAL. e_1 + l: LOCAL2. f_1 + g: GLOBAL. g_1 + l: LOCAL3. h_1 + """; + checkEQ(code, expected); + } + + public static void testDataNames0d() { + var template = Template.make(() -> scope( + addDataName("x", myInt, MUTABLE), + addDataName("y", myInt, MUTABLE), + addDataName("z", myInt, MUTABLE), + addDataName("a", myLong, MUTABLE), + addDataName("b", myLong, MUTABLE), + addDataName("c", myLong, MUTABLE), + dataNames(MUTABLE).exactOf(myInt).forEach((DataName dn) -> scope( + let("name", dn.name()), + let("type", dn.type()), + "listI: #name #type.\n" + )), + dataNames(MUTABLE).exactOf(myLong).forEach((DataName dn) -> scope( + let("name", dn.name()), + let("type", dn.type()), + "listL: #name #type.\n" + )) + )); + + String code = template.render(); + String expected = + """ + listI: x int. + listI: y int. + listI: z int. + listL: a long. + listL: b long. + listL: c long. + """; + checkEQ(code, expected); + } public static void testDataNames1() { var hook1 = new Hook("Hook1"); - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "[", - dataNames(MUTABLE_OR_IMMUTABLE).exactOf(myInt).hasAny(), + dataNames(MUTABLE_OR_IMMUTABLE).exactOf(myInt).hasAny(h -> scope(h)), ", ", - dataNames(MUTABLE_OR_IMMUTABLE).exactOf(myInt).count(), + dataNames(MUTABLE_OR_IMMUTABLE).exactOf(myInt).count(c -> scope(c)), ", names: {", - String.join(", ", dataNames(MUTABLE_OR_IMMUTABLE).exactOf(myInt).toList().stream().map(DataName::name).toList()), + dataNames(MUTABLE_OR_IMMUTABLE).exactOf(myInt).toList(list -> scope( + String.join(", ", list.stream().map(DataName::name).toList()) + )), "}]\n" )); - var template2 = Template.make("name", "type", (String name, DataName.Type type) -> body( - addDataName(name, type, MUTABLE), + // Note: the scope of the template must be transparentScope, so that the addDataName can escape. + var template2 = Template.make("name", "type", (String name, DataName.Type type) -> transparentScope( + addDataName(name, type, MUTABLE), // escapes "define #type #name\n", template1.asToken() )); - var template3 = Template.make(() -> body( + var template3 = Template.make(() -> scope( "<\n", hook1.insert(template2.asToken($("name"), myInt)), "$name = 5\n", ">\n" )); - var template4 = Template.make(() -> body( + var template4 = Template.make(() -> scope( "{\n", template1.asToken(), - hook1.anchor( + hook1.anchor(scope( template1.asToken(), "something\n", - template3.asToken(), + template3.asToken(), // name_4 is inserted to hook1 "more\n", template1.asToken(), "more\n", - template2.asToken($("name"), myInt), + template2.asToken($("name"), myInt), // name_1 escapes "more\n", + template1.asToken(), + "extra\n", + hook1.insert(scope( + addDataName($("extra1"), myInt, MUTABLE), // does not escape + "$extra1 = 666\n" + )), + hook1.insert(transparentScope( + addDataName($("extra2"), myInt, MUTABLE), // escapes + "$extra2 = 42\n" + )), template1.asToken() - ), + )), + // But no names escape to down here, because the anchor scope is "scope". + "final:\n", template1.asToken(), "}\n" )); @@ -1053,6 +1424,8 @@ public static void testDataNames1() { [false, 0, names: {}] define int name_4 [true, 1, names: {name_4}] + extra1_1 = 666 + extra2_1 = 42 [false, 0, names: {}] something < @@ -1064,7 +1437,10 @@ public static void testDataNames1() { define int name_1 [true, 2, names: {name_4, name_1}] more - [true, 1, names: {name_4}] + [true, 2, names: {name_4, name_1}] + extra + [true, 3, names: {name_4, extra2_1, name_1}] + final: [false, 0, names: {}] } """; @@ -1074,17 +1450,19 @@ public static void testDataNames1() { public static void testDataNames2() { var hook1 = new Hook("Hook1"); - var template0 = Template.make("type", "mutability", (DataName.Type type, DataName.Mutability mutability) -> body( + var template0 = Template.make("type", "mutability", (DataName.Type type, DataName.Mutability mutability) -> scope( " #mutability: [", - dataNames(mutability).exactOf(myInt).hasAny(), + dataNames(mutability).exactOf(myInt).hasAny(h -> scope(h)), ", ", - dataNames(mutability).exactOf(myInt).count(), + dataNames(mutability).exactOf(myInt).count(c -> scope(c)), ", names: {", - String.join(", ", dataNames(mutability).exactOf(myInt).toList().stream().map(DataName::name).toList()), + dataNames(mutability).exactOf(myInt).toList(list -> scope( + String.join(", ", list.stream().map(DataName::name).toList()) + )), "}]\n" )); - var template1 = Template.make("type", (DataName.Type type) -> body( + var template1 = Template.make("type", (DataName.Type type) -> scope( "[#type:\n", template0.asToken(type, MUTABLE), template0.asToken(type, IMMUTABLE), @@ -1092,51 +1470,51 @@ public static void testDataNames2() { "]\n" )); - var template2 = Template.make("name", "type", (String name, DataName.Type type) -> body( - addDataName(name, type, MUTABLE), + var template2 = Template.make("name", "type", (String name, DataName.Type type) -> transparentScope( + addDataName(name, type, MUTABLE), // escapes "define mutable #type #name\n", template1.asToken(type) )); - var template3 = Template.make("name", "type", (String name, DataName.Type type) -> body( - addDataName(name, type, IMMUTABLE), + var template3 = Template.make("name", "type", (String name, DataName.Type type) -> transparentScope( + addDataName(name, type, IMMUTABLE), // escapes "define immutable #type #name\n", template1.asToken(type) )); - var template4 = Template.make("type", (DataName.Type type) -> body( + var template4 = Template.make("type", (DataName.Type type) -> scope( "{ $store\n", hook1.insert(template2.asToken($("name"), type)), "$name = 5\n", "} $store\n" )); - var template5 = Template.make("type", (DataName.Type type) -> body( + var template5 = Template.make("type", (DataName.Type type) -> scope( "{ $load\n", hook1.insert(template3.asToken($("name"), type)), "blackhole($name)\n", "} $load\n" )); - var template6 = Template.make("type", (DataName.Type type) -> body( - let("v", dataNames(MUTABLE).exactOf(type).sample().name()), + var template6 = Template.make("type", (DataName.Type type) -> scope( + dataNames(MUTABLE).exactOf(type).sampleAndLetAs("v"), "{ $sample\n", "#v = 7\n", "} $sample\n" )); - var template7 = Template.make("type", (DataName.Type type) -> body( - let("v", dataNames(MUTABLE_OR_IMMUTABLE).exactOf(type).sample().name()), + var template7 = Template.make("type", (DataName.Type type) -> scope( + dataNames(MUTABLE_OR_IMMUTABLE).exactOf(type).sampleAndLetAs("v"), "{ $sample\n", "blackhole(#v)\n", "} $sample\n" )); - var template8 = Template.make(() -> body( + var template8 = Template.make(() -> scope( "class $X {\n", template1.asToken(myInt), - hook1.anchor( - "begin $body\n", + hook1.anchor(scope( + "begin $scope\n", template1.asToken(myInt), "start with immutable\n", template5.asToken(myInt), @@ -1148,7 +1526,7 @@ public static void testDataNames2() { "then store to it\n", template6.asToken(myInt), template1.asToken(myInt) - ), + )), template1.asToken(myInt), "}\n" )); @@ -1174,7 +1552,7 @@ class X_1 { IMMUTABLE: [true, 1, names: {name_10}] MUTABLE_OR_IMMUTABLE: [true, 2, names: {name_10, name_21}] ] - begin body_1 + begin scope_1 [int: MUTABLE: [false, 0, names: {}] IMMUTABLE: [false, 0, names: {}] @@ -1219,17 +1597,19 @@ class X_1 { public static void testDataNames3() { var hook1 = new Hook("Hook1"); - var template0 = Template.make("type", "mutability", (DataName.Type type, DataName.Mutability mutability) -> body( + var template0 = Template.make("type", "mutability", (DataName.Type type, DataName.Mutability mutability) -> scope( " #mutability: [", - dataNames(mutability).exactOf(myInt).hasAny(), + dataNames(mutability).exactOf(myInt).hasAny(h -> scope(h)), ", ", - dataNames(mutability).exactOf(myInt).count(), + dataNames(mutability).exactOf(myInt).count(c -> scope(c)), ", names: {", - String.join(", ", dataNames(mutability).exactOf(myInt).toList().stream().map(DataName::name).toList()), + dataNames(mutability).exactOf(myInt).toList(list -> scope( + String.join(", ", list.stream().map(DataName::name).toList()) + )), "}]\n" )); - var template1 = Template.make("type", (DataName.Type type) -> body( + var template1 = Template.make("type", (DataName.Type type) -> scope( "[#type:\n", template0.asToken(type, MUTABLE), template0.asToken(type, IMMUTABLE), @@ -1237,11 +1617,11 @@ public static void testDataNames3() { "]\n" )); - var template2 = Template.make(() -> body( + var template2 = Template.make(() -> scope( "class $Y {\n", template1.asToken(myInt), - hook1.anchor( - "begin $body\n", + hook1.anchor(scope( + "begin $scope\n", template1.asToken(myInt), "define mutable $v1\n", addDataName($("v1"), myInt, MUTABLE), @@ -1249,7 +1629,7 @@ public static void testDataNames3() { "define immutable $v2\n", addDataName($("v2"), myInt, IMMUTABLE), template1.asToken(myInt) - ), + )), template1.asToken(myInt), "}\n" )); @@ -1263,7 +1643,7 @@ class Y_1 { IMMUTABLE: [false, 0, names: {}] MUTABLE_OR_IMMUTABLE: [false, 0, names: {}] ] - begin body_1 + begin scope_1 [int: MUTABLE: [false, 0, names: {}] IMMUTABLE: [false, 0, names: {}] @@ -1294,47 +1674,62 @@ class Y_1 { public static void testDataNames4() { var hook1 = new Hook("Hook1"); - var template1 = Template.make("type", (DataName.Type type) -> body( + var template1 = Template.make("type", (DataName.Type type) -> scope( "[#type:\n", " exact: ", - dataNames(MUTABLE).exactOf(type).hasAny(), + dataNames(MUTABLE).exactOf(type).hasAny(h -> scope(h)), ", ", - dataNames(MUTABLE).exactOf(type).count(), + dataNames(MUTABLE).exactOf(type).count(c -> scope(c)), ", {", - String.join(", ", dataNames(MUTABLE).exactOf(type).toList().stream().map(DataName::name).toList()), + dataNames(MUTABLE).exactOf(type).toList(list -> scope( + String.join(", ", list.stream().map(DataName::name).toList()) + )), "}\n", " subtype: ", - dataNames(MUTABLE).subtypeOf(type).hasAny(), + dataNames(MUTABLE).subtypeOf(type).hasAny(h -> scope(h)), ", ", - dataNames(MUTABLE).subtypeOf(type).count(), + dataNames(MUTABLE).subtypeOf(type).count(c -> scope(c)), ", {", - String.join(", ", dataNames(MUTABLE).subtypeOf(type).toList().stream().map(DataName::name).toList()), + dataNames(MUTABLE).subtypeOf(type).toList(list -> scope( + String.join(", ", list.stream().map(DataName::name).toList()) + )), + "}\n", " supertype: ", - dataNames(MUTABLE).supertypeOf(type).hasAny(), + dataNames(MUTABLE).supertypeOf(type).hasAny(h -> scope(h)), ", ", - dataNames(MUTABLE).supertypeOf(type).count(), + dataNames(MUTABLE).supertypeOf(type).count(c -> scope(c)), ", {", - String.join(", ", dataNames(MUTABLE).supertypeOf(type).toList().stream().map(DataName::name).toList()), + dataNames(MUTABLE).supertypeOf(type).toList(list -> scope( + String.join(", ", list.stream().map(DataName::name).toList()) + )), "}\n", "]\n" )); List types = List.of(myClassA, myClassA1, myClassA2, myClassA11, myClassB); - var template2 = Template.make(() -> body( + var template2 = Template.make(() -> scope( "DataNames:\n", types.stream().map(t -> template1.asToken(t)).toList() )); - var template3 = Template.make("type", (DataName.Type type) -> body( - let("name", dataNames(MUTABLE).subtypeOf(type).sample()), - "Sample #type: #name\n" + var template3 = Template.make("type", (DataName.Type type) -> scope( + dataNames(MUTABLE).subtypeOf(type).sampleAndLetAs("name1"), + "Sample #type: #name1\n", + dataNames(MUTABLE).subtypeOf(type).sampleAndLetAs("name2", "type2"), + "Sample #type: #name2 #type2\n", + dataNames(MUTABLE).subtypeOf(type).sample((DataName dn) -> scope( + let("name3", dn.name()), + let("type3", dn.type()), + let("dn", dn), // format the whole DataName with toString + "Sample #type: #name3 #type3 #dn\n" + )) )); - var template4 = Template.make(() -> body( + var template4 = Template.make(() -> scope( "class $W {\n", template2.asToken(), - hook1.anchor( + hook1.anchor(scope( "Create name for myClassA11, should be visible for the super classes\n", addDataName($("v1"), myClassA11, MUTABLE), template3.asToken(myClassA11), @@ -1345,7 +1740,7 @@ public static void testDataNames4() { template3.asToken(myClassA11), template3.asToken(myClassA1), template2.asToken() - ), + )), template2.asToken(), "}\n" )); @@ -1381,12 +1776,22 @@ class W_1 { supertype: false, 0, {} ] Create name for myClassA11, should be visible for the super classes - Sample myClassA11: DataName[name=v1_1, type=myClassA11, mutable=true, weight=1] - Sample myClassA1: DataName[name=v1_1, type=myClassA11, mutable=true, weight=1] - Sample myClassA: DataName[name=v1_1, type=myClassA11, mutable=true, weight=1] + Sample myClassA11: v1_1 + Sample myClassA11: v1_1 myClassA11 + Sample myClassA11: v1_1 myClassA11 DataName[name=v1_1, type=myClassA11, mutable=true, weight=1] + Sample myClassA1: v1_1 + Sample myClassA1: v1_1 myClassA11 + Sample myClassA1: v1_1 myClassA11 DataName[name=v1_1, type=myClassA11, mutable=true, weight=1] + Sample myClassA: v1_1 + Sample myClassA: v1_1 myClassA11 + Sample myClassA: v1_1 myClassA11 DataName[name=v1_1, type=myClassA11, mutable=true, weight=1] Create name for myClassA, should never be visible for the sub classes - Sample myClassA11: DataName[name=v1_1, type=myClassA11, mutable=true, weight=1] - Sample myClassA1: DataName[name=v1_1, type=myClassA11, mutable=true, weight=1] + Sample myClassA11: v1_1 + Sample myClassA11: v1_1 myClassA11 + Sample myClassA11: v1_1 myClassA11 DataName[name=v1_1, type=myClassA11, mutable=true, weight=1] + Sample myClassA1: v1_1 + Sample myClassA1: v1_1 myClassA11 + Sample myClassA1: v1_1 myClassA11 DataName[name=v1_1, type=myClassA11, mutable=true, weight=1] DataNames: [myClassA: exact: true, 1, {v2_1} @@ -1449,49 +1854,61 @@ public static void testDataNames5() { var hook1 = new Hook("Hook1"); var hook2 = new Hook("Hook2"); - // It is safe in separate Hook scopes. - var template1 = Template.make(() -> body( - hook1.anchor( + // It is safe in separate scopes. + var template1 = Template.make(() -> scope( + scope( + addDataName("name1", myInt, MUTABLE) + ), + scope( + addDataName("name1", myInt, MUTABLE) + ), + nameScope( + addDataName("name1", myInt, MUTABLE) + ), + nameScope( addDataName("name1", myInt, MUTABLE) ), - hook1.anchor( + hook1.anchor(scope( addDataName("name1", myInt, MUTABLE) - ) + )), + hook1.anchor(scope( + addDataName("name1", myInt, MUTABLE) + )) )); // It is safe in separate Template scopes. - var template2 = Template.make(() -> body( + var template2 = Template.make(() -> scope( addDataName("name2", myInt, MUTABLE) )); - var template3 = Template.make(() -> body( + var template3 = Template.make(() -> scope( template2.asToken(), template2.asToken() )); - var template4 = Template.make(() -> body( + var template4 = Template.make(() -> scope( // The following is not safe, it would collide // with (1), because it would be inserted to the // hook1.anchor in template5, and hence be available // inside the scope where (1) is available. // See: testFailingAddNameDuplication8 // addDataName("name", myInt, MUTABLE), - hook2.anchor( + hook2.anchor(scope( // (2) This one is added second. Since it is // inside the hook2.anchor, it does not go // out to the hook1.anchor, and is not // available inside the scope of (1). addDataName("name3", myInt, MUTABLE) - ) + )) )); - var template5 = Template.make(() -> body( - hook1.anchor( + var template5 = Template.make(() -> scope( + hook1.anchor(scope( // (1) this is the first one we add. addDataName("name3", myInt, MUTABLE) - ) + )) )); // Put it all together into a single test. - var template6 = Template.make(() -> body( + var template6 = Template.make(() -> scope( template1.asToken(), template3.asToken(), template5.asToken() @@ -1502,31 +1919,128 @@ public static void testDataNames5() { checkEQ(code, expected); } + public static void testDataNames6() { + var template = Template.make(() -> scope( + addDataName("x", myInt, IMMUTABLE), + "int x = 5;\n", + // A DataName can be captured, and used to define a new one with the same type. + // It is important that the new DataName can escape the hashtagScope, so we have + // access to it later. + // Using "scope", it does not escape. + dataNames(IMMUTABLE).exactOf(myInt).sample(dn -> scope( + addDataName("a", dn.type(), MUTABLE), + let("v1", "a"), + "int #v1 = x + 1;\n" + )), + // Using "transparentScope", it is available. + dataNames(IMMUTABLE).exactOf(myInt).sample(dn -> transparentScope( + addDataName("b", dn.type(), MUTABLE), + let("v2", "b"), + "int #v2 = x + 2;\n" + )), + // Using "nameScope", it does not escape. + dataNames(IMMUTABLE).exactOf(myInt).sample(dn -> nameScope( + addDataName("c", dn.type(), MUTABLE), + let("v3", "c"), + "int #v3 = x + 3;\n" + )), + // Using "hashtagScope", it is available. + dataNames(IMMUTABLE).exactOf(myInt).sample(dn -> hashtagScope( + addDataName("d", dn.type(), MUTABLE), + let("v4", "d"), + "int #v4 = x + 4;\n" + )), + // Using "setFuelCostScope", it is available. + dataNames(IMMUTABLE).exactOf(myInt).sample(dn -> setFuelCostScope( + addDataName("e", dn.type(), MUTABLE), + let("v5", "e"), + "int #v5 = x + 5;\n" + )), + dataNames(MUTABLE_OR_IMMUTABLE).exactOf(myInt).forEach("name", "type", dn -> scope( + "available1: #name #type.\n" + )), + dataNames(MUTABLE_OR_IMMUTABLE).exactOf(myInt).forEach("name", "type", dn -> hashtagScope( + "available2: #name #type.\n" + )), + // Check that hashtags escape correctly too. + "hashtag v2: #v2.\n", + "hashtag v3: #v3.\n", + "hashtag v5: #v5.\n", + let("v1", "aaa"), + let("v4", "ddd") + )); + + String code = template.render(); + String expected = + """ + int x = 5; + int a = x + 1; + int b = x + 2; + int c = x + 3; + int d = x + 4; + int e = x + 5; + available1: x int. + available1: b int. + available1: d int. + available1: e int. + available2: x int. + available2: b int. + available2: d int. + available2: e int. + hashtag v2: b. + hashtag v3: c. + hashtag v5: e. + """; + checkEQ(code, expected); + } + + public static void testStructuralNames0() { + var template = Template.make(() -> scope( + // When a StructuralName is added, it is immediately available afterwards. + // This may seem trivial, but it requires that either both "add" and + // "sample" happen in lambda execution, or in token evaluation. + // Otherwise, one can float above the other, and lead to unintuitive + // behavior. + addStructuralName("x", myStructuralTypeA), + structuralNames().exactOf(myStructuralTypeA).sampleAndLetAs("v"), + "sample: #v." + )); + + String code = template.render(); + checkEQ(code, "sample: x."); + } + public static void testStructuralNames1() { var hook1 = new Hook("Hook1"); - var template1 = Template.make("type", (StructuralName.Type type) -> body( + var template1 = Template.make("type", (StructuralName.Type type) -> scope( "[#type:\n", " exact: ", - structuralNames().exactOf(type).hasAny(), + structuralNames().exactOf(type).hasAny(h -> scope(h)), ", ", - structuralNames().exactOf(type).count(), + structuralNames().exactOf(type).count(c -> scope(c)), ", {", - String.join(", ", structuralNames().exactOf(type).toList().stream().map(StructuralName::name).toList()), + structuralNames().exactOf(type).toList(list -> scope( + String.join(", ", list.stream().map(StructuralName::name).toList()) + )), "}\n", " subtype: ", - structuralNames().subtypeOf(type).hasAny(), + structuralNames().subtypeOf(type).hasAny(h -> scope(h)), ", ", - structuralNames().subtypeOf(type).count(), + structuralNames().subtypeOf(type).count(c -> scope(c)), ", {", - String.join(", ", structuralNames().subtypeOf(type).toList().stream().map(StructuralName::name).toList()), + structuralNames().subtypeOf(type).toList(list -> scope( + String.join(", ", list.stream().map(StructuralName::name).toList()) + )), "}\n", " supertype: ", - structuralNames().supertypeOf(type).hasAny(), + structuralNames().supertypeOf(type).hasAny(h -> scope(h)), ", ", - structuralNames().supertypeOf(type).count(), + structuralNames().supertypeOf(type).count(c -> scope(c)), ", {", - String.join(", ", structuralNames().supertypeOf(type).toList().stream().map(StructuralName::name).toList()), + structuralNames().supertypeOf(type).toList(list -> scope( + String.join(", ", list.stream().map(StructuralName::name).toList()) + )), "}\n", "]\n" )); @@ -1536,20 +2050,28 @@ public static void testStructuralNames1() { myStructuralTypeA2, myStructuralTypeA11, myStructuralTypeB); - var template2 = Template.make(() -> body( + var template2 = Template.make(() -> scope( "StructuralNames:\n", types.stream().map(t -> template1.asToken(t)).toList() )); - var template3 = Template.make("type", (StructuralName.Type type) -> body( - let("name", structuralNames().subtypeOf(type).sample()), - "Sample #type: #name\n" + var template3 = Template.make("type", (StructuralName.Type type) -> scope( + structuralNames().subtypeOf(type).sampleAndLetAs("name1"), + "Sample #type: #name1\n", + structuralNames().subtypeOf(type).sampleAndLetAs("name2", "type2"), + "Sample #type: #name2 #type2\n", + structuralNames().subtypeOf(type).sample((StructuralName sn) -> scope( + let("name3", sn.name()), + let("type3", sn.type()), + let("sn", sn), // format the whole StructuralName with toString + "Sample #type: #name3 #type3 #sn\n" + )) )); - var template4 = Template.make(() -> body( + var template4 = Template.make(() -> scope( "class $Q {\n", template2.asToken(), - hook1.anchor( + hook1.anchor(scope( "Create name for myStructuralTypeA11, should be visible for the supertypes\n", addStructuralName($("v1"), myStructuralTypeA11), template3.asToken(myStructuralTypeA11), @@ -1560,7 +2082,7 @@ public static void testStructuralNames1() { template3.asToken(myStructuralTypeA11), template3.asToken(myStructuralTypeA1), template2.asToken() - ), + )), template2.asToken(), "}\n" )); @@ -1596,12 +2118,22 @@ class Q_1 { supertype: false, 0, {} ] Create name for myStructuralTypeA11, should be visible for the supertypes - Sample StructuralA11: StructuralName[name=v1_1, type=StructuralA11, weight=1] - Sample StructuralA1: StructuralName[name=v1_1, type=StructuralA11, weight=1] - Sample StructuralA: StructuralName[name=v1_1, type=StructuralA11, weight=1] + Sample StructuralA11: v1_1 + Sample StructuralA11: v1_1 StructuralA11 + Sample StructuralA11: v1_1 StructuralA11 StructuralName[name=v1_1, type=StructuralA11, weight=1] + Sample StructuralA1: v1_1 + Sample StructuralA1: v1_1 StructuralA11 + Sample StructuralA1: v1_1 StructuralA11 StructuralName[name=v1_1, type=StructuralA11, weight=1] + Sample StructuralA: v1_1 + Sample StructuralA: v1_1 StructuralA11 + Sample StructuralA: v1_1 StructuralA11 StructuralName[name=v1_1, type=StructuralA11, weight=1] Create name for myStructuralTypeA, should never be visible for the subtypes - Sample StructuralA11: StructuralName[name=v1_1, type=StructuralA11, weight=1] - Sample StructuralA1: StructuralName[name=v1_1, type=StructuralA11, weight=1] + Sample StructuralA11: v1_1 + Sample StructuralA11: v1_1 StructuralA11 + Sample StructuralA11: v1_1 StructuralA11 StructuralName[name=v1_1, type=StructuralA11, weight=1] + Sample StructuralA1: v1_1 + Sample StructuralA1: v1_1 StructuralA11 + Sample StructuralA1: v1_1 StructuralA11 StructuralName[name=v1_1, type=StructuralA11, weight=1] StructuralNames: [StructuralA: exact: true, 1, {v2_1} @@ -1662,41 +2194,43 @@ class Q_1 { public static void testStructuralNames2() { var hook1 = new Hook("Hook1"); - var template1 = Template.make("type", (StructuralName.Type type) -> body( + var template1 = Template.make("type", (StructuralName.Type type) -> scope( "[#type: ", - structuralNames().exactOf(type).hasAny(), + structuralNames().exactOf(type).hasAny(h -> scope(h)), ", ", - structuralNames().exactOf(type).count(), + structuralNames().exactOf(type).count(c -> scope(c)), ", names: {", - String.join(", ", structuralNames().exactOf(type).toList().stream().map(StructuralName::name).toList()), + structuralNames().exactOf(type).toList(list -> scope( + String.join(", ", list.stream().map(StructuralName::name).toList()) + )), "}]\n" )); - var template2 = Template.make("name", "type", (String name, StructuralName.Type type) -> body( - addStructuralName(name, type), + var template2 = Template.make("name", "type", (String name, StructuralName.Type type) -> transparentScope( + addStructuralName(name, type), // escapes "define #type #name\n" )); - var template3 = Template.make("type", (StructuralName.Type type) -> body( + var template3 = Template.make("type", (StructuralName.Type type) -> scope( "{ $access\n", hook1.insert(template2.asToken($("name"), type)), "$name = 5\n", "} $access\n" )); - var template4 = Template.make("type", (StructuralName.Type type) -> body( - let("v", structuralNames().exactOf(type).sample().name()), + var template4 = Template.make("type", (StructuralName.Type type) -> scope( + structuralNames().exactOf(type).sampleAndLetAs("v"), "{ $sample\n", "blackhole(#v)\n", "} $sample\n" )); - var template8 = Template.make(() -> body( + var template8 = Template.make(() -> scope( "class $X {\n", template1.asToken(myStructuralTypeA), template1.asToken(myStructuralTypeB), - hook1.anchor( - "begin $body\n", + hook1.anchor(scope( + "begin $scope\n", template1.asToken(myStructuralTypeA), template1.asToken(myStructuralTypeB), "start with A\n", @@ -1711,7 +2245,7 @@ public static void testStructuralNames2() { template4.asToken(myStructuralTypeB), template1.asToken(myStructuralTypeA), template1.asToken(myStructuralTypeB) - ), + )), template1.asToken(myStructuralTypeA), template1.asToken(myStructuralTypeB), "}\n" @@ -1725,7 +2259,7 @@ class X_1 { [StructuralB: false, 0, names: {}] define StructuralA name_6 define StructuralB name_11 - begin body_1 + begin scope_1 [StructuralA: false, 0, names: {}] [StructuralB: false, 0, names: {}] start with A @@ -1755,16 +2289,269 @@ class X_1 { checkEQ(code, expected); } + public static void testStructuralNames3() { + var template = Template.make(() -> scope( + addStructuralName("a", myStructuralTypeA), + addStructuralName("b", myStructuralTypeA), + structuralNames().exactOf(myStructuralTypeA).forEach(sn -> scope( + let("name1", sn.name()), + "sn1: #name1.\n", + addStructuralName("scope_garbage1", myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).forEach(sn -> nameScope( + // We cannot use "let" here (at least not easily), otherwise we get + // a duplicate hashtag replacement. It would probably be better style + // to use a "let", but we are just checking that "nameScope" works + // for reuse of names. + "sn2: ", sn.name(), ".\n", + // But for testing, we still do a "let", just with different key. + // (This is probably bad practice, we just do this for testing) + let("name2_" + sn.name(), sn.name()), + addStructuralName("scope_garbage2", myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).forEach(sn -> transparentScope( + // Same issue with hashtags as with "nameScope". + "sn3: ", sn.name(), ".\n", + let("name3_" + sn.name(), sn.name()), + // Using the same name for each would lead to duplicates, + // so we have to modify the name here. + addStructuralName("x_" + sn.name(), myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).forEach(sn -> hashtagScope( + let("name4", sn.name()), + "sn4: #name4.\n", + // Same issue with duplicate names as with "transparentScope". + addStructuralName("y_" + sn.name(), myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).forEach(sn -> setFuelCostScope( + // Same issue with hashtags as with "nameScope". + "sn5: ", sn.name(), ".\n", + let("name5_" + sn.name(), sn.name()), + // Same issue with duplicate names as with "transparentScope". + addStructuralName("z_" + sn.name(), myStructuralTypeA) + )), + "sn2: #name2_a #name2_b.\n", // hashtags escaped + "sn3: #name3_a #name3_b.\n", // hashtags escaped + "sn5: #name5_a #name5_b #name5_x_a #name5_x_b.\n", // hashtags escaped + let("name1", "shouldBeOK1"), // hashtag did not escape + let("name4", "shouldBeOk4") // hashtag did not escape + )); + + String code = template.render(); + String expected = + """ + sn1: a. + sn1: b. + sn2: a. + sn2: b. + sn3: a. + sn3: b. + sn4: a. + sn4: b. + sn4: x_a. + sn4: x_b. + sn5: a. + sn5: b. + sn5: x_a. + sn5: x_b. + sn5: y_a. + sn5: y_b. + sn5: y_x_a. + sn5: y_x_b. + sn2: a b. + sn3: a b. + sn5: a b x_a x_b. + """; + checkEQ(code, expected); + } + + public static void testStructuralNames4() { + var template = Template.make(() -> scope( + addStructuralName("a", myStructuralTypeA), + addStructuralName("b", myStructuralTypeA), + structuralNames().exactOf(myStructuralTypeA).toList(list -> scope( + let("name1", list.size()), + "list1: #name1.\n", + addStructuralName("scope_garbage1", myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).toList(list -> nameScope( + let("name2", list.size()), + "list2: #name2.\n", + addStructuralName("scope_garbage2", myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).toList(list -> transparentScope( + let("name3", list.size()), + "list3: #name3.\n", + addStructuralName("x", myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).toList(list -> hashtagScope( + let("name4", list.size()), + "list4: #name4.\n", + addStructuralName("y", myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).toList(list -> setFuelCostScope( + let("name5", list.size()), + "list5: #name5.\n", + addStructuralName("z", myStructuralTypeA) + )), + "list2: #name2.\n", // hashtag escaped + "list3: #name3.\n", // hashtag escaped + "list5: #name5.\n", // hashtag escaped + let("name1", "shouldBeOk4"), // hashtag did not escape + let("name4", "shouldBeOk4"), // hashtag did not escape + structuralNames().exactOf(myStructuralTypeA).forEach("name", "type", sn -> scope( + "available: #name #type.\n" + )) + )); + + String code = template.render(); + String expected = + """ + list1: 2. + list2: 2. + list3: 2. + list4: 3. + list5: 4. + list2: 2. + list3: 2. + list5: 4. + available: a StructuralA. + available: b StructuralA. + available: x StructuralA. + available: y StructuralA. + available: z StructuralA. + """; + checkEQ(code, expected); + } + + public static void testStructuralNames5() { + var template = Template.make(() -> scope( + addStructuralName("a", myStructuralTypeA), + addStructuralName("b", myStructuralTypeA), + structuralNames().exactOf(myStructuralTypeA).count(c -> scope( + let("name1", c), + "list1: #name1.\n", + addStructuralName("scope_garbage1", myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).count(c -> nameScope( + let("name2", c), + "list2: #name2.\n", + addStructuralName("scope_garbage2", myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).count(c -> transparentScope( + let("name3", c), + "list3: #name3.\n", + addStructuralName("x", myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).count(c -> hashtagScope( + let("name4", c), + "list4: #name4.\n", + addStructuralName("y", myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).count(c -> setFuelCostScope( + let("name5", c), + "list5: #name5.\n", + addStructuralName("z", myStructuralTypeA) + )), + "list2: #name2.\n", // hashtag escaped + "list3: #name3.\n", // hashtag escaped + "list5: #name5.\n", // hashtag escaped + let("name1", "shouldBeOk4"), // hashtag did not escape + let("name4", "shouldBeOk4"), // hashtag did not escape + structuralNames().exactOf(myStructuralTypeA).forEach("name", "type", sn -> scope( + "available: #name #type.\n" + )) + )); + + String code = template.render(); + String expected = + """ + list1: 2. + list2: 2. + list3: 2. + list4: 3. + list5: 4. + list2: 2. + list3: 2. + list5: 4. + available: a StructuralA. + available: b StructuralA. + available: x StructuralA. + available: y StructuralA. + available: z StructuralA. + """; + checkEQ(code, expected); + } + + public static void testStructuralNames6() { + var template = Template.make(() -> scope( + addStructuralName("a", myStructuralTypeA), + addStructuralName("b", myStructuralTypeA), + structuralNames().exactOf(myStructuralTypeA).hasAny(h -> scope( + let("name1", h), + "list1: #name1.\n", + addStructuralName("scope_garbage1", myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).hasAny(h -> nameScope( + let("name2", h), + "list2: #name2.\n", + addStructuralName("scope_garbage2", myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).hasAny(h -> transparentScope( + let("name3", h), + "list3: #name3.\n", + addStructuralName("x", myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).hasAny(h -> hashtagScope( + let("name4", h), + "list4: #name4.\n", + addStructuralName("y", myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).hasAny(h -> setFuelCostScope( + let("name5", h), + "list5: #name5.\n", + addStructuralName("z", myStructuralTypeA) + )), + "list2: #name2.\n", // hashtag escaped + "list3: #name3.\n", // hashtag escaped + "list5: #name5.\n", // hashtag escaped + let("name1", "shouldBeOk4"), // hashtag did not escape + let("name4", "shouldBeOk4"), // hashtag did not escape + structuralNames().exactOf(myStructuralTypeA).forEach("name", "type", sn -> scope( + "available: #name #type.\n" + )) + )); + + String code = template.render(); + String expected = + """ + list1: true. + list2: true. + list3: true. + list4: true. + list5: true. + list2: true. + list3: true. + list5: true. + available: a StructuralA. + available: b StructuralA. + available: x StructuralA. + available: y StructuralA. + available: z StructuralA. + """; + checkEQ(code, expected); + } + record MyItem(DataName.Type type, String op) {} public static void testListArgument() { - var template1 = Template.make("item", (MyItem item) -> body( + var template1 = Template.make("item", (MyItem item) -> scope( let("type", item.type()), let("op", item.op()), "#type apply #op\n" )); - var template2 = Template.make("list", (List list) -> body( + var template2 = Template.make("list", (List list) -> scope( "class $Z {\n", // Use template1 for every item in the list. list.stream().map(item -> template1.asToken(item)).toList(), @@ -1797,12 +2584,746 @@ class Z_1 { checkEQ(code, expected); } + public static void testNestedScopes1() { + var listDataNames = Template.make(() -> scope( + "dataNames: {", + dataNames(MUTABLE).exactOf(myInt).forEach("name", "type", (DataName dn) -> scope( + "#name #type; " + )), + "}\n" + )); + + var template = Template.make("x", (String x) -> scope( + "$start\n", + addDataName("vx", myInt, MUTABLE), + "x: #x.\n", + listDataNames.asToken(), + // A "transparentScope" nesting essencially does nothing but create + // a list of tokens. It passes through names and hashtags. + "open transparentScope:\n", + transparentScope( + "$transparentScope\n", + let("y", "YYY"), + addDataName("vy", myInt, MUTABLE), + "x: #x.\n", + "y: #y.\n", + listDataNames.asToken() + ), + "close transparentScope.\n", + "x: #x.\n", + "y: #y.\n", + listDataNames.asToken(), + // A "hashtagScope" nesting makes hashtags local, but names + // escape the nesting. + "open hashtagScope:\n", + hashtagScope( + "$hashtagScope\n", + let("z", "ZZZ1"), + "z: #z.\n", + addDataName("vz", myInt, MUTABLE), + listDataNames.asToken() + ), + "close hashtagScope.\n", + let("z", "ZZZ2"), // we can define it again outside. + "z: #z.\n", + listDataNames.asToken(), + // We can also use hashtagScopes for loops. + List.of("a", "b", "c").stream().map(str -> hashtagScope( + "$hashtagScope\n", + let("str", str), // the hashtag is local to every element + "str: #str.\n", + addDataName("v_" + str, myInt, MUTABLE), + listDataNames.asToken() + )).toList(), + "finish str list.\n", + listDataNames.asToken(), + // A "nameScope" nesting makes names local, but hashtags + // escape the nesting. + "open nameScope:\n", + nameScope( + "$nameScope\n", + let("p", "PPP"), + "p: #p.\n", + addDataName("vp", myInt, MUTABLE), + listDataNames.asToken() + ), + "close hashtagScope.\n", + "p: #p.\n", + listDataNames.asToken(), + // A "scope" nesting makes names and hashtags local + "open scope:\n", + scope( + "$scope\n", + let("q", "QQQ1"), + "q: #q.\n", + addDataName("vq", myInt, MUTABLE), + listDataNames.asToken() + ), + "close scope.\n", + let("q", "QQQ2"), + "q: #q.\n", + listDataNames.asToken(), + // A "setFuelCostScope" nesting behaves the same as "transparentScope", as we are not using fuel here. + "open setFuelCostScope:\n", + setFuelCostScope( + "$setFuelCostScope\n", + let("r", "RRR"), + "r: #r.\n", + addDataName("vr", myInt, MUTABLE), + listDataNames.asToken() + ), + "close setFuelCostScope.\n", + "r: #r.\n", + listDataNames.asToken() + + )); + + String code = template.render("XXX"); + String expected = + """ + start_1 + x: XXX. + dataNames: {vx int; } + open transparentScope: + transparentScope_1 + x: XXX. + y: YYY. + dataNames: {vx int; vy int; } + close transparentScope. + x: XXX. + y: YYY. + dataNames: {vx int; vy int; } + open hashtagScope: + hashtagScope_1 + z: ZZZ1. + dataNames: {vx int; vy int; vz int; } + close hashtagScope. + z: ZZZ2. + dataNames: {vx int; vy int; vz int; } + hashtagScope_1 + str: a. + dataNames: {vx int; vy int; vz int; v_a int; } + hashtagScope_1 + str: b. + dataNames: {vx int; vy int; vz int; v_a int; v_b int; } + hashtagScope_1 + str: c. + dataNames: {vx int; vy int; vz int; v_a int; v_b int; v_c int; } + finish str list. + dataNames: {vx int; vy int; vz int; v_a int; v_b int; v_c int; } + open nameScope: + nameScope_1 + p: PPP. + dataNames: {vx int; vy int; vz int; v_a int; v_b int; v_c int; vp int; } + close hashtagScope. + p: PPP. + dataNames: {vx int; vy int; vz int; v_a int; v_b int; v_c int; } + open scope: + scope_1 + q: QQQ1. + dataNames: {vx int; vy int; vz int; v_a int; v_b int; v_c int; vq int; } + close scope. + q: QQQ2. + dataNames: {vx int; vy int; vz int; v_a int; v_b int; v_c int; } + open setFuelCostScope: + setFuelCostScope_1 + r: RRR. + dataNames: {vx int; vy int; vz int; v_a int; v_b int; v_c int; vr int; } + close setFuelCostScope. + r: RRR. + dataNames: {vx int; vy int; vz int; v_a int; v_b int; v_c int; vr int; } + """; + checkEQ(code, expected); + } + + public static void testNestedScopes2() { + var listDataNames = Template.make(() -> scope( + "dataNames: {", + dataNames(MUTABLE).exactOf(myInt).forEach("name", "type", (DataName dn) -> scope( + "#name #type; " + )), + "}\n" + )); + + var template = Template.make(() -> scope( + // Define some global variables. + List.of("a", "b", "c").stream().map(str -> hashtagScope( + let("var", "g_" + str), + addDataName("g_" + str, myInt, MUTABLE), + "def global #var.\n" + )).toList(), + listDataNames.asToken(), + scope( + "open scope:\n", + // Define some variables. + List.of("i", "j", "k").stream().map(str -> hashtagScope( + let("var", "v_" + str), + addDataName("v_" + str, myInt, MUTABLE), + "def #var.\n" + )).toList(), + listDataNames.asToken(), + scope( + "open inner scope:\n", + addDataName("v_local", myInt, MUTABLE), + "def v_local.\n", + listDataNames.asToken(), + "close inner scope.\n" + ), + listDataNames.asToken(), + "close scope.\n" + ), + listDataNames.asToken() + )); + + String code = template.render(); + String expected = + """ + def global g_a. + def global g_b. + def global g_c. + dataNames: {g_a int; g_b int; g_c int; } + open scope: + def v_i. + def v_j. + def v_k. + dataNames: {g_a int; g_b int; g_c int; v_i int; v_j int; v_k int; } + open inner scope: + def v_local. + dataNames: {g_a int; g_b int; g_c int; v_i int; v_j int; v_k int; v_local int; } + close inner scope. + dataNames: {g_a int; g_b int; g_c int; v_i int; v_j int; v_k int; } + close scope. + dataNames: {g_a int; g_b int; g_c int; } + """; + checkEQ(code, expected); + } + + public static void testTemplateScopes() { + var statusTemplate = Template.make(() -> scope( + "{", + structuralNames().exactOf(myStructuralTypeA).toList(list -> scope( + String.join(", ", list.stream().map(StructuralName::name).toList()) + )), + "}\n", + let("fuel", fuel()), + "fuel: #fuel\n" + )); + + var scopeTemplate = Template.make(() -> scope( + "scope:\n", + let("local", "inner scope"), + addStructuralName("x", myStructuralTypeA), + statusTemplate.asToken(), + setFuelCost(50) + )); + + var transparentScopeTemplate = Template.make(() -> transparentScope( + "transparentScope:\n", + let("local", "inner flag"), + addStructuralName("y", myStructuralTypeA), // should escape + statusTemplate.asToken(), + setFuelCost(50) + )); + + var template = Template.make(() -> scope( + setFuelCost(1), + let("local", "root"), + addStructuralName("a", myStructuralTypeA), + statusTemplate.asToken(), + scopeTemplate.asToken(), + statusTemplate.asToken(), + transparentScopeTemplate.asToken(), + statusTemplate.asToken() + )); + + String code = template.render(); + String expected = + """ + {a} + fuel: 99.0f + scope: + {a, x} + fuel: 89.0f + {a} + fuel: 99.0f + transparentScope: + {a, y} + fuel: 89.0f + {a, y} + fuel: 99.0f + """; + checkEQ(code, expected); + } + + public static void testHookAndScopes1() { + Hook hook1 = new Hook("Hook1"); + + var listNamesTemplate = Template.make(() -> scope( + "{", + structuralNames().exactOf(myStructuralTypeA).toList(list -> scope( + String.join(", ", list.stream().map(StructuralName::name).toList()) + )), + "}\n" + )); + + var insertScopeTemplate = Template.make("name", (String name) -> scope( + let("local", "insert scope garbage"), + addStructuralName(name, myStructuralTypeA), + "inserted scope: #name\n", + listNamesTemplate.asToken() + )); + + var insertTransparentScopeTemplate = Template.make("name", (String name) -> transparentScope( + let("local", "insert transparentScope garbage"), + addStructuralName(name, myStructuralTypeA), + "inserted transparentScope: #name\n", + listNamesTemplate.asToken() + )); + + var probeTemplate = Template.make(() -> scope( + "inserted probe:\n", + listNamesTemplate.asToken() + )); + + var template = Template.make(() -> scope( + "scope:\n", + hook1.anchor(scope( + let("local", "scope garbage"), + addStructuralName("x1a", myStructuralTypeA), + "scope before insert scope:\n", + listNamesTemplate.asToken(), + hook1.insert(insertScopeTemplate.asToken("x1b")), + "scope after insert scope:\n", + listNamesTemplate.asToken(), + "scope before insert transparentScope:\n", + listNamesTemplate.asToken(), + hook1.insert(insertTransparentScopeTemplate.asToken("x1c")), + "scope after insert transparentScope:\n", + listNamesTemplate.asToken(), + "scope insert probe.\n", + hook1.insert(probeTemplate.asToken()) + )), + "after scope:\n", + listNamesTemplate.asToken(), + + "transparentScope:\n", + hook1.anchor(transparentScope( + let("transparentScope2", "abc"), + addStructuralName("x2a", myStructuralTypeA), + "transparentScope before insert scope:\n", + listNamesTemplate.asToken(), + hook1.insert(insertScopeTemplate.asToken("x2b")), + "transparentScope after insert scope:\n", + listNamesTemplate.asToken(), + "transparentScope before insert transparentScope:\n", + listNamesTemplate.asToken(), + hook1.insert(insertTransparentScopeTemplate.asToken("x2c")), + "transparentScope after insert transparentScope:\n", + listNamesTemplate.asToken(), + "transparentScope insert probe.\n", + hook1.insert(probeTemplate.asToken()) + )), + "after transparentScope:\n", + listNamesTemplate.asToken(), + "transparentScope2: #transparentScope2\n", + + "hashtagScope:\n", + hook1.anchor(hashtagScope( + let("local", "hashtagScope garbage"), + addStructuralName("x3a", myStructuralTypeA), + "hashtagScope before insert scope:\n", + listNamesTemplate.asToken(), + hook1.insert(insertScopeTemplate.asToken("x3b")), + "hashtagScope after insert scope:\n", + listNamesTemplate.asToken(), + "hashtagScope before insert transparentScope:\n", + listNamesTemplate.asToken(), + hook1.insert(insertTransparentScopeTemplate.asToken("x3c")), + "hashtagScope after insert transparentScope:\n", + listNamesTemplate.asToken(), + "hashtagScope insert probe.\n", + hook1.insert(probeTemplate.asToken()) + )), + "after hashtagScope:\n", + listNamesTemplate.asToken(), + + "nameScope:\n", + hook1.anchor(nameScope( + let("transparentScope4", "abcde"), + addStructuralName("x4a", myStructuralTypeA), + "nameScope before insert scope:\n", + listNamesTemplate.asToken(), + hook1.insert(insertScopeTemplate.asToken("x4b")), + "nameScope after insert scope:\n", + listNamesTemplate.asToken(), + "nameScope before insert transparentScope:\n", + listNamesTemplate.asToken(), + hook1.insert(insertTransparentScopeTemplate.asToken("x4c")), + "nameScope after insert transparentScope:\n", + listNamesTemplate.asToken(), + "nameScope insert probe.\n", + hook1.insert(probeTemplate.asToken()) + )), + "after nameScope:\n", + listNamesTemplate.asToken(), + "transparentScope4: #transparentScope4\n", + + let("local", "outer garbage") + )); + + String code = template.render(); + String expected = + """ + scope: + inserted scope: x1b + {x1b} + inserted transparentScope: x1c + {x1c} + inserted probe: + {x1c} + scope before insert scope: + {x1a} + scope after insert scope: + {x1a} + scope before insert transparentScope: + {x1a} + scope after insert transparentScope: + {x1c, x1a} + scope insert probe. + after scope: + {} + transparentScope: + inserted scope: x2b + {x2a, x2b} + inserted transparentScope: x2c + {x2a, x2c} + inserted probe: + {x2a, x2c} + transparentScope before insert scope: + {x2a} + transparentScope after insert scope: + {x2a} + transparentScope before insert transparentScope: + {x2a} + transparentScope after insert transparentScope: + {x2a, x2c} + transparentScope insert probe. + after transparentScope: + {x2a, x2c} + transparentScope2: abc + hashtagScope: + inserted scope: x3b + {x2a, x2c, x3a, x3b} + inserted transparentScope: x3c + {x2a, x2c, x3a, x3c} + inserted probe: + {x2a, x2c, x3a, x3c} + hashtagScope before insert scope: + {x2a, x2c, x3a} + hashtagScope after insert scope: + {x2a, x2c, x3a} + hashtagScope before insert transparentScope: + {x2a, x2c, x3a} + hashtagScope after insert transparentScope: + {x2a, x2c, x3a, x3c} + hashtagScope insert probe. + after hashtagScope: + {x2a, x2c, x3a, x3c} + nameScope: + inserted scope: x4b + {x2a, x2c, x3a, x3c, x4b} + inserted transparentScope: x4c + {x2a, x2c, x3a, x3c, x4c} + inserted probe: + {x2a, x2c, x3a, x3c, x4c} + nameScope before insert scope: + {x2a, x2c, x3a, x3c, x4a} + nameScope after insert scope: + {x2a, x2c, x3a, x3c, x4a} + nameScope before insert transparentScope: + {x2a, x2c, x3a, x3c, x4a} + nameScope after insert transparentScope: + {x2a, x2c, x3a, x3c, x4c, x4a} + nameScope insert probe. + after nameScope: + {x2a, x2c, x3a, x3c} + transparentScope4: abcde + """; + checkEQ(code, expected); + } + + public static void testHookAndScopes2() { + Hook hook1 = new Hook("Hook1"); + + var listNamesTemplate = Template.make(() -> scope( + "{", + structuralNames().exactOf(myStructuralTypeA).toList(list -> scope( + String.join(", ", list.stream().map(StructuralName::name).toList()) + )), + "}\n" + )); + + var template = Template.make(() -> scope( + "scope:\n", + hook1.anchor(scope( + let("local0", "scope garbage"), + let("local1", "LOCAL1"), + addStructuralName("x1a", myStructuralTypeA), + + "scope before insert scope:\n", + listNamesTemplate.asToken(), + hook1.insert(scope( + let("local2", "insert scope garbage"), + let("name", "x1b"), + addStructuralName("x1b", myStructuralTypeA), // does NOT escape to anchor scope + "inserted scope: #name\n", + "local1: #local1\n", + listNamesTemplate.asToken() + )), + "scope after insert scope:\n", + listNamesTemplate.asToken(), + + "scope before insert transparentScope:\n", + listNamesTemplate.asToken(), + hook1.insert(transparentScope( + let("nameTransparentScope", "x1c"), // escapes to caller + addStructuralName("x1c", myStructuralTypeA), // escapes to anchor scope + "inserted transparentScope: #nameTransparentScope\n", + "local1: #local1\n", + listNamesTemplate.asToken() + )), + "scope after insert transparentScope:\n", + "nameTransparentScope: #nameTransparentScope\n", + listNamesTemplate.asToken(), + + "scope before insert nameScope:\n", + listNamesTemplate.asToken(), + hook1.insert(nameScope( + let("nameNameScope", "x1d"), // escapes to caller + addStructuralName("x1d", myStructuralTypeA), // does NOT escape to anchor scope + "inserted nameScope: #nameNameScope\n", + "local1: #local1\n", + listNamesTemplate.asToken() + )), + "scope after insert nameScope:\n", + "nameNameScope: #nameNameScope\n", + listNamesTemplate.asToken(), + + "scope before insert hashtagScope:\n", + listNamesTemplate.asToken(), + hook1.insert(hashtagScope( + let("local2", "insert hashtagScope garbage"), + let("name", "x1e"), // escapes to caller + addStructuralName("x1e", myStructuralTypeA), // escapes to anchor scope + "inserted hashtagScope: #name\n", + "local1: #local1\n", + listNamesTemplate.asToken() + )), + "scope after insert hashtagScope:\n", + listNamesTemplate.asToken(), + + "scope insert probe.\n", + hook1.insert(scope( + "inserted probe:\n", + listNamesTemplate.asToken() + )) + )), + "after scope:\n", + listNamesTemplate.asToken(), + + let("name", "name garbage"), + let("local0", "outer garbage 0"), + let("local1", "outer garbage 1"), + let("local2", "outer garbage 2"), + let("nameTransparentScope", "outer garbage nameTransparentScope"), + let("nameNameScope", "outer garbage nameNameScope") + )); + + String code = template.render(); + String expected = + """ + scope: + inserted scope: x1b + local1: LOCAL1 + {x1b} + inserted transparentScope: x1c + local1: LOCAL1 + {x1c} + inserted nameScope: x1d + local1: LOCAL1 + {x1c, x1d} + inserted hashtagScope: x1e + local1: LOCAL1 + {x1c, x1e} + inserted probe: + {x1c, x1e} + scope before insert scope: + {x1a} + scope after insert scope: + {x1a} + scope before insert transparentScope: + {x1a} + scope after insert transparentScope: + nameTransparentScope: x1c + {x1c, x1a} + scope before insert nameScope: + {x1c, x1a} + scope after insert nameScope: + nameNameScope: x1d + {x1c, x1a} + scope before insert hashtagScope: + {x1c, x1a} + scope after insert hashtagScope: + {x1c, x1e, x1a} + scope insert probe. + after scope: + {} + """; + checkEQ(code, expected); + } + + // Analogue to testHookAndScopes2, but with "transparentScope" instead of "scope". + public static void testHookAndScopes3() { + Hook hook1 = new Hook("Hook1"); + + var listNamesTemplate = Template.make(() -> scope( + "{", + structuralNames().exactOf(myStructuralTypeA).toList(list -> scope( + String.join(", ", list.stream().map(StructuralName::name).toList()) + )), + "}\n" + )); + + var template = Template.make(() -> scope( + "transparentScope:\n", + hook1.anchor(transparentScope( + let("global0", "transparentScope garbage"), + let("global1", "GLOBAL1"), + addStructuralName("x1a", myStructuralTypeA), + + "transparentScope before insert scope:\n", + listNamesTemplate.asToken(), + hook1.insert(scope( + let("local2", "insert scope garbage"), + let("name", "x1b"), + addStructuralName("x1b", myStructuralTypeA), // does NOT escape to anchor scope + "inserted scope: #name\n", + "global1: #global1\n", + listNamesTemplate.asToken() + )), + "transparentScope after insert scope:\n", + listNamesTemplate.asToken(), + + "transparentScope before insert transparentScope:\n", + listNamesTemplate.asToken(), + hook1.insert(transparentScope( + let("nameTransparentScope", "x1c"), // escapes to caller + addStructuralName("x1c", myStructuralTypeA), // escapes to anchor scope + "inserted transparentScope: #nameTransparentScope\n", + "global1: #global1\n", + listNamesTemplate.asToken() + )), + "transparentScope after insert transparentScope:\n", + "nameTransparentScope: #nameTransparentScope\n", + listNamesTemplate.asToken(), + + "transparentScope before insert nameScope:\n", + listNamesTemplate.asToken(), + hook1.insert(nameScope( + let("nameNameScope", "x1d"), // escapes to caller + addStructuralName("x1d", myStructuralTypeA), // does NOT escape to anchor scope + "inserted nameScope: #nameNameScope\n", + "global1: #global1\n", + listNamesTemplate.asToken() + )), + "transparentScope after insert nameScope:\n", + "nameNameScope: #nameNameScope\n", + listNamesTemplate.asToken(), + + "transparentScope before insert hashtagScope:\n", + listNamesTemplate.asToken(), + hook1.insert(hashtagScope( + let("local2", "insert hashtagScope garbage"), + let("name", "x1e"), // escapes to caller + addStructuralName("x1e", myStructuralTypeA), // escapes to anchor scope + "inserted hashtagScope: #name\n", + "global1: #global1\n", + listNamesTemplate.asToken() + )), + "transparentScope after insert hashtagScope:\n", + listNamesTemplate.asToken(), + + "transparentScope insert probe.\n", + hook1.insert(scope( + "inserted probe:\n", + listNamesTemplate.asToken() + )) + )), + "after transparentScope:\n", + listNamesTemplate.asToken(), + """ + global0: #global0 + global1: #global1 + nameTransparentScope: #nameTransparentScope + nameNameScope: #nameNameScope + """, + let("name", "name garbage"), + let("local2", "outer garbage 2") + )); + + String code = template.render(); + String expected = + """ + transparentScope: + inserted scope: x1b + global1: GLOBAL1 + {x1a, x1b} + inserted transparentScope: x1c + global1: GLOBAL1 + {x1a, x1c} + inserted nameScope: x1d + global1: GLOBAL1 + {x1a, x1c, x1d} + inserted hashtagScope: x1e + global1: GLOBAL1 + {x1a, x1c, x1e} + inserted probe: + {x1a, x1c, x1e} + transparentScope before insert scope: + {x1a} + transparentScope after insert scope: + {x1a} + transparentScope before insert transparentScope: + {x1a} + transparentScope after insert transparentScope: + nameTransparentScope: x1c + {x1a, x1c} + transparentScope before insert nameScope: + {x1a, x1c} + transparentScope after insert nameScope: + nameNameScope: x1d + {x1a, x1c} + transparentScope before insert hashtagScope: + {x1a, x1c} + transparentScope after insert hashtagScope: + {x1a, x1c, x1e} + transparentScope insert probe. + after transparentScope: + {x1a, x1c, x1e} + global0: transparentScope garbage + global1: GLOBAL1 + nameTransparentScope: x1c + nameNameScope: x1d + """; + checkEQ(code, expected); + } + public static void testFailingNestedRendering() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "alpha\n" )); - var template2 = Template.make(() -> body( + var template2 = Template.make(() -> scope( "beta\n", // Nested "render" call not allowed! template1.render(), @@ -1813,63 +3334,63 @@ public static void testFailingNestedRendering() { } public static void testFailingDollarName1() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( let("x", $("")) // empty string not allowed )); String code = template1.render(); } public static void testFailingDollarName2() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( let("x", $("#abc")) // "#" character not allowed )); String code = template1.render(); } public static void testFailingDollarName3() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( let("x", $("abc#")) // "#" character not allowed )); String code = template1.render(); } public static void testFailingDollarName4() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( let("x", $(null)) // Null input to dollar )); String code = template1.render(); } public static void testFailingDollarName5() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "$" // empty dollar name )); String code = template1.render(); } public static void testFailingDollarName6() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "asdf$" // empty dollar name )); String code = template1.render(); } public static void testFailingDollarName7() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "asdf$1" // Bad pattern after dollar )); String code = template1.render(); } public static void testFailingDollarName8() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "abc$$abc" // empty dollar name )); String code = template1.render(); } public static void testFailingLetName1() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( let(null, $("abc")) // Null input for hashtag name )); String code = template1.render(); @@ -1877,20 +3398,20 @@ public static void testFailingLetName1() { public static void testFailingHashtagName1() { // Empty Template argument - var template1 = Template.make("", (String x) -> body( + var template1 = Template.make("", (String x) -> scope( )); String code = template1.render("abc"); } public static void testFailingHashtagName2() { // "#" character not allowed in template argument - var template1 = Template.make("abc#abc", (String x) -> body( + var template1 = Template.make("abc#abc", (String x) -> scope( )); String code = template1.render("abc"); } public static void testFailingHashtagName3() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( // Empty let hashtag name not allowed let("", "abc") )); @@ -1898,7 +3419,7 @@ public static void testFailingHashtagName3() { } public static void testFailingHashtagName4() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( // "#" character not allowed in let hashtag name let("xyz#xyz", "abc") )); @@ -1906,56 +3427,56 @@ public static void testFailingHashtagName4() { } public static void testFailingHashtagName5() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "#" // empty hashtag name )); String code = template1.render(); } public static void testFailingHashtagName6() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "asdf#" // empty hashtag name )); String code = template1.render(); } public static void testFailingHashtagName7() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "asdf#1" // Bad pattern after hashtag )); String code = template1.render(); } public static void testFailingHashtagName8() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "abc##abc" // empty hashtag name )); String code = template1.render(); } public static void testFailingDollarHashtagName1() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "#$" // empty hashtag name )); String code = template1.render(); } public static void testFailingDollarHashtagName2() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "$#" // empty dollar name )); String code = template1.render(); } public static void testFailingDollarHashtagName3() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "#$name" // empty hashtag name )); String code = template1.render(); } public static void testFailingDollarHashtagName4() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "$#name" // empty dollar name )); String code = template1.render(); @@ -1964,11 +3485,11 @@ public static void testFailingDollarHashtagName4() { public static void testFailingHook() { var hook1 = new Hook("Hook1"); - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "alpha\n" )); - var template2 = Template.make(() -> body( + var template2 = Template.make(() -> scope( "beta\n", // Use hook without hook1.anchor hook1.insert(template1.asToken()), @@ -1978,20 +3499,40 @@ public static void testFailingHook() { String code = template2.render(); } - public static void testFailingSample1() { - var template1 = Template.make(() -> body( - // No variable added yet. - let("v", dataNames(MUTABLE).exactOf(myInt).sample().name()), + public static void testFailingSample1a() { + var template1 = Template.make(() -> scope( + // No DataName added yet. + dataNames(MUTABLE).exactOf(myInt).sampleAndLetAs("v"), "v is #v\n" )); String code = template1.render(); } - public static void testFailingSample2() { - var template1 = Template.make(() -> body( + public static void testFailingSample1b() { + var template1 = Template.make(() -> scope( + // No StructuralName added yet. + structuralNames().exactOf(myStructuralTypeA).sampleAndLetAs("v"), + "v is #v\n" + )); + + String code = template1.render(); + } + + public static void testFailingSample2a() { + var template1 = Template.make(() -> scope( // no type restriction - let("v", dataNames(MUTABLE).sample().name()), + dataNames(MUTABLE).sampleAndLetAs("v"), + "v is #v\n" + )); + + String code = template1.render(); + } + + public static void testFailingSample2b() { + var template1 = Template.make(() -> scope( + // no type restriction + structuralNames().sampleAndLetAs("v"), "v is #v\n" )); @@ -2000,7 +3541,7 @@ public static void testFailingSample2() { public static void testFailingHashtag1() { // Duplicate hashtag definition from arguments. - var template1 = Template.make("a", "a", (String _, String _) -> body( + var template1 = Template.make("a", "a", (String _, String _) -> scope( "nothing\n" )); @@ -2008,7 +3549,7 @@ public static void testFailingHashtag1() { } public static void testFailingHashtag2() { - var template1 = Template.make("a", (String _) -> body( + var template1 = Template.make("a", (String _) -> scope( // Duplicate hashtag name let("a", "x"), "nothing\n" @@ -2018,7 +3559,7 @@ public static void testFailingHashtag2() { } public static void testFailingHashtag3() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( let("a", "x"), // Duplicate hashtag name let("a", "y"), @@ -2029,7 +3570,7 @@ public static void testFailingHashtag3() { } public static void testFailingHashtag4() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( // Missing hashtag name definition "#a\n" )); @@ -2037,9 +3578,20 @@ public static void testFailingHashtag4() { String code = template1.render(); } + public static void testFailingHashtag5() { + var template1 = Template.make(() -> scope( + "use before definition: #a\n", + // let is a token, and is only evaluated after + // the string above, and so the string above fails. + let("a", "x") + )); + + String code = template1.render(); + } + public static void testFailingBinding1() { var binding = new TemplateBinding(); - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "nothing\n" )); binding.bind(template1); @@ -2049,7 +3601,7 @@ public static void testFailingBinding1() { public static void testFailingBinding2() { var binding = new TemplateBinding(); - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "nothing\n", // binding was never bound. binding.get() @@ -2059,7 +3611,7 @@ public static void testFailingBinding2() { } public static void testFailingAddDataName1() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( // Must pick either MUTABLE or IMMUTABLE. addDataName("name", myInt, MUTABLE_OR_IMMUTABLE) )); @@ -2067,7 +3619,7 @@ public static void testFailingAddDataName1() { } public static void testFailingAddDataName2() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( // weight out of bounds [0..1000] addDataName("name", myInt, MUTABLE, 0) )); @@ -2075,7 +3627,7 @@ public static void testFailingAddDataName2() { } public static void testFailingAddDataName3() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( // weight out of bounds [0..1000] addDataName("name", myInt, MUTABLE, -1) )); @@ -2083,7 +3635,7 @@ public static void testFailingAddDataName3() { } public static void testFailingAddDataName4() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( // weight out of bounds [0..1000] addDataName("name", myInt, MUTABLE, 1001) )); @@ -2091,7 +3643,7 @@ public static void testFailingAddDataName4() { } public static void testFailingAddStructuralName1() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( // weight out of bounds [0..1000] addStructuralName("name", myStructuralTypeA, 0) )); @@ -2099,7 +3651,7 @@ public static void testFailingAddStructuralName1() { } public static void testFailingAddStructuralName2() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( // weight out of bounds [0..1000] addStructuralName("name", myStructuralTypeA, -1) )); @@ -2107,7 +3659,7 @@ public static void testFailingAddStructuralName2() { } public static void testFailingAddStructuralName3() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( // weight out of bounds [0..1000] addStructuralName("name", myStructuralTypeA, 1001) )); @@ -2116,7 +3668,7 @@ public static void testFailingAddStructuralName3() { // Duplicate name in the same scope, name identical -> expect RendererException. public static void testFailingAddNameDuplication1() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( addDataName("name", myInt, MUTABLE), addDataName("name", myInt, MUTABLE) )); @@ -2125,7 +3677,7 @@ public static void testFailingAddNameDuplication1() { // Duplicate name in the same scope, names have different mutability -> expect RendererException. public static void testFailingAddNameDuplication2() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( addDataName("name", myInt, MUTABLE), addDataName("name", myInt, IMMUTABLE) )); @@ -2134,7 +3686,7 @@ public static void testFailingAddNameDuplication2() { // Duplicate name in the same scope, names have different type -> expect RendererException. public static void testFailingAddNameDuplication3() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( addDataName("name", myInt, MUTABLE), addDataName("name", myLong, MUTABLE) )); @@ -2143,7 +3695,7 @@ public static void testFailingAddNameDuplication3() { // Duplicate name in the same scope, name identical -> expect RendererException. public static void testFailingAddNameDuplication4() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( addStructuralName("name", myStructuralTypeA), addStructuralName("name", myStructuralTypeA) )); @@ -2152,7 +3704,7 @@ public static void testFailingAddNameDuplication4() { // Duplicate name in the same scope, names have different type -> expect RendererException. public static void testFailingAddNameDuplication5() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( addStructuralName("name", myStructuralTypeA), addStructuralName("name", myStructuralTypeB) )); @@ -2161,10 +3713,10 @@ public static void testFailingAddNameDuplication5() { // Duplicate name in inner Template, name identical -> expect RendererException. public static void testFailingAddNameDuplication6() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( addDataName("name", myInt, MUTABLE) )); - var template2 = Template.make(() -> body( + var template2 = Template.make(() -> scope( addDataName("name", myInt, MUTABLE), template1.asToken() )); @@ -2175,11 +3727,11 @@ public static void testFailingAddNameDuplication6() { public static void testFailingAddNameDuplication7() { var hook1 = new Hook("Hook1"); - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( addDataName("name", myInt, MUTABLE), - hook1.anchor( + hook1.anchor(scope( addDataName("name", myInt, MUTABLE) - ) + )) )); String code = template1.render(); } @@ -2188,19 +3740,94 @@ public static void testFailingAddNameDuplication7() { public static void testFailingAddNameDuplication8() { var hook1 = new Hook("Hook1"); - var template1 = Template.make(() -> body( - addDataName("name", myInt, MUTABLE) + var template1 = Template.make(() -> transparentScope( + addDataName("name", myInt, MUTABLE) // escapes )); - var template2 = Template.make(() -> body( - hook1.anchor( + var template2 = Template.make(() -> scope( + hook1.anchor(scope( addDataName("name", myInt, MUTABLE), hook1.insert(template1.asToken()) - ) + )) )); String code = template2.render(); } + public static void testFailingScope1() { + var template = Template.make(() -> scope( + transparentScope( + let("x", "x1") // escapes + ), + let("x", "x2") // second definition + )); + String code = template.render(); + } + + public static void testFailingScope2() { + var template = Template.make(() -> scope( + nameScope( + let("x", "x1") // escapes + ), + let("x", "x2") // second definition + )); + String code = template.render(); + } + + public static void testFailingScope3() { + var template = Template.make(() -> scope( + addStructuralName("a", myStructuralTypeA), + addStructuralName("b", myStructuralTypeA), + structuralNames().exactOf(myStructuralTypeA).forEach(sn -> transparentScope( + let("x", sn.name()) // leads to duplicate hashtag + )) + )); + String code = template.render(); + } + + public static void testFailingScope4() { + var template = Template.make(() -> scope( + addStructuralName("a", myStructuralTypeA), + addStructuralName("b", myStructuralTypeA), + structuralNames().exactOf(myStructuralTypeA).forEach(sn -> nameScope( + let("x", sn.name()) // leads to duplicate hashtag + )) + )); + String code = template.render(); + } + + public static void testFailingScope5() { + var template = Template.make(() -> scope( + addStructuralName("a", myStructuralTypeA), + addStructuralName("b", myStructuralTypeA), + structuralNames().exactOf(myStructuralTypeA).forEach(sn -> transparentScope( + addStructuralName("x", myStructuralTypeA) // leads to duplicate name + )) + )); + String code = template.render(); + } + + public static void testFailingScope6() { + var template = Template.make(() -> scope( + addStructuralName("a", myStructuralTypeA), + addStructuralName("b", myStructuralTypeA), + structuralNames().exactOf(myStructuralTypeA).forEach(sn -> hashtagScope( + addStructuralName("x", myStructuralTypeA) // leads to duplicate name + )) + )); + String code = template.render(); + } + + public static void testFailingScope7() { + var template = Template.make(() -> scope( + addStructuralName("a", myStructuralTypeA), + addStructuralName("b", myStructuralTypeA), + structuralNames().exactOf(myStructuralTypeA).forEach(sn -> setFuelCostScope( + addStructuralName("x", myStructuralTypeA) // leads to duplicate name + )) + )); + String code = template.render(); + } + public static void expectRendererException(FailingTest test, String errorPrefix) { try { test.run(); From c6d47ed1e1d512b4025ae2dba75a57c1f2fd2cec Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Tue, 24 Mar 2026 15:11:24 +0000 Subject: [PATCH 078/168] 8373866: Refactor java/net/httpclient/ThrowingSubscribers*.java tests to use JUnit5 Reviewed-by: phh Backport-of: d8eb1259f4c0d80861401612e9fc7def1466602e --- .../AbstractThrowingSubscribers.java | 143 +++++++++--------- .../ThrowingSubscribersAsInputStream.java | 10 +- ...ThrowingSubscribersAsInputStreamAsync.java | 10 +- .../ThrowingSubscribersAsLimiting.java | 8 +- .../ThrowingSubscribersAsLimitingAsync.java | 8 +- .../ThrowingSubscribersAsLines.java | 10 +- .../ThrowingSubscribersAsLinesAsync.java | 10 +- .../ThrowingSubscribersAsString.java | 10 +- .../ThrowingSubscribersAsStringAsync.java | 10 +- .../httpclient/ThrowingSubscribersSanity.java | 10 +- 10 files changed, 125 insertions(+), 104 deletions(-) diff --git a/test/jdk/java/net/httpclient/AbstractThrowingSubscribers.java b/test/jdk/java/net/httpclient/AbstractThrowingSubscribers.java index 7362ada97721..20d8a332263a 100644 --- a/test/jdk/java/net/httpclient/AbstractThrowingSubscribers.java +++ b/test/jdk/java/net/httpclient/AbstractThrowingSubscribers.java @@ -22,14 +22,6 @@ */ import jdk.test.lib.net.SimpleSSLContext; -import org.testng.ITestContext; -import org.testng.ITestResult; -import org.testng.SkipException; -import org.testng.annotations.AfterTest; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.DataProvider; import javax.net.ssl.SSLContext; import java.io.BufferedReader; @@ -47,7 +39,6 @@ import java.net.http.HttpResponse.BodySubscriber; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; -import java.util.Arrays; import java.util.EnumSet; import java.util.List; import java.util.concurrent.CompletableFuture; @@ -71,24 +62,33 @@ import static java.net.http.HttpClient.Version.HTTP_1_1; import static java.net.http.HttpClient.Version.HTTP_2; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; +import org.junit.jupiter.api.AfterAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.api.extension.TestWatcher; public abstract class AbstractThrowingSubscribers implements HttpServerAdapters { - SSLContext sslContext; - HttpTestServer httpTestServer; // HTTP/1.1 [ 4 servers ] - HttpTestServer httpsTestServer; // HTTPS/1.1 - HttpTestServer http2TestServer; // HTTP/2 ( h2c ) - HttpTestServer https2TestServer; // HTTP/2 ( h2 ) - String httpURI_fixed; - String httpURI_chunk; - String httpsURI_fixed; - String httpsURI_chunk; - String http2URI_fixed; - String http2URI_chunk; - String https2URI_fixed; - String https2URI_chunk; + static SSLContext sslContext; + static HttpTestServer httpTestServer; // HTTP/1.1 [ 4 servers ] + static HttpTestServer httpsTestServer; // HTTPS/1.1 + static HttpTestServer http2TestServer; // HTTP/2 ( h2c ) + static HttpTestServer https2TestServer; // HTTP/2 ( h2 ) + static String httpURI_fixed; + static String httpURI_chunk; + static String httpsURI_fixed; + static String httpsURI_chunk; + static String http2URI_fixed; + static String http2URI_chunk; + static String https2URI_fixed; + static String https2URI_chunk; static final int ITERATION_COUNT = 1; static final int REPEAT_RESPONSE = 3; @@ -107,8 +107,34 @@ public static String now() { return String.format("[%d s, %d ms, %d ns] ", secs, mill, nan); } - final ReferenceTracker TRACKER = ReferenceTracker.INSTANCE; - private volatile HttpClient sharedClient; + static final class TestStopper implements TestWatcher, BeforeEachCallback { + final AtomicReference failed = new AtomicReference<>(); + TestStopper() { } + @Override + public void testFailed(ExtensionContext context, Throwable cause) { + if (stopAfterFirstFailure()) { + String msg = "Aborting due to: " + cause; + failed.compareAndSet(null, msg); + FAILURES.putIfAbsent(context.getDisplayName(), cause); + System.out.printf("%nTEST FAILED: %s%s%n\tAborting due to %s%n%n", + now(), context.getDisplayName(), cause); + System.err.printf("%nTEST FAILED: %s%s%n\tAborting due to %s%n%n", + now(), context.getDisplayName(), cause); + } + } + + @Override + public void beforeEach(ExtensionContext context) { + String msg = failed.get(); + Assumptions.assumeTrue(msg == null, msg); + } + } + + @RegisterExtension + static final TestStopper stopper = new TestStopper(); + + static final ReferenceTracker TRACKER = ReferenceTracker.INSTANCE; + private static volatile HttpClient sharedClient; static class TestExecutor implements Executor { final AtomicLong tasks = new AtomicLong(); @@ -134,34 +160,12 @@ public void execute(Runnable command) { } } - protected boolean stopAfterFirstFailure() { + protected static boolean stopAfterFirstFailure() { return Boolean.getBoolean("jdk.internal.httpclient.debug"); } - final AtomicReference skiptests = new AtomicReference<>(); - void checkSkip() { - var skip = skiptests.get(); - if (skip != null) throw skip; - } - static String name(ITestResult result) { - var params = result.getParameters(); - return result.getName() - + (params == null ? "()" : Arrays.toString(result.getParameters())); - } - - @BeforeMethod - void beforeMethod(ITestContext context) { - if (stopAfterFirstFailure() && context.getFailedTests().size() > 0) { - if (skiptests.get() == null) { - SkipException skip = new SkipException("some tests failed"); - skip.setStackTrace(new StackTraceElement[0]); - skiptests.compareAndSet(null, skip); - } - } - } - - @AfterClass - static final void printFailedTests(ITestContext context) { + @AfterAll + static final void printFailedTests() { out.println("\n========================="); try { // Exceptions should already have been added to FAILURES @@ -186,7 +190,7 @@ static final void printFailedTests(ITestContext context) { } } - private String[] uris() { + private static String[] uris() { return new String[] { httpURI_fixed, httpURI_chunk, @@ -199,10 +203,9 @@ private String[] uris() { }; } - static AtomicLong URICOUNT = new AtomicLong(); + static final AtomicLong URICOUNT = new AtomicLong(); - @DataProvider(name = "sanity") - public Object[][] sanity() { + public static Object[][] sanity() { String[] uris = uris(); Object[][] result = new Object[uris.length * 2][]; int i = 0; @@ -215,11 +218,7 @@ public Object[][] sanity() { return result; } - @DataProvider(name = "variants") - public Object[][] variants(ITestContext context) { - if (stopAfterFirstFailure() && context.getFailedTests().size() > 0) { - return new Object[0][]; - } + public static Object[][] variants() { String[] uris = uris(); Object[][] result = new Object[uris.length * 2 * 2][]; int i = 0; @@ -236,7 +235,7 @@ public Object[][] variants(ITestContext context) { return result; } - private HttpClient makeNewClient() { + private static HttpClient makeNewClient() { clientCount.incrementAndGet(); HttpClient client = HttpClient.newBuilder() .proxy(HttpClient.Builder.NO_PROXY) @@ -246,11 +245,11 @@ private HttpClient makeNewClient() { return TRACKER.track(client); } - HttpClient newHttpClient(boolean share) { + static HttpClient newHttpClient(boolean share) { if (!share) return makeNewClient(); HttpClient shared = sharedClient; if (shared != null) return shared; - synchronized (this) { + synchronized (AbstractThrowingSubscribers.class) { shared = sharedClient; if (shared == null) { shared = sharedClient = makeNewClient(); @@ -307,7 +306,7 @@ protected void testSanityImpl(String uri, boolean sameClient) HttpResponse response = client.send(req, handler); String body = response.body(); Stream.of(body.split("\n")).forEach(u -> - assertEquals(URI.create(u).getPath(), URI.create(uri2).getPath())); + assertEquals(URI.create(uri2).getPath(), URI.create(u).getPath())); if (!sameClient) { // Wait for the client to be garbage collected. // we use the ReferenceTracker API rather than HttpClient::close here, @@ -419,7 +418,6 @@ void testThrowing(String name, String uri, boolean sameClient, boolean async, EnumSet excludes) throws Exception { - checkSkip(); out.printf("%n%s%s%n", now(), name); try { testThrowing(uri, sameClient, handlers, finisher, thrower, async, excludes); @@ -498,7 +496,6 @@ private void testThrowing(String uri, boolean sameClient, if (error != null) throw error; System.out.println(now() + "operation finished normally: " + tracker.getName()); System.err.println(now() + "operation finished normally: " + tracker.getName()); - } } } @@ -757,8 +754,11 @@ public CompletionStage getBody() { } - @BeforeTest - public void setup() throws Exception { + @BeforeAll + public static void setup() throws Exception { + System.out.println(now() + "setup"); + System.err.println(now() + "setup"); + sslContext = new SimpleSSLContext().get(); if (sslContext == null) throw new AssertionError("Unexpected null sslContext"); @@ -801,8 +801,11 @@ public void setup() throws Exception { https2TestServer.start(); } - @AfterTest - public void teardown() throws Exception { + @AfterAll + public static void teardown() throws Exception { + System.out.println(now() + "teardown"); + System.err.println(now() + "teardown"); + String sharedClientName = sharedClient == null ? null : sharedClient.toString(); sharedClient = null; diff --git a/test/jdk/java/net/httpclient/ThrowingSubscribersAsInputStream.java b/test/jdk/java/net/httpclient/ThrowingSubscribersAsInputStream.java index 35ac1fd3f9c3..a4c0fc72004b 100644 --- a/test/jdk/java/net/httpclient/ThrowingSubscribersAsInputStream.java +++ b/test/jdk/java/net/httpclient/ThrowingSubscribersAsInputStream.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,14 +29,16 @@ * @build jdk.test.lib.net.SimpleSSLContext * ReferenceTracker ThrowingSubscribersAsInputStream AbstractThrowingSubscribers * jdk.httpclient.test.lib.common.HttpServerAdapters - * @run testng/othervm -Djdk.internal.httpclient.debug=true ThrowingSubscribersAsInputStream + * @run junit/othervm -Djdk.internal.httpclient.debug=true ThrowingSubscribersAsInputStream */ -import org.testng.annotations.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; public class ThrowingSubscribersAsInputStream extends AbstractThrowingSubscribers { - @Test(dataProvider = "variants") + @ParameterizedTest + @MethodSource("variants") public void testThrowingAsInputStream(String uri, boolean sameClient, Thrower thrower) throws Exception { super.testThrowingAsInputStreamImpl(uri, sameClient, thrower); diff --git a/test/jdk/java/net/httpclient/ThrowingSubscribersAsInputStreamAsync.java b/test/jdk/java/net/httpclient/ThrowingSubscribersAsInputStreamAsync.java index db25ebaca3e8..aec4641917cd 100644 --- a/test/jdk/java/net/httpclient/ThrowingSubscribersAsInputStreamAsync.java +++ b/test/jdk/java/net/httpclient/ThrowingSubscribersAsInputStreamAsync.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,14 +29,16 @@ * @build jdk.test.lib.net.SimpleSSLContext * ReferenceTracker ThrowingSubscribersAsInputStreamAsync AbstractThrowingSubscribers * jdk.httpclient.test.lib.common.HttpServerAdapters - * @run testng/othervm -Djdk.internal.httpclient.debug=true ThrowingSubscribersAsInputStreamAsync + * @run junit/othervm -Djdk.internal.httpclient.debug=true ThrowingSubscribersAsInputStreamAsync */ -import org.testng.annotations.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; public class ThrowingSubscribersAsInputStreamAsync extends AbstractThrowingSubscribers { - @Test(dataProvider = "variants") + @ParameterizedTest + @MethodSource("variants") public void testThrowingAsInputStreamAsync(String uri, boolean sameClient, Thrower thrower) throws Exception { super.testThrowingAsInputStreamAsyncImpl(uri, sameClient, thrower); diff --git a/test/jdk/java/net/httpclient/ThrowingSubscribersAsLimiting.java b/test/jdk/java/net/httpclient/ThrowingSubscribersAsLimiting.java index 11ced214ece9..603a8558856a 100644 --- a/test/jdk/java/net/httpclient/ThrowingSubscribersAsLimiting.java +++ b/test/jdk/java/net/httpclient/ThrowingSubscribersAsLimiting.java @@ -35,18 +35,20 @@ * ReferenceTracker * jdk.httpclient.test.lib.common.HttpServerAdapters * jdk.test.lib.net.SimpleSSLContext - * @run testng/othervm -Djdk.internal.httpclient.debug=true ThrowingSubscribersAsLimiting + * @run junit/othervm -Djdk.internal.httpclient.debug=true ThrowingSubscribersAsLimiting */ -import org.testng.annotations.Test; import java.net.http.HttpResponse; import java.util.function.Supplier; import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; public class ThrowingSubscribersAsLimiting extends AbstractThrowingSubscribers { - @Test(dataProvider = "variants") + @ParameterizedTest + @MethodSource("variants") public void test(String uri, boolean sameClient, Thrower thrower) throws Exception { test(uri, sameClient, thrower, false); } diff --git a/test/jdk/java/net/httpclient/ThrowingSubscribersAsLimitingAsync.java b/test/jdk/java/net/httpclient/ThrowingSubscribersAsLimitingAsync.java index 00e00c12db53..e45c6d6487e6 100644 --- a/test/jdk/java/net/httpclient/ThrowingSubscribersAsLimitingAsync.java +++ b/test/jdk/java/net/httpclient/ThrowingSubscribersAsLimitingAsync.java @@ -35,15 +35,17 @@ * ReferenceTracker * jdk.httpclient.test.lib.common.HttpServerAdapters * jdk.test.lib.net.SimpleSSLContext - * @run testng/othervm -Djdk.internal.httpclient.debug=true ThrowingSubscribersAsLimitingAsync + * @run junit/othervm -Djdk.internal.httpclient.debug=true ThrowingSubscribersAsLimitingAsync */ -import org.testng.annotations.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; public class ThrowingSubscribersAsLimitingAsync extends ThrowingSubscribersAsLimiting { @Override - @Test(dataProvider = "variants") + @ParameterizedTest + @MethodSource("variants") public void test(String uri, boolean sameClient, Thrower thrower) throws Exception { test(uri, sameClient, thrower, true); } diff --git a/test/jdk/java/net/httpclient/ThrowingSubscribersAsLines.java b/test/jdk/java/net/httpclient/ThrowingSubscribersAsLines.java index f303ef12b54b..ba594166b725 100644 --- a/test/jdk/java/net/httpclient/ThrowingSubscribersAsLines.java +++ b/test/jdk/java/net/httpclient/ThrowingSubscribersAsLines.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,14 +29,16 @@ * @build jdk.test.lib.net.SimpleSSLContext * ReferenceTracker ThrowingSubscribersAsLines AbstractThrowingSubscribers * jdk.httpclient.test.lib.common.HttpServerAdapters - * @run testng/othervm -Djdk.internal.httpclient.debug=true ThrowingSubscribersAsLines + * @run junit/othervm -Djdk.internal.httpclient.debug=true ThrowingSubscribersAsLines */ -import org.testng.annotations.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; public class ThrowingSubscribersAsLines extends AbstractThrowingSubscribers { - @Test(dataProvider = "variants") + @ParameterizedTest + @MethodSource("variants") public void testThrowingAsLines(String uri, boolean sameClient, Thrower thrower) throws Exception { super.testThrowingAsLinesImpl(uri, sameClient, thrower); diff --git a/test/jdk/java/net/httpclient/ThrowingSubscribersAsLinesAsync.java b/test/jdk/java/net/httpclient/ThrowingSubscribersAsLinesAsync.java index 2e1fc942a736..a76ff8824630 100644 --- a/test/jdk/java/net/httpclient/ThrowingSubscribersAsLinesAsync.java +++ b/test/jdk/java/net/httpclient/ThrowingSubscribersAsLinesAsync.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,14 +29,16 @@ * @build jdk.test.lib.net.SimpleSSLContext * ReferenceTracker ThrowingSubscribersAsLinesAsync AbstractThrowingSubscribers * jdk.httpclient.test.lib.common.HttpServerAdapters - * @run testng/othervm -Djdk.internal.httpclient.debug=true ThrowingSubscribersAsLinesAsync + * @run junit/othervm -Djdk.internal.httpclient.debug=true ThrowingSubscribersAsLinesAsync */ -import org.testng.annotations.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; public class ThrowingSubscribersAsLinesAsync extends AbstractThrowingSubscribers { - @Test(dataProvider = "variants") + @ParameterizedTest + @MethodSource("variants") public void testThrowingAsLinesAsync(String uri, boolean sameClient, Thrower thrower) throws Exception { super.testThrowingAsLinesAsyncImpl(uri, sameClient, thrower); diff --git a/test/jdk/java/net/httpclient/ThrowingSubscribersAsString.java b/test/jdk/java/net/httpclient/ThrowingSubscribersAsString.java index 56e444f09c2b..ba5506750964 100644 --- a/test/jdk/java/net/httpclient/ThrowingSubscribersAsString.java +++ b/test/jdk/java/net/httpclient/ThrowingSubscribersAsString.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,14 +29,16 @@ * @build jdk.test.lib.net.SimpleSSLContext * ReferenceTracker ThrowingSubscribersAsString AbstractThrowingSubscribers * jdk.httpclient.test.lib.common.HttpServerAdapters - * @run testng/othervm -Djdk.internal.httpclient.debug=true ThrowingSubscribersAsString + * @run junit/othervm -Djdk.internal.httpclient.debug=true ThrowingSubscribersAsString */ -import org.testng.annotations.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; public class ThrowingSubscribersAsString extends AbstractThrowingSubscribers { - @Test(dataProvider = "variants") + @ParameterizedTest + @MethodSource("variants") public void testThrowingAsString(String uri, boolean sameClient, Thrower thrower) throws Exception { super.testThrowingAsStringImpl(uri, sameClient, thrower); diff --git a/test/jdk/java/net/httpclient/ThrowingSubscribersAsStringAsync.java b/test/jdk/java/net/httpclient/ThrowingSubscribersAsStringAsync.java index 563fe39bd69b..304d98e69392 100644 --- a/test/jdk/java/net/httpclient/ThrowingSubscribersAsStringAsync.java +++ b/test/jdk/java/net/httpclient/ThrowingSubscribersAsStringAsync.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,14 +29,16 @@ * @build jdk.test.lib.net.SimpleSSLContext * ReferenceTracker ThrowingSubscribersAsStringAsync AbstractThrowingSubscribers * jdk.httpclient.test.lib.common.HttpServerAdapters - * @run testng/othervm -Djdk.internal.httpclient.debug=true ThrowingSubscribersAsStringAsync + * @run junit/othervm -Djdk.internal.httpclient.debug=true ThrowingSubscribersAsStringAsync */ -import org.testng.annotations.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; public class ThrowingSubscribersAsStringAsync extends AbstractThrowingSubscribers { - @Test(dataProvider = "variants") + @ParameterizedTest + @MethodSource("variants") public void testThrowingAsStringAsync(String uri, boolean sameClient, Thrower thrower) throws Exception { super.testThrowingAsStringAsyncImpl(uri, sameClient, thrower); diff --git a/test/jdk/java/net/httpclient/ThrowingSubscribersSanity.java b/test/jdk/java/net/httpclient/ThrowingSubscribersSanity.java index c480d55e1479..296e9151c9ee 100644 --- a/test/jdk/java/net/httpclient/ThrowingSubscribersSanity.java +++ b/test/jdk/java/net/httpclient/ThrowingSubscribersSanity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,14 +29,16 @@ * @build jdk.test.lib.net.SimpleSSLContext * ReferenceTracker ThrowingSubscribersSanity AbstractThrowingSubscribers * jdk.httpclient.test.lib.common.HttpServerAdapters - * @run testng/othervm -Djdk.internal.httpclient.debug=true ThrowingSubscribersSanity + * @run junit/othervm -Djdk.internal.httpclient.debug=true ThrowingSubscribersSanity */ -import org.testng.annotations.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; public class ThrowingSubscribersSanity extends AbstractThrowingSubscribers { - @Test(dataProvider = "sanity") + @ParameterizedTest + @MethodSource("sanity") public void testSanity(String uri, boolean sameClient) throws Exception { super.testSanityImpl(uri, sameClient); From c6590d8ef2735a8ed7f66b90c2db3c95981a0b70 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Tue, 24 Mar 2026 15:11:46 +0000 Subject: [PATCH 079/168] 8377602: Create automated test for PageRange Backport-of: 66e192c6005fccaba07fbb41393ddd16fc9fad30 --- .../awt/print/PrinterJob/PageRangesAuto.java | 199 ++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 test/jdk/java/awt/print/PrinterJob/PageRangesAuto.java diff --git a/test/jdk/java/awt/print/PrinterJob/PageRangesAuto.java b/test/jdk/java/awt/print/PrinterJob/PageRangesAuto.java new file mode 100644 index 000000000000..8d50ef9c2c22 --- /dev/null +++ b/test/jdk/java/awt/print/PrinterJob/PageRangesAuto.java @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2007, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.awt.Font; +import java.awt.Graphics; +import java.awt.print.PageFormat; +import java.awt.print.Pageable; +import java.awt.print.Printable; +import java.awt.print.PrinterException; +import java.awt.print.PrinterJob; +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.List; +import java.util.stream.IntStream; + +import javax.print.attribute.HashPrintRequestAttributeSet; +import javax.print.attribute.PrintRequestAttributeSet; +import javax.print.attribute.standard.Destination; +import javax.print.attribute.standard.PageRanges; + +/* + * @test + * @bug 6575331 8297191 + * @key printer + * @summary Automatically verifies all the pages in a range are printed + * @run main PageRangesAuto + */ +public class PageRangesAuto implements Pageable, Printable { + + private static final Font font = new Font(Font.SERIF, Font.PLAIN, 50); + + private static final int MAX_PAGE = 10; + + private static final int[][] ranges = { + {1, 1}, + {1, MAX_PAGE}, + {2, 3}, + {3, 6}, + {4, 7}, + {7, 7}, + {9, MAX_PAGE}, + {MAX_PAGE, MAX_PAGE}, + }; + + private enum Type { + PRINTABLE, + PAGEABLE + } + + private final BitSet printedPages = new BitSet(); + + /** + * Configures a printer job and prints it. + * @param type the type of the interface tested + * ({@code Printable} or {@code Pageable}) + * @param pageRange the range of pages to print; + * if {@code null}, print all pages + * @return a bit set of printed page numbers + */ + private static BitSet printJob(final Type type, + final PageRanges pageRange) + throws PrinterException { + final PageRangesAuto test = new PageRangesAuto(); + + final PrinterJob job = PrinterJob.getPrinterJob(); + final String baseName = type.name().toLowerCase(); + + switch (type) { + case PRINTABLE -> job.setPrintable(test); + case PAGEABLE -> job.setPageable(test); + } + + String fileName = pageRange == null + ? baseName + "-all.pdf" + : String.format("%s-%d-%d.pdf", + baseName, + pageRange.getMembers()[0][0], + pageRange.getMembers()[0][1]); + + PrintRequestAttributeSet set = new HashPrintRequestAttributeSet(); + set.add(new Destination(new File(fileName) + .toURI())); + if (pageRange != null) { + set.add(pageRange); + } + + job.print(set); + + return test.printedPages; + } + + public static void main(String[] args) throws Exception { + final List errors = new ArrayList<>(); + + for (Type type : Type.values()) { + BitSet pages; // Printed pages + + // Print all pages + System.out.println(type + " - all pages"); + pages = printJob(type, null); + if (!IntStream.range(0, MAX_PAGE) + .allMatch(pages::get)) { + errors.add(new Error("Not all pages printed in " + type + ": " + + pages)); + } + + // Print page range + for (int[] range : ranges) { + System.out.println(type + " - " + Arrays.toString(range)); + pages = printJob(type, new PageRanges(range[0], range[1])); + if (!IntStream.range(range[0] - 1, range[1]) + .allMatch(pages::get)) { + errors.add(new Error("Not all pages printed in " + type + + " within the range " + + Arrays.toString(range) + + ": " + pages)); + } + } + } + + if (!errors.isEmpty()) { + errors.forEach(System.err::println); + throw new RuntimeException("Errors detected: " + errors.size() + + ". - " + errors.getFirst()); + } + } + + @Override + public int print(Graphics g, PageFormat format, int pageIndex) + throws PrinterException { + printedPages.set(pageIndex); + + final int pageNo = pageIndex + 1; + System.out.println(" test.printPage " + pageNo); + if (pageIndex >= MAX_PAGE) { + return NO_SUCH_PAGE; + } + + g.setFont(font); + g.drawString("Page: " + pageNo, + 100, 150); + + return PAGE_EXISTS; + } + + @Override + public int getNumberOfPages() { + System.out.println(" test.getNumberOfPages = " + MAX_PAGE); + return MAX_PAGE; + } + + @Override + public PageFormat getPageFormat(int pageIndex) + throws IndexOutOfBoundsException { + checkPageIndex(pageIndex); + return new PageFormat(); + } + + @Override + public Printable getPrintable(int pageIndex) + throws IndexOutOfBoundsException { + checkPageIndex(pageIndex); + System.out.println(" test.getPrintable(" + (pageIndex + 1) + ")"); + return this; + } + + private static void checkPageIndex(int pageIndex) + throws IndexOutOfBoundsException { + if (pageIndex < 0) { + throw new IndexOutOfBoundsException("pageIndex < 0"); + } + + if (pageIndex >= MAX_PAGE) { + throw new IndexOutOfBoundsException("pageIndex >= " + MAX_PAGE); + } + } +} From 40506bd177c01335c624039650d093e85f7628b0 Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Tue, 24 Mar 2026 15:20:24 +0000 Subject: [PATCH 080/168] 8373120: Virtual thread stuck in BLOCKED state Reviewed-by: pchilanomate, phh Backport-of: 26aab3cccdbcf98c329c8d67093eb2dbf4b164e5 --- .../classes/java/lang/VirtualThread.java | 25 ++-- .../stress/NotifiedThenTimedOutWait.java | 134 ++++++++++++++++++ 2 files changed, 148 insertions(+), 11 deletions(-) create mode 100644 test/jdk/java/lang/Thread/virtual/stress/NotifiedThenTimedOutWait.java diff --git a/src/java.base/share/classes/java/lang/VirtualThread.java b/src/java.base/share/classes/java/lang/VirtualThread.java index b0b134e69a35..c4b01f9f76fe 100644 --- a/src/java.base/share/classes/java/lang/VirtualThread.java +++ b/src/java.base/share/classes/java/lang/VirtualThread.java @@ -603,8 +603,11 @@ private void afterYield() { // Object.wait if (s == WAITING || s == TIMED_WAITING) { int newState; + boolean blocked; if (s == WAITING) { setState(newState = WAIT); + // may have been notified while in transition + blocked = notified && compareAndSetState(WAIT, BLOCKED); } else { // For timed-wait, a timeout task is scheduled to execute. The timeout // task will change the thread state to UNBLOCKED and submit the thread @@ -619,22 +622,22 @@ private void afterYield() { byte seqNo = ++timedWaitSeqNo; timeoutTask = schedule(() -> waitTimeoutExpired(seqNo), timeout, MILLISECONDS); setState(newState = TIMED_WAIT); + // May have been notified while in transition. This must be done while + // holding the monitor to avoid changing the state of a new timed wait call. + blocked = notified && compareAndSetState(TIMED_WAIT, BLOCKED); } } - // may have been notified while in transition to wait state - if (notified && compareAndSetState(newState, BLOCKED)) { - // may have even been unblocked already + if (blocked) { + // may have been unblocked already if (blockPermit && compareAndSetState(BLOCKED, UNBLOCKED)) { - submitRunContinuation(); + lazySubmitRunContinuation(); + } + } else { + // may have been interrupted while in transition to wait state + if (interrupted && compareAndSetState(newState, UNBLOCKED)) { + lazySubmitRunContinuation(); } - return; - } - - // may have been interrupted while in transition to wait state - if (interrupted && compareAndSetState(newState, UNBLOCKED)) { - submitRunContinuation(); - return; } return; } diff --git a/test/jdk/java/lang/Thread/virtual/stress/NotifiedThenTimedOutWait.java b/test/jdk/java/lang/Thread/virtual/stress/NotifiedThenTimedOutWait.java new file mode 100644 index 000000000000..0734a794b90e --- /dev/null +++ b/test/jdk/java/lang/Thread/virtual/stress/NotifiedThenTimedOutWait.java @@ -0,0 +1,134 @@ +/* + * 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 8373120 + * @summary Stress test two consecutive timed Object.wait calls where only the first one is notified. + * @run main/othervm -XX:CompileCommand=exclude,java.lang.VirtualThread::afterYield NotifiedThenTimedOutWait 1 100 100 + */ + +/* + * @test + * @run main/othervm -XX:CompileCommand=exclude,java.lang.VirtualThread::afterYield NotifiedThenTimedOutWait 2 100 100 + */ + +import java.time.Instant; +import java.util.concurrent.Phaser; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadLocalRandom; + +public class NotifiedThenTimedOutWait { + public static void main(String[] args) throws Exception { + int race = (args.length > 0) ? Integer.parseInt(args[0]) : 1; + int nruns = (args.length > 1) ? Integer.parseInt(args[1]) : 100; + int iterations = (args.length > 2) ? Integer.parseInt(args[2]) : 100; + + for (int i = 1; i <= nruns; i++) { + System.out.println(Instant.now() + " => " + i + " of " + nruns); + switch (race) { + case 1 -> race1(iterations); + case 2 -> race2(iterations); + } + } + } + + /** + * Barrier in synchronized block. + */ + private static void race1(int iterations) throws InterruptedException { + final int timeout = 1; + var lock = new Object(); + var start = new Phaser(2); + var end = new Phaser(2); + + var vthread = Thread.ofVirtual().start(() -> { + try { + for (int j = 0; j < iterations; j++) { + synchronized (lock) { + start.arriveAndAwaitAdvance(); + lock.wait(timeout); + lock.wait(timeout); + } + end.arriveAndAwaitAdvance(); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + }); + + ThreadFactory factory = ThreadLocalRandom.current().nextBoolean() + ? Thread.ofPlatform().factory() : Thread.ofVirtual().factory(); + var notifier = factory.newThread(() -> { + for (int j = 0; j < iterations; j++) { + start.arriveAndAwaitAdvance(); + synchronized (lock) { + lock.notify(); + } + end.arriveAndAwaitAdvance(); + } + }); + notifier.start(); + + vthread.join(); + notifier.join(); + } + + /** + * Barrier before synchronized block. + */ + private static void race2(int iterations) throws InterruptedException { + final int timeout = 1; + var lock = new Object(); + var start = new Phaser(2); + + var vthread = Thread.startVirtualThread(() -> { + try { + for (int i = 0; i < iterations; i++) { + start.arriveAndAwaitAdvance(); + synchronized (lock) { + lock.wait(timeout); + lock.wait(timeout); + } + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + }); + + ThreadFactory factory = ThreadLocalRandom.current().nextBoolean() + ? Thread.ofPlatform().factory() : Thread.ofVirtual().factory(); + var notifier = factory.newThread(() -> { + for (int i = 0; i < iterations; i++) { + start.arriveAndAwaitAdvance(); + synchronized (lock) { + lock.notify(); + } + } + }); + notifier.start(); + + vthread.join(); + notifier.join(); + } +} From bcffdd2edf1331476ac8b673e618f9e192f2c7e6 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Tue, 24 Mar 2026 20:32:49 +0000 Subject: [PATCH 081/168] 8362428: Update IANA Language Subtag Registry to Version 2025-08-25 Backport-of: fa6ca0bbd14436cd3778a7a3383183cd73688123 --- .../data/lsrdata/language-subtag-registry.txt | 61 +++++++++++++++++-- .../Locale/LanguageSubtagRegistryTest.java | 4 +- 2 files changed, 59 insertions(+), 6 deletions(-) diff --git a/src/java.base/share/data/lsrdata/language-subtag-registry.txt b/src/java.base/share/data/lsrdata/language-subtag-registry.txt index 64c40f28162c..82618f9b40eb 100644 --- a/src/java.base/share/data/lsrdata/language-subtag-registry.txt +++ b/src/java.base/share/data/lsrdata/language-subtag-registry.txt @@ -1,4 +1,4 @@ -File-Date: 2025-05-15 +File-Date: 2025-08-25 %% Type: language Subtag: aa @@ -3102,6 +3102,7 @@ Added: 2009-07-29 Type: language Subtag: asb Description: Assiniboine +Description: Nakoda Assiniboine Added: 2009-07-29 %% Type: language @@ -3269,6 +3270,7 @@ Added: 2009-07-29 Type: language Subtag: atj Description: Atikamekw +Description: Nehirowimowin Added: 2009-07-29 %% Type: language @@ -7981,6 +7983,7 @@ Added: 2009-07-29 Type: language Subtag: clc Description: Chilcotin +Description: Tsilhqot’in Added: 2009-07-29 %% Type: language @@ -8021,6 +8024,7 @@ Added: 2009-07-29 %% Type: language Subtag: clm +Description: Klallam Description: Clallam Added: 2009-07-29 %% @@ -13509,7 +13513,7 @@ Added: 2009-07-29 %% Type: language Subtag: haa -Description: Han +Description: Hän Added: 2009-07-29 %% Type: language @@ -19022,6 +19026,7 @@ Added: 2009-07-29 %% Type: language Subtag: kwk +Description: Kwak'wala Description: Kwakiutl Added: 2009-07-29 %% @@ -22262,7 +22267,7 @@ Added: 2009-07-29 %% Type: language Subtag: mhn -Description: Mócheno +Description: Mòcheno Added: 2009-07-29 %% Type: language @@ -31655,6 +31660,7 @@ Added: 2009-07-29 Type: language Subtag: sec Description: Sechelt +Description: She shashishalhem Added: 2009-07-29 %% Type: language @@ -32003,6 +32009,7 @@ Added: 2009-07-29 Type: language Subtag: shs Description: Shuswap +Description: Secwepemctsín Added: 2009-07-29 %% Type: language @@ -33014,6 +33021,7 @@ Added: 2009-07-29 Type: language Subtag: squ Description: Squamish +Description: Sḵwx̱wú7mesh sníchim Added: 2009-07-29 %% Type: language @@ -34664,6 +34672,8 @@ Added: 2009-07-29 Type: language Subtag: thp Description: Thompson +Description: Nłeʔkepmxcín +Description: Thompson River Salish Added: 2009-07-29 %% Type: language @@ -34684,6 +34694,7 @@ Added: 2009-07-29 Type: language Subtag: tht Description: Tahltan +Description: Tāłtān Added: 2009-07-29 %% Type: language @@ -42419,7 +42430,7 @@ Added: 2009-07-29 %% Type: language Subtag: zmp -Description: Mpuono +Description: Mbuun Added: 2009-07-29 %% Type: language @@ -47639,6 +47650,12 @@ Comments: Denotes conventions established by the Academia Brasileira de Letras in 1943 and generally used in Brazil until 2009 %% Type: variant +Subtag: akhmimic +Description: Akhmimic dialect of Coptic +Added: 2025-07-14 +Prefix: cop +%% +Type: variant Subtag: akuapem Description: Akuapem Twi Added: 2017-06-05 @@ -47814,6 +47831,12 @@ Comments: Black American Sign Language (BASL) or Black Sign Variation (BSV) is a dialect of American Sign Language (ASL) %% Type: variant +Subtag: bohairic +Description: Bohairic dialect of Coptic +Added: 2025-07-14 +Prefix: cop +%% +Type: variant Subtag: bohoric Description: Slovene in Bohorič alphabet Added: 2012-06-27 @@ -47898,6 +47921,12 @@ Comments: Represents the standard written form of Ladin in Fascia which unified the three subvarieties Cazet, Brach and Moenat %% Type: variant +Subtag: fayyumic +Description: Fayyumic dialect of Coptic +Added: 2025-07-14 +Prefix: cop +%% +Type: variant Subtag: fodom Description: Fodom standard of Ladin Added: 2024-03-04 @@ -48167,6 +48196,12 @@ Comments: Russian orthography as established by the 1917/1918 orthographic reforms %% Type: variant +Subtag: lycopol +Description: Lycopolitan alias Subakhmimic dialect of Coptic +Added: 2025-07-14 +Prefix: cop +%% +Type: variant Subtag: mdcegyp Description: Ancient Egyptian hieroglyphs encoded in Manuel de Codage Added: 2025-02-06 @@ -48180,6 +48215,12 @@ Added: 2025-02-06 Prefix: egy %% Type: variant +Subtag: mesokem +Description: Mesokemic alias Oxyrhynchite dialect of Coptic +Added: 2025-07-14 +Prefix: cop +%% +Type: variant Subtag: metelko Description: Slovene in Metelko alphabet Added: 2012-06-27 @@ -48367,6 +48408,12 @@ Prefix: rm Comments: Supraregional Romansh written standard %% Type: variant +Subtag: sahidic +Description: Sahidic dialect of Coptic +Added: 2025-07-14 +Prefix: cop +%% +Type: variant Subtag: saigon Description: The Sài Gòn variant of Vietnamese Added: 2025-03-10 @@ -48555,6 +48602,12 @@ Comments: The subtag represents the old orthography of the Latvian language used during c. 1600s–1920s. %% Type: variant +Subtag: viennese +Description: The Viennese dialect of German +Added: 2025-06-22 +Prefix: de +%% +Type: variant Subtag: vivaraup Description: Vivaro-Alpine Added: 2018-04-22 diff --git a/test/jdk/java/util/Locale/LanguageSubtagRegistryTest.java b/test/jdk/java/util/Locale/LanguageSubtagRegistryTest.java index 12f5a96d3fb5..07cc7a412b6a 100644 --- a/test/jdk/java/util/Locale/LanguageSubtagRegistryTest.java +++ b/test/jdk/java/util/Locale/LanguageSubtagRegistryTest.java @@ -25,9 +25,9 @@ * @test * @bug 8025703 8040211 8191404 8203872 8222980 8225435 8241082 8242010 8247432 * 8258795 8267038 8287180 8302512 8304761 8306031 8308021 8313702 8318322 - * 8327631 8332424 8334418 8344589 8348328 + * 8327631 8332424 8334418 8344589 8348328 8362428 * @summary Checks the IANA language subtag registry data update - * (LSR Revision: 2025-05-15) with Locale and Locale.LanguageRange + * (LSR Revision: 2025-08-25) with Locale and Locale.LanguageRange * class methods. * @run main LanguageSubtagRegistryTest */ From 3cc363ae9a53c37a813e48c85f0046923d2a77f1 Mon Sep 17 00:00:00 2001 From: Arno Zeller Date: Thu, 26 Mar 2026 08:32:01 +0000 Subject: [PATCH 082/168] 8369683: Exclude runtime/Monitor/MonitorWithDeadObjectTest.java#DumpThreadsBeforeDetach on Alpine Linux debug Backport-of: 17c13e53aff16b294c7c0286ccb6ea3054b1de91 --- .../jtreg/runtime/Monitor/MonitorWithDeadObjectTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/hotspot/jtreg/runtime/Monitor/MonitorWithDeadObjectTest.java b/test/hotspot/jtreg/runtime/Monitor/MonitorWithDeadObjectTest.java index 7f9b44a4a76b..b1e6d0aa8c7d 100644 --- a/test/hotspot/jtreg/runtime/Monitor/MonitorWithDeadObjectTest.java +++ b/test/hotspot/jtreg/runtime/Monitor/MonitorWithDeadObjectTest.java @@ -39,7 +39,8 @@ /* * @test id=DumpThreadsBeforeDetach - * @requires os.family != "windows" & os.family != "aix" + * @comment Temporarily exclude on Musl-C debug until JDK-8366133 is fixed. + * @requires os.family != "windows" & os.family != "aix" & (!vm.musl | !vm.debug) * @run main/othervm/native MonitorWithDeadObjectTest 1 */ From d09e1ccff31ce548f4920e13e6f55073ec4e5ceb Mon Sep 17 00:00:00 2001 From: Thomas Devoogdt Date: Thu, 26 Mar 2026 15:20:44 +0000 Subject: [PATCH 083/168] 8376684: Compile OpenJDK in headless mode without required X11 libraries Reviewed-by: andrew Backport-of: 1069ccebcc32e02055985e2babfa2986a2e295ca --- doc/building.html | 7 +++---- doc/building.md | 6 ++---- make/autoconf/libraries.m4 | 8 ++++---- make/modules/java.desktop/lib/AwtLibraries.gmk | 16 ++++++++++++++-- .../unix/native/common/awt/utility/rect.h | 4 ++-- 5 files changed, 25 insertions(+), 16 deletions(-) diff --git a/doc/building.html b/doc/building.html index 51ced17fe99d..1fbac93a075b 100644 --- a/doc/building.html +++ b/doc/building.html @@ -1380,10 +1380,9 @@

ALSA

can specify it by --with-alsa.

X11

-

You will need X11 libraries suitable for your target system. -In most cases, using Debian's pre-built libraries work fine.

-

Note that X11 is needed even if you only want to build a headless -JDK.

+

When not building a headless JDK, you will need X11 libraries +suitable for your target system. In most cases, using Debian's +pre-built libraries work fine.

  • Go to Debian Package Search, search for the following packages for your diff --git a/doc/building.md b/doc/building.md index 11af23d94477..986643094ec2 100644 --- a/doc/building.md +++ b/doc/building.md @@ -1173,10 +1173,8 @@ Note that alsa is needed even if you only want to build a headless JDK. #### X11 -You will need X11 libraries suitable for your *target* system. In most cases, -using Debian's pre-built libraries work fine. - -Note that X11 is needed even if you only want to build a headless JDK. +When not building a headless JDK, you will need X11 libraries suitable for your +*target* system. In most cases, using Debian's pre-built libraries work fine. * Go to [Debian Package Search](https://www.debian.org/distrib/packages), search for the following packages for your *target* system, and download them diff --git a/make/autoconf/libraries.m4 b/make/autoconf/libraries.m4 index 8dc3d55ed0c8..5daacdc1ced5 100644 --- a/make/autoconf/libraries.m4 +++ b/make/autoconf/libraries.m4 @@ -42,12 +42,12 @@ m4_include([lib-tests.m4]) AC_DEFUN_ONCE([LIB_DETERMINE_DEPENDENCIES], [ # Check if X11 is needed - if test "x$OPENJDK_TARGET_OS" = xwindows || test "x$OPENJDK_TARGET_OS" = xmacosx; then - # No X11 support on windows or macosx + if test "x$OPENJDK_TARGET_OS" = xwindows || + test "x$OPENJDK_TARGET_OS" = xmacosx || + test "x$ENABLE_HEADLESS_ONLY" = xtrue; then NEEDS_LIB_X11=false else - # All other instances need X11, even if building headless only, libawt still - # needs X11 headers. + # All other instances need X11 for libawt. NEEDS_LIB_X11=true fi diff --git a/make/modules/java.desktop/lib/AwtLibraries.gmk b/make/modules/java.desktop/lib/AwtLibraries.gmk index 463e09e12dce..8b6b50b9e623 100644 --- a/make/modules/java.desktop/lib/AwtLibraries.gmk +++ b/make/modules/java.desktop/lib/AwtLibraries.gmk @@ -88,6 +88,10 @@ LIBAWT_EXTRA_HEADER_DIRS := \ LIBAWT_CFLAGS := -D__MEDIALIB_OLD_NAMES -D__USE_J2D_NAMES -DMLIB_NO_LIBSUNMATH +ifeq ($(ENABLE_HEADLESS_ONLY), true) + LIBAWT_CFLAGS += -DHEADLESS +endif + ifeq ($(call isTargetOs, windows), true) LIBAWT_CFLAGS += -EHsc -DUNICODE -D_UNICODE -DMLIB_OS64BIT LIBAWT_RCFLAGS ?= -I$(TOPDIR)/src/java.base/windows/native/launcher/icons @@ -167,11 +171,18 @@ ifeq ($(call isTargetOs, windows macosx), false) $(TOPDIR)/src/$(MODULE)/$(OPENJDK_TARGET_OS_TYPE)/native/common/awt \ # + LIBAWT_HEADLESS_EXCLUDE_FILES := \ + GLXGraphicsConfig.c \ + GLXSurfaceData.c \ + X11PMBlitLoops.c \ + X11Renderer.c \ + X11SurfaceData.c \ + # + LIBAWT_HEADLESS_EXTRA_HEADER_DIRS := \ $(LIBAWT_DEFAULT_HEADER_DIRS) \ common/awt/debug \ common/font \ - common/java2d/opengl \ java.base:libjvm \ # @@ -191,7 +202,8 @@ ifeq ($(call isTargetOs, windows macosx), false) $(eval $(call SetupJdkLibrary, BUILD_LIBAWT_HEADLESS, \ NAME := awt_headless, \ EXTRA_SRC := $(LIBAWT_HEADLESS_EXTRA_SRC), \ - EXCLUDES := medialib, \ + EXCLUDES := medialib opengl, \ + EXCLUDE_FILES := $(LIBAWT_HEADLESS_EXCLUDE_FILES), \ ONLY_EXPORTED := $(LIBAWT_HEADLESS_ONLY_EXPORTED), \ OPTIMIZATION := LOW, \ CFLAGS := -DHEADLESS=true $(CUPS_CFLAGS) $(FONTCONFIG_CFLAGS) \ diff --git a/src/java.desktop/unix/native/common/awt/utility/rect.h b/src/java.desktop/unix/native/common/awt/utility/rect.h index ceea38f4349a..91b5a17ec58c 100644 --- a/src/java.desktop/unix/native/common/awt/utility/rect.h +++ b/src/java.desktop/unix/native/common/awt/utility/rect.h @@ -28,7 +28,7 @@ #ifndef _AWT_RECT_H #define _AWT_RECT_H -#ifndef MACOSX +#if !defined(HEADLESS) && !defined(MACOSX) #include typedef XRectangle RECT_T; #else @@ -39,7 +39,7 @@ typedef struct { int width; int height; } RECT_T; -#endif /* !MACOSX */ +#endif /* !HEADLESS && !MACOSX */ #define RECT_EQ_X(r1,r2) ((r1).x==(r2).x && (r1).width==(r2).width) From b51af16fc064794d29a1ffafad38eb2eaa23cd80 Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Fri, 27 Mar 2026 07:32:46 +0000 Subject: [PATCH 084/168] 8379202: Support linktime-gc on Linux with clang Backport-of: dc6f7014aa3791a5a6fd2b89ac19d88382c16da2 --- make/autoconf/flags-cflags.m4 | 5 +++++ make/autoconf/flags-ldflags.m4 | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/make/autoconf/flags-cflags.m4 b/make/autoconf/flags-cflags.m4 index 3a61840e8c97..9bea6b5062e8 100644 --- a/make/autoconf/flags-cflags.m4 +++ b/make/autoconf/flags-cflags.m4 @@ -565,6 +565,11 @@ AC_DEFUN([FLAGS_SETUP_CFLAGS_HELPER], TOOLCHAIN_CFLAGS_JDK_CONLY="-fno-strict-aliasing" # technically NOT for CXX fi + if test "x$ENABLE_LINKTIME_GC" = xtrue; then + TOOLCHAIN_CFLAGS_JDK="$TOOLCHAIN_CFLAGS_JDK -ffunction-sections -fdata-sections" + TOOLCHAIN_CFLAGS_JVM="$TOOLCHAIN_CFLAGS_JVM -ffunction-sections -fdata-sections" + fi + if test "x$OPENJDK_TARGET_OS" = xaix; then TOOLCHAIN_CFLAGS_JVM="$TOOLCHAIN_CFLAGS_JVM -ffunction-sections -ftls-model -fno-math-errno" TOOLCHAIN_CFLAGS_JDK="-ffunction-sections -fsigned-char" diff --git a/make/autoconf/flags-ldflags.m4 b/make/autoconf/flags-ldflags.m4 index 509e0dd825f0..84ba24bb440f 100644 --- a/make/autoconf/flags-ldflags.m4 +++ b/make/autoconf/flags-ldflags.m4 @@ -76,6 +76,10 @@ AC_DEFUN([FLAGS_SETUP_LDFLAGS_HELPER], if test "x$CXX_IS_USER_SUPPLIED" = xfalse && test "x$CC_IS_USER_SUPPLIED" = xfalse; then UTIL_REQUIRE_PROGS(LLD, lld, $TOOLCHAIN_PATH:$PATH) fi + + if test "x$ENABLE_LINKTIME_GC" = xtrue; then + BASIC_LDFLAGS_JDK_ONLY="$BASIC_LDFLAGS_JDK_ONLY -Wl,--gc-sections" + fi fi if test "x$OPENJDK_TARGET_OS" = xaix; then BASIC_LDFLAGS="-Wl,-b64 -Wl,-brtl -Wl,-bnorwexec -Wl,-blibpath:/usr/lib:lib -Wl,-bnoexpall \ From 14d3f56f99c9b6d630b0f6ca0e858b43df323cd0 Mon Sep 17 00:00:00 2001 From: SendaoYan Date: Fri, 27 Mar 2026 09:49:49 +0000 Subject: [PATCH 085/168] 8377944: LowMemoryTest2.java#id1 intermittent fails OOME: Metaspace Backport-of: 0ed34913bac44f3f0895cd9ab15d4e7ff2d5f5c2 --- .../MemoryMXBean/LowMemoryTest2.java | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/test/jdk/java/lang/management/MemoryMXBean/LowMemoryTest2.java b/test/jdk/java/lang/management/MemoryMXBean/LowMemoryTest2.java index de1b22c4075a..b093f086ba44 100644 --- a/test/jdk/java/lang/management/MemoryMXBean/LowMemoryTest2.java +++ b/test/jdk/java/lang/management/MemoryMXBean/LowMemoryTest2.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -35,18 +35,14 @@ * @test * @bug 4982128 * @summary Test low memory detection of non-heap memory pool - * - * @run main/othervm/timeout=600 -Xnoclassgc -XX:MaxMetaspaceSize=32m - * LowMemoryTest2 - */ - -/* - * @test - * @bug 4982128 - * @summary Test low memory detection of non-heap memory pool - * - * @run main/othervm/timeout=600 -Xnoclassgc -XX:MaxMetaspaceSize=16m - * -XX:CompressedClassSpaceSize=4m LowMemoryTest2 + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=600 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. + * -Xnoclassgc -XX:MaxMetaspaceSize=32m LowMemoryTest2 + * @run main/othervm/timeout=600 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. + * -Xnoclassgc -XX:MaxMetaspaceSize=16m + * -XX:CompressedClassSpaceSize=4m LowMemoryTest2 */ import java.lang.management.*; @@ -54,6 +50,8 @@ import javax.management.openmbean.CompositeData; import java.util.*; +import jdk.test.whitebox.WhiteBox; + public class LowMemoryTest2 { private static volatile boolean listenerInvoked = false; @@ -177,7 +175,7 @@ public void run() { // If we don't force a GC we may get an // OutOfMemoryException before the counters are updated. System.out.println("Force GC"); - System.gc(); + WhiteBox.getWhiteBox().fullGC(); } isThresholdCountSet = isAnyThresholdCountSet(pools); } From b164bb02b74541a43e9b871c97679b1846c265b4 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Fri, 27 Mar 2026 15:00:14 +0000 Subject: [PATCH 086/168] 8373913: Refactor serialization tests to use JUnit Reviewed-by: phh Backport-of: 3007365b73d400ee6a5ea9a9041899bb81cf357a --- .../Serializable/GetField/ReadFieldsCNF.java | 34 ++-- .../class/NonSerializableTest.java | 76 +++++---- .../Serializable/records/RecordClassTest.java | 10 +- .../records/SerialVersionUIDTest.java | 12 +- .../serialFilter/CheckArrayTest.java | 28 ++-- .../serialFilter/CheckInputOrderTest.java | 23 +-- .../serialFilter/GlobalFilterTest.java | 71 ++++---- .../serialFilter/InvalidGlobalFilterTest.java | 40 ++--- .../serialFilter/MixedFiltersTest.java | 52 +++--- .../serialFilter/SerialFactoryExample.java | 33 ++-- .../serialFilter/SerialFactoryFaults.java | 52 +++--- .../serialFilter/SerialFilterFactoryTest.java | 76 +++++---- .../SerialFilterFunctionTest.java | 51 +++--- .../serialFilter/SerialFilterTest.java | 151 ++++++++---------- 14 files changed, 357 insertions(+), 352 deletions(-) diff --git a/test/jdk/java/io/Serializable/GetField/ReadFieldsCNF.java b/test/jdk/java/io/Serializable/GetField/ReadFieldsCNF.java index 7b91222d737e..2b3f1c6aa45b 100644 --- a/test/jdk/java/io/Serializable/GetField/ReadFieldsCNF.java +++ b/test/jdk/java/io/Serializable/GetField/ReadFieldsCNF.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,8 +27,8 @@ * @summary Verify that ObjectInputStream ReadFields correctly reports ClassNotFoundException * while getting the field value. The test uses Vector that calls ReadFields from its readObject. * @library /test/lib - * @run testng ReadFieldsCNF - * @run testng/othervm -Djdk.serialGetFieldCnfeReturnsNull=true ReadFieldsCNF + * @run junit ReadFieldsCNF + * @run junit/othervm -Djdk.serialGetFieldCnfeReturnsNull=true ReadFieldsCNF */ import java.io.ByteArrayInputStream; @@ -41,12 +41,12 @@ import java.nio.charset.StandardCharsets; import java.util.Vector; -import org.testng.annotations.Test; -import org.testng.Assert; - import jdk.test.lib.hexdump.HexPrinter; import jdk.test.lib.hexdump.ObjectStreamPrinter; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + public class ReadFieldsCNF { private static final boolean GETFIELD_CNFE_RETURNS_NULL = @@ -58,7 +58,7 @@ public class ReadFieldsCNF { * @throws IOException If any other exception occurs */ @Test - private static void testVectorWithRole() throws IOException { + void testVectorWithRole() throws IOException { System.out.println("Property GETFIELD_CNFE_RETURNS_NULL: " + GETFIELD_CNFE_RETURNS_NULL); Role role = new Role(); @@ -75,7 +75,7 @@ private static void testVectorWithRole() throws IOException { System.out.printf("Role offset: %d (0x%x) : %s%n", off, off, Role.class.getName()); if (off < 0) { HexPrinter.simple().formatter(ObjectStreamPrinter.formatter()).format(bytes); - Assert.fail("classname not found"); + Assertions.fail("classname not found"); } bytes[off] = (byte) 'X'; // replace R with X -> Class not found @@ -85,18 +85,18 @@ private static void testVectorWithRole() throws IOException { try { Object obj = in.readObject(); System.out.println("Read: " + obj); - Assert.fail("Should not reach here, an exception should always occur"); + Assertions.fail("Should not reach here, an exception should always occur"); } catch (ClassNotFoundException cnfe) { // Expected ClassNotFoundException String expected = "XeadFieldsCNF$Role"; - Assert.assertEquals(expected, cnfe.getMessage(), "Wrong classname"); + Assertions.assertEquals(expected, cnfe.getMessage(), "Wrong classname"); if (GETFIELD_CNFE_RETURNS_NULL) { - Assert.fail("Expected IOException got ClassNotFoundException", cnfe); + Assertions.fail("Expected IOException got ClassNotFoundException", cnfe); } System.out.println("Normal: OIS.readObject: " + cnfe); } catch (StreamCorruptedException ioe) { if (!GETFIELD_CNFE_RETURNS_NULL) { - Assert.fail("Expected ClassNotFoundException got StreamCorruptedException ", ioe); + Assertions.fail("Expected ClassNotFoundException got StreamCorruptedException ", ioe); } System.out.println("Normal: " + ioe); } @@ -108,7 +108,7 @@ private static void testVectorWithRole() throws IOException { * @throws IOException If any other exception occurs */ @Test - private static void testHolderWithRole() throws IOException { + void testHolderWithRole() throws IOException { System.out.println("Property GETFIELD_CNFE_RETURNS_NULL: " + GETFIELD_CNFE_RETURNS_NULL); Role role = new Role(); Holder holder = new Holder(role); @@ -123,7 +123,7 @@ private static void testHolderWithRole() throws IOException { System.out.printf("Role offset: %d (0x%x)%n", off, off); if (off < 0) { HexPrinter.simple().formatter(ObjectStreamPrinter.formatter()).format(bytes); - Assert.fail("classname found at index: " + off + " (0x" + Integer.toHexString(off) + ")"); + Assertions.fail("classname found at index: " + off + " (0x" + Integer.toHexString(off) + ")"); } bytes[off] = (byte) 'X'; // replace R with X -> Class not found @@ -133,15 +133,15 @@ private static void testHolderWithRole() throws IOException { try { Holder obj = (Holder)in.readObject(); System.out.println("Read: " + obj); - Assert.fail("Should not reach here, an exception should always occur"); + Assertions.fail("Should not reach here, an exception should always occur"); } catch (ClassNotFoundException cnfe) { // Expected ClassNotFoundException String expected = "XeadFieldsCNF$Role"; - Assert.assertEquals(expected, cnfe.getMessage(), "Wrong classname"); + Assertions.assertEquals(expected, cnfe.getMessage(), "Wrong classname"); System.out.println("Normal: OIS.readObject: " + cnfe); } catch (StreamCorruptedException ioe) { if (!GETFIELD_CNFE_RETURNS_NULL) { - Assert.fail("Expected ClassNotFoundException got StreamCorruptedException ", ioe); + Assertions.fail("Expected ClassNotFoundException got StreamCorruptedException ", ioe); } System.out.println("Normal: " + ioe); } diff --git a/test/jdk/java/io/Serializable/class/NonSerializableTest.java b/test/jdk/java/io/Serializable/class/NonSerializableTest.java index fa81b3e3ce73..e17cd7ca4d13 100644 --- a/test/jdk/java/io/Serializable/class/NonSerializableTest.java +++ b/test/jdk/java/io/Serializable/class/NonSerializableTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -32,79 +32,87 @@ * jdk.test.lib.JDKToolLauncher * jdk.test.lib.Platform * jdk.test.lib.process.* - * @run testng/timeout=300 NonSerializableTest + * @run junit/timeout=300 NonSerializableTest * @summary Enable serialize of nonSerializable Class descriptor. */ import java.nio.file.Paths; import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; -import org.testng.annotations.DataProvider; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; import jdk.test.lib.compiler.CompilerUtils; import jdk.test.lib.process.ProcessTools; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + public class NonSerializableTest { - @BeforeClass - public void setup() throws Exception { + @BeforeAll + public static void setup() throws Exception { boolean b = CompilerUtils.compile( Paths.get(System.getProperty("test.src"), "TestEntry.java"), Paths.get(System.getProperty("user.dir"))); assertTrue(b, "Compilation failed"); } - @DataProvider - public Object[][] provider() { - return new String[][][] { + // Test cases to compile and run + public static Stream> provider() { + return Stream.of( // Write NonSerial1, Read NonSerial1 - {{"NonSerialA_1", "-cp", ".", "TestEntry", "-s", "A"}}, - {{"NonSerialA_1", "-cp", ".", "TestEntry", "-d"}}, + List.of("NonSerialA_1", "-cp", ".", "TestEntry", "-s", "A"), + List.of("NonSerialA_1", "-cp", ".", "TestEntry", "-d"), // Write NonSerial1, Read NonSerial2 - {{"NonSerialA_1", "-cp", ".", "TestEntry", "-s", "A"}}, - {{"NonSerialA_2", "-cp", ".", "TestEntry", "-d"}}, + List.of("NonSerialA_1", "-cp", ".", "TestEntry", "-s", "A"), + List.of("NonSerialA_2", "-cp", ".", "TestEntry", "-d"), // Write NonSerial1, Read Serial1 - {{"NonSerialA_1", "-cp", ".", "TestEntry", "-s", "A"}}, - {{"SerialA_1", "-cp", ".", "TestEntry", "-d"}}, + List.of("NonSerialA_1", "-cp", ".", "TestEntry", "-s", "A"), + List.of("SerialA_1", "-cp", ".", "TestEntry", "-d"), // Write Serial1, Read NonSerial1 - {{"SerialA_1", "-cp", ".", "TestEntry", "-s", "A"}}, - {{"NonSerialA_1", "-cp", ".", "TestEntry", "-doe"}}, + List.of("SerialA_1", "-cp", ".", "TestEntry", "-s", "A"), + List.of("NonSerialA_1", "-cp", ".", "TestEntry", "-doe"), // Write Serial1, Read Serial2 - {{"SerialA_1", "-cp", ".", "TestEntry", "-s", "A"}}, - {{"SerialA_2", "-cp", ".", "TestEntry", "-d"}}, + List.of("SerialA_1", "-cp", ".", "TestEntry", "-s", "A"), + List.of("SerialA_2", "-cp", ".", "TestEntry", "-d"), // Write Serial2, Read Serial1 - {{"SerialA_2", "-cp", ".", "TestEntry", "-s", "A"}}, - {{"SerialA_1", "-cp", ".", "TestEntry", "-d"}}, + List.of("SerialA_2", "-cp", ".", "TestEntry", "-s", "A"), + List.of("SerialA_1", "-cp", ".", "TestEntry", "-d"), // Write Serial1, Read Serial3 - {{"SerialA_1", "-cp", ".", "TestEntry", "-s", "A"}}, - {{"SerialA_3", "-cp", ".", "TestEntry", "-de"}}, + List.of("SerialA_1", "-cp", ".", "TestEntry", "-s", "A"), + List.of("SerialA_3", "-cp", ".", "TestEntry", "-de"), // Write Serial3, Read Serial1 - {{"SerialA_3", "-cp", ".", "TestEntry", "-s", "A"}}, - {{"SerialA_1", "-cp", ".", "TestEntry", "-de"}}, - }; + List.of("SerialA_3", "-cp", ".", "TestEntry", "-s", "A"), + List.of("SerialA_1", "-cp", ".", "TestEntry", "-de")); } - @Test(dataProvider="provider") - public void test(String[] args) throws Exception { + @ParameterizedTest + @MethodSource("provider") + public void test(List argList) throws Exception { + String[] args = argList.toArray(new String[0]); boolean b = CompilerUtils.compile(Paths.get(System.getProperty("test.src"), args[0]), Paths.get(System.getProperty("user.dir"))); assertTrue(b, "Compilation failed"); - String params[] = Arrays.copyOfRange(args, 1, args.length); + String[] params = Arrays.copyOfRange(args, 1, args.length); ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(params); Process p = ProcessTools.startProcess("Serializable Test", pb); - int exitValue = p.waitFor(); - assertEquals(exitValue, 0, "Test failed"); + try { + int exitValue = p.waitFor(); + assertEquals(0, exitValue, "Test failed"); + } finally { + p.destroy(); + } } } diff --git a/test/jdk/java/io/Serializable/records/RecordClassTest.java b/test/jdk/java/io/Serializable/records/RecordClassTest.java index ba920ce92e59..951dd2f44dcd 100644 --- a/test/jdk/java/io/Serializable/records/RecordClassTest.java +++ b/test/jdk/java/io/Serializable/records/RecordClassTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -37,6 +37,7 @@ import java.io.ObjectOutput; import java.io.ObjectOutputStream; import java.io.ObjectStreamClass; +import java.io.Serial; import java.io.Serializable; import static java.lang.System.out; @@ -49,12 +50,12 @@ /** * Serializes and deserializes record classes. Ensures that the SUID is 0. */ -@TestInstance(TestInstance.Lifecycle.PER_CLASS) public class RecordClassTest { record Foo () implements Serializable { } record Bar (int x) implements Serializable { + @Serial private static final long serialVersionUID = 987654321L; } @@ -70,6 +71,7 @@ default void readExternal(ObjectInput in) { } record Wibble () implements ThrowingExternalizable { + @Serial private static final long serialVersionUID = 12345678L; } @@ -77,7 +79,7 @@ record Wobble (long l) implements ThrowingExternalizable { } record Wubble (Wobble wobble, Wibble wibble, String s) implements ThrowingExternalizable { } - public Object[][] recordClasses() { + public static Object[][] recordClasses() { return new Object[][] { new Object[] { Foo.class , 0L }, new Object[] { Bar.class , 987654321L }, @@ -124,7 +126,7 @@ record NotSerializable2(int x) { } record NotSerializable3(T t) { } - public Object[][] notSerRecordClasses() { + public static Object[][] notSerRecordClasses() { return new Object[][] { new Object[] { NotSerializable1.class }, new Object[] { NotSerializable2.class }, diff --git a/test/jdk/java/io/Serializable/records/SerialVersionUIDTest.java b/test/jdk/java/io/Serializable/records/SerialVersionUIDTest.java index 2c133392dcb5..f313e8a44a31 100644 --- a/test/jdk/java/io/Serializable/records/SerialVersionUIDTest.java +++ b/test/jdk/java/io/Serializable/records/SerialVersionUIDTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -35,6 +35,7 @@ import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; +import java.io.Serial; import java.io.Serializable; import java.util.ArrayList; import java.util.List; @@ -44,18 +45,18 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -@TestInstance(TestInstance.Lifecycle.PER_CLASS) public class SerialVersionUIDTest { record R1 () implements Serializable { + @Serial private static final long serialVersionUID = 1L; } record R2 (int x, int y) implements Serializable { + @Serial private static final long serialVersionUID = 0L; } @@ -64,10 +65,11 @@ record R3 () implements Serializable { } record R4 (String s) implements Serializable { } record R5 (long l) implements Serializable { + @Serial private static final long serialVersionUID = 5678L; } - public Object[][] recordObjects() { + public static Object[][] recordObjects() { return new Object[][] { new Object[] { new R1(), 1L }, new Object[] { new R2(1, 2), 0L }, @@ -103,7 +105,7 @@ public void testSerialize(Object objectToSerialize, long expectedUID) assertEquals(expectedUID, dis.readLong()); } - public Object[][] recordClasses() { + public static Object[][] recordClasses() { List list = new ArrayList<>(); List> recordClasses = List.of(R1.class, R2.class, R3.class, R4.class, R5.class); LongStream.of(0L, 1L, 100L, 10_000L, 1_000_000L).forEach(suid -> diff --git a/test/jdk/java/io/Serializable/serialFilter/CheckArrayTest.java b/test/jdk/java/io/Serializable/serialFilter/CheckArrayTest.java index d00c670bd15d..2081375ca599 100644 --- a/test/jdk/java/io/Serializable/serialFilter/CheckArrayTest.java +++ b/test/jdk/java/io/Serializable/serialFilter/CheckArrayTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -30,15 +30,15 @@ import jdk.internal.access.SharedSecrets; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; -import org.testng.Assert; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; /* @test * @build CheckArrayTest SerialFilterTest * @bug 8203368 * @modules java.base/jdk.internal.access - * @run testng CheckArrayTest + * @run junit CheckArrayTest * * @summary Test the SharedSecret access to ObjectInputStream.checkArray works * with overridden subclasses. @@ -53,8 +53,8 @@ */ public class CheckArrayTest { - @DataProvider(name = "Patterns") - Object[][] patterns() { + // Test patterns for arrays + private static Object[][] patterns() { return new Object[][]{ new Object[]{"maxarray=10", 10, new String[10]}, // successful new Object[]{"maxarray=10", 11, new String[11]}, // exception expected @@ -64,7 +64,8 @@ Object[][] patterns() { /** * Test SharedSecrets checkArray with unmodified ObjectInputStream. */ - @Test(dataProvider = "Patterns") + @ParameterizedTest + @MethodSource("patterns") public void normalOIS(String pattern, int arraySize, Object[] array) throws IOException { ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(pattern); byte[] bytes = SerialFilterTest.writeObjects(array); @@ -75,10 +76,10 @@ public void normalOIS(String pattern, int arraySize, Object[] array) throws IOEx ois.setObjectInputFilter(filter); SharedSecrets.getJavaObjectInputStreamAccess() .checkArray(ois, array.getClass(), arraySize); - Assert.assertTrue(array.length >= arraySize, + Assertions.assertTrue(array.length >= arraySize, "Should have thrown InvalidClassException due to array size"); } catch (InvalidClassException ice) { - Assert.assertFalse(array.length > arraySize, + Assertions.assertFalse(array.length > arraySize, "Should NOT have thrown InvalidClassException due to array size"); } } @@ -88,7 +89,8 @@ public void normalOIS(String pattern, int arraySize, Object[] array) throws IOEx * Test SharedSecrets checkArray with an ObjectInputStream subclassed to * handle all input stream functions. */ - @Test(dataProvider = "Patterns") + @ParameterizedTest + @MethodSource("patterns") public void subclassedOIS(String pattern, int arraySize, Object[] array) throws IOException { byte[] bytes = SerialFilterTest.writeObjects(array); try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes); @@ -98,10 +100,10 @@ public void subclassedOIS(String pattern, int arraySize, Object[] array) throws ois.setObjectInputFilter(filter); SharedSecrets.getJavaObjectInputStreamAccess() .checkArray(ois, array.getClass(), arraySize); - Assert.assertTrue(array.length >= arraySize, + Assertions.assertTrue(array.length >= arraySize, "Should have thrown InvalidClassException due to array size"); } catch (InvalidClassException ice) { - Assert.assertFalse(array.length > arraySize, + Assertions.assertFalse(array.length > arraySize, "Should NOT have thrown InvalidClassException due to array size"); } } diff --git a/test/jdk/java/io/Serializable/serialFilter/CheckInputOrderTest.java b/test/jdk/java/io/Serializable/serialFilter/CheckInputOrderTest.java index 0230eb653ceb..d824c947ecee 100644 --- a/test/jdk/java/io/Serializable/serialFilter/CheckInputOrderTest.java +++ b/test/jdk/java/io/Serializable/serialFilter/CheckInputOrderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,28 +25,28 @@ import java.io.InvalidClassException; import java.io.ObjectInputFilter; import java.io.ObjectInputStream; +import java.io.Serial; import java.io.Serializable; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; - -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertFalse; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; /* @test * @build CheckInputOrderTest SerialFilterTest - * @run testng/othervm CheckInputOrderTest + * @run junit/othervm CheckInputOrderTest * * @summary Test that when both global filter and specific filter are set, * global filter will not affect specific filter. */ public class CheckInputOrderTest implements Serializable { + @Serial private static final long serialVersionUID = 12345678901L; - @DataProvider(name="Patterns") - Object[][] patterns() { + // Test cases for serial filter strings + static Object[][] patterns() { return new Object[][] { new Object[] { SerialFilterTest.genTestObject("maxarray=1", true), "java.**;java.lang.*;java.lang.Long;maxarray=0", false }, new Object[] { SerialFilterTest.genTestObject("maxarray=1", true), "java.**;java.lang.*;java.lang.Long", true }, @@ -75,7 +75,8 @@ Object[][] patterns() { * "global filter reject" + "specific ObjectInputStream filter is empty" => should reject * "global filter reject" + "specific ObjectInputStream filter allow" => should allow */ - @Test(dataProvider="Patterns") + @ParameterizedTest + @MethodSource("patterns") public void testRejectedInGlobal(Object toDeserialized, String pattern, boolean allowed) throws Exception { byte[] bytes = SerialFilterTest.writeObjects(toDeserialized); ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(pattern); diff --git a/test/jdk/java/io/Serializable/serialFilter/GlobalFilterTest.java b/test/jdk/java/io/Serializable/serialFilter/GlobalFilterTest.java index 9e38d6c499da..18bbf265e434 100644 --- a/test/jdk/java/io/Serializable/serialFilter/GlobalFilterTest.java +++ b/test/jdk/java/io/Serializable/serialFilter/GlobalFilterTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -21,9 +21,8 @@ * questions. */ -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertSame; -import static org.testng.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.ByteArrayInputStream; import java.io.EOFException; @@ -35,39 +34,40 @@ import java.security.Security; import java.util.Objects; -import org.testng.Assert; -import org.testng.annotations.Test; -import org.testng.annotations.DataProvider; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIf; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; /* @test * @bug 8231422 * @build GlobalFilterTest SerialFilterTest - * @run testng/othervm GlobalFilterTest - * @run testng/othervm -Djdk.serialFilter=java.** + * @run junit/othervm GlobalFilterTest + * @run junit/othervm -Djdk.serialFilter=java.** * -Dexpected-jdk.serialFilter=java.** GlobalFilterTest * @summary Test Global Filters */ -@Test public class GlobalFilterTest { private static final String serialPropName = "jdk.serialFilter"; private static final String badSerialFilter = "java.lang.StringBuffer;!*"; private static final String origSerialFilterProperty = System.setProperty(serialPropName, badSerialFilter); + private static final String EXPECTED_GLOBAL_FILTER = System.getProperty("expected-" + serialPropName, + Security.getProperty(serialPropName)); + + static boolean hasGlobalFilter() { + return EXPECTED_GLOBAL_FILTER != null && !EXPECTED_GLOBAL_FILTER.isEmpty(); + } + /** * DataProvider of patterns and objects derived from the configured process-wide filter. * @return Array of arrays of pattern, object, allowed boolean, and API factory */ - @DataProvider(name="globalPatternElements") - Object[][] globalPatternElements() { - String globalFilter = - System.getProperty("expected-" + serialPropName, - Security.getProperty(serialPropName)); - if (globalFilter == null) { - return new Object[0][]; - } + static Object[][] globalPatternElements() { - String[] patterns = globalFilter.split(";"); + String[] patterns = EXPECTED_GLOBAL_FILTER.split(";"); Object[][] objects = new Object[patterns.length][]; for (int i = 0; i < patterns.length; i++) { @@ -83,7 +83,7 @@ Object[][] globalPatternElements() { ? SerialFilterTest.genTestObject(pattern, true) : SerialFilterTest.genTestObject(pattern.substring(1), false); - Assert.assertNotNull(o, "fail generation failed"); + Assertions.assertNotNull(o, "fail generation failed"); } objects[i] = new Object[3]; objects[i][0] = pattern; @@ -98,13 +98,13 @@ Object[][] globalPatternElements() { * and has the toString matching the configured pattern. */ @Test() - static void globalFilter() { + void globalFilter() { ObjectInputFilter filter = ObjectInputFilter.Config.getSerialFilter(); // Check that the System.setProperty(jdk.serialFilter) DOES NOT affect the filter. String asSetSystemProp = System.getProperty(serialPropName, Security.getProperty(serialPropName)); - Assert.assertNotEquals(Objects.toString(filter, null), asSetSystemProp, + Assertions.assertNotEquals(asSetSystemProp, Objects.toString(filter, null), "System.setProperty(\"jdk.serialfilter\", ...) should not change filter: " + asSetSystemProp); @@ -112,7 +112,7 @@ static void globalFilter() { System.getProperty("expected-" + serialPropName, Security.getProperty(serialPropName)); System.out.printf("global pattern: %s, filter: %s%n", pattern, filter); - Assert.assertEquals(Objects.toString(filter, null), pattern, + assertEquals(pattern, Objects.toString(filter, null), "process-wide filter pattern does not match"); } @@ -120,16 +120,15 @@ static void globalFilter() { * If the Global filter is already set, it should always refuse to be * set again. */ - @Test() - @SuppressWarnings("removal") - static void setGlobalFilter() { + @Test + void setGlobalFilter() { ObjectInputFilter filter = new SerialFilterTest.Validator(); ObjectInputFilter global = ObjectInputFilter.Config.getSerialFilter(); if (global != null) { // once set, can never be re-set try { ObjectInputFilter.Config.setSerialFilter(filter); - Assert.fail("set only once process-wide filter"); + Assertions.fail("set only once process-wide filter"); } catch (IllegalStateException ise) { // Normal, once set can never be re-set } @@ -141,7 +140,7 @@ static void setGlobalFilter() { try { // Try to set it again, expecting it to throw ObjectInputFilter.Config.setSerialFilter(filter); - Assert.fail("set only once process-wide filter"); + Assertions.fail("set only once process-wide filter"); } catch (IllegalStateException ise) { // Normal case } @@ -154,8 +153,10 @@ static void setGlobalFilter() { * * @param pattern a pattern extracted from the configured global pattern */ - @Test(dataProvider = "globalPatternElements") - static void globalFilterElements(String pattern, boolean allowed,Object obj) { + @ParameterizedTest + @EnabledIf("hasGlobalFilter") + @MethodSource("globalPatternElements") + void globalFilterElements(String pattern, boolean allowed,Object obj) { testGlobalPattern(pattern, obj, allowed); } @@ -177,15 +178,15 @@ static void testGlobalPattern(String pattern, Object object, boolean allowed) { } catch (EOFException eof) { // normal completion } catch (ClassNotFoundException cnf) { - Assert.fail("Deserializing", cnf); + Assertions.fail("Deserializing", cnf); } - Assert.assertTrue(allowed, "filter should have thrown an exception"); + assertTrue(allowed, "filter should have thrown an exception"); } catch (IllegalArgumentException iae) { - Assert.fail("bad format pattern", iae); + Assertions.fail("bad format pattern", iae); } catch (InvalidClassException ice) { - Assert.assertFalse(allowed, "filter should not have thrown an exception: " + ice); + Assertions.assertFalse(allowed, "filter should not have thrown an exception: " + ice); } catch (IOException ioe) { - Assert.fail("Unexpected IOException", ioe); + Assertions.fail("Unexpected IOException", ioe); } } } diff --git a/test/jdk/java/io/Serializable/serialFilter/InvalidGlobalFilterTest.java b/test/jdk/java/io/Serializable/serialFilter/InvalidGlobalFilterTest.java index a017354b103d..b924a96c86ca 100644 --- a/test/jdk/java/io/Serializable/serialFilter/InvalidGlobalFilterTest.java +++ b/test/jdk/java/io/Serializable/serialFilter/InvalidGlobalFilterTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -21,9 +21,6 @@ * questions. */ -import org.testng.Assert; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -31,18 +28,22 @@ import java.io.ObjectInputStream; import java.util.Map; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.function.Executable; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + /* * @test * @bug 8278087 * @summary Test that an invalid pattern value for the jdk.serialFilter system property causes an * exception to be thrown when an attempt is made to use the filter or deserialize. * A subset of invalid filter patterns is tested. - * @run testng/othervm -Djdk.serialFilter=.* InvalidGlobalFilterTest - * @run testng/othervm -Djdk.serialFilter=! InvalidGlobalFilterTest - * @run testng/othervm -Djdk.serialFilter=/ InvalidGlobalFilterTest + * @run junit/othervm -Djdk.serialFilter=.* InvalidGlobalFilterTest + * @run junit/othervm -Djdk.serialFilter=! InvalidGlobalFilterTest + * @run junit/othervm -Djdk.serialFilter=/ InvalidGlobalFilterTest * */ -@Test public class InvalidGlobalFilterTest { private static final String serialPropName = "jdk.serialFilter"; private static final String serialFilter = System.getProperty(serialPropName); @@ -64,13 +65,13 @@ public class InvalidGlobalFilterTest { "java.base/", "Invalid jdk.serialFilter: class or package missing in: \"java.base/\"", "/", "Invalid jdk.serialFilter: module name is missing in: \"/\""); - @DataProvider(name = "MethodsToCall") - private Object[][] cases() { + // Test cases for exceptions + private static Object[][] cases() { return new Object[][] { - {serialFilter, "getSerialFilter", (Assert.ThrowingRunnable) () -> ObjectInputFilter.Config.getSerialFilter()}, - {serialFilter, "setSerialFilter", (Assert.ThrowingRunnable) () -> ObjectInputFilter.Config.setSerialFilter(new NoopFilter())}, - {serialFilter, "new ObjectInputStream(is)", (Assert.ThrowingRunnable) () -> new ObjectInputStream(new ByteArrayInputStream(new byte[0]))}, - {serialFilter, "new OISSubclass()", (Assert.ThrowingRunnable) () -> new OISSubclass()}, + {serialFilter, "getSerialFilter", (Executable) () -> ObjectInputFilter.Config.getSerialFilter()}, + {serialFilter, "setSerialFilter", (Executable) () -> ObjectInputFilter.Config.setSerialFilter(new NoopFilter())}, + {serialFilter, "new ObjectInputStream(is)", (Executable) () -> new ObjectInputStream(new ByteArrayInputStream(new byte[0]))}, + {serialFilter, "new OISSubclass()", (Executable) () -> new OISSubclass()}, }; } @@ -78,18 +79,19 @@ private Object[][] cases() { * Test each method that should throw IllegalStateException based on * the invalid arguments it was launched with. */ - @Test(dataProvider = "MethodsToCall") - public void initFaultTest(String pattern, String method, Assert.ThrowingRunnable runnable) { + @ParameterizedTest + @MethodSource("cases") + public void initFaultTest(String pattern, String method, Executable runnable) { - IllegalStateException ex = Assert.expectThrows(IllegalStateException.class, + IllegalStateException ex = Assertions.assertThrows(IllegalStateException.class, runnable); String expected = invalidMessages.get(serialFilter); if (expected == null) { - Assert.fail("No expected message for filter: " + serialFilter); + Assertions.fail("No expected message for filter: " + serialFilter); } System.out.println(ex.getMessage()); - Assert.assertEquals(ex.getMessage(), expected, "wrong message"); + Assertions.assertEquals(expected, ex.getMessage(), "wrong message"); } private static class NoopFilter implements ObjectInputFilter { diff --git a/test/jdk/java/io/Serializable/serialFilter/MixedFiltersTest.java b/test/jdk/java/io/Serializable/serialFilter/MixedFiltersTest.java index 8e24f27f5981..5af5f4d15ed1 100644 --- a/test/jdk/java/io/Serializable/serialFilter/MixedFiltersTest.java +++ b/test/jdk/java/io/Serializable/serialFilter/MixedFiltersTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,20 +25,21 @@ import java.io.InvalidClassException; import java.io.ObjectInputFilter; import java.io.ObjectInputStream; +import java.io.Serial; import java.io.Serializable; import java.security.Security; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; - -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import org.junit.jupiter.api.condition.DisabledIf; +import org.junit.jupiter.api.condition.EnabledIf; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; /* @test * @build MixedFiltersTest SerialFilterTest - * @run testng/othervm -Djdk.serialFilter=!java.**;!java.lang.Long;maxdepth=5;maxarray=5;maxbytes=90;maxrefs=5 MixedFiltersTest - * @run testng/othervm -Djdk.serialFilter=java.**;java.lang.Long;maxdepth=1000;maxarray=1000;maxbytes=1000;maxrefs=1000 MixedFiltersTest + * @run junit/othervm -Djdk.serialFilter=!java.**;!java.lang.Long;maxdepth=5;maxarray=5;maxbytes=90;maxrefs=5 MixedFiltersTest + * @run junit/othervm -Djdk.serialFilter=java.**;java.lang.Long;maxdepth=1000;maxarray=1000;maxbytes=1000;maxrefs=1000 MixedFiltersTest * * @summary Test that when both global filter and specific filter are set, * global filter will not affect specific filter. @@ -46,23 +47,17 @@ public class MixedFiltersTest implements Serializable { + @Serial private static final long serialVersionUID = 1234567890L; + private static final String JDK_SERIAL_FILTER = System.getProperty("jdk.serialFilter", + Security.getProperty("jdk.serialFilter")); - boolean globalRejected; - - @BeforeClass - public void setup() { - String pattern = System.getProperty("jdk.serialFilter", - Security.getProperty("jdk.serialFilter")); - globalRejected = pattern.startsWith("!"); + private static boolean globalRejected() { + return JDK_SERIAL_FILTER.startsWith("!"); } - @DataProvider(name="RejectedInGlobal") - Object[][] rejectedInGlobal() { - if (!globalRejected) { - return new Object[0][]; - } + static Object[][] rejectedInGlobal() { return new Object[][] { new Object[] { Long.MAX_VALUE, "java.**" }, new Object[] { Long.MAX_VALUE, "java.lang.Long" }, @@ -79,7 +74,9 @@ Object[][] rejectedInGlobal() { * "global filter reject" + "specific ObjectInputStream filter is empty" => should reject * "global filter reject" + "specific ObjectInputStream filter allow" => should allow */ - @Test(dataProvider="RejectedInGlobal") + @ParameterizedTest + @EnabledIf("globalRejected") + @MethodSource("rejectedInGlobal") public void testRejectedInGlobal(Object toDeserialized, String pattern) throws Exception { byte[] bytes = SerialFilterTest.writeObjects(toDeserialized); try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes); @@ -96,12 +93,7 @@ public void testRejectedInGlobal(Object toDeserialized, String pattern) throws E } } - @DataProvider(name="AllowedInGlobal") - Object[][] allowedInGlobal() { - if (globalRejected) { - return new Object[0][]; - } - + static Object[][] allowedInGlobal() { return new Object[][] { new Object[] { Long.MAX_VALUE, "!java.**" }, new Object[] { Long.MAX_VALUE, "!java.lang.Long" }, @@ -118,7 +110,9 @@ Object[][] allowedInGlobal() { * "global filter allow" + "specific ObjectInputStream filter is empty" => should allow * "global filter allow" + "specific ObjectInputStream filter reject" => should reject */ - @Test(dataProvider="AllowedInGlobal") + @ParameterizedTest + @DisabledIf("globalRejected") + @MethodSource("allowedInGlobal") public void testAllowedInGlobal(Object toDeserialized, String pattern) throws Exception { byte[] bytes = SerialFilterTest.writeObjects(toDeserialized); try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes); diff --git a/test/jdk/java/io/Serializable/serialFilter/SerialFactoryExample.java b/test/jdk/java/io/Serializable/serialFilter/SerialFactoryExample.java index 45b1872bae03..271c22b917cc 100644 --- a/test/jdk/java/io/Serializable/serialFilter/SerialFactoryExample.java +++ b/test/jdk/java/io/Serializable/serialFilter/SerialFactoryExample.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -21,10 +21,6 @@ * questions. */ -import org.testng.Assert; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; - import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -46,9 +42,13 @@ import static java.io.ObjectInputFilter.Status.REJECTED; import static java.io.ObjectInputFilter.Status.UNDECIDED; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + /* @test - * @run testng/othervm SerialFactoryExample - * @run testng/othervm -Djdk.serialFilterFactory=SerialFactoryExample$FilterInThread SerialFactoryExample + * @run junit/othervm SerialFactoryExample + * @run junit/othervm -Djdk.serialFilterFactory=SerialFactoryExample$FilterInThread SerialFactoryExample * @summary Test SerialFactoryExample */ @@ -76,10 +76,9 @@ * * The `doWithSerialFilter` calls can be nested. When nested, the filters are concatenated. */ -@Test public class SerialFactoryExample { - @DataProvider(name = "Examples") + // Test cases for filters static Object[][] examples() { return new Object[][]{ {new Point(1, 2), null, @@ -108,7 +107,8 @@ static Object[][] examples() { } - @Test(dataProvider = "Examples") + @ParameterizedTest + @MethodSource("examples") void examples(Serializable obj, ObjectInputFilter filter, Status expected) { // Establish FilterInThread as the application-wide filter factory FilterInThread filterInThread; @@ -128,11 +128,11 @@ void examples(Serializable obj, ObjectInputFilter filter, Status expected) { Object o = deserializeObject(bytes); }); if (expected.equals(REJECTED)) - Assert.fail("IllegalClassException should have occurred"); + Assertions.fail("IllegalClassException should have occurred"); } catch (UncheckedIOException uioe) { IOException ioe = uioe.getCause(); - Assert.assertEquals(ioe.getClass(), InvalidClassException.class, "Wrong exception"); - Assert.assertEquals(REJECTED, expected, "Exception should not have occurred"); + Assertions.assertEquals(InvalidClassException.class, ioe.getClass(), "Wrong exception"); + Assertions.assertEquals(expected, REJECTED, "Exception should not have occurred"); } } @@ -142,7 +142,8 @@ void examples(Serializable obj, ObjectInputFilter filter, Status expected) { * @param filter a filter * @param expected status */ - @Test(dataProvider = "Examples") + @ParameterizedTest + @MethodSource("examples") void checkStatus(Serializable obj, ObjectInputFilter filter, Status expected) { // Establish FilterInThread as the application-wide filter factory FilterInThread filterInThread; @@ -166,12 +167,12 @@ void checkStatus(Serializable obj, ObjectInputFilter filter, Status expected) { System.out.println(" filter in effect: " + filterInThread.currFilter); if (compositeFilter != null) { Status actualStatus = compositeFilter.checkInput(info); - Assert.assertEquals(actualStatus, expected, "Wrong Status"); + Assertions.assertEquals(expected, actualStatus, "Wrong Status"); } }); } catch (Exception ex) { - Assert.fail("unexpected exception", ex); + Assertions.fail("unexpected exception", ex); } } diff --git a/test/jdk/java/io/Serializable/serialFilter/SerialFactoryFaults.java b/test/jdk/java/io/Serializable/serialFilter/SerialFactoryFaults.java index 1d35306a4268..add9f3557da9 100644 --- a/test/jdk/java/io/Serializable/serialFilter/SerialFactoryFaults.java +++ b/test/jdk/java/io/Serializable/serialFilter/SerialFactoryFaults.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -21,9 +21,6 @@ * questions. */ -import org.testng.Assert; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -32,15 +29,19 @@ import java.io.ObjectInputStream; import java.util.function.BinaryOperator; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.function.Executable; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + /* @test - * @run testng/othervm -Djdk.serialFilterFactory=ForcedError_NoSuchClass SerialFactoryFaults - * @run testng/othervm -Djdk.serialFilterFactory=SerialFactoryFaults$NoPublicConstructor SerialFactoryFaults - * @run testng/othervm -Djdk.serialFilterFactory=SerialFactoryFaults$ConstructorThrows SerialFactoryFaults - * @run testng/othervm -Djdk.serialFilterFactory=SerialFactoryFaults$FactorySetsFactory SerialFactoryFaults + * @run junit/othervm -Djdk.serialFilterFactory=ForcedError_NoSuchClass SerialFactoryFaults + * @run junit/othervm -Djdk.serialFilterFactory=SerialFactoryFaults$NoPublicConstructor SerialFactoryFaults + * @run junit/othervm -Djdk.serialFilterFactory=SerialFactoryFaults$ConstructorThrows SerialFactoryFaults + * @run junit/othervm -Djdk.serialFilterFactory=SerialFactoryFaults$FactorySetsFactory SerialFactoryFaults * @summary Check cases where the Filter Factory initialization from properties fails */ -@Test public class SerialFactoryFaults { // Sample the serial factory class name @@ -52,13 +53,13 @@ public class SerialFactoryFaults { System.getProperty("test.src", ".") + "/logging.properties"); } - @DataProvider(name = "MethodsToCall") - private Object[][] cases() { + // Test cases of faults + private static Object[][] cases() { return new Object[][] { - {"getSerialFilterFactory", (Assert.ThrowingRunnable) () -> Config.getSerialFilterFactory()}, - {"setSerialFilterFactory", (Assert.ThrowingRunnable) () -> Config.setSerialFilterFactory(new NoopFactory())}, - {"new ObjectInputStream(is)", (Assert.ThrowingRunnable) () -> new ObjectInputStream(new ByteArrayInputStream(new byte[0]))}, - {"new OISSubclass()", (Assert.ThrowingRunnable) () -> new OISSubclass()}, + {"getSerialFilterFactory", (Executable) () -> Config.getSerialFilterFactory()}, + {"setSerialFilterFactory", (Executable) () -> Config.setSerialFilterFactory(new NoopFactory())}, + {"new ObjectInputStream(is)", (Executable) () -> new ObjectInputStream(new ByteArrayInputStream(new byte[0]))}, + {"new OISSubclass()", (Executable) () -> new OISSubclass()}, }; } @@ -66,26 +67,23 @@ private Object[][] cases() { * Test each method that should throw IllegalStateException based on * the invalid arguments it was launched with. */ - @Test(dataProvider = "MethodsToCall") - public void initFaultTest(String name, Assert.ThrowingRunnable runnable) { - IllegalStateException ex = Assert.expectThrows(IllegalStateException.class, + @ParameterizedTest + @MethodSource("cases") + public void initFaultTest(String name, Executable runnable) { + IllegalStateException ex = Assertions.assertThrows(IllegalStateException.class, runnable); final String msg = ex.getMessage(); if (factoryName.equals("ForcedError_NoSuchClass")) { - Assert.assertEquals(msg, - "invalid jdk.serialFilterFactory: ForcedError_NoSuchClass: java.lang.ClassNotFoundException: ForcedError_NoSuchClass", "wrong exception"); + Assertions.assertEquals("invalid jdk.serialFilterFactory: ForcedError_NoSuchClass: java.lang.ClassNotFoundException: ForcedError_NoSuchClass", msg, "wrong exception"); } else if (factoryName.equals("SerialFactoryFaults$NoPublicConstructor")) { - Assert.assertEquals(msg, - "invalid jdk.serialFilterFactory: SerialFactoryFaults$NoPublicConstructor: java.lang.NoSuchMethodException: SerialFactoryFaults$NoPublicConstructor.()", "wrong exception"); + Assertions.assertEquals("invalid jdk.serialFilterFactory: SerialFactoryFaults$NoPublicConstructor: java.lang.NoSuchMethodException: SerialFactoryFaults$NoPublicConstructor.()", msg, "wrong exception"); } else if (factoryName.equals("SerialFactoryFaults$ConstructorThrows")) { - Assert.assertEquals(msg, - "invalid jdk.serialFilterFactory: SerialFactoryFaults$ConstructorThrows: java.lang.RuntimeException: constructor throwing a runtime exception", "wrong exception"); + Assertions.assertEquals("invalid jdk.serialFilterFactory: SerialFactoryFaults$ConstructorThrows: java.lang.RuntimeException: constructor throwing a runtime exception", msg, "wrong exception"); } else if (factoryName.equals("SerialFactoryFaults$FactorySetsFactory")) { - Assert.assertEquals(msg, - "invalid jdk.serialFilterFactory: SerialFactoryFaults$FactorySetsFactory: java.lang.IllegalStateException: Serial filter factory initialization incomplete", "wrong exception"); + Assertions.assertEquals("invalid jdk.serialFilterFactory: SerialFactoryFaults$FactorySetsFactory: java.lang.IllegalStateException: Serial filter factory initialization incomplete", msg, "wrong exception"); } else { - Assert.fail("No test for filter factory: " + factoryName); + Assertions.fail("No test for filter factory: " + factoryName); } } diff --git a/test/jdk/java/io/Serializable/serialFilter/SerialFilterFactoryTest.java b/test/jdk/java/io/Serializable/serialFilter/SerialFilterFactoryTest.java index 6842eabe9b0c..f34f95a72b3d 100644 --- a/test/jdk/java/io/Serializable/serialFilter/SerialFilterFactoryTest.java +++ b/test/jdk/java/io/Serializable/serialFilter/SerialFilterFactoryTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,9 +22,6 @@ */ -import org.testng.Assert; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -38,17 +35,26 @@ import java.io.Serializable; import java.util.function.BinaryOperator; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.condition.EnabledIf; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + /* @test * @build SerialFilterFactoryTest - * @run testng/othervm SerialFilterFactoryTest - * @run testng/othervm -Djdk.serialFilterFactory=SerialFilterFactoryTest$PropertyFilterFactory + * @run junit/othervm SerialFilterFactoryTest + * @run junit/othervm -Djdk.serialFilterFactory=SerialFilterFactoryTest$PropertyFilterFactory * -Djava.util.logging.config.file=${test.src}/logging.properties SerialFilterFactoryTest - * @run testng/othervm -Djdk.serialFilterFactory=SerialFilterFactoryTest$NotMyFilterFactory + * @run junit/othervm -Djdk.serialFilterFactory=SerialFilterFactoryTest$NotMyFilterFactory * -Djava.util.logging.config.file=${test.src}/logging.properties SerialFilterFactoryTest * * @summary Test Context-specific Deserialization Filters */ -@Test +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class SerialFilterFactoryTest { // A stream with just the header, enough to create a OIS @@ -70,7 +76,7 @@ private static byte[] simpleStream() { ois.writeObject(new Dummy("Here")); return boas.toByteArray(); } catch (IOException ioe) { - Assert.fail("unexpected IOE", ioe); + Assertions.fail("unexpected IOE", ioe); } throw new RuntimeException("should not reach here"); } @@ -109,7 +115,7 @@ private static boolean isValidFilterFactory() { return !(ObjectInputFilter.Config.getSerialFilterFactory() instanceof NotMyFilterFactory); } - @DataProvider(name="FilterCases") + // Test cases for filter factory static Object[][] filterCases() { if (isValidFilterFactory()) { return new Object[][]{ @@ -124,9 +130,11 @@ static Object[][] filterCases() { } // Setting the filter factory to null is not allowed. - @Test(expectedExceptions=NullPointerException.class) + @Test void testNull() { - Config.setSerialFilterFactory(null); + Assertions.assertThrows(NullPointerException.class, () -> { + Config.setSerialFilterFactory(null); + }); } /** @@ -136,7 +144,6 @@ void testNull() { * Try to set it again, the second should throw. */ @Test - @SuppressWarnings("removal") void testSecondSetShouldThrow() { var currFF = Config.getSerialFilterFactory(); if (currFF.getClass().getClassLoader() == null) { @@ -145,14 +152,14 @@ void testSecondSetShouldThrow() { Config.setSerialFilterFactory(contextFilterFactory); currFF = contextFilterFactory; } catch (IllegalStateException ise) { - Assert.fail("First setSerialFilterFactory should not throw"); + Assertions.fail("First setSerialFilterFactory should not throw"); } } // Setting it again will throw - Assert.expectThrows(IllegalStateException.class, + Assertions.assertThrows(IllegalStateException.class, () -> Config.setSerialFilterFactory(new MyFilterFactory("f11"))); var resetFF = Config.getSerialFilterFactory(); - Assert.assertEquals(resetFF, currFF, "Setting again should not change filter factory"); + Assertions.assertEquals(currFF, resetFF, "Setting again should not change filter factory"); } /** @@ -167,8 +174,10 @@ void testSecondSetShouldThrow() { * @throws IOException if an I/O error occurs (should not occur) * @throws ClassNotFoundException for class not found (should not occur) */ - @Test(dataProvider="FilterCases") - @SuppressWarnings("removal") + @ParameterizedTest + @EnabledIf("isValidFilterFactory") + @MethodSource("filterCases") + @Order(1) void testCase(MyFilterFactory dynFilterFactory, Validator dynFilter, Validator streamFilter) throws IOException, ClassNotFoundException { @@ -182,32 +191,32 @@ void testCase(MyFilterFactory dynFilterFactory, Validator dynFilter, Validator s InputStream is = new ByteArrayInputStream(simpleStream); ObjectInputStream ois = new ObjectInputStream(is); - Assert.assertNull(factory.current(), "initially current should be null"); - Assert.assertEquals(factory.next(), configFilter, "initially next should be the configured filter"); + Assertions.assertNull(factory.current(), "initially current should be null"); + Assertions.assertEquals(configFilter, factory.next(), "initially next should be the configured filter"); var currFilter = ois.getObjectInputFilter(); if (currFilter != null && currFilter.getClass().getClassLoader() == null) { // Builtin loader; defaults to configured filter - Assert.assertEquals(currFilter, configFilter, "getObjectInputFilter should be configured filter"); + Assertions.assertEquals(configFilter, currFilter, "getObjectInputFilter should be configured filter"); } else { - Assert.assertEquals(currFilter, configFilter, "getObjectInputFilter should be null"); + Assertions.assertEquals(configFilter, currFilter, "getObjectInputFilter should be null"); } if (streamFilter != null) { ois.setObjectInputFilter(streamFilter); // MyFilterFactory is called when the stream filter is changed; verify values passed it - Assert.assertEquals(factory.current(), currFilter, "when setObjectInputFilter, current should be current filter"); - Assert.assertEquals(factory.next(), streamFilter, "next should be stream specific filter"); + Assertions.assertEquals(currFilter, factory.current(), "when setObjectInputFilter, current should be current filter"); + Assertions.assertEquals(streamFilter, factory.next(), "next should be stream specific filter"); // Check the OIS filter after the factory has updated it. currFilter = ois.getObjectInputFilter(); - Assert.assertEquals(currFilter, streamFilter, "getObjectInputFilter should be set"); + Assertions.assertEquals(streamFilter, currFilter, "getObjectInputFilter should be set"); // Verify that it can not be set again - Assert.assertThrows(IllegalStateException.class, () -> ois.setObjectInputFilter(streamFilter)); + Assertions.assertThrows(IllegalStateException.class, () -> ois.setObjectInputFilter(streamFilter)); } if (currFilter instanceof Validator validator) { validator.reset(); Object o = ois.readObject(); // Invoke only for the side effect of calling the Filter - Assert.assertEquals(validator.count, 1, "Wrong number of calls to the stream filter"); + Assertions.assertEquals(1, validator.count, "Wrong number of calls to the stream filter"); } else { Object o = ois.readObject(); // Invoke only for the side effect of calling the filter } @@ -217,18 +226,19 @@ void testCase(MyFilterFactory dynFilterFactory, Validator dynFilter, Validator s @Test void testPropertyFilterFactory() { if (jdkSerialFilterFactoryProp != null) { - Assert.assertEquals(jdkSerialFilterFactory.getClass().getName(), jdkSerialFilterFactoryProp, + Assertions.assertEquals(jdkSerialFilterFactoryProp, jdkSerialFilterFactory.getClass().getName(), "jdk.serialFilterFactory property classname mismatch"); } } // Test that setting the filter factory after any deserialization (any testCase) // throws IllegalStateException with the specific message - @Test(dependsOnMethods="testCase") + @Test + @Order(99) void testSetFactoryAfterDeserialization() { BinaryOperator factory = Config.getSerialFilterFactory(); - IllegalStateException ise = Assert.expectThrows(IllegalStateException.class, () -> Config.setSerialFilterFactory(factory)); - Assert.assertTrue(ise.getMessage().startsWith("Cannot replace filter factory: ")); + IllegalStateException ise = Assertions.assertThrows(IllegalStateException.class, () -> Config.setSerialFilterFactory(factory)); + Assertions.assertTrue(ise.getMessage().startsWith("Cannot replace filter factory: ")); } @@ -243,11 +253,11 @@ void testDisableFailFilter() throws IOException { // Try to set the filter to null ois.setObjectInputFilter(null); if (curr != null) { - Assert.fail("setting filter to null after a non-null filter should throw"); + Assertions.fail("setting filter to null after a non-null filter should throw"); } } catch (IllegalStateException ise) { if (curr == null) { - Assert.fail("setting filter to null after a null filter should not throw"); + Assertions.fail("setting filter to null after a null filter should not throw"); } } } diff --git a/test/jdk/java/io/Serializable/serialFilter/SerialFilterFunctionTest.java b/test/jdk/java/io/Serializable/serialFilter/SerialFilterFunctionTest.java index 41832d583c22..8930c2dfa9db 100644 --- a/test/jdk/java/io/Serializable/serialFilter/SerialFilterFunctionTest.java +++ b/test/jdk/java/io/Serializable/serialFilter/SerialFilterFunctionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -21,11 +21,6 @@ * questions. */ - -import org.testng.Assert; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; - import java.io.ObjectInputFilter; import java.io.ObjectInputFilter.FilterInfo; import java.util.function.Predicate; @@ -35,12 +30,16 @@ import static java.io.ObjectInputFilter.Status.REJECTED; import static java.io.ObjectInputFilter.Status.UNDECIDED; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + /* @test - * @run testng/othervm -Djava.util.logging.config.file=${test.src}/logging.properties + * @run junit/othervm -Djava.util.logging.config.file=${test.src}/logging.properties * SerialFilterFunctionTest * @summary ObjectInputFilter.Config Function Tests */ -@Test public class SerialFilterFunctionTest { @Test @@ -53,10 +52,10 @@ void testMerge() { ObjectInputFilter filter2 = getFilter(st2); ObjectInputFilter f = ObjectInputFilter.merge(filter1, filter2); Status r = f.checkInput(info); - Assert.assertEquals(merge(st1, st2), r, "merge"); + Assertions.assertEquals(r, merge(st1, st2), "merge"); } - Assert.assertSame(ObjectInputFilter.merge(filter1, null), filter1, "merge with null fail"); - Assert.assertThrows(NullPointerException.class, () -> ObjectInputFilter.merge(null, filter1)); + Assertions.assertSame(ObjectInputFilter.merge(filter1, null), filter1, "merge with null fail"); + Assertions.assertThrows(NullPointerException.class, () -> ObjectInputFilter.merge(null, filter1)); } } @@ -84,7 +83,7 @@ static Predicate> isInteger() { return (cl) -> cl.equals(Integer.class); } - @DataProvider(name = "AllowPredicateCases") + // Test cases of filter strings static Object[][] allowPredicateCases() { return new Object[][]{ { Integer.class, isInteger(), REJECTED, ALLOWED}, @@ -95,18 +94,17 @@ static Object[][] allowPredicateCases() { }; } - @Test(dataProvider = "AllowPredicateCases") + @ParameterizedTest + @MethodSource("allowPredicateCases") void testAllowPredicates(Class clazz, Predicate> predicate, Status otherStatus, Status expected) { ObjectInputFilter.FilterInfo info = new SerialInfo(clazz); if (predicate == null || expected == null) { - Assert.assertThrows(NullPointerException.class, () -> ObjectInputFilter.allowFilter(predicate, expected)); + Assertions.assertThrows(NullPointerException.class, () -> ObjectInputFilter.allowFilter(predicate, expected)); } else { - Assert.assertEquals(ObjectInputFilter.allowFilter(predicate, otherStatus).checkInput(info), - expected, "Predicate result"); + Assertions.assertEquals( expected, ObjectInputFilter.allowFilter(predicate, otherStatus).checkInput(info), "Predicate result"); } } - @DataProvider(name = "RejectPredicateCases") static Object[][] rejectPredicateCases() { return new Object[][]{ { Integer.class, isInteger(), REJECTED, REJECTED}, @@ -117,14 +115,15 @@ static Object[][] rejectPredicateCases() { }; } - @Test(dataProvider = "RejectPredicateCases") + @ParameterizedTest + @MethodSource("rejectPredicateCases") void testRejectPredicates(Class clazz, Predicate> predicate, Status otherStatus, Status expected) { ObjectInputFilter.FilterInfo info = new SerialInfo(clazz); if (predicate == null || expected == null) { - Assert.assertThrows(NullPointerException.class, () -> ObjectInputFilter.allowFilter(predicate, expected)); + Assertions.assertThrows(NullPointerException.class, () -> ObjectInputFilter.allowFilter(predicate, expected)); } else { - Assert.assertEquals(ObjectInputFilter.rejectFilter(predicate, otherStatus) - .checkInput(info), expected, "Predicate result"); + Assertions.assertEquals(expected, ObjectInputFilter.rejectFilter(predicate, otherStatus) + .checkInput(info), "Predicate result"); } } @@ -133,11 +132,11 @@ void testRejectUndecided() { FilterInfo info = new SerialInfo(Object.class); // an info structure, unused ObjectInputFilter undecided = getFilter(UNDECIDED); - Assert.assertEquals(ObjectInputFilter.rejectUndecidedClass(undecided).checkInput(info), REJECTED, "undecided -> rejected"); + Assertions.assertEquals(REJECTED, ObjectInputFilter.rejectUndecidedClass(undecided).checkInput(info), "undecided -> rejected"); ObjectInputFilter allowed = getFilter(ALLOWED); - Assert.assertEquals(ObjectInputFilter.rejectUndecidedClass(allowed).checkInput(info), ALLOWED, "allowed -> rejected"); + Assertions.assertEquals(ALLOWED, ObjectInputFilter.rejectUndecidedClass(allowed).checkInput(info), "allowed -> rejected"); ObjectInputFilter rejected = getFilter(REJECTED); - Assert.assertEquals(ObjectInputFilter.rejectUndecidedClass(rejected).checkInput(info), REJECTED, "rejected -> rejected"); + Assertions.assertEquals(REJECTED, ObjectInputFilter.rejectUndecidedClass(rejected).checkInput(info), "rejected -> rejected"); // Specific cases of Classes the result in allowed, rejected, and undecided status ObjectInputFilter numberFilter = ObjectInputFilter.Config.createFilter("java.lang.Integer;!java.lang.Double"); @@ -164,9 +163,9 @@ void testRejectUndecided() { while (clazz.isArray()) clazz = clazz.getComponentType(); Status expected = (clazz.isPrimitive()) ? UNDECIDED : REJECTED; - Assert.assertEquals(st, expected, "Wrong status for class: " + obj.getClass()); + Assertions.assertEquals(expected, st, "Wrong status for class: " + obj.getClass()); } else { - Assert.assertEquals(rawSt, st, "raw filter and rejectUndecided filter disagree"); + Assertions.assertEquals(st, rawSt, "raw filter and rejectUndecided filter disagree"); } } } diff --git a/test/jdk/java/io/Serializable/serialFilter/SerialFilterTest.java b/test/jdk/java/io/Serializable/serialFilter/SerialFilterTest.java index fb02cd022686..3b2ecc3d1b14 100644 --- a/test/jdk/java/io/Serializable/serialFilter/SerialFilterTest.java +++ b/test/jdk/java/io/Serializable/serialFilter/SerialFilterTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -46,20 +46,20 @@ import javax.net.ssl.SSLEngineResult; -import org.testng.Assert; -import org.testng.annotations.Test; -import org.testng.annotations.DataProvider; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; /* @test * @bug 8234836 * @build SerialFilterTest - * @run testng/othervm -Djava.util.logging.config.file=${test.src}/logging.properties + * @run junit/othervm -Djava.util.logging.config.file=${test.src}/logging.properties * SerialFilterTest - * @run testng/othervm -Djdk.serialSetFilterAfterRead=true SerialFilterTest + * @run junit/othervm -Djdk.serialSetFilterAfterRead=true SerialFilterTest * * @summary Test ObjectInputFilters using Builtin Filter Factory */ -@Test public class SerialFilterTest implements Serializable { @Serial @@ -89,9 +89,8 @@ interface TriConsumer< T, U, V> { * Expand the patterns into cases for each of the Std and Compatibility APIs. * @return an array of arrays of the parameters including factories */ - @DataProvider(name="Patterns") static Object[][] patterns() { - Object[][] patterns = new Object[][]{ + return new Object[][]{ {"java.util.Hashtable"}, {"java.util.Hash*"}, {"javax.net.ssl.*"}, @@ -105,10 +104,8 @@ static Object[][] patterns() { {"maxbytes=+1024"}, {"java.base/java.util.Hashtable"}, }; - return patterns; } - @DataProvider(name="InvalidPatterns") static Object[][] invalidPatterns() { return new Object [][] { {".*"}, @@ -120,7 +117,6 @@ static Object[][] invalidPatterns() { }; } - @DataProvider(name="Limits") static Object[][] limits() { // The numbers are arbitrary > 1 return new Object[][] { @@ -133,7 +129,6 @@ static Object[][] limits() { }; } - @DataProvider(name="InvalidLimits") static Object[][] invalidLimits() { return new Object[][] { {"maxrefs=-1"}, @@ -157,7 +152,6 @@ static Object[][] invalidLimits() { * available to the filter. * @return Arrays of parameters with objects */ - @DataProvider(name="Objects") static Object[][] objects() { byte[] byteArray = new byte[0]; Object[] objArray = new Object[7]; @@ -168,7 +162,7 @@ static Object[][] objects() { try { serClass = Class.forName(className); } catch (Exception e) { - Assert.fail("missing class: " + className, e); + Assertions.fail("missing class: " + className, e); } Class[] interfaces = {Runnable.class}; @@ -213,7 +207,6 @@ static Object[][] objects() { return objects; } - @DataProvider(name="Arrays") static Object[][] arrays() { return new Object[][]{ {new Object[16], 16}, @@ -244,21 +237,22 @@ static Object[][] arrays() { * @param classes the expected (unique) classes * @throws IOException */ - @Test(dataProvider="Objects") + @ParameterizedTest + @MethodSource("objects") void t1(Object object, long count, long maxArray, long maxRefs, long maxDepth, long maxBytes, List> classes) throws IOException { byte[] bytes = writeObjects(object); Validator validator = new Validator(); validate(bytes, validator); - System.out.printf("v: %s%n", validator); + System.err.printf("v: %s%n", validator); - Assert.assertEquals(validator.count, count, "callback count wrong"); - Assert.assertEquals(validator.classes, classes, "classes mismatch"); - Assert.assertEquals(validator.maxArray, maxArray, "maxArray mismatch"); - Assert.assertEquals(validator.maxRefs, maxRefs, "maxRefs wrong"); - Assert.assertEquals(validator.maxDepth, maxDepth, "depth wrong"); - Assert.assertEquals(validator.maxBytes, maxBytes, "maxBytes wrong"); + Assertions.assertEquals(count, validator.count, "callback count wrong"); + Assertions.assertEquals(classes, validator.classes, "classes mismatch"); + Assertions.assertEquals(maxArray, validator.maxArray, "maxArray mismatch"); + Assertions.assertEquals(maxRefs, validator.maxRefs, "maxRefs wrong"); + Assertions.assertEquals(maxDepth, validator.maxDepth, "depth wrong"); + Assertions.assertEquals(maxBytes, validator.maxBytes, "maxBytes wrong"); } /** @@ -269,7 +263,8 @@ void t1(Object object, * * @param pattern a pattern */ - @Test(dataProvider="Patterns") + @ParameterizedTest + @MethodSource("patterns") void testPatterns(String pattern) { evalPattern(pattern, (p, o, neg) -> testPatterns(p, o, neg)); } @@ -297,23 +292,23 @@ void nonResettableFilter() { // Check the initial filter is the global filter; may be null ObjectInputFilter global = ObjectInputFilter.Config.getSerialFilter(); ObjectInputFilter initial = ois.getObjectInputFilter(); - Assert.assertEquals(global, initial, "initial filter should be the global filter"); + Assertions.assertEquals(initial, global, "initial filter should be the global filter"); ois.setObjectInputFilter(validator); Object o = ois.readObject(); try { ois.setObjectInputFilter(validator2); - Assert.fail("Should not be able to set filter twice"); + Assertions.fail("Should not be able to set filter twice"); } catch (IllegalStateException ise) { // success, the exception was expected } } catch (EOFException eof) { - Assert.fail("Should not reach end-of-file", eof); + Assertions.fail("Should not reach end-of-file", eof); } catch (ClassNotFoundException cnf) { - Assert.fail("Deserializing", cnf); + Assertions.fail("Deserializing", cnf); } } catch (IOException ex) { - Assert.fail("Unexpected IOException", ex); + Assertions.fail("Unexpected IOException", ex); } } } @@ -336,7 +331,7 @@ void testNonSettableAfterReadObject() throws IOException, ClassNotFoundException try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes); ObjectInputStream ois = new ObjectInputStream(bais)) { Object actual1 = toggle ? ois.readObject() : ois.readUnshared(); - Assert.assertEquals(actual1, expected1, "unexpected string"); + Assertions.assertEquals(expected1, actual1, "unexpected string"); // Attempt to set filter ois.setObjectInputFilter(new ObjectInputFilter() { @Override @@ -345,14 +340,14 @@ public Status checkInput(FilterInfo filterInfo) { } }); if (!SET_FILTER_AFTER_READ) - Assert.fail("Should not be able to set filter after readObject has been called"); + Assertions.fail("Should not be able to set filter after readObject has been called"); } catch (IllegalStateException ise) { // success, the exception was expected if (SET_FILTER_AFTER_READ) - Assert.fail("With jdk.serialSetFilterAfterRead property set = true; " + + Assertions.fail("With jdk.serialSetFilterAfterRead property set = true; " + "should be able to set the filter after a read"); } catch (EOFException eof) { - Assert.fail("Should not reach end-of-file", eof); + Assertions.fail("Should not reach end-of-file", eof); } } } @@ -362,12 +357,13 @@ public Status checkInput(FilterInfo filterInfo) { * that the callback to the filter includes the proper array length. * @throws IOException if an error occurs */ - @Test(dataProvider="Arrays") + @ParameterizedTest + @MethodSource("arrays") void testReadResolveToArray(Object array, int length) throws IOException { ReadResolveToArray object = new ReadResolveToArray(array, length); byte[] bytes = writeObjects(object); Object o = validate(bytes, object); // the object is its own filter - Assert.assertEquals(o.getClass(), array.getClass(), "Filter not called with the array"); + Assertions.assertEquals(array.getClass(), o.getClass(), "Filter not called with the array"); } @@ -379,18 +375,17 @@ void testReadResolveToArray(Object array, int length) throws IOException { * @param name the name of the limit to test * @param value a test value */ - @Test(dataProvider="Limits") + @ParameterizedTest + @MethodSource("limits") void testLimits(String name, long value) { Class arrayClass = new int[0].getClass(); String pattern = String.format("%s=%d;%s=%d", name, value, name, value - 1); ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(pattern); - Assert.assertEquals( + Assertions.assertEquals(ObjectInputFilter.Status.REJECTED, filter.checkInput(new FilterValues(arrayClass, value, value, value, value)), - ObjectInputFilter.Status.REJECTED, "last limit value not used: " + filter); - Assert.assertEquals( + Assertions.assertEquals(ObjectInputFilter.Status.UNDECIDED, filter.checkInput(new FilterValues(arrayClass, value-1, value-1, value-1, value-1)), - ObjectInputFilter.Status.UNDECIDED, "last limit value not used: " + filter); } @@ -399,46 +394,36 @@ void testLimits(String name, long value) { * Construct a filter with the limit, it should throw IllegalArgumentException. * @param pattern a pattern to test */ - @Test(dataProvider="InvalidLimits", expectedExceptions=java.lang.IllegalArgumentException.class) + @ParameterizedTest + @MethodSource("invalidLimits") void testInvalidLimits(String pattern) { - try { - ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(pattern); - } catch (IllegalArgumentException iae) { - System.out.printf(" success exception: %s%n", iae); - throw iae; - } + var iae = Assertions.assertThrows(IllegalArgumentException.class, + () -> ObjectInputFilter.Config.createFilter(pattern)); + System.err.printf(" success exception: %s%n", iae); } /** * Test that returning null from a filter causes deserialization to fail. */ - @Test(expectedExceptions=InvalidClassException.class) + @Test void testNullStatus() throws IOException { - byte[] bytes = writeObjects(0); // an Integer - try { - Object o = validate(bytes, new ObjectInputFilter() { - public ObjectInputFilter.Status checkInput(ObjectInputFilter.FilterInfo f) { - return null; - } - }); - } catch (InvalidClassException ice) { - System.out.printf(" success exception: %s%n", ice); - throw ice; - } + var ice = Assertions.assertThrows(InvalidClassException.class, () -> { + byte[] bytes = writeObjects(0); // an Integer + validate(bytes, f -> null); + }); + System.err.printf(" success exception: %s%n", ice); } /** * Verify that malformed patterns throw IAE. * @param pattern pattern from the data source */ - @Test(dataProvider="InvalidPatterns", expectedExceptions=IllegalArgumentException.class) + @ParameterizedTest + @MethodSource("invalidPatterns") void testInvalidPatterns(String pattern) { - try { - ObjectInputFilter.Config.createFilter(pattern); - } catch (IllegalArgumentException iae) { - System.out.printf(" success exception: %s%n", iae); - throw iae; - } + var iae = Assertions.assertThrows(IllegalArgumentException.class, + () -> ObjectInputFilter.Config.createFilter(pattern)); + System.err.printf(" success exception: %s%n", iae); } /** @@ -447,10 +432,10 @@ void testInvalidPatterns(String pattern) { @Test() void testEmptyPattern() { ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(""); - Assert.assertNull(filter, "empty pattern did not return null"); + Assertions.assertNull(filter, "empty pattern did not return null"); filter = ObjectInputFilter.Config.createFilter(";;;;"); - Assert.assertNull(filter, "pattern with only delimiters did not return null"); + Assertions.assertNull(filter, "pattern with only delimiters did not return null"); } /** @@ -472,7 +457,7 @@ static Object validate(byte[] bytes, } catch (EOFException eof) { // normal completion } catch (ClassNotFoundException cnf) { - Assert.fail("Deserializing", cnf); + Assertions.fail("Deserializing", cnf); } return null; } @@ -514,7 +499,7 @@ static class Validator implements ObjectInputFilter { @Override public ObjectInputFilter.Status checkInput(FilterInfo filter) { Class serialClass = filter.serialClass(); - System.out.printf(" checkInput: class: %s, arrayLen: %d, refs: %d, depth: %d, bytes; %d%n", + System.err.printf(" checkInput: class: %s, arrayLen: %d, refs: %d, depth: %d, bytes; %d%n", serialClass, filter.arrayLength(), filter.references(), filter.depth(), filter.streamBytes()); count++; @@ -561,13 +546,13 @@ static void testPatterns(String pattern, Object object, boolean allowed) { byte[] bytes = SerialFilterTest.writeObjects(object); ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(pattern); validate(bytes, filter); - Assert.assertTrue(allowed, "filter should have thrown an exception"); + Assertions.assertTrue(allowed, "filter should have thrown an exception"); } catch (IllegalArgumentException iae) { - Assert.fail("bad format pattern", iae); + Assertions.fail("bad format pattern", iae); } catch (InvalidClassException ice) { - Assert.assertFalse(allowed, "filter should not have thrown an exception: " + ice); + Assertions.assertFalse(allowed, "filter should not have thrown an exception: " + ice); } catch (IOException ioe) { - Assert.fail("Unexpected IOException", ioe); + Assertions.fail("Unexpected IOException", ioe); } } @@ -578,12 +563,12 @@ static void testPatterns(String pattern, Object object, boolean allowed) { */ static void evalPattern(String pattern, TriConsumer action) { Object o = genTestObject(pattern, true); - Assert.assertNotNull(o, "success generation failed"); + Assertions.assertNotNull(o, "success generation failed"); action.accept(pattern, o, true); // Test the negative pattern o = genTestObject(pattern, false); - Assert.assertNotNull(o, "fail generation failed"); + Assertions.assertNotNull(o, "fail generation failed"); String negPattern = pattern.contains("=") ? pattern : "!" + pattern; action.accept(negPattern, o, false); } @@ -616,12 +601,12 @@ static Object genTestObject(String pattern, boolean allowed) { Constructor cons = clazz.getConstructor(); return cons.newInstance(); } catch (ClassNotFoundException ex) { - Assert.fail("no such class available: " + pattern); + Assertions.fail("no such class available: " + pattern); } catch (InvocationTargetException | NoSuchMethodException | InstantiationException | IllegalAccessException ex1) { - Assert.fail("newInstance: " + ex1); + Assertions.fail("newInstance: " + ex1); } } return null; @@ -661,7 +646,7 @@ static Object genTestObjectWildcard(String pattern, boolean allowed) { return new Hashtable(); } } - Assert.fail("Object could not be generated for pattern: " + Assertions.fail("Object could not be generated for pattern: " + pattern + ", allowed: " + allowed); return null; @@ -677,7 +662,7 @@ static Object genTestObjectWildcard(String pattern, boolean allowed) { */ static Object genTestLimit(String pattern, boolean allowed) { int ndx = pattern.indexOf('='); - Assert.assertNotEquals(ndx, -1, "missing value in limit"); + Assertions.assertNotEquals(-1, ndx, "missing value in limit"); long value = Long.parseUnsignedLong(pattern.substring(ndx+1)); if (pattern.startsWith("maxdepth=")) { // Return an object with the requested depth (or 1 greater) @@ -703,7 +688,7 @@ static Object genTestLimit(String pattern, boolean allowed) { } else if (pattern.startsWith("maxarray=")) { return allowed ? new int[(int)value] : new int[(int)value+1]; } - Assert.fail("Object could not be generated for pattern: " + Assertions.fail("Object could not be generated for pattern: " + pattern + ", allowed: " + allowed); return null; @@ -736,7 +721,7 @@ private static Object genMaxBytesObject(boolean allowed, long maxBytes) { os.flush(); actualSize = baos.size(); } catch (IOException ie) { - Assert.fail("exception generating stream", ie); + Assertions.fail("exception generating stream", ie); } } while (actualSize != desiredSize); return holder; From 9353e89628e43b8050993b41244a87371780f7db Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Fri, 27 Mar 2026 15:00:53 +0000 Subject: [PATCH 087/168] 8321687: Test vmTestbase/nsk/jvmti/scenarios/contention/TC03/tc03t002/TestDescription.java failed: JVMTI_ERROR_THREAD_NOT_ALIVE Backport-of: 181657084a547457327b8657d7a8d3faa17eb1f5 --- .../scenarios/contention/TC03/tc03t001.java | 6 +- .../contention/TC03/tc03t001/tc03t001.cpp | 64 ++++++++++-------- .../scenarios/contention/TC03/tc03t002.java | 7 +- .../contention/TC03/tc03t002/tc03t002.cpp | 65 +++++++++++-------- 4 files changed, 85 insertions(+), 57 deletions(-) diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/contention/TC03/tc03t001.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/contention/TC03/tc03t001.java index 4c7905809d97..eb202c87ef3d 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/contention/TC03/tc03t001.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/contention/TC03/tc03t001.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -123,6 +123,8 @@ public int runIt(String argv[], PrintStream out) { /* =================================================================== */ class tc03t001Thread extends Thread { + // The thread name prefix is used to find thread from jvmti agent. + final static String threadNamePrefix = "Debuggee Thread"; Object lock1; Object lock2; @@ -130,7 +132,7 @@ class tc03t001Thread extends Thread { int lock2Counter = 0; public tc03t001Thread(Object o1, Object o2) { - super("Debuggee Thread " + o1 + o2); + super(threadNamePrefix + " " + o1 + o2); lock1 = o1; lock2 = o2; } diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/contention/TC03/tc03t001/tc03t001.cpp b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/contention/TC03/tc03t001/tc03t001.cpp index ff7d346d237a..9ea61a27bc65 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/contention/TC03/tc03t001/tc03t001.cpp +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/contention/TC03/tc03t001/tc03t001.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -41,10 +41,13 @@ typedef struct { static jlong timeout = 0; /* test objects */ -static threadDesc *threadList = nullptr; -static jint threads_count = 0; +static threadDesc *debuggee_threads = nullptr; +static jint debuggee_threads_cnt = 0; static int numberOfDeadlocks = 0; +static const char* THREAD_NAME_PREFIX = "Debugee Thread"; +static const size_t THREAD_NAME_PREFIX_LEN = strlen(THREAD_NAME_PREFIX); + /* ========================================================================== */ static int printDeadlock(jvmtiEnv* jvmti, JNIEnv* jni, int dThread) { @@ -56,9 +59,9 @@ static int printDeadlock(jvmtiEnv* jvmti, JNIEnv* jni, int dThread) { NSK_DISPLAY1("Found deadlock #%d:\n", numberOfDeadlocks); for (pThread = dThread;;pThread = cThread) { - NSK_DISPLAY1(" \"%s\":\n", threadList[pThread].name); + NSK_DISPLAY1(" \"%s\":\n", debuggee_threads[pThread].name); if (!NSK_JVMTI_VERIFY( - jvmti->GetCurrentContendedMonitor(threadList[pThread].thread, &monitor))) + jvmti->GetCurrentContendedMonitor(debuggee_threads[pThread].thread, &monitor))) return NSK_FALSE; if (monitor != nullptr) { if (!NSK_JNI_VERIFY(jni, (klass = jni->GetObjectClass(monitor)) != nullptr)) @@ -74,8 +77,8 @@ static int printDeadlock(jvmtiEnv* jvmti, JNIEnv* jni, int dThread) { return NSK_FALSE; if (usageInfo.owner == nullptr) break; - for (cThread = 0; cThread < threads_count; cThread++) { - if (jni->IsSameObject(threadList[cThread].thread, usageInfo.owner)) + for (cThread = 0; cThread < debuggee_threads_cnt; cThread++) { + if (jni->IsSameObject(debuggee_threads[cThread].thread, usageInfo.owner)) break; } if (usageInfo.waiters != nullptr) { @@ -84,10 +87,10 @@ static int printDeadlock(jvmtiEnv* jvmti, JNIEnv* jni, int dThread) { if (usageInfo.notify_waiters != nullptr) { jvmti->Deallocate((unsigned char*)usageInfo.notify_waiters); } - if (!NSK_VERIFY(cThread != threads_count)) + if (!NSK_VERIFY(cThread != debuggee_threads_cnt)) return NSK_FALSE; NSK_DISPLAY1(" which is held by \"%s\"\n", - threadList[cThread].name); + debuggee_threads[cThread].name); if (cThread == dThread) break; } @@ -103,8 +106,9 @@ static int findDeadlockThreads(jvmtiEnv* jvmti, JNIEnv* jni) { int tDfn = 0, gDfn = 0; int pThread, cThread; int i; + int threads_count = 0; - NSK_DISPLAY0("Create threadList\n"); + NSK_DISPLAY0("Create debuggee_threads\n"); /* get all live threads */ if (!NSK_JVMTI_VERIFY(jvmti->GetAllThreads(&threads_count, &threads))) @@ -114,7 +118,7 @@ static int findDeadlockThreads(jvmtiEnv* jvmti, JNIEnv* jni) { return NSK_FALSE; if (!NSK_JVMTI_VERIFY( - jvmti->Allocate(threads_count*sizeof(threadDesc), (unsigned char**)&threadList))) + jvmti->Allocate(threads_count*sizeof(threadDesc), (unsigned char**)&debuggee_threads))) return NSK_FALSE; for (i = 0; i < threads_count; i++) { @@ -127,22 +131,30 @@ static int findDeadlockThreads(jvmtiEnv* jvmti, JNIEnv* jni) { NSK_DISPLAY3(" thread #%d (%s): %p\n", i, info.name, threads[i]); - threadList[i].thread = threads[i]; - threadList[i].dfn = -1; - threadList[i].name = info.name; + if (!strncmp(info.name, THREAD_NAME_PREFIX, THREAD_NAME_PREFIX_LEN)) { + NSK_DISPLAY1("Skipping thread %s\n", info.name); + if (!NSK_JVMTI_VERIFY(jvmti->Deallocate((unsigned char*)info.name))) + return NSK_FALSE; + continue; + } + + debuggee_threads[debuggee_threads_cnt].thread = threads[i]; + debuggee_threads[debuggee_threads_cnt].dfn = -1; + debuggee_threads[debuggee_threads_cnt].name = info.name; + debuggee_threads_cnt++; } /* deallocate thread list */ if (!NSK_JVMTI_VERIFY(jvmti->Deallocate((unsigned char*)threads))) return NSK_FALSE; - for (i = 0; i < threads_count; i++) { - if (threadList[i].dfn < 0) { + for (i = 0; i < debuggee_threads_cnt; i++) { + if (debuggee_threads[i].dfn < 0) { tDfn = gDfn; - threadList[i].dfn = gDfn++; + debuggee_threads[i].dfn = gDfn++; for (pThread = i;;pThread = cThread) { if (!NSK_JVMTI_VERIFY( - jvmti->GetCurrentContendedMonitor(threadList[pThread].thread, &monitor))) + jvmti->GetCurrentContendedMonitor(debuggee_threads[pThread].thread, &monitor))) return NSK_FALSE; if (monitor == nullptr) break; @@ -150,8 +162,8 @@ static int findDeadlockThreads(jvmtiEnv* jvmti, JNIEnv* jni) { return NSK_FALSE; if (usageInfo.owner == nullptr) break; - for (cThread = 0; cThread < threads_count; cThread++) { - if (jni->IsSameObject(threadList[cThread].thread, usageInfo.owner)) + for (cThread = 0; cThread < debuggee_threads_cnt; cThread++) { + if (jni->IsSameObject(debuggee_threads[cThread].thread, usageInfo.owner)) break; } if (usageInfo.waiters != nullptr) { @@ -160,10 +172,10 @@ static int findDeadlockThreads(jvmtiEnv* jvmti, JNIEnv* jni) { if (usageInfo.notify_waiters != nullptr) { jvmti->Deallocate((unsigned char*)usageInfo.notify_waiters); } - if (!NSK_VERIFY(cThread != threads_count)) + if (!NSK_VERIFY(cThread != debuggee_threads_cnt)) return NSK_FALSE; - if (threadList[cThread].dfn < 0) { - threadList[cThread].dfn = gDfn++; + if (debuggee_threads[cThread].dfn < 0) { + debuggee_threads[cThread].dfn = gDfn++; } else if (cThread == pThread) { break; } else { @@ -179,9 +191,9 @@ static int findDeadlockThreads(jvmtiEnv* jvmti, JNIEnv* jni) { } /* deallocate thread names */ - for (i = 0; i < threads_count; i++) { - if (threadList[i].name != nullptr) { - if (!NSK_JVMTI_VERIFY(jvmti->Deallocate((unsigned char*)threadList[i].name))) + for (i = 0; i < debuggee_threads_cnt; i++) { + if (debuggee_threads[i].name != nullptr) { + if (!NSK_JVMTI_VERIFY(jvmti->Deallocate((unsigned char*)debuggee_threads[i].name))) return NSK_FALSE; } } diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/contention/TC03/tc03t002.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/contention/TC03/tc03t002.java index f4ca83c0b546..852b915acd74 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/contention/TC03/tc03t002.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/contention/TC03/tc03t002.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -104,7 +104,8 @@ public int runIt(String argv[], PrintStream out) { /* =================================================================== */ class tc03t002Thread extends Thread { - + // The thread name prefix is used to find thread from jvmti agent. + final static String threadNamePrefix = "Debuggee Thread"; static Wicket startingBarrier = new Wicket(3); static Wicket lockingBarrier = new Wicket(3); Wicket waitingBarrier = new Wicket(); @@ -112,7 +113,7 @@ class tc03t002Thread extends Thread { Object lock2; public tc03t002Thread(Object o1, Object o2) { - super("Debuggee Thread " + o1 + o2); + super(threadNamePrefix + " " + o1 + o2); lock1 = o1; lock2 = o2; } diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/contention/TC03/tc03t002/tc03t002.cpp b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/contention/TC03/tc03t002/tc03t002.cpp index 11c74e3a9e2c..5d18d6c23afd 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/contention/TC03/tc03t002/tc03t002.cpp +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/contention/TC03/tc03t002/tc03t002.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -41,10 +41,13 @@ typedef struct { static jlong timeout = 0; /* test objects */ -static threadDesc *threadList = nullptr; -static jint threads_count = 0; +static threadDesc *debuggee_threads = nullptr; +static jint debuggee_threads_cnt = 0; static int numberOfDeadlocks = 0; +static const char* THREAD_NAME_PREFIX = "Debugee Thread"; +static const size_t THREAD_NAME_PREFIX_LEN = strlen(THREAD_NAME_PREFIX); + /* ========================================================================== */ static int printDeadlock(jvmtiEnv* jvmti, JNIEnv* jni, int dThread) { @@ -56,9 +59,9 @@ static int printDeadlock(jvmtiEnv* jvmti, JNIEnv* jni, int dThread) { NSK_DISPLAY1("Found deadlock #%d:\n", numberOfDeadlocks); for (pThread = dThread;;pThread = cThread) { - NSK_DISPLAY1(" \"%s\":\n", threadList[pThread].name); + NSK_DISPLAY1(" \"%s\":\n", debuggee_threads[pThread].name); if (!NSK_JVMTI_VERIFY( - jvmti->GetCurrentContendedMonitor(threadList[pThread].thread, &monitor))) + jvmti->GetCurrentContendedMonitor(debuggee_threads[pThread].thread, &monitor))) return NSK_FALSE; if (monitor != nullptr) { if (!NSK_JNI_VERIFY(jni, (klass = jni->GetObjectClass(monitor)) != nullptr)) @@ -74,8 +77,8 @@ static int printDeadlock(jvmtiEnv* jvmti, JNIEnv* jni, int dThread) { return NSK_FALSE; if (usageInfo.owner == nullptr) break; - for (cThread = 0; cThread < threads_count; cThread++) { - if (jni->IsSameObject(threadList[cThread].thread, usageInfo.owner)) + for (cThread = 0; cThread < debuggee_threads_cnt; cThread++) { + if (jni->IsSameObject(debuggee_threads[cThread].thread, usageInfo.owner)) break; } if (usageInfo.waiters != nullptr) { @@ -84,10 +87,10 @@ static int printDeadlock(jvmtiEnv* jvmti, JNIEnv* jni, int dThread) { if (usageInfo.notify_waiters != nullptr) { jvmti->Deallocate((unsigned char*)usageInfo.notify_waiters); } - if (!NSK_VERIFY(cThread != threads_count)) + if (!NSK_VERIFY(cThread != debuggee_threads_cnt)) return NSK_FALSE; NSK_DISPLAY1(" which is held by \"%s\"\n", - threadList[cThread].name); + debuggee_threads[cThread].name); if (cThread == dThread) break; } @@ -103,8 +106,9 @@ static int findDeadlockThreads(jvmtiEnv* jvmti, JNIEnv* jni) { int tDfn = 0, gDfn = 0; int pThread, cThread; int i; + int threads_count = 0; - NSK_DISPLAY0("Create threadList\n"); + NSK_DISPLAY0("Create debuggee_threads\n"); /* get all live threads */ if (!NSK_JVMTI_VERIFY(jvmti->GetAllThreads(&threads_count, &threads))) @@ -114,7 +118,7 @@ static int findDeadlockThreads(jvmtiEnv* jvmti, JNIEnv* jni) { return NSK_FALSE; if (!NSK_JVMTI_VERIFY( - jvmti->Allocate(threads_count*sizeof(threadDesc), (unsigned char**)&threadList))) + jvmti->Allocate(threads_count*sizeof(threadDesc), (unsigned char**)&debuggee_threads))) return NSK_FALSE; for (i = 0; i < threads_count; i++) { @@ -127,22 +131,31 @@ static int findDeadlockThreads(jvmtiEnv* jvmti, JNIEnv* jni) { NSK_DISPLAY3(" thread #%d (%s): %p\n", i, info.name, threads[i]); - threadList[i].thread = threads[i]; - threadList[i].dfn = -1; - threadList[i].name = info.name; + if (!strncmp(info.name, THREAD_NAME_PREFIX, THREAD_NAME_PREFIX_LEN)) { + NSK_DISPLAY1("Skipping thread %s\n", info.name); + if (!NSK_JVMTI_VERIFY(jvmti->Deallocate((unsigned char*)info.name))) + return NSK_FALSE; + continue; + } + + debuggee_threads[debuggee_threads_cnt].thread = threads[i]; + debuggee_threads[debuggee_threads_cnt].dfn = -1; + debuggee_threads[debuggee_threads_cnt].name = info.name; + debuggee_threads_cnt++; } /* deallocate thread list */ if (!NSK_JVMTI_VERIFY(jvmti->Deallocate((unsigned char*)threads))) return NSK_FALSE; - for (i = 0; i < threads_count; i++) { - if (threadList[i].dfn < 0) { + for (i = 0; i < debuggee_threads_cnt; i++) { + + if (debuggee_threads[i].dfn < 0) { tDfn = gDfn; - threadList[i].dfn = gDfn++; + debuggee_threads[i].dfn = gDfn++; for (pThread = i;;pThread = cThread) { if (!NSK_JVMTI_VERIFY( - jvmti->GetCurrentContendedMonitor(threadList[pThread].thread, &monitor))) + jvmti->GetCurrentContendedMonitor(debuggee_threads[pThread].thread, &monitor))) return NSK_FALSE; if (monitor == nullptr) break; @@ -150,8 +163,8 @@ static int findDeadlockThreads(jvmtiEnv* jvmti, JNIEnv* jni) { return NSK_FALSE; if (usageInfo.owner == nullptr) break; - for (cThread = 0; cThread < threads_count; cThread++) { - if (jni->IsSameObject(threadList[cThread].thread, usageInfo.owner)) + for (cThread = 0; cThread < debuggee_threads_cnt; cThread++) { + if (jni->IsSameObject(debuggee_threads[cThread].thread, usageInfo.owner)) break; } if (usageInfo.waiters != nullptr) { @@ -160,10 +173,10 @@ static int findDeadlockThreads(jvmtiEnv* jvmti, JNIEnv* jni) { if (usageInfo.notify_waiters != nullptr) { jvmti->Deallocate((unsigned char*)usageInfo.notify_waiters); } - if (!NSK_VERIFY(cThread != threads_count)) + if (!NSK_VERIFY(cThread != debuggee_threads_cnt)) return NSK_FALSE; - if (threadList[cThread].dfn < 0) { - threadList[cThread].dfn = gDfn++; + if (debuggee_threads[cThread].dfn < 0) { + debuggee_threads[cThread].dfn = gDfn++; } else if (cThread == pThread) { break; } else { @@ -179,9 +192,9 @@ static int findDeadlockThreads(jvmtiEnv* jvmti, JNIEnv* jni) { } /* deallocate thread names */ - for (i = 0; i < threads_count; i++) { - if (threadList[i].name != nullptr) { - if (!NSK_JVMTI_VERIFY(jvmti->Deallocate((unsigned char*)threadList[i].name))) + for (i = 0; i < debuggee_threads_cnt; i++) { + if (debuggee_threads[i].name != nullptr) { + if (!NSK_JVMTI_VERIFY(jvmti->Deallocate((unsigned char*)debuggee_threads[i].name))) return NSK_FALSE; } } From 4975b2d8c5a9dcf70a286003db752b686eeb4ed3 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Fri, 27 Mar 2026 15:01:23 +0000 Subject: [PATCH 088/168] 8370489: Some compiler tests miss the @key randomness Reviewed-by: phh Backport-of: 4b774cb46d9355015a6bfcf53b47233d6f235239 --- .../jtreg/compiler/c2/TestMergeStores.java | 3 ++ .../c2/TestMergeStoresMemorySegment.java | 12 ++++++- .../jtreg/compiler/c2/TestMinMaxSubword.java | 1 + .../compiler/c2/irTests/ModDNodeTests.java | 1 + .../compiler/c2/irTests/ModFNodeTests.java | 1 + .../c2/irTests/ModINodeIdealizationTests.java | 1 + .../c2/irTests/ModLNodeIdealizationTests.java | 1 + .../c2/irTests/TestMulNodeIdealization.java | 3 +- .../compiler/c2/irTests/TestShiftAndMask.java | 1 + .../irTests/UDivINodeIdealizationTests.java | 3 +- .../irTests/UDivLNodeIdealizationTests.java | 3 +- .../irTests/UModINodeIdealizationTests.java | 3 +- .../irTests/UModLNodeIdealizationTests.java | 3 +- .../TestDivDependentOnMainLoopGuard.java | 1 + .../jtreg/compiler/igvn/ExpressionFuzzer.java | 1 + .../TestFloat16MaxMinSpecialValues.java | 1 + .../InvariantCodeMotionReassociateAddSub.java | 1 + .../InvariantCodeMotionReassociateCmp.java | 1 + .../TestParallelIvInIntCountedLoop.java | 1 + .../loopopts/superword/MinMaxRed_Int.java | 1 + .../loopopts/superword/ReductionPerf.java | 3 +- .../loopopts/superword/TestAlignVector.java | 1 + .../TestCompatibleUseDefTypeSize.java | 3 +- .../superword/TestDependencyOffsets.java | 33 +++++++++++++++++++ .../superword/TestEquivalentInvariants.java | 1 + .../loopopts/superword/TestMemorySegment.java | 1 + .../TestMemorySegmentUnalignedAddress.java | 1 + .../loopopts/superword/TestSplitPacks.java | 1 + .../jtreg/compiler/vectorapi/Test8278948.java | 3 +- .../vectorapi/TestVectorAddMulReduction.java | 1 + .../TestVectorCompressExpandBits.java | 1 + .../compiler/vectorapi/VectorMultiplyOpt.java | 3 +- .../VectorSaturatedOperationsTest.java | 1 + .../vectorization/TestAutoVecIntMinMax.java | 1 + .../vectorization/TestEor3AArch64.java | 1 + .../vectorization/TestMacroLogicVector.java | 3 +- .../vectorization/TestVectorZeroCount.java | 12 +++---- 37 files changed, 96 insertions(+), 17 deletions(-) diff --git a/test/hotspot/jtreg/compiler/c2/TestMergeStores.java b/test/hotspot/jtreg/compiler/c2/TestMergeStores.java index 22920eda8282..f67370f78fe6 100644 --- a/test/hotspot/jtreg/compiler/c2/TestMergeStores.java +++ b/test/hotspot/jtreg/compiler/c2/TestMergeStores.java @@ -34,6 +34,7 @@ /* * @test * @bug 8318446 8331054 8331311 8335392 8347405 + * @key randomness * @summary Test merging of consecutive stores * @modules java.base/jdk.internal.misc * @library /test/lib / @@ -43,6 +44,7 @@ /* * @test * @bug 8318446 8331054 8331311 8335392 8347405 + * @key randomness * @summary Test merging of consecutive stores * @modules java.base/jdk.internal.misc * @library /test/lib / @@ -52,6 +54,7 @@ /* * @test * @bug 8318446 8331054 8331311 8335392 8348959 8351414 + * @key randomness * @summary Test merging of consecutive stores * @modules java.base/jdk.internal.misc * @library /test/lib / diff --git a/test/hotspot/jtreg/compiler/c2/TestMergeStoresMemorySegment.java b/test/hotspot/jtreg/compiler/c2/TestMergeStoresMemorySegment.java index a5302d1b5158..a275570747b4 100644 --- a/test/hotspot/jtreg/compiler/c2/TestMergeStoresMemorySegment.java +++ b/test/hotspot/jtreg/compiler/c2/TestMergeStoresMemorySegment.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -34,6 +34,7 @@ /* * @test id=byte-array * @bug 8335392 + * @key randomness * @summary Test MergeStores optimization for MemorySegment * @library /test/lib / * @run driver compiler.c2.TestMergeStoresMemorySegment ByteArray @@ -42,6 +43,7 @@ /* * @test id=char-array * @bug 8335392 + * @key randomness * @summary Test MergeStores optimization for MemorySegment * @library /test/lib / * @run driver compiler.c2.TestMergeStoresMemorySegment CharArray @@ -50,6 +52,7 @@ /* * @test id=short-array * @bug 8335392 + * @key randomness * @summary Test MergeStores optimization for MemorySegment * @library /test/lib / * @run driver compiler.c2.TestMergeStoresMemorySegment ShortArray @@ -58,6 +61,7 @@ /* * @test id=int-array * @bug 8335392 + * @key randomness * @summary Test MergeStores optimization for MemorySegment * @library /test/lib / * @run driver compiler.c2.TestMergeStoresMemorySegment IntArray @@ -66,6 +70,7 @@ /* * @test id=long-array * @bug 8335392 + * @key randomness * @summary Test MergeStores optimization for MemorySegment * @library /test/lib / * @run driver compiler.c2.TestMergeStoresMemorySegment LongArray @@ -74,6 +79,7 @@ /* * @test id=float-array * @bug 8335392 + * @key randomness * @summary Test MergeStores optimization for MemorySegment * @library /test/lib / * @run driver compiler.c2.TestMergeStoresMemorySegment FloatArray @@ -82,6 +88,7 @@ /* * @test id=double-array * @bug 8335392 + * @key randomness * @summary Test MergeStores optimization for MemorySegment * @library /test/lib / * @run driver compiler.c2.TestMergeStoresMemorySegment DoubleArray @@ -90,6 +97,7 @@ /* * @test id=byte-buffer * @bug 8335392 + * @key randomness * @summary Test MergeStores optimization for MemorySegment * @library /test/lib / * @run driver compiler.c2.TestMergeStoresMemorySegment ByteBuffer @@ -98,6 +106,7 @@ /* * @test id=byte-buffer-direct * @bug 8335392 + * @key randomness * @summary Test MergeStores optimization for MemorySegment * @library /test/lib / * @run driver compiler.c2.TestMergeStoresMemorySegment ByteBufferDirect @@ -106,6 +115,7 @@ /* * @test id=native * @bug 8335392 + * @key randomness * @summary Test MergeStores optimization for MemorySegment * @library /test/lib / * @run driver compiler.c2.TestMergeStoresMemorySegment Native diff --git a/test/hotspot/jtreg/compiler/c2/TestMinMaxSubword.java b/test/hotspot/jtreg/compiler/c2/TestMinMaxSubword.java index 955aa4058f04..a7e90353f905 100644 --- a/test/hotspot/jtreg/compiler/c2/TestMinMaxSubword.java +++ b/test/hotspot/jtreg/compiler/c2/TestMinMaxSubword.java @@ -31,6 +31,7 @@ /* * @test * @bug 8294816 + * @key randomness * @summary Test Math.min/max vectorization miscompilation for integer subwords * @library /test/lib / * @requires vm.compiler2.enabled diff --git a/test/hotspot/jtreg/compiler/c2/irTests/ModDNodeTests.java b/test/hotspot/jtreg/compiler/c2/irTests/ModDNodeTests.java index 3c28c936d310..7eed798871e8 100644 --- a/test/hotspot/jtreg/compiler/c2/irTests/ModDNodeTests.java +++ b/test/hotspot/jtreg/compiler/c2/irTests/ModDNodeTests.java @@ -30,6 +30,7 @@ /* * @test * @bug 8345766 + * @key randomness * @summary Test that Ideal transformations of ModDNode are being performed as expected. * @library /test/lib / * @run driver compiler.c2.irTests.ModDNodeTests diff --git a/test/hotspot/jtreg/compiler/c2/irTests/ModFNodeTests.java b/test/hotspot/jtreg/compiler/c2/irTests/ModFNodeTests.java index 1b5e14b48357..886efe571241 100644 --- a/test/hotspot/jtreg/compiler/c2/irTests/ModFNodeTests.java +++ b/test/hotspot/jtreg/compiler/c2/irTests/ModFNodeTests.java @@ -30,6 +30,7 @@ /* * @test * @bug 8345766 + * @key randomness * @summary Test that Ideal transformations of ModFNode are being performed as expected. * @library /test/lib / * @run driver compiler.c2.irTests.ModFNodeTests diff --git a/test/hotspot/jtreg/compiler/c2/irTests/ModINodeIdealizationTests.java b/test/hotspot/jtreg/compiler/c2/irTests/ModINodeIdealizationTests.java index d19e25627794..1ba715840315 100644 --- a/test/hotspot/jtreg/compiler/c2/irTests/ModINodeIdealizationTests.java +++ b/test/hotspot/jtreg/compiler/c2/irTests/ModINodeIdealizationTests.java @@ -32,6 +32,7 @@ /* * @test * @bug 8332268 + * @key randomness * @summary Test that Ideal transformations of ModINode* are being performed as expected. * @library /test/lib / * @run driver compiler.c2.irTests.ModINodeIdealizationTests diff --git a/test/hotspot/jtreg/compiler/c2/irTests/ModLNodeIdealizationTests.java b/test/hotspot/jtreg/compiler/c2/irTests/ModLNodeIdealizationTests.java index fc08ef60603e..719cc5962152 100644 --- a/test/hotspot/jtreg/compiler/c2/irTests/ModLNodeIdealizationTests.java +++ b/test/hotspot/jtreg/compiler/c2/irTests/ModLNodeIdealizationTests.java @@ -32,6 +32,7 @@ /* * @test * @bug 8267265 + * @key randomness * @summary Test that Ideal transformations of ModLNode* are being performed as expected. * @library /test/lib / * @run driver compiler.c2.irTests.ModLNodeIdealizationTests diff --git a/test/hotspot/jtreg/compiler/c2/irTests/TestMulNodeIdealization.java b/test/hotspot/jtreg/compiler/c2/irTests/TestMulNodeIdealization.java index ca6d5fbe1185..e0307f2eb981 100644 --- a/test/hotspot/jtreg/compiler/c2/irTests/TestMulNodeIdealization.java +++ b/test/hotspot/jtreg/compiler/c2/irTests/TestMulNodeIdealization.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -31,6 +31,7 @@ /* * @test * @bug 8291336 + * @key randomness * @summary Test that transformation of multiply-by-2 is appropriately turned into additions. * @library /test/lib / * @requires vm.compiler2.enabled diff --git a/test/hotspot/jtreg/compiler/c2/irTests/TestShiftAndMask.java b/test/hotspot/jtreg/compiler/c2/irTests/TestShiftAndMask.java index 2cdc6414685b..1413ee0cafa5 100644 --- a/test/hotspot/jtreg/compiler/c2/irTests/TestShiftAndMask.java +++ b/test/hotspot/jtreg/compiler/c2/irTests/TestShiftAndMask.java @@ -31,6 +31,7 @@ /* * @test * @bug 8277850 8278949 8285793 8346664 + * @key randomness * @summary C2: optimize mask checks in counted loops * @library /test/lib / * @run driver compiler.c2.irTests.TestShiftAndMask diff --git a/test/hotspot/jtreg/compiler/c2/irTests/UDivINodeIdealizationTests.java b/test/hotspot/jtreg/compiler/c2/irTests/UDivINodeIdealizationTests.java index 7183e0b311ac..dd135c93e0d7 100644 --- a/test/hotspot/jtreg/compiler/c2/irTests/UDivINodeIdealizationTests.java +++ b/test/hotspot/jtreg/compiler/c2/irTests/UDivINodeIdealizationTests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -32,6 +32,7 @@ /* * @test * @bug 8332268 + * @key randomness * @summary Test that Ideal transformations of UDivINode* are being performed as expected. * @library /test/lib / * @run driver compiler.c2.irTests.UDivINodeIdealizationTests diff --git a/test/hotspot/jtreg/compiler/c2/irTests/UDivLNodeIdealizationTests.java b/test/hotspot/jtreg/compiler/c2/irTests/UDivLNodeIdealizationTests.java index a5cfc2cddc5f..bc80805cabd1 100644 --- a/test/hotspot/jtreg/compiler/c2/irTests/UDivLNodeIdealizationTests.java +++ b/test/hotspot/jtreg/compiler/c2/irTests/UDivLNodeIdealizationTests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -32,6 +32,7 @@ /* * @test * @bug 8332268 + * @key randomness * @summary Test that Ideal transformations of UDivLNode* are being performed as expected. * @library /test/lib / * @run driver compiler.c2.irTests.UDivLNodeIdealizationTests diff --git a/test/hotspot/jtreg/compiler/c2/irTests/UModINodeIdealizationTests.java b/test/hotspot/jtreg/compiler/c2/irTests/UModINodeIdealizationTests.java index fa24033ab46b..99982c70b6e4 100644 --- a/test/hotspot/jtreg/compiler/c2/irTests/UModINodeIdealizationTests.java +++ b/test/hotspot/jtreg/compiler/c2/irTests/UModINodeIdealizationTests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -32,6 +32,7 @@ /* * @test * @bug 8332268 + * @key randomness * @summary Test that Ideal transformations of UModINode* are being performed as expected. * @library /test/lib / * @run driver compiler.c2.irTests.UModINodeIdealizationTests diff --git a/test/hotspot/jtreg/compiler/c2/irTests/UModLNodeIdealizationTests.java b/test/hotspot/jtreg/compiler/c2/irTests/UModLNodeIdealizationTests.java index 93ae861cfaac..9bbded327e0d 100644 --- a/test/hotspot/jtreg/compiler/c2/irTests/UModLNodeIdealizationTests.java +++ b/test/hotspot/jtreg/compiler/c2/irTests/UModLNodeIdealizationTests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -32,6 +32,7 @@ /* * @test * @bug 8332268 + * @key randomness * @summary Test that Ideal transformations of UModLNode* are being performed as expected. * @library /test/lib / * @run driver compiler.c2.irTests.UModLNodeIdealizationTests diff --git a/test/hotspot/jtreg/compiler/controldependency/TestDivDependentOnMainLoopGuard.java b/test/hotspot/jtreg/compiler/controldependency/TestDivDependentOnMainLoopGuard.java index a6b7a4627430..27fdc6b9e346 100644 --- a/test/hotspot/jtreg/compiler/controldependency/TestDivDependentOnMainLoopGuard.java +++ b/test/hotspot/jtreg/compiler/controldependency/TestDivDependentOnMainLoopGuard.java @@ -24,6 +24,7 @@ /* * @test * @bug 8349139 + * @key randomness * @summary C2: Div looses dependency on condition that guarantees divisor not null in counted loop * @library /test/lib / * @run main/othervm -Xcomp -XX:-TieredCompilation -XX:CompileOnly=TestDivDependentOnMainLoopGuard::* diff --git a/test/hotspot/jtreg/compiler/igvn/ExpressionFuzzer.java b/test/hotspot/jtreg/compiler/igvn/ExpressionFuzzer.java index 40bfb2e43194..570c59f0da2a 100644 --- a/test/hotspot/jtreg/compiler/igvn/ExpressionFuzzer.java +++ b/test/hotspot/jtreg/compiler/igvn/ExpressionFuzzer.java @@ -24,6 +24,7 @@ /* * @test * @bug 8359412 + * @key randomness * @summary Use the template framework library to generate random expressions. * @modules java.base/jdk.internal.misc * @library /test/lib / diff --git a/test/hotspot/jtreg/compiler/intrinsics/float16/TestFloat16MaxMinSpecialValues.java b/test/hotspot/jtreg/compiler/intrinsics/float16/TestFloat16MaxMinSpecialValues.java index f83ca307d84e..f85297859f3d 100644 --- a/test/hotspot/jtreg/compiler/intrinsics/float16/TestFloat16MaxMinSpecialValues.java +++ b/test/hotspot/jtreg/compiler/intrinsics/float16/TestFloat16MaxMinSpecialValues.java @@ -30,6 +30,7 @@ /** * @test * @bug 8352585 + * @key randomness * @library /test/lib / * @summary Add special case handling for Float16.max/min x86 backend * @modules jdk.incubator.vector diff --git a/test/hotspot/jtreg/compiler/loopopts/InvariantCodeMotionReassociateAddSub.java b/test/hotspot/jtreg/compiler/loopopts/InvariantCodeMotionReassociateAddSub.java index 4e8daf3d163c..3b26972ec321 100644 --- a/test/hotspot/jtreg/compiler/loopopts/InvariantCodeMotionReassociateAddSub.java +++ b/test/hotspot/jtreg/compiler/loopopts/InvariantCodeMotionReassociateAddSub.java @@ -31,6 +31,7 @@ /* * @test * @bug 8323220 + * @key randomness * @summary Test loop invariant code motion of add/sub through reassociation * @library /test/lib / * @run driver compiler.c2.loopopts.InvariantCodeMotionReassociateAddSub diff --git a/test/hotspot/jtreg/compiler/loopopts/InvariantCodeMotionReassociateCmp.java b/test/hotspot/jtreg/compiler/loopopts/InvariantCodeMotionReassociateCmp.java index 566e294b4bff..bf139befc986 100644 --- a/test/hotspot/jtreg/compiler/loopopts/InvariantCodeMotionReassociateCmp.java +++ b/test/hotspot/jtreg/compiler/loopopts/InvariantCodeMotionReassociateCmp.java @@ -31,6 +31,7 @@ /* * @test * @bug 8323220 + * @key randomness * @summary Test loop invariant code motion for cmp nodes through reassociation * @library /test/lib / * @run driver compiler.c2.loopopts.InvariantCodeMotionReassociateCmp diff --git a/test/hotspot/jtreg/compiler/loopopts/parallel_iv/TestParallelIvInIntCountedLoop.java b/test/hotspot/jtreg/compiler/loopopts/parallel_iv/TestParallelIvInIntCountedLoop.java index 95ba9e6e795a..f8abb716e425 100644 --- a/test/hotspot/jtreg/compiler/loopopts/parallel_iv/TestParallelIvInIntCountedLoop.java +++ b/test/hotspot/jtreg/compiler/loopopts/parallel_iv/TestParallelIvInIntCountedLoop.java @@ -32,6 +32,7 @@ /** * @test * @bug 8328528 + * @key randomness * @summary test the long typed parallel iv replacing transformation for int counted loop * @library /test/lib / * @requires vm.compiler2.enabled diff --git a/test/hotspot/jtreg/compiler/loopopts/superword/MinMaxRed_Int.java b/test/hotspot/jtreg/compiler/loopopts/superword/MinMaxRed_Int.java index 4e15c0de6e90..08f5e200aa00 100644 --- a/test/hotspot/jtreg/compiler/loopopts/superword/MinMaxRed_Int.java +++ b/test/hotspot/jtreg/compiler/loopopts/superword/MinMaxRed_Int.java @@ -24,6 +24,7 @@ /** * @test * @bug 8302673 + * @key randomness * @summary [SuperWord] MaxReduction and MinReduction should vectorize for int * @library /test/lib / * @run driver compiler.loopopts.superword.MinMaxRed_Int diff --git a/test/hotspot/jtreg/compiler/loopopts/superword/ReductionPerf.java b/test/hotspot/jtreg/compiler/loopopts/superword/ReductionPerf.java index 56acdd349f81..10181ab3da29 100644 --- a/test/hotspot/jtreg/compiler/loopopts/superword/ReductionPerf.java +++ b/test/hotspot/jtreg/compiler/loopopts/superword/ReductionPerf.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,6 +24,7 @@ /* * @test * @bug 8074981 8302652 + * @key randomness * @summary Test SuperWord Reduction Perf. * @library /test/lib / * @run main/othervm -Xbatch diff --git a/test/hotspot/jtreg/compiler/loopopts/superword/TestAlignVector.java b/test/hotspot/jtreg/compiler/loopopts/superword/TestAlignVector.java index 322c36c39e15..5a2d11ef2e1b 100644 --- a/test/hotspot/jtreg/compiler/loopopts/superword/TestAlignVector.java +++ b/test/hotspot/jtreg/compiler/loopopts/superword/TestAlignVector.java @@ -36,6 +36,7 @@ /* * @test id=NoAlignVector * @bug 8310190 + * @key randomness * @summary Test AlignVector with various loop init, stride, scale, invar, etc. * @modules java.base/jdk.internal.misc * @library /test/lib / diff --git a/test/hotspot/jtreg/compiler/loopopts/superword/TestCompatibleUseDefTypeSize.java b/test/hotspot/jtreg/compiler/loopopts/superword/TestCompatibleUseDefTypeSize.java index f5445f3106b5..53e17cdafb53 100644 --- a/test/hotspot/jtreg/compiler/loopopts/superword/TestCompatibleUseDefTypeSize.java +++ b/test/hotspot/jtreg/compiler/loopopts/superword/TestCompatibleUseDefTypeSize.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -35,6 +35,7 @@ /* * @test * @bug 8325155 + * @key randomness * @summary Test some cases that vectorize after the removal of the alignment boundaries code. * Now, we instead check if use-def connections have compatible type size. * @library /test/lib / diff --git a/test/hotspot/jtreg/compiler/loopopts/superword/TestDependencyOffsets.java b/test/hotspot/jtreg/compiler/loopopts/superword/TestDependencyOffsets.java index cfa19ce385a8..ab1f1757c70a 100644 --- a/test/hotspot/jtreg/compiler/loopopts/superword/TestDependencyOffsets.java +++ b/test/hotspot/jtreg/compiler/loopopts/superword/TestDependencyOffsets.java @@ -24,6 +24,7 @@ /* * @test id=vanilla-A * @bug 8298935 8308606 8310308 8312570 8310190 + * @key randomness * @summary Test SuperWord: vector size, offsets, dependencies, alignment. * @library /test/lib / * @compile ../../lib/ir_framework/TestFramework.java @@ -33,6 +34,7 @@ /* * @test id=vanilla-U * @bug 8298935 8308606 8310308 8312570 8310190 + * @key randomness * @summary Test SuperWord: vector size, offsets, dependencies, alignment. * @library /test/lib / * @compile ../../lib/ir_framework/TestFramework.java @@ -42,6 +44,7 @@ /* * @test id=sse4-v016-A * @bug 8298935 8308606 8310308 8312570 8310190 + * @key randomness * @summary Test SuperWord: vector size, offsets, dependencies, alignment. * @requires vm.compiler2.enabled * @requires (os.arch=="x86" | os.arch=="i386" | os.arch=="amd64" | os.arch=="x86_64") @@ -54,6 +57,7 @@ /* * @test id=sse4-v016-U * @bug 8298935 8308606 8310308 8312570 8310190 + * @key randomness * @summary Test SuperWord: vector size, offsets, dependencies, alignment. * @requires vm.compiler2.enabled * @requires (os.arch=="x86" | os.arch=="i386" | os.arch=="amd64" | os.arch=="x86_64") @@ -66,6 +70,7 @@ /* * @test id=sse4-v008-A * @bug 8298935 8308606 8310308 8312570 8310190 + * @key randomness * @summary Test SuperWord: vector size, offsets, dependencies, alignment. * @requires vm.compiler2.enabled * @requires (os.arch=="x86" | os.arch=="i386" | os.arch=="amd64" | os.arch=="x86_64") @@ -78,6 +83,7 @@ /* * @test id=sse4-v008-U * @bug 8298935 8308606 8310308 8312570 8310190 + * @key randomness * @summary Test SuperWord: vector size, offsets, dependencies, alignment. * @requires vm.compiler2.enabled * @requires (os.arch=="x86" | os.arch=="i386" | os.arch=="amd64" | os.arch=="x86_64") @@ -102,6 +108,7 @@ /* * @test id=sse4-v004-U * @bug 8298935 8308606 8310308 8312570 8310190 + * @key randomness * @summary Test SuperWord: vector size, offsets, dependencies, alignment. * @requires vm.compiler2.enabled * @requires (os.arch=="x86" | os.arch=="i386" | os.arch=="amd64" | os.arch=="x86_64") @@ -114,6 +121,7 @@ /* * @test id=avx1-v032-A * @bug 8298935 8308606 8310308 8312570 8310190 + * @key randomness * @summary Test SuperWord: vector size, offsets, dependencies, alignment. * @requires vm.compiler2.enabled * @requires (os.arch=="x86" | os.arch=="i386" | os.arch=="amd64" | os.arch=="x86_64") @@ -126,6 +134,7 @@ /* * @test id=avx1-v032-U * @bug 8298935 8308606 8310308 8312570 8310190 + * @key randomness * @summary Test SuperWord: vector size, offsets, dependencies, alignment. * @requires vm.compiler2.enabled * @requires (os.arch=="x86" | os.arch=="i386" | os.arch=="amd64" | os.arch=="x86_64") @@ -138,6 +147,7 @@ /* * @test id=avx1-v016-A * @bug 8298935 8308606 8310308 8312570 8310190 + * @key randomness * @summary Test SuperWord: vector size, offsets, dependencies, alignment. * @requires vm.compiler2.enabled * @requires (os.arch=="x86" | os.arch=="i386" | os.arch=="amd64" | os.arch=="x86_64") @@ -150,6 +160,7 @@ /* * @test id=avx1-v016-U * @bug 8298935 8308606 8310308 8312570 8310190 + * @key randomness * @summary Test SuperWord: vector size, offsets, dependencies, alignment. * @requires vm.compiler2.enabled * @requires (os.arch=="x86" | os.arch=="i386" | os.arch=="amd64" | os.arch=="x86_64") @@ -162,6 +173,7 @@ /* * @test id=avx2-v032-A * @bug 8298935 8308606 8310308 8312570 8310190 + * @key randomness * @summary Test SuperWord: vector size, offsets, dependencies, alignment. * @requires vm.compiler2.enabled * @requires (os.arch=="x86" | os.arch=="i386" | os.arch=="amd64" | os.arch=="x86_64") @@ -174,6 +186,7 @@ /* * @test id=avx2-v032-U * @bug 8298935 8308606 8310308 8312570 8310190 + * @key randomness * @summary Test SuperWord: vector size, offsets, dependencies, alignment. * @requires vm.compiler2.enabled * @requires (os.arch=="x86" | os.arch=="i386" | os.arch=="amd64" | os.arch=="x86_64") @@ -186,6 +199,7 @@ /* * @test id=avx2-v016-A * @bug 8298935 8308606 8310308 8312570 8310190 + * @key randomness * @summary Test SuperWord: vector size, offsets, dependencies, alignment. * @requires vm.compiler2.enabled * @requires (os.arch=="x86" | os.arch=="i386" | os.arch=="amd64" | os.arch=="x86_64") @@ -198,6 +212,7 @@ /* * @test id=avx2-v016-U * @bug 8298935 8308606 8310308 8312570 8310190 + * @key randomness * @summary Test SuperWord: vector size, offsets, dependencies, alignment. * @requires vm.compiler2.enabled * @requires (os.arch=="x86" | os.arch=="i386" | os.arch=="amd64" | os.arch=="x86_64") @@ -210,6 +225,7 @@ /* * @test id=avx512-v064-A * @bug 8298935 8308606 8310308 8312570 8310190 + * @key randomness * @summary Test SuperWord: vector size, offsets, dependencies, alignment. * @requires vm.compiler2.enabled * @requires (os.arch=="x86" | os.arch=="i386" | os.arch=="amd64" | os.arch=="x86_64") @@ -222,6 +238,7 @@ /* * @test id=avx512-v064-U * @bug 8298935 8308606 8310308 8312570 8310190 + * @key randomness * @summary Test SuperWord: vector size, offsets, dependencies, alignment. * @requires vm.compiler2.enabled * @requires (os.arch=="x86" | os.arch=="i386" | os.arch=="amd64" | os.arch=="x86_64") @@ -234,6 +251,7 @@ /* * @test id=avx512-v032-A * @bug 8298935 8308606 8310308 8312570 8310190 + * @key randomness * @summary Test SuperWord: vector size, offsets, dependencies, alignment. * @requires vm.compiler2.enabled * @requires (os.arch=="x86" | os.arch=="i386" | os.arch=="amd64" | os.arch=="x86_64") @@ -246,6 +264,7 @@ /* * @test id=avx512-v032-U * @bug 8298935 8308606 8310308 8312570 8310190 + * @key randomness * @summary Test SuperWord: vector size, offsets, dependencies, alignment. * @requires vm.compiler2.enabled * @requires (os.arch=="x86" | os.arch=="i386" | os.arch=="amd64" | os.arch=="x86_64") @@ -258,6 +277,7 @@ /* * @test id=avx512bw-v064-A * @bug 8298935 8308606 8310308 8312570 8310190 + * @key randomness * @summary Test SuperWord: vector size, offsets, dependencies, alignment. * @requires vm.compiler2.enabled * @requires (os.arch=="x86" | os.arch=="i386" | os.arch=="amd64" | os.arch=="x86_64") @@ -270,6 +290,7 @@ /* * @test id=avx512bw-v064-U * @bug 8298935 8308606 8310308 8312570 8310190 + * @key randomness * @summary Test SuperWord: vector size, offsets, dependencies, alignment. * @requires vm.compiler2.enabled * @requires (os.arch=="x86" | os.arch=="i386" | os.arch=="amd64" | os.arch=="x86_64") @@ -282,6 +303,7 @@ /* * @test id=avx512bw-v032-A * @bug 8298935 8308606 8310308 8312570 8310190 + * @key randomness * @summary Test SuperWord: vector size, offsets, dependencies, alignment. * @requires vm.compiler2.enabled * @requires (os.arch=="x86" | os.arch=="i386" | os.arch=="amd64" | os.arch=="x86_64") @@ -294,6 +316,7 @@ /* * @test id=avx512bw-v032-U * @bug 8298935 8308606 8310308 8312570 8310190 + * @key randomness * @summary Test SuperWord: vector size, offsets, dependencies, alignment. * @requires vm.compiler2.enabled * @requires (os.arch=="x86" | os.arch=="i386" | os.arch=="amd64" | os.arch=="x86_64") @@ -306,6 +329,7 @@ /* * @test id=vec-v064-A * @bug 8298935 8308606 8310308 8312570 8310190 + * @key randomness * @summary Test SuperWord: vector size, offsets, dependencies, alignment. * @requires vm.compiler2.enabled * @requires (os.arch!="x86" & os.arch!="i386" & os.arch!="amd64" & os.arch!="x86_64") @@ -317,6 +341,7 @@ /* * @test id=vec-v064-U * @bug 8298935 8308606 8310308 8312570 8310190 + * @key randomness * @summary Test SuperWord: vector size, offsets, dependencies, alignment. * @requires vm.compiler2.enabled * @requires (os.arch!="x86" & os.arch!="i386" & os.arch!="amd64" & os.arch!="x86_64") @@ -328,6 +353,7 @@ /* * @test id=vec-v032-A * @bug 8298935 8308606 8310308 8312570 8310190 + * @key randomness * @summary Test SuperWord: vector size, offsets, dependencies, alignment. * @requires vm.compiler2.enabled * @requires (os.arch!="x86" & os.arch!="i386" & os.arch!="amd64" & os.arch!="x86_64") @@ -339,6 +365,7 @@ /* * @test id=vec-v032-U * @bug 8298935 8308606 8310308 8312570 8310190 + * @key randomness * @summary Test SuperWord: vector size, offsets, dependencies, alignment. * @requires vm.compiler2.enabled * @requires (os.arch!="x86" & os.arch!="i386" & os.arch!="amd64" & os.arch!="x86_64") @@ -350,6 +377,7 @@ /* * @test id=vec-v016-A * @bug 8298935 8308606 8310308 8312570 8310190 + * @key randomness * @summary Test SuperWord: vector size, offsets, dependencies, alignment. * @requires vm.compiler2.enabled * @requires (os.arch!="x86" & os.arch!="i386" & os.arch!="amd64" & os.arch!="x86_64") @@ -361,6 +389,7 @@ /* * @test id=vec-v016-U * @bug 8298935 8308606 8310308 8312570 8310190 + * @key randomness * @summary Test SuperWord: vector size, offsets, dependencies, alignment. * @requires vm.compiler2.enabled * @requires (os.arch!="x86" & os.arch!="i386" & os.arch!="amd64" & os.arch!="x86_64") @@ -372,6 +401,7 @@ /* * @test id=vec-v008-A * @bug 8298935 8308606 8310308 8312570 8310190 + * @key randomness * @summary Test SuperWord: vector size, offsets, dependencies, alignment. * @requires vm.compiler2.enabled * @requires (os.arch!="x86" & os.arch!="i386" & os.arch!="amd64" & os.arch!="x86_64") @@ -383,6 +413,7 @@ /* * @test id=vec-v008-U * @bug 8298935 8308606 8310308 8312570 8310190 + * @key randomness * @summary Test SuperWord: vector size, offsets, dependencies, alignment. * @requires vm.compiler2.enabled * @requires (os.arch!="x86" & os.arch!="i386" & os.arch!="amd64" & os.arch!="x86_64") @@ -394,6 +425,7 @@ /* * @test id=vec-v004-A * @bug 8298935 8308606 8310308 8312570 8310190 + * @key randomness * @summary Test SuperWord: vector size, offsets, dependencies, alignment. * @requires vm.compiler2.enabled * @requires (os.arch!="x86" & os.arch!="i386" & os.arch!="amd64" & os.arch!="x86_64") @@ -405,6 +437,7 @@ /* * @test id=vec-v004-U * @bug 8298935 8308606 8310308 8312570 8310190 + * @key randomness * @summary Test SuperWord: vector size, offsets, dependencies, alignment. * @requires vm.compiler2.enabled * @requires (os.arch!="x86" & os.arch!="i386" & os.arch!="amd64" & os.arch!="x86_64") diff --git a/test/hotspot/jtreg/compiler/loopopts/superword/TestEquivalentInvariants.java b/test/hotspot/jtreg/compiler/loopopts/superword/TestEquivalentInvariants.java index 7cec26d65322..94f1848673ed 100644 --- a/test/hotspot/jtreg/compiler/loopopts/superword/TestEquivalentInvariants.java +++ b/test/hotspot/jtreg/compiler/loopopts/superword/TestEquivalentInvariants.java @@ -35,6 +35,7 @@ /* * @test * @bug 8343685 8331659 + * @key randomness * @summary Test vectorization with various invariants that are equivalent, but not trivially so, * i.e. where the invariants have the same summands, but in a different order. * @modules java.base/jdk.internal.misc diff --git a/test/hotspot/jtreg/compiler/loopopts/superword/TestMemorySegment.java b/test/hotspot/jtreg/compiler/loopopts/superword/TestMemorySegment.java index b4003cc5e73c..07ca52f77af0 100644 --- a/test/hotspot/jtreg/compiler/loopopts/superword/TestMemorySegment.java +++ b/test/hotspot/jtreg/compiler/loopopts/superword/TestMemorySegment.java @@ -34,6 +34,7 @@ /* * @test id=byte-array * @bug 8329273 + * @key randomness * @summary Test vectorization of loops over MemorySegment * @library /test/lib / * @run driver compiler.loopopts.superword.TestMemorySegment ByteArray diff --git a/test/hotspot/jtreg/compiler/loopopts/superword/TestMemorySegmentUnalignedAddress.java b/test/hotspot/jtreg/compiler/loopopts/superword/TestMemorySegmentUnalignedAddress.java index 969014e2293f..7354ba896d00 100644 --- a/test/hotspot/jtreg/compiler/loopopts/superword/TestMemorySegmentUnalignedAddress.java +++ b/test/hotspot/jtreg/compiler/loopopts/superword/TestMemorySegmentUnalignedAddress.java @@ -35,6 +35,7 @@ /* * @test id=byte-buffer-direct * @bug 8323582 + * @key randomness * @summary Test vectorization of loops over MemorySegment, with native memory where the address is not always aligned. * @library /test/lib / * @run driver compiler.loopopts.superword.TestMemorySegmentUnalignedAddress ByteBufferDirect diff --git a/test/hotspot/jtreg/compiler/loopopts/superword/TestSplitPacks.java b/test/hotspot/jtreg/compiler/loopopts/superword/TestSplitPacks.java index 80a877bfce53..6ac3385afabc 100644 --- a/test/hotspot/jtreg/compiler/loopopts/superword/TestSplitPacks.java +++ b/test/hotspot/jtreg/compiler/loopopts/superword/TestSplitPacks.java @@ -35,6 +35,7 @@ /* * @test * @bug 8326139 8348659 + * @key randomness * @summary Test splitting packs in SuperWord * @library /test/lib / * @run driver compiler.loopopts.superword.TestSplitPacks nCOH_nAV diff --git a/test/hotspot/jtreg/compiler/vectorapi/Test8278948.java b/test/hotspot/jtreg/compiler/vectorapi/Test8278948.java index 5218e10c4af6..5170c188133a 100644 --- a/test/hotspot/jtreg/compiler/vectorapi/Test8278948.java +++ b/test/hotspot/jtreg/compiler/vectorapi/Test8278948.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -33,6 +33,7 @@ /* * @test * @bug 8278948 + * @key randomness * @summary Intermediate integer promotion vector length encoding is calculated incorrectly on x86 * @modules jdk.incubator.vector * @library /test/lib diff --git a/test/hotspot/jtreg/compiler/vectorapi/TestVectorAddMulReduction.java b/test/hotspot/jtreg/compiler/vectorapi/TestVectorAddMulReduction.java index 38a2753a7eda..ddf16ce185fc 100644 --- a/test/hotspot/jtreg/compiler/vectorapi/TestVectorAddMulReduction.java +++ b/test/hotspot/jtreg/compiler/vectorapi/TestVectorAddMulReduction.java @@ -38,6 +38,7 @@ /** * @test * @bug 8320725 + * @key randomness * @library /test/lib / * @summary Verify non-strictly ordered AddReductionVF/VD and MulReductionVF/VD * nodes are generated in VectorAPI diff --git a/test/hotspot/jtreg/compiler/vectorapi/TestVectorCompressExpandBits.java b/test/hotspot/jtreg/compiler/vectorapi/TestVectorCompressExpandBits.java index f5174b7e6dcd..8889ccd0d268 100644 --- a/test/hotspot/jtreg/compiler/vectorapi/TestVectorCompressExpandBits.java +++ b/test/hotspot/jtreg/compiler/vectorapi/TestVectorCompressExpandBits.java @@ -38,6 +38,7 @@ /** * @test * @bug 8301012 + * @key randomness * @library /test/lib / * @requires os.arch == "aarch64" & vm.cpu.features ~= ".*sve2.*" & vm.cpu.features ~= ".*svebitperm.*" * @summary [vectorapi]: Intrinsify CompressBitsV/ExpandBitsV and add the AArch64 SVE backend implementation diff --git a/test/hotspot/jtreg/compiler/vectorapi/VectorMultiplyOpt.java b/test/hotspot/jtreg/compiler/vectorapi/VectorMultiplyOpt.java index a48cd25e47fb..a8394f41f8a4 100644 --- a/test/hotspot/jtreg/compiler/vectorapi/VectorMultiplyOpt.java +++ b/test/hotspot/jtreg/compiler/vectorapi/VectorMultiplyOpt.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -32,6 +32,7 @@ /** * @test * @bug 8341137 + * @key randomness * @summary Optimize long vector multiplication using x86 VPMUL[U]DQ instruction. * @modules jdk.incubator.vector * @library /test/lib / diff --git a/test/hotspot/jtreg/compiler/vectorapi/VectorSaturatedOperationsTest.java b/test/hotspot/jtreg/compiler/vectorapi/VectorSaturatedOperationsTest.java index 887c41efd48e..e51e6d31cb5c 100644 --- a/test/hotspot/jtreg/compiler/vectorapi/VectorSaturatedOperationsTest.java +++ b/test/hotspot/jtreg/compiler/vectorapi/VectorSaturatedOperationsTest.java @@ -24,6 +24,7 @@ /** * @test * @bug 8338021 8342677 8349522 +* @key randomness * @summary Add IR validation tests for newly added saturated vector add / sub operations * @modules jdk.incubator.vector * @library /test/lib / diff --git a/test/hotspot/jtreg/compiler/vectorization/TestAutoVecIntMinMax.java b/test/hotspot/jtreg/compiler/vectorization/TestAutoVecIntMinMax.java index 6d868b48dd77..917d69876cee 100644 --- a/test/hotspot/jtreg/compiler/vectorization/TestAutoVecIntMinMax.java +++ b/test/hotspot/jtreg/compiler/vectorization/TestAutoVecIntMinMax.java @@ -30,6 +30,7 @@ /* * @test * @bug 8288107 + * @key randomness * @summary Auto-vectorization enhancement for integer Math.max/Math.min operations * @library /test/lib / * @requires vm.compiler2.enabled diff --git a/test/hotspot/jtreg/compiler/vectorization/TestEor3AArch64.java b/test/hotspot/jtreg/compiler/vectorization/TestEor3AArch64.java index d5a3de09123b..3fed3eda1249 100644 --- a/test/hotspot/jtreg/compiler/vectorization/TestEor3AArch64.java +++ b/test/hotspot/jtreg/compiler/vectorization/TestEor3AArch64.java @@ -33,6 +33,7 @@ /* * @test * @bug 8293488 + * @key randomness * @summary Test EOR3 Neon/SVE2 instruction for aarch64 SHA3 extension * @library /test/lib / * @requires os.arch == "aarch64" diff --git a/test/hotspot/jtreg/compiler/vectorization/TestMacroLogicVector.java b/test/hotspot/jtreg/compiler/vectorization/TestMacroLogicVector.java index d2486f0ed774..9ab5994f5663 100644 --- a/test/hotspot/jtreg/compiler/vectorization/TestMacroLogicVector.java +++ b/test/hotspot/jtreg/compiler/vectorization/TestMacroLogicVector.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,6 +24,7 @@ /** * @test * @bug 8241040 + * @key randomness * @library /test/lib * * @run main/othervm -XX:+IgnoreUnrecognizedVMOptions diff --git a/test/hotspot/jtreg/compiler/vectorization/TestVectorZeroCount.java b/test/hotspot/jtreg/compiler/vectorization/TestVectorZeroCount.java index f4c8845b8570..573b3b020d35 100644 --- a/test/hotspot/jtreg/compiler/vectorization/TestVectorZeroCount.java +++ b/test/hotspot/jtreg/compiler/vectorization/TestVectorZeroCount.java @@ -21,20 +21,20 @@ * questions. */ +package compiler.vectorization; + +import java.util.Random; +import jdk.test.lib.Utils; + /* @test * @bug 8349637 + * @key randomness * @requires vm.flavor == "server" & (vm.opt.TieredStopAtLevel == null | vm.opt.TieredStopAtLevel == 4) * @summary Ensure that vectorization of numberOfLeadingZeros and numberOfTrailingZeros outputs correct values * @library /test/lib / * @run main/othervm compiler.vectorization.TestVectorZeroCount */ -package compiler.vectorization; - -import java.util.Random; - -import jdk.test.lib.Utils; - public class TestVectorZeroCount { private static final int SIZE = 1024; private static final Random RANDOM = Utils.getRandomInstance(); From 7b35fc8ae584129290c91f6960e19ec087c54315 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Fri, 27 Mar 2026 15:03:28 +0000 Subject: [PATCH 089/168] 8367096: jdk/open/test/jdk/sun/security/pkcs11/ rsa, ec, config, secmod and sslecc tests are skipping but showing as pass Reviewed-by: phh Backport-of: 561c544d85ecdbfa7895e434e98aed8df250a305 --- .../pkcs11/Config/ReadConfInUTF16Env.java | 6 +++--- test/jdk/sun/security/pkcs11/PKCS11Test.java | 2 +- .../security/pkcs11/Secmod/AddPrivateKey.java | 6 ++---- .../pkcs11/Secmod/AddTrustedCert.java | 5 +---- .../sun/security/pkcs11/Secmod/Crypto.java | 7 ++----- .../security/pkcs11/Secmod/GetPrivateKey.java | 7 ++----- .../pkcs11/Secmod/JksSetPrivateKey.java | 7 ++----- .../security/pkcs11/Secmod/LoadKeystore.java | 7 ++----- .../pkcs11/Secmod/TestNssDbSqlite.java | 20 +++++-------------- .../security/pkcs11/Secmod/TrustAnchors.java | 7 ++----- test/jdk/sun/security/pkcs11/SecmodTest.java | 5 ++--- .../security/pkcs11/ec/ReadCertificates.java | 4 ++-- .../sun/security/pkcs11/ec/ReadPKCS12.java | 4 ++-- .../security/pkcs11/ec/TestKeyFactory.java | 7 ++++--- test/jdk/sun/security/pkcs11/rsa/KeyWrap.java | 10 +++++----- .../pkcs11/sslecc/ClientJSSEServerJSSE.java | 4 ++-- .../pkcs11/tls/tls12/FipsModeTLS12.java | 4 +--- 17 files changed, 40 insertions(+), 72 deletions(-) diff --git a/test/jdk/sun/security/pkcs11/Config/ReadConfInUTF16Env.java b/test/jdk/sun/security/pkcs11/Config/ReadConfInUTF16Env.java index eacc0337cb62..23f5fc3d6a1c 100644 --- a/test/jdk/sun/security/pkcs11/Config/ReadConfInUTF16Env.java +++ b/test/jdk/sun/security/pkcs11/Config/ReadConfInUTF16Env.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,6 +29,7 @@ */ import jdk.test.lib.process.ProcessTools; +import jtreg.SkippedException; import org.testng.annotations.Test; import java.security.Provider; @@ -47,8 +48,7 @@ static class TestSunPKCS11Provider { public static void main(String[] args) throws Exception { Provider p = Security.getProvider("SunPKCS11"); if (p == null) { - System.out.println("Skipping test - no PKCS11 provider available"); - return; + throw new SkippedException("No PKCS11 provider available"); } System.out.println(p.getName()); } diff --git a/test/jdk/sun/security/pkcs11/PKCS11Test.java b/test/jdk/sun/security/pkcs11/PKCS11Test.java index 0e8d2d61d539..3b09f845f8e4 100644 --- a/test/jdk/sun/security/pkcs11/PKCS11Test.java +++ b/test/jdk/sun/security/pkcs11/PKCS11Test.java @@ -828,7 +828,7 @@ protected boolean skipTest(Provider p) { private void premain(Provider p) throws Exception { if (skipTest(p)) { - return; + throw new SkippedException("See logs for details"); } long start = System.currentTimeMillis(); diff --git a/test/jdk/sun/security/pkcs11/Secmod/AddPrivateKey.java b/test/jdk/sun/security/pkcs11/Secmod/AddPrivateKey.java index 29950d762e0c..34a813ab858e 100644 --- a/test/jdk/sun/security/pkcs11/Secmod/AddPrivateKey.java +++ b/test/jdk/sun/security/pkcs11/Secmod/AddPrivateKey.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2006, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -62,9 +62,7 @@ public class AddPrivateKey extends SecmodTest { private static final byte[] DATA = generateData(DATA_LENGTH); public static void main(String[] args) throws Exception { - if (initSecmod() == false) { - return; - } + initSecmod(); String configName = BASE + SEP + "nss.cfg"; Provider p = getSunPKCS11(configName); diff --git a/test/jdk/sun/security/pkcs11/Secmod/AddTrustedCert.java b/test/jdk/sun/security/pkcs11/Secmod/AddTrustedCert.java index 7b4a5075da86..aaa38fe1c71e 100644 --- a/test/jdk/sun/security/pkcs11/Secmod/AddTrustedCert.java +++ b/test/jdk/sun/security/pkcs11/Secmod/AddTrustedCert.java @@ -31,7 +31,6 @@ * @run main/othervm AddTrustedCert */ -import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.security.KeyStore; @@ -47,9 +46,7 @@ public class AddTrustedCert extends SecmodTest { public static void main(String[] args) throws Exception { - if (initSecmod() == false) { - return; - } + initSecmod(); X509Certificate cert; try (InputStream in = new FileInputStream(BASE + SEP + "anchor.cer")) { diff --git a/test/jdk/sun/security/pkcs11/Secmod/Crypto.java b/test/jdk/sun/security/pkcs11/Secmod/Crypto.java index 1b4c0883e806..e9e9bd733219 100644 --- a/test/jdk/sun/security/pkcs11/Secmod/Crypto.java +++ b/test/jdk/sun/security/pkcs11/Secmod/Crypto.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -31,7 +31,6 @@ * @run main/othervm Crypto */ -import java.io.File; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.Provider; @@ -40,9 +39,7 @@ public class Crypto extends SecmodTest { public static void main(String[] args) throws Exception { - if (initSecmod() == false) { - return; - } + initSecmod(); String configName = BASE + SEP + "nsscrypto.cfg"; Provider p = getSunPKCS11(configName); diff --git a/test/jdk/sun/security/pkcs11/Secmod/GetPrivateKey.java b/test/jdk/sun/security/pkcs11/Secmod/GetPrivateKey.java index e89cafb920c4..8d613ee63d28 100644 --- a/test/jdk/sun/security/pkcs11/Secmod/GetPrivateKey.java +++ b/test/jdk/sun/security/pkcs11/Secmod/GetPrivateKey.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -32,7 +32,6 @@ * @run main/othervm GetPrivateKey */ -import java.io.File; import java.security.KeyStore; import java.security.PrivateKey; import java.security.Provider; @@ -46,9 +45,7 @@ public class GetPrivateKey extends SecmodTest { public static void main(String[] args) throws Exception { - if (initSecmod() == false) { - return; - } + initSecmod(); String configName = BASE + SEP + "nss.cfg"; Provider p = getSunPKCS11(configName); diff --git a/test/jdk/sun/security/pkcs11/Secmod/JksSetPrivateKey.java b/test/jdk/sun/security/pkcs11/Secmod/JksSetPrivateKey.java index d6c084c882de..8549a338a58b 100644 --- a/test/jdk/sun/security/pkcs11/Secmod/JksSetPrivateKey.java +++ b/test/jdk/sun/security/pkcs11/Secmod/JksSetPrivateKey.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2006, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -31,7 +31,6 @@ * @run main/othervm JksSetPrivateKey */ -import java.io.File; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.PrivateKey; @@ -45,9 +44,7 @@ public class JksSetPrivateKey extends SecmodTest { public static void main(String[] args) throws Exception { - if (initSecmod() == false) { - return; - } + initSecmod(); String configName = BASE + SEP + "nss.cfg"; Provider p = getSunPKCS11(configName); diff --git a/test/jdk/sun/security/pkcs11/Secmod/LoadKeystore.java b/test/jdk/sun/security/pkcs11/Secmod/LoadKeystore.java index 55c473ce90e1..d657d805ca0f 100644 --- a/test/jdk/sun/security/pkcs11/Secmod/LoadKeystore.java +++ b/test/jdk/sun/security/pkcs11/Secmod/LoadKeystore.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -30,7 +30,6 @@ * @run main/othervm LoadKeystore */ -import java.io.File; import java.io.IOException; import java.security.KeyStore; import java.security.KeyStoreException; @@ -42,9 +41,7 @@ public class LoadKeystore extends SecmodTest { public static void main(String[] args) throws Exception { - if (!initSecmod()) { - return; - } + initSecmod(); String configName = BASE + SEP + "nss.cfg"; Provider p = getSunPKCS11(configName); diff --git a/test/jdk/sun/security/pkcs11/Secmod/TestNssDbSqlite.java b/test/jdk/sun/security/pkcs11/Secmod/TestNssDbSqlite.java index 7b22a4abfc6c..57309749e93a 100644 --- a/test/jdk/sun/security/pkcs11/Secmod/TestNssDbSqlite.java +++ b/test/jdk/sun/security/pkcs11/Secmod/TestNssDbSqlite.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018, Red Hat, Inc. and/or its affiliates. + * Copyright (c) 2017, 2025, Red Hat, Inc. and/or its affiliates. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -46,6 +46,7 @@ import java.security.Provider; import java.security.Signature; +import jtreg.SkippedException; import sun.security.rsa.SunRsaSign; import sun.security.jca.ProviderList; import sun.security.jca.Providers; @@ -66,9 +67,7 @@ public final class TestNssDbSqlite extends SecmodTest { public static void main(String[] args) throws Exception { - if (!initialize()) { - return; - } + initializeProvider(); if (enableDebug) { System.out.println("SunPKCS11 provider: " + @@ -110,16 +109,9 @@ private static void testRetrieveKeysFromKeystore() throws Exception { } } - private static boolean initialize() throws Exception { - return initializeProvider(); - } - - private static boolean initializeProvider() throws Exception { + private static void initializeProvider() throws Exception { useSqlite(true); - if (!initSecmod()) { - System.out.println("Cannot init security module database, skipping"); - return false; - } + initSecmod(); sunPKCS11NSSProvider = getSunPKCS11(BASE + SEP + "nss-sqlite.cfg"); sunJCEProvider = new com.sun.crypto.provider.SunJCE(); @@ -135,7 +127,5 @@ private static boolean initializeProvider() throws Exception { gen.generate(2048); privateKey = gen.getPrivateKey(); certificate = gen.getSelfCertificate(new X500Name("CN=Me"), 365); - - return true; } } diff --git a/test/jdk/sun/security/pkcs11/Secmod/TrustAnchors.java b/test/jdk/sun/security/pkcs11/Secmod/TrustAnchors.java index efe34478a9e9..14977fde340f 100644 --- a/test/jdk/sun/security/pkcs11/Secmod/TrustAnchors.java +++ b/test/jdk/sun/security/pkcs11/Secmod/TrustAnchors.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -31,7 +31,6 @@ * @run main/othervm TrustAnchors */ -import java.io.File; import java.security.KeyStore; import java.security.Provider; import java.security.Security; @@ -43,9 +42,7 @@ public class TrustAnchors extends SecmodTest { public static void main(String[] args) throws Exception { - if (initSecmod() == false) { - return; - } + initSecmod(); // our secmod.db file says nssckbi.*so*, so NSS does not find the // *DLL* on Windows nor the *DYLIB* on Mac OSX. diff --git a/test/jdk/sun/security/pkcs11/SecmodTest.java b/test/jdk/sun/security/pkcs11/SecmodTest.java index 8201548bf9da..1979066e26ad 100644 --- a/test/jdk/sun/security/pkcs11/SecmodTest.java +++ b/test/jdk/sun/security/pkcs11/SecmodTest.java @@ -42,7 +42,7 @@ static void useSqlite(boolean b) { useSqlite = b; } - static boolean initSecmod() throws Exception { + static void initSecmod() throws Exception { useNSS(); LIBPATH = getNSSLibDir(); // load all the libraries except libnss3 into memory @@ -60,7 +60,7 @@ static boolean initSecmod() throws Exception { System.setProperty("pkcs11test.nss.db", DBDIR); } File dbdirFile = new File(DBDIR); - if (dbdirFile.exists() == false) { + if (!dbdirFile.exists()) { dbdirFile.mkdir(); } @@ -73,7 +73,6 @@ static boolean initSecmod() throws Exception { copyFile("key3.db", BASE, DBDIR); copyFile("cert8.db", BASE, DBDIR); } - return true; } private static void copyFile(String name, String srcDir, String dstDir) throws IOException { diff --git a/test/jdk/sun/security/pkcs11/ec/ReadCertificates.java b/test/jdk/sun/security/pkcs11/ec/ReadCertificates.java index e2bfa3dc048e..4ff17356eb62 100644 --- a/test/jdk/sun/security/pkcs11/ec/ReadCertificates.java +++ b/test/jdk/sun/security/pkcs11/ec/ReadCertificates.java @@ -55,6 +55,7 @@ import java.util.Map; import javax.security.auth.x500.X500Principal; import jdk.test.lib.security.Providers; +import jtreg.SkippedException; public class ReadCertificates extends PKCS11Test { @@ -78,8 +79,7 @@ public static void main(String[] args) throws Exception { @Override public void main(Provider p) throws Exception { if (p.getService("Signature", "SHA1withECDSA") == null) { - System.out.println("Provider does not support ECDSA, skipping..."); - return; + throw new SkippedException("Provider does not support ECDSA"); } /* diff --git a/test/jdk/sun/security/pkcs11/ec/ReadPKCS12.java b/test/jdk/sun/security/pkcs11/ec/ReadPKCS12.java index 15a5b14ec40a..eea360b64fe9 100644 --- a/test/jdk/sun/security/pkcs11/ec/ReadPKCS12.java +++ b/test/jdk/sun/security/pkcs11/ec/ReadPKCS12.java @@ -54,6 +54,7 @@ import java.util.Map; import java.util.Random; import jdk.test.lib.security.Providers; +import jtreg.SkippedException; public class ReadPKCS12 extends PKCS11Test { @@ -66,8 +67,7 @@ public static void main(String[] args) throws Exception { @Override public void main(Provider p) throws Exception { if (p.getService("Signature", "SHA1withECDSA") == null) { - System.out.println("Provider does not support ECDSA, skipping..."); - return; + throw new SkippedException("Provider does not support ECDSA"); } /* diff --git a/test/jdk/sun/security/pkcs11/ec/TestKeyFactory.java b/test/jdk/sun/security/pkcs11/ec/TestKeyFactory.java index d23c302f38f5..94453dd649da 100644 --- a/test/jdk/sun/security/pkcs11/ec/TestKeyFactory.java +++ b/test/jdk/sun/security/pkcs11/ec/TestKeyFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2006, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -31,6 +31,8 @@ * @run main/othervm TestKeyFactory */ +import jtreg.SkippedException; + import java.security.Key; import java.security.KeyFactory; import java.security.KeyPair; @@ -126,8 +128,7 @@ public static void main(String[] args) throws Exception { @Override public void main(Provider p) throws Exception { if (p.getService("KeyFactory", "EC") == null) { - System.out.println("Provider does not support EC, skipping"); - return; + throw new SkippedException("Provider does not support EC, skipping"); } int[] keyLengths = {256, 521}; KeyFactory kf = KeyFactory.getInstance("EC", p); diff --git a/test/jdk/sun/security/pkcs11/rsa/KeyWrap.java b/test/jdk/sun/security/pkcs11/rsa/KeyWrap.java index 4fd4082f5239..8d71b5e26caf 100644 --- a/test/jdk/sun/security/pkcs11/rsa/KeyWrap.java +++ b/test/jdk/sun/security/pkcs11/rsa/KeyWrap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -32,6 +32,8 @@ * @run main/othervm KeyWrap */ +import jtreg.SkippedException; + import java.security.GeneralSecurityException; import java.security.InvalidKeyException; import java.security.Key; @@ -54,8 +56,7 @@ public void main(Provider p) throws Exception { try { Cipher.getInstance("RSA/ECB/PKCS1Padding", p); } catch (GeneralSecurityException e) { - System.out.println("Not supported by provider, skipping"); - return; + throw new SkippedException("Not supported by provider, skipping"); } KeyPair kp; try { @@ -74,8 +75,7 @@ public void main(Provider p) throws Exception { kp = new KeyPair(pub, priv); } catch (NoSuchAlgorithmException | InvalidKeyException ee) { ee.printStackTrace(); - System.out.println("Provider does not support RSA, skipping"); - return; + throw new SkippedException("Provider does not support RSA, skipping"); } } System.out.println(kp); diff --git a/test/jdk/sun/security/pkcs11/sslecc/ClientJSSEServerJSSE.java b/test/jdk/sun/security/pkcs11/sslecc/ClientJSSEServerJSSE.java index c125464659b5..2630d9e310b9 100644 --- a/test/jdk/sun/security/pkcs11/sslecc/ClientJSSEServerJSSE.java +++ b/test/jdk/sun/security/pkcs11/sslecc/ClientJSSEServerJSSE.java @@ -40,6 +40,7 @@ import java.security.Provider; import java.security.Security; import jdk.test.lib.security.Providers; +import jtreg.SkippedException; public class ClientJSSEServerJSSE extends PKCS11Test { @@ -58,8 +59,7 @@ public static void main(String[] args) throws Exception { @Override public void main(Provider p) throws Exception { if (p.getService("KeyFactory", "EC") == null) { - System.out.println("Provider does not support EC, skipping"); - return; + throw new SkippedException("Provider does not support EC, skipping"); } Providers.setAt(p, 1); CipherTest.main(new JSSEFactory(), cmdArgs); diff --git a/test/jdk/sun/security/pkcs11/tls/tls12/FipsModeTLS12.java b/test/jdk/sun/security/pkcs11/tls/tls12/FipsModeTLS12.java index f9e9bd472c76..6abee6c4685c 100644 --- a/test/jdk/sun/security/pkcs11/tls/tls12/FipsModeTLS12.java +++ b/test/jdk/sun/security/pkcs11/tls/tls12/FipsModeTLS12.java @@ -444,9 +444,7 @@ private static void initialize() throws Exception { // 2. SUN (to handle X.509 certificates) // 3. SunJSSE (for a TLS engine) - if (initSecmod() == false) { - return; - } + initSecmod(); String configName = BASE + SEP + "nss.cfg"; sunPKCS11NSSProvider = getSunPKCS11(configName); System.out.println("SunPKCS11 provider: " + sunPKCS11NSSProvider); From 1950120915349c54276701d417e93e2a8a00cd42 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Fri, 27 Mar 2026 15:04:07 +0000 Subject: [PATCH 090/168] 8278102: containers/docker/TestJcmd.java failed with "RuntimeException: Could not find specified process" Reviewed-by: sgehwolf Backport-of: 9ef2e8dc1c993a875eb7e47525df277d96066fe1 --- test/hotspot/jtreg/ProblemList.txt | 1 - test/hotspot/jtreg/containers/docker/TestJcmd.java | 8 ++------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/test/hotspot/jtreg/ProblemList.txt b/test/hotspot/jtreg/ProblemList.txt index 6c7994421e34..7f671554bab3 100644 --- a/test/hotspot/jtreg/ProblemList.txt +++ b/test/hotspot/jtreg/ProblemList.txt @@ -114,7 +114,6 @@ runtime/NMT/VirtualAllocCommitMerge.java 8309698 linux-s390x applications/jcstress/copy.java 8229852 linux-all -containers/docker/TestJcmd.java 8278102 linux-all containers/docker/TestMemoryAwareness.java 8303470 linux-all containers/docker/TestJFREvents.java 8327723 linux-x64 containers/docker/TestJcmdWithSideCar.java 8341518 linux-all diff --git a/test/hotspot/jtreg/containers/docker/TestJcmd.java b/test/hotspot/jtreg/containers/docker/TestJcmd.java index 8c210544bb68..3f5afd31801f 100644 --- a/test/hotspot/jtreg/containers/docker/TestJcmd.java +++ b/test/hotspot/jtreg/containers/docker/TestJcmd.java @@ -168,14 +168,10 @@ private static Process startObservedContainer() throws Exception { opts.addDockerOpts("--volume", Utils.TEST_CLASSES + ":/test-classes/:z") .addJavaOpts("-cp", "/test-classes/") .addDockerOpts("--cap-add=SYS_PTRACE") + .addDockerOpts("--pull=never") .addDockerOpts("--name", CONTAINER_NAME) .addClassOptions("" + TIME_TO_RUN_CONTAINER_PROCESS); - if (IS_PODMAN && !ROOT_UID.equals(getId("-u"))) { - // map the current userid to the one in the target namespace - opts.addDockerOpts("--userns=keep-id"); - } - // avoid large Xmx opts.appendTestJavaOptions = false; @@ -184,7 +180,7 @@ private static Process startObservedContainer() throws Exception { return ProcessTools.startProcess("main-container-process", pb, line -> line.contains(EventGeneratorLoop.MAIN_METHOD_STARTED), - 5, TimeUnit.SECONDS); + 15, TimeUnit.SECONDS); } From a0ac2b145ea795d134bac825cae2b7d0ab02b94a Mon Sep 17 00:00:00 2001 From: David Briemann Date: Mon, 30 Mar 2026 08:28:02 +0000 Subject: [PATCH 091/168] 8354650: [PPC64] Try to reduce register definitions Backport-of: a08208283bcfe395c9962c8de3ba19fdd8cab985 --- .../ppc/gc/shared/barrierSetAssembler_ppc.cpp | 4 +- src/hotspot/cpu/ppc/ppc.ad | 847 +++++++----------- src/hotspot/cpu/ppc/register_ppc.hpp | 6 +- src/hotspot/cpu/ppc/sharedRuntime_ppc.cpp | 140 +-- src/hotspot/cpu/ppc/vmreg_ppc.cpp | 2 +- src/hotspot/cpu/ppc/vmreg_ppc.hpp | 14 +- src/hotspot/cpu/ppc/vmreg_ppc.inline.hpp | 4 +- 7 files changed, 412 insertions(+), 605 deletions(-) diff --git a/src/hotspot/cpu/ppc/gc/shared/barrierSetAssembler_ppc.cpp b/src/hotspot/cpu/ppc/gc/shared/barrierSetAssembler_ppc.cpp index 32a7011ac261..405ac4b2310e 100644 --- a/src/hotspot/cpu/ppc/gc/shared/barrierSetAssembler_ppc.cpp +++ b/src/hotspot/cpu/ppc/gc/shared/barrierSetAssembler_ppc.cpp @@ -333,9 +333,9 @@ int SaveLiveRegisters::iterate_over_register_mask(IterationAction action, int of } } else if (vm_reg->is_ConditionRegister()) { // NOP. Conditions registers are covered by save_LR_CR - } else if (vm_reg->is_VectorSRegister()) { + } else if (vm_reg->is_VectorRegister()) { assert(SuperwordUseVSX, "or should not reach here"); - VectorSRegister vs_reg = vm_reg->as_VectorSRegister(); + VectorSRegister vs_reg = (vm_reg->as_VectorRegister()).to_vsr(); if (vs_reg->encoding() >= VSR32->encoding() && vs_reg->encoding() <= VSR51->encoding()) { reg_save_index += (2 + (reg_save_index & 1)); // 2 slots + alignment if needed diff --git a/src/hotspot/cpu/ppc/ppc.ad b/src/hotspot/cpu/ppc/ppc.ad index a04e397c52b4..e25e86546244 100644 --- a/src/hotspot/cpu/ppc/ppc.ad +++ b/src/hotspot/cpu/ppc/ppc.ad @@ -255,329 +255,168 @@ register %{ reg_def SR_PPR( SOC, SOC, Op_RegP, 5, SR_PPR->as_VMReg()); // v // ---------------------------- -// Vector-Scalar Registers +// Vector Registers // ---------------------------- - // 1st 32 VSRs are aliases for the FPRs which are already defined above. - reg_def VSR0 (SOC, SOC, Op_RegF, 0, VMRegImpl::Bad()); - reg_def VSR0_H (SOC, SOC, Op_RegF, 0, VMRegImpl::Bad()); - reg_def VSR0_J (SOC, SOC, Op_RegF, 0, VMRegImpl::Bad()); - reg_def VSR0_K (SOC, SOC, Op_RegF, 0, VMRegImpl::Bad()); - - reg_def VSR1 (SOC, SOC, Op_RegF, 1, VMRegImpl::Bad()); - reg_def VSR1_H (SOC, SOC, Op_RegF, 1, VMRegImpl::Bad()); - reg_def VSR1_J (SOC, SOC, Op_RegF, 1, VMRegImpl::Bad()); - reg_def VSR1_K (SOC, SOC, Op_RegF, 1, VMRegImpl::Bad()); - - reg_def VSR2 (SOC, SOC, Op_RegF, 2, VMRegImpl::Bad()); - reg_def VSR2_H (SOC, SOC, Op_RegF, 2, VMRegImpl::Bad()); - reg_def VSR2_J (SOC, SOC, Op_RegF, 2, VMRegImpl::Bad()); - reg_def VSR2_K (SOC, SOC, Op_RegF, 2, VMRegImpl::Bad()); - - reg_def VSR3 (SOC, SOC, Op_RegF, 3, VMRegImpl::Bad()); - reg_def VSR3_H (SOC, SOC, Op_RegF, 3, VMRegImpl::Bad()); - reg_def VSR3_J (SOC, SOC, Op_RegF, 3, VMRegImpl::Bad()); - reg_def VSR3_K (SOC, SOC, Op_RegF, 3, VMRegImpl::Bad()); - - reg_def VSR4 (SOC, SOC, Op_RegF, 4, VMRegImpl::Bad()); - reg_def VSR4_H (SOC, SOC, Op_RegF, 4, VMRegImpl::Bad()); - reg_def VSR4_J (SOC, SOC, Op_RegF, 4, VMRegImpl::Bad()); - reg_def VSR4_K (SOC, SOC, Op_RegF, 4, VMRegImpl::Bad()); - - reg_def VSR5 (SOC, SOC, Op_RegF, 5, VMRegImpl::Bad()); - reg_def VSR5_H (SOC, SOC, Op_RegF, 5, VMRegImpl::Bad()); - reg_def VSR5_J (SOC, SOC, Op_RegF, 5, VMRegImpl::Bad()); - reg_def VSR5_K (SOC, SOC, Op_RegF, 5, VMRegImpl::Bad()); - - reg_def VSR6 (SOC, SOC, Op_RegF, 6, VMRegImpl::Bad()); - reg_def VSR6_H (SOC, SOC, Op_RegF, 6, VMRegImpl::Bad()); - reg_def VSR6_J (SOC, SOC, Op_RegF, 6, VMRegImpl::Bad()); - reg_def VSR6_K (SOC, SOC, Op_RegF, 6, VMRegImpl::Bad()); - - reg_def VSR7 (SOC, SOC, Op_RegF, 7, VMRegImpl::Bad()); - reg_def VSR7_H (SOC, SOC, Op_RegF, 7, VMRegImpl::Bad()); - reg_def VSR7_J (SOC, SOC, Op_RegF, 7, VMRegImpl::Bad()); - reg_def VSR7_K (SOC, SOC, Op_RegF, 7, VMRegImpl::Bad()); - - reg_def VSR8 (SOC, SOC, Op_RegF, 8, VMRegImpl::Bad()); - reg_def VSR8_H (SOC, SOC, Op_RegF, 8, VMRegImpl::Bad()); - reg_def VSR8_J (SOC, SOC, Op_RegF, 8, VMRegImpl::Bad()); - reg_def VSR8_K (SOC, SOC, Op_RegF, 8, VMRegImpl::Bad()); - - reg_def VSR9 (SOC, SOC, Op_RegF, 9, VMRegImpl::Bad()); - reg_def VSR9_H (SOC, SOC, Op_RegF, 9, VMRegImpl::Bad()); - reg_def VSR9_J (SOC, SOC, Op_RegF, 9, VMRegImpl::Bad()); - reg_def VSR9_K (SOC, SOC, Op_RegF, 9, VMRegImpl::Bad()); - - reg_def VSR10 (SOC, SOC, Op_RegF, 10, VMRegImpl::Bad()); - reg_def VSR10_H(SOC, SOC, Op_RegF, 10, VMRegImpl::Bad()); - reg_def VSR10_J(SOC, SOC, Op_RegF, 10, VMRegImpl::Bad()); - reg_def VSR10_K(SOC, SOC, Op_RegF, 10, VMRegImpl::Bad()); - - reg_def VSR11 (SOC, SOC, Op_RegF, 11, VMRegImpl::Bad()); - reg_def VSR11_H(SOC, SOC, Op_RegF, 11, VMRegImpl::Bad()); - reg_def VSR11_J(SOC, SOC, Op_RegF, 11, VMRegImpl::Bad()); - reg_def VSR11_K(SOC, SOC, Op_RegF, 11, VMRegImpl::Bad()); - - reg_def VSR12 (SOC, SOC, Op_RegF, 12, VMRegImpl::Bad()); - reg_def VSR12_H(SOC, SOC, Op_RegF, 12, VMRegImpl::Bad()); - reg_def VSR12_J(SOC, SOC, Op_RegF, 12, VMRegImpl::Bad()); - reg_def VSR12_K(SOC, SOC, Op_RegF, 12, VMRegImpl::Bad()); - - reg_def VSR13 (SOC, SOC, Op_RegF, 13, VMRegImpl::Bad()); - reg_def VSR13_H(SOC, SOC, Op_RegF, 13, VMRegImpl::Bad()); - reg_def VSR13_J(SOC, SOC, Op_RegF, 13, VMRegImpl::Bad()); - reg_def VSR13_K(SOC, SOC, Op_RegF, 13, VMRegImpl::Bad()); - - reg_def VSR14 (SOC, SOC, Op_RegF, 14, VMRegImpl::Bad()); - reg_def VSR14_H(SOC, SOC, Op_RegF, 14, VMRegImpl::Bad()); - reg_def VSR14_J(SOC, SOC, Op_RegF, 14, VMRegImpl::Bad()); - reg_def VSR14_K(SOC, SOC, Op_RegF, 14, VMRegImpl::Bad()); - - reg_def VSR15 (SOC, SOC, Op_RegF, 15, VMRegImpl::Bad()); - reg_def VSR15_H(SOC, SOC, Op_RegF, 15, VMRegImpl::Bad()); - reg_def VSR15_J(SOC, SOC, Op_RegF, 15, VMRegImpl::Bad()); - reg_def VSR15_K(SOC, SOC, Op_RegF, 15, VMRegImpl::Bad()); - - reg_def VSR16 (SOC, SOC, Op_RegF, 16, VMRegImpl::Bad()); - reg_def VSR16_H(SOC, SOC, Op_RegF, 16, VMRegImpl::Bad()); - reg_def VSR16_J(SOC, SOC, Op_RegF, 16, VMRegImpl::Bad()); - reg_def VSR16_K(SOC, SOC, Op_RegF, 16, VMRegImpl::Bad()); - - reg_def VSR17 (SOC, SOC, Op_RegF, 17, VMRegImpl::Bad()); - reg_def VSR17_H(SOC, SOC, Op_RegF, 17, VMRegImpl::Bad()); - reg_def VSR17_J(SOC, SOC, Op_RegF, 17, VMRegImpl::Bad()); - reg_def VSR17_K(SOC, SOC, Op_RegF, 17, VMRegImpl::Bad()); - - reg_def VSR18 (SOC, SOC, Op_RegF, 18, VMRegImpl::Bad()); - reg_def VSR18_H(SOC, SOC, Op_RegF, 18, VMRegImpl::Bad()); - reg_def VSR18_J(SOC, SOC, Op_RegF, 18, VMRegImpl::Bad()); - reg_def VSR18_K(SOC, SOC, Op_RegF, 18, VMRegImpl::Bad()); - - reg_def VSR19 (SOC, SOC, Op_RegF, 19, VMRegImpl::Bad()); - reg_def VSR19_H(SOC, SOC, Op_RegF, 19, VMRegImpl::Bad()); - reg_def VSR19_J(SOC, SOC, Op_RegF, 19, VMRegImpl::Bad()); - reg_def VSR19_K(SOC, SOC, Op_RegF, 19, VMRegImpl::Bad()); - - reg_def VSR20 (SOC, SOC, Op_RegF, 20, VMRegImpl::Bad()); - reg_def VSR20_H(SOC, SOC, Op_RegF, 20, VMRegImpl::Bad()); - reg_def VSR20_J(SOC, SOC, Op_RegF, 20, VMRegImpl::Bad()); - reg_def VSR20_K(SOC, SOC, Op_RegF, 20, VMRegImpl::Bad()); - - reg_def VSR21 (SOC, SOC, Op_RegF, 21, VMRegImpl::Bad()); - reg_def VSR21_H(SOC, SOC, Op_RegF, 21, VMRegImpl::Bad()); - reg_def VSR21_J(SOC, SOC, Op_RegF, 21, VMRegImpl::Bad()); - reg_def VSR21_K(SOC, SOC, Op_RegF, 21, VMRegImpl::Bad()); - - reg_def VSR22 (SOC, SOC, Op_RegF, 22, VMRegImpl::Bad()); - reg_def VSR22_H(SOC, SOC, Op_RegF, 22, VMRegImpl::Bad()); - reg_def VSR22_J(SOC, SOC, Op_RegF, 22, VMRegImpl::Bad()); - reg_def VSR22_K(SOC, SOC, Op_RegF, 22, VMRegImpl::Bad()); - - reg_def VSR23 (SOC, SOC, Op_RegF, 23, VMRegImpl::Bad()); - reg_def VSR23_H(SOC, SOC, Op_RegF, 23, VMRegImpl::Bad()); - reg_def VSR23_J(SOC, SOC, Op_RegF, 23, VMRegImpl::Bad()); - reg_def VSR23_K(SOC, SOC, Op_RegF, 23, VMRegImpl::Bad()); - - reg_def VSR24 (SOC, SOC, Op_RegF, 24, VMRegImpl::Bad()); - reg_def VSR24_H(SOC, SOC, Op_RegF, 24, VMRegImpl::Bad()); - reg_def VSR24_J(SOC, SOC, Op_RegF, 24, VMRegImpl::Bad()); - reg_def VSR24_K(SOC, SOC, Op_RegF, 24, VMRegImpl::Bad()); - - reg_def VSR25 (SOC, SOC, Op_RegF, 25, VMRegImpl::Bad()); - reg_def VSR25_H(SOC, SOC, Op_RegF, 25, VMRegImpl::Bad()); - reg_def VSR25_J(SOC, SOC, Op_RegF, 25, VMRegImpl::Bad()); - reg_def VSR25_K(SOC, SOC, Op_RegF, 25, VMRegImpl::Bad()); - - reg_def VSR26 (SOC, SOC, Op_RegF, 26, VMRegImpl::Bad()); - reg_def VSR26_H(SOC, SOC, Op_RegF, 26, VMRegImpl::Bad()); - reg_def VSR26_J(SOC, SOC, Op_RegF, 26, VMRegImpl::Bad()); - reg_def VSR26_K(SOC, SOC, Op_RegF, 26, VMRegImpl::Bad()); - - reg_def VSR27 (SOC, SOC, Op_RegF, 27, VMRegImpl::Bad()); - reg_def VSR27_H(SOC, SOC, Op_RegF, 27, VMRegImpl::Bad()); - reg_def VSR27_J(SOC, SOC, Op_RegF, 27, VMRegImpl::Bad()); - reg_def VSR27_K(SOC, SOC, Op_RegF, 27, VMRegImpl::Bad()); - - reg_def VSR28 (SOC, SOC, Op_RegF, 28, VMRegImpl::Bad()); - reg_def VSR28_H(SOC, SOC, Op_RegF, 28, VMRegImpl::Bad()); - reg_def VSR28_J(SOC, SOC, Op_RegF, 28, VMRegImpl::Bad()); - reg_def VSR28_K(SOC, SOC, Op_RegF, 28, VMRegImpl::Bad()); - - reg_def VSR29 (SOC, SOC, Op_RegF, 29, VMRegImpl::Bad()); - reg_def VSR29_H(SOC, SOC, Op_RegF, 29, VMRegImpl::Bad()); - reg_def VSR29_J(SOC, SOC, Op_RegF, 29, VMRegImpl::Bad()); - reg_def VSR29_K(SOC, SOC, Op_RegF, 29, VMRegImpl::Bad()); - - reg_def VSR30 (SOC, SOC, Op_RegF, 30, VMRegImpl::Bad()); - reg_def VSR30_H(SOC, SOC, Op_RegF, 30, VMRegImpl::Bad()); - reg_def VSR30_J(SOC, SOC, Op_RegF, 30, VMRegImpl::Bad()); - reg_def VSR30_K(SOC, SOC, Op_RegF, 30, VMRegImpl::Bad()); - - reg_def VSR31 (SOC, SOC, Op_RegF, 31, VMRegImpl::Bad()); - reg_def VSR31_H(SOC, SOC, Op_RegF, 31, VMRegImpl::Bad()); - reg_def VSR31_J(SOC, SOC, Op_RegF, 31, VMRegImpl::Bad()); - reg_def VSR31_K(SOC, SOC, Op_RegF, 31, VMRegImpl::Bad()); - - // 2nd 32 VSRs are aliases for the VRs which are only defined here. - reg_def VSR32 (SOC, SOC, Op_RegF, 32, VSR32->as_VMReg() ); - reg_def VSR32_H(SOC, SOC, Op_RegF, 32, VSR32->as_VMReg()->next() ); - reg_def VSR32_J(SOC, SOC, Op_RegF, 32, VSR32->as_VMReg()->next(2)); - reg_def VSR32_K(SOC, SOC, Op_RegF, 32, VSR32->as_VMReg()->next(3)); - - reg_def VSR33 (SOC, SOC, Op_RegF, 33, VSR33->as_VMReg() ); - reg_def VSR33_H(SOC, SOC, Op_RegF, 33, VSR33->as_VMReg()->next() ); - reg_def VSR33_J(SOC, SOC, Op_RegF, 33, VSR33->as_VMReg()->next(2)); - reg_def VSR33_K(SOC, SOC, Op_RegF, 33, VSR33->as_VMReg()->next(3)); - - reg_def VSR34 (SOC, SOC, Op_RegF, 34, VSR34->as_VMReg() ); - reg_def VSR34_H(SOC, SOC, Op_RegF, 34, VSR34->as_VMReg()->next() ); - reg_def VSR34_J(SOC, SOC, Op_RegF, 34, VSR34->as_VMReg()->next(2)); - reg_def VSR34_K(SOC, SOC, Op_RegF, 34, VSR34->as_VMReg()->next(3)); - - reg_def VSR35 (SOC, SOC, Op_RegF, 35, VSR35->as_VMReg() ); - reg_def VSR35_H(SOC, SOC, Op_RegF, 35, VSR35->as_VMReg()->next() ); - reg_def VSR35_J(SOC, SOC, Op_RegF, 35, VSR35->as_VMReg()->next(2)); - reg_def VSR35_K(SOC, SOC, Op_RegF, 35, VSR35->as_VMReg()->next(3)); - - reg_def VSR36 (SOC, SOC, Op_RegF, 36, VSR36->as_VMReg() ); - reg_def VSR36_H(SOC, SOC, Op_RegF, 36, VSR36->as_VMReg()->next() ); - reg_def VSR36_J(SOC, SOC, Op_RegF, 36, VSR36->as_VMReg()->next(2)); - reg_def VSR36_K(SOC, SOC, Op_RegF, 36, VSR36->as_VMReg()->next(3)); - - reg_def VSR37 (SOC, SOC, Op_RegF, 37, VSR37->as_VMReg() ); - reg_def VSR37_H(SOC, SOC, Op_RegF, 37, VSR37->as_VMReg()->next() ); - reg_def VSR37_J(SOC, SOC, Op_RegF, 37, VSR37->as_VMReg()->next(2)); - reg_def VSR37_K(SOC, SOC, Op_RegF, 37, VSR37->as_VMReg()->next(3)); - - reg_def VSR38 (SOC, SOC, Op_RegF, 38, VSR38->as_VMReg() ); - reg_def VSR38_H(SOC, SOC, Op_RegF, 38, VSR38->as_VMReg()->next() ); - reg_def VSR38_J(SOC, SOC, Op_RegF, 38, VSR38->as_VMReg()->next(2)); - reg_def VSR38_K(SOC, SOC, Op_RegF, 38, VSR38->as_VMReg()->next(3)); - - reg_def VSR39 (SOC, SOC, Op_RegF, 39, VSR39->as_VMReg() ); - reg_def VSR39_H(SOC, SOC, Op_RegF, 39, VSR39->as_VMReg()->next() ); - reg_def VSR39_J(SOC, SOC, Op_RegF, 39, VSR39->as_VMReg()->next(2)); - reg_def VSR39_K(SOC, SOC, Op_RegF, 39, VSR39->as_VMReg()->next(3)); - - reg_def VSR40 (SOC, SOC, Op_RegF, 40, VSR40->as_VMReg() ); - reg_def VSR40_H(SOC, SOC, Op_RegF, 40, VSR40->as_VMReg()->next() ); - reg_def VSR40_J(SOC, SOC, Op_RegF, 40, VSR40->as_VMReg()->next(2)); - reg_def VSR40_K(SOC, SOC, Op_RegF, 40, VSR40->as_VMReg()->next(3)); - - reg_def VSR41 (SOC, SOC, Op_RegF, 41, VSR41->as_VMReg() ); - reg_def VSR41_H(SOC, SOC, Op_RegF, 41, VSR41->as_VMReg()->next() ); - reg_def VSR41_J(SOC, SOC, Op_RegF, 41, VSR41->as_VMReg()->next(2)); - reg_def VSR41_K(SOC, SOC, Op_RegF, 41, VSR41->as_VMReg()->next(3)); - - reg_def VSR42 (SOC, SOC, Op_RegF, 42, VSR42->as_VMReg() ); - reg_def VSR42_H(SOC, SOC, Op_RegF, 42, VSR42->as_VMReg()->next() ); - reg_def VSR42_J(SOC, SOC, Op_RegF, 42, VSR42->as_VMReg()->next(2)); - reg_def VSR42_K(SOC, SOC, Op_RegF, 42, VSR42->as_VMReg()->next(3)); - - reg_def VSR43 (SOC, SOC, Op_RegF, 43, VSR43->as_VMReg() ); - reg_def VSR43_H(SOC, SOC, Op_RegF, 43, VSR43->as_VMReg()->next() ); - reg_def VSR43_J(SOC, SOC, Op_RegF, 43, VSR43->as_VMReg()->next(2)); - reg_def VSR43_K(SOC, SOC, Op_RegF, 43, VSR43->as_VMReg()->next(3)); - - reg_def VSR44 (SOC, SOC, Op_RegF, 44, VSR44->as_VMReg() ); - reg_def VSR44_H(SOC, SOC, Op_RegF, 44, VSR44->as_VMReg()->next() ); - reg_def VSR44_J(SOC, SOC, Op_RegF, 44, VSR44->as_VMReg()->next(2)); - reg_def VSR44_K(SOC, SOC, Op_RegF, 44, VSR44->as_VMReg()->next(3)); - - reg_def VSR45 (SOC, SOC, Op_RegF, 45, VSR45->as_VMReg() ); - reg_def VSR45_H(SOC, SOC, Op_RegF, 45, VSR45->as_VMReg()->next() ); - reg_def VSR45_J(SOC, SOC, Op_RegF, 45, VSR45->as_VMReg()->next(2)); - reg_def VSR45_K(SOC, SOC, Op_RegF, 45, VSR45->as_VMReg()->next(3)); - - reg_def VSR46 (SOC, SOC, Op_RegF, 46, VSR46->as_VMReg() ); - reg_def VSR46_H(SOC, SOC, Op_RegF, 46, VSR46->as_VMReg()->next() ); - reg_def VSR46_J(SOC, SOC, Op_RegF, 46, VSR46->as_VMReg()->next(2)); - reg_def VSR46_K(SOC, SOC, Op_RegF, 46, VSR46->as_VMReg()->next(3)); - - reg_def VSR47 (SOC, SOC, Op_RegF, 47, VSR47->as_VMReg() ); - reg_def VSR47_H(SOC, SOC, Op_RegF, 47, VSR47->as_VMReg()->next() ); - reg_def VSR47_J(SOC, SOC, Op_RegF, 47, VSR47->as_VMReg()->next(2)); - reg_def VSR47_K(SOC, SOC, Op_RegF, 47, VSR47->as_VMReg()->next(3)); - - reg_def VSR48 (SOC, SOC, Op_RegF, 48, VSR48->as_VMReg() ); - reg_def VSR48_H(SOC, SOC, Op_RegF, 48, VSR48->as_VMReg()->next() ); - reg_def VSR48_J(SOC, SOC, Op_RegF, 48, VSR48->as_VMReg()->next(2)); - reg_def VSR48_K(SOC, SOC, Op_RegF, 48, VSR48->as_VMReg()->next(3)); - - reg_def VSR49 (SOC, SOC, Op_RegF, 49, VSR49->as_VMReg() ); - reg_def VSR49_H(SOC, SOC, Op_RegF, 49, VSR49->as_VMReg()->next() ); - reg_def VSR49_J(SOC, SOC, Op_RegF, 49, VSR49->as_VMReg()->next(2)); - reg_def VSR49_K(SOC, SOC, Op_RegF, 49, VSR49->as_VMReg()->next(3)); - - reg_def VSR50 (SOC, SOC, Op_RegF, 50, VSR50->as_VMReg() ); - reg_def VSR50_H(SOC, SOC, Op_RegF, 50, VSR50->as_VMReg()->next() ); - reg_def VSR50_J(SOC, SOC, Op_RegF, 50, VSR50->as_VMReg()->next(2)); - reg_def VSR50_K(SOC, SOC, Op_RegF, 50, VSR50->as_VMReg()->next(3)); - - reg_def VSR51 (SOC, SOC, Op_RegF, 51, VSR51->as_VMReg() ); - reg_def VSR51_H(SOC, SOC, Op_RegF, 51, VSR51->as_VMReg()->next() ); - reg_def VSR51_J(SOC, SOC, Op_RegF, 51, VSR51->as_VMReg()->next(2)); - reg_def VSR51_K(SOC, SOC, Op_RegF, 51, VSR51->as_VMReg()->next(3)); - - reg_def VSR52 (SOC, SOE, Op_RegF, 52, VSR52->as_VMReg() ); - reg_def VSR52_H(SOC, SOE, Op_RegF, 52, VSR52->as_VMReg()->next() ); - reg_def VSR52_J(SOC, SOE, Op_RegF, 52, VSR52->as_VMReg()->next(2)); - reg_def VSR52_K(SOC, SOE, Op_RegF, 52, VSR52->as_VMReg()->next(3)); - - reg_def VSR53 (SOC, SOE, Op_RegF, 53, VSR53->as_VMReg() ); - reg_def VSR53_H(SOC, SOE, Op_RegF, 53, VSR53->as_VMReg()->next() ); - reg_def VSR53_J(SOC, SOE, Op_RegF, 53, VSR53->as_VMReg()->next(2)); - reg_def VSR53_K(SOC, SOE, Op_RegF, 53, VSR53->as_VMReg()->next(3)); - - reg_def VSR54 (SOC, SOE, Op_RegF, 54, VSR54->as_VMReg() ); - reg_def VSR54_H(SOC, SOE, Op_RegF, 54, VSR54->as_VMReg()->next() ); - reg_def VSR54_J(SOC, SOE, Op_RegF, 54, VSR54->as_VMReg()->next(2)); - reg_def VSR54_K(SOC, SOE, Op_RegF, 54, VSR54->as_VMReg()->next(3)); - - reg_def VSR55 (SOC, SOE, Op_RegF, 55, VSR55->as_VMReg() ); - reg_def VSR55_H(SOC, SOE, Op_RegF, 55, VSR55->as_VMReg()->next() ); - reg_def VSR55_J(SOC, SOE, Op_RegF, 55, VSR55->as_VMReg()->next(2)); - reg_def VSR55_K(SOC, SOE, Op_RegF, 55, VSR55->as_VMReg()->next(3)); - - reg_def VSR56 (SOC, SOE, Op_RegF, 56, VSR56->as_VMReg() ); - reg_def VSR56_H(SOC, SOE, Op_RegF, 56, VSR56->as_VMReg()->next() ); - reg_def VSR56_J(SOC, SOE, Op_RegF, 56, VSR56->as_VMReg()->next(2)); - reg_def VSR56_K(SOC, SOE, Op_RegF, 56, VSR56->as_VMReg()->next(3)); - - reg_def VSR57 (SOC, SOE, Op_RegF, 57, VSR57->as_VMReg() ); - reg_def VSR57_H(SOC, SOE, Op_RegF, 57, VSR57->as_VMReg()->next() ); - reg_def VSR57_J(SOC, SOE, Op_RegF, 57, VSR57->as_VMReg()->next(2)); - reg_def VSR57_K(SOC, SOE, Op_RegF, 57, VSR57->as_VMReg()->next(3)); - - reg_def VSR58 (SOC, SOE, Op_RegF, 58, VSR58->as_VMReg() ); - reg_def VSR58_H(SOC, SOE, Op_RegF, 58, VSR58->as_VMReg()->next() ); - reg_def VSR58_J(SOC, SOE, Op_RegF, 58, VSR58->as_VMReg()->next(2)); - reg_def VSR58_K(SOC, SOE, Op_RegF, 58, VSR58->as_VMReg()->next(3)); - - reg_def VSR59 (SOC, SOE, Op_RegF, 59, VSR59->as_VMReg() ); - reg_def VSR59_H(SOC, SOE, Op_RegF, 59, VSR59->as_VMReg()->next() ); - reg_def VSR59_J(SOC, SOE, Op_RegF, 59, VSR59->as_VMReg()->next(2)); - reg_def VSR59_K(SOC, SOE, Op_RegF, 59, VSR59->as_VMReg()->next(3)); - - reg_def VSR60 (SOC, SOE, Op_RegF, 60, VSR60->as_VMReg() ); - reg_def VSR60_H(SOC, SOE, Op_RegF, 60, VSR60->as_VMReg()->next() ); - reg_def VSR60_J(SOC, SOE, Op_RegF, 60, VSR60->as_VMReg()->next(2)); - reg_def VSR60_K(SOC, SOE, Op_RegF, 60, VSR60->as_VMReg()->next(3)); - - reg_def VSR61 (SOC, SOE, Op_RegF, 61, VSR61->as_VMReg() ); - reg_def VSR61_H(SOC, SOE, Op_RegF, 61, VSR61->as_VMReg()->next() ); - reg_def VSR61_J(SOC, SOE, Op_RegF, 61, VSR61->as_VMReg()->next(2)); - reg_def VSR61_K(SOC, SOE, Op_RegF, 61, VSR61->as_VMReg()->next(3)); - - reg_def VSR62 (SOC, SOE, Op_RegF, 62, VSR62->as_VMReg() ); - reg_def VSR62_H(SOC, SOE, Op_RegF, 62, VSR62->as_VMReg()->next() ); - reg_def VSR62_J(SOC, SOE, Op_RegF, 62, VSR62->as_VMReg()->next(2)); - reg_def VSR62_K(SOC, SOE, Op_RegF, 62, VSR62->as_VMReg()->next(3)); - - reg_def VSR63 (SOC, SOE, Op_RegF, 63, VSR63->as_VMReg() ); - reg_def VSR63_H(SOC, SOE, Op_RegF, 63, VSR63->as_VMReg()->next() ); - reg_def VSR63_J(SOC, SOE, Op_RegF, 63, VSR63->as_VMReg()->next(2)); - reg_def VSR63_K(SOC, SOE, Op_RegF, 63, VSR63->as_VMReg()->next(3)); + + reg_def VR0 (SOC, SOC, Op_RegF, 0, VR0->as_VMReg() ); + reg_def VR0_H(SOC, SOC, Op_RegF, 0, VR0->as_VMReg()->next() ); + reg_def VR0_J(SOC, SOC, Op_RegF, 0, VR0->as_VMReg()->next(2)); + reg_def VR0_K(SOC, SOC, Op_RegF, 0, VR0->as_VMReg()->next(3)); + + reg_def VR1 (SOC, SOC, Op_RegF, 1, VR1->as_VMReg() ); + reg_def VR1_H(SOC, SOC, Op_RegF, 1, VR1->as_VMReg()->next() ); + reg_def VR1_J(SOC, SOC, Op_RegF, 1, VR1->as_VMReg()->next(2)); + reg_def VR1_K(SOC, SOC, Op_RegF, 1, VR1->as_VMReg()->next(3)); + + reg_def VR2 (SOC, SOC, Op_RegF, 2, VR2->as_VMReg() ); + reg_def VR2_H(SOC, SOC, Op_RegF, 2, VR2->as_VMReg()->next() ); + reg_def VR2_J(SOC, SOC, Op_RegF, 2, VR2->as_VMReg()->next(2)); + reg_def VR2_K(SOC, SOC, Op_RegF, 2, VR2->as_VMReg()->next(3)); + + reg_def VR3 (SOC, SOC, Op_RegF, 3, VR3->as_VMReg() ); + reg_def VR3_H(SOC, SOC, Op_RegF, 3, VR3->as_VMReg()->next() ); + reg_def VR3_J(SOC, SOC, Op_RegF, 3, VR3->as_VMReg()->next(2)); + reg_def VR3_K(SOC, SOC, Op_RegF, 3, VR3->as_VMReg()->next(3)); + + reg_def VR4 (SOC, SOC, Op_RegF, 4, VR4->as_VMReg() ); + reg_def VR4_H(SOC, SOC, Op_RegF, 4, VR4->as_VMReg()->next() ); + reg_def VR4_J(SOC, SOC, Op_RegF, 4, VR4->as_VMReg()->next(2)); + reg_def VR4_K(SOC, SOC, Op_RegF, 4, VR4->as_VMReg()->next(3)); + + reg_def VR5 (SOC, SOC, Op_RegF, 5, VR5->as_VMReg() ); + reg_def VR5_H(SOC, SOC, Op_RegF, 5, VR5->as_VMReg()->next() ); + reg_def VR5_J(SOC, SOC, Op_RegF, 5, VR5->as_VMReg()->next(2)); + reg_def VR5_K(SOC, SOC, Op_RegF, 5, VR5->as_VMReg()->next(3)); + + reg_def VR6 (SOC, SOC, Op_RegF, 6, VR6->as_VMReg() ); + reg_def VR6_H(SOC, SOC, Op_RegF, 6, VR6->as_VMReg()->next() ); + reg_def VR6_J(SOC, SOC, Op_RegF, 6, VR6->as_VMReg()->next(2)); + reg_def VR6_K(SOC, SOC, Op_RegF, 6, VR6->as_VMReg()->next(3)); + + reg_def VR7 (SOC, SOC, Op_RegF, 7, VR7->as_VMReg() ); + reg_def VR7_H(SOC, SOC, Op_RegF, 7, VR7->as_VMReg()->next() ); + reg_def VR7_J(SOC, SOC, Op_RegF, 7, VR7->as_VMReg()->next(2)); + reg_def VR7_K(SOC, SOC, Op_RegF, 7, VR7->as_VMReg()->next(3)); + + reg_def VR8 (SOC, SOC, Op_RegF, 8, VR8->as_VMReg() ); + reg_def VR8_H(SOC, SOC, Op_RegF, 8, VR8->as_VMReg()->next() ); + reg_def VR8_J(SOC, SOC, Op_RegF, 8, VR8->as_VMReg()->next(2)); + reg_def VR8_K(SOC, SOC, Op_RegF, 8, VR8->as_VMReg()->next(3)); + + reg_def VR9 (SOC, SOC, Op_RegF, 9, VR9->as_VMReg() ); + reg_def VR9_H(SOC, SOC, Op_RegF, 9, VR9->as_VMReg()->next() ); + reg_def VR9_J(SOC, SOC, Op_RegF, 9, VR9->as_VMReg()->next(2)); + reg_def VR9_K(SOC, SOC, Op_RegF, 9, VR9->as_VMReg()->next(3)); + + reg_def VR10 (SOC, SOC, Op_RegF, 10, VR10->as_VMReg() ); + reg_def VR10_H(SOC, SOC, Op_RegF, 10, VR10->as_VMReg()->next() ); + reg_def VR10_J(SOC, SOC, Op_RegF, 10, VR10->as_VMReg()->next(2)); + reg_def VR10_K(SOC, SOC, Op_RegF, 10, VR10->as_VMReg()->next(3)); + + reg_def VR11 (SOC, SOC, Op_RegF, 11, VR11->as_VMReg() ); + reg_def VR11_H(SOC, SOC, Op_RegF, 11, VR11->as_VMReg()->next() ); + reg_def VR11_J(SOC, SOC, Op_RegF, 11, VR11->as_VMReg()->next(2)); + reg_def VR11_K(SOC, SOC, Op_RegF, 11, VR11->as_VMReg()->next(3)); + + reg_def VR12 (SOC, SOC, Op_RegF, 12, VR12->as_VMReg() ); + reg_def VR12_H(SOC, SOC, Op_RegF, 12, VR12->as_VMReg()->next() ); + reg_def VR12_J(SOC, SOC, Op_RegF, 12, VR12->as_VMReg()->next(2)); + reg_def VR12_K(SOC, SOC, Op_RegF, 12, VR12->as_VMReg()->next(3)); + + reg_def VR13 (SOC, SOC, Op_RegF, 13, VR13->as_VMReg() ); + reg_def VR13_H(SOC, SOC, Op_RegF, 13, VR13->as_VMReg()->next() ); + reg_def VR13_J(SOC, SOC, Op_RegF, 13, VR13->as_VMReg()->next(2)); + reg_def VR13_K(SOC, SOC, Op_RegF, 13, VR13->as_VMReg()->next(3)); + + reg_def VR14 (SOC, SOC, Op_RegF, 14, VR14->as_VMReg() ); + reg_def VR14_H(SOC, SOC, Op_RegF, 14, VR14->as_VMReg()->next() ); + reg_def VR14_J(SOC, SOC, Op_RegF, 14, VR14->as_VMReg()->next(2)); + reg_def VR14_K(SOC, SOC, Op_RegF, 14, VR14->as_VMReg()->next(3)); + + reg_def VR15 (SOC, SOC, Op_RegF, 15, VR15->as_VMReg() ); + reg_def VR15_H(SOC, SOC, Op_RegF, 15, VR15->as_VMReg()->next() ); + reg_def VR15_J(SOC, SOC, Op_RegF, 15, VR15->as_VMReg()->next(2)); + reg_def VR15_K(SOC, SOC, Op_RegF, 15, VR15->as_VMReg()->next(3)); + + reg_def VR16 (SOC, SOC, Op_RegF, 16, VR16->as_VMReg() ); + reg_def VR16_H(SOC, SOC, Op_RegF, 16, VR16->as_VMReg()->next() ); + reg_def VR16_J(SOC, SOC, Op_RegF, 16, VR16->as_VMReg()->next(2)); + reg_def VR16_K(SOC, SOC, Op_RegF, 16, VR16->as_VMReg()->next(3)); + + reg_def VR17 (SOC, SOC, Op_RegF, 17, VR17->as_VMReg() ); + reg_def VR17_H(SOC, SOC, Op_RegF, 17, VR17->as_VMReg()->next() ); + reg_def VR17_J(SOC, SOC, Op_RegF, 17, VR17->as_VMReg()->next(2)); + reg_def VR17_K(SOC, SOC, Op_RegF, 17, VR17->as_VMReg()->next(3)); + + reg_def VR18 (SOC, SOC, Op_RegF, 18, VR18->as_VMReg() ); + reg_def VR18_H(SOC, SOC, Op_RegF, 18, VR18->as_VMReg()->next() ); + reg_def VR18_J(SOC, SOC, Op_RegF, 18, VR18->as_VMReg()->next(2)); + reg_def VR18_K(SOC, SOC, Op_RegF, 18, VR18->as_VMReg()->next(3)); + + reg_def VR19 (SOC, SOC, Op_RegF, 19, VR19->as_VMReg() ); + reg_def VR19_H(SOC, SOC, Op_RegF, 19, VR19->as_VMReg()->next() ); + reg_def VR19_J(SOC, SOC, Op_RegF, 19, VR19->as_VMReg()->next(2)); + reg_def VR19_K(SOC, SOC, Op_RegF, 19, VR19->as_VMReg()->next(3)); + + reg_def VR20 (SOC, SOE, Op_RegF, 20, VR20->as_VMReg() ); + reg_def VR20_H(SOC, SOE, Op_RegF, 20, VR20->as_VMReg()->next() ); + reg_def VR20_J(SOC, SOE, Op_RegF, 20, VR20->as_VMReg()->next(2)); + reg_def VR20_K(SOC, SOE, Op_RegF, 20, VR20->as_VMReg()->next(3)); + + reg_def VR21 (SOC, SOE, Op_RegF, 21, VR21->as_VMReg() ); + reg_def VR21_H(SOC, SOE, Op_RegF, 21, VR21->as_VMReg()->next() ); + reg_def VR21_J(SOC, SOE, Op_RegF, 21, VR21->as_VMReg()->next(2)); + reg_def VR21_K(SOC, SOE, Op_RegF, 21, VR21->as_VMReg()->next(3)); + + reg_def VR22 (SOC, SOE, Op_RegF, 22, VR22->as_VMReg() ); + reg_def VR22_H(SOC, SOE, Op_RegF, 22, VR22->as_VMReg()->next() ); + reg_def VR22_J(SOC, SOE, Op_RegF, 22, VR22->as_VMReg()->next(2)); + reg_def VR22_K(SOC, SOE, Op_RegF, 22, VR22->as_VMReg()->next(3)); + + reg_def VR23 (SOC, SOE, Op_RegF, 23, VR23->as_VMReg() ); + reg_def VR23_H(SOC, SOE, Op_RegF, 23, VR23->as_VMReg()->next() ); + reg_def VR23_J(SOC, SOE, Op_RegF, 23, VR23->as_VMReg()->next(2)); + reg_def VR23_K(SOC, SOE, Op_RegF, 23, VR23->as_VMReg()->next(3)); + + reg_def VR24 (SOC, SOE, Op_RegF, 24, VR24->as_VMReg() ); + reg_def VR24_H(SOC, SOE, Op_RegF, 24, VR24->as_VMReg()->next() ); + reg_def VR24_J(SOC, SOE, Op_RegF, 24, VR24->as_VMReg()->next(2)); + reg_def VR24_K(SOC, SOE, Op_RegF, 24, VR24->as_VMReg()->next(3)); + + reg_def VR25 (SOC, SOE, Op_RegF, 25, VR25->as_VMReg() ); + reg_def VR25_H(SOC, SOE, Op_RegF, 25, VR25->as_VMReg()->next() ); + reg_def VR25_J(SOC, SOE, Op_RegF, 25, VR25->as_VMReg()->next(2)); + reg_def VR25_K(SOC, SOE, Op_RegF, 25, VR25->as_VMReg()->next(3)); + + reg_def VR26 (SOC, SOE, Op_RegF, 26, VR26->as_VMReg() ); + reg_def VR26_H(SOC, SOE, Op_RegF, 26, VR26->as_VMReg()->next() ); + reg_def VR26_J(SOC, SOE, Op_RegF, 26, VR26->as_VMReg()->next(2)); + reg_def VR26_K(SOC, SOE, Op_RegF, 26, VR26->as_VMReg()->next(3)); + + reg_def VR27 (SOC, SOE, Op_RegF, 27, VR27->as_VMReg() ); + reg_def VR27_H(SOC, SOE, Op_RegF, 27, VR27->as_VMReg()->next() ); + reg_def VR27_J(SOC, SOE, Op_RegF, 27, VR27->as_VMReg()->next(2)); + reg_def VR27_K(SOC, SOE, Op_RegF, 27, VR27->as_VMReg()->next(3)); + + reg_def VR28 (SOC, SOE, Op_RegF, 28, VR28->as_VMReg() ); + reg_def VR28_H(SOC, SOE, Op_RegF, 28, VR28->as_VMReg()->next() ); + reg_def VR28_J(SOC, SOE, Op_RegF, 28, VR28->as_VMReg()->next(2)); + reg_def VR28_K(SOC, SOE, Op_RegF, 28, VR28->as_VMReg()->next(3)); + + reg_def VR29 (SOC, SOE, Op_RegF, 29, VR29->as_VMReg() ); + reg_def VR29_H(SOC, SOE, Op_RegF, 29, VR29->as_VMReg()->next() ); + reg_def VR29_J(SOC, SOE, Op_RegF, 29, VR29->as_VMReg()->next(2)); + reg_def VR29_K(SOC, SOE, Op_RegF, 29, VR29->as_VMReg()->next(3)); + + reg_def VR30 (SOC, SOE, Op_RegF, 30, VR30->as_VMReg() ); + reg_def VR30_H(SOC, SOE, Op_RegF, 30, VR30->as_VMReg()->next() ); + reg_def VR30_J(SOC, SOE, Op_RegF, 30, VR30->as_VMReg()->next(2)); + reg_def VR30_K(SOC, SOE, Op_RegF, 30, VR30->as_VMReg()->next(3)); + + reg_def VR31 (SOC, SOE, Op_RegF, 31, VR31->as_VMReg() ); + reg_def VR31_H(SOC, SOE, Op_RegF, 31, VR31->as_VMReg()->next() ); + reg_def VR31_J(SOC, SOE, Op_RegF, 31, VR31->as_VMReg()->next(2)); + reg_def VR31_K(SOC, SOE, Op_RegF, 31, VR31->as_VMReg()->next(3)); // ---------------------------- // Specify priority of register selection within phases of register @@ -696,70 +535,38 @@ alloc_class chunk1 ( ); alloc_class chunk2 ( - VSR0 , VSR0_H , VSR0_J , VSR0_K , - VSR1 , VSR1_H , VSR1_J , VSR1_K , - VSR2 , VSR2_H , VSR2_J , VSR2_K , - VSR3 , VSR3_H , VSR3_J , VSR3_K , - VSR4 , VSR4_H , VSR4_J , VSR4_K , - VSR5 , VSR5_H , VSR5_J , VSR5_K , - VSR6 , VSR6_H , VSR6_J , VSR6_K , - VSR7 , VSR7_H , VSR7_J , VSR7_K , - VSR8 , VSR8_H , VSR8_J , VSR8_K , - VSR9 , VSR9_H , VSR9_J , VSR9_K , - VSR10, VSR10_H, VSR10_J, VSR10_K, - VSR11, VSR11_H, VSR11_J, VSR11_K, - VSR12, VSR12_H, VSR12_J, VSR12_K, - VSR13, VSR13_H, VSR13_J, VSR13_K, - VSR14, VSR14_H, VSR14_J, VSR14_K, - VSR15, VSR15_H, VSR15_J, VSR15_K, - VSR16, VSR16_H, VSR16_J, VSR16_K, - VSR17, VSR17_H, VSR17_J, VSR17_K, - VSR18, VSR18_H, VSR18_J, VSR18_K, - VSR19, VSR19_H, VSR19_J, VSR19_K, - VSR20, VSR20_H, VSR20_J, VSR20_K, - VSR21, VSR21_H, VSR21_J, VSR21_K, - VSR22, VSR22_H, VSR22_J, VSR22_K, - VSR23, VSR23_H, VSR23_J, VSR23_K, - VSR24, VSR24_H, VSR24_J, VSR24_K, - VSR25, VSR25_H, VSR25_J, VSR25_K, - VSR26, VSR26_H, VSR26_J, VSR26_K, - VSR27, VSR27_H, VSR27_J, VSR27_K, - VSR28, VSR28_H, VSR28_J, VSR28_K, - VSR29, VSR29_H, VSR29_J, VSR29_K, - VSR30, VSR30_H, VSR30_J, VSR30_K, - VSR31, VSR31_H, VSR31_J, VSR31_K, - VSR32, VSR32_H, VSR32_J, VSR32_K, - VSR33, VSR33_H, VSR33_J, VSR33_K, - VSR34, VSR34_H, VSR34_J, VSR34_K, - VSR35, VSR35_H, VSR35_J, VSR35_K, - VSR36, VSR36_H, VSR36_J, VSR36_K, - VSR37, VSR37_H, VSR37_J, VSR37_K, - VSR38, VSR38_H, VSR38_J, VSR38_K, - VSR39, VSR39_H, VSR39_J, VSR39_K, - VSR40, VSR40_H, VSR40_J, VSR40_K, - VSR41, VSR41_H, VSR41_J, VSR41_K, - VSR42, VSR42_H, VSR42_J, VSR42_K, - VSR43, VSR43_H, VSR43_J, VSR43_K, - VSR44, VSR44_H, VSR44_J, VSR44_K, - VSR45, VSR45_H, VSR45_J, VSR45_K, - VSR46, VSR46_H, VSR46_J, VSR46_K, - VSR47, VSR47_H, VSR47_J, VSR47_K, - VSR48, VSR48_H, VSR48_J, VSR48_K, - VSR49, VSR49_H, VSR49_J, VSR49_K, - VSR50, VSR50_H, VSR50_J, VSR50_K, - VSR51, VSR51_H, VSR51_J, VSR51_K, - VSR52, VSR52_H, VSR52_J, VSR52_K, - VSR53, VSR53_H, VSR53_J, VSR53_K, - VSR54, VSR54_H, VSR54_J, VSR54_K, - VSR55, VSR55_H, VSR55_J, VSR55_K, - VSR56, VSR56_H, VSR56_J, VSR56_K, - VSR57, VSR57_H, VSR57_J, VSR57_K, - VSR58, VSR58_H, VSR58_J, VSR58_K, - VSR59, VSR59_H, VSR59_J, VSR59_K, - VSR60, VSR60_H, VSR60_J, VSR60_K, - VSR61, VSR61_H, VSR61_J, VSR61_K, - VSR62, VSR62_H, VSR62_J, VSR62_K, - VSR63, VSR63_H, VSR63_J, VSR63_K + VR0 , VR0_H , VR0_J , VR0_K , + VR1 , VR1_H , VR1_J , VR1_K , + VR2 , VR2_H , VR2_J , VR2_K , + VR3 , VR3_H , VR3_J , VR3_K , + VR4 , VR4_H , VR4_J , VR4_K , + VR5 , VR5_H , VR5_J , VR5_K , + VR6 , VR6_H , VR6_J , VR6_K , + VR7 , VR7_H , VR7_J , VR7_K , + VR8 , VR8_H , VR8_J , VR8_K , + VR9 , VR9_H , VR9_J , VR9_K , + VR10, VR10_H, VR10_J, VR10_K, + VR11, VR11_H, VR11_J, VR11_K, + VR12, VR12_H, VR12_J, VR12_K, + VR13, VR13_H, VR13_J, VR13_K, + VR14, VR14_H, VR14_J, VR14_K, + VR15, VR15_H, VR15_J, VR15_K, + VR16, VR16_H, VR16_J, VR16_K, + VR17, VR17_H, VR17_J, VR17_K, + VR18, VR18_H, VR18_J, VR18_K, + VR19, VR19_H, VR19_J, VR19_K, + VR20, VR20_H, VR20_J, VR20_K, + VR21, VR21_H, VR21_J, VR21_K, + VR22, VR22_H, VR22_J, VR22_K, + VR23, VR23_H, VR23_J, VR23_K, + VR24, VR24_H, VR24_J, VR24_K, + VR25, VR25_H, VR25_J, VR25_K, + VR26, VR26_H, VR26_J, VR26_K, + VR27, VR27_H, VR27_J, VR27_K, + VR28, VR28_H, VR28_J, VR28_K, + VR29, VR29_H, VR29_J, VR29_K, + VR30, VR30_H, VR30_J, VR30_K, + VR31, VR31_H, VR31_J, VR31_K ); alloc_class chunk3 ( @@ -1163,39 +970,39 @@ reg_class dbl_reg( // Vector-Scalar Register Class // ---------------------------- -reg_class vs_reg( - VSR32, VSR32_H, VSR32_J, VSR32_K, - VSR33, VSR33_H, VSR33_J, VSR33_K, - VSR34, VSR34_H, VSR34_J, VSR34_K, - VSR35, VSR35_H, VSR35_J, VSR35_K, - VSR36, VSR36_H, VSR36_J, VSR36_K, - VSR37, VSR37_H, VSR37_J, VSR37_K, - VSR38, VSR38_H, VSR38_J, VSR38_K, - VSR39, VSR39_H, VSR39_J, VSR39_K, - VSR40, VSR40_H, VSR40_J, VSR40_K, - VSR41, VSR41_H, VSR41_J, VSR41_K, - VSR42, VSR42_H, VSR42_J, VSR42_K, - VSR43, VSR43_H, VSR43_J, VSR43_K, - VSR44, VSR44_H, VSR44_J, VSR44_K, - VSR45, VSR45_H, VSR45_J, VSR45_K, - VSR46, VSR46_H, VSR46_J, VSR46_K, - VSR47, VSR47_H, VSR47_J, VSR47_K, - VSR48, VSR48_H, VSR48_J, VSR48_K, - VSR49, VSR49_H, VSR49_J, VSR49_K, - VSR50, VSR50_H, VSR50_J, VSR50_K, - VSR51, VSR51_H, VSR51_J, VSR51_K, - VSR52, VSR52_H, VSR52_J, VSR52_K, // non-volatile - VSR53, VSR53_H, VSR53_J, VSR53_K, // non-volatile - VSR54, VSR54_H, VSR54_J, VSR54_K, // non-volatile - VSR55, VSR55_H, VSR55_J, VSR55_K, // non-volatile - VSR56, VSR56_H, VSR56_J, VSR56_K, // non-volatile - VSR57, VSR57_H, VSR57_J, VSR57_K, // non-volatile - VSR58, VSR58_H, VSR58_J, VSR58_K, // non-volatile - VSR59, VSR59_H, VSR59_J, VSR59_K, // non-volatile - VSR60, VSR60_H, VSR60_J, VSR60_K, // non-volatile - VSR61, VSR61_H, VSR61_J, VSR61_K, // non-volatile - VSR62, VSR62_H, VSR62_J, VSR62_K, // non-volatile - VSR63, VSR63_H, VSR63_J, VSR63_K // non-volatile +reg_class v_reg( + VR0 , VR0_H , VR0_J , VR0_K , + VR1 , VR1_H , VR1_J , VR1_K , + VR2 , VR2_H , VR2_J , VR2_K , + VR3 , VR3_H , VR3_J , VR3_K , + VR4 , VR4_H , VR4_J , VR4_K , + VR5 , VR5_H , VR5_J , VR5_K , + VR6 , VR6_H , VR6_J , VR6_K , + VR7 , VR7_H , VR7_J , VR7_K , + VR8 , VR8_H , VR8_J , VR8_K , + VR9 , VR9_H , VR9_J , VR9_K , + VR10, VR10_H, VR10_J, VR10_K, + VR11, VR11_H, VR11_J, VR11_K, + VR12, VR12_H, VR12_J, VR12_K, + VR13, VR13_H, VR13_J, VR13_K, + VR14, VR14_H, VR14_J, VR14_K, + VR15, VR15_H, VR15_J, VR15_K, + VR16, VR16_H, VR16_J, VR16_K, + VR17, VR17_H, VR17_J, VR17_K, + VR18, VR18_H, VR18_J, VR18_K, + VR19, VR19_H, VR19_J, VR19_K, + VR20, VR20_H, VR20_J, VR20_K, + VR21, VR21_H, VR21_J, VR21_K, + VR22, VR22_H, VR22_J, VR22_K, + VR23, VR23_H, VR23_J, VR23_K, + VR24, VR24_H, VR24_J, VR24_K, + VR25, VR25_H, VR25_J, VR25_K, + VR26, VR26_H, VR26_J, VR26_K, + VR27, VR27_H, VR27_J, VR27_K, + VR28, VR28_H, VR28_J, VR28_K, + VR29, VR29_H, VR29_J, VR29_K, + VR30, VR30_H, VR30_J, VR30_K, + VR31, VR31_H, VR31_J, VR31_K ); %} @@ -1908,9 +1715,9 @@ const Pipeline * MachEpilogNode::pipeline() const { // ============================================================================= -// Figure out which register class each belongs in: rc_int, rc_float, rc_vs or +// Figure out which register class each belongs in: rc_int, rc_float, rc_vec or // rc_stack. -enum RC { rc_bad, rc_int, rc_float, rc_vs, rc_stack }; +enum RC { rc_bad, rc_int, rc_float, rc_vec, rc_stack }; static enum RC rc_class(OptoReg::Name reg) { // Return the register class for the given register. The given register @@ -1924,12 +1731,12 @@ static enum RC rc_class(OptoReg::Name reg) { if (reg < ConcreteRegisterImpl::max_gpr) return rc_int; // We have 64 floating-point register halves, starting at index 64. - STATIC_ASSERT((int)ConcreteRegisterImpl::max_fpr == (int)MachRegisterNumbers::VSR0_num); + STATIC_ASSERT((int)ConcreteRegisterImpl::max_fpr == (int)MachRegisterNumbers::VR0_num); if (reg < ConcreteRegisterImpl::max_fpr) return rc_float; // We have 64 vector-scalar registers, starting at index 128. - STATIC_ASSERT((int)ConcreteRegisterImpl::max_vsr == (int)MachRegisterNumbers::CR0_num); - if (reg < ConcreteRegisterImpl::max_vsr) return rc_vs; + STATIC_ASSERT((int)ConcreteRegisterImpl::max_vr == (int)MachRegisterNumbers::CR0_num); + if (reg < ConcreteRegisterImpl::max_vr) return rc_vec; // Condition and special purpose registers are not allocated. We only accept stack from here. assert(OptoReg::is_stack(reg), "what else is it?"); @@ -2005,9 +1812,9 @@ uint MachSpillCopyNode::implementation(C2_MacroAssembler *masm, PhaseRegAlloc *r } size += 16; } - // VectorSRegister->Memory Spill. - else if (src_lo_rc == rc_vs && dst_lo_rc == rc_stack) { - VectorSRegister Rsrc = as_VectorSRegister(Matcher::_regEncode[src_lo]); + // VectorRegister->Memory Spill. + else if (src_lo_rc == rc_vec && dst_lo_rc == rc_stack) { + VectorSRegister Rsrc = as_VectorRegister(Matcher::_regEncode[src_lo]).to_vsr(); int dst_offset = ra_->reg2offset(dst_lo); if (PowerArchitecturePPC64 >= 9) { if (is_aligned(dst_offset, 16)) { @@ -2032,9 +1839,9 @@ uint MachSpillCopyNode::implementation(C2_MacroAssembler *masm, PhaseRegAlloc *r size += 8; } } - // Memory->VectorSRegister Spill. - else if (src_lo_rc == rc_stack && dst_lo_rc == rc_vs) { - VectorSRegister Rdst = as_VectorSRegister(Matcher::_regEncode[dst_lo]); + // Memory->VectorRegister Spill. + else if (src_lo_rc == rc_stack && dst_lo_rc == rc_vec) { + VectorSRegister Rdst = as_VectorRegister(Matcher::_regEncode[dst_lo]).to_vsr(); int src_offset = ra_->reg2offset(src_lo); if (PowerArchitecturePPC64 >= 9) { if (is_aligned(src_offset, 16)) { @@ -2057,17 +1864,17 @@ uint MachSpillCopyNode::implementation(C2_MacroAssembler *masm, PhaseRegAlloc *r size += 8; } } - // VectorSRegister->VectorSRegister. - else if (src_lo_rc == rc_vs && dst_lo_rc == rc_vs) { - VectorSRegister Rsrc = as_VectorSRegister(Matcher::_regEncode[src_lo]); - VectorSRegister Rdst = as_VectorSRegister(Matcher::_regEncode[dst_lo]); + // VectorRegister->VectorRegister. + else if (src_lo_rc == rc_vec && dst_lo_rc == rc_vec) { + VectorSRegister Rsrc = as_VectorRegister(Matcher::_regEncode[src_lo]).to_vsr(); + VectorSRegister Rdst = as_VectorRegister(Matcher::_regEncode[dst_lo]).to_vsr(); if (masm) { __ xxlor(Rdst, Rsrc, Rsrc); } size += 4; } else { - ShouldNotReachHere(); // No VSR spill. + ShouldNotReachHere(); // No VR spill. } return size; } @@ -4044,7 +3851,7 @@ ins_attrib ins_field_load_ic_node(0); // Formats are generated automatically for constants and base registers. operand vecX() %{ - constraint(ALLOC_IN_RC(vs_reg)); + constraint(ALLOC_IN_RC(v_reg)); match(VecX); format %{ %} @@ -5620,7 +5427,7 @@ instruct loadV16_Power8(vecX dst, indirect mem) %{ format %{ "LXVD2X $dst, $mem \t// load 16-byte Vector" %} size(4); ins_encode %{ - __ lxvd2x($dst$$VectorSRegister, $mem$$Register); + __ lxvd2x($dst$$VectorRegister.to_vsr(), $mem$$Register); %} ins_pipe(pipe_class_default); %} @@ -5633,7 +5440,7 @@ instruct loadV16_Power9(vecX dst, memoryAlg16 mem) %{ format %{ "LXV $dst, $mem \t// load 16-byte Vector" %} size(4); ins_encode %{ - __ lxv($dst$$VectorSRegister, $mem$$disp, $mem$$Register); + __ lxv($dst$$VectorRegister.to_vsr(), $mem$$disp, $mem$$Register); %} ins_pipe(pipe_class_default); %} @@ -6660,7 +6467,7 @@ instruct storeV16_Power8(indirect mem, vecX src) %{ format %{ "STXVD2X $mem, $src \t// store 16-byte Vector" %} size(4); ins_encode %{ - __ stxvd2x($src$$VectorSRegister, $mem$$Register); + __ stxvd2x($src$$VectorRegister.to_vsr(), $mem$$Register); %} ins_pipe(pipe_class_default); %} @@ -6673,7 +6480,7 @@ instruct storeV16_Power9(memoryAlg16 mem, vecX src) %{ format %{ "STXV $mem, $src \t// store 16-byte Vector" %} size(4); ins_encode %{ - __ stxv($src$$VectorSRegister, $mem$$disp, $mem$$Register); + __ stxv($src$$VectorRegister.to_vsr(), $mem$$disp, $mem$$Register); %} ins_pipe(pipe_class_default); %} @@ -12645,9 +12452,9 @@ instruct bytes_reverse_int_vec(iRegIdst dst, iRegIsrc src, vecX tmpV) %{ "\tMFVSRWZ $dst, $tmpV" %} ins_encode %{ - __ mtvsrwz($tmpV$$VectorSRegister, $src$$Register); - __ xxbrw($tmpV$$VectorSRegister, $tmpV$$VectorSRegister); - __ mfvsrwz($dst$$Register, $tmpV$$VectorSRegister); + __ mtvsrwz($tmpV$$VectorRegister.to_vsr(), $src$$Register); + __ xxbrw($tmpV$$VectorRegister.to_vsr(), $tmpV$$VectorRegister->to_vsr()); + __ mfvsrwz($dst$$Register, $tmpV$$VectorRegister->to_vsr()); %} ins_pipe(pipe_class_default); %} @@ -12717,9 +12524,9 @@ instruct bytes_reverse_long_vec(iRegLdst dst, iRegLsrc src, vecX tmpV) %{ "\tMFVSRD $dst, $tmpV" %} ins_encode %{ - __ mtvsrd($tmpV$$VectorSRegister, $src$$Register); - __ xxbrd($tmpV$$VectorSRegister, $tmpV$$VectorSRegister); - __ mfvsrd($dst$$Register, $tmpV$$VectorSRegister); + __ mtvsrd($tmpV$$VectorRegister->to_vsr(), $src$$Register); + __ xxbrd($tmpV$$VectorRegister->to_vsr(), $tmpV$$VectorRegister->to_vsr()); + __ mfvsrd($dst$$Register, $tmpV$$VectorRegister->to_vsr()); %} ins_pipe(pipe_class_default); %} @@ -12958,7 +12765,7 @@ instruct mtvsrwz(vecX temp1, iRegIsrc src) %{ format %{ "MTVSRWZ $temp1, $src \t// Move to 16-byte register" %} size(4); ins_encode %{ - __ mtvsrwz($temp1$$VectorSRegister, $src$$Register); + __ mtvsrwz($temp1$$VectorRegister->to_vsr(), $src$$Register); %} ins_pipe(pipe_class_default); %} @@ -12969,7 +12776,7 @@ instruct xxspltw(vecX dst, vecX src, immI8 imm1) %{ format %{ "XXSPLTW $dst, $src, $imm1 \t// Splat word" %} size(4); ins_encode %{ - __ xxspltw($dst$$VectorSRegister, $src$$VectorSRegister, $imm1$$constant); + __ xxspltw($dst$$VectorRegister->to_vsr(), $src$$VectorRegister->to_vsr(), $imm1$$constant); %} ins_pipe(pipe_class_default); %} @@ -12980,7 +12787,7 @@ instruct xscvdpspn_regF(vecX dst, regF src) %{ format %{ "XSCVDPSPN $dst, $src \t// Convert scalar single precision to vector single precision" %} size(4); ins_encode %{ - __ xscvdpspn($dst$$VectorSRegister, $src$$FloatRegister->to_vsr()); + __ xscvdpspn($dst$$VectorRegister->to_vsr(), $src$$FloatRegister->to_vsr()); %} ins_pipe(pipe_class_default); %} @@ -13087,7 +12894,7 @@ instruct repl16B_immI0(vecX dst, immI_0 zero) %{ format %{ "XXLXOR $dst, $zero \t// replicate16B" %} size(4); ins_encode %{ - __ xxlxor($dst$$VectorSRegister, $dst$$VectorSRegister, $dst$$VectorSRegister); + __ xxlxor($dst$$VectorRegister->to_vsr(), $dst$$VectorRegister->to_vsr(), $dst$$VectorRegister->to_vsr()); %} ins_pipe(pipe_class_default); %} @@ -13100,7 +12907,7 @@ instruct repl16B_immIminus1(vecX dst, immI_minus1 src) %{ format %{ "XXLEQV $dst, $src \t// replicate16B" %} size(4); ins_encode %{ - __ xxleqv($dst$$VectorSRegister, $dst$$VectorSRegister, $dst$$VectorSRegister); + __ xxleqv($dst$$VectorRegister->to_vsr(), $dst$$VectorRegister->to_vsr(), $dst$$VectorRegister->to_vsr()); %} ins_pipe(pipe_class_default); %} @@ -13165,7 +12972,7 @@ instruct repl8S_immI0(vecX dst, immI_0 zero) %{ format %{ "XXLXOR $dst, $zero \t// replicate8S" %} size(4); ins_encode %{ - __ xxlxor($dst$$VectorSRegister, $dst$$VectorSRegister, $dst$$VectorSRegister); + __ xxlxor($dst$$VectorRegister->to_vsr(), $dst$$VectorRegister->to_vsr(), $dst$$VectorRegister->to_vsr()); %} ins_pipe(pipe_class_default); %} @@ -13178,7 +12985,7 @@ instruct repl8S_immIminus1(vecX dst, immI_minus1 src) %{ format %{ "XXLEQV $dst, $src \t// replicate8S" %} size(4); ins_encode %{ - __ xxleqv($dst$$VectorSRegister, $dst$$VectorSRegister, $dst$$VectorSRegister); + __ xxleqv($dst$$VectorRegister->to_vsr(), $dst$$VectorRegister->to_vsr(), $dst$$VectorRegister->to_vsr()); %} ins_pipe(pipe_class_default); %} @@ -13243,7 +13050,7 @@ instruct repl4I_immI0(vecX dst, immI_0 zero) %{ format %{ "XXLXOR $dst, $zero \t// replicate4I" %} size(4); ins_encode %{ - __ xxlxor($dst$$VectorSRegister, $dst$$VectorSRegister, $dst$$VectorSRegister); + __ xxlxor($dst$$VectorRegister->to_vsr(), $dst$$VectorRegister->to_vsr(), $dst$$VectorRegister->to_vsr()); %} ins_pipe(pipe_class_default); %} @@ -13256,7 +13063,7 @@ instruct repl4I_immIminus1(vecX dst, immI_minus1 src) %{ format %{ "XXLEQV $dst, $dst, $dst \t// replicate4I" %} size(4); ins_encode %{ - __ xxleqv($dst$$VectorSRegister, $dst$$VectorSRegister, $dst$$VectorSRegister); + __ xxleqv($dst$$VectorRegister->to_vsr(), $dst$$VectorRegister->to_vsr(), $dst$$VectorRegister->to_vsr()); %} ins_pipe(pipe_class_default); %} @@ -13312,7 +13119,7 @@ instruct vadd16B_reg(vecX dst, vecX src1, vecX src2) %{ format %{ "VADDUBM $dst,$src1,$src2\t// add packed16B" %} size(4); ins_encode %{ - __ vaddubm($dst$$VectorSRegister->to_vr(), $src1$$VectorSRegister->to_vr(), $src2$$VectorSRegister->to_vr()); + __ vaddubm($dst$$VectorRegister, $src1$$VectorRegister, $src2$$VectorRegister); %} ins_pipe(pipe_class_default); %} @@ -13323,7 +13130,7 @@ instruct vadd8S_reg(vecX dst, vecX src1, vecX src2) %{ format %{ "VADDUHM $dst,$src1,$src2\t// add packed8S" %} size(4); ins_encode %{ - __ vadduhm($dst$$VectorSRegister->to_vr(), $src1$$VectorSRegister->to_vr(), $src2$$VectorSRegister->to_vr()); + __ vadduhm($dst$$VectorRegister, $src1$$VectorRegister, $src2$$VectorRegister); %} ins_pipe(pipe_class_default); %} @@ -13334,7 +13141,7 @@ instruct vadd4I_reg(vecX dst, vecX src1, vecX src2) %{ format %{ "VADDUWM $dst,$src1,$src2\t// add packed4I" %} size(4); ins_encode %{ - __ vadduwm($dst$$VectorSRegister->to_vr(), $src1$$VectorSRegister->to_vr(), $src2$$VectorSRegister->to_vr()); + __ vadduwm($dst$$VectorRegister, $src1$$VectorRegister, $src2$$VectorRegister); %} ins_pipe(pipe_class_default); %} @@ -13345,7 +13152,7 @@ instruct vadd4F_reg(vecX dst, vecX src1, vecX src2) %{ format %{ "VADDFP $dst,$src1,$src2\t// add packed4F" %} size(4); ins_encode %{ - __ vaddfp($dst$$VectorSRegister->to_vr(), $src1$$VectorSRegister->to_vr(), $src2$$VectorSRegister->to_vr()); + __ vaddfp($dst$$VectorRegister, $src1$$VectorRegister, $src2$$VectorRegister); %} ins_pipe(pipe_class_default); %} @@ -13356,7 +13163,7 @@ instruct vadd2L_reg(vecX dst, vecX src1, vecX src2) %{ format %{ "VADDUDM $dst,$src1,$src2\t// add packed2L" %} size(4); ins_encode %{ - __ vaddudm($dst$$VectorSRegister->to_vr(), $src1$$VectorSRegister->to_vr(), $src2$$VectorSRegister->to_vr()); + __ vaddudm($dst$$VectorRegister, $src1$$VectorRegister, $src2$$VectorRegister); %} ins_pipe(pipe_class_default); %} @@ -13367,7 +13174,7 @@ instruct vadd2D_reg(vecX dst, vecX src1, vecX src2) %{ format %{ "XVADDDP $dst,$src1,$src2\t// add packed2D" %} size(4); ins_encode %{ - __ xvadddp($dst$$VectorSRegister, $src1$$VectorSRegister, $src2$$VectorSRegister); + __ xvadddp($dst$$VectorRegister->to_vsr(), $src1$$VectorRegister->to_vsr(), $src2$$VectorRegister->to_vsr()); %} ins_pipe(pipe_class_default); %} @@ -13380,7 +13187,7 @@ instruct vsub16B_reg(vecX dst, vecX src1, vecX src2) %{ format %{ "VSUBUBM $dst,$src1,$src2\t// sub packed16B" %} size(4); ins_encode %{ - __ vsububm($dst$$VectorSRegister->to_vr(), $src1$$VectorSRegister->to_vr(), $src2$$VectorSRegister->to_vr()); + __ vsububm($dst$$VectorRegister, $src1$$VectorRegister, $src2$$VectorRegister); %} ins_pipe(pipe_class_default); %} @@ -13391,7 +13198,7 @@ instruct vsub8S_reg(vecX dst, vecX src1, vecX src2) %{ format %{ "VSUBUHM $dst,$src1,$src2\t// sub packed8S" %} size(4); ins_encode %{ - __ vsubuhm($dst$$VectorSRegister->to_vr(), $src1$$VectorSRegister->to_vr(), $src2$$VectorSRegister->to_vr()); + __ vsubuhm($dst$$VectorRegister, $src1$$VectorRegister, $src2$$VectorRegister); %} ins_pipe(pipe_class_default); %} @@ -13402,7 +13209,7 @@ instruct vsub4I_reg(vecX dst, vecX src1, vecX src2) %{ format %{ "VSUBUWM $dst,$src1,$src2\t// sub packed4I" %} size(4); ins_encode %{ - __ vsubuwm($dst$$VectorSRegister->to_vr(), $src1$$VectorSRegister->to_vr(), $src2$$VectorSRegister->to_vr()); + __ vsubuwm($dst$$VectorRegister, $src1$$VectorRegister, $src2$$VectorRegister); %} ins_pipe(pipe_class_default); %} @@ -13413,7 +13220,7 @@ instruct vsub4F_reg(vecX dst, vecX src1, vecX src2) %{ format %{ "VSUBFP $dst,$src1,$src2\t// sub packed4F" %} size(4); ins_encode %{ - __ vsubfp($dst$$VectorSRegister->to_vr(), $src1$$VectorSRegister->to_vr(), $src2$$VectorSRegister->to_vr()); + __ vsubfp($dst$$VectorRegister, $src1$$VectorRegister, $src2$$VectorRegister); %} ins_pipe(pipe_class_default); %} @@ -13424,7 +13231,7 @@ instruct vsub2L_reg(vecX dst, vecX src1, vecX src2) %{ format %{ "VSUBUDM $dst,$src1,$src2\t// sub packed2L" %} size(4); ins_encode %{ - __ vsubudm($dst$$VectorSRegister->to_vr(), $src1$$VectorSRegister->to_vr(), $src2$$VectorSRegister->to_vr()); + __ vsubudm($dst$$VectorRegister, $src1$$VectorRegister, $src2$$VectorRegister); %} ins_pipe(pipe_class_default); %} @@ -13435,7 +13242,7 @@ instruct vsub2D_reg(vecX dst, vecX src1, vecX src2) %{ format %{ "XVSUBDP $dst,$src1,$src2\t// sub packed2D" %} size(4); ins_encode %{ - __ xvsubdp($dst$$VectorSRegister, $src1$$VectorSRegister, $src2$$VectorSRegister); + __ xvsubdp($dst$$VectorRegister->to_vsr(), $src1$$VectorRegister->to_vsr(), $src2$$VectorRegister->to_vsr()); %} ins_pipe(pipe_class_default); %} @@ -13450,8 +13257,8 @@ instruct vmul8S_reg(vecX dst, vecX src1, vecX src2, vecX tmp) %{ format %{ "VMLADDUHM $dst,$src1,$src2\t// mul packed8S" %} size(8); ins_encode %{ - __ vspltish($tmp$$VectorSRegister->to_vr(), 0); - __ vmladduhm($dst$$VectorSRegister->to_vr(), $src1$$VectorSRegister->to_vr(), $src2$$VectorSRegister->to_vr(), $tmp$$VectorSRegister->to_vr()); + __ vspltish($tmp$$VectorRegister, 0); + __ vmladduhm($dst$$VectorRegister, $src1$$VectorRegister, $src2$$VectorRegister, $tmp$$VectorRegister); %} ins_pipe(pipe_class_default); %} @@ -13462,7 +13269,7 @@ instruct vmul4I_reg(vecX dst, vecX src1, vecX src2) %{ format %{ "VMULUWM $dst,$src1,$src2\t// mul packed4I" %} size(4); ins_encode %{ - __ vmuluwm($dst$$VectorSRegister->to_vr(), $src1$$VectorSRegister->to_vr(), $src2$$VectorSRegister->to_vr()); + __ vmuluwm($dst$$VectorRegister, $src1$$VectorRegister, $src2$$VectorRegister); %} ins_pipe(pipe_class_default); %} @@ -13473,7 +13280,7 @@ instruct vmul4F_reg(vecX dst, vecX src1, vecX src2) %{ format %{ "XVMULSP $dst,$src1,$src2\t// mul packed4F" %} size(4); ins_encode %{ - __ xvmulsp($dst$$VectorSRegister, $src1$$VectorSRegister, $src2$$VectorSRegister); + __ xvmulsp($dst$$VectorRegister->to_vsr(), $src1$$VectorRegister->to_vsr(), $src2$$VectorRegister->to_vsr()); %} ins_pipe(pipe_class_default); %} @@ -13484,7 +13291,7 @@ instruct vmul2D_reg(vecX dst, vecX src1, vecX src2) %{ format %{ "XVMULDP $dst,$src1,$src2\t// mul packed2D" %} size(4); ins_encode %{ - __ xvmuldp($dst$$VectorSRegister, $src1$$VectorSRegister, $src2$$VectorSRegister); + __ xvmuldp($dst$$VectorRegister->to_vsr(), $src1$$VectorRegister->to_vsr(), $src2$$VectorRegister->to_vsr()); %} ins_pipe(pipe_class_default); %} @@ -13497,7 +13304,7 @@ instruct vdiv4F_reg(vecX dst, vecX src1, vecX src2) %{ format %{ "XVDIVSP $dst,$src1,$src2\t// div packed4F" %} size(4); ins_encode %{ - __ xvdivsp($dst$$VectorSRegister, $src1$$VectorSRegister, $src2$$VectorSRegister); + __ xvdivsp($dst$$VectorRegister->to_vsr(), $src1$$VectorRegister->to_vsr(), $src2$$VectorRegister->to_vsr()); %} ins_pipe(pipe_class_default); %} @@ -13508,7 +13315,7 @@ instruct vdiv2D_reg(vecX dst, vecX src1, vecX src2) %{ format %{ "XVDIVDP $dst,$src1,$src2\t// div packed2D" %} size(4); ins_encode %{ - __ xvdivdp($dst$$VectorSRegister, $src1$$VectorSRegister, $src2$$VectorSRegister); + __ xvdivdp($dst$$VectorRegister->to_vsr(), $src1$$VectorRegister->to_vsr(), $src2$$VectorRegister->to_vsr()); %} ins_pipe(pipe_class_default); %} @@ -13523,10 +13330,10 @@ instruct vmin_reg(vecX dst, vecX src1, vecX src2) %{ BasicType bt = Matcher::vector_element_basic_type(this); switch (bt) { case T_INT: - __ vminsw($dst$$VectorSRegister->to_vr(), $src1$$VectorSRegister->to_vr(), $src2$$VectorSRegister->to_vr()); + __ vminsw($dst$$VectorRegister, $src1$$VectorRegister, $src2$$VectorRegister); break; case T_LONG: - __ vminsd($dst$$VectorSRegister->to_vr(), $src1$$VectorSRegister->to_vr(), $src2$$VectorSRegister->to_vr()); + __ vminsd($dst$$VectorRegister, $src1$$VectorRegister, $src2$$VectorRegister); break; default: ShouldNotReachHere(); @@ -13543,10 +13350,10 @@ instruct vmax_reg(vecX dst, vecX src1, vecX src2) %{ BasicType bt = Matcher::vector_element_basic_type(this); switch (bt) { case T_INT: - __ vmaxsw($dst$$VectorSRegister->to_vr(), $src1$$VectorSRegister->to_vr(), $src2$$VectorSRegister->to_vr()); + __ vmaxsw($dst$$VectorRegister, $src1$$VectorRegister, $src2$$VectorRegister); break; case T_LONG: - __ vmaxsd($dst$$VectorSRegister->to_vr(), $src1$$VectorSRegister->to_vr(), $src2$$VectorSRegister->to_vr()); + __ vmaxsd($dst$$VectorRegister, $src1$$VectorRegister, $src2$$VectorRegister); break; default: ShouldNotReachHere(); @@ -13560,7 +13367,7 @@ instruct vand(vecX dst, vecX src1, vecX src2) %{ size(4); format %{ "VAND $dst,$src1,$src2\t// and vectors" %} ins_encode %{ - __ vand($dst$$VectorSRegister->to_vr(), $src1$$VectorSRegister->to_vr(), $src2$$VectorSRegister->to_vr()); + __ vand($dst$$VectorRegister, $src1$$VectorRegister, $src2$$VectorRegister); %} ins_pipe(pipe_class_default); %} @@ -13570,7 +13377,7 @@ instruct vor(vecX dst, vecX src1, vecX src2) %{ size(4); format %{ "VOR $dst,$src1,$src2\t// or vectors" %} ins_encode %{ - __ vor($dst$$VectorSRegister->to_vr(), $src1$$VectorSRegister->to_vr(), $src2$$VectorSRegister->to_vr()); + __ vor($dst$$VectorRegister, $src1$$VectorRegister, $src2$$VectorRegister); %} ins_pipe(pipe_class_default); %} @@ -13580,7 +13387,7 @@ instruct vxor(vecX dst, vecX src1, vecX src2) %{ size(4); format %{ "VXOR $dst,$src1,$src2\t// xor vectors" %} ins_encode %{ - __ vxor($dst$$VectorSRegister->to_vr(), $src1$$VectorSRegister->to_vr(), $src2$$VectorSRegister->to_vr()); + __ vxor($dst$$VectorRegister, $src1$$VectorRegister, $src2$$VectorRegister); %} ins_pipe(pipe_class_default); %} @@ -13598,8 +13405,8 @@ instruct reductionI_arith_logic(iRegIdst dst, iRegIsrc srcInt, vecX srcVec, vecX size(24); ins_encode %{ int opcode = this->ideal_Opcode(); - __ reduceI(opcode, $dst$$Register, $srcInt$$Register, $srcVec$$VectorSRegister->to_vr(), - $tmp1$$VectorSRegister->to_vr(), $tmp2$$VectorSRegister->to_vr()); + __ reduceI(opcode, $dst$$Register, $srcInt$$Register, $srcVec$$VectorRegister, + $tmp1$$VectorRegister, $tmp2$$VectorRegister); %} ins_pipe(pipe_class_default); %} @@ -13614,8 +13421,8 @@ instruct reductionI_min_max(iRegIdst dst, iRegIsrc srcInt, vecX srcVec, vecX tmp size(28); ins_encode %{ int opcode = this->ideal_Opcode(); - __ reduceI(opcode, $dst$$Register, $srcInt$$Register, $srcVec$$VectorSRegister->to_vr(), - $tmp1$$VectorSRegister->to_vr(), $tmp2$$VectorSRegister->to_vr()); + __ reduceI(opcode, $dst$$Register, $srcInt$$Register, $srcVec$$VectorRegister, + $tmp1$$VectorRegister, $tmp2$$VectorRegister); %} ins_pipe(pipe_class_default); %} @@ -13628,7 +13435,7 @@ instruct vabs4F_reg(vecX dst, vecX src) %{ format %{ "XVABSSP $dst,$src\t// absolute packed4F" %} size(4); ins_encode %{ - __ xvabssp($dst$$VectorSRegister, $src$$VectorSRegister); + __ xvabssp($dst$$VectorRegister->to_vsr(), $src$$VectorRegister->to_vsr()); %} ins_pipe(pipe_class_default); %} @@ -13639,7 +13446,7 @@ instruct vabs2D_reg(vecX dst, vecX src) %{ format %{ "XVABSDP $dst,$src\t// absolute packed2D" %} size(4); ins_encode %{ - __ xvabsdp($dst$$VectorSRegister, $src$$VectorSRegister); + __ xvabsdp($dst$$VectorRegister->to_vsr(), $src$$VectorRegister->to_vsr()); %} ins_pipe(pipe_class_default); %} @@ -13676,13 +13483,13 @@ instruct vround2D_reg(vecX dst, vecX src, immI8 rmode) %{ ins_encode %{ switch ($rmode$$constant) { case RoundDoubleModeNode::rmode_rint: - __ xvrdpic($dst$$VectorSRegister, $src$$VectorSRegister); + __ xvrdpic($dst$$VectorRegister->to_vsr(), $src$$VectorRegister->to_vsr()); break; case RoundDoubleModeNode::rmode_floor: - __ xvrdpim($dst$$VectorSRegister, $src$$VectorSRegister); + __ xvrdpim($dst$$VectorRegister->to_vsr(), $src$$VectorRegister->to_vsr()); break; case RoundDoubleModeNode::rmode_ceil: - __ xvrdpip($dst$$VectorSRegister, $src$$VectorSRegister); + __ xvrdpip($dst$$VectorRegister->to_vsr(), $src$$VectorRegister->to_vsr()); break; default: ShouldNotReachHere(); @@ -13699,7 +13506,7 @@ instruct vneg4F_reg(vecX dst, vecX src) %{ format %{ "XVNEGSP $dst,$src\t// negate packed4F" %} size(4); ins_encode %{ - __ xvnegsp($dst$$VectorSRegister, $src$$VectorSRegister); + __ xvnegsp($dst$$VectorRegister->to_vsr(), $src$$VectorRegister->to_vsr()); %} ins_pipe(pipe_class_default); %} @@ -13710,7 +13517,7 @@ instruct vneg2D_reg(vecX dst, vecX src) %{ format %{ "XVNEGDP $dst,$src\t// negate packed2D" %} size(4); ins_encode %{ - __ xvnegdp($dst$$VectorSRegister, $src$$VectorSRegister); + __ xvnegdp($dst$$VectorRegister->to_vsr(), $src$$VectorRegister->to_vsr()); %} ins_pipe(pipe_class_default); %} @@ -13723,7 +13530,7 @@ instruct vsqrt4F_reg(vecX dst, vecX src) %{ format %{ "XVSQRTSP $dst,$src\t// sqrt packed4F" %} size(4); ins_encode %{ - __ xvsqrtsp($dst$$VectorSRegister, $src$$VectorSRegister); + __ xvsqrtsp($dst$$VectorRegister->to_vsr(), $src$$VectorRegister->to_vsr()); %} ins_pipe(pipe_class_default); %} @@ -13734,7 +13541,7 @@ instruct vsqrt2D_reg(vecX dst, vecX src) %{ format %{ "XVSQRTDP $dst,$src\t// sqrt packed2D" %} size(4); ins_encode %{ - __ xvsqrtdp($dst$$VectorSRegister, $src$$VectorSRegister); + __ xvsqrtdp($dst$$VectorRegister->to_vsr(), $src$$VectorRegister->to_vsr()); %} ins_pipe(pipe_class_default); %} @@ -13750,16 +13557,16 @@ instruct vpopcnt_reg(vecX dst, vecX src) %{ BasicType bt = Matcher::vector_element_basic_type(this); switch (bt) { case T_BYTE: - __ vpopcntb($dst$$VectorSRegister->to_vr(), $src$$VectorSRegister->to_vr()); + __ vpopcntb($dst$$VectorRegister, $src$$VectorRegister); break; case T_SHORT: - __ vpopcnth($dst$$VectorSRegister->to_vr(), $src$$VectorSRegister->to_vr()); + __ vpopcnth($dst$$VectorRegister, $src$$VectorRegister); break; case T_INT: - __ vpopcntw($dst$$VectorSRegister->to_vr(), $src$$VectorSRegister->to_vr()); + __ vpopcntw($dst$$VectorRegister, $src$$VectorRegister); break; case T_LONG: - __ vpopcntd($dst$$VectorSRegister->to_vr(), $src$$VectorSRegister->to_vr()); + __ vpopcntd($dst$$VectorRegister, $src$$VectorRegister); break; default: ShouldNotReachHere(); @@ -13776,16 +13583,16 @@ instruct vcount_leading_zeros_reg(vecX dst, vecX src) %{ BasicType bt = Matcher::vector_element_basic_type(this); switch (bt) { case T_BYTE: - __ vclzb($dst$$VectorSRegister->to_vr(), $src$$VectorSRegister->to_vr()); + __ vclzb($dst$$VectorRegister, $src$$VectorRegister); break; case T_SHORT: - __ vclzh($dst$$VectorSRegister->to_vr(), $src$$VectorSRegister->to_vr()); + __ vclzh($dst$$VectorRegister, $src$$VectorRegister); break; case T_INT: - __ vclzw($dst$$VectorSRegister->to_vr(), $src$$VectorSRegister->to_vr()); + __ vclzw($dst$$VectorRegister, $src$$VectorRegister); break; case T_LONG: - __ vclzd($dst$$VectorSRegister->to_vr(), $src$$VectorSRegister->to_vr()); + __ vclzd($dst$$VectorRegister, $src$$VectorRegister); break; default: ShouldNotReachHere(); @@ -13802,16 +13609,16 @@ instruct vcount_trailing_zeros_reg(vecX dst, vecX src) %{ BasicType bt = Matcher::vector_element_basic_type(this); switch (bt) { case T_BYTE: - __ vctzb($dst$$VectorSRegister->to_vr(), $src$$VectorSRegister->to_vr()); + __ vctzb($dst$$VectorRegister, $src$$VectorRegister); break; case T_SHORT: - __ vctzh($dst$$VectorSRegister->to_vr(), $src$$VectorSRegister->to_vr()); + __ vctzh($dst$$VectorRegister, $src$$VectorRegister); break; case T_INT: - __ vctzw($dst$$VectorSRegister->to_vr(), $src$$VectorSRegister->to_vr()); + __ vctzw($dst$$VectorRegister, $src$$VectorRegister); break; case T_LONG: - __ vctzd($dst$$VectorSRegister->to_vr(), $src$$VectorSRegister->to_vr()); + __ vctzd($dst$$VectorRegister, $src$$VectorRegister); break; default: ShouldNotReachHere(); @@ -13831,7 +13638,7 @@ instruct vfma4F(vecX dst, vecX src1, vecX src2) %{ size(4); ins_encode %{ assert(UseFMA, "Needs FMA instructions support."); - __ xvmaddasp($dst$$VectorSRegister, $src1$$VectorSRegister, $src2$$VectorSRegister); + __ xvmaddasp($dst$$VectorRegister->to_vsr(), $src1$$VectorRegister->to_vsr(), $src2$$VectorRegister->to_vsr()); %} ins_pipe(pipe_class_default); %} @@ -13847,7 +13654,7 @@ instruct vfma4F_neg1(vecX dst, vecX src1, vecX src2) %{ size(4); ins_encode %{ assert(UseFMA, "Needs FMA instructions support."); - __ xvnmsubasp($dst$$VectorSRegister, $src1$$VectorSRegister, $src2$$VectorSRegister); + __ xvnmsubasp($dst$$VectorRegister->to_vsr(), $src1$$VectorRegister->to_vsr(), $src2$$VectorRegister->to_vsr()); %} ins_pipe(pipe_class_default); %} @@ -13862,7 +13669,7 @@ instruct vfma4F_neg2(vecX dst, vecX src1, vecX src2) %{ size(4); ins_encode %{ assert(UseFMA, "Needs FMA instructions support."); - __ xvmsubasp($dst$$VectorSRegister, $src1$$VectorSRegister, $src2$$VectorSRegister); + __ xvmsubasp($dst$$VectorRegister->to_vsr(), $src1$$VectorRegister->to_vsr(), $src2$$VectorRegister->to_vsr()); %} ins_pipe(pipe_class_default); %} @@ -13877,7 +13684,7 @@ instruct vfma2D(vecX dst, vecX src1, vecX src2) %{ size(4); ins_encode %{ assert(UseFMA, "Needs FMA instructions support."); - __ xvmaddadp($dst$$VectorSRegister, $src1$$VectorSRegister, $src2$$VectorSRegister); + __ xvmaddadp($dst$$VectorRegister->to_vsr(), $src1$$VectorRegister->to_vsr(), $src2$$VectorRegister->to_vsr()); %} ins_pipe(pipe_class_default); %} @@ -13893,7 +13700,7 @@ instruct vfma2D_neg1(vecX dst, vecX src1, vecX src2) %{ size(4); ins_encode %{ assert(UseFMA, "Needs FMA instructions support."); - __ xvnmsubadp($dst$$VectorSRegister, $src1$$VectorSRegister, $src2$$VectorSRegister); + __ xvnmsubadp($dst$$VectorRegister->to_vsr(), $src1$$VectorRegister->to_vsr(), $src2$$VectorRegister->to_vsr()); %} ins_pipe(pipe_class_default); %} @@ -13908,7 +13715,7 @@ instruct vfma2D_neg2(vecX dst, vecX src1, vecX src2) %{ size(4); ins_encode %{ assert(UseFMA, "Needs FMA instructions support."); - __ xvmsubadp($dst$$VectorSRegister, $src1$$VectorSRegister, $src2$$VectorSRegister); + __ xvmsubadp($dst$$VectorRegister->to_vsr(), $src1$$VectorRegister->to_vsr(), $src2$$VectorRegister->to_vsr()); %} ins_pipe(pipe_class_default); %} @@ -13998,7 +13805,7 @@ instruct repl4F_immF0(vecX dst, immF_0 zero) %{ format %{ "XXLXOR $dst, $zero \t// replicate4F" %} ins_encode %{ - __ xxlxor($dst$$VectorSRegister, $dst$$VectorSRegister, $dst$$VectorSRegister); + __ xxlxor($dst$$VectorRegister->to_vsr(), $dst$$VectorRegister->to_vsr(), $dst$$VectorRegister->to_vsr()); %} ins_pipe(pipe_class_default); %} @@ -14011,7 +13818,7 @@ instruct repl2D_reg_Ex(vecX dst, regD src) %{ format %{ "XXPERMDI $dst, $src, $src, 0 \t// Splat doubleword" %} size(4); ins_encode %{ - __ xxpermdi($dst$$VectorSRegister, $src$$FloatRegister->to_vsr(), $src$$FloatRegister->to_vsr(), 0); + __ xxpermdi($dst$$VectorRegister->to_vsr(), $src$$FloatRegister->to_vsr(), $src$$FloatRegister->to_vsr(), 0); %} ins_pipe(pipe_class_default); %} @@ -14024,7 +13831,7 @@ instruct repl2D_immD0(vecX dst, immD_0 zero) %{ format %{ "XXLXOR $dst, $zero \t// replicate2D" %} size(4); ins_encode %{ - __ xxlxor($dst$$VectorSRegister, $dst$$VectorSRegister, $dst$$VectorSRegister); + __ xxlxor($dst$$VectorRegister->to_vsr(), $dst$$VectorRegister->to_vsr(), $dst$$VectorRegister->to_vsr()); %} ins_pipe(pipe_class_default); %} @@ -14036,7 +13843,7 @@ instruct mtvsrd(vecX dst, iRegLsrc src) %{ format %{ "MTVSRD $dst, $src \t// Move to 16-byte register" %} size(4); ins_encode %{ - __ mtvsrd($dst$$VectorSRegister, $src$$Register); + __ mtvsrd($dst$$VectorRegister->to_vsr(), $src$$Register); %} ins_pipe(pipe_class_default); %} @@ -14047,7 +13854,7 @@ instruct xxspltd(vecX dst, vecX src, immI8 zero) %{ format %{ "XXSPLATD $dst, $src, $zero \t// Splat doubleword" %} size(4); ins_encode %{ - __ xxpermdi($dst$$VectorSRegister, $src$$VectorSRegister, $src$$VectorSRegister, $zero$$constant); + __ xxpermdi($dst$$VectorRegister->to_vsr(), $src$$VectorRegister->to_vsr(), $src$$VectorRegister->to_vsr(), $zero$$constant); %} ins_pipe(pipe_class_default); %} @@ -14058,7 +13865,7 @@ instruct xxpermdi(vecX dst, vecX src1, vecX src2, immI8 zero) %{ format %{ "XXPERMDI $dst, $src1, $src2, $zero \t// Splat doubleword" %} size(4); ins_encode %{ - __ xxpermdi($dst$$VectorSRegister, $src1$$VectorSRegister, $src2$$VectorSRegister, $zero$$constant); + __ xxpermdi($dst$$VectorRegister->to_vsr(), $src1$$VectorRegister->to_vsr(), $src2$$VectorRegister->to_vsr(), $zero$$constant); %} ins_pipe(pipe_class_default); %} @@ -14083,7 +13890,7 @@ instruct repl2L_immI0(vecX dst, immI_0 zero) %{ format %{ "XXLXOR $dst, $zero \t// replicate2L" %} size(4); ins_encode %{ - __ xxlxor($dst$$VectorSRegister, $dst$$VectorSRegister, $dst$$VectorSRegister); + __ xxlxor($dst$$VectorRegister->to_vsr(), $dst$$VectorRegister->to_vsr(), $dst$$VectorRegister->to_vsr()); %} ins_pipe(pipe_class_default); %} @@ -14096,7 +13903,7 @@ instruct repl2L_immIminus1(vecX dst, immI_minus1 src) %{ format %{ "XXLEQV $dst, $src \t// replicate2L" %} size(4); ins_encode %{ - __ xxleqv($dst$$VectorSRegister, $dst$$VectorSRegister, $dst$$VectorSRegister); + __ xxleqv($dst$$VectorRegister->to_vsr(), $dst$$VectorRegister->to_vsr(), $dst$$VectorRegister->to_vsr()); %} ins_pipe(pipe_class_default); %} diff --git a/src/hotspot/cpu/ppc/register_ppc.hpp b/src/hotspot/cpu/ppc/register_ppc.hpp index b7949750dcc9..2613d6c98222 100644 --- a/src/hotspot/cpu/ppc/register_ppc.hpp +++ b/src/hotspot/cpu/ppc/register_ppc.hpp @@ -321,6 +321,7 @@ class VectorRegister { // accessors constexpr int encoding() const { assert(is_valid(), "invalid register"); return _encoding; } + inline VMReg as_VMReg() const; // testers constexpr bool is_valid() const { return (0 <= _encoding && _encoding < number_of_registers); } @@ -392,7 +393,6 @@ class VectorSRegister { // accessors constexpr int encoding() const { assert(is_valid(), "invalid register"); return _encoding; } - inline VMReg as_VMReg() const; VectorSRegister successor() const { return VectorSRegister(encoding() + 1); } // testers @@ -484,8 +484,8 @@ class ConcreteRegisterImpl : public AbstractRegisterImpl { enum { max_gpr = Register::number_of_registers * 2, max_fpr = max_gpr + FloatRegister::number_of_registers * 2, - max_vsr = max_fpr + VectorSRegister::number_of_registers * 4, - max_cnd = max_vsr + ConditionRegister::number_of_registers, + max_vr = max_fpr + VectorRegister::number_of_registers * 4, + max_cnd = max_vr + ConditionRegister::number_of_registers, max_spr = max_cnd + SpecialRegister::number_of_registers, // This number must be large enough to cover REG_COUNT (defined by c2) registers. // There is no requirement that any ordering here matches any ordering c2 gives diff --git a/src/hotspot/cpu/ppc/sharedRuntime_ppc.cpp b/src/hotspot/cpu/ppc/sharedRuntime_ppc.cpp index 4ec2483b267c..37d6c9e6d51b 100644 --- a/src/hotspot/cpu/ppc/sharedRuntime_ppc.cpp +++ b/src/hotspot/cpu/ppc/sharedRuntime_ppc.cpp @@ -111,13 +111,13 @@ class RegisterSaver { int_reg, float_reg, special_reg, - vs_reg + vec_reg } RegisterType; typedef enum { reg_size = 8, half_reg_size = reg_size / 2, - vs_reg_size = 16 + vec_reg_size = 16 } RegisterConstants; typedef struct { @@ -137,8 +137,8 @@ class RegisterSaver { #define RegisterSaver_LiveSpecialReg(regname) \ { RegisterSaver::special_reg, regname->encoding(), regname->as_VMReg() } -#define RegisterSaver_LiveVSReg(regname) \ - { RegisterSaver::vs_reg, regname->encoding(), regname->as_VMReg() } +#define RegisterSaver_LiveVecReg(regname) \ + { RegisterSaver::vec_reg, regname->encoding(), regname->as_VMReg() } static const RegisterSaver::LiveRegType RegisterSaver_LiveRegs[] = { // Live registers which get spilled to the stack. Register @@ -220,42 +220,42 @@ static const RegisterSaver::LiveRegType RegisterSaver_LiveRegs[] = { RegisterSaver_LiveIntReg( R31 ) // must be the last register (see save/restore functions below) }; -static const RegisterSaver::LiveRegType RegisterSaver_LiveVSRegs[] = { +static const RegisterSaver::LiveRegType RegisterSaver_LiveVecRegs[] = { // - // live vector scalar registers (optional, only these ones are used by C2): + // live vector registers (optional, only these ones are used by C2): // - RegisterSaver_LiveVSReg( VSR32 ), - RegisterSaver_LiveVSReg( VSR33 ), - RegisterSaver_LiveVSReg( VSR34 ), - RegisterSaver_LiveVSReg( VSR35 ), - RegisterSaver_LiveVSReg( VSR36 ), - RegisterSaver_LiveVSReg( VSR37 ), - RegisterSaver_LiveVSReg( VSR38 ), - RegisterSaver_LiveVSReg( VSR39 ), - RegisterSaver_LiveVSReg( VSR40 ), - RegisterSaver_LiveVSReg( VSR41 ), - RegisterSaver_LiveVSReg( VSR42 ), - RegisterSaver_LiveVSReg( VSR43 ), - RegisterSaver_LiveVSReg( VSR44 ), - RegisterSaver_LiveVSReg( VSR45 ), - RegisterSaver_LiveVSReg( VSR46 ), - RegisterSaver_LiveVSReg( VSR47 ), - RegisterSaver_LiveVSReg( VSR48 ), - RegisterSaver_LiveVSReg( VSR49 ), - RegisterSaver_LiveVSReg( VSR50 ), - RegisterSaver_LiveVSReg( VSR51 ), - RegisterSaver_LiveVSReg( VSR52 ), - RegisterSaver_LiveVSReg( VSR53 ), - RegisterSaver_LiveVSReg( VSR54 ), - RegisterSaver_LiveVSReg( VSR55 ), - RegisterSaver_LiveVSReg( VSR56 ), - RegisterSaver_LiveVSReg( VSR57 ), - RegisterSaver_LiveVSReg( VSR58 ), - RegisterSaver_LiveVSReg( VSR59 ), - RegisterSaver_LiveVSReg( VSR60 ), - RegisterSaver_LiveVSReg( VSR61 ), - RegisterSaver_LiveVSReg( VSR62 ), - RegisterSaver_LiveVSReg( VSR63 ) + RegisterSaver_LiveVecReg( VR0 ), + RegisterSaver_LiveVecReg( VR1 ), + RegisterSaver_LiveVecReg( VR2 ), + RegisterSaver_LiveVecReg( VR3 ), + RegisterSaver_LiveVecReg( VR4 ), + RegisterSaver_LiveVecReg( VR5 ), + RegisterSaver_LiveVecReg( VR6 ), + RegisterSaver_LiveVecReg( VR7 ), + RegisterSaver_LiveVecReg( VR8 ), + RegisterSaver_LiveVecReg( VR9 ), + RegisterSaver_LiveVecReg( VR10 ), + RegisterSaver_LiveVecReg( VR11 ), + RegisterSaver_LiveVecReg( VR12 ), + RegisterSaver_LiveVecReg( VR13 ), + RegisterSaver_LiveVecReg( VR14 ), + RegisterSaver_LiveVecReg( VR15 ), + RegisterSaver_LiveVecReg( VR16 ), + RegisterSaver_LiveVecReg( VR17 ), + RegisterSaver_LiveVecReg( VR18 ), + RegisterSaver_LiveVecReg( VR19 ), + RegisterSaver_LiveVecReg( VR20 ), + RegisterSaver_LiveVecReg( VR21 ), + RegisterSaver_LiveVecReg( VR22 ), + RegisterSaver_LiveVecReg( VR23 ), + RegisterSaver_LiveVecReg( VR24 ), + RegisterSaver_LiveVecReg( VR25 ), + RegisterSaver_LiveVecReg( VR26 ), + RegisterSaver_LiveVecReg( VR27 ), + RegisterSaver_LiveVecReg( VR28 ), + RegisterSaver_LiveVecReg( VR29 ), + RegisterSaver_LiveVecReg( VR30 ), + RegisterSaver_LiveVecReg( VR31 ) }; @@ -277,10 +277,10 @@ OopMap* RegisterSaver::push_frame_reg_args_and_save_live_registers(MacroAssemble // calculate frame size const int regstosave_num = sizeof(RegisterSaver_LiveRegs) / sizeof(RegisterSaver::LiveRegType); - const int vsregstosave_num = save_vectors ? (sizeof(RegisterSaver_LiveVSRegs) / + const int vecregstosave_num = save_vectors ? (sizeof(RegisterSaver_LiveVecRegs) / sizeof(RegisterSaver::LiveRegType)) : 0; - const int register_save_size = regstosave_num * reg_size + vsregstosave_num * vs_reg_size; + const int register_save_size = regstosave_num * reg_size + vecregstosave_num * vec_reg_size; const int frame_size_in_bytes = align_up(register_save_size, frame::alignment_in_bytes) + frame::native_abi_reg_args_size; @@ -298,8 +298,8 @@ OopMap* RegisterSaver::push_frame_reg_args_and_save_live_registers(MacroAssemble // Save some registers in the last (non-vector) slots of the new frame so we // can use them as scratch regs or to determine the return pc. - __ std(R31, frame_size_in_bytes - reg_size - vsregstosave_num * vs_reg_size, R1_SP); - __ std(R30, frame_size_in_bytes - 2*reg_size - vsregstosave_num * vs_reg_size, R1_SP); + __ std(R31, frame_size_in_bytes - reg_size - vecregstosave_num * vec_reg_size, R1_SP); + __ std(R30, frame_size_in_bytes - 2*reg_size - vecregstosave_num * vec_reg_size, R1_SP); // save the flags // Do the save_LR by hand and adjust the return pc if requested. @@ -360,37 +360,37 @@ OopMap* RegisterSaver::push_frame_reg_args_and_save_live_registers(MacroAssemble // the utilized instructions (PowerArchitecturePPC64). assert(is_aligned(offset, StackAlignmentInBytes), "should be"); if (PowerArchitecturePPC64 >= 10) { - assert(is_even(vsregstosave_num), "expectation"); - for (int i = 0; i < vsregstosave_num; i += 2) { - int reg_num = RegisterSaver_LiveVSRegs[i].reg_num; - assert(RegisterSaver_LiveVSRegs[i + 1].reg_num == reg_num + 1, "or use other instructions!"); + assert(is_even(vecregstosave_num), "expectation"); + for (int i = 0; i < vecregstosave_num; i += 2) { + int reg_num = RegisterSaver_LiveVecRegs[i].reg_num; + assert(RegisterSaver_LiveVecRegs[i + 1].reg_num == reg_num + 1, "or use other instructions!"); - __ stxvp(as_VectorSRegister(reg_num), offset, R1_SP); + __ stxvp(as_VectorRegister(reg_num).to_vsr(), offset, R1_SP); // Note: The contents were read in the same order (see loadV16_Power9 node in ppc.ad). if (generate_oop_map) { map->set_callee_saved(VMRegImpl::stack2reg(offset >> 2), - RegisterSaver_LiveVSRegs[i LITTLE_ENDIAN_ONLY(+1) ].vmreg); - map->set_callee_saved(VMRegImpl::stack2reg((offset + vs_reg_size) >> 2), - RegisterSaver_LiveVSRegs[i BIG_ENDIAN_ONLY(+1) ].vmreg); + RegisterSaver_LiveVecRegs[i LITTLE_ENDIAN_ONLY(+1) ].vmreg); + map->set_callee_saved(VMRegImpl::stack2reg((offset + vec_reg_size) >> 2), + RegisterSaver_LiveVecRegs[i BIG_ENDIAN_ONLY(+1) ].vmreg); } - offset += (2 * vs_reg_size); + offset += (2 * vec_reg_size); } } else { - for (int i = 0; i < vsregstosave_num; i++) { - int reg_num = RegisterSaver_LiveVSRegs[i].reg_num; + for (int i = 0; i < vecregstosave_num; i++) { + int reg_num = RegisterSaver_LiveVecRegs[i].reg_num; if (PowerArchitecturePPC64 >= 9) { - __ stxv(as_VectorSRegister(reg_num), offset, R1_SP); + __ stxv(as_VectorRegister(reg_num)->to_vsr(), offset, R1_SP); } else { __ li(R31, offset); - __ stxvd2x(as_VectorSRegister(reg_num), R31, R1_SP); + __ stxvd2x(as_VectorRegister(reg_num)->to_vsr(), R31, R1_SP); } // Note: The contents were read in the same order (see loadV16_Power8 / loadV16_Power9 node in ppc.ad). if (generate_oop_map) { - VMReg vsr = RegisterSaver_LiveVSRegs[i].vmreg; + VMReg vsr = RegisterSaver_LiveVecRegs[i].vmreg; map->set_callee_saved(VMRegImpl::stack2reg(offset >> 2), vsr); } - offset += vs_reg_size; + offset += vec_reg_size; } } @@ -411,10 +411,10 @@ void RegisterSaver::restore_live_registers_and_pop_frame(MacroAssembler* masm, bool save_vectors) { const int regstosave_num = sizeof(RegisterSaver_LiveRegs) / sizeof(RegisterSaver::LiveRegType); - const int vsregstosave_num = save_vectors ? (sizeof(RegisterSaver_LiveVSRegs) / + const int vecregstosave_num = save_vectors ? (sizeof(RegisterSaver_LiveVecRegs) / sizeof(RegisterSaver::LiveRegType)) : 0; - const int register_save_size = regstosave_num * reg_size + vsregstosave_num * vs_reg_size; + const int register_save_size = regstosave_num * reg_size + vecregstosave_num * vec_reg_size; const int register_save_offset = frame_size_in_bytes - register_save_size; @@ -456,26 +456,26 @@ void RegisterSaver::restore_live_registers_and_pop_frame(MacroAssembler* masm, assert(is_aligned(offset, StackAlignmentInBytes), "should be"); if (PowerArchitecturePPC64 >= 10) { - for (int i = 0; i < vsregstosave_num; i += 2) { - int reg_num = RegisterSaver_LiveVSRegs[i].reg_num; - assert(RegisterSaver_LiveVSRegs[i + 1].reg_num == reg_num + 1, "or use other instructions!"); + for (int i = 0; i < vecregstosave_num; i += 2) { + int reg_num = RegisterSaver_LiveVecRegs[i].reg_num; + assert(RegisterSaver_LiveVecRegs[i + 1].reg_num == reg_num + 1, "or use other instructions!"); - __ lxvp(as_VectorSRegister(reg_num), offset, R1_SP); + __ lxvp(as_VectorRegister(reg_num).to_vsr(), offset, R1_SP); - offset += (2 * vs_reg_size); + offset += (2 * vec_reg_size); } } else { - for (int i = 0; i < vsregstosave_num; i++) { - int reg_num = RegisterSaver_LiveVSRegs[i].reg_num; + for (int i = 0; i < vecregstosave_num; i++) { + int reg_num = RegisterSaver_LiveVecRegs[i].reg_num; if (PowerArchitecturePPC64 >= 9) { - __ lxv(as_VectorSRegister(reg_num), offset, R1_SP); + __ lxv(as_VectorRegister(reg_num).to_vsr(), offset, R1_SP); } else { __ li(R31, offset); - __ lxvd2x(as_VectorSRegister(reg_num), R31, R1_SP); + __ lxvd2x(as_VectorRegister(reg_num).to_vsr(), R31, R1_SP); } - offset += vs_reg_size; + offset += vec_reg_size; } } @@ -486,7 +486,7 @@ void RegisterSaver::restore_live_registers_and_pop_frame(MacroAssembler* masm, __ mtlr(R31); // restore scratch register's value - __ ld(R31, frame_size_in_bytes - reg_size - vsregstosave_num * vs_reg_size, R1_SP); + __ ld(R31, frame_size_in_bytes - reg_size - vecregstosave_num * vec_reg_size, R1_SP); // pop the frame __ addi(R1_SP, R1_SP, frame_size_in_bytes); diff --git a/src/hotspot/cpu/ppc/vmreg_ppc.cpp b/src/hotspot/cpu/ppc/vmreg_ppc.cpp index 2ed68578a80a..0edbf700ec61 100644 --- a/src/hotspot/cpu/ppc/vmreg_ppc.cpp +++ b/src/hotspot/cpu/ppc/vmreg_ppc.cpp @@ -47,7 +47,7 @@ void VMRegImpl::set_regName() { } VectorSRegister vsreg = ::as_VectorSRegister(0); - for ( ; i < ConcreteRegisterImpl::max_vsr; ) { + for ( ; i < ConcreteRegisterImpl::max_vr; ) { regName[i++] = vsreg->name(); regName[i++] = vsreg->name(); regName[i++] = vsreg->name(); diff --git a/src/hotspot/cpu/ppc/vmreg_ppc.hpp b/src/hotspot/cpu/ppc/vmreg_ppc.hpp index 4e25c8b3cead..194b5fd93ef6 100644 --- a/src/hotspot/cpu/ppc/vmreg_ppc.hpp +++ b/src/hotspot/cpu/ppc/vmreg_ppc.hpp @@ -35,13 +35,13 @@ inline bool is_FloatRegister() { value() < ConcreteRegisterImpl::max_fpr; } -inline bool is_VectorSRegister() { +inline bool is_VectorRegister() { return value() >= ConcreteRegisterImpl::max_fpr && - value() < ConcreteRegisterImpl::max_vsr; + value() < ConcreteRegisterImpl::max_vr; } inline bool is_ConditionRegister() { - return value() >= ConcreteRegisterImpl::max_vsr && + return value() >= ConcreteRegisterImpl::max_vr && value() < ConcreteRegisterImpl::max_cnd; } @@ -60,15 +60,15 @@ inline FloatRegister as_FloatRegister() { return ::as_FloatRegister((value() - ConcreteRegisterImpl::max_gpr) >> 1); } -inline VectorSRegister as_VectorSRegister() { - assert(is_VectorSRegister(), "must be"); - return ::as_VectorSRegister((value() - ConcreteRegisterImpl::max_fpr) >> 2); +inline VectorRegister as_VectorRegister() { + assert(is_VectorRegister(), "must be"); + return ::as_VectorRegister((value() - ConcreteRegisterImpl::max_fpr) >> 2); } inline bool is_concrete() { assert(is_reg(), "must be"); if (is_Register() || is_FloatRegister()) return is_even(value()); - if (is_VectorSRegister()) { + if (is_VectorRegister()) { int base = value() - ConcreteRegisterImpl::max_fpr; return (base & 3) == 0; } diff --git a/src/hotspot/cpu/ppc/vmreg_ppc.inline.hpp b/src/hotspot/cpu/ppc/vmreg_ppc.inline.hpp index 2424df8da01e..a7810266b897 100644 --- a/src/hotspot/cpu/ppc/vmreg_ppc.inline.hpp +++ b/src/hotspot/cpu/ppc/vmreg_ppc.inline.hpp @@ -40,13 +40,13 @@ inline VMReg FloatRegister::as_VMReg() const { return VMRegImpl::as_VMReg((encoding() << 1) + ConcreteRegisterImpl::max_gpr); } -inline VMReg VectorSRegister::as_VMReg() const { +inline VMReg VectorRegister::as_VMReg() const { // Four halves, multiply by 4. return VMRegImpl::as_VMReg((encoding() << 2) + ConcreteRegisterImpl::max_fpr); } inline VMReg ConditionRegister::as_VMReg() const { - return VMRegImpl::as_VMReg((encoding()) + ConcreteRegisterImpl::max_vsr); + return VMRegImpl::as_VMReg((encoding()) + ConcreteRegisterImpl::max_vr); } inline VMReg SpecialRegister::as_VMReg() const { From 253dacae04ed1a44a6c5d73d322843ab393bb5cc Mon Sep 17 00:00:00 2001 From: William Kemper Date: Mon, 30 Mar 2026 16:14:58 +0000 Subject: [PATCH 092/168] 8367451: GenShen: Remove the option to compute age census during evacuation Backport-of: 850f904a84186b514a9b79fd4625b4651e73149b --- src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp | 8 ++++---- src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp | 9 ++++----- .../share/gc/shenandoah/shenandoahEvacTracker.cpp | 4 ++-- src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp | 2 +- .../share/gc/shenandoah/shenandoahGenerationalHeap.cpp | 2 +- .../share/gc/shenandoah/shenandoahMark.inline.hpp | 2 +- src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp | 4 ---- 7 files changed, 13 insertions(+), 18 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp index bd66f55bd8fa..c3b99af8a806 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp @@ -57,7 +57,7 @@ ShenandoahAgeCensus::ShenandoahAgeCensus(uint max_workers) // Sentinel value _tenuring_threshold[i] = MAX_COHORTS; } - if (ShenandoahGenerationalAdaptiveTenuring && !ShenandoahGenerationalCensusAtEvac) { + if (ShenandoahGenerationalAdaptiveTenuring) { _local_age_table = NEW_C_HEAP_ARRAY(AgeTable*, _max_workers, mtGC); CENSUS_NOISE(_local_noise = NEW_C_HEAP_ARRAY(ShenandoahNoiseStats, max_workers, mtGC);) for (uint i = 0; i < _max_workers; i++) { @@ -152,7 +152,7 @@ void ShenandoahAgeCensus::update_census(size_t age0_pop, AgeTable* pv1, AgeTable prepare_for_census_update(); assert(_global_age_table[_epoch]->is_clear(), "Dirty decks"); CENSUS_NOISE(assert(_global_noise[_epoch].is_clear(), "Dirty decks");) - if (ShenandoahGenerationalAdaptiveTenuring && !ShenandoahGenerationalCensusAtEvac) { + if (ShenandoahGenerationalAdaptiveTenuring) { assert(pv1 == nullptr && pv2 == nullptr, "Error, check caller"); // Seed cohort 0 with population that may have been missed during // regular census. @@ -197,7 +197,7 @@ void ShenandoahAgeCensus::reset_global() { // Reset the local age tables, clearing any partial census. void ShenandoahAgeCensus::reset_local() { - if (!ShenandoahGenerationalAdaptiveTenuring || ShenandoahGenerationalCensusAtEvac) { + if (!ShenandoahGenerationalAdaptiveTenuring) { assert(_local_age_table == nullptr, "Error"); return; } @@ -223,7 +223,7 @@ bool ShenandoahAgeCensus::is_clear_global() { // Is local census information clear? bool ShenandoahAgeCensus::is_clear_local() { - if (!ShenandoahGenerationalAdaptiveTenuring || ShenandoahGenerationalCensusAtEvac) { + if (!ShenandoahGenerationalAdaptiveTenuring) { assert(_local_age_table == nullptr, "Error"); return true; } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp index 90d188e1fcae..d862498c69da 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp @@ -173,7 +173,7 @@ class ShenandoahAgeCensus: public CHeapObj { ~ShenandoahAgeCensus(); // Return the local age table (population vector) for worker_id. - // Only used in the case of (ShenandoahGenerationalAdaptiveTenuring && !ShenandoahGenerationalCensusAtEvac) + // Only used in the case of ShenandoahGenerationalAdaptiveTenuring AgeTable* get_local_age_table(uint worker_id) const { return _local_age_table[worker_id]; } @@ -204,9 +204,8 @@ class ShenandoahAgeCensus: public CHeapObj { #endif // SHENANDOAH_CENSUS_NOISE // Update the census data, and compute the new tenuring threshold. - // This method should be called at the end of each marking (or optionally - // evacuation) cycle to update the tenuring threshold to be used in - // the next cycle. + // This method should be called at the end of each marking cycle to update + // the tenuring threshold to be used in the next cycle. // age0_pop is the population of Cohort 0 that may have been missed in // the regular census during the marking cycle, corresponding to objects // allocated when the concurrent marking was in progress. @@ -231,7 +230,7 @@ class ShenandoahAgeCensus: public CHeapObj { // Return the net size of objects encountered (counted or skipped) in census // at most recent epoch. - size_t get_total() { return _total; } + size_t get_total() const { return _total; } #endif // !PRODUCT // Print the age census information diff --git a/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.cpp b/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.cpp index 499e1342083e..7883e2c5b294 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.cpp @@ -45,7 +45,7 @@ ShenandoahEvacuationStats::ShenandoahEvacuations* ShenandoahEvacuationStats::get } ShenandoahEvacuationStats::ShenandoahEvacuationStats() - : _use_age_table(ShenandoahGenerationalCensusAtEvac || !ShenandoahGenerationalAdaptiveTenuring), + : _use_age_table(!ShenandoahGenerationalAdaptiveTenuring), _age_table(nullptr) { if (_use_age_table) { _age_table = new AgeTable(false); @@ -168,7 +168,7 @@ ShenandoahCycleStats ShenandoahEvacuationTracker::flush_cycle_to_global() { _mutators_global.accumulate(&mutators); _workers_global.accumulate(&workers); - if (ShenandoahGenerationalCensusAtEvac || !ShenandoahGenerationalAdaptiveTenuring) { + if (!ShenandoahGenerationalAdaptiveTenuring) { // Ingest mutator & worker collected population vectors into the heap's // global census data, and use it to compute an appropriate tenuring threshold // for use in the next cycle. diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp index 2fd1cebfa775..7efb824088d3 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp @@ -678,7 +678,7 @@ void ShenandoahGeneration::prepare_regions_and_collection_set(bool concurrent) { } // Tally the census counts and compute the adaptive tenuring threshold - if (is_generational && ShenandoahGenerationalAdaptiveTenuring && !ShenandoahGenerationalCensusAtEvac) { + if (is_generational && ShenandoahGenerationalAdaptiveTenuring) { // Objects above TAMS weren't included in the age census. Since they were all // allocated in this cycle they belong in the age 0 cohort. We walk over all // young regions and sum the volume of objects between TAMS and top. diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp index 188c41aec3a6..0de8f88a8023 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp @@ -357,7 +357,7 @@ oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, Shena assert(target_gen == YOUNG_GENERATION, "Error"); // We record this census only when simulating pre-adaptive tenuring behavior, or // when we have been asked to record the census at evacuation rather than at mark - if (ShenandoahGenerationalCensusAtEvac || !ShenandoahGenerationalAdaptiveTenuring) { + if (!ShenandoahGenerationalAdaptiveTenuring) { evac_tracker()->record_age(thread, size * HeapWordSize, ShenandoahHeap::get_object_age(copy_val)); } } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp index 2dc0813e5135..0a95ee9f1496 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp @@ -118,7 +118,7 @@ inline void ShenandoahMark::count_liveness(ShenandoahLiveData* live_data, oop ob // Age census for objects in the young generation if (GENERATION == YOUNG || (GENERATION == GLOBAL && region->is_young())) { assert(heap->mode()->is_generational(), "Only if generational"); - if (ShenandoahGenerationalAdaptiveTenuring && !ShenandoahGenerationalCensusAtEvac) { + if (ShenandoahGenerationalAdaptiveTenuring) { assert(region->is_young(), "Only for young objects"); uint age = ShenandoahHeap::get_object_age(obj); ShenandoahAgeCensus* const census = ShenandoahGenerationalHeap::heap()->age_census(); diff --git a/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp b/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp index dc49de9a7629..5e7f9b6d71a9 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp @@ -84,10 +84,6 @@ "many consecutive young-gen collections have been " \ "completed following the preceding old-gen collection.") \ \ - product(bool, ShenandoahGenerationalCensusAtEvac, false, EXPERIMENTAL, \ - "(Generational mode only) Object age census at evacuation, " \ - "rather than during marking.") \ - \ product(bool, ShenandoahGenerationalAdaptiveTenuring, true, EXPERIMENTAL, \ "(Generational mode only) Dynamically adapt tenuring age.") \ \ From de43a34b0e9056dfead8b4a5dfb48c7b7271d2ee Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 30 Mar 2026 17:13:46 +0000 Subject: [PATCH 093/168] 8015444: java/awt/Focus/KeyStrokeTest.java sometimes fails Backport-of: 60e9222fe147413f20c140f2c00541b6472dfaa4 --- test/jdk/java/awt/Focus/KeyStrokeTest.java | 79 +++++++++++----------- 1 file changed, 41 insertions(+), 38 deletions(-) diff --git a/test/jdk/java/awt/Focus/KeyStrokeTest.java b/test/jdk/java/awt/Focus/KeyStrokeTest.java index 7c462ce8f22d..668bc1216c89 100644 --- a/test/jdk/java/awt/Focus/KeyStrokeTest.java +++ b/test/jdk/java/awt/Focus/KeyStrokeTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,9 +29,9 @@ * @run main KeyStrokeTest */ -import java.awt.BorderLayout; import java.awt.Button; import java.awt.Dialog; +import java.awt.EventQueue; import java.awt.Frame; import java.awt.Robot; import java.awt.TextField; @@ -39,25 +39,53 @@ import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; public class KeyStrokeTest { static boolean keyTyped; static Frame frame; + static Robot robot; + static final CountDownLatch latch = new CountDownLatch(1); public static void main(String[] args) throws Exception { + robot = new Robot(); try { - KeyStrokeTest test = new KeyStrokeTest(); - test.doTest(); - } finally { - if (frame != null) { - frame.dispose(); + EventQueue.invokeAndWait(() -> { + KeyStrokeTest test = new KeyStrokeTest(); + test.initTest(); + }); + robot.waitForIdle(); + robot.delay(1000); + robot.keyPress(KeyEvent.VK_TAB); + robot.keyRelease(KeyEvent.VK_TAB); + + robot.delay(1000); + robot.keyPress(KeyEvent.VK_SPACE); + robot.keyRelease(KeyEvent.VK_SPACE); + + robot.delay(1000); + robot.keyPress(KeyEvent.VK_A); + robot.keyRelease(KeyEvent.VK_A); + try { + latch.await(3, TimeUnit.SECONDS); + } catch (InterruptedException e) {} + if (!keyTyped) { + throw new + RuntimeException("First keystroke after JDialog is closed is lost"); } + System.out.println("Test passed"); + } finally { + EventQueue.invokeAndWait(() -> { + if (frame != null) { + frame.dispose(); + } + }); } } - private static void doTest() throws Exception { - final Object monitor = new Object(); - frame = new Frame(); + private static void initTest() { + frame = new Frame("KeyStrokeTest"); TextField textField = new TextField() { public void transferFocus() { System.err.println("transferFocus()"); @@ -71,6 +99,7 @@ public void actionPerformed(ActionEvent e) { }); dialog.add(btn); dialog.setSize(200, 200); + dialog.setLocationRelativeTo(null); dialog.setVisible(true); } }; @@ -81,38 +110,12 @@ public void keyTyped(KeyEvent e) { if (e.getKeyChar() == 'a') { keyTyped = true; } - - synchronized (monitor) { - monitor.notifyAll(); - } + latch.countDown(); } }); frame.add(textField); frame.setSize(400, 400); + frame.setLocationRelativeTo(null); frame.setVisible(true); - - Robot robot = new Robot(); - robot.waitForIdle(); - robot.delay(1000); - robot.keyPress(KeyEvent.VK_TAB); - robot.keyRelease(KeyEvent.VK_TAB); - - robot.delay(1000); - robot.keyPress(KeyEvent.VK_SPACE); - robot.keyRelease(KeyEvent.VK_SPACE); - - robot.delay(1000); - synchronized (monitor) { - robot.keyPress(KeyEvent.VK_A); - robot.keyRelease(KeyEvent.VK_A); - monitor.wait(3000); - } - - if (!keyTyped) { - throw new RuntimeException("TEST FAILED"); - } - - System.out.println("Test passed"); } - } From dc01cec83097bdf20153d643445c1f5634429960 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 30 Mar 2026 17:14:19 +0000 Subject: [PATCH 094/168] 8366695: Test sun/jvmstat/monitor/MonitoredVm/MonitorVmStartTerminate.java timed out Backport-of: 11743b1ed3d681ce17c2342616c4040c4b539b31 --- .../jvmstat/monitor/MonitoredVm/MonitorVmStartTerminate.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jdk/sun/jvmstat/monitor/MonitoredVm/MonitorVmStartTerminate.java b/test/jdk/sun/jvmstat/monitor/MonitoredVm/MonitorVmStartTerminate.java index b4291645007d..f363d6a7dd55 100644 --- a/test/jdk/sun/jvmstat/monitor/MonitoredVm/MonitorVmStartTerminate.java +++ b/test/jdk/sun/jvmstat/monitor/MonitoredVm/MonitorVmStartTerminate.java @@ -69,7 +69,7 @@ * @modules java.management * jdk.internal.jvmstat/sun.jvmstat.monitor * jdk.internal.jvmstat/sun.jvmstat.monitor.event - * @run main/othervm MonitorVmStartTerminate + * @run main/othervm/timeout=240 MonitorVmStartTerminate */ public final class MonitorVmStartTerminate { From f8c6ad250dff19a3dcb3f02f3a690d63cd613861 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 30 Mar 2026 17:15:00 +0000 Subject: [PATCH 095/168] 8378727: [macOS] Missing dispatch_release for semaphores in CDesktopPeer Backport-of: d52e5bd0357a074f74757f7a8256ed14a2e0eaee --- .../macosx/native/libawt_lwawt/awt/CDesktopPeer.m | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/java.desktop/macosx/native/libawt_lwawt/awt/CDesktopPeer.m b/src/java.desktop/macosx/native/libawt_lwawt/awt/CDesktopPeer.m index 460749c363dd..faacef5adea0 100644 --- a/src/java.desktop/macosx/native/libawt_lwawt/awt/CDesktopPeer.m +++ b/src/java.desktop/macosx/native/libawt_lwawt/awt/CDesktopPeer.m @@ -70,6 +70,7 @@ dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(NSEC_PER_SEC)); // 1 second timeout // Asynchronous call to openURL + dispatch_retain(semaphore); [[NSWorkspace sharedWorkspace] openURLs:urls withApplicationAtURL:appURI configuration:configuration @@ -78,9 +79,11 @@ status = (OSStatus) error.code; } dispatch_semaphore_signal(semaphore); + dispatch_release(semaphore); }]; dispatch_semaphore_wait(semaphore, timeout); + dispatch_release(semaphore); JNI_COCOA_EXIT(env); return status; @@ -146,6 +149,7 @@ dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(NSEC_PER_SEC)); // 1 second timeout // Asynchronous call - openURLs:withApplicationAtURL + dispatch_retain(semaphore); [[NSWorkspace sharedWorkspace] openURLs:urls withApplicationAtURL:appURI configuration:configuration @@ -154,9 +158,11 @@ status = (OSStatus) error.code; } dispatch_semaphore_signal(semaphore); + dispatch_release(semaphore); }]; dispatch_semaphore_wait(semaphore, timeout); + dispatch_release(semaphore); [urlToOpen release]; JNI_COCOA_EXIT(env); From 5d157d5a5755d6514f51e099e2f8354a3d49ec1a Mon Sep 17 00:00:00 2001 From: Daniel Hu Date: Mon, 30 Mar 2026 18:02:54 +0000 Subject: [PATCH 096/168] 8341735: Rewrite the build/AbsPathsInImage.java test to not load the entire file at once Backport-of: c25f35205ae4544970bbaca233de8745f8e4e92c --- test/jdk/build/AbsPathsInImage.java | 203 ++++++++++++++++++++-------- 1 file changed, 145 insertions(+), 58 deletions(-) diff --git a/test/jdk/build/AbsPathsInImage.java b/test/jdk/build/AbsPathsInImage.java index 1aa7e59941e8..7b2c60c3ddaf 100644 --- a/test/jdk/build/AbsPathsInImage.java +++ b/test/jdk/build/AbsPathsInImage.java @@ -21,6 +21,7 @@ * questions. */ +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.file.FileVisitResult; @@ -35,6 +36,8 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; +import static java.util.Comparator.comparing; + /* * @test * @bug 8226346 @@ -42,7 +45,7 @@ * @requires !vm.debug * @comment ASAN keeps the 'unwanted' paths in the binaries because of its build options * @requires !vm.asan - * @run main/othervm -Xmx900m AbsPathsInImage + * @run main AbsPathsInImage */ public class AbsPathsInImage { @@ -51,9 +54,14 @@ public class AbsPathsInImage { public static final String DIR_PROPERTY = "jdk.test.build.AbsPathsInImage.dir"; private static final boolean IS_WINDOWS = System.getProperty("os.name").toLowerCase().contains("windows"); private static final boolean IS_LINUX = System.getProperty("os.name").toLowerCase().contains("linux"); + private static final int DEFAULT_BUFFER_SIZE = 8192; + private static List searchPatterns = new ArrayList<>(); + private static List prefixTables = new ArrayList<>(); private boolean matchFound = false; + record Match(int begin, int end) { } + public static void main(String[] args) throws Exception { String jdkPathString = System.getProperty("test.jdk"); Path jdkHome = Paths.get(jdkPathString); @@ -107,9 +115,9 @@ public static void main(String[] args) throws Exception { throw new Error("Output root is not an absolute path: " + buildOutputRoot); } - List searchPatterns = new ArrayList<>(); - expandPatterns(searchPatterns, buildWorkspaceRoot); - expandPatterns(searchPatterns, buildOutputRoot); + expandPatterns(buildWorkspaceRoot); + expandPatterns(buildOutputRoot); + createPrefixTables(); System.out.println("Looking for:"); for (byte[] searchPattern : searchPatterns) { @@ -118,7 +126,7 @@ public static void main(String[] args) throws Exception { System.out.println(); AbsPathsInImage absPathsInImage = new AbsPathsInImage(); - absPathsInImage.scanFiles(dirToScan, searchPatterns); + absPathsInImage.scanFiles(dirToScan); if (absPathsInImage.matchFound) { throw new Exception("Test failed"); @@ -129,7 +137,7 @@ public static void main(String[] args) throws Exception { * Add path pattern to list of patterns to search for. Create all possible * variants depending on platform. */ - private static void expandPatterns(List searchPatterns, String pattern) { + private static void expandPatterns(String pattern) { if (IS_WINDOWS) { String forward = pattern.replace('\\', '/'); String back = pattern.replace('/', '\\'); @@ -151,7 +159,42 @@ private static void expandPatterns(List searchPatterns, String pattern) } } - private void scanFiles(Path root, List searchPatterns) throws IOException { + /** + * The failure function for KMP. Returns the correct index in the pattern to jump + * back to when encountering a mismatched character. Used in both + * createPrefixTables (pre-processing) and scanBytes (matching). + */ + private static int getPrefixIndex(int patternIdx, int state, byte match) { + if (state == 0) { + return 0; + } + byte[] searchPattern = searchPatterns.get(patternIdx); + int[] prefixTable = prefixTables.get(patternIdx); + int i = prefixTable[state - 1]; + while (i > 0 && searchPattern[i] != match) { + i = prefixTable[i - 1]; + } + return searchPattern[i] == match ? i + 1 : i; + } + + /** + * Pre-processing string patterns for Knuth–Morris–Pratt (KMP) search algorithm. + * Lookup tables of longest prefixes at each given index are created for each + * search pattern string. These tables are later used in scanBytes during matching + * as lookups for failure state transitions. + */ + private static void createPrefixTables() { + for (int patternIdx = 0; patternIdx < searchPatterns.size(); patternIdx++) { + int patternLen = searchPatterns.get(patternIdx).length; + int[] prefixTable = new int[patternLen]; + prefixTables.add(prefixTable); + for (int i = 1; i < patternLen; i++) { + prefixTable[i] = getPrefixIndex(patternIdx, i, searchPatterns.get(patternIdx)[i]); + } + } + } + + private void scanFiles(Path root) throws IOException { Files.walkFileTree(root, new SimpleFileVisitor<>() { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { @@ -170,84 +213,128 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO } else if ((fileName.endsWith(".debuginfo") && !IS_LINUX) || fileName.endsWith(".pdb")) { // Do nothing } else if (fileName.endsWith(".zip")) { - scanZipFile(file, searchPatterns); + scanZipFile(file); } else { - scanFile(file, searchPatterns); + scanFile(file); } return super.visitFile(file, attrs); } }); } - private void scanFile(Path file, List searchPatterns) throws IOException { - List matches = scanBytes(Files.readAllBytes(file), searchPatterns); - if (matches.size() > 0) { - matchFound = true; - System.out.println(file + ":"); - for (String match : matches) { - System.out.println(match); - } - System.out.println(); + private void scanFile(Path file) throws IOException { + List matches; + try (InputStream inputStream = Files.newInputStream(file)) { + matches = scanBytes(inputStream); + } + // test succeeds + if (matches.size() == 0) { + return; + } + // test fails; pay penalty and re-scan file for debug output + try (InputStream inputStream = Files.newInputStream(file)) { + printDebugOutput(inputStream, matches, file + ":"); } } - private void scanZipFile(Path zipFile, List searchPatterns) throws IOException { + private void scanZipFile(Path zipFile) throws IOException { + List> entryMatches = new ArrayList<>(); + boolean found = false; + ZipEntry zipEntry; try (ZipInputStream zipInputStream = new ZipInputStream(Files.newInputStream(zipFile))) { - ZipEntry zipEntry; while ((zipEntry = zipInputStream.getNextEntry()) != null) { - List matches = scanBytes(zipInputStream.readAllBytes(), searchPatterns); + List matches = scanBytes(zipInputStream); if (matches.size() > 0) { - matchFound = true; - System.out.println(zipFile + ", " + zipEntry.getName() + ":"); - for (String match : matches) { - System.out.println(match); - } - System.out.println(); + entryMatches.add(matches); + found = true; + } else { + entryMatches.add(null); + } + } + } + // test succeeds + if (!found) { + return; + } + // test fails + try (ZipInputStream zipInputStream = new ZipInputStream(Files.newInputStream(zipFile))) { + int i = 0; + while ((zipEntry = zipInputStream.getNextEntry()) != null) { + List matches = entryMatches.get(i); + i++; + if (matches != null) { + printDebugOutput(zipInputStream, matches, zipFile + ", " + zipEntry.getName() + ":"); } } } } - private List scanBytes(byte[] data, List searchPatterns) { - List matches = new ArrayList<>(); - for (int i = 0; i < data.length; i++) { - for (byte[] searchPattern : searchPatterns) { - boolean found = true; - for (int j = 0; j < searchPattern.length; j++) { - if ((i + j >= data.length || data[i + j] != searchPattern[j])) { - found = false; + /** + * Scans each byte until encounters a match with one of searchPatterns. Uses KMP to + * perform matches. Keep track of current matched index (states) for each search + * pattern. At each given byte, update states accordingly (increment if match or + * failure function transition if mismatch). Returns a list of Match objects. + */ + private List scanBytes(InputStream input) throws IOException { + List matches = new ArrayList<>(); + byte[] buf = new byte[DEFAULT_BUFFER_SIZE]; + int[] states = new int[searchPatterns.size()]; + int fileIdx = 0; + int bytesRead, patternLen; + while ((bytesRead = input.read(buf)) != -1) { + for (int bufIdx = 0; bufIdx < bytesRead; bufIdx++, fileIdx++) { + byte datum = buf[bufIdx]; + for (int i = 0; i < searchPatterns.size(); i++) { + patternLen = searchPatterns.get(i).length; + if (datum != searchPatterns.get(i)[states[i]]) { + states[i] = getPrefixIndex(i, states[i], datum); + } else if (++states[i] == patternLen) { + // technically at last match, state should reset according to failure function + // but in original test, matching didn't search same string for multiple matches + states[i] = 0; + matches.add(new Match(fileIdx - patternLen + 1, fileIdx)); break; } } - if (found) { - matches.add(new String(data, charsStart(data, i), charsOffset(data, i, searchPattern.length))); - // No need to search the same string for multiple patterns - break; - } } } return matches; } - private int charsStart(byte[] data, int startIndex) { - int index = startIndex; - while (--index > 0) { - byte datum = data[index]; - if (datum < 32 || datum > 126) { - break; - } - } - return index + 1; - } - - private int charsOffset(byte[] data, int startIndex, int startOffset) { - int offset = startOffset; - while (startIndex + ++offset < data.length) { - byte datum = data[startIndex + offset]; - if (datum < 32 || datum > 126) { - break; + /** + * In original test, failed test output would backtrack to last non-ascii byte on + * matched pattern. This is incompatible with the new buffered approach (and a + * proper solution requires a 2nd dynamic buffer). Instead, on failed test case, + * files are scanned a 2nd time to print debug output. Failed runs will pay + * additional performance/space penalty, but passing runs are faster. + */ + private void printDebugOutput(InputStream input, List matches, final String HEADER) throws IOException{ + matchFound = true; + System.out.println(HEADER); + matches.sort(comparing(Match::begin)); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + byte[] buf = new byte[DEFAULT_BUFFER_SIZE]; + int matchIdx = 0; + int fileIdx = 0; + int bytesRead; + while (matchIdx < matches.size() && (bytesRead = input.read(buf)) != -1) { + for (int i = 0; matchIdx < matches.size() && i < bytesRead; i++, fileIdx++) { + byte datum = buf[i]; + if (datum >= 32 && datum <= 126) { + output.write(datum); + } else if (fileIdx < matches.get(matchIdx).begin()) { + output.reset(); + } else if (fileIdx > matches.get(matchIdx).end()) { + System.out.println(output.toString()); + output.reset(); + // This imperfect as incorrect in edge cases with patterns containing non-ascii? + // but high-accuracy not priority + output still legible and useful + for (; matchIdx < matches.size() && matches.get(matchIdx).end() < fileIdx; matchIdx++); + } else { + output.write(datum); + } } } - return offset; + System.out.println(); } } From b4dd8457236dd9c7a2b3aae48a54583f37732339 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Tue, 31 Mar 2026 13:32:04 +0000 Subject: [PATCH 097/168] 8367949: JFR: MethodTrace double-counts methods that catch their own exceptions Backport-of: fa2eb626478806dc64fe03d8729f53f7ed26a172 --- .../jfr/internal/tracing/Instrumentation.java | 22 +- .../jdk/jfr/internal/tracing/Method.java | 2 +- .../jdk/jfr/internal/tracing/Transform.java | 197 ++++++++++++++++-- .../jfr/event/tracing/TestConstructors.java | 167 +++++++++++++++ .../event/tracing/TestInstrumentation.java | 4 + 5 files changed, 362 insertions(+), 30 deletions(-) create mode 100644 test/jdk/jdk/jfr/event/tracing/TestConstructors.java diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/Instrumentation.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/Instrumentation.java index dbafca4ed3ce..e06d361b2039 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/Instrumentation.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/Instrumentation.java @@ -58,7 +58,13 @@ public Instrumentation(ClassLoader classLoader, String internalClassName, byte[] } public void addMethod(long methodId, String name, String signature, int modification) { - modificationMap.put(name + signature, new Method(methodId, Modification.valueOf(modification), className + "::" + name)); + Method method = new Method( + methodId, + Modification.valueOf(modification), + name.equals(""), + className + "::" + name + ); + modificationMap.put(name + signature, method); } public List getMethods() { @@ -71,7 +77,7 @@ public byte[] generateBytecode() { ClassModel classModel = classFile.parse(bytecode); byte[] generated = classFile.build(classModel.thisClass().asSymbol(), classBuilder -> { for (var ce : classModel) { - if (modifyClassElement(classBuilder, ce)) { + if (modifyClassElement(classModel, classBuilder, ce)) { modified[0] = true; } else { classBuilder.with(ce); @@ -93,7 +99,7 @@ private ClassHierarchyResolver resolver() { } } - private boolean modifyClassElement(ClassBuilder classBuilder, ClassElement ce) { + private boolean modifyClassElement(ClassModel classModel, ClassBuilder classBuilder, ClassElement ce) { if (ce instanceof MethodModel mm) { String method = mm.methodName().stringValue(); String signature = mm.methodType().stringValue(); @@ -102,15 +108,15 @@ private boolean modifyClassElement(ClassBuilder classBuilder, ClassElement ce) { if (tm != null) { Modification m = tm.modification(); if (m.tracing() || m.timing()) { - return modifyMethod(classBuilder, mm, tm); + return modifyMethod(classModel, classBuilder, mm, tm); } } } return false; } - private boolean modifyMethod(ClassBuilder classBuilder, MethodModel m, Method method) { - var code = m.code(); + private boolean modifyMethod(ClassModel classModel, ClassBuilder classBuilder, MethodModel methodModel, Method method) { + var code = methodModel.code(); if (code.isPresent()) { if (classLoader == null && ExcludeList.containsMethod(method.name())) { String msg = "Risk of recursion, skipping bytecode generation of " + method.name(); @@ -118,9 +124,9 @@ private boolean modifyMethod(ClassBuilder classBuilder, MethodModel m, Method me return false; } MethodTransform s = MethodTransform.ofStateful( - () -> MethodTransform.transformingCode(new Transform(method)) + () -> MethodTransform.transformingCode(new Transform(classModel, code.get(), method)) ); - classBuilder.transformMethod(m, s); + classBuilder.transformMethod(methodModel, s); return true; } return false; diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/Method.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/Method.java index d685083153d6..d85e458e9d58 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/Method.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/Method.java @@ -31,7 +31,7 @@ /** * Class that holds information about an instrumented method. */ -record Method(long methodId, Modification modification, String name) { +record Method(long methodId, Modification modification, boolean constructor, String name) { @Override public String toString() { return name + (modification.timing() ? " +timing" : " -timing") + (modification.tracing() ? " +tracing" : " -tracing") + " (Method ID: " + String.format("0x%08X)", methodId); diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/Transform.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/Transform.java index cd65a119cee4..377eede79256 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/Transform.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tracing/Transform.java @@ -24,13 +24,19 @@ */ package jdk.jfr.internal.tracing; +import java.lang.classfile.ClassModel; import java.lang.classfile.CodeBuilder; import java.lang.classfile.CodeElement; +import java.lang.classfile.CodeModel; import java.lang.classfile.CodeTransform; +import java.lang.classfile.Label; import java.lang.classfile.TypeKind; +import java.lang.classfile.instruction.InvokeInstruction; import java.lang.classfile.instruction.ReturnInstruction; import java.lang.classfile.instruction.ThrowInstruction; import java.lang.constant.ClassDesc; +import java.util.ArrayList; +import java.util.List; import jdk.jfr.internal.util.Bytecode; import jdk.jfr.internal.util.Bytecode.MethodDesc; @@ -43,59 +49,208 @@ * The method ID is determined by native code. */ final class Transform implements CodeTransform { + private static class TryBlock { + Label start; + Label end; + } private static final ClassDesc METHOD_TRACER_CLASS = ClassDesc.of(MethodTracer.class.getName()); private static final MethodDesc TRACE_METHOD = MethodDesc.of("trace", "(JJ)V"); private static final MethodDesc TIMING_METHOD = MethodDesc.of("timing", "(JJ)V"); private static final MethodDesc TRACE_TIMING_METHOD = MethodDesc.of("traceTiming", "(JJ)V"); private static final MethodDesc TIMESTAMP_METHOD = MethodDesc.of("timestamp", "()J"); + private final List tryBlocks = new ArrayList<>(); + private final boolean simplifiedInstrumentation; + private final ClassModel classModel; private final Method method; private int timestampSlot = -1; - Transform(Method method) { + Transform(ClassModel classModel, CodeModel model, Method method) { this.method = method; + this.classModel = classModel; + // The JVMS (not the JLS) allows multiple mutually exclusive super/this. + // invocations in a constructor body as long as only one lies on any given + // execution path. For example, this is valid bytecode: + // + // Foo(boolean value) { + // if (value) { + // staticMethodThatMayThrow(); + // super(); + // } else { + // try { + // if (value == 0) { + // throw new Exception(""); + // } + // } catch (Throwable t) { + // throw t; + // } + // super(); + // } + // } + // + // If such a method is found, instrumentation falls back to instrumenting only + // RET and ATHROW. This can cause exceptions to be missed or counted twice. + // + // An effect of this heuristic is that constructors like the one below + // will also trigger simplified instrumentation. + // + // class Bar { + // } + // + // class Foo extends Bar { + // Foo() { + // new Bar(); + // } + // } + // + // java.lang.Object:: with zero constructor invocations should use simplified instrumentation + this.simplifiedInstrumentation = method.constructor() && constructorInvocations(model.elementList()) != 1; + } + + private int constructorInvocations(List elementList) { + int count = 0; + for (CodeElement e : elementList) { + if (isConstructorInvocation(e)) { + count++; + } + } + return count; + } + + private boolean isConstructorInvocation(CodeElement element) { + if (element instanceof InvokeInstruction inv && inv.name().equalsString("")) { + if (classModel.thisClass().equals(inv.owner())) { + return true; + } + if (classModel.superclass().isPresent()) { + return classModel.superclass().get().equals(inv.owner()); + } + } + return false; } @Override - public final void accept(CodeBuilder builder, CodeElement element) { + public void accept(CodeBuilder builder, CodeElement element) { + if (simplifiedInstrumentation) { + acceptSimplifiedInstrumentation(builder, element); + return; + } + if (method.constructor()) { + acceptConstructor(builder, element, isConstructorInvocation(element)); + } else { + acceptMethod(builder, element); + } + } + + @Override + public void atEnd(CodeBuilder builder) { + endTryBlock(builder); + for (TryBlock block : tryBlocks) { + addCatchHandler(block, builder); + } + } + + private void acceptConstructor(CodeBuilder builder, CodeElement element, boolean isConstructorInvocation) { + if (timestampSlot == -1) { + timestampSlot = invokeTimestamp(builder); + builder.lstore(timestampSlot); + if (!isConstructorInvocation) { + beginTryBlock(builder); + } + } + if (isConstructorInvocation) { + endTryBlock(builder); + builder.with(element); + beginTryBlock(builder); + return; + } + if (element instanceof ReturnInstruction) { + addTracing(builder); + } + builder.with(element); + } + + private void endTryBlock(CodeBuilder builder) { + if (tryBlocks.isEmpty()) { + return; + } + TryBlock last = tryBlocks.getLast(); + if (last.end == null) { + last.end = builder.newBoundLabel(); + } + } + + private void beginTryBlock(CodeBuilder builder) { + TryBlock block = new TryBlock(); + block.start = builder.newBoundLabel(); + tryBlocks.add(block); + } + + private void acceptSimplifiedInstrumentation(CodeBuilder builder, CodeElement element) { if (timestampSlot == -1) { timestampSlot = invokeTimestamp(builder); builder.lstore(timestampSlot); } if (element instanceof ReturnInstruction || element instanceof ThrowInstruction) { - builder.lload(timestampSlot); - builder.ldc(method.methodId()); - Modification modification = method.modification(); - boolean objectInit = method.name().equals("java.lang.Object::"); - String suffix = objectInit ? "ObjectInit" : ""; - if (modification.timing()) { - if (modification.tracing()) { - invokeTraceTiming(builder, suffix); - } else { - invokeTiming(builder, suffix); - } + addTracing(builder); + } + builder.with(element); + } + + private void acceptMethod(CodeBuilder builder, CodeElement element) { + if (timestampSlot == -1) { + timestampSlot = invokeTimestamp(builder); + builder.lstore(timestampSlot); + beginTryBlock(builder); + } + if (element instanceof ReturnInstruction) { + addTracing(builder); + } + builder.with(element); + } + + private void addCatchHandler(TryBlock block, CodeBuilder builder) { + Label catchHandler = builder.newBoundLabel(); + int exceptionSlot = builder.allocateLocal(TypeKind.REFERENCE); + builder.astore(exceptionSlot); + addTracing(builder); + builder.aload(exceptionSlot); + builder.athrow(); + builder.exceptionCatchAll(block.start, block.end, catchHandler); + } + + private void addTracing(CodeBuilder builder) { + builder.lload(timestampSlot); + builder.ldc(method.methodId()); + Modification modification = method.modification(); + boolean objectInit = method.name().equals("java.lang.Object::"); + String suffix = objectInit ? "ObjectInit" : ""; + if (modification.timing()) { + if (modification.tracing()) { + invokeTraceTiming(builder, suffix); } else { - if (modification.tracing()) { - invokeTrace(builder, suffix); - } + invokeTiming(builder, suffix); + } + } else { + if (modification.tracing()) { + invokeTrace(builder, suffix); } } - builder.with(element); } - public static void invokeTiming(CodeBuilder builder, String suffix) { + private static void invokeTiming(CodeBuilder builder, String suffix) { builder.invokestatic(METHOD_TRACER_CLASS, TIMING_METHOD.name() + suffix, TIMING_METHOD.descriptor()); } - public static void invokeTrace(CodeBuilder builder, String suffix) { + private static void invokeTrace(CodeBuilder builder, String suffix) { builder.invokestatic(METHOD_TRACER_CLASS, TRACE_METHOD.name() + suffix, TRACE_METHOD.descriptor()); } - public static void invokeTraceTiming(CodeBuilder builder, String suffix) { + private static void invokeTraceTiming(CodeBuilder builder, String suffix) { builder.invokestatic(METHOD_TRACER_CLASS, TRACE_TIMING_METHOD.name() + suffix, TRACE_TIMING_METHOD.descriptor()); } - public static int invokeTimestamp(CodeBuilder builder) { + private static int invokeTimestamp(CodeBuilder builder) { Bytecode.invokestatic(builder, METHOD_TRACER_CLASS, TIMESTAMP_METHOD); return builder.allocateLocal(TypeKind.LONG); } diff --git a/test/jdk/jdk/jfr/event/tracing/TestConstructors.java b/test/jdk/jdk/jfr/event/tracing/TestConstructors.java new file mode 100644 index 000000000000..26548646b494 --- /dev/null +++ b/test/jdk/jdk/jfr/event/tracing/TestConstructors.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.jfr.event.tracing; + +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordedMethod; +import jdk.jfr.consumer.RecordingFile; +import jdk.test.lib.jfr.Events; + +/** + * @test + * @summary Tests that constructors are instrumented correctly. + * @requires vm.flagless + * @requires vm.hasJFR + * @library /test/lib + * @run main/othervm -Xlog:jfr+methodtrace=debug + * jdk.jfr.event.tracing.TestConstructors + **/ +public class TestConstructors { + static private void methodThatThrows() { + throw new RuntimeException(); + } + + public static class Cat { + Cat() { + new String(); + methodThatThrows(); + super(); + methodThatThrows(); + } + } + + public static class Dog { + Dog() { + super(); + methodThatThrows(); + } + } + + public static class Tiger { + Tiger() { + methodThatThrows(); + super(); + } + } + + public static class Zebra { + Zebra(boolean shouldThrow) { + this(shouldThrow ? 1 : 0); + } + + Zebra(int shouldThrow) { + if (shouldThrow == 1) { + throw new RuntimeException(); + } + } + } + + public static class Snake { + Snake() { + try { + throw new RuntimeException(); + } catch (Exception e) { + // Ignore + } + super(); + } + } + + public static void main(String... args) throws Exception { + try (Recording r = new Recording()) { + r.enable("jdk.MethodTrace").with("filter", Dog.class.getName() + ";" + Cat.class.getName() + ";" + Tiger.class.getName() + ";" + Zebra.class.getName() + ";" + Snake.class.getName()); + r.start(); + try { + new Cat(); + } catch (Exception e) { + // ignore + } + try { + new Dog(); + } catch (Exception e) { + // ignore + } + try { + new Tiger(); + } catch (Exception e) { + // ignore + } + try { + new Zebra(true); + } catch (Exception e) { + // ignore + } + try { + new Zebra(false); + } catch (Exception e) { + // ignore + } + try { + new Snake(); + } catch (Exception e) { + // ignore + } + r.stop(); + List events = Events.fromRecording(r); + var methods = buildMethodMap(events); + if (methods.size() != 5) { + throw new Exception("Expected 5 different methods"); + } + assertMethodCount(methods, "Cat", 1); + assertMethodCount(methods, "Dog", 1); + assertMethodCount(methods, "Snake", 1); + assertMethodCount(methods, "Tiger", 1); + assertMethodCount(methods, "Zebra", 3); + } + } + + private static void assertMethodCount(Map methods, String className, int expectedCount) throws Exception { + String name = TestConstructors.class.getName() + "$" + className + "::"; + Long count = methods.get(name); + if (count == null) { + throw new Exception("Could not find traced method " + name); + } + if (count != expectedCount) { + throw new Exception("Expected " + expectedCount + " trace event for " + name); + } + } + + private static Map buildMethodMap(List events) { + Map map = new TreeMap<>(); + for (RecordedEvent e : events) { + RecordedMethod m = e.getValue("method"); + String name = m.getType().getName() + "::" + m.getName(); + map.compute(name, (_, value) -> (value == null) ? 1 : value + 1); + } + for (var e : map.entrySet()) { + System.out.println(e.getKey() + " " + e.getValue()); + } + return map; + } +} \ No newline at end of file diff --git a/test/jdk/jdk/jfr/event/tracing/TestInstrumentation.java b/test/jdk/jdk/jfr/event/tracing/TestInstrumentation.java index 834d4ab4989e..5709c95812aa 100644 --- a/test/jdk/jdk/jfr/event/tracing/TestInstrumentation.java +++ b/test/jdk/jdk/jfr/event/tracing/TestInstrumentation.java @@ -93,6 +93,8 @@ private static void verifyTracing(List events) throws Exception { assertMethod(map, "exception", 2); assertMethod(map, "switchExpression", 3); assertMethod(map, "recursive", 4); + assertMethod(map, "deepException", 1); + assertMethod(map, "whileTrue", 1); assertMethod(map, "multipleReturns", 5); if (!map.isEmpty()) { throw new Exception("Found unexpected methods " + map.keySet()); @@ -105,6 +107,8 @@ private static void verifyTiming(List events) throws Exception { assertMethod(map, "exception", 2); assertMethod(map, "switchExpression", 3); assertMethod(map, "recursive", 4); + assertMethod(map, "deepException", 1); + assertMethod(map, "whileTrue", 1); assertMethod(map, "multipleReturns", 5); for (var entry : map.entrySet()) { long invocations = entry.getValue(); From f18c87f1dd0bec2b4e5e3f184b9a096a9df88833 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Tue, 31 Mar 2026 13:34:36 +0000 Subject: [PATCH 098/168] 8373239: Test java/awt/print/PrinterJob/PageRanges.java fails with incorrect selection of printed pages Backport-of: 1161a640abe454b47de95ed73452a78535160deb --- .../windows/classes/sun/awt/windows/WPrinterJob.java | 6 +++++- test/jdk/java/awt/print/PrinterJob/PageRanges.java | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/java.desktop/windows/classes/sun/awt/windows/WPrinterJob.java b/src/java.desktop/windows/classes/sun/awt/windows/WPrinterJob.java index e50cfcff33b1..6879ab327d70 100644 --- a/src/java.desktop/windows/classes/sun/awt/windows/WPrinterJob.java +++ b/src/java.desktop/windows/classes/sun/awt/windows/WPrinterJob.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -1741,6 +1741,10 @@ private void setRangeCopiesAttribute(int from, int to, boolean isRangeSet, if (isRangeSet) { attributes.add(new PageRanges(from, to)); setPageRange(from, to); + } else { + attributes.remove(PageRanges.class); + setPageRange(Pageable.UNKNOWN_NUMBER_OF_PAGES, + Pageable.UNKNOWN_NUMBER_OF_PAGES); } defaultCopies = false; attributes.add(new Copies(copies)); diff --git a/test/jdk/java/awt/print/PrinterJob/PageRanges.java b/test/jdk/java/awt/print/PrinterJob/PageRanges.java index aea60516f782..691357d5a3c8 100644 --- a/test/jdk/java/awt/print/PrinterJob/PageRanges.java +++ b/test/jdk/java/awt/print/PrinterJob/PageRanges.java @@ -23,7 +23,7 @@ /* * @test - * @bug 6575331 8297191 + * @bug 6575331 8297191 8373239 * @key printer * @summary The specified pages should be printed. * @library /java/awt/regtesthelpers From f361c4816d38807f535d0e75aa85d4c148e55ff6 Mon Sep 17 00:00:00 2001 From: Goetz Lindenmaier Date: Tue, 31 Mar 2026 13:51:25 +0000 Subject: [PATCH 099/168] 8365424: [macos26] java/awt/Frame/DisposeTest.java fails on macOS 26 Backport-of: aee73d3568fbcb2fe7293f92154e6677c080d20c --- test/jdk/java/awt/Frame/DisposeTest.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/jdk/java/awt/Frame/DisposeTest.java b/test/jdk/java/awt/Frame/DisposeTest.java index 08c0def638e6..ea1b2428f5c0 100644 --- a/test/jdk/java/awt/Frame/DisposeTest.java +++ b/test/jdk/java/awt/Frame/DisposeTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -44,6 +44,7 @@ public class DisposeTest { private static Frame backgroundFrame; private static Frame testedFrame; + private static final int PIXEL_OFFSET = 4; private static final Rectangle backgroundFrameBounds = new Rectangle(100, 100, 200, 200); @@ -74,8 +75,8 @@ private static void test() { BufferedImage bi = robot.createScreenCapture(backgroundFrameBounds); int redPix = Color.RED.getRGB(); - for (int x = 0; x < bi.getWidth(); x++) { - for (int y = 0; y < bi.getHeight(); y++) { + for (int x = PIXEL_OFFSET; x < bi.getWidth() - PIXEL_OFFSET; x++) { + for (int y = PIXEL_OFFSET; y < bi.getHeight() - PIXEL_OFFSET; y++) { if (bi.getRGB(x, y) != redPix) { try { ImageIO.write(bi, "png", From 12dea557a50743b6ff196f444a401a432a7d3fd5 Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Wed, 1 Apr 2026 11:14:23 +0000 Subject: [PATCH 100/168] 8372272: Hotspot shared lib loading - add load attempts to Events::log Backport-of: d328e4e7e2f58fbfeb661f3502f95016159d7230 --- src/hotspot/os/aix/os_aix.cpp | 2 ++ src/hotspot/os/bsd/os_bsd.cpp | 2 ++ src/hotspot/os/linux/os_linux.cpp | 2 ++ src/hotspot/os/windows/os_windows.cpp | 2 ++ 4 files changed, 8 insertions(+) diff --git a/src/hotspot/os/aix/os_aix.cpp b/src/hotspot/os/aix/os_aix.cpp index e6d8791367ae..5cf34a894c33 100644 --- a/src/hotspot/os/aix/os_aix.cpp +++ b/src/hotspot/os/aix/os_aix.cpp @@ -1049,6 +1049,8 @@ static void* dll_load_library(const char *filename, int *eno, char *ebuf, int eb dflags |= RTLD_MEMBER; } + Events::log_dll_message(nullptr, "Attempting to load shared library %s", filename); + void* result; const char* error_report = nullptr; JFR_ONLY(NativeLibraryLoadEvent load_event(filename, &result);) diff --git a/src/hotspot/os/bsd/os_bsd.cpp b/src/hotspot/os/bsd/os_bsd.cpp index bfbea8497687..e58e1e085113 100644 --- a/src/hotspot/os/bsd/os_bsd.cpp +++ b/src/hotspot/os/bsd/os_bsd.cpp @@ -1047,6 +1047,8 @@ void *os::Bsd::dlopen_helper(const char *filename, int mode, char *ebuf, int ebu assert(rtn == 0, "fegetenv must succeed"); #endif // IA32 + Events::log_dll_message(nullptr, "Attempting to load shared library %s", filename); + void* result; JFR_ONLY(NativeLibraryLoadEvent load_event(filename, &result);) result = ::dlopen(filename, RTLD_LAZY); diff --git a/src/hotspot/os/linux/os_linux.cpp b/src/hotspot/os/linux/os_linux.cpp index 760c44a500b7..3ef58971c2c1 100644 --- a/src/hotspot/os/linux/os_linux.cpp +++ b/src/hotspot/os/linux/os_linux.cpp @@ -1949,6 +1949,8 @@ void * os::Linux::dlopen_helper(const char *filename, char *ebuf, int ebuflen) { assert(rtn == 0, "fegetenv must succeed"); #endif // IA32 + Events::log_dll_message(nullptr, "Attempting to load shared library %s", filename); + void* result; JFR_ONLY(NativeLibraryLoadEvent load_event(filename, &result);) result = ::dlopen(filename, RTLD_LAZY); diff --git a/src/hotspot/os/windows/os_windows.cpp b/src/hotspot/os/windows/os_windows.cpp index f09b1f5eb7b0..fee71654cf40 100644 --- a/src/hotspot/os/windows/os_windows.cpp +++ b/src/hotspot/os/windows/os_windows.cpp @@ -1719,6 +1719,8 @@ static int _print_module(const char* fname, address base_address, // same architecture as Hotspot is running on void * os::dll_load(const char *name, char *ebuf, int ebuflen) { log_info(os)("attempting shared library load of %s", name); + Events::log_dll_message(nullptr, "Attempting to load shared library %s", name); + void* result; JFR_ONLY(NativeLibraryLoadEvent load_event(name, &result);) result = LoadLibrary(name); From fe067a780c62fe2bf9d0f496f48235e1948e02e0 Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Wed, 1 Apr 2026 11:26:32 +0000 Subject: [PATCH 101/168] 8378836: Enable linktime-gc by default on Linux ppc64le Backport-of: cb059a6b1b1686b7adcb2e88536060f4f7d47118 --- make/autoconf/jdk-options.m4 | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/make/autoconf/jdk-options.m4 b/make/autoconf/jdk-options.m4 index d4299078abf2..9d67582b425f 100644 --- a/make/autoconf/jdk-options.m4 +++ b/make/autoconf/jdk-options.m4 @@ -103,8 +103,12 @@ AC_DEFUN_ONCE([JDKOPT_SETUP_JDK_OPTIONS], AC_SUBST(ENABLE_HEADLESS_ONLY) # should we linktime gc unused code sections in the JDK build ? - if test "x$OPENJDK_TARGET_OS" = "xlinux" && test "x$OPENJDK_TARGET_CPU" = xs390x; then - LINKTIME_GC_DEFAULT=true + if test "x$OPENJDK_TARGET_OS" = "xlinux"; then + if test "x$OPENJDK_TARGET_CPU" = "xs390x" || test "x$OPENJDK_TARGET_CPU" = "xppc64le"; then + LINKTIME_GC_DEFAULT=true + else + LINKTIME_GC_DEFAULT=false + fi else LINKTIME_GC_DEFAULT=false fi From 013dc169fdb21c0de33f075671a8dc9a34a498e5 Mon Sep 17 00:00:00 2001 From: Goetz Lindenmaier Date: Wed, 1 Apr 2026 15:01:17 +0000 Subject: [PATCH 102/168] 8365423: [macos26] java/awt/MenuBar/8007006/bug8007006.java fails on macOS 26 Backport-of: bbbb9c5f1557cb1e80d72a820c034c71308cb3a2 --- .../java/awt/MenuBar/8007006/bug8007006.java | 136 ++++++++++-------- 1 file changed, 73 insertions(+), 63 deletions(-) diff --git a/test/jdk/java/awt/MenuBar/8007006/bug8007006.java b/test/jdk/java/awt/MenuBar/8007006/bug8007006.java index cebf9cd8da9b..0bc9bdeefffe 100644 --- a/test/jdk/java/awt/MenuBar/8007006/bug8007006.java +++ b/test/jdk/java/awt/MenuBar/8007006/bug8007006.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -21,76 +21,86 @@ * questions. */ -/** +import java.awt.Color; +import java.awt.EventQueue; +import java.awt.Frame; +import java.awt.GraphicsConfiguration; +import java.awt.GraphicsEnvironment; +import java.awt.Insets; +import java.awt.Menu; +import java.awt.MenuBar; +import java.awt.MenuItem; +import java.awt.Robot; +import java.awt.Toolkit; +import java.awt.event.InputEvent; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/* * @test * @key headful * @bug 8007006 * @requires (os.family == "mac") * @summary [macosx] Closing subwindow loses main window menus. - * @library /test/lib - * @build jdk.test.lib.Platform - * @run main bug8007006 */ -import java.awt.*; -import java.awt.event.*; - -import jdk.test.lib.Platform; - public class bug8007006 { - private static Frame frame1; - private static Frame frame2; - private static volatile boolean isActionPerformed; + private static Frame mainFrame; + private static Frame subFrame; + private static final CountDownLatch isActionPerformed = new CountDownLatch(1); public static void main(String[] args) throws Exception { - if (!Platform.isOSX()) { - System.out.println("This test is for MacOS only. Automatically passed on other platforms."); - return; - } - - System.setProperty("apple.laf.useScreenMenuBar", "true"); + try { + System.setProperty("apple.laf.useScreenMenuBar", "true"); + Robot robot = new Robot(); - Robot robot = new Robot(); - robot.setAutoDelay(300); + EventQueue.invokeAndWait(bug8007006::createAndShowGUI); + robot.waitForIdle(); + robot.delay(1000); - createAndShowGUI(); - robot.waitForIdle(); - frame2.dispose(); - robot.waitForIdle(); + EventQueue.invokeAndWait(() -> subFrame.dispose()); + robot.waitForIdle(); + robot.delay(300); - performMenuItemTest(robot); + performMenuItemTest(robot); - frame1.dispose(); - if (!isActionPerformed) { - throw new Exception("Test failed: menu item action was not performed"); + if (!isActionPerformed.await(1, TimeUnit.SECONDS)) { + throw new Exception("Test failed: menu item action was not performed"); + } + } finally { + EventQueue.invokeAndWait(() -> { + if (mainFrame != null) { + mainFrame.dispose(); + } + }); } } private static void createAndShowGUI() { - frame1 = new Frame("Frame 1"); - frame1.setMenuBar(createMenuBar()); - frame1.setSize(200, 200); - - frame2 = new Frame("Frame 2"); - frame2.setMenuBar(createMenuBar()); - frame2.setSize(200, 200); - - frame1.setVisible(true); - frame2.setVisible(true); + mainFrame = new Frame("Frame 1"); + mainFrame.setMenuBar(createMenuBar()); + mainFrame.setSize(200, 200); + mainFrame.setBackground(Color.GREEN); + mainFrame.setLocationRelativeTo(null); + + subFrame = new Frame("Frame 2"); + subFrame.setMenuBar(createMenuBar()); + subFrame.setSize(200, 200); + subFrame.setBackground(Color.RED); + subFrame.setLocationRelativeTo(null); + + mainFrame.setVisible(true); + subFrame.setVisible(true); } private static MenuBar createMenuBar() { - // A very long name makes it more likely that the robot will hit the - // menu + // A very long name makes it more likely + // that the robot will hit the menu Menu menu = new Menu("TestTestTestTestTestTestTestTestTestTest"); MenuItem item = new MenuItem("TestTestTestTestTestTestTestTestTestTest"); - item.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent ev) { - isActionPerformed = true; - } - }); menu.add(item); + item.addActionListener(ev -> isActionPerformed.countDown()); + MenuBar mb = new MenuBar(); mb.add(menu); return mb; @@ -102,29 +112,29 @@ private static void performMenuItemTest(Robot robot) { // of the first menu. // Unfortunately, the application name can vary based on how the // application is run. - // The work around is to make the menu and the menu item names very + // The workaround is to make the menu and the menu item names very // long. + Toolkit toolkit = Toolkit.getDefaultToolkit(); + GraphicsConfiguration gc = + GraphicsEnvironment.getLocalGraphicsEnvironment() + .getDefaultScreenDevice() + .getDefaultConfiguration(); + + Insets screenInsets = toolkit.getScreenInsets(gc); + System.out.println("Screen insets: " + screenInsets); + int menuBarX = 250; - int menuBarY = 11; + int menuBarY = screenInsets.top / 2; int menuItemX = menuBarX; - int menuItemY = 34; + int menuItemY = screenInsets.top + 10; robot.mouseMove(menuBarX, menuBarY); robot.mousePress(InputEvent.BUTTON1_DOWN_MASK); - robot.mouseMove(menuItemX, menuItemY); robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK); robot.waitForIdle(); - waitForAction(); - } - private static void waitForAction() { - try { - for (int i = 0; i < 10; i++) { - if (isActionPerformed) { - return; - } - Thread.sleep(100); - } - } catch (InterruptedException ex) { - } + robot.mouseMove(menuItemX, menuItemY); + robot.mousePress(InputEvent.BUTTON1_DOWN_MASK); + robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK); + robot.waitForIdle(); } } From 4d6a994bbcaebca9f37255e5826e0fe8ce10a757 Mon Sep 17 00:00:00 2001 From: Goetz Lindenmaier Date: Wed, 1 Apr 2026 15:04:19 +0000 Subject: [PATCH 103/168] 8374304: MultiResolutionSplashTest.java fails in CI: "Image with wrong resolution is used for splash screen!" Backport-of: 615aba8257460edd08dc1d825d9394d98cef8e35 --- .../MultiResolutionSplashTest.java | 185 +++++++++--------- 1 file changed, 89 insertions(+), 96 deletions(-) diff --git a/test/jdk/java/awt/SplashScreen/MultiResolutionSplash/MultiResolutionSplashTest.java b/test/jdk/java/awt/SplashScreen/MultiResolutionSplash/MultiResolutionSplashTest.java index 1f2de081cce1..c0a9c90feba3 100644 --- a/test/jdk/java/awt/SplashScreen/MultiResolutionSplash/MultiResolutionSplashTest.java +++ b/test/jdk/java/awt/SplashScreen/MultiResolutionSplash/MultiResolutionSplashTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,30 +22,25 @@ */ import java.awt.Color; -import java.awt.Dialog; import java.awt.Frame; import java.awt.Graphics; -import java.awt.Graphics2D; import java.awt.GraphicsEnvironment; -import java.awt.Panel; import java.awt.Rectangle; import java.awt.Robot; import java.awt.SplashScreen; import java.awt.TextField; -import java.awt.Window; import java.awt.event.KeyEvent; import java.awt.image.BufferedImage; import java.io.File; -import javax.imageio.ImageIO; -import sun.java2d.SunGraphics2D; +import java.io.IOException; +import javax.imageio.ImageIO; -/** +/* * @test * @key headful * @bug 8043869 8075244 8078082 8145173 8151787 8212213 * @summary Tests the HiDPI splash screen support for windows and MAC - * @modules java.desktop/sun.java2d * @run main MultiResolutionSplashTest GENERATE_IMAGES * @run main/othervm -splash:splash1.png MultiResolutionSplashTest TEST_SPLASH 0 * @run main/othervm -splash:splash2 MultiResolutionSplashTest TEST_SPLASH 1 @@ -56,47 +51,54 @@ public class MultiResolutionSplashTest { private static final int IMAGE_WIDTH = 300; private static final int IMAGE_HEIGHT = 200; - private static boolean isMac; - static { - isMac = System.getProperty("os.name").contains("OS X"); - } + private static final boolean isMac = System.getProperty("os.name") + .contains("OS X"); + private static final ImageInfo[] tests = { - new ImageInfo("splash1.png", "splash1@2x.png", Color.BLUE, Color.GREEN), - new ImageInfo("splash2", "splash2@2x", Color.WHITE, Color.BLACK), - new ImageInfo("splash3.", "splash3@2x.", Color.YELLOW, Color.RED) + new ImageInfo("splash1", ".png", Color.BLUE, Color.GREEN), + new ImageInfo("splash2", "", Color.WHITE, Color.BLACK), + new ImageInfo("splash3", ".", Color.YELLOW, Color.RED) }; public static void main(String[] args) throws Exception { - - String test = args[0]; - switch (test) { + switch (args[0]) { case "GENERATE_IMAGES": generateImages(); break; case "TEST_SPLASH": - int index = Integer.parseInt(args[1]); - testSplash(tests[index]); + testSplash(tests[Integer.parseInt(args[1])]); break; case "TEST_FOCUS": testFocus(); break; default: - throw new RuntimeException("Unknown test: " + test); + throw new RuntimeException("Unknown test: " + args[0]); } } static void testSplash(ImageInfo test) throws Exception { SplashScreen splashScreen = SplashScreen.getSplashScreen(); - if (splashScreen == null) { throw new RuntimeException("Splash screen is not shown!"); } - Graphics2D g = splashScreen.createGraphics(); - Rectangle splashBounds = splashScreen.getBounds(); - int screenX = (int) splashBounds.getCenterX(); - int screenY = (int) splashBounds.getCenterY(); + final Rectangle splashBounds = splashScreen.getBounds(); + final double scaleFactor = getScreenScaleFactor(); + + final Robot robot = new Robot(); + // Allow time for the splash screen to show + robot.delay(100); + + BufferedImage splashCapture = robot.createScreenCapture(splashBounds); + String captureFileName = "splashscreen-%1.2f-%s.png" + .formatted(scaleFactor, test.name1x); + saveImageNoError(splashCapture, new File(captureFileName)); + + // Close the splash screen; this gives time for it to be fully removed + splashScreen.close(); + robot.waitForIdle(); + if (splashBounds.width != IMAGE_WIDTH) { throw new RuntimeException( "SplashScreen#getBounds has wrong width"); @@ -106,19 +108,19 @@ static void testSplash(ImageInfo test) throws Exception { "SplashScreen#getBounds has wrong height"); } - Robot robot = new Robot(); - Color splashScreenColor = robot.getPixelColor(screenX, screenY); - float scaleFactor = getScaleFactor(); + Color splashScreenColor = + new Color(splashCapture.getRGB(splashBounds.width / 2, + splashBounds.height / 2)); Color testColor = (1 < scaleFactor) ? test.color2x : test.color1x; if (!compare(testColor, splashScreenColor)) { throw new RuntimeException( - "Image with wrong resolution is used for splash screen!"); + "Image with wrong resolution is used for splash screen! " + + "Refer to " + captureFileName); } } static void testFocus() throws Exception { - Robot robot = new Robot(); robot.setAutoWaitForIdle(true); robot.setAutoDelay(50); @@ -157,94 +159,85 @@ static boolean compare(int n, int m) { return Math.abs(n - m) <= 50; } - static float getScaleFactor() { - - final Dialog dialog = new Dialog((Window) null); - dialog.setSize(100, 100); - dialog.setModal(true); - final float[] scaleFactors = new float[1]; - Panel panel = new Panel() { - - @Override - public void paint(Graphics g) { - float scaleFactor = 1; - if (g instanceof SunGraphics2D) { - scaleFactor = getScreenScaleFactor(); - } - scaleFactors[0] = scaleFactor; - dialog.setVisible(false); - } - }; - - dialog.add(panel); - dialog.setVisible(true); - dialog.dispose(); - - return scaleFactors[0]; - } - static void generateImages() throws Exception { for (ImageInfo test : tests) { - generateImage(test.name1x, test.color1x, 1); - generateImage(test.name2x, test.color2x, getScreenScaleFactor()); + generateImage(test.name1x, test.color1x, 1.0); + + // Ensure the second image uses scale greater than 1.0 + double scale = getAdjustedScaleFactor(); + generateImage(test.name2x, test.color2x, scale); } } - static void generateImage(String name, Color color, float scale) throws Exception { + static void generateImage(final String name, + final Color color, + final double scale) throws Exception { File file = new File(name); if (file.exists()) { return; } - BufferedImage image = new BufferedImage((int) (scale * IMAGE_WIDTH), - (int) (scale * IMAGE_HEIGHT), BufferedImage.TYPE_INT_RGB); + + final int width = (int) (scale * IMAGE_WIDTH); + final int height = (int) (scale * IMAGE_HEIGHT); + BufferedImage image = new BufferedImage(width, + height, + BufferedImage.TYPE_INT_RGB); Graphics g = image.getGraphics(); g.setColor(color); - g.fillRect(0, 0, (int) (scale * IMAGE_WIDTH), (int) (scale * IMAGE_HEIGHT)); + g.fillRect(0, 0, width, height); + + saveImage(image, file); + } + + private static void saveImage(BufferedImage image, + File file) throws IOException { ImageIO.write(image, "png", file); } - static float getScreenScaleFactor() { - return (float) GraphicsEnvironment. - getLocalGraphicsEnvironment(). - getDefaultScreenDevice().getDefaultConfiguration(). - getDefaultTransform().getScaleX(); + private static void saveImageNoError(BufferedImage image, + File file) { + try { + saveImage(image, file); + } catch (IOException ignored) { + } } - static class ImageInfo { + static double getScreenScaleFactor() { + return GraphicsEnvironment + .getLocalGraphicsEnvironment() + .getDefaultScreenDevice() + .getDefaultConfiguration() + .getDefaultTransform() + .getScaleX(); + } + + // Ensure the second image uses scale greater than 1.0 + static double getAdjustedScaleFactor() { + double scale = getScreenScaleFactor(); + return scale < 1.25 ? 2.0 : scale; + } + static class ImageInfo { final String name1x; final String name2x; final Color color1x; final Color color2x; - public ImageInfo(String name1x, String name2x, Color color1x, Color color2x) { - this.name1x = name1x; - if (!isMac) { - float scale = getScreenScaleFactor(); - StringBuffer buff = new StringBuffer(); - if (scale - (int) scale > 0) { - buff.append("@").append((int) (scale * 100)).append("pct"); - } else { - buff.append("@").append((int) scale).append("x"); - } - StringBuffer buffer = new StringBuffer(); - String[] splitStr = name1x.split("\\."); - if (splitStr.length == 2) { - this.name2x = buffer.append(splitStr[0]).append(buff) - .append(".").append(splitStr[1]).toString(); - } else { - if (name1x.indexOf(".") > 0) { - this.name2x = buffer.append(splitStr[0]).append(buff).append(".").toString(); - } else { - this.name2x = buffer.append(splitStr[0]).append(buff).toString(); - } - } - } else { - this.name2x = name2x; - } + public ImageInfo(String baseName, String ext, + Color color1x, Color color2x) { + this.name1x = baseName + ext; + this.name2x = createName2x(baseName, ext); this.color1x = color1x; this.color2x = color2x; } + + private static String createName2x(String baseName, String ext) { + double scale = getAdjustedScaleFactor(); + if (!isMac && (((int) (scale * 100)) % 100 != 0)) { + return baseName + "@" + ((int) (scale * 100)) + "pct" + ext; + } else { + return baseName + "@" + ((int) scale) + "x" + ext; + } + } } } - From 2dcae678a36c58aa3a03faf09c04405a49ec62ad Mon Sep 17 00:00:00 2001 From: Satyen Subramaniam Date: Wed, 1 Apr 2026 16:24:54 +0000 Subject: [PATCH 104/168] 8369251: Opensource few tests Backport-of: 4ed364033daef96f6141a3ad2d217fa1ec5eca3e --- test/jdk/java/awt/Choice/PaintArtefacts.java | 122 ++++++++++++ .../awt/Choice/SelectBetweenPressRelease.java | 146 ++++++++++++++ .../FullScreen/DisplayModeNoRefreshTest.java | 115 +++++++++++ .../ImagePrinting/BitmaskImage.java | 142 +++++++++++++ .../ClientDecoratedIconTest.java | 186 ++++++++++++++++++ .../ClientDecoratedIcon/DialogIconTest.java | 173 ++++++++++++++++ .../DialogInheritIcon.java | 71 +++++++ .../swing/text/JTextComponent/bug4532590.java | 147 ++++++++++++++ 8 files changed, 1102 insertions(+) create mode 100644 test/jdk/java/awt/Choice/PaintArtefacts.java create mode 100644 test/jdk/java/awt/Choice/SelectBetweenPressRelease.java create mode 100644 test/jdk/java/awt/FullScreen/DisplayModeNoRefreshTest.java create mode 100644 test/jdk/java/awt/print/PrinterJob/ImagePrinting/BitmaskImage.java create mode 100644 test/jdk/javax/swing/JFrame/ClientDecoratedIcon/ClientDecoratedIconTest.java create mode 100644 test/jdk/javax/swing/JFrame/ClientDecoratedIcon/DialogIconTest.java create mode 100644 test/jdk/javax/swing/JFrame/ClientDecoratedIcon/DialogInheritIcon.java create mode 100644 test/jdk/javax/swing/text/JTextComponent/bug4532590.java diff --git a/test/jdk/java/awt/Choice/PaintArtefacts.java b/test/jdk/java/awt/Choice/PaintArtefacts.java new file mode 100644 index 000000000000..859525e8c943 --- /dev/null +++ b/test/jdk/java/awt/Choice/PaintArtefacts.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2006, 2025, Oracle and/or its affiliates. All rights reserved. + * 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 6405689 + * @key headful + * @summary Reg: Painting is not happening properly, + * when Choice scrollbar gets disappeared after selected item + * @library /java/awt/regtesthelpers + * @build PassFailJFrame + * @run main/manual PaintArtefacts +*/ + +import java.awt.Button; +import java.awt.Choice; +import java.awt.FlowLayout; +import java.awt.Frame; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; + +public class PaintArtefacts { + + static boolean removeItems = true; + private static final String INSTRUCTIONS = """ + The problem is seen on XToolkit only. + Open the choice and press down or up key several times + until the scrollbar gets disappeared. + At that moment you may see a painting artefacts on dropdown menu + If you see them, press Fail. + If you don't see them press Add/Remove switch button and + again open the choice and press Up/Down key several times + until Scrollbar gets appeared back. + If you still don't see any painting artefacts press Pass. + """; + + private static Frame init() { + Frame frame = new Frame("Painting Frame"); + Button b = new Button ("Add/Remove switch"); + final Choice ch = new Choice(); + ch.add("Praveen"); + ch.add("Mohan"); + ch.add("Rakesh"); + ch.add("Menon"); + ch.add("Girish"); + ch.add("Ramachandran"); + ch.add("Elancheran"); + ch.add("Subramanian"); + ch.add("Raju"); + ch.add("Pallath"); + ch.add("Mayank"); + ch.add("Joshi"); + ch.add("Sundar"); + ch.add("Srinivas"); + ch.add("Mandalika"); + ch.add("Suresh"); + ch.add("Chandar"); + ch.select(1); + frame.setLayout(new FlowLayout()); + frame.add(ch); + frame.add(b); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent ae) { + PassFailJFrame.log(ae.toString()); + PassFailJFrame.log("selected index = " + ch.getSelectedIndex()); + removeItems = !removeItems; + } + }); + ch.addItemListener(new ItemListener() { + public void itemStateChanged(ItemEvent ie) { + PassFailJFrame.log(ie.toString()); + PassFailJFrame.log("selected index = " + ch.getSelectedIndex()); + if (removeItems) { + PassFailJFrame.log("REMOVE : " + ch.getSelectedIndex()); + ch.remove(ch.getSelectedIndex()); + } else { + PassFailJFrame.log("ADD : "+ch.getSelectedIndex() + "/" + "new item"); + ch.add("new item"); + } + } + }); + frame.setSize(200, 200); + + for (int i = 0; i < 5; i++){ + ch.remove(ch.getSelectedIndex()); + } + + return frame; + } + + public static void main(String[] args) throws Exception { + PassFailJFrame.builder() + .instructions(INSTRUCTIONS) + .columns(45) + .testUI(PaintArtefacts::init) + .logArea() + .build() + .awaitAndCheck(); + } +} diff --git a/test/jdk/java/awt/Choice/SelectBetweenPressRelease.java b/test/jdk/java/awt/Choice/SelectBetweenPressRelease.java new file mode 100644 index 000000000000..21262c67922f --- /dev/null +++ b/test/jdk/java/awt/Choice/SelectBetweenPressRelease.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * 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 6318746 + * @key headful + * @summary REG: File Selection is failing for every second selection made in the FileDlg drop-down, XToolkit +*/ + +import java.awt.Choice; +import java.awt.EventQueue; +import java.awt.FlowLayout; +import java.awt.Frame; +import java.awt.Point; +import java.awt.Robot; +import java.awt.event.InputEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +public class SelectBetweenPressRelease { + static Frame frame; + static Choice ch; + static Robot r; + static volatile Point loc; + static volatile int selectedIndex; + + public static void main(final String[] args) throws Exception { + r = new Robot(); + try { + EventQueue.invokeAndWait(() -> init()); + r.waitForIdle(); + r.delay(1000); + test(); + } finally { + EventQueue.invokeAndWait(() -> frame.dispose()); + } + } + + private static void init() { + frame = new Frame("SelectBetweenPressRelease"); + ch = new Choice(); + ch.add("0"); + ch.add("1"); + frame.add(ch); + + frame.setLayout (new FlowLayout ()); + + addListener(); + frame.setSize(200, 200); + frame.setLocationRelativeTo(null); + frame.setVisible(true); + frame.validate(); + } + + + private static void test() throws Exception { + EventQueue.invokeAndWait(() -> ch.select(0)); + + EventQueue.invokeAndWait(() -> { + loc = ch.getLocationOnScreen(); + }); + r.delay(1000); + r.waitForIdle(); + + r.mouseMove(loc.x+ch.getWidth()/2, loc.y+ch.getHeight()/2); + r.delay(10); + r.mousePress(InputEvent.BUTTON1_DOWN_MASK); + r.delay(100); + // This code leads to the bug + EventQueue.invokeAndWait(() -> ch.select(1)); + r.delay(100); + r.mouseRelease(InputEvent.BUTTON1_DOWN_MASK); + r.delay(1000); + r.waitForIdle(); + + // 'selected' variable wich stored in the peer still equals 0 + // if the bug is reproducible + // so the next ISC to the first item by mouse will be ignored + + // try to hit the first item + if (System.getProperty("os.name").startsWith("Mac")) { + r.mouseMove(loc.x + ch.getWidth() / 2, loc.y + ch.getHeight() / 2); + } else { + r.mouseMove(loc.x + ch.getWidth() / 2, + loc.y + ch.getHeight() * 3 / 2); + } + r.delay(10); + r.mousePress(InputEvent.BUTTON1_DOWN_MASK); + r.delay(100); + r.mouseRelease(InputEvent.BUTTON1_DOWN_MASK); + r.delay(1000); + r.waitForIdle(); + + EventQueue.invokeAndWait(() -> { + selectedIndex = ch.getSelectedIndex(); + }); + if (selectedIndex != 0){ + throw new RuntimeException("Test failed. ch.getSelectedIndex() = "+selectedIndex); + } + + } + + // just for logging + private static void addListener(){ + frame.addMouseListener( + new MouseAdapter(){ + public void mousePressed(MouseEvent me){ + System.out.println(me); + } + public void mouseReleased(MouseEvent me){ + System.out.println(me); + } + }); + ch.addMouseListener( + new MouseAdapter(){ + public void mousePressed(MouseEvent me){ + System.out.println(me); + } + public void mouseReleased(MouseEvent me){ + System.out.println(me); + } + }); + } + +} diff --git a/test/jdk/java/awt/FullScreen/DisplayModeNoRefreshTest.java b/test/jdk/java/awt/FullScreen/DisplayModeNoRefreshTest.java new file mode 100644 index 000000000000..a52990371221 --- /dev/null +++ b/test/jdk/java/awt/FullScreen/DisplayModeNoRefreshTest.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2004, 2025, Oracle and/or its affiliates. All rights reserved. + * 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 5041225 + * @key headful + * @summary Tests that we can set a display mode with unknown refresh rate + * if corresponding system display mode (with equal w/h/d) is available. + * @run main DisplayModeNoRefreshTest + */ + +import java.awt.Color; +import java.awt.DisplayMode; +import java.awt.EventQueue; +import java.awt.Frame; +import java.awt.GraphicsDevice; +import java.awt.GraphicsEnvironment; + +public class DisplayModeNoRefreshTest extends Frame { + + private static DisplayModeNoRefreshTest fs; + + private static final GraphicsDevice gd = + GraphicsEnvironment.getLocalGraphicsEnvironment() + .getDefaultScreenDevice(); + + private static final DisplayMode origMode = gd.getDisplayMode(); + + public DisplayModeNoRefreshTest() { + super("DisplayModeNoRefreshTest"); + if (!gd.isFullScreenSupported()) { + System.out.println("Full Screen is not supported, test considered passed."); + return; + } + setBackground(Color.green); + gd.setFullScreenWindow(this); + DisplayMode dlMode = getNoRefreshDisplayMode(gd.getDisplayModes()); + if (dlMode != null) { + System.out.println("Selected Display Mode: " + + " Width " + dlMode.getWidth() + + " Height " + dlMode.getHeight() + + " BitDepth " + dlMode.getBitDepth() + + " Refresh Rate " + dlMode.getRefreshRate()); + try { + gd.setDisplayMode(dlMode); + } catch (IllegalArgumentException ex) { + throw new RuntimeException("Test Failed due to IAE", ex); + } + } else { + System.out.println("No suitable display mode available, test considered passed."); + return; + } + + try { Thread.sleep(2000); } catch (InterruptedException e) {} + + System.out.println("Test Passed."); + } + + public DisplayMode getNoRefreshDisplayMode(DisplayMode dm[]) { + DisplayMode mode = new DisplayMode(640, 480, 32, DisplayMode.REFRESH_RATE_UNKNOWN); + int i = 0; + for (i = 0; i < dm.length; i++) { + if (mode.getWidth() == dm[i].getWidth() + && mode.getHeight() == dm[i].getHeight() + && mode.getBitDepth() == dm[i].getBitDepth()) { + return mode; + } + } + if (dm.length > 0) { + return + new DisplayMode(dm[0].getWidth(), dm[0].getHeight(), + dm[0].getBitDepth(), + DisplayMode.REFRESH_RATE_UNKNOWN); + } + + return null; + } + + public static void main(String[] args) throws Exception { + try { + EventQueue.invokeAndWait(() -> { + System.setProperty("sun.java2d.noddraw", "true"); + fs = new DisplayModeNoRefreshTest(); + }); + } finally { + gd.setDisplayMode(origMode); + EventQueue.invokeAndWait(() -> { + if (fs != null) { + fs.dispose(); + } + }); + } + } +} diff --git a/test/jdk/java/awt/print/PrinterJob/ImagePrinting/BitmaskImage.java b/test/jdk/java/awt/print/PrinterJob/ImagePrinting/BitmaskImage.java new file mode 100644 index 000000000000..f6a19fb20703 --- /dev/null +++ b/test/jdk/java/awt/print/PrinterJob/ImagePrinting/BitmaskImage.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2006, 2025, Oracle and/or its affiliates. All rights reserved. + * 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 6444688 + * @key printer + * @summary Print an image with an IndexedColorModel with transparent pixel. + * @library /java/awt/regtesthelpers + * @build PassFailJFrame + * @run main/manual BitmaskImage + */ + +import java.awt.Button; +import java.awt.Color; +import java.awt.Frame; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.geom.AffineTransform; +import java.awt.image.BufferedImage; +import java.awt.image.DataBuffer; +import java.awt.image.IndexColorModel; +import java.awt.print.PageFormat; +import java.awt.print.PrinterJob; +import java.awt.print.Printable; +import java.awt.print.PrinterException; + +import static java.awt.print.Printable.NO_SUCH_PAGE; +import static java.awt.print.Printable.PAGE_EXISTS; + +public class BitmaskImage implements Printable, ActionListener { + + static int sz = 1000; + BufferedImage bi; + + public BitmaskImage() { + int i = 0; + int[] cmap = new int[256]; + for (int r = 0; r < 256; r += 51) { + for (int g = 0; g < 256; g += 51) { + for (int b = 0; b < 256; b += 51) { + cmap[i++] = (0xff << 24) | (r << 16) | (g << 8) | b; + } + } + } + + IndexColorModel icm = new + IndexColorModel(8, 256, cmap, 0, true, 253, DataBuffer.TYPE_BYTE); + bi = new BufferedImage(sz, sz, BufferedImage.TYPE_BYTE_INDEXED, icm); + Graphics g = bi.getGraphics(); + Graphics2D g2d = (Graphics2D)g; + g.setColor(Color.white); + g.fillRect(0, 0, sz, sz); + g.setColor(Color.black); + int off = sz / 20; + int wh = sz / 10; + for (int x = off; x < sz; x += wh * 2) { + for (int y = off; y < sz; y += wh * 2) { + g.fillRect(x, y, wh, wh); + } + } + } + + public int print(Graphics g, PageFormat pf, int page) throws + PrinterException { + + if (page > 0) { /* We have only one page, and 'page' is zero-based */ + return NO_SUCH_PAGE; + } + + Graphics2D g2d = (Graphics2D)g; + AffineTransform tx = g2d.getTransform(); + double sx = tx.getScaleX(); + double sy = tx.getScaleY(); + g2d.translate(pf.getImageableX(), pf.getImageableY()); + g2d.scale(1/sx, 1/sx); + g.drawImage(bi, 10, 10, null); + + /* tell the caller that this page is part of the printed document */ + return PAGE_EXISTS; + } + + public void actionPerformed(ActionEvent e) { + PrinterJob job = PrinterJob.getPrinterJob(); + job.setPrintable(this); + boolean ok = job.printDialog(); + if (ok) { + try { + job.print(); + } catch (PrinterException ex) { + /* The job did not successfully complete */ + } + } + System.out.println("done"); + } + + static String INSTRUCTIONS = """ + Press the "Print Simple ICM Image" button and if a printer is available, + choose one in the dialog and click OK to start printing. + This test will print an image which contains a grid of black squares. + If it prints so, press Pass otherwise press Fail."""; + + public static Frame initTest() { + Frame f = new Frame("Image Printer"); + Button printButton = new Button("Print Simple ICM image..."); + printButton.addActionListener(new BitmaskImage()); + f.add(printButton); + f.pack(); + return f; + } + + public static void main(String[] args) throws Exception { + PassFailJFrame.builder() + .instructions(INSTRUCTIONS) + .testUI(BitmaskImage::initTest) + .columns(35) + .build() + .awaitAndCheck(); + } +} diff --git a/test/jdk/javax/swing/JFrame/ClientDecoratedIcon/ClientDecoratedIconTest.java b/test/jdk/javax/swing/JFrame/ClientDecoratedIcon/ClientDecoratedIconTest.java new file mode 100644 index 000000000000..95098984f720 --- /dev/null +++ b/test/jdk/javax/swing/JFrame/ClientDecoratedIcon/ClientDecoratedIconTest.java @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2006, 2025, Oracle and/or its affiliates. All rights reserved. + * 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 6436437 + * @requires (os.family == "windows") + * @summary Test setIconImages() for client-decorated JFrame + * @library ../../regtesthelpers + * @run main/manual ClientDecoratedIconTest + */ + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.FlowLayout; +import java.awt.Graphics; +import java.awt.Image; +import java.awt.image.BufferedImage; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.JRootPane; +import javax.swing.JTextArea; + +public class ClientDecoratedIconTest extends SwingTestHelper implements ActionListener { + JButton passed; + JButton failed; + + java.util.List icons1; + java.util.List icons2; + IconFrame frame1; + IconFrame frame2; + + Object lock = new Object(); + boolean done = false; + + protected String getInstructions() { + StringBuilder instructionsStr = new StringBuilder(); + instructionsStr.append("This tests the functionality of the setIconImages() API\n"); + instructionsStr.append("You will see two JFrames with custom icons1. Both JFrames should have the same icon: a colored box.\n"); + instructionsStr.append("If either of the JFrames has the default, coffe-cup icon, the test fails.\n"); + instructionsStr.append("If the JFrames DO NOT both have the same colored box as their icon, the test fails.\n"); + instructionsStr.append("If both JFrames DO have the same colored box as their icon, then the test passes."); + return instructionsStr.toString(); + } + + protected Component createContentPane() { + JPanel panel = new JPanel(); + panel.setLayout(new BorderLayout()); + JTextArea instructions = new JTextArea(getInstructions()); + panel.add(instructions, BorderLayout.CENTER); + + passed = new JButton("Icons match (PASS)"); + passed.addActionListener(this); + failed = new JButton("Icons don't match (FAIL)"); + failed.addActionListener(this); + JPanel btnPanel = new JPanel(); + btnPanel.setLayout(new FlowLayout()); + btnPanel.add(passed); + btnPanel.add(failed); + panel.add(btnPanel, BorderLayout.SOUTH); + + return panel; + } + + public void onEDT10() throws IOException { + Image img1 = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB); + Graphics g = img1.getGraphics(); + g.setColor(Color.green); + g.fillRect(0, 0, 16, 16); + g.dispose(); + Image img2 = new BufferedImage(24, 24, BufferedImage.TYPE_INT_ARGB); + g = img2.getGraphics(); + g.setColor(Color.magenta); + g.fillRect(0, 0, 24, 24); + g.dispose(); + Image img3 = new BufferedImage(32, 32, BufferedImage.TYPE_INT_ARGB); + g = img3.getGraphics(); + g.setColor(Color.red); + g.fillRect(0, 0, 32, 32); + g.dispose(); + Image img4 = new BufferedImage(64, 64, BufferedImage.TYPE_INT_ARGB); + g = img4.getGraphics(); + g.setColor(Color.blue); + g.fillRect(0, 0, 64, 64); + g.dispose(); + + icons1 = new ArrayList(4); + icons1.add(img1); + icons1.add(img2); + icons1.add(img3); + icons1.add(img4); + + icons2 = new ArrayList(4); + icons2.add(img4); + icons2.add(img3); + icons2.add(img2); + icons2.add(img1); + + frame1 = new IconFrame(icons1); + frame2 = new IconFrame(icons2); + + frame1.setLocation(50, 250); + frame2.setLocation(275, 250); + + frame1.setVisible(true); + frame2.setVisible(true); + } + + public void onEDT20() { + waitForCondition(new Runnable() { + public void run() { + while (true) { + synchronized(lock) { + if (done) { + return; + } + } + try { + Thread.sleep(250); + } + catch(InterruptedException e) {} + } + } + }); + System.out.println("done waiting"); + } + + public void onEDT30() { + // Needed so waitForCondition() has something to wait for :) + } + + public void actionPerformed(ActionEvent e) { + System.out.println("actionPerformed()"); + if (e.getSource() == passed) { + synchronized(lock) { + done = true; + } + } + if (e.getSource() == failed) { + throw new RuntimeException("Test Failed"); + } + } + + class IconFrame extends JFrame { + public IconFrame(java.util.List icons) { + super("Custom Icon Frame"); + setUndecorated(true); + getRootPane().setWindowDecorationStyle(JRootPane.FRAME); + setIconImages(icons); + setSize(200, 200); + } + } + + public static void main(String[] args) throws Throwable { + new ClientDecoratedIconTest().run(args); + System.out.println("end of main()"); + } +} diff --git a/test/jdk/javax/swing/JFrame/ClientDecoratedIcon/DialogIconTest.java b/test/jdk/javax/swing/JFrame/ClientDecoratedIcon/DialogIconTest.java new file mode 100644 index 000000000000..a129e38d39ab --- /dev/null +++ b/test/jdk/javax/swing/JFrame/ClientDecoratedIcon/DialogIconTest.java @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2006, 2025, Oracle and/or its affiliates. All rights reserved. + * 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 6436437 + * @requires (os.family == "windows") + * @summary Test setIconImages() for client-decorated JDialog + * @library ../../regtesthelpers + * @run main/manual DialogIconTest + */ + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.FlowLayout; +import java.awt.Graphics; +import java.awt.Frame; +import java.awt.Image; +import java.awt.image.BufferedImage; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JPanel; +import javax.swing.JRootPane; +import javax.swing.JTextArea; + +public class DialogIconTest extends SwingTestHelper implements ActionListener { + JButton passed; + JButton failed; + + java.util.List icons1; + IconDialog dialog1; + + Object lock = new Object(); + boolean done = false; + + protected String getInstructions() { + StringBuilder instructionsStr = new StringBuilder(); + instructionsStr.append("This tests the functionality of the setIconImages() API\n"); + instructionsStr.append("You will see a client-decorated JDialog. The JDialog should have a custom icon: a solid-colored box.\n"); + instructionsStr.append("If the JDialog has a colored box for an icon, then the test passes.\n"); + instructionsStr.append("If the JDialog has the default icon, then the test fails.\n"); + return instructionsStr.toString(); + } + + protected Component createContentPane() { + JPanel panel = new JPanel(); + panel.setLayout(new BorderLayout()); + JTextArea instructions = new JTextArea(getInstructions()); + panel.add(instructions, BorderLayout.CENTER); + + passed = new JButton("Solid-color Icon (PASS)"); + passed.addActionListener(this); + failed = new JButton("Default Icon (FAIL)"); + failed.addActionListener(this); + JPanel btnPanel = new JPanel(); + btnPanel.setLayout(new FlowLayout()); + btnPanel.add(passed); + btnPanel.add(failed); + panel.add(btnPanel, BorderLayout.SOUTH); + + return panel; + } + + public void onEDT10() throws IOException { + Image img1 = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB); + Graphics g = img1.getGraphics(); + g.setColor(Color.green); + g.fillRect(0, 0, 16, 16); + g.dispose(); + Image img2 = new BufferedImage(24, 24, BufferedImage.TYPE_INT_ARGB); + g = img2.getGraphics(); + g.setColor(Color.magenta); + g.fillRect(0, 0, 24, 24); + g.dispose(); + Image img3 = new BufferedImage(32, 32, BufferedImage.TYPE_INT_ARGB); + g = img3.getGraphics(); + g.setColor(Color.red); + g.fillRect(0, 0, 32, 32); + g.dispose(); + Image img4 = new BufferedImage(64, 64, BufferedImage.TYPE_INT_ARGB); + g = img4.getGraphics(); + g.setColor(Color.blue); + g.fillRect(0, 0, 64, 64); + g.dispose(); + + icons1 = new ArrayList(4); + icons1.add(img1); + icons1.add(img2); + icons1.add(img3); + icons1.add(img4); + + dialog1 = new IconDialog(icons1); + dialog1.setLocation(50, 250); + dialog1.setVisible(true); + } + + public void onEDT20() { + waitForCondition(new Runnable() { + public void run() { + while (true) { + synchronized(lock) { + if (done) { + return; + } + } + try { + Thread.sleep(250); + } + catch(InterruptedException e) {} + } + } + }); + System.out.println("done waiting"); + } + + public void onEDT30() { + // Needed so waitForCondition() has something to wait for :) + } + + public void actionPerformed(ActionEvent e) { + System.out.println("actionPerformed()"); + if (e.getSource() == passed) { + synchronized(lock) { + done = true; + } + } + if (e.getSource() == failed) { + throw new RuntimeException("Test Failed"); + } + } + + class IconDialog extends JDialog { + public IconDialog(java.util.List icons) { + super((Frame)null, "Custom Icon Frame", false); + setUndecorated(true); + getRootPane().setWindowDecorationStyle(JRootPane.FRAME); + setIconImages(icons); + setSize(200, 200); + } + } + + public static void main(String[] args) throws Throwable { + new DialogIconTest().run(args); + System.out.println("end of main()"); + } +} diff --git a/test/jdk/javax/swing/JFrame/ClientDecoratedIcon/DialogInheritIcon.java b/test/jdk/javax/swing/JFrame/ClientDecoratedIcon/DialogInheritIcon.java new file mode 100644 index 000000000000..908f83a6af43 --- /dev/null +++ b/test/jdk/javax/swing/JFrame/ClientDecoratedIcon/DialogInheritIcon.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2006, 2025, Oracle and/or its affiliates. All rights reserved. + * 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 6425606 + * @requires (os.family == "windows") + * @summary Test JDialog's inheritance of icons set with JFrame.setIconImages() + * @library ../../regtesthelpers + * @run main/manual DialogInheritIcon + */ + +import javax.swing.JDialog; + +public class DialogInheritIcon extends ClientDecoratedIconTest { + JDialog dialog1; + JDialog dialog2; + + /* + * @Override + */ + protected String getInstructions() { + StringBuilder instructionsStr = new StringBuilder(); + instructionsStr.append("This tests the functionality of JDialog-inherited icons set using the setIconImages() API.\n"); + instructionsStr.append("You will see two JFrames with custom icons, each with a child JDialog below it.\n"); + instructionsStr.append("Both JDialogs should have the same icon: a colored box.\n"); + instructionsStr.append("If either of the JDialogs has the default, coffe-cup icon, the test fails.\n"); + instructionsStr.append("If the JDialogs DO NOT both have the same colored box as their icon, the test fails.\n"); + instructionsStr.append("If both JDialogs DO have the same colored box as their icon, then the test passes.\n"); + instructionsStr.append("Note: If the JDialog icons don't match the icons of the parent JFrame, that is OK."); + return instructionsStr.toString(); + } + + public void onEDT15() { + createDialogs(); + dialog1.setVisible(true); + dialog2.setVisible(true); + } + + protected void createDialogs() { + dialog1 = new JDialog(frame1, "Child JDialog 1", false); + dialog1.setBounds(frame1.getLocation().x, frame1.getLocation().y + frame1.getSize().height + 5, 200, 200); + dialog2 = new JDialog(frame2, "Child JDialog 2", false); + dialog2.setBounds(frame2.getLocation().x, frame2.getLocation().y + frame2.getSize().height + 5, 200, 200); + } + + public static void main(String[] args) throws Throwable { + new DialogInheritIcon().run(args); + System.out.println("end of main()"); + } +} diff --git a/test/jdk/javax/swing/text/JTextComponent/bug4532590.java b/test/jdk/javax/swing/text/JTextComponent/bug4532590.java new file mode 100644 index 000000000000..d317a33fdea2 --- /dev/null +++ b/test/jdk/javax/swing/text/JTextComponent/bug4532590.java @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * 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 4532590 + * @summary Tests that selection is not painted when highlighter is set to null + * @run main bug4532590 + */ + +import java.awt.Color; +import java.awt.image.BufferedImage; +import javax.swing.JTextArea; +import javax.swing.JTextPane; +import javax.swing.text.BadLocationException; +import javax.swing.text.DefaultStyledDocument; +import javax.swing.text.JTextComponent; +import javax.swing.text.SimpleAttributeSet; +import javax.swing.SwingUtilities; + +public class bug4532590 { + + static final int SELECTION_START = 5; + static final int SELECTION_END = 10; + static final String TEXT = "Typein the missing word."; + + static final Color TEXT_FG = Color.BLACK; + static final Color TEXT_BG = Color.WHITE; + static final Color SELECTION_FG = Color.RED; + static final Color SELECTION_BG = Color.YELLOW; + + JTextComponent[] comps; + JTextPane pane; + JTextArea area, warea; + + int selFG = SELECTION_FG.getRGB(); + int selBG = SELECTION_BG.getRGB(); + + public bug4532590() throws BadLocationException { + // text pane + pane = new JTextPane(); + pane.setContentType("text/plain"); + + // populate the pane + DefaultStyledDocument dsd = new DefaultStyledDocument(); + dsd.insertString(0, "\n" + TEXT + "\n\n", new SimpleAttributeSet()); + pane.setDocument(dsd); + + // text area + area = new JTextArea(); + area.setText("\n" + TEXT); + + // wrapped text area + warea = new JTextArea(); + warea.setText("\n" + TEXT); + + comps = new JTextComponent[3]; + comps[0] = pane; + comps[1] = area; + comps[2] = warea; + } + + void initComp(JTextComponent comp) { + comp.setEditable(false); + comp.setForeground(TEXT_FG); + comp.setBackground(TEXT_BG); + comp.setSelectedTextColor(SELECTION_FG); + comp.setSelectionColor(SELECTION_BG); + comp.setHighlighter(null); + comp.setSize(comp.getPreferredSize()); + + comp.setSelectionStart(SELECTION_START); + comp.setSelectionEnd(SELECTION_END); + comp.getCaret().setSelectionVisible(true); + } + + /** + * Paint given component on an offscreen buffer + */ + BufferedImage drawComp(JTextComponent comp) { + int w = comp.getWidth(); + int h = comp.getHeight(); + + BufferedImage img = + new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); + comp.paint(img.createGraphics()); + return img; + } + + void testComp(JTextComponent comp) { + initComp(comp); + BufferedImage img = drawComp(comp); + int w = img.getWidth(null); + int h = img.getHeight(null); + + // scan the image + // there should be no SELECTION_FG or SELECTION_BG pixels + for (int i = 0; i < w; i++) { + for (int j = 0; j < h; j++) { + int rgb = img.getRGB(i, j); + if (rgb == selFG) { + throw new RuntimeException( + "Failed: selection foreground painted"); + } else if (rgb == selBG) { + throw new RuntimeException( + "Failed: selection background painted"); + } + } + } + } + + void test() { + for (int i = 0; i < comps.length; i++) { + testComp(comps[i]); + } + } + + public static void main(String[] args) throws Exception { + SwingUtilities.invokeAndWait(() -> { + try { + new bug4532590().test(); + } catch (BadLocationException e) { + throw new RuntimeException(e); + } + }); + } +} From be7cad4d7238ee3d0b46f218ca5a159e1dd9d9d0 Mon Sep 17 00:00:00 2001 From: Boris Ulasevich Date: Thu, 2 Apr 2026 17:51:34 +0000 Subject: [PATCH 105/168] 8374343: Fix SIGSEGV when lib/modules is unreadable Backport-of: 48846744ca96ce3c6464a1a440b9e46119dfbb88 --- src/hotspot/share/classfile/classLoader.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/hotspot/share/classfile/classLoader.cpp b/src/hotspot/share/classfile/classLoader.cpp index b8734dcea258..8bb6a94fb855 100644 --- a/src/hotspot/share/classfile/classLoader.cpp +++ b/src/hotspot/share/classfile/classLoader.cpp @@ -1386,6 +1386,10 @@ char* ClassLoader::lookup_vm_options() { jio_snprintf(modules_path, JVM_MAXPATHLEN, "%s%slib%smodules", Arguments::get_java_home(), fileSep, fileSep); JImage_file =(*JImageOpen)(modules_path, &error); if (JImage_file == nullptr) { + if (Arguments::has_jimage()) { + // The modules file exists but is unreadable or corrupt + vm_exit_during_initialization(err_msg("Unable to load %s", modules_path)); + } return nullptr; } From 7705b59e59d31a22a811c9472d0dbf7432907c9e Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Tue, 7 Apr 2026 13:20:27 +0000 Subject: [PATCH 106/168] 8372851: Modify java/io/File/GetXSpace.java to print path on failure of native call Backport-of: ba777f6610fa3744d5f4bdfb87066b137ab543af --- test/jdk/java/io/File/GetXSpace.java | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/test/jdk/java/io/File/GetXSpace.java b/test/jdk/java/io/File/GetXSpace.java index e61880edb2c7..96ce4ede1b2e 100644 --- a/test/jdk/java/io/File/GetXSpace.java +++ b/test/jdk/java/io/File/GetXSpace.java @@ -106,7 +106,7 @@ private static class Space { Space(String name) { this.name = name; long[] sizes = new long[4]; - if (Platform.isWindows() & isCDDrive(name)) { + if (Platform.isWindows() && isCDDrive(name)) { try { getCDDriveSpace(name, sizes); } catch (IOException e) { @@ -114,7 +114,7 @@ private static class Space { throw new RuntimeException("can't get CDDrive sizes"); } } else { - if (getSpace0(name, sizes)) + if (getSpace(name, sizes)) System.err.println("WARNING: total space is estimated"); } this.size = sizes[0]; @@ -184,7 +184,7 @@ private static void compare(Space s) { out.format("%s (%d):%n", s.name(), s.size()); String fmt = " %-4s total = %12d free = %12d usable = %12d%n"; - String method = Platform.isWindows() & isCDDrive(s.name()) ? "getCDDriveSpace" : "getSpace0"; + String method = Platform.isWindows() && isCDDrive(s.name()) ? "getCDDriveSpace" : "getSpace"; out.format(fmt, method, s.total(), s.free(), s.available()); out.format(fmt, "getXSpace", ts, fs, us); @@ -336,7 +336,7 @@ private static int testFile(Path dir) { private static int testVolumes() { out.println("--- Testing volumes"); // Find all of the partitions on the machine and verify that the sizes - // returned by File::getXSpace are equivalent to those from getSpace0 or getCDDriveSpace + // returned by File::getXSpace are equivalent to those from getSpace or getCDDriveSpace ArrayList l; try { l = paths(); @@ -412,6 +412,19 @@ public static void main(String[] args) throws Exception { private static native boolean isCDDrive(String root); + private static boolean getSpace(String root, long[] space) { + try { + return getSpace0(root, space); + } catch (RuntimeException e) { + File f = new File(root); + boolean exists = f.exists(); + boolean readable = f.canRead(); + System.err.printf("getSpace0 failed for %s (%s, %s)%n", + root, exists, readable); + throw e; + } + } + private static void getCDDriveSpace(String root, long[] sizes) throws IOException { String[] cmd = new String[] {"df", "-k", "-P", root}; From 44dc1098f86e20cb93a1386bd574245b9b02421a Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Tue, 7 Apr 2026 17:12:57 +0000 Subject: [PATCH 107/168] 8378878: Refactor java/nio/channels/AsynchronousSocketChannel test to use JUnit Backport-of: c52d7b7cbc89548c3e9cd68a29ff0cec04888b09 --- .../CompletionHandlerRelease.java | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/test/jdk/java/nio/channels/AsynchronousSocketChannel/CompletionHandlerRelease.java b/test/jdk/java/nio/channels/AsynchronousSocketChannel/CompletionHandlerRelease.java index 7abbf064b403..962d7728a551 100644 --- a/test/jdk/java/nio/channels/AsynchronousSocketChannel/CompletionHandlerRelease.java +++ b/test/jdk/java/nio/channels/AsynchronousSocketChannel/CompletionHandlerRelease.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,7 +23,7 @@ /* @test * @bug 8202252 - * @run testng CompletionHandlerRelease + * @run junit CompletionHandlerRelease * @summary Verify that reference to CompletionHandler is cleared after use */ @@ -44,10 +44,12 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; -import org.testng.annotations.AfterTest; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.Test; -import static org.testng.Assert.*; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; public class CompletionHandlerRelease { @Test @@ -132,16 +134,16 @@ public void testRead() throws Exception { } } - private AsynchronousChannelGroup GROUP; + private static AsynchronousChannelGroup GROUP; - @BeforeTest - void setup() throws IOException { + @BeforeAll + static void setup() throws IOException { GROUP = AsynchronousChannelGroup.withFixedThreadPool(2, Executors.defaultThreadFactory()); } - @AfterTest - void cleanup() throws IOException { + @AfterAll + static void cleanup() throws IOException { GROUP.shutdownNow(); } @@ -199,13 +201,13 @@ public void failed(Throwable exc, A attachment) { } } - private void waitForRefToClear(Reference ref, ReferenceQueue queue) + private static void waitForRefToClear(Reference ref, ReferenceQueue queue) throws InterruptedException { Reference r; while ((r = queue.remove(20)) == null) { System.gc(); } - assertEquals(r, ref); + assertSame(ref, r); assertNull(r.get()); } } From c5b2de21d60cf6abff05d153945e76c596ccc8a2 Mon Sep 17 00:00:00 2001 From: Sergey Bylokhov Date: Wed, 8 Apr 2026 01:19:03 +0000 Subject: [PATCH 108/168] 8359433: The final modifier on Windows L&F internal UI classes prevents extending them in apps Backport-of: 0d251968c02295c1cc64fbaff0522d767a84b284 --- .../java/swing/plaf/windows/WindowsBorders.java | 8 ++++---- .../java/swing/plaf/windows/WindowsButtonUI.java | 2 +- .../plaf/windows/WindowsCheckBoxMenuItemUI.java | 4 ++-- .../java/swing/plaf/windows/WindowsCheckBoxUI.java | 2 +- .../plaf/windows/WindowsClassicLookAndFeel.java | 4 ++-- .../java/swing/plaf/windows/WindowsComboBoxUI.java | 10 +++++----- .../swing/plaf/windows/WindowsDesktopIconUI.java | 4 ++-- .../swing/plaf/windows/WindowsDesktopManager.java | 4 ++-- .../swing/plaf/windows/WindowsDesktopPaneUI.java | 4 ++-- .../swing/plaf/windows/WindowsEditorPaneUI.java | 4 ++-- .../swing/plaf/windows/WindowsFileChooserUI.java | 14 +++++++------- .../windows/WindowsInternalFrameTitlePane.java | 8 ++++---- .../swing/plaf/windows/WindowsInternalFrameUI.java | 4 ++-- .../java/swing/plaf/windows/WindowsLabelUI.java | 2 +- .../java/swing/plaf/windows/WindowsMenuBarUI.java | 4 ++-- .../java/swing/plaf/windows/WindowsMenuItemUI.java | 2 +- .../sun/java/swing/plaf/windows/WindowsMenuUI.java | 6 +++--- .../swing/plaf/windows/WindowsOptionPaneUI.java | 4 ++-- .../swing/plaf/windows/WindowsPasswordFieldUI.java | 4 ++-- .../plaf/windows/WindowsPopupMenuSeparatorUI.java | 4 ++-- .../swing/plaf/windows/WindowsPopupMenuUI.java | 4 ++-- .../swing/plaf/windows/WindowsProgressBarUI.java | 4 ++-- .../plaf/windows/WindowsRadioButtonMenuItemUI.java | 4 ++-- .../java/swing/plaf/windows/WindowsRootPaneUI.java | 4 ++-- .../swing/plaf/windows/WindowsScrollBarUI.java | 4 ++-- .../swing/plaf/windows/WindowsScrollPaneUI.java | 4 ++-- .../swing/plaf/windows/WindowsSeparatorUI.java | 4 ++-- .../java/swing/plaf/windows/WindowsSliderUI.java | 4 ++-- .../java/swing/plaf/windows/WindowsSpinnerUI.java | 4 ++-- .../plaf/windows/WindowsSplitPaneDivider.java | 4 ++-- .../swing/plaf/windows/WindowsSplitPaneUI.java | 4 ++-- .../swing/plaf/windows/WindowsTabbedPaneUI.java | 4 ++-- .../swing/plaf/windows/WindowsTableHeaderUI.java | 4 ++-- .../java/swing/plaf/windows/WindowsTextAreaUI.java | 4 ++-- .../swing/plaf/windows/WindowsTextFieldUI.java | 4 ++-- .../java/swing/plaf/windows/WindowsTextPaneUI.java | 4 ++-- .../swing/plaf/windows/WindowsToggleButtonUI.java | 2 +- .../plaf/windows/WindowsToolBarSeparatorUI.java | 4 ++-- .../java/swing/plaf/windows/WindowsToolBarUI.java | 4 ++-- .../sun/java/swing/plaf/windows/WindowsTreeUI.java | 6 +++--- test/jdk/javax/swing/plaf/windows/bug4991587.java | 4 ++-- 41 files changed, 91 insertions(+), 91 deletions(-) diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsBorders.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsBorders.java index 31a490bebccc..572e9e8c1178 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsBorders.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsBorders.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2014, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 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 @@ -115,7 +115,7 @@ public static Border getInternalFrameBorder() { } @SuppressWarnings("serial") // Superclass is not serializable across versions - public static final class ProgressBarBorder extends AbstractBorder implements UIResource { + public static class ProgressBarBorder extends AbstractBorder implements UIResource { protected Color shadow; protected Color highlight; @@ -148,7 +148,7 @@ public Insets getBorderInsets(Component c, Insets insets) { * @since 1.4 */ @SuppressWarnings("serial") // Superclass is not serializable across versions - public static final class ToolBarBorder extends AbstractBorder implements UIResource, SwingConstants { + public static class ToolBarBorder extends AbstractBorder implements UIResource, SwingConstants { protected Color shadow; protected Color highlight; @@ -308,7 +308,7 @@ public void paintBorder(Component c, Graphics g, int x, int y, int width, int he * @since 1.4 */ @SuppressWarnings("serial") // Superclass is not serializable across versions - public static final class InternalFrameLineBorder extends LineBorder implements + public static class InternalFrameLineBorder extends LineBorder implements UIResource { protected Color activeColor; protected Color inactiveColor; diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsButtonUI.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsButtonUI.java index 33dea2b3b082..6f280ba8b674 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsButtonUI.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsButtonUI.java @@ -58,7 +58,7 @@ * * @author Jeff Dinkins */ -public final class WindowsButtonUI extends BasicButtonUI +public class WindowsButtonUI extends BasicButtonUI { protected int dashedRectGapX; protected int dashedRectGapY; diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsCheckBoxMenuItemUI.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsCheckBoxMenuItemUI.java index 2ce47e380667..0e6e6b8c3d3b 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsCheckBoxMenuItemUI.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsCheckBoxMenuItemUI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -42,7 +42,7 @@ /** * Windows check box menu item. */ -public final class WindowsCheckBoxMenuItemUI extends BasicCheckBoxMenuItemUI { +public class WindowsCheckBoxMenuItemUI extends BasicCheckBoxMenuItemUI { final WindowsMenuItemUIAccessor accessor = new WindowsMenuItemUIAccessor() { diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsCheckBoxUI.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsCheckBoxUI.java index 3ead1228b0e6..4fec3b16081a 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsCheckBoxUI.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsCheckBoxUI.java @@ -37,7 +37,7 @@ * * @author Jeff Dinkins */ -public final class WindowsCheckBoxUI extends WindowsRadioButtonUI +public class WindowsCheckBoxUI extends WindowsRadioButtonUI { // NOTE: WindowsCheckBoxUI inherits from WindowsRadioButtonUI instead // of BasicCheckBoxUI because we want to pick up all the diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsClassicLookAndFeel.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsClassicLookAndFeel.java index 59eace01a4c8..802b5f668885 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsClassicLookAndFeel.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsClassicLookAndFeel.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -31,7 +31,7 @@ * @since 1.5 */ @SuppressWarnings("serial") // Superclass is not serializable across versions -public final class WindowsClassicLookAndFeel extends WindowsLookAndFeel { +public class WindowsClassicLookAndFeel extends WindowsLookAndFeel { @Override public String getName() { return "Windows Classic"; diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsComboBoxUI.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsComboBoxUI.java index f37ce17d8763..8717fd715ea6 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsComboBoxUI.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsComboBoxUI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -75,7 +75,7 @@ * @author Tom Santos * @author Igor Kushnirskiy */ -public final class WindowsComboBoxUI extends BasicComboBoxUI { +public class WindowsComboBoxUI extends BasicComboBoxUI { private static final MouseListener rolloverListener = new MouseAdapter() { @@ -532,7 +532,7 @@ WindowsComboBoxUI getWindowsComboBoxUI() { } @SuppressWarnings("serial") // Same-version serialization only - protected final class WinComboPopUp extends BasicComboPopup { + protected class WinComboPopUp extends BasicComboPopup { private Skin listBoxBorder = null; private XPStyle xp; @@ -550,7 +550,7 @@ protected KeyListener createKeyListener() { return new InvocationKeyHandler(); } - protected final class InvocationKeyHandler extends BasicComboPopup.InvocationKeyHandler { + protected class InvocationKeyHandler extends BasicComboPopup.InvocationKeyHandler { protected InvocationKeyHandler() { WinComboPopUp.this.super(); } @@ -570,7 +570,7 @@ protected void paintComponent(Graphics g) { /** * Subclassed to highlight selected item in an editable combo box. */ - public static final class WindowsComboBoxEditor + public static class WindowsComboBoxEditor extends BasicComboBoxEditor.UIResource { /** diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsDesktopIconUI.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsDesktopIconUI.java index 8da4bd7921b8..2cebb050396b 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsDesktopIconUI.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsDesktopIconUI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -36,7 +36,7 @@ /** * Windows icon for a minimized window on the desktop. */ -public final class WindowsDesktopIconUI extends BasicDesktopIconUI { +public class WindowsDesktopIconUI extends BasicDesktopIconUI { private int width; public static ComponentUI createUI(JComponent c) { diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsDesktopManager.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsDesktopManager.java index 355f70b46071..b12b95b52a51 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsDesktopManager.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsDesktopManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2014, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -52,7 +52,7 @@ * @author Thomas Ball */ @SuppressWarnings("serial") // JDK-implementation class -public final class WindowsDesktopManager extends DefaultDesktopManager +public class WindowsDesktopManager extends DefaultDesktopManager implements java.io.Serializable, javax.swing.plaf.UIResource { /* The frame which is currently selected/activated. diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsDesktopPaneUI.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsDesktopPaneUI.java index 49ab809dddd1..4a3f0ec38b1d 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsDesktopPaneUI.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsDesktopPaneUI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -34,7 +34,7 @@ * * @author David Kloba */ -public final class WindowsDesktopPaneUI extends BasicDesktopPaneUI +public class WindowsDesktopPaneUI extends BasicDesktopPaneUI { public static ComponentUI createUI(JComponent c) { return new WindowsDesktopPaneUI(); diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsEditorPaneUI.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsEditorPaneUI.java index abccb6b9a481..ea21b41c6199 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsEditorPaneUI.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsEditorPaneUI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -33,7 +33,7 @@ /** * Windows rendition of the component. */ -public final class WindowsEditorPaneUI extends BasicEditorPaneUI +public class WindowsEditorPaneUI extends BasicEditorPaneUI { /** diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsFileChooserUI.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsFileChooserUI.java index 08c01760be9f..86c40ea70d63 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsFileChooserUI.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsFileChooserUI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -101,7 +101,7 @@ * * @author Jeff Dinkins */ -public final class WindowsFileChooserUI extends BasicFileChooserUI { +public class WindowsFileChooserUI extends BasicFileChooserUI { // The following are private because the implementation of the // Windows FileChooser L&F is not complete yet. @@ -1122,7 +1122,7 @@ protected DirectoryComboBoxModel createDirectoryComboBoxModel(JFileChooser fc) { * Data model for a type-face selection combo-box. */ @SuppressWarnings("serial") // Superclass is not serializable across versions - protected final class DirectoryComboBoxModel extends AbstractListModel implements ComboBoxModel { + protected class DirectoryComboBoxModel extends AbstractListModel implements ComboBoxModel { Vector directories = new Vector(); int[] depths = null; File selectedDirectory = null; @@ -1252,7 +1252,7 @@ protected FilterComboBoxRenderer createFilterComboBoxRenderer() { * Render different type sizes and styles. */ @SuppressWarnings("serial") // Superclass is not serializable across versions - public final class FilterComboBoxRenderer extends DefaultListCellRenderer { + public class FilterComboBoxRenderer extends DefaultListCellRenderer { @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, @@ -1279,7 +1279,7 @@ protected FilterComboBoxModel createFilterComboBoxModel() { * Data model for a type-face selection combo-box. */ @SuppressWarnings("serial") // Superclass is not serializable across versions - protected final class FilterComboBoxModel extends AbstractListModel implements ComboBoxModel, + protected class FilterComboBoxModel extends AbstractListModel implements ComboBoxModel, PropertyChangeListener { protected FileFilter[] filters; protected FilterComboBoxModel() { @@ -1362,7 +1362,7 @@ public void valueChanged(ListSelectionEvent e) { /** * Acts when DirectoryComboBox has changed the selected item. */ - protected final class DirectoryComboBoxAction implements ActionListener { + protected class DirectoryComboBoxAction implements ActionListener { @@ -1387,7 +1387,7 @@ public FileView getFileView(JFileChooser fc) { // *********************** // * FileView operations * // *********************** - protected final class WindowsFileView extends BasicFileView { + protected class WindowsFileView extends BasicFileView { /* FileView type descriptions */ @Override diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsInternalFrameTitlePane.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsInternalFrameTitlePane.java index ba4bde12122d..029e139fe8f3 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsInternalFrameTitlePane.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsInternalFrameTitlePane.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -418,7 +418,7 @@ protected LayoutManager createLayout() { return new WindowsTitlePaneLayout(); } - public final class WindowsTitlePaneLayout extends BasicInternalFrameTitlePane.TitlePaneLayout { + public class WindowsTitlePaneLayout extends BasicInternalFrameTitlePane.TitlePaneLayout { private Insets captionMargin = null; private Insets contentMargin = null; private XPStyle xp = XPStyle.getXP(); @@ -506,7 +506,7 @@ public void layoutContainer(Container c) { } } // end WindowsTitlePaneLayout - public final class WindowsPropertyChangeHandler extends PropertyChangeHandler { + public class WindowsPropertyChangeHandler extends PropertyChangeHandler { @Override public void propertyChange(PropertyChangeEvent evt) { String prop = evt.getPropertyName(); @@ -530,7 +530,7 @@ public void propertyChange(PropertyChangeEvent evt) { *

    * Note: We assume here that icons are square. */ - public static final class ScalableIconUIResource implements Icon, UIResource { + public static class ScalableIconUIResource implements Icon, UIResource { // We can use an arbitrary size here because we scale to it in paintIcon() private static final int SIZE = 16; diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsInternalFrameUI.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsInternalFrameUI.java index 5c331533af94..6e76ac6a5b44 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsInternalFrameUI.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsInternalFrameUI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -45,7 +45,7 @@ /** * Windows rendition of the component. */ -public final class WindowsInternalFrameUI extends BasicInternalFrameUI +public class WindowsInternalFrameUI extends BasicInternalFrameUI { XPStyle xp = XPStyle.getXP(); diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsLabelUI.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsLabelUI.java index d10f3f47c3c3..a9e14fb09429 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsLabelUI.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsLabelUI.java @@ -41,7 +41,7 @@ /** * Windows rendition of the component. */ -public final class WindowsLabelUI extends BasicLabelUI { +public class WindowsLabelUI extends BasicLabelUI { private static final Object WINDOWS_LABEL_UI_KEY = new Object(); diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsMenuBarUI.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsMenuBarUI.java index f663d8d1e908..ac26dcbf425f 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsMenuBarUI.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsMenuBarUI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -57,7 +57,7 @@ /** * Windows rendition of the component. */ -public final class WindowsMenuBarUI extends BasicMenuBarUI +public class WindowsMenuBarUI extends BasicMenuBarUI { /* to be accessed on the EDT only */ private WindowListener windowListener = null; diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsMenuItemUI.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsMenuItemUI.java index 5206d5575478..ceab93f1f18f 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsMenuItemUI.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsMenuItemUI.java @@ -58,7 +58,7 @@ * * @author Igor Kushnirskiy */ -public final class WindowsMenuItemUI extends BasicMenuItemUI { +public class WindowsMenuItemUI extends BasicMenuItemUI { /** * The instance of {@code PropertyChangeListener}. */ diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsMenuUI.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsMenuUI.java index 8208c77cdb82..464c80a6733f 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsMenuUI.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsMenuUI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -50,7 +50,7 @@ /** * Windows rendition of the component. */ -public final class WindowsMenuUI extends BasicMenuUI { +public class WindowsMenuUI extends BasicMenuUI { protected Integer menuBarHeight; protected boolean hotTrackingOn; @@ -282,7 +282,7 @@ protected MouseInputListener createMouseInputListener(JComponent c) { * true when the mouse enters the menu and false when it exits. * @since 1.4 */ - protected final class WindowsMouseInputHandler extends BasicMenuUI.MouseInputHandler { + protected class WindowsMouseInputHandler extends BasicMenuUI.MouseInputHandler { @Override public void mouseEntered(MouseEvent evt) { super.mouseEntered(evt); diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsOptionPaneUI.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsOptionPaneUI.java index 5ced1659adfe..3bed1856a557 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsOptionPaneUI.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsOptionPaneUI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -30,5 +30,5 @@ /** * Windows rendition of the component. */ -public final class WindowsOptionPaneUI extends BasicOptionPaneUI { +public class WindowsOptionPaneUI extends BasicOptionPaneUI { } diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsPasswordFieldUI.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsPasswordFieldUI.java index 1f64c18e61f6..0c30b291648f 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsPasswordFieldUI.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsPasswordFieldUI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -33,7 +33,7 @@ /** * Windows rendition of the component. */ -public final class WindowsPasswordFieldUI extends BasicPasswordFieldUI { +public class WindowsPasswordFieldUI extends BasicPasswordFieldUI { /** * Creates a UI for a JPasswordField diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsPopupMenuSeparatorUI.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsPopupMenuSeparatorUI.java index 9d67278526a9..f236c6b14fc6 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsPopupMenuSeparatorUI.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsPopupMenuSeparatorUI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2014, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -42,7 +42,7 @@ * @author Igor Kushnirskiy */ -public final class WindowsPopupMenuSeparatorUI extends BasicPopupMenuSeparatorUI { +public class WindowsPopupMenuSeparatorUI extends BasicPopupMenuSeparatorUI { public static ComponentUI createUI(JComponent c) { return new WindowsPopupMenuSeparatorUI(); diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsPopupMenuUI.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsPopupMenuUI.java index 4df236115fb2..1c85cfebd940 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsPopupMenuUI.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsPopupMenuUI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -57,7 +57,7 @@ * * @author Igor Kushnirskiy */ -public final class WindowsPopupMenuUI extends BasicPopupMenuUI { +public class WindowsPopupMenuUI extends BasicPopupMenuUI { static MnemonicListener mnemonicListener = null; static final Object GUTTER_OFFSET_KEY = diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsProgressBarUI.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsProgressBarUI.java index 9cc7d277ff18..5440b98cd1b7 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsProgressBarUI.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsProgressBarUI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -51,7 +51,7 @@ * * @author Michael C. Albers */ -public final class WindowsProgressBarUI extends BasicProgressBarUI +public class WindowsProgressBarUI extends BasicProgressBarUI { private Rectangle previousFullBox; diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsRadioButtonMenuItemUI.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsRadioButtonMenuItemUI.java index c9d314a3a88a..a0acca095e17 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsRadioButtonMenuItemUI.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsRadioButtonMenuItemUI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -42,7 +42,7 @@ /** * Windows rendition of the component. */ -public final class WindowsRadioButtonMenuItemUI extends BasicRadioButtonMenuItemUI { +public class WindowsRadioButtonMenuItemUI extends BasicRadioButtonMenuItemUI { final WindowsMenuItemUIAccessor accessor = new WindowsMenuItemUIAccessor() { diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsRootPaneUI.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsRootPaneUI.java index 19bfad0a1da8..d41fd9421e4d 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsRootPaneUI.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsRootPaneUI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -70,7 +70,7 @@ * @author Mark Davidson * @since 1.4 */ -public final class WindowsRootPaneUI extends BasicRootPaneUI { +public class WindowsRootPaneUI extends BasicRootPaneUI { private static final WindowsRootPaneUI windowsRootPaneUI = new WindowsRootPaneUI(); static final AltProcessor altProcessor = new AltProcessor(); diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsScrollBarUI.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsScrollBarUI.java index c8d62e52834a..2755f3543f1c 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsScrollBarUI.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsScrollBarUI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -55,7 +55,7 @@ /** * Windows rendition of the component. */ -public final class WindowsScrollBarUI extends BasicScrollBarUI { +public class WindowsScrollBarUI extends BasicScrollBarUI { private Grid thumbGrid; private Grid highlightGrid; private Dimension horizontalThumbSize; diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsScrollPaneUI.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsScrollPaneUI.java index 393f7595402a..56b8eb1004e8 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsScrollPaneUI.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsScrollPaneUI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -30,5 +30,5 @@ /** * Windows rendition of the component. */ -public final class WindowsScrollPaneUI extends BasicScrollPaneUI +public class WindowsScrollPaneUI extends BasicScrollPaneUI {} diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsSeparatorUI.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsSeparatorUI.java index 9ce5db3b4ef8..12eaa33872c9 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsSeparatorUI.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsSeparatorUI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -30,4 +30,4 @@ /** * Windows Separator. */ -public final class WindowsSeparatorUI extends BasicSeparatorUI { } +public class WindowsSeparatorUI extends BasicSeparatorUI { } diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsSliderUI.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsSliderUI.java index 88dc91d572b8..cfc509babf44 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsSliderUI.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsSliderUI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -44,7 +44,7 @@ /** * Windows rendition of the component. */ -public final class WindowsSliderUI extends BasicSliderUI +public class WindowsSliderUI extends BasicSliderUI { private boolean rollover = false; private boolean pressed = false; diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsSpinnerUI.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsSpinnerUI.java index bad66ce3a047..8934bf9ff218 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsSpinnerUI.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsSpinnerUI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -37,7 +37,7 @@ import static com.sun.java.swing.plaf.windows.XPStyle.Skin; -public final class WindowsSpinnerUI extends BasicSpinnerUI { +public class WindowsSpinnerUI extends BasicSpinnerUI { public static ComponentUI createUI(JComponent c) { return new WindowsSpinnerUI(); } diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsSplitPaneDivider.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsSplitPaneDivider.java index 95062ef586ff..26cd1bd8c2df 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsSplitPaneDivider.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsSplitPaneDivider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -39,7 +39,7 @@ * @author Jeff Dinkins */ @SuppressWarnings("serial") // Superclass is not serializable across versions -public final class WindowsSplitPaneDivider extends BasicSplitPaneDivider +public class WindowsSplitPaneDivider extends BasicSplitPaneDivider { /** diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsSplitPaneUI.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsSplitPaneUI.java index 8c50e3911648..b67ab22f48f2 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsSplitPaneUI.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsSplitPaneUI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -33,7 +33,7 @@ /** * Windows rendition of the component. */ -public final class WindowsSplitPaneUI extends BasicSplitPaneUI +public class WindowsSplitPaneUI extends BasicSplitPaneUI { public WindowsSplitPaneUI() { diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsTabbedPaneUI.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsTabbedPaneUI.java index 86e2ee1fc523..da8e8b9d3857 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsTabbedPaneUI.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsTabbedPaneUI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -48,7 +48,7 @@ /** * Windows rendition of the component. */ -public final class WindowsTabbedPaneUI extends BasicTabbedPaneUI { +public class WindowsTabbedPaneUI extends BasicTabbedPaneUI { /** * Keys to use for forward focus traversal when the JComponent is * managing focus. diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsTableHeaderUI.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsTableHeaderUI.java index b670bd294b0e..1db0050f1627 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsTableHeaderUI.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsTableHeaderUI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -50,7 +50,7 @@ import static com.sun.java.swing.plaf.windows.TMSchema.State; import static com.sun.java.swing.plaf.windows.XPStyle.Skin; -public final class WindowsTableHeaderUI extends BasicTableHeaderUI { +public class WindowsTableHeaderUI extends BasicTableHeaderUI { private TableCellRenderer originalHeaderRenderer; public static ComponentUI createUI(JComponent h) { diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsTextAreaUI.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsTextAreaUI.java index e695d9f67080..7c9abb12e050 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsTextAreaUI.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsTextAreaUI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -33,7 +33,7 @@ /** * Windows rendition of the component. */ -public final class WindowsTextAreaUI extends BasicTextAreaUI { +public class WindowsTextAreaUI extends BasicTextAreaUI { /** * Creates the object to use for a caret. By default an * instance of WindowsCaret is created. This method diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsTextFieldUI.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsTextFieldUI.java index 508d260895c6..9920ed371d88 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsTextFieldUI.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsTextFieldUI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -62,7 +62,7 @@ * * @author Timothy Prinzing */ -public final class WindowsTextFieldUI extends BasicTextFieldUI +public class WindowsTextFieldUI extends BasicTextFieldUI { /** * Creates a UI for a JTextField. diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsTextPaneUI.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsTextPaneUI.java index 7858a1195e80..d1418205385c 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsTextPaneUI.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsTextPaneUI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -33,7 +33,7 @@ /** * Windows rendition of the component. */ -public final class WindowsTextPaneUI extends BasicTextPaneUI +public class WindowsTextPaneUI extends BasicTextPaneUI { /** * Creates a UI for a JTextPane. diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsToggleButtonUI.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsToggleButtonUI.java index 0f468fdf6b10..c813f2016fa6 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsToggleButtonUI.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsToggleButtonUI.java @@ -45,7 +45,7 @@ * * @author Jeff Dinkins */ -public final class WindowsToggleButtonUI extends BasicToggleButtonUI +public class WindowsToggleButtonUI extends BasicToggleButtonUI { protected int dashedRectGapX; protected int dashedRectGapY; diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsToolBarSeparatorUI.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsToolBarSeparatorUI.java index 5350de9ae5c6..1707ce5a80ca 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsToolBarSeparatorUI.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsToolBarSeparatorUI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2005, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -40,7 +40,7 @@ * * @author Mark Davidson */ -public final class WindowsToolBarSeparatorUI extends BasicToolBarSeparatorUI { +public class WindowsToolBarSeparatorUI extends BasicToolBarSeparatorUI { public static ComponentUI createUI( JComponent c ) { return new WindowsToolBarSeparatorUI(); diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsToolBarUI.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsToolBarUI.java index 68e077fa3503..4e2cf42bf5dd 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsToolBarUI.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsToolBarUI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2006, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -45,7 +45,7 @@ import static com.sun.java.swing.plaf.windows.TMSchema.Part; -public final class WindowsToolBarUI extends BasicToolBarUI { +public class WindowsToolBarUI extends BasicToolBarUI { public static ComponentUI createUI(JComponent c) { return new WindowsToolBarUI(); diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsTreeUI.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsTreeUI.java index 6ab2352641f3..26edfb978bd7 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsTreeUI.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsTreeUI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -167,7 +167,7 @@ public int getIconHeight() { * The plus sign button icon */ @SuppressWarnings("serial") // Superclass is not serializable across versions - public static final class CollapsedIcon extends ExpandedIcon { + public static class CollapsedIcon extends ExpandedIcon { public static Icon createCollapsedIcon() { return new CollapsedIcon(); } @@ -185,7 +185,7 @@ public void paintIcon(Component c, Graphics g, int x, int y) { } @SuppressWarnings("serial") // Superclass is not serializable across versions - public final class WindowsTreeCellRenderer extends DefaultTreeCellRenderer { + public class WindowsTreeCellRenderer extends DefaultTreeCellRenderer { /** * Configures the renderer based on the passed in components. diff --git a/test/jdk/javax/swing/plaf/windows/bug4991587.java b/test/jdk/javax/swing/plaf/windows/bug4991587.java index e4e4fde2b862..439bdd4cc61b 100644 --- a/test/jdk/javax/swing/plaf/windows/bug4991587.java +++ b/test/jdk/javax/swing/plaf/windows/bug4991587.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,7 +23,7 @@ /* * @test - * @bug 4991587 + * @bug 4991587 8359433 * @requires (os.family == "windows") * @summary Tests that disabled JButton text is positioned properly in Windows L&F * @modules java.desktop/com.sun.java.swing.plaf.windows From d348f058ee8bb69a4a2aab2d70f76d567b4f8828 Mon Sep 17 00:00:00 2001 From: SendaoYan Date: Wed, 8 Apr 2026 08:57:31 +0000 Subject: [PATCH 109/168] 8279196: Test: jdk/jfr/event/gc/stacktrace/TestG1OldAllocationPendingStackTrace.java timed out Backport-of: bd73864314eefcdf0a57d8a580e40de711f3cf26 --- .../gc/stacktrace/TestG1OldAllocationPendingStackTrace.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/jdk/jdk/jfr/event/gc/stacktrace/TestG1OldAllocationPendingStackTrace.java b/test/jdk/jdk/jfr/event/gc/stacktrace/TestG1OldAllocationPendingStackTrace.java index 1346c36c04f3..64389a0e921a 100644 --- a/test/jdk/jdk/jfr/event/gc/stacktrace/TestG1OldAllocationPendingStackTrace.java +++ b/test/jdk/jdk/jfr/event/gc/stacktrace/TestG1OldAllocationPendingStackTrace.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,7 +29,7 @@ * * @requires vm.gc == "null" | vm.gc == "G1" * @library /test/lib /test/jdk - * @run main/othervm -XX:MaxNewSize=10M -Xmx128M -XX:+UseG1GC -Xlog:gc* + * @run main/othervm -XX:MaxNewSize=10M -Xmx64M -XX:+UseG1GC -Xlog:gc* * -XX:FlightRecorderOptions:stackdepth=256 * jdk.jfr.event.gc.stacktrace.TestG1OldAllocationPendingStackTrace */ From 23e5828a493b0e6e9feca14f10b093766c6b6c71 Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Wed, 8 Apr 2026 13:20:46 +0000 Subject: [PATCH 110/168] 8371893: [macOS] use dead_strip linker option to reduce binary size Backport-of: 8f3d0ade11ddb45bb1719b6818e1b51df237a59b --- make/autoconf/flags-ldflags.m4 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/make/autoconf/flags-ldflags.m4 b/make/autoconf/flags-ldflags.m4 index 84ba24bb440f..77ea7a693c91 100644 --- a/make/autoconf/flags-ldflags.m4 +++ b/make/autoconf/flags-ldflags.m4 @@ -102,6 +102,9 @@ AC_DEFUN([FLAGS_SETUP_LDFLAGS_HELPER], # Setup OS-dependent LDFLAGS if test "x$OPENJDK_TARGET_OS" = xmacosx && test "x$TOOLCHAIN_TYPE" = xclang; then + if test x$DEBUG_LEVEL = xrelease; then + BASIC_LDFLAGS_JDK_ONLY="$BASIC_LDFLAGS_JDK_ONLY -Wl,-dead_strip" + fi # FIXME: We should really generalize SET_SHARED_LIBRARY_ORIGIN instead. OS_LDFLAGS_JVM_ONLY="-Wl,-rpath,@loader_path/. -Wl,-rpath,@loader_path/.." OS_LDFLAGS="-mmacosx-version-min=$MACOSX_VERSION_MIN -Wl,-reproducible" From f981751c705be9ddf22f26a0906be58580273aef Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Wed, 8 Apr 2026 13:42:07 +0000 Subject: [PATCH 111/168] 8347938: Add Support for the Latest ML-KEM and ML-DSA Private Key Encodings Reviewed-by: phh Backport-of: e51ccef9cb415ed31db70971bb439ca3d96c5bce --- .../com/sun/crypto/provider/ML_KEM.java | 18 +- .../com/sun/crypto/provider/ML_KEM_Impls.java | 110 +++++-- .../sun/security/pkcs/NamedPKCS8Key.java | 119 ++++++-- .../classes/sun/security/provider/ML_DSA.java | 50 ++- .../sun/security/provider/ML_DSA_Impls.java | 110 +++++-- .../sun/security/provider/NamedKEM.java | 57 ++-- .../security/provider/NamedKeyFactory.java | 175 ++++++----- .../provider/NamedKeyPairGenerator.java | 85 ++++-- .../sun/security/provider/NamedSignature.java | 57 ++-- .../classes/sun/security/util/KeyChoices.java | 289 ++++++++++++++++++ .../sun/security/x509/NamedX509Key.java | 5 +- .../share/conf/security/java.security | 25 ++ .../sun/security/provider/acvp/Launcher.java | 8 +- .../security/provider/acvp/ML_DSA_Test.java | 14 +- .../security/provider/acvp/ML_KEM_Test.java | 19 +- .../provider/{ => named}/NamedEdDSA.java | 39 ++- .../{ => named}/NamedKeyFactoryTest.java | 54 +++- .../security/provider/named/NamedKeys.java | 103 +++++++ .../provider/pqc/PrivateKeyEncodings.java | 227 ++++++++++++++ .../security/provider/pqc/SeedOrExpanded.java | 194 ++++++++++++ test/lib/jdk/test/lib/process/Proc.java | 11 +- .../lib/security/RepositoryFileReader.java | 160 ++++++++++ 22 files changed, 1641 insertions(+), 288 deletions(-) create mode 100644 src/java.base/share/classes/sun/security/util/KeyChoices.java rename test/jdk/sun/security/provider/{ => named}/NamedEdDSA.java (84%) rename test/jdk/sun/security/provider/{ => named}/NamedKeyFactoryTest.java (85%) create mode 100644 test/jdk/sun/security/provider/named/NamedKeys.java create mode 100644 test/jdk/sun/security/provider/pqc/PrivateKeyEncodings.java create mode 100644 test/jdk/sun/security/provider/pqc/SeedOrExpanded.java create mode 100644 test/lib/jdk/test/lib/security/RepositoryFileReader.java diff --git a/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM.java b/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM.java index 1280ccdad74d..b463f3599952 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM.java @@ -498,7 +498,7 @@ protected Object checkPrivateKey(byte[] sk) throws InvalidKeyException { /* Main internal algorithms from Section 6 of specification */ - protected ML_KEM_KeyPair generateKemKeyPair(byte[] kem_d, byte[] kem_z) { + protected ML_KEM_KeyPair generateKemKeyPair(byte[] kem_d_z) { MessageDigest mlKemH; try { mlKemH = MessageDigest.getInstance(HASH_H_NAME); @@ -508,7 +508,8 @@ protected ML_KEM_KeyPair generateKemKeyPair(byte[] kem_d, byte[] kem_z) { } //Generate K-PKE keys - var kPkeKeyPair = generateK_PkeKeyPair(kem_d); + //The 1st 32-byte `d` is used in K-PKE key pair generation + var kPkeKeyPair = generateK_PkeKeyPair(kem_d_z); //encaps key = kPke encryption key byte[] encapsKey = kPkeKeyPair.publicKey.keyBytes; @@ -527,7 +528,8 @@ protected ML_KEM_KeyPair generateKemKeyPair(byte[] kem_d, byte[] kem_z) { // This should never happen. throw new RuntimeException(e); } - System.arraycopy(kem_z, 0, decapsKey, + // The 2nd 32-byte `z` is copied into decapsKey + System.arraycopy(kem_d_z, 32, decapsKey, kPkePrivateKey.length + encapsKey.length + 32, 32); return new ML_KEM_KeyPair( @@ -535,6 +537,12 @@ protected ML_KEM_KeyPair generateKemKeyPair(byte[] kem_d, byte[] kem_z) { new ML_KEM_DecapsulationKey(decapsKey)); } + public byte[] privKeyToPubKey(byte[] decapsKey) { + int pkLen = (mlKem_k * ML_KEM_N * 12) / 8 + 32 /* rho */; + int skLen = (mlKem_k * ML_KEM_N * 12) / 8; + return Arrays.copyOfRange(decapsKey, skLen, skLen + pkLen); + } + protected ML_KEM_EncapsulateResult encapsulate( ML_KEM_EncapsulationKey encapsulationKey, byte[] randomMessage) { MessageDigest mlKemH; @@ -648,10 +656,12 @@ private K_PKE_KeyPair generateK_PkeKeyPair(byte[] seed) { throw new RuntimeException(e); } - mlKemG.update(seed); + // Note: only the 1st 32-byte in the seed is used + mlKemG.update(seed, 0, 32); mlKemG.update((byte)mlKem_k); var rhoSigma = mlKemG.digest(); + mlKemG.reset(); var rho = Arrays.copyOfRange(rhoSigma, 0, 32); var sigma = Arrays.copyOfRange(rhoSigma, 32, 64); Arrays.fill(rhoSigma, (byte)0); diff --git a/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM_Impls.java b/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM_Impls.java index 2ce5b3324e76..117f26e69810 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM_Impls.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM_Impls.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,9 +26,12 @@ package com.sun.crypto.provider; import sun.security.jca.JCAUtil; +import sun.security.pkcs.NamedPKCS8Key; import sun.security.provider.NamedKEM; import sun.security.provider.NamedKeyFactory; import sun.security.provider.NamedKeyPairGenerator; +import sun.security.util.KeyChoices; +import sun.security.x509.NamedX509Key; import java.security.*; import java.util.Arrays; @@ -37,6 +40,20 @@ public final class ML_KEM_Impls { + private static final int SEED_LEN = 64; + + public static byte[] seedToExpanded(String pname, byte[] seed) { + return new ML_KEM(pname).generateKemKeyPair(seed) + .decapsulationKey() + .keyBytes(); + } + + public static NamedX509Key privKeyToPubKey(NamedPKCS8Key npk) { + return new NamedX509Key(npk.getAlgorithm(), + npk.getParams().getName(), + new ML_KEM(npk.getParams().getName()).privKeyToPubKey(npk.getExpanded())); + } + public sealed static class KPG extends NamedKeyPairGenerator permits KPG2, KPG3, KPG5 { @@ -50,25 +67,27 @@ protected KPG(String pname) { } @Override - protected byte[][] implGenerateKeyPair(String name, SecureRandom random) { - byte[] seed = new byte[32]; + protected byte[][] implGenerateKeyPair(String pname, SecureRandom random) { + byte[] seed = new byte[SEED_LEN]; var r = random != null ? random : JCAUtil.getDefSecureRandom(); r.nextBytes(seed); - byte[] z = new byte[32]; - r.nextBytes(z); - ML_KEM mlKem = new ML_KEM(name); + ML_KEM mlKem = new ML_KEM(pname); ML_KEM.ML_KEM_KeyPair kp; + kp = mlKem.generateKemKeyPair(seed); + var expanded = kp.decapsulationKey().keyBytes(); + try { - kp = mlKem.generateKemKeyPair(seed, z); + return new byte[][]{ + kp.encapsulationKey().keyBytes(), + KeyChoices.writeToChoice( + KeyChoices.getPreferred("mlkem"), + seed, expanded), + expanded + }; } finally { - Arrays.fill(seed, (byte)0); - Arrays.fill(z, (byte)0); + Arrays.fill(seed, (byte) 0); } - return new byte[][] { - kp.encapsulationKey().keyBytes(), - kp.decapsulationKey().keyBytes() - }; } } @@ -94,8 +113,39 @@ public sealed static class KF extends NamedKeyFactory permits KF2, KF3, KF5 { public KF() { super("ML-KEM", "ML-KEM-512", "ML-KEM-768", "ML-KEM-1024"); } - public KF(String name) { - super("ML-KEM", name); + public KF(String pname) { + super("ML-KEM", pname); + } + + @Override + protected byte[] implExpand(String pname, byte[] input) + throws InvalidKeyException { + return KeyChoices.choiceToExpanded(pname, SEED_LEN, input, + ML_KEM_Impls::seedToExpanded); + } + + @Override + protected Key engineTranslateKey(Key key) throws InvalidKeyException { + var nk = toNamedKey(key); + if (nk instanceof NamedPKCS8Key npk) { + var type = KeyChoices.getPreferred("mlkem"); + if (KeyChoices.typeOfChoice(npk.getRawBytes()) != type) { + var encoding = KeyChoices.choiceToChoice( + type, + npk.getParams().getName(), + SEED_LEN, npk.getRawBytes(), + ML_KEM_Impls::seedToExpanded); + nk = NamedPKCS8Key.internalCreate( + npk.getAlgorithm(), + npk.getParams().getName(), + encoding, + npk.getExpanded().clone()); + if (npk != key) { // npk is neither input or output + npk.destroy(); + } + } + } + return nk; } } @@ -121,15 +171,15 @@ public sealed static class K extends NamedKEM permits K2, K3, K5 { private static final int SEED_SIZE = 32; @Override - protected byte[][] implEncapsulate(String name, byte[] encapsulationKey, + protected byte[][] implEncapsulate(String pname, byte[] encapsulationKey, Object ek, SecureRandom secureRandom) { byte[] randomBytes = new byte[SEED_SIZE]; var r = secureRandom != null ? secureRandom : JCAUtil.getDefSecureRandom(); r.nextBytes(randomBytes); - ML_KEM mlKem = new ML_KEM(name); - ML_KEM.ML_KEM_EncapsulateResult mlKemEncapsulateResult = null; + ML_KEM mlKem = new ML_KEM(pname); + ML_KEM.ML_KEM_EncapsulateResult mlKemEncapsulateResult; try { mlKemEncapsulateResult = mlKem.encapsulate( new ML_KEM.ML_KEM_EncapsulationKey( @@ -145,49 +195,49 @@ protected byte[][] implEncapsulate(String name, byte[] encapsulationKey, } @Override - protected byte[] implDecapsulate(String name, byte[] decapsulationKey, + protected byte[] implDecapsulate(String pname, byte[] decapsulationKey, Object dk, byte[] cipherText) throws DecapsulateException { - ML_KEM mlKem = new ML_KEM(name); + ML_KEM mlKem = new ML_KEM(pname); var kpkeCipherText = new ML_KEM.K_PKE_CipherText(cipherText); return mlKem.decapsulate(new ML_KEM.ML_KEM_DecapsulationKey( decapsulationKey), kpkeCipherText); } @Override - protected int implSecretSize(String name) { + protected int implSecretSize(String pname) { return ML_KEM.SECRET_SIZE; } @Override - protected int implEncapsulationSize(String name) { - ML_KEM mlKem = new ML_KEM(name); + protected int implEncapsulationSize(String pname) { + ML_KEM mlKem = new ML_KEM(pname); return mlKem.getEncapsulationSize(); } @Override - protected Object implCheckPublicKey(String name, byte[] pk) + protected Object implCheckPublicKey(String pname, byte[] pk) throws InvalidKeyException { - ML_KEM mlKem = new ML_KEM(name); + ML_KEM mlKem = new ML_KEM(pname); return mlKem.checkPublicKey(pk); } @Override - protected Object implCheckPrivateKey(String name, byte[] sk) + protected Object implCheckPrivateKey(String pname, byte[] sk) throws InvalidKeyException { - ML_KEM mlKem = new ML_KEM(name); + ML_KEM mlKem = new ML_KEM(pname); return mlKem.checkPrivateKey(sk); } public K() { - super("ML-KEM", "ML-KEM-512", "ML-KEM-768", "ML-KEM-1024"); + super("ML-KEM", new KF()); } - public K(String name) { - super("ML-KEM", name); + public K(String pname) { + super("ML-KEM", new KF(pname)); } } diff --git a/src/java.base/share/classes/sun/security/pkcs/NamedPKCS8Key.java b/src/java.base/share/classes/sun/security/pkcs/NamedPKCS8Key.java index a748433da875..e1beb8b6b9bc 100644 --- a/src/java.base/share/classes/sun/security/pkcs/NamedPKCS8Key.java +++ b/src/java.base/share/classes/sun/security/pkcs/NamedPKCS8Key.java @@ -25,11 +25,8 @@ package sun.security.pkcs; -import sun.security.util.DerInputStream; -import sun.security.util.DerValue; import sun.security.x509.AlgorithmId; -import javax.security.auth.DestroyFailedException; import java.io.IOException; import java.io.InvalidObjectException; import java.io.ObjectInputStream; @@ -39,6 +36,7 @@ import java.security.ProviderException; import java.security.spec.NamedParameterSpec; import java.util.Arrays; +import java.util.Objects; /// Represents a private key from an algorithm family that is specialized /// with a named parameter set. @@ -50,6 +48,28 @@ /// identifier in the PKCS #8 encoding of the key is always a single OID derived /// from the parameter set name. /// +/// Besides the existing [PKCS8Key#privKeyMaterial] field, this class optionally +/// supports an expanded format stored in [#expanded]. While `privKeyMaterial` +/// always represents the format used for encoding, `expanded` is always used +/// in computations. The expanded format must be self-sufficient for +/// cryptographic computations without requiring the encoding format. +/// +/// 1. If only `privKeyMaterial` is present, it's also the expanded format. +/// 2. If both `privKeyMaterial` and `expanded` are available, `privKeyMaterial` +/// is the encoding format, and `expanded` is the expanded format. +/// +/// If the two formats are the same, only `privKeyMaterial` is included, and +/// `expanded` must be `null`. Some implementations might be tempted to put the +/// same value into `privKeyMaterial` and `expanded`. However, problems can +/// arise if they happen to be the same object. To avoid ambiguity, always set +/// `expanded` to `null`. +/// +/// If the `expanded` field is required by the algorithm, it is either +/// [calculated from the PKCS #8 encoding][#NamedPKCS8Key(String, byte\[\], Expander)], +/// or [provided directly][#internalCreate(String, String, byte\[\], byte\[\])]. +/// In the latter case, the caller must ensure the consistency of the `encoded` +/// and `expanded` arguments. For example, seed and expanded key must match. +/// /// @see sun.security.provider.NamedKeyPairGenerator public final class NamedPKCS8Key extends PKCS8Key { @Serial @@ -57,42 +77,64 @@ public final class NamedPKCS8Key extends PKCS8Key { private final String fname; private final transient NamedParameterSpec paramSpec; - private final byte[] rawBytes; + private final transient byte[] expanded; private transient boolean destroyed = false; - /// Ctor from family name, parameter set name, raw key bytes. - /// Key bytes won't be cloned, caller must relinquish ownership - public NamedPKCS8Key(String fname, String pname, byte[] rawBytes) { + /// Creates a `NamedPKCS8Key` from raw components. + /// + /// @param fname family name + /// @param pname parameter set name + /// @param encoded raw key bytes, not null + /// @param expanded expanded key format, can be `null`. + private NamedPKCS8Key(String fname, String pname, byte[] encoded, byte[] expanded) { this.fname = fname; this.paramSpec = new NamedParameterSpec(pname); + this.expanded = expanded; + this.privKeyMaterial = Objects.requireNonNull(encoded); try { this.algid = AlgorithmId.get(pname); } catch (NoSuchAlgorithmException e) { throw new ProviderException(e); } - this.rawBytes = rawBytes; + } - DerValue val = new DerValue(DerValue.tag_OctetString, rawBytes); - try { - this.privKeyMaterial = val.toByteArray(); - } finally { - val.clear(); - } + /// Creates a `NamedPKCS8Key` from raw components. + /// + /// `encoded` and `expanded` won't be cloned, caller must relinquish + /// ownership. This caller must ensure `encoded` and `expanded` match + /// each other and `encoded` is valid and internally-consistent. + /// + /// @param fname family name + /// @param pname parameter set name + /// @param encoded raw key bytes, not null + /// @param expanded expanded key format, can be `null`. + public static NamedPKCS8Key internalCreate(String fname, String pname, + byte[] encoded, byte[] expanded) { + return new NamedPKCS8Key(fname, pname, encoded, expanded); } - /// Ctor from family name, and PKCS #8 bytes - public NamedPKCS8Key(String fname, byte[] encoded) throws InvalidKeyException { + /// Creates a `NamedPKCS8Key` from family name and PKCS #8 encoding. + /// + /// @param fname family name + /// @param encoded PKCS #8 encoding. It is copied so caller can modify + /// it after the method call. + /// @param expander a function that is able to calculate the expanded + /// format from the encoding format inside `encoded`. If it recognizes + /// the input already in expanded format, it must return `null`. + /// This argument must be `null` if the algorithm's expanded format + /// is always the same as its encoding format. Whatever the case, the + /// ownership of the result is fully granted to this object. + public NamedPKCS8Key(String fname, byte[] encoded, Expander expander) + throws InvalidKeyException { super(encoded); this.fname = fname; - try { - paramSpec = new NamedParameterSpec(algid.getName()); - if (algid.getEncodedParams() != null) { - throw new InvalidKeyException("algorithm identifier has params"); - } - rawBytes = new DerInputStream(privKeyMaterial).getOctetString(); - } catch (IOException e) { - throw new InvalidKeyException("Cannot parse input", e); + this.expanded = expander == null + ? null + : expander.expand(algid.getName(), this.privKeyMaterial); + paramSpec = new NamedParameterSpec(algid.getName()); + if (algid.getEncodedParams() != null) { + throw new InvalidKeyException("algorithm identifier has params"); } } @@ -104,9 +146,15 @@ public String toString() { } /// Returns the reference to the internal key. Caller must not modify - /// the content or keep a reference. + /// the content or pass the reference to untrusted application code. public byte[] getRawBytes() { - return rawBytes; + return privKeyMaterial; + } + + /// Returns the reference to the key in expanded format. Caller must not + /// modify the content or pass the reference to untrusted application code. + public byte[] getExpanded() { + return expanded == null ? privKeyMaterial : expanded; } @Override @@ -127,9 +175,11 @@ private void readObject(ObjectInputStream stream) } @Override - public void destroy() throws DestroyFailedException { - Arrays.fill(rawBytes, (byte)0); + public void destroy() { Arrays.fill(privKeyMaterial, (byte)0); + if (expanded != null) { + Arrays.fill(expanded, (byte)0); + } if (encodedKey != null) { Arrays.fill(encodedKey, (byte)0); } @@ -140,4 +190,17 @@ public void destroy() throws DestroyFailedException { public boolean isDestroyed() { return destroyed; } + + /// Expands from encoding format to expanded format. + @FunctionalInterface + public interface Expander { + /// The expand method + /// + /// @param pname parameter set name + /// @param input input encoding + /// @return the expanded key, `null` if `input` is already in expanded + /// @throws InvalidKeyException if `input` is invalid, for example, + /// wrong encoding, or internal inconsistency + byte[] expand(String pname, byte[] input) throws InvalidKeyException; + } } diff --git a/src/java.base/share/classes/sun/security/provider/ML_DSA.java b/src/java.base/share/classes/sun/security/provider/ML_DSA.java index af64ef399a8d..a55f7d258b16 100644 --- a/src/java.base/share/classes/sun/security/provider/ML_DSA.java +++ b/src/java.base/share/classes/sun/security/provider/ML_DSA.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -568,6 +568,54 @@ public ML_DSA_KeyPair generateKeyPairInternal(byte[] randomBytes) { return new ML_DSA_KeyPair(sk, pk); } + private static int[][] deepClone(int[][] array) { + int[][] clone = new int[array.length][]; + for (int i = 0; i < array.length; i++) { + clone[i] = array[i].clone(); + } + return clone; + } + + // This is similar to the generateKeyPairInternal method. Instead of + // generating from a seed, it uses stored fields inside the private key + // to calculate the public key. It performs several checks during the + // calculation to make sure the private key is a valid one. Otherwise, + // an IllegalArgumentException is thrown. + public ML_DSA_PublicKey privKeyToPubKey(ML_DSA_PrivateKey sk) { + // Sample A + int[][][] keygenA = generateA(sk.rho); //A is in NTT domain + + // Compute t and tr + // make a copy of sk.s1 and modify it. Although we can also + // take it out of NTT domain later, it was modified for a while. + var s1 = deepClone(sk.s1); + mlDsaVectorNtt(s1); //s1 now in NTT domain + int[][] As1 = integerMatrixAlloc(mlDsa_k, ML_DSA_N); + matrixVectorPointwiseMultiply(As1, keygenA, s1); + + mlDsaVectorInverseNtt(As1); + int[][] t = vectorAddPos(As1, sk.s2); + int[][] t0 = integerMatrixAlloc(mlDsa_k, ML_DSA_N); + int[][] t1 = integerMatrixAlloc(mlDsa_k, ML_DSA_N); + power2Round(t, t0, t1); + if (!Arrays.deepEquals(t0, sk.t0)) { + throw new IllegalArgumentException("t0 does not patch"); + } + + var crHash = new SHAKE256(TR_LEN); + + ML_DSA_PublicKey pk = new ML_DSA_PublicKey(sk.rho, t1); + byte[] publicKeyBytes = pkEncode(pk); + crHash.update(publicKeyBytes); + byte[] tr = crHash.digest(); + if (!Arrays.equals(tr, sk.tr)) { + throw new IllegalArgumentException("tr does not patch"); + } + + //Encode PK + return new ML_DSA_PublicKey(sk.rho, t1); + } + public ML_DSA_Signature signInternal(byte[] message, byte[] rnd, byte[] skBytes) { //Decode private key and initialize hash function ML_DSA_PrivateKey sk = skDecode(skBytes); diff --git a/src/java.base/share/classes/sun/security/provider/ML_DSA_Impls.java b/src/java.base/share/classes/sun/security/provider/ML_DSA_Impls.java index dffe7c5cdb18..730e253f407f 100644 --- a/src/java.base/share/classes/sun/security/provider/ML_DSA_Impls.java +++ b/src/java.base/share/classes/sun/security/provider/ML_DSA_Impls.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,12 +26,35 @@ package sun.security.provider; import sun.security.jca.JCAUtil; +import sun.security.pkcs.NamedPKCS8Key; +import sun.security.util.KeyChoices; +import sun.security.x509.NamedX509Key; + import java.security.*; import java.security.SecureRandom; import java.util.Arrays; public class ML_DSA_Impls { + private static final int SEED_LEN = 32; + + public static byte[] seedToExpanded(String pname, byte[] seed) { + var impl = new ML_DSA(name2int(pname)); + var sk = impl.generateKeyPairInternal(seed).privateKey(); + try { + return impl.skEncode(sk); + } finally { + sk.destroy(); + } + } + + public static NamedX509Key privKeyToPubKey(NamedPKCS8Key npk) { + var dsa = new ML_DSA(name2int(npk.getParams().getName())); + return new NamedX509Key(npk.getAlgorithm(), + npk.getParams().getName(), + dsa.pkEncode(dsa.privKeyToPubKey(dsa.skDecode(npk.getExpanded())))); + } + public enum Version { DRAFT, FINAL } @@ -43,16 +66,16 @@ public enum Version { // --add-exports java.base/sun.security.provider=ALL-UNNAMED public static Version version = Version.FINAL; - static int name2int(String name) { - if (name.endsWith("44")) { + static int name2int(String pname) { + if (pname.endsWith("44")) { return 2; - } else if (name.endsWith("65")) { + } else if (pname.endsWith("65")) { return 3; - } else if (name.endsWith("87")) { + } else if (pname.endsWith("87")) { return 5; } else { // should not happen - throw new ProviderException("Unknown name " + name); + throw new ProviderException("Unknown name " + pname); } } @@ -69,20 +92,26 @@ public KPG(String pname) { } @Override - protected byte[][] implGenerateKeyPair(String name, SecureRandom sr) { - byte[] seed = new byte[32]; - var r = sr != null ? sr : JCAUtil.getDefSecureRandom(); + protected byte[][] implGenerateKeyPair(String pname, SecureRandom random) { + byte[] seed = new byte[SEED_LEN]; + var r = random != null ? random : JCAUtil.getDefSecureRandom(); r.nextBytes(seed); - ML_DSA mlDsa = new ML_DSA(name2int(name)); + + ML_DSA mlDsa = new ML_DSA(name2int(pname)); ML_DSA.ML_DSA_KeyPair kp = mlDsa.generateKeyPairInternal(seed); + var expanded = mlDsa.skEncode(kp.privateKey()); + try { return new byte[][]{ mlDsa.pkEncode(kp.publicKey()), - mlDsa.skEncode(kp.privateKey()) + KeyChoices.writeToChoice( + KeyChoices.getPreferred("mldsa"), + seed, expanded), + expanded }; } finally { kp.privateKey().destroy(); - Arrays.fill(seed, (byte)0); + Arrays.fill(seed, (byte) 0); } } } @@ -109,8 +138,39 @@ public sealed static class KF extends NamedKeyFactory permits KF2, KF3, KF5 { public KF() { super("ML-DSA", "ML-DSA-44", "ML-DSA-65", "ML-DSA-87"); } - public KF(String name) { - super("ML-DSA", name); + public KF(String pname) { + super("ML-DSA", pname); + } + + @Override + protected byte[] implExpand(String pname, byte[] input) + throws InvalidKeyException { + return KeyChoices.choiceToExpanded(pname, SEED_LEN, input, + ML_DSA_Impls::seedToExpanded); + } + + @Override + protected Key engineTranslateKey(Key key) throws InvalidKeyException { + var nk = toNamedKey(key); + if (nk instanceof NamedPKCS8Key npk) { + var type = KeyChoices.getPreferred("mldsa"); + if (KeyChoices.typeOfChoice(npk.getRawBytes()) != type) { + var encoding = KeyChoices.choiceToChoice( + type, + npk.getParams().getName(), + SEED_LEN, npk.getRawBytes(), + ML_DSA_Impls::seedToExpanded); + nk = NamedPKCS8Key.internalCreate( + npk.getAlgorithm(), + npk.getParams().getName(), + encoding, + npk.getExpanded().clone()); + if (npk != key) { // npk is neither input or output + npk.destroy(); + } + } + } + return nk; } } @@ -134,16 +194,16 @@ public KF5() { public sealed static class SIG extends NamedSignature permits SIG2, SIG3, SIG5 { public SIG() { - super("ML-DSA", "ML-DSA-44", "ML-DSA-65", "ML-DSA-87"); + super("ML-DSA", new KF()); } - public SIG(String name) { - super("ML-DSA", name); + public SIG(String pname) { + super("ML-DSA", new KF(pname)); } @Override - protected byte[] implSign(String name, byte[] skBytes, + protected byte[] implSign(String pname, byte[] skBytes, Object sk2, byte[] msg, SecureRandom sr) { - var size = name2int(name); + var size = name2int(pname); var r = sr != null ? sr : JCAUtil.getDefSecureRandom(); byte[] rnd = new byte[32]; r.nextBytes(rnd); @@ -160,10 +220,10 @@ protected byte[] implSign(String name, byte[] skBytes, } @Override - protected boolean implVerify(String name, byte[] pkBytes, + protected boolean implVerify(String pname, byte[] pkBytes, Object pk2, byte[] msg, byte[] sigBytes) throws SignatureException { - var size = name2int(name); + var size = name2int(pname); var mlDsa = new ML_DSA(size); if (version == Version.FINAL) { // FIPS 204 Algorithm 3 ML-DSA.Verify prepend {0, len(ctx)} @@ -176,18 +236,18 @@ protected boolean implVerify(String name, byte[] pkBytes, } @Override - protected Object implCheckPublicKey(String name, byte[] pk) + protected Object implCheckPublicKey(String pname, byte[] pk) throws InvalidKeyException { - ML_DSA mlDsa = new ML_DSA(name2int(name)); + ML_DSA mlDsa = new ML_DSA(name2int(pname)); return mlDsa.checkPublicKey(pk); } @Override - protected Object implCheckPrivateKey(String name, byte[] sk) + protected Object implCheckPrivateKey(String pname, byte[] sk) throws InvalidKeyException { - ML_DSA mlDsa = new ML_DSA(name2int(name)); + ML_DSA mlDsa = new ML_DSA(name2int(pname)); return mlDsa.checkPrivateKey(sk); } } diff --git a/src/java.base/share/classes/sun/security/provider/NamedKEM.java b/src/java.base/share/classes/sun/security/provider/NamedKEM.java index 2731b3460af3..60449396d4d8 100644 --- a/src/java.base/share/classes/sun/security/provider/NamedKEM.java +++ b/src/java.base/share/classes/sun/security/provider/NamedKEM.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -42,7 +42,6 @@ import java.security.spec.AlgorithmParameterSpec; import java.security.spec.NamedParameterSpec; import java.util.Arrays; -import java.util.Objects; /// A base class for all `KEM` implementations that can be /// configured with a named parameter set. See [NamedKeyPairGenerator] @@ -50,21 +49,19 @@ public abstract class NamedKEM implements KEMSpi { private final String fname; // family name - private final String[] pnames; // allowed parameter set name (at least one) + private final NamedKeyFactory fac; /// Creates a new `NamedKEM` object. /// /// @param fname the family name - /// @param pnames the standard parameter set names, at least one is needed. - protected NamedKEM(String fname, String... pnames) { + /// @param fac the `KeyFactory` used to translate foreign keys and + /// perform key validation + protected NamedKEM(String fname, NamedKeyFactory fac) { if (fname == null) { throw new AssertionError("fname cannot be null"); } - if (pnames == null || pnames.length == 0) { - throw new AssertionError("pnames cannot be null or empty"); - } this.fname = fname; - this.pnames = pnames; + this.fac = fac; } @Override @@ -76,8 +73,7 @@ public EncapsulatorSpi engineNewEncapsulator(PublicKey publicKey, "The " + fname + " algorithm does not take any parameters"); } // translate also check the key - var nk = (NamedX509Key) new NamedKeyFactory(fname, pnames) - .engineTranslateKey(publicKey); + var nk = (NamedX509Key) fac.toNamedKey(publicKey); var pk = nk.getRawBytes(); return getKeyConsumerImpl(this, nk.getParams(), pk, implCheckPublicKey(nk.getParams().getName(), pk), secureRandom); @@ -92,16 +88,15 @@ public DecapsulatorSpi engineNewDecapsulator( "The " + fname + " algorithm does not take any parameters"); } // translate also check the key - var nk = (NamedPKCS8Key) new NamedKeyFactory(fname, pnames) - .engineTranslateKey(privateKey); - var sk = nk.getRawBytes(); + var nk = (NamedPKCS8Key) fac.toNamedKey(privateKey); + var sk = nk.getExpanded(); return getKeyConsumerImpl(this, nk.getParams(), sk, implCheckPrivateKey(nk.getParams().getName(), sk), null); } // We don't have a flag on whether key is public key or private key. // The correct method should always be called. - private record KeyConsumerImpl(NamedKEM kem, String name, int sslen, + private record KeyConsumerImpl(NamedKEM kem, String pname, int sslen, int clen, byte[] key, Object k2, SecureRandom sr) implements KEMSpi.EncapsulatorSpi, KEMSpi.DecapsulatorSpi { @Override @@ -110,7 +105,7 @@ public SecretKey engineDecapsulate(byte[] encapsulation, int from, int to, if (encapsulation.length != clen) { throw new DecapsulateException("Invalid key encapsulation message length"); } - var ss = kem.implDecapsulate(name, key, k2, encapsulation); + var ss = kem.implDecapsulate(pname, key, k2, encapsulation); try { return new SecretKeySpec(ss, from, to - from, algorithm); @@ -121,7 +116,7 @@ public SecretKey engineDecapsulate(byte[] encapsulation, int from, int to, @Override public KEM.Encapsulated engineEncapsulate(int from, int to, String algorithm) { - var enc = kem.implEncapsulate(name, key, k2, sr); + var enc = kem.implEncapsulate(pname, key, k2, sr); try { return new KEM.Encapsulated( new SecretKeySpec(enc[1], @@ -146,46 +141,46 @@ public int engineEncapsulationSize() { private static KeyConsumerImpl getKeyConsumerImpl(NamedKEM kem, NamedParameterSpec nps, byte[] key, Object k2, SecureRandom sr) { - String name = nps.getName(); - return new KeyConsumerImpl(kem, name, kem.implSecretSize(name), kem.implEncapsulationSize(name), + String pname = nps.getName(); + return new KeyConsumerImpl(kem, pname, kem.implSecretSize(pname), kem.implEncapsulationSize(pname), key, k2, sr); } /// User-defined encap function. /// - /// @param name parameter name + /// @param pname parameter name /// @param pk public key in raw bytes /// @param pk2 parsed public key, `null` if none. See [#implCheckPublicKey]. /// @param sr SecureRandom object, `null` if not initialized /// @return the key encapsulation message and the shared key (in this order) /// @throws ProviderException if there is an internal error - protected abstract byte[][] implEncapsulate(String name, byte[] pk, Object pk2, SecureRandom sr); + protected abstract byte[][] implEncapsulate(String pname, byte[] pk, Object pk2, SecureRandom sr); /// User-defined decap function. /// - /// @param name parameter name + /// @param pname parameter name /// @param sk private key in raw bytes /// @param sk2 parsed private key, `null` if none. See [#implCheckPrivateKey]. /// @param encap the key encapsulation message /// @return the shared key /// @throws ProviderException if there is an internal error /// @throws DecapsulateException if there is another error - protected abstract byte[] implDecapsulate(String name, byte[] sk, Object sk2, byte[] encap) + protected abstract byte[] implDecapsulate(String pname, byte[] sk, Object sk2, byte[] encap) throws DecapsulateException; /// User-defined function returning shared secret key length. /// - /// @param name parameter name + /// @param pname parameter name /// @return shared secret key length /// @throws ProviderException if there is an internal error - protected abstract int implSecretSize(String name); + protected abstract int implSecretSize(String pname); /// User-defined function returning key encapsulation message length. /// - /// @param name parameter name + /// @param pname parameter name /// @return key encapsulation message length /// @throws ProviderException if there is an internal error - protected abstract int implEncapsulationSize(String name); + protected abstract int implEncapsulationSize(String pname); /// User-defined function to validate a public key. /// @@ -196,11 +191,11 @@ protected abstract byte[] implDecapsulate(String name, byte[] sk, Object sk2, by /// /// The default implementation returns `null`. /// - /// @param name parameter name + /// @param pname parameter name /// @param pk public key in raw bytes /// @return a parsed key, `null` if none. /// @throws InvalidKeyException if the key is invalid - protected Object implCheckPublicKey(String name, byte[] pk) throws InvalidKeyException { + protected Object implCheckPublicKey(String pname, byte[] pk) throws InvalidKeyException { return null; } @@ -213,11 +208,11 @@ protected Object implCheckPublicKey(String name, byte[] pk) throws InvalidKeyExc /// /// The default implementation returns `null`. /// - /// @param name parameter name + /// @param pname parameter name /// @param sk private key in raw bytes /// @return a parsed key, `null` if none. /// @throws InvalidKeyException if the key is invalid - protected Object implCheckPrivateKey(String name, byte[] sk) throws InvalidKeyException { + protected Object implCheckPrivateKey(String pname, byte[] sk) throws InvalidKeyException { return null; } } diff --git a/src/java.base/share/classes/sun/security/provider/NamedKeyFactory.java b/src/java.base/share/classes/sun/security/provider/NamedKeyFactory.java index 727358dd0749..9099f1446ff9 100644 --- a/src/java.base/share/classes/sun/security/provider/NamedKeyFactory.java +++ b/src/java.base/share/classes/sun/security/provider/NamedKeyFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -42,7 +42,6 @@ import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.Arrays; -import java.util.Objects; /// A base class for all `KeyFactory` implementations that can be /// configured with a named parameter set. See [NamedKeyPairGenerator] @@ -58,7 +57,7 @@ /// /// When reading from a RAW format, it needs enough info to derive the /// parameter set name. -public class NamedKeyFactory extends KeyFactorySpi { +public abstract class NamedKeyFactory extends KeyFactorySpi { private final String fname; // family name private final String[] pnames; // allowed parameter set name (at least one) @@ -78,92 +77,110 @@ protected NamedKeyFactory(String fname, String... pnames) { this.pnames = pnames; } - private String checkName(String name) throws InvalidKeyException { - for (var pname : pnames) { - if (pname.equalsIgnoreCase(name)) { + private String checkName(String pname) throws InvalidKeyException { + for (var n : pnames) { + if (n.equalsIgnoreCase(pname)) { // return the stored standard name - return pname; + return n; } } - throw new InvalidKeyException("Unsupported parameter set name: " + name); + throw new InvalidKeyException("Unsupported parameter set name: " + pname); } @Override protected PublicKey engineGeneratePublic(KeySpec keySpec) throws InvalidKeySpecException { - if (keySpec instanceof X509EncodedKeySpec xspec) { - try { - return fromX509(xspec.getEncoded()); - } catch (InvalidKeyException e) { - throw new InvalidKeySpecException(e); + return switch (keySpec) { + case X509EncodedKeySpec xspec -> { + try { + yield fromX509(xspec.getEncoded()); + } catch (InvalidKeyException e) { + throw new InvalidKeySpecException(e); + } } - } else if (keySpec instanceof RawKeySpec rks) { - if (pnames.length == 1) { - return new NamedX509Key(fname, pnames[0], rks.getKeyArr()); - } else { - throw new InvalidKeySpecException("Parameter set name unavailable"); + case RawKeySpec rks -> { + if (pnames.length == 1) { + yield new NamedX509Key(fname, pnames[0], rks.getKeyArr()); + } else { + throw new InvalidKeySpecException("Parameter set name unavailable"); + } } - } else if (keySpec instanceof EncodedKeySpec espec - && espec.getFormat().equalsIgnoreCase("RAW")) { - if (pnames.length == 1) { - return new NamedX509Key(fname, pnames[0], espec.getEncoded()); - } else { - throw new InvalidKeySpecException("Parameter set name unavailable"); + case EncodedKeySpec espec when espec.getFormat().equalsIgnoreCase("RAW") -> { + if (pnames.length == 1) { + yield new NamedX509Key(fname, pnames[0], espec.getEncoded()); + } else { + throw new InvalidKeySpecException("Parameter set name unavailable"); + } } - } else { - throw new InvalidKeySpecException("Unsupported keyspec: " + keySpec); - } + case null -> throw new InvalidKeySpecException( + "keySpec must not be null"); + default -> + throw new InvalidKeySpecException(keySpec.getClass().getName() + + " not supported."); + }; } @Override protected PrivateKey engineGeneratePrivate(KeySpec keySpec) throws InvalidKeySpecException { - if (keySpec instanceof PKCS8EncodedKeySpec pspec) { - var bytes = pspec.getEncoded(); - try { - return fromPKCS8(bytes); - } catch (InvalidKeyException e) { - throw new InvalidKeySpecException(e); - } finally { - Arrays.fill(bytes, (byte) 0); - } - } else if (keySpec instanceof RawKeySpec rks) { - if (pnames.length == 1) { - var bytes = rks.getKeyArr(); + return switch (keySpec) { + case PKCS8EncodedKeySpec pspec -> { + var bytes = pspec.getEncoded(); try { - return new NamedPKCS8Key(fname, pnames[0], bytes); + yield fromPKCS8(bytes); + } catch (InvalidKeyException e) { + throw new InvalidKeySpecException(e); } finally { Arrays.fill(bytes, (byte) 0); } - } else { - throw new InvalidKeySpecException("Parameter set name unavailable"); } - } else if (keySpec instanceof EncodedKeySpec espec - && espec.getFormat().equalsIgnoreCase("RAW")) { - if (pnames.length == 1) { - var bytes = espec.getEncoded(); - try { - return new NamedPKCS8Key(fname, pnames[0], bytes); - } finally { - Arrays.fill(bytes, (byte) 0); + case RawKeySpec rks -> { + if (pnames.length == 1) { + var raw = rks.getKeyArr(); + try { + yield fromRaw(pnames[0], raw); + } catch (InvalidKeyException e) { + throw new InvalidKeySpecException("Invalid key input", e); + } + } else { + throw new InvalidKeySpecException("Parameter set name unavailable"); } - } else { - throw new InvalidKeySpecException("Parameter set name unavailable"); } - } else { - throw new InvalidKeySpecException("Unsupported keyspec: " + keySpec); - } + case EncodedKeySpec espec when espec.getFormat().equalsIgnoreCase("RAW") -> { + if (pnames.length == 1) { + var raw = espec.getEncoded(); + try { + yield fromRaw(pnames[0], raw); + } catch (InvalidKeyException e) { + throw new InvalidKeySpecException("Invalid key input", e); + } + } else { + throw new InvalidKeySpecException("Parameter set name unavailable"); + } + } + case null -> throw new InvalidKeySpecException( + "keySpec must not be null"); + default -> + throw new InvalidKeySpecException(keySpec.getClass().getName() + + " not supported."); + }; + } + + private PrivateKey fromRaw(String pname, byte[] raw) + throws InvalidKeyException { + return NamedPKCS8Key.internalCreate( + fname, pname, raw, implExpand(pname, raw)); } private PrivateKey fromPKCS8(byte[] bytes) - throws InvalidKeyException, InvalidKeySpecException { - var k = new NamedPKCS8Key(fname, bytes); + throws InvalidKeyException { + var k = new NamedPKCS8Key(fname, bytes, this::implExpand); checkName(k.getParams().getName()); return k; } private PublicKey fromX509(byte[] bytes) - throws InvalidKeyException, InvalidKeySpecException { + throws InvalidKeyException { var k = new NamedX509Key(fname, bytes); checkName(k.getParams().getName()); return k; @@ -184,7 +201,7 @@ public String getFormat() { protected T engineGetKeySpec(Key key, Class keySpec) throws InvalidKeySpecException { try { - key = engineTranslateKey(key); + key = toNamedKey(key); } catch (InvalidKeyException e) { throw new InvalidKeySpecException(e); } @@ -225,6 +242,12 @@ protected T engineGetKeySpec(Key key, Class keySpec) @Override protected Key engineTranslateKey(Key key) throws InvalidKeyException { + // The base toNamedKey only makes sure key is translated into a NamedKey. + // the key material is still the same as the input. + return toNamedKey(key); + } + + protected Key toNamedKey(Key key) throws InvalidKeyException { if (key == null) { throw new InvalidKeyException("Key must not be null"); } @@ -242,27 +265,28 @@ protected Key engineTranslateKey(Key key) throws InvalidKeyException { } else if (format.equalsIgnoreCase("RAW")) { var kAlg = key.getAlgorithm(); if (key instanceof AsymmetricKey pk) { - String name; + String pname; // Three cases that we can find the parameter set name from a RAW key: // 1. getParams() returns one // 2. getAlgorithm() returns param set name (some provider does this) // 3. getAlgorithm() returns family name but this KF is for param set name if (pk.getParams() instanceof NamedParameterSpec nps) { - name = checkName(nps.getName()); + pname = checkName(nps.getName()); } else { if (kAlg.equalsIgnoreCase(fname)) { if (pnames.length == 1) { - name = pnames[0]; + pname = pnames[0]; } else { throw new InvalidKeyException("No parameter set info"); } } else { - name = checkName(kAlg); + pname = checkName(kAlg); } } + var raw = key.getEncoded(); return key instanceof PrivateKey - ? new NamedPKCS8Key(fname, name, key.getEncoded()) - : new NamedX509Key(fname, name, key.getEncoded()); + ? fromRaw(pname, raw) + : new NamedX509Key(fname, pname, raw); } else { throw new InvalidKeyException("Unsupported key type: " + key.getClass()); } @@ -270,19 +294,26 @@ protected Key engineTranslateKey(Key key) throws InvalidKeyException { var bytes = key.getEncoded(); try { return fromPKCS8(bytes); - } catch (InvalidKeySpecException e) { - throw new InvalidKeyException("Invalid PKCS#8 key", e); } finally { Arrays.fill(bytes, (byte) 0); } } else if (format.equalsIgnoreCase("X.509") && key instanceof PublicKey) { - try { - return fromX509(key.getEncoded()); - } catch (InvalidKeySpecException e) { - throw new InvalidKeyException("Invalid X.509 key", e); - } + return fromX509(key.getEncoded()); } else { throw new InvalidKeyException("Unsupported key format: " + key.getFormat()); } } + + /// User-defined function to generate the expanded format of + /// a [NamedPKCS8Key] from its encoding format. + /// + /// This method is called when the key factory is constructing a private + /// key. The ownership of the result is fully granted to the caller. + /// + /// @param pname the parameter set name + /// @param input the encoding, could be any format + /// @return the expanded key, not null + /// @throws InvalidKeyException if `input` is invalid + protected abstract byte[] implExpand(String pname, byte[] input) + throws InvalidKeyException; } diff --git a/src/java.base/share/classes/sun/security/provider/NamedKeyPairGenerator.java b/src/java.base/share/classes/sun/security/provider/NamedKeyPairGenerator.java index 5be2b2b2a08b..6b55924b0fe2 100644 --- a/src/java.base/share/classes/sun/security/provider/NamedKeyPairGenerator.java +++ b/src/java.base/share/classes/sun/security/provider/NamedKeyPairGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -36,7 +36,6 @@ import java.security.SecureRandom; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.NamedParameterSpec; -import java.util.Objects; /// A base class for all `KeyPairGenerator` implementations that can be /// configured with a named parameter set. @@ -52,15 +51,21 @@ /// with `getAlgorithm` returning the family name, and `getParams` returning /// the parameter set name as a [NamedParameterSpec] object. /// -/// An implementation must include a zero-argument public constructor that -/// calls `super(fname, pnames)`, where `fname` is the family name of the -/// algorithm and `pnames` are its supported parameter set names. `pnames` -/// must contain at least one element. For an implementation of -/// `NamedKeyPairGenerator`, the first element becomes its default parameter -/// set, i.e. the parameter set to be used in key pair generation unless +/// A `NamedKeyPairGenerator` or `NamedKeyFactory` implementation must include +/// a zero-argument public constructor that calls `super(fname, pnames)`, where +/// `fname` is the family name of the algorithm and `pnames` are its supported +/// parameter set names. `pnames` must contain at least one element. For an +/// implementation of `NamedKeyPairGenerator`, the first element becomes its +/// default parameter set, i.e. the parameter set used by generated keys unless /// [#initialize(AlgorithmParameterSpec, java.security.SecureRandom)] /// is called on a different parameter set. /// +/// A `NamedKEM` or `NamedSignature` implementation must include a zero-argument +/// public constructor that calls `super(fname, factory)`, where `fname` is the +/// family name of the algorithm and `factory` is the `NamedKeyFactory` object +/// that is used to translate foreign keys. `factory` only recognizes +/// parameter sets supported by this implementation. +/// /// An implementation must implement all abstract methods. For all these /// methods, the implementation must relinquish any "ownership" of any input /// and output array argument. Precisely, the implementation must not retain @@ -69,8 +74,8 @@ /// array argument and must not retain any reference to an input array argument /// after the call. /// -/// Also, an implementation must not keep any extra copy of a private key. -/// For key generation, the only copy is the one returned in the +/// Also, an implementation must not keep any extra copy of a private key in +/// any format. For key generation, the only copy is the one returned in the /// [#implGenerateKeyPair] call. For all other methods, it must not make /// a copy of the input private key. A `KEM` implementation also must not /// keep a copy of the shared secret key, no matter if it's an encapsulator @@ -84,6 +89,34 @@ /// (For example, `implSign`) later. An implementation must not retain /// a reference of the parsed key. /// +/// The private key, represented as a byte array when used in `NamedKEM` or +/// `NamedSignature`, is referred to as its expanded format. For some +/// algorithms, this format may differ from the +/// [key material][NamedPKCS8Key#getRawBytes()] inside a PKCS #8 file. For example, +/// [FIPS 204](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.204.pdf) +/// Table 2 defines the ML-DSA-65 private key as a 4032-byte array, which is +/// used in the ML-DSA.Sign function in Algorithm 2, representing the +/// expanded format. However, in +/// [RFC 9881](https://datatracker.ietf.org/doc/html/rfc9881#name-private-key-format), +/// a private key can be encoded into a CHOICE of three formats, none in the +/// same as the FIPS 204 format. The choices are defined in +/// [sun.security.util.KeyChoices]. A `NamedKeyPairGenerator` implementation +/// should return both the expanded key and a preferred encoding in its +/// [#implGenerateKeyPair] method. +/// +/// A `NamedKeyFactory` must override the `implExpand` method to derive +/// the expanded format from an encoding format, or return `null` if there +/// is no difference. +/// +/// Implementations may support multiple encoding formats. +/// +/// A `NamedKeyFactory` must not modify the encoding when generating a key +/// from a `KeySpec` object, ensuring that when re-encoded, the key retains +/// its original encoding format. +/// +/// A `NamedKeyFactory` can choose a different encoding format when +/// `translateKey` is called. +/// /// When constructing a [NamedX509Key] or [NamedPKCS8Key] object from raw key /// bytes, the key bytes are directly referenced within the object, so the /// caller must not modify them afterward. Similarly, the key's `getRawBytes` @@ -105,9 +138,9 @@ public abstract class NamedKeyPairGenerator extends KeyPairGeneratorSpi { private final String fname; // family name - private final String[] pnames; // allowed parameter set name (at least one) + private final String[] pnames; // allowed parameter set names (at least one) - protected String name; // init as + protected String pname; // parameter set name, if can be determined private SecureRandom secureRandom; /// Creates a new `NamedKeyPairGenerator` object. @@ -126,22 +159,22 @@ protected NamedKeyPairGenerator(String fname, String... pnames) { this.pnames = pnames; } - private String checkName(String name) throws InvalidAlgorithmParameterException { - for (var pname : pnames) { - if (pname.equalsIgnoreCase(name)) { - // return the stored standard name - return pname; + private String checkName(String pname) throws InvalidAlgorithmParameterException { + for (var n : pnames) { + if (n.equalsIgnoreCase(pname)) { + // return the stored standard pname + return n; } } throw new InvalidAlgorithmParameterException( - "Unsupported parameter set name: " + name); + "Unsupported parameter set name: " + pname); } @Override public void initialize(AlgorithmParameterSpec params, SecureRandom random) throws InvalidAlgorithmParameterException { if (params instanceof NamedParameterSpec spec) { - name = checkName(spec.getName()); + pname = checkName(spec.getName()); } else { throw new InvalidAlgorithmParameterException( "Unsupported AlgorithmParameterSpec: " + params); @@ -161,17 +194,21 @@ public void initialize(int keysize, SecureRandom random) { @Override public KeyPair generateKeyPair() { - String pname = name != null ? name : pnames[0]; - var keys = implGenerateKeyPair(pname, secureRandom); - return new KeyPair(new NamedX509Key(fname, pname, keys[0]), - new NamedPKCS8Key(fname, pname, keys[1])); + String tmpName = pname != null ? pname : pnames[0]; + var keys = implGenerateKeyPair(tmpName, secureRandom); + return new KeyPair(new NamedX509Key(fname, tmpName, keys[0]), + NamedPKCS8Key.internalCreate(fname, tmpName, keys[1], + keys.length == 2 ? null : keys[2])); } /// User-defined key pair generator. /// /// @param pname parameter set name /// @param sr `SecureRandom` object, `null` if not initialized - /// @return public key and private key (in this order) in raw bytes + /// @return the public key, the private key in its encoding format, and + /// the private key in its expanded format (in this order) in + /// raw bytes. If the expanded format of the private key is the + /// same as its encoding format, the 3rd element must be omitted. /// @throws ProviderException if there is an internal error protected abstract byte[][] implGenerateKeyPair(String pname, SecureRandom sr); } diff --git a/src/java.base/share/classes/sun/security/provider/NamedSignature.java b/src/java.base/share/classes/sun/security/provider/NamedSignature.java index 921a39cfc926..07d20828c3c1 100644 --- a/src/java.base/share/classes/sun/security/provider/NamedSignature.java +++ b/src/java.base/share/classes/sun/security/provider/NamedSignature.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -40,7 +40,6 @@ import java.security.SignatureException; import java.security.SignatureSpi; import java.security.spec.AlgorithmParameterSpec; -import java.util.Objects; /// A base class for all `Signature` implementations that can be /// configured with a named parameter set. See [NamedKeyPairGenerator] @@ -50,12 +49,12 @@ public abstract class NamedSignature extends SignatureSpi { private final String fname; // family name - private final String[] pnames; // allowed parameter set name (at least one) + private final NamedKeyFactory fac; private final ByteArrayOutputStream bout = new ByteArrayOutputStream(); // init with... - private String name; + private String pname; private byte[] secKey; private byte[] pubKey; @@ -65,26 +64,23 @@ public abstract class NamedSignature extends SignatureSpi { /// Creates a new `NamedSignature` object. /// /// @param fname the family name - /// @param pnames the standard parameter set names, at least one is needed. - protected NamedSignature(String fname, String... pnames) { + /// @param fac the `KeyFactory` used to translate foreign keys and + /// perform key validation + protected NamedSignature(String fname, NamedKeyFactory fac) { if (fname == null) { throw new AssertionError("fname cannot be null"); } - if (pnames == null || pnames.length == 0) { - throw new AssertionError("pnames cannot be null or empty"); - } this.fname = fname; - this.pnames = pnames; + this.fac = fac; } @Override protected void engineInitVerify(PublicKey publicKey) throws InvalidKeyException { // translate also check the key - var nk = (NamedX509Key) new NamedKeyFactory(fname, pnames) - .engineTranslateKey(publicKey); - name = nk.getParams().getName(); + var nk = (NamedX509Key) fac.toNamedKey(publicKey); + pname = nk.getParams().getName(); pubKey = nk.getRawBytes(); - pk2 = implCheckPublicKey(name, pubKey); + pk2 = implCheckPublicKey(pname, pubKey); secKey = null; bout.reset(); } @@ -92,11 +88,10 @@ protected void engineInitVerify(PublicKey publicKey) throws InvalidKeyException @Override protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException { // translate also check the key - var nk = (NamedPKCS8Key) new NamedKeyFactory(fname, pnames) - .engineTranslateKey(privateKey); - name = nk.getParams().getName(); - secKey = nk.getRawBytes(); - sk2 = implCheckPrivateKey(name, secKey); + var nk = (NamedPKCS8Key) fac.toNamedKey(privateKey); + pname = nk.getParams().getName(); + secKey = nk.getExpanded(); + sk2 = implCheckPrivateKey(pname, secKey); pubKey = null; bout.reset(); } @@ -116,7 +111,7 @@ protected byte[] engineSign() throws SignatureException { if (secKey != null) { var msg = bout.toByteArray(); bout.reset(); - return implSign(name, secKey, sk2, msg, appRandom); + return implSign(pname, secKey, sk2, msg, appRandom); } else { throw new SignatureException("No private key"); } @@ -127,21 +122,21 @@ protected boolean engineVerify(byte[] sig) throws SignatureException { if (pubKey != null) { var msg = bout.toByteArray(); bout.reset(); - return implVerify(name, pubKey, pk2, msg, sig); + return implVerify(pname, pubKey, pk2, msg, sig); } else { throw new SignatureException("No public key"); } } @Override - @SuppressWarnings("deprecation") + @Deprecated protected void engineSetParameter(String param, Object value) throws InvalidParameterException { throw new InvalidParameterException("setParameter() not supported"); } @Override - @SuppressWarnings("deprecation") + @Deprecated protected Object engineGetParameter(String param) throws InvalidParameterException { throw new InvalidParameterException("getParameter() not supported"); } @@ -162,7 +157,7 @@ protected AlgorithmParameters engineGetParameters() { /// User-defined sign function. /// - /// @param name parameter name + /// @param pname parameter name /// @param sk private key in raw bytes /// @param sk2 parsed private key, `null` if none. See [#implCheckPrivateKey]. /// @param msg the message @@ -170,12 +165,12 @@ protected AlgorithmParameters engineGetParameters() { /// @return the signature /// @throws ProviderException if there is an internal error /// @throws SignatureException if there is another error - protected abstract byte[] implSign(String name, byte[] sk, Object sk2, + protected abstract byte[] implSign(String pname, byte[] sk, Object sk2, byte[] msg, SecureRandom sr) throws SignatureException; /// User-defined verify function. /// - /// @param name parameter name + /// @param pname parameter name /// @param pk public key in raw bytes /// @param pk2 parsed public key, `null` if none. See [#implCheckPublicKey]. /// @param msg the message @@ -183,7 +178,7 @@ protected abstract byte[] implSign(String name, byte[] sk, Object sk2, /// @return true if verified /// @throws ProviderException if there is an internal error /// @throws SignatureException if there is another error - protected abstract boolean implVerify(String name, byte[] pk, Object pk2, + protected abstract boolean implVerify(String pname, byte[] pk, Object pk2, byte[] msg, byte[] sig) throws SignatureException; /// User-defined function to validate a public key. @@ -195,11 +190,11 @@ protected abstract boolean implVerify(String name, byte[] pk, Object pk2, /// /// The default implementation returns `null`. /// - /// @param name parameter name + /// @param pname parameter name /// @param pk public key in raw bytes /// @return a parsed key, `null` if none. /// @throws InvalidKeyException if the key is invalid - protected Object implCheckPublicKey(String name, byte[] pk) throws InvalidKeyException { + protected Object implCheckPublicKey(String pname, byte[] pk) throws InvalidKeyException { return null; } @@ -212,11 +207,11 @@ protected Object implCheckPublicKey(String name, byte[] pk) throws InvalidKeyExc /// /// The default implementation returns `null`. /// - /// @param name parameter name + /// @param pname parameter name /// @param sk private key in raw bytes /// @return a parsed key, `null` if none. /// @throws InvalidKeyException if the key is invalid - protected Object implCheckPrivateKey(String name, byte[] sk) throws InvalidKeyException { + protected Object implCheckPrivateKey(String pname, byte[] sk) throws InvalidKeyException { return null; } } diff --git a/src/java.base/share/classes/sun/security/util/KeyChoices.java b/src/java.base/share/classes/sun/security/util/KeyChoices.java new file mode 100644 index 000000000000..da3c611750e2 --- /dev/null +++ b/src/java.base/share/classes/sun/security/util/KeyChoices.java @@ -0,0 +1,289 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.security.util; + +import java.security.*; +import java.util.Arrays; +import java.util.Locale; +import java.util.function.BiFunction; + +/** + * The content of an ML-KEM or ML-DSA private key is defined as a CHOICE + * among three different representations. For example: + *

    + *  ML-KEM-1024-PrivateKey ::= CHOICE {
    + *       seed [0] OCTET STRING (SIZE (64)),
    + *       expandedKey OCTET STRING (SIZE (3168)),
    + *       both SEQUENCE {
    + *           seed OCTET STRING (SIZE (64)),
    + *           expandedKey OCTET STRING (SIZE (3168))
    + *           }
    + *       }
    + * 
    + * This class supports reading, writing, and converting between them. + *

    + * Current code follows draft-ietf-lamps-kyber-certificates-11 and RFC 9881. + */ +public final class KeyChoices { + + public enum Type { SEED, EXPANDED_KEY, BOTH } + + private record Choice(Type type, byte[] seed, byte[] expanded) {} + + /** + * Gets the preferred choice type for an algorithm, defined as an + * overridable security property "jdk..pkcs8.encoding". + * + * @param name "mlkem" or "mldsa". + * @throws IllegalArgumentException if property is invalid value + * @return the type + */ + public static Type getPreferred(String name) { + var prop = SecurityProperties.getOverridableProperty( + "jdk." + name + ".pkcs8.encoding"); + if (prop == null) { + return Type.SEED; + } + return switch (prop.toLowerCase(Locale.ROOT)) { + case "seed" -> Type.SEED; + case "expandedkey" -> Type.EXPANDED_KEY; + case "both" -> Type.BOTH; + default -> throw new IllegalArgumentException("Unknown format: " + prop); + }; + } + + /** + * Writes one of the ML-KEM or ML-DSA private key formats. + *

    + * This method does not check the length of the inputs or whether + * they match each other. The caller must make sure `seed` and/or + * `expanded` are provided if `type` requires any of them. + * + * @param type preferred output choice type + * @param seed the seed, could be null + * @param expanded the expanded key, could be null + * @return one of the choices + */ + public static byte[] writeToChoice(Type type, byte[] seed, byte[] expanded) { + byte[] skOctets; + // Ensures using one-byte len in DER + assert seed == null || seed.length < 128; + // Ensures using two-byte len in DER + assert expanded == null || expanded.length > 256 && expanded.length < 60000; + + return switch (type) { + case SEED -> { + assert seed != null; + skOctets = new byte[seed.length + 2]; + skOctets[0] = (byte)0x80; + skOctets[1] = (byte) seed.length; + System.arraycopy(seed, 0, skOctets, 2, seed.length); + yield skOctets; + } + case EXPANDED_KEY -> { + assert expanded != null; + skOctets = new byte[expanded.length + 4]; + skOctets[0] = 0x04; + writeShortLength(skOctets, 1, expanded.length); + System.arraycopy(expanded, 0, skOctets, 4, expanded.length); + yield skOctets; + } + case BOTH -> { + assert seed != null; + assert expanded != null; + skOctets = new byte[10 + seed.length + expanded.length]; + skOctets[0] = 0x30; + writeShortLength(skOctets, 1, 6 + seed.length + expanded.length); + skOctets[4] = 0x04; + skOctets[5] = (byte)seed.length; + System.arraycopy(seed, 0, skOctets, 6, seed.length); + skOctets[6 + seed.length] = 0x04; + writeShortLength(skOctets, 7 + seed.length, expanded.length); + System.arraycopy(expanded, 0, skOctets, 10 + seed.length, expanded.length); + yield skOctets; + } + }; + } + + /** + * Gets the type of input. + * + * @param input input bytes + * @return the type + * @throws InvalidKeyException if input is invalid + */ + public static Type typeOfChoice(byte[] input) throws InvalidKeyException { + if (input.length < 1) { + throw new InvalidKeyException("Empty key"); + } + return switch (input[0]) { + case (byte) 0x80 -> Type.SEED; + case 0x04 -> Type.EXPANDED_KEY; + case 0x30 -> Type.BOTH; + default -> throw new InvalidKeyException("Wrong tag: " + input[0]); + }; + } + + /** + * Splits one of the ML-KEM or ML-DSA private key formats into + * seed and expandedKey, if exists. + * + * @param seedLen correct seed length + * @param input input bytes + * @return a {@code Choice} object. Byte arrays inside are newly allocated + * @throws InvalidKeyException if input is invalid + */ + private static Choice readFromChoice(int seedLen, byte[] input) + throws InvalidKeyException { + if (input.length < seedLen + 2) { + throw new InvalidKeyException("Too short"); + } + return switch (input[0]) { + case (byte) 0x80 -> { + // 80 SEED_LEN + if (input[1] != seedLen && input.length != seedLen + 2) { + throw new InvalidKeyException("Invalid seed"); + } + yield new Choice(Type.SEED, + Arrays.copyOfRange(input, 2, seedLen + 2), null); + } + case 0x04 -> { + // 04 82 nn nn + if (readShortLength(input, 1) != input.length - 4) { + throw new InvalidKeyException("Invalid expandedKey"); + } + yield new Choice(Type.EXPANDED_KEY, + null, Arrays.copyOfRange(input, 4, input.length)); + } + case 0x30 -> { + // 30 82 mm mm 04 SEED_LEN 04 82 nn nn + if (input.length < 6 + seedLen + 4) { + throw new InvalidKeyException("Too short"); + } + if (readShortLength(input, 1) != input.length - 4 + || input[4] != 0x04 + || input[5] != (byte)seedLen + || input[seedLen + 6] != 0x04 + || readShortLength(input, seedLen + 7) + != input.length - 10 - seedLen) { + throw new InvalidKeyException("Invalid both"); + } + yield new Choice(Type.BOTH, + Arrays.copyOfRange(input, 6, 6 + seedLen), + Arrays.copyOfRange(input, seedLen + 10, input.length)); + } + default -> throw new InvalidKeyException("Wrong tag: " + input[0]); + }; + } + + /** + * Reads from any encoding and write to the specified type. + * + * @param type preferred output choice type + * @param pname parameter set name + * @param seedLen seed length + * @param input the input encoding + * @param expander function to calculate expanded from seed, could be null + * if there is already expanded in input + * @return the preferred encoding + * @throws InvalidKeyException if input is invalid or does not have enough + * information to generate the output + */ + public static byte[] choiceToChoice(Type type, String pname, + int seedLen, byte[] input, + BiFunction expander) + throws InvalidKeyException { + var choice = readFromChoice(seedLen, input); + try { + if (type != Type.EXPANDED_KEY && choice.type == Type.EXPANDED_KEY) { + throw new InvalidKeyException( + "key contains not enough info to translate"); + } + var expanded = (choice.expanded == null && type != Type.SEED) + ? expander.apply(pname, choice.seed) + : choice.expanded; + return writeToChoice(type, choice.seed, expanded); + } finally { + if (choice.seed != null) { + Arrays.fill(choice.seed, (byte) 0); + } + if (choice.expanded != null) { + Arrays.fill(choice.expanded, (byte) 0); + } + } + } + + /** + * Reads from any choice of encoding and return the expanded format. + * + * @param pname parameter set name + * @param seedLen seed length + * @param input input encoding + * @param expander function to calculate expanded from seed, could be null + * if there is already expanded in input + * @return the expanded key + * @throws InvalidKeyException if input is invalid + */ + public static byte[] choiceToExpanded(String pname, + int seedLen, byte[] input, + BiFunction expander) + throws InvalidKeyException { + var choice = readFromChoice(seedLen, input); + if (choice.type == Type.BOTH) { + var calculated = expander.apply(pname, choice.seed); + if (!Arrays.equals(choice.expanded, calculated)) { + throw new InvalidKeyException("seed and expandedKey do not match"); + } + Arrays.fill(calculated, (byte)0); + } + try { + if (choice.expanded != null) { + return choice.expanded; + } + return expander.apply(pname, choice.seed); + } finally { + if (choice.seed != null) { + Arrays.fill(choice.seed, (byte)0); + } + } + } + + // Reads a 2 bytes length from DER encoding + private static int readShortLength(byte[] input, int from) + throws InvalidKeyException { + if (input[from] != (byte)0x82) { + throw new InvalidKeyException("Unexpected length"); + } + return ((input[from + 1] & 0xff) << 8) + (input[from + 2] & 0xff); + } + + // Writes a 2 bytes length to DER encoding + private static void writeShortLength(byte[] input, int from, int value) { + input[from] = (byte)0x82; + input[from + 1] = (byte) (value >> 8); + input[from + 2] = (byte) (value); + } +} diff --git a/src/java.base/share/classes/sun/security/x509/NamedX509Key.java b/src/java.base/share/classes/sun/security/x509/NamedX509Key.java index dc36bd3b9b30..0c3fe2bf1212 100644 --- a/src/java.base/share/classes/sun/security/x509/NamedX509Key.java +++ b/src/java.base/share/classes/sun/security/x509/NamedX509Key.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -71,7 +71,8 @@ public NamedX509Key(String fname, String pname, byte[] rawBytes) { setKey(new BitArray(rawBytes.length * 8, rawBytes)); } - /// Ctor from family name, and X.509 bytes + /// Ctor from family name, and X.509 bytes. Input byte array + /// is copied. Caller can modify it after the method call. public NamedX509Key(String fname, byte[] encoded) throws InvalidKeyException { this.fname = fname; decode(encoded); diff --git a/src/java.base/share/conf/security/java.security b/src/java.base/share/conf/security/java.security index 6086bcd10684..dc392ba0af57 100644 --- a/src/java.base/share/conf/security/java.security +++ b/src/java.base/share/conf/security/java.security @@ -1671,3 +1671,28 @@ jdk.epkcs8.defaultAlgorithm=PBEWithHmacSHA256AndAES_128 # com.sun.security.allowedAIALocations=http://some.company.com/cacert \ # ldap://ldap.company.com/dc=company,dc=com?caCertificate;binary com.sun.security.allowedAIALocations= + +# +# PKCS #8 encoding format for newly created ML-KEM and ML-DSA private keys +# +# draft-ietf-lamps-kyber-certificates-11 and RFC 9881 define three possible formats for a private key: +# a seed (64 bytes for ML-KEM, 32 bytes for ML-DSA), an expanded private key, +# or a sequence containing both. +# +# The following security properties determine the encoding format used when a +# new keypair is generated with a KeyPairGenerator, and the output of the +# translateKey method on an existing key using a ML-KEM or ML-DSA KeyFactory. +# +# Valid values for these properties are "seed", "expandedKey", and "both" +# (case-insensitive). The default is "seed". +# +# If a system property of the same name is also specified, it supersedes the +# security property value defined here. +# +# Note: These properties are currently used by the SunJCE (for ML-KEM) and SUN +# (for ML-DSA) providers in the JDK Reference implementation. They are not +# guaranteed to be supported by other implementations or third-party security +# providers. +# +#jdk.mlkem.pkcs8.encoding = seed +#jdk.mldsa.pkcs8.encoding = seed diff --git a/test/jdk/sun/security/provider/acvp/Launcher.java b/test/jdk/sun/security/provider/acvp/Launcher.java index 1f7b9fdd4836..799f145bb26c 100644 --- a/test/jdk/sun/security/provider/acvp/Launcher.java +++ b/test/jdk/sun/security/provider/acvp/Launcher.java @@ -39,7 +39,9 @@ * @bug 8342442 8345057 * @library /test/lib * @modules java.base/sun.security.provider - * @run main Launcher + * java.base/sun.security.util + * java.base/com.sun.crypto.provider + * @run main/othervm/timeout=480 Launcher */ /* @@ -48,7 +50,9 @@ * @bug 8342442 8345057 * @library /test/lib * @modules java.base/sun.security.provider - * @run main/othervm -Xcomp Launcher + * java.base/sun.security.util + * java.base/com.sun.crypto.provider + * @run main/othervm/timeout=480 -Xcomp Launcher */ /// This test runs on `internalProjection.json`-style files generated by NIST's diff --git a/test/jdk/sun/security/provider/acvp/ML_DSA_Test.java b/test/jdk/sun/security/provider/acvp/ML_DSA_Test.java index 281bb415305b..ac56642b8d7b 100644 --- a/test/jdk/sun/security/provider/acvp/ML_DSA_Test.java +++ b/test/jdk/sun/security/provider/acvp/ML_DSA_Test.java @@ -24,6 +24,7 @@ import jdk.test.lib.json.JSONValue; import jdk.test.lib.security.FixedSecureRandom; import sun.security.provider.ML_DSA_Impls; +import sun.security.util.DerOutputStream; import java.security.*; import java.security.spec.EncodedKeySpec; @@ -68,12 +69,13 @@ static void keyGenTest(JSONValue kat, Provider p) throws Exception { System.out.println(">> " + pname); for (var c : t.get("tests").asArray()) { System.out.print(c.get("tcId").asString() + " "); - g.initialize(np, new FixedSecureRandom(toByteArray(c.get("seed").asString()))); + var seed = toByteArray(c.get("seed").asString()); + g.initialize(np, new FixedSecureRandom(seed)); var kp = g.generateKeyPair(); var pk = f.getKeySpec(kp.getPublic(), EncodedKeySpec.class).getEncoded(); - var sk = f.getKeySpec(kp.getPrivate(), EncodedKeySpec.class).getEncoded(); Asserts.assertEqualsByteArray(toByteArray(c.get("pk").asString()), pk); - Asserts.assertEqualsByteArray(toByteArray(c.get("sk").asString()), sk); + Asserts.assertEqualsByteArray(toByteArray(c.get("sk").asString()), + ML_DSA_Impls.seedToExpanded(pname, seed)); } System.out.println(); } @@ -106,7 +108,7 @@ static void sigGenTest(JSONValue kat, Provider p) throws Exception { var sk = new PrivateKey() { public String getAlgorithm() { return pname; } public String getFormat() { return "RAW"; } - public byte[] getEncoded() { return toByteArray(c.get("sk").asString()); } + public byte[] getEncoded() { return oct(toByteArray(c.get("sk").asString())); } }; var sr = new FixedSecureRandom( det ? new byte[32] : toByteArray(c.get("rnd").asString())); @@ -119,6 +121,10 @@ static void sigGenTest(JSONValue kat, Provider p) throws Exception { } } + static byte[] oct(byte[] in) { + return new DerOutputStream().putOctetString(in).toByteArray(); + } + static void sigVerTest(JSONValue kat, Provider p) throws Exception { var s = p == null ? Signature.getInstance("ML-DSA") diff --git a/test/jdk/sun/security/provider/acvp/ML_KEM_Test.java b/test/jdk/sun/security/provider/acvp/ML_KEM_Test.java index c46c6a99e6da..35c1ce611dae 100644 --- a/test/jdk/sun/security/provider/acvp/ML_KEM_Test.java +++ b/test/jdk/sun/security/provider/acvp/ML_KEM_Test.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -20,9 +20,11 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ +import com.sun.crypto.provider.ML_KEM_Impls; import jdk.test.lib.Asserts; import jdk.test.lib.json.JSONValue; import jdk.test.lib.security.FixedSecureRandom; +import sun.security.util.DerOutputStream; import javax.crypto.KEM; import java.security.*; @@ -65,13 +67,14 @@ static void keyGenTest(JSONValue kat, Provider p) throws Exception { System.out.println(">> " + pname); for (var c : t.get("tests").asArray()) { System.out.print(c.get("tcId").asString() + " "); - g.initialize(np, new FixedSecureRandom( - toByteArray(c.get("d").asString()), toByteArray(c.get("z").asString()))); + var seed = toByteArray(c.get("d").asString() + c.get("z").asString()); + g.initialize(np, new FixedSecureRandom(seed)); var kp = g.generateKeyPair(); var pk = f.getKeySpec(kp.getPublic(), EncodedKeySpec.class).getEncoded(); - var sk = f.getKeySpec(kp.getPrivate(), EncodedKeySpec.class).getEncoded(); Asserts.assertEqualsByteArray(toByteArray(c.get("ek").asString()), pk); - Asserts.assertEqualsByteArray(toByteArray(c.get("dk").asString()), sk); + Asserts.assertEqualsByteArray( + toByteArray(c.get("dk").asString()), + ML_KEM_Impls.seedToExpanded(pname, seed)); } System.out.println(); } @@ -106,7 +109,7 @@ static void encapDecapTest(JSONValue kat, Provider p) throws Exception { var dk = new PrivateKey() { public String getAlgorithm() { return pname; } public String getFormat() { return "RAW"; } - public byte[] getEncoded() { return toByteArray(t.get("dk").asString()); } + public byte[] getEncoded() { return oct(toByteArray(t.get("dk").asString())); } }; for (var c : t.get("tests").asArray()) { System.out.print(c.get("tcId").asString() + " "); @@ -118,4 +121,8 @@ static void encapDecapTest(JSONValue kat, Provider p) throws Exception { } } } + + static byte[] oct(byte[] in) { + return new DerOutputStream().putOctetString(in).toByteArray(); + } } diff --git a/test/jdk/sun/security/provider/NamedEdDSA.java b/test/jdk/sun/security/provider/named/NamedEdDSA.java similarity index 84% rename from test/jdk/sun/security/provider/NamedEdDSA.java rename to test/jdk/sun/security/provider/named/NamedEdDSA.java index 4d0e3e9228aa..30df8b22b317 100644 --- a/test/jdk/sun/security/provider/NamedEdDSA.java +++ b/test/jdk/sun/security/provider/named/NamedEdDSA.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,11 +23,12 @@ /* * @test - * @bug 8340327 + * @bug 8340327 8347938 * @modules java.base/sun.security.ec.ed * java.base/sun.security.ec.point * java.base/sun.security.jca * java.base/sun.security.provider + * java.base/sun.security.util * @library /test/lib */ @@ -40,7 +41,10 @@ import sun.security.provider.NamedKeyFactory; import sun.security.provider.NamedKeyPairGenerator; import sun.security.provider.NamedSignature; +import sun.security.util.DerOutputStream; +import sun.security.util.DerValue; +import java.io.IOException; import java.security.*; import java.security.spec.EdDSAParameterSpec; import java.security.spec.NamedParameterSpec; @@ -66,11 +70,11 @@ public ProviderImpl() { public static class EdDSASignature extends NamedSignature { public EdDSASignature() { - super("EdDSA", "Ed25519", "Ed448"); + super("EdDSA", new EdDSAKeyFactory()); } protected EdDSASignature(String pname) { - super("EdDSA", pname); + super("EdDSA", new EdDSAKeyFactory(pname)); } public static class Ed25519 extends EdDSASignature { @@ -86,22 +90,32 @@ public Ed448() { } @Override - public byte[] implSign(String name, byte[] sk, Object sk2, byte[] msg, SecureRandom sr) throws SignatureException { - return getOps(name).sign(plain, sk, msg); + public byte[] implSign(String pname, byte[] sk, Object sk2, byte[] msg, SecureRandom sr) { + return getOps(pname).sign(plain, sk, msg); } @Override - public boolean implVerify(String name, byte[] pk, Object pk2, byte[] msg, byte[] sig) throws SignatureException { - return getOps(name).verify(plain, (AffinePoint) pk2, pk, msg, sig); + public boolean implVerify(String pname, byte[] pk, Object pk2, byte[] msg, byte[] sig) throws SignatureException { + return getOps(pname).verify(plain, (AffinePoint) pk2, pk, msg, sig); } @Override - public Object implCheckPublicKey(String name, byte[] pk) throws InvalidKeyException { - return getOps(name).decodeAffinePoint(InvalidKeyException::new, pk); + public Object implCheckPublicKey(String pname, byte[] pk) throws InvalidKeyException { + return getOps(pname).decodeAffinePoint(InvalidKeyException::new, pk); } } public static class EdDSAKeyFactory extends NamedKeyFactory { + @Override + protected byte[] implExpand(String pname, byte[] input) + throws InvalidKeyException { + try { + return new DerValue(input).getOctetString(); + } catch (IOException e) { + throw new InvalidKeyException(e); + } + } + public EdDSAKeyFactory() { super("EdDSA", "Ed25519", "Ed448"); } @@ -157,7 +171,10 @@ public byte[][] implGenerateKeyPair(String pname, SecureRandom sr) { // set the high-order bit of the encoded point byte msb = (byte) (point.isXOdd() ? 0x80 : 0); encodedPoint[encodedPoint.length - 1] |= msb; - return new byte[][] { encodedPoint, sk }; + return new byte[][] { + encodedPoint, + new DerOutputStream().putOctetString(sk).toByteArray(), + sk}; } private static void swap(byte[] arr, int i, int j) { diff --git a/test/jdk/sun/security/provider/NamedKeyFactoryTest.java b/test/jdk/sun/security/provider/named/NamedKeyFactoryTest.java similarity index 85% rename from test/jdk/sun/security/provider/NamedKeyFactoryTest.java rename to test/jdk/sun/security/provider/named/NamedKeyFactoryTest.java index 1ca179bc0469..e58809fcb692 100644 --- a/test/jdk/sun/security/provider/NamedKeyFactoryTest.java +++ b/test/jdk/sun/security/provider/named/NamedKeyFactoryTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,7 +23,7 @@ /* * @test - * @bug 8340327 + * @bug 8340327 8347938 * @modules java.base/sun.security.x509 * java.base/sun.security.pkcs * java.base/sun.security.provider @@ -41,10 +41,13 @@ import java.security.*; import java.security.spec.*; +import java.util.Arrays; public class NamedKeyFactoryTest { private static final SeededSecureRandom RAND = SeededSecureRandom.one(); + private static final byte[] RAW_SK = RAND.nBytes(16); + private static final byte[] RAW_PK = RAND.nBytes(16); public static void main(String[] args) throws Exception { Security.addProvider(new ProviderImpl()); @@ -78,8 +81,8 @@ public static void main(String[] args) throws Exception { g.initialize(new NamedParameterSpec("ShA-256")); checkKeyPair(g.generateKeyPair(), "SHA", "SHA-256"); - var pk = new NamedX509Key("sHa", "ShA-256", RAND.nBytes(2)); - var sk = new NamedPKCS8Key("sHa", "SHa-256", RAND.nBytes(2)); + var pk = new NamedX509Key("sHa", "ShA-256", RAW_PK); + var sk = NamedPKCS8Key.internalCreate("sHa", "SHa-256", RAW_SK, null); checkKey(pk, "sHa", "ShA-256"); checkKey(sk, "sHa", "SHa-256"); @@ -134,25 +137,27 @@ public static void main(String[] args) throws Exception { Asserts.assertEquals("RAW", srk2.getFormat()); Asserts.assertEqualsByteArray(srk2.getEncoded(), sk.getRawBytes()); + checkKey(kf2.generatePrivate(srk), "SHA", "SHA-256"); Asserts.assertEqualsByteArray(kf2.generatePrivate(srk).getEncoded(), sk.getEncoded()); Utils.runAndCheckException(() -> kf.generatePrivate(srk), InvalidKeySpecException.class); // no pname + checkKey(kf2.generatePrivate(srk), "SHA", "SHA-256"); Asserts.assertEqualsByteArray(kf2.generatePrivate(srk2).getEncoded(), sk.getEncoded()); Utils.runAndCheckException(() -> kf.generatePrivate(srk2), InvalidKeySpecException.class); // no pname var pk1 = new PublicKey() { public String getAlgorithm() { return "SHA"; } public String getFormat() { return "RAW"; } - public byte[] getEncoded() { return RAND.nBytes(2); } + public byte[] getEncoded() { return RAW_PK; } }; var pk2 = new PublicKey() { public String getAlgorithm() { return "sHA-256"; } public String getFormat() { return "RAW"; } - public byte[] getEncoded() { return RAND.nBytes(2); } + public byte[] getEncoded() { return RAW_PK; } }; var pk3 = new PublicKey() { public String getAlgorithm() { return "SHA"; } public String getFormat() { return "RAW"; } - public byte[] getEncoded() { return RAND.nBytes(2); } + public byte[] getEncoded() { return RAW_PK; } public AlgorithmParameterSpec getParams() { return new NamedParameterSpec("sHA-256"); } }; @@ -167,17 +172,17 @@ public static void main(String[] args) throws Exception { var sk1 = new PrivateKey() { public String getAlgorithm() { return "SHA"; } public String getFormat() { return "RAW"; } - public byte[] getEncoded() { return RAND.nBytes(2); } + public byte[] getEncoded() { return RAW_SK; } }; var sk2 = new PrivateKey() { public String getAlgorithm() { return "sHA-256"; } public String getFormat() { return "RAW"; } - public byte[] getEncoded() { return RAND.nBytes(2); } + public byte[] getEncoded() { return RAW_SK; } }; var sk3 = new PrivateKey() { public String getAlgorithm() { return "SHA"; } public String getFormat() { return "RAW"; } - public byte[] getEncoded() { return RAND.nBytes(2); } + public byte[] getEncoded() { return RAW_SK; } public AlgorithmParameterSpec getParams() { return new NamedParameterSpec("sHA-256"); } }; @@ -201,6 +206,14 @@ static void checkKey(Key k, String algName, String pname) { if (k instanceof AsymmetricKey ak && ak.getParams() instanceof NamedParameterSpec nps) { Asserts.assertEquals(pname, nps.getName()); } + if (k instanceof NamedPKCS8Key nsk) { + var raw = nsk.getRawBytes(); + Asserts.assertEqualsByteArray(Arrays.copyOf(RAW_SK, raw.length), raw); + } + if (k instanceof NamedX509Key npk) { + var raw = npk.getRawBytes(); + Asserts.assertEqualsByteArray(Arrays.copyOf(RAW_PK, raw.length), raw); + } } // Provider @@ -220,15 +233,24 @@ public static class KF extends NamedKeyFactory { public KF() { super("SHA", "SHA-256", "SHA-512"); } + + public KF(String name) { + super("SHA", name); + } + + @Override + protected byte[] implExpand(String pname, byte[] input) throws InvalidKeyException { + return null; + } } - public static class KF1 extends NamedKeyFactory { + public static class KF1 extends KF { public KF1() { - super("SHA", "SHA-256"); + super("SHA-256"); } } - public static class KF2 extends NamedKeyFactory { + public static class KF2 extends KF { public KF2() { - super("SHA", "SHA-512"); + super("SHA-512"); } } public static class KPG extends NamedKeyPairGenerator { @@ -243,8 +265,8 @@ public KPG(String pname) { @Override public byte[][] implGenerateKeyPair(String name, SecureRandom sr) { var out = new byte[2][]; - out[0] = RAND.nBytes(name.endsWith("256") ? 2 : 4); - out[1] = RAND.nBytes(name.endsWith("256") ? 2 : 4); + out[0] = name.endsWith("256") ? Arrays.copyOf(RAW_PK, 8) : RAW_PK; + out[1] = name.endsWith("256") ? Arrays.copyOf(RAW_SK, 8) : RAW_SK; return out; } } diff --git a/test/jdk/sun/security/provider/named/NamedKeys.java b/test/jdk/sun/security/provider/named/NamedKeys.java new file mode 100644 index 000000000000..e46cd981c708 --- /dev/null +++ b/test/jdk/sun/security/provider/named/NamedKeys.java @@ -0,0 +1,103 @@ +/* + * 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 8347938 + * @modules java.base/sun.security.pkcs + * java.base/sun.security.x509 + * @library /test/lib + * @summary check the Named***Key behavior + */ +import jdk.test.lib.Asserts; +import jdk.test.lib.security.SeededSecureRandom; +import sun.security.pkcs.NamedPKCS8Key; +import sun.security.x509.NamedX509Key; + +import java.util.Arrays; + +public class NamedKeys { + public static void main(String[] args) throws Exception { + + // This test uses fictional key algorithms SHA and SHA-256, + // simply because they look like a family name and parameter + // set name and SHA-256 already have its OID defined. + + var r = SeededSecureRandom.one(); + var raw = r.nBytes(32); + + // Create a key using raw bytes + var sk = NamedPKCS8Key.internalCreate("SHA", "SHA-256", raw, null); + var enc = sk.getEncoded(); + + // The raw bytes array is re-used + Asserts.assertTrue(sk.getRawBytes() == sk.getRawBytes()); + // but the encoding is different + Asserts.assertTrue(sk.getEncoded() != sk.getEncoded()); + + // When source change + Arrays.fill(raw, (byte)0); + // Internal raw bytes also changes + Asserts.assertEqualsByteArray(sk.getRawBytes(), new byte[32]); + // No guarantee on getEncoded() output, could be cached + + // Create a key using encoding + var sk1 = new NamedPKCS8Key("SHA", enc, null); + var sk2 = new NamedPKCS8Key("SHA", enc, null); + var raw1 = sk1.getRawBytes(); + Asserts.assertTrue(raw1 != sk2.getRawBytes()); + Asserts.assertTrue(sk1.getEncoded() != sk2.getEncoded()); + + var encCopy = enc.clone(); // store a copy + Arrays.fill(enc, (byte)0); // clean the source and the key unchanged + Asserts.assertEqualsByteArray(encCopy, sk1.getEncoded()); + + // Same with public key + // Create a key using raw bytes + raw = r.nBytes(32); + var pk = new NamedX509Key("SHA", "SHA-256", raw); + enc = pk.getEncoded().clone(); + + // The raw bytes array is re-used + Asserts.assertTrue(pk.getRawBytes() == pk.getRawBytes()); + // but the encoding is different + Asserts.assertTrue(pk.getEncoded() != pk.getEncoded()); + + // When source change + Arrays.fill(raw, (byte)0); + // Internal raw bytes also changes + Asserts.assertEqualsByteArray(pk.getRawBytes(), new byte[32]); + // No guarantee on getEncoded() output, could be cached + + // Create a key using encoding + var pk1 = new NamedX509Key("SHA", enc); + var pk2 = new NamedX509Key("SHA", enc); + raw1 = pk1.getRawBytes(); + Asserts.assertTrue(raw1 != pk2.getRawBytes()); + Asserts.assertTrue(pk1.getEncoded() != pk2.getEncoded()); + + encCopy = enc.clone(); // store a copy + Arrays.fill(enc, (byte)0); // clean the source and the key unchanged + Asserts.assertEqualsByteArray(encCopy, pk1.getEncoded()); + } +} diff --git a/test/jdk/sun/security/provider/pqc/PrivateKeyEncodings.java b/test/jdk/sun/security/provider/pqc/PrivateKeyEncodings.java new file mode 100644 index 000000000000..25060d0b74e9 --- /dev/null +++ b/test/jdk/sun/security/provider/pqc/PrivateKeyEncodings.java @@ -0,0 +1,227 @@ +/* + * 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 8347938 + * @library /test/lib + * @summary ensure ML-KEM and ML-DSA encodings consistent with + * draft-ietf-lamps-kyber-certificates-11 and RFC 9881 + * @modules java.base/com.sun.crypto.provider + * java.base/sun.security.pkcs + * java.base/sun.security.provider + * @run main/othervm PrivateKeyEncodings + */ +import com.sun.crypto.provider.ML_KEM_Impls; +import jdk.test.lib.Asserts; +import jdk.test.lib.security.RepositoryFileReader; +import jdk.test.lib.security.FixedSecureRandom; +import sun.security.pkcs.NamedPKCS8Key; +import sun.security.provider.ML_DSA_Impls; + +import javax.crypto.KEM; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.Signature; +import java.security.cert.CertificateFactory; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.NamedParameterSpec; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.Base64; +import java.util.HashMap; +import java.util.List; +import java.util.stream.Collectors; + +public class PrivateKeyEncodings { + + public static void main(String[] args) throws Exception { + // Example keys and certificates draft-ietf-lamps-kyber-certificates-11, Appendix B + // (https://datatracker.ietf.org/doc/html/draft-ietf-lamps-kyber-certificates-11#autoid-17) + // and RFC 9881, Appendix C.3 + // (https://datatracker.ietf.org/doc/html/rfc9881#name-example-certificates) + // + // These data can be retrieved from the following GitHub releases: + // https://github.com/lamps-wg/kyber-certificates/releases/tag/draft-ietf-lamps-kyber-certificates-11 + // https://github.com/lamps-wg/dilithium-certificates/releases/tag/draft-ietf-lamps-dilithium-certificates-13 + // + // Although the release tags include "draft", these values are the + // same as those in the final RFC 9881. + try (var kemReader = RepositoryFileReader.of(RepositoryFileReader.KYBER_CERTIFICATES.class, + "kyber-certificates-draft-ietf-lamps-kyber-certificates-11/"); + var dsaReader = RepositoryFileReader.of(RepositoryFileReader.DILITHIUM_CERTIFICATES.class, + "dilithium-certificates-draft-ietf-lamps-dilithium-certificates-13/")) { + good(kemReader, dsaReader); + badkem(kemReader); + baddsa(dsaReader); + } + } + + static void badkem(RepositoryFileReader f) throws Exception { + var kf = KeyFactory.getInstance("ML-KEM"); + + // The first ML-KEM-512-PrivateKey example includes the both CHOICE, + // i.e., both seed and expandedKey are included. The seed and expanded + // values can be checked for inconsistencies. + Asserts.assertThrows(InvalidKeySpecException.class, () -> + kf.generatePrivate(new PKCS8EncodedKeySpec( + readData(f, "example/bad-ML-KEM-512-1.priv")))); + + // The second ML-KEM-512-PrivateKey example includes only expandedKey. + // The expanded private key has a mutated s_0 and a valid public key hash, + // but a pairwise consistency check would find that the public key + // fails to match private. + var k2 = kf.generatePrivate(new PKCS8EncodedKeySpec( + readData(f, "example/bad-ML-KEM-512-2.priv"))); + var pk2 = ML_KEM_Impls.privKeyToPubKey((NamedPKCS8Key) k2); + var enc = KEM.getInstance("ML-KEM").newEncapsulator(pk2).encapsulate(); + var dk = KEM.getInstance("ML-KEM").newDecapsulator(k2).decapsulate(enc.encapsulation()); + Asserts.assertNotEqualsByteArray(enc.key().getEncoded(), dk.getEncoded()); + + // The third ML-KEM-512-PrivateKey example includes only expandedKey. + // The expanded private key has a mutated H(ek); both a public key + // digest check and a pairwise consistency check should fail. + var k3 = kf.generatePrivate(new PKCS8EncodedKeySpec( + readData(f, "example/bad-ML-KEM-512-3.priv"))); + Asserts.assertThrows(InvalidKeyException.class, () -> + KEM.getInstance("ML-KEM").newDecapsulator(k3)); + + // The fourth ML-KEM-512-PrivateKey example includes the both CHOICE, + // i.e., both seed and expandedKey are included. There is mismatch + // of the seed and expanded private key in only the z implicit rejection + // secret; here the private and public vectors match and the pairwise + // consistency check passes, but z is different. + Asserts.assertThrows(InvalidKeySpecException.class, () -> + kf.generatePrivate(new PKCS8EncodedKeySpec( + readData(f, "example/bad-ML-KEM-512-4.priv")))); + } + + static void baddsa(RepositoryFileReader f) throws Exception { + var kf = KeyFactory.getInstance("ML-DSA"); + + // The first ML-DSA-PrivateKey example includes the both CHOICE, i.e., + // both seed and expandedKey are included. The seed and expanded values + // can be checked for inconsistencies. + Asserts.assertThrows(InvalidKeySpecException.class, () -> + kf.generatePrivate(new PKCS8EncodedKeySpec( + readData(f, "examples/bad-ML-DSA-44-1.priv")))); + + // The second ML-DSA-PrivateKey example includes only expandedKey. + // The public key fails to match the tr hash value in the private key. + var k2 = kf.generatePrivate(new PKCS8EncodedKeySpec( + readData(f, "examples/bad-ML-DSA-44-2.priv"))); + Asserts.assertThrows(IllegalArgumentException.class, () -> + ML_DSA_Impls.privKeyToPubKey((NamedPKCS8Key) k2)); + + // The third ML-DSA-PrivateKey example also includes only expandedKey. + // The private s_1 and s_2 vectors imply a t vector whose private low + // bits do not match the t_0 vector portion of the private key + // (its high bits t_1 are the primary content of the public key). + var k3 = kf.generatePrivate(new PKCS8EncodedKeySpec( + readData(f, "examples/bad-ML-DSA-44-3.priv"))); + Asserts.assertThrows(IllegalArgumentException.class, () -> + ML_DSA_Impls.privKeyToPubKey((NamedPKCS8Key) k3)); + } + + static void good(RepositoryFileReader kemReader, RepositoryFileReader dsaReader) + throws Exception { + + var seed = new byte[64]; + for (var i = 0; i < seed.length; i++) { + seed[i] = (byte) i; + } + var cf = CertificateFactory.getInstance("X.509"); + var allPublicKeys = new HashMap(); + + for (var pname: List.of("ML-DSA-44", "ML-DSA-65", "ML-DSA-87", // DSA first, will sign KEM + "ML-KEM-512", "ML-KEM-768", "ML-KEM-1024")) { + + var isKem = pname.startsWith("ML-KEM"); + KeyPairGenerator g = KeyPairGenerator.getInstance(isKem ? "ML-KEM" : "ML-DSA"); + var prop = isKem ? "mlkem" : "mldsa"; + var f = isKem ? kemReader : dsaReader; + var example = isKem ? "example/" : "examples/"; + + g.initialize(new NamedParameterSpec(pname), new FixedSecureRandom(seed)); + var pk = g.generateKeyPair().getPublic(); + allPublicKeys.put(pname, pk); + Asserts.assertEqualsByteArray(readData(f, example + pname + ".pub"), pk.getEncoded()); + + var in = new ByteArrayInputStream(readData(f, example + pname + ".crt")); + var c = cf.generateCertificate(in); + var signer = switch (pname) { + case "ML-KEM-512" -> allPublicKeys.get("ML-DSA-44"); + case "ML-KEM-768" -> allPublicKeys.get("ML-DSA-65"); + case "ML-KEM-1024" -> allPublicKeys.get("ML-DSA-87"); + default -> c.getPublicKey(); + }; + c.verify(signer); + Asserts.assertEquals(c.getPublicKey(), pk); + + for (var type : List.of("seed", "expandedkey", "both")) { + System.err.println(pname + " " + type); + System.setProperty("jdk." + prop + ".pkcs8.encoding", type); + g.initialize(new NamedParameterSpec(pname), new FixedSecureRandom(seed)); + var sk = g.generateKeyPair().getPrivate(); + if (type.equals("expandedkey")) type = "expanded"; + Asserts.assertEqualsByteArray( + readData(f, example + pname + "-" + type + ".priv"), sk.getEncoded()); + checkInterop(pk, sk); + } + } + } + + // Ensures pk and sk interop with each other + static void checkInterop(PublicKey pk, PrivateKey sk) throws Exception { + if (pk.getAlgorithm().startsWith("ML-KEM")) { + var kem = KEM.getInstance("ML-KEM"); + var enc = kem.newEncapsulator(pk).encapsulate(); + var k = kem.newDecapsulator(sk).decapsulate(enc.encapsulation()); + Asserts.assertEqualsByteArray(k.getEncoded(), enc.key().getEncoded()); + } else { + var msg = "hello".getBytes(StandardCharsets.UTF_8); + var s = Signature.getInstance("ML-DSA"); + s.initSign(sk); + s.update(msg); + var sig = s.sign(); + s.initVerify(pk); + s.update(msg); + Asserts.assertTrue(s.verify(sig)); + } + } + + static byte[] readData(RepositoryFileReader f, String entry) throws Exception { + byte[] data = f.read(entry); + var pem = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(data))) + .lines() + .filter(s -> !s.contains("-----")) + .collect(Collectors.joining()); + return Base64.getMimeDecoder().decode(pem); + } +} diff --git a/test/jdk/sun/security/provider/pqc/SeedOrExpanded.java b/test/jdk/sun/security/provider/pqc/SeedOrExpanded.java new file mode 100644 index 000000000000..0a5b462b037c --- /dev/null +++ b/test/jdk/sun/security/provider/pqc/SeedOrExpanded.java @@ -0,0 +1,194 @@ +/* + * 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 8347938 + * @library /test/lib + * @modules java.base/com.sun.crypto.provider + * java.base/sun.security.pkcs + * java.base/sun.security.provider + * java.base/sun.security.util + * java.base/sun.security.x509 + * @summary check key reading compatibility + * @run main/othervm SeedOrExpanded + */ + +import com.sun.crypto.provider.ML_KEM_Impls; +import jdk.test.lib.Asserts; +import jdk.test.lib.security.DerUtils; +import jdk.test.lib.security.FixedSecureRandom; +import jdk.test.lib.security.SeededSecureRandom; +import sun.security.pkcs.NamedPKCS8Key; +import sun.security.provider.ML_DSA_Impls; +import sun.security.util.DerValue; + +import javax.crypto.KEM; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.KeyFactory; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.Signature; + +public class SeedOrExpanded { + + static final SeededSecureRandom RAND = SeededSecureRandom.one(); + + public static void main(String[] args) throws Exception { + test("mlkem", "ML-KEM-768"); + test("mldsa", "ML-DSA-65"); + } + + static void test(String type, String alg) throws Exception { + + var seed = RAND.nBytes(alg.contains("ML-KEM") ? 64 : 32); + var g = KeyPairGenerator.getInstance(alg); + + // Generation + + g.initialize(-1, new FixedSecureRandom(seed)); + var kp = g.generateKeyPair(); + var pk = kp.getPublic(); + var kDefault = kp.getPrivate(); + + // Property value is case-insensitive + System.setProperty("jdk." + type + ".pkcs8.encoding", "SEED"); + g.initialize(-1, new FixedSecureRandom(seed)); + var kSeed = g.generateKeyPair().getPrivate(); + System.setProperty("jdk." + type + ".pkcs8.encoding", "expandedkey"); + g.initialize(-1, new FixedSecureRandom(seed)); + var kExpanded = g.generateKeyPair().getPrivate(); + System.setProperty("jdk." + type + ".pkcs8.encoding", "boTH"); + g.initialize(-1, new FixedSecureRandom(seed)); + var kBoth = g.generateKeyPair().getPrivate(); + + // Invalid property value + System.setProperty("jdk." + type + ".pkcs8.encoding", "bogus"); + g.initialize(-1, new FixedSecureRandom(seed)); + Asserts.assertThrows(IllegalArgumentException.class, + () -> g.generateKeyPair().getPrivate()); + + byte[] kExpandedEncoded = kExpanded.getEncoded(); + byte[] kSeedEncoded = kSeed.getEncoded(); + byte[] kBothEncoded = kBoth.getEncoded(); + + // Ensure tags match the CHOICE definition + Asserts.assertEquals((byte) 0x80, DerUtils.innerDerValue(kSeedEncoded, "2c").tag); + Asserts.assertEquals((byte) 0x04, DerUtils.innerDerValue(kExpandedEncoded, "2c").tag); + Asserts.assertEquals((byte) 0x30, DerUtils.innerDerValue(kBothEncoded, "2c").tag); + + byte[] seedData = DerUtils.innerDerValue(kSeedEncoded, "2c") + .withTag(DerValue.tag_OctetString).getOctetString(); + byte[] expandedData = DerUtils.innerDerValue(kExpandedEncoded, "2c").getOctetString(); + + Asserts.assertEqualsByteArray(seed, seedData); + Asserts.assertEqualsByteArray( + seedData, + DerUtils.innerDerValue(kBothEncoded, "2c0").getOctetString()); + Asserts.assertEqualsByteArray( + expandedData, + DerUtils.innerDerValue(kBothEncoded, "2c1").getOctetString()); + + // Ensure seedToExpanded correctly called + if (alg.contains("ML-KEM")) { + Asserts.assertEqualsByteArray(expandedData, + ML_KEM_Impls.seedToExpanded(alg, seedData)); + } else { + Asserts.assertEqualsByteArray(expandedData, + ML_DSA_Impls.seedToExpanded(alg, seedData)); + } + + test(alg, pk, kSeed); + test(alg, pk, kExpanded); + test(alg, pk, kBoth); + + var kf = KeyFactory.getInstance(alg); + + System.setProperty("jdk." + type + ".pkcs8.encoding", "seed"); + Asserts.assertEqualsByteArray( + test(alg, pk, kf.translateKey(kBoth)).getEncoded(), + kSeedEncoded); + Asserts.assertTrue(kf.translateKey(kSeed) == kSeed); + Asserts.assertThrows(InvalidKeyException.class, () -> kf.translateKey(kExpanded)); + + System.setProperty("jdk." + type + ".pkcs8.encoding", "expandedkey"); + Asserts.assertEqualsByteArray( + test(alg, pk, kf.translateKey(kBoth)).getEncoded(), + kExpandedEncoded); + Asserts.assertEqualsByteArray( + test(alg, pk, kf.translateKey(kSeed)).getEncoded(), + kExpandedEncoded); + Asserts.assertTrue(kf.translateKey(kExpanded) == kExpanded); + + System.setProperty("jdk." + type + ".pkcs8.encoding", "both"); + Asserts.assertTrue(kf.translateKey(kBoth) == kBoth); + Asserts.assertEqualsByteArray( + test(alg, pk, kf.translateKey(kSeed)).getEncoded(), + kBothEncoded); + Asserts.assertThrows(InvalidKeyException.class, () -> kf.translateKey(kExpanded)); + + // The following makes sure key is not mistakenly cleaned during + // translations. + var xk = new PrivateKey() { + public String getAlgorithm() { return alg; } + public String getFormat() { return "PKCS#8"; } + public byte[] getEncoded() { return kBothEncoded.clone(); } + }; + test(alg, pk, xk); + var xk2 = (PrivateKey) kf.translateKey(xk); + test(alg, pk, xk2); + test(alg, pk, xk); + } + + static PrivateKey test(String alg, PublicKey pk, Key k) throws Exception { + var sk = (PrivateKey) k; + if (alg.contains("ML-KEM")) { + var kem = KEM.getInstance("ML-KEM"); + var e = kem.newEncapsulator(pk, RAND); + var enc = e.encapsulate(); + var k1 = kem.newDecapsulator(sk).decapsulate(enc.encapsulation()); + Asserts.assertEqualsByteArray(k1.getEncoded(), enc.key().getEncoded()); + if (k instanceof NamedPKCS8Key npk) { + Asserts.assertEqualsByteArray( + ML_KEM_Impls.privKeyToPubKey(npk).getEncoded(), pk.getEncoded()); + } + } else { + var s = Signature.getInstance("ML-DSA"); + var rnd = RAND.nBytes(32); // randomness for signature generation + var msg = RAND.nBytes(20); + s.initSign(sk, new FixedSecureRandom(rnd)); + s.update(msg); + var sig1 = s.sign(); + s.initVerify(pk); + s.update(msg); + Asserts.assertTrue(s.verify(sig1)); + if (k instanceof NamedPKCS8Key npk) { + Asserts.assertEqualsByteArray( + ML_DSA_Impls.privKeyToPubKey(npk).getEncoded(), pk.getEncoded()); + } + } + return sk; + } +} diff --git a/test/lib/jdk/test/lib/process/Proc.java b/test/lib/jdk/test/lib/process/Proc.java index 2fe802fed6cb..a989906b2abe 100644 --- a/test/lib/jdk/test/lib/process/Proc.java +++ b/test/lib/jdk/test/lib/process/Proc.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -256,6 +256,15 @@ public Proc start() throws IOException { } } } + String patchPath = System.getProperty("test.patch.path"); + if (patchPath != null) { + try (var subs = Files.newDirectoryStream(Path.of(patchPath))) { + for (var sub : subs) { + var name = sub.getFileName(); + cmd.add("--patch-module=" + name + "=" + sub); + } + } + } var lcp = fullcp(); if (lcp != null) { diff --git a/test/lib/jdk/test/lib/security/RepositoryFileReader.java b/test/lib/jdk/test/lib/security/RepositoryFileReader.java new file mode 100644 index 000000000000..04bc3c88d2a0 --- /dev/null +++ b/test/lib/jdk/test/lib/security/RepositoryFileReader.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.test.lib.security; + +import jdk.test.lib.artifacts.Artifact; +import jdk.test.lib.artifacts.ArtifactResolver; +import jdk.test.lib.artifacts.ArtifactResolverException; +import jtreg.SkippedException; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +/// A helper class to read files from a code repository. +/// +/// By default, the code repository is stored on the artifact server +/// as a ZIP file. +/// +/// Users can specify the "jdk.tests.repos.pattern" system property to read +/// the files from an alternative location. The value of this system property +/// represents the URL for each file entry where: +/// - "%o" maps to the last part of [organization name][Artifact#organization()], +/// - "%n" maps to [name][Artifact#name()], +/// - "%r" maps to [version][Artifact#revision()], +/// - "%e" maps to the name of the file entry to read. +/// +/// For example, with the [CMS_ML_DSA] class inside this test: +/// - The pattern `file:///Users/tester/repos/external/%o/%n/%e` resolves to +/// a local file like `/Users/tester/repos/external/lamps-wg/cms-ml-dsa/entry`. +/// - The pattern `https://raw.repos.com/%o/%n/%r/%e` resolves to +/// `https://raw.repos.com/lamps-wg/cms-ml-dsa/c8f0cf7/entry`. +/// +public sealed interface RepositoryFileReader extends AutoCloseable { + + /// Reads the content of `entry` as a byte array. + byte[] read(String entry) throws IOException; + + /// Overrides the method with a different exception type + /// to avoid compiler warnings about `InterruptedException`. + @Override + void close() throws IOException; + + /// Returns a `RepositoryFileReader`. + /// @param klass the `Artifact` class + /// @param zipPrefix the prefix used in the ZIP file. See + /// [ZipReader#ZipReader(ZipFile, String)]. + static RepositoryFileReader of(Class klass, String zipPrefix) { + Artifact artifact = klass.getAnnotation(Artifact.class); + var org = artifact.organization(); + var prop = System.getProperty("jdk.tests.repos.pattern"); + if (prop != null && org.startsWith("jpg.tests.jdk.repos.")) { + prop = prop.replace("%o", org.substring(org.lastIndexOf('.') + 1)); + prop = prop.replace("%n", artifact.name()); + prop = prop.replace("%r", artifact.revision()); + System.out.println("Creating URLReader on " + prop); + return new URLReader(prop); + } else { + try { + Path p = ArtifactResolver.resolve(klass).entrySet().stream() + .findAny().get().getValue(); + System.out.println("Creating ZipReader on " + p); + return new ZipReader(new ZipFile(p.toFile()), zipPrefix); + } catch (ArtifactResolverException | IOException e) { + throw new SkippedException("Cannot find " + artifact.name(), e); + } + } + } + + /// A `RepositoryFileReader` to read file from a URL. + /// @param base the base URL string, contains "%e" mapping to entry name + record URLReader(String base) implements RepositoryFileReader { + @Override + public void close() { + // nothing to do + } + + @Override + public byte[] read(String entry) throws IOException { + System.out.println("Reading " + entry + "..."); + try (var is = new URI(base.replace("%e", entry)).toURL().openStream()) { + return is.readAllBytes(); + } catch (URISyntaxException e) { + throw new IOException("Cannot create URI", e); + } + } + } + + /// A `RepositoryFileReader` to read file from a ZIP file. + /// @param zf the `ZipFile` + /// @param zipPrefix optional prefix string inside the ZIP file. For example, + /// if an entry "folder/file" is represented as "archive/folder/file" + /// inside the ZIP, "archive/" should be provided as `zipPrefix`. + record ZipReader(ZipFile zf, String zipPrefix) implements RepositoryFileReader { + @Override + public void close() throws IOException { + zf.close(); + } + + @Override + public byte[] read(String entry) throws IOException { + System.out.println("Reading " + entry + "..."); + ZipEntry ze = zf.getEntry(zipPrefix + entry); + if (ze != null) { + return zf.getInputStream(ze).readAllBytes(); + } else { + throw new RuntimeException("Entry not found: " + entry); + } + } + } + + @Artifact( + organization = "jpg.tests.jdk.repos.lamps-wg", + name = "dilithium-certificates", + revision = "785a549", + extension = "zip", + unpack = false) + public static class DILITHIUM_CERTIFICATES { + } + + @Artifact( + organization = "jpg.tests.jdk.repos.lamps-wg", + name = "cms-ml-dsa", + revision = "c8f0cf7", + extension = "zip", + unpack = false) + public static class CMS_ML_DSA { + } + + @Artifact( + organization = "jpg.tests.jdk.repos.lamps-wg", + name = "kyber-certificates", + revision = "29f3215", + extension = "zip", + unpack = false) + public static class KYBER_CERTIFICATES { + } +} From eaecaf8031a0f441a8cd00067e777ba213d00f9c Mon Sep 17 00:00:00 2001 From: Goetz Lindenmaier Date: Thu, 9 Apr 2026 07:42:08 +0000 Subject: [PATCH 112/168] 8323792: ThreadSnapshot::initialize can cause assert in Thread::check_for_dangling_thread_pointer (possibility of dangling Thread pointer) Reviewed-by: mdoerr Backport-of: 4e9b35f9e8771e18352c7dfd3e3bc85f1811f617 --- src/hotspot/share/runtime/threadSMR.cpp | 7 +- src/hotspot/share/runtime/threads.cpp | 2 +- src/hotspot/share/services/management.cpp | 3 +- src/hotspot/share/services/threadIdTable.cpp | 22 ++- src/hotspot/share/services/threadIdTable.hpp | 7 +- .../threads/ThreadInfoTest.java | 187 ++++++++++++++++++ 6 files changed, 211 insertions(+), 17 deletions(-) create mode 100644 test/hotspot/jtreg/serviceability/threads/ThreadInfoTest.java diff --git a/src/hotspot/share/runtime/threadSMR.cpp b/src/hotspot/share/runtime/threadSMR.cpp index 631a7ed8d797..1eb6195543a1 100644 --- a/src/hotspot/share/runtime/threadSMR.cpp +++ b/src/hotspot/share/runtime/threadSMR.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -726,7 +726,8 @@ JavaThread* ThreadsList::find_JavaThread_from_java_tid(jlong java_tid) const { } } } - } else if (!thread->is_exiting()) { + } else if (includes(thread) && !thread->is_exiting()) { + // The thread is protected by this list and has not yet exited return thread; } return nullptr; @@ -865,7 +866,7 @@ void ThreadsSMRSupport::add_thread(JavaThread *thread){ ThreadsList *old_list = xchg_java_thread_list(new_list); free_list(old_list); - if (ThreadIdTable::is_initialized()) { + if (ThreadIdTable::is_initialized_acquire()) { jlong tid = SharedRuntime::get_java_tid(thread); ThreadIdTable::add_thread(tid, thread); } diff --git a/src/hotspot/share/runtime/threads.cpp b/src/hotspot/share/runtime/threads.cpp index 7251411a1675..7e164ed16e2e 100644 --- a/src/hotspot/share/runtime/threads.cpp +++ b/src/hotspot/share/runtime/threads.cpp @@ -1106,7 +1106,7 @@ void Threads::remove(JavaThread* p, bool is_daemon) { ConditionalMutexLocker throttle_ml(ThreadsLockThrottle_lock, UseThreadsLockThrottleLock); MonitorLocker ml(Threads_lock); - if (ThreadIdTable::is_initialized()) { + if (ThreadIdTable::is_initialized_acquire()) { // This cleanup must be done before the current thread's GC barrier // is detached since we need to touch the threadObj oop. jlong tid = SharedRuntime::get_java_tid(p); diff --git a/src/hotspot/share/services/management.cpp b/src/hotspot/share/services/management.cpp index c414938debb1..9d4ed5ff94a5 100644 --- a/src/hotspot/share/services/management.cpp +++ b/src/hotspot/share/services/management.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -1127,6 +1127,7 @@ JVM_ENTRY(jint, jmm_GetThreadInfo(JNIEnv *env, jlongArray ids, jint maxDepth, jo // create dummy snapshot dump_result.add_thread_snapshot(); } else { + assert(dump_result.t_list()->includes(jt), "Must be protected"); dump_result.add_thread_snapshot(jt); } } diff --git a/src/hotspot/share/services/threadIdTable.cpp b/src/hotspot/share/services/threadIdTable.cpp index e7fd97911483..0f63424284dd 100644 --- a/src/hotspot/share/services/threadIdTable.cpp +++ b/src/hotspot/share/services/threadIdTable.cpp @@ -1,6 +1,6 @@ /* -* Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. +* Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,7 +24,6 @@ */ #include "classfile/javaClasses.inline.hpp" -#include "runtime/atomic.hpp" #include "runtime/interfaceSupport.inline.hpp" #include "runtime/javaThread.inline.hpp" #include "runtime/threadSMR.hpp" @@ -82,24 +81,25 @@ class ThreadIdTableConfig : public AllStatic { // Lazily creates the table and populates it with the given // thread list void ThreadIdTable::lazy_initialize(const ThreadsList *threads) { - if (!_is_initialized) { + if (!Atomic::load_acquire(&_is_initialized)) { { // There is no obvious benefit in allowing the thread table // to be concurrently populated during initialization. MutexLocker ml(ThreadIdTableCreate_lock); - if (_is_initialized) { + if (Atomic::load(&_is_initialized)) { return; } create_table(threads->length()); - _is_initialized = true; + Atomic::release_store(&_is_initialized, true); } + for (uint i = 0; i < threads->length(); i++) { JavaThread* thread = threads->thread_at(i); oop tobj = thread->threadObj(); if (tobj != nullptr) { - jlong java_tid = java_lang_Thread::thread_id(tobj); MutexLocker ml(Threads_lock); if (!thread->is_exiting()) { + jlong java_tid = java_lang_Thread::thread_id(tobj); // Must be inside the lock to ensure that we don't add a thread to the table // that has just passed the removal point in Threads::remove(). add_thread(java_tid, thread); @@ -212,7 +212,7 @@ class ThreadGet : public StackObj { }; void ThreadIdTable::do_concurrent_work(JavaThread* jt) { - assert(_is_initialized, "Thread table is not initialized"); + assert(Atomic::load(&_is_initialized), "Thread table is not initialized"); _has_work = false; double load_factor = get_load_factor(); log_debug(thread, table)("Concurrent work, load factor: %g", load_factor); @@ -222,7 +222,8 @@ void ThreadIdTable::do_concurrent_work(JavaThread* jt) { } JavaThread* ThreadIdTable::add_thread(jlong tid, JavaThread* java_thread) { - assert(_is_initialized, "Thread table is not initialized"); + assert(Threads_lock->owned_by_self(), "Must hold Threads_lock"); + assert(Atomic::load(&_is_initialized), "Thread table is not initialized"); Thread* thread = Thread::current(); ThreadIdTableLookup lookup(tid); ThreadGet tg; @@ -241,7 +242,7 @@ JavaThread* ThreadIdTable::add_thread(jlong tid, JavaThread* java_thread) { } JavaThread* ThreadIdTable::find_thread_by_tid(jlong tid) { - assert(_is_initialized, "Thread table is not initialized"); + assert(Atomic::load(&_is_initialized), "Thread table is not initialized"); Thread* thread = Thread::current(); ThreadIdTableLookup lookup(tid); ThreadGet tg; @@ -250,7 +251,8 @@ JavaThread* ThreadIdTable::find_thread_by_tid(jlong tid) { } bool ThreadIdTable::remove_thread(jlong tid) { - assert(_is_initialized, "Thread table is not initialized"); + assert(Threads_lock->owned_by_self(), "Must hold Threads_lock"); + assert(Atomic::load(&_is_initialized), "Thread table is not initialized"); Thread* thread = Thread::current(); ThreadIdTableLookup lookup(tid); return _local_table->remove(thread, lookup); diff --git a/src/hotspot/share/services/threadIdTable.hpp b/src/hotspot/share/services/threadIdTable.hpp index 12772aed88c0..a292d04452a2 100644 --- a/src/hotspot/share/services/threadIdTable.hpp +++ b/src/hotspot/share/services/threadIdTable.hpp @@ -1,6 +1,6 @@ /* -* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. +* Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,6 +27,7 @@ #define SHARE_SERVICES_THREADIDTABLE_HPP #include "memory/allStatic.hpp" +#include "runtime/atomic.hpp" class JavaThread; class ThreadsList; @@ -41,7 +42,9 @@ class ThreadIdTable : public AllStatic { public: // Initialization static void lazy_initialize(const ThreadsList* threads); - static bool is_initialized() { return _is_initialized; } + static bool is_initialized_acquire() { + return Atomic::load_acquire(&_is_initialized); + } // Lookup and list management static JavaThread* find_thread_by_tid(jlong tid); diff --git a/test/hotspot/jtreg/serviceability/threads/ThreadInfoTest.java b/test/hotspot/jtreg/serviceability/threads/ThreadInfoTest.java new file mode 100644 index 000000000000..b5db455cc201 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/threads/ThreadInfoTest.java @@ -0,0 +1,187 @@ +/* + * 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. + */ + +import jdk.test.lib.Utils; + +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadInfo; +import java.lang.management.ThreadMXBean; + +import java.util.ArrayList; +import java.util.List; + +/* + * @test + * @bug 8323792 + * @summary Make sure that jmm_GetThreadInfo() call does not crash JVM + * @library /test/lib + * @modules java.management + * @run main/othervm ThreadInfoTest + * + * @comment Exercise getThreadInfo(ids, 0). Depth parameter of zero means + * no VM operation, which could crash with Threads starting and ending. + */ + +public class ThreadInfoTest { + private static com.sun.management.ThreadMXBean mbean = + (com.sun.management.ThreadMXBean)ManagementFactory.getThreadMXBean(); + + private static final int NUM_THREADS = 2; + static long[] ids = new long[NUM_THREADS]; + static ThreadInfo[] infos = new ThreadInfo[NUM_THREADS]; + static volatile int count = 0; + static int ITERATIONS = 4; + + public static void main(String[] argv) throws Exception { + boolean replacing = false; + + startThreads(ids, NUM_THREADS); + new MyGetThreadInfoThread(ids).start(); + new MyReplacerThread(ids).start(); + for (int i = 0; i < ITERATIONS; i++) { + do { + count = countInfo(infos); + System.out.println("Iteration " + i + ": ThreadInfos found (Threads alive): " + count); + goSleep(100); + } while (count > 0); + } + } + + private static Thread newThread(int i) { + Thread thread = new MyThread(i); + thread.setDaemon(true); + return thread; + } + + private static void startThreads(long[] ids, int count) { + System.out.println("Starting " + count + " Threads..."); + Thread[] threads = new Thread[count]; + for (int i = 0; i < count; i++) { + threads[i] = newThread(i); + threads[i].start(); + ids[i] = threads[i].getId(); + } + System.out.println(ids); + } + + // Count ThreadInfo from array, return how many are non-null. + private static int countInfo(ThreadInfo[] info) { + int count = 0; + if (info != null) { + int i = 0; + for (ThreadInfo ti: info) { + if (ti != null) { + count++; + } + i++; + } + } + return count; + } + + private static int replaceThreads(long[] ids, ThreadInfo[] info) { + int replaced = 0; + if (info != null) { + for (int i = 0; i < info.length; i++) { + ThreadInfo ti = info[i]; + if (ti == null) { + Thread thread = newThread(i); + thread.start(); + ids[i] = thread.getId(); + replaced++; + } + } + } + return replaced; + } + + private static void goSleep(long ms) { + try { + Thread.sleep(ms); + } catch (InterruptedException e) { + System.out.println("Unexpected exception is thrown: " + e); + } + } + + // A Thread which replaces Threads in the shared array of threads. + static class MyReplacerThread extends Thread { + long[] ids; + + public MyReplacerThread(long[] ids) { + this.ids = ids; + this.setDaemon(true); + } + + public void run() { + boolean replacing = false; + while (true) { + if (replacing) { + replaceThreads(ids, infos); + } + if (count < 10) { + replacing = true; + } + if (count > 20) { + replacing = false; + } + goSleep(1); + } + } + } + + // A Thread which lives for a short while. + static class MyThread extends Thread { + long endTimeMs; + + public MyThread(long n) { + super("MyThread-" + n); + endTimeMs = (n * n * 10) + System.currentTimeMillis(); + } + + public void run() { + try { + long sleep = Math.max(1, endTimeMs - System.currentTimeMillis()); + goSleep(sleep); + } catch (Exception e) { + System.out.println(Thread.currentThread().getName() + ": " + e); + } + } + } + + // A Thread to continually call getThreadInfo on a shared array of thread ids. + static class MyGetThreadInfoThread extends Thread { + long[] ids; + + public MyGetThreadInfoThread(long[] ids) { + this.ids = ids; + this.setDaemon(true); + } + + public void run() { + while (true) { + infos = mbean.getThreadInfo(ids, 0); + goSleep(10); + } + } + } +} From c4c044e077538f380d90526001ffe8f8f09bec65 Mon Sep 17 00:00:00 2001 From: Goetz Lindenmaier Date: Thu, 9 Apr 2026 07:47:06 +0000 Subject: [PATCH 113/168] 8380474: Crash SEGV in ThreadIdTable::lazy_initialize after JDK-8323792 Backport-of: 7e943e7d41ee8175660e236c4b7fe39604fdae2f --- src/hotspot/share/services/threadIdTable.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/hotspot/share/services/threadIdTable.cpp b/src/hotspot/share/services/threadIdTable.cpp index 0f63424284dd..54041dfbb83c 100644 --- a/src/hotspot/share/services/threadIdTable.cpp +++ b/src/hotspot/share/services/threadIdTable.cpp @@ -24,6 +24,7 @@ */ #include "classfile/javaClasses.inline.hpp" +#include "runtime/handles.hpp" #include "runtime/interfaceSupport.inline.hpp" #include "runtime/javaThread.inline.hpp" #include "runtime/threadSMR.hpp" @@ -95,11 +96,11 @@ void ThreadIdTable::lazy_initialize(const ThreadsList *threads) { for (uint i = 0; i < threads->length(); i++) { JavaThread* thread = threads->thread_at(i); - oop tobj = thread->threadObj(); + Handle tobj = Handle(JavaThread::current(), thread->threadObj()); if (tobj != nullptr) { MutexLocker ml(Threads_lock); if (!thread->is_exiting()) { - jlong java_tid = java_lang_Thread::thread_id(tobj); + jlong java_tid = java_lang_Thread::thread_id(tobj()); // Must be inside the lock to ensure that we don't add a thread to the table // that has just passed the removal point in Threads::remove(). add_thread(java_tid, thread); From 8ecc43cbe863d410a454c995df3c72e0030953b6 Mon Sep 17 00:00:00 2001 From: William Kemper Date: Thu, 9 Apr 2026 15:51:13 +0000 Subject: [PATCH 114/168] 8367473: Shenandoah: Make the detailed evacuation metrics a runtime diagnostic option 8367722: [GenShen] ShenandoahEvacuationStats is always empty Backport-of: e4cb86df2b05cef6dd7e29e8803ebbbf5b4fe5a2 --- .../gc/shenandoah/shenandoahControlThread.cpp | 11 +++--- .../gc/shenandoah/shenandoahEvacTracker.cpp | 39 +++++++++++-------- .../shenandoah/shenandoahGenerationalHeap.cpp | 11 ++++-- .../share/gc/shenandoah/shenandoahHeap.cpp | 20 ++++++---- .../gc/shenandoah/shenandoah_globals.hpp | 7 ++++ 5 files changed, 54 insertions(+), 34 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp index 52ecd811b9c0..a1aaa4fe6ca1 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp @@ -218,12 +218,11 @@ void ShenandoahControlThread::run_service() { if (ShenandoahPacing) { heap->pacer()->print_cycle_on(&ls); } -#ifdef NOT_PRODUCT - ShenandoahEvacuationTracker* evac_tracker = heap->evac_tracker(); - ShenandoahCycleStats evac_stats = evac_tracker->flush_cycle_to_global(); - evac_tracker->print_evacuations_on(&ls, &evac_stats.workers, - &evac_stats.mutators); -#endif + if (ShenandoahEvacTracking) { + ShenandoahEvacuationTracker* evac_tracker = heap->evac_tracker(); + ShenandoahCycleStats evac_stats = evac_tracker->flush_cycle_to_global(); + evac_tracker->print_evacuations_on(&ls, &evac_stats.workers, &evac_stats.mutators); + } } } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.cpp b/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.cpp index 7883e2c5b294..bc8fad713af4 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.cpp @@ -108,8 +108,10 @@ void ShenandoahEvacuationStats::ShenandoahEvacuations::print_on(outputStream* st void ShenandoahEvacuationStats::print_on(outputStream* st) const { st->print("Young: "); _young.print_on(st); - st->print("Promotion: "); _promotion.print_on(st); - st->print("Old: "); _old.print_on(st); + if (ShenandoahHeap::heap()->mode()->is_generational()) { + st->print("Promotion: "); _promotion.print_on(st); + st->print("Old: "); _old.print_on(st); + } if (_use_age_table) { _age_table->print_on(st); @@ -123,25 +125,28 @@ void ShenandoahEvacuationTracker::print_global_on(outputStream* st) { void ShenandoahEvacuationTracker::print_evacuations_on(outputStream* st, ShenandoahEvacuationStats* workers, ShenandoahEvacuationStats* mutators) { - st->print_cr("Workers: "); - workers->print_on(st); - st->cr(); - st->print_cr("Mutators: "); - mutators->print_on(st); - st->cr(); + if (ShenandoahEvacTracking) { + st->print_cr("Workers: "); + workers->print_on(st); + st->cr(); + st->print_cr("Mutators: "); + mutators->print_on(st); + st->cr(); + } ShenandoahHeap* heap = ShenandoahHeap::heap(); - - AgeTable young_region_ages(false); - for (uint i = 0; i < heap->num_regions(); ++i) { - ShenandoahHeapRegion* r = heap->get_region(i); - if (r->is_young()) { - young_region_ages.add(r->age(), r->get_live_data_words()); + if (heap->mode()->is_generational()) { + AgeTable young_region_ages(false); + for (uint i = 0; i < heap->num_regions(); ++i) { + ShenandoahHeapRegion* r = heap->get_region(i); + if (r->is_young()) { + young_region_ages.add(r->age(), r->get_live_data_words()); + } } + st->print("Young regions: "); + young_region_ages.print_on(st); + st->cr(); } - st->print("Young regions: "); - young_region_ages.print_on(st); - st->cr(); } class ShenandoahStatAggregator : public ThreadClosure { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp index 0de8f88a8023..e3ed9c5eebb6 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp @@ -324,8 +324,11 @@ oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, Shena return ShenandoahBarrierSet::resolve_forwarded(p); } + if (ShenandoahEvacTracking) { + evac_tracker()->begin_evacuation(thread, size * HeapWordSize, from_region->affiliation(), target_gen); + } + // Copy the object: - NOT_PRODUCT(evac_tracker()->begin_evacuation(thread, size * HeapWordSize, from_region->affiliation(), target_gen)); Copy::aligned_disjoint_words(cast_from_oop(p), copy, size); oop copy_val = cast_to_oop(copy); @@ -346,8 +349,10 @@ oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, Shena // safe to do this on the public copy (this is also done during concurrent mark). ContinuationGCSupport::relativize_stack_chunk(copy_val); - // Record that the evacuation succeeded - NOT_PRODUCT(evac_tracker()->end_evacuation(thread, size * HeapWordSize, from_region->affiliation(), target_gen)); + if (ShenandoahEvacTracking) { + // Record that the evacuation succeeded + evac_tracker()->end_evacuation(thread, size * HeapWordSize, from_region->affiliation(), target_gen); + } if (target_gen == OLD_GENERATION) { old_generation()->handle_evacuation(copy, size, from_region->is_young()); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp index 41b848d06890..51c06e18e255 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp @@ -1395,8 +1395,11 @@ oop ShenandoahHeap::try_evacuate_object(oop p, Thread* thread, ShenandoahHeapReg return ShenandoahBarrierSet::resolve_forwarded(p); } + if (ShenandoahEvacTracking) { + evac_tracker()->begin_evacuation(thread, size * HeapWordSize, from_region->affiliation(), target_gen); + } + // Copy the object: - NOT_PRODUCT(evac_tracker()->begin_evacuation(thread, size * HeapWordSize, from_region->affiliation(), target_gen)); Copy::aligned_disjoint_words(cast_from_oop(p), copy, size); // Try to install the new forwarding pointer. @@ -1406,7 +1409,9 @@ oop ShenandoahHeap::try_evacuate_object(oop p, Thread* thread, ShenandoahHeapReg // Successfully evacuated. Our copy is now the public one! ContinuationGCSupport::relativize_stack_chunk(copy_val); shenandoah_assert_correct(nullptr, copy_val); - NOT_PRODUCT(evac_tracker()->end_evacuation(thread, size * HeapWordSize, from_region->affiliation(), target_gen)); + if (ShenandoahEvacTracking) { + evac_tracker()->end_evacuation(thread, size * HeapWordSize, from_region->affiliation(), target_gen); + } return copy_val; } else { // Failed to evacuate. We need to deal with the object that is left behind. Since this @@ -1636,12 +1641,11 @@ void ShenandoahHeap::print_tracing_info() const { ResourceMark rm; LogStream ls(lt); -#ifdef NOT_PRODUCT - evac_tracker()->print_global_on(&ls); - - ls.cr(); - ls.cr(); -#endif + if (ShenandoahEvacTracking) { + evac_tracker()->print_global_on(&ls); + ls.cr(); + ls.cr(); + } phase_timings()->print_global_on(&ls); diff --git a/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp b/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp index 5e7f9b6d71a9..7ea7ecf7f495 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp @@ -408,6 +408,13 @@ "events.") \ range(0,100) \ \ + product(bool, ShenandoahEvacTracking, false, DIAGNOSTIC, \ + "Collect additional metrics about evacuations. Enabling this " \ + "tracks how many objects and how many bytes were evacuated, and " \ + "how many were abandoned. The information will be categorized " \ + "by thread type (worker or mutator) and evacuation type (young, " \ + "old, or promotion.") \ + \ product(uintx, ShenandoahMinYoungPercentage, 20, EXPERIMENTAL, \ "The minimum percentage of the heap to use for the young " \ "generation. Heuristics will not adjust the young generation " \ From 623ab91b0a6ee3c957c36adb179fc4baea770b6d Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Thu, 9 Apr 2026 16:15:34 +0000 Subject: [PATCH 115/168] 8378417: Printing All pages results in NPE for 1.1 PrintJob Reviewed-by: phh Backport-of: 8c4c8a16171187a4a1e9a95c04d20f2efd94145f --- .../classes/sun/awt/windows/WPrinterJob.java | 4 +- .../awt/PrintJob/TestPrintNoException.java | 61 +++++++++++++++++++ .../java/awt/print/PrinterJob/PageRanges.java | 2 +- 3 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 test/jdk/java/awt/PrintJob/TestPrintNoException.java diff --git a/src/java.desktop/windows/classes/sun/awt/windows/WPrinterJob.java b/src/java.desktop/windows/classes/sun/awt/windows/WPrinterJob.java index 6879ab327d70..4ee48a5c725b 100644 --- a/src/java.desktop/windows/classes/sun/awt/windows/WPrinterJob.java +++ b/src/java.desktop/windows/classes/sun/awt/windows/WPrinterJob.java @@ -1742,7 +1742,9 @@ private void setRangeCopiesAttribute(int from, int to, boolean isRangeSet, attributes.add(new PageRanges(from, to)); setPageRange(from, to); } else { - attributes.remove(PageRanges.class); + // Sets default values for PageRange attribute and setPageRange + attributes.add(new PageRanges(1, + Integer.MAX_VALUE)); setPageRange(Pageable.UNKNOWN_NUMBER_OF_PAGES, Pageable.UNKNOWN_NUMBER_OF_PAGES); } diff --git a/test/jdk/java/awt/PrintJob/TestPrintNoException.java b/test/jdk/java/awt/PrintJob/TestPrintNoException.java new file mode 100644 index 000000000000..b54ac8de56bd --- /dev/null +++ b/test/jdk/java/awt/PrintJob/TestPrintNoException.java @@ -0,0 +1,61 @@ +/* + * 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. + */ + +import java.awt.Frame; +import java.awt.JobAttributes; +import java.awt.PrintJob; +import java.awt.Robot; +import java.awt.Toolkit; +import java.awt.event.KeyEvent; + +/* + * @test + * @bug 8378417 + * @key headful printer + * @summary Verifies No Exception is thrown when Printing "All" pages + * @run main TestPrintNoException + */ + +public class TestPrintNoException { + + public static void main(String[] args) throws Exception { + Robot robot = new Robot(); + Thread t = new Thread (() -> { + robot.delay(5000); + robot.keyPress(KeyEvent.VK_ENTER); + robot.keyRelease(KeyEvent.VK_ENTER); + robot.waitForIdle(); + }); + + Frame testFrame = new Frame("print"); + try { + t.start(); + PrintJob pj = Toolkit.getDefaultToolkit().getPrintJob(testFrame, null, null); + if (pj != null) { + pj.end(); + } + } finally { + testFrame.dispose(); + } + } +} diff --git a/test/jdk/java/awt/print/PrinterJob/PageRanges.java b/test/jdk/java/awt/print/PrinterJob/PageRanges.java index 691357d5a3c8..3a84725910c3 100644 --- a/test/jdk/java/awt/print/PrinterJob/PageRanges.java +++ b/test/jdk/java/awt/print/PrinterJob/PageRanges.java @@ -23,7 +23,7 @@ /* * @test - * @bug 6575331 8297191 8373239 + * @bug 6575331 8297191 8373239 8378417 * @key printer * @summary The specified pages should be printed. * @library /java/awt/regtesthelpers From bf5901b1c341e9e32e4c5d1cebe734bd9a4ebc94 Mon Sep 17 00:00:00 2001 From: Martin Doerr Date: Fri, 10 Apr 2026 10:46:22 +0000 Subject: [PATCH 116/168] 8380565: PPC64: deoptimization stub should save vector registers 8381315: compiler/vectorapi/TestVectorReallocation.java fails with -XX:UseAVX=1 after JDK-8380565 Reviewed-by: rrich Backport-of: 7f4d121185d3e96798641a9813e735e61ee40963 --- src/hotspot/cpu/ppc/registerMap_ppc.cpp | 46 ++ src/hotspot/cpu/ppc/registerMap_ppc.hpp | 8 +- src/hotspot/cpu/ppc/sharedRuntime_ppc.cpp | 27 +- src/hotspot/cpu/ppc/vm_version_ppc.cpp | 3 +- .../vectorapi/TestVectorReallocation.java | 414 ++++++++++++++++++ 5 files changed, 483 insertions(+), 15 deletions(-) create mode 100644 src/hotspot/cpu/ppc/registerMap_ppc.cpp create mode 100644 test/hotspot/jtreg/compiler/vectorapi/TestVectorReallocation.java diff --git a/src/hotspot/cpu/ppc/registerMap_ppc.cpp b/src/hotspot/cpu/ppc/registerMap_ppc.cpp new file mode 100644 index 000000000000..2e7f8af89d35 --- /dev/null +++ b/src/hotspot/cpu/ppc/registerMap_ppc.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2026 SAP SE. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "runtime/registerMap.hpp" + +address RegisterMap::pd_location(VMReg base_reg, int slot_idx) const { + if (base_reg->is_VectorRegister()) { + // Not all physical slots belonging to a VectorRegister have corresponding + // valid VMReg locations in the RegisterMap. + // (See RegisterSaver::push_frame_reg_args_and_save_live_registers.) + // However, the slots are always saved to the stack in a contiguous region + // of memory so we can calculate the address of the upper slots by + // offsetting from the base address. + assert(base_reg->is_concrete(), "must pass base reg"); + address base_location = location(base_reg, nullptr); + if (base_location != nullptr) { + intptr_t offset_in_bytes = slot_idx * VMRegImpl::stack_slot_size; + return base_location + offset_in_bytes; + } else { + return nullptr; + } + } else { + return location(base_reg->next(slot_idx), nullptr); + } +} diff --git a/src/hotspot/cpu/ppc/registerMap_ppc.hpp b/src/hotspot/cpu/ppc/registerMap_ppc.hpp index 01eb642107cb..607c712d10f2 100644 --- a/src/hotspot/cpu/ppc/registerMap_ppc.hpp +++ b/src/hotspot/cpu/ppc/registerMap_ppc.hpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 2000, 2023, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2012, 2013 SAP SE. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2026 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -35,9 +35,7 @@ // Since there is none, we just return null. address pd_location(VMReg reg) const { return nullptr; } - address pd_location(VMReg base_reg, int slot_idx) const { - return location(base_reg->next(slot_idx), nullptr); - } + address pd_location(VMReg base_reg, int slot_idx) const; // no PD state to clear or copy: void pd_clear() {} diff --git a/src/hotspot/cpu/ppc/sharedRuntime_ppc.cpp b/src/hotspot/cpu/ppc/sharedRuntime_ppc.cpp index 37d6c9e6d51b..8d34f494d96b 100644 --- a/src/hotspot/cpu/ppc/sharedRuntime_ppc.cpp +++ b/src/hotspot/cpu/ppc/sharedRuntime_ppc.cpp @@ -103,7 +103,7 @@ class RegisterSaver { // During deoptimization only the result registers need to be restored // all the other values have already been extracted. - static void restore_result_registers(MacroAssembler* masm, int frame_size_in_bytes); + static void restore_result_registers(MacroAssembler* masm, int frame_size_in_bytes, bool save_vectors); // Constants and data structures: @@ -355,7 +355,7 @@ OopMap* RegisterSaver::push_frame_reg_args_and_save_live_registers(MacroAssemble } // Note that generate_oop_map in the following loop is only used for the - // polling_page_vectors_safepoint_handler_blob. + // polling_page_vectors_safepoint_handler_blob and the deopt_blob. // The order in which the vector contents are stored depends on Endianess and // the utilized instructions (PowerArchitecturePPC64). assert(is_aligned(offset, StackAlignmentInBytes), "should be"); @@ -367,6 +367,7 @@ OopMap* RegisterSaver::push_frame_reg_args_and_save_live_registers(MacroAssemble __ stxvp(as_VectorRegister(reg_num).to_vsr(), offset, R1_SP); // Note: The contents were read in the same order (see loadV16_Power9 node in ppc.ad). + // RegisterMap::pd_location only uses the first VMReg for each VectorRegister. if (generate_oop_map) { map->set_callee_saved(VMRegImpl::stack2reg(offset >> 2), RegisterSaver_LiveVecRegs[i LITTLE_ENDIAN_ONLY(+1) ].vmreg); @@ -386,6 +387,7 @@ OopMap* RegisterSaver::push_frame_reg_args_and_save_live_registers(MacroAssemble __ stxvd2x(as_VectorRegister(reg_num)->to_vsr(), R31, R1_SP); } // Note: The contents were read in the same order (see loadV16_Power8 / loadV16_Power9 node in ppc.ad). + // RegisterMap::pd_location only uses the first VMReg for each VectorRegister. if (generate_oop_map) { VMReg vsr = RegisterSaver_LiveVecRegs[i].vmreg; map->set_callee_saved(VMRegImpl::stack2reg(offset >> 2), vsr); @@ -572,10 +574,14 @@ void RegisterSaver::restore_argument_registers_and_pop_frame(MacroAssembler*masm } // Restore the registers that might be holding a result. -void RegisterSaver::restore_result_registers(MacroAssembler* masm, int frame_size_in_bytes) { +void RegisterSaver::restore_result_registers(MacroAssembler* masm, int frame_size_in_bytes, bool save_vectors) { const int regstosave_num = sizeof(RegisterSaver_LiveRegs) / sizeof(RegisterSaver::LiveRegType); - const int register_save_size = regstosave_num * reg_size; // VS registers not relevant here. + const int vecregstosave_num = save_vectors ? (sizeof(RegisterSaver_LiveVecRegs) / + sizeof(RegisterSaver::LiveRegType)) + : 0; + const int register_save_size = regstosave_num * reg_size + vecregstosave_num * vec_reg_size; + const int register_save_offset = frame_size_in_bytes - register_save_size; // restore all result registers (ints and floats) @@ -604,7 +610,7 @@ void RegisterSaver::restore_result_registers(MacroAssembler* masm, int frame_siz offset += reg_size; } - assert(offset == frame_size_in_bytes, "consistency check"); + assert(offset == frame_size_in_bytes - (save_vectors ? vecregstosave_num * vec_reg_size : 0), "consistency check"); } // Is vector's size (in bytes) bigger than a size saved by default? @@ -2993,7 +2999,8 @@ void SharedRuntime::generate_deopt_blob() { &first_frame_size_in_bytes, /*generate_oop_map=*/ true, return_pc_adjustment_no_exception, - RegisterSaver::return_pc_is_lr); + RegisterSaver::return_pc_is_lr, + /*save_vectors*/ SuperwordUseVSX); assert(map != nullptr, "OopMap must have been created"); __ li(exec_mode_reg, Deoptimization::Unpack_deopt); @@ -3028,7 +3035,8 @@ void SharedRuntime::generate_deopt_blob() { &first_frame_size_in_bytes, /*generate_oop_map=*/ false, /*return_pc_adjustment_exception=*/ 0, - RegisterSaver::return_pc_is_pre_saved); + RegisterSaver::return_pc_is_pre_saved, + /*save_vectors*/ SuperwordUseVSX); // Deopt during an exception. Save exec mode for unpack_frames. __ li(exec_mode_reg, Deoptimization::Unpack_exception); @@ -3046,7 +3054,8 @@ void SharedRuntime::generate_deopt_blob() { &first_frame_size_in_bytes, /*generate_oop_map=*/ false, /*return_pc_adjustment_reexecute=*/ 0, - RegisterSaver::return_pc_is_pre_saved); + RegisterSaver::return_pc_is_pre_saved, + /*save_vectors*/ SuperwordUseVSX); __ li(exec_mode_reg, Deoptimization::Unpack_reexecute); #endif @@ -3072,7 +3081,7 @@ void SharedRuntime::generate_deopt_blob() { // Restore only the result registers that have been saved // by save_volatile_registers(...). - RegisterSaver::restore_result_registers(masm, first_frame_size_in_bytes); + RegisterSaver::restore_result_registers(masm, first_frame_size_in_bytes, /*save_vectors*/ SuperwordUseVSX); // reload the exec mode from the UnrollBlock (it might have changed) __ lwz(exec_mode_reg, in_bytes(Deoptimization::UnrollBlock::unpack_kind_offset()), unroll_block_reg); diff --git a/src/hotspot/cpu/ppc/vm_version_ppc.cpp b/src/hotspot/cpu/ppc/vm_version_ppc.cpp index 721364ce412b..d076308026b6 100644 --- a/src/hotspot/cpu/ppc/vm_version_ppc.cpp +++ b/src/hotspot/cpu/ppc/vm_version_ppc.cpp @@ -25,6 +25,7 @@ #include "asm/assembler.inline.hpp" #include "asm/macroAssembler.inline.hpp" +#include "compiler/compilerDefinitions.inline.hpp" #include "compiler/disassembler.hpp" #include "jvm.h" #include "memory/resourceArea.hpp" @@ -103,7 +104,7 @@ void VM_Version::initialize() { if (PowerArchitecturePPC64 >= 9) { // Performance is good since Power9. - if (FLAG_IS_DEFAULT(SuperwordUseVSX)) { + if (FLAG_IS_DEFAULT(SuperwordUseVSX) && CompilerConfig::is_c2_enabled()) { FLAG_SET_ERGO(SuperwordUseVSX, true); } } diff --git a/test/hotspot/jtreg/compiler/vectorapi/TestVectorReallocation.java b/test/hotspot/jtreg/compiler/vectorapi/TestVectorReallocation.java new file mode 100644 index 000000000000..6b21b693d1ed --- /dev/null +++ b/test/hotspot/jtreg/compiler/vectorapi/TestVectorReallocation.java @@ -0,0 +1,414 @@ +/* + * Copyright (c) 2026 SAP SE. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * 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.vectorapi; + +import java.util.Arrays; + +import compiler.lib.ir_framework.*; + +import jdk.incubator.vector.ByteVector; +import jdk.incubator.vector.ShortVector; +import jdk.incubator.vector.IntVector; +import jdk.incubator.vector.LongVector; +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.DoubleVector; +import jdk.incubator.vector.VectorSpecies; + +import static jdk.test.lib.Asserts.*; + +/** + * @test + * @bug 8380565 + * @library /test/lib / + * @summary Test deoptimization involving vector reallocation + * @modules jdk.incubator.vector + * @requires vm.opt.final.MaxVectorSize == null | vm.opt.final.MaxVectorSize >= 16 + * + * @run driver compiler.vectorapi.TestVectorReallocation + */ + +public class TestVectorReallocation { + + private static final VectorSpecies B_SPECIES = ByteVector.SPECIES_PREFERRED; + private static final VectorSpecies S_SPECIES = ShortVector.SPECIES_PREFERRED; + private static final VectorSpecies I_SPECIES = IntVector.SPECIES_PREFERRED; + private static final VectorSpecies L_SPECIES = LongVector.SPECIES_PREFERRED; + private static final VectorSpecies F_SPECIES = FloatVector.SPECIES_PREFERRED; + private static final VectorSpecies D_SPECIES = DoubleVector.SPECIES_PREFERRED; + private static final int B_LENGTH = B_SPECIES.length(); + private static final int S_LENGTH = S_SPECIES.length(); + private static final int I_LENGTH = I_SPECIES.length(); + private static final int L_LENGTH = L_SPECIES.length(); + private static final int F_LENGTH = F_SPECIES.length(); + private static final int D_LENGTH = D_SPECIES.length(); + + // The input arrays for the @Test methods match the length of the preferred species for each type + private static byte[] b_a; + private static short[] s_a; + private static int[] i_a; + private static long[] l_a; + private static float[] f_a; + private static double[] d_a; + + // The output arrays for the @Test methods + private static byte[] b_r; + private static short[] s_r; + private static int[] i_r; + private static long[] l_r; + private static float[] f_r; + private static double[] d_r; + + public static void main(String[] args) { + TestFramework.runWithFlags("--add-modules=jdk.incubator.vector"); + } + + // The test methods annotated with @Test are warmed up by the framework. The calls are indirect + // through the runner methods annotated with @Run. Note that each @Test method has its own instance of + // the test class TestVectorReallocation as receiver of the calls. + // + // The @Test methods just copy the elements of the input array (0, 1, 2, 3, ...) to the output array + // by means of a vector add operation. The added value is computed but actually always zero. The + // computation is done in a loop with a virtual call that is inlined based on class hierarchy analysis + // when the method gets compiled. + // + // The final call after warmup of the now compiled @Test method is performed concurrently in a second + // thread. Before the variable `loopIterations` is set very high such that the loop runs practically + // infinitely. While the loop is running, a class with an overridden version of the method `value` + // (called in the loop) is loaded. This invalidates the result of the class hierarchy analysis that + // there is just one implementation of the method and causes deoptimization where the vector `v0` used + // in the @Test method is reallocated from a register to the java heap. Finally it is verified that + // input and ouput arrays are equal. + // + // NB: each @Test needs its own Zero class for the desired result of the class hierarchy analysis. + + volatile boolean enteredLoop; + volatile long loopIterations; + + void sharedRunner(RunInfo runInfo, Runnable test, Runnable loadOverridingClass, Runnable verify) { + enteredLoop = false; + if (runInfo.isWarmUp()) { + loopIterations = 100; + test.run(); + } else { + loopIterations = 1L << 60; // basically infinite + Thread t = Thread.ofPlatform().start(test); + waitUntilLoopEntered(); + loadOverridingClass.run(); // invalidates inlining causing deoptimization/reallocation of v0 + loopIterations = 0; + waitUntilLoopLeft(); + joinThread(t); + verify.run(); // verify that input and ouput arrays are equal + } + } + + ///////////////////////////////////////////////////////////////////////////////////// + // byte + + static class ByteZero { + volatile byte zero; + byte value() { + return zero; + } + } + volatile ByteZero byteZero = new ByteZero(); + + @Run(test = "byteIdentityWithReallocation") + void byteIdentityWithReallocation_runner(RunInfo runInfo) { + sharedRunner(runInfo, () -> byteIdentityWithReallocation(), () -> { + // Loading the class with the overridden method will cause deoptimization and reallocation of v0 + byteZero = new ByteZero() { + @Override + byte value() { + return super.value(); // override but doing the same + } + }; + }, + () -> assertTrue(Arrays.equals(b_a, b_r), "Input/Output arrays differ")); + } + + @Test + @IR(counts = {IRNode.ADD_VB, " >0 "}) + void byteIdentityWithReallocation() { + ByteVector v0 = ByteVector.fromArray(B_SPECIES, b_a, 0); + byte zeroSum = 0; + enteredLoop = true; + for (long i = 0; i < loopIterations; i++) { + zeroSum += byteZero.value(); // inlined based on class hierarchy analysis + } + v0.add(zeroSum).intoArray(b_r, 0); + enteredLoop = false; + } + + ///////////////////////////////////////////////////////////////////////////////////// + // short + + static class ShortZero { + volatile short zero; + short value() { + return zero; + } + } + volatile ShortZero shortZero = new ShortZero(); + + @Run(test = "shortIdentityWithReallocation") + void shortIdentityWithReallocation_runner(RunInfo runInfo) { + sharedRunner(runInfo, () -> shortIdentityWithReallocation(), () -> { + // Loading the class with the overridden method will cause deoptimization and reallocation of v0 + shortZero = new ShortZero() { + @Override + short value() { + return super.value(); // override but doing the same + } + }; + }, + () -> assertTrue(Arrays.equals(s_a, s_r), "Input/Output arrays differ")); + } + + @Test + @IR(counts = {IRNode.ADD_VS, " >0 "}) + void shortIdentityWithReallocation() { + ShortVector v0 = ShortVector.fromArray(S_SPECIES, s_a, 0); + short zeroSum = 0; + enteredLoop = true; + for (long i = 0; i < loopIterations; i++) { + zeroSum += shortZero.value(); // inlined based on class hierarchy analysis + } + v0.add(zeroSum).intoArray(s_r, 0); + enteredLoop = false; + } + + ///////////////////////////////////////////////////////////////////////////////////// + // int + + static class IntZero { + volatile int zero; + int value() { + return zero; + } + } + volatile IntZero intZero = new IntZero(); + + @Run(test = "intIdentityWithReallocation") + void intIdentityWithReallocation_runner(RunInfo runInfo) { + sharedRunner(runInfo, () -> intIdentityWithReallocation(), () -> { + // Loading the class with the overridden method will cause deoptimization and reallocation of v0 + intZero = new IntZero() { + @Override + int value() { + return super.value(); // override but doing the same + } + }; + }, + () -> assertTrue(Arrays.equals(i_a, i_r), "Input/Output arrays differ")); + } + + @Test + @IR(counts = {IRNode.ADD_VI, " >0 "}) + void intIdentityWithReallocation() { + IntVector v0 = IntVector.fromArray(I_SPECIES, i_a, 0); + int zeroSum = 0; + enteredLoop = true; + for (long i = 0; i < loopIterations; i++) { + zeroSum += intZero.value(); // inlined based on class hierarchy analysis + } + v0.add(zeroSum).intoArray(i_r, 0); + enteredLoop = false; + } + + ///////////////////////////////////////////////////////////////////////////////////// + // long + + static class LongZero { + volatile long zero; + long value() { + return zero; + } + } + volatile LongZero longZero = new LongZero(); + + @Run(test = "longIdentityWithReallocation") + void longIdentityWithReallocation_runner(RunInfo runInfo) { + sharedRunner(runInfo, () -> longIdentityWithReallocation(), () -> { + // Loading the class with the overridden method will cause deoptimization and reallocation of v0 + longZero = new LongZero() { + @Override + long value() { + return super.value(); // override but doing the same + } + }; + }, + () -> assertTrue(Arrays.equals(l_a, l_r), "Input/Output arrays differ")); + } + + @Test + @IR(counts = {IRNode.ADD_VL, " >0 "}) + void longIdentityWithReallocation() { + LongVector v0 = LongVector.fromArray(L_SPECIES, l_a, 0); + long zeroSum = 0; + enteredLoop = true; + for (long i = 0; i < loopIterations; i++) { + zeroSum += longZero.value(); // inlined based on class hierarchy analysis + } + v0.add(zeroSum).intoArray(l_r, 0); + enteredLoop = false; + } + + ///////////////////////////////////////////////////////////////////////////////////// + // float + + static class FloatZero { + volatile float zero; + float value() { + return zero; + } + } + volatile FloatZero floatZero = new FloatZero(); + + @Run(test = "floatIdentityWithReallocation") + void floatIdentityWithReallocation_runner(RunInfo runInfo) { + sharedRunner(runInfo, () -> floatIdentityWithReallocation(), () -> { + // Loading the class with the overridden method will cause deoptimization and reallocation of v0 + floatZero = new FloatZero() { + @Override + float value() { + return super.value(); // override but doing the same + } + }; + }, + () -> assertTrue(Arrays.equals(f_a, f_r), "Input/Output arrays differ")); + } + + @Test + @IR(counts = {IRNode.ADD_VF, IRNode.VECTOR_SIZE_ANY, " >0 "}) + void floatIdentityWithReallocation() { + FloatVector v0 = FloatVector.fromArray(F_SPECIES, f_a, 0); + float zeroSum = 0; + enteredLoop = true; + for (long i = 0; i < loopIterations; i++) { + zeroSum += floatZero.value(); // inlined based on class hierarchy analysis + } + v0.add(zeroSum).intoArray(f_r, 0); + enteredLoop = false; + } + + ///////////////////////////////////////////////////////////////////////////////////// + // double + + static class DoubleZero { + volatile double zero; + double value() { + return zero; + } + } + volatile DoubleZero doubleZero = new DoubleZero(); + + @Run(test = "doubleIdentityWithReallocation") + void doubleIdentityWithReallocation_runner(RunInfo runInfo) { + sharedRunner(runInfo, () -> doubleIdentityWithReallocation(), () -> { + // Loading the class with the overridden method will cause deoptimization and reallocation of v0 + doubleZero = new DoubleZero() { + @Override + double value() { + return super.value(); // override but doing the same + } + }; + }, + () -> assertTrue(Arrays.equals(d_a, d_r), "Input/Output arrays differ")); + } + + @Test + @IR(counts = {IRNode.ADD_VD, IRNode.VECTOR_SIZE_ANY, " >0 "}) + void doubleIdentityWithReallocation() { + DoubleVector v0 = DoubleVector.fromArray(D_SPECIES, d_a, 0); + double zeroSum = 0; + enteredLoop = true; + for (long i = 0; i < loopIterations; i++) { + zeroSum += doubleZero.value(); // inlined based on class hierarchy analysis + } + v0.add(zeroSum).intoArray(d_r, 0); + enteredLoop = false; + } + + ///////////////////////////////////////////////////////////////////////////////////// + + private void waitUntilLoopEntered() { + while (!enteredLoop) { + sleep(10); + } + } + + private void waitUntilLoopLeft() { + while (enteredLoop) { + sleep(10); + } + } + + private static void sleep(int ms) { + try { + Thread.sleep(ms); + } catch (InterruptedException e) { /* ignore */ } + } + + private static void joinThread(Thread t) { + try { + t.join(); + } catch (InterruptedException e) { /* ignore */ } + } + + static { + b_a = new byte[B_LENGTH]; + s_a = new short[S_LENGTH]; + i_a = new int[I_LENGTH]; + l_a = new long[L_LENGTH]; + f_a = new float[F_LENGTH]; + d_a = new double[D_LENGTH]; + + b_r = new byte[B_LENGTH]; + s_r = new short[S_LENGTH]; + i_r = new int[I_LENGTH]; + l_r = new long[L_LENGTH]; + f_r = new float[F_LENGTH]; + d_r = new double[D_LENGTH]; + + for (int i = 0; i < b_a.length ; i++) { + b_a[i] = (byte)i; + } + for (int i = 0; i < s_a.length ; i++) { + s_a[i] = (short)i; + } + for (int i = 0; i < i_a.length ; i++) { + i_a[i] = i; + } + for (int i = 0; i < l_a.length ; i++) { + l_a[i] = i; + } + for (int i = 0; i < f_a.length ; i++) { + f_a[i] = i; + } + for (int i = 0; i < d_a.length ; i++) { + d_a[i] = i; + } + } +} From b5d705e99ef888dbb22f1a97ddc1129b6c7320e5 Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Fri, 10 Apr 2026 14:03:50 +0000 Subject: [PATCH 117/168] 8379499: [AIX] headless-only build of libjawt.so fails Backport-of: 4d42ce23f03ecf256ff4af66446a9b263e37a186 --- make/modules/java.desktop/lib/AwtLibraries.gmk | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/make/modules/java.desktop/lib/AwtLibraries.gmk b/make/modules/java.desktop/lib/AwtLibraries.gmk index 8b6b50b9e623..1aa2578d2e2e 100644 --- a/make/modules/java.desktop/lib/AwtLibraries.gmk +++ b/make/modules/java.desktop/lib/AwtLibraries.gmk @@ -1,5 +1,5 @@ # -# Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2011, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -423,6 +423,9 @@ endif ifeq ($(call isTargetOs, linux)+$(ENABLE_HEADLESS_ONLY), true+true) LIBJAWT_CFLAGS += -DHEADLESS endif +ifeq ($(call isTargetOs, aix)+$(ENABLE_HEADLESS_ONLY), true+true) + LIBJAWT_CFLAGS += -DHEADLESS +endif ifeq ($(call isTargetOs, windows)+$(call isTargetCpu, x86), true+true) LIBJAWT_LIBS_windows := kernel32.lib From 38d7614c1f8cba8eb90d8792a829d0eb8c2e2f42 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 13 Apr 2026 16:48:42 +0000 Subject: [PATCH 118/168] 8379515: draft-ietf-lamps-kyber-certificates is now RFC 9935 Backport-of: 375f8216271b370740658c40e826ae47e13a07fb --- .../share/classes/sun/security/util/KeyChoices.java | 4 ++-- src/java.base/share/conf/security/java.security | 2 +- .../security/provider/pqc/PrivateKeyEncodings.java | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/java.base/share/classes/sun/security/util/KeyChoices.java b/src/java.base/share/classes/sun/security/util/KeyChoices.java index da3c611750e2..00c4463d0987 100644 --- a/src/java.base/share/classes/sun/security/util/KeyChoices.java +++ b/src/java.base/share/classes/sun/security/util/KeyChoices.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -45,7 +45,7 @@ * * This class supports reading, writing, and converting between them. *

    - * Current code follows draft-ietf-lamps-kyber-certificates-11 and RFC 9881. + * Current code follows RFC 9935 and RFC 9881. */ public final class KeyChoices { diff --git a/src/java.base/share/conf/security/java.security b/src/java.base/share/conf/security/java.security index dc392ba0af57..1c20fb53b067 100644 --- a/src/java.base/share/conf/security/java.security +++ b/src/java.base/share/conf/security/java.security @@ -1675,7 +1675,7 @@ com.sun.security.allowedAIALocations= # # PKCS #8 encoding format for newly created ML-KEM and ML-DSA private keys # -# draft-ietf-lamps-kyber-certificates-11 and RFC 9881 define three possible formats for a private key: +# RFC 9935 and RFC 9881 define three possible formats for a private key: # a seed (64 bytes for ML-KEM, 32 bytes for ML-DSA), an expanded private key, # or a sequence containing both. # diff --git a/test/jdk/sun/security/provider/pqc/PrivateKeyEncodings.java b/test/jdk/sun/security/provider/pqc/PrivateKeyEncodings.java index 25060d0b74e9..39183eb9881d 100644 --- a/test/jdk/sun/security/provider/pqc/PrivateKeyEncodings.java +++ b/test/jdk/sun/security/provider/pqc/PrivateKeyEncodings.java @@ -26,7 +26,7 @@ * @bug 8347938 * @library /test/lib * @summary ensure ML-KEM and ML-DSA encodings consistent with - * draft-ietf-lamps-kyber-certificates-11 and RFC 9881 + * RFC 9935 and RFC 9881 * @modules java.base/com.sun.crypto.provider * java.base/sun.security.pkcs * java.base/sun.security.provider @@ -62,17 +62,17 @@ public class PrivateKeyEncodings { public static void main(String[] args) throws Exception { - // Example keys and certificates draft-ietf-lamps-kyber-certificates-11, Appendix B - // (https://datatracker.ietf.org/doc/html/draft-ietf-lamps-kyber-certificates-11#autoid-17) - // and RFC 9881, Appendix C.3 - // (https://datatracker.ietf.org/doc/html/rfc9881#name-example-certificates) + // Example keys and certificates in RFC 9935, Appendix C + // (https://datatracker.ietf.org/doc/html/rfc9935#name-examples) + // and RFC 9881, Appendix C + // (https://datatracker.ietf.org/doc/html/rfc9881#name-examples) // // These data can be retrieved from the following GitHub releases: // https://github.com/lamps-wg/kyber-certificates/releases/tag/draft-ietf-lamps-kyber-certificates-11 // https://github.com/lamps-wg/dilithium-certificates/releases/tag/draft-ietf-lamps-dilithium-certificates-13 // // Although the release tags include "draft", these values are the - // same as those in the final RFC 9881. + // same as those in the final RFCs. try (var kemReader = RepositoryFileReader.of(RepositoryFileReader.KYBER_CERTIFICATES.class, "kyber-certificates-draft-ietf-lamps-kyber-certificates-11/"); var dsaReader = RepositoryFileReader.of(RepositoryFileReader.DILITHIUM_CERTIFICATES.class, From 204f732aa5c4915249402eb29b406efcb9d0626f Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 13 Apr 2026 16:49:36 +0000 Subject: [PATCH 119/168] 8221451: PIT: sun/java2d/X11SurfaceData/SharedMemoryPixmapsTest/SharedMemoryPixmapsTest.sh fails 7184899: Test sun/java2d/X11SurfaceData/SharedMemoryPixmapsTest/SharedMemoryPixmapsTest.sh fail Backport-of: f489598d43e786aabcf0e26e9f9b9a840c699654 --- test/jdk/ProblemList.txt | 1 - .../SharedMemoryPixmapsTest.java | 106 ++++++++---------- .../SharedMemoryPixmapsTest.sh | 3 +- 3 files changed, 47 insertions(+), 63 deletions(-) diff --git a/test/jdk/ProblemList.txt b/test/jdk/ProblemList.txt index 89c9048babbf..5f3dfa2c9fb9 100644 --- a/test/jdk/ProblemList.txt +++ b/test/jdk/ProblemList.txt @@ -257,7 +257,6 @@ sun/java2d/SunGraphics2D/PolyVertTest.java 6986565 generic-all sun/java2d/SunGraphics2D/SimplePrimQuality.java 6992007 generic-all sun/java2d/SunGraphics2D/SourceClippingBlitTest/SourceClippingBlitTest.java 8196185 generic-all -sun/java2d/X11SurfaceData/SharedMemoryPixmapsTest/SharedMemoryPixmapsTest.sh 7184899,8221451 linux-all,macosx-aarch64 java/awt/FullScreen/DisplayChangeVITest/DisplayChangeVITest.java 8169469,8273617 windows-all,macosx-aarch64 java/awt/FullScreen/UninitializedDisplayModeChangeTest/UninitializedDisplayModeChangeTest.java 8273617 macosx-all java/awt/print/PrinterJob/PSQuestionMark.java 7003378 generic-all diff --git a/test/jdk/sun/java2d/X11SurfaceData/SharedMemoryPixmapsTest/SharedMemoryPixmapsTest.java b/test/jdk/sun/java2d/X11SurfaceData/SharedMemoryPixmapsTest/SharedMemoryPixmapsTest.java index efa938036706..9b109e954ebc 100644 --- a/test/jdk/sun/java2d/X11SurfaceData/SharedMemoryPixmapsTest/SharedMemoryPixmapsTest.java +++ b/test/jdk/sun/java2d/X11SurfaceData/SharedMemoryPixmapsTest/SharedMemoryPixmapsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -21,10 +21,10 @@ * questions. */ -import java.awt.AWTException; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; +import java.awt.EventQueue; import java.awt.Frame; import java.awt.Graphics; import java.awt.Point; @@ -42,18 +42,18 @@ * make sure the pixels on the screen are red. * * Note that we force the use of shared memory pixmaps in the shell script. - * - * @author Dmitri.Trembovetski */ public class SharedMemoryPixmapsTest { - static final int IMAGE_SIZE = 100; - static boolean show = false; - final Frame testFrame; - /** Creates a new instance of SharedMemoryPixmapsTest */ - public SharedMemoryPixmapsTest() { + static final int IMAGE_SIZE = 200; + static volatile boolean show = false; + static volatile Frame testFrame; + static volatile TestComponent testComponent; + + static void createUI() { testFrame = new Frame("SharedMemoryPixmapsTest"); - testFrame.add(new TestComponent()); + testComponent = new TestComponent(); + testFrame.add(testComponent); testFrame.setUndecorated(true); testFrame.setResizable(false); testFrame.pack(); @@ -62,7 +62,7 @@ public SharedMemoryPixmapsTest() { testFrame.toFront(); } - public static void main(String[] args) { + public static void main(String[] args) throws Exception { for (String s : args) { if ("-show".equals(s)) { show = true; @@ -70,12 +70,43 @@ public static void main(String[] args) { System.err.println("Usage: SharedMemoryPixmapsTest [-show]"); } } - new SharedMemoryPixmapsTest(); + EventQueue.invokeAndWait(SharedMemoryPixmapsTest::createUI); + if (testRendering()) { + System.err.println("Test Passed"); + } else { + System.err.println("Test Failed"); + } + if (!show && testFrame != null) { + EventQueue.invokeAndWait(testFrame::dispose); + } + } + + static boolean testRendering() throws Exception { + Robot r = new Robot(); + r.waitForIdle(); + r.delay(2000); + Point p = testComponent.getLocationOnScreen(); + BufferedImage b = + r.createScreenCapture(new Rectangle(p, testComponent.getPreferredSize())); + for (int y = 20; y < b.getHeight() - 40; y++) { + for (int x = 20; x < b.getWidth() - 40; x++) { + if (b.getRGB(x, y) != Color.red.getRGB()) { + System.err.println("Incorrect pixel at " + + x + "x" + y + " : " + + Integer.toHexString(b.getRGB(x, y))); + if (show) { + return false; + } + System.err.println("Test Failed"); + System.exit(1); + } + } + } + return true; } - private class TestComponent extends Component { + static class TestComponent extends Component { VolatileImage vi = null; - boolean tested = false; void initVI() { int res; @@ -104,54 +135,10 @@ public synchronized void paint(Graphics g) { g.setColor(Color.green); g.fillRect(0, 0, getWidth(), getHeight()); + vi = null; initVI(); g.drawImage(vi, 0, 0, null); } while (vi.contentsLost()); - - Toolkit.getDefaultToolkit().sync(); - if (!tested) { - if (testRendering()) { - System.err.println("Test Passed"); - } else { - System.err.println("Test Failed"); - } - tested = true; - } - if (!show) { - testFrame.setVisible(false); - testFrame.dispose(); - } - } - - private boolean testRendering() throws RuntimeException { - try { - Thread.sleep(2000); - } catch (InterruptedException ex) {} - Robot r = null; - try { - r = new Robot(); - } catch (AWTException ex) { - ex.printStackTrace(); - throw new RuntimeException("Can't create Robot"); - } - Point p = getLocationOnScreen(); - BufferedImage b = - r.createScreenCapture(new Rectangle(p, getPreferredSize())); - for (int y = 0; y < b.getHeight(); y++) { - for (int x = 0; x < b.getWidth(); x++) { - if (b.getRGB(x, y) != Color.red.getRGB()) { - System.err.println("Incorrect pixel" + " at " - + x + "x" + y + " : " + - Integer.toHexString(b.getRGB(x, y))); - if (show) { - return false; - } - System.err.println("Test Failed"); - System.exit(1); - } - } - } - return true; } @Override @@ -159,5 +146,4 @@ public Dimension getPreferredSize() { return new Dimension(IMAGE_SIZE, IMAGE_SIZE); } } - } diff --git a/test/jdk/sun/java2d/X11SurfaceData/SharedMemoryPixmapsTest/SharedMemoryPixmapsTest.sh b/test/jdk/sun/java2d/X11SurfaceData/SharedMemoryPixmapsTest/SharedMemoryPixmapsTest.sh index 10dd58d90de8..786957ca0ebc 100644 --- a/test/jdk/sun/java2d/X11SurfaceData/SharedMemoryPixmapsTest/SharedMemoryPixmapsTest.sh +++ b/test/jdk/sun/java2d/X11SurfaceData/SharedMemoryPixmapsTest/SharedMemoryPixmapsTest.sh @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (c) 2005, 2008, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -29,7 +29,6 @@ # by filling a VolatileImage with red color and copying it # to the screen. # Note that we force the use of shared memory pixmaps. -# @author Dmitri.Trembovetski echo "TESTJAVA=${TESTJAVA}" echo "TESTSRC=${TESTSRC}" From 31a140ae8d944382884d577ee8c5c97217f1807d Mon Sep 17 00:00:00 2001 From: Andrew John Hughes Date: Wed, 15 Apr 2026 09:16:33 +0000 Subject: [PATCH 120/168] 8380947: Add pull request template Backport-of: df09910ec879dc628484588a3137298504fceaf1 --- .github/pull_request_template.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000000..d3f63784ecec --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,5 @@ + + + +--------- +- [ ] I confirm that I make this contribution in accordance with the [OpenJDK Interim AI Policy](https://openjdk.org/legal/ai). From f7b5db1a5be2d8a56bb88c7df0451709ab68de49 Mon Sep 17 00:00:00 2001 From: Goetz Lindenmaier Date: Wed, 15 Apr 2026 10:07:18 +0000 Subject: [PATCH 121/168] 8365623: test/jdk/sun/security/pkcs11/tls/ tests skipped without skip exception Reviewed-by: mdoerr Backport-of: da8e41a368bd98a7a35f5706302ecb9475b58363 --- test/jdk/sun/security/pkcs11/tls/TestKeyMaterial.java | 7 +++---- .../security/pkcs11/tls/TestKeyMaterialChaCha20.java | 11 +++++------ .../jdk/sun/security/pkcs11/tls/TestMasterSecret.java | 7 ++++--- test/jdk/sun/security/pkcs11/tls/TestPRF.java | 7 ++++--- test/jdk/sun/security/pkcs11/tls/TestPremaster.java | 10 +++++----- .../sun/security/pkcs11/tls/tls12/FipsModeTLS12.java | 10 +++++----- 6 files changed, 26 insertions(+), 26 deletions(-) diff --git a/test/jdk/sun/security/pkcs11/tls/TestKeyMaterial.java b/test/jdk/sun/security/pkcs11/tls/TestKeyMaterial.java index 977ae5338bdc..682c09bab412 100644 --- a/test/jdk/sun/security/pkcs11/tls/TestKeyMaterial.java +++ b/test/jdk/sun/security/pkcs11/tls/TestKeyMaterial.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -35,7 +35,6 @@ import java.io.BufferedReader; import java.nio.file.Files; import java.nio.file.Paths; -import java.security.InvalidAlgorithmParameterException; import java.security.Provider; import java.security.ProviderException; import java.util.Arrays; @@ -45,6 +44,7 @@ import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; +import jtreg.SkippedException; import sun.security.internal.spec.TlsKeyMaterialParameterSpec; import sun.security.internal.spec.TlsKeyMaterialSpec; @@ -60,8 +60,7 @@ public static void main(String[] args) throws Exception { @Override public void main(Provider provider) throws Exception { if (provider.getService("KeyGenerator", "SunTlsKeyMaterial") == null) { - System.out.println("Provider does not support algorithm, skipping"); - return; + throw new SkippedException("Provider does not support algorithm, skipping"); } try (BufferedReader reader = Files.newBufferedReader( diff --git a/test/jdk/sun/security/pkcs11/tls/TestKeyMaterialChaCha20.java b/test/jdk/sun/security/pkcs11/tls/TestKeyMaterialChaCha20.java index 51471fca65ae..b784a3127c62 100644 --- a/test/jdk/sun/security/pkcs11/tls/TestKeyMaterialChaCha20.java +++ b/test/jdk/sun/security/pkcs11/tls/TestKeyMaterialChaCha20.java @@ -35,6 +35,8 @@ import javax.crypto.SecretKey; import java.security.Provider; import java.security.NoSuchAlgorithmException; + +import jtreg.SkippedException; import sun.security.internal.spec.TlsRsaPremasterSecretParameterSpec; import sun.security.internal.spec.TlsMasterSecretParameterSpec; import sun.security.internal.spec.TlsKeyMaterialParameterSpec; @@ -52,20 +54,17 @@ public void main(Provider provider) throws Exception { try { kg1 = KeyGenerator.getInstance("SunTlsRsaPremasterSecret", provider); } catch (Exception e) { - System.out.println("Skipping, SunTlsRsaPremasterSecret KeyGenerator not supported"); - return; + throw new SkippedException("Skipping, SunTlsRsaPremasterSecret KeyGenerator not supported"); } try { kg2 = KeyGenerator.getInstance("SunTls12MasterSecret", provider); } catch (Exception e) { - System.out.println("Skipping, SunTls12MasterSecret KeyGenerator not supported"); - return; + throw new SkippedException("Skipping, SunTls12MasterSecret KeyGenerator not supported"); } try { kg3 = KeyGenerator.getInstance("SunTls12KeyMaterial", provider); } catch (Exception e) { - System.out.println("Skipping, SunTls12KeyMaterial KeyGenerator not supported"); - return; + throw new SkippedException("Skipping, SunTls12KeyMaterial KeyGenerator not supported"); } kg1.init(new TlsRsaPremasterSecretParameterSpec(0x0303, 0x0303)); diff --git a/test/jdk/sun/security/pkcs11/tls/TestMasterSecret.java b/test/jdk/sun/security/pkcs11/tls/TestMasterSecret.java index 52e2327cfc91..f969aece532d 100644 --- a/test/jdk/sun/security/pkcs11/tls/TestMasterSecret.java +++ b/test/jdk/sun/security/pkcs11/tls/TestMasterSecret.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -42,6 +42,8 @@ import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; + +import jtreg.SkippedException; import sun.security.internal.interfaces.TlsMasterSecret; import sun.security.internal.spec.TlsMasterSecretParameterSpec; @@ -56,8 +58,7 @@ public static void main(String[] args) throws Exception { @Override public void main(Provider provider) throws Exception { if (provider.getService("KeyGenerator", "SunTlsMasterSecret") == null) { - System.out.println("Not supported by provider, skipping"); - return; + throw new SkippedException("Not supported by provider, skipping"); } try (BufferedReader reader = Files.newBufferedReader( diff --git a/test/jdk/sun/security/pkcs11/tls/TestPRF.java b/test/jdk/sun/security/pkcs11/tls/TestPRF.java index ada1716c98b5..662383095a9d 100644 --- a/test/jdk/sun/security/pkcs11/tls/TestPRF.java +++ b/test/jdk/sun/security/pkcs11/tls/TestPRF.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -40,6 +40,8 @@ import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; + +import jtreg.SkippedException; import sun.security.internal.spec.TlsPrfParameterSpec; public class TestPRF extends PKCS11Test { @@ -53,8 +55,7 @@ public static void main(String[] args) throws Exception { @Override public void main(Provider provider) throws Exception { if (provider.getService("KeyGenerator", "SunTlsPrf") == null) { - System.out.println("Provider does not support algorithm, skipping"); - return; + throw new SkippedException("Provider does not support algorithm, skipping"); } try (BufferedReader reader = Files.newBufferedReader( diff --git a/test/jdk/sun/security/pkcs11/tls/TestPremaster.java b/test/jdk/sun/security/pkcs11/tls/TestPremaster.java index 9ee1dddb8e58..d34330fa073a 100644 --- a/test/jdk/sun/security/pkcs11/tls/TestPremaster.java +++ b/test/jdk/sun/security/pkcs11/tls/TestPremaster.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -36,6 +36,8 @@ import java.security.InvalidAlgorithmParameterException; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; + +import jtreg.SkippedException; import sun.security.internal.spec.TlsRsaPremasterSecretParameterSpec; public class TestPremaster extends PKCS11Test { @@ -48,8 +50,7 @@ public static void main(String[] args) throws Exception { public void main(Provider provider) throws Exception { if (provider.getService( "KeyGenerator", "SunTlsRsaPremasterSecret") == null) { - System.out.println("Not supported by provider, skipping"); - return; + throw new SkippedException("Not supported by provider, skipping"); } KeyGenerator kg; kg = KeyGenerator.getInstance("SunTlsRsaPremasterSecret", provider); @@ -87,8 +88,7 @@ private static void test(KeyGenerator kg, } catch (InvalidAlgorithmParameterException iape) { // S12 removed support for SSL v3.0 if (clientVersion == 0x300 || serverVersion == 0x300) { - System.out.println("Skip testing SSLv3 due to no support"); - return; + throw new SkippedException("Skip testing SSLv3 due to no support"); } // unexpected, pass it up throw iape; diff --git a/test/jdk/sun/security/pkcs11/tls/tls12/FipsModeTLS12.java b/test/jdk/sun/security/pkcs11/tls/tls12/FipsModeTLS12.java index 6abee6c4685c..fd5c99dd700d 100644 --- a/test/jdk/sun/security/pkcs11/tls/tls12/FipsModeTLS12.java +++ b/test/jdk/sun/security/pkcs11/tls/tls12/FipsModeTLS12.java @@ -65,6 +65,7 @@ import javax.net.ssl.TrustManagerFactory; import jdk.test.lib.security.SecurityUtils; +import jtreg.SkippedException; import sun.security.internal.spec.TlsMasterSecretParameterSpec; import sun.security.internal.spec.TlsPrfParameterSpec; import sun.security.internal.spec.TlsRsaPremasterSecretParameterSpec; @@ -88,12 +89,11 @@ public static void main(String[] args) throws Exception { try { initialize(); } catch (Exception e) { - System.out.println("Test skipped: failure during" + - " initialization"); if (enableDebug) { System.out.println(e); } - return; + throw new SkippedException("Test skipped: failure during" + + " initialization"); } if (shouldRun()) { @@ -105,8 +105,8 @@ public static void main(String[] args) throws Exception { System.out.println("Test PASS - OK"); } else { - System.out.println("Test skipped: TLS 1.2 mechanisms" + - " not supported by current SunPKCS11 back-end"); + throw new SkippedException("Test skipped: TLS 1.2 mechanisms" + + " not supported by current SunPKCS11 back-end"); } } From 445f04b504d2516631d911816bef48742d5d492d Mon Sep 17 00:00:00 2001 From: Goetz Lindenmaier Date: Wed, 15 Apr 2026 10:10:09 +0000 Subject: [PATCH 122/168] 8380663: Update jcmd man page to include AOT.end_recording diagnostic command Backport-of: 28529282545f6b59596a445409d59398253176f1 --- src/jdk.jcmd/share/man/jcmd.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/jdk.jcmd/share/man/jcmd.md b/src/jdk.jcmd/share/man/jcmd.md index 4e67e7a45023..a25161444273 100644 --- a/src/jdk.jcmd/share/man/jcmd.md +++ b/src/jdk.jcmd/share/man/jcmd.md @@ -134,6 +134,16 @@ The following commands are available: - `-all`: (Optional) Show help for all commands (BOOLEAN, false) . +`AOT.end_recording` +: Ends an in-progress AOT training and records the results to the file(s) specified by `-XX:AOTConfiguration` and/or `-XX:AOTCacheOutput`. + + Impact: Low + + **Note:** + + The JVM must be started in AOT training mode using command-line arguments such as `-XX:AOTMode=record` or `-XX:AOTCacheOutput=`. + The results of the AOT training can be an AOT configuration file, an AOT cache file, or both. + `Compiler.CodeHeap_Analytics` \[*function*\] \[*granularity*\] : Print CodeHeap analytics From dbbdc1dffd10608195487fa139e77c4a90c80658 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Van=C4=9Bk?= Date: Wed, 15 Apr 2026 12:54:49 +0000 Subject: [PATCH 123/168] 8375294: (fs) Files.copy can fail with EOPNOTSUPP when copy_file_range not supported Reviewed-by: andrew Backport-of: 30cda00010888b6e9a2bf8cdeaedbb3eb4b6a222 --- src/java.base/linux/native/libnio/ch/FileDispatcherImpl.c | 5 +++-- src/java.base/linux/native/libnio/fs/LinuxNativeDispatcher.c | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/java.base/linux/native/libnio/ch/FileDispatcherImpl.c b/src/java.base/linux/native/libnio/ch/FileDispatcherImpl.c index efbd0ca56847..54d640b03a44 100644 --- a/src/java.base/linux/native/libnio/ch/FileDispatcherImpl.c +++ b/src/java.base/linux/native/libnio/ch/FileDispatcherImpl.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -63,7 +63,7 @@ Java_sun_nio_ch_FileDispatcherImpl_transferFrom0(JNIEnv *env, jobject this, if (n < 0) { if (errno == EAGAIN) return IOS_UNAVAILABLE; - if (errno == ENOSYS) + if (errno == ENOSYS || errno == EOPNOTSUPP) return IOS_UNSUPPORTED_CASE; if ((errno == EBADF || errno == EINVAL || errno == EXDEV) && ((ssize_t)count >= 0)) @@ -103,6 +103,7 @@ Java_sun_nio_ch_FileDispatcherImpl_transferTo0(JNIEnv *env, jobject this, case EINVAL: case ENOSYS: case EXDEV: + case EOPNOTSUPP: // ignore and try sendfile() break; default: diff --git a/src/java.base/linux/native/libnio/fs/LinuxNativeDispatcher.c b/src/java.base/linux/native/libnio/fs/LinuxNativeDispatcher.c index c90e99dda07b..4677411b0bad 100644 --- a/src/java.base/linux/native/libnio/fs/LinuxNativeDispatcher.c +++ b/src/java.base/linux/native/libnio/fs/LinuxNativeDispatcher.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -193,6 +193,7 @@ Java_sun_nio_fs_LinuxNativeDispatcher_directCopy0 case EINVAL: case ENOSYS: case EXDEV: + case EOPNOTSUPP: // ignore and try sendfile() break; default: From e96e28db1a1ce2d3a57aa2a9bfcef9de60929565 Mon Sep 17 00:00:00 2001 From: Goetz Lindenmaier Date: Wed, 15 Apr 2026 19:02:45 +0000 Subject: [PATCH 124/168] 8365057: Add support for java.util.concurrent lock information to Thread.dump_to_file Backport-of: cedc0117ac36243cc240e8ab6adb3c78af4055fc --- src/hotspot/share/services/threadService.cpp | 23 +++++++++++++++---- .../classes/jdk/internal/vm/ThreadDumper.java | 8 ++++++- .../jdk/internal/vm/ThreadSnapshot.java | 20 ++++++++++++---- .../doc-files/threadDump.schema.json | 8 +++++-- .../HotSpotDiagnosticMXBean/DumpThreads.java | 22 ++++++++++++++++-- .../jdk/test/lib/threaddump/ThreadDump.java | 10 ++++++++ 6 files changed, 78 insertions(+), 13 deletions(-) diff --git a/src/hotspot/share/services/threadService.cpp b/src/hotspot/share/services/threadService.cpp index 57c5f330bfcb..e7a934787f49 100644 --- a/src/hotspot/share/services/threadService.cpp +++ b/src/hotspot/share/services/threadService.cpp @@ -1161,9 +1161,11 @@ class GetThreadSnapshotClosure: public HandshakeClosure { Type _type; // park blocker or an object the thread waiting on/trying to lock OopHandle _obj; + // thread that owns park blocker object when park blocker is AbstractOwnableSynchronizer + OopHandle _owner; - Blocker(Type type, OopHandle obj): _type(type), _obj(obj) {} - Blocker(): _type(NOTHING), _obj(nullptr) {} + Blocker(Type type, OopHandle obj): _type(type), _obj(obj), _owner() {} + Blocker(): _type(NOTHING), _obj(), _owner() {} bool is_empty() const { return _type == NOTHING; @@ -1198,6 +1200,7 @@ class GetThreadSnapshotClosure: public HandshakeClosure { delete _locks; } _blocker._obj.release(oop_storage()); + _blocker._owner.release(oop_storage()); } private: @@ -1299,6 +1302,13 @@ class GetThreadSnapshotClosure: public HandshakeClosure { oop park_blocker = java_lang_Thread::park_blocker(_thread_h()); if (park_blocker != nullptr) { _blocker = Blocker(Blocker::PARK_BLOCKER, OopHandle(oop_storage(), park_blocker)); + if (park_blocker->is_a(vmClasses::java_util_concurrent_locks_AbstractOwnableSynchronizer_klass())) { + // could be stale (unlikely in practice), but it's good enough to see deadlocks + oop ownerObj = java_util_concurrent_locks_AbstractOwnableSynchronizer::get_owner_threadObj(park_blocker); + if (ownerObj != nullptr) { + _blocker._owner = OopHandle(oop_storage(), ownerObj); + } + } } ResourceMark rm(current); @@ -1380,6 +1390,7 @@ class jdk_internal_vm_ThreadSnapshot: AllStatic { static int _locks_offset; static int _blockerTypeOrdinal_offset; static int _blockerObject_offset; + static int _parkBlockerOwner_offset; static void compute_offsets(InstanceKlass* klass, TRAPS) { JavaClasses::compute_offset(_name_offset, klass, "name", vmSymbols::string_signature(), false); @@ -1389,6 +1400,7 @@ class jdk_internal_vm_ThreadSnapshot: AllStatic { JavaClasses::compute_offset(_locks_offset, klass, "locks", vmSymbols::jdk_internal_vm_ThreadLock_array(), false); JavaClasses::compute_offset(_blockerTypeOrdinal_offset, klass, "blockerTypeOrdinal", vmSymbols::int_signature(), false); JavaClasses::compute_offset(_blockerObject_offset, klass, "blockerObject", vmSymbols::object_signature(), false); + JavaClasses::compute_offset(_parkBlockerOwner_offset, klass, "parkBlockerOwner", vmSymbols::thread_signature(), false); } public: static void init(InstanceKlass* klass, TRAPS) { @@ -1419,9 +1431,10 @@ class jdk_internal_vm_ThreadSnapshot: AllStatic { static void set_locks(oop snapshot, oop locks) { snapshot->obj_field_put(_locks_offset, locks); } - static void set_blocker(oop snapshot, int type_ordinal, oop lock) { + static void set_blocker(oop snapshot, int type_ordinal, oop lock, oop owner) { snapshot->int_field_put(_blockerTypeOrdinal_offset, type_ordinal); snapshot->obj_field_put(_blockerObject_offset, lock); + snapshot->obj_field_put(_parkBlockerOwner_offset, owner); } }; @@ -1433,6 +1446,7 @@ int jdk_internal_vm_ThreadSnapshot::_stackTrace_offset; int jdk_internal_vm_ThreadSnapshot::_locks_offset; int jdk_internal_vm_ThreadSnapshot::_blockerTypeOrdinal_offset; int jdk_internal_vm_ThreadSnapshot::_blockerObject_offset; +int jdk_internal_vm_ThreadSnapshot::_parkBlockerOwner_offset; oop ThreadSnapshotFactory::get_thread_snapshot(jobject jthread, TRAPS) { ThreadsListHandle tlh(THREAD); @@ -1548,7 +1562,8 @@ oop ThreadSnapshotFactory::get_thread_snapshot(jobject jthread, TRAPS) { jdk_internal_vm_ThreadSnapshot::set_stack_trace(snapshot(), trace()); jdk_internal_vm_ThreadSnapshot::set_locks(snapshot(), locks()); if (!cl._blocker.is_empty()) { - jdk_internal_vm_ThreadSnapshot::set_blocker(snapshot(), cl._blocker._type, cl._blocker._obj.resolve()); + jdk_internal_vm_ThreadSnapshot::set_blocker(snapshot(), + cl._blocker._type, cl._blocker._obj.resolve(), cl._blocker._owner.resolve()); } return snapshot(); } diff --git a/src/java.base/share/classes/jdk/internal/vm/ThreadDumper.java b/src/java.base/share/classes/jdk/internal/vm/ThreadDumper.java index a26003a3afbe..276c379a5646 100644 --- a/src/java.base/share/classes/jdk/internal/vm/ThreadDumper.java +++ b/src/java.base/share/classes/jdk/internal/vm/ThreadDumper.java @@ -205,7 +205,10 @@ private static boolean dumpThread(Thread thread, TextWriter writer) { // park blocker Object parkBlocker = snapshot.parkBlocker(); if (parkBlocker != null) { - writer.println(" - parking to wait for " + decorateObject(parkBlocker)); + String suffix = (snapshot.parkBlockerOwner() instanceof Thread owner) + ? ", owner #" + owner.threadId() + : ""; + writer.println(" - parking to wait for " + decorateObject(parkBlocker) + suffix); } // blocked on monitor enter or Object.wait @@ -335,6 +338,9 @@ private static boolean dumpThread(Thread thread, JsonWriter jsonWriter) { // parkBlocker is an object to allow for exclusiveOwnerThread in the future jsonWriter.startObject("parkBlocker"); jsonWriter.writeProperty("object", Objects.toIdentityString(parkBlocker)); + if (snapshot.parkBlockerOwner() instanceof Thread owner) { + jsonWriter.writeProperty("owner", owner.threadId()); + } jsonWriter.endObject(); } diff --git a/src/java.base/share/classes/jdk/internal/vm/ThreadSnapshot.java b/src/java.base/share/classes/jdk/internal/vm/ThreadSnapshot.java index 4fcbaf24d2e6..357d38008d15 100644 --- a/src/java.base/share/classes/jdk/internal/vm/ThreadSnapshot.java +++ b/src/java.base/share/classes/jdk/internal/vm/ThreadSnapshot.java @@ -44,6 +44,8 @@ class ThreadSnapshot { // an object the thread is blocked/waiting on, converted to ThreadBlocker by ThreadSnapshot.of() private int blockerTypeOrdinal; private Object blockerObject; + // the owner of the blockerObject when the object is park blocker and is AbstractOwnableSynchronizer + private Thread parkBlockerOwner; // set by ThreadSnapshot.of() private ThreadBlocker blocker; @@ -70,8 +72,11 @@ static ThreadSnapshot of(Thread thread) { snapshot.locks = EMPTY_LOCKS; } if (snapshot.blockerObject != null) { - snapshot.blocker = new ThreadBlocker(snapshot.blockerTypeOrdinal, snapshot.blockerObject); + snapshot.blocker = new ThreadBlocker(snapshot.blockerTypeOrdinal, + snapshot.blockerObject, + snapshot.parkBlockerOwner); snapshot.blockerObject = null; // release + snapshot.parkBlockerOwner = null; } return snapshot; } @@ -104,6 +109,13 @@ Object parkBlocker() { return getBlocker(BlockerLockType.PARK_BLOCKER); } + /** + * Returns the owner of the parkBlocker if the parkBlocker is an AbstractOwnableSynchronizer. + */ + Thread parkBlockerOwner() { + return (blocker != null && blocker.type == BlockerLockType.PARK_BLOCKER) ? blocker.owner : null; + } + /** * Returns the object that the thread is blocked on. * @throws IllegalStateException if not in the blocked state @@ -211,11 +223,11 @@ Object lockObject() { } } - private record ThreadBlocker(BlockerLockType type, Object obj) { + private record ThreadBlocker(BlockerLockType type, Object obj, Thread owner) { private static final BlockerLockType[] lockTypeValues = BlockerLockType.values(); // cache - ThreadBlocker(int typeOrdinal, Object obj) { - this(lockTypeValues[typeOrdinal], obj); + ThreadBlocker(int typeOrdinal, Object obj, Thread owner) { + this(lockTypeValues[typeOrdinal], obj, owner); } } diff --git a/src/jdk.management/share/classes/com/sun/management/doc-files/threadDump.schema.json b/src/jdk.management/share/classes/com/sun/management/doc-files/threadDump.schema.json index 57ef5c8b859d..bf52bb3915d2 100644 --- a/src/jdk.management/share/classes/com/sun/management/doc-files/threadDump.schema.json +++ b/src/jdk.management/share/classes/com/sun/management/doc-files/threadDump.schema.json @@ -78,6 +78,10 @@ "description": "The blocker object responsible for the thread parking." } }, + "owner": { + "type": "string", + "description": "The thread identifier of the owner when the parkBlocker is an AbstractOwnableSynchronizer." + } "required": [ "object" ] @@ -115,9 +119,9 @@ "items": { "type": [ "string", - null + "null" ], - "description": "The object for which the monitor is owned by the thread, null if eliminated" + "description": "The object for which the monitor is owned by the thread, null if eliminated." } } }, diff --git a/test/jdk/com/sun/management/HotSpotDiagnosticMXBean/DumpThreads.java b/test/jdk/com/sun/management/HotSpotDiagnosticMXBean/DumpThreads.java index adf643749c7e..77020491c29b 100644 --- a/test/jdk/com/sun/management/HotSpotDiagnosticMXBean/DumpThreads.java +++ b/test/jdk/com/sun/management/HotSpotDiagnosticMXBean/DumpThreads.java @@ -23,7 +23,7 @@ /* * @test - * @bug 8284161 8287008 8309406 8356870 + * @bug 8284161 8287008 8309406 8356870 8365057 * @summary Basic test for com.sun.management.HotSpotDiagnosticMXBean.dumpThreads * @requires vm.continuations * @modules java.base/jdk.internal.vm jdk.management @@ -425,7 +425,9 @@ void testParkedThread(ThreadFactory factory, boolean pinned) throws Exception { ThreadFields fields = findThread(tid, lines); assertNotNull(fields, "thread not found"); assertEquals("WAITING", fields.state()); - assertTrue(contains(lines, "- parking to wait for lines, String text) { .anyMatch(l -> l.contains(text)); } + /** + * Finds the line of a plain text thread dump containing the given text. + */ + private String find(List lines, String text) { + return lines.stream().map(String::trim) + .filter(l -> l.contains(text)) + .findAny() + .orElse(null); + } + /** * Dump threads to a file in plain text format, return the lines in the file. */ diff --git a/test/lib/jdk/test/lib/threaddump/ThreadDump.java b/test/lib/jdk/test/lib/threaddump/ThreadDump.java index f4964a9521f7..ca728e625fc8 100644 --- a/test/lib/jdk/test/lib/threaddump/ThreadDump.java +++ b/test/lib/jdk/test/lib/threaddump/ThreadDump.java @@ -296,6 +296,16 @@ public String parkBlocker() { return getStringProperty("parkBlocker", "object"); } + /** + * Returns the owner of the parkBlocker if the parkBlocker is an AbstractOwnableSynchronizer. + */ + public OptionalLong parkBlockerOwner() { + String owner = getStringProperty("parkBlocker", "owner"); + return (owner != null) + ? OptionalLong.of(Long.parseLong(owner)) + : OptionalLong.empty(); + } + /** * Returns the object that the thread is blocked entering its monitor. */ From 01842310f75aa37ad07b8f7aa7ed53b4ee04365e Mon Sep 17 00:00:00 2001 From: Goetz Lindenmaier Date: Thu, 16 Apr 2026 13:43:19 +0000 Subject: [PATCH 125/168] 8382047: Update Libpng to 1.6.57 Reviewed-by: andrew Backport-of: 20e8ea0e0640bf8b0727cf30ced041a1def9c350 --- src/java.desktop/share/legal/libpng.md | 3 +- .../native/libsplashscreen/libpng/CHANGES | 11 ++++ .../native/libsplashscreen/libpng/README | 2 +- .../share/native/libsplashscreen/libpng/png.c | 4 +- .../share/native/libsplashscreen/libpng/png.h | 14 ++--- .../native/libsplashscreen/libpng/pngconf.h | 2 +- .../libsplashscreen/libpng/pnglibconf.h | 2 +- .../native/libsplashscreen/libpng/pngrtran.c | 28 +++++----- .../native/libsplashscreen/libpng/pngset.c | 54 +++++++++++++++++-- 9 files changed, 89 insertions(+), 31 deletions(-) diff --git a/src/java.desktop/share/legal/libpng.md b/src/java.desktop/share/legal/libpng.md index 034de22bf25f..7783fc7ff032 100644 --- a/src/java.desktop/share/legal/libpng.md +++ b/src/java.desktop/share/legal/libpng.md @@ -1,4 +1,4 @@ -## libpng v1.6.56 +## libpng v1.6.57 ### libpng License

    @@ -180,6 +180,7 @@ Authors, for copyright and licensing purposes.
      * Mans Rullgard
      * Matt Sarett
      * Mike Klein
    + * Mohammad Seet
      * Pascal Massimino
      * Paul Schmidt
      * Petr Simecek
    diff --git a/src/java.desktop/share/native/libsplashscreen/libpng/CHANGES b/src/java.desktop/share/native/libsplashscreen/libpng/CHANGES
    index 673d4d50420f..ba81df0c0e61 100644
    --- a/src/java.desktop/share/native/libsplashscreen/libpng/CHANGES
    +++ b/src/java.desktop/share/native/libsplashscreen/libpng/CHANGES
    @@ -6368,6 +6368,17 @@ Version 1.6.56 [March 25, 2026]
         (Contributed by Bob Friesenhahn and Philippe Antoine.)
       Performed various refactorings and cleanups.
     
    +Version 1.6.57 [April 8, 2026]
    +  Fixed CVE-2026-34757 (medium severity):
    +    Use-after-free in `png_set_PLTE`, `png_set_tRNS` and `png_set_hIST`
    +    leading to corrupted chunk data and potential heap information disclosure.
    +    Also hardened the append-style setters (`png_set_text`, `png_set_sPLT`,
    +    `png_set_unknown_chunks`) against a theoretical variant of the same
    +    aliasing pattern.
    +    (Reported by Iv4n .)
    +  Fixed integer overflow in rowbytes computation in read transforms.
    +    (Contributed by Mohammad Seet.)
    +
     Send comments/corrections/commendations to png-mng-implement at lists.sf.net.
     Subscription is required; visit
     
    diff --git a/src/java.desktop/share/native/libsplashscreen/libpng/README b/src/java.desktop/share/native/libsplashscreen/libpng/README
    index d0b085f79334..179b8dc8cb4d 100644
    --- a/src/java.desktop/share/native/libsplashscreen/libpng/README
    +++ b/src/java.desktop/share/native/libsplashscreen/libpng/README
    @@ -1,4 +1,4 @@
    -README for libpng version 1.6.56
    +README for libpng version 1.6.57
     ================================
     
     See the note about version numbers near the top of `png.h`.
    diff --git a/src/java.desktop/share/native/libsplashscreen/libpng/png.c b/src/java.desktop/share/native/libsplashscreen/libpng/png.c
    index fd095b515b91..e4e13b0a6840 100644
    --- a/src/java.desktop/share/native/libsplashscreen/libpng/png.c
    +++ b/src/java.desktop/share/native/libsplashscreen/libpng/png.c
    @@ -42,7 +42,7 @@
     #include "pngpriv.h"
     
     /* Generate a compiler error if there is an old png.h in the search path. */
    -typedef png_libpng_version_1_6_56 Your_png_h_is_not_version_1_6_56;
    +typedef png_libpng_version_1_6_57 Your_png_h_is_not_version_1_6_57;
     
     /* Sanity check the chunks definitions - PNG_KNOWN_CHUNKS from pngpriv.h and the
      * corresponding macro definitions.  This causes a compile time failure if
    @@ -849,7 +849,7 @@ png_get_copyright(png_const_structrp png_ptr)
        return PNG_STRING_COPYRIGHT
     #else
        return PNG_STRING_NEWLINE \
    -      "libpng version 1.6.56" PNG_STRING_NEWLINE \
    +      "libpng version 1.6.57" PNG_STRING_NEWLINE \
           "Copyright (c) 2018-2026 Cosmin Truta" PNG_STRING_NEWLINE \
           "Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson" \
           PNG_STRING_NEWLINE \
    diff --git a/src/java.desktop/share/native/libsplashscreen/libpng/png.h b/src/java.desktop/share/native/libsplashscreen/libpng/png.h
    index 56ec204cd1a4..349e7d073831 100644
    --- a/src/java.desktop/share/native/libsplashscreen/libpng/png.h
    +++ b/src/java.desktop/share/native/libsplashscreen/libpng/png.h
    @@ -29,7 +29,7 @@
      * However, the following notice accompanied the original version of this
      * file and, per its terms, should not be removed:
      *
    - * libpng version 1.6.56
    + * libpng version 1.6.57
      *
      * Copyright (c) 2018-2026 Cosmin Truta
      * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson
    @@ -43,7 +43,7 @@
      *   libpng versions 0.89, June 1996, through 0.96, May 1997: Andreas Dilger
      *   libpng versions 0.97, January 1998, through 1.6.35, July 2018:
      *     Glenn Randers-Pehrson
    - *   libpng versions 1.6.36, December 2018, through 1.6.56, March 2026:
    + *   libpng versions 1.6.36, December 2018, through 1.6.57, April 2026:
      *     Cosmin Truta
      *   See also "Contributing Authors", below.
      */
    @@ -267,7 +267,7 @@
      *    ...
      *    1.5.30                  15    10530  15.so.15.30[.0]
      *    ...
    - *    1.6.56                  16    10656  16.so.16.56[.0]
    + *    1.6.57                  16    10657  16.so.16.57[.0]
      *
      *    Henceforth the source version will match the shared-library major and
      *    minor numbers; the shared-library major version number will be used for
    @@ -303,7 +303,7 @@
      */
     
     /* Version information for png.h - this should match the version in png.c */
    -#define PNG_LIBPNG_VER_STRING "1.6.56"
    +#define PNG_LIBPNG_VER_STRING "1.6.57"
     #define PNG_HEADER_VERSION_STRING " libpng version " PNG_LIBPNG_VER_STRING "\n"
     
     /* The versions of shared library builds should stay in sync, going forward */
    @@ -314,7 +314,7 @@
     /* These should match the first 3 components of PNG_LIBPNG_VER_STRING: */
     #define PNG_LIBPNG_VER_MAJOR   1
     #define PNG_LIBPNG_VER_MINOR   6
    -#define PNG_LIBPNG_VER_RELEASE 56
    +#define PNG_LIBPNG_VER_RELEASE 57
     
     /* This should be zero for a public release, or non-zero for a
      * development version.
    @@ -345,7 +345,7 @@
      * From version 1.0.1 it is:
      * XXYYZZ, where XX=major, YY=minor, ZZ=release
      */
    -#define PNG_LIBPNG_VER 10656 /* 1.6.56 */
    +#define PNG_LIBPNG_VER 10657 /* 1.6.57 */
     
     /* Library configuration: these options cannot be changed after
      * the library has been built.
    @@ -455,7 +455,7 @@ extern "C" {
     /* This triggers a compiler error in png.c, if png.c and png.h
      * do not agree upon the version number.
      */
    -typedef char *png_libpng_version_1_6_56;
    +typedef char *png_libpng_version_1_6_57;
     
     /* Basic control structions.  Read libpng-manual.txt or libpng.3 for more info.
      *
    diff --git a/src/java.desktop/share/native/libsplashscreen/libpng/pngconf.h b/src/java.desktop/share/native/libsplashscreen/libpng/pngconf.h
    index 5772e6ebb1c8..1a5bb7b60f8a 100644
    --- a/src/java.desktop/share/native/libsplashscreen/libpng/pngconf.h
    +++ b/src/java.desktop/share/native/libsplashscreen/libpng/pngconf.h
    @@ -29,7 +29,7 @@
      * However, the following notice accompanied the original version of this
      * file and, per its terms, should not be removed:
      *
    - * libpng version 1.6.56
    + * libpng version 1.6.57
      *
      * Copyright (c) 2018-2026 Cosmin Truta
      * Copyright (c) 1998-2002,2004,2006-2016,2018 Glenn Randers-Pehrson
    diff --git a/src/java.desktop/share/native/libsplashscreen/libpng/pnglibconf.h b/src/java.desktop/share/native/libsplashscreen/libpng/pnglibconf.h
    index 4a7e51d112dc..de63c9989279 100644
    --- a/src/java.desktop/share/native/libsplashscreen/libpng/pnglibconf.h
    +++ b/src/java.desktop/share/native/libsplashscreen/libpng/pnglibconf.h
    @@ -31,7 +31,7 @@
      * However, the following notice accompanied the original version of this
      * file and, per its terms, should not be removed:
      */
    -/* libpng version 1.6.56 */
    +/* libpng version 1.6.57 */
     
     /* Copyright (c) 2018-2026 Cosmin Truta */
     /* Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson */
    diff --git a/src/java.desktop/share/native/libsplashscreen/libpng/pngrtran.c b/src/java.desktop/share/native/libsplashscreen/libpng/pngrtran.c
    index f0972ba9bef9..838c8460f910 100644
    --- a/src/java.desktop/share/native/libsplashscreen/libpng/pngrtran.c
    +++ b/src/java.desktop/share/native/libsplashscreen/libpng/pngrtran.c
    @@ -2408,7 +2408,7 @@ png_do_unpack(png_row_infop row_info, png_bytep row)
           }
           row_info->bit_depth = 8;
           row_info->pixel_depth = (png_byte)(8 * row_info->channels);
    -      row_info->rowbytes = row_width * row_info->channels;
    +      row_info->rowbytes = (size_t)row_width * row_info->channels;
        }
     }
     #endif
    @@ -2610,7 +2610,7 @@ png_do_scale_16_to_8(png_row_infop row_info, png_bytep row)
     
           row_info->bit_depth = 8;
           row_info->pixel_depth = (png_byte)(8 * row_info->channels);
    -      row_info->rowbytes = row_info->width * row_info->channels;
    +      row_info->rowbytes = (size_t)row_info->width * row_info->channels;
        }
     }
     #endif
    @@ -2638,7 +2638,7 @@ png_do_chop(png_row_infop row_info, png_bytep row)
     
           row_info->bit_depth = 8;
           row_info->pixel_depth = (png_byte)(8 * row_info->channels);
    -      row_info->rowbytes = row_info->width * row_info->channels;
    +      row_info->rowbytes = (size_t)row_info->width * row_info->channels;
        }
     }
     #endif
    @@ -2874,7 +2874,7 @@ png_do_read_filler(png_row_infop row_info, png_bytep row,
                 *(--dp) = lo_filler;
                 row_info->channels = 2;
                 row_info->pixel_depth = 16;
    -            row_info->rowbytes = row_width * 2;
    +            row_info->rowbytes = (size_t)row_width * 2;
              }
     
              else
    @@ -2889,7 +2889,7 @@ png_do_read_filler(png_row_infop row_info, png_bytep row,
                 }
                 row_info->channels = 2;
                 row_info->pixel_depth = 16;
    -            row_info->rowbytes = row_width * 2;
    +            row_info->rowbytes = (size_t)row_width * 2;
              }
           }
     
    @@ -2912,7 +2912,7 @@ png_do_read_filler(png_row_infop row_info, png_bytep row,
                 *(--dp) = hi_filler;
                 row_info->channels = 2;
                 row_info->pixel_depth = 32;
    -            row_info->rowbytes = row_width * 4;
    +            row_info->rowbytes = (size_t)row_width * 4;
              }
     
              else
    @@ -2929,7 +2929,7 @@ png_do_read_filler(png_row_infop row_info, png_bytep row,
                 }
                 row_info->channels = 2;
                 row_info->pixel_depth = 32;
    -            row_info->rowbytes = row_width * 4;
    +            row_info->rowbytes = (size_t)row_width * 4;
              }
           }
     #endif
    @@ -2953,7 +2953,7 @@ png_do_read_filler(png_row_infop row_info, png_bytep row,
                 *(--dp) = lo_filler;
                 row_info->channels = 4;
                 row_info->pixel_depth = 32;
    -            row_info->rowbytes = row_width * 4;
    +            row_info->rowbytes = (size_t)row_width * 4;
              }
     
              else
    @@ -2970,7 +2970,7 @@ png_do_read_filler(png_row_infop row_info, png_bytep row,
                 }
                 row_info->channels = 4;
                 row_info->pixel_depth = 32;
    -            row_info->rowbytes = row_width * 4;
    +            row_info->rowbytes = (size_t)row_width * 4;
              }
           }
     
    @@ -2997,7 +2997,7 @@ png_do_read_filler(png_row_infop row_info, png_bytep row,
                 *(--dp) = hi_filler;
                 row_info->channels = 4;
                 row_info->pixel_depth = 64;
    -            row_info->rowbytes = row_width * 8;
    +            row_info->rowbytes = (size_t)row_width * 8;
              }
     
              else
    @@ -3019,7 +3019,7 @@ png_do_read_filler(png_row_infop row_info, png_bytep row,
     
                 row_info->channels = 4;
                 row_info->pixel_depth = 64;
    -            row_info->rowbytes = row_width * 8;
    +            row_info->rowbytes = (size_t)row_width * 8;
              }
           }
     #endif
    @@ -4513,7 +4513,7 @@ png_do_expand_palette(png_structrp png_ptr, png_row_infop row_info,
                    }
                    row_info->bit_depth = 8;
                    row_info->pixel_depth = 32;
    -               row_info->rowbytes = row_width * 4;
    +               row_info->rowbytes = (size_t)row_width * 4;
                    row_info->color_type = 6;
                    row_info->channels = 4;
                 }
    @@ -4521,7 +4521,7 @@ png_do_expand_palette(png_structrp png_ptr, png_row_infop row_info,
                 else
                 {
                    sp = row + (size_t)row_width - 1;
    -               dp = row + (size_t)(row_width * 3) - 1;
    +               dp = row + (size_t)row_width * 3 - 1;
                    i = 0;
     #ifdef PNG_ARM_NEON_INTRINSICS_AVAILABLE
                    i = png_do_expand_palette_rgb8_neon(png_ptr, row_info, row,
    @@ -4540,7 +4540,7 @@ png_do_expand_palette(png_structrp png_ptr, png_row_infop row_info,
     
                    row_info->bit_depth = 8;
                    row_info->pixel_depth = 24;
    -               row_info->rowbytes = row_width * 3;
    +               row_info->rowbytes = (size_t)row_width * 3;
                    row_info->color_type = 2;
                    row_info->channels = 3;
                 }
    diff --git a/src/java.desktop/share/native/libsplashscreen/libpng/pngset.c b/src/java.desktop/share/native/libsplashscreen/libpng/pngset.c
    index 05d18cd06b74..29082a6be089 100644
    --- a/src/java.desktop/share/native/libsplashscreen/libpng/pngset.c
    +++ b/src/java.desktop/share/native/libsplashscreen/libpng/pngset.c
    @@ -414,6 +414,7 @@ void PNGAPI
     png_set_hIST(png_const_structrp png_ptr, png_inforp info_ptr,
         png_const_uint_16p hist)
     {
    +   png_uint_16 safe_hist[PNG_MAX_PALETTE_LENGTH];
        int i;
     
        png_debug1(1, "in %s storage function", "hIST");
    @@ -430,6 +431,13 @@ png_set_hIST(png_const_structrp png_ptr, png_inforp info_ptr,
           return;
        }
     
    +   /* Snapshot the caller's hist before freeing, in case it points to
    +    * info_ptr->hist (getter-to-setter aliasing).
    +    */
    +   memcpy(safe_hist, hist, (unsigned int)info_ptr->num_palette *
    +       (sizeof (png_uint_16)));
    +   hist = safe_hist;
    +
        png_free_data(png_ptr, info_ptr, PNG_FREE_HIST, 0);
     
        /* Changed from info->num_palette to PNG_MAX_PALETTE_LENGTH in
    @@ -771,7 +779,7 @@ void PNGAPI
     png_set_PLTE(png_structrp png_ptr, png_inforp info_ptr,
         png_const_colorp palette, int num_palette)
     {
    -
    +   png_color safe_palette[PNG_MAX_PALETTE_LENGTH];
        png_uint_32 max_palette_length;
     
        png_debug1(1, "in %s storage function", "PLTE");
    @@ -805,6 +813,15 @@ png_set_PLTE(png_structrp png_ptr, png_inforp info_ptr,
           png_error(png_ptr, "Invalid palette");
        }
     
    +   /* Snapshot the caller's palette before freeing, in case it points to
    +    * info_ptr->palette (getter-to-setter aliasing).
    +    */
    +   if (num_palette > 0)
    +      memcpy(safe_palette, palette, (unsigned int)num_palette *
    +          (sizeof (png_color)));
    +
    +   palette = safe_palette;
    +
        png_free_data(png_ptr, info_ptr, PNG_FREE_PLTE, 0);
     
        /* Changed in libpng-1.2.1 to allocate PNG_MAX_PALETTE_LENGTH instead
    @@ -966,6 +983,7 @@ png_set_text_2(png_const_structrp png_ptr, png_inforp info_ptr,
         png_const_textp text_ptr, int num_text)
     {
        int i;
    +   png_textp old_text = NULL;
     
        png_debug1(1, "in text storage function, chunk typeid = 0x%lx",
           png_ptr == NULL ? 0xabadca11UL : (unsigned long)png_ptr->chunk_name);
    @@ -1013,7 +1031,10 @@ png_set_text_2(png_const_structrp png_ptr, png_inforp info_ptr,
              return 1;
           }
     
    -      png_free(png_ptr, info_ptr->text);
    +      /* Defer freeing the old array until after the copy loop below,
    +       * in case text_ptr aliases info_ptr->text (getter-to-setter).
    +       */
    +      old_text = info_ptr->text;
     
           info_ptr->text = new_text;
           info_ptr->free_me |= PNG_FREE_TEXT;
    @@ -1098,6 +1119,7 @@ png_set_text_2(png_const_structrp png_ptr, png_inforp info_ptr,
           {
              png_chunk_report(png_ptr, "text chunk: out of memory",
                  PNG_CHUNK_WRITE_ERROR);
    +         png_free(png_ptr, old_text);
     
              return 1;
           }
    @@ -1151,6 +1173,8 @@ png_set_text_2(png_const_structrp png_ptr, png_inforp info_ptr,
           png_debug1(3, "transferred text chunk %d", info_ptr->num_text);
        }
     
    +   png_free(png_ptr, old_text);
    +
        return 0;
     }
     #endif
    @@ -1194,6 +1218,16 @@ png_set_tRNS(png_structrp png_ptr, png_inforp info_ptr,
     
        if (trans_alpha != NULL)
        {
    +       /* Snapshot the caller's trans_alpha before freeing, in case it
    +        * points to info_ptr->trans_alpha (getter-to-setter aliasing).
    +        */
    +       png_byte safe_trans[PNG_MAX_PALETTE_LENGTH];
    +
    +       if (num_trans > 0 && num_trans <= PNG_MAX_PALETTE_LENGTH)
    +          memcpy(safe_trans, trans_alpha, (size_t)num_trans);
    +
    +       trans_alpha = safe_trans;
    +
            png_free_data(png_ptr, info_ptr, PNG_FREE_TRNS, 0);
     
            if (num_trans > 0 && num_trans <= PNG_MAX_PALETTE_LENGTH)
    @@ -1278,6 +1312,7 @@ png_set_sPLT(png_const_structrp png_ptr,
      */
     {
        png_sPLT_tp np;
    +   png_sPLT_tp old_spalettes;
     
        png_debug1(1, "in %s storage function", "sPLT");
     
    @@ -1298,7 +1333,10 @@ png_set_sPLT(png_const_structrp png_ptr,
           return;
        }
     
    -   png_free(png_ptr, info_ptr->splt_palettes);
    +   /* Defer freeing the old array until after the copy loop below,
    +    * in case entries aliases info_ptr->splt_palettes (getter-to-setter).
    +    */
    +   old_spalettes = info_ptr->splt_palettes;
     
        info_ptr->splt_palettes = np;
        info_ptr->free_me |= PNG_FREE_SPLT;
    @@ -1362,6 +1400,8 @@ png_set_sPLT(png_const_structrp png_ptr,
        }
        while (--nentries);
     
    +   png_free(png_ptr, old_spalettes);
    +
        if (nentries > 0)
           png_chunk_report(png_ptr, "sPLT out of memory", PNG_CHUNK_WRITE_ERROR);
     }
    @@ -1410,6 +1450,7 @@ png_set_unknown_chunks(png_const_structrp png_ptr,
         png_inforp info_ptr, png_const_unknown_chunkp unknowns, int num_unknowns)
     {
        png_unknown_chunkp np;
    +   png_unknown_chunkp old_unknowns;
     
        if (png_ptr == NULL || info_ptr == NULL || num_unknowns <= 0 ||
            unknowns == NULL)
    @@ -1456,7 +1497,10 @@ png_set_unknown_chunks(png_const_structrp png_ptr,
           return;
        }
     
    -   png_free(png_ptr, info_ptr->unknown_chunks);
    +   /* Defer freeing the old array until after the copy loop below,
    +    * in case unknowns aliases info_ptr->unknown_chunks (getter-to-setter).
    +    */
    +   old_unknowns = info_ptr->unknown_chunks;
     
        info_ptr->unknown_chunks = np; /* safe because it is initialized */
        info_ptr->free_me |= PNG_FREE_UNKN;
    @@ -1502,6 +1546,8 @@ png_set_unknown_chunks(png_const_structrp png_ptr,
           ++np;
           ++(info_ptr->unknown_chunks_num);
        }
    +
    +   png_free(png_ptr, old_unknowns);
     }
     
     void PNGAPI
    
    From 31221a3c5ecdc5ac0a1dad9264a0109a8192987e Mon Sep 17 00:00:00 2001
    From: Matthias Baesken 
    Date: Mon, 20 Apr 2026 07:03:19 +0000
    Subject: [PATCH 126/168] 8364657: Crash for SecureRandom.generateSeed(0) on
     Windows x86-64
    
    Reviewed-by: semery
    Backport-of: 360b6af1b1c39e6d3a01c4a32473cf007ed632c6
    ---
     .../windows/native/libsunmscapi/security.cpp  |  8 +++-
     .../security/SecureRandom/TestStrong.java     | 44 +++++++++++++++++++
     2 files changed, 50 insertions(+), 2 deletions(-)
     create mode 100644 test/jdk/java/security/SecureRandom/TestStrong.java
    
    diff --git a/src/jdk.crypto.mscapi/windows/native/libsunmscapi/security.cpp b/src/jdk.crypto.mscapi/windows/native/libsunmscapi/security.cpp
    index 747db0001580..97304ace1606 100644
    --- a/src/jdk.crypto.mscapi/windows/native/libsunmscapi/security.cpp
    +++ b/src/jdk.crypto.mscapi/windows/native/libsunmscapi/security.cpp
    @@ -1,5 +1,5 @@
     /*
    - * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved.
    + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved.
      * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
      *
      * This code is free software; you can redistribute it and/or modify it
    @@ -366,7 +366,11 @@ JNIEXPORT jbyteArray JNICALL Java_sun_security_mscapi_PRNG_generateSeed
     
             } else {
     
    -            if (length > 0) {
    +            /*
    +             * seed could be NULL here when SecureRandom.generateSeed() is
    +             * called with a length of zero.
    +             */
    +            if ((length > 0) || (seed == NULL)) {
                     seed = env->NewByteArray(length);
                     if (seed == NULL) {
                         __leave;
    diff --git a/test/jdk/java/security/SecureRandom/TestStrong.java b/test/jdk/java/security/SecureRandom/TestStrong.java
    new file mode 100644
    index 000000000000..f5de07244e15
    --- /dev/null
    +++ b/test/jdk/java/security/SecureRandom/TestStrong.java
    @@ -0,0 +1,44 @@
    +/*
    + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
    + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
    + *
    + * This code is free software; you can redistribute it and/or modify it
    + * under the terms of the GNU General Public License version 2 only, as
    + * published by the Free Software Foundation.
    + *
    + * This code is distributed in the hope that it will be useful, but WITHOUT
    + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
    + * version 2 for more details (a copy is included in the LICENSE file that
    + * accompanied this code).
    + *
    + * You should have received a copy of the GNU General Public License version
    + * 2 along with this work; if not, write to the Free Software Foundation,
    + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
    + *
    + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
    + * or visit www.oracle.com if you need additional information or have any
    + * questions.
    + */
    +
    +import java.security.SecureRandom;
    +import java.util.Arrays;
    +
    +/*
    + * @test
    + * @bug 8364657
    + * @summary verify the behavior of SecureRandom instance returned by
    + *          SecureRandom.getInstanceStrong()
    + * @run main TestStrong
    + */
    +public class TestStrong {
    +
    +    public static void main(String[] args) throws Exception {
    +
    +        final SecureRandom random = SecureRandom.getInstanceStrong();
    +        System.out.println("going to generate random seed using " + random);
    +        final byte[] seed = random.generateSeed(0);
    +        System.out.println("random seed generated");
    +        System.out.println("seed: " + Arrays.toString(seed));
    +    }
    +}
    
    From 50d5e8451366188226497c618d212181e91b6407 Mon Sep 17 00:00:00 2001
    From: William Kemper 
    Date: Mon, 20 Apr 2026 18:28:40 +0000
    Subject: [PATCH 127/168] 8371381: [Shenandoah] Setting ergo flags should use
     FLAG_SET_ERGO
    
    Backport-of: 2199b5fef4540ae8da77c5c4feafc8822a3d9d3d
    ---
     .../share/gc/shenandoah/heuristics/shenandoahHeuristics.hpp | 6 +++---
     .../share/gc/shenandoah/mode/shenandoahPassiveMode.cpp      | 6 +++++-
     2 files changed, 8 insertions(+), 4 deletions(-)
    
    diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.hpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.hpp
    index d036a62a32cf..33230c0cfda5 100644
    --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.hpp
    +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.hpp
    @@ -36,7 +36,7 @@
       do {                                                                      \
         if (FLAG_IS_DEFAULT(name) && (name)) {                                  \
           log_info(gc)("Heuristics ergonomically sets -XX:-" #name);            \
    -      FLAG_SET_DEFAULT(name, false);                                        \
    +      FLAG_SET_ERGO(name, false);                                           \
         }                                                                       \
       } while (0)
     
    @@ -44,7 +44,7 @@
       do {                                                                      \
         if (FLAG_IS_DEFAULT(name) && !(name)) {                                 \
           log_info(gc)("Heuristics ergonomically sets -XX:+" #name);            \
    -      FLAG_SET_DEFAULT(name, true);                                         \
    +      FLAG_SET_ERGO(name, true);                                            \
         }                                                                       \
       } while (0)
     
    @@ -52,7 +52,7 @@
       do {                                                                      \
         if (FLAG_IS_DEFAULT(name)) {                                            \
           log_info(gc)("Heuristics ergonomically sets -XX:" #name "=" #value);  \
    -      FLAG_SET_DEFAULT(name, value);                                        \
    +      FLAG_SET_ERGO(name, value);                                           \
         }                                                                       \
       } while (0)
     
    diff --git a/src/hotspot/share/gc/shenandoah/mode/shenandoahPassiveMode.cpp b/src/hotspot/share/gc/shenandoah/mode/shenandoahPassiveMode.cpp
    index 296a1979b01b..2dfa52b86bb9 100644
    --- a/src/hotspot/share/gc/shenandoah/mode/shenandoahPassiveMode.cpp
    +++ b/src/hotspot/share/gc/shenandoah/mode/shenandoahPassiveMode.cpp
    @@ -29,6 +29,7 @@
     #include "gc/shenandoah/shenandoahHeap.inline.hpp"
     #include "logging/log.hpp"
     #include "logging/logTag.hpp"
    +#include "runtime/globals_extension.hpp"
     #include "runtime/java.hpp"
     
     void ShenandoahPassiveMode::initialize_flags() const {
    @@ -41,7 +42,10 @@ void ShenandoahPassiveMode::initialize_flags() const {
     
       // No need for evacuation reserve with Full GC, only for Degenerated GC.
       if (!ShenandoahDegeneratedGC) {
    -    SHENANDOAH_ERGO_OVERRIDE_DEFAULT(ShenandoahEvacReserve, 0);
    +    if (FLAG_IS_DEFAULT(ShenandoahEvacReserve)) {
    +      log_info(gc)("Heuristics sets -XX:ShenandoahEvacReserve=0");
    +      FLAG_SET_DEFAULT(ShenandoahEvacReserve, 0);
    +    }
       }
     
       // Disable known barriers by default.
    
    From 37fbcaa7d4123f8a81e2a33252b63814a048959f Mon Sep 17 00:00:00 2001
    From: William Kemper 
    Date: Mon, 20 Apr 2026 18:31:20 +0000
    Subject: [PATCH 128/168] 8241066: Shenandoah: fix or cleanup
     SH::do_full_collection
    
    Backport-of: f84be36dd59ae6b00aea334944b8266ecf8f5cbd
    ---
     src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp | 9 ++++++---
     1 file changed, 6 insertions(+), 3 deletions(-)
    
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp
    index 51c06e18e255..c6730e256d6e 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp
    @@ -1579,8 +1579,8 @@ void ShenandoahHeap::collect_as_vm_thread(GCCause::Cause cause) {
       // cycle. We _could_ cancel the concurrent cycle and then try to run a cycle directly
       // on the VM thread, but this would confuse the control thread mightily and doesn't
       // seem worth the trouble. Instead, we will have the caller thread run (and wait for) a
    -  // concurrent cycle in the prologue of the heap inspect/dump operation. This is how
    -  // other concurrent collectors in the JVM handle this scenario as well.
    +  // concurrent cycle in the prologue of the heap inspect/dump operation (see VM_HeapDumper::doit_prologue).
    +  // This is how other concurrent collectors in the JVM handle this scenario as well.
       assert(Thread::current()->is_VM_thread(), "Should be the VM thread");
       guarantee(cause == GCCause::_heap_dump || cause == GCCause::_heap_inspection, "Invalid cause");
     }
    @@ -1590,7 +1590,10 @@ void ShenandoahHeap::collect(GCCause::Cause cause) {
     }
     
     void ShenandoahHeap::do_full_collection(bool clear_all_soft_refs) {
    -  //assert(false, "Shouldn't need to do full collections");
    +  // This method is only called by `CollectedHeap::collect_as_vm_thread`, which we have
    +  // overridden to do nothing. See the comment there for an explanation of how heap inspections
    +  // work for Shenandoah.
    +  ShouldNotReachHere();
     }
     
     HeapWord* ShenandoahHeap::block_start(const void* addr) const {
    
    From 1bb37306cd2e3c88dcf49863c82c95a01c4eefaf Mon Sep 17 00:00:00 2001
    From: William Kemper 
    Date: Mon, 20 Apr 2026 18:31:56 +0000
    Subject: [PATCH 129/168] 8367709: GenShen: Dirty cards for objects that get
     promoted by safepoint that intervenes between allocation and stores
    
    Backport-of: d4472979c43d9825ed2d008dbaed26dbf6d36180
    ---
     .../share/gc/shenandoah/shenandoahBarrierSet.cpp  | 15 +++++++++++++--
     .../gc/shenandoah/shenandoahMarkingContext.cpp    |  8 ++++----
     .../shenandoahMarkingContext.inline.hpp           |  4 ++--
     .../gc/shenandoah/shenandoahScanRemembered.cpp    |  6 +++---
     .../shenandoahScanRemembered.inline.hpp           |  6 +++---
     5 files changed, 25 insertions(+), 14 deletions(-)
    
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.cpp b/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.cpp
    index 5d19a6a34e31..f6733d4a923d 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.cpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.cpp
    @@ -89,8 +89,19 @@ bool ShenandoahBarrierSet::need_keep_alive_barrier(DecoratorSet decorators, Basi
     
     void ShenandoahBarrierSet::on_slowpath_allocation_exit(JavaThread* thread, oop new_obj) {
     #if COMPILER2_OR_JVMCI
    -  assert(!ReduceInitialCardMarks || !ShenandoahCardBarrier || ShenandoahGenerationalHeap::heap()->is_in_young(new_obj),
    -         "Allocating new object outside of young generation: " INTPTR_FORMAT, p2i(new_obj));
    +  if (ReduceInitialCardMarks && ShenandoahCardBarrier && !ShenandoahHeap::heap()->is_in_young(new_obj)) {
    +    log_debug(gc)("Newly allocated object (" PTR_FORMAT ") is not in the young generation", p2i(new_obj));
    +    // This can happen when an object is newly allocated, but we come to a safepoint before returning
    +    // the object. If the safepoint runs a degenerated cycle that is upgraded to a full GC, this object
    +    // will have survived two GC cycles. If the tenuring age is very low (1), this object may be promoted.
    +    // In this case, we have an allocated object, but it has received no stores yet. If card marking barriers
    +    // have been elided, we could end up with an object in old holding pointers to young that won't be in
    +    // the remembered set. The solution here is conservative, but this problem should be rare, and it will
    +    // correct itself on subsequent cycles when the remembered set is updated.
    +    ShenandoahGenerationalHeap::heap()->old_generation()->card_scan()->mark_range_as_dirty(
    +      cast_from_oop(new_obj), new_obj->size()
    +    );
    +  }
     #endif // COMPILER2_OR_JVMCI
       assert(thread->deferred_card_mark().is_empty(), "We don't use this");
     }
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahMarkingContext.cpp b/src/hotspot/share/gc/shenandoah/shenandoahMarkingContext.cpp
    index 0babeaffd3e0..40eee8c342ba 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahMarkingContext.cpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahMarkingContext.cpp
    @@ -74,8 +74,8 @@ void ShenandoahMarkingContext::initialize_top_at_mark_start(ShenandoahHeapRegion
       _top_at_mark_starts_base[idx] = bottom;
       _top_bitmaps[idx] = bottom;
     
    -  log_debug(gc)("SMC:initialize_top_at_mark_start for Region %zu, TAMS: " PTR_FORMAT ", TopOfBitMap: " PTR_FORMAT,
    -                r->index(), p2i(bottom), p2i(r->end()));
    +  log_debug(gc, mark)("SMC:initialize_top_at_mark_start for Region %zu, TAMS: " PTR_FORMAT ", TopOfBitMap: " PTR_FORMAT,
    +                      r->index(), p2i(bottom), p2i(r->end()));
     }
     
     HeapWord* ShenandoahMarkingContext::top_bitmap(ShenandoahHeapRegion* r) {
    @@ -86,8 +86,8 @@ void ShenandoahMarkingContext::clear_bitmap(ShenandoahHeapRegion* r) {
       HeapWord* bottom = r->bottom();
       HeapWord* top_bitmap = _top_bitmaps[r->index()];
     
    -  log_debug(gc)("SMC:clear_bitmap for %s Region %zu, top_bitmap: " PTR_FORMAT,
    -                r->affiliation_name(), r->index(), p2i(top_bitmap));
    +  log_debug(gc, mark)("SMC:clear_bitmap for %s Region %zu, top_bitmap: " PTR_FORMAT,
    +                      r->affiliation_name(), r->index(), p2i(top_bitmap));
     
       if (top_bitmap > bottom) {
         _mark_bit_map.clear_range_large(MemRegion(bottom, top_bitmap));
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahMarkingContext.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahMarkingContext.inline.hpp
    index fe98413a8ccb..637dbf47c3f4 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahMarkingContext.inline.hpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahMarkingContext.inline.hpp
    @@ -108,8 +108,8 @@ inline void ShenandoahMarkingContext::capture_top_at_mark_start(ShenandoahHeapRe
              "Region %zu, bitmap should be clear while adjusting TAMS: " PTR_FORMAT " -> " PTR_FORMAT,
              idx, p2i(old_tams), p2i(new_tams));
     
    -  log_debug(gc)("Capturing TAMS for %s Region %zu, was: " PTR_FORMAT ", now: " PTR_FORMAT,
    -                r->affiliation_name(), idx, p2i(old_tams), p2i(new_tams));
    +  log_debug(gc, mark)("Capturing TAMS for %s Region %zu, was: " PTR_FORMAT ", now: " PTR_FORMAT,
    +                      r->affiliation_name(), idx, p2i(old_tams), p2i(new_tams));
     
       _top_at_mark_starts_base[idx] = new_tams;
       _top_bitmaps[idx] = new_tams;
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.cpp b/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.cpp
    index ea94c2926e86..b6a21939b558 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.cpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.cpp
    @@ -777,9 +777,9 @@ void ShenandoahScanRememberedTask::do_work(uint worker_id) {
       struct ShenandoahRegionChunk assignment;
       while (_work_list->next(&assignment)) {
         ShenandoahHeapRegion* region = assignment._r;
    -    log_debug(gc)("ShenandoahScanRememberedTask::do_work(%u), processing slice of region "
    -                  "%zu at offset %zu, size: %zu",
    -                  worker_id, region->index(), assignment._chunk_offset, assignment._chunk_size);
    +    log_debug(gc, remset)("ShenandoahScanRememberedTask::do_work(%u), processing slice of region "
    +                          "%zu at offset %zu, size: %zu",
    +                          worker_id, region->index(), assignment._chunk_offset, assignment._chunk_size);
         if (region->is_old()) {
           size_t cluster_size =
             CardTable::card_size_in_words() * ShenandoahCardCluster::CardsPerCluster;
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.inline.hpp
    index 82022420a2a8..998e786eff28 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.inline.hpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.inline.hpp
    @@ -361,9 +361,9 @@ ShenandoahScanRemembered::process_region_slice(ShenandoahHeapRegion *region, siz
         }
       }
     
    -  log_debug(gc)("Remembered set scan processing Region %zu, from " PTR_FORMAT " to " PTR_FORMAT ", using %s table",
    -                region->index(), p2i(start_of_range), p2i(end_of_range),
    -                use_write_table? "read/write (updating)": "read (marking)");
    +  log_debug(gc, remset)("Remembered set scan processing Region %zu, from " PTR_FORMAT " to " PTR_FORMAT ", using %s table",
    +                        region->index(), p2i(start_of_range), p2i(end_of_range),
    +                        use_write_table? "read/write (updating)": "read (marking)");
     
       // Note that end_of_range may point to the middle of a cluster because we limit scanning to
       // region->top() or region->get_update_watermark(). We avoid processing past end_of_range.
    
    From 50341ae78692fdace0fc1743a15b67046112ff03 Mon Sep 17 00:00:00 2001
    From: William Kemper 
    Date: Mon, 20 Apr 2026 18:32:27 +0000
    Subject: [PATCH 130/168] 8261743: Shenandoah: enable String deduplication with
     compact heuristics
    
    Backport-of: e34a831814996be3e0a2df86b11b1718a76ea558
    ---
     .../gc/shenandoah/heuristics/shenandoahCompactHeuristics.cpp     | 1 +
     1 file changed, 1 insertion(+)
    
    diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahCompactHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahCompactHeuristics.cpp
    index 592bba677574..f8a77d95d51c 100644
    --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahCompactHeuristics.cpp
    +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahCompactHeuristics.cpp
    @@ -38,6 +38,7 @@ ShenandoahCompactHeuristics::ShenandoahCompactHeuristics(ShenandoahSpaceInfo* sp
       SHENANDOAH_ERGO_ENABLE_FLAG(ShenandoahImplicitGCInvokesConcurrent);
       SHENANDOAH_ERGO_ENABLE_FLAG(ShenandoahUncommit);
       SHENANDOAH_ERGO_ENABLE_FLAG(ShenandoahAlwaysClearSoftRefs);
    +  SHENANDOAH_ERGO_ENABLE_FLAG(UseStringDeduplication);
       SHENANDOAH_ERGO_OVERRIDE_DEFAULT(ShenandoahAllocationThreshold,  10);
       SHENANDOAH_ERGO_OVERRIDE_DEFAULT(ShenandoahImmediateThreshold,   100);
       SHENANDOAH_ERGO_OVERRIDE_DEFAULT(ShenandoahUncommitDelay,        1000);
    
    From 03bb624eed26313f79afd5384675e1a419ea792a Mon Sep 17 00:00:00 2001
    From: William Kemper 
    Date: Mon, 20 Apr 2026 18:32:51 +0000
    Subject: [PATCH 131/168] 8368307: Shenandoah: get_next_bit_impl should special
     case weak and strong mark bits
    
    Backport-of: af8fb20ac0325a231ee14bd72e9764e02ca07681
    ---
     .../gc/shenandoah/shenandoahMarkBitMap.inline.hpp  | 14 ++++++--------
     1 file changed, 6 insertions(+), 8 deletions(-)
    
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahMarkBitMap.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahMarkBitMap.inline.hpp
    index 21b526f99954..73442ff0520a 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahMarkBitMap.inline.hpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahMarkBitMap.inline.hpp
    @@ -136,14 +136,12 @@ inline ShenandoahMarkBitMap::idx_t ShenandoahMarkBitMap::get_next_bit_impl(idx_t
         // Get the word containing l_index, and shift out low bits.
         idx_t index = to_words_align_down(l_index);
         bm_word_t cword = (map(index) ^ flip) >> bit_in_word(l_index);
    -    if ((cword & 1) != 0) {
    -      // The first bit is similarly often interesting. When it matters
    -      // (density or features of the calling algorithm make it likely
    -      // the first bit is set), going straight to the next clause compares
    -      // poorly with doing this check first; count_trailing_zeros can be
    -      // relatively expensive, plus there is the additional range check.
    -      // But when the first bit isn't set, the cost of having tested for
    -      // it is relatively small compared to the rest of the search.
    +    if ((cword & 0x03) != 0) {
    +      // The first bits (representing weak mark or strong mark) are similarly often interesting. When it matters
    +      // (density or features of the calling algorithm make it likely the first bits are set), going straight to
    +      // the next clause compares poorly with doing this check first; count_trailing_zeros can be relatively expensive,
    +      // plus there is the additional range check.  But when the first bits are not set, the cost of having tested for
    +      // them is relatively small compared to the rest of the search.
           return l_index;
         } else if (cword != 0) {
           // Flipped and shifted first word is non-zero.
    
    From c7da0f998c0db4924fc626ab21a70044a6b12462 Mon Sep 17 00:00:00 2001
    From: Goetz Lindenmaier 
    Date: Tue, 21 Apr 2026 05:48:15 +0000
    Subject: [PATCH 132/168] 8373928: 4 Dangling pointer defect groups in java.c
    
    Backport-of: 5dfda66e13df5a88a66a6e4b1ae1bcd4e20ac674
    ---
     src/java.base/share/native/libjli/java.c | 7 +++++--
     1 file changed, 5 insertions(+), 2 deletions(-)
    
    diff --git a/src/java.base/share/native/libjli/java.c b/src/java.base/share/native/libjli/java.c
    index 6072bff50c6b..4621ab588d19 100644
    --- a/src/java.base/share/native/libjli/java.c
    +++ b/src/java.base/share/native/libjli/java.c
    @@ -1,5 +1,5 @@
     /*
    - * Copyright (c) 1995, 2025, Oracle and/or its affiliates. All rights reserved.
    + * Copyright (c) 1995, 2026, Oracle and/or its affiliates. All rights reserved.
      * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
      *
      * This code is free software; you can redistribute it and/or modify it
    @@ -1505,6 +1505,7 @@ InitializeJVM(JavaVM **pvm, JNIEnv **penv, InvocationFunctions *ifn)
     
         r = ifn->CreateJavaVM(pvm, (void **)penv, &args);
         JLI_MemFree(options);
    +    options = NULL;
         return r == JNI_OK;
     }
     
    @@ -2203,6 +2204,7 @@ FreeKnownVMs()
             knownVMs[i].name = NULL;
         }
         JLI_MemFree(knownVMs);
    +    knownVMs = NULL;
     }
     
     /*
    @@ -2276,8 +2278,9 @@ ShowSplashScreen()
         (void)UnsetEnv(SPLASH_JAR_ENV_ENTRY);
     
         JLI_MemFree(splash_jar_entry);
    +    splash_jar_entry = NULL;
         JLI_MemFree(splash_file_entry);
    -
    +    splash_file_entry = NULL;
     }
     
     static const char* GetFullVersion()
    
    From f65951db6ab61681c594ccc52ba1a5d37d5f7f4b Mon Sep 17 00:00:00 2001
    From: Goetz Lindenmaier 
    Date: Tue, 21 Apr 2026 05:50:10 +0000
    Subject: [PATCH 133/168] 8376152: Test
     javax/sound/sampled/Clip/bug5070081.java timed out then completed
    
    Backport-of: 0ab8a85e87cb607c48a45900550998f0d36cf761
    ---
     test/jdk/javax/sound/sampled/Clip/bug5070081.java | 5 +++--
     1 file changed, 3 insertions(+), 2 deletions(-)
    
    diff --git a/test/jdk/javax/sound/sampled/Clip/bug5070081.java b/test/jdk/javax/sound/sampled/Clip/bug5070081.java
    index e7bf7e30de2a..89e9d591d28d 100644
    --- a/test/jdk/javax/sound/sampled/Clip/bug5070081.java
    +++ b/test/jdk/javax/sound/sampled/Clip/bug5070081.java
    @@ -1,5 +1,5 @@
     /*
    - * Copyright (c) 2005, 2017, Oracle and/or its affiliates. All rights reserved.
    + * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved.
      * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
      *
      * This code is free software; you can redistribute it and/or modify it
    @@ -31,7 +31,8 @@
     
     /*
      * @test
    - * @bug 5070081
    + * @key sound
    + * @bug 5070081 8376152
      * @summary Tests that javax.sound.sampled.Clip does not loses position through
      *          stop/start
      */
    
    From 18dbb82b864732bda02a9041f8f54d923fc6b13a Mon Sep 17 00:00:00 2001
    From: Arno Zeller 
    Date: Tue, 21 Apr 2026 05:52:06 +0000
    Subject: [PATCH 134/168] 8382018:
     test/jdk/java/nio/file/spi/SetDefaultProvider.java leaves a directory in /tmp
    
    Backport-of: 1e2b0d2b67c7ae0843c9adbc641471040fbe7d71
    ---
     test/jdk/java/nio/file/spi/testapp/testapp/Main.java | 4 ++--
     1 file changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/test/jdk/java/nio/file/spi/testapp/testapp/Main.java b/test/jdk/java/nio/file/spi/testapp/testapp/Main.java
    index 6f61d431707e..5a48ff473842 100644
    --- a/test/jdk/java/nio/file/spi/testapp/testapp/Main.java
    +++ b/test/jdk/java/nio/file/spi/testapp/testapp/Main.java
    @@ -1,5 +1,5 @@
     /*
    - * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved.
    + * Copyright (c) 2017, 2026, Oracle and/or its affiliates. All rights reserved.
      * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
      *
      * This code is free software; you can redistribute it and/or modify it
    @@ -41,7 +41,7 @@ public static void main(String[] args) throws Exception {
                 throw new RuntimeException("FileSystemProvider not overridden");
     
             // exercise the file system
    -        Path dir = Files.createTempDirectory("tmp");
    +        Path dir = Files.createTempDirectory(Path.of(""), "tmp");
             if (dir.getFileSystem() != fs)
                 throw new RuntimeException("'dir' not in default file system");
             System.out.println("created: " + dir);
    
    From 4a9608f4cb29c65371527787b1e7c76c11a14d44 Mon Sep 17 00:00:00 2001
    From: Anton Voznia 
    Date: Wed, 22 Apr 2026 20:53:23 +0000
    Subject: [PATCH 135/168] 8377512: AOT cache creation fails with invalid native
     pointer
    
    Reviewed-by: iklam
    Backport-of: 00c1f4b6248e116c8839b1fb19ce20182711667a
    ---
     .../share/cds/aotConstantPoolResolver.cpp     | 55 ++++++++++++++++++-
     .../share/cds/aotConstantPoolResolver.hpp     |  7 ++-
     src/hotspot/share/cds/archiveHeapWriter.cpp   | 11 +++-
     src/hotspot/share/oops/cpCache.cpp            | 31 ++++++++---
     .../cds/appcds/aotCache/ExcludedClasses.java  | 48 +++++++++++++++-
     5 files changed, 134 insertions(+), 18 deletions(-)
    
    diff --git a/src/hotspot/share/cds/aotConstantPoolResolver.cpp b/src/hotspot/share/cds/aotConstantPoolResolver.cpp
    index 6ab63418e09e..3ea0c76f8702 100644
    --- a/src/hotspot/share/cds/aotConstantPoolResolver.cpp
    +++ b/src/hotspot/share/cds/aotConstantPoolResolver.cpp
    @@ -81,6 +81,7 @@ bool AOTConstantPoolResolver::is_resolution_deterministic(ConstantPool* cp, int
     bool AOTConstantPoolResolver::is_class_resolution_deterministic(InstanceKlass* cp_holder, Klass* resolved_class) {
       assert(!is_in_archivebuilder_buffer(cp_holder), "sanity");
       assert(!is_in_archivebuilder_buffer(resolved_class), "sanity");
    +  assert_at_safepoint(); // try_add_candidate() is called below and requires to be at safepoint.
     
       if (resolved_class->is_instance_klass()) {
         InstanceKlass* ik = InstanceKlass::cast(resolved_class);
    @@ -285,7 +286,15 @@ void AOTConstantPoolResolver::maybe_resolve_fmi_ref(InstanceKlass* ik, Method* m
         break;
     
       case Bytecodes::_invokehandle:
    -    InterpreterRuntime::cds_resolve_invokehandle(raw_index, cp, CHECK);
    +    if (CDSConfig::is_dumping_method_handles()) {
    +      ResolvedMethodEntry* method_entry = cp->resolved_method_entry_at(raw_index);
    +      int cp_index = method_entry->constant_pool_index();
    +      Symbol* sig = cp->uncached_signature_ref_at(cp_index);
    +      Klass* k;
    +      if (check_methodtype_signature(cp(), sig, &k, true)) {
    +        InterpreterRuntime::cds_resolve_invokehandle(raw_index, cp, CHECK);
    +      }
    +    }
         break;
     
       default:
    @@ -339,7 +348,7 @@ void AOTConstantPoolResolver::preresolve_indy_cp_entries(JavaThread* current, In
     // Check the MethodType signatures used by parameters to the indy BSMs. Make sure we don't
     // use types that have been excluded, or else we might end up creating MethodTypes that cannot be stored
     // in the AOT cache.
    -bool AOTConstantPoolResolver::check_methodtype_signature(ConstantPool* cp, Symbol* sig, Klass** return_type_ret) {
    +bool AOTConstantPoolResolver::check_methodtype_signature(ConstantPool* cp, Symbol* sig, Klass** return_type_ret, bool is_invokehandle) {
       ResourceMark rm;
       for (SignatureStream ss(sig); !ss.is_done(); ss.next()) {
         if (ss.is_reference()) {
    @@ -352,11 +361,18 @@ bool AOTConstantPoolResolver::check_methodtype_signature(ConstantPool* cp, Symbo
           if (SystemDictionaryShared::should_be_excluded(k)) {
             if (log_is_enabled(Warning, aot, resolve)) {
               ResourceMark rm;
    -          log_warning(aot, resolve)("Cannot aot-resolve Lambda proxy because %s is excluded", k->external_name());
    +          log_warning(aot, resolve)("Cannot aot-resolve %s because %s is excluded",
    +                                    is_invokehandle ? "invokehandle" : "Lambda proxy",
    +                                    k->external_name());
             }
             return false;
           }
     
    +      // cp->pool_holder() must be able to resolve k in production run
    +      precond(CDSConfig::is_dumping_aot_linked_classes());
    +      precond(SystemDictionaryShared::is_builtin_loader(cp->pool_holder()->class_loader_data()));
    +      precond(SystemDictionaryShared::is_builtin_loader(k->class_loader_data()));
    +
           if (ss.at_return_type() && return_type_ret != nullptr) {
             *return_type_ret = k;
           }
    @@ -414,11 +430,44 @@ bool AOTConstantPoolResolver::check_lambda_metafactory_methodhandle_arg(Constant
         return false;
       }
     
    +  // klass and sigature of the method (no need to check the method name)
       Symbol* sig = cp->method_handle_signature_ref_at(mh_index);
    +  Symbol* klass_name = cp->klass_name_at(cp->method_handle_klass_index_at(mh_index));
    +
       if (log_is_enabled(Debug, aot, resolve)) {
         ResourceMark rm;
         log_debug(aot, resolve)("Checking MethodType of MethodHandle for LambdaMetafactory BSM arg %d: %s", arg_i, sig->as_C_string());
       }
    +
    +  {
    +    Klass* k = find_loaded_class(Thread::current(), cp->pool_holder()->class_loader(), klass_name);
    +    if (k == nullptr) {
    +      // Dumping AOT cache: all classes should have been loaded by FinalImageRecipes::load_all_classes(). k must have
    +      // been a class that was excluded when FinalImageRecipes recorded all classes at the end of the training run.
    +      //
    +      // Dumping static CDS archive: all classes in the classlist have already been loaded, before we resolve
    +      // constants. k must have been a class that was excluded when the classlist was written
    +      // at the end of the training run.
    +      if (log_is_enabled(Warning, aot, resolve)) {
    +        ResourceMark rm;
    +        log_warning(aot, resolve)("Cannot aot-resolve Lambda proxy because %s is not loaded", klass_name->as_C_string());
    +      }
    +      return false;
    +    }
    +    if (SystemDictionaryShared::should_be_excluded(k)) {
    +      if (log_is_enabled(Warning, aot, resolve)) {
    +        ResourceMark rm;
    +        log_warning(aot, resolve)("Cannot aot-resolve Lambda proxy because %s is excluded", k->external_name());
    +      }
    +      return false;
    +    }
    +
    +    // cp->pool_holder() must be able to resolve k in production run
    +    precond(CDSConfig::is_dumping_aot_linked_classes());
    +    precond(SystemDictionaryShared::is_builtin_loader(cp->pool_holder()->class_loader_data()));
    +    precond(SystemDictionaryShared::is_builtin_loader(k->class_loader_data()));
    +  }
    +
       return check_methodtype_signature(cp, sig);
     }
     
    diff --git a/src/hotspot/share/cds/aotConstantPoolResolver.hpp b/src/hotspot/share/cds/aotConstantPoolResolver.hpp
    index 89d9d5c24764..6ad762b4121d 100644
    --- a/src/hotspot/share/cds/aotConstantPoolResolver.hpp
    +++ b/src/hotspot/share/cds/aotConstantPoolResolver.hpp
    @@ -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.
      * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
      *
      * This code is free software; you can redistribute it and/or modify it
    @@ -74,7 +74,10 @@ class AOTConstantPoolResolver :  AllStatic {
       static void maybe_resolve_fmi_ref(InstanceKlass* ik, Method* m, Bytecodes::Code bc, int raw_index,
                                         GrowableArray* resolve_fmi_list, TRAPS);
     
    -  static bool check_methodtype_signature(ConstantPool* cp, Symbol* sig, Klass** return_type_ret = nullptr);
    +public:
    +  static bool check_methodtype_signature(ConstantPool* cp, Symbol* sig, Klass** return_type_ret = nullptr, bool is_invokehandle = false);
    +
    +private:
       static bool check_lambda_metafactory_signature(ConstantPool* cp, Symbol* sig);
       static bool check_lambda_metafactory_methodtype_arg(ConstantPool* cp, int bsms_attribute_index, int arg_i);
       static bool check_lambda_metafactory_methodhandle_arg(ConstantPool* cp, int bsms_attribute_index, int arg_i);
    diff --git a/src/hotspot/share/cds/archiveHeapWriter.cpp b/src/hotspot/share/cds/archiveHeapWriter.cpp
    index ee1e334e84bf..6c6ab9012676 100644
    --- a/src/hotspot/share/cds/archiveHeapWriter.cpp
    +++ b/src/hotspot/share/cds/archiveHeapWriter.cpp
    @@ -754,8 +754,15 @@ void ArchiveHeapWriter::compute_ptrmap(ArchiveHeapInfo* heap_info) {
         Metadata** buffered_field_addr = requested_addr_to_buffered_addr(requested_field_addr);
         Metadata* native_ptr = *buffered_field_addr;
         guarantee(native_ptr != nullptr, "sanity");
    -    guarantee(ArchiveBuilder::current()->has_been_buffered((address)native_ptr),
    -              "Metadata %p should have been archived", native_ptr);
    +
    +    if (!ArchiveBuilder::current()->has_been_archived((address)native_ptr)) {
    +      ResourceMark rm;
    +      LogStreamHandle(Error, aot) log;
    +      log.print("Marking native pointer for oop %p (type = %s, offset = %d)",
    +                cast_from_oop(src_obj), src_obj->klass()->external_name(), field_offset);
    +      src_obj->print_on(&log);
    +      fatal("Metadata %p should have been archived", native_ptr);
    +    }
     
         address buffered_native_ptr = ArchiveBuilder::current()->get_buffered_addr((address)native_ptr);
         address requested_native_ptr = ArchiveBuilder::current()->to_requested(buffered_native_ptr);
    diff --git a/src/hotspot/share/oops/cpCache.cpp b/src/hotspot/share/oops/cpCache.cpp
    index a855639e74b8..b0f629bf572d 100644
    --- a/src/hotspot/share/oops/cpCache.cpp
    +++ b/src/hotspot/share/oops/cpCache.cpp
    @@ -1,5 +1,5 @@
     /*
    - * Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved.
    + * Copyright (c) 1998, 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
    @@ -550,6 +550,8 @@ bool ConstantPoolCache::can_archive_resolved_method(ConstantPool* src_cp, Resolv
         return false;
       }
     
    +  int cp_index = method_entry->constant_pool_index();
    +
       if (!method_entry->is_resolved(Bytecodes::_invokevirtual)) {
         if (method_entry->method() == nullptr) {
           return false;
    @@ -557,9 +559,27 @@ bool ConstantPoolCache::can_archive_resolved_method(ConstantPool* src_cp, Resolv
         if (method_entry->method()->is_continuation_native_intrinsic()) {
           return false; // FIXME: corresponding stub is generated on demand during method resolution (see LinkResolver::resolve_static_call).
         }
    +    if (method_entry->is_resolved(Bytecodes::_invokehandle)) {
    +      if (!CDSConfig::is_dumping_method_handles()) {
    +        return false;
    +      }
    +
    +      Symbol* sig = constant_pool()->uncached_signature_ref_at(cp_index);
    +      Klass* k;
    +      if (!AOTConstantPoolResolver::check_methodtype_signature(constant_pool(), sig, &k, true)) {
    +        // invokehandles that were resolved in the training run should have been filtered in
    +        // AOTConstantPoolResolver::maybe_resolve_fmi_ref so we shouldn't come to here.
    +        //
    +        // If we come here it's because the AOT assembly phase has executed an invokehandle
    +        // that uses an excluded type like jdk.jfr.Event. This should not happen because the
    +        // AOT assembly phase should execute only a very limited set of Java code.
    +        ResourceMark rm;
    +        fatal("AOT assembly phase must not resolve any invokehandles whose signatures include an excluded type");
    +      }
    +      return true;
    +    }
       }
     
    -  int cp_index = method_entry->constant_pool_index();
       assert(src_cp->tag_at(cp_index).is_method() || src_cp->tag_at(cp_index).is_interface_method(), "sanity");
     
       if (!AOTConstantPoolResolver::is_resolution_deterministic(src_cp, cp_index)) {
    @@ -570,13 +590,6 @@ bool ConstantPoolCache::can_archive_resolved_method(ConstantPool* src_cp, Resolv
           method_entry->is_resolved(Bytecodes::_invokevirtual) ||
           method_entry->is_resolved(Bytecodes::_invokespecial)) {
         return true;
    -  } else if (method_entry->is_resolved(Bytecodes::_invokehandle)) {
    -    if (CDSConfig::is_dumping_method_handles()) {
    -      // invokehandle depends on archived MethodType and LambdaForms.
    -      return true;
    -    } else {
    -      return false;
    -    }
       } else {
         return false;
       }
    diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/ExcludedClasses.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/ExcludedClasses.java
    index f50a2d1f9052..dcd98282d69b 100644
    --- a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/ExcludedClasses.java
    +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/ExcludedClasses.java
    @@ -1,5 +1,5 @@
     /*
    - * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved.
    + * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved.
      * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
      *
      * This code is free software; you can redistribute it and/or modify it
    @@ -32,6 +32,7 @@
      * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar
      *                 TestApp
      *                 TestApp$Foo
    + *                 TestApp$Foo$A
      *                 TestApp$Foo$Bar
      *                 TestApp$Foo$ShouldBeExcluded
      *                 TestApp$Foo$ShouldBeExcludedChild
    @@ -43,6 +44,8 @@
      */
     
     import java.io.File;
    +import java.lang.invoke.MethodHandle;
    +import java.lang.invoke.MethodHandles;
     import java.lang.reflect.Array;
     import java.lang.reflect.InvocationHandler;
     import java.lang.reflect.Method;
    @@ -166,6 +169,8 @@ static int hotSpot() {
                 long start = System.currentTimeMillis();
                 while (System.currentTimeMillis() - start < 1000) {
                     lambdaHotSpot();
    +                lambdaHotSpot2();
    +                invokeHandleHotSpot();
                     s.hotSpot2();
                     b.hotSpot3();
                     Taz.hotSpot4();
    @@ -208,12 +213,52 @@ static void lambdaHotSpot() {
                 }
             }
     
    +        interface A {
    +            Object get();
    +        }
    +
    +        // Lambdas that refer to excluded classes should not be AOT-resolved
    +        static void lambdaHotSpot2() {
    +            long start = System.currentTimeMillis();
    +            A a = ShouldBeExcluded::new;
    +            while (System.currentTimeMillis() - start < 20) {
    +                Object obj = (ShouldBeExcluded)a.get();
    +            }
    +        }
    +
    +        static void invokeHandleHotSpot() {
    +            try {
    +                invokeHandleHotSpotImpl();
    +            } catch (Throwable t) {
    +                throw new RuntimeException("Unexpected", t);
    +            }
    +        }
    +
    +        static void invokeHandleHotSpotImpl() throws Throwable {
    +            MethodHandle constructorHandle =
    +                MethodHandles.lookup().unreflectConstructor(ShouldBeExcluded.class.getConstructor());
    +            long start = System.currentTimeMillis();
    +            while (System.currentTimeMillis() - start < 20) {
    +                // The JVM rewrites this to:
    +                // invokehandle  
    +                //
    +                // The AOT cache must not contain a java.lang.invoke.MethodType that refers to the
    +                // ShouldBeExcluded class.
    +                ShouldBeExcluded o = (ShouldBeExcluded)constructorHandle.invoke();
    +                if (o.getClass() != ShouldBeExcluded.class) {
    +                    throw new RuntimeException("Unexpected object: " + o);
    +                }
    +            }
    +        }
    +
             static void doit(Runnable r) {
                 r.run();
             }
     
             // All subclasses of jdk.jfr.Event are excluded from the CDS archive.
             static class ShouldBeExcluded extends jdk.jfr.Event {
    +            public ShouldBeExcluded() {}
    +
                 int f = (int)(System.currentTimeMillis()) + 123;
                 int m() {
                     return f + 456;
    @@ -281,4 +326,3 @@ static void hotSpot4() {
             }
         }
     }
    -
    
    From 9652460b0f514cd0d442365ec5f80b923a6cb72f Mon Sep 17 00:00:00 2001
    From: Matthias Baesken 
    Date: Thu, 23 Apr 2026 07:48:15 +0000
    Subject: [PATCH 136/168] 8381937: Make exceptions in
     Java_sun_security_mscapi_CKeyPairGenerator generateCKeyPair more specific
    
    Backport-of: 78a6aa9c7a907fe3375cd20e45bb293b5d15732e
    ---
     .../windows/native/libsunmscapi/security.cpp                | 6 +++---
     1 file changed, 3 insertions(+), 3 deletions(-)
    
    diff --git a/src/jdk.crypto.mscapi/windows/native/libsunmscapi/security.cpp b/src/jdk.crypto.mscapi/windows/native/libsunmscapi/security.cpp
    index 97304ace1606..fab17b66f56f 100644
    --- a/src/jdk.crypto.mscapi/windows/native/libsunmscapi/security.cpp
    +++ b/src/jdk.crypto.mscapi/windows/native/libsunmscapi/security.cpp
    @@ -1,5 +1,5 @@
     /*
    - * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved.
    + * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved.
      * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
      *
      * This code is free software; you can redistribute it and/or modify it
    @@ -1373,7 +1373,7 @@ JNIEXPORT jobject JNICALL Java_sun_security_mscapi_CKeyPairGenerator_00024RSA_ge
                     PROV_RSA_FULL,
                     CRYPT_NEWKEYSET) == FALSE)
                 {
    -                ThrowException(env, KEY_EXCEPTION, GetLastError());
    +                ThrowExceptionWithMessageAndErrcode(env, KEY_EXCEPTION, "CryptAcquireContext failure", GetLastError());
                     __leave;
                 }
             }
    @@ -1385,7 +1385,7 @@ JNIEXPORT jobject JNICALL Java_sun_security_mscapi_CKeyPairGenerator_00024RSA_ge
                dwFlags,
                &hKeyPair) == FALSE)
             {
    -            ThrowException(env, KEY_EXCEPTION, GetLastError());
    +            ThrowExceptionWithMessageAndErrcode(env, KEY_EXCEPTION, "CryptGenKey failure", GetLastError());
                 __leave;
             }
     
    
    From 22ae100b57a020660d6aec2f8a02766e47413710 Mon Sep 17 00:00:00 2001
    From: Dingli Zhang 
    Date: Thu, 23 Apr 2026 12:51:42 +0000
    Subject: [PATCH 137/168] 8382522: Disable stringop-overflow in
     safepointMechanism.cpp
    
    Backport-of: 94adc66dac21566940944f614e8f29af35244d76
    ---
     make/hotspot/lib/CompileJvm.gmk | 1 +
     1 file changed, 1 insertion(+)
    
    diff --git a/make/hotspot/lib/CompileJvm.gmk b/make/hotspot/lib/CompileJvm.gmk
    index f7a7732993a2..0e5cb31302e1 100644
    --- a/make/hotspot/lib/CompileJvm.gmk
    +++ b/make/hotspot/lib/CompileJvm.gmk
    @@ -204,6 +204,7 @@ $(eval $(call SetupJdkLibrary, BUILD_LIBJVM, \
         DISABLED_WARNINGS_gcc_jvmtiTagMap.cpp := stringop-overflow, \
         DISABLED_WARNINGS_gcc_macroAssembler_ppc_sha.cpp := unused-const-variable, \
         DISABLED_WARNINGS_gcc_postaloc.cpp := address, \
    +    DISABLED_WARNINGS_gcc_safepointMechanism.cpp := stringop-overflow, \
         DISABLED_WARNINGS_gcc_shenandoahLock.cpp := stringop-overflow, \
         DISABLED_WARNINGS_gcc_stubGenerator_s390.cpp := unused-const-variable, \
         DISABLED_WARNINGS_gcc_synchronizer.cpp := stringop-overflow, \
    
    From 962fd11e95c2ec1eb06aab8856fa92f8ffe0905c Mon Sep 17 00:00:00 2001
    From: William Kemper 
    Date: Thu, 23 Apr 2026 16:44:28 +0000
    Subject: [PATCH 138/168] 8376969: Shenandoah: GC state getters should be
     inlineable
    
    Backport-of: 692444f071cab930d1b92bbfac79f87d0d801aab
    ---
     src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp   | 12 ------------
     src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp   |  4 ++--
     .../share/gc/shenandoah/shenandoahHeap.inline.hpp    | 11 +++++++++++
     3 files changed, 13 insertions(+), 14 deletions(-)
    
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp
    index c6730e256d6e..83cf75d33e8b 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp
    @@ -2756,18 +2756,6 @@ bool ShenandoahRegionIterator::has_next() const {
       return _index < _heap->num_regions();
     }
     
    -char ShenandoahHeap::gc_state() const {
    -  return _gc_state.raw_value();
    -}
    -
    -bool ShenandoahHeap::is_gc_state(GCState state) const {
    -  // If the global gc state has been changed, but hasn't yet been propagated to all threads, then
    -  // the global gc state is the correct value. Once the gc state has been synchronized with all threads,
    -  // _gc_state_changed will be toggled to false and we need to use the thread local state.
    -  return _gc_state_changed ? _gc_state.is_set(state) : ShenandoahThreadLocalData::is_gc_state(state);
    -}
    -
    -
     ShenandoahLiveData* ShenandoahHeap::get_liveness_cache(uint worker_id) {
     #ifdef ASSERT
       assert(_liveness_cache != nullptr, "sanity");
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp
    index a2dd53370356..1d0b3fd39d72 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp
    @@ -375,7 +375,7 @@ class ShenandoahHeap : public CollectedHeap {
     
     public:
       // This returns the raw value of the singular, global gc state.
    -  char gc_state() const;
    +  inline char gc_state() const;
     
       // Compares the given state against either the global gc state, or the thread local state.
       // The global gc state may change on a safepoint and is the correct value to use until
    @@ -383,7 +383,7 @@ class ShenandoahHeap : public CollectedHeap {
       // compare against the thread local state). The thread local gc state may also be changed
       // by a handshake operation, in which case, this function continues using the updated thread
       // local value.
    -  bool is_gc_state(GCState state) const;
    +  inline bool is_gc_state(GCState state) const;
     
       // This copies the global gc state into a thread local variable for all threads.
       // The thread local gc state is primarily intended to support quick access at barriers.
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.inline.hpp
    index cf9d808f7ce8..a4c5c91966fc 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.inline.hpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.inline.hpp
    @@ -451,6 +451,17 @@ inline bool ShenandoahHeap::in_collection_set_loc(void* p) const {
       return collection_set()->is_in_loc(p);
     }
     
    +inline char ShenandoahHeap::gc_state() const {
    +  return _gc_state.raw_value();
    +}
    +
    +inline bool ShenandoahHeap::is_gc_state(GCState state) const {
    +  // If the global gc state has been changed, but hasn't yet been propagated to all threads, then
    +  // the global gc state is the correct value. Once the gc state has been synchronized with all threads,
    +  // _gc_state_changed will be toggled to false and we need to use the thread local state.
    +  return _gc_state_changed ? _gc_state.is_set(state) : ShenandoahThreadLocalData::is_gc_state(state);
    +}
    +
     inline bool ShenandoahHeap::is_idle() const {
       return _gc_state_changed ? _gc_state.is_clear() : ShenandoahThreadLocalData::gc_state(Thread::current()) == 0;
     }
    
    From b5e34c12645d32074a2ea69de6501a73f74d712f Mon Sep 17 00:00:00 2001
    From: William Kemper 
    Date: Thu, 23 Apr 2026 18:31:38 +0000
    Subject: [PATCH 139/168] 8371284: GenShen: Avoid unnecessary card marking
    
    Backport-of: 8a93658e87e2e2f344d7dbfa6f916bd28175d013
    ---
     .../gc/shenandoah/shenandoahBarrierSet.inline.hpp  | 14 ++++++++++++++
     1 file changed, 14 insertions(+)
    
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.inline.hpp
    index b176446452a1..86244711844f 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.inline.hpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.inline.hpp
    @@ -191,6 +191,20 @@ inline void ShenandoahBarrierSet::keep_alive_if_weak(DecoratorSet decorators, oo
     template 
     inline void ShenandoahBarrierSet::write_ref_field_post(T* field) {
       assert(ShenandoahCardBarrier, "Should have been checked by caller");
    +  if (_heap->is_in_young(field)) {
    +    // Young field stores do not require card mark.
    +    return;
    +  }
    +  T heap_oop = RawAccess<>::oop_load(field);
    +  if (CompressedOops::is_null(heap_oop)) {
    +    // Null reference store do not require card mark.
    +    return;
    +  }
    +  oop obj = CompressedOops::decode_not_null(heap_oop);
    +  if (!_heap->is_in_young(obj)) {
    +    // Not an old->young reference store.
    +    return;
    +  }
       volatile CardTable::CardValue* byte = card_table()->byte_for(field);
       *byte = CardTable::dirty_card_val();
     }
    
    From 45c9937b67530fb57ab06e7f0adce2fdffb2a0fe Mon Sep 17 00:00:00 2001
    From: William Kemper 
    Date: Thu, 23 Apr 2026 18:43:22 +0000
    Subject: [PATCH 140/168] 8376970: Shenandoah: Verifier should do basic
     verification before touching oops
    
    Backport-of: 8e2bd92bacd6503346a48df236959c8a959c9c77
    ---
     .../gc/shenandoah/shenandoahVerifier.cpp      | 46 ++++++++++++++-----
     1 file changed, 34 insertions(+), 12 deletions(-)
    
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahVerifier.cpp b/src/hotspot/share/gc/shenandoah/shenandoahVerifier.cpp
    index cdf784852076..82358aafd3ce 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahVerifier.cpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahVerifier.cpp
    @@ -115,15 +115,15 @@ class ShenandoahVerifyOopClosure : public BasicOopIterateClosure {
       void do_oop_work(T* p) {
         T o = RawAccess<>::oop_load(p);
         if (!CompressedOops::is_null(o)) {
    -      oop obj = CompressedOops::decode_not_null(o);
    +      // Basic verification should happen before we touch anything else.
    +      // For performance reasons, only fully verify non-marked field values.
    +      // We are here when the host object for *p is already marked.
    +      oop obj = CompressedOops::decode_raw_not_null(o);
    +      verify_oop_at_basic(p, obj);
    +
           if (is_instance_ref_klass(ShenandoahForwarding::klass(obj))) {
             obj = ShenandoahForwarding::get_forwardee(obj);
           }
    -      // Single threaded verification can use faster non-atomic stack and bitmap
    -      // methods.
    -      //
    -      // For performance reasons, only fully verify non-marked field values.
    -      // We are here when the host object for *p is already marked.
           if (in_generation(obj) && _map->par_mark(obj)) {
             verify_oop_at(p, obj);
             _stack->push(ShenandoahVerifierTask(obj));
    @@ -140,7 +140,7 @@ class ShenandoahVerifyOopClosure : public BasicOopIterateClosure {
         return _generation->contains(region);
       }
     
    -  void verify_oop(oop obj) {
    +  void verify_oop(oop obj, bool basic = false) {
         // Perform consistency checks with gradually decreasing safety level. This guarantees
         // that failure report would not try to touch something that was not yet verified to be
         // safe to process.
    @@ -177,10 +177,14 @@ class ShenandoahVerifyOopClosure : public BasicOopIterateClosure {
             }
           }
     
    +      check(ShenandoahAsserts::_safe_unknown, obj, obj_reg->is_active(),
    +           "Object should be in active region");
    +
           // ------------ obj is safe at this point --------------
     
    -      check(ShenandoahAsserts::_safe_oop, obj, obj_reg->is_active(),
    -            "Object should be in active region");
    +      if (basic) {
    +        return;
    +      }
     
           switch (_options._verify_liveness) {
             case ShenandoahVerifier::_verify_liveness_disable:
    @@ -333,6 +337,18 @@ class ShenandoahVerifyOopClosure : public BasicOopIterateClosure {
         _interior_loc = nullptr;
       }
     
    +  /**
    +   * Verify object with known interior reference, with only basic verification.
    +   * @param p interior reference where the object is referenced from; can be off-heap
    +   * @param obj verified object
    +   */
    +  template 
    +  void verify_oop_at_basic(T* p, oop obj) {
    +    _interior_loc = p;
    +    verify_oop(obj, /* basic = */ true);
    +    _interior_loc = nullptr;
    +  }
    +
       /**
        * Verify object without known interior reference.
        * Useful when picking up the object at known offset in heap,
    @@ -1203,7 +1219,9 @@ class ShenandoahVerifyNoForwarded : public BasicOopIterateClosure {
       void do_oop_work(T* p) {
         T o = RawAccess<>::oop_load(p);
         if (!CompressedOops::is_null(o)) {
    -      oop obj = CompressedOops::decode_not_null(o);
    +      oop obj = CompressedOops::decode_raw_not_null(o);
    +      ShenandoahAsserts::assert_correct(p, obj, __FILE__, __LINE__);
    +
           oop fwd = ShenandoahForwarding::get_forwardee_raw_unchecked(obj);
           if (obj != fwd) {
             ShenandoahAsserts::print_failure(ShenandoahAsserts::_safe_all, obj, p, nullptr,
    @@ -1223,7 +1241,9 @@ class ShenandoahVerifyInToSpaceClosure : public BasicOopIterateClosure {
       void do_oop_work(T* p) {
         T o = RawAccess<>::oop_load(p);
         if (!CompressedOops::is_null(o)) {
    -      oop obj = CompressedOops::decode_not_null(o);
    +      oop obj = CompressedOops::decode_raw_not_null(o);
    +      ShenandoahAsserts::assert_correct(p, obj, __FILE__, __LINE__);
    +
           ShenandoahHeap* heap = ShenandoahHeap::heap();
     
           if (!heap->marking_context()->is_marked_or_old(obj)) {
    @@ -1277,7 +1297,9 @@ class ShenandoahVerifyRemSetClosure : public BasicOopIterateClosure {
       inline void work(T* p) {
         T o = RawAccess<>::oop_load(p);
         if (!CompressedOops::is_null(o)) {
    -      oop obj = CompressedOops::decode_not_null(o);
    +      oop obj = CompressedOops::decode_raw_not_null(o);
    +      ShenandoahAsserts::assert_correct(p, obj, __FILE__, __LINE__);
    +
           if (_heap->is_in_young(obj) && !_scanner->is_card_dirty((HeapWord*) p)) {
             ShenandoahAsserts::print_failure(ShenandoahAsserts::_safe_all, obj, p, nullptr,
                                              _message, "clean card, it should be dirty.", __FILE__, __LINE__);
    
    From 1e9b1e0ba1f287f7827531bf87f7073e6a3f8328 Mon Sep 17 00:00:00 2001
    From: William Kemper 
    Date: Thu, 23 Apr 2026 18:59:19 +0000
    Subject: [PATCH 141/168] 8372861: Genshen: Override parallel_region_stride of
     ShenandoahResetBitmapClosure to a reasonable value for better parallelism
    
    Backport-of: db2a5420a2e3d0f5f0f066eace37a8fd4f075802
    ---
     src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp  | 3 +++
     src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp        | 2 +-
     src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp        | 1 +
     .../share/gc/shenandoah/shenandoahHeapRegionClosures.hpp  | 8 ++++++++
     4 files changed, 13 insertions(+), 1 deletion(-)
    
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp
    index 7efb824088d3..31f6fd6f44a2 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp
    @@ -74,6 +74,9 @@ class ShenandoahResetBitmapClosure final : public ShenandoahHeapRegionClosure {
         }
       }
     
    +  // Bitmap reset task is heavy-weight and benefits from much smaller tasks than the default.
    +  size_t parallel_region_stride() override { return 8; }
    +
       bool is_thread_safe() override { return true; }
     };
     
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp
    index 83cf75d33e8b..6d48e59e0c61 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp
    @@ -2020,7 +2020,7 @@ void ShenandoahHeap::parallel_heap_region_iterate(ShenandoahHeapRegionClosure* b
       assert(blk->is_thread_safe(), "Only thread-safe closures here");
       const uint active_workers = workers()->active_workers();
       const size_t n_regions = num_regions();
    -  size_t stride = ShenandoahParallelRegionStride;
    +  size_t stride = blk->parallel_region_stride();
       if (stride == 0 && active_workers > 1) {
         // Automatically derive the stride to balance the work between threads
         // evenly. Do not try to split work if below the reasonable threshold.
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp
    index 1d0b3fd39d72..8e81c500dda0 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp
    @@ -115,6 +115,7 @@ class ShenandoahRegionIterator : public StackObj {
     class ShenandoahHeapRegionClosure : public StackObj {
     public:
       virtual void heap_region_do(ShenandoahHeapRegion* r) = 0;
    +  virtual size_t parallel_region_stride() { return ShenandoahParallelRegionStride; }
       virtual bool is_thread_safe() { return false; }
     };
     
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegionClosures.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegionClosures.hpp
    index 0daf268628c1..3f3b57c9bb2d 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegionClosures.hpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegionClosures.hpp
    @@ -44,6 +44,10 @@ class ShenandoahIncludeRegionClosure : public ShenandoahHeapRegionClosure {
         }
       }
     
    +  size_t parallel_region_stride() override {
    +    return _closure->parallel_region_stride();
    +  }
    +
       bool is_thread_safe() override {
         return _closure->is_thread_safe();
       }
    @@ -64,6 +68,10 @@ class ShenandoahExcludeRegionClosure : public ShenandoahHeapRegionClosure {
         }
       }
     
    +  size_t parallel_region_stride() override {
    +    return _closure->parallel_region_stride();
    +  }
    +
       bool is_thread_safe() override {
         return _closure->is_thread_safe();
       }
    
    From ad9d9988c5824f3de76650ce242ebef8afdc77ef Mon Sep 17 00:00:00 2001
    From: William Kemper 
    Date: Thu, 23 Apr 2026 19:14:56 +0000
    Subject: [PATCH 142/168] 8373039: Remove Incorrect Asserts in
     shenandoahScanRemembered
    
    Backport-of: c8b30da7ef48edb3d43e07d2c1b8622d8123c3a9
    ---
     .../gc/shenandoah/shenandoahScanRemembered.cpp   | 16 +++-------------
     1 file changed, 3 insertions(+), 13 deletions(-)
    
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.cpp b/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.cpp
    index b6a21939b558..1643b34e853a 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.cpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.cpp
    @@ -323,7 +323,6 @@ HeapWord* ShenandoahCardCluster::first_object_start(const size_t card_index, con
         if (ctx->is_marked(p)) {
           oop obj = cast_to_oop(p);
           assert(oopDesc::is_oop(obj), "Should be an object");
    -      assert(Klass::is_valid(obj->klass()), "Not a valid klass ptr");
           assert(p + obj->size() > left, "This object should span start of card");
           assert(p < right, "Result must precede right");
           return p;
    @@ -350,15 +349,15 @@ HeapWord* ShenandoahCardCluster::first_object_start(const size_t card_index, con
     
       // Recall that we already dealt with the co-initial object case above
       assert(p < left, "obj should start before left");
    -  // While it is safe to ask an object its size in the loop that
    -  // follows, the (ifdef'd out) loop should never be needed.
    +  // While it is safe to ask an object its size in the block that
    +  // follows, the (ifdef'd out) block should never be needed.
       // 1. we ask this question only for regions in the old generation, and those
       //    that are not humongous regions
       // 2. there is no direct allocation ever by mutators in old generation
       //    regions walked by this code. Only GC will ever allocate in old regions,
       //    and then too only during promotion/evacuation phases. Thus there is no danger
       //    of races between reading from and writing to the object start array,
    -  //    or of asking partially initialized objects their size (in the loop below).
    +  //    or of asking partially initialized objects their size (in the ifdef below).
       //    Furthermore, humongous regions (and their dirty cards) are never processed
       //    by this code.
       // 3. only GC asks this question during phases when it is not concurrently
    @@ -370,15 +369,6 @@ HeapWord* ShenandoahCardCluster::first_object_start(const size_t card_index, con
     #ifdef ASSERT
       oop obj = cast_to_oop(p);
       assert(oopDesc::is_oop(obj), "Should be an object");
    -  while (p + obj->size() < left) {
    -    p += obj->size();
    -    obj = cast_to_oop(p);
    -    assert(oopDesc::is_oop(obj), "Should be an object");
    -    assert(Klass::is_valid(obj->klass()), "Not a valid klass ptr");
    -    // Check assumptions in previous block comment if this assert fires
    -    fatal("Should never need forward walk in block start");
    -  }
    -  assert(p <= left, "p should start at or before left end of card");
       assert(p + obj->size() > left, "obj should end after left end of card");
     #endif // ASSERT
       return p;
    
    From 997e50624079d2c5e5047bd51e92c3d79a9db182 Mon Sep 17 00:00:00 2001
    From: William Kemper 
    Date: Thu, 23 Apr 2026 19:17:50 +0000
    Subject: [PATCH 143/168] 8352914: Shenandoah: Change definition of
     ShenandoahSharedValue to int32_t to leverage platform atomics
    
    Backport-of: b86b2cbc7d9dd57aeaf64f70f248a120ae3cb751
    ---
     .../share/gc/shenandoah/shenandoahSharedVariables.hpp       | 6 +-----
     1 file changed, 1 insertion(+), 5 deletions(-)
    
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahSharedVariables.hpp b/src/hotspot/share/gc/shenandoah/shenandoahSharedVariables.hpp
    index 1d1c93599464..1c86531f21e5 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahSharedVariables.hpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahSharedVariables.hpp
    @@ -29,11 +29,7 @@
     #include "memory/allocation.hpp"
     #include "runtime/atomic.hpp"
     
    -typedef jbyte ShenandoahSharedValue;
    -
    -// Needed for cooperation with generated code.
    -STATIC_ASSERT(sizeof(ShenandoahSharedValue) == 1);
    -
    +typedef int32_t ShenandoahSharedValue;
     typedef struct ShenandoahSharedFlag {
       enum {
         UNSET = 0,
    
    From 4744046e6bca21ebee9489cc64b404445a54fa68 Mon Sep 17 00:00:00 2001
    From: William Kemper 
    Date: Thu, 23 Apr 2026 20:01:22 +0000
    Subject: [PATCH 144/168] 8372513: Shenandoah: ShenandoahMaxRegionSize can
     produce an unaligned heap alignment
    
    Backport-of: 5291e1c1e1ddc19d814dbdb3a981049fe40575ea
    ---
     .../share/gc/shenandoah/shenandoahArguments.cpp       |  3 ++-
     src/hotspot/share/runtime/arguments.cpp               |  1 +
     .../gc/shenandoah/options/TestRegionSizeArgs.java     | 11 +++++++++++
     3 files changed, 14 insertions(+), 1 deletion(-)
    
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahArguments.cpp b/src/hotspot/share/gc/shenandoah/shenandoahArguments.cpp
    index 65495882ef0b..8247b82e8618 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahArguments.cpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahArguments.cpp
    @@ -37,6 +37,7 @@
     #include "runtime/globals_extension.hpp"
     #include "runtime/java.hpp"
     #include "utilities/defaultStream.hpp"
    +#include "utilities/powerOfTwo.hpp"
     
     void ShenandoahArguments::initialize() {
     #if !(defined AARCH64 || defined AMD64 || defined IA32 || defined PPC64 || defined RISCV64)
    @@ -204,7 +205,7 @@ void ShenandoahArguments::initialize() {
     }
     
     size_t ShenandoahArguments::conservative_max_heap_alignment() {
    -  size_t align = ShenandoahMaxRegionSize;
    +  size_t align = next_power_of_2(ShenandoahMaxRegionSize);
       if (UseLargePages) {
         align = MAX2(align, os::large_page_size());
       }
    diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp
    index 43b779c5d9a3..79c2130fb9ab 100644
    --- a/src/hotspot/share/runtime/arguments.cpp
    +++ b/src/hotspot/share/runtime/arguments.cpp
    @@ -1452,6 +1452,7 @@ void Arguments::set_conservative_max_heap_alignment() {
                                               os::vm_allocation_granularity(),
                                               os::max_page_size(),
                                               GCArguments::compute_heap_alignment());
    +  assert(is_power_of_2(_conservative_max_heap_alignment), "Expected to be a power-of-2");
     }
     
     jint Arguments::set_ergonomics_flags() {
    diff --git a/test/hotspot/jtreg/gc/shenandoah/options/TestRegionSizeArgs.java b/test/hotspot/jtreg/gc/shenandoah/options/TestRegionSizeArgs.java
    index a8d5155584b2..80962f0ffa6e 100644
    --- a/test/hotspot/jtreg/gc/shenandoah/options/TestRegionSizeArgs.java
    +++ b/test/hotspot/jtreg/gc/shenandoah/options/TestRegionSizeArgs.java
    @@ -192,6 +192,17 @@ private static void testMinRegionSize() throws Exception {
                 output.shouldHaveExitValue(0);
             }
     
    +        // This used to assert that _conservative_max_heap_alignment is not a power-of-2.
    +        {
    +            OutputAnalyzer output = ProcessTools.executeLimitedTestJava("-XX:+UnlockExperimentalVMOptions",
    +                    "-XX:+UseShenandoahGC",
    +                    "-Xms100m",
    +                    "-Xmx1g",
    +                    "-XX:ShenandoahMaxRegionSize=33m",
    +                    "-version");
    +            output.shouldHaveExitValue(0);
    +        }
    +
         }
     
         private static void testMaxRegionSize() throws Exception {
    
    From c214a3b6ae5cb945a2d782eb19e392a3bcdf22b6 Mon Sep 17 00:00:00 2001
    From: William Kemper 
    Date: Thu, 23 Apr 2026 20:01:58 +0000
    Subject: [PATCH 145/168] 8368499: GenShen: Do not collect age census during
     evac when adaptive tenuring is disabled
    
    Reviewed-by: phh
    Backport-of: 8ca1feaf7e29c1370853b9b95c2ee7a62c6b84b7
    ---
     .../gc/shenandoah/shenandoahAgeCensus.cpp     | 100 +++++++++---------
     .../gc/shenandoah/shenandoahAgeCensus.hpp     |  12 +--
     .../gc/shenandoah/shenandoahControlThread.cpp |  28 +----
     .../gc/shenandoah/shenandoahEvacTracker.cpp   |  75 ++-----------
     .../gc/shenandoah/shenandoahEvacTracker.hpp   |   9 --
     .../shenandoahGenerationalControlThread.cpp   |  55 +++++-----
     .../shenandoahGenerationalControlThread.hpp   |   6 +-
     .../shenandoah/shenandoahGenerationalHeap.cpp |   5 -
     .../share/gc/shenandoah/shenandoahHeap.cpp    |  25 +++++
     .../share/gc/shenandoah/shenandoahHeap.hpp    |   5 +-
     .../shenandoah/shenandoahThreadLocalData.hpp  |   4 -
     11 files changed, 117 insertions(+), 207 deletions(-)
    
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp
    index c3b99af8a806..86ff6f22c721 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp
    @@ -43,7 +43,7 @@ ShenandoahAgeCensus::ShenandoahAgeCensus(uint max_workers)
                   ShenandoahGenerationalMinTenuringAge, ShenandoahGenerationalMaxTenuringAge));
       }
     
    -  _global_age_table = NEW_C_HEAP_ARRAY(AgeTable*, MAX_SNAPSHOTS, mtGC);
    +  _global_age_tables = NEW_C_HEAP_ARRAY(AgeTable*, MAX_SNAPSHOTS, mtGC);
       CENSUS_NOISE(_global_noise = NEW_C_HEAP_ARRAY(ShenandoahNoiseStats, MAX_SNAPSHOTS, mtGC);)
       _tenuring_threshold = NEW_C_HEAP_ARRAY(uint, MAX_SNAPSHOTS, mtGC);
       CENSUS_NOISE(_skipped = 0);
    @@ -52,36 +52,40 @@ ShenandoahAgeCensus::ShenandoahAgeCensus(uint max_workers)
     
       for (int i = 0; i < MAX_SNAPSHOTS; i++) {
         // Note that we don't now get perfdata from age_table
    -    _global_age_table[i] = new AgeTable(false);
    +    _global_age_tables[i] = new AgeTable(false);
         CENSUS_NOISE(_global_noise[i].clear();)
         // Sentinel value
         _tenuring_threshold[i] = MAX_COHORTS;
       }
       if (ShenandoahGenerationalAdaptiveTenuring) {
    -    _local_age_table = NEW_C_HEAP_ARRAY(AgeTable*, _max_workers, mtGC);
    +    _local_age_tables = NEW_C_HEAP_ARRAY(AgeTable*, _max_workers, mtGC);
         CENSUS_NOISE(_local_noise = NEW_C_HEAP_ARRAY(ShenandoahNoiseStats, max_workers, mtGC);)
         for (uint i = 0; i < _max_workers; i++) {
    -      _local_age_table[i] = new AgeTable(false);
    +      _local_age_tables[i] = new AgeTable(false);
           CENSUS_NOISE(_local_noise[i].clear();)
         }
       } else {
    -    _local_age_table = nullptr;
    +    _local_age_tables = nullptr;
    +  }
    +  _epoch = MAX_SNAPSHOTS - 1;  // see prepare_for_census_update()
    +
    +  if (!ShenandoahGenerationalAdaptiveTenuring) {
    +    _tenuring_threshold[_epoch] = InitialTenuringThreshold;
       }
    -  _epoch = MAX_SNAPSHOTS - 1;  // see update_epoch()
     }
     
     ShenandoahAgeCensus::~ShenandoahAgeCensus() {
       for (uint i = 0; i < MAX_SNAPSHOTS; i++) {
    -    delete _global_age_table[i];
    +    delete _global_age_tables[i];
       }
    -  FREE_C_HEAP_ARRAY(AgeTable*, _global_age_table);
    +  FREE_C_HEAP_ARRAY(AgeTable*, _global_age_tables);
       FREE_C_HEAP_ARRAY(uint, _tenuring_threshold);
       CENSUS_NOISE(FREE_C_HEAP_ARRAY(ShenandoahNoiseStats, _global_noise));
    -  if (_local_age_table) {
    +  if (_local_age_tables) {
         for (uint i = 0; i < _max_workers; i++) {
    -      delete _local_age_table[i];
    +      delete _local_age_tables[i];
         }
    -    FREE_C_HEAP_ARRAY(AgeTable*, _local_age_table);
    +    FREE_C_HEAP_ARRAY(AgeTable*, _local_age_tables);
         CENSUS_NOISE(FREE_C_HEAP_ARRAY(ShenandoahNoiseStats, _local_noise));
       }
     }
    @@ -142,37 +146,31 @@ void ShenandoahAgeCensus::prepare_for_census_update() {
       if (++_epoch >= MAX_SNAPSHOTS) {
         _epoch=0;
       }
    -  _global_age_table[_epoch]->clear();
    +  _global_age_tables[_epoch]->clear();
       CENSUS_NOISE(_global_noise[_epoch].clear();)
     }
     
     // Update the census data from appropriate sources,
     // and compute the new tenuring threshold.
    -void ShenandoahAgeCensus::update_census(size_t age0_pop, AgeTable* pv1, AgeTable* pv2) {
    +void ShenandoahAgeCensus::update_census(size_t age0_pop) {
       prepare_for_census_update();
    -  assert(_global_age_table[_epoch]->is_clear(), "Dirty decks");
    +  assert(ShenandoahGenerationalAdaptiveTenuring, "Only update census when adaptive tenuring is enabled");
    +  assert(_global_age_tables[_epoch]->is_clear(), "Dirty decks");
       CENSUS_NOISE(assert(_global_noise[_epoch].is_clear(), "Dirty decks");)
    -  if (ShenandoahGenerationalAdaptiveTenuring) {
    -    assert(pv1 == nullptr && pv2 == nullptr, "Error, check caller");
    -    // Seed cohort 0 with population that may have been missed during
    -    // regular census.
    -    _global_age_table[_epoch]->add(0u, age0_pop);
     
    -    // Merge data from local age tables into the global age table for the epoch,
    -    // clearing the local tables.
    -    for (uint i = 0; i < _max_workers; i++) {
    -      // age stats
    -      _global_age_table[_epoch]->merge(_local_age_table[i]);
    -      _local_age_table[i]->clear();   // clear for next census
    -      // Merge noise stats
    -      CENSUS_NOISE(_global_noise[_epoch].merge(_local_noise[i]);)
    -      CENSUS_NOISE(_local_noise[i].clear();)
    -    }
    -  } else {
    -    // census during evac
    -    assert(pv1 != nullptr && pv2 != nullptr, "Error, check caller");
    -    _global_age_table[_epoch]->merge(pv1);
    -    _global_age_table[_epoch]->merge(pv2);
    +  // Seed cohort 0 with population that may have been missed during
    +  // regular census.
    +  _global_age_tables[_epoch]->add(0u, age0_pop);
    +
    +  // Merge data from local age tables into the global age table for the epoch,
    +  // clearing the local tables.
    +  for (uint i = 0; i < _max_workers; i++) {
    +    // age stats
    +    _global_age_tables[_epoch]->merge(_local_age_tables[i]);
    +    _local_age_tables[i]->clear();   // clear for next census
    +    // Merge noise stats
    +    CENSUS_NOISE(_global_noise[_epoch].merge(_local_noise[i]);)
    +    CENSUS_NOISE(_local_noise[i].clear();)
       }
     
       update_tenuring_threshold();
    @@ -188,7 +186,7 @@ void ShenandoahAgeCensus::update_census(size_t age0_pop, AgeTable* pv1, AgeTable
     void ShenandoahAgeCensus::reset_global() {
       assert(_epoch < MAX_SNAPSHOTS, "Out of bounds");
       for (uint i = 0; i < MAX_SNAPSHOTS; i++) {
    -    _global_age_table[i]->clear();
    +    _global_age_tables[i]->clear();
         CENSUS_NOISE(_global_noise[i].clear();)
       }
       _epoch = MAX_SNAPSHOTS;
    @@ -198,11 +196,11 @@ void ShenandoahAgeCensus::reset_global() {
     // Reset the local age tables, clearing any partial census.
     void ShenandoahAgeCensus::reset_local() {
       if (!ShenandoahGenerationalAdaptiveTenuring) {
    -    assert(_local_age_table == nullptr, "Error");
    +    assert(_local_age_tables == nullptr, "Error");
         return;
       }
       for (uint i = 0; i < _max_workers; i++) {
    -    _local_age_table[i]->clear();
    +    _local_age_tables[i]->clear();
         CENSUS_NOISE(_local_noise[i].clear();)
       }
     }
    @@ -212,7 +210,7 @@ void ShenandoahAgeCensus::reset_local() {
     bool ShenandoahAgeCensus::is_clear_global() {
       assert(_epoch < MAX_SNAPSHOTS, "Out of bounds");
       for (uint i = 0; i < MAX_SNAPSHOTS; i++) {
    -    bool clear = _global_age_table[i]->is_clear();
    +    bool clear = _global_age_tables[i]->is_clear();
         CENSUS_NOISE(clear |= _global_noise[i].is_clear();)
         if (!clear) {
           return false;
    @@ -224,11 +222,11 @@ bool ShenandoahAgeCensus::is_clear_global() {
     // Is local census information clear?
     bool ShenandoahAgeCensus::is_clear_local() {
       if (!ShenandoahGenerationalAdaptiveTenuring) {
    -    assert(_local_age_table == nullptr, "Error");
    +    assert(_local_age_tables == nullptr, "Error");
         return true;
       }
       for (uint i = 0; i < _max_workers; i++) {
    -    bool clear = _local_age_table[i]->is_clear();
    +    bool clear = _local_age_tables[i]->is_clear();
         CENSUS_NOISE(clear |= _local_noise[i].is_clear();)
         if (!clear) {
           return false;
    @@ -240,7 +238,7 @@ bool ShenandoahAgeCensus::is_clear_local() {
     size_t ShenandoahAgeCensus::get_all_ages(uint snap) {
       assert(snap < MAX_SNAPSHOTS, "Out of bounds");
       size_t pop = 0;
    -  const AgeTable* pv = _global_age_table[snap];
    +  const AgeTable* pv = _global_age_tables[snap];
       for (uint i = 0; i < MAX_COHORTS; i++) {
         pop += pv->sizes[i];
       }
    @@ -260,13 +258,11 @@ void ShenandoahAgeCensus::update_total() {
     #endif // !PRODUCT
     
     void ShenandoahAgeCensus::update_tenuring_threshold() {
    -  if (!ShenandoahGenerationalAdaptiveTenuring) {
    -    _tenuring_threshold[_epoch] = InitialTenuringThreshold;
    -  } else {
    -    uint tt = compute_tenuring_threshold();
    -    assert(tt <= MAX_COHORTS, "Out of bounds");
    -    _tenuring_threshold[_epoch] = tt;
    -  }
    +  assert(ShenandoahGenerationalAdaptiveTenuring, "Only update when adaptive tenuring is enabled");
    +  uint tt = compute_tenuring_threshold();
    +  assert(tt <= MAX_COHORTS, "Out of bounds");
    +  _tenuring_threshold[_epoch] = tt;
    +
       print();
       log_info(gc, age)("New tenuring threshold %zu (min %zu, max %zu)",
         (uintx) _tenuring_threshold[_epoch], ShenandoahGenerationalMinTenuringAge, ShenandoahGenerationalMaxTenuringAge);
    @@ -296,8 +292,8 @@ uint ShenandoahAgeCensus::compute_tenuring_threshold() {
       const uint prev_epoch = cur_epoch > 0  ? cur_epoch - 1 : markWord::max_age;
     
       // Current and previous population vectors in ring
    -  const AgeTable* cur_pv = _global_age_table[cur_epoch];
    -  const AgeTable* prev_pv = _global_age_table[prev_epoch];
    +  const AgeTable* cur_pv = _global_age_tables[cur_epoch];
    +  const AgeTable* prev_pv = _global_age_tables[prev_epoch];
       uint upper_bound = ShenandoahGenerationalMaxTenuringAge;
       const uint prev_tt = previous_tenuring_threshold();
       if (ShenandoahGenerationalCensusIgnoreOlderCohorts && prev_tt > 0) {
    @@ -372,8 +368,8 @@ void ShenandoahAgeCensus::print() {
       const uint cur_epoch = _epoch;
       const uint prev_epoch = cur_epoch > 0 ? cur_epoch - 1: markWord::max_age;
     
    -  const AgeTable* cur_pv = _global_age_table[cur_epoch];
    -  const AgeTable* prev_pv = _global_age_table[prev_epoch];
    +  const AgeTable* cur_pv = _global_age_tables[cur_epoch];
    +  const AgeTable* prev_pv = _global_age_tables[prev_epoch];
     
       const uint tt = tenuring_threshold();
     
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp
    index d862498c69da..39ea4ee9002a 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp
    @@ -97,8 +97,8 @@ struct ShenandoahNoiseStats {
     // once the per-worker data is consolidated into the appropriate population vector
     // per minor collection. The _local_age_table is thus C x N, for N GC workers.
     class ShenandoahAgeCensus: public CHeapObj {
    -  AgeTable** _global_age_table;      // Global age table used for adapting tenuring threshold, one per snapshot
    -  AgeTable** _local_age_table;       // Local scratch age tables to track object ages, one per worker
    +  AgeTable** _global_age_tables;      // Global age tables used for adapting tenuring threshold, one per snapshot
    +  AgeTable** _local_age_tables;       // Local scratch age tables to track object ages, one per worker
     
     #ifdef SHENANDOAH_CENSUS_NOISE
       ShenandoahNoiseStats* _global_noise; // Noise stats, one per snapshot
    @@ -175,7 +175,7 @@ class ShenandoahAgeCensus: public CHeapObj {
       // Return the local age table (population vector) for worker_id.
       // Only used in the case of ShenandoahGenerationalAdaptiveTenuring
       AgeTable* get_local_age_table(uint worker_id) const {
    -    return _local_age_table[worker_id];
    +    return _local_age_tables[worker_id];
       }
     
       // Return the most recently computed tenuring threshold.
    @@ -209,11 +209,7 @@ class ShenandoahAgeCensus: public CHeapObj {
       // age0_pop is the population of Cohort 0 that may have been missed in
       // the regular census during the marking cycle, corresponding to objects
       // allocated when the concurrent marking was in progress.
    -  // Optional parameters, pv1 and pv2 are population vectors that together
    -  // provide object census data (only) for the case when
    -  // ShenandoahGenerationalCensusAtEvac. In this case, the age0_pop
    -  // is 0, because the evacuated objects have all had their ages incremented.
    -  void update_census(size_t age0_pop, AgeTable* pv1 = nullptr, AgeTable* pv2 = nullptr);
    +  void update_census(size_t age0_pop);
     
       // Reset the epoch, clearing accumulated census history
       // Note: this isn't currently used, but reserved for planned
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp
    index a1aaa4fe6ca1..bdd495a86853 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp
    @@ -202,32 +202,8 @@ void ShenandoahControlThread::run_service() {
             heuristics->clear_metaspace_oom();
           }
     
    -      // Commit worker statistics to cycle data
    -      heap->phase_timings()->flush_par_workers_to_cycle();
    -      if (ShenandoahPacing) {
    -        heap->pacer()->flush_stats_to_cycle();
    -      }
    -
    -      // Print GC stats for current cycle
    -      {
    -        LogTarget(Info, gc, stats) lt;
    -        if (lt.is_enabled()) {
    -          ResourceMark rm;
    -          LogStream ls(lt);
    -          heap->phase_timings()->print_cycle_on(&ls);
    -          if (ShenandoahPacing) {
    -            heap->pacer()->print_cycle_on(&ls);
    -          }
    -          if (ShenandoahEvacTracking) {
    -            ShenandoahEvacuationTracker* evac_tracker = heap->evac_tracker();
    -            ShenandoahCycleStats         evac_stats   = evac_tracker->flush_cycle_to_global();
    -            evac_tracker->print_evacuations_on(&ls, &evac_stats.workers, &evac_stats.mutators);
    -          }
    -        }
    -      }
    -
    -      // Commit statistics to globals
    -      heap->phase_timings()->flush_cycle_to_global();
    +      // Manage and print gc stats
    +      heap->process_gc_stats();
     
           // Print Metaspace change following GC (if logging is enabled).
           MetaspaceUtils::print_metaspace_change(meta_sizes);
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.cpp b/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.cpp
    index bc8fad713af4..72d0773eccdf 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.cpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.cpp
    @@ -23,7 +23,6 @@
      *
      */
     
    -#include "gc/shenandoah/shenandoahAgeCensus.hpp"
     #include "gc/shenandoah/shenandoahEvacTracker.hpp"
     #include "gc/shenandoah/shenandoahHeap.inline.hpp"
     #include "gc/shenandoah/shenandoahThreadLocalData.hpp"
    @@ -44,19 +43,6 @@ ShenandoahEvacuationStats::ShenandoahEvacuations* ShenandoahEvacuationStats::get
       return &_old;
     }
     
    -ShenandoahEvacuationStats::ShenandoahEvacuationStats()
    -  : _use_age_table(!ShenandoahGenerationalAdaptiveTenuring),
    -    _age_table(nullptr) {
    -  if (_use_age_table) {
    -    _age_table = new AgeTable(false);
    -  }
    -}
    -
    -AgeTable* ShenandoahEvacuationStats::age_table() const {
    -  assert(_use_age_table, "Don't call");
    -  return _age_table;
    -}
    -
     void ShenandoahEvacuationStats::begin_evacuation(size_t bytes, ShenandoahAffiliation from, ShenandoahAffiliation to) {
       ShenandoahEvacuations* category = get_category(from, to);
       category->_evacuations_attempted++;
    @@ -70,31 +56,16 @@ void ShenandoahEvacuationStats::end_evacuation(size_t bytes, ShenandoahAffiliati
       category->_bytes_completed += bytes;
     }
     
    -void ShenandoahEvacuationStats::record_age(size_t bytes, uint age) {
    -  assert(_use_age_table, "Don't call!");
    -  if (age <= markWord::max_age) { // Filter age sentinel.
    -    _age_table->add(age, bytes >> LogBytesPerWord);
    -  }
    -}
    -
     void ShenandoahEvacuationStats::accumulate(const ShenandoahEvacuationStats* other) {
       _young.accumulate(other->_young);
       _old.accumulate(other->_old);
       _promotion.accumulate(other->_promotion);
    -
    -  if (_use_age_table) {
    -    _age_table->merge(other->age_table());
    -  }
     }
     
     void ShenandoahEvacuationStats::reset() {
       _young.reset();
       _old.reset();
       _promotion.reset();
    -
    -  if (_use_age_table) {
    -    _age_table->clear();
    -  }
     }
     
     void ShenandoahEvacuationStats::ShenandoahEvacuations::print_on(outputStream* st) const {
    @@ -112,10 +83,6 @@ void ShenandoahEvacuationStats::print_on(outputStream* st) const {
         st->print("Promotion: "); _promotion.print_on(st);
         st->print("Old: "); _old.print_on(st);
       }
    -
    -  if (_use_age_table) {
    -    _age_table->print_on(st);
    -  }
     }
     
     void ShenandoahEvacuationTracker::print_global_on(outputStream* st) {
    @@ -125,28 +92,13 @@ void ShenandoahEvacuationTracker::print_global_on(outputStream* st) {
     void ShenandoahEvacuationTracker::print_evacuations_on(outputStream* st,
                                                            ShenandoahEvacuationStats* workers,
                                                            ShenandoahEvacuationStats* mutators) {
    -  if (ShenandoahEvacTracking) {
    -    st->print_cr("Workers: ");
    -    workers->print_on(st);
    -    st->cr();
    -    st->print_cr("Mutators: ");
    -    mutators->print_on(st);
    -    st->cr();
    -  }
    -
    -  ShenandoahHeap* heap = ShenandoahHeap::heap();
    -  if (heap->mode()->is_generational()) {
    -    AgeTable young_region_ages(false);
    -    for (uint i = 0; i < heap->num_regions(); ++i) {
    -      ShenandoahHeapRegion* r = heap->get_region(i);
    -      if (r->is_young()) {
    -        young_region_ages.add(r->age(), r->get_live_data_words());
    -      }
    -    }
    -    st->print("Young regions: ");
    -    young_region_ages.print_on(st);
    -    st->cr();
    -  }
    +  assert(ShenandoahEvacTracking, "Only when evac tracking is enabled");
    +  st->print_cr("Workers: ");
    +  workers->print_on(st);
    +  st->cr();
    +  st->print_cr("Mutators: ");
    +  mutators->print_on(st);
    +  st->cr();
     }
     
     class ShenandoahStatAggregator : public ThreadClosure {
    @@ -173,15 +125,6 @@ ShenandoahCycleStats ShenandoahEvacuationTracker::flush_cycle_to_global() {
       _mutators_global.accumulate(&mutators);
       _workers_global.accumulate(&workers);
     
    -  if (!ShenandoahGenerationalAdaptiveTenuring) {
    -    // Ingest mutator & worker collected population vectors into the heap's
    -    // global census data, and use it to compute an appropriate tenuring threshold
    -    // for use in the next cycle.
    -    // The first argument is used for any age 0 cohort population that we may otherwise have
    -    // missed during the census. This is non-zero only when census happens at marking.
    -    ShenandoahGenerationalHeap::heap()->age_census()->update_census(0, mutators.age_table(), workers.age_table());
    -  }
    -
       return {workers, mutators};
     }
     
    @@ -192,7 +135,3 @@ void ShenandoahEvacuationTracker::begin_evacuation(Thread* thread, size_t bytes,
     void ShenandoahEvacuationTracker::end_evacuation(Thread* thread, size_t bytes, ShenandoahAffiliation from, ShenandoahAffiliation to) {
       ShenandoahThreadLocalData::end_evacuation(thread, bytes, from, to);
     }
    -
    -void ShenandoahEvacuationTracker::record_age(Thread* thread, size_t bytes, uint age) {
    -  ShenandoahThreadLocalData::record_age(thread, bytes, age);
    -}
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.hpp b/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.hpp
    index e5d7a7fec944..6e1182680a55 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.hpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.hpp
    @@ -66,20 +66,12 @@ class ShenandoahEvacuationStats : public CHeapObj {
       ShenandoahEvacuations _old;
       ShenandoahEvacuations _promotion;
     
    -  bool      _use_age_table;
    -  AgeTable* _age_table;
    -
      public:
    -  ShenandoahEvacuationStats();
    -
    -  AgeTable* age_table() const;
    -
       // Record that the current thread is attempting to copy this many bytes.
       void begin_evacuation(size_t bytes, ShenandoahAffiliation from, ShenandoahAffiliation to);
     
       // Record that the current thread has completed copying this many bytes.
       void end_evacuation(size_t bytes, ShenandoahAffiliation from, ShenandoahAffiliation to);
    -  void record_age(size_t bytes, uint age);
     
       void print_on(outputStream* st) const;
       void accumulate(const ShenandoahEvacuationStats* other);
    @@ -106,7 +98,6 @@ class ShenandoahEvacuationTracker : public CHeapObj {
       // Multiple threads may attempt to evacuate the same object, but only the successful thread will end the evacuation.
       // Evacuations that were begun, but not ended are considered 'abandoned'.
       void end_evacuation(Thread* thread, size_t bytes, ShenandoahAffiliation from, ShenandoahAffiliation to);
    -  void record_age(Thread* thread, size_t bytes, uint age);
     
       void print_global_on(outputStream* st);
       void print_evacuations_on(outputStream* st,
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp
    index 2555f73d018d..cd144cf05712 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp
    @@ -210,6 +210,24 @@ ShenandoahGenerationalControlThread::GCMode ShenandoahGenerationalControlThread:
       return request.generation->is_old() ? servicing_old : concurrent_normal;
     }
     
    +void ShenandoahGenerationalControlThread::maybe_print_young_region_ages() const {
    +  LogTarget(Debug, gc, age) lt;
    +  if (lt.is_enabled()) {
    +    LogStream ls(lt);
    +    AgeTable young_region_ages(false);
    +    for (uint i = 0; i < _heap->num_regions(); ++i) {
    +      const ShenandoahHeapRegion* r = _heap->get_region(i);
    +      if (r->is_young()) {
    +        young_region_ages.add(r->age(), r->get_live_data_words());
    +      }
    +    }
    +
    +    ls.print("Young regions: ");
    +    young_region_ages.print_on(&ls);
    +    ls.cr();
    +  }
    +}
    +
     void ShenandoahGenerationalControlThread::maybe_set_aging_cycle() {
       if (_age_period-- == 0) {
         _heap->set_aging_cycle(true);
    @@ -304,7 +322,11 @@ void ShenandoahGenerationalControlThread::run_gc_cycle(const ShenandoahGCRequest
         _heap->global_generation()->heuristics()->clear_metaspace_oom();
       }
     
    -  process_phase_timings();
    +  // Manage and print gc stats
    +  _heap->process_gc_stats();
    +
    +  // Print table for young region ages if log is enabled
    +  maybe_print_young_region_ages();
     
       // Print Metaspace change following GC (if logging is enabled).
       MetaspaceUtils::print_metaspace_change(meta_sizes);
    @@ -328,35 +350,6 @@ void ShenandoahGenerationalControlThread::run_gc_cycle(const ShenandoahGCRequest
         gc_mode_name(gc_mode()), GCCause::to_string(request.cause), request.generation->name(), GCCause::to_string(_heap->cancelled_cause()));
     }
     
    -void ShenandoahGenerationalControlThread::process_phase_timings() const {
    -  // Commit worker statistics to cycle data
    -  _heap->phase_timings()->flush_par_workers_to_cycle();
    -  if (ShenandoahPacing) {
    -    _heap->pacer()->flush_stats_to_cycle();
    -  }
    -
    -  ShenandoahEvacuationTracker* evac_tracker = _heap->evac_tracker();
    -  ShenandoahCycleStats         evac_stats   = evac_tracker->flush_cycle_to_global();
    -
    -  // Print GC stats for current cycle
    -  {
    -    LogTarget(Info, gc, stats) lt;
    -    if (lt.is_enabled()) {
    -      ResourceMark rm;
    -      LogStream ls(lt);
    -      _heap->phase_timings()->print_cycle_on(&ls);
    -      evac_tracker->print_evacuations_on(&ls, &evac_stats.workers,
    -                                              &evac_stats.mutators);
    -      if (ShenandoahPacing) {
    -        _heap->pacer()->print_cycle_on(&ls);
    -      }
    -    }
    -  }
    -
    -  // Commit statistics to globals
    -  _heap->phase_timings()->flush_cycle_to_global();
    -}
    -
     // Young and old concurrent cycles are initiated by the regulator. Implicit
     // and explicit GC requests are handled by the controller thread and always
     // run a global cycle (which is concurrent by default, but may be overridden
    @@ -432,7 +425,7 @@ void ShenandoahGenerationalControlThread::service_concurrent_old_cycle(const She
           set_gc_mode(bootstrapping_old);
           young_generation->set_old_gen_task_queues(old_generation->task_queues());
           service_concurrent_cycle(young_generation, request.cause, true);
    -      process_phase_timings();
    +      _heap->process_gc_stats();
           if (_heap->cancelled_gc()) {
             // Young generation bootstrap cycle has failed. Concurrent mark for old generation
             // is going to resume after degenerated bootstrap cycle completes.
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.hpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.hpp
    index 1586205742a7..6a4f5bde5789 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.hpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.hpp
    @@ -129,9 +129,6 @@ class ShenandoahGenerationalControlThread: public ShenandoahController {
       // Returns true if the old generation marking was interrupted to allow a young cycle.
       bool preempt_old_marking(ShenandoahGeneration* generation);
     
    -  // Flushes cycle timings to global timings and prints the phase timings for the last completed cycle.
    -  void process_phase_timings() const;
    -
       // Set the gc mode and post a notification if it has changed. The overloaded variant should be used
       // when the _control_lock is already held.
       void set_gc_mode(GCMode new_mode);
    @@ -160,6 +157,9 @@ class ShenandoahGenerationalControlThread: public ShenandoahController {
       GCMode prepare_for_allocation_failure_gc(ShenandoahGCRequest &request);
       GCMode prepare_for_explicit_gc(ShenandoahGCRequest &request) const;
       GCMode prepare_for_concurrent_gc(const ShenandoahGCRequest &request) const;
    +
    +  // Print table for young region ages if log is enabled
    +  void maybe_print_young_region_ages() const;
     };
     
     #endif // SHARE_GC_SHENANDOAH_SHENANDOAHGENERATIONALCONTROLTHREAD_HPP
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp
    index e3ed9c5eebb6..96969806aada 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp
    @@ -360,11 +360,6 @@ oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, Shena
           // When copying to the old generation above, we don't care
           // about recording object age in the census stats.
           assert(target_gen == YOUNG_GENERATION, "Error");
    -      // We record this census only when simulating pre-adaptive tenuring behavior, or
    -      // when we have been asked to record the census at evacuation rather than at mark
    -      if (!ShenandoahGenerationalAdaptiveTenuring) {
    -        evac_tracker()->record_age(thread, size * HeapWordSize, ShenandoahHeap::get_object_age(copy_val));
    -      }
         }
         shenandoah_assert_correct(nullptr, copy_val);
         return copy_val;
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp
    index 6d48e59e0c61..18872afb1c55 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp
    @@ -1465,6 +1465,31 @@ void ShenandoahHeap::print_heap_regions_on(outputStream* st) const {
       }
     }
     
    +void ShenandoahHeap::process_gc_stats() const {
    +  // Commit worker statistics to cycle data
    +  phase_timings()->flush_par_workers_to_cycle();
    +
    +  if (ShenandoahPacing) {
    +    pacer()->flush_stats_to_cycle();
    +  }
    +
    +  // Print GC stats for current cycle
    +  LogTarget(Info, gc, stats) lt;
    +  if (lt.is_enabled()) {
    +    ResourceMark rm;
    +    LogStream ls(lt);
    +    phase_timings()->print_cycle_on(&ls);
    +    if (ShenandoahEvacTracking) {
    +      ShenandoahCycleStats  evac_stats = evac_tracker()->flush_cycle_to_global();
    +      evac_tracker()->print_evacuations_on(&ls, &evac_stats.workers,
    +                                               &evac_stats.mutators);
    +    }
    +  }
    +
    +  // Commit statistics to globals
    +  phase_timings()->flush_cycle_to_global();
    +}
    +
     size_t ShenandoahHeap::trash_humongous_region_at(ShenandoahHeapRegion* start) const {
       assert(start->is_humongous_start(), "reclaim regions starting with the first one");
       assert(!start->has_live(), "liveness must be zero");
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp
    index 8e81c500dda0..f8d609d61527 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp
    @@ -204,10 +204,13 @@ class ShenandoahHeap : public CollectedHeap {
       void initialize_serviceability() override;
     
       void print_heap_on(outputStream* st)         const override;
    -  void print_gc_on(outputStream *st)           const override;
    +  void print_gc_on(outputStream* st)           const override;
       void print_tracing_info()                    const override;
       void print_heap_regions_on(outputStream* st) const;
     
    +  // Flushes cycle timings to global timings and prints the phase timings for the last completed cycle.
    +  void process_gc_stats() const;
    +
       void stop() override;
     
       void prepare_for_verify() override;
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.hpp b/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.hpp
    index 098e20a72ec8..8a3132bdf171 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.hpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.hpp
    @@ -168,10 +168,6 @@ class ShenandoahThreadLocalData {
         data(thread)->_evacuation_stats->end_evacuation(bytes, from, to);
       }
     
    -  static void record_age(Thread* thread, size_t bytes, uint age) {
    -    data(thread)->_evacuation_stats->record_age(bytes, age);
    -  }
    -
       static ShenandoahEvacuationStats* evacuation_stats(Thread* thread) {
         return data(thread)->_evacuation_stats;
       }
    
    From d0b6d9f60451a68774712fa1ee04dbef8c7e30e9 Mon Sep 17 00:00:00 2001
    From: Patrick Fontanilla 
    Date: Fri, 24 Apr 2026 14:49:48 +0000
    Subject: [PATCH 146/168] 8368015: Shenandoah: fix error in computation of
     average allocation rate
    
    Reviewed-by: phh
    Backport-of: a2870d6b4985a68beb3fe3bf6622e6245e9a82ec
    ---
     .../shenandoahAdaptiveHeuristics.cpp          | 33 ++++++++++++++-----
     .../shenandoahAdaptiveHeuristics.hpp          | 27 ++++++++++-----
     .../heuristics/shenandoahHeuristics.hpp       |  5 +++
     .../heuristics/shenandoahSpaceInfo.hpp        |  4 +++
     .../gc/shenandoah/shenandoahGeneration.cpp    |  4 +--
     .../gc/shenandoah/shenandoahGeneration.hpp    |  2 +-
     .../share/gc/shenandoah/shenandoahHeap.cpp    | 23 ++++++++++---
     7 files changed, 74 insertions(+), 24 deletions(-)
    
    diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.cpp
    index 144773908caa..6498f0acdb67 100644
    --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.cpp
    +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.cpp
    @@ -240,13 +240,14 @@ bool ShenandoahAdaptiveHeuristics::should_start_gc() {
       log_debug(gc)("should_start_gc? available: %zu, soft_max_capacity: %zu"
                     ", allocated: %zu", available, capacity, allocated);
     
    +  // Track allocation rate even if we decide to start a cycle for other reasons.
    +  double rate = _allocation_rate.sample(allocated);
    +
       if (_start_gc_is_pending) {
         log_trigger("GC start is already pending");
         return true;
       }
     
    -  // Track allocation rate even if we decide to start a cycle for other reasons.
    -  double rate = _allocation_rate.sample(allocated);
       _last_trigger = OTHER;
     
       size_t min_threshold = min_free_threshold();
    @@ -360,16 +361,32 @@ ShenandoahAllocationRate::ShenandoahAllocationRate() :
       _rate_avg(int(ShenandoahAdaptiveSampleSizeSeconds * ShenandoahAdaptiveSampleFrequencyHz), ShenandoahAdaptiveDecayFactor) {
     }
     
    +double ShenandoahAllocationRate::force_sample(size_t allocated, size_t &unaccounted_bytes_allocated) {
    +  const double MinSampleTime = 0.002;    // Do not sample if time since last update is less than 2 ms
    +  double now = os::elapsedTime();
    +  double time_since_last_update = now -_last_sample_time;
    +  if (time_since_last_update < MinSampleTime) {
    +    unaccounted_bytes_allocated = allocated - _last_sample_value;
    +    _last_sample_value = 0;
    +    return 0.0;
    +  } else {
    +    double rate = instantaneous_rate(now, allocated);
    +    _rate.add(rate);
    +    _rate_avg.add(_rate.avg());
    +    _last_sample_time = now;
    +    _last_sample_value = allocated;
    +    unaccounted_bytes_allocated = 0;
    +    return rate;
    +  }
    +}
    +
     double ShenandoahAllocationRate::sample(size_t allocated) {
       double now = os::elapsedTime();
       double rate = 0.0;
       if (now - _last_sample_time > _interval_sec) {
    -    if (allocated >= _last_sample_value) {
    -      rate = instantaneous_rate(now, allocated);
    -      _rate.add(rate);
    -      _rate_avg.add(_rate.avg());
    -    }
    -
    +    rate = instantaneous_rate(now, allocated);
    +    _rate.add(rate);
    +    _rate_avg.add(_rate.avg());
         _last_sample_time = now;
         _last_sample_value = allocated;
       }
    diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.hpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.hpp
    index 68e540960c79..014a4d991311 100644
    --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.hpp
    +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.hpp
    @@ -37,10 +37,12 @@ class ShenandoahAllocationRate : public CHeapObj {
       explicit ShenandoahAllocationRate();
       void allocation_counter_reset();
     
    +  double force_sample(size_t allocated, size_t &unaccounted_bytes_allocated);
       double sample(size_t allocated);
     
       double upper_bound(double sds) const;
       bool is_spiking(double rate, double threshold) const;
    +
      private:
     
       double instantaneous_rate(double time, size_t allocated) const;
    @@ -71,18 +73,18 @@ class ShenandoahAdaptiveHeuristics : public ShenandoahHeuristics {
     
       virtual void choose_collection_set_from_regiondata(ShenandoahCollectionSet* cset,
                                                          RegionData* data, size_t size,
    -                                                     size_t actual_free);
    +                                                     size_t actual_free) override;
     
    -  void record_cycle_start();
    -  void record_success_concurrent();
    -  void record_success_degenerated();
    -  void record_success_full();
    +  virtual void record_cycle_start() override;
    +  virtual void record_success_concurrent() override;
    +  virtual void record_success_degenerated() override;
    +  virtual void record_success_full() override;
     
    -  virtual bool should_start_gc();
    +  virtual bool should_start_gc() override;
     
    -  virtual const char* name()     { return "Adaptive"; }
    -  virtual bool is_diagnostic()   { return false; }
    -  virtual bool is_experimental() { return false; }
    +  virtual const char* name() override     { return "Adaptive"; }
    +  virtual bool is_diagnostic() override   { return false; }
    +  virtual bool is_experimental() override { return false; }
     
      private:
       // These are used to adjust the margin of error and the spike threshold
    @@ -150,6 +152,13 @@ class ShenandoahAdaptiveHeuristics : public ShenandoahHeuristics {
         _last_trigger = trigger_type;
         ShenandoahHeuristics::accept_trigger();
       }
    +
    +public:
    +  virtual size_t force_alloc_rate_sample(size_t bytes_allocated) override {
    +    size_t unaccounted_bytes;
    +    _allocation_rate.force_sample(bytes_allocated, unaccounted_bytes);
    +    return unaccounted_bytes;
    +  }
     };
     
     #endif // SHARE_GC_SHENANDOAH_HEURISTICS_SHENANDOAHADAPTIVEHEURISTICS_HPP
    diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.hpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.hpp
    index 33230c0cfda5..3cd2cb1d171d 100644
    --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.hpp
    +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.hpp
    @@ -241,6 +241,11 @@ class ShenandoahHeuristics : public CHeapObj {
     
       double elapsed_cycle_time() const;
     
    +  virtual size_t force_alloc_rate_sample(size_t bytes_allocated) {
    +    // do nothing
    +    return 0;
    +  }
    +
       // Format prefix and emit log message indicating a GC cycle hs been triggered
       void log_trigger(const char* fmt, ...) ATTRIBUTE_PRINTF(2, 3);
     };
    diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahSpaceInfo.hpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahSpaceInfo.hpp
    index 2131f95b413d..7fb44c7b71ba 100644
    --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahSpaceInfo.hpp
    +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahSpaceInfo.hpp
    @@ -41,6 +41,10 @@ class ShenandoahSpaceInfo {
       virtual size_t soft_available() const = 0;
       virtual size_t available() const = 0;
       virtual size_t used() const = 0;
    +
    +  // Return an approximation of the bytes allocated since GC start.  The value returned is monotonically non-decreasing
    +  // in time within each GC cycle.  For certain GC cycles, the value returned may include some bytes allocated before
    +  // the start of the current GC cycle.
       virtual size_t bytes_allocated_since_gc_start() const = 0;
     };
     
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp
    index 31f6fd6f44a2..63953d9e718d 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp
    @@ -154,8 +154,8 @@ size_t ShenandoahGeneration::bytes_allocated_since_gc_start() const {
       return Atomic::load(&_bytes_allocated_since_gc_start);
     }
     
    -void ShenandoahGeneration::reset_bytes_allocated_since_gc_start() {
    -  Atomic::store(&_bytes_allocated_since_gc_start, (size_t)0);
    +void ShenandoahGeneration::reset_bytes_allocated_since_gc_start(size_t initial_bytes_allocated) {
    +  Atomic::store(&_bytes_allocated_since_gc_start, initial_bytes_allocated);
     }
     
     void ShenandoahGeneration::increase_allocated(size_t bytes) {
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.hpp b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.hpp
    index 2b7aca342dad..b5a14fcc8622 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.hpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.hpp
    @@ -142,7 +142,7 @@ class ShenandoahGeneration : public CHeapObj, public ShenandoahSpaceInfo {
       size_t soft_available() const override;
     
       size_t bytes_allocated_since_gc_start() const override;
    -  void reset_bytes_allocated_since_gc_start();
    +  void reset_bytes_allocated_since_gc_start(size_t initial_bytes_allocated);
       void increase_allocated(size_t bytes);
     
       // These methods change the capacity of the generation by adding or subtracting the given number of bytes from the current
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp
    index 18872afb1c55..1611916fa873 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp
    @@ -2364,12 +2364,27 @@ address ShenandoahHeap::in_cset_fast_test_addr() {
     }
     
     void ShenandoahHeap::reset_bytes_allocated_since_gc_start() {
    +  // It is important to force_alloc_rate_sample() before the associated generation's bytes_allocated has been reset.
    +  // Note that there is no lock to prevent additional alloations between sampling bytes_allocated_since_gc_start() and
    +  // reset_bytes_allocated_since_gc_start().  If additional allocations happen, they will be ignored in the average
    +  // allocation rate computations.  This effect is considered to be be negligible.
    +
    +  // unaccounted_bytes is the bytes not accounted for by our forced sample.  If the sample interval is too short,
    +  // the "forced sample" will not happen, and any recently allocated bytes are "unaccounted for".  We pretend these
    +  // bytes are allocated after the start of subsequent gc.
    +  size_t unaccounted_bytes;
       if (mode()->is_generational()) {
    -    young_generation()->reset_bytes_allocated_since_gc_start();
    -    old_generation()->reset_bytes_allocated_since_gc_start();
    +    size_t bytes_allocated = young_generation()->bytes_allocated_since_gc_start();
    +    unaccounted_bytes = young_generation()->heuristics()->force_alloc_rate_sample(bytes_allocated);
    +    young_generation()->reset_bytes_allocated_since_gc_start(unaccounted_bytes);
    +    unaccounted_bytes = 0;
    +    old_generation()->reset_bytes_allocated_since_gc_start(unaccounted_bytes);
    +  } else {
    +    size_t bytes_allocated = global_generation()->bytes_allocated_since_gc_start();
    +    // Single-gen Shenandoah uses global heuristics.
    +    unaccounted_bytes = heuristics()->force_alloc_rate_sample(bytes_allocated);
       }
    -
    -  global_generation()->reset_bytes_allocated_since_gc_start();
    +  global_generation()->reset_bytes_allocated_since_gc_start(unaccounted_bytes);
     }
     
     void ShenandoahHeap::set_degenerated_gc_in_progress(bool in_progress) {
    
    From 9dfe29df3d253294370dd46b00c4a77c87ddc36d Mon Sep 17 00:00:00 2001
    From: Goetz Lindenmaier 
    Date: Mon, 27 Apr 2026 06:16:00 +0000
    Subject: [PATCH 147/168] 8373579: Problem list
     compiler/runtime/Test7196199.java
    
    Backport-of: a05d5d2514c835f2bfeaf7a8c7df0ac241f0177f
    ---
     test/hotspot/jtreg/ProblemList.txt | 1 +
     1 file changed, 1 insertion(+)
    
    diff --git a/test/hotspot/jtreg/ProblemList.txt b/test/hotspot/jtreg/ProblemList.txt
    index 7f671554bab3..04e5c09d182d 100644
    --- a/test/hotspot/jtreg/ProblemList.txt
    +++ b/test/hotspot/jtreg/ProblemList.txt
    @@ -49,6 +49,7 @@ compiler/cpuflags/TestAESIntrinsicsOnSupportedConfig.java 8190680 generic-all
     
     compiler/runtime/Test8168712.java#with-dtrace 8211769,8211771 generic-ppc64,generic-ppc64le,linux-s390x
     compiler/runtime/Test8168712.java#without-dtrace 8211769,8211771 generic-ppc64,generic-ppc64le,linux-s390x
    +compiler/runtime/Test7196199.java 8365196 windows-x64
     compiler/loopopts/TestUnreachableInnerLoop.java 8288981 linux-s390x
     
     compiler/c2/Test8004741.java 8235801 generic-all
    
    From 075c00a22458b95bbb3da348a6e52be25c7bf011 Mon Sep 17 00:00:00 2001
    From: Goetz Lindenmaier 
    Date: Mon, 27 Apr 2026 06:19:51 +0000
    Subject: [PATCH 148/168] 8382242: JFR: Metadata reconstruction invalidates
     ConstantMap for java.lang.String
    
    Backport-of: ca643010a27292b99c6f2182764bc7cd4ac93b02
    ---
     .../jfr/internal/consumer/ParserFactory.java  |   9 +-
     ...aReconstructionWithRetainedStringPool.java | 106 ++++++++++++++++++
     2 files changed, 112 insertions(+), 3 deletions(-)
     create mode 100644 test/jdk/jdk/jfr/api/consumer/streaming/TestMetadataReconstructionWithRetainedStringPool.java
    
    diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ParserFactory.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ParserFactory.java
    index e7dd234ca8d0..c973dadb3b7f 100644
    --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ParserFactory.java
    +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ParserFactory.java
    @@ -132,9 +132,12 @@ private Parser createPrimitiveParser(Type type, boolean event) throws IOExceptio
                 case "short" -> new ShortParser();
                 case "byte" ->  new ByteParser();
                 case "java.lang.String" -> {
    -                ConstantMap pool = new ConstantMap(ObjectFactory.create(type, timeConverter), type);
    -                ConstantLookup lookup = new ConstantLookup(pool, type);
    -                constantLookups.put(type.getId(), lookup);
    +                ConstantLookup lookup = constantLookups.get(type.getId());
    +                if (lookup == null) {
    +                    ConstantMap pool = new ConstantMap(ObjectFactory.create(type, timeConverter), type);
    +                    lookup = new ConstantLookup(pool, type);
    +                    constantLookups.put(type.getId(), lookup);
    +                }
                     yield new StringParser(lookup, event);
                 }
                 default ->  throw new IOException("Unknown primitive type " + type.getName());
    diff --git a/test/jdk/jdk/jfr/api/consumer/streaming/TestMetadataReconstructionWithRetainedStringPool.java b/test/jdk/jdk/jfr/api/consumer/streaming/TestMetadataReconstructionWithRetainedStringPool.java
    new file mode 100644
    index 000000000000..8bbadc6db290
    --- /dev/null
    +++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestMetadataReconstructionWithRetainedStringPool.java
    @@ -0,0 +1,106 @@
    +/*
    + * 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 jdk.jfr.api.consumer.streaming;
    +
    +import java.util.concurrent.atomic.AtomicInteger;
    +import java.util.concurrent.CountDownLatch;
    +
    +import jdk.jfr.Event;
    +import jdk.jfr.consumer.RecordingStream;
    +
    +/**
    + * @test
    + * @summary Test that it is possible to register new metadata in a new segment while retaining the string pool.
    + * @requires vm.flagless
    + * @requires vm.hasJFR
    + * @library /test/lib
    + * @run main/othervm jdk.jfr.api.consumer.streaming.TestMetadataReconstructionWithRetainedStringPool
    + */
    +public class TestMetadataReconstructionWithRetainedStringPool {
    +    /// Minimum string length required to trigger StringPool usage.
    +    /// Mirrors `jdk.jfr.internal.StringPool.MIN_LIMIT`.
    +    private static final int STRING_POOL_MIN_LIMIT = 16;
    +    private static final int EXPECTED_EVENTS = 3;
    +
    +    // Condition 1: String length > STRING_POOL_MIN_LIMIT triggers CONSTANT_POOL encoding.
    +    private static final String TEXT = "a".repeat(STRING_POOL_MIN_LIMIT + 1);;
    +
    +    static final class EventA extends Event {
    +        String text = TEXT;
    +    }
    +
    +    static final class EventB extends Event {
    +        String text = TEXT;
    +    }
    +
    +    public static void main(String... args) throws InterruptedException {
    +        var aEventsPosted = new CountDownLatch(1);
    +        var readyToPostEventB = new CountDownLatch(1);
    +        var remaining = new CountDownLatch(EXPECTED_EVENTS);
    +
    +        try (var rs = new RecordingStream()) {
    +            rs.onEvent(e -> {
    +                String textValue = e.getValue("text");
    +                if (textValue == null) {
    +                    throw new RuntimeException("e.getValue(\"text\") returned null");
    +                }
    +                remaining.countDown();
    +                System.out.printf("Event #%d [%s]: text=%s%n",
    +                        EXPECTED_EVENTS - remaining.getCount(),
    +                        e.getEventType().getName(),
    +                        textValue);
    +            });
    +
    +            rs.onFlush(() -> {
    +                if (aEventsPosted.getCount() == 0) {
    +                    readyToPostEventB.countDown();
    +                }
    +            });
    +
    +            rs.startAsync();
    +
    +            // Condition 2: Two distinct event types are required.
    +            //              First, load EventA as the initial event type and emit its first event.
    +            //              This first event looks into the StringPool pre-cache. Although the
    +            //              string length qualifies for pooling, because it isn't pre-cached,
    +            //              the first event encodes the string inline.
    +            //              The second event finds the string in the pre-cache and adds it to the
    +            //              pool. A constant pool ID to the pooled string is encoded in the event.
    +            //
    +            new EventA().commit();
    +            new EventA().commit();
    +            aEventsPosted.countDown();
    +
    +            // Condition 3: Wait for JFR flush.
    +            //              The default flush period is ~1 second.
    +            readyToPostEventB.await();
    +
    +            // Load the second event type, EventB, AFTER the flush segment containing the two events of type EventA.
    +            // A new metadata description will be constructed, and we verify that the StringPool added in the previous
    +            // segment is still available for the EventB string pool reference to be resolved correctly.
    +            new EventB().commit();
    +            remaining.await();
    +        }
    +    }
    +}
    
    From ac803ee520d45228ed5b5d26868123c0713c798e Mon Sep 17 00:00:00 2001
    From: Goetz Lindenmaier 
    Date: Mon, 27 Apr 2026 06:21:50 +0000
    Subject: [PATCH 149/168] 8382090: Remove .rej and .orig from .gitignore
    
    Backport-of: 35f623b61279a448e2ddcbb6e1ba9eebe4dee8e2
    ---
     .gitignore | 2 --
     1 file changed, 2 deletions(-)
    
    diff --git a/.gitignore b/.gitignore
    index 9145a9fa67b0..2d82e0d943c1 100644
    --- a/.gitignore
    +++ b/.gitignore
    @@ -23,5 +23,3 @@ NashornProfile.txt
     /.gdbinit
     /.lldbinit
     **/core.[0-9]*
    -*.rej
    -*.orig
    
    From 13cca2ead6064af355d785010dc289fedfc594f8 Mon Sep 17 00:00:00 2001
    From: Martin Doerr 
    Date: Mon, 27 Apr 2026 11:10:54 +0000
    Subject: [PATCH 150/168] 8383161: [PPC64]
     MachCallDynamicJavaNode::ret_addr_offset() needs adaptation for COH
    
    Reviewed-by: rrich
    Backport-of: d5d5334a14aa71a69b0d2726adf460550d0bc5a3
    ---
     src/hotspot/cpu/ppc/macroAssembler_ppc.cpp | 20 ++++++++------------
     src/hotspot/cpu/ppc/macroAssembler_ppc.hpp |  2 +-
     src/hotspot/cpu/ppc/ppc.ad                 |  2 +-
     3 files changed, 10 insertions(+), 14 deletions(-)
    
    diff --git a/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp b/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp
    index 33faa555d4fb..bcb2df203a2c 100644
    --- a/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp
    +++ b/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp
    @@ -3436,23 +3436,19 @@ void MacroAssembler::store_klass_gap(Register dst_oop, Register val) {
       }
     }
     
    -int MacroAssembler::instr_size_for_decode_klass_not_null() {
    +int MacroAssembler::instr_size_for_load_klass() {
       static int computed_size = -1;
     
       // Not yet computed?
       if (computed_size == -1) {
     
    -    if (!UseCompressedClassPointers) {
    -      computed_size = 0;
    -    } else {
    -      // Determine by scratch emit.
    -      ResourceMark rm;
    -      int code_size = 8 * BytesPerInstWord;
    -      CodeBuffer cb("decode_klass_not_null scratch buffer", code_size, 0);
    -      MacroAssembler* a = new MacroAssembler(&cb);
    -      a->decode_klass_not_null(R11_scratch1);
    -      computed_size = a->offset();
    -    }
    +    // Determine by scratch emit.
    +    ResourceMark rm;
    +    int code_size = 16 * BytesPerInstWord;
    +    CodeBuffer cb("load_klass scratch buffer", code_size, 0);
    +    MacroAssembler* a = new MacroAssembler(&cb);
    +    a->load_klass(R11_scratch1, R11_scratch1);
    +    computed_size = a->offset();
       }
     
       return computed_size;
    diff --git a/src/hotspot/cpu/ppc/macroAssembler_ppc.hpp b/src/hotspot/cpu/ppc/macroAssembler_ppc.hpp
    index 471ebb7459a1..9f272e96bcfc 100644
    --- a/src/hotspot/cpu/ppc/macroAssembler_ppc.hpp
    +++ b/src/hotspot/cpu/ppc/macroAssembler_ppc.hpp
    @@ -818,7 +818,7 @@ class MacroAssembler: public Assembler {
                                MacroAssembler::PreservationLevel preservation_level);
       void load_method_holder(Register holder, Register method);
     
    -  static int instr_size_for_decode_klass_not_null();
    +  static int instr_size_for_load_klass();
       void decode_klass_not_null(Register dst, Register src = noreg);
       Register encode_klass_not_null(Register dst, Register src = noreg);
     
    diff --git a/src/hotspot/cpu/ppc/ppc.ad b/src/hotspot/cpu/ppc/ppc.ad
    index 6f785d965bf8..c4a8172cf927 100644
    --- a/src/hotspot/cpu/ppc/ppc.ad
    +++ b/src/hotspot/cpu/ppc/ppc.ad
    @@ -1187,7 +1187,7 @@ int MachCallDynamicJavaNode::ret_addr_offset() {
         assert(vtable_index == Method::invalid_vtable_index, "correct sentinel value");
         return 12;
       } else {
    -    return 24 + MacroAssembler::instr_size_for_decode_klass_not_null();
    +    return 20 + MacroAssembler::instr_size_for_load_klass();
       }
     }
     
    
    From 81a2e3b6f68e51be312e80392f42c0e39c047bb9 Mon Sep 17 00:00:00 2001
    From: SendaoYan 
    Date: Mon, 27 Apr 2026 12:44:09 +0000
    Subject: [PATCH 151/168] 8374322: TestMemoryWithSubgroups.java fails
     Permission denied
    
    Backport-of: 18ac17236f29367ef2ee502436560e1832e28013
    ---
     .../docker/TestMemoryWithSubgroups.java       | 74 +++++++------------
     1 file changed, 28 insertions(+), 46 deletions(-)
    
    diff --git a/test/hotspot/jtreg/containers/docker/TestMemoryWithSubgroups.java b/test/hotspot/jtreg/containers/docker/TestMemoryWithSubgroups.java
    index 3b901765ee9b..621aed5ee855 100644
    --- a/test/hotspot/jtreg/containers/docker/TestMemoryWithSubgroups.java
    +++ b/test/hotspot/jtreg/containers/docker/TestMemoryWithSubgroups.java
    @@ -65,66 +65,48 @@ public static void main(String[] args) throws Exception {
             Common.prepareWhiteBox();
             DockerTestUtils.buildJdkContainerImage(imageName);
     
    -        if ("cgroupv1".equals(metrics.getProvider())) {
    -            try {
    -                testMemoryLimitSubgroupV1("200m", "100m", "104857600", false);
    -                testMemoryLimitSubgroupV1("1g", "500m", "524288000", false);
    -                testMemoryLimitSubgroupV1("200m", "100m", "104857600", true);
    -                testMemoryLimitSubgroupV1("1g", "500m", "524288000", true);
    -            } finally {
    -                DockerTestUtils.removeDockerImage(imageName);
    -            }
    -        } else if ("cgroupv2".equals(metrics.getProvider())) {
    -            try {
    -                testMemoryLimitSubgroupV2("200m", "100m", "104857600", false);
    -                testMemoryLimitSubgroupV2("1g", "500m", "524288000", false);
    -                testMemoryLimitSubgroupV2("200m", "100m", "104857600", true);
    -                testMemoryLimitSubgroupV2("1g", "500m", "524288000", true);
    -            } finally {
    -                DockerTestUtils.removeDockerImage(imageName);
    -            }
    -        } else {
    +        String provider = metrics.getProvider();
    +        if (!"cgroupv1".equals(provider) && !"cgroupv2".equals(provider)) {
                 throw new SkippedException("Metrics are from neither cgroup v1 nor v2, skipped for now.");
             }
    -    }
    -
    -    private static void testMemoryLimitSubgroupV1(String containerMemorySize, String valueToSet, String expectedValue, boolean privateNamespace)
    -            throws Exception {
    -
    -        Common.logNewTestCase("Cgroup V1 subgroup memory limit: " + valueToSet);
     
    -        DockerRunOptions opts = new DockerRunOptions(imageName, "sh", "-c");
    -        opts.javaOpts = new ArrayList<>();
    -        opts.appendTestJavaOptions = false;
    -        opts.addDockerOpts("--privileged")
    -            .addDockerOpts("--cgroupns=" + (privateNamespace ? "private" : "host"))
    -            .addDockerOpts("--memory", containerMemorySize);
    -        opts.addClassOptions("mkdir -p /sys/fs/cgroup/memory/test ; " +
    -            "echo " + valueToSet + " > /sys/fs/cgroup/memory/test/memory.limit_in_bytes ; " +
    -            "echo $$ > /sys/fs/cgroup/memory/test/cgroup.procs ; " +
    -            "/jdk/bin/java -Xlog:os+container=trace -version");
    -
    -        Common.run(opts)
    -            .shouldMatch("Lowest limit was:.*" + expectedValue);
    +        try {
    +            testMemoryLimitSubgroup(provider, "200m", "100m", "104857600", false);
    +            testMemoryLimitSubgroup(provider, "1g", "500m", "524288000", false);
    +            testMemoryLimitSubgroup(provider, "200m", "100m", "104857600", true);
    +            testMemoryLimitSubgroup(provider, "1g", "500m", "524288000", true);
    +        } finally {
    +            DockerTestUtils.removeDockerImage(imageName);
    +        }
         }
     
    -    private static void testMemoryLimitSubgroupV2(String containerMemorySize, String valueToSet, String expectedValue, boolean privateNamespace)
    +    private static void testMemoryLimitSubgroup(String cgroupVersion, String containerMemorySize,
    +                                                String valueToSet, String expectedValue, boolean privateNamespace)
                 throws Exception {
     
    -        Common.logNewTestCase("Cgroup V2 subgroup memory limit: " + valueToSet);
    +        final String upperVersion = "cgroupv1".equals(cgroupVersion) ? "V1" : "V2";
    +        Common.logNewTestCase("Cgroup " + upperVersion + " subgroup memory limit: " + valueToSet);
     
             DockerRunOptions opts = new DockerRunOptions(imageName, "sh", "-c");
             opts.javaOpts = new ArrayList<>();
             opts.appendTestJavaOptions = false;
             opts.addDockerOpts("--privileged")
    +            .addDockerOpts("--user", "root")
                 .addDockerOpts("--cgroupns=" + (privateNamespace ? "private" : "host"))
                 .addDockerOpts("--memory", containerMemorySize);
    -        opts.addClassOptions("mkdir -p /sys/fs/cgroup/memory/test ; " +
    -            "echo $$ > /sys/fs/cgroup/memory/test/cgroup.procs ; " +
    -            "echo '+memory' > /sys/fs/cgroup/cgroup.subtree_control ; " +
    -            "echo '+memory' > /sys/fs/cgroup/memory/cgroup.subtree_control ; " +
    -            "echo " + valueToSet + " > /sys/fs/cgroup/memory/test/memory.max ; " +
    -            "/jdk/bin/java -Xlog:os+container=trace -version");
    +        if ("cgroupv1".equals(cgroupVersion)) {
    +            opts.addClassOptions("mkdir -p /sys/fs/cgroup/memory/test ; " +
    +                "echo " + valueToSet + " > /sys/fs/cgroup/memory/test/memory.limit_in_bytes ; " +
    +                "echo $$ > /sys/fs/cgroup/memory/test/cgroup.procs ; " +
    +                "/jdk/bin/java -Xlog:os+container=trace -version");
    +        } else {
    +            opts.addClassOptions("mkdir -p /sys/fs/cgroup/memory/test ; " +
    +                "echo $$ > /sys/fs/cgroup/memory/test/cgroup.procs ; " +
    +                "echo '+memory' > /sys/fs/cgroup/cgroup.subtree_control ; " +
    +                "echo '+memory' > /sys/fs/cgroup/memory/cgroup.subtree_control ; " +
    +                "echo " + valueToSet + " > /sys/fs/cgroup/memory/test/memory.max ; " +
    +                "/jdk/bin/java -Xlog:os+container=trace -version");
    +        }
     
             Common.run(opts)
                 .shouldMatch("Lowest limit was:.*" + expectedValue);
    
    From 11826bab451af8a73707feb8ddcf2ff5abb02a3e Mon Sep 17 00:00:00 2001
    From: Gui Cao 
    Date: Mon, 27 Apr 2026 15:16:48 +0000
    Subject: [PATCH 152/168] 8382878: RISC-V: Missing
     InlineSkippedInstructionsCounter in ZGC barriers stubs
    
    Backport-of: d1b60e23dbfc347ae6a2538efcb16559022606c1
    ---
     src/hotspot/cpu/riscv/gc/z/zBarrierSetAssembler_riscv.cpp | 2 ++
     src/hotspot/cpu/riscv/gc/z/z_riscv.ad                     | 4 +++-
     2 files changed, 5 insertions(+), 1 deletion(-)
    
    diff --git a/src/hotspot/cpu/riscv/gc/z/zBarrierSetAssembler_riscv.cpp b/src/hotspot/cpu/riscv/gc/z/zBarrierSetAssembler_riscv.cpp
    index 09dea62b6d18..a294fba8e1ff 100644
    --- a/src/hotspot/cpu/riscv/gc/z/zBarrierSetAssembler_riscv.cpp
    +++ b/src/hotspot/cpu/riscv/gc/z/zBarrierSetAssembler_riscv.cpp
    @@ -713,6 +713,7 @@ class ZSetupArguments {
     #define __ masm->
     
     void ZBarrierSetAssembler::generate_c2_load_barrier_stub(MacroAssembler* masm, ZLoadBarrierStubC2* stub) const {
    +  Assembler::InlineSkippedInstructionsCounter skipped_counter(masm);
       BLOCK_COMMENT("ZLoadBarrierStubC2");
     
       // Stub entry
    @@ -732,6 +733,7 @@ void ZBarrierSetAssembler::generate_c2_load_barrier_stub(MacroAssembler* masm, Z
     }
     
     void ZBarrierSetAssembler::generate_c2_store_barrier_stub(MacroAssembler* masm, ZStoreBarrierStubC2* stub) const {
    +  Assembler::InlineSkippedInstructionsCounter skipped_counter(masm);
       BLOCK_COMMENT("ZStoreBarrierStubC2");
     
       // Stub entry
    diff --git a/src/hotspot/cpu/riscv/gc/z/z_riscv.ad b/src/hotspot/cpu/riscv/gc/z/z_riscv.ad
    index fd9a1d43afc6..4a17e512ae3c 100644
    --- a/src/hotspot/cpu/riscv/gc/z/z_riscv.ad
    +++ b/src/hotspot/cpu/riscv/gc/z/z_riscv.ad
    @@ -1,5 +1,5 @@
     //
    -// Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved.
    +// Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved.
     // Copyright (c) 2020, 2023, Huawei Technologies Co., Ltd. All rights reserved.
     // DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
     //
    @@ -57,6 +57,7 @@ static void check_color(MacroAssembler* masm, Register ref, bool on_non_strong,
     }
     
     static void z_load_barrier(MacroAssembler* masm, const MachNode* node, Address ref_addr, Register ref, Register tmp) {
    +  Assembler::InlineSkippedInstructionsCounter skipped_counter(masm);
       const bool on_non_strong =
           ((node->barrier_data() & ZBarrierWeak) != 0) ||
           ((node->barrier_data() & ZBarrierPhantom) != 0);
    @@ -78,6 +79,7 @@ static void z_load_barrier(MacroAssembler* masm, const MachNode* node, Address r
     }
     
     static void z_store_barrier(MacroAssembler* masm, const MachNode* node, Address ref_addr, Register rnew_zaddress, Register rnew_zpointer, Register tmp, bool is_atomic) {
    +  Assembler::InlineSkippedInstructionsCounter skipped_counter(masm);
       if (node->barrier_data() == ZBarrierElided) {
         z_color(masm, node, rnew_zpointer, rnew_zaddress, tmp);
       } else {
    
    From 20d3ea4643857e41ab7a5f8784feabc322a16d60 Mon Sep 17 00:00:00 2001
    From: William Kemper 
    Date: Mon, 27 Apr 2026 17:24:08 +0000
    Subject: [PATCH 153/168] 8368501: Shenandoah: GC progress evaluation does not
     use generation
    
    Backport-of: 501fa2041a77139a9ac42fef69f28b1fd50fee65
    ---
     .../gc/shenandoah/shenandoahDegeneratedGC.cpp |  7 +-
     .../share/gc/shenandoah/shenandoahFullGC.cpp  | 11 +--
     .../share/gc/shenandoah/shenandoahMetrics.cpp | 79 +++++++------------
     .../share/gc/shenandoah/shenandoahMetrics.hpp | 18 ++---
     4 files changed, 42 insertions(+), 73 deletions(-)
    
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahDegeneratedGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahDegeneratedGC.cpp
    index 8a0eba5cd9f2..0f84d95d02a8 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahDegeneratedGC.cpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahDegeneratedGC.cpp
    @@ -115,8 +115,7 @@ void ShenandoahDegenGC::op_degenerated() {
       }
     #endif
     
    -  ShenandoahMetricsSnapshot metrics;
    -  metrics.snap_before();
    +  ShenandoahMetricsSnapshot metrics(heap->free_set());
     
       switch (_degen_point) {
         // The cases below form the Duff's-like device: it describes the actual GC cycle,
    @@ -308,10 +307,8 @@ void ShenandoahDegenGC::op_degenerated() {
         Universe::verify();
       }
     
    -  metrics.snap_after();
    -
       // Decide if this cycle made good progress, and, if not, should it upgrade to a full GC.
    -  const bool progress = metrics.is_good_progress(_generation);
    +  const bool progress = metrics.is_good_progress();
       ShenandoahCollectorPolicy* policy = heap->shenandoah_policy();
       policy->record_degenerated(_generation->is_young(), _abbreviated, progress);
       if (progress) {
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahFullGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahFullGC.cpp
    index 27ff45e67de1..557a3847bab6 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahFullGC.cpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahFullGC.cpp
    @@ -104,21 +104,18 @@ void ShenandoahFullGC::entry_full(GCCause::Cause cause) {
     }
     
     void ShenandoahFullGC::op_full(GCCause::Cause cause) {
    -  ShenandoahMetricsSnapshot metrics;
    -  metrics.snap_before();
    +  ShenandoahHeap* const heap = ShenandoahHeap::heap();
    +
    +  ShenandoahMetricsSnapshot metrics(heap->free_set());
     
       // Perform full GC
       do_it(cause);
     
    -  ShenandoahHeap* const heap = ShenandoahHeap::heap();
    -
       if (heap->mode()->is_generational()) {
         ShenandoahGenerationalFullGC::handle_completion(heap);
       }
     
    -  metrics.snap_after();
    -
    -  if (metrics.is_good_progress(heap->global_generation())) {
    +  if (metrics.is_good_progress()) {
         heap->notify_gc_progress();
       } else {
         // Nothing to do. Tell the allocation path that we have failed to make
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahMetrics.cpp b/src/hotspot/share/gc/shenandoah/shenandoahMetrics.cpp
    index dc666a34c595..d774a8dba427 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahMetrics.cpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahMetrics.cpp
    @@ -28,69 +28,45 @@
     #include "gc/shenandoah/shenandoahHeapRegion.hpp"
     #include "gc/shenandoah/shenandoahMetrics.hpp"
     
    -ShenandoahMetricsSnapshot::ShenandoahMetricsSnapshot() {
    -  _heap = ShenandoahHeap::heap();
    +ShenandoahMetricsSnapshot::ShenandoahMetricsSnapshot(ShenandoahFreeSet* free_set)
    +  : _free_set(free_set)
    +  , _used_before(free_set->used())
    +  , _if_before(free_set->internal_fragmentation())
    +  , _ef_before(free_set->external_fragmentation()) {
     }
     
    -void ShenandoahMetricsSnapshot::snap_before() {
    -  _used_before = _heap->used();
    -  _if_before = _heap->free_set()->internal_fragmentation();
    -  _ef_before = _heap->free_set()->external_fragmentation();
    -}
    -void ShenandoahMetricsSnapshot::snap_after() {
    -  _used_after = _heap->used();
    -  _if_after = _heap->free_set()->internal_fragmentation();
    -  _ef_after = _heap->free_set()->external_fragmentation();
    -}
    -
    -// For degenerated GC, generation is Young in generational mode, Global in non-generational mode.
    -// For full GC, generation is always Global.
    -//
    -// Note that the size of the chosen collection set is proportional to the relevant generation's collection set.
    -// Note also that the generation size may change following selection of the collection set, as a side effect
    -// of evacuation.  Evacuation may promote objects, causing old to grow and young to shrink.  Or this may be a
    -// mixed evacuation.  When old regions are evacuated, this typically allows young to expand.  In all of these
    -// various scenarios, the purpose of asking is_good_progress() is to determine if there is enough memory available
    -// within young generation to justify making an attempt to perform a concurrent collection.  For this reason, we'll
    -// use the current size of the generation (which may not be different than when the collection set was chosen) to
    -// assess how much free memory we require in order to consider the most recent GC to have had good progress.
    -
    -bool ShenandoahMetricsSnapshot::is_good_progress(ShenandoahGeneration* generation) {
    +bool ShenandoahMetricsSnapshot::is_good_progress() const {
       // Under the critical threshold?
    -  ShenandoahFreeSet* free_set = _heap->free_set();
    -  size_t free_actual   = free_set->available();
    +  const size_t free_actual = _free_set->available();
       assert(free_actual != ShenandoahFreeSet::FreeSetUnderConstruction, "Avoid this race");
     
    -  // ShenandoahCriticalFreeThreshold is expressed as a percentage.  We multiple this percentage by 1/100th
    -  // of the generation capacity to determine whether the available memory within the generation exceeds the
    -  // critical threshold.
    -  size_t free_expected = (ShenandoahHeap::heap()->soft_max_capacity() / 100) * ShenandoahCriticalFreeThreshold;
    -
    -  bool prog_free = free_actual >= free_expected;
    -  log_info(gc, ergo)("%s progress for free space: %zu%s, need %zu%s",
    -                     prog_free ? "Good" : "Bad",
    -                     byte_size_in_proper_unit(free_actual),   proper_unit_for_byte_size(free_actual),
    -                     byte_size_in_proper_unit(free_expected), proper_unit_for_byte_size(free_expected));
    +  // ShenandoahCriticalFreeThreshold is expressed as a percentage.  We multiply this percentage by 1/100th
    +  // of the soft max capacity to determine whether the available memory within the mutator partition of the
    +  // freeset exceeds the critical threshold.
    +  const size_t free_expected = (ShenandoahHeap::heap()->soft_max_capacity() / 100) * ShenandoahCriticalFreeThreshold;
    +  const bool prog_free = free_actual >= free_expected;
    +  log_info(gc, ergo)("%s progress for free space: " PROPERFMT ", need " PROPERFMT,
    +                     prog_free ? "Good" : "Bad", PROPERFMTARGS(free_actual), PROPERFMTARGS(free_expected));
       if (!prog_free) {
         return false;
       }
     
       // Freed up enough?
    -  size_t progress_actual   = (_used_before > _used_after) ? _used_before - _used_after : 0;
    -  size_t progress_expected = ShenandoahHeapRegion::region_size_bytes();
    -  bool prog_used = progress_actual >= progress_expected;
    -  log_info(gc, ergo)("%s progress for used space: %zu%s, need %zu%s",
    -                     prog_used ? "Good" : "Bad",
    -                     byte_size_in_proper_unit(progress_actual),   proper_unit_for_byte_size(progress_actual),
    -                     byte_size_in_proper_unit(progress_expected), proper_unit_for_byte_size(progress_expected));
    +  const size_t used_after = _free_set->used();
    +  const size_t progress_actual   = (_used_before > used_after) ? _used_before - used_after : 0;
    +  const size_t progress_expected = ShenandoahHeapRegion::region_size_bytes();
    +  const bool prog_used = progress_actual >= progress_expected;
    +  log_info(gc, ergo)("%s progress for used space: " PROPERFMT ", need " PROPERFMT,
    +                     prog_used ? "Good" : "Bad", PROPERFMTARGS(progress_actual), PROPERFMTARGS(progress_expected));
       if (prog_used) {
         return true;
       }
     
       // Internal fragmentation is down?
    -  double if_actual = _if_before - _if_after;
    -  double if_expected = 0.01; // 1% should be enough
    -  bool prog_if = if_actual >= if_expected;
    +  const double if_after = _free_set->internal_fragmentation();
    +  const double if_actual = _if_before - if_after;
    +  const double if_expected = 0.01; // 1% should be enough
    +  const bool prog_if = if_actual >= if_expected;
       log_info(gc, ergo)("%s progress for internal fragmentation: %.1f%%, need %.1f%%",
                          prog_if ? "Good" : "Bad",
                          if_actual * 100, if_expected * 100);
    @@ -99,9 +75,10 @@ bool ShenandoahMetricsSnapshot::is_good_progress(ShenandoahGeneration* generatio
       }
     
       // External fragmentation is down?
    -  double ef_actual = _ef_before - _ef_after;
    -  double ef_expected = 0.01; // 1% should be enough
    -  bool prog_ef = ef_actual >= ef_expected;
    +  const double ef_after = _free_set->external_fragmentation();
    +  const double ef_actual = _ef_before - ef_after;
    +  const double ef_expected = 0.01; // 1% should be enough
    +  const bool prog_ef = ef_actual >= ef_expected;
       log_info(gc, ergo)("%s progress for external fragmentation: %.1f%%, need %.1f%%",
                          prog_ef ? "Good" : "Bad",
                          ef_actual * 100, ef_expected * 100);
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahMetrics.hpp b/src/hotspot/share/gc/shenandoah/shenandoahMetrics.hpp
    index 20d8ebfd5957..c554a0653867 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahMetrics.hpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahMetrics.hpp
    @@ -25,22 +25,20 @@
     #ifndef SHARE_GC_SHENANDOAH_SHENANDOAHMETRICS_HPP
     #define SHARE_GC_SHENANDOAH_SHENANDOAHMETRICS_HPP
     
    -#include "gc/shenandoah/shenandoahHeap.hpp"
    +#include "gc/shenandoah/shenandoahFreeSet.hpp"
     
     class ShenandoahMetricsSnapshot : public StackObj {
     private:
    -  ShenandoahHeap* _heap;
    -  size_t _used_before, _used_after;
    -  double _if_before, _if_after;
    -  double _ef_before, _ef_after;
    +  ShenandoahFreeSet* _free_set;
    +  size_t _used_before;
    +  double _if_before;
    +  double _ef_before;
     
     public:
    -  ShenandoahMetricsSnapshot();
    +  explicit ShenandoahMetricsSnapshot(ShenandoahFreeSet* free_set);
     
    -  void snap_before();
    -  void snap_after();
    -
    -  bool is_good_progress(ShenandoahGeneration *generation);
    +  // Decide if the GC made "good" progress (i.e., reduced fragmentation, freed up sufficient memory).
    +  bool is_good_progress() const;
     };
     
     #endif // SHARE_GC_SHENANDOAH_SHENANDOAHMETRICS_HPP
    
    From 23d302c9fe6304786d05d500e4c58fcf9f5a6826 Mon Sep 17 00:00:00 2001
    From: William Kemper 
    Date: Mon, 27 Apr 2026 17:24:40 +0000
    Subject: [PATCH 154/168] 8370521: GenShen: Various code cleanup related to
     promotion
    
    Backport-of: 6347f10bf1dd3959cc1f2aba32e72ca8d9d56e82
    ---
     .../heuristics/shenandoahOldHeuristics.cpp    | 10 +++++--
     .../gc/shenandoah/shenandoahCollectionSet.cpp |  6 ++--
     .../gc/shenandoah/shenandoahCollectionSet.hpp | 12 ++++----
     .../shenandoahCollectionSet.inline.hpp        |  6 ++--
     .../gc/shenandoah/shenandoahGeneration.cpp    | 30 +++++++++++--------
     .../gc/shenandoah/shenandoahGeneration.hpp    |  2 +-
     .../shenandoah/shenandoahGenerationalHeap.cpp | 21 ++++++-------
     .../share/gc/shenandoah/shenandoahHeap.cpp    |  2 +-
     .../share/gc/shenandoah/shenandoahOldGC.cpp   |  6 ----
     .../gc/shenandoah/shenandoahOldGeneration.cpp |  5 ++++
     .../share/gc/shenandoah/shenandoahTrace.cpp   |  6 ++--
     .../gc/shenandoah/shenandoah_globals.hpp      | 16 +++++-----
     12 files changed, 65 insertions(+), 57 deletions(-)
    
    diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.cpp
    index 2361a50e76dc..e963bcc35bb4 100644
    --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.cpp
    +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.cpp
    @@ -141,7 +141,7 @@ bool ShenandoahOldHeuristics::prime_collection_set(ShenandoahCollectionSet* coll
         // If region r is evacuated to fragmented memory (to free memory within a partially used region), then we need
         // to decrease the capacity of the fragmented memory by the scaled loss.
     
    -    size_t live_data_for_evacuation = r->get_live_data_bytes();
    +    const size_t live_data_for_evacuation = r->get_live_data_bytes();
         size_t lost_available = r->free();
     
         if ((lost_available > 0) && (excess_fragmented_available > 0)) {
    @@ -169,7 +169,9 @@ bool ShenandoahOldHeuristics::prime_collection_set(ShenandoahCollectionSet* coll
           // We were not able to account for the lost free memory within fragmented memory, so we need to take this
           // allocation out of unfragmented memory.  Unfragmented memory does not need to account for loss of free.
           if (live_data_for_evacuation > unfragmented_available) {
    -        // There is not room to evacuate this region or any that come after it in within the candidates array.
    +        // There is no room to evacuate this region or any that come after it in within the candidates array.
    +        log_debug(gc, cset)("Not enough unfragmented memory (%zu) to hold evacuees (%zu) from region: (%zu)",
    +                            unfragmented_available, live_data_for_evacuation, r->index());
             break;
           } else {
             unfragmented_available -= live_data_for_evacuation;
    @@ -187,7 +189,9 @@ bool ShenandoahOldHeuristics::prime_collection_set(ShenandoahCollectionSet* coll
             evacuation_need = 0;
           }
           if (evacuation_need > unfragmented_available) {
    -        // There is not room to evacuate this region or any that come after it in within the candidates array.
    +        // There is no room to evacuate this region or any that come after it in within the candidates array.
    +        log_debug(gc, cset)("Not enough unfragmented memory (%zu) to hold evacuees (%zu) from region: (%zu)",
    +                            unfragmented_available, live_data_for_evacuation, r->index());
             break;
           } else {
             unfragmented_available -= evacuation_need;
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.cpp b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.cpp
    index 19a9367d6882..2d808cef834b 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.cpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.cpp
    @@ -225,9 +225,9 @@ void ShenandoahCollectionSet::summarize(size_t total_garbage, size_t immediate_g
                      count());
     
         if (garbage() > 0) {
    -      const size_t young_evac_bytes = get_young_bytes_reserved_for_evacuation();
    -      const size_t promote_evac_bytes = get_young_bytes_to_be_promoted();
    -      const size_t old_evac_bytes = get_old_bytes_reserved_for_evacuation();
    +      const size_t young_evac_bytes = get_live_bytes_in_untenurable_regions();
    +      const size_t promote_evac_bytes = get_live_bytes_in_tenurable_regions();
    +      const size_t old_evac_bytes = get_live_bytes_in_old_regions();
           const size_t total_evac_bytes = young_evac_bytes + promote_evac_bytes + old_evac_bytes;
           ls.print_cr("Evacuation Targets: "
                       "YOUNG: " PROPERFMT ", " "PROMOTE: " PROPERFMT ", " "OLD: " PROPERFMT ", " "TOTAL: " PROPERFMT,
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.hpp b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.hpp
    index d4a590a3d89a..a1b77baa2d3c 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.hpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.hpp
    @@ -109,14 +109,14 @@ class ShenandoahCollectionSet : public CHeapObj {
       // Prints a summary of the collection set when gc+ergo=info
       void summarize(size_t total_garbage, size_t immediate_garbage, size_t immediate_regions) const;
     
    -  // Returns the amount of live bytes in young regions in the collection set. It is not known how many of these bytes will be promoted.
    -  inline size_t get_young_bytes_reserved_for_evacuation() const;
    +  // Returns the amount of live bytes in young regions with an age below the tenuring threshold.
    +  inline size_t get_live_bytes_in_untenurable_regions() const;
     
       // Returns the amount of live bytes in old regions in the collection set.
    -  inline size_t get_old_bytes_reserved_for_evacuation() const;
    +  inline size_t get_live_bytes_in_old_regions() const;
     
    -  // Returns the amount of live bytes in young regions with an age above the tenuring threshold.
    -  inline size_t get_young_bytes_to_be_promoted() const;
    +  // Returns the amount of live bytes in young regions with an age at or above the tenuring threshold.
    +  inline size_t get_live_bytes_in_tenurable_regions() const;
     
       // Returns the amount of free bytes in young regions in the collection set.
       size_t get_young_available_bytes_collected() const { return _young_available_bytes_collected; }
    @@ -125,7 +125,7 @@ class ShenandoahCollectionSet : public CHeapObj {
       inline size_t get_old_garbage() const;
     
       bool is_preselected(size_t region_idx) {
    -    assert(_preselected_regions != nullptr, "Missing etsablish after abandon");
    +    assert(_preselected_regions != nullptr, "Missing establish after abandon");
         return _preselected_regions[region_idx];
       }
     
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.inline.hpp
    index 4adcec4fbb55..3ff5f2f81d70 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.inline.hpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.inline.hpp
    @@ -54,15 +54,15 @@ bool ShenandoahCollectionSet::is_in_loc(void* p) const {
       return _biased_cset_map[index] == 1;
     }
     
    -size_t ShenandoahCollectionSet::get_old_bytes_reserved_for_evacuation() const {
    +size_t ShenandoahCollectionSet::get_live_bytes_in_old_regions() const {
       return _old_bytes_to_evacuate;
     }
     
    -size_t ShenandoahCollectionSet::get_young_bytes_reserved_for_evacuation() const {
    +size_t ShenandoahCollectionSet::get_live_bytes_in_untenurable_regions() const {
       return _young_bytes_to_evacuate - _young_bytes_to_promote;
     }
     
    -size_t ShenandoahCollectionSet::get_young_bytes_to_be_promoted() const {
    +size_t ShenandoahCollectionSet::get_live_bytes_in_tenurable_regions() const {
       return _young_bytes_to_promote;
     }
     
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp
    index 63953d9e718d..7b425a8fd46b 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp
    @@ -385,11 +385,11 @@ void ShenandoahGeneration::adjust_evacuation_budgets(ShenandoahHeap* const heap,
       // available that results from a decrease in memory consumed by old evacuation is not necessarily available to be loaned
       // to young-gen.
     
    -  size_t region_size_bytes = ShenandoahHeapRegion::region_size_bytes();
    +  const size_t region_size_bytes = ShenandoahHeapRegion::region_size_bytes();
       ShenandoahOldGeneration* const old_generation = heap->old_generation();
       ShenandoahYoungGeneration* const young_generation = heap->young_generation();
     
    -  size_t old_evacuated = collection_set->get_old_bytes_reserved_for_evacuation();
    +  const size_t old_evacuated = collection_set->get_live_bytes_in_old_regions();
       size_t old_evacuated_committed = (size_t) (ShenandoahOldEvacWaste * double(old_evacuated));
       size_t old_evacuation_reserve = old_generation->get_evacuation_reserve();
     
    @@ -402,14 +402,15 @@ void ShenandoahGeneration::adjust_evacuation_budgets(ShenandoahHeap* const heap,
         // Leave old_evac_reserve as previously configured
       } else if (old_evacuated_committed < old_evacuation_reserve) {
         // This happens if the old-gen collection consumes less than full budget.
    +    log_debug(gc, cset)("Shrinking old evac reserve to match old_evac_commited: " PROPERFMT, PROPERFMTARGS(old_evacuated_committed));
         old_evacuation_reserve = old_evacuated_committed;
         old_generation->set_evacuation_reserve(old_evacuation_reserve);
       }
     
    -  size_t young_advance_promoted = collection_set->get_young_bytes_to_be_promoted();
    +  size_t young_advance_promoted = collection_set->get_live_bytes_in_tenurable_regions();
       size_t young_advance_promoted_reserve_used = (size_t) (ShenandoahPromoEvacWaste * double(young_advance_promoted));
     
    -  size_t young_evacuated = collection_set->get_young_bytes_reserved_for_evacuation();
    +  size_t young_evacuated = collection_set->get_live_bytes_in_untenurable_regions();
       size_t young_evacuated_reserve_used = (size_t) (ShenandoahEvacWaste * double(young_evacuated));
     
       size_t total_young_available = young_generation->available_with_reserve();
    @@ -527,7 +528,7 @@ inline void assert_no_in_place_promotions() {
     // that this allows us to more accurately budget memory to hold the results of evacuation.  Memory for evacuation
     // of aged regions must be reserved in the old generation.  Memory for evacuation of all other regions must be
     // reserved in the young generation.
    -size_t ShenandoahGeneration::select_aged_regions(size_t old_available) {
    +size_t ShenandoahGeneration::select_aged_regions(const size_t old_promotion_reserve) {
     
       // There should be no regions configured for subsequent in-place-promotions carried over from the previous cycle.
       assert_no_in_place_promotions();
    @@ -540,7 +541,6 @@ size_t ShenandoahGeneration::select_aged_regions(size_t old_available) {
     
       const size_t pip_used_threshold = (ShenandoahHeapRegion::region_size_bytes() * ShenandoahGenerationalMinPIPUsage) / 100;
     
    -  size_t old_consumed = 0;
       size_t promo_potential = 0;
       size_t candidates = 0;
     
    @@ -563,7 +563,7 @@ size_t ShenandoahGeneration::select_aged_regions(size_t old_available) {
         }
         if (heap->is_tenurable(r)) {
           if ((r->garbage() < old_garbage_threshold) && (r->used() > pip_used_threshold)) {
    -        // We prefer to promote this region in place because is has a small amount of garbage and a large usage.
    +        // We prefer to promote this region in place because it has a small amount of garbage and a large usage.
             HeapWord* tams = ctx->top_at_mark_start(r);
             HeapWord* original_top = r->top();
             if (!heap->is_concurrent_old_mark_in_progress() && tams == original_top) {
    @@ -623,17 +623,21 @@ size_t ShenandoahGeneration::select_aged_regions(size_t old_available) {
         // Note that we keep going even if one region is excluded from selection.
         // Subsequent regions may be selected if they have smaller live data.
       }
    +
    +  log_info(gc, ergo)("Promotion potential of aged regions with sufficient garbage: " PROPERFMT, PROPERFMTARGS(promo_potential));
    +
       // Sort in increasing order according to live data bytes.  Note that candidates represents the number of regions
       // that qualify to be promoted by evacuation.
    +  size_t old_consumed = 0;
       if (candidates > 0) {
         size_t selected_regions = 0;
         size_t selected_live = 0;
         QuickSort::sort(sorted_regions, candidates, compare_by_aged_live);
         for (size_t i = 0; i < candidates; i++) {
           ShenandoahHeapRegion* const region = sorted_regions[i]._region;
    -      size_t region_live_data = sorted_regions[i]._live_data;
    -      size_t promotion_need = (size_t) (region_live_data * ShenandoahPromoEvacWaste);
    -      if (old_consumed + promotion_need <= old_available) {
    +      const size_t region_live_data = sorted_regions[i]._live_data;
    +      const size_t promotion_need = (size_t) (region_live_data * ShenandoahPromoEvacWaste);
    +      if (old_consumed + promotion_need <= old_promotion_reserve) {
             old_consumed += promotion_need;
             candidate_regions_for_promotion_by_copy[region->index()] = true;
             selected_regions++;
    @@ -647,9 +651,9 @@ size_t ShenandoahGeneration::select_aged_regions(size_t old_available) {
           // We keep going even if one region is excluded from selection because we need to accumulate all eligible
           // regions that are not preselected into promo_potential
         }
    -    log_debug(gc)("Preselected %zu regions containing %zu live bytes,"
    -                 " consuming: %zu of budgeted: %zu",
    -                 selected_regions, selected_live, old_consumed, old_available);
    +    log_debug(gc, ergo)("Preselected %zu regions containing " PROPERFMT " live data,"
    +                        " consuming: " PROPERFMT " of budgeted: " PROPERFMT,
    +                        selected_regions, PROPERFMTARGS(selected_live), PROPERFMTARGS(old_consumed), PROPERFMTARGS(old_promotion_reserve));
       }
     
       heap->old_generation()->set_pad_for_promote_in_place(promote_in_place_pad);
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.hpp b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.hpp
    index b5a14fcc8622..c49deef561b0 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.hpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.hpp
    @@ -97,7 +97,7 @@ class ShenandoahGeneration : public CHeapObj, public ShenandoahSpaceInfo {
       // regions, which are marked in the preselected_regions() indicator
       // array of the heap's collection set, which should be initialized
       // to false.
    -  size_t select_aged_regions(size_t old_available);
    +  size_t select_aged_regions(size_t old_promotion_reserve);
     
       size_t available(size_t capacity) const;
     
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp
    index 96969806aada..316ac61dc352 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp
    @@ -110,7 +110,6 @@ void ShenandoahGenerationalHeap::initialize_heuristics() {
       _generation_sizer.heap_size_changed(max_capacity());
       size_t initial_capacity_young = _generation_sizer.max_young_size();
       size_t max_capacity_young = _generation_sizer.max_young_size();
    -  size_t initial_capacity_old = max_capacity() - max_capacity_young;
       size_t max_capacity_old = max_capacity() - initial_capacity_young;
     
       _young_generation = new ShenandoahYoungGeneration(max_workers(), max_capacity_young);
    @@ -267,6 +266,7 @@ oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, Shena
                   // the requested object does not fit within the current plab but the plab still has an "abundance" of memory,
                   // where abundance is defined as >= ShenGenHeap::plab_min_size().  In the former case, we try shrinking the
                   // desired PLAB size to the minimum and retry PLAB allocation to avoid cascading of shared memory allocations.
    +              // Shrinking the desired PLAB size may allow us to eke out a small PLAB while staying beneath evacuation reserve.
                   if (plab->words_remaining() < plab_min_size()) {
                     ShenandoahThreadLocalData::set_plab_size(thread, plab_min_size());
                     copy = allocate_from_plab(thread, size, is_promotion);
    @@ -436,9 +436,8 @@ inline HeapWord* ShenandoahGenerationalHeap::allocate_from_plab(Thread* thread,
     
     // Establish a new PLAB and allocate size HeapWords within it.
     HeapWord* ShenandoahGenerationalHeap::allocate_from_plab_slow(Thread* thread, size_t size, bool is_promotion) {
    -  // New object should fit the PLAB size
    -
       assert(mode()->is_generational(), "PLABs only relevant to generational GC");
    +
       const size_t plab_min_size = this->plab_min_size();
       // PLABs are aligned to card boundaries to avoid synchronization with concurrent
       // allocations in other PLABs.
    @@ -451,23 +450,24 @@ HeapWord* ShenandoahGenerationalHeap::allocate_from_plab_slow(Thread* thread, si
       }
     
       // Expand aggressively, doubling at each refill in this epoch, ceiling at plab_max_size()
    -  size_t future_size = MIN2(cur_size * 2, plab_max_size());
    +  const size_t future_size = MIN2(cur_size * 2, plab_max_size());
       // Doubling, starting at a card-multiple, should give us a card-multiple. (Ceiling and floor
       // are card multiples.)
       assert(is_aligned(future_size, CardTable::card_size_in_words()), "Card multiple by construction, future_size: %zu"
    -          ", card_size: %zu, cur_size: %zu, max: %zu",
    -         future_size, (size_t) CardTable::card_size_in_words(), cur_size, plab_max_size());
    +          ", card_size: %u, cur_size: %zu, max: %zu",
    +         future_size, CardTable::card_size_in_words(), cur_size, plab_max_size());
     
       // Record new heuristic value even if we take any shortcut. This captures
       // the case when moderately-sized objects always take a shortcut. At some point,
       // heuristics should catch up with them.  Note that the requested cur_size may
       // not be honored, but we remember that this is the preferred size.
    -  log_debug(gc, free)("Set new PLAB size: %zu", future_size);
    +  log_debug(gc, plab)("Set next PLAB refill size: %zu bytes", future_size * HeapWordSize);
       ShenandoahThreadLocalData::set_plab_size(thread, future_size);
    +
       if (cur_size < size) {
         // The PLAB to be allocated is still not large enough to hold the object. Fall back to shared allocation.
         // This avoids retiring perfectly good PLABs in order to represent a single large object allocation.
    -    log_debug(gc, free)("Current PLAB size (%zu) is too small for %zu", cur_size, size);
    +    log_debug(gc, plab)("Current PLAB size (%zu) is too small for %zu", cur_size * HeapWordSize, size * HeapWordSize);
         return nullptr;
       }
     
    @@ -553,6 +553,7 @@ void ShenandoahGenerationalHeap::retire_plab(PLAB* plab, Thread* thread) {
       ShenandoahThreadLocalData::reset_plab_promoted(thread);
       ShenandoahThreadLocalData::set_plab_actual_size(thread, 0);
       if (not_promoted > 0) {
    +    log_debug(gc, plab)("Retire PLAB, unexpend unpromoted: %zu", not_promoted * HeapWordSize);
         old_generation()->unexpend_promoted(not_promoted);
       }
       const size_t original_waste = plab->waste();
    @@ -564,8 +565,8 @@ void ShenandoahGenerationalHeap::retire_plab(PLAB* plab, Thread* thread) {
       if (top != nullptr && plab->waste() > original_waste && is_in_old(top)) {
         // If retiring the plab created a filler object, then we need to register it with our card scanner so it can
         // safely walk the region backing the plab.
    -    log_debug(gc)("retire_plab() is registering remnant of size %zu at " PTR_FORMAT,
    -                  plab->waste() - original_waste, p2i(top));
    +    log_debug(gc, plab)("retire_plab() is registering remnant of size %zu at " PTR_FORMAT,
    +                        (plab->waste() - original_waste) * HeapWordSize, p2i(top));
         // No lock is necessary because the PLAB memory is aligned on card boundaries.
         old_generation()->card_scan()->register_object_without_lock(top);
       }
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp
    index 1611916fa873..9be46eb20d5d 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp
    @@ -1288,7 +1288,7 @@ void ShenandoahHeap::evacuate_collection_set(bool concurrent) {
     
     void ShenandoahHeap::concurrent_prepare_for_update_refs() {
       {
    -    // Java threads take this lock while they are being attached and added to the list of thread.
    +    // Java threads take this lock while they are being attached and added to the list of threads.
         // If another thread holds this lock before we update the gc state, it will receive a stale
         // gc state, but they will have been added to the list of java threads and so will be corrected
         // by the following handshake.
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahOldGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahOldGC.cpp
    index 1724fc2849f7..707c2690b581 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahOldGC.cpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahOldGC.cpp
    @@ -69,12 +69,6 @@ void ShenandoahOldGC::op_final_mark() {
         heap->set_unload_classes(false);
         heap->prepare_concurrent_roots();
     
    -    // Believe verification following old-gen concurrent mark needs to be different than verification following
    -    // young-gen concurrent mark, so am commenting this out for now:
    -    //   if (ShenandoahVerify) {
    -    //     heap->verifier()->verify_after_concmark();
    -    //   }
    -
         if (VerifyAfterGC) {
           Universe::verify();
         }
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.cpp
    index 5cccd395d381..0d5a13fa5471 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.cpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.cpp
    @@ -300,6 +300,8 @@ ShenandoahOldGeneration::configure_plab_for_current_thread(const ShenandoahAlloc
           if (can_promote(actual_size)) {
             // Assume the entirety of this PLAB will be used for promotion.  This prevents promotion from overreach.
             // When we retire this plab, we'll unexpend what we don't really use.
    +        log_debug(gc, plab)("Thread can promote using PLAB of %zu bytes. Expended: %zu, available: %zu",
    +                            actual_size, get_promoted_expended(), get_promoted_reserve());
             expend_promoted(actual_size);
             ShenandoahThreadLocalData::enable_plab_promotions(thread);
             ShenandoahThreadLocalData::set_plab_actual_size(thread, actual_size);
    @@ -307,9 +309,12 @@ ShenandoahOldGeneration::configure_plab_for_current_thread(const ShenandoahAlloc
             // Disable promotions in this thread because entirety of this PLAB must be available to hold old-gen evacuations.
             ShenandoahThreadLocalData::disable_plab_promotions(thread);
             ShenandoahThreadLocalData::set_plab_actual_size(thread, 0);
    +        log_debug(gc, plab)("Thread cannot promote using PLAB of %zu bytes. Expended: %zu, available: %zu, mixed evacuations? %s",
    +                            actual_size, get_promoted_expended(), get_promoted_reserve(), BOOL_TO_STR(ShenandoahHeap::heap()->collection_set()->has_old_regions()));
           }
         } else if (req.is_promotion()) {
           // Shared promotion.
    +      log_debug(gc, plab)("Expend shared promotion of %zu bytes", actual_size);
           expend_promoted(actual_size);
         }
       }
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahTrace.cpp b/src/hotspot/share/gc/shenandoah/shenandoahTrace.cpp
    index a786f8ae216b..bbb44348355b 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahTrace.cpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahTrace.cpp
    @@ -37,9 +37,9 @@ void ShenandoahTracer::report_evacuation_info(const ShenandoahCollectionSet* cse
         e.set_cSetRegions(cset->count());
         e.set_cSetUsedBefore(cset->used());
         e.set_cSetUsedAfter(cset->live());
    -    e.set_collectedOld(cset->get_old_bytes_reserved_for_evacuation());
    -    e.set_collectedPromoted(cset->get_young_bytes_to_be_promoted());
    -    e.set_collectedYoung(cset->get_young_bytes_reserved_for_evacuation());
    +    e.set_collectedOld(cset->get_live_bytes_in_old_regions());
    +    e.set_collectedPromoted(cset->get_live_bytes_in_tenurable_regions());
    +    e.set_collectedYoung(cset->get_live_bytes_in_untenurable_regions());
         e.set_regionsPromotedHumongous(regions_promoted_humongous);
         e.set_regionsPromotedRegular(regions_promoted_regular);
         e.set_regularPromotedGarbage(regular_promoted_garbage);
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp b/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp
    index 7ea7ecf7f495..82378d43cf1d 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp
    @@ -387,13 +387,13 @@
                                                                                 \
       product(uintx, ShenandoahOldEvacRatioPercent, 75, EXPERIMENTAL,           \
               "The maximum proportion of evacuation from old-gen memory, "      \
    -          "expressed as a percentage. The default value 75 denotes that no" \
    -          "more than 75% of the collection set evacuation workload may be " \
    -          "towards evacuation of old-gen heap regions. This limits both the"\
    -          "promotion of aged regions and the compaction of existing old "   \
    -          "regions.  A value of 75 denotes that the total evacuation work"  \
    -          "may increase to up to four times the young gen evacuation work." \
    -          "A larger value allows quicker promotion and allows"              \
    +          "expressed as a percentage. The default value 75 denotes that "   \
    +          "no more than 75% of the collection set evacuation workload may " \
    +          "be towards evacuation of old-gen heap regions. This limits both "\
    +          "the promotion of aged regions and the compaction of existing "   \
    +          "old regions. A value of 75 denotes that the total evacuation "   \
    +          "work may increase to up to four times the young gen evacuation " \
    +          "work. A larger value allows quicker promotion and allows "       \
               "a smaller number of mixed evacuations to process "               \
               "the entire list of old-gen collection candidates at the cost "   \
               "of an increased disruption of the normal cadence of young-gen "  \
    @@ -401,7 +401,7 @@
               "focus entirely on old-gen memory, allowing no young-gen "        \
               "regions to be collected, likely resulting in subsequent "        \
               "allocation failures because the allocation pool is not "         \
    -          "replenished.  A value of 0 allows a mixed evacuation to"         \
    +          "replenished.  A value of 0 allows a mixed evacuation to "        \
               "focus entirely on young-gen memory, allowing no old-gen "        \
               "regions to be collected, likely resulting in subsequent "        \
               "promotion failures and triggering of stop-the-world full GC "    \
    
    From ef0054c676183c7c3c1d83b545c3a7a68ede16bf Mon Sep 17 00:00:00 2001
    From: Matthias Baesken 
    Date: Tue, 28 Apr 2026 13:59:42 +0000
    Subject: [PATCH 155/168] 8376956: Add JVMTI phase entering/setting to hserr
     event log
    
    Backport-of: 1ac965893da6a9a3d220d572cab4ac6030ba1722
    ---
     src/hotspot/share/prims/jvmtiExport.cpp | 9 ++++++++-
     1 file changed, 8 insertions(+), 1 deletion(-)
    
    diff --git a/src/hotspot/share/prims/jvmtiExport.cpp b/src/hotspot/share/prims/jvmtiExport.cpp
    index c767a0eeac25..e0a2c54137d3 100644
    --- a/src/hotspot/share/prims/jvmtiExport.cpp
    +++ b/src/hotspot/share/prims/jvmtiExport.cpp
    @@ -1,5 +1,5 @@
     /*
    - * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved.
    + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved.
      * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
      *
      * This code is free software; you can redistribute it and/or modify it
    @@ -71,6 +71,7 @@
     #include "runtime/threadSMR.hpp"
     #include "runtime/vframe.inline.hpp"
     #include "runtime/vm_version.hpp"
    +#include "utilities/events.hpp"
     #include "utilities/macros.hpp"
     
     #ifdef JVMTI_TRACE
    @@ -626,22 +627,27 @@ JvmtiExport::decode_version_values(jint version, int * major, int * minor,
     }
     
     void JvmtiExport::enter_primordial_phase() {
    +  Events::log(Thread::current_or_null(), "JVMTI - enter primordial phase");
       JvmtiEnvBase::set_phase(JVMTI_PHASE_PRIMORDIAL);
     }
     
     void JvmtiExport::enter_early_start_phase() {
    +  Events::log(Thread::current_or_null(), "JVMTI - enter early start phase");
       set_early_vmstart_recorded(true);
     }
     
     void JvmtiExport::enter_start_phase() {
    +  Events::log(Thread::current_or_null(), "JVMTI - enter start phase");
       JvmtiEnvBase::set_phase(JVMTI_PHASE_START);
     }
     
     void JvmtiExport::enter_onload_phase() {
    +  Events::log(Thread::current_or_null(), "JVMTI - enter onload phase");
       JvmtiEnvBase::set_phase(JVMTI_PHASE_ONLOAD);
     }
     
     void JvmtiExport::enter_live_phase() {
    +  Events::log(Thread::current_or_null(), "JVMTI - enter live phase");
       JvmtiEnvBase::set_phase(JVMTI_PHASE_LIVE);
     }
     
    @@ -779,6 +785,7 @@ void JvmtiExport::post_vm_death() {
         }
       }
     
    +  Events::log(Thread::current_or_null(), "JVMTI - enter dead phase");
       JvmtiEnvBase::set_phase(JVMTI_PHASE_DEAD);
       JvmtiEventController::vm_death();
     }
    
    From a1119f54e94f264e378fe6ae4089a54421736b61 Mon Sep 17 00:00:00 2001
    From: William Kemper 
    Date: Tue, 28 Apr 2026 15:56:20 +0000
    Subject: [PATCH 156/168] 8376287: Crashes when using
     -XX:ObjArrayMarkingStride=0
    
    Backport-of: e3b5b261af6acbe7ab074f301c70283b06c17d39
    ---
     src/hotspot/share/gc/shared/gc_globals.hpp                | 1 +
     src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp | 2 --
     2 files changed, 1 insertion(+), 2 deletions(-)
    
    diff --git a/src/hotspot/share/gc/shared/gc_globals.hpp b/src/hotspot/share/gc/shared/gc_globals.hpp
    index 4b3cbf04b40e..9fdf0d63eccf 100644
    --- a/src/hotspot/share/gc/shared/gc_globals.hpp
    +++ b/src/hotspot/share/gc/shared/gc_globals.hpp
    @@ -260,6 +260,7 @@
       develop(uintx, ObjArrayMarkingStride, 2048,                               \
               "Number of object array elements to push onto the marking stack " \
               "before pushing a continuation entry")                            \
    +          range(1, INT_MAX/2)                                               \
                                                                                 \
       product_pd(bool, NeverActAsServerClassMachine,                            \
               "Never act like a server-class machine")                          \
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp
    index 0a95ee9f1496..c588a03fdf90 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp
    @@ -226,8 +226,6 @@ inline void ShenandoahMark::do_chunked_array(ShenandoahObjToScanQueue* q, T* cl,
       assert(obj->is_objArray(), "expect object array");
       objArrayOop array = objArrayOop(obj);
     
    -  assert (ObjArrayMarkingStride > 0, "sanity");
    -
       // Split out tasks, as suggested in ShenandoahMarkTask docs. Avoid pushing tasks that
       // are known to start beyond the array.
       while ((1 << pow) > (int)ObjArrayMarkingStride && (chunk*2 < ShenandoahMarkTask::chunk_size())) {
    
    From 0cfdfea3b23157b11031d182cd956956be3410e0 Mon Sep 17 00:00:00 2001
    From: Martin Doerr 
    Date: Wed, 29 Apr 2026 09:20:02 +0000
    Subject: [PATCH 157/168] 8379457: Test EATests.java#id0 ERROR: monitor list
     errors: error_cnt=1
    
    Backport-of: d3be157fd5f94502e73dd2bda35653a4ea95248d
    ---
     src/hotspot/share/runtime/synchronizer.cpp | 6 +++++-
     1 file changed, 5 insertions(+), 1 deletion(-)
    
    diff --git a/src/hotspot/share/runtime/synchronizer.cpp b/src/hotspot/share/runtime/synchronizer.cpp
    index 503b78333517..6c1591e57cec 100644
    --- a/src/hotspot/share/runtime/synchronizer.cpp
    +++ b/src/hotspot/share/runtime/synchronizer.cpp
    @@ -2048,7 +2048,11 @@ void ObjectSynchronizer::chk_in_use_entry(ObjectMonitor* n, outputStream* out,
       }
     
       const markWord mark = obj->mark();
    -  if (!mark.has_monitor()) {
    +  // Note: When using ObjectMonitorTable we may observe an intermediate state,
    +  // where the monitor is globally visible, but no thread has yet transitioned
    +  // the markWord. To avoid reporting a false positive during this transition, we
    +  // skip the `!mark.has_monitor()` test if we are using the ObjectMonitorTable.
    +  if (!UseObjectMonitorTable && !mark.has_monitor()) {
         out->print_cr("ERROR: monitor=" INTPTR_FORMAT ": in-use monitor's "
                       "object does not think it has a monitor: obj="
                       INTPTR_FORMAT ", mark=" INTPTR_FORMAT, p2i(n),
    
    From 51f0a464f0e0473ba2fb15e1d2f3a4023d4564bd Mon Sep 17 00:00:00 2001
    From: Matthias Baesken 
    Date: Wed, 29 Apr 2026 11:16:04 +0000
    Subject: [PATCH 158/168] 8381935: Improve numChunks range in the PPC64
     CallAranger
    
    Backport-of: 61849f47add0d96d4723858f10fe67de411c4166
    ---
     .../jdk/internal/foreign/abi/ppc64/CallArranger.java       | 7 ++++++-
     1 file changed, 6 insertions(+), 1 deletion(-)
    
    diff --git a/src/java.base/share/classes/jdk/internal/foreign/abi/ppc64/CallArranger.java b/src/java.base/share/classes/jdk/internal/foreign/abi/ppc64/CallArranger.java
    index 0fd90ef6f73a..c9994ec29306 100644
    --- a/src/java.base/share/classes/jdk/internal/foreign/abi/ppc64/CallArranger.java
    +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/ppc64/CallArranger.java
    @@ -245,7 +245,12 @@ VMStorage nextStorage(int type, boolean is32Bit) {
             // Regular struct, no HFA.
             VMStorage[] structAlloc(MemoryLayout layout) {
                 // Allocate enough gp slots (regs and stack) such that the struct fits in them.
    -            int numChunks = (int) Utils.alignUp(layout.byteSize(), MAX_COPY_SIZE) / MAX_COPY_SIZE;
    +            final int numChunks;
    +            try {
    +                numChunks = Math.toIntExact(Utils.alignUp(layout.byteSize(), MAX_COPY_SIZE) / MAX_COPY_SIZE);
    +            } catch (ArithmeticException ae) {
    +                throw new IllegalArgumentException("Layout too large: " + layout, ae);
    +            }
                 VMStorage[] result = new VMStorage[numChunks];
                 for (int i = 0; i < numChunks; i++) {
                     result[i] = nextStorage(StorageType.INTEGER, false);
    
    From 975070106abc58ce69b4346e33833aeaaf0fa1fc Mon Sep 17 00:00:00 2001
    From: Roland Mesde 
    Date: Wed, 29 Apr 2026 13:29:19 +0000
    Subject: [PATCH 159/168] 8380222: Refactor test/jdk/java/lang/Character TestNG
     tests to JUnit
    
    Backport-of: 7d805e113948196ac4e45ac05e09413e63b9bb46
    ---
     .../lang/Character/Latin1CaseConversion.java  | 47 +++++++++----------
     .../UnicodeBlock/NumberEntities.java          | 17 +++----
     2 files changed, 32 insertions(+), 32 deletions(-)
    
    diff --git a/test/jdk/java/lang/Character/Latin1CaseConversion.java b/test/jdk/java/lang/Character/Latin1CaseConversion.java
    index a176bd4b002e..436193c5f168 100644
    --- a/test/jdk/java/lang/Character/Latin1CaseConversion.java
    +++ b/test/jdk/java/lang/Character/Latin1CaseConversion.java
    @@ -1,5 +1,5 @@
     /*
    - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
    + * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved.
      * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
      *
      * This code is free software; you can redistribute it and/or modify it
    @@ -21,17 +21,16 @@
      * questions.
      */
     
    -import org.testng.annotations.Test;
    -
    -import static org.testng.Assert.assertEquals;
    -import static org.testng.Assert.fail;
    +import org.junit.jupiter.api.Test;
    +import static org.junit.jupiter.api.Assertions.assertEquals;
    +import static org.junit.jupiter.api.Assertions.fail;
     
     /**
      * @test
      * @bug 8302877
      * @summary Provides exhaustive verification of Character.toUpperCase and Character.toLowerCase
      * for all code points in the latin1 range 0-255.
    - * @run testng Latin1CaseConversion
    + * @run junit Latin1CaseConversion
      */
     public class Latin1CaseConversion {
     
    @@ -44,41 +43,41 @@ public void shouldUpperCaseAndLowerCaseLatin1() {
                 if (c < 0x41) { // Before A
                     assertUnchanged(upper, lower, c);
                 } else if (c <= 0x5A) { // A-Z
    -                assertEquals(upper, c);
    -                assertEquals(lower, c + 32);
    +                assertEquals(c, upper);
    +                assertEquals(c + 32, lower);
                 } else if (c < 0x61) { // Between Z and a
                     assertUnchanged(upper, lower, c);
                 } else if (c <= 0x7A) { // a-z
    -                assertEquals(upper, c - 32);
    -                assertEquals(lower, c);
    +                assertEquals(c - 32, upper);
    +                assertEquals(c, lower);
                 } else if (c < 0xB5) { // Between z and Micro Sign
                     assertUnchanged(upper, lower, c);
                 } else if (c == 0xB5) { // Special case for Micro Sign
    -                assertEquals(upper, 0x39C);
    -                assertEquals(lower, c);
    +                assertEquals(0x39C, upper);
    +                assertEquals(c, lower);
                 } else if (c < 0xC0) { // Between my and A-grave
                     assertUnchanged(upper, lower, c);
                 } else if (c < 0xD7) { // A-grave - O with Diaeresis
    -                assertEquals(upper, c);
    -                assertEquals(lower, c + 32);
    +                assertEquals(c, upper);
    +                assertEquals(c + 32, lower);
                 } else if (c == 0xD7) { // Multiplication
                     assertUnchanged(upper, lower, c);
                 } else if (c <= 0xDE) { // O with slash - Thorn
    -                assertEquals(upper, c);
    -                assertEquals(lower, c + 32);
    +                assertEquals(c, upper);
    +                assertEquals(c + 32, lower);
                 } else if (c == 0xDF) { // Sharp s
                     assertUnchanged(upper, lower, c);
                 } else if (c < 0xF7) { // a-grave - divsion
    -                assertEquals(upper, c - 32);
    -                assertEquals(lower, c);
    +                assertEquals(c - 32, upper);
    +                assertEquals(c, lower);
                 } else if (c == 0xF7) { // Division
                     assertUnchanged(upper, lower, c);
                 } else if (c < 0xFF) { // o with slash - thorn
    -                assertEquals(upper, c - 32);
    -                assertEquals(lower, c);
    +                assertEquals(c - 32, upper);
    +                assertEquals(c, lower);
                 } else if (c == 0XFF) { // Special case for y with Diaeresis
    -                assertEquals(upper, 0x178);
    -                assertEquals(lower, c);
    +                assertEquals(0x178, upper);
    +                assertEquals(c, lower);
                 } else {
                     fail("Uncovered code point: " + Integer.toHexString(c));
                 }
    @@ -86,7 +85,7 @@ public void shouldUpperCaseAndLowerCaseLatin1() {
         }
     
         private static void assertUnchanged(int upper, int lower, int c) {
    -        assertEquals(upper, c);
    -        assertEquals(lower, c);
    +        assertEquals(c, upper);
    +        assertEquals(c, lower);
         }
     }
    diff --git a/test/jdk/java/lang/Character/UnicodeBlock/NumberEntities.java b/test/jdk/java/lang/Character/UnicodeBlock/NumberEntities.java
    index 30382c3cfe39..86b9fe6ea692 100644
    --- a/test/jdk/java/lang/Character/UnicodeBlock/NumberEntities.java
    +++ b/test/jdk/java/lang/Character/UnicodeBlock/NumberEntities.java
    @@ -1,5 +1,5 @@
     /*
    - * Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved.
    + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved.
      * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
      *
      * This code is free software; you can redistribute it and/or modify it
    @@ -28,17 +28,17 @@
      *      of Character.UnicodeBlock constants. Also checks the size of
      *      Character.UnicodeScript's "aliases" map.
      * @modules java.base/java.lang:open
    - * @run testng NumberEntities
    + * @run junit NumberEntities
      */
     
    -import static org.testng.Assert.assertEquals;
    -import org.testng.annotations.Test;
    -
     import java.lang.reflect.Field;
     import java.util.Map;
     
    -@Test
    +import org.junit.jupiter.api.Test;
    +import static org.junit.jupiter.api.Assertions.assertEquals;
    +
     public class NumberEntities {
    +    @Test
         public void test_UnicodeBlock_NumberEntities() throws Throwable {
             // The number of entries in Character.UnicodeBlock.map.
             // See src/java.base/share/classes/java/lang/Character.java
    @@ -46,13 +46,14 @@ public void test_UnicodeBlock_NumberEntities() throws Throwable {
             Field m = Character.UnicodeBlock.class.getDeclaredField("map");
             n.setAccessible(true);
             m.setAccessible(true);
    -        assertEquals(((Map)m.get(null)).size(), n.getInt(null));
    +        assertEquals(n.getInt(null), ((Map)m.get(null)).size());
         }
    +    @Test
         public void test_UnicodeScript_aliases() throws Throwable {
             // The number of entries in Character.UnicodeScript.aliases.
             // See src/java.base/share/classes/java/lang/Character.java
             Field aliases = Character.UnicodeScript.class.getDeclaredField("aliases");
             aliases.setAccessible(true);
    -        assertEquals(((Map)aliases.get(null)).size(), Character.UnicodeScript.UNKNOWN.ordinal() + 1);
    +        assertEquals(Character.UnicodeScript.UNKNOWN.ordinal() + 1, ((Map)aliases.get(null)).size());
         }
     }
    
    From 6cbe21c76113c5ac5ba80ec21f3ddcfbffd5da30 Mon Sep 17 00:00:00 2001
    From: Roland Mesde 
    Date: Wed, 29 Apr 2026 13:30:25 +0000
    Subject: [PATCH 160/168] 8366852:
     java/awt/Choice/ChoiceMouseWheelTest/ChoiceMouseWheelTest.java test is
     failing
    
    Backport-of: 3db3c06218f37ed4f14d4f53538663d2a5547095
    ---
     test/jdk/ProblemList.txt                      |   1 -
     .../ChoiceMouseWheelTest.java                 | 168 ++++++++++--------
     2 files changed, 92 insertions(+), 77 deletions(-)
    
    diff --git a/test/jdk/ProblemList.txt b/test/jdk/ProblemList.txt
    index 5f3dfa2c9fb9..c55468615ecc 100644
    --- a/test/jdk/ProblemList.txt
    +++ b/test/jdk/ProblemList.txt
    @@ -261,7 +261,6 @@ java/awt/FullScreen/DisplayChangeVITest/DisplayChangeVITest.java 8169469,8273617
     java/awt/FullScreen/UninitializedDisplayModeChangeTest/UninitializedDisplayModeChangeTest.java 8273617 macosx-all
     java/awt/print/PrinterJob/PSQuestionMark.java 7003378 generic-all
     java/awt/print/PrinterJob/GlyphPositions.java 7003378 generic-all
    -java/awt/Choice/ChoiceMouseWheelTest/ChoiceMouseWheelTest.java 8366852 generic-all
     java/awt/Component/GetScreenLocTest/GetScreenLocTest.java 4753654 generic-all
     java/awt/Component/SetEnabledPerformance/SetEnabledPerformance.java 8165863 macosx-all
     java/awt/Clipboard/PasteNullToTextComponentsTest.java 8234140 macosx-all,windows-all
    diff --git a/test/jdk/java/awt/Choice/ChoiceMouseWheelTest/ChoiceMouseWheelTest.java b/test/jdk/java/awt/Choice/ChoiceMouseWheelTest/ChoiceMouseWheelTest.java
    index 6426a3779108..3a94680b7237 100644
    --- a/test/jdk/java/awt/Choice/ChoiceMouseWheelTest/ChoiceMouseWheelTest.java
    +++ b/test/jdk/java/awt/Choice/ChoiceMouseWheelTest/ChoiceMouseWheelTest.java
    @@ -1,5 +1,5 @@
     /*
    - * Copyright (c) 2011, 2016, Oracle and/or its affiliates. All rights reserved.
    + * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
      * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
      *
      * This code is free software; you can redistribute it and/or modify it
    @@ -22,58 +22,66 @@
      */
     
     /*
    -  @test
    -  @key headful
    -  @bug 7050935
    -  @summary closed/java/awt/Choice/WheelEventsConsumed/WheelEventsConsumed.html fails on win32
    -  @library ../../regtesthelpers
    -  @author Oleg Pekhovskiy: area=awt-choice
    -  @build Util
    -  @run main ChoiceMouseWheelTest
    -*/
    + * @test
    + * @key headful
    + * @bug 7050935
    + * @summary closed/java/awt/Choice/WheelEventsConsumed/WheelEventsConsumed.html fails on win32
    + * @library /java/awt/regtesthelpers
    + * @build Util
    + * @run main ChoiceMouseWheelTest
    + */
     
     import test.java.awt.regtesthelpers.Util;
     
    -import java.awt.*;
    -import java.awt.event.*;
    +import java.awt.Choice;
    +import java.awt.Dimension;
    +import java.awt.EventQueue;
    +import java.awt.FlowLayout;
    +import java.awt.Frame;
    +import java.awt.Point;
    +import java.awt.Rectangle;
    +import java.awt.Robot;
    +import java.awt.Toolkit;
    +import java.awt.event.InputEvent;
    +import java.awt.event.MouseAdapter;
    +import java.awt.event.MouseEvent;
    +import java.awt.event.WindowAdapter;
    +import java.awt.event.WindowEvent;
     
     public class ChoiceMouseWheelTest extends Frame {
     
         private volatile boolean itemChanged = false;
         private volatile boolean wheelMoved = false;
         private volatile boolean frameExited = false;
    +    private final Choice choice;
     
    -    public static void main(String[] args) {
    -        new ChoiceMouseWheelTest();
    +    public static void main(String[] args) throws Exception {
    +        ChoiceMouseWheelTest test = Util.invokeOnEDT(ChoiceMouseWheelTest::new);
    +        try {
    +            test.test();
    +        } finally {
    +            EventQueue.invokeAndWait(test::dispose);
    +        }
         }
     
         ChoiceMouseWheelTest() {
             super("ChoiceMouseWheelTest");
             setLayout(new FlowLayout());
     
    -        Choice choice = new Choice();
    -
             addWindowListener(new WindowAdapter() {
                 @Override
                 public void windowClosing(WindowEvent e) {
    -                System.exit(0);
    +                e.getWindow().dispose();
                 }
             });
     
    -        for(Integer i = 0; i < 50; i++) {
    -            choice.add(i.toString());
    +        choice = new Choice();
    +        for(int i = 0; i < 50; i++) {
    +            choice.add(Integer.toString(i));
             }
     
    -        choice.addItemListener(new ItemListener() {
    -            public void itemStateChanged(ItemEvent e) {
    -                itemChanged = true;
    -            }
    -        });
    -        choice.addMouseWheelListener(new MouseWheelListener() {
    -            public void mouseWheelMoved(MouseWheelEvent e) {
    -                wheelMoved = true;
    -            }
    -        });
    +        choice.addItemListener(e -> itemChanged = true);
    +        choice.addMouseWheelListener(e -> wheelMoved = true);
     
             addMouseListener(new MouseAdapter() {
                 @Override
    @@ -84,71 +92,79 @@ public void mouseExited(MouseEvent e) {
     
             add(choice);
             setSize(200, 300);
    +        setLocationRelativeTo(null);
             setVisible(true);
             toFront();
    +    }
     
    -        try {
    -            Robot robot = new Robot();
    -            robot.setAutoDelay(20);
    -            Util.waitForIdle(robot);
    +    private void test() throws Exception {
    +        Robot robot = new Robot();
    +        robot.setAutoDelay(20);
    +        robot.waitForIdle();
    +        robot.delay(500);
     
    +        Rectangle choiceBounds = Util.invokeOnEDT(() -> {
                 Point pt = choice.getLocationOnScreen();
                 Dimension size = choice.getSize();
    -            int x = pt.x + size.width / 3;
    -            robot.mouseMove(x, pt.y + size.height / 2);
    +            return new Rectangle(pt, size);
    +        });
    +
    +        int x = choiceBounds.x + choiceBounds.width / 3;
    +        robot.mouseMove(x, choiceBounds.y + choiceBounds.height / 2);
     
    -            // Test mouse wheel over the choice
    -            String name = Toolkit.getDefaultToolkit().getClass().getName();
    +        // Test mouse wheel over the choice
    +        String name = Toolkit.getDefaultToolkit().getClass().getName();
    +        boolean isXtoolkit = name.equals("sun.awt.X11.XToolkit");
    +        boolean isLWCToolkit = name.equals("sun.lwawt.macosx.LWCToolkit");
     
    -            // mouse wheel doesn't work for the choice on X11 and Mac, so skip it
    -            if(!name.equals("sun.awt.X11.XToolkit")
    -               && !name.equals("sun.lwawt.macosx.LWCToolkit")) {
    -                robot.mouseWheel(1);
    -                Util.waitForIdle(robot);
    +        // mouse wheel doesn't work for the choice on X11 and Mac, so skip it
    +        if (!isXtoolkit && !isLWCToolkit) {
    +            robot.mouseWheel(1);
    +            robot.waitForIdle();
     
    -                if(!wheelMoved || !itemChanged) {
    -                    throw new RuntimeException("Mouse Wheel over the choice failed!");
    -                }
    +            if (!wheelMoved || !itemChanged) {
    +                throw new RuntimeException("Mouse Wheel over the choice failed!");
                 }
    +        }
     
    -            // Test mouse wheel over the drop-down list
    -            robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
    -            Util.waitForIdle(robot);
    -            robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
    -            Util.waitForIdle(robot);
    +        // Test mouse wheel over the drop-down list
    +        robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
    +        robot.waitForIdle();
    +        robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
    +        robot.waitForIdle();
     
    -            int y = getLocationOnScreen().y + getSize().height;
    -            while(!frameExited && y >= 0) { // move to the bottom of drop-down list
    -                robot.mouseMove(x, --y);
    -                Util.waitForIdle(robot);
    -            }
    +        frameExited = false;
     
    -            if(x < 0) {
    -                throw new RuntimeException("Could not enter drop-down list!");
    -            }
    +        Rectangle frameBounds = Util.invokeOnEDT(this::getBounds);
     
    -            y -= choice.getHeight() / 2;
    -            robot.mouseMove(x, y); // move to the last visible item in the drop-down list
    -            Util.waitForIdle(robot);
    +        int y = frameBounds.y + frameBounds.height;
    +        while (!frameExited && y >= frameBounds.y) { // move to the bottom of drop-down list
    +            robot.mouseMove(x, --y);
    +            robot.waitForIdle();
    +        }
     
    -            robot.mouseWheel(choice.getItemCount()); // wheel to the last item
    -            Util.waitForIdle(robot);
    +        if (y < frameBounds.y) {
    +            throw new RuntimeException("Could not enter drop-down list!");
    +        }
     
    -            // click the last item
    -            itemChanged = false;
    -            robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
    -            Util.waitForIdle(robot);
    -            robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
    -            Util.waitForIdle(robot);
    +        y -= choiceBounds.height / 2;
    +        robot.mouseMove(x, y); // move to the last visible item in the drop-down list
    +        robot.waitForIdle();
     
    -            if(!itemChanged || choice.getSelectedIndex() != choice.getItemCount() - 1) {
    -                throw new RuntimeException("Mouse Wheel scroll position error!");
    -            }
    +        int scrollDirection = isLWCToolkit ? -1 : 1;
    +        // wheel to the last item
    +        robot.mouseWheel(scrollDirection * choice.getItemCount());
    +        robot.waitForIdle();
     
    -            dispose();
    -        } catch (AWTException e) {
    -            throw new RuntimeException("AWTException occurred - problem creating robot!");
    +        // click the last item
    +        itemChanged = false;
    +        robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
    +        robot.waitForIdle();
    +        robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
    +        robot.waitForIdle();
    +
    +        if (!itemChanged || choice.getSelectedIndex() != choice.getItemCount() - 1) {
    +            throw new RuntimeException("Mouse Wheel scroll position error!");
             }
         }
     }
    -
    
    From f5f8589498f307dfbb81fc30420c362b9829575f Mon Sep 17 00:00:00 2001
    From: Goetz Lindenmaier 
    Date: Thu, 30 Apr 2026 12:55:56 +0000
    Subject: [PATCH 161/168] 8374888: Implement internal test cache to help
     UserIterCount test performance
    
    Backport-of: 99c2cd01c3f82b37ef7faac38a1b92a083c1ab91
    ---
     .../sun/security/krb5/auto/UserIterCount.java | 22 ++++++++++++++++++-
     1 file changed, 21 insertions(+), 1 deletion(-)
    
    diff --git a/test/jdk/sun/security/krb5/auto/UserIterCount.java b/test/jdk/sun/security/krb5/auto/UserIterCount.java
    index a90da012a061..f972e20ee1e1 100644
    --- a/test/jdk/sun/security/krb5/auto/UserIterCount.java
    +++ b/test/jdk/sun/security/krb5/auto/UserIterCount.java
    @@ -1,5 +1,5 @@
     /*
    - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
    + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved.
      * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
      *
      * This code is free software; you can redistribute it and/or modify it
    @@ -30,11 +30,19 @@
      * @run main jdk.test.lib.FileInstaller TestHosts TestHosts
      * @run main/othervm -Djdk.net.hosts.file=TestHosts UserIterCount
      */
    +
    +import java.util.HashMap;
    +
    +import sun.security.krb5.EncryptionKey;
    +import sun.security.krb5.KrbException;
     import sun.security.krb5.PrincipalName;
     
     public class UserIterCount {
     
         static class MyKDC extends OneKDC {
    +        static final HashMap CACHE
    +                = new HashMap<>();
    +
             public MyKDC() throws Exception {
                 super(null);
             }
    @@ -51,6 +59,18 @@ protected byte[] getParams(PrincipalName p, int etype) {
                     return super.getParams(p, etype);
                 }
             }
    +
    +        @Override
    +        EncryptionKey keyForUser(PrincipalName p, int etype, boolean server)
    +                throws KrbException {
    +            var key = p.toString() + etype + server;
    +            var v = CACHE.get(key);
    +            if (v == null) {
    +                v = super.keyForUser(p, etype, server);
    +                CACHE.put(key, v);
    +            }
    +            return v;
    +        }
         }
     
         public static void main(String[] args) throws Exception {
    
    From c6c8b3918c79f84d05fe722467b2637fcad1f752 Mon Sep 17 00:00:00 2001
    From: Goetz Lindenmaier 
    Date: Mon, 4 May 2026 07:41:02 +0000
    Subject: [PATCH 162/168] 8382419: Add missed @key randomness after JDK-8370489
    
    Backport-of: e7ee8cd39de1843f4d4e1d7c331846b0460436cd
    ---
     .../compiler/loopopts/superword/TestDependencyOffsets.java     | 3 ++-
     1 file changed, 2 insertions(+), 1 deletion(-)
    
    diff --git a/test/hotspot/jtreg/compiler/loopopts/superword/TestDependencyOffsets.java b/test/hotspot/jtreg/compiler/loopopts/superword/TestDependencyOffsets.java
    index ab1f1757c70a..663c55c4b139 100644
    --- a/test/hotspot/jtreg/compiler/loopopts/superword/TestDependencyOffsets.java
    +++ b/test/hotspot/jtreg/compiler/loopopts/superword/TestDependencyOffsets.java
    @@ -1,5 +1,5 @@
     /*
    - * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
    + * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved.
      * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
      *
      * This code is free software; you can redistribute it and/or modify it
    @@ -96,6 +96,7 @@
     /*
      * @test id=sse4-v004-A
      * @bug 8298935 8308606 8310308 8312570 8310190
    + * @key randomness
      * @summary Test SuperWord: vector size, offsets, dependencies, alignment.
      * @requires vm.compiler2.enabled
      * @requires (os.arch=="x86" | os.arch=="i386" | os.arch=="amd64" | os.arch=="x86_64")
    
    From 7770bd10ac81b82805618ff1a549dc9464b0806e Mon Sep 17 00:00:00 2001
    From: Fei Yang 
    Date: Tue, 5 May 2026 06:03:58 +0000
    Subject: [PATCH 163/168] 8383601: RISC-V:
     ShenandoahBarrierSetAssembler::load_reference_barrier calls "weak" on
     "phantom" path
    
    Backport-of: 631d0e92f045b302ce24dbe54eb6a94767b09d14
    ---
     .../riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.cpp | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.cpp b/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.cpp
    index 4c1056e75a55..f936ae9ba27b 100644
    --- a/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.cpp
    +++ b/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.cpp
    @@ -301,7 +301,7 @@ void ShenandoahBarrierSetAssembler::load_reference_barrier(MacroAssembler* masm,
       } else {
         assert(is_phantom, "only remaining strength");
         assert(!is_narrow, "phantom access cannot be narrow");
    -    target = CAST_FROM_FN_PTR(address, ShenandoahRuntime::load_reference_barrier_weak);
    +    target = CAST_FROM_FN_PTR(address, ShenandoahRuntime::load_reference_barrier_phantom);
       }
       __ rt_call(target);
       __ mv(t0, x10);
    
    From a47b2ce9dc9a858a5e2a1c46d3eb6934ee258026 Mon Sep 17 00:00:00 2001
    From: Matthias Baesken 
    Date: Tue, 5 May 2026 07:56:42 +0000
    Subject: [PATCH 164/168] 8379425: Windows and macOS should not allow
     unsupported headless-only build
    
    Backport-of: 02001567f0d8ba83d28b014b9e8564f1e594ff74
    ---
     make/autoconf/jdk-options.m4 | 7 +++++++
     1 file changed, 7 insertions(+)
    
    diff --git a/make/autoconf/jdk-options.m4 b/make/autoconf/jdk-options.m4
    index 9d67582b425f..c4d203aff3c9 100644
    --- a/make/autoconf/jdk-options.m4
    +++ b/make/autoconf/jdk-options.m4
    @@ -102,6 +102,13 @@ AC_DEFUN_ONCE([JDKOPT_SETUP_JDK_OPTIONS],
           CHECKING_MSG: [if we should build headless-only (no GUI)])
       AC_SUBST(ENABLE_HEADLESS_ONLY)
     
    +  # Avoid headless-only on macOS and Windows, it is not supported there
    +  if test "x$ENABLE_HEADLESS_ONLY" = xtrue; then
    +    if test "x$OPENJDK_TARGET_OS" = xwindows || test "x$OPENJDK_TARGET_OS" = xmacosx; then
    +      AC_MSG_ERROR([headless-only is not supported on macOS and Windows])
    +    fi
    +  fi
    +
       # should we linktime gc unused code sections in the JDK build ?
       if test "x$OPENJDK_TARGET_OS" = "xlinux"; then
         if test "x$OPENJDK_TARGET_CPU" = "xs390x" || test "x$OPENJDK_TARGET_CPU" = "xppc64le"; then
    
    From e5bdebd003bd9c4bab81301ec00565948d912fd3 Mon Sep 17 00:00:00 2001
    From: SendaoYan 
    Date: Tue, 5 May 2026 13:33:04 +0000
    Subject: [PATCH 165/168] 8345631:
     TestRegionSamplingLogging.java#generational-rotation intermittent fails
    
    Backport-of: 496317030433f335d64ba75d31aeeebbcc061685
    ---
     .../jtreg/gc/shenandoah/TestRegionSamplingLogging.java        | 4 +++-
     .../jtreg/gc/shenandoah/TestShenandoahRegionLogging.java      | 4 +++-
     2 files changed, 6 insertions(+), 2 deletions(-)
    
    diff --git a/test/hotspot/jtreg/gc/shenandoah/TestRegionSamplingLogging.java b/test/hotspot/jtreg/gc/shenandoah/TestRegionSamplingLogging.java
    index 0017328b517d..5c9536223f31 100644
    --- a/test/hotspot/jtreg/gc/shenandoah/TestRegionSamplingLogging.java
    +++ b/test/hotspot/jtreg/gc/shenandoah/TestRegionSamplingLogging.java
    @@ -59,8 +59,10 @@ public static void main(String[] args) throws Exception {
             }
     
             File directory = new File(".");
    -        File[] files = directory.listFiles((dir, name) -> name.startsWith("region-snapshots") && name.endsWith(".log"));
    +        File[] files = directory.listFiles((dir, name) -> name.startsWith("region-snapshots"));
             System.out.println(Arrays.toString(files));
    +
    +        // Expect one or more log files when region logging is enabled
             if (files == null || files.length == 0) {
                 throw new IllegalStateException("Did not find expected snapshot log file.");
             }
    diff --git a/test/hotspot/jtreg/gc/shenandoah/TestShenandoahRegionLogging.java b/test/hotspot/jtreg/gc/shenandoah/TestShenandoahRegionLogging.java
    index 81e66c9d0cbb..a5fe1589d251 100644
    --- a/test/hotspot/jtreg/gc/shenandoah/TestShenandoahRegionLogging.java
    +++ b/test/hotspot/jtreg/gc/shenandoah/TestShenandoahRegionLogging.java
    @@ -33,6 +33,7 @@
      *      TestShenandoahRegionLogging
      */
     import java.io.File;
    +import java.util.Arrays;
     
     public class TestShenandoahRegionLogging {
         public static void main(String[] args) throws Exception {
    @@ -40,9 +41,10 @@ public static void main(String[] args) throws Exception {
     
             File directory = new File(".");
             File[] files = directory.listFiles((dir, name) -> name.startsWith("region-snapshots"));
    +        System.out.println(Arrays.toString(files));
     
             // Expect one or more log files when region logging is enabled
    -        if (files.length == 0) {
    +        if (files == null || files.length == 0) {
                 throw new Error("Expected at least one log file for region sampling data.");
             }
         }
    
    From 44438d09a4a16664df2f22adbe987ce989035213 Mon Sep 17 00:00:00 2001
    From: Roland Mesde 
    Date: Tue, 5 May 2026 13:39:19 +0000
    Subject: [PATCH 166/168] 8382740: JFR: Disable jdk.OldObjectSample event for
     generational ZGC
    
    Reviewed-by: phh
    Backport-of: 17e91514a85c8b39f837eae0e6154daf523d4de1
    ---
     src/hotspot/share/jfr/leakprofiler/leakProfiler.cpp | 11 +++++++++--
     test/jdk/ProblemList.txt                            |  1 +
     2 files changed, 10 insertions(+), 2 deletions(-)
    
    diff --git a/src/hotspot/share/jfr/leakprofiler/leakProfiler.cpp b/src/hotspot/share/jfr/leakprofiler/leakProfiler.cpp
    index a5b4babb2b19..11be1c87ff41 100644
    --- a/src/hotspot/share/jfr/leakprofiler/leakProfiler.cpp
    +++ b/src/hotspot/share/jfr/leakprofiler/leakProfiler.cpp
    @@ -34,9 +34,15 @@
     #include "runtime/vmThread.hpp"
     
     bool LeakProfiler::is_supported() {
    -  if (UseShenandoahGC) {
    +  if (UseShenandoahGC || UseZGC) {
         // Leak Profiler uses mark words in the ways that might interfere
         // with concurrent GC uses of them. This affects Shenandoah.
    +    //
    +    // Generational ZGC only does weak reference processing in the old generation.
    +    // All objects that would usually die, because we are sampling stuff
    +    // that immediately becomes garbage, will be artificially kept alive
    +    // until an old-generation collection. This incurs a significant
    +    // performance hit by causing allocation stalls.
         return false;
       }
       return true;
    @@ -58,7 +64,8 @@ bool LeakProfiler::start(int sample_count) {
     
       // Exit cleanly if not supported
       if (!is_supported()) {
    -    log_trace(jfr, system)("Object sampling is not supported");
    +    log_info(jfr, system)("jdk.OldObjectSample event is currently not supported for %s.",
    +      UseShenandoahGC ? "ShenandoahGC" : "ZGC");
         return false;
       }
     
    diff --git a/test/jdk/ProblemList.txt b/test/jdk/ProblemList.txt
    index c55468615ecc..8ca72e8cd150 100644
    --- a/test/jdk/ProblemList.txt
    +++ b/test/jdk/ProblemList.txt
    @@ -758,6 +758,7 @@ jdk/jfr/event/compiler/TestCodeSweeper.java                     8338127 generic-
     jdk/jfr/event/oldobject/TestShenandoah.java                     8342951 generic-all
     jdk/jfr/event/runtime/TestResidentSetSizeEvent.java             8309846 aix-ppc64
     jdk/jfr/jvm/TestWaste.java                                      8282427 generic-all
    +jdk/jfr/event/oldobject/TestZ.java                              8375615 generic-all
     jdk/jfr/event/io/TestIOTopFrame.java                            8362556 generic-all
     
     ############################################################################
    
    From 892d08b4d35dfc43dc3332be28e4ddeb887ae1c2 Mon Sep 17 00:00:00 2001
    From: Goetz Lindenmaier 
    Date: Tue, 5 May 2026 14:59:38 +0000
    Subject: [PATCH 167/168] 8340182: Java HttpClient does not follow default
     retry limit of 3 retries
    
    Backport-of: ead4529c9219009fc4224e52e9ac4af5055e7137
    ---
     .../share/classes/module-info.java            |   3 +-
     .../HttpClientAuthRetryLimitTest.java         | 134 ++++++++++++++++++
     2 files changed, 136 insertions(+), 1 deletion(-)
     create mode 100644 test/jdk/java/net/httpclient/HttpClientAuthRetryLimitTest.java
    
    diff --git a/src/java.net.http/share/classes/module-info.java b/src/java.net.http/share/classes/module-info.java
    index 7d7c3fb351ad..14cbb85291d7 100644
    --- a/src/java.net.http/share/classes/module-info.java
    +++ b/src/java.net.http/share/classes/module-info.java
    @@ -156,7 +156,8 @@
      * 
  • *
  • {@systemProperty jdk.httpclient.auth.retrylimit} (default: 3)
    * The number of attempts the Basic authentication filter will attempt to retry a failed - * authentication. + * authentication. The number of authentication attempts is always one greater than the + * retry limit, as the initial request does not count toward the retries. *

  • *
  • {@systemProperty jdk.httpclient.sendBufferSize} (default: operating system * default)
    The HTTP client {@linkplain java.nio.channels.SocketChannel socket} diff --git a/test/jdk/java/net/httpclient/HttpClientAuthRetryLimitTest.java b/test/jdk/java/net/httpclient/HttpClientAuthRetryLimitTest.java new file mode 100644 index 000000000000..818fd8c7a017 --- /dev/null +++ b/test/jdk/java/net/httpclient/HttpClientAuthRetryLimitTest.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +/* + * @test + * @bug 8340182 + * @summary Auth retry limit system property + * @library /test/lib /test/jdk/java/net/httpclient/lib + * @build jdk.httpclient.test.lib.http2.Http2TestServer + * @run junit HttpClientAuthRetryLimitTest + * @run junit/othervm -Djdk.httpclient.auth.retrylimit=1 HttpClientAuthRetryLimitTest + * @run junit/othervm -Djdk.httpclient.auth.retrylimit=0 HttpClientAuthRetryLimitTest + * @run junit/othervm -Djdk.httpclient.auth.retrylimit=-1 HttpClientAuthRetryLimitTest + */ + +import jdk.httpclient.test.lib.common.HttpServerAdapters; +import jdk.test.lib.net.SimpleSSLContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import javax.net.ssl.SSLContext; +import java.io.IOException; +import java.net.Authenticator; +import java.net.PasswordAuthentication; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class HttpClientAuthRetryLimitTest implements HttpServerAdapters { + + private static final SSLContext SSL_CONTEXT = createSslContext(); + + private static SSLContext createSslContext() { + try { + return new SimpleSSLContext().get(); + } catch (IOException exception) { + throw new RuntimeException(exception); + } + } + + // This is the system default value for jdk.httpclient.auth.retrylimit + private static final int DEFAULT_RETRY_LIMIT = 3; + private static final int RETRY_LIMIT = Integer.getInteger( + "jdk.httpclient.auth.retrylimit", DEFAULT_RETRY_LIMIT); + + private static Stream args() { + return Stream.of( + HttpClient.Version.HTTP_1_1, + HttpClient.Version.HTTP_2) + .flatMap(version -> Stream + .of(false, true) + .map(secure -> Arguments.of(version, secure))); + } + + private static HttpClient.Builder createClient(boolean secure) { + HttpClient.Builder builder = HttpClient.newBuilder(); + if (secure) { + builder.sslContext(SSL_CONTEXT); + } + return builder; + } + + @ParameterizedTest + @MethodSource("args") + void testDefaultSystemProperty(HttpClient.Version version, boolean secure) throws Exception { + + AtomicInteger requestCount = new AtomicInteger(0); + + try (HttpTestServer httpTestServer = ((secure)? HttpTestServer.create( + version, SSL_CONTEXT): HttpTestServer.create(version))) { + final String requestUriScheme = secure ? "https" : "http"; + final String requestPath = "/" + this.getClass().getSimpleName() + "/"; + final String uriString = "://" + httpTestServer.serverAuthority() + requestPath; + final URI requestUri = URI.create(requestUriScheme + uriString); + + HttpTestHandler httpTestHandler = t -> { + t.getResponseHeaders() + .addHeader("WWW-Authenticate", "Basic realm=\"Test\""); + t.sendResponseHeaders(401,0); + }; + + httpTestServer.addHandler(httpTestHandler, requestPath); + httpTestServer.start(); + try ( + HttpClient client = createClient(secure) + .authenticator(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + requestCount.incrementAndGet(); + return new PasswordAuthentication("username", "password".toCharArray()); + } + }) + .build()) { + HttpRequest request = HttpRequest.newBuilder().version(version) + .GET() + .uri(requestUri) + .build(); + IOException exception = assertThrows(IOException.class, () -> client.send( + request, HttpResponse.BodyHandlers.discarding())); + assertEquals("too many authentication attempts. Limit: " + RETRY_LIMIT, exception.getMessage()); + int totalRequestCount = requestCount.get(); + assertEquals(totalRequestCount, Math.max(RETRY_LIMIT, 0) + 1); + } + } + } +} From 5cc14e537ce7c6df41d44230ae5512703af1c0a0 Mon Sep 17 00:00:00 2001 From: Goetz Lindenmaier Date: Tue, 5 May 2026 15:04:05 +0000 Subject: [PATCH 168/168] 8359223: HttpClient: Remove leftovers from the SecurityManager cleanup Backport-of: e55ddabffa90e28d22f546b387007fe4e434c3e0 --- .../FilePublisher/FilePublisherPermsTest.java | 255 ---------- .../FilePublisher/SecureZipFSProvider.java | 446 ------------------ .../FilePublisherTest.java | 0 3 files changed, 701 deletions(-) delete mode 100644 test/jdk/java/net/httpclient/FilePublisher/FilePublisherPermsTest.java delete mode 100644 test/jdk/java/net/httpclient/FilePublisher/SecureZipFSProvider.java rename test/jdk/java/net/httpclient/{FilePublisher => }/FilePublisherTest.java (100%) diff --git a/test/jdk/java/net/httpclient/FilePublisher/FilePublisherPermsTest.java b/test/jdk/java/net/httpclient/FilePublisher/FilePublisherPermsTest.java deleted file mode 100644 index bafe1496a868..000000000000 --- a/test/jdk/java/net/httpclient/FilePublisher/FilePublisherPermsTest.java +++ /dev/null @@ -1,255 +0,0 @@ -/* - * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -/* - * @test - * @bug 8235459 - * @summary Confirm that HttpRequest.BodyPublishers#ofFile(Path) - * works as expected - * @library /test/lib /test/jdk/java/net/httpclient/lib - * @build jdk.httpclient.test.lib.common.HttpServerAdapters jdk.test.lib.net.SimpleSSLContext - * SecureZipFSProvider - * @run testng/othervm FilePublisherPermsTest - */ - -import jdk.test.lib.net.SimpleSSLContext; -import org.testng.annotations.AfterTest; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; - -import javax.net.ssl.SSLContext; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpRequest.BodyPublisher; -import java.net.http.HttpRequest.BodyPublishers; -import java.net.http.HttpResponse; -import java.nio.file.FileSystem; -import java.nio.file.FileSystems; -import java.nio.file.Files; -import java.nio.file.Path; -import java.security.*; -import java.util.Map; -import jdk.httpclient.test.lib.common.HttpServerAdapters; - -import static java.lang.System.out; -import static java.net.http.HttpClient.Builder.NO_PROXY; -import static java.net.http.HttpClient.Version.HTTP_1_1; -import static java.net.http.HttpClient.Version.HTTP_2; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.fail; - -public class FilePublisherPermsTest implements HttpServerAdapters { - - SSLContext sslContext; - HttpServerAdapters.HttpTestServer httpTestServer; // HTTP/1.1 [ 4 servers ] - HttpServerAdapters.HttpTestServer httpsTestServer; // HTTPS/1.1 - HttpServerAdapters.HttpTestServer http2TestServer; // HTTP/2 ( h2c ) - HttpServerAdapters.HttpTestServer https2TestServer; // HTTP/2 ( h2 ) - String httpURI; - String httpsURI; - String http2URI; - String https2URI; - - FileSystem zipFs; - static Path zipFsPath; - static Path defaultFsPath; - - String policyFile; - - // Default file system set up - static final String DEFAULT_FS_MSG = "default fs"; - - private Path defaultFsFile() throws Exception { - var file = Path.of("defaultFile.txt"); - if (Files.notExists(file)) { - Files.createFile(file); - Files.writeString(file, DEFAULT_FS_MSG); - } - assertEquals(Files.readString(file), DEFAULT_FS_MSG); - return file; - } - - @DataProvider(name = "defaultFsData") - public Object[][] defaultFsData() { - return new Object[][]{ - { httpURI, defaultFsPath }, - { httpsURI, defaultFsPath }, - { http2URI, defaultFsPath }, - { https2URI, defaultFsPath }, - { httpURI, defaultFsPath }, - { httpsURI, defaultFsPath }, - { http2URI, defaultFsPath }, - { https2URI, defaultFsPath }, - }; - } - - @Test(dataProvider = "defaultFsData") - public void testDefaultFs(String uriString, Path path) - throws Exception { - out.printf("\n\n--- testDefaultFs(%s, %s): starting\n", - uriString, path); - BodyPublisher bodyPublisher = BodyPublishers.ofFile(path); - send(uriString, bodyPublisher); - } - - // Zip File system set up - static final String ZIP_FS_MSG = "zip fs"; - - static FileSystem newZipFs(Path zipFile) throws Exception { - return FileSystems.newFileSystem(zipFile, Map.of("create", "true")); - } - - static FileSystem newSecureZipFs(Path zipFile) throws Exception { - FileSystem fs = newZipFs(zipFile); - return new SecureZipFSProvider(fs.provider()).newFileSystem(fs); - } - - static Path zipFsFile(FileSystem fs) throws Exception { - var file = fs.getPath("fileInZip.txt"); - if (Files.notExists(file)) { - Files.createFile(file); - Files.writeString(file, ZIP_FS_MSG); - } - assertEquals(Files.readString(file), ZIP_FS_MSG); - return file; - } - - @DataProvider(name = "zipFsData") - public Object[][] zipFsData() { - return new Object[][]{ - { httpURI, zipFsPath }, - { httpsURI, zipFsPath }, - { http2URI, zipFsPath }, - { https2URI, zipFsPath }, - { httpURI, zipFsPath }, - { httpsURI, zipFsPath }, - { http2URI, zipFsPath }, - { https2URI, zipFsPath }, - }; - } - - @Test(dataProvider = "zipFsData") - public void testZipFs(String uriString, Path path) throws Exception { - out.printf("\n\n--- testZipFsCustomPerm(%s, %s): starting\n", uriString, path); - BodyPublisher bodyPublisher = BodyPublishers.ofFile(path); - send(uriString, bodyPublisher); - } - - @Test - public void testFileNotFound() throws Exception { - out.printf("\n\n--- testFileNotFound(): starting\n"); - var zipPath = Path.of("fileNotFound.zip"); - try (FileSystem fs = newZipFs(zipPath)) { - Path fileInZip = zipFsFile(fs); - Files.deleteIfExists(fileInZip); - BodyPublishers.ofFile(fileInZip); - fail(); - } catch (FileNotFoundException e) { - out.println("Caught expected: " + e); - } - var path = Path.of("fileNotFound.txt"); - try { - Files.deleteIfExists(path); - BodyPublishers.ofFile(path); - fail(); - } catch (FileNotFoundException e) { - out.println("Caught expected: " + e); - } - } - - private void send(String uriString, BodyPublisher bodyPublisher) - throws Exception { - HttpClient client = HttpClient.newBuilder() - .proxy(NO_PROXY) - .sslContext(sslContext) - .build(); - var req = HttpRequest.newBuilder(URI.create(uriString)) - .POST(bodyPublisher) - .build(); - client.send(req, HttpResponse.BodyHandlers.discarding()); - } - - - static class HttpEchoHandler implements HttpServerAdapters.HttpTestHandler { - @Override - public void handle(HttpServerAdapters.HttpTestExchange t) throws IOException { - try (InputStream is = t.getRequestBody(); - OutputStream os = t.getResponseBody()) { - byte[] bytes = is.readAllBytes(); - t.sendResponseHeaders(200, bytes.length); - os.write(bytes); - } - } - } - - @BeforeTest - public void setup() throws Exception { - sslContext = new SimpleSSLContext().get(); - if (sslContext == null) - throw new AssertionError("Unexpected null sslContext"); - - zipFs = newSecureZipFs(Path.of("file.zip")); - zipFsPath = zipFsFile(zipFs); - defaultFsPath = defaultFsFile(); - - httpTestServer = HttpServerAdapters.HttpTestServer.create(HTTP_1_1); - httpTestServer.addHandler( - new FilePublisherPermsTest.HttpEchoHandler(), "/http1/echo"); - httpURI = "http://" + httpTestServer.serverAuthority() + "/http1/echo"; - - httpsTestServer = HttpServerAdapters.HttpTestServer.create(HTTP_1_1, sslContext); - httpsTestServer.addHandler( - new FilePublisherPermsTest.HttpEchoHandler(), "/https1/echo"); - httpsURI = "https://" + httpsTestServer.serverAuthority() + "/https1/echo"; - - http2TestServer = HttpServerAdapters.HttpTestServer.create(HTTP_2); - http2TestServer.addHandler( - new FilePublisherPermsTest.HttpEchoHandler(), "/http2/echo"); - http2URI = "http://" + http2TestServer.serverAuthority() + "/http2/echo"; - - https2TestServer = HttpServerAdapters.HttpTestServer.create(HTTP_2, sslContext); - https2TestServer.addHandler( - new FilePublisherPermsTest.HttpEchoHandler(), "/https2/echo"); - https2URI = "https://" + https2TestServer.serverAuthority() + "/https2/echo"; - - httpTestServer.start(); - httpsTestServer.start(); - http2TestServer.start(); - https2TestServer.start(); - } - - @AfterTest - public void teardown() throws Exception { - httpTestServer.stop(); - httpsTestServer.stop(); - http2TestServer.stop(); - https2TestServer.stop(); - zipFs.close(); - } -} diff --git a/test/jdk/java/net/httpclient/FilePublisher/SecureZipFSProvider.java b/test/jdk/java/net/httpclient/FilePublisher/SecureZipFSProvider.java deleted file mode 100644 index 00f532ba47c8..000000000000 --- a/test/jdk/java/net/httpclient/FilePublisher/SecureZipFSProvider.java +++ /dev/null @@ -1,446 +0,0 @@ -/* - * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; -import java.nio.channels.FileChannel; -import java.nio.channels.SeekableByteChannel; -import java.nio.file.AccessMode; -import java.nio.file.CopyOption; -import java.nio.file.DirectoryStream; -import java.nio.file.FileStore; -import java.nio.file.FileSystem; -import java.nio.file.LinkOption; -import java.nio.file.OpenOption; -import java.nio.file.Path; -import java.nio.file.PathMatcher; -import java.nio.file.ProviderMismatchException; -import java.nio.file.WatchEvent; -import java.nio.file.WatchKey; -import java.nio.file.WatchService; -import java.nio.file.attribute.BasicFileAttributes; -import java.nio.file.attribute.FileAttribute; -import java.nio.file.attribute.FileAttributeView; -import java.nio.file.attribute.UserPrincipalLookupService; -import java.nio.file.spi.FileSystemProvider; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -public class SecureZipFSProvider extends FileSystemProvider { - private final ConcurrentHashMap map = - new ConcurrentHashMap<>(); - private final FileSystemProvider defaultProvider; - - public SecureZipFSProvider(FileSystemProvider provider) { - defaultProvider = provider; - } - - @Override - public String getScheme() { - return "jar"; - } - - public FileSystem newFileSystem(FileSystem fs) { - return map.computeIfAbsent(fs, (sfs) -> - new SecureZipFS(this, fs)); - } - - @Override - public FileSystem newFileSystem(URI uri, Map env) - throws IOException { - FileSystem fs = defaultProvider.newFileSystem(uri, env); - return map.computeIfAbsent(fs, (sfs) -> - new SecureZipFS(this, fs) - ); - } - - @Override - public FileSystem getFileSystem(URI uri) { - return map.get(defaultProvider.getFileSystem(uri)); - } - - @Override - public Path getPath(URI uri) { - Path p = defaultProvider.getPath(uri); - return map.get(defaultProvider.getFileSystem(uri)).wrap(p); - } - - @Override - public InputStream newInputStream(Path path, OpenOption... options) - throws IOException { - Path p = toTestPath(path).unwrap(); - return defaultProvider.newInputStream(p, options); - } - - @Override - public SeekableByteChannel newByteChannel(Path path, - Set options, - FileAttribute... attrs) - throws IOException { - Path p = toTestPath(path).unwrap(); - return defaultProvider.newByteChannel(p, options, attrs); - } - - @Override - public FileChannel newFileChannel(Path path, - Set options, - FileAttribute... attrs) - throws IOException { - Path p = toTestPath(path).unwrap(); - return defaultProvider.newFileChannel(p, options, attrs); - } - - - @Override - public DirectoryStream newDirectoryStream(Path dir, - DirectoryStream.Filter filter) { - throw new RuntimeException("not implemented"); - } - - @Override - public void createDirectory(Path dir, FileAttribute... attrs) - throws IOException { - Path p = toTestPath(dir).unwrap(); - defaultProvider.createDirectory(p, attrs); - } - - @Override - public void delete(Path path) throws IOException { - Path p = toTestPath(path).unwrap(); - defaultProvider.delete(p); - } - - @Override - public void copy(Path source, Path target, CopyOption... options) - throws IOException { - Path sp = toTestPath(source).unwrap(); - Path tp = toTestPath(target).unwrap(); - defaultProvider.copy(sp, tp, options); - } - - @Override - public void move(Path source, Path target, CopyOption... options) - throws IOException { - Path sp = toTestPath(source).unwrap(); - Path tp = toTestPath(target).unwrap(); - defaultProvider.move(sp, tp, options); - } - - @Override - public boolean isSameFile(Path path, Path path2) - throws IOException { - Path p = toTestPath(path).unwrap(); - Path p2 = toTestPath(path2).unwrap(); - return defaultProvider.isSameFile(p, p2); - } - - @Override - public boolean isHidden(Path path) throws IOException { - Path p = toTestPath(path).unwrap(); - return defaultProvider.isHidden(p); - } - - @Override - public FileStore getFileStore(Path path) throws IOException { - Path p = toTestPath(path).unwrap(); - return defaultProvider.getFileStore(p); - } - - @Override - public void checkAccess(Path path, AccessMode... modes) throws IOException { - Path p = toTestPath(path).unwrap(); - defaultProvider.checkAccess(p, modes); - } - - @Override - public V getFileAttributeView(Path path, - Class type, - LinkOption... options) { - Path p = toTestPath(path).unwrap(); - return defaultProvider.getFileAttributeView(p, type, options); - } - - @Override - public A readAttributes(Path path, - Class type, - LinkOption... options) - throws IOException { - Path p = toTestPath(path).unwrap(); - return defaultProvider.readAttributes(p, type, options); - } - - @Override - public Map readAttributes(Path path, - String attributes, - LinkOption... options) - throws IOException { - Path p = toTestPath(path).unwrap(); - return defaultProvider.readAttributes(p, attributes, options); - } - - @Override - public void setAttribute(Path path, String attribute, - Object value, LinkOption... options) - throws IOException { - Path p = toTestPath(path).unwrap(); - defaultProvider.setAttribute(p, attribute, options); - } - - // Checks that the given file is a TestPath - static TestPath toTestPath(Path obj) { - if (obj == null) - throw new NullPointerException(); - if (!(obj instanceof TestPath)) - throw new ProviderMismatchException(); - return (TestPath) obj; - } - - static class SecureZipFS extends FileSystem { - private final SecureZipFSProvider provider; - private final FileSystem delegate; - - public SecureZipFS(SecureZipFSProvider provider, FileSystem delegate) { - this.provider = provider; - this.delegate = delegate; - } - - Path wrap(Path path) { - return (path != null) ? new TestPath(this, path) : null; - } - - Path unwrap(Path wrapper) { - if (wrapper == null) - throw new NullPointerException(); - if (!(wrapper instanceof TestPath)) - throw new ProviderMismatchException(); - return ((TestPath) wrapper).unwrap(); - } - - @Override - public FileSystemProvider provider() { - return provider; - } - - @Override - public void close() throws IOException { - delegate.close(); - } - - @Override - public boolean isOpen() { - return delegate.isOpen(); - } - - @Override - public boolean isReadOnly() { - return delegate.isReadOnly(); - } - - @Override - public String getSeparator() { - return delegate.getSeparator(); - } - - @Override - public Iterable getRootDirectories() { - return delegate.getRootDirectories(); - } - - @Override - public Iterable getFileStores() { - return delegate.getFileStores(); - } - - @Override - public Set supportedFileAttributeViews() { - return delegate.supportedFileAttributeViews(); - } - - @Override - public Path getPath(String first, String... more) { - return wrap(delegate.getPath(first, more)); - } - - @Override - public PathMatcher getPathMatcher(String syntaxAndPattern) { - return delegate.getPathMatcher(syntaxAndPattern); - } - - @Override - public UserPrincipalLookupService getUserPrincipalLookupService() { - return delegate.getUserPrincipalLookupService(); - } - - @Override - public WatchService newWatchService() throws IOException { - return delegate.newWatchService(); - } - } - - static class TestPath implements Path { - private final SecureZipFS fs; - private final Path delegate; - - TestPath(SecureZipFS fs, Path delegate) { - this.fs = fs; - this.delegate = delegate; - } - - Path unwrap() { - return delegate; - } - - @Override - public SecureZipFS getFileSystem() { - return fs; - } - - @Override - public boolean isAbsolute() { - return delegate.isAbsolute(); - } - - @Override - public Path getRoot() { - return fs.wrap(delegate.getRoot()); - } - - @Override - public Path getFileName() { - return fs.wrap(delegate.getFileName()); - } - - @Override - public Path getParent() { - return fs.wrap(delegate.getParent()); - } - - @Override - public int getNameCount() { - return delegate.getNameCount(); - } - - @Override - public Path getName(int index) { - return fs.wrap(delegate.getName(index)); - } - - @Override - public Path subpath(int beginIndex, int endIndex) { - return fs.wrap(delegate.subpath(beginIndex, endIndex)); - } - - @Override - public boolean startsWith(Path other) { - return delegate.startsWith(other); - } - - @Override - public boolean endsWith(Path other) { - return delegate.endsWith(other); - } - - @Override - public Path normalize() { - return fs.wrap(delegate.normalize()); - } - - @Override - public Path resolve(Path other) { - return fs.wrap(delegate.resolve(fs.wrap(other))); - } - - @Override - public Path relativize(Path other) { - return fs.wrap(delegate.relativize(fs.wrap(other))); - } - - @Override - public URI toUri() { - String ssp = delegate.toUri().getSchemeSpecificPart(); - return URI.create(fs.provider().getScheme() + ":" + ssp); - } - - @Override - public Path toAbsolutePath() { - return fs.wrap(delegate.toAbsolutePath()); - } - - @Override - public Path toRealPath(LinkOption... options) throws IOException { - return fs.wrap(delegate.toRealPath(options)); - } - - @Override - public WatchKey register(WatchService watcher, - WatchEvent.Kind[] events, - WatchEvent.Modifier... modifiers) - throws IOException { - return delegate.register(watcher, events, modifiers); - } - - @Override - public Iterator iterator() { - final Iterator itr = delegate.iterator(); - return new Iterator<>() { - @Override - public boolean hasNext() { - return itr.hasNext(); - } - - @Override - public Path next() { - return fs.wrap(itr.next()); - } - - @Override - public void remove() { - itr.remove(); - } - }; - } - - @Override - public int compareTo(Path other) { - return delegate.compareTo(fs.unwrap(other)); - } - - @Override - public int hashCode() { - return delegate.hashCode(); - } - - @Override - public boolean equals(Object other) { - return other instanceof TestPath && delegate.equals(fs.unwrap((TestPath) other)); - } - - @Override - public String toString() { - return delegate.toString(); - } - } -} diff --git a/test/jdk/java/net/httpclient/FilePublisher/FilePublisherTest.java b/test/jdk/java/net/httpclient/FilePublisherTest.java similarity index 100% rename from test/jdk/java/net/httpclient/FilePublisher/FilePublisherTest.java rename to test/jdk/java/net/httpclient/FilePublisherTest.java