From 62e6efe8764f65594a0d613c162aa7d8a7faa8f3 Mon Sep 17 00:00:00 2001 From: SendaoYan Date: Wed, 4 Mar 2026 05:56:15 +0000 Subject: [PATCH 001/234] 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 b780733d2974a2a7ccb3a8daf65da1633a91ac74 Mon Sep 17 00:00:00 2001 From: Goetz Lindenmaier Date: Wed, 4 Mar 2026 10:22:21 +0000 Subject: [PATCH 002/234] 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 4e5a725b545db4a0c6c1d5022a9b614df505bd7e Mon Sep 17 00:00:00 2001 From: Fei Yang Date: Sun, 8 Mar 2026 01:35:38 +0000 Subject: [PATCH 003/234] 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 d36372f35cd1a6f0d665cc03a77bf7478e362ea4 Mon Sep 17 00:00:00 2001 From: Fei Yang Date: Sun, 8 Mar 2026 01:37:14 +0000 Subject: [PATCH 004/234] 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 a73304f69addd70c53ed2415633d94155e5e5c97 Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Mon, 9 Mar 2026 07:37:00 +0000 Subject: [PATCH 005/234] 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 ce547da76137f0543a5d1db3d5f1b9bec8603ed4 Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Mon, 9 Mar 2026 07:44:21 +0000 Subject: [PATCH 006/234] 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 933b0d9f56bcd90e4f7d1230500a90c971ac48d2 Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Mon, 9 Mar 2026 07:55:43 +0000 Subject: [PATCH 007/234] 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/234] 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 aafdb5e6bc63cb9d26b05b5259bda1480625f069 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Tue, 10 Mar 2026 14:39:47 +0000 Subject: [PATCH 009/234] 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 845a806e0140..20eff01050ec 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 dd9254f303b7..83a8c31d0a00 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 @@ -374,6 +374,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 978707bdcbd576d03ce54ea8a365e295d1321976 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Tue, 10 Mar 2026 14:42:44 +0000 Subject: [PATCH 010/234] 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 cb203b0a0d0e65c3a22e18fa2dc3b4df07653be2 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Tue, 10 Mar 2026 14:43:06 +0000 Subject: [PATCH 011/234] 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 6d090a2f31a1..c2c6da5d7cd0 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 @@ -332,20 +332,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) { @@ -370,7 +359,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(); } @@ -384,7 +373,7 @@ public void print(PrintRequestAttributeSet attributes) throws PrinterException { onEventThread = false; try { - printLoop(true, firstPage, lastPage); + printLoop(true, firstPage, totalPages); } catch (Exception e) { e.printStackTrace(); } @@ -394,7 +383,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 { @@ -644,7 +632,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 debeb9fb291512bf77965bb6aaefcca96e9f0a23 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Tue, 10 Mar 2026 14:43:30 +0000 Subject: [PATCH 012/234] 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 f2a7a383548ddffd608d199853347bf152519ead Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Tue, 10 Mar 2026 14:45:58 +0000 Subject: [PATCH 013/234] 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 a89ff8d3f601d310d86df4946521a585ecad1d1f Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Tue, 10 Mar 2026 14:49:35 +0000 Subject: [PATCH 014/234] 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 fd5d0b1ecf1dc04ca7d9c776aaa17faf83baa6e5 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Tue, 10 Mar 2026 14:53:54 +0000 Subject: [PATCH 015/234] 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 82ceb35a3aa78524db0176c63f208297d7e6a27b Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Tue, 10 Mar 2026 14:54:34 +0000 Subject: [PATCH 016/234] 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 24671e64433545751e5334b68cbcea098b9844e5 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Tue, 10 Mar 2026 14:58:25 +0000 Subject: [PATCH 017/234] 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 501694f1d2ef99babc30ff09e6339c8066d35859 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Tue, 10 Mar 2026 14:59:10 +0000 Subject: [PATCH 018/234] 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 3adb14d27cf66105ca4d8a924c5d60c698d6e4f5 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Tue, 10 Mar 2026 14:59:34 +0000 Subject: [PATCH 019/234] 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 6505328099dcf06e2cac9c41e3aaf3d1c1d9093e Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Tue, 10 Mar 2026 15:00:05 +0000 Subject: [PATCH 020/234] 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 985d42d26999da1570e76e8911b32c29d1bb9a08 Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Tue, 10 Mar 2026 18:51:21 +0000 Subject: [PATCH 021/234] 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 210f58e134f8..8b8c641c57c1 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 efb68e75b827fca71612d48ca8c37d9fd6693da8 Mon Sep 17 00:00:00 2001 From: Ozan Cetin Date: Wed, 11 Mar 2026 14:23:22 +0000 Subject: [PATCH 022/234] 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 3b908753a64b26f7b590584296f86e4dbb21faaa Mon Sep 17 00:00:00 2001 From: William Kemper Date: Thu, 12 Mar 2026 00:35:22 +0000 Subject: [PATCH 023/234] 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 26405071e754ae4955531e508fa61246e96cbd2c Mon Sep 17 00:00:00 2001 From: Fei Yang Date: Thu, 12 Mar 2026 11:15:12 +0000 Subject: [PATCH 024/234] 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 640e10808d5d55a02f73b3d931d2071c4dfba41a Mon Sep 17 00:00:00 2001 From: William Kemper Date: Thu, 12 Mar 2026 15:41:55 +0000 Subject: [PATCH 025/234] 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 8c1357a1b87213001499b00e10f9b2d411492c63 Mon Sep 17 00:00:00 2001 From: SendaoYan Date: Mon, 16 Mar 2026 09:04:30 +0000 Subject: [PATCH 026/234] 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 eb451c577334395c138ff23aae144f727ade03ba Mon Sep 17 00:00:00 2001 From: SendaoYan Date: Mon, 16 Mar 2026 09:26:41 +0000 Subject: [PATCH 027/234] 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 264426530dd78070fcff3c350dd99d588f7cc34a Mon Sep 17 00:00:00 2001 From: Richard Reingruber Date: Mon, 16 Mar 2026 09:29:21 +0000 Subject: [PATCH 028/234] 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 396306550607cf7bead3916e426cc2f3ae67b41f 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/234] 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 faf8671b0dd654f68b3ea367e2c4117ec9417d8f Mon Sep 17 00:00:00 2001 From: Goetz Lindenmaier Date: Mon, 16 Mar 2026 09:57:57 +0000 Subject: [PATCH 030/234] 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 ++- .../prims/jvmtiEnhancedRedefineClasses.cpp | 15 ++- .../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 +- src/hotspot/share/utilities/vmError.cpp | 18 ++- 24 files changed, 276 insertions(+), 172 deletions(-) diff --git a/src/hotspot/os/aix/os_aix.cpp b/src/hotspot/os/aix/os_aix.cpp index 1872e24920e5..12fa3503c188 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. @@ -2271,7 +2274,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 e8b852ad9dfe..6859d44a2803 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 } @@ -1469,11 +1474,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 42cbfdf72d61..6aebf5a19071 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"); } @@ -2597,10 +2621,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 f730e54ed587..b4fcc1c54055 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(); } @@ -4110,7 +4127,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; @@ -4340,8 +4357,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 9e6692abcacb..44602ab3e31b 100644 --- a/src/hotspot/share/compiler/compileBroker.cpp +++ b/src/hotspot/share/compiler/compileBroker.cpp @@ -1060,7 +1060,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/jvmtiEnhancedRedefineClasses.cpp b/src/hotspot/share/prims/jvmtiEnhancedRedefineClasses.cpp index 614470f22c9b..f6207f58d8a7 100644 --- a/src/hotspot/share/prims/jvmtiEnhancedRedefineClasses.cpp +++ b/src/hotspot/share/prims/jvmtiEnhancedRedefineClasses.cpp @@ -1150,9 +1150,11 @@ jvmtiError VM_EnhancedRedefineClasses::load_new_class_versions_single_step(Old2N } } + size_t avail_mem = 0; + (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); // class bytes... const unsigned char* class_bytes; @@ -1304,8 +1306,9 @@ jvmtiError VM_EnhancedRedefineClasses::load_new_class_versions_single_step(Old2N _object_klass_redefined = true; } + (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; @@ -2366,9 +2369,11 @@ void VM_EnhancedRedefineClasses::redefine_single_class(Thread *current, Instance // increment the classRedefinedCount field in the_class and in any // direct and indirect subclasses of the_class increment_class_counter(current, new_class); + size_t avail_mem = 0; + (void)os::available_memory(avail_mem); log_info(redefine, class, load) - ("redefined name=%s, count=%d (avail_mem=" UINT64_FORMAT "K)", - new_class->external_name(), java_lang_Class::classRedefinedCount(new_class->java_mirror()), os::available_memory() >> 10); + ("redefined name=%s, count=%d (avail_mem=%zuK)", + new_class->external_name(), java_lang_Class::classRedefinedCount(new_class->java_mirror()), avail_mem >> 10); Events::log_redefinition(current, "redefined class name=%s, count=%d", new_class->external_name(), java_lang_Class::classRedefinedCount(new_class->java_mirror())); diff --git a/src/hotspot/share/prims/jvmtiRedefineClasses.cpp b/src/hotspot/share/prims/jvmtiRedefineClasses.cpp index d2c1fc423f94..5b448bcc5da0 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, @@ -1524,9 +1526,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; @@ -4423,9 +4426,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 00dd260c750b..282d852b095a 100644 --- a/src/hotspot/share/runtime/arguments.cpp +++ b/src/hotspot/share/runtime/arguments.cpp @@ -1534,13 +1534,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; } @@ -1662,7 +1662,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 6f8d492e1403..054d91b409a2 100644 --- a/src/hotspot/share/runtime/os.cpp +++ b/src/hotspot/share/runtime/os.cpp @@ -1184,9 +1184,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); @@ -1941,10 +1942,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) { @@ -2203,16 +2204,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 b23336f2b739..cb4818e814be 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; diff --git a/src/hotspot/share/utilities/vmError.cpp b/src/hotspot/share/utilities/vmError.cpp index f3ca6be60421..415c4ad1bc5c 100644 --- a/src/hotspot/share/utilities/vmError.cpp +++ b/src/hotspot/share/utilities/vmError.cpp @@ -2395,10 +2395,12 @@ static void print_process_memory_usage_platform(outputStream *st) os::Linux::meminfo_t info; if (os::Linux::query_process_memory_info(&info)) { ssize_t phys_total_kb = os::physical_memory() / K; - ssize_t phys_avail_kb = os::available_memory() / K; + size_t avail_mem = 0; + (void)os::available_memory(avail_mem); + size_t phys_avail_kb = avail_mem / K; int rss_percentile = (int)(info.vmrss * 100.0 / phys_total_kb); st->print_cr("Resident Set Size: %zdK (%d%% of " - "%zdK total physical memory with %zdK free physical memory)", + "%zdK total physical memory with %zuK free physical memory)", info.vmrss, rss_percentile, phys_total_kb, phys_avail_kb); } else { st->print_cr("Could not open /proc/self/status to get process memory related information"); @@ -2418,10 +2420,12 @@ static void print_process_memory_usage_platform(outputStream *st) if (ret != 0) { ssize_t rss_kb = pmex.WorkingSetSize / K; ssize_t phys_total_kb = os::physical_memory() / K; - ssize_t phys_avail_kb = os::available_memory() / K; + size_t avail_mem = 0; + (void)os::available_memory(avail_mem); + size_t phys_avail_kb = avail_mem / K; int rss_percentile = (int)(rss_kb * 100.0 / phys_total_kb); st->print_cr("Resident Set Size: %zdK (%d%% of " - "%zdK total physical memory with %zdK free physical memory)", + "%zdK total physical memory with %zuK free physical memory)", rss_kb, rss_percentile, phys_total_kb, phys_avail_kb); } else { st->print_cr("GetProcessMemoryInfo() call did not succeed"); @@ -2440,10 +2444,12 @@ static void print_process_memory_usage_platform(outputStream *st) if (ret == KERN_SUCCESS) { ssize_t rss_kb = info.resident_size / K; ssize_t phys_total_kb = os::physical_memory() / K; - ssize_t phys_avail_kb = os::available_memory() / K; + size_t avail_mem = 0; + (void)os::available_memory(avail_mem); + size_t phys_avail_kb = avail_mem / K; int rss_percentile = (int)(rss_kb * 100.0 / phys_total_kb); st->print_cr("Resident Set Size: %zdK (%d%% of " - "%zdK total physical memory with %zdK free physical memory)", + "%zdK total physical memory with %zuK free physical memory)", rss_kb, rss_percentile, phys_total_kb, phys_avail_kb); } else { st->print_cr("task_info() call did not succeed"); From e9f70cb472a98fc7437789def7a976c2e86f4df2 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 16 Mar 2026 14:53:36 +0000 Subject: [PATCH 031/234] 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 d5eadae62908dc319be0ebd2e77df22acabc4702 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 16 Mar 2026 15:20:25 +0000 Subject: [PATCH 032/234] 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 e459cb33b1c4429b89dd3e4db0387454da11f689 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 16 Mar 2026 15:21:01 +0000 Subject: [PATCH 033/234] 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 82f543f4ac80..e0a20331cd22 100644 --- a/test/jdk/ProblemList.txt +++ b/test/jdk/ProblemList.txt @@ -457,7 +457,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 @@ -495,7 +494,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 2082cc959fa9877a4da2c2775fdd5625cf61e515 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 16 Mar 2026 15:21:39 +0000 Subject: [PATCH 034/234] 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 018a8dc4c616675f3801ec57ea469758fe01a971 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 16 Mar 2026 15:22:09 +0000 Subject: [PATCH 035/234] 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 96de7bc95a5b..6ba023439fb6 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 cb8f6d78c69df20b44ad1abf45e97275dfc2d125 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 16 Mar 2026 15:25:33 +0000 Subject: [PATCH 036/234] 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 e0a20331cd22..07dee1cff04d 100644 --- a/test/jdk/ProblemList.txt +++ b/test/jdk/ProblemList.txt @@ -189,7 +189,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 98709bcfb348a68a7dc006799fce4414ec404528 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 16 Mar 2026 15:31:42 +0000 Subject: [PATCH 037/234] =?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 80fa452baa8c03e34e21f3e1e8fea6670b2dcc65 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 16 Mar 2026 15:32:32 +0000 Subject: [PATCH 038/234] 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 b185e474c3568d26a9e7b46706f3c5fbea243631 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 16 Mar 2026 15:33:01 +0000 Subject: [PATCH 039/234] 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 51d3b4d8c66ad23bf9137ae1478504ff762483cb Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 16 Mar 2026 15:33:32 +0000 Subject: [PATCH 040/234] 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 4ae2aa846373c1f15a4a76c0dcecf8530148d494 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 16 Mar 2026 15:34:02 +0000 Subject: [PATCH 041/234] 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 8be1355aedfdd39a3ea79a29ecedf65a1664ecdd Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 16 Mar 2026 15:34:25 +0000 Subject: [PATCH 042/234] 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/234] 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 0bbba234386677d1f5e7b2bdb6d2b4423f7ecc25 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 16 Mar 2026 15:35:27 +0000 Subject: [PATCH 044/234] 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 1bcd3302ef38f1bf4b6a10c40ce32150c69e450f Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 16 Mar 2026 15:35:53 +0000 Subject: [PATCH 045/234] 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 07dee1cff04d..ae781681b2d0 100644 --- a/test/jdk/ProblemList.txt +++ b/test/jdk/ProblemList.txt @@ -412,7 +412,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 d7992eb667bc636d6281d2cc45ee98a07008552d Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 16 Mar 2026 15:36:26 +0000 Subject: [PATCH 046/234] 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 ae781681b2d0..c18ee5a326f8 100644 --- a/test/jdk/ProblemList.txt +++ b/test/jdk/ProblemList.txt @@ -273,6 +273,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 60ff01e95fd683a8a22596b0341bb132ea7ff048 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 16 Mar 2026 15:36:48 +0000 Subject: [PATCH 047/234] 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 1c5ffb27a83f2bf7c524c41e801da26ea4ac2785 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 16 Mar 2026 15:39:04 +0000 Subject: [PATCH 048/234] 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 1abb8f1680d756dae33273f4b2167b2b918d380b Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Tue, 17 Mar 2026 12:44:07 +0000 Subject: [PATCH 049/234] 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 def394e4b8f48d9eeba4134c75b6047bc4d023a9 Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Tue, 17 Mar 2026 15:36:50 +0000 Subject: [PATCH 050/234] 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 1ef56aa7fc818548c42117b72eae94864583819a Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Tue, 17 Mar 2026 15:37:23 +0000 Subject: [PATCH 051/234] 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 5d94ee5278b3b71b90fec38834b9aed2e740dbdc Mon Sep 17 00:00:00 2001 From: Mat Carter Date: Tue, 17 Mar 2026 16:26:11 +0000 Subject: [PATCH 052/234] 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 c1413929b6c3..f7d7aa15fc47 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 7be1ac004ed6475626979f3a9af6e168e4ad1f84 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Tue, 17 Mar 2026 18:02:19 +0000 Subject: [PATCH 053/234] 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 c5156fe236de6c328c32928793f3cb30ad350f2f Mon Sep 17 00:00:00 2001 From: Srinivas Vamsi Parasa Date: Tue, 17 Mar 2026 21:01:42 +0000 Subject: [PATCH 054/234] =?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 62b25457def7a0036807008997660641bd3bcba7 Mon Sep 17 00:00:00 2001 From: Goetz Lindenmaier Date: Wed, 18 Mar 2026 13:52:35 +0000 Subject: [PATCH 055/234] 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 ++-- .../prims/jvmtiEnhancedRedefineClasses.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 ++ src/hotspot/share/utilities/vmError.cpp | 18 +++--- 22 files changed, 166 insertions(+), 159 deletions(-) diff --git a/src/hotspot/os/aix/os_aix.cpp b/src/hotspot/os/aix/os_aix.cpp index 12fa3503c188..c9806bad0440 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. @@ -2274,7 +2274,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 6859d44a2803..5c5c3e3c08ba 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 } @@ -1474,12 +1474,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 6aebf5a19071..c810fd84cc7e 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"); } @@ -2621,12 +2621,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 b4fcc1c54055..7cf500c8c92d 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(); } @@ -4124,25 +4124,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; @@ -4361,7 +4361,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 44602ab3e31b..c31563a3c2e5 100644 --- a/src/hotspot/share/compiler/compileBroker.cpp +++ b/src/hotspot/share/compiler/compileBroker.cpp @@ -1060,7 +1060,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/jvmtiEnhancedRedefineClasses.cpp b/src/hotspot/share/prims/jvmtiEnhancedRedefineClasses.cpp index f6207f58d8a7..a2edf7296575 100644 --- a/src/hotspot/share/prims/jvmtiEnhancedRedefineClasses.cpp +++ b/src/hotspot/share/prims/jvmtiEnhancedRedefineClasses.cpp @@ -1150,10 +1150,10 @@ jvmtiError VM_EnhancedRedefineClasses::load_new_class_versions_single_step(Old2N } } - size_t avail_mem = 0; + physical_memory_size_type avail_mem = 0; (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); // class bytes... @@ -1308,7 +1308,7 @@ jvmtiError VM_EnhancedRedefineClasses::load_new_class_versions_single_step(Old2N (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; @@ -2369,10 +2369,10 @@ void VM_EnhancedRedefineClasses::redefine_single_class(Thread *current, Instance // increment the classRedefinedCount field in the_class and in any // direct and indirect subclasses of the_class increment_class_counter(current, new_class); - size_t avail_mem = 0; + physical_memory_size_type avail_mem = 0; (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)", new_class->external_name(), java_lang_Class::classRedefinedCount(new_class->java_mirror()), avail_mem >> 10); Events::log_redefinition(current, "redefined class name=%s, count=%d", new_class->external_name(), diff --git a/src/hotspot/share/prims/jvmtiRedefineClasses.cpp b/src/hotspot/share/prims/jvmtiRedefineClasses.cpp index 5b448bcc5da0..3a8464c9752d 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, @@ -1529,7 +1529,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; @@ -4426,11 +4426,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 282d852b095a..7c44e76dd839 100644 --- a/src/hotspot/share/runtime/arguments.cpp +++ b/src/hotspot/share/runtime/arguments.cpp @@ -1662,7 +1662,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 054d91b409a2..462c3a4073aa 100644 --- a/src/hotspot/share/runtime/os.cpp +++ b/src/hotspot/share/runtime/os.cpp @@ -1184,13 +1184,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); @@ -1935,17 +1935,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) { @@ -2204,22 +2204,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 cb4818e814be..dfe46f6ddd3f 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 diff --git a/src/hotspot/share/utilities/vmError.cpp b/src/hotspot/share/utilities/vmError.cpp index 415c4ad1bc5c..6447376bad82 100644 --- a/src/hotspot/share/utilities/vmError.cpp +++ b/src/hotspot/share/utilities/vmError.cpp @@ -2395,12 +2395,12 @@ static void print_process_memory_usage_platform(outputStream *st) os::Linux::meminfo_t info; if (os::Linux::query_process_memory_info(&info)) { ssize_t phys_total_kb = os::physical_memory() / K; - size_t avail_mem = 0; + physical_memory_size_type avail_mem = 0; (void)os::available_memory(avail_mem); - size_t phys_avail_kb = avail_mem / K; + physical_memory_size_type phys_avail_kb = avail_mem / K; int rss_percentile = (int)(info.vmrss * 100.0 / phys_total_kb); st->print_cr("Resident Set Size: %zdK (%d%% of " - "%zdK total physical memory with %zuK free physical memory)", + "%zdK total physical memory with " PHYS_MEM_TYPE_FORMAT "K free physical memory)", info.vmrss, rss_percentile, phys_total_kb, phys_avail_kb); } else { st->print_cr("Could not open /proc/self/status to get process memory related information"); @@ -2420,12 +2420,12 @@ static void print_process_memory_usage_platform(outputStream *st) if (ret != 0) { ssize_t rss_kb = pmex.WorkingSetSize / K; ssize_t phys_total_kb = os::physical_memory() / K; - size_t avail_mem = 0; + physical_memory_size_type avail_mem = 0; (void)os::available_memory(avail_mem); - size_t phys_avail_kb = avail_mem / K; + physical_memory_size_type phys_avail_kb = avail_mem / K; int rss_percentile = (int)(rss_kb * 100.0 / phys_total_kb); st->print_cr("Resident Set Size: %zdK (%d%% of " - "%zdK total physical memory with %zuK free physical memory)", + "%zdK total physical memory with " PHYS_MEM_TYPE_FORMAT "K free physical memory)", rss_kb, rss_percentile, phys_total_kb, phys_avail_kb); } else { st->print_cr("GetProcessMemoryInfo() call did not succeed"); @@ -2444,12 +2444,12 @@ static void print_process_memory_usage_platform(outputStream *st) if (ret == KERN_SUCCESS) { ssize_t rss_kb = info.resident_size / K; ssize_t phys_total_kb = os::physical_memory() / K; - size_t avail_mem = 0; + physical_memory_size_type avail_mem = 0; (void)os::available_memory(avail_mem); - size_t phys_avail_kb = avail_mem / K; + physical_memory_size_type phys_avail_kb = avail_mem / K; int rss_percentile = (int)(rss_kb * 100.0 / phys_total_kb); st->print_cr("Resident Set Size: %zdK (%d%% of " - "%zdK total physical memory with %zuK free physical memory)", + "%zdK total physical memory with " PHYS_MEM_TYPE_FORMAT "K free physical memory)", rss_kb, rss_percentile, phys_total_kb, phys_avail_kb); } else { st->print_cr("task_info() call did not succeed"); From 0ba88cd64a1024db979464faf069d191c6b59112 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Wed, 18 Mar 2026 15:26:45 +0000 Subject: [PATCH 056/234] 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 3481028ddfa4b9a667f98d8559c0c100b2473bd2 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Wed, 18 Mar 2026 15:27:22 +0000 Subject: [PATCH 057/234] 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 3ddb83690077e6f4bb6fc53536d31b50e9c6501a Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Wed, 18 Mar 2026 15:27:51 +0000 Subject: [PATCH 058/234] 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 35eca266bee38afb94a3fc6623abf61637c86291 Mon Sep 17 00:00:00 2001
From: Roman Marchenko 
Date: Thu, 19 Mar 2026 15:24:09 +0000
Subject: [PATCH 059/234] 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/234] 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 c18ee5a326f8..3dab12a131c6 100644
--- a/test/jdk/ProblemList.txt
+++ b/test/jdk/ProblemList.txt
@@ -786,7 +786,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 d2fcb39607fb93e48ffec6f68f297efd85c33256 Mon Sep 17 00:00:00 2001
From: Goetz Lindenmaier 
Date: Fri, 20 Mar 2026 06:46:19 +0000
Subject: [PATCH 061/234] 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 3dab12a131c6..fc77afc7d255 100644
--- a/test/jdk/ProblemList.txt
+++ b/test/jdk/ProblemList.txt
@@ -682,7 +682,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 3770c4eea16615a0bb88a33b187999ff74ea1de8 Mon Sep 17 00:00:00 2001
From: William Kemper 
Date: Fri, 20 Mar 2026 16:07:13 +0000
Subject: [PATCH 062/234] 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 b1d8cada0342099c09f382395ab6583d3d9c9f37 Mon Sep 17 00:00:00 2001
From: Matthias Baesken 
Date: Mon, 23 Mar 2026 07:45:28 +0000
Subject: [PATCH 063/234] 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 b9b860da66f8077c46bbe72f5f00aac230118349 Mon Sep 17 00:00:00 2001
From: Roland Mesde 
Date: Mon, 23 Mar 2026 15:26:23 +0000
Subject: [PATCH 064/234] 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 8f264278d9fd..0497dbf69fa2 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, 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
@@ -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;
 
     J2dTraceLn(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 fc77afc7d255..bda6a185238a 100644
--- a/test/jdk/ProblemList.txt
+++ b/test/jdk/ProblemList.txt
@@ -249,6 +249,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 4c7a78806069e96ec11f416beec78f68eb51c062 Mon Sep 17 00:00:00 2001
From: Roland Mesde 
Date: Mon, 23 Mar 2026 15:27:19 +0000
Subject: [PATCH 065/234] 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 a061ca20ed7d07caf4033443eb11381fe6d747b7 Mon Sep 17 00:00:00 2001
From: Roland Mesde 
Date: Mon, 23 Mar 2026 15:29:53 +0000
Subject: [PATCH 066/234] 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 94250dcbb7f7457b2fb9945247e7d4b273579fce Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 23 Mar 2026 15:30:34 +0000 Subject: [PATCH 067/234] 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 fc622673d5f7df1136c5adbcbc82ffc2a42d11e2 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 23 Mar 2026 15:31:03 +0000 Subject: [PATCH 068/234] 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 f9eaaf5513a3786133a72fcdd136a853327c3d1a Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 23 Mar 2026 15:31:45 +0000 Subject: [PATCH 069/234] 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 c4d0a3b4a978bc4953a20c877b36a4502ec18e7d Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Mon, 23 Mar 2026 15:33:04 +0000 Subject: [PATCH 070/234] 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 bda6a185238a..8a024c982947 100644 --- a/test/jdk/ProblemList.txt +++ b/test/jdk/ProblemList.txt @@ -492,7 +492,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 8e9dc072577db913dfbc36d8d2609fabee76bc45 Mon Sep 17 00:00:00 2001 From: SendaoYan Date: Tue, 24 Mar 2026 01:44:14 +0000 Subject: [PATCH 071/234] 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 63625f2a4078e7f22a5228472a918b75bfe09af8 Mon Sep 17 00:00:00 2001 From: Zhaokun Xie Date: Tue, 24 Mar 2026 08:03:26 +0000 Subject: [PATCH 072/234] 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 e8636e97cd81..976dbdac9edb 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp @@ -2245,7 +2245,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 6447376bad82..8d40af0fd654 100644 --- a/src/hotspot/share/utilities/vmError.cpp +++ b/src/hotspot/share/utilities/vmError.cpp @@ -677,6 +677,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(); @@ -1301,7 +1302,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 d70f6a5de04ac68cb477d6d2199ca76cef93f575 Mon Sep 17 00:00:00 2001 From: Arno Zeller Date: Tue, 24 Mar 2026 08:05:12 +0000 Subject: [PATCH 073/234] 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 5b2c1c2336aae8849544be2501fa0809b43ba23a Mon Sep 17 00:00:00 2001 From: Sergey Nazarkin Date: Tue, 24 Mar 2026 10:50:07 +0000 Subject: [PATCH 074/234] 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 a8da651a95ca515abc699eead8c92b2c941dd36d Mon Sep 17 00:00:00 2001 From: Thomas Stuefe Date: Tue, 24 Mar 2026 13:14:13 +0000 Subject: [PATCH 075/234] 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 14ce918700ee..459365beedf6 100644 --- a/test/jdk/TEST.groups +++ b/test/jdk/TEST.groups @@ -691,6 +691,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 64840843b3f92183b9bb23608d44ac1fbdc2f101 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Tue, 24 Mar 2026 15:08:46 +0000 Subject: [PATCH 076/234] 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 d05ea404ffb06797d0c5e53ab0c780a99f7758b7 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Tue, 24 Mar 2026 15:09:21 +0000 Subject: [PATCH 077/234] 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 542eb053632ad4da35f31980e44f081b5ef43efc Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Tue, 24 Mar 2026 15:11:24 +0000 Subject: [PATCH 078/234] 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 82ca81513e9973cb730d973989e13938aaf0775c Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Tue, 24 Mar 2026 15:11:46 +0000 Subject: [PATCH 079/234] 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 de2204665de31a1f102e043c06df0a6b7983656b Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Tue, 24 Mar 2026 15:20:24 +0000 Subject: [PATCH 080/234] 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 ef032ddee9b72277053858c4b51e811767d0ccc5 Mon Sep 17 00:00:00 2001 From: Roland Mesde Date: Tue, 24 Mar 2026 20:32:49 +0000 Subject: [PATCH 081/234] 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 5f7ec186ed8c494836fe8ebaa95f0b6067ede2b4 Mon Sep 17 00:00:00 2001 From: Arno Zeller Date: Thu, 26 Mar 2026 08:32:01 +0000 Subject: [PATCH 082/234] 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 a631832e90b23485c71dad05688eb5ce144c2ffc Mon Sep 17 00:00:00 2001 From: Thomas Devoogdt Date: Thu, 26 Mar 2026 15:20:44 +0000 Subject: [PATCH 083/234] 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 | 12 ++++++++++-- .../unix/native/common/awt/utility/rect.h | 4 ++-- 5 files changed, 21 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.