From 57553ca1dbc63e329116bc11764816a4c5ccb297 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Mon, 4 Aug 2025 04:17:16 +0000 Subject: [PATCH 001/580] 8361298: SwingUtilities/bug4967768.java fails where character P is not underline Reviewed-by: dnguyen, psadhukhan, achung, azvegint --- test/jdk/javax/swing/SwingUtilities/bug4967768.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/test/jdk/javax/swing/SwingUtilities/bug4967768.java b/test/jdk/javax/swing/SwingUtilities/bug4967768.java index 43f9f7cabfb..6ce1f5c787b 100644 --- a/test/jdk/javax/swing/SwingUtilities/bug4967768.java +++ b/test/jdk/javax/swing/SwingUtilities/bug4967768.java @@ -37,13 +37,19 @@ import javax.swing.JPanel; public class bug4967768 { private static final String INSTRUCTIONS = """ - When the test starts you'll see a button "Oops" - with the "p" letter underlined at the bottom - of the instruction frame. + When the test starts you'll see a button "Oops". + + For Windows and GTK Look and Feel, you will need to + press the ALT key to make the mnemonic visible. + Once the ALT key is pressed, the letter "p" will be + underlined at the bottom of the instruction frame. Ensure the underline cuts through the descender of letter "p", i.e. the underline is painted not below the letter but below the baseline. + + Press Pass if you see the expected behaviour else + press Fail. """; public static void main(String[] args) throws Exception { From 8269fdc78ef40d8238bf1509531f5cfc25d54820 Mon Sep 17 00:00:00 2001 From: Andrey Turbanov Date: Mon, 4 Aug 2025 08:15:09 +0000 Subject: [PATCH 002/580] 8362067: Remove unnecessary List.contains key from SpringLayout.Constraints.pushConstraint Reviewed-by: aivanov --- .../share/classes/javax/swing/SpringLayout.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/java.desktop/share/classes/javax/swing/SpringLayout.java b/src/java.desktop/share/classes/javax/swing/SpringLayout.java index 76050d31ce3..925ef138bc2 100644 --- a/src/java.desktop/share/classes/javax/swing/SpringLayout.java +++ b/src/java.desktop/share/classes/javax/swing/SpringLayout.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 @@ -27,11 +27,15 @@ package javax.swing; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; -import java.awt.FontMetrics; import java.awt.Insets; import java.awt.LayoutManager2; -import java.awt.Rectangle; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; /** * A SpringLayout lays out the children of its associated container @@ -403,8 +407,7 @@ public class SpringLayout implements LayoutManager2 { boolean valid = true; List history = horizontal ? horizontalHistory : verticalHistory; - if (history.contains(name)) { - history.remove(name); + if (history.remove(name)) { valid = false; } else if (history.size() == 2 && value != null) { history.remove(0); From 3387b3195c8f2a9faa3c93322f6e11ce2aad3e2b Mon Sep 17 00:00:00 2001 From: Francesco Andreuzzi Date: Mon, 4 Aug 2025 08:20:22 +0000 Subject: [PATCH 003/580] 8364519: Sort share/classfile includes Reviewed-by: shade, ayang --- src/hotspot/share/classfile/classFileParser.cpp | 2 +- src/hotspot/share/classfile/classLoader.cpp | 2 +- src/hotspot/share/classfile/defaultMethods.cpp | 6 +++--- src/hotspot/share/classfile/defaultMethods.hpp | 2 +- src/hotspot/share/classfile/fieldLayoutBuilder.cpp | 2 +- src/hotspot/share/classfile/javaClasses.cpp | 5 ++--- src/hotspot/share/classfile/klassFactory.cpp | 1 - src/hotspot/share/classfile/modules.cpp | 1 - src/hotspot/share/classfile/packageEntry.cpp | 2 +- src/hotspot/share/classfile/packageEntry.hpp | 2 +- src/hotspot/share/classfile/placeholders.cpp | 2 +- src/hotspot/share/classfile/systemDictionary.cpp | 2 -- src/hotspot/share/classfile/systemDictionaryShared.cpp | 3 +-- src/hotspot/share/classfile/systemDictionaryShared.hpp | 2 +- src/hotspot/share/classfile/verifier.cpp | 2 +- src/hotspot/share/classfile/vmIntrinsics.cpp | 6 +++--- src/hotspot/share/classfile/vmIntrinsics.hpp | 2 +- src/hotspot/share/classfile/vmSymbols.cpp | 2 +- src/hotspot/share/classfile/vmSymbols.hpp | 2 +- test/hotspot/jtreg/sources/TestIncludesAreSorted.java | 1 + 20 files changed, 22 insertions(+), 27 deletions(-) diff --git a/src/hotspot/share/classfile/classFileParser.cpp b/src/hotspot/share/classfile/classFileParser.cpp index 1499c0a8381..9aca20ee29f 100644 --- a/src/hotspot/share/classfile/classFileParser.cpp +++ b/src/hotspot/share/classfile/classFileParser.cpp @@ -77,8 +77,8 @@ #include "utilities/bitMap.inline.hpp" #include "utilities/checkedCast.hpp" #include "utilities/copy.hpp" -#include "utilities/formatBuffer.hpp" #include "utilities/exceptions.hpp" +#include "utilities/formatBuffer.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/growableArray.hpp" #include "utilities/macros.hpp" diff --git a/src/hotspot/share/classfile/classLoader.cpp b/src/hotspot/share/classfile/classLoader.cpp index 359970a3bcd..e5159662708 100644 --- a/src/hotspot/share/classfile/classLoader.cpp +++ b/src/hotspot/share/classfile/classLoader.cpp @@ -80,8 +80,8 @@ #include "utilities/ostream.hpp" #include "utilities/utf8.hpp" -#include #include +#include // Entry point in java.dll for path canonicalization diff --git a/src/hotspot/share/classfile/defaultMethods.cpp b/src/hotspot/share/classfile/defaultMethods.cpp index f84c3b65f5f..80de93261f4 100644 --- a/src/hotspot/share/classfile/defaultMethods.cpp +++ b/src/hotspot/share/classfile/defaultMethods.cpp @@ -35,13 +35,13 @@ #include "memory/metadataFactory.hpp" #include "memory/resourceArea.hpp" #include "memory/universe.hpp" +#include "oops/instanceKlass.hpp" +#include "oops/klass.hpp" +#include "oops/method.hpp" #include "prims/jvmtiExport.hpp" #include "runtime/handles.inline.hpp" #include "runtime/javaThread.hpp" #include "runtime/signature.hpp" -#include "oops/instanceKlass.hpp" -#include "oops/klass.hpp" -#include "oops/method.hpp" #include "utilities/accessFlags.hpp" #include "utilities/exceptions.hpp" #include "utilities/ostream.hpp" diff --git a/src/hotspot/share/classfile/defaultMethods.hpp b/src/hotspot/share/classfile/defaultMethods.hpp index 2176488726d..c369cdc6cfd 100644 --- a/src/hotspot/share/classfile/defaultMethods.hpp +++ b/src/hotspot/share/classfile/defaultMethods.hpp @@ -26,8 +26,8 @@ #define SHARE_CLASSFILE_DEFAULTMETHODS_HPP #include "runtime/handles.hpp" -#include "utilities/growableArray.hpp" #include "utilities/exceptions.hpp" +#include "utilities/growableArray.hpp" class InstanceKlass; class Symbol; diff --git a/src/hotspot/share/classfile/fieldLayoutBuilder.cpp b/src/hotspot/share/classfile/fieldLayoutBuilder.cpp index 03afe89f4f8..21f47e3de10 100644 --- a/src/hotspot/share/classfile/fieldLayoutBuilder.cpp +++ b/src/hotspot/share/classfile/fieldLayoutBuilder.cpp @@ -28,8 +28,8 @@ #include "memory/resourceArea.hpp" #include "oops/array.hpp" #include "oops/fieldStreams.inline.hpp" -#include "oops/instanceMirrorKlass.hpp" #include "oops/instanceKlass.inline.hpp" +#include "oops/instanceMirrorKlass.hpp" #include "oops/klass.inline.hpp" #include "runtime/fieldDescriptor.inline.hpp" diff --git a/src/hotspot/share/classfile/javaClasses.cpp b/src/hotspot/share/classfile/javaClasses.cpp index a6452523db9..28bebee7d9a 100644 --- a/src/hotspot/share/classfile/javaClasses.cpp +++ b/src/hotspot/share/classfile/javaClasses.cpp @@ -55,15 +55,14 @@ #include "oops/fieldStreams.inline.hpp" #include "oops/instanceKlass.inline.hpp" #include "oops/instanceMirrorKlass.hpp" -#include "oops/klass.hpp" #include "oops/klass.inline.hpp" #include "oops/method.inline.hpp" #include "oops/objArrayKlass.hpp" #include "oops/objArrayOop.inline.hpp" -#include "oops/oopCast.inline.hpp" #include "oops/oop.inline.hpp" -#include "oops/symbol.hpp" +#include "oops/oopCast.inline.hpp" #include "oops/recordComponent.hpp" +#include "oops/symbol.hpp" #include "oops/typeArrayOop.inline.hpp" #include "prims/jvmtiExport.hpp" #include "prims/methodHandles.hpp" diff --git a/src/hotspot/share/classfile/klassFactory.cpp b/src/hotspot/share/classfile/klassFactory.cpp index f2f31de4a37..5dd2cd2385b 100644 --- a/src/hotspot/share/classfile/klassFactory.cpp +++ b/src/hotspot/share/classfile/klassFactory.cpp @@ -27,7 +27,6 @@ #include "classfile/classFileParser.hpp" #include "classfile/classFileStream.hpp" #include "classfile/classLoader.hpp" -#include "classfile/classLoaderData.hpp" #include "classfile/classLoaderData.inline.hpp" #include "classfile/classLoadInfo.hpp" #include "classfile/klassFactory.hpp" diff --git a/src/hotspot/share/classfile/modules.cpp b/src/hotspot/share/classfile/modules.cpp index c7b5a729451..132c1c4ca49 100644 --- a/src/hotspot/share/classfile/modules.cpp +++ b/src/hotspot/share/classfile/modules.cpp @@ -31,7 +31,6 @@ #include "classfile/classLoaderData.inline.hpp" #include "classfile/classLoaderDataShared.hpp" #include "classfile/javaAssertions.hpp" -#include "classfile/javaClasses.hpp" #include "classfile/javaClasses.inline.hpp" #include "classfile/moduleEntry.hpp" #include "classfile/modules.hpp" diff --git a/src/hotspot/share/classfile/packageEntry.cpp b/src/hotspot/share/classfile/packageEntry.cpp index 26af9a4936c..9a21578630f 100644 --- a/src/hotspot/share/classfile/packageEntry.cpp +++ b/src/hotspot/share/classfile/packageEntry.cpp @@ -34,8 +34,8 @@ #include "memory/resourceArea.hpp" #include "oops/array.hpp" #include "oops/symbol.hpp" -#include "runtime/java.hpp" #include "runtime/handles.inline.hpp" +#include "runtime/java.hpp" #include "utilities/events.hpp" #include "utilities/growableArray.hpp" #include "utilities/ostream.hpp" diff --git a/src/hotspot/share/classfile/packageEntry.hpp b/src/hotspot/share/classfile/packageEntry.hpp index 039c7f21fa6..aa572dc15b2 100644 --- a/src/hotspot/share/classfile/packageEntry.hpp +++ b/src/hotspot/share/classfile/packageEntry.hpp @@ -30,9 +30,9 @@ #include "oops/symbolHandle.hpp" #include "runtime/atomic.hpp" #include "utilities/growableArray.hpp" -#include "utilities/resourceHash.hpp" #include "utilities/macros.hpp" #include "utilities/ostream.hpp" +#include "utilities/resourceHash.hpp" #if INCLUDE_JFR #include "jfr/support/jfrTraceIdExtension.hpp" #endif diff --git a/src/hotspot/share/classfile/placeholders.cpp b/src/hotspot/share/classfile/placeholders.cpp index 6ee421fa023..b2f1ed40106 100644 --- a/src/hotspot/share/classfile/placeholders.cpp +++ b/src/hotspot/share/classfile/placeholders.cpp @@ -25,8 +25,8 @@ #include "classfile/classLoaderData.inline.hpp" #include "classfile/placeholders.hpp" #include "logging/log.hpp" -#include "logging/logTag.hpp" #include "logging/logStream.hpp" +#include "logging/logTag.hpp" #include "memory/resourceArea.hpp" #include "oops/symbolHandle.hpp" #include "runtime/javaThread.hpp" diff --git a/src/hotspot/share/classfile/systemDictionary.cpp b/src/hotspot/share/classfile/systemDictionary.cpp index c3623dc25a1..f482a5bb303 100644 --- a/src/hotspot/share/classfile/systemDictionary.cpp +++ b/src/hotspot/share/classfile/systemDictionary.cpp @@ -60,8 +60,6 @@ #include "oops/objArrayKlass.hpp" #include "oops/objArrayOop.inline.hpp" #include "oops/oop.inline.hpp" -#include "oops/oop.hpp" -#include "oops/oopHandle.hpp" #include "oops/oopHandle.inline.hpp" #include "oops/symbol.hpp" #include "oops/typeArrayKlass.hpp" diff --git a/src/hotspot/share/classfile/systemDictionaryShared.cpp b/src/hotspot/share/classfile/systemDictionaryShared.cpp index f863347c152..7dd3a7d1bb2 100644 --- a/src/hotspot/share/classfile/systemDictionaryShared.cpp +++ b/src/hotspot/share/classfile/systemDictionaryShared.cpp @@ -35,8 +35,8 @@ #include "cds/dynamicArchive.hpp" #include "cds/filemap.hpp" #include "cds/heapShared.hpp" -#include "cds/lambdaProxyClassDictionary.hpp" #include "cds/lambdaFormInvokers.inline.hpp" +#include "cds/lambdaProxyClassDictionary.hpp" #include "cds/metaspaceShared.hpp" #include "cds/runTimeClassInfo.hpp" #include "cds/unregisteredClasses.hpp" @@ -45,7 +45,6 @@ #include "classfile/classLoaderData.inline.hpp" #include "classfile/classLoaderDataGraph.hpp" #include "classfile/dictionary.hpp" -#include "classfile/javaClasses.hpp" #include "classfile/javaClasses.inline.hpp" #include "classfile/symbolTable.hpp" #include "classfile/systemDictionary.hpp" diff --git a/src/hotspot/share/classfile/systemDictionaryShared.hpp b/src/hotspot/share/classfile/systemDictionaryShared.hpp index 4343bd5f0e8..5f6dd055fd6 100644 --- a/src/hotspot/share/classfile/systemDictionaryShared.hpp +++ b/src/hotspot/share/classfile/systemDictionaryShared.hpp @@ -26,8 +26,8 @@ #define SHARE_CLASSFILE_SYSTEMDICTIONARYSHARED_HPP #include "cds/cds_globals.hpp" -#include "cds/filemap.hpp" #include "cds/dumpTimeClassInfo.hpp" +#include "cds/filemap.hpp" #include "cds/runTimeClassInfo.hpp" #include "classfile/classLoaderData.hpp" #include "classfile/packageEntry.hpp" diff --git a/src/hotspot/share/classfile/verifier.cpp b/src/hotspot/share/classfile/verifier.cpp index 0f1468f0309..4c0a40b837d 100644 --- a/src/hotspot/share/classfile/verifier.cpp +++ b/src/hotspot/share/classfile/verifier.cpp @@ -26,8 +26,8 @@ #include "classfile/classFileStream.hpp" #include "classfile/classLoader.hpp" #include "classfile/javaClasses.hpp" -#include "classfile/stackMapTable.hpp" #include "classfile/stackMapFrame.hpp" +#include "classfile/stackMapTable.hpp" #include "classfile/stackMapTableFormat.hpp" #include "classfile/symbolTable.hpp" #include "classfile/systemDictionary.hpp" diff --git a/src/hotspot/share/classfile/vmIntrinsics.cpp b/src/hotspot/share/classfile/vmIntrinsics.cpp index dd28e1a898c..1145310da46 100644 --- a/src/hotspot/share/classfile/vmIntrinsics.cpp +++ b/src/hotspot/share/classfile/vmIntrinsics.cpp @@ -27,12 +27,12 @@ #include "compiler/compilerDirectives.hpp" #include "jvm_constants.h" #include "jvm_io.h" -#ifdef COMPILER2 -#include "opto/c2_globals.hpp" -#endif #include "runtime/vm_version.hpp" #include "utilities/checkedCast.hpp" #include "utilities/tribool.hpp" +#ifdef COMPILER2 +#include "opto/c2_globals.hpp" +#endif // These are flag-matching functions: inline bool match_F_R(u2 flags) { diff --git a/src/hotspot/share/classfile/vmIntrinsics.hpp b/src/hotspot/share/classfile/vmIntrinsics.hpp index ed737cf60c2..b142ac9f1e4 100644 --- a/src/hotspot/share/classfile/vmIntrinsics.hpp +++ b/src/hotspot/share/classfile/vmIntrinsics.hpp @@ -27,8 +27,8 @@ #include "jfr/support/jfrIntrinsics.hpp" #include "memory/allStatic.hpp" -#include "utilities/globalDefinitions.hpp" #include "utilities/enumIterator.hpp" +#include "utilities/globalDefinitions.hpp" #include "utilities/vmEnums.hpp" class Method; diff --git a/src/hotspot/share/classfile/vmSymbols.cpp b/src/hotspot/share/classfile/vmSymbols.cpp index 3dfeebf7ea1..bd32eac4f34 100644 --- a/src/hotspot/share/classfile/vmSymbols.cpp +++ b/src/hotspot/share/classfile/vmSymbols.cpp @@ -29,8 +29,8 @@ #include "compiler/compilerDirectives.hpp" #include "jvm.h" #include "memory/allocation.inline.hpp" -#include "memory/oopFactory.hpp" #include "memory/metaspaceClosure.hpp" +#include "memory/oopFactory.hpp" #include "oops/oop.inline.hpp" #include "runtime/handles.inline.hpp" #include "runtime/signature.hpp" diff --git a/src/hotspot/share/classfile/vmSymbols.hpp b/src/hotspot/share/classfile/vmSymbols.hpp index 5c50ec46dfa..06f27f09c5c 100644 --- a/src/hotspot/share/classfile/vmSymbols.hpp +++ b/src/hotspot/share/classfile/vmSymbols.hpp @@ -29,8 +29,8 @@ #include "jvmci/vmSymbols_jvmci.hpp" #include "memory/iterator.hpp" #include "oops/symbol.hpp" -#include "utilities/macros.hpp" #include "utilities/enumIterator.hpp" +#include "utilities/macros.hpp" class SerializeClosure; diff --git a/test/hotspot/jtreg/sources/TestIncludesAreSorted.java b/test/hotspot/jtreg/sources/TestIncludesAreSorted.java index 263eb9b36d9..5b3280b312f 100644 --- a/test/hotspot/jtreg/sources/TestIncludesAreSorted.java +++ b/test/hotspot/jtreg/sources/TestIncludesAreSorted.java @@ -47,6 +47,7 @@ public class TestIncludesAreSorted { "share/c1", "share/cds", "share/ci", + "share/classfile", "share/compiler", "share/jvmci", "share/libadt", From ea7e943874288e1cbea10a6bd82d6c7f2a1c9ae0 Mon Sep 17 00:00:00 2001 From: Erik Gahlin Date: Mon, 4 Aug 2025 08:50:35 +0000 Subject: [PATCH 004/580] 8364257: JFR: User-defined events and settings with a one-letter name cannot be configured Reviewed-by: mgronlun --- .../classes/jdk/jfr/internal/SettingsManager.java | 2 +- .../api/flightrecorder/TestSettingsControl.java | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/SettingsManager.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/SettingsManager.java index 8521cd4303f..84caf7cb460 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/SettingsManager.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/SettingsManager.java @@ -194,7 +194,7 @@ final class SettingsManager { String key = entry.getKey(); String value = entry.getValue(); int index = key.indexOf("#"); - if (index > 1 && index < key.length() - 2) { + if (index > 0 && index < key.length() - 1) { String eventName = key.substring(0, index); eventName = Utils.upgradeLegacyJDKEvent(eventName); InternalSetting s = internals.get(eventName); diff --git a/test/jdk/jdk/jfr/api/flightrecorder/TestSettingsControl.java b/test/jdk/jdk/jfr/api/flightrecorder/TestSettingsControl.java index e186db41bc2..e0e7e8b09c6 100644 --- a/test/jdk/jdk/jfr/api/flightrecorder/TestSettingsControl.java +++ b/test/jdk/jdk/jfr/api/flightrecorder/TestSettingsControl.java @@ -28,6 +28,7 @@ import static jdk.test.lib.Asserts.assertTrue; import java.util.Set; import jdk.jfr.Event; +import jdk.jfr.Name; import jdk.jfr.Recording; import jdk.jfr.SettingControl; import jdk.jfr.SettingDefinition; @@ -42,7 +43,7 @@ import jdk.jfr.SettingDefinition; public class TestSettingsControl { static class MySettingsControl extends SettingControl { - public static boolean setWasCalled; + public static boolean myvalueSet; private String value = "default"; @@ -57,7 +58,9 @@ public class TestSettingsControl { @Override public void setValue(String value) { - setWasCalled = true; + if ("myvalue".equals(value)) { + myvalueSet = true; + } this.value = value; } @@ -67,9 +70,10 @@ public class TestSettingsControl { } } - + @Name("M") static class MyCustomSettingEvent extends Event { @SettingDefinition + @Name("m") boolean mySetting(MySettingsControl msc) { return true; } @@ -77,13 +81,13 @@ public class TestSettingsControl { public static void main(String[] args) throws Throwable { Recording r = new Recording(); - r.enable(MyCustomSettingEvent.class).with("mySetting", "myvalue"); + r.enable("M").with("m", "myvalue"); r.start(); MyCustomSettingEvent e = new MyCustomSettingEvent(); e.commit(); r.stop(); r.close(); - assertTrue(MySettingsControl.setWasCalled, "SettingControl.setValue was not called"); + assertTrue(MySettingsControl.myvalueSet, "SettingControl.setValue(\"myvalue\") was not called"); } } From cf5a25538e09e449ff621562df6529abaa9b3685 Mon Sep 17 00:00:00 2001 From: Erik Gahlin Date: Mon, 4 Aug 2025 09:12:12 +0000 Subject: [PATCH 005/580] 8364427: JFR: Possible resource leak in Recording::getStream Reviewed-by: mgronlun --- .../jdk/jfr/internal/ChunkInputStream.java | 40 +++-- .../misc/TestGetStreamWithFailure.java | 146 ++++++++++++++++++ 2 files changed, 170 insertions(+), 16 deletions(-) create mode 100644 test/jdk/jdk/jfr/api/recording/misc/TestGetStreamWithFailure.java diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/ChunkInputStream.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/ChunkInputStream.java index 4c8913e96a0..3e95932b197 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/ChunkInputStream.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/ChunkInputStream.java @@ -49,7 +49,9 @@ final class ChunkInputStream extends InputStream { } this.chunks = l.iterator(); - nextStream(); + if (!nextStream()) { + throw new IOException("Recording data missing on disk."); + } } @Override @@ -62,13 +64,18 @@ final class ChunkInputStream extends InputStream { } private boolean nextStream() throws IOException { - if (!nextChunk()) { - return false; + while (nextChunk()) { + try { + stream = new BufferedInputStream(Files.newInputStream(currentChunk.getFile())); + unstreamedSize -= currentChunk.getSize(); + return true; + } catch (IOException e) { + Logger.log(LogTag.JFR, LogLevel.INFO, "Could not open chunk file for stream: " + e.getMessage() + ". Skipping."); + // Release chunk if it can't be found/accessed. + closeChunk(); + } } - - stream = new BufferedInputStream(Files.newInputStream(currentChunk.getFile())); - unstreamedSize -= currentChunk.getSize(); - return true; + return false; } private boolean nextChunk() { @@ -126,11 +133,14 @@ final class ChunkInputStream extends InputStream { } private void closeStream() throws IOException { - if (stream != null) { - stream.close(); - stream = null; + try { + if (stream != null) { + stream.close(); + stream = null; + } + } finally { + closeChunk(); } - closeChunk(); } private void closeChunk() { @@ -143,11 +153,9 @@ final class ChunkInputStream extends InputStream { @Override public void close() throws IOException { closeStream(); - while (currentChunk != null) { - closeChunk(); - if (!nextChunk()) { - return; - } + while (chunks.hasNext()) { + RepositoryChunk c = chunks.next(); + c.release(); } } } diff --git a/test/jdk/jdk/jfr/api/recording/misc/TestGetStreamWithFailure.java b/test/jdk/jdk/jfr/api/recording/misc/TestGetStreamWithFailure.java new file mode 100644 index 00000000000..d294b06270e --- /dev/null +++ b/test/jdk/jdk/jfr/api/recording/misc/TestGetStreamWithFailure.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.jfr.api.recording.misc; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.text.ParseException; +import java.util.List; +import java.util.function.BiConsumer; + +import jdk.jfr.Configuration; +import jdk.jfr.Event; +import jdk.jfr.Recording; + +/** + * @test + * @summary Verify that resources are not leaked in case of failure + * @requires vm.flagless + * @requires vm.hasJFR + * @library /test/lib + * @run main/othervm jdk.jfr.api.recording.misc.TestGetStreamWithFailure + */ +public class TestGetStreamWithFailure { + private interface TestCase { + void execute(Recording recording, List chunks) throws Exception; + } + + private static class FillerEvent extends Event { + } + + public static void main(String[] args) throws Exception { + testMissingChunk(); + testClosedStream(); + testNoChunks(); + } + + // Simulates a user deleting all chunks for a recording + private static void testNoChunks() throws Exception { + testStream((r, chunks) -> { + deleteAllChunks(); + try { + InputStream is = r.getStream(null, null); + throw new Exception("Expected exception when all chunks are missing"); + } catch (IOException ioe) { + if (!ioe.getMessage().equals("Recording data missing on disk.")) { + throw new Exception("Unexpected exception: " + ioe.getMessage()); + } + } + }); + } + + private static void deleteAllChunks() throws Exception { + for (Path chunk : findChunks()) { + Files.delete(chunk); + } + } + + // Simulates a user deleting a single chunk file on disk. + private static void testMissingChunk() throws Exception { + testStream((r, chunks) -> { + Files.delete(chunks.get(1)); + try (InputStream is = r.getStream(null, null)) { + is.readAllBytes(); + } + }); + } + + // Simulates a user closing a stream before all the data has been read, for + // example, if InputStream::read throws an IOException. + private static void testClosedStream() throws Exception { + testStream((r, chunks) -> { + int size = (int) (r.getSize()); + try (InputStream is = r.getStream(null, null)) { + is.readNBytes(size / 2); + } + }); + } + + private static void testStream(TestCase testCase) throws Exception { + try (Recording r = createRecordingData()) { + List chunks = findChunks(); + if (chunks.size() < 3) { + throw new Exception("Expected recording to have at least three chunks"); + } + testCase.execute(r, chunks); + r.close(); + if (!findChunks().isEmpty()) { + throw new Exception("Chunks left behind."); + } + deleteAllChunks(); + } + } + + private static List findChunks() throws Exception { + String repository = System.getProperty("jdk.jfr.repository"); + if (repository == null) { + throw new Exception("No system property for JFR repository"); + } + Path dir = Path.of(repository); + return Files.walk(dir).filter(p -> p.toString().endsWith(".jfr")).toList(); + } + + private static Recording createRecordingData() throws IOException, ParseException, InterruptedException { + Configuration c = Configuration.getConfiguration("default"); + Recording r = new Recording(); + r.start(); + emitEvents(); // Chunk 1 + try (Recording s = new Recording()) { + s.start(); + emitEvents(); // Chunk 2 + } + emitEvents(); // Chunk 3 + r.stop(); + return r; + } + + private static void emitEvents() throws InterruptedException { + for (int i = 0; i < 100_000; i++) { + FillerEvent e = new FillerEvent(); + e.commit(); + } + } +} From 3bc449797eb59f9770d2a06d260b23b6efd5ff0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Gr=C3=B6nlund?= Date: Mon, 4 Aug 2025 09:42:05 +0000 Subject: [PATCH 006/580] 8364258: ThreadGroup constant pool serialization is not normalized Reviewed-by: egahlin --- .../checkpoint/types/jfrThreadGroup.cpp | 417 ------------------ .../types/jfrThreadGroupManager.cpp | 331 ++++++++++++++ ...eadGroup.hpp => jfrThreadGroupManager.hpp} | 41 +- .../jfr/recorder/checkpoint/types/jfrType.cpp | 17 +- .../jfr/recorder/checkpoint/types/jfrType.hpp | 7 +- .../checkpoint/types/jfrTypeManager.cpp | 4 +- .../share/jfr/recorder/jfrRecorder.cpp | 9 + .../share/jfr/recorder/jfrRecorder.hpp | 1 + .../share/jfr/support/jfrThreadLocal.cpp | 58 ++- .../share/jfr/support/jfrThreadLocal.hpp | 4 + src/hotspot/share/runtime/javaThread.cpp | 2 + 11 files changed, 420 insertions(+), 471 deletions(-) delete mode 100644 src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadGroup.cpp create mode 100644 src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadGroupManager.cpp rename src/hotspot/share/jfr/recorder/checkpoint/types/{jfrThreadGroup.hpp => jfrThreadGroupManager.hpp} (50%) diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadGroup.cpp b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadGroup.cpp deleted file mode 100644 index a4817cbc87d..00000000000 --- a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadGroup.cpp +++ /dev/null @@ -1,417 +0,0 @@ -/* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - * - */ - -#include "jfr/recorder/checkpoint/jfrCheckpointWriter.hpp" -#include "jfr/recorder/checkpoint/types/jfrThreadGroup.hpp" -#include "jfr/utilities/jfrTypes.hpp" -#include "runtime/handles.inline.hpp" -#include "runtime/jniHandles.inline.hpp" -#include "runtime/safepoint.hpp" -#include "runtime/semaphore.hpp" -#include "utilities/growableArray.hpp" - -static const int initial_array_size = 30; - -class ThreadGroupExclusiveAccess : public StackObj { - private: - static Semaphore _mutex_semaphore; - public: - ThreadGroupExclusiveAccess() { _mutex_semaphore.wait(); } - ~ThreadGroupExclusiveAccess() { _mutex_semaphore.signal(); } -}; - -Semaphore ThreadGroupExclusiveAccess::_mutex_semaphore(1); -JfrThreadGroup* JfrThreadGroup::_instance = nullptr; - -class JfrThreadGroupPointers : public ResourceObj { - private: - const Handle _thread_group_handle; - jweak _thread_group_weak_ref; - public: - JfrThreadGroupPointers(Handle thread_group_handle, jweak thread_group_weak_ref); - Handle thread_group_handle() const; - jweak thread_group_weak_ref() const; - oopDesc* thread_group_oop() const; - jweak transfer_weak_global_handle_ownership(); - void clear_weak_ref(); -}; - -JfrThreadGroupPointers::JfrThreadGroupPointers(Handle thread_group_handle, jweak thread_group_weak_ref) : - _thread_group_handle(thread_group_handle), - _thread_group_weak_ref(thread_group_weak_ref) {} - -Handle JfrThreadGroupPointers::thread_group_handle() const { - return _thread_group_handle; -} - -jweak JfrThreadGroupPointers::thread_group_weak_ref() const { - return _thread_group_weak_ref; -} - -oopDesc* JfrThreadGroupPointers::thread_group_oop() const { - assert(_thread_group_weak_ref == nullptr || - JNIHandles::resolve_non_null(_thread_group_weak_ref) == _thread_group_handle(), "invariant"); - return _thread_group_handle(); -} - -jweak JfrThreadGroupPointers::transfer_weak_global_handle_ownership() { - jweak temp = _thread_group_weak_ref; - _thread_group_weak_ref = nullptr; - return temp; -} - -void JfrThreadGroupPointers::clear_weak_ref() { - if (nullptr != _thread_group_weak_ref) { - JNIHandles::destroy_weak_global(_thread_group_weak_ref); - } -} - -class JfrThreadGroupsHelper : public ResourceObj { - private: - static const int invalid_iterator_pos = -1; - GrowableArray* _thread_group_hierarchy; - int _current_iterator_pos; - - int populate_thread_group_hierarchy(const JavaThread* jt, Thread* current); - JfrThreadGroupPointers& at(int index); - - public: - JfrThreadGroupsHelper(const JavaThread* jt, Thread* current); - ~JfrThreadGroupsHelper(); - JfrThreadGroupPointers& next(); - bool is_valid() const; - bool has_next() const; -}; - -JfrThreadGroupsHelper::JfrThreadGroupsHelper(const JavaThread* jt, Thread* current) { - _thread_group_hierarchy = new GrowableArray(10); - _current_iterator_pos = populate_thread_group_hierarchy(jt, current) - 1; -} - -JfrThreadGroupsHelper::~JfrThreadGroupsHelper() { - assert(_current_iterator_pos == invalid_iterator_pos, "invariant"); - for (int i = 0; i < _thread_group_hierarchy->length(); ++i) { - _thread_group_hierarchy->at(i)->clear_weak_ref(); - } -} - -JfrThreadGroupPointers& JfrThreadGroupsHelper::at(int index) { - assert(_thread_group_hierarchy != nullptr, "invariant"); - assert(index > invalid_iterator_pos && index < _thread_group_hierarchy->length(), "invariant"); - return *(_thread_group_hierarchy->at(index)); -} - -bool JfrThreadGroupsHelper::has_next() const { - return _current_iterator_pos > invalid_iterator_pos; -} - -bool JfrThreadGroupsHelper::is_valid() const { - return (_thread_group_hierarchy != nullptr && _thread_group_hierarchy->length() > 0); -} - -JfrThreadGroupPointers& JfrThreadGroupsHelper::next() { - assert(is_valid(), "invariant"); - return at(_current_iterator_pos--); -} - -/* - * If not at a safepoint, we create global weak references for - * all reachable threadgroups for this thread. - * If we are at a safepoint, the caller is the VMThread during - * JFR checkpointing. It can use naked oops, because nothing - * will move before the list of threadgroups is cleared and - * mutator threads restarted. The threadgroup list is cleared - * later by the VMThread as one of the final steps in JFR checkpointing - * (not here). - */ -int JfrThreadGroupsHelper::populate_thread_group_hierarchy(const JavaThread* jt, Thread* current) { - assert(jt != nullptr && jt->is_Java_thread(), "invariant"); - assert(current != nullptr, "invariant"); - assert(_thread_group_hierarchy != nullptr, "invariant"); - - oop thread_oop = jt->threadObj(); - if (thread_oop == nullptr) { - return 0; - } - // immediate thread group - Handle thread_group_handle(current, java_lang_Thread::threadGroup(thread_oop)); - if (thread_group_handle == nullptr) { - return 0; - } - - const bool use_weak_handles = !SafepointSynchronize::is_at_safepoint(); - jweak thread_group_weak_ref = use_weak_handles ? JNIHandles::make_weak_global(thread_group_handle) : nullptr; - - JfrThreadGroupPointers* thread_group_pointers = new JfrThreadGroupPointers(thread_group_handle, thread_group_weak_ref); - _thread_group_hierarchy->append(thread_group_pointers); - // immediate parent thread group - oop parent_thread_group_obj = java_lang_ThreadGroup::parent(thread_group_handle()); - Handle parent_thread_group_handle(current, parent_thread_group_obj); - - // and check parents parents... - while (parent_thread_group_handle != nullptr) { - const jweak parent_group_weak_ref = use_weak_handles ? JNIHandles::make_weak_global(parent_thread_group_handle) : nullptr; - thread_group_pointers = new JfrThreadGroupPointers(parent_thread_group_handle, parent_group_weak_ref); - _thread_group_hierarchy->append(thread_group_pointers); - parent_thread_group_obj = java_lang_ThreadGroup::parent(parent_thread_group_handle()); - parent_thread_group_handle = Handle(current, parent_thread_group_obj); - } - return _thread_group_hierarchy->length(); -} - -static traceid next_id() { - static traceid _current_threadgroup_id = 1; // 1 is reserved for thread group "VirtualThreads" - return ++_current_threadgroup_id; -} - -class JfrThreadGroup::JfrThreadGroupEntry : public JfrCHeapObj { - friend class JfrThreadGroup; - private: - traceid _thread_group_id; - traceid _parent_group_id; - char* _thread_group_name; // utf8 format - // If an entry is created during a safepoint, the - // _thread_group_oop contains a direct oop to - // the java.lang.ThreadGroup object. - // If an entry is created on javathread exit time (not at safepoint), - // _thread_group_weak_ref contains a JNI weak global handle - // indirection to the java.lang.ThreadGroup object. - // Note: we cannot use a union here since CHECK_UNHANDLED_OOPS makes oop have - // a ctor which isn't allowed in a union by the SunStudio compiler - oop _thread_group_oop; - jweak _thread_group_weak_ref; - - JfrThreadGroupEntry(const char* tgstr, JfrThreadGroupPointers& ptrs); - ~JfrThreadGroupEntry(); - - traceid thread_group_id() const { return _thread_group_id; } - void set_thread_group_id(traceid tgid) { _thread_group_id = tgid; } - - const char* thread_group_name() const { return _thread_group_name; } - void set_thread_group_name(const char* tgname); - - traceid parent_group_id() const { return _parent_group_id; } - void set_parent_group_id(traceid pgid) { _parent_group_id = pgid; } - - void set_thread_group(JfrThreadGroupPointers& ptrs); - bool is_equal(const JfrThreadGroupPointers& ptrs) const; - oop thread_group() const; -}; - -JfrThreadGroup::JfrThreadGroupEntry::JfrThreadGroupEntry(const char* tgname, JfrThreadGroupPointers& ptrs) : - _thread_group_id(0), - _parent_group_id(0), - _thread_group_name(nullptr), - _thread_group_oop(nullptr), - _thread_group_weak_ref(nullptr) { - set_thread_group_name(tgname); - set_thread_group(ptrs); -} - -JfrThreadGroup::JfrThreadGroupEntry::~JfrThreadGroupEntry() { - if (_thread_group_name != nullptr) { - JfrCHeapObj::free(_thread_group_name, strlen(_thread_group_name) + 1); - } - if (_thread_group_weak_ref != nullptr) { - JNIHandles::destroy_weak_global(_thread_group_weak_ref); - } -} - -void JfrThreadGroup::JfrThreadGroupEntry::set_thread_group_name(const char* tgname) { - assert(_thread_group_name == nullptr, "invariant"); - if (tgname != nullptr) { - size_t len = strlen(tgname); - _thread_group_name = JfrCHeapObj::new_array(len + 1); - strncpy(_thread_group_name, tgname, len + 1); - } -} - -oop JfrThreadGroup::JfrThreadGroupEntry::thread_group() const { - return _thread_group_weak_ref != nullptr ? JNIHandles::resolve(_thread_group_weak_ref) : _thread_group_oop; -} - -void JfrThreadGroup::JfrThreadGroupEntry::set_thread_group(JfrThreadGroupPointers& ptrs) { - _thread_group_weak_ref = ptrs.transfer_weak_global_handle_ownership(); - if (_thread_group_weak_ref == nullptr) { - _thread_group_oop = ptrs.thread_group_oop(); - assert(_thread_group_oop != nullptr, "invariant"); - } else { - _thread_group_oop = nullptr; - } -} - -JfrThreadGroup::JfrThreadGroup() : - _list(new (mtTracing) GrowableArray(initial_array_size, mtTracing)) {} - -JfrThreadGroup::~JfrThreadGroup() { - if (_list != nullptr) { - for (int i = 0; i < _list->length(); i++) { - JfrThreadGroupEntry* e = _list->at(i); - delete e; - } - delete _list; - } -} - -JfrThreadGroup* JfrThreadGroup::instance() { - return _instance; -} - -void JfrThreadGroup::set_instance(JfrThreadGroup* new_instance) { - _instance = new_instance; -} - -traceid JfrThreadGroup::thread_group_id(const JavaThread* jt, Thread* current) { - HandleMark hm(current); - JfrThreadGroupsHelper helper(jt, current); - return helper.is_valid() ? thread_group_id_internal(helper) : 0; -} - -traceid JfrThreadGroup::thread_group_id(JavaThread* const jt) { - return thread_group_id(jt, jt); -} - -traceid JfrThreadGroup::thread_group_id_internal(JfrThreadGroupsHelper& helper) { - ThreadGroupExclusiveAccess lock; - JfrThreadGroup* tg_instance = instance(); - if (tg_instance == nullptr) { - tg_instance = new JfrThreadGroup(); - if (tg_instance == nullptr) { - return 0; - } - set_instance(tg_instance); - } - - JfrThreadGroupEntry* tge = nullptr; - traceid parent_thread_group_id = 0; - while (helper.has_next()) { - JfrThreadGroupPointers& ptrs = helper.next(); - tge = tg_instance->find_entry(ptrs); - if (nullptr == tge) { - tge = tg_instance->new_entry(ptrs); - assert(tge != nullptr, "invariant"); - tge->set_parent_group_id(parent_thread_group_id); - } - parent_thread_group_id = tge->thread_group_id(); - } - // the last entry in the hierarchy is the immediate thread group - return tge->thread_group_id(); -} - -bool JfrThreadGroup::JfrThreadGroupEntry::is_equal(const JfrThreadGroupPointers& ptrs) const { - return ptrs.thread_group_oop() == thread_group(); -} - -JfrThreadGroup::JfrThreadGroupEntry* -JfrThreadGroup::find_entry(const JfrThreadGroupPointers& ptrs) const { - for (int index = 0; index < _list->length(); ++index) { - JfrThreadGroupEntry* curtge = _list->at(index); - if (curtge->is_equal(ptrs)) { - return curtge; - } - } - return (JfrThreadGroupEntry*) nullptr; -} - -// Assumes you already searched for the existence -// of a corresponding entry in find_entry(). -JfrThreadGroup::JfrThreadGroupEntry* -JfrThreadGroup::new_entry(JfrThreadGroupPointers& ptrs) { - JfrThreadGroupEntry* const tge = new JfrThreadGroupEntry(java_lang_ThreadGroup::name(ptrs.thread_group_oop()), ptrs); - add_entry(tge); - return tge; -} - -int JfrThreadGroup::add_entry(JfrThreadGroupEntry* tge) { - assert(tge != nullptr, "attempting to add a null entry!"); - assert(0 == tge->thread_group_id(), "id must be unassigned!"); - tge->set_thread_group_id(next_id()); - return _list->append(tge); -} - -void JfrThreadGroup::write_thread_group_entries(JfrCheckpointWriter& writer) const { - assert(_list != nullptr && !_list->is_empty(), "should not need be here!"); - const int number_of_tg_entries = _list->length(); - writer.write_count(number_of_tg_entries + 1); // + VirtualThread group - writer.write_key(1); // 1 is reserved for VirtualThread group - writer.write(0); // parent - const oop vgroup = java_lang_Thread_Constants::get_VTHREAD_GROUP(); - assert(vgroup != (oop)nullptr, "invariant"); - const char* const vgroup_name = java_lang_ThreadGroup::name(vgroup); - assert(vgroup_name != nullptr, "invariant"); - writer.write(vgroup_name); - for (int index = 0; index < number_of_tg_entries; ++index) { - const JfrThreadGroupEntry* const curtge = _list->at(index); - writer.write_key(curtge->thread_group_id()); - writer.write(curtge->parent_group_id()); - writer.write(curtge->thread_group_name()); - } -} - -void JfrThreadGroup::write_selective_thread_group(JfrCheckpointWriter* writer, traceid thread_group_id) const { - assert(writer != nullptr, "invariant"); - assert(_list != nullptr && !_list->is_empty(), "should not need be here!"); - assert(thread_group_id != 1, "should not need be here!"); - const int number_of_tg_entries = _list->length(); - - // save context - const JfrCheckpointContext ctx = writer->context(); - writer->write_type(TYPE_THREADGROUP); - const jlong count_offset = writer->reserve(sizeof(u4)); // Don't know how many yet - int number_of_entries_written = 0; - for (int index = number_of_tg_entries - 1; index >= 0; --index) { - const JfrThreadGroupEntry* const curtge = _list->at(index); - if (thread_group_id == curtge->thread_group_id()) { - writer->write_key(curtge->thread_group_id()); - writer->write(curtge->parent_group_id()); - writer->write(curtge->thread_group_name()); - ++number_of_entries_written; - thread_group_id = curtge->parent_group_id(); - } - } - if (number_of_entries_written == 0) { - // nothing to write, restore context - writer->set_context(ctx); - return; - } - assert(number_of_entries_written > 0, "invariant"); - writer->write_count(number_of_entries_written, count_offset); -} - -// Write out JfrThreadGroup instance and then delete it -void JfrThreadGroup::serialize(JfrCheckpointWriter& writer) { - ThreadGroupExclusiveAccess lock; - JfrThreadGroup* tg_instance = instance(); - assert(tg_instance != nullptr, "invariant"); - tg_instance->write_thread_group_entries(writer); -} - -// for writing a particular thread group -void JfrThreadGroup::serialize(JfrCheckpointWriter* writer, traceid thread_group_id) { - assert(writer != nullptr, "invariant"); - ThreadGroupExclusiveAccess lock; - JfrThreadGroup* const tg_instance = instance(); - assert(tg_instance != nullptr, "invariant"); - tg_instance->write_selective_thread_group(writer, thread_group_id); -} diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadGroupManager.cpp b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadGroupManager.cpp new file mode 100644 index 00000000000..55864af4d5c --- /dev/null +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadGroupManager.cpp @@ -0,0 +1,331 @@ +/* + * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "jfr/jni/jfrJavaSupport.hpp" +#include "jfr/recorder/checkpoint/jfrCheckpointWriter.hpp" +#include "jfr/recorder/checkpoint/types/jfrThreadGroupManager.hpp" +#include "jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.hpp" +#include "jfr/utilities/jfrAllocation.hpp" +#include "jfr/utilities/jfrLinkedList.inline.hpp" +#include "memory/resourceArea.hpp" +#include "runtime/handles.inline.hpp" +#include "runtime/jniHandles.inline.hpp" +#include "runtime/safepoint.hpp" +#include "runtime/semaphore.hpp" +#include "runtime/thread.inline.hpp" +#include "utilities/growableArray.hpp" + +class ThreadGroupExclusiveAccess : public StackObj { + private: + static Semaphore _mutex_semaphore; + public: + ThreadGroupExclusiveAccess() { _mutex_semaphore.wait(); } + ~ThreadGroupExclusiveAccess() { _mutex_semaphore.signal(); } +}; + +Semaphore ThreadGroupExclusiveAccess::_mutex_semaphore(1); + +static traceid next_id() { + static traceid _tgid = 1; // 1 is reserved for thread group "VirtualThreads" + return ++_tgid; +} + +class JfrThreadGroup : public JfrCHeapObj { + template + friend class JfrLinkedList; + private: + mutable const JfrThreadGroup* _next; + const JfrThreadGroup* _parent; + traceid _tgid; + char* _tg_name; // utf8 format + jweak _tg_handle; + mutable u2 _generation; + + public: + JfrThreadGroup(Handle tg, const JfrThreadGroup* parent) : + _next(nullptr), _parent(parent), _tgid(next_id()), _tg_name(nullptr), + _tg_handle(JNIHandles::make_weak_global(tg)), _generation(0) { + const char* name = java_lang_ThreadGroup::name(tg()); + if (name != nullptr) { + const size_t len = strlen(name); + _tg_name = JfrCHeapObj::new_array(len + 1); + strncpy(_tg_name, name, len + 1); + } + } + + ~JfrThreadGroup() { + JNIHandles::destroy_weak_global(_tg_handle); + if (_tg_name != nullptr) { + JfrCHeapObj::free(_tg_name, strlen(_tg_name) + 1); + } + } + + const JfrThreadGroup* next() const { return _next; } + + traceid id() const { return _tgid; } + + const char* name() const { + return _tg_name; + } + + const JfrThreadGroup* parent() const { return _parent; } + + traceid parent_id() const { + return _parent != nullptr ? _parent->id() : 0; + } + + bool is_dead() const { + return JNIHandles::resolve(_tg_handle) == nullptr; + } + + bool operator==(oop tg) const { + assert(tg != nullptr, "invariant"); + return tg == JNIHandles::resolve(_tg_handle); + } + + bool should_write() const { + return !JfrTraceIdEpoch::is_current_epoch_generation(_generation); + } + + void set_written() const { + assert(should_write(), "invariant"); + _generation = JfrTraceIdEpoch::epoch_generation(); + } +}; + +typedef JfrLinkedList JfrThreadGroupList; + +static JfrThreadGroupList* _list = nullptr; + +static JfrThreadGroupList& list() { + assert(_list != nullptr, "invariant"); + return *_list; +} + +bool JfrThreadGroupManager::create() { + assert(_list == nullptr, "invariant"); + _list = new JfrThreadGroupList(); + return _list != nullptr; +} + +void JfrThreadGroupManager::destroy() { + delete _list; + _list = nullptr; +} + +static int populate(GrowableArray* hierarchy, const JavaThread* jt, Thread* current) { + assert(hierarchy != nullptr, "invariant"); + assert(jt != nullptr, "invariant"); + assert(current == Thread::current(), "invariant"); + + oop thread_oop = jt->threadObj(); + if (thread_oop == nullptr) { + return 0; + } + // Immediate thread group. + const Handle tg_handle(current, java_lang_Thread::threadGroup(thread_oop)); + if (tg_handle.is_null()) { + return 0; + } + hierarchy->append(tg_handle); + + // Thread group parent and then its parents... + Handle parent_tg_handle(current, java_lang_ThreadGroup::parent(tg_handle())); + + while (parent_tg_handle != nullptr) { + hierarchy->append(parent_tg_handle); + parent_tg_handle = Handle(current, java_lang_ThreadGroup::parent(parent_tg_handle())); + } + + return hierarchy->length(); +} + +class JfrThreadGroupLookup : public ResourceObj { + static const int invalid_iterator = -1; + private: + GrowableArray* _hierarchy; + mutable int _iterator; + + public: + JfrThreadGroupLookup(const JavaThread* jt, Thread* current) : + _hierarchy(new GrowableArray(16)), + _iterator(populate(_hierarchy, jt, current) - 1) {} + + bool has_next() const { + return _iterator > invalid_iterator; + } + + const Handle& next() const { + assert(has_next(), "invariant"); + return _hierarchy->at(_iterator--); + } +}; + +static const JfrThreadGroup* find_or_add(const Handle& tg_oop, const JfrThreadGroup* parent) { + assert(parent == nullptr || list().in_list(parent), "invariant"); + const JfrThreadGroup* tg = list().head(); + const JfrThreadGroup* result = nullptr; + while (tg != nullptr) { + if (*tg == tg_oop()) { + assert(tg->parent() == parent, "invariant"); + result = tg; + tg = nullptr; + continue; + } + tg = tg->next(); + } + if (result == nullptr) { + result = new JfrThreadGroup(tg_oop, parent); + list().add(result); + } + return result; +} + +static traceid find_tgid(const JfrThreadGroupLookup& lookup) { + const JfrThreadGroup* tg = nullptr; + const JfrThreadGroup* ptg = nullptr; + while (lookup.has_next()) { + tg = find_or_add(lookup.next(), ptg); + ptg = tg; + } + return tg != nullptr ? tg->id() : 0; +} + +static traceid find(const JfrThreadGroupLookup& lookup) { + ThreadGroupExclusiveAccess lock; + return find_tgid(lookup); +} + +traceid JfrThreadGroupManager::thread_group_id(JavaThread* jt) { + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(jt);) + ResourceMark rm(jt); + HandleMark hm(jt); + const JfrThreadGroupLookup lookup(jt, jt); + return find(lookup); +} + +traceid JfrThreadGroupManager::thread_group_id(const JavaThread* jt, Thread* current) { + assert(jt != nullptr, "invariant"); + assert(current != nullptr, "invariant"); + assert(!current->is_Java_thread() || JavaThread::cast(current)->thread_state() == _thread_in_vm, "invariant"); + ResourceMark rm(current); + HandleMark hm(current); + const JfrThreadGroupLookup lookup(jt, current); + return find(lookup); +} + +static void write_virtual_thread_group(JfrCheckpointWriter& writer) { + writer.write_key(1); // 1 is reserved for VirtualThread group + writer.write(0); // parent + const oop vgroup = java_lang_Thread_Constants::get_VTHREAD_GROUP(); + assert(vgroup != (oop)nullptr, "invariant"); + const char* const vgroup_name = java_lang_ThreadGroup::name(vgroup); + assert(vgroup_name != nullptr, "invariant"); + writer.write(vgroup_name); +} + +static int write_thread_group(JfrCheckpointWriter& writer, const JfrThreadGroup* tg, bool to_blob = false) { + assert(tg != nullptr, "invariant"); + if (tg->should_write() || to_blob) { + writer.write_key(tg->id()); + writer.write(tg->parent_id()); + writer.write(tg->name()); + if (!to_blob) { + tg->set_written(); + } + return 1; + } + return 0; +} + +// For writing all live thread groups while removing and deleting dead thread groups. +void JfrThreadGroupManager::serialize(JfrCheckpointWriter& writer) { + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(JavaThread::current());) + + const uint64_t count_offset = writer.reserve(sizeof(u4)); // Don't know how many yet + + // First write the pre-defined ThreadGroup for virtual threads. + write_virtual_thread_group(writer); + int number_of_groups_written = 1; + + const JfrThreadGroup* next = nullptr; + const JfrThreadGroup* prev = nullptr; + + { + ThreadGroupExclusiveAccess lock; + const JfrThreadGroup* tg = list().head(); + while (tg != nullptr) { + next = tg->next(); + if (tg->is_dead()) { + prev = list().excise(prev, tg); + assert(!list().in_list(tg), "invariant"); + delete tg; + tg = next; + continue; + } + number_of_groups_written += write_thread_group(writer, tg); + prev = tg; + tg = next; + } + } + + assert(number_of_groups_written > 0, "invariant"); + writer.write_count(number_of_groups_written, count_offset); +} + +// For writing a specific thread group and its ancestry. +void JfrThreadGroupManager::serialize(JfrCheckpointWriter& writer, traceid tgid, bool to_blob) { + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(JavaThread::current());) + // save context + const JfrCheckpointContext ctx = writer.context(); + + writer.write_type(TYPE_THREADGROUP); + const uint64_t count_offset = writer.reserve(sizeof(u4)); // Don't know how many yet + + int number_of_groups_written = 0; + + { + ThreadGroupExclusiveAccess lock; + const JfrThreadGroup* tg = list().head(); + while (tg != nullptr) { + if (tgid == tg->id()) { + while (tg != nullptr) { + number_of_groups_written += write_thread_group(writer, tg, to_blob); + tg = tg->parent(); + } + break; + } + tg = tg->next(); + } + } + + if (number_of_groups_written == 0) { + // nothing to write, restore context + writer.set_context(ctx); + return; + } + + assert(number_of_groups_written > 0, "invariant"); + writer.write_count(number_of_groups_written, count_offset); +} diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadGroup.hpp b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadGroupManager.hpp similarity index 50% rename from src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadGroup.hpp rename to src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadGroupManager.hpp index 8226c6ebef2..22c140c6f8f 100644 --- a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadGroup.hpp +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadGroupManager.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,44 +22,27 @@ * */ -#ifndef SHARE_JFR_RECORDER_CHECKPOINT_TYPES_JFRTHREADGROUP_HPP -#define SHARE_JFR_RECORDER_CHECKPOINT_TYPES_JFRTHREADGROUP_HPP +#ifndef SHARE_JFR_RECORDER_CHECKPOINT_TYPES_JFRTHREADGROUPMANAGER_HPP +#define SHARE_JFR_RECORDER_CHECKPOINT_TYPES_JFRTHREADGROUPMANAGER_HPP -#include "jfr/utilities/jfrAllocation.hpp" #include "jfr/utilities/jfrTypes.hpp" -#include "jni.h" +#include "memory/allStatic.hpp" class JfrCheckpointWriter; -template -class GrowableArray; -class JfrThreadGroupsHelper; -class JfrThreadGroupPointers; -class JfrThreadGroup : public JfrCHeapObj { - friend class JfrCheckpointThreadClosure; +class JfrThreadGroupManager : public AllStatic { + friend class JfrRecorder; + private: - static JfrThreadGroup* _instance; - class JfrThreadGroupEntry; - GrowableArray* _list; - - JfrThreadGroup(); - JfrThreadGroupEntry* find_entry(const JfrThreadGroupPointers& ptrs) const; - JfrThreadGroupEntry* new_entry(JfrThreadGroupPointers& ptrs); - int add_entry(JfrThreadGroupEntry* const tge); - - void write_thread_group_entries(JfrCheckpointWriter& writer) const; - void write_selective_thread_group(JfrCheckpointWriter* writer, traceid thread_group_id) const; - - static traceid thread_group_id_internal(JfrThreadGroupsHelper& helper); - static JfrThreadGroup* instance(); - static void set_instance(JfrThreadGroup* new_instance); + static bool create(); + static void destroy(); public: - ~JfrThreadGroup(); static void serialize(JfrCheckpointWriter& w); - static void serialize(JfrCheckpointWriter* w, traceid thread_group_id); + static void serialize(JfrCheckpointWriter& w, traceid tgid, bool is_blob); + static traceid thread_group_id(JavaThread* thread); static traceid thread_group_id(const JavaThread* thread, Thread* current); }; -#endif // SHARE_JFR_RECORDER_CHECKPOINT_TYPES_JFRTHREADGROUP_HPP +#endif // SHARE_JFR_RECORDER_CHECKPOINT_TYPES_JFRTHREADGROUPMANAGER_HPP diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrType.cpp b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrType.cpp index 9179395a451..17d945af65e 100644 --- a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrType.cpp +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrType.cpp @@ -32,7 +32,7 @@ #include "gc/shared/gcWhen.hpp" #include "jfr/leakprofiler/leakProfiler.hpp" #include "jfr/recorder/checkpoint/jfrCheckpointWriter.hpp" -#include "jfr/recorder/checkpoint/types/jfrThreadGroup.hpp" +#include "jfr/recorder/checkpoint/types/jfrThreadGroupManager.hpp" #include "jfr/recorder/checkpoint/types/jfrThreadState.hpp" #include "jfr/recorder/checkpoint/types/jfrType.hpp" #include "jfr/recorder/jfrRecorder.hpp" @@ -106,7 +106,7 @@ void JfrCheckpointThreadClosure::do_thread(Thread* t) { } else { _writer.write(name); _writer.write(tid); - _writer.write(JfrThreadGroup::thread_group_id(JavaThread::cast(t), _curthread)); + _writer.write(JfrThreadGroupManager::thread_group_id(JavaThread::cast(t), _curthread)); } _writer.write(false); // isVirtual } @@ -115,7 +115,10 @@ void JfrThreadConstantSet::serialize(JfrCheckpointWriter& writer) { JfrCheckpointThreadClosure tc(writer); JfrJavaThreadIterator javathreads; while (javathreads.has_next()) { - tc.do_thread(javathreads.next()); + JavaThread* const jt = javathreads.next(); + if (jt->jfr_thread_local()->should_write()) { + tc.do_thread(jt); + } } JfrNonJavaThreadIterator nonjavathreads; while (nonjavathreads.has_next()) { @@ -124,7 +127,7 @@ void JfrThreadConstantSet::serialize(JfrCheckpointWriter& writer) { } void JfrThreadGroupConstant::serialize(JfrCheckpointWriter& writer) { - JfrThreadGroup::serialize(writer); + JfrThreadGroupManager::serialize(writer); } static const char* flag_value_origin_to_string(JVMFlagOrigin origin) { @@ -303,11 +306,11 @@ void JfrThreadConstant::serialize(JfrCheckpointWriter& writer) { writer.write(JfrThreadId::jfr_id(_thread, _tid)); // java thread group - VirtualThread threadgroup reserved id 1 const traceid thread_group_id = is_vthread ? 1 : - JfrThreadGroup::thread_group_id(JavaThread::cast(_thread), Thread::current()); + JfrThreadGroupManager::thread_group_id(JavaThread::cast(_thread), Thread::current()); writer.write(thread_group_id); writer.write(is_vthread); // isVirtual - if (!is_vthread) { - JfrThreadGroup::serialize(&writer, thread_group_id); + if (thread_group_id > 1) { + JfrThreadGroupManager::serialize(writer, thread_group_id, _to_blob); } // VirtualThread threadgroup already serialized invariant. } diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrType.hpp b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrType.hpp index de35ecaf917..9798a6d29c0 100644 --- a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrType.hpp +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrType.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -109,11 +109,12 @@ class JfrThreadConstant : public JfrSerializer { oop _vthread; const char* _name; int _length; + const bool _to_blob; void write_name(JfrCheckpointWriter& writer); void write_os_name(JfrCheckpointWriter& writer, bool is_vthread); public: - JfrThreadConstant(Thread* t, traceid tid, oop vthread = nullptr) : - _thread(t), _tid(tid), _vthread(vthread), _name(nullptr), _length(-1) {} + JfrThreadConstant(Thread* t, traceid tid, bool to_blob, oop vthread = nullptr) : + _thread(t), _tid(tid), _vthread(vthread), _name(nullptr), _length(-1), _to_blob(to_blob) {} void serialize(JfrCheckpointWriter& writer); }; diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeManager.cpp b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeManager.cpp index 493942bade4..58d12f70980 100644 --- a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeManager.cpp +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeManager.cpp @@ -109,7 +109,7 @@ JfrBlobHandle JfrTypeManager::create_thread_blob(JavaThread* jt, traceid tid /* // TYPE_THREAD and count is written unconditionally for blobs, also for vthreads. writer.write_type(TYPE_THREAD); writer.write_count(1); - JfrThreadConstant type_thread(jt, tid, vthread); + JfrThreadConstant type_thread(jt, tid, true, vthread); type_thread.serialize(writer); return writer.move(); } @@ -128,7 +128,7 @@ void JfrTypeManager::write_checkpoint(Thread* t, traceid tid /* 0 */, oop vthrea writer.write_type(TYPE_THREAD); writer.write_count(1); } - JfrThreadConstant type_thread(t, tid, vthread); + JfrThreadConstant type_thread(t, tid, false, vthread); type_thread.serialize(writer); } diff --git a/src/hotspot/share/jfr/recorder/jfrRecorder.cpp b/src/hotspot/share/jfr/recorder/jfrRecorder.cpp index dd75cb2929f..4ef278ab522 100644 --- a/src/hotspot/share/jfr/recorder/jfrRecorder.cpp +++ b/src/hotspot/share/jfr/recorder/jfrRecorder.cpp @@ -33,6 +33,7 @@ #include "jfr/periodic/sampling/jfrThreadSampler.hpp" #include "jfr/recorder/jfrRecorder.hpp" #include "jfr/recorder/checkpoint/jfrCheckpointManager.hpp" +#include "jfr/recorder/checkpoint/types/jfrThreadGroupManager.hpp" #include "jfr/recorder/repository/jfrRepository.hpp" #include "jfr/recorder/service/jfrEventThrottler.hpp" #include "jfr/recorder/service/jfrOptionSet.hpp" @@ -311,6 +312,9 @@ bool JfrRecorder::create_components() { if (!create_event_throttler()) { return false; } + if (!create_thread_group_manager()) { + return false; + } return true; } @@ -405,6 +409,10 @@ bool JfrRecorder::create_event_throttler() { return JfrEventThrottler::create(); } +bool JfrRecorder::create_thread_group_manager() { + return JfrThreadGroupManager::create(); +} + void JfrRecorder::destroy_components() { JfrJvmtiAgent::destroy(); if (_post_box != nullptr) { @@ -444,6 +452,7 @@ void JfrRecorder::destroy_components() { _cpu_time_thread_sampling = nullptr; } JfrEventThrottler::destroy(); + JfrThreadGroupManager::destroy(); } bool JfrRecorder::create_recorder_thread() { diff --git a/src/hotspot/share/jfr/recorder/jfrRecorder.hpp b/src/hotspot/share/jfr/recorder/jfrRecorder.hpp index 3099c8ad344..34cc8fda949 100644 --- a/src/hotspot/share/jfr/recorder/jfrRecorder.hpp +++ b/src/hotspot/share/jfr/recorder/jfrRecorder.hpp @@ -53,6 +53,7 @@ class JfrRecorder : public JfrCHeapObj { static bool create_stacktrace_repository(); static bool create_storage(); static bool create_stringpool(); + static bool create_thread_group_manager(); static bool create_thread_sampler(); static bool create_cpu_time_thread_sampling(); static bool create_event_throttler(); diff --git a/src/hotspot/share/jfr/support/jfrThreadLocal.cpp b/src/hotspot/share/jfr/support/jfrThreadLocal.cpp index e38e0427a05..291169b9aa7 100644 --- a/src/hotspot/share/jfr/support/jfrThreadLocal.cpp +++ b/src/hotspot/share/jfr/support/jfrThreadLocal.cpp @@ -29,6 +29,7 @@ #include "jfr/periodic/sampling/jfrCPUTimeThreadSampler.hpp" #include "jfr/recorder/checkpoint/jfrCheckpointManager.hpp" #include "jfr/recorder/checkpoint/types/traceid/jfrOopTraceId.inline.hpp" +#include "jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.hpp" #include "jfr/recorder/jfrRecorder.hpp" #include "jfr/recorder/service/jfrOptionSet.hpp" #include "jfr/recorder/stacktrace/jfrStackTraceRepository.hpp" @@ -75,6 +76,7 @@ JfrThreadLocal::JfrThreadLocal() : _wallclock_time(os::javaTimeNanos()), _non_reentrant_nesting(0), _vthread_epoch(0), + _generation(0), _vthread_excluded(false), _jvm_thread_excluded(false), _enqueued_requests(false), @@ -136,17 +138,33 @@ static void send_java_thread_start_event(JavaThread* jt) { } void JfrThreadLocal::on_start(Thread* t) { - assign_thread_id(t, t->jfr_thread_local()); + JfrThreadLocal* const tl = t->jfr_thread_local(); + assert(tl != nullptr, "invariant"); + assign_thread_id(t, tl); if (JfrRecorder::is_recording()) { - JfrCheckpointManager::write_checkpoint(t); - if (t->is_Java_thread()) { - JavaThread *const jt = JavaThread::cast(t); + if (!t->is_Java_thread()) { + JfrCheckpointManager::write_checkpoint(t); + return; + } + JavaThread* const jt = JavaThread::cast(t); + if (jt->thread_state() == _thread_new) { JfrCPUTimeThreadSampling::on_javathread_create(jt); + } else { + assert(jt->thread_state() == _thread_in_vm, "invariant"); + if (tl->should_write()) { + JfrCheckpointManager::write_checkpoint(t); + } send_java_thread_start_event(jt); + if (tl->has_cached_stack_trace()) { + tl->clear_cached_stack_trace(); + } + return; } } - if (t->jfr_thread_local()->has_cached_stack_trace()) { - t->jfr_thread_local()->clear_cached_stack_trace(); + if (t->is_Java_thread() && JavaThread::cast(t)->thread_state() == _thread_in_vm) { + if (tl->has_cached_stack_trace()) { + tl->clear_cached_stack_trace(); + } } } @@ -229,13 +247,18 @@ void JfrThreadLocal::on_exit(Thread* t) { JfrThreadLocal * const tl = t->jfr_thread_local(); assert(!tl->is_dead(), "invariant"); if (JfrRecorder::is_recording()) { - JfrCheckpointManager::write_checkpoint(t); - } - if (t->is_Java_thread()) { - JavaThread* const jt = JavaThread::cast(t); - send_java_thread_end_event(jt, JfrThreadLocal::jvm_thread_id(jt)); - JfrCPUTimeThreadSampling::on_javathread_terminate(jt); - JfrThreadCPULoadEvent::send_event_for_thread(jt); + if (!t->is_Java_thread()) { + JfrCheckpointManager::write_checkpoint(t); + } else { + JavaThread* const jt = JavaThread::cast(t); + assert(jt->thread_state() == _thread_in_vm, "invariant"); + if (tl->should_write()) { + JfrCheckpointManager::write_checkpoint(t); + } + send_java_thread_end_event(jt, JfrThreadLocal::jvm_thread_id(jt)); + JfrCPUTimeThreadSampling::on_javathread_terminate(jt); + JfrThreadCPULoadEvent::send_event_for_thread(jt); + } } release(tl, Thread::current()); // because it could be that Thread::current() != t } @@ -425,6 +448,15 @@ u2 JfrThreadLocal::vthread_epoch(const JavaThread* jt) { return Atomic::load(&jt->jfr_thread_local()->_vthread_epoch); } +bool JfrThreadLocal::should_write() const { + const u2 current_generation = JfrTraceIdEpoch::epoch_generation(); + if (Atomic::load(&_generation) != current_generation) { + Atomic::store(&_generation, current_generation); + return true; + } + return false; +} + traceid JfrThreadLocal::thread_id(const Thread* t) { assert(t != nullptr, "invariant"); if (is_impersonating(t)) { diff --git a/src/hotspot/share/jfr/support/jfrThreadLocal.hpp b/src/hotspot/share/jfr/support/jfrThreadLocal.hpp index 715a2c44f93..8c82dfad8af 100644 --- a/src/hotspot/share/jfr/support/jfrThreadLocal.hpp +++ b/src/hotspot/share/jfr/support/jfrThreadLocal.hpp @@ -75,6 +75,7 @@ class JfrThreadLocal { jlong _wallclock_time; int32_t _non_reentrant_nesting; u2 _vthread_epoch; + mutable u2 _generation; bool _vthread_excluded; bool _jvm_thread_excluded; volatile bool _enqueued_requests; @@ -348,6 +349,9 @@ class JfrThreadLocal { return _sampling_critical_section; } + // Serialization state. + bool should_write() const; + static int32_t make_non_reentrant(Thread* thread); static void make_reentrant(Thread* thread, int32_t previous_nesting); diff --git a/src/hotspot/share/runtime/javaThread.cpp b/src/hotspot/share/runtime/javaThread.cpp index 3d9421fc94c..a0ac7bd4768 100644 --- a/src/hotspot/share/runtime/javaThread.cpp +++ b/src/hotspot/share/runtime/javaThread.cpp @@ -738,6 +738,8 @@ void JavaThread::run() { assert(JavaThread::current() == this, "sanity check"); assert(!Thread::current()->owns_locks(), "sanity check"); + JFR_ONLY(Jfr::on_thread_start(this);) + DTRACE_THREAD_PROBE(start, this); // This operation might block. We call that after all safepoint checks for a new thread has From b96b9c3d5b2ffaeaa365b2f0d33674a980c96547 Mon Sep 17 00:00:00 2001 From: Erik Gahlin Date: Mon, 4 Aug 2025 10:25:14 +0000 Subject: [PATCH 007/580] 8364461: JFR: Default constructor may not be first in setting control Reviewed-by: mgronlun --- .../jdk/jfr/internal/EventControl.java | 19 ++++++++++++------- .../jdk/jfr/api/settings/RegExpControl.java | 10 +++++++++- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/EventControl.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/EventControl.java index 02775b7707a..7a8d490d6e5 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/EventControl.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/EventControl.java @@ -285,20 +285,25 @@ public final class EventControl { private SettingControl instantiateSettingControl(Class settingControlClass) throws IllegalAccessException, InstantiationException { SecuritySupport.makeVisibleToJFR(settingControlClass); - final Constructor cc; - try { - cc = settingControlClass.getDeclaredConstructors()[0]; - } catch (Exception e) { - throw (Error) new InternalError("Could not get constructor for " + settingControlClass.getName()).initCause(e); - } - cc.setAccessible(true); try { + Constructor cc = findDefaultConstructor(settingControlClass); + cc.setAccessible(true); return (SettingControl) cc.newInstance(); } catch (IllegalArgumentException | InvocationTargetException e) { throw new InternalError("Could not instantiate setting for class " + settingControlClass.getName()); } } + private Constructor findDefaultConstructor(Class settingControlClass) { + for (Constructor c : settingControlClass.getDeclaredConstructors()) { + if (c.getParameterCount() == 0) { + return c; + } + } + // Programming error by user, fail fast + throw new InstantiationError("Could not find default constructor for " + settingControlClass.getName()); + } + private static Control defineEnabled(PlatformEventType type) { // Java events are enabled by default, // JVM events are not, maybe they should be? Would lower learning curve diff --git a/test/jdk/jdk/jfr/api/settings/RegExpControl.java b/test/jdk/jdk/jfr/api/settings/RegExpControl.java index 051cfb3aa09..182cd3998b9 100644 --- a/test/jdk/jdk/jfr/api/settings/RegExpControl.java +++ b/test/jdk/jdk/jfr/api/settings/RegExpControl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 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 @@ -31,6 +31,14 @@ import jdk.jfr.SettingControl; public final class RegExpControl extends SettingControl { private Pattern pattern = Pattern.compile(".*"); + // Purpose of this constructor is to ensure that the correct + // constructor is picked when the event class is registered + public RegExpControl(String dummy) { + } + + public RegExpControl() { + } + public void setValue(String value) { this.pattern = Pattern.compile(value); } From da0d9598d049b17c04da95b61214b093c97fb60e Mon Sep 17 00:00:00 2001 From: Erik Gahlin Date: Mon, 4 Aug 2025 10:41:21 +0000 Subject: [PATCH 008/580] 8364190: JFR: RemoteRecordingStream withers don't work Reviewed-by: mgronlun --- .../management/jfr/RemoteRecordingStream.java | 14 +- .../jfr/jmx/streaming/TestEnableDisable.java | 4 +- .../jdk/jfr/jmx/streaming/TestWithers.java | 141 ++++++++++++++++++ 3 files changed, 151 insertions(+), 8 deletions(-) create mode 100644 test/jdk/jdk/jfr/jmx/streaming/TestWithers.java diff --git a/src/jdk.management.jfr/share/classes/jdk/management/jfr/RemoteRecordingStream.java b/src/jdk.management.jfr/share/classes/jdk/management/jfr/RemoteRecordingStream.java index 862781802fc..a8752e159ad 100644 --- a/src/jdk.management.jfr/share/classes/jdk/management/jfr/RemoteRecordingStream.java +++ b/src/jdk.management.jfr/share/classes/jdk/management/jfr/RemoteRecordingStream.java @@ -98,10 +98,12 @@ public final class RemoteRecordingStream implements EventStream { private final FlightRecorderMXBean mbean; private final long recordingId; + private final String identifier; - RemoteSettings(FlightRecorderMXBean mbean, long recordingId) { + RemoteSettings(FlightRecorderMXBean mbean, long recordingId, String identifier) { this.mbean = mbean; this.recordingId = recordingId; + this.identifier = identifier; } @Override @@ -111,7 +113,7 @@ public final class RemoteRecordingStream implements EventStream { // FlightRecorderMXBean implementation always returns // new instance of Map so no need to create new here. Map newSettings = getEventSettings(); - newSettings.put(name, value); + newSettings.put(identifier + "#" + name, value); mbean.setRecordingSettings(recordingId, newSettings); } @@ -340,9 +342,9 @@ public final class RemoteRecordingStream implements EventStream { */ public EventSettings disable(String name) { Objects.requireNonNull(name, "name"); - EventSettings s = ManagementSupport.newEventSettings(new RemoteSettings(mbean, recordingId)); + EventSettings s = ManagementSupport.newEventSettings(new RemoteSettings(mbean, recordingId, name)); try { - return s.with(name + "#" + ENABLED, "false"); + return s.with(ENABLED, "false"); } catch (Exception e) { ManagementSupport.logDebug(e.getMessage()); close(); @@ -364,9 +366,9 @@ public final class RemoteRecordingStream implements EventStream { */ public EventSettings enable(String name) { Objects.requireNonNull(name, "name"); - EventSettings s = ManagementSupport.newEventSettings(new RemoteSettings(mbean, recordingId)); + EventSettings s = ManagementSupport.newEventSettings(new RemoteSettings(mbean, recordingId, name)); try { - return s.with(name + "#" + ENABLED, "true"); + return s.with(ENABLED, "true"); } catch (Exception e) { ManagementSupport.logDebug(e.getMessage()); close(); diff --git a/test/jdk/jdk/jfr/jmx/streaming/TestEnableDisable.java b/test/jdk/jdk/jfr/jmx/streaming/TestEnableDisable.java index 06e9691cfa2..5b1435c9d9d 100644 --- a/test/jdk/jdk/jfr/jmx/streaming/TestEnableDisable.java +++ b/test/jdk/jdk/jfr/jmx/streaming/TestEnableDisable.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 @@ -36,7 +36,7 @@ import jdk.management.jfr.RemoteRecordingStream; /** * @test * @requires vm.flagless - * @summary Tests that event settings for a RemoteRecordingStream can be changed + * @summary Tests that the enabled setting can be configured for a RemoteRecordingStream * @requires vm.hasJFR * @library /test/lib /test/jdk * @run main/othervm jdk.jfr.jmx.streaming.TestEnableDisable diff --git a/test/jdk/jdk/jfr/jmx/streaming/TestWithers.java b/test/jdk/jdk/jfr/jmx/streaming/TestWithers.java new file mode 100644 index 00000000000..933337ef76b --- /dev/null +++ b/test/jdk/jdk/jfr/jmx/streaming/TestWithers.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.jfr.jmx.streaming; + +import java.lang.management.ManagementFactory; +import java.time.Duration; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Predicate; + +import javax.management.MBeanServerConnection; + +import jdk.jfr.Event; +import jdk.jfr.EventSettings; +import jdk.jfr.FlightRecorder; +import jdk.jfr.Name; +import jdk.jfr.Period; +import jdk.jfr.StackTrace; +import jdk.jfr.Threshold; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordedStackTrace; +import jdk.management.jfr.RemoteRecordingStream; + +/** + * @test + * @requires vm.flagless + * @summary Tests that event settings for a RemoteRecordingStream can be changed + * @requires vm.hasJFR + * @library /test/lib /test/jdk + * @run main/othervm jdk.jfr.jmx.streaming.TestWithers + */ +public class TestWithers { + private static final Set RESULT = Collections.synchronizedSet(new HashSet<>()); + + @Name("AA") + @StackTrace(false) + static class A extends Event { + } + + @Name("BB") + @StackTrace(true) + static class B extends Event { + } + + @Name("CC") + @Threshold("10 h") + static class C extends Event { + } + + @Name("DD") + @Threshold("10 h") + static class D extends Event { + } + + @Name("EE") + @StackTrace(false) + static class E extends Event { + } + + @Name("FF") + @Period("10 h") + static class F extends Event { + } + + public static void main(String... args) throws Exception { + MBeanServerConnection conn = ManagementFactory.getPlatformMBeanServer(); + try (RemoteRecordingStream stream = new RemoteRecordingStream(conn)) { + addCheck(stream, es -> es.withStackTrace(), "AA", TestWithers::hasStackTrace); + addCheck(stream, es -> es.withoutStackTrace(), "BB", e -> !hasStackTrace(e)); + addCheck(stream, es -> es.withThreshold(Duration.ofMillis(0)), "CC", e -> true); + addCheck(stream, es -> es.withoutThreshold(), "DD", e -> true); + addCheck(stream, es -> es.with("stackTrace", "true"), "EE", TestWithers::hasStackTrace); + addCheck(stream, es -> es.withPeriod(Duration.ofMillis(700)), "FF", e -> true); + FlightRecorder.addPeriodicEvent(F.class, () -> { + F f = new F(); + f.commit(); + }); + stream.onFlush(() -> { + System.out.println(RESULT); + if (RESULT.size() == 6) { + stream.close(); + } + }); + + stream.startAsync(); + A a = new A(); + a.commit(); + + B b = new B(); + b.commit(); + + C c = new C(); + c.commit(); + + D d = new D(); + d.commit(); + + E e = new E(); + e.commit(); + + stream.awaitTermination(); + } + } + + private static void addCheck(RemoteRecordingStream stream, Consumer es, String eventName, Predicate validator) { + es.accept(stream.enable(eventName)); + stream.onEvent(eventName, e -> { + System.out.println(e); + if (validator.test(e)) { + RESULT.add(eventName); + } + }); + } + + private static boolean hasStackTrace(RecordedEvent e) { + RecordedStackTrace rs = e.getStackTrace(); + return rs != null && !rs.getFrames().isEmpty(); + } +} \ No newline at end of file From 68a4396dbc1f7bc02fea91934fc71366ad879637 Mon Sep 17 00:00:00 2001 From: Erik Gahlin Date: Mon, 4 Aug 2025 10:53:40 +0000 Subject: [PATCH 009/580] 8364316: JFR: Incorrect validation of mirror fields Reviewed-by: shade, mgronlun --- .../share/classes/jdk/jfr/internal/Type.java | 4 +- .../classes/jdk/jfr/internal/TypeLibrary.java | 16 +---- .../classes/jdk/jfr/internal/util/Utils.java | 58 ++++++++----------- 3 files changed, 27 insertions(+), 51 deletions(-) diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/Type.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/Type.java index 7ede3de9d15..80edba97e59 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/Type.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/Type.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -149,7 +149,7 @@ public class Type implements Comparable { return null; } - static boolean isKnownType(Class type) { + public static boolean isKnownType(Class type) { if (type.isPrimitive()) { return true; } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/TypeLibrary.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/TypeLibrary.java index bf26294ef57..de7584d3e3f 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/TypeLibrary.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/TypeLibrary.java @@ -330,7 +330,7 @@ public final class TypeLibrary { dynamicFieldSet.put(dynamicField.getName(), dynamicField); } List newTypes = new ArrayList<>(); - for (Field field : Utils.getVisibleEventFields(clazz)) { + for (Field field : Utils.getEventFields(clazz)) { ValueDescriptor vd = dynamicFieldSet.get(field.getName()); if (vd != null) { if (!vd.getTypeName().equals(field.getType().getName())) { @@ -343,9 +343,7 @@ public final class TypeLibrary { } else { vd = createField(field); } - if (vd != null) { - type.add(vd); - } + type.add(vd); } addTypes(newTypes); } @@ -384,17 +382,7 @@ public final class TypeLibrary { } private static ValueDescriptor createField(Field field) { - int mod = field.getModifiers(); - if (Modifier.isTransient(mod)) { - return null; - } - if (Modifier.isStatic(mod)) { - return null; - } Class fieldType = field.getType(); - if (!Type.isKnownType(fieldType)) { - return null; - } boolean constantPool = Thread.class == fieldType || fieldType == Class.class; Type type = createType(fieldType); String fieldName = field.getName(); diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Utils.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Utils.java index 7de6617f806..db832626ded 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Utils.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Utils.java @@ -211,13 +211,15 @@ public final class Utils { return sanitized; } - public static List getVisibleEventFields(Class clazz) { + public static List getEventFields(Class clazz) { List fields = new ArrayList<>(); for (Class c = clazz; !Utils.isEventBaseClass(c); c = c.getSuperclass()) { for (Field field : c.getDeclaredFields()) { - // skip private field in base classes - if (c == clazz || !Modifier.isPrivate(field.getModifiers())) { - fields.add(field); + if (isSupportedField(field)) { + // skip private field in base classes + if (c == clazz || !Modifier.isPrivate(field.getModifiers())) { + fields.add(field); + } } } } @@ -311,48 +313,34 @@ public final class Utils { } public static void verifyMirror(Class mirror, Class real) { - Class cMirror = Objects.requireNonNull(mirror); - Class cReal = Objects.requireNonNull(real); - Map mirrorFields = new HashMap<>(); - while (cMirror != null) { - for (Field f : cMirror.getDeclaredFields()) { - if (isSupportedType(f.getType())) { - mirrorFields.put(f.getName(), f); - } - } - cMirror = cMirror.getSuperclass(); + for (Field f : mirror.getDeclaredFields()) { + mirrorFields.put(f.getName(), f); } - while (cReal != null) { - for (Field realField : cReal.getDeclaredFields()) { - if (isSupportedType(realField.getType()) && !realField.isSynthetic()) { - String fieldName = realField.getName(); - Field mirrorField = mirrorFields.get(fieldName); - if (mirrorField == null) { - throw new InternalError("Missing mirror field for " + cReal.getName() + "#" + fieldName); - } - if (realField.getType() != mirrorField.getType()) { - throw new InternalError("Incorrect type for mirror field " + fieldName); - } - if (realField.getModifiers() != mirrorField.getModifiers()) { - throw new InternalError("Incorrect modifier for mirror field " + fieldName); - } - mirrorFields.remove(fieldName); - } + for (Field realField : Utils.getEventFields(real)) { + String fieldName = realField.getName(); + Field mirrorField = mirrorFields.remove(fieldName); + if (mirrorField == null) { + throw new InternalError("Missing mirror field for " + real.getName() + "#" + fieldName); + } + if (realField.getType() != mirrorField.getType()) { + throw new InternalError("Incorrect type for mirror field " + fieldName); + } + if (realField.getModifiers() != mirrorField.getModifiers()) { + throw new InternalError("Incorrect modifier for mirror field " + fieldName); } - cReal = cReal.getSuperclass(); } - if (!mirrorFields.isEmpty()) { throw new InternalError("Found additional fields in mirror class " + mirrorFields.keySet()); } } - private static boolean isSupportedType(Class type) { - if (Modifier.isTransient(type.getModifiers()) || Modifier.isStatic(type.getModifiers())) { + public static boolean isSupportedField(Field field) { + int modifiers = field.getModifiers(); + if (Modifier.isTransient(modifiers) || Modifier.isStatic(modifiers)) { return false; } - return Type.isValidJavaFieldType(type.getName()); + return Type.isKnownType(field.getType()); } public static void notifyFlush() { From 500462fb690c25da3816467e27fc66d25b4eb7dc Mon Sep 17 00:00:00 2001 From: Jasmine Karthikeyan Date: Mon, 4 Aug 2025 12:11:10 +0000 Subject: [PATCH 010/580] 8364580: Test compiler/vectorization/TestSubwordTruncation.java fails on platforms without RoundF/RoundD Reviewed-by: chagedorn, shade --- .../jtreg/compiler/vectorization/TestSubwordTruncation.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/hotspot/jtreg/compiler/vectorization/TestSubwordTruncation.java b/test/hotspot/jtreg/compiler/vectorization/TestSubwordTruncation.java index 43861c789ba..2256fd90e35 100644 --- a/test/hotspot/jtreg/compiler/vectorization/TestSubwordTruncation.java +++ b/test/hotspot/jtreg/compiler/vectorization/TestSubwordTruncation.java @@ -403,7 +403,7 @@ public class TestSubwordTruncation { } @Test - @IR(counts = { IRNode.ROUND_F, ">0" }) + @IR(applyIfPlatformOr = {"x64", "true", "aarch64", "true", "riscv64", "true"}, counts = { IRNode.ROUND_F, ">0" }) @Arguments(setup = "setupByteArray") public Object[] testRoundF(byte[] in) { short[] res = new short[SIZE]; @@ -416,7 +416,7 @@ public class TestSubwordTruncation { } @Test - @IR(counts = { IRNode.ROUND_D, ">0" }) + @IR(applyIfPlatformOr = {"x64", "true", "aarch64", "true", "riscv64", "true"}, counts = { IRNode.ROUND_D, ">0" }) @Arguments(setup = "setupByteArray") public Object[] testRoundD(byte[] in) { short[] res = new short[SIZE]; From a9f3d3a290060f98967feaad2fa03ef077a64534 Mon Sep 17 00:00:00 2001 From: Ao Qi Date: Mon, 4 Aug 2025 12:37:11 +0000 Subject: [PATCH 011/580] 8364177: JDK fails to build due to undefined symbol in libpng on LoongArch64 Reviewed-by: prr, aivanov, erikj --- make/modules/java.desktop/lib/ClientLibraries.gmk | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/make/modules/java.desktop/lib/ClientLibraries.gmk b/make/modules/java.desktop/lib/ClientLibraries.gmk index dcb41defba3..a69b65180d7 100644 --- a/make/modules/java.desktop/lib/ClientLibraries.gmk +++ b/make/modules/java.desktop/lib/ClientLibraries.gmk @@ -177,7 +177,8 @@ ifeq ($(ENABLE_HEADLESS_ONLY), false) endif LIBSPLASHSCREEN_CFLAGS += -DSPLASHSCREEN -DPNG_NO_MMX_CODE \ - -DPNG_ARM_NEON_OPT=0 -DPNG_ARM_NEON_IMPLEMENTATION=0 + -DPNG_ARM_NEON_OPT=0 -DPNG_ARM_NEON_IMPLEMENTATION=0 \ + -DPNG_LOONGARCH_LSX_OPT=0 ifeq ($(call isTargetOs, linux)+$(call isTargetCpuArch, ppc), true+true) LIBSPLASHSCREEN_CFLAGS += -DPNG_POWERPC_VSX_OPT=0 From fc4755535d61c2fd4d9a2c9a673da148f742f035 Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Mon, 4 Aug 2025 12:59:26 +0000 Subject: [PATCH 012/580] 8364516: Serial: Move class unloading logic inside SerialFullGC::invoke_at_safepoint Reviewed-by: tschatzl, sangheki --- src/hotspot/share/gc/serial/serialFullGC.cpp | 17 +++++++++++++++++ src/hotspot/share/gc/serial/serialHeap.cpp | 10 ---------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/hotspot/share/gc/serial/serialFullGC.cpp b/src/hotspot/share/gc/serial/serialFullGC.cpp index 6eacb08f218..fc63b81b7ce 100644 --- a/src/hotspot/share/gc/serial/serialFullGC.cpp +++ b/src/hotspot/share/gc/serial/serialFullGC.cpp @@ -705,6 +705,16 @@ void SerialFullGC::invoke_at_safepoint(bool clear_all_softrefs) { allocate_stacks(); + // Usually, all class unloading work occurs at the end of phase 1, but Serial + // full-gc accesses dead-objs' klass to find out the start of next live-obj + // during phase 2. This requires klasses of dead-objs to be kept loaded. + // Therefore, we declare ClassUnloadingContext at the same level as + // full-gc phases, and purge dead classes (invoking + // ClassLoaderDataGraph::purge) after all phases of full-gc. + ClassUnloadingContext ctx(1 /* num_nmethod_unlink_workers */, + false /* unregister_nmethods_during_purge */, + false /* lock_nmethod_free_separately */); + phase1_mark(clear_all_softrefs); Compacter compacter{gch}; @@ -755,6 +765,13 @@ void SerialFullGC::invoke_at_safepoint(bool clear_all_softrefs) { compacter.phase4_compact(); } + // Delete metaspaces for unloaded class loaders and clean up CLDG. + ClassLoaderDataGraph::purge(true /* at_safepoint */); + DEBUG_ONLY(MetaspaceUtils::verify();) + + // Need to clear claim bits for the next full-gc (specifically phase 1 and 3). + ClassLoaderDataGraph::clear_claimed_marks(); + restore_marks(); deallocate_stacks(); diff --git a/src/hotspot/share/gc/serial/serialHeap.cpp b/src/hotspot/share/gc/serial/serialHeap.cpp index d6d51eef453..48d6a1b74ea 100644 --- a/src/hotspot/share/gc/serial/serialHeap.cpp +++ b/src/hotspot/share/gc/serial/serialHeap.cpp @@ -603,9 +603,6 @@ void SerialHeap::do_full_collection(bool clear_all_soft_refs) { gc_prologue(); COMPILER2_OR_JVMCI_PRESENT(DerivedPointerTable::clear()); CodeCache::on_gc_marking_cycle_start(); - ClassUnloadingContext ctx(1 /* num_nmethod_unlink_workers */, - false /* unregister_nmethods_during_purge */, - false /* lock_nmethod_free_separately */); STWGCTimer* gc_timer = SerialFullGC::gc_timer(); gc_timer->register_gc_start(); @@ -630,13 +627,6 @@ void SerialHeap::do_full_collection(bool clear_all_soft_refs) { _old_gen->compute_new_size(); _young_gen->compute_new_size(); - // Delete metaspaces for unloaded class loaders and clean up loader_data graph - ClassLoaderDataGraph::purge(/*at_safepoint*/true); - DEBUG_ONLY(MetaspaceUtils::verify();) - - // Need to clear claim bits for the next mark. - ClassLoaderDataGraph::clear_claimed_marks(); - _old_gen->update_promote_stats(); // Resize the metaspace capacity after full collections From 567c0c9335c3ff020871115e5a58f3f40fd4b1ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Galder=20Zamarre=C3=B1o?= Date: Mon, 4 Aug 2025 13:51:14 +0000 Subject: [PATCH 013/580] 8354244: Use random data in MinMaxRed_Long data arrays Reviewed-by: chagedorn, mhaessig --- .../loopopts/superword/MinMaxRed_Long.java | 95 +++++-------------- 1 file changed, 22 insertions(+), 73 deletions(-) diff --git a/test/hotspot/jtreg/compiler/loopopts/superword/MinMaxRed_Long.java b/test/hotspot/jtreg/compiler/loopopts/superword/MinMaxRed_Long.java index 9c3e2c7bc5d..f1e68fb95fb 100644 --- a/test/hotspot/jtreg/compiler/loopopts/superword/MinMaxRed_Long.java +++ b/test/hotspot/jtreg/compiler/loopopts/superword/MinMaxRed_Long.java @@ -31,16 +31,21 @@ package compiler.loopopts.superword; -import compiler.lib.ir_framework.*; -import jdk.test.lib.Utils; +import compiler.lib.generators.Generator; +import compiler.lib.generators.Generators; +import compiler.lib.ir_framework.IR; +import compiler.lib.ir_framework.IRNode; +import compiler.lib.ir_framework.Run; +import compiler.lib.ir_framework.RunMode; +import compiler.lib.ir_framework.Test; +import compiler.lib.ir_framework.TestFramework; import java.util.Arrays; -import java.util.Random; -import java.util.stream.LongStream; public class MinMaxRed_Long { - private static final Random random = Utils.getRandomInstance(); + private static final int SIZE = 1024; + private static final Generator GEN_LONG = Generators.G.longs(); public static void main(String[] args) throws Exception { TestFramework framework = new TestFramework(); @@ -53,97 +58,41 @@ public class MinMaxRed_Long { @Run(test = {"maxReductionImplement"}, mode = RunMode.STANDALONE) public void runMaxTest() { - runMaxTest(50); - runMaxTest(80); - runMaxTest(100); - } + long[] longs = new long[SIZE]; + Generators.G.fill(GEN_LONG, longs); - private static void runMaxTest(int probability) { - long[] longs = reductionInit(probability); long res = 0; for (int j = 0; j < 2000; j++) { res = maxReductionImplement(longs, res); } - if (res == 11 * Arrays.stream(longs).max().getAsLong()) { + + final long expected = Arrays.stream(longs).map(l -> l * 11).max().getAsLong(); + if (res == expected) { System.out.println("Success"); } else { - throw new AssertionError("Failed"); + throw new AssertionError("Failed, got result " + res + " but expected " + expected); } } @Run(test = {"minReductionImplement"}, mode = RunMode.STANDALONE) public void runMinTest() { - runMinTest(50); - runMinTest(80); - runMinTest(100); - } + long[] longs = new long[SIZE]; + Generators.G.fill(GEN_LONG, longs); - private static void runMinTest(int probability) { - long[] longs = reductionInit(probability); - // Negating the values generated for controlling max branching - // allows same logic to be used for min tests. - longs = negate(longs); long res = 0; for (int j = 0; j < 2000; j++) { res = minReductionImplement(longs, res); } - if (res == 11 * Arrays.stream(longs).min().getAsLong()) { + + final long expected = Arrays.stream(longs).map(l -> l * 11).min().getAsLong(); + if (res == expected) { System.out.println("Success"); } else { - throw new AssertionError("Failed"); + throw new AssertionError("Failed, got result " + res + " but expected " + expected); } } - static long[] negate(long[] nums) { - return LongStream.of(nums).map(l -> -l).toArray(); - } - - public static long[] reductionInit(int probability) { - int aboveCount, abovePercent; - long[] longs = new long[1024]; - - // Generates an array of numbers such that as the array is iterated - // there is P probability of finding a new max value, - // and 100-P probability of not finding a new max value. - // The algorithm loops around if the distribution does not match the probability, - // but it approximates the probability as the array sizes increase. - // The worst case of this algorithm is when the desired array size is 100 - // and the aim is to get 50% of probability, which can only be satisfied - // with 50 elements being a new max. This situation can take 15 rounds. - // As sizes increase, say 10'000 elements, - // the number of elements that have to satisfy 50% increases, - // so the algorithm will stop as an example when 5027 elements are a new max values. - // Also, probability values in the edges will achieve their objective quicker, - // with 0% or 100% probability doing it in a single loop. - // To support the same algorithm for min calculations, - // negating the array elements achieves the same objective. - do { - long max = random.nextLong(10); - longs[0] = max; - - aboveCount = 0; - for (int i = 1; i < longs.length; i++) { - long value; - if (random.nextLong(101) <= probability) { - long increment = random.nextLong(10); - value = max + increment; - aboveCount++; - } else { - // Decrement by at least 1 - long diffToMax = random.nextLong(10) + 1; - value = max - diffToMax; - } - longs[i] = value; - max = Math.max(max, value); - } - - abovePercent = ((aboveCount + 1) * 100) / longs.length; - } while (abovePercent != probability); - - return longs; - } - @Test @IR(applyIfAnd = {"SuperWordReductions", "true", "MaxVectorSize", ">=32"}, applyIfCPUFeatureOr = {"avx512", "true", "asimd" , "true"}, From 6c52b73465b0d0daeafc54c3c6cec3062bf490c5 Mon Sep 17 00:00:00 2001 From: Artur Barashev Date: Mon, 4 Aug 2025 13:55:58 +0000 Subject: [PATCH 014/580] 8209992: Align SSLSocket and SSLEngine Javadocs Reviewed-by: wetmore --- .../classes/javax/net/ssl/SSLEngine.java | 15 ++++++++---- .../classes/javax/net/ssl/SSLSocket.java | 23 ++++++++++++------- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/java.base/share/classes/javax/net/ssl/SSLEngine.java b/src/java.base/share/classes/javax/net/ssl/SSLEngine.java index 228750e080c..9376b7d1876 100644 --- a/src/java.base/share/classes/javax/net/ssl/SSLEngine.java +++ b/src/java.base/share/classes/javax/net/ssl/SSLEngine.java @@ -1088,7 +1088,17 @@ public abstract class SSLEngine { /** - * Initiates handshaking (initial or renegotiation) on this SSLEngine. + * Begins handshaking on this {@code SSLEngine}. + *

+ * Common reasons include a need to initiate a new protected session, + * create new encryption keys, or to change cipher suites. To force + * complete reauthentication, the current session should be invalidated + * before starting this handshake. + *

+ * The behavior of this method is protocol (and possibly implementation) + * dependent. For example, in TLSv1.3 calling this method after the + * connection has been established will force a key update. For prior TLS + * versions it will force a renegotiation (re-handshake). *

* This method is not needed for the initial handshake, as the * {@code wrap()} and {@code unwrap()} methods will @@ -1102,9 +1112,6 @@ public abstract class SSLEngine { * SSLSocket#startHandshake()} method, this method does not block * until handshaking is completed. *

- * To force a complete SSL/TLS/DTLS session renegotiation, the current - * session should be invalidated prior to calling this method. - *

* Some protocols may not support multiple handshakes on an existing * engine and may throw an {@code SSLException}. * diff --git a/src/java.base/share/classes/javax/net/ssl/SSLSocket.java b/src/java.base/share/classes/javax/net/ssl/SSLSocket.java index a18c203320d..117f2de9bbc 100644 --- a/src/java.base/share/classes/javax/net/ssl/SSLSocket.java +++ b/src/java.base/share/classes/javax/net/ssl/SSLSocket.java @@ -502,19 +502,26 @@ public abstract class SSLSocket extends Socket /** - * Starts an SSL handshake on this connection. Common reasons include - * a need to use new encryption keys, to change cipher suites, or to - * initiate a new session. To force complete reauthentication, the - * current session could be invalidated before starting this handshake. - * - *

If data has already been sent on the connection, it continues + * Starts handshaking on this {@code SSLSocket}. + *

+ * Common reasons include a need to initiate a new protected session, + * create new encryption keys, or to change cipher suites. To force + * complete reauthentication, the current session should be invalidated + * before starting this handshake. + *

+ * The behavior of this method is protocol (and possibly implementation) + * dependent. For example, in TLSv1.3 calling this method after the + * connection has been established will force a key update. For prior TLS + * versions it will force a renegotiation (re-handshake). + *

+ * If data has already been sent on the connection, it continues * to flow during this handshake. When the handshake completes, this * will be signaled with an event. - * + *

* This method is synchronous for the initial handshake on a connection * and returns when the negotiated handshake is complete. Some * protocols may not support multiple handshakes on an existing socket - * and may throw an IOException. + * and may throw an {@code IOException}. * * @throws IOException on a network level error * @see #addHandshakeCompletedListener(HandshakeCompletedListener) From b5f450a599a0f63df0307cced09295fe1309bf6f Mon Sep 17 00:00:00 2001 From: Kevin Driver Date: Mon, 4 Aug 2025 15:59:57 +0000 Subject: [PATCH 015/580] 8364226: Better ECDSASignature Memory Management Reviewed-by: ascarpino, hchao --- .../classes/sun/security/ec/ECDSASignature.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/classes/sun/security/ec/ECDSASignature.java b/src/java.base/share/classes/sun/security/ec/ECDSASignature.java index c6c49bd6c21..029affbac3d 100644 --- a/src/java.base/share/classes/sun/security/ec/ECDSASignature.java +++ b/src/java.base/share/classes/sun/security/ec/ECDSASignature.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -30,6 +30,7 @@ import java.nio.ByteBuffer; import java.security.*; import java.security.interfaces.*; import java.security.spec.*; +import java.util.Arrays; import java.util.Optional; import sun.security.jca.JCAUtil; @@ -436,7 +437,14 @@ abstract class ECDSASignature extends SignatureSpi { random.nextBytes(seedBytes); ECDSAOperations.Seed seed = new ECDSAOperations.Seed(seedBytes); try { - return ops.signDigest(s, digest, seed); + // a new allocation for this value, rather than just + // returning it, is a trade-off to zero-out the local + // value for "s" when necessary + byte[] retValue = ops.signDigest(s, digest, seed); + if (!(priv instanceof ECPrivateKeyImpl)) { + Arrays.fill(s, (byte)0x00); + } + return retValue; } catch (IntermediateValueException ex) { // try again in the next iteration } From 05f8a6fca87d472a80e5952ddc90d8fa6589c75c Mon Sep 17 00:00:00 2001 From: Mohamed Issa Date: Mon, 4 Aug 2025 18:47:57 +0000 Subject: [PATCH 016/580] 8360559: Optimize Math.sinh for x86 64 bit platforms Reviewed-by: sviswanathan, sparasa --- .../arm/templateInterpreterGenerator_arm.cpp | 1 + .../ppc/templateInterpreterGenerator_ppc.cpp | 1 + .../templateInterpreterGenerator_s390.cpp | 1 + src/hotspot/cpu/x86/c1_LIRGenerator_x86.cpp | 10 +- src/hotspot/cpu/x86/stubGenerator_x86_64.cpp | 3 + src/hotspot/cpu/x86/stubGenerator_x86_64.hpp | 1 + .../cpu/x86/stubGenerator_x86_64_sinh.cpp | 525 ++++++++++++++++++ .../templateInterpreterGenerator_x86_64.cpp | 7 + src/hotspot/share/c1/c1_Compiler.cpp | 1 + src/hotspot/share/c1/c1_GraphBuilder.cpp | 1 + src/hotspot/share/c1/c1_LIRGenerator.cpp | 1 + src/hotspot/share/c1/c1_Runtime1.cpp | 1 + src/hotspot/share/classfile/vmIntrinsics.cpp | 3 + src/hotspot/share/classfile/vmIntrinsics.hpp | 4 +- .../share/interpreter/abstractInterpreter.cpp | 3 + .../share/interpreter/abstractInterpreter.hpp | 2 + .../templateInterpreterGenerator.cpp | 2 + .../zero/zeroInterpreterGenerator.cpp | 2 + src/hotspot/share/jvmci/jvmciCompilerToVM.hpp | 1 + .../share/jvmci/jvmciCompilerToVMInit.cpp | 2 + src/hotspot/share/jvmci/vmStructs_jvmci.cpp | 1 + src/hotspot/share/opto/c2compiler.cpp | 1 + src/hotspot/share/opto/library_call.cpp | 4 + .../share/runtime/stubDeclarations.hpp | 2 + .../share/classes/java/lang/Math.java | 1 + .../org/openjdk/bench/java/lang/SinhPerf.java | 164 ++++++ 26 files changed, 742 insertions(+), 3 deletions(-) create mode 100644 src/hotspot/cpu/x86/stubGenerator_x86_64_sinh.cpp create mode 100644 test/micro/org/openjdk/bench/java/lang/SinhPerf.java diff --git a/src/hotspot/cpu/arm/templateInterpreterGenerator_arm.cpp b/src/hotspot/cpu/arm/templateInterpreterGenerator_arm.cpp index db4a5c8625c..8abefe39b2d 100644 --- a/src/hotspot/cpu/arm/templateInterpreterGenerator_arm.cpp +++ b/src/hotspot/cpu/arm/templateInterpreterGenerator_arm.cpp @@ -174,6 +174,7 @@ address TemplateInterpreterGenerator::generate_math_entry(AbstractInterpreter::M break; case Interpreter::java_lang_math_fmaD: case Interpreter::java_lang_math_fmaF: + case Interpreter::java_lang_math_sinh: case Interpreter::java_lang_math_tanh: case Interpreter::java_lang_math_cbrt: // TODO: Implement intrinsic diff --git a/src/hotspot/cpu/ppc/templateInterpreterGenerator_ppc.cpp b/src/hotspot/cpu/ppc/templateInterpreterGenerator_ppc.cpp index ab4f35f4d8c..b5f1c76c5da 100644 --- a/src/hotspot/cpu/ppc/templateInterpreterGenerator_ppc.cpp +++ b/src/hotspot/cpu/ppc/templateInterpreterGenerator_ppc.cpp @@ -1089,6 +1089,7 @@ address TemplateInterpreterGenerator::generate_math_entry(AbstractInterpreter::M case Interpreter::java_lang_math_sin : runtime_entry = CAST_FROM_FN_PTR(address, SharedRuntime::dsin); break; case Interpreter::java_lang_math_cos : runtime_entry = CAST_FROM_FN_PTR(address, SharedRuntime::dcos); break; case Interpreter::java_lang_math_tan : runtime_entry = CAST_FROM_FN_PTR(address, SharedRuntime::dtan); break; + case Interpreter::java_lang_math_sinh : /* run interpreted */ break; case Interpreter::java_lang_math_tanh : /* run interpreted */ break; case Interpreter::java_lang_math_cbrt : /* run interpreted */ break; case Interpreter::java_lang_math_abs : /* run interpreted */ break; diff --git a/src/hotspot/cpu/s390/templateInterpreterGenerator_s390.cpp b/src/hotspot/cpu/s390/templateInterpreterGenerator_s390.cpp index e03d891496a..3f568572966 100644 --- a/src/hotspot/cpu/s390/templateInterpreterGenerator_s390.cpp +++ b/src/hotspot/cpu/s390/templateInterpreterGenerator_s390.cpp @@ -1239,6 +1239,7 @@ address TemplateInterpreterGenerator::generate_math_entry(AbstractInterpreter::M case Interpreter::java_lang_math_sin : runtime_entry = CAST_FROM_FN_PTR(address, SharedRuntime::dsin); break; case Interpreter::java_lang_math_cos : runtime_entry = CAST_FROM_FN_PTR(address, SharedRuntime::dcos); break; case Interpreter::java_lang_math_tan : runtime_entry = CAST_FROM_FN_PTR(address, SharedRuntime::dtan); break; + case Interpreter::java_lang_math_sinh : /* run interpreted */ break; case Interpreter::java_lang_math_tanh : /* run interpreted */ break; case Interpreter::java_lang_math_cbrt : /* run interpreted */ break; case Interpreter::java_lang_math_abs : /* run interpreted */ break; diff --git a/src/hotspot/cpu/x86/c1_LIRGenerator_x86.cpp b/src/hotspot/cpu/x86/c1_LIRGenerator_x86.cpp index 04c90c43b6e..3bd86d9b7e5 100644 --- a/src/hotspot/cpu/x86/c1_LIRGenerator_x86.cpp +++ b/src/hotspot/cpu/x86/c1_LIRGenerator_x86.cpp @@ -720,8 +720,8 @@ void LIRGenerator::do_MathIntrinsic(Intrinsic* x) { if (x->id() == vmIntrinsics::_dexp || x->id() == vmIntrinsics::_dlog || x->id() == vmIntrinsics::_dpow || x->id() == vmIntrinsics::_dcos || x->id() == vmIntrinsics::_dsin || x->id() == vmIntrinsics::_dtan || - x->id() == vmIntrinsics::_dlog10 || x->id() == vmIntrinsics::_dtanh || - x->id() == vmIntrinsics::_dcbrt + x->id() == vmIntrinsics::_dlog10 || x->id() == vmIntrinsics::_dsinh || + x->id() == vmIntrinsics::_dtanh || x->id() == vmIntrinsics::_dcbrt ) { do_LibmIntrinsic(x); return; @@ -835,6 +835,12 @@ void LIRGenerator::do_LibmIntrinsic(Intrinsic* x) { __ call_runtime_leaf(CAST_FROM_FN_PTR(address, SharedRuntime::dtan), getThreadTemp(), result_reg, cc->args()); } break; + case vmIntrinsics::_dsinh: + assert(StubRoutines::dsinh() != nullptr, "sinh intrinsic not found"); + if (StubRoutines::dsinh() != nullptr) { + __ call_runtime_leaf(StubRoutines::dsinh(), getThreadTemp(), result_reg, cc->args()); + } + break; case vmIntrinsics::_dtanh: assert(StubRoutines::dtanh() != nullptr, "tanh intrinsic not found"); if (StubRoutines::dtanh() != nullptr) { diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64.cpp index 8a9bf3aa0b8..058ea06ab4e 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64.cpp @@ -3689,6 +3689,9 @@ void StubGenerator::generate_libm_stubs() { if (vmIntrinsics::is_intrinsic_available(vmIntrinsics::_dtan)) { StubRoutines::_dtan = generate_libmTan(); // from stubGenerator_x86_64_tan.cpp } + if (vmIntrinsics::is_intrinsic_available(vmIntrinsics::_dsinh)) { + StubRoutines::_dsinh = generate_libmSinh(); // from stubGenerator_x86_64_sinh.cpp + } if (vmIntrinsics::is_intrinsic_available(vmIntrinsics::_dtanh)) { StubRoutines::_dtanh = generate_libmTanh(); // from stubGenerator_x86_64_tanh.cpp } diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64.hpp b/src/hotspot/cpu/x86/stubGenerator_x86_64.hpp index de2b0ad506d..44d13bbbf31 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64.hpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64.hpp @@ -555,6 +555,7 @@ class StubGenerator: public StubCodeGenerator { address generate_libmSin(); address generate_libmCos(); address generate_libmTan(); + address generate_libmSinh(); address generate_libmTanh(); address generate_libmCbrt(); address generate_libmExp(); diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64_sinh.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_sinh.cpp new file mode 100644 index 00000000000..6e190542379 --- /dev/null +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_sinh.cpp @@ -0,0 +1,525 @@ +/* + * Copyright (c) 2025, Intel Corporation. All rights reserved. + * Intel Math Library (LIBM) Source Code + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "macroAssembler_x86.hpp" +#include "stubGenerator_x86_64.hpp" + +/******************************************************************************/ +// ALGORITHM DESCRIPTION +// --------------------- +// +// sinh(x)=(exp(x)-exp(-x))/2 +// +// Let |x|=xH+xL (upper 26 bits, lower 27 bits) +// log2(e) rounded to 26 bits (high part) plus a double precision low part is +// L2EH+L2EL (upper 26, lower 53 bits) +// +// Let xH*L2EH=k+f+r`, where (k+f)*2^7=int(xH*L2EH*2^7), +// f=0.b1 b2 ... b7, k integer +// 2^f is approximated as Tp[f]+Dp[f], and 2^{-f} as Tn[f]+Dn[f] +// Tp stores the high 53 bits, Dp stores (2^f-Tp[f]) rounded to double precision +// +// e^|x|=2^{k+f}*2^r, r=r`+xL*L2EH+|x|*L2EL, |r|<2^{-8}+2^{-14}, +// for |x| in [23/64,3*2^7) +// e^{-|x|}=2^{-k-f}*2^{-r} +// +// e^|x| is approximated as 2^k*Tp+2^k*Tp*c1*r(1+c2*r+..+c5*r^4)+2^k*Dp= +// =2^k*Tp+2^k*Tp*P15+2^k*Dp +// e^{-|x|} approximated as 2^{-k}*Tn-2^{-k}*Tn*c1*r(1-c2*r+..+c5*r^4)+2^{-k}*Dn +// +// For |x| in [1/8, 3*2^7), sinh(x) is formed as +// RN(2^k*Tp-2^{-k}*Tn)+2^k*Tp*P15-2^{-k}*Tn*P`15-2^{-k}*TnL-2^{-k}*Dn+2^k*Dp +// +// For x in (3*2^7, 3*2^8), sign(x)*(e^|x|)/2 is returned, and +// the result is checked for overflow. +// +// For |x|<23/64, a Taylor polynomial expansion is used (degree 13) +// To reduce rounding errors, the p3*x^3 term is computed as +// (p3*xh^3)_high+[(p3*xl*(3*x*xh+xl^2))+(p3*xh^3)_low], +// where x=xh+xl, (xh are the leading 17 bits of x), and +// (p3*xh^3)_high=RN(x+p3*xh^3)-x +// +// Error bound: +// 0.51 ulp +// +// Special cases: +// sinh(NaN) = quiet NaN, and raise invalid exception +// sinh(+/-INF) = +/-INF +// sinh(+/-0) = +/-0 +/******************************************************************************/ + +ATTRIBUTE_ALIGNED(8) static const juint _HALFMASK[] = +{ + 0xF8000000UL, 0x7FFFFFFFUL +}; + +ATTRIBUTE_ALIGNED(16) static const juint _MASK3[] = +{ + 0x00000000UL, 0xFFFFFFF0UL, 0x00000000UL, 0xFFFFFFF0UL +}; + +ATTRIBUTE_ALIGNED(16) static const juint _L2E[] = +{ + 0x60000000UL, 0x40671547UL, 0xF85DDF44UL, 0x3EC4AE0BUL +}; + +ATTRIBUTE_ALIGNED(16) static const juint _Shifter[] = +{ + 0x00000000UL, 0x43380000UL, 0x00000000UL, 0xC3380000UL +}; + +ATTRIBUTE_ALIGNED(16) static const juint _cv[] = +{ + 0xD704A0C0UL, 0x3E3C6B08UL, 0xD704A0C0UL, 0xBE3C6B08UL, 0xFEFA39EFUL, + 0x3F662E42UL, 0xFEFA39EFUL, 0xBF662E42UL, 0x7F907D8BUL, 0x3D9F8445UL, + 0x7F907D8BUL, 0x3D9F8445UL, 0xFFAC83B4UL, 0x3ED47FD3UL, 0xFFAC83B4UL, + 0x3ED47FD3UL, 0xFEFA39EFUL, 0x3F762E42UL, 0xFEFA39EFUL, 0x3F762E42UL +}; + +ATTRIBUTE_ALIGNED(16) static const juint _pv[] = +{ + 0x13A86D08UL, 0x3DE61246UL, 0xA556C732UL, 0x3EC71DE3UL, 0x11111111UL, + 0x3F811111UL, 0x55555555UL, 0x3FC55555UL, 0x67F544E1UL, 0x3E5AE645UL, + 0x1A01A019UL, 0x3F2A01A0UL +}; + +ATTRIBUTE_ALIGNED(16) static const juint _T2f[] = +{ + 0x00000000UL, 0x3FF00000UL, 0x00000000UL, 0x00000000UL, 0xA9FB3335UL, 0x3FF0163DUL, + 0x9AB8CDB7UL, 0x3C9B6129UL, 0x3E778061UL, 0x3FF02C9AUL, 0x535B085DUL, 0xBC719083UL, + 0xE86E7F85UL, 0x3FF04315UL, 0x1977C96EUL, 0xBC90A31CUL, 0xD3158574UL, 0x3FF059B0UL, + 0xA475B465UL, 0x3C8D73E2UL, 0x29DDF6DEUL, 0x3FF0706BUL, 0xE2B13C27UL, 0xBC8C91DFUL, + 0x18759BC8UL, 0x3FF08745UL, 0x4BB284FFUL, 0x3C6186BEUL, 0xCAC6F383UL, 0x3FF09E3EUL, + 0x18316136UL, 0x3C914878UL, 0x6CF9890FUL, 0x3FF0B558UL, 0x4ADC610BUL, 0x3C98A62EUL, + 0x2B7247F7UL, 0x3FF0CC92UL, 0x16E24F71UL, 0x3C901EDCUL, 0x32D3D1A2UL, 0x3FF0E3ECUL, + 0x27C57B52UL, 0x3C403A17UL, 0xAFFED31BUL, 0x3FF0FB66UL, 0xC44EBD7BUL, 0xBC6B9BEDUL, + 0xD0125B51UL, 0x3FF11301UL, 0x39449B3AUL, 0xBC96C510UL, 0xC06C31CCUL, 0x3FF12ABDUL, + 0xB36CA5C7UL, 0xBC51B514UL, 0xAEA92DE0UL, 0x3FF1429AUL, 0x9AF1369EUL, 0xBC932FBFUL, + 0xC8A58E51UL, 0x3FF15A98UL, 0xB9EEAB0AUL, 0x3C82406AUL, 0x3C7D517BUL, 0x3FF172B8UL, + 0xB9D78A76UL, 0xBC819041UL, 0x388C8DEAUL, 0x3FF18AF9UL, 0xD1970F6CUL, 0xBC911023UL, + 0xEB6FCB75UL, 0x3FF1A35BUL, 0x7B4968E4UL, 0x3C8E5B4CUL, 0x84045CD4UL, 0x3FF1BBE0UL, + 0x352EF607UL, 0xBC995386UL, 0x3168B9AAUL, 0x3FF1D487UL, 0x00A2643CUL, 0x3C9E016EUL, + 0x22FCD91DUL, 0x3FF1ED50UL, 0x027BB78CUL, 0xBC91DF98UL, 0x88628CD6UL, 0x3FF2063BUL, + 0x814A8495UL, 0x3C8DC775UL, 0x917DDC96UL, 0x3FF21F49UL, 0x9494A5EEUL, 0x3C82A97EUL, + 0x6E756238UL, 0x3FF2387AUL, 0xB6C70573UL, 0x3C99B07EUL, 0x4FB2A63FUL, 0x3FF251CEUL, + 0xBEF4F4A4UL, 0x3C8AC155UL, 0x65E27CDDUL, 0x3FF26B45UL, 0x9940E9D9UL, 0x3C82BD33UL, + 0xE1F56381UL, 0x3FF284DFUL, 0x8C3F0D7EUL, 0xBC9A4C3AUL, 0xF51FDEE1UL, 0x3FF29E9DUL, + 0xAFAD1255UL, 0x3C8612E8UL, 0xD0DAD990UL, 0x3FF2B87FUL, 0xD6381AA4UL, 0xBC410ADCUL, + 0xA6E4030BUL, 0x3FF2D285UL, 0x54DB41D5UL, 0x3C900247UL, 0xA93E2F56UL, 0x3FF2ECAFUL, + 0x45D52383UL, 0x3C71CA0FUL, 0x0A31B715UL, 0x3FF306FEUL, 0xD23182E4UL, 0x3C86F46AUL, + 0xFC4CD831UL, 0x3FF32170UL, 0x8E18047CUL, 0x3C8A9CE7UL, 0xB26416FFUL, 0x3FF33C08UL, + 0x843659A6UL, 0x3C932721UL, 0x5F929FF1UL, 0x3FF356C5UL, 0x5C4E4628UL, 0xBC8B5CEEUL, + 0x373AA9CBUL, 0x3FF371A7UL, 0xBF42EAE2UL, 0xBC963AEAUL, 0x6D05D866UL, 0x3FF38CAEUL, + 0x3C9904BDUL, 0xBC9E958DUL, 0x34E59FF7UL, 0x3FF3A7DBUL, 0xD661F5E3UL, 0xBC75E436UL, + 0xC313A8E5UL, 0x3FF3C32DUL, 0x375D29C3UL, 0xBC9EFFF8UL, 0x4C123422UL, 0x3FF3DEA6UL, + 0x11F09EBCUL, 0x3C8ADA09UL, 0x04AC801CUL, 0x3FF3FA45UL, 0xF956F9F3UL, 0xBC97D023UL, + 0x21F72E2AUL, 0x3FF4160AUL, 0x1C309278UL, 0xBC5EF369UL, 0xD950A897UL, 0x3FF431F5UL, + 0xE35F7999UL, 0xBC81C7DDUL, 0x6061892DUL, 0x3FF44E08UL, 0x04EF80D0UL, 0x3C489B7AUL, + 0xED1D0057UL, 0x3FF46A41UL, 0xD1648A76UL, 0x3C9C944BUL, 0xB5C13CD0UL, 0x3FF486A2UL, + 0xB69062F0UL, 0x3C73C1A3UL, 0xF0D7D3DEUL, 0x3FF4A32AUL, 0xF3D1BE56UL, 0x3C99CB62UL, + 0xD5362A27UL, 0x3FF4BFDAUL, 0xAFEC42E2UL, 0x3C7D4397UL, 0x99FDDD0DUL, 0x3FF4DCB2UL, + 0xBC6A7833UL, 0x3C98ECDBUL, 0x769D2CA7UL, 0x3FF4F9B2UL, 0xD25957E3UL, 0xBC94B309UL, + 0xA2CF6642UL, 0x3FF516DAUL, 0x69BD93EFUL, 0xBC8F7685UL, 0x569D4F82UL, 0x3FF5342BUL, + 0x1DB13CADUL, 0xBC807ABEUL, 0xCA5D920FUL, 0x3FF551A4UL, 0xEFEDE59BUL, 0xBC8D689CUL, + 0x36B527DAUL, 0x3FF56F47UL, 0x011D93ADUL, 0x3C99BB2CUL, 0xD497C7FDUL, 0x3FF58D12UL, + 0x5B9A1DE8UL, 0x3C8295E1UL, 0xDD485429UL, 0x3FF5AB07UL, 0x054647ADUL, 0x3C96324CUL, + 0x8A5946B7UL, 0x3FF5C926UL, 0x816986A2UL, 0x3C3C4B1BUL, 0x15AD2148UL, 0x3FF5E76FUL, + 0x3080E65EUL, 0x3C9BA6F9UL, 0xB976DC09UL, 0x3FF605E1UL, 0x9B56DE47UL, 0xBC93E242UL, + 0xB03A5585UL, 0x3FF6247EUL, 0x7E40B497UL, 0xBC9383C1UL, 0x34CCC320UL, 0x3FF64346UL, + 0x759D8933UL, 0xBC8C483CUL, 0x82552225UL, 0x3FF66238UL, 0x87591C34UL, 0xBC9BB609UL, + 0xD44CA973UL, 0x3FF68155UL, 0x44F73E65UL, 0x3C6038AEUL, 0x667F3BCDUL, 0x3FF6A09EUL, + 0x13B26456UL, 0xBC9BDD34UL, 0x750BDABFUL, 0x3FF6C012UL, 0x67FF0B0DUL, 0xBC728956UL, + 0x3C651A2FUL, 0x3FF6DFB2UL, 0x683C88ABUL, 0xBC6BBE3AUL, 0xF9519484UL, 0x3FF6FF7DUL, + 0x25860EF6UL, 0xBC883C0FUL, 0xE8EC5F74UL, 0x3FF71F75UL, 0x86887A99UL, 0xBC816E47UL, + 0x48A58174UL, 0x3FF73F9AUL, 0x6C65D53CUL, 0xBC90A8D9UL, 0x564267C9UL, 0x3FF75FEBUL, + 0x57316DD3UL, 0xBC902459UL, 0x4FDE5D3FUL, 0x3FF78069UL, 0x0A02162DUL, 0x3C9866B8UL, + 0x73EB0187UL, 0x3FF7A114UL, 0xEE04992FUL, 0xBC841577UL, 0x0130C132UL, 0x3FF7C1EDUL, + 0xD1164DD6UL, 0x3C9F124CUL, 0x36CF4E62UL, 0x3FF7E2F3UL, 0xBA15797EUL, 0x3C705D02UL, + 0x543E1A12UL, 0x3FF80427UL, 0x626D972BUL, 0xBC927C86UL, 0x994CCE13UL, 0x3FF82589UL, + 0xD41532D8UL, 0xBC9D4C1DUL, 0x4623C7ADUL, 0x3FF8471AUL, 0xA341CDFBUL, 0xBC88D684UL, + 0x9B4492EDUL, 0x3FF868D9UL, 0x9BD4F6BAUL, 0xBC9FC6F8UL, 0xD98A6699UL, 0x3FF88AC7UL, + 0xF37CB53AUL, 0x3C9994C2UL, 0x422AA0DBUL, 0x3FF8ACE5UL, 0x56864B27UL, 0x3C96E9F1UL, + 0x16B5448CUL, 0x3FF8CF32UL, 0x32E9E3AAUL, 0xBC70D55EUL, 0x99157736UL, 0x3FF8F1AEUL, + 0xA2E3976CUL, 0x3C85CC13UL, 0x0B91FFC6UL, 0x3FF9145BUL, 0x2E582524UL, 0xBC9DD679UL, + 0xB0CDC5E5UL, 0x3FF93737UL, 0x81B57EBCUL, 0xBC675FC7UL, 0xCBC8520FUL, 0x3FF95A44UL, + 0x96A5F039UL, 0xBC764B7CUL, 0x9FDE4E50UL, 0x3FF97D82UL, 0x7C1B85D1UL, 0xBC9D185BUL, + 0x70CA07BAUL, 0x3FF9A0F1UL, 0x91CEE632UL, 0xBC9173BDUL, 0x82A3F090UL, 0x3FF9C491UL, + 0xB071F2BEUL, 0x3C7C7C46UL, 0x19E32323UL, 0x3FF9E863UL, 0x78E64C6EUL, 0x3C7824CAUL, + 0x7B5DE565UL, 0x3FFA0C66UL, 0x5D1CD533UL, 0xBC935949UL, 0xEC4A2D33UL, 0x3FFA309BUL, + 0x7DDC36ABUL, 0x3C96305CUL, 0xB23E255DUL, 0x3FFA5503UL, 0xDB8D41E1UL, 0xBC9D2F6EUL, + 0x1330B358UL, 0x3FFA799EUL, 0xCAC563C7UL, 0x3C9BCB7EUL, 0x5579FDBFUL, 0x3FFA9E6BUL, + 0x0EF7FD31UL, 0x3C90FAC9UL, 0xBFD3F37AUL, 0x3FFAC36BUL, 0xCAE76CD0UL, 0xBC8F9234UL, + 0x995AD3ADUL, 0x3FFAE89FUL, 0x345DCC81UL, 0x3C97A1CDUL, 0x298DB666UL, 0x3FFB0E07UL, + 0x4C80E425UL, 0xBC9BDEF5UL, 0xB84F15FBUL, 0x3FFB33A2UL, 0x3084D708UL, 0xBC62805EUL, + 0x8DE5593AUL, 0x3FFB5972UL, 0xBBBA6DE3UL, 0xBC9C71DFUL, 0xF2FB5E47UL, 0x3FFB7F76UL, + 0x7E54AC3BUL, 0xBC75584FUL, 0x30A1064AUL, 0x3FFBA5B0UL, 0x0E54292EUL, 0xBC9EFCD3UL, + 0x904BC1D2UL, 0x3FFBCC1EUL, 0x7A2D9E84UL, 0x3C823DD0UL, 0x5BD71E09UL, 0x3FFBF2C2UL, + 0x3F6B9C73UL, 0xBC9EFDCAUL, 0xDD85529CUL, 0x3FFC199BUL, 0x895048DDUL, 0x3C811065UL, + 0x5FFFD07AUL, 0x3FFC40ABUL, 0xE083C60AUL, 0x3C9B4537UL, 0x2E57D14BUL, 0x3FFC67F1UL, + 0xFF483CADUL, 0x3C92884DUL, 0x9406E7B5UL, 0x3FFC8F6DUL, 0x48805C44UL, 0x3C71ACBCUL, + 0xDCEF9069UL, 0x3FFCB720UL, 0xD1E949DCUL, 0x3C7503CBUL, 0x555DC3FAUL, 0x3FFCDF0BUL, + 0x53829D72UL, 0xBC8DD83BUL, 0x4A07897CUL, 0x3FFD072DUL, 0x43797A9CUL, 0xBC9CBC37UL, + 0x080D89F2UL, 0x3FFD2F87UL, 0x719D8578UL, 0xBC9D487BUL, 0xDCFBA487UL, 0x3FFD5818UL, + 0xD75B3707UL, 0x3C82ED02UL, 0x16C98398UL, 0x3FFD80E3UL, 0x8BEDDFE8UL, 0xBC911EC1UL, + 0x03DB3285UL, 0x3FFDA9E6UL, 0x696DB532UL, 0x3C9C2300UL, 0xF301B460UL, 0x3FFDD321UL, + 0x78F018C3UL, 0x3C92DA57UL, 0x337B9B5FUL, 0x3FFDFC97UL, 0x4F184B5CUL, 0xBC91A5CDUL, + 0x14F5A129UL, 0x3FFE2646UL, 0x817A1496UL, 0xBC97B627UL, 0xE78B3FF6UL, 0x3FFE502EUL, + 0x80A9CC8FUL, 0x3C839E89UL, 0xFBC74C83UL, 0x3FFE7A51UL, 0xCA0C8DE2UL, 0x3C92D522UL, + 0xA2A490DAUL, 0x3FFEA4AFUL, 0x179C2893UL, 0xBC9E9C23UL, 0x2D8E67F1UL, 0x3FFECF48UL, + 0xB411AD8CUL, 0xBC9C93F3UL, 0xEE615A27UL, 0x3FFEFA1BUL, 0x86A4B6B0UL, 0x3C9DC7F4UL, + 0x376BBA97UL, 0x3FFF252BUL, 0xBF0D8E43UL, 0x3C93A1A5UL, 0x5B6E4540UL, 0x3FFF5076UL, + 0x2DD8A18BUL, 0x3C99D3E1UL, 0xAD9CBE14UL, 0x3FFF7BFDUL, 0xD006350AUL, 0xBC9DBB12UL, + 0x819E90D8UL, 0x3FFFA7C1UL, 0xF3A5931EUL, 0x3C874853UL, 0x2B8F71F1UL, 0x3FFFD3C2UL, + 0x966579E7UL, 0x3C62EB74UL +}; + +ATTRIBUTE_ALIGNED(16) static const juint _T2_neg_f[] = +{ + 0x00000000UL, 0x3FF00000UL, 0x00000000UL, 0x00000000UL, 0x2B8F71F1UL, 0x3FEFD3C2UL, + 0x966579E7UL, 0x3C52EB74UL, 0x819E90D8UL, 0x3FEFA7C1UL, 0xF3A5931EUL, 0x3C774853UL, + 0xAD9CBE14UL, 0x3FEF7BFDUL, 0xD006350AUL, 0xBC8DBB12UL, 0x5B6E4540UL, 0x3FEF5076UL, + 0x2DD8A18BUL, 0x3C89D3E1UL, 0x376BBA97UL, 0x3FEF252BUL, 0xBF0D8E43UL, 0x3C83A1A5UL, + 0xEE615A27UL, 0x3FEEFA1BUL, 0x86A4B6B0UL, 0x3C8DC7F4UL, 0x2D8E67F1UL, 0x3FEECF48UL, + 0xB411AD8CUL, 0xBC8C93F3UL, 0xA2A490DAUL, 0x3FEEA4AFUL, 0x179C2893UL, 0xBC8E9C23UL, + 0xFBC74C83UL, 0x3FEE7A51UL, 0xCA0C8DE2UL, 0x3C82D522UL, 0xE78B3FF6UL, 0x3FEE502EUL, + 0x80A9CC8FUL, 0x3C739E89UL, 0x14F5A129UL, 0x3FEE2646UL, 0x817A1496UL, 0xBC87B627UL, + 0x337B9B5FUL, 0x3FEDFC97UL, 0x4F184B5CUL, 0xBC81A5CDUL, 0xF301B460UL, 0x3FEDD321UL, + 0x78F018C3UL, 0x3C82DA57UL, 0x03DB3285UL, 0x3FEDA9E6UL, 0x696DB532UL, 0x3C8C2300UL, + 0x16C98398UL, 0x3FED80E3UL, 0x8BEDDFE8UL, 0xBC811EC1UL, 0xDCFBA487UL, 0x3FED5818UL, + 0xD75B3707UL, 0x3C72ED02UL, 0x080D89F2UL, 0x3FED2F87UL, 0x719D8578UL, 0xBC8D487BUL, + 0x4A07897CUL, 0x3FED072DUL, 0x43797A9CUL, 0xBC8CBC37UL, 0x555DC3FAUL, 0x3FECDF0BUL, + 0x53829D72UL, 0xBC7DD83BUL, 0xDCEF9069UL, 0x3FECB720UL, 0xD1E949DCUL, 0x3C6503CBUL, + 0x9406E7B5UL, 0x3FEC8F6DUL, 0x48805C44UL, 0x3C61ACBCUL, 0x2E57D14BUL, 0x3FEC67F1UL, + 0xFF483CADUL, 0x3C82884DUL, 0x5FFFD07AUL, 0x3FEC40ABUL, 0xE083C60AUL, 0x3C8B4537UL, + 0xDD85529CUL, 0x3FEC199BUL, 0x895048DDUL, 0x3C711065UL, 0x5BD71E09UL, 0x3FEBF2C2UL, + 0x3F6B9C73UL, 0xBC8EFDCAUL, 0x904BC1D2UL, 0x3FEBCC1EUL, 0x7A2D9E84UL, 0x3C723DD0UL, + 0x30A1064AUL, 0x3FEBA5B0UL, 0x0E54292EUL, 0xBC8EFCD3UL, 0xF2FB5E47UL, 0x3FEB7F76UL, + 0x7E54AC3BUL, 0xBC65584FUL, 0x8DE5593AUL, 0x3FEB5972UL, 0xBBBA6DE3UL, 0xBC8C71DFUL, + 0xB84F15FBUL, 0x3FEB33A2UL, 0x3084D708UL, 0xBC52805EUL, 0x298DB666UL, 0x3FEB0E07UL, + 0x4C80E425UL, 0xBC8BDEF5UL, 0x995AD3ADUL, 0x3FEAE89FUL, 0x345DCC81UL, 0x3C87A1CDUL, + 0xBFD3F37AUL, 0x3FEAC36BUL, 0xCAE76CD0UL, 0xBC7F9234UL, 0x5579FDBFUL, 0x3FEA9E6BUL, + 0x0EF7FD31UL, 0x3C80FAC9UL, 0x1330B358UL, 0x3FEA799EUL, 0xCAC563C7UL, 0x3C8BCB7EUL, + 0xB23E255DUL, 0x3FEA5503UL, 0xDB8D41E1UL, 0xBC8D2F6EUL, 0xEC4A2D33UL, 0x3FEA309BUL, + 0x7DDC36ABUL, 0x3C86305CUL, 0x7B5DE565UL, 0x3FEA0C66UL, 0x5D1CD533UL, 0xBC835949UL, + 0x19E32323UL, 0x3FE9E863UL, 0x78E64C6EUL, 0x3C6824CAUL, 0x82A3F090UL, 0x3FE9C491UL, + 0xB071F2BEUL, 0x3C6C7C46UL, 0x70CA07BAUL, 0x3FE9A0F1UL, 0x91CEE632UL, 0xBC8173BDUL, + 0x9FDE4E50UL, 0x3FE97D82UL, 0x7C1B85D1UL, 0xBC8D185BUL, 0xCBC8520FUL, 0x3FE95A44UL, + 0x96A5F039UL, 0xBC664B7CUL, 0xB0CDC5E5UL, 0x3FE93737UL, 0x81B57EBCUL, 0xBC575FC7UL, + 0x0B91FFC6UL, 0x3FE9145BUL, 0x2E582524UL, 0xBC8DD679UL, 0x99157736UL, 0x3FE8F1AEUL, + 0xA2E3976CUL, 0x3C75CC13UL, 0x16B5448CUL, 0x3FE8CF32UL, 0x32E9E3AAUL, 0xBC60D55EUL, + 0x422AA0DBUL, 0x3FE8ACE5UL, 0x56864B27UL, 0x3C86E9F1UL, 0xD98A6699UL, 0x3FE88AC7UL, + 0xF37CB53AUL, 0x3C8994C2UL, 0x9B4492EDUL, 0x3FE868D9UL, 0x9BD4F6BAUL, 0xBC8FC6F8UL, + 0x4623C7ADUL, 0x3FE8471AUL, 0xA341CDFBUL, 0xBC78D684UL, 0x994CCE13UL, 0x3FE82589UL, + 0xD41532D8UL, 0xBC8D4C1DUL, 0x543E1A12UL, 0x3FE80427UL, 0x626D972BUL, 0xBC827C86UL, + 0x36CF4E62UL, 0x3FE7E2F3UL, 0xBA15797EUL, 0x3C605D02UL, 0x0130C132UL, 0x3FE7C1EDUL, + 0xD1164DD6UL, 0x3C8F124CUL, 0x73EB0187UL, 0x3FE7A114UL, 0xEE04992FUL, 0xBC741577UL, + 0x4FDE5D3FUL, 0x3FE78069UL, 0x0A02162DUL, 0x3C8866B8UL, 0x564267C9UL, 0x3FE75FEBUL, + 0x57316DD3UL, 0xBC802459UL, 0x48A58174UL, 0x3FE73F9AUL, 0x6C65D53CUL, 0xBC80A8D9UL, + 0xE8EC5F74UL, 0x3FE71F75UL, 0x86887A99UL, 0xBC716E47UL, 0xF9519484UL, 0x3FE6FF7DUL, + 0x25860EF6UL, 0xBC783C0FUL, 0x3C651A2FUL, 0x3FE6DFB2UL, 0x683C88ABUL, 0xBC5BBE3AUL, + 0x750BDABFUL, 0x3FE6C012UL, 0x67FF0B0DUL, 0xBC628956UL, 0x667F3BCDUL, 0x3FE6A09EUL, + 0x13B26456UL, 0xBC8BDD34UL, 0xD44CA973UL, 0x3FE68155UL, 0x44F73E65UL, 0x3C5038AEUL, + 0x82552225UL, 0x3FE66238UL, 0x87591C34UL, 0xBC8BB609UL, 0x34CCC320UL, 0x3FE64346UL, + 0x759D8933UL, 0xBC7C483CUL, 0xB03A5585UL, 0x3FE6247EUL, 0x7E40B497UL, 0xBC8383C1UL, + 0xB976DC09UL, 0x3FE605E1UL, 0x9B56DE47UL, 0xBC83E242UL, 0x15AD2148UL, 0x3FE5E76FUL, + 0x3080E65EUL, 0x3C8BA6F9UL, 0x8A5946B7UL, 0x3FE5C926UL, 0x816986A2UL, 0x3C2C4B1BUL, + 0xDD485429UL, 0x3FE5AB07UL, 0x054647ADUL, 0x3C86324CUL, 0xD497C7FDUL, 0x3FE58D12UL, + 0x5B9A1DE8UL, 0x3C7295E1UL, 0x36B527DAUL, 0x3FE56F47UL, 0x011D93ADUL, 0x3C89BB2CUL, + 0xCA5D920FUL, 0x3FE551A4UL, 0xEFEDE59BUL, 0xBC7D689CUL, 0x569D4F82UL, 0x3FE5342BUL, + 0x1DB13CADUL, 0xBC707ABEUL, 0xA2CF6642UL, 0x3FE516DAUL, 0x69BD93EFUL, 0xBC7F7685UL, + 0x769D2CA7UL, 0x3FE4F9B2UL, 0xD25957E3UL, 0xBC84B309UL, 0x99FDDD0DUL, 0x3FE4DCB2UL, + 0xBC6A7833UL, 0x3C88ECDBUL, 0xD5362A27UL, 0x3FE4BFDAUL, 0xAFEC42E2UL, 0x3C6D4397UL, + 0xF0D7D3DEUL, 0x3FE4A32AUL, 0xF3D1BE56UL, 0x3C89CB62UL, 0xB5C13CD0UL, 0x3FE486A2UL, + 0xB69062F0UL, 0x3C63C1A3UL, 0xED1D0057UL, 0x3FE46A41UL, 0xD1648A76UL, 0x3C8C944BUL, + 0x6061892DUL, 0x3FE44E08UL, 0x04EF80D0UL, 0x3C389B7AUL, 0xD950A897UL, 0x3FE431F5UL, + 0xE35F7999UL, 0xBC71C7DDUL, 0x21F72E2AUL, 0x3FE4160AUL, 0x1C309278UL, 0xBC4EF369UL, + 0x04AC801CUL, 0x3FE3FA45UL, 0xF956F9F3UL, 0xBC87D023UL, 0x4C123422UL, 0x3FE3DEA6UL, + 0x11F09EBCUL, 0x3C7ADA09UL, 0xC313A8E5UL, 0x3FE3C32DUL, 0x375D29C3UL, 0xBC8EFFF8UL, + 0x34E59FF7UL, 0x3FE3A7DBUL, 0xD661F5E3UL, 0xBC65E436UL, 0x6D05D866UL, 0x3FE38CAEUL, + 0x3C9904BDUL, 0xBC8E958DUL, 0x373AA9CBUL, 0x3FE371A7UL, 0xBF42EAE2UL, 0xBC863AEAUL, + 0x5F929FF1UL, 0x3FE356C5UL, 0x5C4E4628UL, 0xBC7B5CEEUL, 0xB26416FFUL, 0x3FE33C08UL, + 0x843659A6UL, 0x3C832721UL, 0xFC4CD831UL, 0x3FE32170UL, 0x8E18047CUL, 0x3C7A9CE7UL, + 0x0A31B715UL, 0x3FE306FEUL, 0xD23182E4UL, 0x3C76F46AUL, 0xA93E2F56UL, 0x3FE2ECAFUL, + 0x45D52383UL, 0x3C61CA0FUL, 0xA6E4030BUL, 0x3FE2D285UL, 0x54DB41D5UL, 0x3C800247UL, + 0xD0DAD990UL, 0x3FE2B87FUL, 0xD6381AA4UL, 0xBC310ADCUL, 0xF51FDEE1UL, 0x3FE29E9DUL, + 0xAFAD1255UL, 0x3C7612E8UL, 0xE1F56381UL, 0x3FE284DFUL, 0x8C3F0D7EUL, 0xBC8A4C3AUL, + 0x65E27CDDUL, 0x3FE26B45UL, 0x9940E9D9UL, 0x3C72BD33UL, 0x4FB2A63FUL, 0x3FE251CEUL, + 0xBEF4F4A4UL, 0x3C7AC155UL, 0x6E756238UL, 0x3FE2387AUL, 0xB6C70573UL, 0x3C89B07EUL, + 0x917DDC96UL, 0x3FE21F49UL, 0x9494A5EEUL, 0x3C72A97EUL, 0x88628CD6UL, 0x3FE2063BUL, + 0x814A8495UL, 0x3C7DC775UL, 0x22FCD91DUL, 0x3FE1ED50UL, 0x027BB78CUL, 0xBC81DF98UL, + 0x3168B9AAUL, 0x3FE1D487UL, 0x00A2643CUL, 0x3C8E016EUL, 0x84045CD4UL, 0x3FE1BBE0UL, + 0x352EF607UL, 0xBC895386UL, 0xEB6FCB75UL, 0x3FE1A35BUL, 0x7B4968E4UL, 0x3C7E5B4CUL, + 0x388C8DEAUL, 0x3FE18AF9UL, 0xD1970F6CUL, 0xBC811023UL, 0x3C7D517BUL, 0x3FE172B8UL, + 0xB9D78A76UL, 0xBC719041UL, 0xC8A58E51UL, 0x3FE15A98UL, 0xB9EEAB0AUL, 0x3C72406AUL, + 0xAEA92DE0UL, 0x3FE1429AUL, 0x9AF1369EUL, 0xBC832FBFUL, 0xC06C31CCUL, 0x3FE12ABDUL, + 0xB36CA5C7UL, 0xBC41B514UL, 0xD0125B51UL, 0x3FE11301UL, 0x39449B3AUL, 0xBC86C510UL, + 0xAFFED31BUL, 0x3FE0FB66UL, 0xC44EBD7BUL, 0xBC5B9BEDUL, 0x32D3D1A2UL, 0x3FE0E3ECUL, + 0x27C57B52UL, 0x3C303A17UL, 0x2B7247F7UL, 0x3FE0CC92UL, 0x16E24F71UL, 0x3C801EDCUL, + 0x6CF9890FUL, 0x3FE0B558UL, 0x4ADC610BUL, 0x3C88A62EUL, 0xCAC6F383UL, 0x3FE09E3EUL, + 0x18316136UL, 0x3C814878UL, 0x18759BC8UL, 0x3FE08745UL, 0x4BB284FFUL, 0x3C5186BEUL, + 0x29DDF6DEUL, 0x3FE0706BUL, 0xE2B13C27UL, 0xBC7C91DFUL, 0xD3158574UL, 0x3FE059B0UL, + 0xA475B465UL, 0x3C7D73E2UL, 0xE86E7F85UL, 0x3FE04315UL, 0x1977C96EUL, 0xBC80A31CUL, + 0x3E778061UL, 0x3FE02C9AUL, 0x535B085DUL, 0xBC619083UL, 0xA9FB3335UL, 0x3FE0163DUL, + 0x9AB8CDB7UL, 0x3C8B6129UL +}; + +#define __ _masm-> + +address StubGenerator::generate_libmSinh() { + StubGenStubId stub_id = StubGenStubId::dsinh_id; + StubCodeMark mark(this, stub_id); + address start = __ pc(); + + Label L_2TAG_PACKET_0_0_2, L_2TAG_PACKET_1_0_2, L_2TAG_PACKET_3_0_2, L_2TAG_PACKET_4_0_2; + Label L_2TAG_PACKET_5_0_2, L_2TAG_PACKET_6_0_2; + Label B1_2, B1_5; + + address HALFMASK = (address)_HALFMASK; + address MASK3 = (address)_MASK3; + address L2E = (address)_L2E; + address Shifter = (address)_Shifter; + address cv = (address)_cv; + address pv = (address)_pv; + address T2f = (address) _T2f; + address T2_neg_f = (address) _T2_neg_f; + + __ enter(); // required for proper stackwalking of RuntimeStub frame + + __ bind(B1_2); + __ xorpd(xmm4, xmm4); + __ movsd(xmm1, ExternalAddress(L2E), r11 /*rscratch*/); + __ movl(rax, 32768); + __ pinsrw(xmm4, rax, 3); + __ pextrw(rcx, xmm0, 3); + __ andnpd(xmm4, xmm0); + __ pshufd(xmm5, xmm4, 68); + __ movl(rdx, 32768); + __ andl(rdx, rcx); + __ andl(rcx, 32767); + __ subl(rcx, 16343); + __ cmpl(rcx, 177); + __ jcc(Assembler::aboveEqual, L_2TAG_PACKET_0_0_2); // Branch only if |x| is not in [23/64, 3*2^8) + __ movsd(xmm3, ExternalAddress(HALFMASK), r11 /*rscratch*/); + __ movsd(xmm2, ExternalAddress(L2E + 8), r11 /*rscratch*/); + __ movsd(xmm6, ExternalAddress(Shifter), r11 /*rscratch*/); + __ andpd(xmm3, xmm0); + __ subsd(xmm4, xmm3); + __ mulsd(xmm3, xmm1); + __ mulsd(xmm2, xmm5); + __ cvtsd2siq(rax, xmm3); + __ shll(rdx, 3); + __ orl(rax, rdx); + __ movq(xmm7, xmm3); + __ addsd(xmm3, xmm6); + __ mulsd(xmm1, xmm4); + __ xorpd(xmm5, xmm5); + __ subsd(xmm3, xmm6); + __ movapd(xmm4, ExternalAddress(cv), r11 /*rscratch*/); + __ addsd(xmm2, xmm1); + __ movapd(xmm6, ExternalAddress(cv + 16), r11 /*rscratch*/); + __ subsd(xmm7, xmm3); + __ movl(rdx, 32704); + __ pinsrw(xmm5, rdx, 3); + __ movapd(xmm1, ExternalAddress(cv + 32), r11 /*rscratch*/); + __ addsd(xmm2, xmm7); + __ movl(rdx, 127); + __ andl(rdx, rax); + __ addl(rdx, rdx); + __ shrl(rax, 3); + __ andl(rax, 65520); + __ addl(rax, 16352); + __ xorpd(xmm0, xmm0); + __ cmpl(rcx, 161); + __ jcc(Assembler::aboveEqual, L_2TAG_PACKET_1_0_2); // Branch only if |x| is not in [23/64, 3*2^7) + __ pshufd(xmm5, xmm5, 68); + __ pinsrw(xmm0, rax, 3); + __ pshufd(xmm0, xmm0, 68); + __ psubw(xmm5, xmm0); + __ lea(r8, ExternalAddress(T2f)); + __ mulpd(xmm0, Address(r8, rdx, Address::times_8)); + __ lea(r8, ExternalAddress(T2_neg_f)); + __ mulpd(xmm5, Address(r8, rdx, Address::times_8)); + __ pshufd(xmm3, xmm2, 68); + __ movapd(xmm7, ExternalAddress(cv + 48), r11 /*rscratch*/); + __ pshufd(xmm2, xmm2, 68); + __ mulpd(xmm3, xmm3); + __ mulpd(xmm4, xmm2); + __ mulpd(xmm6, xmm2); + __ mulpd(xmm2, ExternalAddress(cv + 64), r11 /*rscratch*/); + __ mulpd(xmm1, xmm3); + __ mulpd(xmm7, xmm3); + __ mulpd(xmm4, xmm3); + __ mulpd(xmm1, xmm3); + __ addpd(xmm6, xmm7); + __ movq(xmm7, xmm0); + __ addpd(xmm4, xmm1); + __ shufpd(xmm7, xmm5, 0); + __ subpd(xmm0, xmm5); + __ mulpd(xmm2, xmm7); + __ addpd(xmm4, xmm6); + __ subsd(xmm7, xmm0); + __ mulpd(xmm4, xmm2); + __ pshufd(xmm6, xmm0, 238); + __ subsd(xmm7, xmm5); + __ addpd(xmm4, xmm2); + __ addsd(xmm7, xmm6); + __ pshufd(xmm2, xmm4, 238); + __ addsd(xmm2, xmm7); + __ addsd(xmm2, xmm4); + __ addsd(xmm0, xmm2); + __ jmp(B1_5); + + __ bind(L_2TAG_PACKET_1_0_2); + __ subl(rax, 16352); + __ movl(rcx, rax); + __ andl(rax, 32752); + __ shrl(rax, 1); + __ andl(rax, 65520); + __ subl(rcx, rax); + __ addl(rax, 16352); + __ pinsrw(xmm0, rax, 3); + __ pshufd(xmm0, xmm0, 68); + __ lea(r8, ExternalAddress(T2f)); + __ mulpd(xmm0, Address(r8, rdx, Address::times_8)); + __ pshufd(xmm3, xmm2, 68); + __ movsd(xmm7, ExternalAddress(cv + 48), r11 /*rscratch*/); + __ mulsd(xmm3, xmm3); + __ mulsd(xmm4, xmm2); + __ mulsd(xmm6, xmm2); + __ mulsd(xmm2, ExternalAddress(cv + 64), r11 /*rscratch*/); + __ mulsd(xmm1, xmm3); + __ mulsd(xmm7, xmm3); + __ mulsd(xmm4, xmm3); + __ addl(rcx, 16368); + __ pinsrw(xmm5, rcx, 3); + __ mulsd(xmm1, xmm3); + __ addsd(xmm6, xmm7); + __ addsd(xmm4, xmm1); + __ mulsd(xmm2, xmm0); + __ addsd(xmm4, xmm6); + __ mulsd(xmm4, xmm2); + __ pshufd(xmm6, xmm0, 238); + __ addsd(xmm4, xmm6); + __ addsd(xmm2, xmm4); + __ addsd(xmm0, xmm2); + __ mulsd(xmm0, xmm5); + __ jmp(B1_5); + + __ bind(L_2TAG_PACKET_0_0_2); + __ addl(rcx, 16343); + __ cmpl(rcx, 16343); + __ jcc(Assembler::above, L_2TAG_PACKET_3_0_2); // Branch only if |x| > 23/64 + __ cmpl(rcx, 15856); + __ jcc(Assembler::below, L_2TAG_PACKET_4_0_2); // Branch only if |x| < 2^-32 + __ movapd(xmm1, ExternalAddress(pv), r11 /*rscratch*/); + __ pshufd(xmm6, xmm0, 68); + __ mulpd(xmm5, xmm5); + __ movapd(xmm2, ExternalAddress(pv + 16), r11 /*rscratch*/); + __ pshufd(xmm7, xmm0, 68); + __ movapd(xmm3, ExternalAddress(pv + 32), r11 /*rscratch*/); + __ pshufd(xmm4, xmm0, 68); + __ andpd(xmm6, ExternalAddress(MASK3), r11 /*rscratch*/); + __ mulpd(xmm1, xmm5); + __ mulsd(xmm2, xmm5); + __ subpd(xmm4, xmm6); + __ mulpd(xmm7, xmm5); + __ addpd(xmm1, xmm3); + __ pshufd(xmm3, xmm6, 68); + __ mulpd(xmm5, xmm5); + __ mulsd(xmm2, xmm7); + __ mulpd(xmm1, xmm7); + __ pshufd(xmm7, xmm0, 68); + __ mulsd(xmm6, xmm6); + __ addsd(xmm7, xmm7); + __ mulsd(xmm4, xmm4); + __ mulpd(xmm1, xmm5); + __ addsd(xmm7, xmm0); + __ mulsd(xmm6, xmm3); + __ mulsd(xmm7, xmm3); + __ pshufd(xmm3, xmm1, 238); + __ mulsd(xmm1, xmm5); + __ pshufd(xmm5, xmm4, 238); + __ addsd(xmm3, xmm2); + __ pshufd(xmm2, xmm2, 238); + __ addsd(xmm7, xmm4); + __ movq(xmm4, xmm0); + __ mulsd(xmm6, xmm2); + __ mulsd(xmm7, xmm5); + __ addsd(xmm0, xmm6); + __ mulsd(xmm7, xmm2); + __ subsd(xmm4, xmm0); + __ addsd(xmm1, xmm7); + __ addsd(xmm6, xmm4); + __ addsd(xmm1, xmm3); + __ addsd(xmm1, xmm6); + __ addsd(xmm0, xmm1); + __ jmp(B1_5); + + __ bind(L_2TAG_PACKET_4_0_2); + __ cmpl(rcx, 16); + __ jcc(Assembler::aboveEqual, L_2TAG_PACKET_5_0_2); // Branch only if |x| is not denormalized + __ movq(xmm1, xmm0); + __ mulsd(xmm1, xmm1); + __ jmp(B1_5); + + __ bind(L_2TAG_PACKET_5_0_2); + __ xorpd(xmm2, xmm2); + __ movl(rcx, 17392); + __ pinsrw(xmm2, rcx, 3); + __ xorpd(xmm3, xmm3); + __ movl(rdx, 15344); + __ pinsrw(xmm3, rdx, 3); + __ mulsd(xmm2, xmm0); + __ addsd(xmm0, xmm2); + __ mulsd(xmm0, xmm3); + __ jmp(B1_5); + + __ bind(L_2TAG_PACKET_3_0_2); + __ cmpl(rcx, 32752); + __ jcc(Assembler::aboveEqual, L_2TAG_PACKET_6_0_2); // Branch only if |x| is INF or NaN + __ xorpd(xmm0, xmm0); + __ movl(rax, 32736); + __ pinsrw(xmm0, rax, 3); + __ orl(rax, rdx); + __ pinsrw(xmm1, rax, 3); + __ mulsd(xmm0, xmm1); + __ jmp(B1_5); + + __ bind(L_2TAG_PACKET_6_0_2); + __ xorpd(xmm1, xmm1); + __ movl(rax, 32768); + __ pinsrw(xmm1, rax, 3); + __ andnpd(xmm1, xmm0); + __ mulsd(xmm0, xmm1); + + __ bind(B1_5); + __ leave(); // required for proper stackwalking of RuntimeStub frame + __ ret(0); + + return start; +} + +#undef __ diff --git a/src/hotspot/cpu/x86/templateInterpreterGenerator_x86_64.cpp b/src/hotspot/cpu/x86/templateInterpreterGenerator_x86_64.cpp index 46af93e9760..c716e10a1b2 100644 --- a/src/hotspot/cpu/x86/templateInterpreterGenerator_x86_64.cpp +++ b/src/hotspot/cpu/x86/templateInterpreterGenerator_x86_64.cpp @@ -464,6 +464,13 @@ address TemplateInterpreterGenerator::generate_math_entry(AbstractInterpreter::M } else { __ call_VM_leaf0(CAST_FROM_FN_PTR(address, SharedRuntime::dtan)); } + } else if (kind == Interpreter::java_lang_math_sinh) { + if (StubRoutines::dsinh() != nullptr) { + __ movdbl(xmm0, Address(rsp, wordSize)); + __ call(RuntimeAddress(CAST_FROM_FN_PTR(address, StubRoutines::dsinh()))); + } else { + return nullptr; // Fallback to default implementation + } } else if (kind == Interpreter::java_lang_math_tanh) { if (StubRoutines::dtanh() != nullptr) { __ movdbl(xmm0, Address(rsp, wordSize)); diff --git a/src/hotspot/share/c1/c1_Compiler.cpp b/src/hotspot/share/c1/c1_Compiler.cpp index 82dea9b0141..d6cabf13d0c 100644 --- a/src/hotspot/share/c1/c1_Compiler.cpp +++ b/src/hotspot/share/c1/c1_Compiler.cpp @@ -166,6 +166,7 @@ bool Compiler::is_intrinsic_supported(vmIntrinsics::ID id) { case vmIntrinsics::_dcos: case vmIntrinsics::_dtan: #if defined(AMD64) + case vmIntrinsics::_dsinh: case vmIntrinsics::_dtanh: case vmIntrinsics::_dcbrt: #endif diff --git a/src/hotspot/share/c1/c1_GraphBuilder.cpp b/src/hotspot/share/c1/c1_GraphBuilder.cpp index 1e4e07360cc..f910ecadc16 100644 --- a/src/hotspot/share/c1/c1_GraphBuilder.cpp +++ b/src/hotspot/share/c1/c1_GraphBuilder.cpp @@ -3296,6 +3296,7 @@ GraphBuilder::GraphBuilder(Compilation* compilation, IRScope* scope) case vmIntrinsics::_dsin : // fall through case vmIntrinsics::_dcos : // fall through case vmIntrinsics::_dtan : // fall through + case vmIntrinsics::_dsinh : // fall through case vmIntrinsics::_dtanh : // fall through case vmIntrinsics::_dcbrt : // fall through case vmIntrinsics::_dlog : // fall through diff --git a/src/hotspot/share/c1/c1_LIRGenerator.cpp b/src/hotspot/share/c1/c1_LIRGenerator.cpp index b3ce7242b4f..cbd84b3a11e 100644 --- a/src/hotspot/share/c1/c1_LIRGenerator.cpp +++ b/src/hotspot/share/c1/c1_LIRGenerator.cpp @@ -2865,6 +2865,7 @@ void LIRGenerator::do_Intrinsic(Intrinsic* x) { case vmIntrinsics::_dsqrt: // fall through case vmIntrinsics::_dsqrt_strict: // fall through case vmIntrinsics::_dtan: // fall through + case vmIntrinsics::_dsinh: // fall through case vmIntrinsics::_dtanh: // fall through case vmIntrinsics::_dsin : // fall through case vmIntrinsics::_dcos : // fall through diff --git a/src/hotspot/share/c1/c1_Runtime1.cpp b/src/hotspot/share/c1/c1_Runtime1.cpp index b230351c5e1..4a1969788f3 100644 --- a/src/hotspot/share/c1/c1_Runtime1.cpp +++ b/src/hotspot/share/c1/c1_Runtime1.cpp @@ -362,6 +362,7 @@ const char* Runtime1::name_for_address(address entry) { FUNCTION_CASE(entry, StubRoutines::dsin()); FUNCTION_CASE(entry, StubRoutines::dcos()); FUNCTION_CASE(entry, StubRoutines::dtan()); + FUNCTION_CASE(entry, StubRoutines::dsinh()); FUNCTION_CASE(entry, StubRoutines::dtanh()); FUNCTION_CASE(entry, StubRoutines::dcbrt()); diff --git a/src/hotspot/share/classfile/vmIntrinsics.cpp b/src/hotspot/share/classfile/vmIntrinsics.cpp index 1145310da46..f9b12df84ca 100644 --- a/src/hotspot/share/classfile/vmIntrinsics.cpp +++ b/src/hotspot/share/classfile/vmIntrinsics.cpp @@ -91,6 +91,7 @@ bool vmIntrinsics::preserves_state(vmIntrinsics::ID id) { case vmIntrinsics::_dsin: case vmIntrinsics::_dcos: case vmIntrinsics::_dtan: + case vmIntrinsics::_dsinh: case vmIntrinsics::_dtanh: case vmIntrinsics::_dcbrt: case vmIntrinsics::_dlog: @@ -144,6 +145,7 @@ bool vmIntrinsics::can_trap(vmIntrinsics::ID id) { case vmIntrinsics::_dsin: case vmIntrinsics::_dcos: case vmIntrinsics::_dtan: + case vmIntrinsics::_dsinh: case vmIntrinsics::_dtanh: case vmIntrinsics::_dcbrt: case vmIntrinsics::_dlog: @@ -314,6 +316,7 @@ bool vmIntrinsics::disabled_by_jvm_flags(vmIntrinsics::ID id) { case vmIntrinsics::_fmaF: if (!InlineMathNatives || !UseFMA) return true; break; + case vmIntrinsics::_dsinh: case vmIntrinsics::_dtanh: case vmIntrinsics::_dcbrt: if (!InlineMathNatives || !InlineIntrinsics) return true; diff --git a/src/hotspot/share/classfile/vmIntrinsics.hpp b/src/hotspot/share/classfile/vmIntrinsics.hpp index b142ac9f1e4..3d110c5706b 100644 --- a/src/hotspot/share/classfile/vmIntrinsics.hpp +++ b/src/hotspot/share/classfile/vmIntrinsics.hpp @@ -135,7 +135,8 @@ class methodHandle; do_name(log_name,"log") do_name(log10_name,"log10") do_name(pow_name,"pow") \ do_name(exp_name,"exp") do_name(min_name,"min") do_name(max_name,"max") \ do_name(floor_name, "floor") do_name(ceil_name, "ceil") do_name(rint_name, "rint") \ - do_name(round_name, "round") do_name(tanh_name,"tanh") do_name(cbrt_name,"cbrt") \ + do_name(round_name, "round") do_name(sinh_name,"sinh") do_name(tanh_name,"tanh") \ + do_name(cbrt_name,"cbrt") \ \ do_name(addExact_name,"addExact") \ do_name(decrementExact_name,"decrementExact") \ @@ -161,6 +162,7 @@ class methodHandle; do_intrinsic(_dcos, java_lang_Math, cos_name, double_double_signature, F_S) \ do_intrinsic(_dtan, java_lang_Math, tan_name, double_double_signature, F_S) \ do_intrinsic(_datan2, java_lang_Math, atan2_name, double2_double_signature, F_S) \ + do_intrinsic(_dsinh, java_lang_Math, sinh_name, double_double_signature, F_S) \ do_intrinsic(_dtanh, java_lang_Math, tanh_name, double_double_signature, F_S) \ do_intrinsic(_dcbrt, java_lang_Math, cbrt_name, double_double_signature, F_S) \ do_intrinsic(_dsqrt, java_lang_Math, sqrt_name, double_double_signature, F_S) \ diff --git a/src/hotspot/share/interpreter/abstractInterpreter.cpp b/src/hotspot/share/interpreter/abstractInterpreter.cpp index ad39169bca0..0d6ccf3a710 100644 --- a/src/hotspot/share/interpreter/abstractInterpreter.cpp +++ b/src/hotspot/share/interpreter/abstractInterpreter.cpp @@ -137,6 +137,7 @@ AbstractInterpreter::MethodKind AbstractInterpreter::method_kind(const methodHan case vmIntrinsics::_dsin: return java_lang_math_sin; case vmIntrinsics::_dcos: return java_lang_math_cos; case vmIntrinsics::_dtan: return java_lang_math_tan; + case vmIntrinsics::_dsinh: return java_lang_math_sinh; case vmIntrinsics::_dtanh: return java_lang_math_tanh; case vmIntrinsics::_dcbrt: return java_lang_math_cbrt; case vmIntrinsics::_dabs: return java_lang_math_abs; @@ -199,6 +200,7 @@ vmIntrinsics::ID AbstractInterpreter::method_intrinsic(MethodKind kind) { case java_lang_math_sin : return vmIntrinsics::_dsin; case java_lang_math_cos : return vmIntrinsics::_dcos; case java_lang_math_tan : return vmIntrinsics::_dtan; + case java_lang_math_sinh : return vmIntrinsics::_dsinh; case java_lang_math_tanh : return vmIntrinsics::_dtanh; case java_lang_math_cbrt : return vmIntrinsics::_dcbrt; case java_lang_math_abs : return vmIntrinsics::_dabs; @@ -304,6 +306,7 @@ void AbstractInterpreter::print_method_kind(MethodKind kind) { case java_lang_math_sin : tty->print("java_lang_math_sin" ); break; case java_lang_math_cos : tty->print("java_lang_math_cos" ); break; case java_lang_math_tan : tty->print("java_lang_math_tan" ); break; + case java_lang_math_sinh : tty->print("java_lang_math_sinh" ); break; case java_lang_math_tanh : tty->print("java_lang_math_tanh" ); break; case java_lang_math_cbrt : tty->print("java_lang_math_cbrt" ); break; case java_lang_math_abs : tty->print("java_lang_math_abs" ); break; diff --git a/src/hotspot/share/interpreter/abstractInterpreter.hpp b/src/hotspot/share/interpreter/abstractInterpreter.hpp index a3e93aa0a30..6f7523fd00a 100644 --- a/src/hotspot/share/interpreter/abstractInterpreter.hpp +++ b/src/hotspot/share/interpreter/abstractInterpreter.hpp @@ -72,6 +72,7 @@ class AbstractInterpreter: AllStatic { java_lang_math_sin, // implementation of java.lang.Math.sin (x) java_lang_math_cos, // implementation of java.lang.Math.cos (x) java_lang_math_tan, // implementation of java.lang.Math.tan (x) + java_lang_math_sinh, // implementation of java.lang.Math.sinh (x) java_lang_math_tanh, // implementation of java.lang.Math.tanh (x) java_lang_math_cbrt, // implementation of java.lang.Math.cbrt (x) java_lang_math_abs, // implementation of java.lang.Math.abs (x) @@ -152,6 +153,7 @@ class AbstractInterpreter: AllStatic { case vmIntrinsics::_dsin : // fall thru case vmIntrinsics::_dcos : // fall thru case vmIntrinsics::_dtan : // fall thru + case vmIntrinsics::_dsinh : // fall thru case vmIntrinsics::_dtanh : // fall thru case vmIntrinsics::_dcbrt : // fall thru case vmIntrinsics::_dabs : // fall thru diff --git a/src/hotspot/share/interpreter/templateInterpreterGenerator.cpp b/src/hotspot/share/interpreter/templateInterpreterGenerator.cpp index 928d1ac9f9c..9a316f3ba46 100644 --- a/src/hotspot/share/interpreter/templateInterpreterGenerator.cpp +++ b/src/hotspot/share/interpreter/templateInterpreterGenerator.cpp @@ -193,6 +193,7 @@ void TemplateInterpreterGenerator::generate_all() { method_entry(java_lang_math_sin ) method_entry(java_lang_math_cos ) method_entry(java_lang_math_tan ) + method_entry(java_lang_math_sinh ) method_entry(java_lang_math_tanh ) method_entry(java_lang_math_cbrt ) method_entry(java_lang_math_abs ) @@ -454,6 +455,7 @@ address TemplateInterpreterGenerator::generate_intrinsic_entry(AbstractInterpret case Interpreter::java_lang_math_sin : // fall thru case Interpreter::java_lang_math_cos : // fall thru case Interpreter::java_lang_math_tan : // fall thru + case Interpreter::java_lang_math_sinh : // fall thru case Interpreter::java_lang_math_tanh : // fall thru case Interpreter::java_lang_math_cbrt : // fall thru case Interpreter::java_lang_math_abs : // fall thru diff --git a/src/hotspot/share/interpreter/zero/zeroInterpreterGenerator.cpp b/src/hotspot/share/interpreter/zero/zeroInterpreterGenerator.cpp index 8fa0835216d..148ef2eb572 100644 --- a/src/hotspot/share/interpreter/zero/zeroInterpreterGenerator.cpp +++ b/src/hotspot/share/interpreter/zero/zeroInterpreterGenerator.cpp @@ -53,6 +53,7 @@ void ZeroInterpreterGenerator::generate_all() { method_entry(java_lang_math_sin ); method_entry(java_lang_math_cos ); method_entry(java_lang_math_tan ); + method_entry(java_lang_math_sinh ); method_entry(java_lang_math_tanh ); method_entry(java_lang_math_cbrt ); method_entry(java_lang_math_abs ); @@ -96,6 +97,7 @@ address ZeroInterpreterGenerator::generate_method_entry( case Interpreter::java_lang_math_sin : // fall thru case Interpreter::java_lang_math_cos : // fall thru case Interpreter::java_lang_math_tan : // fall thru + case Interpreter::java_lang_math_sinh : // fall thru case Interpreter::java_lang_math_tanh : // fall thru case Interpreter::java_lang_math_cbrt : // fall thru case Interpreter::java_lang_math_abs : // fall thru diff --git a/src/hotspot/share/jvmci/jvmciCompilerToVM.hpp b/src/hotspot/share/jvmci/jvmciCompilerToVM.hpp index 71331b578a5..f06ee667ff2 100644 --- a/src/hotspot/share/jvmci/jvmciCompilerToVM.hpp +++ b/src/hotspot/share/jvmci/jvmciCompilerToVM.hpp @@ -124,6 +124,7 @@ class CompilerToVM { static address dsin; static address dcos; static address dtan; + static address dsinh; static address dtanh; static address dcbrt; static address dexp; diff --git a/src/hotspot/share/jvmci/jvmciCompilerToVMInit.cpp b/src/hotspot/share/jvmci/jvmciCompilerToVMInit.cpp index 8a1a02d62b3..753d673f54c 100644 --- a/src/hotspot/share/jvmci/jvmciCompilerToVMInit.cpp +++ b/src/hotspot/share/jvmci/jvmciCompilerToVMInit.cpp @@ -144,6 +144,7 @@ int CompilerToVM::Data::sizeof_ZStoreBarrierEntry = sizeof(ZStoreBarrierEntry); address CompilerToVM::Data::dsin; address CompilerToVM::Data::dcos; address CompilerToVM::Data::dtan; +address CompilerToVM::Data::dsinh; address CompilerToVM::Data::dtanh; address CompilerToVM::Data::dcbrt; address CompilerToVM::Data::dexp; @@ -289,6 +290,7 @@ void CompilerToVM::Data::initialize(JVMCI_TRAPS) { name = nullptr; \ } + SET_TRIGFUNC_OR_NULL(dsinh); SET_TRIGFUNC_OR_NULL(dtanh); SET_TRIGFUNC_OR_NULL(dcbrt); SET_TRIGFUNC_OR_NULL(crc_table_addr); diff --git a/src/hotspot/share/jvmci/vmStructs_jvmci.cpp b/src/hotspot/share/jvmci/vmStructs_jvmci.cpp index 88d098468e9..737718096c5 100644 --- a/src/hotspot/share/jvmci/vmStructs_jvmci.cpp +++ b/src/hotspot/share/jvmci/vmStructs_jvmci.cpp @@ -141,6 +141,7 @@ static_field(CompilerToVM::Data, dsin, address) \ static_field(CompilerToVM::Data, dcos, address) \ static_field(CompilerToVM::Data, dtan, address) \ + static_field(CompilerToVM::Data, dsinh, address) \ static_field(CompilerToVM::Data, dtanh, address) \ static_field(CompilerToVM::Data, dcbrt, address) \ static_field(CompilerToVM::Data, dexp, address) \ diff --git a/src/hotspot/share/opto/c2compiler.cpp b/src/hotspot/share/opto/c2compiler.cpp index 5aa7e4f9727..bed0f38d6a6 100644 --- a/src/hotspot/share/opto/c2compiler.cpp +++ b/src/hotspot/share/opto/c2compiler.cpp @@ -615,6 +615,7 @@ bool C2Compiler::is_intrinsic_supported(vmIntrinsics::ID id) { case vmIntrinsics::_dsin: case vmIntrinsics::_dcos: case vmIntrinsics::_dtan: + case vmIntrinsics::_dsinh: case vmIntrinsics::_dtanh: case vmIntrinsics::_dcbrt: case vmIntrinsics::_dabs: diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp index 90830ddeefc..6c5efaafc21 100644 --- a/src/hotspot/share/opto/library_call.cpp +++ b/src/hotspot/share/opto/library_call.cpp @@ -245,6 +245,7 @@ bool LibraryCallKit::try_to_inline(int predicate) { case vmIntrinsics::_dsin: case vmIntrinsics::_dcos: case vmIntrinsics::_dtan: + case vmIntrinsics::_dsinh: case vmIntrinsics::_dtanh: case vmIntrinsics::_dcbrt: case vmIntrinsics::_dabs: @@ -1886,6 +1887,9 @@ bool LibraryCallKit::inline_math_native(vmIntrinsics::ID id) { return StubRoutines::dtan() != nullptr ? runtime_math(OptoRuntime::Math_D_D_Type(), StubRoutines::dtan(), "dtan") : runtime_math(OptoRuntime::Math_D_D_Type(), CAST_FROM_FN_PTR(address, SharedRuntime::dtan), "TAN"); + case vmIntrinsics::_dsinh: + return StubRoutines::dsinh() != nullptr ? + runtime_math(OptoRuntime::Math_D_D_Type(), StubRoutines::dsinh(), "dsinh") : false; case vmIntrinsics::_dtanh: return StubRoutines::dtanh() != nullptr ? runtime_math(OptoRuntime::Math_D_D_Type(), StubRoutines::dtanh(), "dtanh") : false; diff --git a/src/hotspot/share/runtime/stubDeclarations.hpp b/src/hotspot/share/runtime/stubDeclarations.hpp index 9ddccf00a6c..c79caa12a6c 100644 --- a/src/hotspot/share/runtime/stubDeclarations.hpp +++ b/src/hotspot/share/runtime/stubDeclarations.hpp @@ -672,6 +672,8 @@ do_entry(initial, dcos, dcos, dcos) \ do_stub(initial, dtan) \ do_entry(initial, dtan, dtan, dtan) \ + do_stub(initial, dsinh) \ + do_entry(initial, dsinh, dsinh, dsinh) \ do_stub(initial, dtanh) \ do_entry(initial, dtanh, dtanh, dtanh) \ do_stub(initial, dcbrt) \ diff --git a/src/java.base/share/classes/java/lang/Math.java b/src/java.base/share/classes/java/lang/Math.java index 9108ba7d944..ef5d1214b11 100644 --- a/src/java.base/share/classes/java/lang/Math.java +++ b/src/java.base/share/classes/java/lang/Math.java @@ -2672,6 +2672,7 @@ public final class Math { * @return The hyperbolic sine of {@code x}. * @since 1.5 */ + @IntrinsicCandidate public static double sinh(double x) { return StrictMath.sinh(x); } diff --git a/test/micro/org/openjdk/bench/java/lang/SinhPerf.java b/test/micro/org/openjdk/bench/java/lang/SinhPerf.java new file mode 100644 index 00000000000..d98830b7ea5 --- /dev/null +++ b/test/micro/org/openjdk/bench/java/lang/SinhPerf.java @@ -0,0 +1,164 @@ +/* + * 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.java.lang; + +import java.util.concurrent.TimeUnit; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.annotations.OperationsPerInvocation; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +import java.util.Random; + +public class SinhPerf { + + @Warmup(iterations = 3, time = 5, timeUnit = TimeUnit.MILLISECONDS) + @Measurement(iterations = 4, time = 5, timeUnit = TimeUnit.MILLISECONDS) + @Fork(2) + @BenchmarkMode(Mode.Throughput) + @State(Scope.Thread) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public static class SinhPerfRanges { + public static int sinhInputCount = 2048; + + @Param({"0", "1", "2", "3", "4"}) + public int sinhRangeIndex; + + public double [] sinhPosRandInputs; + public double [] sinhNegRandInputs; + public int sinhInputIndex = 0; + public double sinhRangeInputs[][] = { {0.0, 0x1.0P-28}, + {0x1.0P-28, 22.0}, + {22.0, Math.log(Double.MAX_VALUE)}, + {Math.log(Double.MAX_VALUE), Double.longBitsToDouble(0x408633CE8FB9F87DL)}, + {Double.longBitsToDouble(0x408633CE8FB9F87DL), Double.MAX_VALUE} }; + + @Setup + public void setupValues() { + Random random = new Random(1023); + + // Fill the positive and negative sinh vectors with random values + sinhPosRandInputs = new double[sinhInputCount]; + sinhNegRandInputs = new double[sinhInputCount]; + + for (int i = 0; i < sinhInputCount; i++) { + double sinhLowerBound = sinhRangeInputs[sinhRangeIndex][0]; + double sinhUpperBound = sinhRangeInputs[sinhRangeIndex][1]; + sinhPosRandInputs[i] = random.nextDouble(sinhLowerBound, sinhUpperBound); + sinhNegRandInputs[i] = random.nextDouble(-sinhUpperBound, -sinhLowerBound); + } + } + + @Benchmark + @OperationsPerInvocation(2048) + public double sinhPosRangeDouble() { + double res = 0.0; + for (int i = 0; i < sinhInputCount; i++) { + res += Math.sinh(sinhPosRandInputs[i]); + } + return res; + } + + @Benchmark + @OperationsPerInvocation(2048) + public double sinhNegRangeDouble() { + double res = 0.0; + for (int i = 0; i < sinhInputCount; i++) { + res += Math.sinh(sinhNegRandInputs[i]); + } + return res; + } + } + + @Warmup(iterations = 3, time = 5, timeUnit = TimeUnit.SECONDS) + @Measurement(iterations = 4, time = 5, timeUnit = TimeUnit.SECONDS) + @Fork(2) + @BenchmarkMode(Mode.Throughput) + @State(Scope.Thread) + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public static class SinhPerfConstant { + public static final double constDoubleTiny = 0x1.0P-30; + public static final double constDoubleSmall = 0x1.0P-27; + public static final double constDouble21 = 21.0; + public static final double constDouble23 = 23.0; + public static final double constDoubleLarge = Math.log(Double.MAX_VALUE) + 0.5; + public static final double constDoubleOverflow = 0x1.0P10; + + @Benchmark + public double sinhConstDoubleTiny() { + return Math.sinh(constDoubleTiny); + } + + @Benchmark + public double sinhConstDoubleSmall() { + return Math.sinh(constDoubleSmall); + } + + @Benchmark + public double sinhConstDouble21() { + return Math.sinh(constDouble21); + } + + @Benchmark + public double sinhConstDouble23() { + return Math.sinh(constDouble23); + } + + @Benchmark + public double sinhConstDoubleLarge() { + return Math.sinh(constDoubleLarge); + } + + @Benchmark + public double sinhConstDoubleOverflow() { + return Math.sinh(constDoubleOverflow); + } + } + + public static void main(String[] args) throws RunnerException { + Options opt = new OptionsBuilder() + .include(SinhPerfRanges.class.getSimpleName()) + .build(); + + new Runner(opt).run(); + + opt = new OptionsBuilder() + .include(SinhPerfConstant.class.getSimpleName()) + .build(); + + new Runner(opt).run(); + } +} From d1e362e9a8aae7f2c095510633624ed90a1d3b78 Mon Sep 17 00:00:00 2001 From: Phil Race Date: Mon, 4 Aug 2025 19:27:23 +0000 Subject: [PATCH 017/580] 8363889: Update sun.print.PrintJob2D to use Disposer Reviewed-by: azvegint, psadhukhan --- .../share/classes/sun/print/PrintJob2D.java | 1158 +--------------- .../classes/sun/print/PrintJobDelegate.java | 1227 +++++++++++++++++ 2 files changed, 1254 insertions(+), 1131 deletions(-) create mode 100644 src/java.desktop/share/classes/sun/print/PrintJobDelegate.java diff --git a/src/java.desktop/share/classes/sun/print/PrintJob2D.java b/src/java.desktop/share/classes/sun/print/PrintJob2D.java index b3811e36367..8128dae1057 100644 --- a/src/java.desktop/share/classes/sun/print/PrintJob2D.java +++ b/src/java.desktop/share/classes/sun/print/PrintJob2D.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -28,47 +28,12 @@ package sun.print; import java.awt.Dimension; import java.awt.Frame; import java.awt.Graphics; -import java.awt.Graphics2D; import java.awt.PrintJob; import java.awt.JobAttributes; -import java.awt.JobAttributes.*; import java.awt.PageAttributes; -import java.awt.PageAttributes.*; - -import java.awt.print.PageFormat; -import java.awt.print.Paper; -import java.awt.print.Printable; -import java.awt.print.PrinterException; -import java.awt.print.PrinterJob; - -import java.io.File; -import java.io.IOException; - -import java.net.URI; -import java.net.URISyntaxException; - -import java.util.ArrayList; import java.util.Properties; - -import javax.print.PrintService; -import javax.print.attribute.HashPrintRequestAttributeSet; -import javax.print.attribute.PrintRequestAttributeSet; -import javax.print.attribute.Size2DSyntax; -import javax.print.attribute.standard.Chromaticity; -import javax.print.attribute.standard.Copies; -import javax.print.attribute.standard.Destination; -import javax.print.attribute.standard.DialogTypeSelection; -import javax.print.attribute.standard.DialogOwner; -import javax.print.attribute.standard.JobName; -import javax.print.attribute.standard.MediaSize; -import javax.print.attribute.standard.PrintQuality; -import javax.print.attribute.standard.SheetCollate; -import javax.print.attribute.standard.Sides; -import javax.print.attribute.standard.Media; -import javax.print.attribute.standard.OrientationRequested; -import javax.print.attribute.standard.MediaSizeName; -import javax.print.attribute.standard.PageRanges; - +import sun.java2d.Disposer; +import sun.java2d.DisposerRecord; /** * A class which initiates and executes a print job using @@ -77,667 +42,27 @@ import javax.print.attribute.standard.PageRanges; * @see java.awt.Toolkit#getPrintJob * */ -public class PrintJob2D extends PrintJob implements Printable, Runnable { +public class PrintJob2D extends PrintJob { - private static final MediaType[] SIZES = { - MediaType.ISO_4A0, MediaType.ISO_2A0, MediaType.ISO_A0, - MediaType.ISO_A1, MediaType.ISO_A2, MediaType.ISO_A3, - MediaType.ISO_A4, MediaType.ISO_A5, MediaType.ISO_A6, - MediaType.ISO_A7, MediaType.ISO_A8, MediaType.ISO_A9, - MediaType.ISO_A10, MediaType.ISO_B0, MediaType.ISO_B1, - MediaType.ISO_B2, MediaType.ISO_B3, MediaType.ISO_B4, - MediaType.ISO_B5, MediaType.ISO_B6, MediaType.ISO_B7, - MediaType.ISO_B8, MediaType.ISO_B9, MediaType.ISO_B10, - MediaType.JIS_B0, MediaType.JIS_B1, MediaType.JIS_B2, - MediaType.JIS_B3, MediaType.JIS_B4, MediaType.JIS_B5, - MediaType.JIS_B6, MediaType.JIS_B7, MediaType.JIS_B8, - MediaType.JIS_B9, MediaType.JIS_B10, MediaType.ISO_C0, - MediaType.ISO_C1, MediaType.ISO_C2, MediaType.ISO_C3, - MediaType.ISO_C4, MediaType.ISO_C5, MediaType.ISO_C6, - MediaType.ISO_C7, MediaType.ISO_C8, MediaType.ISO_C9, - MediaType.ISO_C10, MediaType.ISO_DESIGNATED_LONG, - MediaType.EXECUTIVE, MediaType.FOLIO, MediaType.INVOICE, - MediaType.LEDGER, MediaType.NA_LETTER, MediaType.NA_LEGAL, - MediaType.QUARTO, MediaType.A, MediaType.B, - MediaType.C, MediaType.D, MediaType.E, - MediaType.NA_10X15_ENVELOPE, MediaType.NA_10X14_ENVELOPE, - MediaType.NA_10X13_ENVELOPE, MediaType.NA_9X12_ENVELOPE, - MediaType.NA_9X11_ENVELOPE, MediaType.NA_7X9_ENVELOPE, - MediaType.NA_6X9_ENVELOPE, MediaType.NA_NUMBER_9_ENVELOPE, - MediaType.NA_NUMBER_10_ENVELOPE, MediaType.NA_NUMBER_11_ENVELOPE, - MediaType.NA_NUMBER_12_ENVELOPE, MediaType.NA_NUMBER_14_ENVELOPE, - MediaType.INVITE_ENVELOPE, MediaType.ITALY_ENVELOPE, - MediaType.MONARCH_ENVELOPE, MediaType.PERSONAL_ENVELOPE - }; - - /* This array maps the above array to the objects used by the - * javax.print APIs - */ - private static final MediaSizeName[] JAVAXSIZES = { - null, null, MediaSizeName.ISO_A0, - MediaSizeName.ISO_A1, MediaSizeName.ISO_A2, MediaSizeName.ISO_A3, - MediaSizeName.ISO_A4, MediaSizeName.ISO_A5, MediaSizeName.ISO_A6, - MediaSizeName.ISO_A7, MediaSizeName.ISO_A8, MediaSizeName.ISO_A9, - MediaSizeName.ISO_A10, MediaSizeName.ISO_B0, MediaSizeName.ISO_B1, - MediaSizeName.ISO_B2, MediaSizeName.ISO_B3, MediaSizeName.ISO_B4, - MediaSizeName.ISO_B5, MediaSizeName.ISO_B6, MediaSizeName.ISO_B7, - MediaSizeName.ISO_B8, MediaSizeName.ISO_B9, MediaSizeName.ISO_B10, - MediaSizeName.JIS_B0, MediaSizeName.JIS_B1, MediaSizeName.JIS_B2, - MediaSizeName.JIS_B3, MediaSizeName.JIS_B4, MediaSizeName.JIS_B5, - MediaSizeName.JIS_B6, MediaSizeName.JIS_B7, MediaSizeName.JIS_B8, - MediaSizeName.JIS_B9, MediaSizeName.JIS_B10, MediaSizeName.ISO_C0, - MediaSizeName.ISO_C1, MediaSizeName.ISO_C2, MediaSizeName.ISO_C3, - MediaSizeName.ISO_C4, MediaSizeName.ISO_C5, MediaSizeName.ISO_C6, - null, null, null, null, - MediaSizeName.ISO_DESIGNATED_LONG, MediaSizeName.EXECUTIVE, - MediaSizeName.FOLIO, MediaSizeName.INVOICE, MediaSizeName.LEDGER, - MediaSizeName.NA_LETTER, MediaSizeName.NA_LEGAL, - MediaSizeName.QUARTO, MediaSizeName.A, MediaSizeName.B, - MediaSizeName.C, MediaSizeName.D, MediaSizeName.E, - MediaSizeName.NA_10X15_ENVELOPE, MediaSizeName.NA_10X14_ENVELOPE, - MediaSizeName.NA_10X13_ENVELOPE, MediaSizeName.NA_9X12_ENVELOPE, - MediaSizeName.NA_9X11_ENVELOPE, MediaSizeName.NA_7X9_ENVELOPE, - MediaSizeName.NA_6X9_ENVELOPE, - MediaSizeName.NA_NUMBER_9_ENVELOPE, - MediaSizeName.NA_NUMBER_10_ENVELOPE, - MediaSizeName.NA_NUMBER_11_ENVELOPE, - MediaSizeName.NA_NUMBER_12_ENVELOPE, - MediaSizeName.NA_NUMBER_14_ENVELOPE, - null, MediaSizeName.ITALY_ENVELOPE, - MediaSizeName.MONARCH_ENVELOPE, MediaSizeName.PERSONAL_ENVELOPE, - }; - - - // widths and lengths in PostScript points (1/72 in.) - private static final int[] WIDTHS = { - /*iso-4a0*/ 4768, /*iso-2a0*/ 3370, /*iso-a0*/ 2384, /*iso-a1*/ 1684, - /*iso-a2*/ 1191, /*iso-a3*/ 842, /*iso-a4*/ 595, /*iso-a5*/ 420, - /*iso-a6*/ 298, /*iso-a7*/ 210, /*iso-a8*/ 147, /*iso-a9*/ 105, - /*iso-a10*/ 74, /*iso-b0*/ 2835, /*iso-b1*/ 2004, /*iso-b2*/ 1417, - /*iso-b3*/ 1001, /*iso-b4*/ 709, /*iso-b5*/ 499, /*iso-b6*/ 354, - /*iso-b7*/ 249, /*iso-b8*/ 176, /*iso-b9*/ 125, /*iso-b10*/ 88, - /*jis-b0*/ 2920, /*jis-b1*/ 2064, /*jis-b2*/ 1460, /*jis-b3*/ 1032, - /*jis-b4*/ 729, /*jis-b5*/ 516, /*jis-b6*/ 363, /*jis-b7*/ 258, - /*jis-b8*/ 181, /*jis-b9*/ 128, /*jis-b10*/ 91, /*iso-c0*/ 2599, - /*iso-c1*/ 1837, /*iso-c2*/ 1298, /*iso-c3*/ 918, /*iso-c4*/ 649, - /*iso-c5*/ 459, /*iso-c6*/ 323, /*iso-c7*/ 230, /*iso-c8*/ 162, - /*iso-c9*/ 113, /*iso-c10*/ 79, /*iso-designated-long*/ 312, - /*executive*/ 522, /*folio*/ 612, /*invoice*/ 396, /*ledger*/ 792, - /*na-letter*/ 612, /*na-legal*/ 612, /*quarto*/ 609, /*a*/ 612, - /*b*/ 792, /*c*/ 1224, /*d*/ 1584, /*e*/ 2448, - /*na-10x15-envelope*/ 720, /*na-10x14-envelope*/ 720, - /*na-10x13-envelope*/ 720, /*na-9x12-envelope*/ 648, - /*na-9x11-envelope*/ 648, /*na-7x9-envelope*/ 504, - /*na-6x9-envelope*/ 432, /*na-number-9-envelope*/ 279, - /*na-number-10-envelope*/ 297, /*na-number-11-envelope*/ 324, - /*na-number-12-envelope*/ 342, /*na-number-14-envelope*/ 360, - /*invite-envelope*/ 624, /*italy-envelope*/ 312, - /*monarch-envelope*/ 279, /*personal-envelope*/ 261 - }; - private static final int[] LENGTHS = { - /*iso-4a0*/ 6741, /*iso-2a0*/ 4768, /*iso-a0*/ 3370, /*iso-a1*/ 2384, - /*iso-a2*/ 1684, /*iso-a3*/ 1191, /*iso-a4*/ 842, /*iso-a5*/ 595, - /*iso-a6*/ 420, /*iso-a7*/ 298, /*iso-a8*/ 210, /*iso-a9*/ 147, - /*iso-a10*/ 105, /*iso-b0*/ 4008, /*iso-b1*/ 2835, /*iso-b2*/ 2004, - /*iso-b3*/ 1417, /*iso-b4*/ 1001, /*iso-b5*/ 729, /*iso-b6*/ 499, - /*iso-b7*/ 354, /*iso-b8*/ 249, /*iso-b9*/ 176, /*iso-b10*/ 125, - /*jis-b0*/ 4127, /*jis-b1*/ 2920, /*jis-b2*/ 2064, /*jis-b3*/ 1460, - /*jis-b4*/ 1032, /*jis-b5*/ 729, /*jis-b6*/ 516, /*jis-b7*/ 363, - /*jis-b8*/ 258, /*jis-b9*/ 181, /*jis-b10*/ 128, /*iso-c0*/ 3677, - /*iso-c1*/ 2599, /*iso-c2*/ 1837, /*iso-c3*/ 1298, /*iso-c4*/ 918, - /*iso-c5*/ 649, /*iso-c6*/ 459, /*iso-c7*/ 323, /*iso-c8*/ 230, - /*iso-c9*/ 162, /*iso-c10*/ 113, /*iso-designated-long*/ 624, - /*executive*/ 756, /*folio*/ 936, /*invoice*/ 612, /*ledger*/ 1224, - /*na-letter*/ 792, /*na-legal*/ 1008, /*quarto*/ 780, /*a*/ 792, - /*b*/ 1224, /*c*/ 1584, /*d*/ 2448, /*e*/ 3168, - /*na-10x15-envelope*/ 1080, /*na-10x14-envelope*/ 1008, - /*na-10x13-envelope*/ 936, /*na-9x12-envelope*/ 864, - /*na-9x11-envelope*/ 792, /*na-7x9-envelope*/ 648, - /*na-6x9-envelope*/ 648, /*na-number-9-envelope*/ 639, - /*na-number-10-envelope*/ 684, /*na-number-11-envelope*/ 747, - /*na-number-12-envelope*/ 792, /*na-number-14-envelope*/ 828, - /*invite-envelope*/ 624, /*italy-envelope*/ 652, - /*monarch-envelope*/ 540, /*personal-envelope*/ 468 - }; - - - private Frame frame; - private String docTitle = ""; - private JobAttributes jobAttributes; - private PageAttributes pageAttributes; - private PrintRequestAttributeSet attributes; - - /* - * Displays the native or cross-platform dialog and allows the - * user to update job & page attributes - */ - - /** - * The PrinterJob being uses to implement the PrintJob. - */ - private PrinterJob printerJob; - - /** - * The size of the page being used for the PrintJob. - */ - private PageFormat pageFormat; - - /** - * The PrinterJob and the application run on different - * threads and communicate through a pair of message - * queues. This queue is the list of Graphics that - * the PrinterJob has requested rendering for, but - * for which the application has not yet called getGraphics(). - * In practice the length of this message queue is always - * 0 or 1. - */ - private MessageQ graphicsToBeDrawn = new MessageQ("tobedrawn"); - - /** - * Used to communicate between the application's thread - * and the PrinterJob's thread this message queue holds - * the list of Graphics into which the application has - * finished drawing, but that have not yet been returned - * to the PrinterJob thread. Again, in practice, the - * length of this message queue is always 0 or 1. - */ - private MessageQ graphicsDrawn = new MessageQ("drawn"); - - /** - * The last Graphics returned to the application via - * getGraphics. This is the Graphics into which the - * application is currently drawing. - */ - private Graphics2D currentGraphics; - - /** - * The zero based index of the page currently being rendered - * by the application. - */ - private int pageIndex = -1; - - // The following Strings are maintained for backward-compatibility with - // Properties based print control. - private static final String DEST_PROP = "awt.print.destination"; - private static final String PRINTER = "printer"; - private static final String FILE = "file"; - - private static final String PRINTER_PROP = "awt.print.printer"; - - private static final String FILENAME_PROP = "awt.print.fileName"; - - private static final String NUMCOPIES_PROP = "awt.print.numCopies"; - - private static final String OPTIONS_PROP = "awt.print.options"; - - private static final String ORIENT_PROP = "awt.print.orientation"; - private static final String PORTRAIT = "portrait"; - private static final String LANDSCAPE = "landscape"; - - private static final String PAPERSIZE_PROP = "awt.print.paperSize"; - private static final String LETTER = "letter"; - private static final String LEGAL = "legal"; - private static final String EXECUTIVE = "executive"; - private static final String A4 = "a4"; - - private Properties props; - - private String options = ""; // REMIND: needs implementation - - /** - * The thread on which PrinterJob is running. - * This is different than the applications thread. - */ - private Thread printerJobThread; + private final PrintJobDelegate printJobDelegate; public PrintJob2D(Frame frame, String doctitle, final Properties props) { - this.props = props; - this.jobAttributes = new JobAttributes(); - this.pageAttributes = new PageAttributes(); - translateInputProps(); - initPrintJob2D(frame, doctitle, - this.jobAttributes, this.pageAttributes); + printJobDelegate = new PrintJobDelegate(frame, doctitle, props); + Disposer.addRecord(this, new PrintJobDisposerRecord(printJobDelegate)); } public PrintJob2D(Frame frame, String doctitle, JobAttributes jobAttributes, PageAttributes pageAttributes) { - initPrintJob2D(frame, doctitle, jobAttributes, pageAttributes); + printJobDelegate = new PrintJobDelegate(frame, doctitle, jobAttributes, pageAttributes); + Disposer.addRecord(this, new PrintJobDisposerRecord(printJobDelegate)); } - private void initPrintJob2D(Frame frame, String doctitle, - JobAttributes jobAttributes, - PageAttributes pageAttributes) { - - if (frame == null && - (jobAttributes == null || - jobAttributes.getDialog() == DialogType.NATIVE)) { - throw new NullPointerException("Frame must not be null"); - } - this.frame = frame; - - this.docTitle = (doctitle == null) ? "" : doctitle; - this.jobAttributes = (jobAttributes != null) - ? jobAttributes : new JobAttributes(); - this.pageAttributes = (pageAttributes != null) - ? pageAttributes : new PageAttributes(); - - // Currently, we always reduce page ranges to xxx or xxx-xxx - int[][] pageRanges = this.jobAttributes.getPageRanges(); - int first = pageRanges[0][0]; - int last = pageRanges[pageRanges.length - 1][1]; - this.jobAttributes.setPageRanges(new int[][] { - new int[] { first, last } - }); - this.jobAttributes.setToPage(last); - this.jobAttributes.setFromPage(first); - - - // Verify that the cross feed and feed resolutions are the same - int[] res = this.pageAttributes.getPrinterResolution(); - if (res[0] != res[1]) { - throw new IllegalArgumentException("Differing cross feed and feed"+ - " resolutions not supported."); - } - - // Verify that the app has access to the file system - DestinationType dest= this.jobAttributes.getDestination(); - if (dest == DestinationType.FILE) { - - // check if given filename is valid - String destStr = jobAttributes.getFileName(); - if ((destStr != null) && - (jobAttributes.getDialog() == JobAttributes.DialogType.NONE)) { - - File f = new File(destStr); - try { - // check if this is a new file and if filename chars are valid - // createNewFile returns false if file exists - if (f.createNewFile()) { - f.delete(); - } - } catch (IOException ioe) { - throw new IllegalArgumentException("Cannot write to file:"+ - destStr); - } - - File pFile = f.getParentFile(); - if ((f.exists() && - (!f.isFile() || !f.canWrite())) || - ((pFile != null) && - (!pFile.exists() || (pFile.exists() && !pFile.canWrite())))) { - throw new IllegalArgumentException("Cannot write to file:"+ - destStr); - } - } - } - } + // PrintJob2D API, not PrintJob public boolean printDialog() { - - boolean proceedWithPrint = false; - - printerJob = PrinterJob.getPrinterJob(); - if (printerJob == null) { - return false; - } - DialogType d = this.jobAttributes.getDialog(); - PrintService pServ = printerJob.getPrintService(); - if ((pServ == null) && (d == DialogType.NONE)){ - return false; - } - copyAttributes(pServ); - - DefaultSelectionType select = - this.jobAttributes.getDefaultSelection(); - if (select == DefaultSelectionType.RANGE) { - attributes.add(SunPageSelection.RANGE); - } else if (select == DefaultSelectionType.SELECTION) { - attributes.add(SunPageSelection.SELECTION); - } else { - attributes.add(SunPageSelection.ALL); - } - - if (frame != null) { - attributes.add(new DialogOwner(frame)); - } - - if ( d == DialogType.NONE) { - proceedWithPrint = true; - } else { - if (d == DialogType.NATIVE) { - attributes.add(DialogTypeSelection.NATIVE); - } else { // (d == DialogType.COMMON) - attributes.add(DialogTypeSelection.COMMON); - } - if (proceedWithPrint = printerJob.printDialog(attributes)) { - if (pServ == null) { - // Windows gives an option to install a service - // when it detects there are no printers so - // we make sure we get the updated print service. - pServ = printerJob.getPrintService(); - if (pServ == null) { - return false; - } - } - updateAttributes(); - translateOutputProps(); - } - } - - if (proceedWithPrint) { - - JobName jname = (JobName)attributes.get(JobName.class); - if (jname != null) { - printerJob.setJobName(jname.toString()); - } - - pageFormat = new PageFormat(); - - Media media = (Media)attributes.get(Media.class); - MediaSize mediaSize = null; - if (media instanceof MediaSizeName msn) { - mediaSize = MediaSize.getMediaSizeForName(msn); - } - - Paper p = pageFormat.getPaper(); - if (mediaSize != null) { - p.setSize(mediaSize.getX(MediaSize.INCH)*72.0, - mediaSize.getY(MediaSize.INCH)*72.0); - } - - if (pageAttributes.getOrigin()==OriginType.PRINTABLE) { - // AWT uses 1/4" borders by default - p.setImageableArea(18.0, 18.0, - p.getWidth()-36.0, - p.getHeight()-36.0); - } else { - p.setImageableArea(0.0,0.0,p.getWidth(),p.getHeight()); - } - - pageFormat.setPaper(p); - - OrientationRequested orient = - (OrientationRequested)attributes.get(OrientationRequested.class); - if (orient!= null && - orient == OrientationRequested.REVERSE_LANDSCAPE) { - pageFormat.setOrientation(PageFormat.REVERSE_LANDSCAPE); - } else if (orient == OrientationRequested.LANDSCAPE) { - pageFormat.setOrientation(PageFormat.LANDSCAPE); - } else { - pageFormat.setOrientation(PageFormat.PORTRAIT); - } - - PageRanges pageRangesAttr - = (PageRanges) attributes.get(PageRanges.class); - if (pageRangesAttr != null) { - // Get the PageRanges from print dialog. - int[][] range = pageRangesAttr.getMembers(); - - int prevFromPage = this.jobAttributes.getFromPage(); - int prevToPage = this.jobAttributes.getToPage(); - - int currFromPage = range[0][0]; - int currToPage = range[range.length - 1][1]; - - // if from < to update fromPage first followed by toPage - // else update toPage first followed by fromPage - if (currFromPage < prevToPage) { - this.jobAttributes.setFromPage(currFromPage); - this.jobAttributes.setToPage(currToPage); - } else { - this.jobAttributes.setToPage(currToPage); - this.jobAttributes.setFromPage(currFromPage); - } - } - printerJob.setPrintable(this, pageFormat); - - } - - return proceedWithPrint; - } - - private void updateAttributes() { - Copies c = (Copies)attributes.get(Copies.class); - jobAttributes.setCopies(c.getValue()); - - SunPageSelection sel = - (SunPageSelection)attributes.get(SunPageSelection.class); - if (sel == SunPageSelection.RANGE) { - jobAttributes.setDefaultSelection(DefaultSelectionType.RANGE); - } else if (sel == SunPageSelection.SELECTION) { - jobAttributes.setDefaultSelection(DefaultSelectionType.SELECTION); - } else { - jobAttributes.setDefaultSelection(DefaultSelectionType.ALL); - } - - Destination dest = (Destination)attributes.get(Destination.class); - if (dest != null) { - jobAttributes.setDestination(DestinationType.FILE); - jobAttributes.setFileName(dest.getURI().getPath()); - } else { - jobAttributes.setDestination(DestinationType.PRINTER); - } - - PrintService serv = printerJob.getPrintService(); - if (serv != null) { - jobAttributes.setPrinter(serv.getName()); - } - - PageRanges range = (PageRanges)attributes.get(PageRanges.class); - int[][] members = range.getMembers(); - jobAttributes.setPageRanges(members); - - SheetCollate collation = - (SheetCollate)attributes.get(SheetCollate.class); - if (collation == SheetCollate.COLLATED) { - jobAttributes.setMultipleDocumentHandling( - MultipleDocumentHandlingType.SEPARATE_DOCUMENTS_COLLATED_COPIES); - } else { - jobAttributes.setMultipleDocumentHandling( - MultipleDocumentHandlingType.SEPARATE_DOCUMENTS_UNCOLLATED_COPIES); - } - - Sides sides = (Sides)attributes.get(Sides.class); - if (sides == Sides.TWO_SIDED_LONG_EDGE) { - jobAttributes.setSides(SidesType.TWO_SIDED_LONG_EDGE); - } else if (sides == Sides.TWO_SIDED_SHORT_EDGE) { - jobAttributes.setSides(SidesType.TWO_SIDED_SHORT_EDGE); - } else { - jobAttributes.setSides(SidesType.ONE_SIDED); - } - - // PageAttributes - - Chromaticity color = - (Chromaticity)attributes.get(Chromaticity.class); - if (color == Chromaticity.COLOR) { - pageAttributes.setColor(ColorType.COLOR); - } else { - pageAttributes.setColor(ColorType.MONOCHROME); - } - - OrientationRequested orient = - (OrientationRequested)attributes.get(OrientationRequested.class); - if (orient == OrientationRequested.LANDSCAPE) { - pageAttributes.setOrientationRequested( - OrientationRequestedType.LANDSCAPE); - } else { - pageAttributes.setOrientationRequested( - OrientationRequestedType.PORTRAIT); - } - - PrintQuality qual = (PrintQuality)attributes.get(PrintQuality.class); - if (qual == PrintQuality.DRAFT) { - pageAttributes.setPrintQuality(PrintQualityType.DRAFT); - } else if (qual == PrintQuality.HIGH) { - pageAttributes.setPrintQuality(PrintQualityType.HIGH); - } else { // NORMAL - pageAttributes.setPrintQuality(PrintQualityType.NORMAL); - } - - Media media = (Media)attributes.get(Media.class); - if (media instanceof MediaSizeName msn) { - MediaType mType = unMapMedia(msn); - - if (mType != null) { - pageAttributes.setMedia(mType); - } - } - debugPrintAttributes(false, false); - } - - private void debugPrintAttributes(boolean ja, boolean pa ) { - if (ja) { - System.out.println("new Attributes\ncopies = "+ - jobAttributes.getCopies()+ - "\nselection = "+ - jobAttributes.getDefaultSelection()+ - "\ndest "+jobAttributes.getDestination()+ - "\nfile "+jobAttributes.getFileName()+ - "\nfromPage "+jobAttributes.getFromPage()+ - "\ntoPage "+jobAttributes.getToPage()+ - "\ncollation "+ - jobAttributes.getMultipleDocumentHandling()+ - "\nPrinter "+jobAttributes.getPrinter()+ - "\nSides2 "+jobAttributes.getSides() - ); - } - - if (pa) { - System.out.println("new Attributes\ncolor = "+ - pageAttributes.getColor()+ - "\norientation = "+ - pageAttributes.getOrientationRequested()+ - "\nquality "+pageAttributes.getPrintQuality()+ - "\nMedia2 "+pageAttributes.getMedia() - ); - } - } - - - /* From JobAttributes we will copy job name and duplex printing - * and destination. - * The majority of the rest of the attributes are reflected - * attributes. - * - * From PageAttributes we copy color, media size, orientation, - * origin type, resolution and print quality. - * We use the media, orientation in creating the page format, and - * the origin type to set its imageable area. - * - * REMIND: Interpretation of resolution, additional media sizes. - */ - private void copyAttributes(PrintService printServ) { - - attributes = new HashPrintRequestAttributeSet(); - attributes.add(new JobName(docTitle, null)); - PrintService pServ = printServ; - - String printerName = jobAttributes.getPrinter(); - if (printerName != null && printerName != "" - && pServ != null && !printerName.equals(pServ.getName())) { - - // Search for the given printerName in the list of PrintServices - PrintService []services = PrinterJob.lookupPrintServices(); - try { - for (int i=0; i queue = new ArrayList<>(); - - MessageQ(String id) { - qid = id; - } - - synchronized void closeWhenEmpty() { - - while (queue != null && queue.size() > 0) { - try { - wait(1000); - } catch (InterruptedException e) { - // do nothing. - } - } - - queue = null; - notifyAll(); - } - - synchronized void close() { - queue = null; - notifyAll(); - } - - synchronized boolean append(Graphics2D g) { - - boolean queued = false; - - if (queue != null) { - queue.add(g); - queued = true; - notify(); - } - - return queued; - } - - synchronized Graphics2D pop() { - Graphics2D g = null; - - while (g == null && queue != null) { - - if (queue.size() > 0) { - g = queue.remove(0); - notify(); - - } else { - try { - wait(2000); - } catch (InterruptedException e) { - // do nothing. - } - } - } - - return g; - } - - synchronized boolean isClosed() { - return queue == null; - } - - } - - - private static int[] getSize(MediaType mType) { - int []dim = new int[2]; - dim[0] = 612; - dim[1] = 792; - - for (int i=0; i < SIZES.length; i++) { - if (SIZES[i] == mType) { - dim[0] = WIDTHS[i]; - dim[1] = LENGTHS[i]; - break; - } - } - return dim; - } - - public static MediaSizeName mapMedia(MediaType mType) { - MediaSizeName media = null; - - // JAVAXSIZES.length and SIZES.length must be equal! - // Attempt to recover by getting the smaller size. - int length = Math.min(SIZES.length, JAVAXSIZES.length); - - for (int i=0; i < length; i++) { - if (SIZES[i] == mType) { - if ((JAVAXSIZES[i] != null) && - MediaSize.getMediaSizeForName(JAVAXSIZES[i]) != null) { - media = JAVAXSIZES[i]; - break; - } else { - /* create Custom Media */ - media = new CustomMediaSizeName(SIZES[i].toString()); - - float w = (float)Math.rint(WIDTHS[i] / 72.0); - float h = (float)Math.rint(LENGTHS[i] / 72.0); - if (w > 0.0 && h > 0.0) { - // add new created MediaSize to our static map - // so it will be found when we call findMedia - new MediaSize(w, h, Size2DSyntax.INCH, media); - } - - break; - } - } - } - return media; - } - - - public static MediaType unMapMedia(MediaSizeName mSize) { - MediaType media = null; - - // JAVAXSIZES.length and SIZES.length must be equal! - // Attempt to recover by getting the smaller size. - int length = Math.min(SIZES.length, JAVAXSIZES.length); - - for (int i=0; i < length; i++) { - if (JAVAXSIZES[i] == mSize) { - if (SIZES[i] != null) { - media = SIZES[i]; - break; - } - } - } - return media; - } - - private void translateInputProps() { - if (props == null) { - return; - } - - String str; - - str = props.getProperty(DEST_PROP); - if (str != null) { - if (str.equals(PRINTER)) { - jobAttributes.setDestination(DestinationType.PRINTER); - } else if (str.equals(FILE)) { - jobAttributes.setDestination(DestinationType.FILE); - } - } - str = props.getProperty(PRINTER_PROP); - if (str != null) { - jobAttributes.setPrinter(str); - } - str = props.getProperty(FILENAME_PROP); - if (str != null) { - jobAttributes.setFileName(str); - } - str = props.getProperty(NUMCOPIES_PROP); - if (str != null) { - jobAttributes.setCopies(Integer.parseInt(str)); - } - - this.options = props.getProperty(OPTIONS_PROP, ""); - - str = props.getProperty(ORIENT_PROP); - if (str != null) { - if (str.equals(PORTRAIT)) { - pageAttributes.setOrientationRequested( - OrientationRequestedType.PORTRAIT); - } else if (str.equals(LANDSCAPE)) { - pageAttributes.setOrientationRequested( - OrientationRequestedType.LANDSCAPE); - } - } - str = props.getProperty(PAPERSIZE_PROP); - if (str != null) { - if (str.equals(LETTER)) { - pageAttributes.setMedia(SIZES[MediaType.LETTER.hashCode()]); - } else if (str.equals(LEGAL)) { - pageAttributes.setMedia(SIZES[MediaType.LEGAL.hashCode()]); - } else if (str.equals(EXECUTIVE)) { - pageAttributes.setMedia(SIZES[MediaType.EXECUTIVE.hashCode()]); - } else if (str.equals(A4)) { - pageAttributes.setMedia(SIZES[MediaType.A4.hashCode()]); - } + public void dispose() { + printJobDelegate.end(); } } - - private void translateOutputProps() { - if (props == null) { - return; - } - - String str; - - props.setProperty(DEST_PROP, - (jobAttributes.getDestination() == DestinationType.PRINTER) ? - PRINTER : FILE); - str = jobAttributes.getPrinter(); - if (str != null && !str.isEmpty()) { - props.setProperty(PRINTER_PROP, str); - } - str = jobAttributes.getFileName(); - if (str != null && !str.isEmpty()) { - props.setProperty(FILENAME_PROP, str); - } - int copies = jobAttributes.getCopies(); - if (copies > 0) { - props.setProperty(NUMCOPIES_PROP, "" + copies); - } - str = this.options; - if (str != null && !str.isEmpty()) { - props.setProperty(OPTIONS_PROP, str); - } - props.setProperty(ORIENT_PROP, - (pageAttributes.getOrientationRequested() == - OrientationRequestedType.PORTRAIT) - ? PORTRAIT : LANDSCAPE); - MediaType media = SIZES[pageAttributes.getMedia().hashCode()]; - if (media == MediaType.LETTER) { - str = LETTER; - } else if (media == MediaType.LEGAL) { - str = LEGAL; - } else if (media == MediaType.EXECUTIVE) { - str = EXECUTIVE; - } else if (media == MediaType.A4) { - str = A4; - } else { - str = media.toString(); - } - props.setProperty(PAPERSIZE_PROP, str); - } - } diff --git a/src/java.desktop/share/classes/sun/print/PrintJobDelegate.java b/src/java.desktop/share/classes/sun/print/PrintJobDelegate.java new file mode 100644 index 00000000000..5a88d4b9d45 --- /dev/null +++ b/src/java.desktop/share/classes/sun/print/PrintJobDelegate.java @@ -0,0 +1,1227 @@ +/* + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.print; + +import java.awt.Dimension; +import java.awt.Frame; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.PrintJob; +import java.awt.JobAttributes; +import java.awt.JobAttributes.*; +import java.awt.PageAttributes; +import java.awt.PageAttributes.*; + +import java.awt.print.PageFormat; +import java.awt.print.Paper; +import java.awt.print.Printable; +import java.awt.print.PrinterException; +import java.awt.print.PrinterJob; + +import java.io.File; +import java.io.IOException; + +import java.net.URI; +import java.net.URISyntaxException; + +import java.util.ArrayList; +import java.util.Properties; + +import javax.print.PrintService; +import javax.print.attribute.HashPrintRequestAttributeSet; +import javax.print.attribute.PrintRequestAttributeSet; +import javax.print.attribute.Size2DSyntax; +import javax.print.attribute.standard.Chromaticity; +import javax.print.attribute.standard.Copies; +import javax.print.attribute.standard.Destination; +import javax.print.attribute.standard.DialogTypeSelection; +import javax.print.attribute.standard.DialogOwner; +import javax.print.attribute.standard.JobName; +import javax.print.attribute.standard.MediaSize; +import javax.print.attribute.standard.PrintQuality; +import javax.print.attribute.standard.SheetCollate; +import javax.print.attribute.standard.Sides; +import javax.print.attribute.standard.Media; +import javax.print.attribute.standard.OrientationRequested; +import javax.print.attribute.standard.MediaSizeName; +import javax.print.attribute.standard.PageRanges; + + +/** + * A class which initiates and executes a print job using + * the underlying PrinterJob graphics conversions. + * + * @see java.awt.Toolkit#getPrintJob + * + */ +public class PrintJobDelegate implements Printable, Runnable { + + private static final MediaType[] SIZES = { + MediaType.ISO_4A0, MediaType.ISO_2A0, MediaType.ISO_A0, + MediaType.ISO_A1, MediaType.ISO_A2, MediaType.ISO_A3, + MediaType.ISO_A4, MediaType.ISO_A5, MediaType.ISO_A6, + MediaType.ISO_A7, MediaType.ISO_A8, MediaType.ISO_A9, + MediaType.ISO_A10, MediaType.ISO_B0, MediaType.ISO_B1, + MediaType.ISO_B2, MediaType.ISO_B3, MediaType.ISO_B4, + MediaType.ISO_B5, MediaType.ISO_B6, MediaType.ISO_B7, + MediaType.ISO_B8, MediaType.ISO_B9, MediaType.ISO_B10, + MediaType.JIS_B0, MediaType.JIS_B1, MediaType.JIS_B2, + MediaType.JIS_B3, MediaType.JIS_B4, MediaType.JIS_B5, + MediaType.JIS_B6, MediaType.JIS_B7, MediaType.JIS_B8, + MediaType.JIS_B9, MediaType.JIS_B10, MediaType.ISO_C0, + MediaType.ISO_C1, MediaType.ISO_C2, MediaType.ISO_C3, + MediaType.ISO_C4, MediaType.ISO_C5, MediaType.ISO_C6, + MediaType.ISO_C7, MediaType.ISO_C8, MediaType.ISO_C9, + MediaType.ISO_C10, MediaType.ISO_DESIGNATED_LONG, + MediaType.EXECUTIVE, MediaType.FOLIO, MediaType.INVOICE, + MediaType.LEDGER, MediaType.NA_LETTER, MediaType.NA_LEGAL, + MediaType.QUARTO, MediaType.A, MediaType.B, + MediaType.C, MediaType.D, MediaType.E, + MediaType.NA_10X15_ENVELOPE, MediaType.NA_10X14_ENVELOPE, + MediaType.NA_10X13_ENVELOPE, MediaType.NA_9X12_ENVELOPE, + MediaType.NA_9X11_ENVELOPE, MediaType.NA_7X9_ENVELOPE, + MediaType.NA_6X9_ENVELOPE, MediaType.NA_NUMBER_9_ENVELOPE, + MediaType.NA_NUMBER_10_ENVELOPE, MediaType.NA_NUMBER_11_ENVELOPE, + MediaType.NA_NUMBER_12_ENVELOPE, MediaType.NA_NUMBER_14_ENVELOPE, + MediaType.INVITE_ENVELOPE, MediaType.ITALY_ENVELOPE, + MediaType.MONARCH_ENVELOPE, MediaType.PERSONAL_ENVELOPE + }; + + /* This array maps the above array to the objects used by the + * javax.print APIs + */ + private static final MediaSizeName[] JAVAXSIZES = { + null, null, MediaSizeName.ISO_A0, + MediaSizeName.ISO_A1, MediaSizeName.ISO_A2, MediaSizeName.ISO_A3, + MediaSizeName.ISO_A4, MediaSizeName.ISO_A5, MediaSizeName.ISO_A6, + MediaSizeName.ISO_A7, MediaSizeName.ISO_A8, MediaSizeName.ISO_A9, + MediaSizeName.ISO_A10, MediaSizeName.ISO_B0, MediaSizeName.ISO_B1, + MediaSizeName.ISO_B2, MediaSizeName.ISO_B3, MediaSizeName.ISO_B4, + MediaSizeName.ISO_B5, MediaSizeName.ISO_B6, MediaSizeName.ISO_B7, + MediaSizeName.ISO_B8, MediaSizeName.ISO_B9, MediaSizeName.ISO_B10, + MediaSizeName.JIS_B0, MediaSizeName.JIS_B1, MediaSizeName.JIS_B2, + MediaSizeName.JIS_B3, MediaSizeName.JIS_B4, MediaSizeName.JIS_B5, + MediaSizeName.JIS_B6, MediaSizeName.JIS_B7, MediaSizeName.JIS_B8, + MediaSizeName.JIS_B9, MediaSizeName.JIS_B10, MediaSizeName.ISO_C0, + MediaSizeName.ISO_C1, MediaSizeName.ISO_C2, MediaSizeName.ISO_C3, + MediaSizeName.ISO_C4, MediaSizeName.ISO_C5, MediaSizeName.ISO_C6, + null, null, null, null, + MediaSizeName.ISO_DESIGNATED_LONG, MediaSizeName.EXECUTIVE, + MediaSizeName.FOLIO, MediaSizeName.INVOICE, MediaSizeName.LEDGER, + MediaSizeName.NA_LETTER, MediaSizeName.NA_LEGAL, + MediaSizeName.QUARTO, MediaSizeName.A, MediaSizeName.B, + MediaSizeName.C, MediaSizeName.D, MediaSizeName.E, + MediaSizeName.NA_10X15_ENVELOPE, MediaSizeName.NA_10X14_ENVELOPE, + MediaSizeName.NA_10X13_ENVELOPE, MediaSizeName.NA_9X12_ENVELOPE, + MediaSizeName.NA_9X11_ENVELOPE, MediaSizeName.NA_7X9_ENVELOPE, + MediaSizeName.NA_6X9_ENVELOPE, + MediaSizeName.NA_NUMBER_9_ENVELOPE, + MediaSizeName.NA_NUMBER_10_ENVELOPE, + MediaSizeName.NA_NUMBER_11_ENVELOPE, + MediaSizeName.NA_NUMBER_12_ENVELOPE, + MediaSizeName.NA_NUMBER_14_ENVELOPE, + null, MediaSizeName.ITALY_ENVELOPE, + MediaSizeName.MONARCH_ENVELOPE, MediaSizeName.PERSONAL_ENVELOPE, + }; + + + // widths and lengths in PostScript points (1/72 in.) + private static final int[] WIDTHS = { + /*iso-4a0*/ 4768, /*iso-2a0*/ 3370, /*iso-a0*/ 2384, /*iso-a1*/ 1684, + /*iso-a2*/ 1191, /*iso-a3*/ 842, /*iso-a4*/ 595, /*iso-a5*/ 420, + /*iso-a6*/ 298, /*iso-a7*/ 210, /*iso-a8*/ 147, /*iso-a9*/ 105, + /*iso-a10*/ 74, /*iso-b0*/ 2835, /*iso-b1*/ 2004, /*iso-b2*/ 1417, + /*iso-b3*/ 1001, /*iso-b4*/ 709, /*iso-b5*/ 499, /*iso-b6*/ 354, + /*iso-b7*/ 249, /*iso-b8*/ 176, /*iso-b9*/ 125, /*iso-b10*/ 88, + /*jis-b0*/ 2920, /*jis-b1*/ 2064, /*jis-b2*/ 1460, /*jis-b3*/ 1032, + /*jis-b4*/ 729, /*jis-b5*/ 516, /*jis-b6*/ 363, /*jis-b7*/ 258, + /*jis-b8*/ 181, /*jis-b9*/ 128, /*jis-b10*/ 91, /*iso-c0*/ 2599, + /*iso-c1*/ 1837, /*iso-c2*/ 1298, /*iso-c3*/ 918, /*iso-c4*/ 649, + /*iso-c5*/ 459, /*iso-c6*/ 323, /*iso-c7*/ 230, /*iso-c8*/ 162, + /*iso-c9*/ 113, /*iso-c10*/ 79, /*iso-designated-long*/ 312, + /*executive*/ 522, /*folio*/ 612, /*invoice*/ 396, /*ledger*/ 792, + /*na-letter*/ 612, /*na-legal*/ 612, /*quarto*/ 609, /*a*/ 612, + /*b*/ 792, /*c*/ 1224, /*d*/ 1584, /*e*/ 2448, + /*na-10x15-envelope*/ 720, /*na-10x14-envelope*/ 720, + /*na-10x13-envelope*/ 720, /*na-9x12-envelope*/ 648, + /*na-9x11-envelope*/ 648, /*na-7x9-envelope*/ 504, + /*na-6x9-envelope*/ 432, /*na-number-9-envelope*/ 279, + /*na-number-10-envelope*/ 297, /*na-number-11-envelope*/ 324, + /*na-number-12-envelope*/ 342, /*na-number-14-envelope*/ 360, + /*invite-envelope*/ 624, /*italy-envelope*/ 312, + /*monarch-envelope*/ 279, /*personal-envelope*/ 261 + }; + private static final int[] LENGTHS = { + /*iso-4a0*/ 6741, /*iso-2a0*/ 4768, /*iso-a0*/ 3370, /*iso-a1*/ 2384, + /*iso-a2*/ 1684, /*iso-a3*/ 1191, /*iso-a4*/ 842, /*iso-a5*/ 595, + /*iso-a6*/ 420, /*iso-a7*/ 298, /*iso-a8*/ 210, /*iso-a9*/ 147, + /*iso-a10*/ 105, /*iso-b0*/ 4008, /*iso-b1*/ 2835, /*iso-b2*/ 2004, + /*iso-b3*/ 1417, /*iso-b4*/ 1001, /*iso-b5*/ 729, /*iso-b6*/ 499, + /*iso-b7*/ 354, /*iso-b8*/ 249, /*iso-b9*/ 176, /*iso-b10*/ 125, + /*jis-b0*/ 4127, /*jis-b1*/ 2920, /*jis-b2*/ 2064, /*jis-b3*/ 1460, + /*jis-b4*/ 1032, /*jis-b5*/ 729, /*jis-b6*/ 516, /*jis-b7*/ 363, + /*jis-b8*/ 258, /*jis-b9*/ 181, /*jis-b10*/ 128, /*iso-c0*/ 3677, + /*iso-c1*/ 2599, /*iso-c2*/ 1837, /*iso-c3*/ 1298, /*iso-c4*/ 918, + /*iso-c5*/ 649, /*iso-c6*/ 459, /*iso-c7*/ 323, /*iso-c8*/ 230, + /*iso-c9*/ 162, /*iso-c10*/ 113, /*iso-designated-long*/ 624, + /*executive*/ 756, /*folio*/ 936, /*invoice*/ 612, /*ledger*/ 1224, + /*na-letter*/ 792, /*na-legal*/ 1008, /*quarto*/ 780, /*a*/ 792, + /*b*/ 1224, /*c*/ 1584, /*d*/ 2448, /*e*/ 3168, + /*na-10x15-envelope*/ 1080, /*na-10x14-envelope*/ 1008, + /*na-10x13-envelope*/ 936, /*na-9x12-envelope*/ 864, + /*na-9x11-envelope*/ 792, /*na-7x9-envelope*/ 648, + /*na-6x9-envelope*/ 648, /*na-number-9-envelope*/ 639, + /*na-number-10-envelope*/ 684, /*na-number-11-envelope*/ 747, + /*na-number-12-envelope*/ 792, /*na-number-14-envelope*/ 828, + /*invite-envelope*/ 624, /*italy-envelope*/ 652, + /*monarch-envelope*/ 540, /*personal-envelope*/ 468 + }; + + + private Frame frame; + private String docTitle = ""; + private JobAttributes jobAttributes; + private PageAttributes pageAttributes; + private PrintRequestAttributeSet attributes; + + /* + * Displays the native or cross-platform dialog and allows the + * user to update job & page attributes + */ + + /** + * The PrinterJob being uses to implement the PrintJob. + */ + private PrinterJob printerJob; + + /** + * The size of the page being used for the PrintJob. + */ + private PageFormat pageFormat; + + /** + * The PrinterJob and the application run on different + * threads and communicate through a pair of message + * queues. This queue is the list of Graphics that + * the PrinterJob has requested rendering for, but + * for which the application has not yet called getGraphics(). + * In practice the length of this message queue is always + * 0 or 1. + */ + private MessageQ graphicsToBeDrawn = new MessageQ("tobedrawn"); + + /** + * Used to communicate between the application's thread + * and the PrinterJob's thread this message queue holds + * the list of Graphics into which the application has + * finished drawing, but that have not yet been returned + * to the PrinterJob thread. Again, in practice, the + * length of this message queue is always 0 or 1. + */ + private MessageQ graphicsDrawn = new MessageQ("drawn"); + + /** + * The last Graphics returned to the application via + * getGraphics. This is the Graphics into which the + * application is currently drawing. + */ + private Graphics2D currentGraphics; + + /** + * The zero based index of the page currently being rendered + * by the application. + */ + private int pageIndex = -1; + + // The following Strings are maintained for backward-compatibility with + // Properties based print control. + private static final String DEST_PROP = "awt.print.destination"; + private static final String PRINTER = "printer"; + private static final String FILE = "file"; + + private static final String PRINTER_PROP = "awt.print.printer"; + + private static final String FILENAME_PROP = "awt.print.fileName"; + + private static final String NUMCOPIES_PROP = "awt.print.numCopies"; + + private static final String OPTIONS_PROP = "awt.print.options"; + + private static final String ORIENT_PROP = "awt.print.orientation"; + private static final String PORTRAIT = "portrait"; + private static final String LANDSCAPE = "landscape"; + + private static final String PAPERSIZE_PROP = "awt.print.paperSize"; + private static final String LETTER = "letter"; + private static final String LEGAL = "legal"; + private static final String EXECUTIVE = "executive"; + private static final String A4 = "a4"; + + private Properties props; + + private String options = ""; // REMIND: needs implementation + + /** + * The thread on which PrinterJob is running. + * This is different than the applications thread. + */ + private Thread printerJobThread; + + public PrintJobDelegate(Frame frame, String doctitle, + final Properties props) { + this.props = props; + this.jobAttributes = new JobAttributes(); + this.pageAttributes = new PageAttributes(); + translateInputProps(); + initPrintJob2D(frame, doctitle, + this.jobAttributes, this.pageAttributes); + } + + public PrintJobDelegate(Frame frame, String doctitle, + JobAttributes jobAttributes, + PageAttributes pageAttributes) { + initPrintJob2D(frame, doctitle, jobAttributes, pageAttributes); + } + + private void initPrintJob2D(Frame frame, String doctitle, + JobAttributes jobAttributes, + PageAttributes pageAttributes) { + + if (frame == null && + (jobAttributes == null || + jobAttributes.getDialog() == DialogType.NATIVE)) { + throw new NullPointerException("Frame must not be null"); + } + this.frame = frame; + + this.docTitle = (doctitle == null) ? "" : doctitle; + this.jobAttributes = (jobAttributes != null) + ? jobAttributes : new JobAttributes(); + this.pageAttributes = (pageAttributes != null) + ? pageAttributes : new PageAttributes(); + + // Currently, we always reduce page ranges to xxx or xxx-xxx + int[][] pageRanges = this.jobAttributes.getPageRanges(); + int first = pageRanges[0][0]; + int last = pageRanges[pageRanges.length - 1][1]; + this.jobAttributes.setPageRanges(new int[][] { + new int[] { first, last } + }); + this.jobAttributes.setToPage(last); + this.jobAttributes.setFromPage(first); + + + // Verify that the cross feed and feed resolutions are the same + int[] res = this.pageAttributes.getPrinterResolution(); + if (res[0] != res[1]) { + throw new IllegalArgumentException("Differing cross feed and feed"+ + " resolutions not supported."); + } + + // Verify that the app has access to the file system + DestinationType dest= this.jobAttributes.getDestination(); + if (dest == DestinationType.FILE) { + + // check if given filename is valid + String destStr = jobAttributes.getFileName(); + if ((destStr != null) && + (jobAttributes.getDialog() == JobAttributes.DialogType.NONE)) { + + File f = new File(destStr); + try { + // check if this is a new file and if filename chars are valid + // createNewFile returns false if file exists + if (f.createNewFile()) { + f.delete(); + } + } catch (IOException ioe) { + throw new IllegalArgumentException("Cannot write to file:"+ + destStr); + } + + File pFile = f.getParentFile(); + if ((f.exists() && + (!f.isFile() || !f.canWrite())) || + ((pFile != null) && + (!pFile.exists() || (pFile.exists() && !pFile.canWrite())))) { + throw new IllegalArgumentException("Cannot write to file:"+ + destStr); + } + } + } + } + + public boolean printDialog() { + + boolean proceedWithPrint = false; + + printerJob = PrinterJob.getPrinterJob(); + if (printerJob == null) { + return false; + } + DialogType d = this.jobAttributes.getDialog(); + PrintService pServ = printerJob.getPrintService(); + if ((pServ == null) && (d == DialogType.NONE)){ + return false; + } + copyAttributes(pServ); + + DefaultSelectionType select = + this.jobAttributes.getDefaultSelection(); + if (select == DefaultSelectionType.RANGE) { + attributes.add(SunPageSelection.RANGE); + } else if (select == DefaultSelectionType.SELECTION) { + attributes.add(SunPageSelection.SELECTION); + } else { + attributes.add(SunPageSelection.ALL); + } + + if (frame != null) { + attributes.add(new DialogOwner(frame)); + } + + if ( d == DialogType.NONE) { + proceedWithPrint = true; + } else { + if (d == DialogType.NATIVE) { + attributes.add(DialogTypeSelection.NATIVE); + } else { // (d == DialogType.COMMON) + attributes.add(DialogTypeSelection.COMMON); + } + if (proceedWithPrint = printerJob.printDialog(attributes)) { + if (pServ == null) { + // Windows gives an option to install a service + // when it detects there are no printers so + // we make sure we get the updated print service. + pServ = printerJob.getPrintService(); + if (pServ == null) { + return false; + } + } + updateAttributes(); + translateOutputProps(); + } + } + + if (proceedWithPrint) { + + JobName jname = (JobName)attributes.get(JobName.class); + if (jname != null) { + printerJob.setJobName(jname.toString()); + } + + pageFormat = new PageFormat(); + + Media media = (Media)attributes.get(Media.class); + MediaSize mediaSize = null; + if (media instanceof MediaSizeName msn) { + mediaSize = MediaSize.getMediaSizeForName(msn); + } + + Paper p = pageFormat.getPaper(); + if (mediaSize != null) { + p.setSize(mediaSize.getX(MediaSize.INCH)*72.0, + mediaSize.getY(MediaSize.INCH)*72.0); + } + + if (pageAttributes.getOrigin()==OriginType.PRINTABLE) { + // AWT uses 1/4" borders by default + p.setImageableArea(18.0, 18.0, + p.getWidth()-36.0, + p.getHeight()-36.0); + } else { + p.setImageableArea(0.0,0.0,p.getWidth(),p.getHeight()); + } + + pageFormat.setPaper(p); + + OrientationRequested orient = + (OrientationRequested)attributes.get(OrientationRequested.class); + if (orient!= null && + orient == OrientationRequested.REVERSE_LANDSCAPE) { + pageFormat.setOrientation(PageFormat.REVERSE_LANDSCAPE); + } else if (orient == OrientationRequested.LANDSCAPE) { + pageFormat.setOrientation(PageFormat.LANDSCAPE); + } else { + pageFormat.setOrientation(PageFormat.PORTRAIT); + } + + PageRanges pageRangesAttr + = (PageRanges) attributes.get(PageRanges.class); + if (pageRangesAttr != null) { + // Get the PageRanges from print dialog. + int[][] range = pageRangesAttr.getMembers(); + + int prevFromPage = this.jobAttributes.getFromPage(); + int prevToPage = this.jobAttributes.getToPage(); + + int currFromPage = range[0][0]; + int currToPage = range[range.length - 1][1]; + + // if from < to update fromPage first followed by toPage + // else update toPage first followed by fromPage + if (currFromPage < prevToPage) { + this.jobAttributes.setFromPage(currFromPage); + this.jobAttributes.setToPage(currToPage); + } else { + this.jobAttributes.setToPage(currToPage); + this.jobAttributes.setFromPage(currFromPage); + } + } + printerJob.setPrintable(this, pageFormat); + + } + + return proceedWithPrint; + } + + private void updateAttributes() { + Copies c = (Copies)attributes.get(Copies.class); + jobAttributes.setCopies(c.getValue()); + + SunPageSelection sel = + (SunPageSelection)attributes.get(SunPageSelection.class); + if (sel == SunPageSelection.RANGE) { + jobAttributes.setDefaultSelection(DefaultSelectionType.RANGE); + } else if (sel == SunPageSelection.SELECTION) { + jobAttributes.setDefaultSelection(DefaultSelectionType.SELECTION); + } else { + jobAttributes.setDefaultSelection(DefaultSelectionType.ALL); + } + + Destination dest = (Destination)attributes.get(Destination.class); + if (dest != null) { + jobAttributes.setDestination(DestinationType.FILE); + jobAttributes.setFileName(dest.getURI().getPath()); + } else { + jobAttributes.setDestination(DestinationType.PRINTER); + } + + PrintService serv = printerJob.getPrintService(); + if (serv != null) { + jobAttributes.setPrinter(serv.getName()); + } + + PageRanges range = (PageRanges)attributes.get(PageRanges.class); + int[][] members = range.getMembers(); + jobAttributes.setPageRanges(members); + + SheetCollate collation = + (SheetCollate)attributes.get(SheetCollate.class); + if (collation == SheetCollate.COLLATED) { + jobAttributes.setMultipleDocumentHandling( + MultipleDocumentHandlingType.SEPARATE_DOCUMENTS_COLLATED_COPIES); + } else { + jobAttributes.setMultipleDocumentHandling( + MultipleDocumentHandlingType.SEPARATE_DOCUMENTS_UNCOLLATED_COPIES); + } + + Sides sides = (Sides)attributes.get(Sides.class); + if (sides == Sides.TWO_SIDED_LONG_EDGE) { + jobAttributes.setSides(SidesType.TWO_SIDED_LONG_EDGE); + } else if (sides == Sides.TWO_SIDED_SHORT_EDGE) { + jobAttributes.setSides(SidesType.TWO_SIDED_SHORT_EDGE); + } else { + jobAttributes.setSides(SidesType.ONE_SIDED); + } + + // PageAttributes + + Chromaticity color = + (Chromaticity)attributes.get(Chromaticity.class); + if (color == Chromaticity.COLOR) { + pageAttributes.setColor(ColorType.COLOR); + } else { + pageAttributes.setColor(ColorType.MONOCHROME); + } + + OrientationRequested orient = + (OrientationRequested)attributes.get(OrientationRequested.class); + if (orient == OrientationRequested.LANDSCAPE) { + pageAttributes.setOrientationRequested( + OrientationRequestedType.LANDSCAPE); + } else { + pageAttributes.setOrientationRequested( + OrientationRequestedType.PORTRAIT); + } + + PrintQuality qual = (PrintQuality)attributes.get(PrintQuality.class); + if (qual == PrintQuality.DRAFT) { + pageAttributes.setPrintQuality(PrintQualityType.DRAFT); + } else if (qual == PrintQuality.HIGH) { + pageAttributes.setPrintQuality(PrintQualityType.HIGH); + } else { // NORMAL + pageAttributes.setPrintQuality(PrintQualityType.NORMAL); + } + + Media media = (Media)attributes.get(Media.class); + if (media instanceof MediaSizeName msn) { + MediaType mType = unMapMedia(msn); + + if (mType != null) { + pageAttributes.setMedia(mType); + } + } + debugPrintAttributes(false, false); + } + + private void debugPrintAttributes(boolean ja, boolean pa ) { + if (ja) { + System.out.println("new Attributes\ncopies = "+ + jobAttributes.getCopies()+ + "\nselection = "+ + jobAttributes.getDefaultSelection()+ + "\ndest "+jobAttributes.getDestination()+ + "\nfile "+jobAttributes.getFileName()+ + "\nfromPage "+jobAttributes.getFromPage()+ + "\ntoPage "+jobAttributes.getToPage()+ + "\ncollation "+ + jobAttributes.getMultipleDocumentHandling()+ + "\nPrinter "+jobAttributes.getPrinter()+ + "\nSides2 "+jobAttributes.getSides() + ); + } + + if (pa) { + System.out.println("new Attributes\ncolor = "+ + pageAttributes.getColor()+ + "\norientation = "+ + pageAttributes.getOrientationRequested()+ + "\nquality "+pageAttributes.getPrintQuality()+ + "\nMedia2 "+pageAttributes.getMedia() + ); + } + } + + + /* From JobAttributes we will copy job name and duplex printing + * and destination. + * The majority of the rest of the attributes are reflected + * attributes. + * + * From PageAttributes we copy color, media size, orientation, + * origin type, resolution and print quality. + * We use the media, orientation in creating the page format, and + * the origin type to set its imageable area. + * + * REMIND: Interpretation of resolution, additional media sizes. + */ + private void copyAttributes(PrintService printServ) { + + attributes = new HashPrintRequestAttributeSet(); + attributes.add(new JobName(docTitle, null)); + PrintService pServ = printServ; + + String printerName = jobAttributes.getPrinter(); + if (printerName != null && printerName != "" + && pServ != null && !printerName.equals(pServ.getName())) { + + // Search for the given printerName in the list of PrintServices + PrintService []services = PrinterJob.lookupPrintServices(); + try { + for (int i=0; i queue = new ArrayList<>(); + + MessageQ(String id) { + qid = id; + } + + synchronized void closeWhenEmpty() { + + while (queue != null && queue.size() > 0) { + try { + wait(1000); + } catch (InterruptedException e) { + // do nothing. + } + } + + queue = null; + notifyAll(); + } + + synchronized void close() { + queue = null; + notifyAll(); + } + + synchronized boolean append(Graphics2D g) { + + boolean queued = false; + + if (queue != null) { + queue.add(g); + queued = true; + notify(); + } + + return queued; + } + + synchronized Graphics2D pop() { + Graphics2D g = null; + + while (g == null && queue != null) { + + if (queue.size() > 0) { + g = queue.remove(0); + notify(); + + } else { + try { + wait(2000); + } catch (InterruptedException e) { + // do nothing. + } + } + } + + return g; + } + + synchronized boolean isClosed() { + return queue == null; + } + + } + + + private static int[] getSize(MediaType mType) { + int []dim = new int[2]; + dim[0] = 612; + dim[1] = 792; + + for (int i=0; i < SIZES.length; i++) { + if (SIZES[i] == mType) { + dim[0] = WIDTHS[i]; + dim[1] = LENGTHS[i]; + break; + } + } + return dim; + } + + public static MediaSizeName mapMedia(MediaType mType) { + MediaSizeName media = null; + + // JAVAXSIZES.length and SIZES.length must be equal! + // Attempt to recover by getting the smaller size. + int length = Math.min(SIZES.length, JAVAXSIZES.length); + + for (int i=0; i < length; i++) { + if (SIZES[i] == mType) { + if ((JAVAXSIZES[i] != null) && + MediaSize.getMediaSizeForName(JAVAXSIZES[i]) != null) { + media = JAVAXSIZES[i]; + break; + } else { + /* create Custom Media */ + media = new CustomMediaSizeName(SIZES[i].toString()); + + float w = (float)Math.rint(WIDTHS[i] / 72.0); + float h = (float)Math.rint(LENGTHS[i] / 72.0); + if (w > 0.0 && h > 0.0) { + // add new created MediaSize to our static map + // so it will be found when we call findMedia + new MediaSize(w, h, Size2DSyntax.INCH, media); + } + + break; + } + } + } + return media; + } + + + public static MediaType unMapMedia(MediaSizeName mSize) { + MediaType media = null; + + // JAVAXSIZES.length and SIZES.length must be equal! + // Attempt to recover by getting the smaller size. + int length = Math.min(SIZES.length, JAVAXSIZES.length); + + for (int i=0; i < length; i++) { + if (JAVAXSIZES[i] == mSize) { + if (SIZES[i] != null) { + media = SIZES[i]; + break; + } + } + } + return media; + } + + private void translateInputProps() { + if (props == null) { + return; + } + + String str; + + str = props.getProperty(DEST_PROP); + if (str != null) { + if (str.equals(PRINTER)) { + jobAttributes.setDestination(DestinationType.PRINTER); + } else if (str.equals(FILE)) { + jobAttributes.setDestination(DestinationType.FILE); + } + } + str = props.getProperty(PRINTER_PROP); + if (str != null) { + jobAttributes.setPrinter(str); + } + str = props.getProperty(FILENAME_PROP); + if (str != null) { + jobAttributes.setFileName(str); + } + str = props.getProperty(NUMCOPIES_PROP); + if (str != null) { + jobAttributes.setCopies(Integer.parseInt(str)); + } + + this.options = props.getProperty(OPTIONS_PROP, ""); + + str = props.getProperty(ORIENT_PROP); + if (str != null) { + if (str.equals(PORTRAIT)) { + pageAttributes.setOrientationRequested( + OrientationRequestedType.PORTRAIT); + } else if (str.equals(LANDSCAPE)) { + pageAttributes.setOrientationRequested( + OrientationRequestedType.LANDSCAPE); + } + } + str = props.getProperty(PAPERSIZE_PROP); + if (str != null) { + if (str.equals(LETTER)) { + pageAttributes.setMedia(SIZES[MediaType.LETTER.hashCode()]); + } else if (str.equals(LEGAL)) { + pageAttributes.setMedia(SIZES[MediaType.LEGAL.hashCode()]); + } else if (str.equals(EXECUTIVE)) { + pageAttributes.setMedia(SIZES[MediaType.EXECUTIVE.hashCode()]); + } else if (str.equals(A4)) { + pageAttributes.setMedia(SIZES[MediaType.A4.hashCode()]); + } + } + } + + private void translateOutputProps() { + if (props == null) { + return; + } + + String str; + + props.setProperty(DEST_PROP, + (jobAttributes.getDestination() == DestinationType.PRINTER) ? + PRINTER : FILE); + str = jobAttributes.getPrinter(); + if (str != null && !str.isEmpty()) { + props.setProperty(PRINTER_PROP, str); + } + str = jobAttributes.getFileName(); + if (str != null && !str.isEmpty()) { + props.setProperty(FILENAME_PROP, str); + } + int copies = jobAttributes.getCopies(); + if (copies > 0) { + props.setProperty(NUMCOPIES_PROP, "" + copies); + } + str = this.options; + if (str != null && !str.isEmpty()) { + props.setProperty(OPTIONS_PROP, str); + } + props.setProperty(ORIENT_PROP, + (pageAttributes.getOrientationRequested() == + OrientationRequestedType.PORTRAIT) + ? PORTRAIT : LANDSCAPE); + MediaType media = SIZES[pageAttributes.getMedia().hashCode()]; + if (media == MediaType.LETTER) { + str = LETTER; + } else if (media == MediaType.LEGAL) { + str = LEGAL; + } else if (media == MediaType.EXECUTIVE) { + str = EXECUTIVE; + } else if (media == MediaType.A4) { + str = A4; + } else { + str = media.toString(); + } + props.setProperty(PAPERSIZE_PROP, str); + } + +} From 0d0d93e8f6e2e6d0831b011e47da0c6069f6f4f5 Mon Sep 17 00:00:00 2001 From: Phil Race Date: Mon, 4 Aug 2025 19:29:03 +0000 Subject: [PATCH 018/580] 8210765: Remove finalize method in CStrike.java Reviewed-by: psadhukhan, achung, azvegint --- .../macosx/classes/sun/font/CStrike.java | 33 ++++++++----------- .../classes/sun/font/CStrikeDisposer.java | 7 ++-- 2 files changed, 16 insertions(+), 24 deletions(-) diff --git a/src/java.desktop/macosx/classes/sun/font/CStrike.java b/src/java.desktop/macosx/classes/sun/font/CStrike.java index 69b53433c4f..4b0ac9fd8d3 100644 --- a/src/java.desktop/macosx/classes/sun/font/CStrike.java +++ b/src/java.desktop/macosx/classes/sun/font/CStrike.java @@ -39,7 +39,7 @@ public final class CStrike extends PhysicalStrike { int fmHint); // Disposes the native strike - private static native void disposeNativeStrikePtr(long nativeStrikePtr); + static native void disposeNativeStrikePtr(long nativeStrikePtr); // Creates a StrikeMetrics from the underlying native system fonts private static native StrikeMetrics getFontMetrics(long nativeStrikePtr); @@ -70,14 +70,11 @@ public final class CStrike extends PhysicalStrike { private AffineTransform invDevTx; private final GlyphInfoCache glyphInfoCache; private final GlyphAdvanceCache glyphAdvanceCache; - private long nativeStrikePtr; + private final long nativeStrikePtr; CStrike(final CFont font, final FontStrikeDesc inDesc) { nativeFont = font; desc = inDesc; - glyphInfoCache = new GlyphInfoCache(font, desc); - glyphAdvanceCache = new GlyphAdvanceCache(); - disposer = glyphInfoCache; // Normally the device transform should be the identity transform // for screen operations. The device transform only becomes @@ -92,12 +89,18 @@ public final class CStrike extends PhysicalStrike { // so we won't worry about it. } } + nativeStrikePtr = initNativeStrikePtr(); // after setting up invDevTx + glyphInfoCache = new GlyphInfoCache(font, desc, nativeStrikePtr); + glyphAdvanceCache = new GlyphAdvanceCache(); + disposer = glyphInfoCache; } public long getNativeStrikePtr() { - if (nativeStrikePtr != 0) { - return nativeStrikePtr; - } + return nativeStrikePtr; + } + + public long initNativeStrikePtr() { + long nativeStrikePtr = 0L; final double[] glyphTx = new double[6]; desc.glyphTx.getMatrix(glyphTx); @@ -136,16 +139,6 @@ public final class CStrike extends PhysicalStrike { return nativeStrikePtr; } - @Override - @SuppressWarnings("removal") - protected synchronized void finalize() throws Throwable { - if (nativeStrikePtr != 0) { - disposeNativeStrikePtr(nativeStrikePtr); - } - nativeStrikePtr = 0; - } - - @Override public int getNumGlyphs() { return nativeFont.getNumGlyphs(); @@ -379,8 +372,8 @@ public final class CStrike extends PhysicalStrike { private SparseBitShiftingTwoLayerArray secondLayerCache; private HashMap generalCache; - GlyphInfoCache(final Font2D nativeFont, final FontStrikeDesc desc) { - super(nativeFont, desc); + GlyphInfoCache(final Font2D nativeFont, final FontStrikeDesc desc, long pScalerContext) { + super(nativeFont, desc, pScalerContext); firstLayerCache = new long[FIRST_LAYER_SIZE]; } diff --git a/src/java.desktop/macosx/classes/sun/font/CStrikeDisposer.java b/src/java.desktop/macosx/classes/sun/font/CStrikeDisposer.java index f032d716e90..c37209c8396 100644 --- a/src/java.desktop/macosx/classes/sun/font/CStrikeDisposer.java +++ b/src/java.desktop/macosx/classes/sun/font/CStrikeDisposer.java @@ -48,7 +48,7 @@ package sun.font; */ class CStrikeDisposer extends FontStrikeDisposer { - long pNativeScalerContext; + private final long pNativeScalerContext; public CStrikeDisposer(Font2D font2D, FontStrikeDesc desc, long pContext, int[] images) @@ -73,19 +73,18 @@ class CStrikeDisposer extends FontStrikeDisposer { public CStrikeDisposer(Font2D font2D, FontStrikeDesc desc) { super(font2D, desc); + pNativeScalerContext = 0L; } @Override public synchronized void dispose() { if (!disposed) { if (pNativeScalerContext != 0L) { - freeNativeScalerContext(pNativeScalerContext); + CStrike.disposeNativeStrikePtr(pNativeScalerContext); } super.dispose(); } } - private native void freeNativeScalerContext(long pContext); - protected static native void removeGlyphInfoFromCache(long glyphInfo); } From da3a5da81bc1d6fe1e47e3a4e65bf390ee1d39a0 Mon Sep 17 00:00:00 2001 From: Coleen Phillimore Date: Mon, 4 Aug 2025 20:13:03 +0000 Subject: [PATCH 019/580] 8343218: Add option to disable allocating interface and abstract classes in non-class metaspace Reviewed-by: shade, kvn, yzheng, stuefe, dholmes --- src/hotspot/share/oops/instanceKlass.cpp | 2 +- src/hotspot/share/oops/klass.inline.hpp | 4 ++-- src/hotspot/share/runtime/globals.hpp | 4 ++++ .../jdk/vm/ci/hotspot/HotSpotMetaspaceConstantImpl.java | 4 ++-- .../share/classes/jdk/vm/ci/hotspot/HotSpotVMConfig.java | 4 +++- 5 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/hotspot/share/oops/instanceKlass.cpp b/src/hotspot/share/oops/instanceKlass.cpp index 0c9d6b0bcdc..9266e8ca091 100644 --- a/src/hotspot/share/oops/instanceKlass.cpp +++ b/src/hotspot/share/oops/instanceKlass.cpp @@ -472,7 +472,7 @@ InstanceKlass* InstanceKlass::allocate_instance_klass(const ClassFileParser& par assert(loader_data != nullptr, "invariant"); InstanceKlass* ik; - const bool use_class_space = parser.klass_needs_narrow_id(); + const bool use_class_space = UseClassMetaspaceForAllClasses || parser.klass_needs_narrow_id(); // Allocation if (parser.is_instance_ref_klass()) { diff --git a/src/hotspot/share/oops/klass.inline.hpp b/src/hotspot/share/oops/klass.inline.hpp index b37c5105f64..19d4954ccad 100644 --- a/src/hotspot/share/oops/klass.inline.hpp +++ b/src/hotspot/share/oops/klass.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -182,6 +182,6 @@ inline bool Klass::needs_narrow_id() const { // never instantiated classes out of class space lessens the class space pressure. // For more details, see JDK-8338526. // Note: don't call this function before access flags are initialized. - return !is_abstract() && !is_interface(); + return UseClassMetaspaceForAllClasses || (!is_abstract() && !is_interface()); } #endif // SHARE_OOPS_KLASS_INLINE_HPP diff --git a/src/hotspot/share/runtime/globals.hpp b/src/hotspot/share/runtime/globals.hpp index dfcb555e830..24dda9ac6d8 100644 --- a/src/hotspot/share/runtime/globals.hpp +++ b/src/hotspot/share/runtime/globals.hpp @@ -1998,6 +1998,10 @@ const int ObjectAlignmentInBytes = 8; develop(uint, BinarySearchThreshold, 16, \ "Minimal number of elements in a sorted collection to prefer" \ "binary search over simple linear search." ) \ + \ + product(bool, UseClassMetaspaceForAllClasses, false, DIAGNOSTIC, \ + "Use the class metaspace for all classes including " \ + "abstract and interface classes.") \ // end of RUNTIME_FLAGS diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotMetaspaceConstantImpl.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotMetaspaceConstantImpl.java index 55d5492c2f8..fd08361f682 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotMetaspaceConstantImpl.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotMetaspaceConstantImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -95,7 +95,7 @@ final class HotSpotMetaspaceConstantImpl implements HotSpotMetaspaceConstant, VM } private boolean canBeStoredInCompressibleMetaSpace() { - if (metaspaceObject instanceof HotSpotResolvedJavaType t && !t.isArray()) { + if (!HotSpotVMConfig.config().useClassMetaspaceForAllClasses && metaspaceObject instanceof HotSpotResolvedJavaType t && !t.isArray()) { // As of JDK-8338526, interface and abstract types are not stored // in compressible metaspace. return !t.isInterface() && !t.isAbstract(); diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotVMConfig.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotVMConfig.java index bc2e121fe90..449b315e467 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotVMConfig.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotVMConfig.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 @@ -67,6 +67,8 @@ class HotSpotVMConfig extends HotSpotVMConfigAccess { final boolean useCompressedOops = getFlag("UseCompressedOops", Boolean.class); + final boolean useClassMetaspaceForAllClasses = getFlag("UseClassMetaspaceForAllClasses", Boolean.class); + final int objectAlignment = getFlag("ObjectAlignmentInBytes", Integer.class); final int klassOffsetInBytes = getFieldValue("CompilerToVM::Data::oopDesc_klass_offset_in_bytes", Integer.class, "int"); From dc4d9b4849f6557f290338643910f0b05751d748 Mon Sep 17 00:00:00 2001 From: Phil Race Date: Mon, 4 Aug 2025 20:25:41 +0000 Subject: [PATCH 020/580] 8362898: Remove finalize() methods from javax.imageio TIFF classes Reviewed-by: azvegint, jdv --- .../plugins/tiff/TIFFBaseJPEGCompressor.java | 25 +++++++++++++++---- .../plugins/tiff/TIFFJPEGDecompressor.java | 24 +++++++++++++++--- .../plugins/tiff/TIFFOldJPEGDecompressor.java | 4 --- 3 files changed, 40 insertions(+), 13 deletions(-) diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFBaseJPEGCompressor.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFBaseJPEGCompressor.java index dba8d878169..d5adb4e8d35 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFBaseJPEGCompressor.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFBaseJPEGCompressor.java @@ -55,6 +55,8 @@ import javax.imageio.plugins.jpeg.JPEGImageWriteParam; import javax.imageio.stream.ImageOutputStream; import javax.imageio.stream.MemoryCacheImageOutputStream; import org.w3c.dom.Node; +import sun.java2d.Disposer; +import sun.java2d.DisposerRecord; /** * Base class for all possible forms of JPEG compression in TIFF. @@ -219,12 +221,14 @@ public abstract class TIFFBaseJPEGCompressor extends TIFFCompressor { if(supportsStreamMetadata) { String smName = spi.getNativeStreamMetadataFormatName(); if(smName == null || !smName.equals(STREAM_METADATA_NAME)) { + this.JPEGWriter.dispose(); this.JPEGWriter = null; } } if(this.JPEGWriter != null && supportsImageMetadata) { String imName = spi.getNativeImageMetadataFormatName(); if(imName == null || !imName.equals(IMAGE_METADATA_NAME)) { + this.JPEGWriter.dispose(); this.JPEGWriter = null; } } @@ -263,6 +267,12 @@ public abstract class TIFFBaseJPEGCompressor extends TIFFCompressor { // Set the writer. this.JPEGWriter = writer; + // The JDK built-in JPEGImageWriter will self-dispose. + // So a Disposer is only needed here if it is an unknown reader. + // This is not common, so likely this will rarely be needed. + if (!(this.JPEGWriter instanceof com.sun.imageio.plugins.jpeg.JPEGImageWriter)) { + Disposer.addRecord(this, new ImageWriterDisposerRecord(this.JPEGWriter)); + } break; } @@ -435,11 +445,16 @@ public abstract class TIFFBaseJPEGCompressor extends TIFFCompressor { return compDataLength; } - @SuppressWarnings("removal") - protected void finalize() throws Throwable { - super.finalize(); - if(JPEGWriter != null) { - JPEGWriter.dispose(); + private static class ImageWriterDisposerRecord implements DisposerRecord { + private final ImageWriter writer; + + public ImageWriterDisposerRecord(ImageWriter writer) { + this.writer = writer; + } + + @Override + public void dispose() { + writer.dispose(); } } } diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFJPEGDecompressor.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFJPEGDecompressor.java index 0b21835901b..3c2ce905958 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFJPEGDecompressor.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFJPEGDecompressor.java @@ -34,6 +34,8 @@ import javax.imageio.stream.MemoryCacheImageInputStream; import javax.imageio.stream.ImageInputStream; import javax.imageio.plugins.tiff.BaselineTIFFTagSet; import javax.imageio.plugins.tiff.TIFFField; +import sun.java2d.Disposer; +import sun.java2d.DisposerRecord; public class TIFFJPEGDecompressor extends TIFFDecompressor { // Start of Image @@ -65,6 +67,13 @@ public class TIFFJPEGDecompressor extends TIFFDecompressor { // Initialize reader to the first one. this.JPEGReader = iter.next(); + // The JDK built-in ImageReader will self-dispose. + // So a Disposer is only needed here if it is an unknown reader. + // This is not common, so likely this will rarely be needed. + if (!(this.JPEGReader instanceof com.sun.imageio.plugins.jpeg.JPEGImageReader)) { + Disposer.addRecord(this, new ImageReaderDisposerRecord(this.JPEGReader)); + } + this.JPEGParam = JPEGReader.getDefaultReadParam(); } @@ -139,9 +148,16 @@ public class TIFFJPEGDecompressor extends TIFFDecompressor { JPEGReader.read(0, JPEGParam); } - @SuppressWarnings("removal") - protected void finalize() throws Throwable { - super.finalize(); - JPEGReader.dispose(); + private static class ImageReaderDisposerRecord implements DisposerRecord { + private final ImageReader reader; + + public ImageReaderDisposerRecord(ImageReader reader) { + this.reader = reader; + } + + @Override + public void dispose() { + reader.dispose(); + } } } diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFOldJPEGDecompressor.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFOldJPEGDecompressor.java index bc787f9127c..2935ca5caf8 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFOldJPEGDecompressor.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFOldJPEGDecompressor.java @@ -610,8 +610,4 @@ public class TIFFOldJPEGDecompressor extends TIFFJPEGDecompressor { JPEGReader.read(0, JPEGParam); } - protected void finalize() throws Throwable { - super.finalize(); - JPEGReader.dispose(); - } } From f96b6bcd4ddbb1d0e0a76d9f4e3b43bec20dcb7a Mon Sep 17 00:00:00 2001 From: Mohamed Issa Date: Mon, 4 Aug 2025 21:31:35 +0000 Subject: [PATCH 021/580] 8364666: Tier1 builds broken by JDK-8360559 Reviewed-by: sviswanathan --- src/hotspot/cpu/x86/stubGenerator_x86_64_sinh.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64_sinh.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_sinh.cpp index 6e190542379..86e4ac20176 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64_sinh.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_sinh.cpp @@ -289,7 +289,7 @@ ATTRIBUTE_ALIGNED(16) static const juint _T2_neg_f[] = #define __ _masm-> address StubGenerator::generate_libmSinh() { - StubGenStubId stub_id = StubGenStubId::dsinh_id; + StubId stub_id = StubId::stubgen_dsinh_id; StubCodeMark mark(this, stub_id); address start = __ pc(); From 84a4a3647c86baecc07abfa7d1d4b88e06984821 Mon Sep 17 00:00:00 2001 From: David Holmes Date: Mon, 4 Aug 2025 21:48:38 +0000 Subject: [PATCH 022/580] 8364314: java_lang_Thread::get_thread_status fails assert(base != nullptr) failed: Invalid base Reviewed-by: amenkov, shade, dcubed, pchilanomate, sspitsyn --- src/hotspot/share/services/threadService.cpp | 12 +++++++++++- test/jdk/ProblemList.txt | 2 -- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/hotspot/share/services/threadService.cpp b/src/hotspot/share/services/threadService.cpp index bf72ece16ea..ad5c19d41c0 100644 --- a/src/hotspot/share/services/threadService.cpp +++ b/src/hotspot/share/services/threadService.cpp @@ -1278,6 +1278,7 @@ public: if (is_virtual) { // mounted vthread, use carrier thread state oop carrier_thread = java_lang_VirtualThread::carrier_thread(_thread_h()); + assert(carrier_thread != nullptr, "should only get here for a mounted vthread"); _thread_status = java_lang_Thread::get_thread_status(carrier_thread); } else { _thread_status = java_lang_Thread::get_thread_status(_thread_h()); @@ -1477,7 +1478,17 @@ oop ThreadSnapshotFactory::get_thread_snapshot(jobject jthread, TRAPS) { carrier_thread = Handle(THREAD, java_lang_VirtualThread::carrier_thread(thread_h())); if (carrier_thread != nullptr) { + // Note: The java_thread associated with this carrier_thread may not be + // protected by the ThreadsListHandle above. There could have been an + // unmount and remount after the ThreadsListHandle above was created + // and before the JvmtiVTMSTransitionDisabler was created. However, as + // we have disabled transitions, if we are mounted on it, then it cannot + // terminate and so is safe to handshake with. java_thread = java_lang_Thread::thread(carrier_thread()); + } else { + // We may have previously found a carrier but the virtual thread has unmounted + // after that, so clear that previous reference. + java_thread = nullptr; } } else { java_thread = java_lang_Thread::thread(thread_h()); @@ -1554,4 +1565,3 @@ oop ThreadSnapshotFactory::get_thread_snapshot(jobject jthread, TRAPS) { } #endif // INCLUDE_JVMTI - diff --git a/test/jdk/ProblemList.txt b/test/jdk/ProblemList.txt index 5784373637b..84555a6edfb 100644 --- a/test/jdk/ProblemList.txt +++ b/test/jdk/ProblemList.txt @@ -554,8 +554,6 @@ java/io/IO/IO.java 8337935 linux-pp # jdk_management -com/sun/management/HotSpotDiagnosticMXBean/DumpThreads.java 8364314 generic-all - com/sun/management/OperatingSystemMXBean/GetProcessCpuLoad.java 8030957 aix-all com/sun/management/OperatingSystemMXBean/GetSystemCpuLoad.java 8030957 aix-all From 0f4c3dc944212c63acee35b7be93767946397ec0 Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Tue, 5 Aug 2025 01:04:38 +0000 Subject: [PATCH 023/580] 8362352: Fix references to non-existing resource strings Reviewed-by: almatvee --- .../jpackage/internal/BuildEnvFromParams.java | 5 ++-- .../internal/RuntimeBuilderBuilder.java | 11 -------- .../internal/StandardBundlerParam.java | 28 ++++++------------- 3 files changed, 11 insertions(+), 33 deletions(-) diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BuildEnvFromParams.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BuildEnvFromParams.java index cc9b490b188..24ae249297e 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BuildEnvFromParams.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BuildEnvFromParams.java @@ -25,6 +25,7 @@ package jdk.jpackage.internal; import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_RUNTIME_IMAGE; +import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE; import static jdk.jpackage.internal.StandardBundlerParam.RESOURCE_DIR; import static jdk.jpackage.internal.StandardBundlerParam.TEMP_ROOT; import static jdk.jpackage.internal.StandardBundlerParam.VERBOSE; @@ -46,9 +47,9 @@ final class BuildEnvFromParams { final var pkg = FromParams.getCurrentPackage(params); if (app.isRuntime()) { - builder.appImageDir(PREDEFINED_RUNTIME_IMAGE.fetchFrom(params)); + PREDEFINED_RUNTIME_IMAGE.copyInto(params, builder::appImageDir); } else if (StandardBundlerParam.hasPredefinedAppImage(params)) { - builder.appImageDir(StandardBundlerParam.getPredefinedAppImage(params)); + PREDEFINED_APP_IMAGE.copyInto(params, builder::appImageDir); } else if (pkg.isPresent()) { builder.appImageDirForPackage(); } else { diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/RuntimeBuilderBuilder.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/RuntimeBuilderBuilder.java index ec27b1fac5c..7b3f936c5a3 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/RuntimeBuilderBuilder.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/RuntimeBuilderBuilder.java @@ -24,13 +24,11 @@ */ package jdk.jpackage.internal; -import static jdk.jpackage.internal.I18N.buildConfigException; import static jdk.jpackage.internal.model.RuntimeBuilder.getDefaultModulePath; import static jdk.jpackage.internal.util.function.ThrowingSupplier.toSupplier; import java.io.IOException; import java.io.UncheckedIOException; -import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Path; import java.util.List; @@ -116,15 +114,6 @@ final class RuntimeBuilderBuilder { private static RuntimeBuilder createCopyingRuntimeBuilder(Path runtimeDir, Path... modulePath) throws ConfigException { - if (!Files.exists(runtimeDir)) { - throw buildConfigException() - .message("message.runtime-image-dir-does-not-exist", - "--runtime-image", runtimeDir) - .advice("message.runtime-image-dir-does-not-exist.advice", - "--runtime-image") - .create(); - } - return appImageLayout -> { try { // copy whole runtime, skipping jmods and src.zip diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/StandardBundlerParam.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/StandardBundlerParam.java index 93d84fd79b6..076d6bfc895 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/StandardBundlerParam.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/StandardBundlerParam.java @@ -94,10 +94,17 @@ final class StandardBundlerParam { null ); + static final BundlerParamInfo PREDEFINED_APP_IMAGE = + new BundlerParamInfo<>( + Arguments.CLIOptions.PREDEFINED_APP_IMAGE.getId(), + Path.class, + params -> null, + (s, p) -> Path.of(s)); + static final BundlerParamInfo PREDEFINED_APP_IMAGE_FILE = BundlerParamInfo.createBundlerParam( ExternalApplication.class, params -> { if (hasPredefinedAppImage(params)) { - var appImage = getPredefinedAppImage(params); + var appImage = PREDEFINED_APP_IMAGE.fetchFrom(params); return AppImageFile.load(appImage, PLATFORM_APPLICATION_LAYOUT); } else { return null; @@ -127,13 +134,6 @@ final class StandardBundlerParam { (s, p) -> Path.of(s) ); - static final BundlerParamInfo PREDEFINED_APP_IMAGE = - new BundlerParamInfo<>( - Arguments.CLIOptions.PREDEFINED_APP_IMAGE.getId(), - Path.class, - params -> null, - (s, p) -> Path.of(s)); - // this is the raw --app-name arg - used in APP_NAME and INSTALLER_NAME static final BundlerParamInfo NAME = new BundlerParamInfo<>( @@ -533,18 +533,6 @@ final class StandardBundlerParam { return params.containsKey(PREDEFINED_APP_IMAGE.getID()); } - static Path getPredefinedAppImage(Map params) { - Path applicationImage = PREDEFINED_APP_IMAGE.fetchFrom(params); - if (applicationImage != null && !IOUtils.exists(applicationImage)) { - throw new RuntimeException( - MessageFormat.format(I18N.getString( - "message.app-image-dir-does-not-exist"), - PREDEFINED_APP_IMAGE.getID(), - applicationImage.toString())); - } - return applicationImage; - } - private static String getDefaultAppVersion(Map params) { String appVersion = DEFAULT_VERSION; From 6b360ac99af356613c6dd2cad0c2c84c5737519d Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Tue, 5 Aug 2025 01:09:56 +0000 Subject: [PATCH 024/580] 8359756: Bug in RuntimePackageTest.testName test Reviewed-by: almatvee --- .../jdk/jpackage/test/PackageTest.java | 8 + .../jpackage/share/RuntimePackageTest.java | 191 +++++++++++------- 2 files changed, 121 insertions(+), 78 deletions(-) diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/PackageTest.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/PackageTest.java index 61e4ccdb4a2..82b1623a42d 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/PackageTest.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/PackageTest.java @@ -336,6 +336,10 @@ public final class PackageTest extends RunnablePackageTest { return forTypes(List.of(type), action); } + public PackageTest forTypes(PackageType type, Consumer action) { + return forTypes(List.of(type), () -> action.accept(this)); + } + public PackageTest notForTypes(Collection types, Runnable action) { Set workset = new HashSet<>(currentTypes); workset.removeAll(types); @@ -346,6 +350,10 @@ public final class PackageTest extends RunnablePackageTest { return notForTypes(List.of(type), action); } + public PackageTest notForTypes(PackageType type, Consumer action) { + return notForTypes(List.of(type), () -> action.accept(this)); + } + public PackageTest configureHelloApp() { return configureHelloApp(null); } diff --git a/test/jdk/tools/jpackage/share/RuntimePackageTest.java b/test/jdk/tools/jpackage/share/RuntimePackageTest.java index 2af6543c2db..f66f774b227 100644 --- a/test/jdk/tools/jpackage/share/RuntimePackageTest.java +++ b/test/jdk/tools/jpackage/share/RuntimePackageTest.java @@ -21,26 +21,28 @@ * questions. */ +import static jdk.internal.util.OperatingSystem.LINUX; +import static jdk.internal.util.OperatingSystem.MACOS; +import static jdk.jpackage.test.TKit.assertFalse; +import static jdk.jpackage.test.TKit.assertTrue; + import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.HashSet; -import java.util.Set; +import java.util.Objects; import java.util.function.Predicate; -import java.util.stream.Collectors; -import jdk.jpackage.test.PackageType; -import jdk.jpackage.test.RunnablePackageTest.Action; -import jdk.jpackage.test.PackageTest; -import jdk.jpackage.test.JPackageCommand; -import jdk.jpackage.test.TKit; -import jdk.jpackage.test.Annotations.Test; +import jdk.jpackage.internal.util.function.ThrowingSupplier; import jdk.jpackage.test.Annotations.Parameter; +import jdk.jpackage.test.Annotations.Test; import jdk.jpackage.test.Executor; +import jdk.jpackage.test.JPackageCommand; import jdk.jpackage.test.JavaTool; import jdk.jpackage.test.LinuxHelper; -import static jdk.jpackage.test.TKit.assertTrue; -import static jdk.jpackage.test.TKit.assertFalse; -import static jdk.internal.util.OperatingSystem.LINUX; +import jdk.jpackage.test.MacHelper; +import jdk.jpackage.test.PackageTest; +import jdk.jpackage.test.PackageType; +import jdk.jpackage.test.RunnablePackageTest.Action; +import jdk.jpackage.test.TKit; /** * Test --runtime-image parameter. @@ -85,6 +87,11 @@ public class RuntimePackageTest { init().run(); } + @Test(ifOS = MACOS) + public static void testFromBundle() { + init(RuntimePackageTest::createInputRuntimeBundle).run(); + } + @Test(ifOS = LINUX) @Parameter("/usr") @Parameter("/usr/lib/Java") @@ -107,88 +114,116 @@ public class RuntimePackageTest { } private static PackageTest init() { + return init(RuntimePackageTest::createInputRuntimeImage); + } + + private static PackageTest init(ThrowingSupplier createRuntime) { + Objects.requireNonNull(createRuntime); + + final Path[] runtimeImageDir = new Path[1]; + return new PackageTest() + .addRunOnceInitializer(() -> { + runtimeImageDir[0] = createRuntime.get(); + }) .addInitializer(cmd -> { - final Path runtimeImageDir; - - if (JPackageCommand.DEFAULT_RUNTIME_IMAGE != null) { - runtimeImageDir = JPackageCommand.DEFAULT_RUNTIME_IMAGE; - } else { - runtimeImageDir = TKit.createTempDirectory("runtime").resolve("data"); - - new Executor() - .setToolProvider(JavaTool.JLINK) - .dumpOutput() - .addArguments( - "--output", runtimeImageDir.toString(), - "--add-modules", "java.desktop", - "--strip-debug", - "--no-header-files", - "--no-man-pages") - .execute(); - } - cmd.addArguments("--runtime-image", runtimeImageDir); + cmd.addArguments("--runtime-image", runtimeImageDir[0]); // Remove --input parameter from jpackage command line as we don't // create input directory in the test and jpackage fails // if --input references non existant directory. cmd.removeArgumentWithValue("--input"); }) .addInstallVerifier(cmd -> { - Set srcRuntime = listFiles(Path.of(cmd.getArgumentValue("--runtime-image"))); + var src = TKit.assertDirectoryContentRecursive(inputRuntimeDir(cmd)).items(); Path dest = cmd.appRuntimeDirectory(); if (TKit.isOSX()) { dest = dest.resolve("Contents/Home"); } - Set dstRuntime = listFiles(dest); - Set intersection = new HashSet<>(srcRuntime); - intersection.retainAll(dstRuntime); - - srcRuntime.removeAll(intersection); - dstRuntime.removeAll(intersection); - - assertFileListEmpty(srcRuntime, "Missing"); - assertFileListEmpty(dstRuntime, "Unexpected"); + TKit.assertDirectoryContentRecursive(dest).match(src); }) - .forTypes(PackageType.LINUX_DEB) - .addInstallVerifier(cmd -> { - String installDir = cmd.getArgumentValue("--install-dir", () -> "/opt"); - Path copyright = Path.of("/usr/share/doc", - LinuxHelper.getPackageName(cmd), "copyright"); - boolean withCopyright = LinuxHelper.getPackageFiles(cmd).anyMatch( - Predicate.isEqual(copyright)); - if (installDir.startsWith("/usr/") || installDir.equals("/usr")) { - assertTrue(withCopyright, String.format( - "Check the package delivers [%s] copyright file", - copyright)); - } else { - assertFalse(withCopyright, String.format( - "Check the package doesn't deliver [%s] copyright file", - copyright)); + .forTypes(PackageType.LINUX_DEB, test -> { + test.addInstallVerifier(cmd -> { + String installDir = cmd.getArgumentValue("--install-dir", () -> "/opt"); + Path copyright = Path.of("/usr/share/doc", + LinuxHelper.getPackageName(cmd), "copyright"); + boolean withCopyright = LinuxHelper.getPackageFiles(cmd).anyMatch( + Predicate.isEqual(copyright)); + if (installDir.startsWith("/usr/") || installDir.equals("/usr")) { + assertTrue(withCopyright, String.format( + "Check the package delivers [%s] copyright file", + copyright)); + } else { + assertFalse(withCopyright, String.format( + "Check the package doesn't deliver [%s] copyright file", + copyright)); + } + }); + }); + } + + private static Path inputRuntimeDir(JPackageCommand cmd) { + var path = Path.of(cmd.getArgumentValue("--runtime-image")); + if (TKit.isOSX()) { + var bundleHome = path.resolve("Contents/Home"); + if (Files.isDirectory(bundleHome)) { + return bundleHome; + } + } + return path; + } + + private static Path createInputRuntimeImage() throws IOException { + + final Path runtimeImageDir; + + if (JPackageCommand.DEFAULT_RUNTIME_IMAGE != null) { + runtimeImageDir = JPackageCommand.DEFAULT_RUNTIME_IMAGE; + } else { + runtimeImageDir = TKit.createTempDirectory("runtime-image").resolve("data"); + + new Executor().setToolProvider(JavaTool.JLINK) + .dumpOutput() + .addArguments( + "--output", runtimeImageDir.toString(), + "--add-modules", "java.desktop", + "--strip-debug", + "--no-header-files", + "--no-man-pages") + .execute(); + } + + return runtimeImageDir; + } + + private static Path createInputRuntimeBundle() throws IOException { + + final var runtimeImage = createInputRuntimeImage(); + + final var runtimeBundleWorkDir = TKit.createTempDirectory("runtime-bundle"); + + final var unpackadeRuntimeBundleDir = runtimeBundleWorkDir.resolve("unpacked"); + + var cmd = new JPackageCommand() + .useToolProvider(true) + .ignoreDefaultRuntime(true) + .dumpOutput(true) + .setPackageType(PackageType.MAC_DMG) + .setArgumentValue("--name", "foo") + .addArguments("--runtime-image", runtimeImage) + .addArguments("--dest", runtimeBundleWorkDir); + + cmd.execute(); + + MacHelper.withExplodedDmg(cmd, dmgImage -> { + if (dmgImage.endsWith(cmd.appInstallationDirectory().getFileName())) { + Executor.of("cp", "-R") + .addArgument(dmgImage) + .addArgument(unpackadeRuntimeBundleDir) + .execute(0); } }); - } - private static Set listFiles(Path root) throws IOException { - try (var files = Files.walk(root)) { - // Ignore files created by system prefs if any. - final Path prefsDir = Path.of(".systemPrefs"); - return files.map(root::relativize) - .filter(x -> !x.startsWith(prefsDir)) - .filter(x -> !x.endsWith(".DS_Store")) - .collect(Collectors.toSet()); - } - } - - private static void assertFileListEmpty(Set paths, String msg) { - TKit.assertTrue(paths.isEmpty(), String.format( - "Check there are no %s files in installed image", - msg.toLowerCase()), () -> { - String msg2 = String.format("%s %d files", msg, paths.size()); - TKit.trace(msg2 + ":"); - paths.stream().map(Path::toString).sorted().forEachOrdered( - TKit::trace); - TKit.trace("Done"); - }); + return unpackadeRuntimeBundleDir; } } From c0c7d39b59e646a51bf3a6729065cceda9b0a0ad Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Tue, 5 Aug 2025 01:42:45 +0000 Subject: [PATCH 025/580] 8364587: Update jpackage internal javadoc Reviewed-by: almatvee --- .../jpackage/internal/PackagingPipeline.java | 30 +++++++++++++++++++ .../jpackage/internal/model/Application.java | 4 +-- .../internal/model/DefaultLauncherIcon.java | 2 +- .../jdk/jpackage/internal/model/Launcher.java | 4 +-- 4 files changed, 35 insertions(+), 5 deletions(-) diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/PackagingPipeline.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/PackagingPipeline.java index 10590a7aa8b..276bed4f31c 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/PackagingPipeline.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/PackagingPipeline.java @@ -55,19 +55,49 @@ import jdk.jpackage.internal.util.function.ExceptionBox; final class PackagingPipeline { + /** + * Runs the pipeline for the given application. + * + * @param env the build environment + * @param app the application + */ void execute(BuildEnv env, Application app) throws PackagerException { execute(appContextMapper.apply(createTaskContext(env, app))); } + /** + * Runs the pipeline for the given package. + *

+ * Building a package may require a directory where the app image bits will be + * accumulated or the existing app image may be used. The decision is made based + * on the properties of the given package. A new build environment will be + * created if an intermediate directory is required. To access the build + * environment that will be used by the pipeline before running the pipeline + * create {@link StartupParameters} instance using + * {@link Builder#createStartupParameters(BuildEnv, Package, Path)} method. + * + * @param env the build environment + * @param pkg the package + * @param outputDir the output directory for the package file + */ void execute(BuildEnv env, Package pkg, Path outputDir) throws PackagerException { execute((StartupParameters)createPackagingTaskContext(env, pkg, outputDir, taskConfig, appImageLayoutForPackaging.apply(pkg))); } + /** + * Runs the pipeline using the startup parameters created with + * {@link Builder#createStartupParameters(BuildEnv, Package, Path)} call. + * + * @param startupParameters the pipeline startup parameters + */ void execute(StartupParameters startupParameters) throws PackagerException { execute(pkgContextMapper.apply(createTaskContext((PackagingTaskContext)startupParameters))); } + /** + * The way to access packaging build environment before building a package in a pipeline. + */ interface StartupParameters { BuildEnv packagingEnv(); } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/Application.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/Application.java index f164daf3eb0..943a42712c8 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/Application.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/Application.java @@ -222,10 +222,10 @@ public interface Application extends BundleSpec { /** * Gets the additional properties of this application for the application entry - * in the app image (".jpackage") file. + * in the app image (".jpackage.xml") file. * * @return the additional properties of this application for the application - * entry in ".jpackage" file + * entry in ".jpackage.xml" file */ Map extraAppImageFileData(); diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/DefaultLauncherIcon.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/DefaultLauncherIcon.java index 6e36949190e..4275b84ba80 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/DefaultLauncherIcon.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/DefaultLauncherIcon.java @@ -29,7 +29,7 @@ import java.util.Optional; /** * Default application launcher icon. *

- * Default icon is either loaded from the resources of {@link jdk.jpackage/} module or picked from the resource directory. + * Default icon is either loaded from the resources of {@link jdk.jpackage} module or picked from the resource directory. *

* Use {@link #INSTANCE} field to get an instance of this type. */ diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/Launcher.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/Launcher.java index 9c20d5432ee..ac60b503fe4 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/Launcher.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/Launcher.java @@ -173,10 +173,10 @@ public interface Launcher { /** * Gets the additional properties for application launcher entries in the app - * image (".jpackage") file. + * image (".jpackage.xml") file. * * @return the additional properties for application launcher entries in - * ".jpackage" file + * ".jpackage.xml" file */ Map extraAppImageFileData(); From febd4b26b2c87030affd9f93524e0d951cbe74e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joel=20Sikstr=C3=B6m?= Date: Tue, 5 Aug 2025 07:41:11 +0000 Subject: [PATCH 026/580] 8360515: PROPERFMTARGS should always use size_t template specialization for unit Reviewed-by: dholmes, stuefe --- src/hotspot/os/bsd/memMapPrinter_macosx.cpp | 8 ++++---- src/hotspot/share/gc/parallel/psAdaptiveSizePolicy.cpp | 5 +++-- src/hotspot/share/utilities/globalDefinitions.hpp | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/hotspot/os/bsd/memMapPrinter_macosx.cpp b/src/hotspot/os/bsd/memMapPrinter_macosx.cpp index 154d2887c53..ee76214ddfa 100644 --- a/src/hotspot/os/bsd/memMapPrinter_macosx.cpp +++ b/src/hotspot/os/bsd/memMapPrinter_macosx.cpp @@ -233,10 +233,10 @@ public: mach_msg_type_number_t num_out = TASK_VM_INFO_COUNT; kern_return_t err = task_info(mach_task_self(), TASK_VM_INFO, (task_info_t)(&vm_info), &num_out); if (err == KERN_SUCCESS) { - st->print_cr(" vsize: %llu (%llu%s)", vm_info.virtual_size, PROPERFMTARGS(vm_info.virtual_size)); - st->print_cr(" rss: %llu (%llu%s)", vm_info.resident_size, PROPERFMTARGS(vm_info.resident_size)); - st->print_cr(" peak rss: %llu (%llu%s)", vm_info.resident_size_peak, PROPERFMTARGS(vm_info.resident_size_peak)); - st->print_cr(" page size: %d (%ld%s)", vm_info.page_size, PROPERFMTARGS((size_t)vm_info.page_size)); + st->print_cr(" vsize: %llu (" PROPERFMT ")", vm_info.virtual_size, PROPERFMTARGS((size_t)vm_info.virtual_size)); + st->print_cr(" rss: %llu (" PROPERFMT ")", vm_info.resident_size, PROPERFMTARGS((size_t)vm_info.resident_size)); + st->print_cr(" peak rss: %llu (" PROPERFMT ")", vm_info.resident_size_peak, PROPERFMTARGS((size_t)vm_info.resident_size_peak)); + st->print_cr(" page size: %d (" PROPERFMT ")", vm_info.page_size, PROPERFMTARGS((size_t)vm_info.page_size)); } else { st->print_cr("error getting vm_info %d", err); } diff --git a/src/hotspot/share/gc/parallel/psAdaptiveSizePolicy.cpp b/src/hotspot/share/gc/parallel/psAdaptiveSizePolicy.cpp index fb57f224652..5d99095c1ed 100644 --- a/src/hotspot/share/gc/parallel/psAdaptiveSizePolicy.cpp +++ b/src/hotspot/share/gc/parallel/psAdaptiveSizePolicy.cpp @@ -67,7 +67,8 @@ void PSAdaptiveSizePolicy::print_stats(bool is_survivor_overflowing) { mutator_time_percent(), minor_gc_time_estimate() * 1000.0, _gc_distance_seconds_seq.davg(), _gc_distance_seconds_seq.last(), - PROPERFMTARGS(promoted_bytes_estimate()), PROPERFMTARGS(_promoted_bytes.last()), + byte_size_in_proper_unit(promoted_bytes_estimate()), proper_unit_for_byte_size((size_t)promoted_bytes_estimate()), + byte_size_in_proper_unit(_promoted_bytes.last()), proper_unit_for_byte_size((size_t)_promoted_bytes.last()), _promotion_rate_bytes_per_sec.davg()/M, _promotion_rate_bytes_per_sec.last()/M, is_survivor_overflowing ? "true" : "false"); } @@ -258,4 +259,4 @@ void PSAdaptiveSizePolicy::update_averages(bool is_survivor_overflow, double promotion_rate = promoted / (_gc_distance_seconds_seq.last() + _trimmed_minor_gc_time_seconds.last()); _promotion_rate_bytes_per_sec.add(promotion_rate); -} \ No newline at end of file +} diff --git a/src/hotspot/share/utilities/globalDefinitions.hpp b/src/hotspot/share/utilities/globalDefinitions.hpp index 38dc397d981..181786cd238 100644 --- a/src/hotspot/share/utilities/globalDefinitions.hpp +++ b/src/hotspot/share/utilities/globalDefinitions.hpp @@ -343,7 +343,7 @@ inline T byte_size_in_proper_unit(T s) { } #define PROPERFMT "%zu%s" -#define PROPERFMTARGS(s) byte_size_in_proper_unit(s), proper_unit_for_byte_size(s) +#define PROPERFMTARGS(s) byte_size_in_proper_unit(s), proper_unit_for_byte_size(s) // Printing a range, with start and bytes given #define RANGEFMT "[" PTR_FORMAT " - " PTR_FORMAT "), (%zu bytes)" From 67ba8b45dd632c40d5e6872d2a6ce24f86c22152 Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Tue, 5 Aug 2025 08:02:54 +0000 Subject: [PATCH 027/580] 8364514: [asan] runtime/jni/checked/TestCharArrayReleasing.java heap-buffer-overflow Reviewed-by: dholmes --- .../jtreg/runtime/jni/checked/TestCharArrayReleasing.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/hotspot/jtreg/runtime/jni/checked/TestCharArrayReleasing.java b/test/hotspot/jtreg/runtime/jni/checked/TestCharArrayReleasing.java index 7cb427cebbb..c26fb2bea4c 100644 --- a/test/hotspot/jtreg/runtime/jni/checked/TestCharArrayReleasing.java +++ b/test/hotspot/jtreg/runtime/jni/checked/TestCharArrayReleasing.java @@ -25,6 +25,8 @@ * @test * @bug 8357601 * @requires vm.flagless + * @comment The check of the array allocated with raw malloc triggers ASAN as we peek into the malloc header space. + * @requires !vm.asan * @library /test/lib * @run main/othervm/native TestCharArrayReleasing 0 0 * @run main/othervm/native TestCharArrayReleasing 1 0 From d25b9befe0a462b9785502806ad14e0a5f6b4320 Mon Sep 17 00:00:00 2001 From: Saranya Natarajan Date: Tue, 5 Aug 2025 08:39:47 +0000 Subject: [PATCH 028/580] 8325482: Test that distinct seeds produce distinct traces for compiler stress flags Reviewed-by: chagedorn, dfenacci --- .../jtreg/compiler/debug/TestStress.java | 12 +- .../debug/TestStressDistinctSeed.java | 131 ++++++++++++++++++ 2 files changed, 142 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 2046488ac40..f43818e8bd8 100644 --- a/test/hotspot/jtreg/compiler/debug/TestStress.java +++ b/test/hotspot/jtreg/compiler/debug/TestStress.java @@ -77,7 +77,17 @@ public class TestStress { 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 00000000000..3b29daf2ce6 --- /dev/null +++ b/test/hotspot/jtreg/compiler/debug/TestStressDistinctSeed.java @@ -0,0 +1,131 @@ +/* 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, macro elimination, 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 String macroEliminationTrace(int stressSeed) throws Exception { + return phaseTrace("StressMacroElimination", + "CompileCommand=PrintIdealPhase,*::*,AFTER_MACRO_ELIMINATION_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<>(); + Set macroEliminationTraceSet = new HashSet<>(); + String igvnTraceOutput, ccpTraceOutput, macroExpansionTraceOutput, macroEliminationTraceOutput; + if (args.length == 0) { + for (int s = 0; s < 5; s++) { + igvnTraceOutput = igvnTrace(s); + ccpTraceOutput = ccpTrace(s); + macroExpansionTraceOutput = macroExpansionTrace(s); + macroEliminationTraceOutput = macroEliminationTrace(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"); + Asserts.assertEQ(macroEliminationTraceOutput, macroEliminationTrace(s), + "got different macro elimination traces for the same seed"); + + igvnTraceSet.add(igvnTraceOutput); + ccpTraceSet.add(ccpTraceOutput); + macroExpansionTraceSet.add(macroExpansionTraceOutput); + macroEliminationTraceSet.add(macroEliminationTraceOutput); + } + 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"); + Asserts.assertGT(macroEliminationTraceSet.size(), 1, + "got same macro elimination traces for 5 different seeds"); + } else if (args.length > 0) { + sum(Integer.parseInt(args[0])); + } + } +} \ No newline at end of file From df736eb5822de2e2465df04972b1afb90334db5e Mon Sep 17 00:00:00 2001 From: Francesco Andreuzzi Date: Tue, 5 Aug 2025 10:23:54 +0000 Subject: [PATCH 029/580] 8364618: Sort share/code includes Reviewed-by: shade, mhaessig --- src/hotspot/share/code/aotCodeCache.cpp | 2 +- src/hotspot/share/code/codeBlob.hpp | 2 +- src/hotspot/share/code/debugInfo.cpp | 2 +- src/hotspot/share/code/dependencies.cpp | 5 ++--- src/hotspot/share/code/dependencyContext.cpp | 2 +- src/hotspot/share/code/nmethod.cpp | 4 ++-- test/hotspot/jtreg/sources/TestIncludesAreSorted.java | 1 + 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/hotspot/share/code/aotCodeCache.cpp b/src/hotspot/share/code/aotCodeCache.cpp index 4c080b90acc..38974da2eeb 100644 --- a/src/hotspot/share/code/aotCodeCache.cpp +++ b/src/hotspot/share/code/aotCodeCache.cpp @@ -61,8 +61,8 @@ #include "gc/z/zBarrierSetRuntime.hpp" #endif -#include #include +#include const char* aot_code_entry_kind_name[] = { #define DECL_KIND_STRING(kind) XSTR(kind), diff --git a/src/hotspot/share/code/codeBlob.hpp b/src/hotspot/share/code/codeBlob.hpp index 98cc13b3859..e5e47e2c5bb 100644 --- a/src/hotspot/share/code/codeBlob.hpp +++ b/src/hotspot/share/code/codeBlob.hpp @@ -28,9 +28,9 @@ #include "asm/codeBuffer.hpp" #include "compiler/compilerDefinitions.hpp" #include "compiler/oopMap.hpp" -#include "runtime/javaFrameAnchor.hpp" #include "runtime/frame.hpp" #include "runtime/handles.hpp" +#include "runtime/javaFrameAnchor.hpp" #include "utilities/align.hpp" #include "utilities/macros.hpp" diff --git a/src/hotspot/share/code/debugInfo.cpp b/src/hotspot/share/code/debugInfo.cpp index 9e895ecf152..b6f58908c2c 100644 --- a/src/hotspot/share/code/debugInfo.cpp +++ b/src/hotspot/share/code/debugInfo.cpp @@ -28,11 +28,11 @@ #include "gc/shared/collectedHeap.hpp" #include "memory/universe.hpp" #include "oops/oop.inline.hpp" -#include "runtime/stackValue.hpp" #include "runtime/handles.inline.hpp" #include "runtime/interfaceSupport.inline.hpp" #include "runtime/javaThread.hpp" #include "runtime/jniHandles.inline.hpp" +#include "runtime/stackValue.hpp" // Constructors diff --git a/src/hotspot/share/code/dependencies.cpp b/src/hotspot/share/code/dependencies.cpp index 7f925388eb0..d90695739a1 100644 --- a/src/hotspot/share/code/dependencies.cpp +++ b/src/hotspot/share/code/dependencies.cpp @@ -29,16 +29,15 @@ #include "classfile/javaClasses.inline.hpp" #include "classfile/vmClasses.hpp" #include "code/dependencies.hpp" -#include "compiler/compileLog.hpp" #include "compiler/compileBroker.hpp" +#include "compiler/compileLog.hpp" #include "compiler/compileTask.hpp" #include "memory/resourceArea.hpp" #include "oops/klass.hpp" -#include "oops/oop.inline.hpp" #include "oops/method.inline.hpp" #include "oops/objArrayKlass.hpp" +#include "oops/oop.inline.hpp" #include "runtime/flags/flagSetting.hpp" -#include "runtime/handles.hpp" #include "runtime/handles.inline.hpp" #include "runtime/javaThread.inline.hpp" #include "runtime/jniHandles.inline.hpp" diff --git a/src/hotspot/share/code/dependencyContext.cpp b/src/hotspot/share/code/dependencyContext.cpp index a8ef707978d..6c56ae20ddf 100644 --- a/src/hotspot/share/code/dependencyContext.cpp +++ b/src/hotspot/share/code/dependencyContext.cpp @@ -22,9 +22,9 @@ * */ -#include "code/nmethod.hpp" #include "code/dependencies.hpp" #include "code/dependencyContext.hpp" +#include "code/nmethod.hpp" #include "logging/log.hpp" #include "logging/logStream.hpp" #include "memory/resourceArea.hpp" diff --git a/src/hotspot/share/code/nmethod.cpp b/src/hotspot/share/code/nmethod.cpp index c062919f61e..d8cbf6b9e66 100644 --- a/src/hotspot/share/code/nmethod.cpp +++ b/src/hotspot/share/code/nmethod.cpp @@ -33,9 +33,9 @@ #include "compiler/compilationLog.hpp" #include "compiler/compileBroker.hpp" #include "compiler/compileLog.hpp" -#include "compiler/compileTask.hpp" #include "compiler/compilerDirectives.hpp" #include "compiler/compilerOracle.hpp" +#include "compiler/compileTask.hpp" #include "compiler/directivesParser.hpp" #include "compiler/disassembler.hpp" #include "compiler/oopMap.inline.hpp" @@ -59,8 +59,8 @@ #include "prims/jvmtiImpl.hpp" #include "prims/jvmtiThreadState.hpp" #include "prims/methodHandles.hpp" -#include "runtime/continuation.hpp" #include "runtime/atomic.hpp" +#include "runtime/continuation.hpp" #include "runtime/deoptimization.hpp" #include "runtime/flags/flagSetting.hpp" #include "runtime/frame.inline.hpp" diff --git a/test/hotspot/jtreg/sources/TestIncludesAreSorted.java b/test/hotspot/jtreg/sources/TestIncludesAreSorted.java index 5b3280b312f..072784cda9d 100644 --- a/test/hotspot/jtreg/sources/TestIncludesAreSorted.java +++ b/test/hotspot/jtreg/sources/TestIncludesAreSorted.java @@ -48,6 +48,7 @@ public class TestIncludesAreSorted { "share/cds", "share/ci", "share/classfile", + "share/code", "share/compiler", "share/jvmci", "share/libadt", From ba0ae4cb28aa520d5244077349e35ef1bb475b61 Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Tue, 5 Aug 2025 10:43:30 +0000 Subject: [PATCH 030/580] 8364254: Serial: Remove soft ref policy update in WhiteBox FullGC Reviewed-by: tschatzl, sangheki --- src/hotspot/share/prims/whitebox.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/hotspot/share/prims/whitebox.cpp b/src/hotspot/share/prims/whitebox.cpp index aaaac349ee3..be5d9d3d8a1 100644 --- a/src/hotspot/share/prims/whitebox.cpp +++ b/src/hotspot/share/prims/whitebox.cpp @@ -1502,12 +1502,12 @@ WB_END WB_ENTRY(void, WB_FullGC(JNIEnv* env, jobject o)) Universe::heap()->soft_ref_policy()->set_should_clear_all_soft_refs(true); Universe::heap()->collect(GCCause::_wb_full_gc); -#if INCLUDE_G1GC || INCLUDE_SERIALGC - if (UseG1GC || UseSerialGC) { - // Needs to be cleared explicitly for G1 and Serial GC. +#if INCLUDE_G1GC + if (UseG1GC) { + // Needs to be cleared explicitly for G1 GC. Universe::heap()->soft_ref_policy()->set_should_clear_all_soft_refs(false); } -#endif // INCLUDE_G1GC || INCLUDE_SERIALGC +#endif // INCLUDE_G1GC WB_END WB_ENTRY(void, WB_YoungGC(JNIEnv* env, jobject o)) From 8a571ee7f2d9a46ff485fd9f3658c552e2d20817 Mon Sep 17 00:00:00 2001 From: Erik Gahlin Date: Tue, 5 Aug 2025 14:33:30 +0000 Subject: [PATCH 031/580] 8364667: JFR: Throttle doesn't work with dynamic events Reviewed-by: mgronlun --- .../jdk/jfr/internal/ClassInspector.java | 5 ++- .../jfr/internal/EventInstrumentation.java | 6 +-- .../metadata/annotations/TestThrottle.java | 43 +++++++++++++++++++ 3 files changed, 48 insertions(+), 6 deletions(-) diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/ClassInspector.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/ClassInspector.java index 3646162e8f7..3cb13fb66ad 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/ClassInspector.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/ClassInspector.java @@ -140,7 +140,7 @@ final class ClassInspector { return true; } - boolean isThrottled() { + boolean isThrottled(MethodDesc staticThrottleMethod) { String result = annotationValue(ANNOTATION_THROTTLE, String.class, "off"); if (result != null) { return true; @@ -151,6 +151,9 @@ final class ClassInspector { return true; } } + if (isJDK()) { + return hasStaticMethod(staticThrottleMethod); + } return false; } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/EventInstrumentation.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/EventInstrumentation.java index 032ccb5f4b5..b7ee6fb5fe0 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/EventInstrumentation.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/EventInstrumentation.java @@ -124,11 +124,7 @@ public final class EventInstrumentation { this.eventClassDesc = inspector.getClassDesc(); this.staticCommitMethod = inspector.findStaticCommitMethod(); this.untypedEventConfiguration = hasUntypedConfiguration(); - if (inspector.isJDK()) { - this.throttled = inspector.hasStaticMethod(METHOD_EVENT_SHOULD_THROTTLE_COMMIT_LONG_LONG); - } else { - this.throttled = inspector.isThrottled(); - } + this.throttled = inspector.isThrottled(METHOD_EVENT_SHOULD_THROTTLE_COMMIT_LONG_LONG); } byte[] buildInstrumented() { diff --git a/test/jdk/jdk/jfr/api/metadata/annotations/TestThrottle.java b/test/jdk/jdk/jfr/api/metadata/annotations/TestThrottle.java index 89aab24b802..fb1985a03ba 100644 --- a/test/jdk/jdk/jfr/api/metadata/annotations/TestThrottle.java +++ b/test/jdk/jdk/jfr/api/metadata/annotations/TestThrottle.java @@ -29,11 +29,17 @@ import java.nio.file.Files; import java.nio.file.Path; import java.time.Duration; import java.time.Instant; +import java.util.ArrayList; +import java.util.List; import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicInteger; +import jdk.jfr.AnnotationElement; import jdk.jfr.Enabled; import jdk.jfr.Event; +import jdk.jfr.EventFactory; +import jdk.jfr.Name; import jdk.jfr.Recording; import jdk.jfr.SettingControl; import jdk.jfr.SettingDefinition; @@ -132,6 +138,7 @@ public class TestThrottle { testThrottleThresholded(); testThrottleNormalRate(); testThrottleUserdefined(); + testThrottleDynamic(); } private static void testUnthrottled() throws Exception { @@ -232,6 +239,40 @@ public class TestThrottle { } } + private static void testThrottleDynamic() throws Exception { + List offAnnotations = new ArrayList<>(); + offAnnotations.add(new AnnotationElement(Name.class, "DynamicZero")); + offAnnotations.add(new AnnotationElement(Throttle.class, "0/s")); + EventFactory offFactory = EventFactory.create(offAnnotations, List.of()); + + List highRateAnnotations = new ArrayList<>(); + highRateAnnotations.add(new AnnotationElement(Name.class, "DynamicHighRate")); + highRateAnnotations.add(new AnnotationElement(Throttle.class, "1000/s")); + EventFactory highRateFactory = EventFactory.create(highRateAnnotations, List.of()); + + List events = new CopyOnWriteArrayList<>(); + try (RecordingStream r = new RecordingStream()) { + r.enable("DynamicZero"); + r.enable("DynamicHighRate"); + r.onEvent(events::add); + r.startAsync(); + Event offEvent = offFactory.newEvent(); + offEvent.commit(); + Event highRateEvent = highRateFactory.newEvent(); + highRateEvent.begin(); + highRateEvent.commit(); + r.stop(); + if (events.size() != 1) { + System.out.println(events); + throw new Exception("Expected one dynamic event"); + } + if (!events.get(0).getEventType().getName().equals("DynamicHighRate")) { + System.out.println(events); + throw new Exception("Expected DynamicHighRate"); + } + } + } + @SuppressWarnings("unchecked") private static void testEvent(Class eventClass, boolean shouldCommit) throws Exception { try (Recording r = new Recording()) { @@ -272,6 +313,8 @@ public class TestThrottle { } if (shouldCommit) { assertEvents(r, eventClass.getName(), 17 + 50 + 11); + } else { + assertEvents(r, eventClass.getName(), 0); } } } From d906e45026f54fe7102e531d3753adc32ff0025c Mon Sep 17 00:00:00 2001 From: Thomas Schatzl Date: Tue, 5 Aug 2025 16:13:53 +0000 Subject: [PATCH 032/580] 8364531: G1: Factor out liveness tracing code Reviewed-by: ayang, sangheki --- src/hotspot/share/gc/g1/g1ConcurrentMark.cpp | 79 ++++++++------------ src/hotspot/share/gc/g1/g1ConcurrentMark.hpp | 6 +- 2 files changed, 36 insertions(+), 49 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp index c728c11d5cd..c5b8c89e2f0 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp @@ -2975,7 +2975,6 @@ G1CMTask::G1CMTask(uint worker_id, #define G1PPRL_BYTE_FORMAT " %9zu" #define G1PPRL_BYTE_H_FORMAT " %9s" #define G1PPRL_DOUBLE_FORMAT "%14.1f" -#define G1PPRL_GCEFF_FORMAT " %14s" #define G1PPRL_GCEFF_H_FORMAT " %14s" #define G1PPRL_GID_H_FORMAT " %9s" #define G1PPRL_GID_FORMAT " " UINT32_FORMAT_W(9) @@ -3090,7 +3089,7 @@ G1PrintRegionLivenessInfoClosure::~G1PrintRegionLivenessInfoClosure() { // add static memory usages to remembered set sizes _total_remset_bytes += G1HeapRegionRemSet::static_mem_size(); - do_cset_groups(); + log_cset_candidate_groups(); // Print the footer of the output. log_trace(gc, liveness)(G1PPRL_LINE_PREFIX); @@ -3110,10 +3109,33 @@ G1PrintRegionLivenessInfoClosure::~G1PrintRegionLivenessInfoClosure() { bytes_to_mb(_total_code_roots_bytes)); } -void G1PrintRegionLivenessInfoClosure::do_cset_groups() { +void G1PrintRegionLivenessInfoClosure::log_cset_candidate_group_add_total(G1CSetCandidateGroup* group, const char* type) { + log_trace(gc, liveness)(G1PPRL_LINE_PREFIX + G1PPRL_GID_FORMAT + G1PPRL_LEN_FORMAT + G1PPRL_GID_GCEFF_FORMAT + G1PPRL_BYTE_FORMAT + G1PPRL_BYTE_FORMAT + G1PPRL_TYPE_H_FORMAT, + group->group_id(), + group->length(), + group->gc_efficiency(), + group->liveness(), + group->card_set()->mem_size(), + type); + _total_remset_bytes += group->card_set()->mem_size(); +} + +void G1PrintRegionLivenessInfoClosure::log_cset_candidate_grouplist(G1CSetCandidateGroupList& gl, const char* type) { + for (G1CSetCandidateGroup* group : gl) { + log_cset_candidate_group_add_total(group, type); + } +} + +void G1PrintRegionLivenessInfoClosure::log_cset_candidate_groups() { log_trace(gc, liveness)(G1PPRL_LINE_PREFIX); - log_trace(gc, liveness)(G1PPRL_LINE_PREFIX" Collectionset Candidate Groups"); - log_trace(gc, liveness)(G1PPRL_LINE_PREFIX " Types: Y=Young Regions, M=From Marking Regions, R=Retained Regions"); + log_trace(gc, liveness)(G1PPRL_LINE_PREFIX" Collection Set Candidate Groups"); + log_trace(gc, liveness)(G1PPRL_LINE_PREFIX " Types: Y=Young, M=From Marking Regions, R=Retained Regions"); log_trace(gc, liveness)(G1PPRL_LINE_PREFIX G1PPRL_GID_H_FORMAT G1PPRL_LEN_H_FORMAT @@ -3137,49 +3159,10 @@ void G1PrintRegionLivenessInfoClosure::do_cset_groups() { "(bytes)", ""); G1CollectedHeap* g1h = G1CollectedHeap::heap(); - G1CSetCandidateGroup* young_only_cset_group =g1h->young_regions_cset_group(); - _total_remset_bytes += young_only_cset_group->card_set()->mem_size(); + log_cset_candidate_group_add_total(g1h->young_regions_cset_group(), "Y"); - log_trace(gc, liveness)(G1PPRL_LINE_PREFIX - G1PPRL_GID_FORMAT - G1PPRL_LEN_FORMAT - G1PPRL_GCEFF_FORMAT - G1PPRL_BYTE_FORMAT - G1PPRL_BYTE_FORMAT - G1PPRL_TYPE_H_FORMAT, - young_only_cset_group->group_id(), young_only_cset_group->length(), - "-", - size_t(0), young_only_cset_group->card_set()->mem_size(), - "Y"); - - for (G1CSetCandidateGroup* group : g1h->policy()->candidates()->from_marking_groups()) { - _total_remset_bytes += group->card_set()->mem_size(); - log_trace(gc, liveness)(G1PPRL_LINE_PREFIX - G1PPRL_GID_FORMAT - G1PPRL_LEN_FORMAT - G1PPRL_GID_GCEFF_FORMAT - G1PPRL_BYTE_FORMAT - G1PPRL_BYTE_FORMAT - G1PPRL_TYPE_H_FORMAT, - group->group_id(), group->length(), - group->gc_efficiency(), - group->liveness(), group->card_set()->mem_size(), - "M"); - } - - for (G1CSetCandidateGroup* group : g1h->policy()->candidates()->retained_groups()) { - _total_remset_bytes += group->card_set()->mem_size(); - log_trace(gc, liveness)(G1PPRL_LINE_PREFIX - G1PPRL_GID_FORMAT - G1PPRL_LEN_FORMAT - G1PPRL_GID_GCEFF_FORMAT - G1PPRL_BYTE_FORMAT - G1PPRL_BYTE_FORMAT - G1PPRL_TYPE_H_FORMAT, - group->group_id(), group->length(), - group->gc_efficiency(), - group->liveness(), group->card_set()->mem_size(), - "R"); - } + G1CollectionSetCandidates* candidates = g1h->policy()->candidates(); + log_cset_candidate_grouplist(candidates->from_marking_groups(), "M"); + log_cset_candidate_grouplist(candidates->retained_groups(), "R"); } diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMark.hpp b/src/hotspot/share/gc/g1/g1ConcurrentMark.hpp index 3c3416ebcad..4977da4729d 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentMark.hpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentMark.hpp @@ -42,6 +42,8 @@ class ConcurrentGCTimer; class G1CollectedHeap; +class G1CSetCandidateGroup; +class G1CSetCandidateGroupList; class G1ConcurrentMark; class G1ConcurrentMarkThread; class G1CMOopClosure; @@ -974,7 +976,9 @@ class G1PrintRegionLivenessInfoClosure : public G1HeapRegionClosure { return (double) val / (double) M; } - void do_cset_groups(); + void log_cset_candidate_group_add_total(G1CSetCandidateGroup* gr, const char* type); + void log_cset_candidate_grouplist(G1CSetCandidateGroupList& gl, const char* type); + void log_cset_candidate_groups(); public: // The header and footer are printed in the constructor and From 68a35511ebd3fd77716242db973104657bc7b541 Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Tue, 5 Aug 2025 18:34:07 +0000 Subject: [PATCH 033/580] 8364212: Shenandoah: Rework archived objects loading Reviewed-by: wkemper, kdnilsen --- .../gc/shenandoah/shenandoahAllocRequest.hpp | 10 +++ .../share/gc/shenandoah/shenandoahFreeSet.cpp | 48 +++++++++---- .../share/gc/shenandoah/shenandoahFreeSet.hpp | 2 +- .../share/gc/shenandoah/shenandoahHeap.cpp | 72 +++++++------------ .../gc/shenandoah/shenandoahHeapRegion.cpp | 16 +---- .../shenandoahHeapRegion.inline.hpp | 1 + 6 files changed, 76 insertions(+), 73 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAllocRequest.hpp b/src/hotspot/share/gc/shenandoah/shenandoahAllocRequest.hpp index 6ac62e8e9ed..78ae78f4c24 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahAllocRequest.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahAllocRequest.hpp @@ -34,6 +34,7 @@ public: enum Type { _alloc_shared, // Allocate common, outside of TLAB _alloc_shared_gc, // Allocate common, outside of GCLAB/PLAB + _alloc_cds, // Allocate for CDS _alloc_tlab, // Allocate TLAB _alloc_gclab, // Allocate GCLAB _alloc_plab, // Allocate PLAB @@ -46,6 +47,8 @@ public: return "Shared"; case _alloc_shared_gc: return "Shared GC"; + case _alloc_cds: + return "CDS"; case _alloc_tlab: return "TLAB"; case _alloc_gclab: @@ -121,6 +124,10 @@ public: return ShenandoahAllocRequest(0, requested_size, _alloc_shared, ShenandoahAffiliation::YOUNG_GENERATION); } + static inline ShenandoahAllocRequest for_cds(size_t requested_size) { + return ShenandoahAllocRequest(0, requested_size, _alloc_cds, ShenandoahAffiliation::YOUNG_GENERATION); + } + inline size_t size() const { return _requested_size; } @@ -163,6 +170,7 @@ public: switch (_alloc_type) { case _alloc_tlab: case _alloc_shared: + case _alloc_cds: return true; case _alloc_gclab: case _alloc_plab: @@ -178,6 +186,7 @@ public: switch (_alloc_type) { case _alloc_tlab: case _alloc_shared: + case _alloc_cds: return false; case _alloc_gclab: case _alloc_plab: @@ -197,6 +206,7 @@ public: return true; case _alloc_shared: case _alloc_shared_gc: + case _alloc_cds: return false; default: ShouldNotReachHere(); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.cpp b/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.cpp index 56a2ff7e01a..293391a86eb 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.cpp @@ -816,6 +816,7 @@ HeapWord* ShenandoahFreeSet::allocate_single(ShenandoahAllocRequest& req, bool& switch (req.type()) { case ShenandoahAllocRequest::_alloc_tlab: case ShenandoahAllocRequest::_alloc_shared: + case ShenandoahAllocRequest::_alloc_cds: return allocate_for_mutator(req, in_new_region); case ShenandoahAllocRequest::_alloc_gclab: case ShenandoahAllocRequest::_alloc_plab: @@ -1169,8 +1170,8 @@ HeapWord* ShenandoahFreeSet::try_allocate_in(ShenandoahHeapRegion* r, Shenandoah return result; } -HeapWord* ShenandoahFreeSet::allocate_contiguous(ShenandoahAllocRequest& req) { - assert(req.is_mutator_alloc(), "All humongous allocations are performed by mutator"); +HeapWord* ShenandoahFreeSet::allocate_contiguous(ShenandoahAllocRequest& req, bool is_humongous) { + assert(req.is_mutator_alloc(), "All contiguous allocations are performed by mutator"); shenandoah_assert_heaplocked(); size_t words_size = req.size(); @@ -1244,10 +1245,16 @@ HeapWord* ShenandoahFreeSet::allocate_contiguous(ShenandoahAllocRequest& req) { assert(i == beg || _heap->get_region(i - 1)->index() + 1 == r->index(), "Should be contiguous"); assert(r->is_empty(), "Should be empty"); - if (i == beg) { - r->make_humongous_start(); + r->set_affiliation(req.affiliation()); + + if (is_humongous) { + if (i == beg) { + r->make_humongous_start(); + } else { + r->make_humongous_cont(); + } } else { - r->make_humongous_cont(); + r->make_regular_allocation(req.affiliation()); } // Trailing region may be non-full, record the remainder there @@ -1257,21 +1264,31 @@ HeapWord* ShenandoahFreeSet::allocate_contiguous(ShenandoahAllocRequest& req) { } else { used_words = ShenandoahHeapRegion::region_size_words(); } - - r->set_affiliation(req.affiliation()); r->set_update_watermark(r->bottom()); r->set_top(r->bottom() + used_words); } generation->increase_affiliated_region_count(num); - // retire_range_from_partition() will adjust bounds on Mutator free set if appropriate - _partitions.retire_range_from_partition(ShenandoahFreeSetPartitionId::Mutator, beg, end); - - size_t total_humongous_size = ShenandoahHeapRegion::region_size_bytes() * num; - _partitions.increase_used(ShenandoahFreeSetPartitionId::Mutator, total_humongous_size); + size_t total_used = 0; + if (is_humongous) { + // Humongous allocation retires all regions at once: no allocation is possible anymore. + _partitions.retire_range_from_partition(ShenandoahFreeSetPartitionId::Mutator, beg, end); + total_used = ShenandoahHeapRegion::region_size_bytes() * num; + } else { + // Non-humongous allocation retires only the regions that cannot be used for allocation anymore. + for (idx_t i = beg; i <= end; i++) { + ShenandoahHeapRegion* r = _heap->get_region(i); + if (r->free() < PLAB::min_size() * HeapWordSize) { + _partitions.retire_from_partition(ShenandoahFreeSetPartitionId::Mutator, i, r->used()); + } + total_used += r->used(); + } + } + _partitions.increase_used(ShenandoahFreeSetPartitionId::Mutator, total_used); _partitions.assert_bounds(); + req.set_actual_size(words_size); - if (remainder != 0) { + if (remainder != 0 && is_humongous) { req.set_waste(ShenandoahHeapRegion::region_size_words() - remainder); } return _heap->get_region(beg)->bottom(); @@ -2060,7 +2077,10 @@ HeapWord* ShenandoahFreeSet::allocate(ShenandoahAllocRequest& req, bool& in_new_ case ShenandoahAllocRequest::_alloc_shared: case ShenandoahAllocRequest::_alloc_shared_gc: in_new_region = true; - return allocate_contiguous(req); + return allocate_contiguous(req, /* is_humongous = */ true); + case ShenandoahAllocRequest::_alloc_cds: + in_new_region = true; + return allocate_contiguous(req, /* is_humongous = */ false); case ShenandoahAllocRequest::_alloc_plab: case ShenandoahAllocRequest::_alloc_gclab: case ShenandoahAllocRequest::_alloc_tlab: diff --git a/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.hpp b/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.hpp index 55f23480618..8ad7055b3d6 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.hpp @@ -345,7 +345,7 @@ private: // object. No other objects are packed into these regions. // // Precondition: ShenandoahHeapRegion::requires_humongous(req.size()) - HeapWord* allocate_contiguous(ShenandoahAllocRequest& req); + HeapWord* allocate_contiguous(ShenandoahAllocRequest& req, bool is_humongous); // Change region r from the Mutator partition to the GC's Collector or OldCollector partition. This requires that the // region is entirely empty. diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp index 9690b35c48d..2baf7e25cba 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp @@ -2773,41 +2773,15 @@ bool ShenandoahHeap::requires_barriers(stackChunkOop obj) const { HeapWord* ShenandoahHeap::allocate_loaded_archive_space(size_t size) { #if INCLUDE_CDS_JAVA_HEAP - // CDS wants a continuous memory range to load a bunch of objects. - // This effectively bypasses normal allocation paths, and requires - // a bit of massaging to unbreak GC invariants. + // CDS wants a raw continuous memory range to load a bunch of objects itself. + // This is an unusual request, since all requested regions should be regular, not humongous. + // + // CDS would guarantee no objects straddle multiple regions, as long as regions are as large + // as MIN_GC_REGION_ALIGNMENT. + guarantee(ShenandoahHeapRegion::region_size_bytes() >= ArchiveHeapWriter::MIN_GC_REGION_ALIGNMENT, "Must be"); - ShenandoahAllocRequest req = ShenandoahAllocRequest::for_shared(size); - - // Easy case: a single regular region, no further adjustments needed. - if (!ShenandoahHeapRegion::requires_humongous(size)) { - return allocate_memory(req); - } - - // Hard case: the requested size would cause a humongous allocation. - // We need to make sure it looks like regular allocation to the rest of GC. - - // CDS code would guarantee no objects straddle multiple regions, as long as - // regions are as large as MIN_GC_REGION_ALIGNMENT. It is impractical at this - // point to deal with case when Shenandoah runs with smaller regions. - // TODO: This check can be dropped once MIN_GC_REGION_ALIGNMENT agrees more with Shenandoah. - if (ShenandoahHeapRegion::region_size_bytes() < ArchiveHeapWriter::MIN_GC_REGION_ALIGNMENT) { - return nullptr; - } - - HeapWord* mem = allocate_memory(req); - size_t start_idx = heap_region_index_containing(mem); - size_t num_regions = ShenandoahHeapRegion::required_regions(size * HeapWordSize); - - // Flip humongous -> regular. - { - ShenandoahHeapLocker locker(lock(), false); - for (size_t c = start_idx; c < start_idx + num_regions; c++) { - get_region(c)->make_regular_bypass(); - } - } - - return mem; + ShenandoahAllocRequest req = ShenandoahAllocRequest::for_cds(size); + return allocate_memory(req); #else assert(false, "Archive heap loader should not be available, should not be here"); return nullptr; @@ -2834,17 +2808,25 @@ void ShenandoahHeap::complete_loaded_archive_space(MemRegion archive_space) { "Archive space should be fully used: " PTR_FORMAT " " PTR_FORMAT, p2i(cur), p2i(end)); - // Region bounds are good. - ShenandoahHeapRegion* begin_reg = heap_region_containing(start); - ShenandoahHeapRegion* end_reg = heap_region_containing(end); - assert(begin_reg->is_regular(), "Must be"); - assert(end_reg->is_regular(), "Must be"); - assert(begin_reg->bottom() == start, - "Must agree: archive-space-start: " PTR_FORMAT ", begin-region-bottom: " PTR_FORMAT, - p2i(start), p2i(begin_reg->bottom())); - assert(end_reg->top() == end, - "Must agree: archive-space-end: " PTR_FORMAT ", end-region-top: " PTR_FORMAT, - p2i(end), p2i(end_reg->top())); + // All regions in contiguous space have good state. + size_t begin_reg_idx = heap_region_index_containing(start); + size_t end_reg_idx = heap_region_index_containing(end); + + for (size_t idx = begin_reg_idx; idx <= end_reg_idx; idx++) { + ShenandoahHeapRegion* r = get_region(idx); + assert(r->is_regular(), "Must be regular"); + assert(r->is_young(), "Must be young"); + assert(idx == end_reg_idx || r->top() == r->end(), + "All regions except the last one should be full: " PTR_FORMAT " " PTR_FORMAT, + p2i(r->top()), p2i(r->end())); + assert(idx != begin_reg_idx || r->bottom() == start, + "Archive space start should be at the bottom of first region: " PTR_FORMAT " " PTR_FORMAT, + p2i(r->bottom()), p2i(start)); + assert(idx != end_reg_idx || r->top() == end, + "Archive space end should be at the top of last region: " PTR_FORMAT " " PTR_FORMAT, + p2i(r->top()), p2i(end)); + } + #endif } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.cpp index 2b2435288c3..3eb7ccba35a 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.cpp @@ -142,27 +142,17 @@ void ShenandoahHeapRegion::make_affiliated_maybe() { void ShenandoahHeapRegion::make_regular_bypass() { shenandoah_assert_heaplocked(); - assert (!Universe::is_fully_initialized() || - ShenandoahHeap::heap()->is_full_gc_in_progress() || + assert (ShenandoahHeap::heap()->is_full_gc_in_progress() || ShenandoahHeap::heap()->is_degenerated_gc_in_progress(), - "Only for STW GC or when Universe is initializing (CDS)"); + "Only for STW GC"); reset_age(); - auto cur_state = state(); - switch (cur_state) { + switch (state()) { case _empty_uncommitted: do_commit(); case _empty_committed: case _cset: case _humongous_start: case _humongous_cont: - if (cur_state == _humongous_start || cur_state == _humongous_cont) { - // CDS allocates chunks of the heap to fill with regular objects. The allocator - // will dutifully track any waste in the unused portion of the last region. Once - // CDS has finished initializing the objects, it will convert these regions to - // regular regions. The 'waste' in the last region is no longer wasted at this point, - // so we must stop treating it as such. - decrement_humongous_waste(); - } set_state(_regular); return; case _pinned_cset: diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.inline.hpp index 503f6656153..53f86c8cc58 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.inline.hpp @@ -112,6 +112,7 @@ inline void ShenandoahHeapRegion::adjust_alloc_metadata(ShenandoahAllocRequest:: switch (type) { case ShenandoahAllocRequest::_alloc_shared: case ShenandoahAllocRequest::_alloc_shared_gc: + case ShenandoahAllocRequest::_alloc_cds: // Counted implicitly by tlab/gclab allocs break; case ShenandoahAllocRequest::_alloc_tlab: From 6656e767db05e8e11ea17385674d8aa4385c60ed Mon Sep 17 00:00:00 2001 From: Anton Artemov Date: Wed, 6 Aug 2025 04:45:35 +0000 Subject: [PATCH 034/580] 8359820: Improve handshake/safepoint timeout diagnostic messages Reviewed-by: dholmes, stuefe --- src/hotspot/share/runtime/handshake.cpp | 8 +++++++- src/hotspot/share/runtime/safepoint.cpp | 2 ++ src/hotspot/share/utilities/vmError.cpp | 18 +++++++++++++++++- src/hotspot/share/utilities/vmError.hpp | 7 +++++++ .../TestAbortVMOnSafepointTimeout.java | 2 +- 5 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/hotspot/share/runtime/handshake.cpp b/src/hotspot/share/runtime/handshake.cpp index c55803242de..39ae4f18998 100644 --- a/src/hotspot/share/runtime/handshake.cpp +++ b/src/hotspot/share/runtime/handshake.cpp @@ -45,6 +45,7 @@ #include "utilities/globalDefinitions.hpp" #include "utilities/preserveException.hpp" #include "utilities/systemMemoryBarrier.hpp" +#include "utilities/vmError.hpp" class HandshakeOperation : public CHeapObj { friend class HandshakeState; @@ -201,6 +202,7 @@ static void handle_timeout(HandshakeOperation* op, JavaThread* target) { } if (target != nullptr) { + VMError::set_handshake_timed_out_thread(p2i(target)); if (os::signal_thread(target, SIGILL, "cannot be handshaked")) { // Give target a chance to report the error and terminate the VM. os::naked_sleep(3000); @@ -208,7 +210,11 @@ static void handle_timeout(HandshakeOperation* op, JavaThread* target) { } else { log_error(handshake)("No thread with an unfinished handshake op(" INTPTR_FORMAT ") found.", p2i(op)); } - fatal("Handshake timeout"); + if (target != nullptr) { + fatal("Thread " PTR_FORMAT " has not cleared handshake op %s, and failed to terminate the JVM", p2i(target), op->name()); + } else { + fatal("Handshake timeout"); + } } static void check_handshake_timeout(jlong start_time, HandshakeOperation* op, JavaThread* target = nullptr) { diff --git a/src/hotspot/share/runtime/safepoint.cpp b/src/hotspot/share/runtime/safepoint.cpp index ab896290007..6fcf1e6c0f5 100644 --- a/src/hotspot/share/runtime/safepoint.cpp +++ b/src/hotspot/share/runtime/safepoint.cpp @@ -66,6 +66,7 @@ #include "utilities/events.hpp" #include "utilities/macros.hpp" #include "utilities/systemMemoryBarrier.hpp" +#include "utilities/vmError.hpp" static void post_safepoint_begin_event(EventSafepointBegin& event, uint64_t safepoint_id, @@ -650,6 +651,7 @@ void SafepointSynchronize::print_safepoint_timeout() { // Send the blocking thread a signal to terminate and write an error file. for (JavaThreadIteratorWithHandle jtiwh; JavaThread *cur_thread = jtiwh.next(); ) { if (cur_thread->safepoint_state()->is_running()) { + VMError::set_safepoint_timed_out_thread(p2i(cur_thread)); if (!os::signal_thread(cur_thread, SIGILL, "blocking a safepoint")) { break; // Could not send signal. Report fatal error. } diff --git a/src/hotspot/share/utilities/vmError.cpp b/src/hotspot/share/utilities/vmError.cpp index df415944682..bd882a7ef3c 100644 --- a/src/hotspot/share/utilities/vmError.cpp +++ b/src/hotspot/share/utilities/vmError.cpp @@ -104,6 +104,8 @@ int VMError::_lineno; size_t VMError::_size; const size_t VMError::_reattempt_required_stack_headroom = 64 * K; const intptr_t VMError::segfault_address = pd_segfault_address; +volatile intptr_t VMError::_handshake_timed_out_thread = p2i(nullptr); +volatile intptr_t VMError::_safepoint_timed_out_thread = p2i(nullptr); // List of environment variables that should be reported in error log file. static const char* env_list[] = { @@ -819,7 +821,13 @@ void VMError::report(outputStream* st, bool _verbose) { st->print(" (0x%x)", _id); // signal number st->print(" at pc=" PTR_FORMAT, p2i(_pc)); if (_siginfo != nullptr && os::signal_sent_by_kill(_siginfo)) { - st->print(" (sent by kill)"); + if (_handshake_timed_out_thread == p2i(_thread)) { + st->print(" (sent by handshake timeout handler"); + } else if (_safepoint_timed_out_thread == p2i(_thread)) { + st->print(" (sent by safepoint timeout handler"); + } else { + st->print(" (sent by kill)"); + } } } else { if (should_report_bug(_id)) { @@ -1330,6 +1338,14 @@ void VMError::report(outputStream* st, bool _verbose) { # undef END } +void VMError::set_handshake_timed_out_thread(intptr_t thread_addr) { + _handshake_timed_out_thread = thread_addr; +} + +void VMError::set_safepoint_timed_out_thread(intptr_t thread_addr) { + _safepoint_timed_out_thread = thread_addr; +} + // Report for the vm_info_cmd. This prints out the information above omitting // crash and thread specific information. If output is added above, it should be added // here also, if it is safe to call during a running process. diff --git a/src/hotspot/share/utilities/vmError.hpp b/src/hotspot/share/utilities/vmError.hpp index 0109b9bf0bd..96e37acff57 100644 --- a/src/hotspot/share/utilities/vmError.hpp +++ b/src/hotspot/share/utilities/vmError.hpp @@ -142,6 +142,10 @@ class VMError : public AllStatic { static jlong get_step_start_time(); static void clear_step_start_time(); + // Handshake/safepoint timed out threads + static volatile intptr_t _handshake_timed_out_thread; + static volatile intptr_t _safepoint_timed_out_thread; + WINDOWS_ONLY([[noreturn]] static void raise_fail_fast(const void* exrecord, const void* context);) public: @@ -218,6 +222,9 @@ public: static int prepare_log_file(const char* pattern, const char* default_pattern, bool overwrite_existing, char* buf, size_t buflen); static bool was_assert_poison_crash(const void* sigInfo); + + static void set_handshake_timed_out_thread(intptr_t thread_addr); + static void set_safepoint_timed_out_thread(intptr_t thread_addr); }; class VMErrorCallback { diff --git a/test/hotspot/jtreg/runtime/Safepoint/TestAbortVMOnSafepointTimeout.java b/test/hotspot/jtreg/runtime/Safepoint/TestAbortVMOnSafepointTimeout.java index c85dd657338..e28dd7f4d2a 100644 --- a/test/hotspot/jtreg/runtime/Safepoint/TestAbortVMOnSafepointTimeout.java +++ b/test/hotspot/jtreg/runtime/Safepoint/TestAbortVMOnSafepointTimeout.java @@ -88,7 +88,7 @@ public class TestAbortVMOnSafepointTimeout { } else { output.shouldContain("SIGILL"); if (Platform.isLinux()) { - output.shouldContain("(sent by kill)"); + output.shouldContain("(sent by safepoint timeout handler"); } } output.shouldNotHaveExitValue(0); From ca41644538c7cba3e2f50b11c5045cf2aa595f7f Mon Sep 17 00:00:00 2001 From: Koushik Thirupattur Date: Wed, 6 Aug 2025 06:40:40 +0000 Subject: [PATCH 035/580] 8355379: Annotate lazy fields in java.security @Stable Reviewed-by: pminborg --- .../java/security/PKCS12Attribute.java | 10 ++- .../security/cert/URICertStoreParameters.java | 14 +-- .../security/PKCS12Attribute/HashCode.java | 90 +++++++++++++++++-- .../URICertStoreParameters/TestBasic.java | 18 +++- 4 files changed, 117 insertions(+), 15 deletions(-) diff --git a/src/java.base/share/classes/java/security/PKCS12Attribute.java b/src/java.base/share/classes/java/security/PKCS12Attribute.java index 114660c06f8..fd844dc7f4d 100644 --- a/src/java.base/share/classes/java/security/PKCS12Attribute.java +++ b/src/java.base/share/classes/java/security/PKCS12Attribute.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,6 +29,8 @@ import java.io.IOException; import java.math.BigInteger; import java.util.Arrays; import java.util.regex.Pattern; + +import jdk.internal.vm.annotation.Stable; import sun.security.util.*; /** @@ -45,7 +47,9 @@ public final class PKCS12Attribute implements KeyStore.Entry.Attribute { private String name; private String value; private final byte[] encoded; - private int hashValue = -1; + + @Stable + private int hashValue; /** * Constructs a PKCS12 attribute from its name and value. @@ -207,7 +211,7 @@ public final class PKCS12Attribute implements KeyStore.Entry.Attribute { @Override public int hashCode() { int h = hashValue; - if (h == -1) { + if (h == 0) { hashValue = h = Arrays.hashCode(encoded); } return h; diff --git a/src/java.base/share/classes/java/security/cert/URICertStoreParameters.java b/src/java.base/share/classes/java/security/cert/URICertStoreParameters.java index b73383cf3d5..0080bb7bc28 100644 --- a/src/java.base/share/classes/java/security/cert/URICertStoreParameters.java +++ b/src/java.base/share/classes/java/security/cert/URICertStoreParameters.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2023, 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 @@ -25,6 +25,8 @@ package java.security.cert; +import jdk.internal.vm.annotation.Stable; + import java.net.URI; /** @@ -57,7 +59,8 @@ public final class URICertStoreParameters implements CertStoreParameters { /* * Hash code for this parameters object. */ - private int myhash = -1; + @Stable + private int myhash; /** * Creates an instance of {@code URICertStoreParameters} with the @@ -105,10 +108,11 @@ public final class URICertStoreParameters implements CertStoreParameters { */ @Override public int hashCode() { - if (myhash == -1) { - myhash = uri.hashCode()*7; + int h = myhash; + if (h == 0) { + myhash = h = uri.hashCode()*7; } - return myhash; + return h; } /** diff --git a/test/jdk/java/security/PKCS12Attribute/HashCode.java b/test/jdk/java/security/PKCS12Attribute/HashCode.java index 21d2fd1f7f2..58217214321 100644 --- a/test/jdk/java/security/PKCS12Attribute/HashCode.java +++ b/test/jdk/java/security/PKCS12Attribute/HashCode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 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 @@ -23,18 +23,98 @@ /* * @test - * @bug 8200792 - * @summary PKCS12Attribute#hashCode is always constant -1 + * @bug 8200792 8355379 + * @summary Tests PKCS12Attribute#hashCode correctness, stability, and caching after Stable annotation fix */ - import java.security.PKCS12Attribute; +import java.util.HashSet; +import java.util.Set; public class HashCode { public static void main(String[] args) throws Exception { int h1 = new PKCS12Attribute("1.2.3.4", "AA").hashCode(); int h2 = new PKCS12Attribute("2.3.4.5", "BB,CC").hashCode(); if (h1 == -1 || h2 == -1 || h1 == h2) { - throw new Exception("I see " + h1 + " and " + h2); + throw new Exception("Unexpected hashCodes: " + h1 + " and " + h2); + } + + PKCS12Attribute attr1 = new PKCS12Attribute("1.2.3.4", "AA"); + + // Equality and hash code consistency + testHashCode(attr1); + + // Inequality and hash code difference + testInequality(new PKCS12Attribute("1.2.3.4", "AA"), + new PKCS12Attribute("1.2.3.4", "BB")); + testInequality(new PKCS12Attribute("1.2.3.4", "AA"), + new PKCS12Attribute("2.3.4.5", "AA")); + + // Repeated hashCode consistency + testRepeatHashCode(attr1); + + // Hash code uniqueness stress test + testHashCodeUniqueness(); + } + + private static void testHashCode(PKCS12Attribute attr) throws Exception { + int originalHash = attr.hashCode(); + + PKCS12Attribute reconstructed = new PKCS12Attribute(attr.getName(), attr.getValue()); + + if (!attr.equals(reconstructed)) { + throw new Exception("Equality failed for: name=" + attr.getName() + + ", value=" + attr.getValue()); + } + + int newHash = reconstructed.hashCode(); + if (originalHash != newHash) { + throw new Exception("Hash code mismatch for: name=" + attr.getName() + + ", original=" + originalHash + ", new=" + newHash); + } + + System.out.println("Pass: name=" + attr.getName() + ", value=" + attr.getValue() + + ", hashCode=" + originalHash); + } + + private static void testInequality(PKCS12Attribute a1, PKCS12Attribute a2) throws Exception { + if (a1.equals(a2)) { + throw new Exception("Unexpected equality: name=" + a1.getName() + + ", values=" + a1.getValue() + " vs " + a2.getValue()); + } + + if (a1.hashCode() == a2.hashCode()) { + System.out.println("Warning: Different attributes share hashCode: " + a1.hashCode()); + } else { + System.out.println("Pass: name=" + a1.getName() + + ", values differ and hashCodes differ"); } } + + private static void testRepeatHashCode(PKCS12Attribute attr) throws Exception { + int h1 = attr.hashCode(); + int h2 = attr.hashCode(); + + if (h1 != h2) { + throw new Exception("Inconsistent hashCode for: name=" + attr.getName() + + ", value=" + attr.getValue()); + } + + System.out.println("Pass: repeat hashCode consistency for name=" + attr.getName()); + } + + private static void testHashCodeUniqueness() { + Set seen = new HashSet<>(); + int collisions = 0; + + for (int i = 0; i < 1000; i++) { + PKCS12Attribute attr = new PKCS12Attribute("1.2.3." + i, "V" + i); + if (!seen.add(attr.hashCode())) { + System.out.println("Hash collision: name=" + attr.getName() + + ", value=" + attr.getValue()); + collisions++; + } + } + + System.out.println("Hash uniqueness test complete. Collisions: " + collisions); + } } diff --git a/test/jdk/java/security/cert/URICertStoreParameters/TestBasic.java b/test/jdk/java/security/cert/URICertStoreParameters/TestBasic.java index ed96d32a5c7..e62c1d469bd 100644 --- a/test/jdk/java/security/cert/URICertStoreParameters/TestBasic.java +++ b/test/jdk/java/security/cert/URICertStoreParameters/TestBasic.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,10 +24,12 @@ import java.security.cert.CertStore; import java.security.cert.URICertStoreParameters; import java.net.URI; +import java.util.HashSet; +import java.util.Set; /* * @test - * @bug 8038084 + * @bug 8038084 8355379 * @summary Basic testing for URICertStoreParameters * @run main TestBasic */ @@ -37,6 +39,7 @@ public class TestBasic { String str1 = "ldap://myownhost:5000/"; String str2 = "ldap://myownhost:5000/cn=foo"; test(str1, str2); + testRepeatedHashCode(str1); System.out.println("Test passed"); } @@ -68,4 +71,15 @@ public class TestBasic { throw new Exception("Error: p1/p1Too should NOT equal p2"); } } + + private static void testRepeatedHashCode(String str) throws Exception { + System.out.println("Testing repeated hashCode consistency"); + URICertStoreParameters p = new URICertStoreParameters(new URI(str)); + int h1 = p.hashCode(); + int h2 = p.hashCode(); + if (h1 != h2) { + throw new Exception("hashCode inconsistent across calls"); + } + System.out.println("hashCode consistency verified"); + } } From 8d529bc4f361407b0b5c3ac1e62c3be0f0b8d71c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joel=20Sikstr=C3=B6m?= Date: Wed, 6 Aug 2025 07:54:44 +0000 Subject: [PATCH 036/580] 8364518: Support for Job Objects in os::commit_memory_limit() on Windows Reviewed-by: ayang, dholmes --- src/hotspot/os/windows/os_windows.cpp | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/hotspot/os/windows/os_windows.cpp b/src/hotspot/os/windows/os_windows.cpp index 9c44ab669aa..f5067757125 100644 --- a/src/hotspot/os/windows/os_windows.cpp +++ b/src/hotspot/os/windows/os_windows.cpp @@ -3297,10 +3297,28 @@ static char* map_or_reserve_memory_aligned(size_t size, size_t alignment, int fi } size_t os::commit_memory_limit() { - MEMORYSTATUSEX ms; - ms.dwLength = sizeof(ms); - GlobalMemoryStatusEx(&ms); - return (size_t)ms.ullAvailVirtual; + JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = {}; + BOOL res = QueryInformationJobObject(nullptr, JobObjectExtendedLimitInformation, &jeli, sizeof(jeli), nullptr); + + if (!res) { + char buf[512]; + size_t buf_len = os::lasterror(buf, sizeof(buf)); + warning("Attempt to query job object information failed: %s", buf_len != 0 ? buf : ""); + + // Conservatively assume no limit when there was an error calling QueryInformationJobObject. + return SIZE_MAX; + } + + if (jeli.BasicLimitInformation.LimitFlags & JOB_OBJECT_LIMIT_PROCESS_MEMORY) { + return jeli.ProcessMemoryLimit; + } + + if (jeli.BasicLimitInformation.LimitFlags & JOB_OBJECT_LIMIT_JOB_MEMORY) { + return jeli.JobMemoryLimit; + } + + // No limit + return SIZE_MAX; } size_t os::reserve_memory_limit() { From e304d37996b075b8b2b44b5762d7d242169add49 Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Wed, 6 Aug 2025 08:32:25 +0000 Subject: [PATCH 037/580] 8361211: C2: Final graph reshaping generates unencodeable klass constants Reviewed-by: kvn, qamai, thartmann, mdoerr --- src/hotspot/share/opto/compile.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/hotspot/share/opto/compile.cpp b/src/hotspot/share/opto/compile.cpp index 4cab6aabbeb..2848ab5cc57 100644 --- a/src/hotspot/share/opto/compile.cpp +++ b/src/hotspot/share/opto/compile.cpp @@ -3620,7 +3620,10 @@ void Compile::final_graph_reshaping_main_switch(Node* n, Final_Reshape_Counts& f } else if (t->isa_oopptr()) { new_in2 = ConNode::make(t->make_narrowoop()); } else if (t->isa_klassptr()) { - new_in2 = ConNode::make(t->make_narrowklass()); + ciKlass* klass = t->is_klassptr()->exact_klass(); + if (klass->is_in_encoding_range()) { + new_in2 = ConNode::make(t->make_narrowklass()); + } } } if (new_in2 != nullptr) { @@ -3657,7 +3660,13 @@ void Compile::final_graph_reshaping_main_switch(Node* n, Final_Reshape_Counts& f } else if (t->isa_oopptr()) { n->subsume_by(ConNode::make(t->make_narrowoop()), this); } else if (t->isa_klassptr()) { - n->subsume_by(ConNode::make(t->make_narrowklass()), this); + ciKlass* klass = t->is_klassptr()->exact_klass(); + if (klass->is_in_encoding_range()) { + n->subsume_by(ConNode::make(t->make_narrowklass()), this); + } else { + assert(false, "unencodable klass in ConP -> EncodeP"); + C->record_failure("unencodable klass in ConP -> EncodeP"); + } } } if (in1->outcnt() == 0) { From 9dffbc9c4cfee7a1f023d548c12194bcf60e4ffd Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 6 Aug 2025 08:52:14 +0000 Subject: [PATCH 038/580] 8364540: Apply @Stable to Shared Secrets Reviewed-by: rriggs --- .../jdk/internal/access/SharedSecrets.java | 63 ++++++++++--------- 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/access/SharedSecrets.java b/src/java.base/share/classes/jdk/internal/access/SharedSecrets.java index 5c6212d0bf6..e20a1e77423 100644 --- a/src/java.base/share/classes/jdk/internal/access/SharedSecrets.java +++ b/src/java.base/share/classes/jdk/internal/access/SharedSecrets.java @@ -25,6 +25,8 @@ package jdk.internal.access; +import jdk.internal.vm.annotation.Stable; + import javax.crypto.SealedObject; import javax.crypto.spec.SecretKeySpec; import java.io.ObjectInputFilter; @@ -59,37 +61,38 @@ import javax.security.auth.x500.X500Principal; */ public class SharedSecrets { + // This field is not necessarily stable private static JavaAWTFontAccess javaAWTFontAccess; - private static JavaBeansAccess javaBeansAccess; - private static JavaLangAccess javaLangAccess; - private static JavaLangInvokeAccess javaLangInvokeAccess; - private static JavaLangModuleAccess javaLangModuleAccess; - private static JavaLangRefAccess javaLangRefAccess; - private static JavaLangReflectAccess javaLangReflectAccess; - private static JavaIOAccess javaIOAccess; - private static JavaIOFileDescriptorAccess javaIOFileDescriptorAccess; - private static JavaIORandomAccessFileAccess javaIORandomAccessFileAccess; - private static JavaObjectInputStreamReadString javaObjectInputStreamReadString; - private static JavaObjectInputStreamAccess javaObjectInputStreamAccess; - private static JavaObjectInputFilterAccess javaObjectInputFilterAccess; - private static JavaObjectStreamReflectionAccess javaObjectStreamReflectionAccess; - private static JavaNetInetAddressAccess javaNetInetAddressAccess; - private static JavaNetHttpCookieAccess javaNetHttpCookieAccess; - private static JavaNetUriAccess javaNetUriAccess; - private static JavaNetURLAccess javaNetURLAccess; - private static JavaNioAccess javaNioAccess; - private static JavaUtilCollectionAccess javaUtilCollectionAccess; - private static JavaUtilConcurrentTLRAccess javaUtilConcurrentTLRAccess; - private static JavaUtilConcurrentFJPAccess javaUtilConcurrentFJPAccess; - private static JavaUtilJarAccess javaUtilJarAccess; - private static JavaUtilZipFileAccess javaUtilZipFileAccess; - private static JavaUtilResourceBundleAccess javaUtilResourceBundleAccess; - private static JavaSecurityPropertiesAccess javaSecurityPropertiesAccess; - private static JavaSecuritySignatureAccess javaSecuritySignatureAccess; - private static JavaSecuritySpecAccess javaSecuritySpecAccess; - private static JavaxCryptoSealedObjectAccess javaxCryptoSealedObjectAccess; - private static JavaxCryptoSpecAccess javaxCryptoSpecAccess; - private static JavaxSecurityAccess javaxSecurityAccess; + @Stable private static JavaBeansAccess javaBeansAccess; + @Stable private static JavaLangAccess javaLangAccess; + @Stable private static JavaLangInvokeAccess javaLangInvokeAccess; + @Stable private static JavaLangModuleAccess javaLangModuleAccess; + @Stable private static JavaLangRefAccess javaLangRefAccess; + @Stable private static JavaLangReflectAccess javaLangReflectAccess; + @Stable private static JavaIOAccess javaIOAccess; + @Stable private static JavaIOFileDescriptorAccess javaIOFileDescriptorAccess; + @Stable private static JavaIORandomAccessFileAccess javaIORandomAccessFileAccess; + @Stable private static JavaObjectInputStreamReadString javaObjectInputStreamReadString; + @Stable private static JavaObjectInputStreamAccess javaObjectInputStreamAccess; + @Stable private static JavaObjectInputFilterAccess javaObjectInputFilterAccess; + @Stable private static JavaObjectStreamReflectionAccess javaObjectStreamReflectionAccess; + @Stable private static JavaNetInetAddressAccess javaNetInetAddressAccess; + @Stable private static JavaNetHttpCookieAccess javaNetHttpCookieAccess; + @Stable private static JavaNetUriAccess javaNetUriAccess; + @Stable private static JavaNetURLAccess javaNetURLAccess; + @Stable private static JavaNioAccess javaNioAccess; + @Stable private static JavaUtilCollectionAccess javaUtilCollectionAccess; + @Stable private static JavaUtilConcurrentTLRAccess javaUtilConcurrentTLRAccess; + @Stable private static JavaUtilConcurrentFJPAccess javaUtilConcurrentFJPAccess; + @Stable private static JavaUtilJarAccess javaUtilJarAccess; + @Stable private static JavaUtilZipFileAccess javaUtilZipFileAccess; + @Stable private static JavaUtilResourceBundleAccess javaUtilResourceBundleAccess; + @Stable private static JavaSecurityPropertiesAccess javaSecurityPropertiesAccess; + @Stable private static JavaSecuritySignatureAccess javaSecuritySignatureAccess; + @Stable private static JavaSecuritySpecAccess javaSecuritySpecAccess; + @Stable private static JavaxCryptoSealedObjectAccess javaxCryptoSealedObjectAccess; + @Stable private static JavaxCryptoSpecAccess javaxCryptoSpecAccess; + @Stable private static JavaxSecurityAccess javaxSecurityAccess; public static void setJavaUtilCollectionAccess(JavaUtilCollectionAccess juca) { javaUtilCollectionAccess = juca; From 0ceb366dc26e2e4f6252da9dd8930b016a5d46ba Mon Sep 17 00:00:00 2001 From: David Beaumont Date: Wed, 6 Aug 2025 08:55:47 +0000 Subject: [PATCH 039/580] 8356645: Javac should utilize new ZIP file system read-only access mode Reviewed-by: jlahoda --- .../com/sun/tools/javac/file/FSInfo.java | 34 ++++++++++++++++--- .../tools/javac/file/JavacFileManager.java | 14 ++++---- .../com/sun/tools/javac/file/Locations.java | 15 ++++---- .../javac/platform/JDKPlatformProvider.java | 22 +++++++++--- .../tools/javac/api/file/SJFM_TestBase.java | 5 +-- .../javac/platform/VerifyCTSymClassFiles.java | 14 ++++++-- 6 files changed, 73 insertions(+), 31 deletions(-) diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/file/FSInfo.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/file/FSInfo.java index e2ab9b1679c..420ef59a647 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/file/FSInfo.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/file/FSInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2025, Oracle and/or its affiliates. All rights reserved. * 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 @@ package com.sun.tools.javac.file; -import java.io.IOError; import java.io.IOException; import java.net.MalformedURLException; +import java.net.URI; import java.net.URISyntaxException; import java.net.URL; -import java.nio.file.FileSystems; import java.nio.file.Files; -import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.file.spi.FileSystemProvider; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.StringTokenizer; import java.util.jar.Attributes; import java.util.jar.JarFile; @@ -163,4 +163,30 @@ public class FSInfo { return null; } + // Must match the keys/values expected by ZipFileSystem.java. + private static final Map READ_ONLY_JARFS_ENV = Map.of( + // Jar files opened by Javac should always be read-only. + "accessMode", "readOnly", + // ignores timestamps not stored in ZIP central directory, reducing I/O. + "zipinfo-time", "false"); + + /** + * Returns a {@link java.nio.file.FileSystem FileSystem} environment map + * suitable for creating read-only JAR file-systems with default timestamp + * information via {@link FileSystemProvider#newFileSystem(Path, Map)} + * or {@link java.nio.file.FileSystems#newFileSystem(Path, Map)}. + * + * @param releaseVersion the release version to use when creating a + * file-system from a multi-release JAR (or + * {@code null} to ignore release versioning). + */ + public Map readOnlyJarFSEnv(String releaseVersion) { + if (releaseVersion == null) { + return READ_ONLY_JARFS_ENV; + } + // Multi-release JARs need an additional attribute. + Map env = new HashMap<>(READ_ONLY_JARFS_ENV); + env.put("releaseVersion", releaseVersion); + return Collections.unmodifiableMap(env); + } } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/file/JavacFileManager.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/file/JavacFileManager.java index 28274d26542..885d377e137 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/file/JavacFileManager.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/file/JavacFileManager.java @@ -561,15 +561,10 @@ public class JavacFileManager extends BaseFileManager implements StandardJavaFil public ArchiveContainer(Path archivePath) throws IOException, ProviderNotFoundException { this.archivePath = archivePath; - Map env = new HashMap<>(); - // ignores timestamps not stored in ZIP central directory, reducing I/O - // This key is handled by ZipFileSystem only. - env.put("zipinfo-time", "false"); - if (multiReleaseValue != null && archivePath.toString().endsWith(".jar")) { - env.put("multi-release", multiReleaseValue); FileSystemProvider jarFSProvider = fsInfo.getJarFSProvider(); Assert.checkNonNull(jarFSProvider, "should have been caught before!"); + Map env = fsInfo.readOnlyJarFSEnv(multiReleaseValue); try { this.fileSystem = jarFSProvider.newFileSystem(archivePath, env); } catch (ZipException ze) { @@ -577,8 +572,11 @@ public class JavacFileManager extends BaseFileManager implements StandardJavaFil } } else { // Less common case is possible if the file manager was not initialized in JavacTask, - // or if non "*.jar" files are on the classpath. - this.fileSystem = FileSystems.newFileSystem(archivePath, env, (ClassLoader)null); + // or if non "*.jar" files are on the classpath. If this is not a ZIP/JAR file then it + // will ignore ZIP specific parameters in env, and may not end up being read-only. + // However, Javac should never attempt to write back to archives either way. + Map env = fsInfo.readOnlyJarFSEnv(null); + this.fileSystem = FileSystems.newFileSystem(archivePath, env); } packages = new HashMap<>(); for (Path root : fileSystem.getRootDirectories()) { diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/file/Locations.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/file/Locations.java index 5ff55d4be3a..ff02de705fb 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/file/Locations.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/file/Locations.java @@ -81,14 +81,10 @@ import com.sun.tools.javac.code.Lint; import com.sun.tools.javac.resources.CompilerProperties.LintWarnings; import jdk.internal.jmod.JmodFile; -import com.sun.tools.javac.code.Lint; -import com.sun.tools.javac.code.Lint.LintCategory; import com.sun.tools.javac.main.Option; import com.sun.tools.javac.resources.CompilerProperties.Errors; -import com.sun.tools.javac.resources.CompilerProperties.Warnings; import com.sun.tools.javac.util.DefinedBy; import com.sun.tools.javac.util.DefinedBy.Api; -import com.sun.tools.javac.util.JCDiagnostic.Warning; import com.sun.tools.javac.util.ListBuffer; import com.sun.tools.javac.util.Log; import com.sun.tools.javac.jvm.ModuleNameReader; @@ -141,7 +137,7 @@ public class Locations { Map fileSystems = new LinkedHashMap<>(); List closeables = new ArrayList<>(); - private Map fsEnv = Collections.emptyMap(); + private String releaseVersion = null; Locations() { initHandlers(); @@ -233,7 +229,8 @@ public class Locations { } public void setMultiReleaseValue(String multiReleaseValue) { - fsEnv = Collections.singletonMap("releaseVersion", multiReleaseValue); + // Null is implicitly allowed and unsets the value. + this.releaseVersion = multiReleaseValue; } private boolean contains(Collection searchPath, Path file) throws IOException { @@ -480,7 +477,7 @@ public class Locations { } /** - * @see JavaFileManager#getLocationForModule(Location, JavaFileObject, String) + * @see JavaFileManager#getLocationForModule(Location, JavaFileObject) */ Location getLocationForModule(Path file) throws IOException { return null; @@ -1387,7 +1384,7 @@ public class Locations { log.error(Errors.NoZipfsForArchive(p)); return null; } - try (FileSystem fs = jarFSProvider.newFileSystem(p, fsEnv)) { + try (FileSystem fs = jarFSProvider.newFileSystem(p, fsInfo.readOnlyJarFSEnv(releaseVersion))) { Path moduleInfoClass = fs.getPath("module-info.class"); if (Files.exists(moduleInfoClass)) { String moduleName = readModuleName(moduleInfoClass); @@ -1463,7 +1460,7 @@ public class Locations { log.error(Errors.LocnCantReadFile(p)); return null; } - fs = jarFSProvider.newFileSystem(p, Collections.emptyMap()); + fs = jarFSProvider.newFileSystem(p, fsInfo.readOnlyJarFSEnv(null)); try { Path moduleInfoClass = fs.getPath("classes/module-info.class"); String moduleName = readModuleName(moduleInfoClass); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/platform/JDKPlatformProvider.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/platform/JDKPlatformProvider.java index 4c24f9892a6..2360fce1f75 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/platform/JDKPlatformProvider.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/platform/JDKPlatformProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -51,10 +51,8 @@ import java.util.TreeSet; import javax.annotation.processing.Processor; import javax.tools.ForwardingJavaFileObject; import javax.tools.JavaFileManager; -import javax.tools.JavaFileManager.Location; import javax.tools.JavaFileObject; import javax.tools.JavaFileObject.Kind; -import javax.tools.StandardJavaFileManager; import javax.tools.StandardLocation; import com.sun.source.util.Plugin; @@ -64,6 +62,7 @@ import com.sun.tools.javac.file.CacheFSInfo; import com.sun.tools.javac.file.JavacFileManager; import com.sun.tools.javac.jvm.Target; import com.sun.tools.javac.main.Option; +import com.sun.tools.javac.util.Assert; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Log; import com.sun.tools.javac.util.StringUtils; @@ -96,6 +95,14 @@ public class JDKPlatformProvider implements PlatformProvider { private static final String[] symbolFileLocation = { "lib", "ct.sym" }; + // These must match attributes defined in ZipFileSystem.java. + private static final Map CT_SYM_ZIP_ENV = Map.of( + // Symbol file should always be opened read-only. + "accessMode", "readOnly", + // Uses less accurate, but faster, timestamp information + // (nobody should care about timestamps in the CT symbol file). + "zipinfo-time", "false"); + private static final Set SUPPORTED_JAVA_PLATFORM_VERSIONS; public static final Comparator NUMERICAL_COMPARATOR = (s1, s2) -> { int i1; @@ -117,7 +124,7 @@ public class JDKPlatformProvider implements PlatformProvider { SUPPORTED_JAVA_PLATFORM_VERSIONS = new TreeSet<>(NUMERICAL_COMPARATOR); Path ctSymFile = findCtSym(); if (Files.exists(ctSymFile)) { - try (FileSystem fs = FileSystems.newFileSystem(ctSymFile, (ClassLoader)null); + try (FileSystem fs = FileSystems.newFileSystem(ctSymFile, CT_SYM_ZIP_ENV); DirectoryStream dir = Files.newDirectoryStream(fs.getRootDirectories().iterator().next())) { for (Path section : dir) { @@ -249,7 +256,12 @@ public class JDKPlatformProvider implements PlatformProvider { try { FileSystem fs = ctSym2FileSystem.get(file); if (fs == null) { - ctSym2FileSystem.put(file, fs = FileSystems.newFileSystem(file, (ClassLoader)null)); + fs = FileSystems.newFileSystem(file, CT_SYM_ZIP_ENV); + // If for any reason this was not opened from a ZIP file, + // then the resulting file system would not be read-only. + // NOTE: This check is disabled until JDK 25 bootstrap! + // Assert.check(fs.isReadOnly()); + ctSym2FileSystem.put(file, fs); } Path root = fs.getRootDirectories().iterator().next(); diff --git a/test/langtools/tools/javac/api/file/SJFM_TestBase.java b/test/langtools/tools/javac/api/file/SJFM_TestBase.java index fb3860edb75..52fd36afc5d 100644 --- a/test/langtools/tools/javac/api/file/SJFM_TestBase.java +++ b/test/langtools/tools/javac/api/file/SJFM_TestBase.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -37,6 +37,7 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @@ -160,7 +161,7 @@ class SJFM_TestBase { List getTestZipPaths() throws IOException { if (zipfs == null) { Path testZip = createSourceZip(); - zipfs = FileSystems.newFileSystem(testZip); + zipfs = FileSystems.newFileSystem(testZip, Map.of("accessMode", "readOnly")); closeables.add(zipfs); zipPaths = Files.list(zipfs.getRootDirectories().iterator().next()) .filter(p -> p.getFileName().toString().endsWith(".java")) diff --git a/test/langtools/tools/javac/platform/VerifyCTSymClassFiles.java b/test/langtools/tools/javac/platform/VerifyCTSymClassFiles.java index 3c563904b16..aeafb1ee08b 100644 --- a/test/langtools/tools/javac/platform/VerifyCTSymClassFiles.java +++ b/test/langtools/tools/javac/platform/VerifyCTSymClassFiles.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,10 +23,11 @@ /** * @test - * @bug 8331027 + * @bug 8331027 8356645 * @summary Verify classfile inside ct.sym * @library /tools/lib * @modules jdk.compiler/com.sun.tools.javac.api + * jdk.compiler/com.sun.tools.javac.file * jdk.compiler/com.sun.tools.javac.main * jdk.compiler/com.sun.tools.javac.platform * jdk.compiler/com.sun.tools.javac.util:+open @@ -44,6 +45,7 @@ import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Map; public class VerifyCTSymClassFiles { @@ -60,7 +62,13 @@ public class VerifyCTSymClassFiles { //no ct.sym, nothing to check: return ; } - try (FileSystem fs = FileSystems.newFileSystem(ctSym)) { + // Expected to always be a ZIP filesystem. + Map env = Map.of("accessMode", "readOnly"); + try (FileSystem fs = FileSystems.newFileSystem(ctSym, env)) { + // Check that the file system is read only (not true if not a zip file system). + if (!fs.isReadOnly()) { + throw new AssertionError("Expected read-only file system"); + } Files.walk(fs.getRootDirectories().iterator().next()) .filter(p -> Files.isRegularFile(p)) .forEach(p -> checkClassFile(p)); From 72d1066ae385ce511a70e762ed68afaa72e96ec4 Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Wed, 6 Aug 2025 12:21:16 +0000 Subject: [PATCH 040/580] 8364722: Parallel: Move CLDG mark clearing to the end of full GC Reviewed-by: tschatzl, zgu --- src/hotspot/share/gc/parallel/psParallelCompact.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hotspot/share/gc/parallel/psParallelCompact.cpp b/src/hotspot/share/gc/parallel/psParallelCompact.cpp index 1e4f7c856b0..b549c61e1c0 100644 --- a/src/hotspot/share/gc/parallel/psParallelCompact.cpp +++ b/src/hotspot/share/gc/parallel/psParallelCompact.cpp @@ -685,6 +685,9 @@ void PSParallelCompact::post_compact() CodeCache::on_gc_marking_cycle_finish(); CodeCache::arm_all_nmethods(); + // Need to clear claim bits for the next full-gc (marking and adjust-pointers). + ClassLoaderDataGraph::clear_claimed_marks(); + for (unsigned int id = old_space_id; id < last_space_id; ++id) { // Clear the marking bitmap, summary data and split info. clear_data_covering_space(SpaceId(id)); @@ -1288,9 +1291,6 @@ void PSParallelCompact::marking_phase(ParallelOldTracer *gc_tracer) { ClassLoaderDataGraph::purge(true /* at_safepoint */); DEBUG_ONLY(MetaspaceUtils::verify();) } - - // Need to clear claim bits for the next mark. - ClassLoaderDataGraph::clear_claimed_marks(); } { From f95af744b07a9ec87e2507b3d584cbcddc827bbd Mon Sep 17 00:00:00 2001 From: Guanqiang Han Date: Wed, 6 Aug 2025 15:37:31 +0000 Subject: [PATCH 041/580] 8364312: debug agent should set FD_CLOEXEC flag rather than explicitly closing every open file Reviewed-by: cjplummer, kevinw --- .../unix/native/libjdwp/exec_md.c | 87 ++++++++++--------- 1 file changed, 44 insertions(+), 43 deletions(-) diff --git a/src/jdk.jdwp.agent/unix/native/libjdwp/exec_md.c b/src/jdk.jdwp.agent/unix/native/libjdwp/exec_md.c index 096a70fe524..3a66e451519 100644 --- a/src/jdk.jdwp.agent/unix/native/libjdwp/exec_md.c +++ b/src/jdk.jdwp.agent/unix/native/libjdwp/exec_md.c @@ -30,6 +30,7 @@ #include #include #include +#include #include "sys.h" #include "util.h" #include "error_messages.h" @@ -48,6 +49,21 @@ static char *skipNonWhitespace(char *p) { return p; } +static int +markCloseOnExec(int fd) +{ + const int flags = fcntl(fd, F_GETFD); + if (flags < 0) { + return -1; + } + if ((flags & FD_CLOEXEC) == 0) { + if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0) { + return -1; + } + } + return 0; +} + #if defined(_AIX) /* AIX does not understand '/proc/self' - it requires the real process ID */ #define FD_DIR aix_fd_dir @@ -57,73 +73,60 @@ static char *skipNonWhitespace(char *p) { #define FD_DIR "/proc/self/fd" #endif -// Closes every file descriptor that is listed as a directory -// entry in "/proc/self/fd" (or its equivalent). Standard -// input/output/error file descriptors will not be closed -// by this function. This function returns 0 on failure -// and 1 on success. +// Marks all file descriptors found in /proc/self/fd with the +// FD_CLOEXEC flag to ensure they are automatically closed +// upon execution of a new program via exec(). This function +// returns -1 on failure and 0 on success. static int -closeDescriptors(void) +markDescriptorsCloseOnExec(void) { DIR *dp; struct dirent *dirp; - /* leave out standard input/output/error descriptors */ - int from_fd = STDERR_FILENO + 1; - - /* We're trying to close all file descriptors, but opendir() might - * itself be implemented using a file descriptor, and we certainly - * don't want to close that while it's in use. We assume that if - * opendir() is implemented using a file descriptor, then it uses - * the lowest numbered file descriptor, just like open(). So - * before calling opendir(), we close a couple explicitly, so that - * opendir() can then use these lowest numbered closed file - * descriptors afresh. - * - * WARNING: We are not allowed to return with a failure until after - * these two closes are done. forkedChildProcess() relies on this. */ - - close(from_fd); /* for possible use by opendir() */ - close(from_fd + 1); /* another one for good luck */ - from_fd += 2; /* leave out the 2 we just closed, which the opendir() may use */ + const int from_fd = STDERR_FILENO; #if defined(_AIX) - /* set FD_DIR for AIX which does not understand '/proc/self' - it - * requires the real process ID */ + /* AIX does not understand '/proc/self' - it requires the real process ID */ char aix_fd_dir[32]; /* the pid has at most 19 digits */ snprintf(aix_fd_dir, 32, "/proc/%d/fd", getpid()); #endif if ((dp = opendir(FD_DIR)) == NULL) { ERROR_MESSAGE(("failed to open dir %s while determining" - " file descriptors to close for process %d", + " file descriptors to mark or close for process %d", FD_DIR, getpid())); - return 0; // failure + return -1; // failure } + int dir_fd = dirfd(dp); + while ((dirp = readdir(dp)) != NULL) { if (!isdigit(dirp->d_name[0])) { continue; } - const long fd = strtol(dirp->d_name, NULL, 10); - if (fd <= INT_MAX && fd >= from_fd) { - (void)close((int)fd); + int fd = strtol(dirp->d_name, NULL, 10); + if (fd <= INT_MAX && fd > from_fd && fd != dir_fd) { + if (markCloseOnExec(fd) == -1) { + (void)close((int)fd); + } } } (void)closedir(dp); - return 1; // success + return 0; // success } -// Does necessary housekeeping of a forked child process -// (like closing copied file descriptors) before -// execing the child process. This function never returns. +// Performs necessary housekeeping in the forked child process, +// such as marking copied file descriptors (except standard input/output/error) +// with FD_CLOEXEC to ensure they are closed during exec(). +// This function never returns. static void forkedChildProcess(const char *file, char *const argv[]) { - /* Close all file descriptors that have been copied over - * from the parent process due to fork(). */ - if (closeDescriptors() == 0) { /* failed, close the old way */ + /* Mark all file descriptors (except standard input/output/error) + * copied from the parent process with FD_CLOEXEC, so they are + * closed automatically upon exec(). */ + if (markDescriptorsCloseOnExec() < 0) { /* failed, close the old way */ /* Find max allowed file descriptors for a process * and assume all were opened for the parent process and * copied over to this child process. We close them all. */ @@ -131,13 +134,11 @@ forkedChildProcess(const char *file, char *const argv[]) JDI_ASSERT(max_fd != (rlim_t)-1); // -1 represents error /* close(), that we subsequently call, takes only int values */ JDI_ASSERT(max_fd <= INT_MAX); - /* Leave out standard input/output/error file descriptors. Also, - * leave out STDERR_FILENO +1 and +2 since closeDescriptors() - * already closed them, even when returning an error. */ - rlim_t i = STDERR_FILENO + 3; + /* leave out standard input/output/error file descriptors */ + rlim_t i = STDERR_FILENO + 1; ERROR_MESSAGE(("failed to close file descriptors of" " child process optimally, falling back to closing" - " %d file descriptors sequentially", (max_fd - i + 1))); + " %d file descriptors sequentially", (max_fd - i))); for (; i < max_fd; i++) { (void)close(i); } From 7e484e2a63e40740282b3da5d7b10e9f500bf6ab Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Thu, 7 Aug 2025 02:02:36 +0000 Subject: [PATCH 042/580] 8334238: Enhance AddLShortcutTest jpackage test Reviewed-by: almatvee --- test/jdk/tools/jpackage/apps/PrintEnv.java | 33 +- test/jdk/tools/jpackage/clean_test_output.sh | 87 ++++ .../jdk/jpackage/test/AdditionalLauncher.java | 393 +++++------------- .../jdk/jpackage/test/AppImageFile.java | 69 ++- .../jdk/jpackage/test/CommandArguments.java | 5 +- .../jdk/jpackage/test/ConfigFilesStasher.java | 2 +- .../jdk/jpackage/test/JPackageCommand.java | 75 ++-- .../test/LauncherAsServiceVerifier.java | 95 ++--- .../jpackage/test/LauncherIconVerifier.java | 21 +- .../jdk/jpackage/test/LauncherShortcut.java | 167 ++++++++ .../jdk/jpackage/test/LauncherVerifier.java | 326 +++++++++++++++ .../jdk/jpackage/test/LinuxHelper.java | 243 ++++++++--- .../jdk/jpackage/test/MsiDatabase.java | 377 +++++++++++++++++ .../jdk/jpackage/test/PackageTest.java | 52 +-- .../helpers/jdk/jpackage/test/TKit.java | 29 +- .../jpackage/test/WinShortcutVerifier.java | 283 +++++++++++++ .../jdk/jpackage/test/WindowsHelper.java | 310 +++++++------- .../jdk/tools/jpackage/linux/UpgradeTest.java | 10 +- .../tools/jpackage/resources/msi-export.js | 81 ++++ .../jpackage/resources/query-msi-property.js | 65 --- .../jpackage/share/AddLShortcutTest.java | 322 +++++++++++++- .../tools/jpackage/share/AddLauncherTest.java | 14 +- .../tools/jpackage/share/PerUserCfgTest.java | 14 +- 23 files changed, 2337 insertions(+), 736 deletions(-) create mode 100644 test/jdk/tools/jpackage/clean_test_output.sh create mode 100644 test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LauncherShortcut.java create mode 100644 test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LauncherVerifier.java create mode 100644 test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MsiDatabase.java create mode 100644 test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WinShortcutVerifier.java create mode 100644 test/jdk/tools/jpackage/resources/msi-export.js delete mode 100644 test/jdk/tools/jpackage/resources/query-msi-property.js diff --git a/test/jdk/tools/jpackage/apps/PrintEnv.java b/test/jdk/tools/jpackage/apps/PrintEnv.java index bb1cef800f4..64a243a0abc 100644 --- a/test/jdk/tools/jpackage/apps/PrintEnv.java +++ b/test/jdk/tools/jpackage/apps/PrintEnv.java @@ -21,18 +21,38 @@ * questions. */ +import java.io.IOException; +import java.io.UncheckedIOException; import java.lang.module.ModuleDescriptor; import java.lang.module.ModuleFinder; import java.lang.module.ModuleReference; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; public class PrintEnv { public static void main(String[] args) { List lines = printArgs(args); - lines.forEach(System.out::println); + Optional.ofNullable(System.getProperty("jpackage.test.appOutput")).map(Path::of).ifPresentOrElse(outputFilePath -> { + Optional.ofNullable(outputFilePath.getParent()).ifPresent(dir -> { + try { + Files.createDirectories(dir); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + }); + try { + Files.write(outputFilePath, lines); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + }, () -> { + lines.forEach(System.out::println); + }); } private static List printArgs(String[] args) { @@ -45,11 +65,13 @@ public class PrintEnv { } else if (arg.startsWith(PRINT_SYS_PROP)) { String name = arg.substring(PRINT_SYS_PROP.length()); lines.add(name + "=" + System.getProperty(name)); - } else if (arg.startsWith(PRINT_MODULES)) { + } else if (arg.equals(PRINT_MODULES)) { lines.add(ModuleFinder.ofSystem().findAll().stream() .map(ModuleReference::descriptor) .map(ModuleDescriptor::name) .collect(Collectors.joining(","))); + } else if (arg.equals(PRINT_WORK_DIR)) { + lines.add("$CD=" + Path.of("").toAbsolutePath()); } else { throw new IllegalArgumentException(); } @@ -58,7 +80,8 @@ public class PrintEnv { return lines; } - private final static String PRINT_ENV_VAR = "--print-env-var="; - private final static String PRINT_SYS_PROP = "--print-sys-prop="; - private final static String PRINT_MODULES = "--print-modules"; + private static final String PRINT_ENV_VAR = "--print-env-var="; + private static final String PRINT_SYS_PROP = "--print-sys-prop="; + private static final String PRINT_MODULES = "--print-modules"; + private static final String PRINT_WORK_DIR = "--print-workdir"; } diff --git a/test/jdk/tools/jpackage/clean_test_output.sh b/test/jdk/tools/jpackage/clean_test_output.sh new file mode 100644 index 00000000000..e472d780ded --- /dev/null +++ b/test/jdk/tools/jpackage/clean_test_output.sh @@ -0,0 +1,87 @@ +#!/bin/bash + +# 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. + +# +# Filters output produced by running jpackage test(s). +# + +set -eu +set -o pipefail + + +sed_inplace_option=-i +sed_version_string=$(sed --version 2>&1 | head -1 || true) +if [ "${sed_version_string#sed (GNU sed)}" != "$sed_version_string" ]; then + # GNU sed, the default + : +elif [ "${sed_version_string#sed: illegal option}" != "$sed_version_string" ]; then + # Macos sed + sed_inplace_option="-i ''" +else + echo 'WARNING: Unknown sed variant, assume it is GNU compatible' +fi + + +filterFile () { + local expressions=( + # Strip leading log message timestamp `[19:33:44.713] ` + -e 's/^\[[0-9]\{2\}:[0-9]\{2\}:[0-9]\{2\}\.[0-9]\{3\}\] //' + + # Strip log message timestamps `[19:33:44.713]` + -e 's/\[[0-9]\{2\}:[0-9]\{2\}:[0-9]\{2\}\.[0-9]\{3\}\]//g' + + # Convert variable part of R/O directory path timestamp `#2025-07-24T16:38:13.3589878Z` + -e 's/#[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}T[0-9]\{2\}:[0-9]\{2\}:[0-9]\{2\}\.[0-9]\{1,\}Z/#Z/' + + # Strip variable part of temporary directory name `jdk.jpackage5060841750457404688` + -e 's|\([\/]\)jdk\.jpackage[0-9]\{1,\}\b|\1jdk.jpackage|g' + + # Convert PID value `[PID: 131561]` + -e 's/\[PID: [0-9]\{1,\}\]/[PID: ]/' + + # Strip a warning message `Windows Defender may prevent jpackage from functioning` + -e '/Windows Defender may prevent jpackage from functioning/d' + + # Convert variable part of test output directory `out-6268` + -e 's|\bout-[0-9]\{1,\}\b|out-N|g' + + # Convert variable part of test summary `[ OK ] IconTest(AppImage, ResourceDirIcon, DefaultIcon).test; checks=39` + -e 's/^\(.*\bchecks=\)[0-9]\{1,\}\(\r\{0,1\}\)$/\1N\2/' + + # Convert variable part of ldd output `libdl.so.2 => /lib64/libdl.so.2 (0x00007fbf63c81000)` + -e 's/(0x[[:xdigit:]]\{1,\})$/(0xHEX)/' + + # Convert variable part of rpmbuild output `Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.CMO6a9` + -e 's|/rpm-tmp\...*$|/rpm-tmp.V|' + + # Convert variable part of stack trace entry `at jdk.jpackage.test.JPackageCommand.execute(JPackageCommand.java:863)` + -e 's/^\(.*\b\.java:\)[0-9]\{1,\}\()\r\{0,1\}\)$/\1N\2/' + ) + + sed $sed_inplace_option "$1" "${expressions[@]}" +} + + +for f in "$@"; do + filterFile "$f" +done diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/AdditionalLauncher.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/AdditionalLauncher.java index 801df8624c4..07c8e06856f 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/AdditionalLauncher.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/AdditionalLauncher.java @@ -22,39 +22,54 @@ */ package jdk.jpackage.test; -import static java.util.stream.Collectors.toMap; -import static jdk.jpackage.internal.util.function.ThrowingFunction.toFunction; +import static jdk.jpackage.internal.util.function.ThrowingSupplier.toSupplier; +import static jdk.jpackage.test.LauncherShortcut.LINUX_SHORTCUT; +import static jdk.jpackage.test.LauncherShortcut.WIN_DESKTOP_SHORTCUT; +import static jdk.jpackage.test.LauncherShortcut.WIN_START_MENU_SHORTCUT; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.Properties; +import java.util.Set; import java.util.function.BiConsumer; -import java.util.function.Supplier; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Stream; import jdk.jpackage.internal.util.function.ThrowingBiConsumer; +import jdk.jpackage.internal.util.function.ThrowingConsumer; +import jdk.jpackage.test.LauncherShortcut.StartupDirectory; +import jdk.jpackage.test.LauncherVerifier.Action; -public class AdditionalLauncher { +public final class AdditionalLauncher { public AdditionalLauncher(String name) { - this.name = name; - this.rawProperties = new ArrayList<>(); + this.name = Objects.requireNonNull(name); setPersistenceHandler(null); } - public final AdditionalLauncher setDefaultArguments(String... v) { + public AdditionalLauncher withVerifyActions(Action... actions) { + verifyActions.addAll(List.of(actions)); + return this; + } + + public AdditionalLauncher withoutVerifyActions(Action... actions) { + verifyActions.removeAll(List.of(actions)); + return this; + } + + public AdditionalLauncher setDefaultArguments(String... v) { defaultArguments = new ArrayList<>(List.of(v)); return this; } - public final AdditionalLauncher addDefaultArguments(String... v) { + public AdditionalLauncher addDefaultArguments(String... v) { if (defaultArguments == null) { return setDefaultArguments(v); } @@ -63,12 +78,12 @@ public class AdditionalLauncher { return this; } - public final AdditionalLauncher setJavaOptions(String... v) { + public AdditionalLauncher setJavaOptions(String... v) { javaOptions = new ArrayList<>(List.of(v)); return this; } - public final AdditionalLauncher addJavaOptions(String... v) { + public AdditionalLauncher addJavaOptions(String... v) { if (javaOptions == null) { return setJavaOptions(v); } @@ -77,51 +92,46 @@ public class AdditionalLauncher { return this; } - public final AdditionalLauncher setVerifyUninstalled(boolean value) { - verifyUninstalled = value; + public AdditionalLauncher setProperty(String name, Object value) { + rawProperties.put(Objects.requireNonNull(name), Objects.requireNonNull(value.toString())); return this; } - public final AdditionalLauncher setLauncherAsService() { - return addRawProperties(LAUNCHER_AS_SERVICE); - } - - public final AdditionalLauncher addRawProperties( - Map.Entry v) { - return addRawProperties(List.of(v)); - } - - public final AdditionalLauncher addRawProperties( - Map.Entry v, Map.Entry v2) { - return addRawProperties(List.of(v, v2)); - } - - public final AdditionalLauncher addRawProperties( - Collection> v) { - rawProperties.addAll(v); + public AdditionalLauncher setShortcuts(boolean menu, boolean desktop) { + if (TKit.isLinux()) { + setShortcut(LINUX_SHORTCUT, desktop); + } else if (TKit.isWindows()) { + setShortcut(WIN_DESKTOP_SHORTCUT, desktop); + setShortcut(WIN_START_MENU_SHORTCUT, menu); + } return this; } - public final String getRawPropertyValue( - String key, Supplier getDefault) { - return rawProperties.stream() - .filter(item -> item.getKey().equals(key)) - .map(e -> e.getValue()).findAny().orElseGet(getDefault); - } - - private String getDesciption(JPackageCommand cmd) { - return getRawPropertyValue("description", () -> cmd.getArgumentValue( - "--description", unused -> cmd.name())); - } - - public final AdditionalLauncher setShortcuts(boolean menu, boolean shortcut) { - withMenuShortcut = menu; - withShortcut = shortcut; + public AdditionalLauncher setShortcut(LauncherShortcut shortcut, StartupDirectory value) { + if (value != null) { + setProperty(shortcut.propertyName(), value.asStringValue()); + } else { + setProperty(shortcut.propertyName(), false); + } return this; } - public final AdditionalLauncher setIcon(Path iconPath) { - if (iconPath == NO_ICON) { + public AdditionalLauncher setShortcut(LauncherShortcut shortcut, boolean value) { + if (value) { + setShortcut(shortcut, StartupDirectory.DEFAULT); + } else { + setShortcut(shortcut, null); + } + return this; + } + + public AdditionalLauncher removeShortcut(LauncherShortcut shortcut) { + rawProperties.remove(shortcut.propertyName()); + return this; + } + + public AdditionalLauncher setIcon(Path iconPath) { + if (iconPath.equals(NO_ICON)) { throw new IllegalArgumentException(); } @@ -129,13 +139,13 @@ public class AdditionalLauncher { return this; } - public final AdditionalLauncher setNoIcon() { + public AdditionalLauncher setNoIcon() { icon = NO_ICON; return this; } - public final AdditionalLauncher setPersistenceHandler( - ThrowingBiConsumer>> handler) { + public AdditionalLauncher setPersistenceHandler( + ThrowingBiConsumer>> handler) { if (handler != null) { createFileHandler = ThrowingBiConsumer.toBiConsumer(handler); } else { @@ -144,21 +154,31 @@ public class AdditionalLauncher { return this; } - public final void applyTo(JPackageCommand cmd) { + public void applyTo(JPackageCommand cmd) { cmd.addPrerequisiteAction(this::initialize); - cmd.addVerifyAction(this::verify); + cmd.addVerifyAction(createVerifierAsConsumer()); } - public final void applyTo(PackageTest test) { + public void applyTo(PackageTest test) { test.addInitializer(this::initialize); - test.addInstallVerifier(this::verify); - if (verifyUninstalled) { - test.addUninstallVerifier(this::verifyUninstalled); - } + test.addInstallVerifier(createVerifierAsConsumer()); } public final void verifyRemovedInUpgrade(PackageTest test) { - test.addInstallVerifier(this::verifyUninstalled); + test.addInstallVerifier(cmd -> { + createVerifier().verify(cmd, LauncherVerifier.Action.VERIFY_UNINSTALLED); + }); + } + + private LauncherVerifier createVerifier() { + return new LauncherVerifier(name, Optional.ofNullable(javaOptions), + Optional.ofNullable(defaultArguments), Optional.ofNullable(icon), rawProperties); + } + + private ThrowingConsumer createVerifierAsConsumer() { + return cmd -> { + createVerifier().verify(cmd, verifyActions.stream().sorted(Comparator.comparing(Action::ordinal)).toArray(Action[]::new)); + }; } static void forEachAdditionalLauncher(JPackageCommand cmd, @@ -179,11 +199,12 @@ public class AdditionalLauncher { PropertyFile shell[] = new PropertyFile[1]; forEachAdditionalLauncher(cmd, (name, propertiesFilePath) -> { if (name.equals(launcherName)) { - shell[0] = toFunction(PropertyFile::new).apply( - propertiesFilePath); + shell[0] = toSupplier(() -> { + return new PropertyFile(propertiesFilePath); + }).get(); } }); - return Optional.of(shell[0]).get(); + return Objects.requireNonNull(shell[0]); } private void initialize(JPackageCommand cmd) throws IOException { @@ -191,259 +212,63 @@ public class AdditionalLauncher { cmd.addArguments("--add-launcher", String.format("%s=%s", name, propsFile)); - List> properties = new ArrayList<>(); + Map properties = new HashMap<>(); if (defaultArguments != null) { - properties.add(Map.entry("arguments", - JPackageCommand.escapeAndJoin(defaultArguments))); + properties.put("arguments", JPackageCommand.escapeAndJoin(defaultArguments)); } if (javaOptions != null) { - properties.add(Map.entry("java-options", - JPackageCommand.escapeAndJoin(javaOptions))); + properties.put("java-options", JPackageCommand.escapeAndJoin(javaOptions)); } if (icon != null) { final String iconPath; - if (icon == NO_ICON) { + if (icon.equals(NO_ICON)) { iconPath = ""; } else { iconPath = icon.toAbsolutePath().toString().replace('\\', '/'); } - properties.add(Map.entry("icon", iconPath)); + properties.put("icon", iconPath); } - if (withShortcut != null) { - if (TKit.isLinux()) { - properties.add(Map.entry("linux-shortcut", withShortcut.toString())); - } else if (TKit.isWindows()) { - properties.add(Map.entry("win-shortcut", withShortcut.toString())); - } - } + properties.putAll(rawProperties); - if (TKit.isWindows() && withMenuShortcut != null) { - properties.add(Map.entry("win-menu", withMenuShortcut.toString())); - } - - properties.addAll(rawProperties); - - createFileHandler.accept(propsFile, properties); - } - - private static Path iconInResourceDir(JPackageCommand cmd, - String launcherName) { - Path resourceDir = cmd.getArgumentValue("--resource-dir", () -> null, - Path::of); - if (resourceDir != null) { - Path icon = resourceDir.resolve( - Optional.ofNullable(launcherName).orElseGet(() -> cmd.name()) - + TKit.ICON_SUFFIX); - if (Files.exists(icon)) { - return icon; - } - } - return null; - } - - private void verifyIcon(JPackageCommand cmd) throws IOException { - var verifier = new LauncherIconVerifier().setLauncherName(name); - - if (TKit.isOSX()) { - // On Mac should be no icon files for additional launchers. - verifier.applyTo(cmd); - return; - } - - boolean withLinuxDesktopFile = false; - - final Path effectiveIcon = Optional.ofNullable(icon).orElseGet( - () -> iconInResourceDir(cmd, name)); - while (effectiveIcon != NO_ICON) { - if (effectiveIcon != null) { - withLinuxDesktopFile = Boolean.FALSE != withShortcut; - verifier.setExpectedIcon(effectiveIcon); - break; - } - - Path customMainLauncherIcon = cmd.getArgumentValue("--icon", - () -> iconInResourceDir(cmd, null), Path::of); - if (customMainLauncherIcon != null) { - withLinuxDesktopFile = Boolean.FALSE != withShortcut; - verifier.setExpectedIcon(customMainLauncherIcon); - break; - } - - verifier.setExpectedDefaultIcon(); - break; - } - - if (TKit.isLinux() && !cmd.isImagePackageType()) { - if (effectiveIcon != NO_ICON && !withLinuxDesktopFile) { - withLinuxDesktopFile = (Boolean.FALSE != withShortcut) && - Stream.of("--linux-shortcut").anyMatch(cmd::hasArgument); - verifier.setExpectedDefaultIcon(); - } - Path desktopFile = LinuxHelper.getDesktopFile(cmd, name); - if (withLinuxDesktopFile) { - TKit.assertFileExists(desktopFile); - } else { - TKit.assertPathExists(desktopFile, false); - } - } - - verifier.applyTo(cmd); - } - - private void verifyShortcuts(JPackageCommand cmd) throws IOException { - if (TKit.isLinux() && !cmd.isImagePackageType() - && withShortcut != null) { - Path desktopFile = LinuxHelper.getDesktopFile(cmd, name); - if (withShortcut) { - TKit.assertFileExists(desktopFile); - } else { - TKit.assertPathExists(desktopFile, false); - } - } - } - - private void verifyDescription(JPackageCommand cmd) throws IOException { - if (TKit.isWindows()) { - String expectedDescription = getDesciption(cmd); - Path launcherPath = cmd.appLauncherPath(name); - String actualDescription = - WindowsHelper.getExecutableDesciption(launcherPath); - TKit.assertEquals(expectedDescription, actualDescription, - String.format("Check file description of [%s]", launcherPath)); - } else if (TKit.isLinux() && !cmd.isImagePackageType()) { - String expectedDescription = getDesciption(cmd); - Path desktopFile = LinuxHelper.getDesktopFile(cmd, name); - if (Files.exists(desktopFile)) { - TKit.assertTextStream("Comment=" + expectedDescription) - .label(String.format("[%s] file", desktopFile)) - .predicate(String::equals) - .apply(Files.readAllLines(desktopFile)); - } - } - } - - private void verifyInstalled(JPackageCommand cmd, boolean installed) throws IOException { - if (TKit.isLinux() && !cmd.isImagePackageType() && !cmd. - isPackageUnpacked(String.format( - "Not verifying package and system .desktop files for [%s] launcher", - cmd.appLauncherPath(name)))) { - Path packageDesktopFile = LinuxHelper.getDesktopFile(cmd, name); - Path systemDesktopFile = LinuxHelper.getSystemDesktopFilesFolder(). - resolve(packageDesktopFile.getFileName()); - if (Files.exists(packageDesktopFile) && installed) { - TKit.assertFileExists(systemDesktopFile); - TKit.assertStringListEquals(Files.readAllLines( - packageDesktopFile), - Files.readAllLines(systemDesktopFile), String.format( - "Check [%s] and [%s] files are equal", - packageDesktopFile, - systemDesktopFile)); - } else { - TKit.assertPathExists(packageDesktopFile, false); - TKit.assertPathExists(systemDesktopFile, false); - } - } - } - - protected void verifyUninstalled(JPackageCommand cmd) throws IOException { - verifyInstalled(cmd, false); - Path launcherPath = cmd.appLauncherPath(name); - TKit.assertPathExists(launcherPath, false); - } - - protected void verify(JPackageCommand cmd) throws IOException { - verifyIcon(cmd); - verifyShortcuts(cmd); - verifyDescription(cmd); - verifyInstalled(cmd, true); - - Path launcherPath = cmd.appLauncherPath(name); - - TKit.assertExecutableFileExists(launcherPath); - - if (!cmd.canRunLauncher(String.format( - "Not running %s launcher", launcherPath))) { - return; - } - - var appVerifier = HelloApp.assertApp(launcherPath) - .addDefaultArguments(Optional - .ofNullable(defaultArguments) - .orElseGet(() -> List.of(cmd.getAllArgumentValues("--arguments")))) - .addJavaOptions(Optional - .ofNullable(javaOptions) - .orElseGet(() -> List.of(cmd.getAllArgumentValues( - "--java-options"))).stream().map( - str -> resolveVariables(cmd, str)).toList()); - - if (!rawProperties.contains(LAUNCHER_AS_SERVICE)) { - appVerifier.executeAndVerifyOutput(); - } else if (!cmd.isPackageUnpacked(String.format( - "Not verifying contents of test output file for [%s] launcher", - launcherPath))) { - appVerifier.verifyOutput(); - } + createFileHandler.accept(propsFile, properties.entrySet()); } public static final class PropertyFile { + PropertyFile(Map data) { + this.data = new Properties(); + this.data.putAll(data); + } + PropertyFile(Path path) throws IOException { - data = Files.readAllLines(path).stream().map(str -> { - return str.split("=", 2); - }).collect(toMap(tokens -> tokens[0], tokens -> { - if (tokens.length == 1) { - return ""; - } else { - return tokens[1]; - } - }, (oldValue, newValue) -> { - return newValue; - })); + data = new Properties(); + try (var reader = Files.newBufferedReader(path)) { + data.load(reader); + } } - public boolean isPropertySet(String name) { + public Optional findProperty(String name) { Objects.requireNonNull(name); - return data.containsKey(name); + return Optional.ofNullable(data.getProperty(name)); } - public Optional getPropertyValue(String name) { - Objects.requireNonNull(name); - return Optional.of(data.get(name)); + public Optional findBooleanProperty(String name) { + return findProperty(name).map(Boolean::parseBoolean); } - public Optional getPropertyBooleanValue(String name) { - Objects.requireNonNull(name); - return Optional.ofNullable(data.get(name)).map(Boolean::parseBoolean); - } - - private final Map data; + private final Properties data; } - private static String resolveVariables(JPackageCommand cmd, String str) { - var map = Stream.of(JPackageCommand.Macro.values()).collect(toMap(x -> { - return String.format("$%s", x.name()); - }, cmd::macroValue)); - for (var e : map.entrySet()) { - str = str.replaceAll(Pattern.quote(e.getKey()), - Matcher.quoteReplacement(e.getValue().toString())); - } - return str; - } - - private boolean verifyUninstalled; private List javaOptions; private List defaultArguments; private Path icon; private final String name; - private final List> rawProperties; - private BiConsumer>> createFileHandler; - private Boolean withMenuShortcut; - private Boolean withShortcut; + private final Map rawProperties = new HashMap<>(); + private BiConsumer>> createFileHandler; + private final Set verifyActions = new HashSet<>(Action.VERIFY_DEFAULTS); - private static final Path NO_ICON = Path.of(""); - private static final Map.Entry LAUNCHER_AS_SERVICE = Map.entry( - "launcher-as-service", "true"); + static final Path NO_ICON = Path.of(""); } diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/AppImageFile.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/AppImageFile.java index 2381aecec2e..e676e0d1e87 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/AppImageFile.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/AppImageFile.java @@ -22,20 +22,28 @@ */ package jdk.jpackage.test; +import static java.util.stream.Collectors.toMap; +import static jdk.jpackage.internal.util.function.ThrowingFunction.toFunction; +import static jdk.jpackage.internal.util.function.ThrowingSupplier.toSupplier; + import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.HashMap; import java.util.Map; +import java.util.Objects; import java.util.Optional; +import java.util.stream.Stream; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathFactory; import jdk.internal.util.OperatingSystem; import jdk.jpackage.internal.util.XmlUtils; -import static jdk.jpackage.internal.util.function.ThrowingSupplier.toSupplier; import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; public record AppImageFile(String mainLauncherName, String mainLauncherClassName, - String version, boolean macSigned, boolean macAppStore) { + String version, boolean macSigned, boolean macAppStore, Map> launchers) { public static Path getPathInAppImage(Path appImageDir) { return ApplicationLayout.platformAppImage() @@ -44,8 +52,23 @@ public record AppImageFile(String mainLauncherName, String mainLauncherClassName .resolve(FILENAME); } + public AppImageFile { + Objects.requireNonNull(mainLauncherName); + Objects.requireNonNull(mainLauncherClassName); + Objects.requireNonNull(version); + if (!launchers.containsKey(mainLauncherName)) { + throw new IllegalArgumentException(); + } + } + public AppImageFile(String mainLauncherName, String mainLauncherClassName) { - this(mainLauncherName, mainLauncherClassName, "1.0", false, false); + this(mainLauncherName, mainLauncherClassName, "1.0", false, false, Map.of(mainLauncherName, Map.of())); + } + + public Map> addLaunchers() { + return launchers.entrySet().stream().filter(e -> { + return !e.getKey().equals(mainLauncherName); + }).collect(toMap(Map.Entry::getKey, Map.Entry::getValue)); } public void save(Path appImageDir) throws IOException { @@ -73,6 +96,18 @@ public record AppImageFile(String mainLauncherName, String mainLauncherClassName xml.writeStartElement("app-store"); xml.writeCharacters(Boolean.toString(macAppStore)); xml.writeEndElement(); + + for (var al : addLaunchers().keySet().stream().sorted().toList()) { + xml.writeStartElement("add-launcher"); + xml.writeAttribute("name", al); + var props = launchers.get(al); + for (var prop : props.keySet().stream().sorted().toList()) { + xml.writeStartElement(prop); + xml.writeCharacters(props.get(prop)); + xml.writeEndElement(); + } + xml.writeEndElement(); + } }); } @@ -99,8 +134,34 @@ public record AppImageFile(String mainLauncherName, String mainLauncherClassName "/jpackage-state/app-store/text()", doc)).map( Boolean::parseBoolean).orElse(false); + var addLaunchers = XmlUtils.queryNodes(doc, xPath, "/jpackage-state/add-launcher").map(Element.class::cast).map(toFunction(addLauncher -> { + Map launcherProps = new HashMap<>(); + + // @name and @service attributes. + XmlUtils.toStream(addLauncher.getAttributes()).forEach(attr -> { + launcherProps.put(attr.getNodeName(), attr.getNodeValue()); + }); + + // Extra properties. + XmlUtils.queryNodes(addLauncher, xPath, "*[count(*) = 0]").map(Element.class::cast).forEach(e -> { + launcherProps.put(e.getNodeName(), e.getTextContent()); + }); + + return launcherProps; + })); + + var mainLauncherProperties = Map.of("name", mainLauncherName); + + var launchers = Stream.concat(Stream.of(mainLauncherProperties), addLaunchers).collect(toMap(attrs -> { + return Objects.requireNonNull(attrs.get("name")); + }, attrs -> { + Map copy = new HashMap<>(attrs); + copy.remove("name"); + return Map.copyOf(copy); + })); + return new AppImageFile(mainLauncherName, mainLauncherClassName, - version, macSigned, macAppStore); + version, macSigned, macAppStore, launchers); }).get(); } diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/CommandArguments.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/CommandArguments.java index cb7f0574afd..4a78ad40cd1 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/CommandArguments.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/CommandArguments.java @@ -35,16 +35,17 @@ public class CommandArguments { } public final T clearArguments() { + verifyMutable(); args.clear(); return thiz(); } public final T addArgument(String v) { - args.add(v); - return thiz(); + return addArguments(v); } public final T addArguments(List v) { + verifyMutable(); args.addAll(v); return thiz(); } diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/ConfigFilesStasher.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/ConfigFilesStasher.java index 98c79131045..e630659bdb1 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/ConfigFilesStasher.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/ConfigFilesStasher.java @@ -220,7 +220,7 @@ final class ConfigFilesStasher { AdditionalLauncher.forEachAdditionalLauncher(cmd, (launcherName, propertyFilePath) -> { try { final var launcherAsService = new AdditionalLauncher.PropertyFile(propertyFilePath) - .getPropertyBooleanValue("launcher-as-service").orElse(false); + .findBooleanProperty("launcher-as-service").orElse(false); if (launcherAsService) { withServices[0] = true; } diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java index 169457d6f58..3a89ba28d26 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java @@ -72,7 +72,7 @@ public class JPackageCommand extends CommandArguments { verifyActions = new Actions(); } - public JPackageCommand(JPackageCommand cmd) { + private JPackageCommand(JPackageCommand cmd, boolean immutable) { args.addAll(cmd.args); withToolProvider = cmd.withToolProvider; saveConsoleOutput = cmd.saveConsoleOutput; @@ -81,7 +81,7 @@ public class JPackageCommand extends CommandArguments { suppressOutput = cmd.suppressOutput; ignoreDefaultRuntime = cmd.ignoreDefaultRuntime; ignoreDefaultVerbose = cmd.ignoreDefaultVerbose; - immutable = cmd.immutable; + this.immutable = immutable; dmgInstallDir = cmd.dmgInstallDir; prerequisiteActions = new Actions(cmd.prerequisiteActions); verifyActions = new Actions(cmd.verifyActions); @@ -90,12 +90,15 @@ public class JPackageCommand extends CommandArguments { outputValidators = cmd.outputValidators; executeInDirectory = cmd.executeInDirectory; winMsiLogFile = cmd.winMsiLogFile; + unpackedPackageDirectory = cmd.unpackedPackageDirectory; } JPackageCommand createImmutableCopy() { - JPackageCommand reply = new JPackageCommand(this); - reply.immutable = true; - return reply; + return new JPackageCommand(this, true); + } + + JPackageCommand createMutableCopy() { + return new JPackageCommand(this, false); } public JPackageCommand setArgumentValue(String argName, String newValue) { @@ -316,13 +319,11 @@ public class JPackageCommand extends CommandArguments { } JPackageCommand addPrerequisiteAction(ThrowingConsumer action) { - verifyMutable(); prerequisiteActions.add(action); return this; } JPackageCommand addVerifyAction(ThrowingConsumer action) { - verifyMutable(); verifyActions.add(action); return this; } @@ -484,7 +485,7 @@ public class JPackageCommand extends CommandArguments { Path unpackedPackageDirectory() { verifyIsOfType(PackageType.NATIVE); - return getArgumentValue(UNPACKED_PATH_ARGNAME, () -> null, Path::of); + return unpackedPackageDirectory; } /** @@ -662,7 +663,7 @@ public class JPackageCommand extends CommandArguments { } public boolean isPackageUnpacked() { - return hasArgument(UNPACKED_PATH_ARGNAME); + return unpackedPackageDirectory != null; } public static void useToolProviderByDefault(ToolProvider jpackageToolProvider) { @@ -791,11 +792,6 @@ public class JPackageCommand extends CommandArguments { return this; } - public JPackageCommand executeVerifyActions() { - verifyActions.run(); - return this; - } - private Executor createExecutor() { Executor exec = new Executor() .saveOutput(saveConsoleOutput).dumpOutput(!suppressOutput) @@ -820,6 +816,7 @@ public class JPackageCommand extends CommandArguments { } public Executor.Result execute(int expectedExitCode) { + verifyMutable(); executePrerequisiteActions(); if (hasArgument("--dest")) { @@ -859,7 +856,7 @@ public class JPackageCommand extends CommandArguments { ConfigFilesStasher.INSTANCE.accept(this); } - final var copy = new JPackageCommand(this).adjustArgumentsBeforeExecution(); + final var copy = createMutableCopy().adjustArgumentsBeforeExecution(); final var directoriesAssert = new ReadOnlyPathsAssert(copy); @@ -876,7 +873,7 @@ public class JPackageCommand extends CommandArguments { } if (result.exitCode() == 0) { - executeVerifyActions(); + verifyActions.run(); } return result; @@ -884,7 +881,7 @@ public class JPackageCommand extends CommandArguments { public Executor.Result executeAndAssertHelloAppImageCreated() { Executor.Result result = executeAndAssertImageCreated(); - HelloApp.executeLauncherAndVerifyOutput(this); + LauncherVerifier.executeMainLauncherAndVerifyOutput(this); return result; } @@ -1046,6 +1043,7 @@ public class JPackageCommand extends CommandArguments { } public JPackageCommand setReadOnlyPathAsserts(ReadOnlyPathAssert... asserts) { + verifyMutable(); readOnlyPathAsserts = Set.of(asserts); return this; } @@ -1059,18 +1057,19 @@ public class JPackageCommand extends CommandArguments { public static enum AppLayoutAssert { APP_IMAGE_FILE(JPackageCommand::assertAppImageFile), PACKAGE_FILE(JPackageCommand::assertPackageFile), - MAIN_LAUNCHER(cmd -> { + NO_MAIN_LAUNCHER_IN_RUNTIME(cmd -> { if (cmd.isRuntime()) { TKit.assertPathExists(convertFromRuntime(cmd).appLauncherPath(), false); - } else { - TKit.assertExecutableFileExists(cmd.appLauncherPath()); } }), - MAIN_LAUNCHER_CFG_FILE(cmd -> { + NO_MAIN_LAUNCHER_CFG_FILE_IN_RUNTIME(cmd -> { if (cmd.isRuntime()) { TKit.assertPathExists(convertFromRuntime(cmd).appLauncherCfgPath(null), false); - } else { - TKit.assertFileExists(cmd.appLauncherCfgPath(null)); + } + }), + MAIN_LAUNCHER_FILES(cmd -> { + if (!cmd.isRuntime()) { + new LauncherVerifier(cmd).verify(cmd, LauncherVerifier.Action.VERIFY_INSTALLED); } }), MAIN_JAR_FILE(cmd -> { @@ -1097,7 +1096,7 @@ public class JPackageCommand extends CommandArguments { } private static JPackageCommand convertFromRuntime(JPackageCommand cmd) { - var copy = new JPackageCommand(cmd); + var copy = cmd.createMutableCopy(); copy.immutable = false; copy.removeArgumentWithValue("--runtime-image"); copy.dmgInstallDir = cmd.appInstallationDirectory(); @@ -1111,6 +1110,7 @@ public class JPackageCommand extends CommandArguments { } public JPackageCommand setAppLayoutAsserts(AppLayoutAssert ... asserts) { + verifyMutable(); appLayoutAsserts = Set.of(asserts); return this; } @@ -1157,12 +1157,12 @@ public class JPackageCommand extends CommandArguments { } else { assertFileInAppImage(lookupPath); + final Path rootDir = isImagePackageType() ? outputBundle() : + pathToUnpackedPackageFile(appInstallationDirectory()); + + final AppImageFile aif = AppImageFile.load(rootDir); + if (TKit.isOSX()) { - final Path rootDir = isImagePackageType() ? outputBundle() : - pathToUnpackedPackageFile(appInstallationDirectory()); - - AppImageFile aif = AppImageFile.load(rootDir); - boolean expectedValue = MacHelper.appImageSigned(this); boolean actualValue = aif.macSigned(); TKit.assertEquals(expectedValue, actualValue, @@ -1173,6 +1173,11 @@ public class JPackageCommand extends CommandArguments { TKit.assertEquals(expectedValue, actualValue, "Check for unexpected value of property in app image file"); } + + TKit.assertStringListEquals( + addLauncherNames().stream().sorted().toList(), + aif.addLaunchers().keySet().stream().sorted().toList(), + "Check additional launcher names"); } } @@ -1254,16 +1259,14 @@ public class JPackageCommand extends CommandArguments { } JPackageCommand setUnpackedPackageLocation(Path path) { + verifyMutable(); verifyIsOfType(PackageType.NATIVE); - if (path != null) { - setArgumentValue(UNPACKED_PATH_ARGNAME, path); - } else { - removeArgumentWithValue(UNPACKED_PATH_ARGNAME); - } + unpackedPackageDirectory = path; return this; } JPackageCommand winMsiLogFile(Path v) { + verifyMutable(); if (!TKit.isWindows()) { throw new UnsupportedOperationException(); } @@ -1286,6 +1289,7 @@ public class JPackageCommand extends CommandArguments { } private JPackageCommand adjustArgumentsBeforeExecution() { + verifyMutable(); if (!isWithToolProvider()) { // if jpackage is launched as a process then set the jlink.debug system property // to allow the jlink process to print exception stacktraces on any failure @@ -1469,6 +1473,7 @@ public class JPackageCommand extends CommandArguments { private final Actions verifyActions; private Path executeInDirectory; private Path winMsiLogFile; + private Path unpackedPackageDirectory; private Set readOnlyPathAsserts = Set.of(ReadOnlyPathAssert.values()); private Set appLayoutAsserts = Set.of(AppLayoutAssert.values()); private List>> outputValidators = new ArrayList<>(); @@ -1496,8 +1501,6 @@ public class JPackageCommand extends CommandArguments { return null; }).get(); - private static final String UNPACKED_PATH_ARGNAME = "jpt-unpacked-folder"; - // [HH:mm:ss.SSS] private static final Pattern TIMESTAMP_REGEXP = Pattern.compile( "^\\[\\d\\d:\\d\\d:\\d\\d.\\d\\d\\d\\] "); diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LauncherAsServiceVerifier.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LauncherAsServiceVerifier.java index fd8b4011341..42821822894 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LauncherAsServiceVerifier.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LauncherAsServiceVerifier.java @@ -22,11 +22,19 @@ */ package jdk.jpackage.test; +import static jdk.jpackage.internal.util.function.ThrowingBiConsumer.toBiConsumer; +import static jdk.jpackage.internal.util.function.ThrowingConsumer.toConsumer; +import static jdk.jpackage.test.AdditionalLauncher.forEachAdditionalLauncher; +import static jdk.jpackage.test.PackageType.LINUX; +import static jdk.jpackage.test.PackageType.MAC_PKG; +import static jdk.jpackage.test.PackageType.WINDOWS; + import java.io.IOException; import java.nio.file.FileSystemException; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -36,12 +44,9 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; import jdk.jpackage.internal.util.PathUtils; -import jdk.jpackage.internal.util.function.ThrowingBiConsumer; -import static jdk.jpackage.internal.util.function.ThrowingConsumer.toConsumer; import jdk.jpackage.internal.util.function.ThrowingRunnable; -import static jdk.jpackage.test.PackageType.LINUX; -import static jdk.jpackage.test.PackageType.MAC_PKG; -import static jdk.jpackage.test.PackageType.WINDOWS; +import jdk.jpackage.test.AdditionalLauncher.PropertyFile; +import jdk.jpackage.test.LauncherVerifier.Action; public final class LauncherAsServiceVerifier { @@ -111,6 +116,7 @@ public final class LauncherAsServiceVerifier { } else { applyToAdditionalLauncher(pkg); } + pkg.addInstallVerifier(this::verifyLauncherExecuted); } static void verify(JPackageCommand cmd) { @@ -127,7 +133,6 @@ public final class LauncherAsServiceVerifier { "service-installer.exe"); if (launcherNames.isEmpty()) { TKit.assertPathExists(serviceInstallerPath, false); - } else { TKit.assertFileExists(serviceInstallerPath); } @@ -188,23 +193,11 @@ public final class LauncherAsServiceVerifier { launcherNames.add(null); } - AdditionalLauncher.forEachAdditionalLauncher(cmd, - ThrowingBiConsumer.toBiConsumer( - (launcherName, propFilePath) -> { - if (Files.readAllLines(propFilePath).stream().anyMatch( - line -> { - if (line.startsWith( - "launcher-as-service=")) { - return Boolean.parseBoolean( - line.substring( - "launcher-as-service=".length())); - } else { - return false; - } - })) { - launcherNames.add(launcherName); - } - })); + forEachAdditionalLauncher(cmd, toBiConsumer((launcherName, propFilePath) -> { + if (new PropertyFile(propFilePath).findBooleanProperty("launcher-as-service").orElse(false)) { + launcherNames.add(launcherName); + } + })); return launcherNames; } @@ -237,45 +230,33 @@ public final class LauncherAsServiceVerifier { + appOutputFilePathInitialize().toString()); cmd.addArguments("--java-options", "-Djpackage.test.noexit=true"); }); - pkg.addInstallVerifier(cmd -> { - if (canVerifyInstall(cmd)) { - delayInstallVerify(); - Path outputFilePath = appOutputFilePathVerify(cmd); - HelloApp.assertApp(cmd.appLauncherPath()) - .addParam("jpackage.test.appOutput", - outputFilePath.toString()) - .addDefaultArguments(expectedValue) - .verifyOutput(); - deleteOutputFile(outputFilePath); - } - }); - pkg.addInstallVerifier(cmd -> { - verify(cmd, launcherName); - }); } private void applyToAdditionalLauncher(PackageTest pkg) { - AdditionalLauncher al = new AdditionalLauncher(launcherName) { - @Override - protected void verify(JPackageCommand cmd) throws IOException { - if (canVerifyInstall(cmd)) { - delayInstallVerify(); - super.verify(cmd); - deleteOutputFile(appOutputFilePathVerify(cmd)); - } - LauncherAsServiceVerifier.verify(cmd, launcherName); - } - }.setLauncherAsService() - .addJavaOptions("-Djpackage.test.appOutput=" - + appOutputFilePathInitialize().toString()) + var al = new AdditionalLauncher(launcherName) + .setProperty("launcher-as-service", true) + .addJavaOptions("-Djpackage.test.appOutput=" + appOutputFilePathInitialize().toString()) .addJavaOptions("-Djpackage.test.noexit=true") - .addDefaultArguments(expectedValue); + .addDefaultArguments(expectedValue) + .withoutVerifyActions(Action.EXECUTE_LAUNCHER); Optional.ofNullable(additionalLauncherCallback).ifPresent(v -> v.accept(al)); al.applyTo(pkg); } + private void verifyLauncherExecuted(JPackageCommand cmd) throws IOException { + if (canVerifyInstall(cmd)) { + delayInstallVerify(); + Path outputFilePath = appOutputFilePathVerify(cmd); + HelloApp.assertApp(cmd.appLauncherPath()) + .addParam("jpackage.test.appOutput", outputFilePath.toString()) + .addDefaultArguments(expectedValue) + .verifyOutput(); + deleteOutputFile(outputFilePath); + } + } + private static void deleteOutputFile(Path file) throws IOException { try { TKit.deleteIfExists(file); @@ -291,8 +272,7 @@ public final class LauncherAsServiceVerifier { } } - private static void verify(JPackageCommand cmd, String launcherName) throws - IOException { + private static void verify(JPackageCommand cmd, String launcherName) throws IOException { if (LINUX.contains(cmd.packageType())) { verifyLinuxUnitFile(cmd, launcherName); } else if (MAC_PKG.equals(cmd.packageType())) { @@ -370,6 +350,9 @@ public final class LauncherAsServiceVerifier { private final Path appOutputFileName; private final Consumer additionalLauncherCallback; - static final Set SUPPORTED_PACKAGES = Stream.of(LINUX, WINDOWS, - Set.of(MAC_PKG)).flatMap(x -> x.stream()).collect(Collectors.toSet()); + static final Set SUPPORTED_PACKAGES = Stream.of( + LINUX, + WINDOWS, + Set.of(MAC_PKG) + ).flatMap(Collection::stream).collect(Collectors.toSet()); } diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LauncherIconVerifier.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LauncherIconVerifier.java index a1971ee0835..6285d9d93a0 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LauncherIconVerifier.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LauncherIconVerifier.java @@ -46,6 +46,11 @@ public final class LauncherIconVerifier { return this; } + public LauncherIconVerifier verifyFileInAppImageOnly(boolean v) { + verifyFileInAppImageOnly = true; + return this; + } + public void applyTo(JPackageCommand cmd) throws IOException { final String curLauncherName; final String label; @@ -62,22 +67,26 @@ public final class LauncherIconVerifier { if (TKit.isWindows()) { TKit.assertPathExists(iconPath, false); - WinExecutableIconVerifier.verifyLauncherIcon(cmd, launcherName, - expectedIcon, expectedDefault); + if (!verifyFileInAppImageOnly) { + WinExecutableIconVerifier.verifyLauncherIcon(cmd, launcherName, expectedIcon, expectedDefault); + } } else if (expectedDefault) { TKit.assertPathExists(iconPath, true); } else if (expectedIcon == null) { TKit.assertPathExists(iconPath, false); } else { TKit.assertFileExists(iconPath); - TKit.assertTrue(-1 == Files.mismatch(expectedIcon, iconPath), - String.format( - "Check icon file [%s] of %s launcher is a copy of source icon file [%s]", - iconPath, label, expectedIcon)); + if (!verifyFileInAppImageOnly) { + TKit.assertTrue(-1 == Files.mismatch(expectedIcon, iconPath), + String.format( + "Check icon file [%s] of %s launcher is a copy of source icon file [%s]", + iconPath, label, expectedIcon)); + } } } private String launcherName; private Path expectedIcon; private boolean expectedDefault; + private boolean verifyFileInAppImageOnly; } diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LauncherShortcut.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LauncherShortcut.java new file mode 100644 index 00000000000..5e86f975870 --- /dev/null +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LauncherShortcut.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.jpackage.test; + +import static java.util.stream.Collectors.toMap; +import static jdk.jpackage.test.AdditionalLauncher.getAdditionalLauncherProperties; + +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Stream; +import jdk.jpackage.test.AdditionalLauncher.PropertyFile; + +public enum LauncherShortcut { + + LINUX_SHORTCUT("linux-shortcut"), + + WIN_DESKTOP_SHORTCUT("win-shortcut"), + + WIN_START_MENU_SHORTCUT("win-menu"); + + public enum StartupDirectory { + DEFAULT("true"), + ; + + StartupDirectory(String stringValue) { + this.stringValue = Objects.requireNonNull(stringValue); + } + + public String asStringValue() { + return stringValue; + } + + /** + * Returns shortcut startup directory or an empty {@link Optional} instance if + * the value of the {@code str} parameter evaluates to {@code false}. + * + * @param str the value of a shortcut startup directory + * @return shortcut startup directory or an empty {@link Optional} instance + * @throws IllegalArgumentException if the value of the {@code str} parameter is + * unrecognized + */ + static Optional parse(String str) { + Objects.requireNonNull(str); + return Optional.ofNullable(VALUE_MAP.get(str)).or(() -> { + if (Boolean.TRUE.toString().equals(str)) { + return Optional.of(StartupDirectory.DEFAULT); + } else if (Boolean.FALSE.toString().equals(str)) { + return Optional.empty(); + } else { + throw new IllegalArgumentException(String.format( + "Unrecognized launcher shortcut startup directory: [%s]", str)); + } + }); + } + + private final String stringValue; + + private final static Map VALUE_MAP = + Stream.of(values()).collect(toMap(StartupDirectory::asStringValue, x -> x)); + } + + LauncherShortcut(String propertyName) { + this.propertyName = Objects.requireNonNull(propertyName); + } + + public String propertyName() { + return propertyName; + } + + public String appImageFilePropertyName() { + return propertyName.substring(propertyName.indexOf('-') + 1); + } + + public String optionName() { + return "--" + propertyName; + } + + Optional expectShortcut(JPackageCommand cmd, Optional predefinedAppImage, String launcherName) { + Objects.requireNonNull(predefinedAppImage); + + final var name = Optional.ofNullable(launcherName).orElseGet(cmd::name); + + if (name.equals(cmd.name())) { + return findMainLauncherShortcut(cmd); + } else { + String[] propertyName = new String[1]; + return findAddLauncherShortcut(cmd, predefinedAppImage.map(appImage -> { + propertyName[0] = appImageFilePropertyName(); + return new PropertyFile(appImage.addLaunchers().get(launcherName)); + }).orElseGet(() -> { + propertyName[0] = this.propertyName; + return getAdditionalLauncherProperties(cmd, launcherName); + })::findProperty, propertyName[0]); + } + } + + + public interface InvokeShortcutSpec { + String launcherName(); + LauncherShortcut shortcut(); + Optional expectedWorkDirectory(); + List commandLine(); + + default Executor.Result execute() { + return HelloApp.configureAndExecute(0, Executor.of(commandLine()).dumpOutput()); + } + + record Stub( + String launcherName, + LauncherShortcut shortcut, + Optional expectedWorkDirectory, + List commandLine) implements InvokeShortcutSpec { + + public Stub { + Objects.requireNonNull(launcherName); + Objects.requireNonNull(shortcut); + Objects.requireNonNull(expectedWorkDirectory); + Objects.requireNonNull(commandLine); + } + } + } + + + private Optional findMainLauncherShortcut(JPackageCommand cmd) { + if (cmd.hasArgument(optionName())) { + return Optional.of(StartupDirectory.DEFAULT); + } else { + return Optional.empty(); + } + } + + private Optional findAddLauncherShortcut(JPackageCommand cmd, + Function> addlauncherProperties, String propertyName) { + var explicit = addlauncherProperties.apply(propertyName); + if (explicit.isPresent()) { + return explicit.flatMap(StartupDirectory::parse); + } else { + return findMainLauncherShortcut(cmd); + } + } + + private final String propertyName; +} diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LauncherVerifier.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LauncherVerifier.java new file mode 100644 index 00000000000..ceda32eb8ed --- /dev/null +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LauncherVerifier.java @@ -0,0 +1,326 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.jpackage.test; + +import static java.util.stream.Collectors.toMap; +import static jdk.jpackage.test.AdditionalLauncher.NO_ICON; +import static jdk.jpackage.test.LauncherShortcut.LINUX_SHORTCUT; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.function.BiConsumer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; +import jdk.jpackage.internal.util.function.ThrowingBiConsumer; +import jdk.jpackage.test.AdditionalLauncher.PropertyFile; +import jdk.jpackage.test.LauncherShortcut.StartupDirectory; + +public final class LauncherVerifier { + + LauncherVerifier(JPackageCommand cmd) { + name = cmd.name(); + javaOptions = Optional.empty(); + arguments = Optional.empty(); + icon = Optional.empty(); + properties = Optional.empty(); + } + + LauncherVerifier(String name, + Optional> javaOptions, + Optional> arguments, + Optional icon, + Map properties) { + this.name = Objects.requireNonNull(name); + this.javaOptions = javaOptions.map(List::copyOf); + this.arguments = arguments.map(List::copyOf); + this.icon = icon; + this.properties = Optional.of(new PropertyFile(properties)); + } + + static void executeMainLauncherAndVerifyOutput(JPackageCommand cmd) { + new LauncherVerifier(cmd).verify(cmd, Action.EXECUTE_LAUNCHER); + } + + + public enum Action { + VERIFY_ICON(LauncherVerifier::verifyIcon), + VERIFY_DESCRIPTION(LauncherVerifier::verifyDescription), + VERIFY_INSTALLED((verifier, cmd) -> { + verifier.verifyInstalled(cmd, true); + }), + VERIFY_UNINSTALLED((verifier, cmd) -> { + verifier.verifyInstalled(cmd, false); + }), + EXECUTE_LAUNCHER(LauncherVerifier::executeLauncher), + ; + + Action(ThrowingBiConsumer action) { + this.action = ThrowingBiConsumer.toBiConsumer(action); + } + + private void apply(LauncherVerifier verifier, JPackageCommand cmd) { + action.accept(verifier, cmd); + } + + private final BiConsumer action; + + static final List VERIFY_APP_IMAGE = List.of( + VERIFY_ICON, VERIFY_DESCRIPTION, VERIFY_INSTALLED + ); + + static final List VERIFY_DEFAULTS = Stream.concat( + VERIFY_APP_IMAGE.stream(), Stream.of(EXECUTE_LAUNCHER) + ).toList(); + } + + + void verify(JPackageCommand cmd, Action... actions) { + verify(cmd, List.of(actions)); + } + + void verify(JPackageCommand cmd, Iterable actions) { + Objects.requireNonNull(cmd); + for (var a : actions) { + a.apply(this, cmd); + } + } + + private boolean isMainLauncher() { + return properties.isEmpty(); + } + + private Optional findProperty(String key) { + return properties.flatMap(v -> { + return v.findProperty(key); + }); + } + + private String getDescription(JPackageCommand cmd) { + return findProperty("description").orElseGet(() -> { + return cmd.getArgumentValue("--description", cmd::name); + }); + } + + private List getArguments(JPackageCommand cmd) { + return getStringArrayProperty(cmd, "--arguments", arguments); + } + + private List getJavaOptions(JPackageCommand cmd) { + return getStringArrayProperty(cmd, "--java-options", javaOptions); + } + + private List getStringArrayProperty(JPackageCommand cmd, String optionName, Optional> items) { + Objects.requireNonNull(cmd); + Objects.requireNonNull(optionName); + Objects.requireNonNull(items); + if (isMainLauncher()) { + return List.of(cmd.getAllArgumentValues(optionName)); + } else { + return items.orElseGet(() -> { + return List.of(cmd.getAllArgumentValues(optionName)); + }); + } + } + + private boolean explicitlyNoShortcut(LauncherShortcut shortcut) { + var explicit = findProperty(shortcut.propertyName()); + if (explicit.isPresent()) { + return explicit.flatMap(StartupDirectory::parse).isEmpty(); + } else { + return false; + } + } + + private static boolean explicitShortcutForMainLauncher(JPackageCommand cmd, LauncherShortcut shortcut) { + return cmd.hasArgument(shortcut.optionName()); + } + + private void verifyIcon(JPackageCommand cmd) throws IOException { + initIconVerifier(cmd).applyTo(cmd); + } + + private LauncherIconVerifier initIconVerifier(JPackageCommand cmd) { + var verifier = new LauncherIconVerifier().setLauncherName(name); + + var mainLauncherIcon = Optional.ofNullable(cmd.getArgumentValue("--icon")).map(Path::of).or(() -> { + return iconInResourceDir(cmd, cmd.name()); + }); + + if (TKit.isOSX()) { + // There should be no icon files on Mac for additional launchers, + // and always an icon file for the main launcher. + if (isMainLauncher()) { + mainLauncherIcon.ifPresentOrElse(verifier::setExpectedIcon, verifier::setExpectedDefaultIcon); + } + return verifier; + } + + if (isMainLauncher()) { + mainLauncherIcon.ifPresentOrElse(verifier::setExpectedIcon, verifier::setExpectedDefaultIcon); + } else { + icon.ifPresentOrElse(icon -> { + if (!NO_ICON.equals(icon)) { + verifier.setExpectedIcon(icon); + } + }, () -> { + // No "icon" property in the property file + iconInResourceDir(cmd, name).ifPresentOrElse(verifier::setExpectedIcon, () -> { + // No icon for this additional launcher in the resource directory. + mainLauncherIcon.ifPresentOrElse(verifier::setExpectedIcon, verifier::setExpectedDefaultIcon); + }); + }); + } + + return verifier; + } + + private static boolean withLinuxMainLauncherDesktopFile(JPackageCommand cmd) { + if (!TKit.isLinux() || cmd.isImagePackageType()) { + return false; + } + + return explicitShortcutForMainLauncher(cmd, LINUX_SHORTCUT) + || cmd.hasArgument("--icon") + || cmd.hasArgument("--file-associations") + || iconInResourceDir(cmd, cmd.name()).isPresent(); + } + + private boolean withLinuxDesktopFile(JPackageCommand cmd) { + if (!TKit.isLinux() || cmd.isImagePackageType()) { + return false; + } + + if (isMainLauncher()) { + return withLinuxMainLauncherDesktopFile(cmd); + } else if (explicitlyNoShortcut(LINUX_SHORTCUT) || icon.map(icon -> { + return icon.equals(NO_ICON); + }).orElse(false)) { + return false; + } else if (iconInResourceDir(cmd, name).isPresent() || icon.map(icon -> { + return !icon.equals(NO_ICON); + }).orElse(false)) { + return true; + } else if (findProperty(LINUX_SHORTCUT.propertyName()).flatMap(StartupDirectory::parse).isPresent()) { + return true; + } else { + return withLinuxMainLauncherDesktopFile(cmd.createMutableCopy().removeArgument("--file-associations")); + } + } + + private void verifyDescription(JPackageCommand cmd) throws IOException { + if (TKit.isWindows()) { + String expectedDescription = getDescription(cmd); + Path launcherPath = cmd.appLauncherPath(name); + String actualDescription = + WindowsHelper.getExecutableDescription(launcherPath); + TKit.assertEquals(expectedDescription, actualDescription, + String.format("Check file description of [%s]", launcherPath)); + } else if (TKit.isLinux() && !cmd.isImagePackageType()) { + String expectedDescription = getDescription(cmd); + Path desktopFile = LinuxHelper.getDesktopFile(cmd, name); + if (Files.exists(desktopFile)) { + TKit.assertTextStream("Comment=" + expectedDescription) + .label(String.format("[%s] file", desktopFile)) + .predicate(String::equals) + .apply(Files.readAllLines(desktopFile)); + } + } + } + + private void verifyInstalled(JPackageCommand cmd, boolean installed) throws IOException { + var launcherPath = cmd.appLauncherPath(name); + var launcherCfgFilePath = cmd.appLauncherCfgPath(name); + if (installed) { + TKit.assertExecutableFileExists(launcherPath); + TKit.assertFileExists(launcherCfgFilePath); + } else { + TKit.assertPathExists(launcherPath, false); + TKit.assertPathExists(launcherCfgFilePath, false); + } + + if (TKit.isLinux() && !cmd.isImagePackageType()) { + final var packageDesktopFile = LinuxHelper.getDesktopFile(cmd, name); + final var withLinuxDesktopFile = withLinuxDesktopFile(cmd) && installed; + if (withLinuxDesktopFile) { + TKit.assertFileExists(packageDesktopFile); + } else { + TKit.assertPathExists(packageDesktopFile, false); + } + } + + if (installed) { + initIconVerifier(cmd).verifyFileInAppImageOnly(true).applyTo(cmd); + } + } + + private void executeLauncher(JPackageCommand cmd) throws IOException { + Path launcherPath = cmd.appLauncherPath(name); + + if (!cmd.canRunLauncher(String.format("Not running [%s] launcher", launcherPath))) { + return; + } + + var appVerifier = HelloApp.assertApp(launcherPath) + .addDefaultArguments(getArguments(cmd)) + .addJavaOptions(getJavaOptions(cmd).stream().map(str -> { + return resolveVariables(cmd, str); + }).toList()); + + appVerifier.executeAndVerifyOutput(); + } + + private static String resolveVariables(JPackageCommand cmd, String str) { + var map = Stream.of(JPackageCommand.Macro.values()).collect(toMap(x -> { + return String.format("$%s", x.name()); + }, cmd::macroValue)); + for (var e : map.entrySet()) { + str = str.replaceAll(Pattern.quote(e.getKey()), + Matcher.quoteReplacement(e.getValue().toString())); + } + return str; + } + + private static Optional iconInResourceDir(JPackageCommand cmd, String launcherName) { + Objects.requireNonNull(launcherName); + return Optional.ofNullable(cmd.getArgumentValue("--resource-dir")).map(Path::of).map(resourceDir -> { + Path icon = resourceDir.resolve(launcherName + TKit.ICON_SUFFIX); + if (Files.exists(icon)) { + return icon; + } else { + return null; + } + }); + } + + private final String name; + private final Optional> javaOptions; + private final Optional> arguments; + private final Optional icon; + private final Optional properties; +} diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LinuxHelper.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LinuxHelper.java index 1669d1f8233..b99d2bdeceb 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LinuxHelper.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LinuxHelper.java @@ -23,25 +23,30 @@ package jdk.jpackage.test; import java.io.IOException; +import java.io.UncheckedIOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.function.Function; +import java.util.function.Predicate; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; import jdk.jpackage.internal.util.PathUtils; import jdk.jpackage.internal.util.function.ThrowingConsumer; +import jdk.jpackage.test.LauncherShortcut.InvokeShortcutSpec; import jdk.jpackage.test.PackageTest.PackageHandlers; @@ -308,8 +313,8 @@ public final class LinuxHelper { } static void verifyPackageBundleEssential(JPackageCommand cmd) { - String packageName = LinuxHelper.getPackageName(cmd); - long packageSize = LinuxHelper.getInstalledPackageSizeKB(cmd); + String packageName = getPackageName(cmd); + long packageSize = getInstalledPackageSizeKB(cmd); TKit.trace("InstalledPackageSize: " + packageSize); TKit.assertNotEquals(0, packageSize, String.format( "Check installed size of [%s] package in not zero", packageName)); @@ -330,7 +335,7 @@ public final class LinuxHelper { checkPrerequisites = packageSize > 5; } - List prerequisites = LinuxHelper.getPrerequisitePackages(cmd); + List prerequisites = getPrerequisitePackages(cmd); if (checkPrerequisites) { final String vitalPackage = "libc"; TKit.assertTrue(prerequisites.stream().filter( @@ -340,13 +345,28 @@ public final class LinuxHelper { vitalPackage, prerequisites, packageName)); } else { TKit.trace(String.format( - "Not cheking %s required packages of [%s] package", + "Not checking %s required packages of [%s] package", prerequisites, packageName)); } } - static void addBundleDesktopIntegrationVerifier(PackageTest test, - boolean integrated) { + public static Collection getInvokeShortcutSpecs(JPackageCommand cmd) { + cmd.verifyIsOfType(PackageType.LINUX); + + final var desktopFiles = getDesktopFiles(cmd); + final var predefinedAppImage = Optional.ofNullable(cmd.getArgumentValue("--app-image")).map(Path::of).map(AppImageFile::load); + + return desktopFiles.stream().map(desktopFile -> { + var systemDesktopFile = getSystemDesktopFilesFolder().resolve(desktopFile.getFileName()); + return new InvokeShortcutSpec.Stub( + launcherNameFromDesktopFile(cmd, predefinedAppImage, desktopFile), + LauncherShortcut.LINUX_SHORTCUT, + new DesktopFile(systemDesktopFile, false).findQuotedValue("Path").map(Path::of), + List.of("gtk-launch", PathUtils.replaceSuffix(systemDesktopFile.getFileName(), "").toString())); + }).toList(); + } + + static void addBundleDesktopIntegrationVerifier(PackageTest test, boolean integrated) { final String xdgUtils = "xdg-utils"; Function, String> verifier = (lines) -> { @@ -392,52 +412,81 @@ public final class LinuxHelper { }); test.addInstallVerifier(cmd -> { - // Verify .desktop files. - try (var files = Files.list(cmd.appLayout().desktopIntegrationDirectory())) { - List desktopFiles = files - .filter(path -> path.getFileName().toString().endsWith(".desktop")) - .toList(); - if (!integrated) { - TKit.assertStringListEquals(List.of(), - desktopFiles.stream().map(Path::toString).collect( - Collectors.toList()), - "Check there are no .desktop files in the package"); - } - for (var desktopFile : desktopFiles) { - verifyDesktopFile(cmd, desktopFile); - } + if (!integrated) { + TKit.assertStringListEquals( + List.of(), + getDesktopFiles(cmd).stream().map(Path::toString).toList(), + "Check there are no .desktop files in the package"); } }); } - private static void verifyDesktopFile(JPackageCommand cmd, Path desktopFile) - throws IOException { + static void verifyDesktopFiles(JPackageCommand cmd, boolean installed) { + final var desktopFiles = getDesktopFiles(cmd); + try { + if (installed) { + var predefinedAppImage = Optional.ofNullable(cmd.getArgumentValue("--app-image")).map(Path::of).map(AppImageFile::load); + for (var desktopFile : desktopFiles) { + verifyDesktopFile(cmd, predefinedAppImage, desktopFile); + } + + if (!cmd.isPackageUnpacked("Not verifying system .desktop files")) { + for (var desktopFile : desktopFiles) { + Path systemDesktopFile = getSystemDesktopFilesFolder().resolve(desktopFile.getFileName()); + TKit.assertFileExists(systemDesktopFile); + TKit.assertStringListEquals( + Files.readAllLines(desktopFile), + Files.readAllLines(systemDesktopFile), + String.format("Check [%s] and [%s] files are equal", desktopFile, systemDesktopFile)); + } + } + } else { + for (var desktopFile : getDesktopFiles(cmd)) { + Path systemDesktopFile = getSystemDesktopFilesFolder().resolve(desktopFile.getFileName()); + TKit.assertPathExists(systemDesktopFile, false); + } + } + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + + private static Collection getDesktopFiles(JPackageCommand cmd) { + var unpackedDir = cmd.appLayout().desktopIntegrationDirectory(); + var packageDir = cmd.pathToPackageFile(unpackedDir); + return getPackageFiles(cmd).filter(path -> { + return path.getParent().equals(packageDir) && path.getFileName().toString().endsWith(".desktop"); + }).map(Path::getFileName).map(unpackedDir::resolve).toList(); + } + + private static String launcherNameFromDesktopFile(JPackageCommand cmd, Optional predefinedAppImage, Path desktopFile) { + Objects.requireNonNull(cmd); + Objects.requireNonNull(predefinedAppImage); + Objects.requireNonNull(desktopFile); + + return predefinedAppImage.map(v -> { + return v.launchers().keySet().stream(); + }).orElseGet(() -> { + return Stream.concat(Stream.of(cmd.name()), cmd.addLauncherNames().stream()); + }).filter(name-> { + return getDesktopFile(cmd, name).equals(desktopFile); + }).findAny().orElseThrow(() -> { + TKit.assertUnexpected(String.format("Failed to find launcher corresponding to [%s] file", desktopFile)); + // Unreachable + return null; + }); + } + + private static void verifyDesktopFile(JPackageCommand cmd, Optional predefinedAppImage, Path desktopFile) throws IOException { + Objects.requireNonNull(cmd); + Objects.requireNonNull(predefinedAppImage); + Objects.requireNonNull(desktopFile); + TKit.trace(String.format("Check [%s] file BEGIN", desktopFile)); - var launcherName = Stream.of(List.of(cmd.name()), cmd.addLauncherNames()).flatMap(List::stream).filter(name -> { - return getDesktopFile(cmd, name).equals(desktopFile); - }).findAny(); - if (!cmd.hasArgument("--app-image")) { - TKit.assertTrue(launcherName.isPresent(), - "Check the desktop file corresponds to one of app launchers"); - } + var launcherName = launcherNameFromDesktopFile(cmd, predefinedAppImage, desktopFile); - List lines = Files.readAllLines(desktopFile); - TKit.assertEquals("[Desktop Entry]", lines.get(0), "Check file header"); - - Map data = lines.stream() - .skip(1) - .peek(str -> TKit.assertTextStream("=").predicate(String::contains).apply(List.of(str))) - .map(str -> { - String components[] = str.split("=(?=.+)"); - if (components.length == 1) { - return Map.entry(str.substring(0, str.length() - 1), ""); - } - return Map.entry(components[0], components[1]); - }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> { - TKit.assertUnexpected("Multiple values of the same key"); - return null; - })); + var data = new DesktopFile(desktopFile, true); final Set mandatoryKeys = new HashSet<>(Set.of("Name", "Comment", "Exec", "Icon", "Terminal", "Type", "Categories")); @@ -447,34 +496,44 @@ public final class LinuxHelper { for (var e : Map.of("Type", "Application", "Terminal", "false").entrySet()) { String key = e.getKey(); - TKit.assertEquals(e.getValue(), data.get(key), String.format( + TKit.assertEquals(e.getValue(), data.find(key).orElseThrow(), String.format( "Check value of [%s] key", key)); } - // Verify the value of `Exec` key is escaped if required - String launcherPath = data.get("Exec"); - if (Pattern.compile("\\s").matcher(launcherPath).find()) { - TKit.assertTrue(launcherPath.startsWith("\"") - && launcherPath.endsWith("\""), - "Check path to the launcher is enclosed in double quotes"); - launcherPath = launcherPath.substring(1, launcherPath.length() - 1); - } + String launcherPath = data.findQuotedValue("Exec").orElseThrow(); - if (launcherName.isPresent()) { - TKit.assertEquals(launcherPath, cmd.pathToPackageFile( - cmd.appLauncherPath(launcherName.get())).toString(), - String.format( - "Check the value of [Exec] key references [%s] app launcher", - launcherName.get())); - } + TKit.assertEquals( + launcherPath, + cmd.pathToPackageFile(cmd.appLauncherPath(launcherName)).toString(), + String.format("Check the value of [Exec] key references [%s] app launcher", launcherName)); + + var appLayout = cmd.appLayout(); + + LauncherShortcut.LINUX_SHORTCUT.expectShortcut(cmd, predefinedAppImage, launcherName).map(shortcutWorkDirType -> { + switch (shortcutWorkDirType) { + case DEFAULT -> { + return (Path)null; + } + default -> { + throw new AssertionError(); + } + } + }).map(Path::toString).ifPresentOrElse(shortcutWorkDir -> { + var actualShortcutWorkDir = data.find("Path"); + TKit.assertTrue(actualShortcutWorkDir.isPresent(), "Check [Path] key exists"); + TKit.assertEquals(actualShortcutWorkDir.get(), shortcutWorkDir, "Check the value of [Path] key"); + }, () -> { + TKit.assertTrue(data.find("Path").isEmpty(), "Check there is no [Path] key"); + }); for (var e : List.>, Function>>of( Map.entry(Map.entry("Exec", Optional.of(launcherPath)), ApplicationLayout::launchersDirectory), Map.entry(Map.entry("Icon", Optional.empty()), ApplicationLayout::desktopIntegrationDirectory))) { - var path = e.getKey().getValue().or(() -> Optional.of(data.get( - e.getKey().getKey()))).map(Path::of).get(); + var path = e.getKey().getValue().or(() -> { + return data.findQuotedValue(e.getKey().getKey()); + }).map(Path::of).get(); TKit.assertFileExists(cmd.pathToUnpackedPackageFile(path)); - Path expectedDir = cmd.pathToPackageFile(e.getValue().apply(cmd.appLayout())); + Path expectedDir = cmd.pathToPackageFile(e.getValue().apply(appLayout)); TKit.assertTrue(path.getParent().equals(expectedDir), String.format( "Check the value of [%s] key references a file in [%s] folder", e.getKey().getKey(), expectedDir)); @@ -761,6 +820,62 @@ public final class LinuxHelper { } } + + private static final class DesktopFile { + DesktopFile(Path path, boolean verify) { + try { + List lines = Files.readAllLines(path); + if (verify) { + TKit.assertEquals("[Desktop Entry]", lines.getFirst(), "Check file header"); + } + + var stream = lines.stream().skip(1).filter(Predicate.not(String::isEmpty)); + if (verify) { + stream = stream.peek(str -> { + TKit.assertTextStream("=").predicate(String::contains).apply(List.of(str)); + }); + } + + data = stream.map(str -> { + String components[] = str.split("=(?=.+)"); + if (components.length == 1) { + return Map.entry(str.substring(0, str.length() - 1), ""); + } else { + return Map.entry(components[0], components[1]); + } + }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + + Set keySet() { + return data.keySet(); + } + + Optional find(String property) { + return Optional.ofNullable(data.get(Objects.requireNonNull(property))); + } + + Optional findQuotedValue(String property) { + return find(property).map(value -> { + if (Pattern.compile("\\s").matcher(value).find()) { + boolean quotesMatched = value.startsWith("\"") && value.endsWith("\""); + if (!quotesMatched) { + TKit.assertTrue(quotesMatched, + String.format("Check the value of key [%s] is enclosed in double quotes", property)); + } + return value.substring(1, value.length() - 1); + } else { + return value; + } + }); + } + + private final Map data; + } + + static final Set CRITICAL_RUNTIME_FILES = Set.of(Path.of( "lib/server/libjvm.so")); diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MsiDatabase.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MsiDatabase.java new file mode 100644 index 00000000000..87236575e2e --- /dev/null +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MsiDatabase.java @@ -0,0 +1,377 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.jpackage.test; + + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + + +final class MsiDatabase { + + static MsiDatabase load(Path msiFile, Path idtFileOutputDir, Set tableNames) { + try { + Files.createDirectories(idtFileOutputDir); + + var orderedTableNames = tableNames.stream().sorted().toList(); + + Executor.of("cscript.exe", "//Nologo") + .addArgument(TKit.TEST_SRC_ROOT.resolve("resources/msi-export.js")) + .addArgument(msiFile) + .addArgument(idtFileOutputDir) + .addArguments(orderedTableNames.stream().map(Table::tableName).toList()) + .dumpOutput() + .execute(0); + + var tables = orderedTableNames.stream().map(tableName -> { + return Map.entry(tableName, idtFileOutputDir.resolve(tableName + ".idt")); + }).filter(e -> { + return Files.exists(e.getValue()); + }).collect(Collectors.toMap(Map.Entry::getKey, e -> { + return MsiTable.loadFromTextArchiveFile(e.getValue()); + })); + + return new MsiDatabase(tables); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + + + enum Table { + COMPONENT("Component"), + DIRECTORY("Directory"), + FILE("File"), + PROPERTY("Property"), + SHORTCUT("Shortcut"), + ; + + Table(String name) { + this.tableName = Objects.requireNonNull(name); + } + + String tableName() { + return tableName; + } + + private final String tableName; + + static final Set
FIND_PROPERTY_REQUIRED_TABLES = Set.of(PROPERTY); + static final Set
LIST_SHORTCUTS_REQUIRED_TABLES = Set.of(COMPONENT, DIRECTORY, FILE, SHORTCUT); + } + + + private MsiDatabase(Map tables) { + this.tables = Map.copyOf(tables); + } + + Set
tableNames() { + return tables.keySet(); + } + + MsiDatabase append(MsiDatabase other) { + Map newTables = new HashMap<>(tables); + newTables.putAll(other.tables); + return new MsiDatabase(newTables); + } + + Optional findProperty(String propertyName) { + Objects.requireNonNull(propertyName); + return tables.get(Table.PROPERTY).findRow("Property", propertyName).map(row -> { + return row.apply("Value"); + }); + } + + Collection listShortcuts() { + var shortcuts = tables.get(Table.SHORTCUT); + if (shortcuts == null) { + return List.of(); + } + return IntStream.range(0, shortcuts.rowCount()).mapToObj(i -> { + var row = shortcuts.row(i); + var shortcutPath = directoryPath(row.apply("Directory_")).resolve(fileNameFromFieldValue(row.apply("Name"))); + var workDir = directoryPath(row.apply("WkDir")); + var shortcutTarget = Path.of(expandFormattedString(row.apply("Target"))); + return new Shortcut(shortcutPath, shortcutTarget, workDir); + }).toList(); + } + + record Shortcut(Path path, Path target, Path workDir) { + + Shortcut { + Objects.requireNonNull(path); + Objects.requireNonNull(target); + Objects.requireNonNull(workDir); + } + + void assertEquals(Shortcut expected) { + TKit.assertEquals(expected.path, path, "Check the shortcut path"); + TKit.assertEquals(expected.target, target, "Check the shortcut target"); + TKit.assertEquals(expected.workDir, workDir, "Check the shortcut work directory"); + } + } + + private Path directoryPath(String directoryId) { + var table = tables.get(Table.DIRECTORY); + Path result = null; + for (var row = table.findRow("Directory", directoryId); + row.isPresent(); + directoryId = row.get().apply("Directory_Parent"), row = table.findRow("Directory", directoryId)) { + + Path pathComponent; + if (DIRECTORY_PROPERTIES.contains(directoryId)) { + pathComponent = Path.of(directoryId); + directoryId = null; + } else { + pathComponent = fileNameFromFieldValue(row.get().apply("DefaultDir")); + } + + if (result != null) { + result = pathComponent.resolve(result); + } else { + result = pathComponent; + } + + if (directoryId == null) { + break; + } + } + + return Objects.requireNonNull(result); + } + + private String expandFormattedString(String str) { + return expandFormattedString(str, token -> { + if (token.charAt(0) == '#') { + var filekey = token.substring(1); + var fileRow = tables.get(Table.FILE).findRow("File", filekey).orElseThrow(); + + var component = fileRow.apply("Component_"); + var componentRow = tables.get(Table.COMPONENT).findRow("Component", component).orElseThrow(); + + var fileName = fileNameFromFieldValue(fileRow.apply("FileName")); + var filePath = directoryPath(componentRow.apply("Directory_")); + + return filePath.resolve(fileName).toString(); + } else { + throw new UnsupportedOperationException(String.format( + "Unrecognized token [%s] in formatted string [%s]", token, str)); + } + }); + } + + private static Path fileNameFromFieldValue(String fieldValue) { + var pipeIdx = fieldValue.indexOf('|'); + if (pipeIdx < 0) { + return Path.of(fieldValue); + } else { + return Path.of(fieldValue.substring(pipeIdx + 1)); + } + } + + private static String expandFormattedString(String str, Function callback) { + // Naive implementation of https://learn.microsoft.com/en-us/windows/win32/msi/formatted + // - No recursive property expansion. + // - No curly brakes ({}) handling. + + Objects.requireNonNull(str); + Objects.requireNonNull(callback); + var sb = new StringBuffer(); + var m = FORMATTED_STRING_TOKEN.matcher(str); + while (m.find()) { + var token = m.group(); + token = token.substring(1, token.length() - 1); + if (token.equals("~")) { + m.appendReplacement(sb, "\0"); + } else { + var replacement = Matcher.quoteReplacement(callback.apply(token)); + m.appendReplacement(sb, replacement); + } + } + m.appendTail(sb); + return sb.toString(); + } + + + private record MsiTable(Map> columns) { + + MsiTable { + Objects.requireNonNull(columns); + if (columns.isEmpty()) { + throw new IllegalArgumentException("Table should have columns"); + } + } + + Optional> findRow(String columnName, String fieldValue) { + Objects.requireNonNull(columnName); + Objects.requireNonNull(fieldValue); + var column = columns.get(columnName); + for (int i = 0; i != column.size(); i++) { + if (fieldValue.equals(column.get(i))) { + return Optional.of(row(i)); + } + } + return Optional.empty(); + } + + /** + * Loads a table from a text archive file. + * @param idtFile path to the input text archive file + * @return the table + */ + static MsiTable loadFromTextArchiveFile(Path idtFile) { + + var header = IdtFileHeader.loadFromTextArchiveFile(idtFile); + + Map> columns = new HashMap<>(); + header.columns.forEach(column -> { + columns.put(column, new ArrayList<>()); + }); + + try { + var lines = Files.readAllLines(idtFile, header.charset()).toArray(String[]::new); + for (int i = 3; i != lines.length; i++) { + var line = lines[i]; + var row = line.split("\t", -1); + if (row.length != header.columns().size()) { + throw new IllegalArgumentException(String.format( + "Expected %d columns. Actual is %d in line %d in [%s] file", + header.columns().size(), row.length, i, idtFile)); + } + for (int j = 0; j != row.length; j++) { + var field = row[j]; + // https://learn.microsoft.com/en-us/windows/win32/msi/archive-file-format + field = field.replace((char)21, (char)0); + field = field.replace((char)27, '\b'); + field = field.replace((char)16, '\t'); + field = field.replace((char)25, '\n'); + field = field.replace((char)24, '\f'); + field = field.replace((char)17, '\r'); + columns.get(header.columns.get(j)).add(field); + } + } + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + + return new MsiTable(columns); + } + + int columnCount() { + return columns.size(); + } + + int rowCount() { + return columns.values().stream().findAny().orElseThrow().size(); + } + + Function row(int rowIndex) { + return columnName -> { + var column = Objects.requireNonNull(columns.get(Objects.requireNonNull(columnName))); + return column.get(rowIndex); + }; + } + } + + + private record IdtFileHeader(Charset charset, List columns) { + + IdtFileHeader { + Objects.requireNonNull(charset); + columns.forEach(Objects::requireNonNull); + if (columns.isEmpty()) { + throw new IllegalArgumentException("Table should have columns"); + } + } + + /** + * Loads a table header from a text archive (.idt) file. + * @see https://learn.microsoft.com/en-us/windows/win32/msi/archive-file-format + * @see https://learn.microsoft.com/en-us/windows/win32/msi/ascii-data-in-text-archive-files + * @param path path to the input text archive file + * @return the table header + */ + static IdtFileHeader loadFromTextArchiveFile(Path idtFile) { + var charset = StandardCharsets.US_ASCII; + try (var stream = Files.lines(idtFile, charset)) { + var headerLines = stream.limit(3).toList(); + if (headerLines.size() != 3) { + throw new IllegalArgumentException(String.format( + "[%s] file should have at least three text lines", idtFile)); + } + + var columns = headerLines.get(0).split("\t"); + + var header = headerLines.get(2).split("\t", 4); + if (header.length == 3) { + if (Pattern.matches("^[1-9]\\d+$", header[0])) { + charset = Charset.forName(header[0]); + } else { + throw new IllegalArgumentException(String.format( + "Unexpected charset name [%s] in [%s] file", header[0], idtFile)); + } + } else if (header.length != 2) { + throw new IllegalArgumentException(String.format( + "Unexpected number of fields (%d) in the 3rd line of [%s] file", + header.length, idtFile)); + } + + return new IdtFileHeader(charset, List.of(columns)); + + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + } + + + private final Map tables; + + // https://learn.microsoft.com/en-us/windows/win32/msi/formatted + private static final Pattern FORMATTED_STRING_TOKEN = Pattern.compile("\\[[^\\]]+\\]"); + + // https://learn.microsoft.com/en-us/windows/win32/msi/property-reference#system-folder-properties + private final Set DIRECTORY_PROPERTIES = Set.of( + "DesktopFolder", + "LocalAppDataFolder", + "ProgramFiles64Folder", + "ProgramMenuFolder" + ); +} diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/PackageTest.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/PackageTest.java index 82b1623a42d..84453038cd2 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/PackageTest.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/PackageTest.java @@ -30,11 +30,13 @@ import static jdk.jpackage.test.PackageType.LINUX; import static jdk.jpackage.test.PackageType.MAC_PKG; import static jdk.jpackage.test.PackageType.NATIVE; import static jdk.jpackage.test.PackageType.WINDOWS; +import static jdk.jpackage.test.PackageType.WIN_MSI; import java.awt.GraphicsEnvironment; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.time.Duration; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -270,8 +272,7 @@ public final class PackageTest extends RunnablePackageTest { PackageTest addHelloAppFileAssociationsVerifier(FileAssociations fa) { Objects.requireNonNull(fa); - // Setup test app to have valid jpackage command line before - // running check of type of environment. + // Setup test app to have valid jpackage command line before running the check. addHelloAppInitializer(null); forTypes(LINUX, () -> { @@ -296,13 +297,9 @@ public final class PackageTest extends RunnablePackageTest { Files.deleteIfExists(appOutput); List expectedArgs = testRun.openFiles(testFiles); - TKit.waitForFileCreated(appOutput, 7); + TKit.waitForFileCreated(appOutput, Duration.ofSeconds(7), Duration.ofSeconds(3)); - // Wait a little bit after file has been created to - // make sure there are no pending writes into it. - Thread.sleep(3000); - HelloApp.verifyOutputFile(appOutput, expectedArgs, - Collections.emptyMap()); + HelloApp.verifyOutputFile(appOutput, expectedArgs, Map.of()); }); if (isOfType(cmd, WINDOWS)) { @@ -360,15 +357,14 @@ public final class PackageTest extends RunnablePackageTest { public PackageTest configureHelloApp(String javaAppDesc) { addHelloAppInitializer(javaAppDesc); - addInstallVerifier(HelloApp::executeLauncherAndVerifyOutput); + addInstallVerifier(LauncherVerifier::executeMainLauncherAndVerifyOutput); return this; } public PackageTest addHelloAppInitializer(String javaAppDesc) { - addInitializer( - cmd -> new HelloApp(JavaAppDesc.parse(javaAppDesc)).addTo(cmd), - "HelloApp"); - return this; + return addInitializer(cmd -> { + new HelloApp(JavaAppDesc.parse(javaAppDesc)).addTo(cmd); + }, "HelloApp"); } public static class Group extends RunnablePackageTest { @@ -611,11 +607,7 @@ public final class PackageTest extends RunnablePackageTest { } } case VERIFY_INSTALL -> { - if (unpackNotSupported()) { - return ActionAction.SKIP; - } - - if (installFailed()) { + if (unpackNotSupported() || installFailed()) { return ActionAction.SKIP; } } @@ -753,6 +745,8 @@ public final class PackageTest extends RunnablePackageTest { if (expectedJPackageExitCode == 0) { if (isOfType(cmd, LINUX)) { LinuxHelper.verifyPackageBundleEssential(cmd); + } else if (isOfType(cmd, WIN_MSI)) { + WinShortcutVerifier.verifyBundleShortcuts(cmd); } } bundleVerifiers.forEach(v -> v.accept(cmd, result)); @@ -774,12 +768,11 @@ public final class PackageTest extends RunnablePackageTest { if (!cmd.isRuntime()) { if (isOfType(cmd, WINDOWS) && !cmd.isPackageUnpacked("Not verifying desktop integration")) { - // Check main launcher - WindowsHelper.verifyDesktopIntegration(cmd, null); - // Check additional launchers - cmd.addLauncherNames().forEach(name -> { - WindowsHelper.verifyDesktopIntegration(cmd, name); - }); + WindowsHelper.verifyDeployedDesktopIntegration(cmd, true); + } + + if (isOfType(cmd, LINUX)) { + LinuxHelper.verifyDesktopFiles(cmd, true); } } @@ -856,12 +849,11 @@ public final class PackageTest extends RunnablePackageTest { TKit.assertPathExists(cmd.appLauncherPath(), false); if (isOfType(cmd, WINDOWS)) { - // Check main launcher - WindowsHelper.verifyDesktopIntegration(cmd, null); - // Check additional launchers - cmd.addLauncherNames().forEach(name -> { - WindowsHelper.verifyDesktopIntegration(cmd, name); - }); + WindowsHelper.verifyDeployedDesktopIntegration(cmd, false); + } + + if (isOfType(cmd, LINUX)) { + LinuxHelper.verifyDesktopFiles(cmd, false); } } diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TKit.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TKit.java index 2508db00295..a94dfa135c1 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TKit.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TKit.java @@ -43,6 +43,8 @@ import java.nio.file.WatchEvent; import java.nio.file.WatchKey; import java.nio.file.WatchService; import java.text.SimpleDateFormat; +import java.time.Duration; +import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; @@ -597,8 +599,14 @@ public final class TKit { return file; } - static void waitForFileCreated(Path fileToWaitFor, - long timeoutSeconds) throws IOException { + public static void waitForFileCreated(Path fileToWaitFor, + Duration timeout, Duration afterCreatedTimeout) throws IOException { + waitForFileCreated(fileToWaitFor, timeout); + // Wait after the file has been created to ensure it is fully written. + ThrowingConsumer.toConsumer(Thread::sleep).accept(afterCreatedTimeout); + } + + private static void waitForFileCreated(Path fileToWaitFor, Duration timeout) throws IOException { trace(String.format("Wait for file [%s] to be available", fileToWaitFor.toAbsolutePath())); @@ -608,22 +616,23 @@ public final class TKit { Path watchDirectory = fileToWaitFor.toAbsolutePath().getParent(); watchDirectory.register(ws, ENTRY_CREATE, ENTRY_MODIFY); - long waitUntil = System.currentTimeMillis() + timeoutSeconds * 1000; + var waitUntil = Instant.now().plus(timeout); for (;;) { - long timeout = waitUntil - System.currentTimeMillis(); - assertTrue(timeout > 0, String.format( - "Check timeout value %d is positive", timeout)); + var remainderTimeout = Instant.now().until(waitUntil); + assertTrue(remainderTimeout.isPositive(), String.format( + "Check timeout value %dms is positive", remainderTimeout.toMillis())); - WatchKey key = ThrowingSupplier.toSupplier(() -> ws.poll(timeout, - TimeUnit.MILLISECONDS)).get(); + WatchKey key = ThrowingSupplier.toSupplier(() -> { + return ws.poll(remainderTimeout.toMillis(), TimeUnit.MILLISECONDS); + }).get(); if (key == null) { - if (fileToWaitFor.toFile().exists()) { + if (Files.exists(fileToWaitFor)) { trace(String.format( "File [%s] is available after poll timeout expired", fileToWaitFor)); return; } - assertUnexpected(String.format("Timeout expired", timeout)); + assertUnexpected(String.format("Timeout %dms expired", remainderTimeout.toMillis())); } for (WatchEvent event : key.pollEvents()) { diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WinShortcutVerifier.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WinShortcutVerifier.java new file mode 100644 index 00000000000..cca904e017e --- /dev/null +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WinShortcutVerifier.java @@ -0,0 +1,283 @@ +/* + * 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 + * 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.jpackage.test; + +import static java.util.stream.Collectors.groupingBy; +import static jdk.jpackage.test.LauncherShortcut.WIN_DESKTOP_SHORTCUT; +import static jdk.jpackage.test.LauncherShortcut.WIN_START_MENU_SHORTCUT; +import static jdk.jpackage.test.WindowsHelper.getInstallationSubDirectory; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Stream; +import jdk.jpackage.internal.util.PathUtils; +import jdk.jpackage.test.LauncherShortcut.InvokeShortcutSpec; +import jdk.jpackage.test.LauncherShortcut.StartupDirectory; +import jdk.jpackage.test.MsiDatabase.Shortcut; +import jdk.jpackage.test.WindowsHelper.SpecialFolder; + + +public final class WinShortcutVerifier { + + static void verifyBundleShortcuts(JPackageCommand cmd) { + cmd.verifyIsOfType(PackageType.WIN_MSI); + + if (Stream.of("--win-menu", "--win-shortcut").noneMatch(cmd::hasArgument) && cmd.addLauncherNames().isEmpty()) { + return; + } + + var actualShortcuts = WindowsHelper.getMsiShortcuts(cmd).stream().collect(groupingBy(shortcut -> { + return PathUtils.replaceSuffix(shortcut.target().getFileName(), "").toString(); + })); + + var expectedShortcuts = expectShortcuts(cmd); + + var launcherNames = expectedShortcuts.keySet().stream().sorted().toList(); + + TKit.assertStringListEquals( + launcherNames, + actualShortcuts.keySet().stream().sorted().toList(), + "Check the list of launchers with shortcuts"); + + Function, List> sorter = shortcuts -> { + return shortcuts.stream().sorted(SHORTCUT_COMPARATOR).toList(); + }; + + for (var name : launcherNames) { + var actualLauncherShortcuts = sorter.apply(actualShortcuts.get(name)); + var expectedLauncherShortcuts = sorter.apply(expectedShortcuts.get(name)); + + TKit.assertEquals(expectedLauncherShortcuts.size(), actualLauncherShortcuts.size(), + String.format("Check the number of shortcuts of launcher [%s]", name)); + + for (int i = 0; i != expectedLauncherShortcuts.size(); i++) { + TKit.trace(String.format("Verify shortcut #%d of launcher [%s]", i + 1, name)); + actualLauncherShortcuts.get(i).assertEquals(expectedLauncherShortcuts.get(i)); + TKit.trace("Done"); + } + } + } + + static void verifyDeployedShortcuts(JPackageCommand cmd, boolean installed) { + cmd.verifyIsOfType(PackageType.WINDOWS); + + verifyDeployedShortcutsInternal(cmd, installed); + var copyCmd = cmd.createMutableCopy(); + if (copyCmd.hasArgument("--win-per-user-install")) { + copyCmd.removeArgument("--win-per-user-install"); + } else { + copyCmd.addArgument("--win-per-user-install"); + } + verifyDeployedShortcutsInternal(copyCmd, false); + } + + public static Collection getInvokeShortcutSpecs(JPackageCommand cmd) { + return expectShortcuts(cmd).entrySet().stream().map(e -> { + return e.getValue().stream().map(shortcut -> { + return convert(cmd, e.getKey(), shortcut); + }); + }).flatMap(x -> x).toList(); + } + + private static void verifyDeployedShortcutsInternal(JPackageCommand cmd, boolean installed) { + + var expectedShortcuts = expectShortcuts(cmd).values().stream().flatMap(Collection::stream).toList(); + + var isUserLocalInstall = WindowsHelper.isUserLocalInstall(cmd); + + expectedShortcuts.stream().map(Shortcut::path).sorted().map(path -> { + return resolvePath(path, !isUserLocalInstall); + }).map(path -> { + return PathUtils.addSuffix(path, ".lnk"); + }).forEach(path -> { + if (installed) { + TKit.assertFileExists(path); + } else { + TKit.assertPathExists(path, false); + } + }); + + if (!installed) { + expectedShortcuts.stream().map(Shortcut::path).filter(path -> { + return Stream.of(ShortcutType.COMMON_START_MENU, ShortcutType.USER_START_MENU).anyMatch(type -> { + return path.startsWith(Path.of(type.rootFolder().getMsiPropertyName())); + }); + }).map(Path::getParent).distinct().map(unresolvedShortcutDir -> { + return resolvePath(unresolvedShortcutDir, !isUserLocalInstall); + }).forEach(shortcutDir -> { + if (Files.isDirectory(shortcutDir)) { + TKit.assertDirectoryNotEmpty(shortcutDir); + } else { + TKit.assertPathExists(shortcutDir, false); + } + }); + } + } + + private enum ShortcutType { + COMMON_START_MENU(SpecialFolder.COMMON_START_MENU_PROGRAMS), + USER_START_MENU(SpecialFolder.USER_START_MENU_PROGRAMS), + COMMON_DESKTOP(SpecialFolder.COMMON_DESKTOP), + USER_DESKTOP(SpecialFolder.USER_DESKTOP), + ; + + ShortcutType(SpecialFolder rootFolder) { + this.rootFolder = Objects.requireNonNull(rootFolder); + } + + SpecialFolder rootFolder() { + return rootFolder; + } + + private final SpecialFolder rootFolder; + } + + private static Path resolvePath(Path path, boolean allUsers) { + var root = path.getName(0); + var resolvedRoot = SpecialFolder.findMsiProperty(root.toString(), allUsers).orElseThrow().getPath(); + return resolvedRoot.resolve(root.relativize(path)); + } + + private static Shortcut createLauncherShortcutSpec(JPackageCommand cmd, String launcherName, + SpecialFolder installRoot, Path workDir, ShortcutType type) { + + var name = Optional.ofNullable(launcherName).orElseGet(cmd::name); + + var appLayout = ApplicationLayout.windowsAppImage().resolveAt( + Path.of(installRoot.getMsiPropertyName()).resolve(getInstallationSubDirectory(cmd))); + + Path path; + switch (type) { + case COMMON_START_MENU, USER_START_MENU -> { + path = Path.of(cmd.getArgumentValue("--win-menu-group", () -> "Unknown"), name); + } + default -> { + path = Path.of(name); + } + } + + return new Shortcut( + Path.of(type.rootFolder().getMsiPropertyName()).resolve(path), + appLayout.launchersDirectory().resolve(name + ".exe"), + workDir); + } + + private static Collection expectLauncherShortcuts(JPackageCommand cmd, + Optional predefinedAppImage, String launcherName) { + Objects.requireNonNull(cmd); + Objects.requireNonNull(predefinedAppImage); + + final List shortcuts = new ArrayList<>(); + + final var winMenu = WIN_START_MENU_SHORTCUT.expectShortcut(cmd, predefinedAppImage, launcherName); + final var desktop = WIN_DESKTOP_SHORTCUT.expectShortcut(cmd, predefinedAppImage, launcherName); + + final var isUserLocalInstall = WindowsHelper.isUserLocalInstall(cmd); + + final SpecialFolder installRoot; + if (isUserLocalInstall) { + installRoot = SpecialFolder.LOCAL_APPLICATION_DATA; + } else { + installRoot = SpecialFolder.PROGRAM_FILES; + } + + final var installDir = Path.of(installRoot.getMsiPropertyName()).resolve(getInstallationSubDirectory(cmd)); + + final Function workDir = startupDirectory -> { + return installDir; + }; + + if (winMenu.isPresent()) { + ShortcutType type; + if (isUserLocalInstall) { + type = ShortcutType.USER_START_MENU; + } else { + type = ShortcutType.COMMON_START_MENU; + } + shortcuts.add(createLauncherShortcutSpec(cmd, launcherName, installRoot, winMenu.map(workDir).orElseThrow(), type)); + } + + if (desktop.isPresent()) { + ShortcutType type; + if (isUserLocalInstall) { + type = ShortcutType.USER_DESKTOP; + } else { + type = ShortcutType.COMMON_DESKTOP; + } + shortcuts.add(createLauncherShortcutSpec(cmd, launcherName, installRoot, desktop.map(workDir).orElseThrow(), type)); + } + + return shortcuts; + } + + private static Map> expectShortcuts(JPackageCommand cmd) { + Map> expectedShortcuts = new HashMap<>(); + + var predefinedAppImage = Optional.ofNullable(cmd.getArgumentValue("--app-image")).map(Path::of).map(AppImageFile::load); + + predefinedAppImage.map(v -> { + return v.launchers().keySet().stream(); + }).orElseGet(() -> { + return Stream.concat(Stream.of(cmd.name()), cmd.addLauncherNames().stream()); + }).forEach(launcherName -> { + var shortcuts = expectLauncherShortcuts(cmd, predefinedAppImage, launcherName); + if (!shortcuts.isEmpty()) { + expectedShortcuts.put(launcherName, shortcuts); + } + }); + + return expectedShortcuts; + } + + private static InvokeShortcutSpec convert(JPackageCommand cmd, String launcherName, Shortcut shortcut) { + LauncherShortcut launcherShortcut; + if (Stream.of(ShortcutType.COMMON_START_MENU, ShortcutType.USER_START_MENU).anyMatch(type -> { + return shortcut.path().startsWith(Path.of(type.rootFolder().getMsiPropertyName())); + })) { + launcherShortcut = WIN_START_MENU_SHORTCUT; + } else { + launcherShortcut = WIN_DESKTOP_SHORTCUT; + } + + var isUserLocalInstall = WindowsHelper.isUserLocalInstall(cmd); + return new InvokeShortcutSpec.Stub( + launcherName, + launcherShortcut, + Optional.of(resolvePath(shortcut.workDir(), !isUserLocalInstall)), + List.of("cmd", "/c", "start", "/wait", PathUtils.addSuffix(resolvePath(shortcut.path(), !isUserLocalInstall), ".lnk").toString())); + } + + + private static final Comparator SHORTCUT_COMPARATOR = Comparator.comparing(Shortcut::target) + .thenComparing(Comparator.comparing(Shortcut::path)) + .thenComparing(Comparator.comparing(Shortcut::workDir)); +} diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java index 3ac302ce0fe..5e97b0d2dde 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java @@ -26,19 +26,24 @@ import static jdk.jpackage.internal.util.function.ExceptionBox.rethrowUnchecked; import static jdk.jpackage.internal.util.function.ThrowingSupplier.toSupplier; import java.io.IOException; +import java.io.UncheckedIOException; +import java.lang.ref.SoftReference; import java.lang.reflect.Method; import java.nio.file.Files; import java.nio.file.Path; +import java.time.Instant; +import java.util.Collection; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; import java.util.Optional; +import java.util.Properties; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Collectors; import java.util.stream.Stream; import jdk.jpackage.internal.util.function.ThrowingRunnable; import jdk.jpackage.test.PackageTest.PackageHandlers; @@ -63,7 +68,7 @@ public class WindowsHelper { return PROGRAM_FILES; } - private static Path getInstallationSubDirectory(JPackageCommand cmd) { + static Path getInstallationSubDirectory(JPackageCommand cmd) { cmd.verifyIsOfType(PackageType.WINDOWS); return Path.of(cmd.getArgumentValue("--install-dir", cmd::name)); } @@ -263,22 +268,22 @@ public class WindowsHelper { } } - static void verifyDesktopIntegration(JPackageCommand cmd, - String launcherName) { - new DesktopIntegrationVerifier(cmd, launcherName); + static void verifyDeployedDesktopIntegration(JPackageCommand cmd, boolean installed) { + WinShortcutVerifier.verifyDeployedShortcuts(cmd, installed); + DesktopIntegrationVerifier.verify(cmd, installed); } public static String getMsiProperty(JPackageCommand cmd, String propertyName) { cmd.verifyIsOfType(PackageType.WIN_MSI); - return Executor.of("cscript.exe", "//Nologo") - .addArgument(TKit.TEST_SRC_ROOT.resolve("resources/query-msi-property.js")) - .addArgument(cmd.outputBundle()) - .addArgument(propertyName) - .dumpOutput() - .executeAndGetOutput().stream().collect(Collectors.joining("\n")); + return MsiDatabaseCache.INSTANCE.findProperty(cmd.outputBundle(), propertyName).orElseThrow(); } - public static String getExecutableDesciption(Path pathToExeFile) { + static Collection getMsiShortcuts(JPackageCommand cmd) { + cmd.verifyIsOfType(PackageType.WIN_MSI); + return MsiDatabaseCache.INSTANCE.listShortcuts(cmd.outputBundle()); + } + + public static String getExecutableDescription(Path pathToExeFile) { Executor exec = Executor.of("powershell", "-NoLogo", "-NoProfile", @@ -386,7 +391,7 @@ public class WindowsHelper { } } - private static boolean isUserLocalInstall(JPackageCommand cmd) { + static boolean isUserLocalInstall(JPackageCommand cmd) { return cmd.hasArgument("--win-per-user-install"); } @@ -394,141 +399,42 @@ public class WindowsHelper { return path.toString().length() > WIN_MAX_PATH; } + private static class DesktopIntegrationVerifier { - DesktopIntegrationVerifier(JPackageCommand cmd, String launcherName) { + static void verify(JPackageCommand cmd, boolean installed) { cmd.verifyIsOfType(PackageType.WINDOWS); - - name = Optional.ofNullable(launcherName).orElseGet(cmd::name); - - isUserLocalInstall = isUserLocalInstall(cmd); - - appInstalled = cmd.appLauncherPath(launcherName).toFile().exists(); - - desktopShortcutPath = Path.of(name + ".lnk"); - - startMenuShortcutPath = Path.of(cmd.getArgumentValue( - "--win-menu-group", () -> "Unknown"), name + ".lnk"); - - if (name.equals(cmd.name())) { - isWinMenu = cmd.hasArgument("--win-menu"); - isDesktop = cmd.hasArgument("--win-shortcut"); - } else { - var props = AdditionalLauncher.getAdditionalLauncherProperties(cmd, - launcherName); - isWinMenu = props.getPropertyBooleanValue("win-menu").orElseGet( - () -> cmd.hasArgument("--win-menu")); - isDesktop = props.getPropertyBooleanValue("win-shortcut").orElseGet( - () -> cmd.hasArgument("--win-shortcut")); - } - - verifyStartMenuShortcut(); - - verifyDesktopShortcut(); - - Stream.of(cmd.getAllArgumentValues("--file-associations")).map( - Path::of).forEach(this::verifyFileAssociationsRegistry); - } - - private void verifyDesktopShortcut() { - if (isDesktop) { - if (isUserLocalInstall) { - verifyUserLocalDesktopShortcut(appInstalled); - verifySystemDesktopShortcut(false); - } else { - verifySystemDesktopShortcut(appInstalled); - verifyUserLocalDesktopShortcut(false); - } - } else { - verifySystemDesktopShortcut(false); - verifyUserLocalDesktopShortcut(false); + for (var faFile : cmd.getAllArgumentValues("--file-associations")) { + verifyFileAssociationsRegistry(Path.of(faFile), installed); } } - private void verifyShortcut(Path path, boolean exists) { - if (exists) { - TKit.assertFileExists(path); - } else { - TKit.assertPathExists(path, false); - } - } + private static void verifyFileAssociationsRegistry(Path faFile, boolean installed) { - private void verifySystemDesktopShortcut(boolean exists) { - Path dir = SpecialFolder.COMMON_DESKTOP.getPath(); - verifyShortcut(dir.resolve(desktopShortcutPath), exists); - } + TKit.trace(String.format( + "Get file association properties from [%s] file", + faFile)); - private void verifyUserLocalDesktopShortcut(boolean exists) { - Path dir = SpecialFolder.USER_DESKTOP.getPath(); - verifyShortcut(dir.resolve(desktopShortcutPath), exists); - } + var faProps = new Properties(); - private void verifyStartMenuShortcut() { - if (isWinMenu) { - if (isUserLocalInstall) { - verifyUserLocalStartMenuShortcut(appInstalled); - verifySystemStartMenuShortcut(false); - } else { - verifySystemStartMenuShortcut(appInstalled); - verifyUserLocalStartMenuShortcut(false); - } - } else { - verifySystemStartMenuShortcut(false); - verifyUserLocalStartMenuShortcut(false); - } - } - - private void verifyStartMenuShortcut(Path shortcutsRoot, boolean exists) { - Path shortcutPath = shortcutsRoot.resolve(startMenuShortcutPath); - verifyShortcut(shortcutPath, exists); - if (!exists) { - final var parentDir = shortcutPath.getParent(); - if (Files.isDirectory(parentDir)) { - TKit.assertDirectoryNotEmpty(parentDir); - } else { - TKit.assertPathExists(parentDir, false); - } - } - } - - private void verifySystemStartMenuShortcut(boolean exists) { - verifyStartMenuShortcut(SpecialFolder.COMMON_START_MENU_PROGRAMS.getPath(), exists); - - } - - private void verifyUserLocalStartMenuShortcut(boolean exists) { - verifyStartMenuShortcut(SpecialFolder.USER_START_MENU_PROGRAMS.getPath(), exists); - } - - private void verifyFileAssociationsRegistry(Path faFile) { - try { - TKit.trace(String.format( - "Get file association properties from [%s] file", - faFile)); - Map faProps = Files.readAllLines(faFile).stream().filter( - line -> line.trim().startsWith("extension=") || line.trim().startsWith( - "mime-type=")).map( - line -> { - String[] keyValue = line.trim().split("=", 2); - return Map.entry(keyValue[0], keyValue[1]); - }).collect(Collectors.toMap( - entry -> entry.getKey(), - entry -> entry.getValue())); - String suffix = faProps.get("extension"); - String contentType = faProps.get("mime-type"); + try (var reader = Files.newBufferedReader(faFile)) { + faProps.load(reader); + String suffix = faProps.getProperty("extension"); + String contentType = faProps.getProperty("mime-type"); TKit.assertNotNull(suffix, String.format( "Check file association suffix [%s] is found in [%s] property file", suffix, faFile)); TKit.assertNotNull(contentType, String.format( "Check file association content type [%s] is found in [%s] property file", contentType, faFile)); - verifyFileAssociations(appInstalled, "." + suffix, contentType); + verifyFileAssociations(installed, "." + suffix, contentType); + } catch (IOException ex) { - throw new RuntimeException(ex); + throw new UncheckedIOException(ex); } } - private void verifyFileAssociations(boolean exists, String suffix, + private static void verifyFileAssociations(boolean exists, String suffix, String contentType) { String contentTypeFromRegistry = queryRegistryValue(Path.of( "HKLM\\Software\\Classes", suffix).toString(), @@ -549,16 +455,9 @@ public class WindowsHelper { "Check content type in registry not found"); } } - - private final Path desktopShortcutPath; - private final Path startMenuShortcutPath; - private final boolean isUserLocalInstall; - private final boolean appInstalled; - private final boolean isWinMenu; - private final boolean isDesktop; - private final String name; } + static String queryRegistryValue(String keyPath, String valueName) { var status = Executor.of("reg", "query", keyPath, "/v", valueName) .saveOutput() @@ -611,7 +510,12 @@ public class WindowsHelper { CommonDesktop, Programs, - CommonPrograms; + CommonPrograms, + + ProgramFiles, + + LocalApplicationData, + ; Path getPath() { final var str = Executor.of("powershell", "-NoLogo", "-NoProfile", @@ -636,33 +540,84 @@ public class WindowsHelper { } } - private enum SpecialFolder { - COMMON_START_MENU_PROGRAMS(SYSTEM_SHELL_FOLDERS_REGKEY, "Common Programs", SpecialFolderDotNet.CommonPrograms), - USER_START_MENU_PROGRAMS(USER_SHELL_FOLDERS_REGKEY, "Programs", SpecialFolderDotNet.Programs), + enum SpecialFolder { + COMMON_START_MENU_PROGRAMS( + SYSTEM_SHELL_FOLDERS_REGKEY, + "Common Programs", + "ProgramMenuFolder", + SpecialFolderDotNet.CommonPrograms), + USER_START_MENU_PROGRAMS( + USER_SHELL_FOLDERS_REGKEY, + "Programs", + "ProgramMenuFolder", + SpecialFolderDotNet.Programs), - COMMON_DESKTOP(SYSTEM_SHELL_FOLDERS_REGKEY, "Common Desktop", SpecialFolderDotNet.CommonDesktop), - USER_DESKTOP(USER_SHELL_FOLDERS_REGKEY, "Desktop", SpecialFolderDotNet.Desktop); + COMMON_DESKTOP( + SYSTEM_SHELL_FOLDERS_REGKEY, + "Common Desktop", + "DesktopFolder", + SpecialFolderDotNet.CommonDesktop), + USER_DESKTOP( + USER_SHELL_FOLDERS_REGKEY, + "Desktop", + "DesktopFolder", + SpecialFolderDotNet.Desktop), - SpecialFolder(String keyPath, String valueName) { - reg = new RegValuePath(keyPath, valueName); + PROGRAM_FILES("ProgramFiles64Folder", SpecialFolderDotNet.ProgramFiles), + + LOCAL_APPLICATION_DATA("LocalAppDataFolder", SpecialFolderDotNet.LocalApplicationData), + ; + + SpecialFolder(String keyPath, String valueName, String msiPropertyName) { + reg = Optional.of(new RegValuePath(keyPath, valueName)); alt = Optional.empty(); + this.msiPropertyName = Objects.requireNonNull(msiPropertyName); } - SpecialFolder(String keyPath, String valueName, SpecialFolderDotNet alt) { - reg = new RegValuePath(keyPath, valueName); + SpecialFolder(String keyPath, String valueName, String msiPropertyName, SpecialFolderDotNet alt) { + reg = Optional.of(new RegValuePath(keyPath, valueName)); this.alt = Optional.of(alt); + this.msiPropertyName = Objects.requireNonNull(msiPropertyName); + } + + SpecialFolder(String msiPropertyName, SpecialFolderDotNet alt) { + reg = Optional.empty(); + this.alt = Optional.of(alt); + this.msiPropertyName = Objects.requireNonNull(msiPropertyName); + } + + static Optional findMsiProperty(String pathComponent, boolean allUsers) { + Objects.requireNonNull(pathComponent); + String regPath; + if (allUsers) { + regPath = SYSTEM_SHELL_FOLDERS_REGKEY; + } else { + regPath = USER_SHELL_FOLDERS_REGKEY; + } + return Stream.of(values()) + .filter(v -> v.msiPropertyName.equals(pathComponent)) + .filter(v -> { + return v.reg.map(r -> r.keyPath().equals(regPath)).orElse(true); + }) + .findFirst(); + } + + String getMsiPropertyName() { + return msiPropertyName; } Path getPath() { - return CACHE.computeIfAbsent(this, k -> reg.findValue().map(Path::of).orElseGet(() -> { + return CACHE.computeIfAbsent(this, k -> reg.flatMap(RegValuePath::findValue).map(Path::of).orElseGet(() -> { return alt.map(SpecialFolderDotNet::getPath).orElseThrow(() -> { return new NoSuchElementException(String.format("Failed to find path to %s folder", name())); }); })); } - private final RegValuePath reg; + private final Optional reg; private final Optional alt; + // One of "System Folder Properties" from https://learn.microsoft.com/en-us/windows/win32/msi/property-reference + private final String msiPropertyName; private static final Map CACHE = new ConcurrentHashMap<>(); } @@ -693,6 +648,63 @@ public class WindowsHelper { private static final ShortPathUtils INSTANCE = new ShortPathUtils(); } + + private static final class MsiDatabaseCache { + + Optional findProperty(Path msiPath, String propertyName) { + return ensureTables(msiPath, MsiDatabase.Table.FIND_PROPERTY_REQUIRED_TABLES).findProperty(propertyName); + } + + Collection listShortcuts(Path msiPath) { + return ensureTables(msiPath, MsiDatabase.Table.LIST_SHORTCUTS_REQUIRED_TABLES).listShortcuts(); + } + + MsiDatabase ensureTables(Path msiPath, Set tableNames) { + Objects.requireNonNull(msiPath); + try { + synchronized (items) { + var value = Optional.ofNullable(items.get(msiPath)).map(SoftReference::get).orElse(null); + if (value != null) { + var lastModifiedTime = Files.getLastModifiedTime(msiPath).toInstant(); + if (lastModifiedTime.isAfter(value.timestamp())) { + value = null; + } else { + tableNames = Comm.compare(value.db().tableNames(), tableNames).unique2(); + } + } + + if (!tableNames.isEmpty()) { + var idtOutputDir = TKit.createTempDirectory("msi-db"); + var db = MsiDatabase.load(msiPath, idtOutputDir, tableNames); + if (value != null) { + value = new MsiDatabaseWithTimestamp(db.append(value.db()), value.timestamp()); + } else { + value = new MsiDatabaseWithTimestamp(db, Files.getLastModifiedTime(msiPath).toInstant()); + } + items.put(msiPath, new SoftReference<>(value)); + } + + return value.db(); + } + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + + private record MsiDatabaseWithTimestamp(MsiDatabase db, Instant timestamp) { + + MsiDatabaseWithTimestamp { + Objects.requireNonNull(db); + Objects.requireNonNull(timestamp); + } + } + + private final Map> items = new HashMap<>(); + + static final MsiDatabaseCache INSTANCE = new MsiDatabaseCache(); + } + + static final Set CRITICAL_RUNTIME_FILES = Set.of(Path.of( "bin\\server\\jvm.dll")); diff --git a/test/jdk/tools/jpackage/linux/UpgradeTest.java b/test/jdk/tools/jpackage/linux/UpgradeTest.java index fb399cec12b..bfbdcf3aa0c 100644 --- a/test/jdk/tools/jpackage/linux/UpgradeTest.java +++ b/test/jdk/tools/jpackage/linux/UpgradeTest.java @@ -60,16 +60,16 @@ public class UpgradeTest { var alA = createAdditionalLauncher("launcherA"); alA.applyTo(pkg); - createAdditionalLauncher("launcherB").addRawProperties(Map.entry( - "description", "Foo")).applyTo(pkg); + createAdditionalLauncher("launcherB").setProperty( + "description", "Foo").applyTo(pkg); var pkg2 = createPackageTest().addInitializer(cmd -> { cmd.addArguments("--app-version", "2.0"); }); alA.verifyRemovedInUpgrade(pkg2); - createAdditionalLauncher("launcherB").addRawProperties(Map.entry( - "description", "Bar")).applyTo(pkg2); + createAdditionalLauncher("launcherB").setProperty( + "description", "Bar").applyTo(pkg2); createAdditionalLauncher("launcherC").applyTo(pkg2); new PackageTest.Group(pkg, pkg2).run(); @@ -88,6 +88,6 @@ public class UpgradeTest { return new AdditionalLauncher(name).setIcon(GOLDEN_ICON); } - private final static Path GOLDEN_ICON = TKit.TEST_SRC_ROOT.resolve(Path.of( + private static final Path GOLDEN_ICON = TKit.TEST_SRC_ROOT.resolve(Path.of( "resources", "icon" + TKit.ICON_SUFFIX)); } diff --git a/test/jdk/tools/jpackage/resources/msi-export.js b/test/jdk/tools/jpackage/resources/msi-export.js new file mode 100644 index 00000000000..d639f19ca44 --- /dev/null +++ b/test/jdk/tools/jpackage/resources/msi-export.js @@ -0,0 +1,81 @@ +/* + * 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. + */ + + +function readMsi(msiPath, callback) { + var installer = new ActiveXObject('WindowsInstaller.Installer') + var database = installer.OpenDatabase(msiPath, 0 /* msiOpenDatabaseModeReadOnly */) + + return callback(database) +} + + +function exportTables(db, outputDir, requestedTableNames) { + var tables = {} + + var view = db.OpenView("SELECT `Name` FROM _Tables") + view.Execute() + + try { + while (true) { + var record = view.Fetch() + if (!record) { + break + } + + var name = record.StringData(1) + + if (requestedTableNames.hasOwnProperty(name)) { + tables[name] = name + } + } + } finally { + view.Close() + } + + var fso = new ActiveXObject("Scripting.FileSystemObject") + for (var table in tables) { + var idtFileName = table + ".idt" + var idtFile = outputDir + "/" + idtFileName + if (fso.FileExists(idtFile)) { + WScript.Echo("Delete [" + idtFile + "]") + fso.DeleteFile(idtFile) + } + WScript.Echo("Export table [" + table + "] in [" + idtFile + "] file") + db.Export(table, fso.GetFolder(outputDir).Path, idtFileName) + } +} + + +(function () { + var msi = WScript.arguments(0) + var outputDir = WScript.arguments(1) + var tables = {} + for (var i = 0; i !== WScript.arguments.Count(); i++) { + tables[WScript.arguments(i)] = true + } + + readMsi(msi, function (db) { + exportTables(db, outputDir, tables) + }) +})() diff --git a/test/jdk/tools/jpackage/resources/query-msi-property.js b/test/jdk/tools/jpackage/resources/query-msi-property.js deleted file mode 100644 index d821f5a8a54..00000000000 --- a/test/jdk/tools/jpackage/resources/query-msi-property.js +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - - -function readMsi(msiPath, callback) { - var installer = new ActiveXObject('WindowsInstaller.Installer') - var database = installer.OpenDatabase(msiPath, 0 /* msiOpenDatabaseModeReadOnly */) - - return callback(database) -} - - -function queryAllProperties(db) { - var reply = {} - - var view = db.OpenView("SELECT `Property`, `Value` FROM Property") - view.Execute() - - try { - while(true) { - var record = view.Fetch() - if (!record) { - break - } - - var name = record.StringData(1) - var value = record.StringData(2) - - reply[name] = value - } - } finally { - view.Close() - } - - return reply -} - - -(function () { - var msi = WScript.arguments(0) - var propName = WScript.arguments(1) - - var props = readMsi(msi, queryAllProperties) - WScript.Echo(props[propName]) -})() diff --git a/test/jdk/tools/jpackage/share/AddLShortcutTest.java b/test/jdk/tools/jpackage/share/AddLShortcutTest.java index 6430a55d784..7d7d8b50c1d 100644 --- a/test/jdk/tools/jpackage/share/AddLShortcutTest.java +++ b/test/jdk/tools/jpackage/share/AddLShortcutTest.java @@ -21,13 +21,32 @@ * questions. */ -import java.nio.file.Path; +import java.io.IOException; import java.lang.invoke.MethodHandles; -import jdk.jpackage.test.PackageTest; -import jdk.jpackage.test.FileAssociations; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import jdk.internal.util.OperatingSystem; import jdk.jpackage.test.AdditionalLauncher; -import jdk.jpackage.test.TKit; +import jdk.jpackage.test.Annotations.Parameter; +import jdk.jpackage.test.Annotations.ParameterSupplier; import jdk.jpackage.test.Annotations.Test; +import jdk.jpackage.test.FileAssociations; +import jdk.jpackage.test.JPackageCommand; +import jdk.jpackage.test.LauncherShortcut; +import jdk.jpackage.test.LauncherShortcut.InvokeShortcutSpec; +import jdk.jpackage.test.LauncherShortcut.StartupDirectory; +import jdk.jpackage.test.LauncherVerifier.Action; +import jdk.jpackage.test.LinuxHelper; +import jdk.jpackage.test.PackageTest; +import jdk.jpackage.test.RunnablePackageTest; +import jdk.jpackage.test.TKit; +import jdk.jpackage.test.WinShortcutVerifier; /** * Test --add-launcher parameter with shortcuts (platform permitting). @@ -44,9 +63,23 @@ import jdk.jpackage.test.Annotations.Test; * @key jpackagePlatformPackage * @library /test/jdk/tools/jpackage/helpers * @build jdk.jpackage.test.* + * @requires (jpackage.test.SQETest != null) * @compile -Xlint:all -Werror AddLShortcutTest.java * @run main/othervm/timeout=540 -Xmx512m * jdk.jpackage.test.Main + * --jpt-run=AddLShortcutTest.test + */ + +/* + * @test + * @summary jpackage with --add-launcher + * @key jpackagePlatformPackage + * @library /test/jdk/tools/jpackage/helpers + * @build jdk.jpackage.test.* + * @requires (jpackage.test.SQETest == null) + * @compile -Xlint:all -Werror AddLShortcutTest.java + * @run main/othervm/timeout=1080 -Xmx512m + * jdk.jpackage.test.Main * --jpt-run=AddLShortcutTest */ @@ -107,6 +140,287 @@ public class AddLShortcutTest { packageTest.run(); } + @Test(ifNotOS = OperatingSystem.MACOS) + @ParameterSupplier(ifOS = OperatingSystem.LINUX, value = "testShortcutStartupDirectoryLinux") + @ParameterSupplier(ifOS = OperatingSystem.WINDOWS, value = "testShortcutStartupDirectoryWindows") + public void testStartupDirectory(LauncherShortcutStartupDirectoryConfig... cfgs) { + + var test = new PackageTest().addInitializer(cmd -> { + cmd.setArgumentValue("--name", "AddLShortcutDirTest"); + }).addInitializer(JPackageCommand::setFakeRuntime).addHelloAppInitializer(null); + + test.addInitializer(cfgs[0]::applyToMainLauncher); + for (var i = 1; i != cfgs.length; ++i) { + var al = new AdditionalLauncher("launcher-" + i); + cfgs[i].applyToAdditionalLauncher(al); + al.withoutVerifyActions(Action.EXECUTE_LAUNCHER).applyTo(test); + } + + test.run(RunnablePackageTest.Action.CREATE_AND_UNPACK); + } + + @Test(ifNotOS = OperatingSystem.MACOS) + @ParameterSupplier(ifOS = OperatingSystem.LINUX, value = "testShortcutStartupDirectoryLinux") + @ParameterSupplier(ifOS = OperatingSystem.WINDOWS, value = "testShortcutStartupDirectoryWindows") + public void testStartupDirectory2(LauncherShortcutStartupDirectoryConfig... cfgs) { + + // + // Launcher shortcuts in the predefined app image. + // + // Shortcut configuration for the main launcher is not supported when building an app image. + // However, shortcut configuration for additional launchers is supported. + // The test configures shortcuts for additional launchers in the app image building jpackage command + // and applies shortcut configuration to the main launcher in the native packaging jpackage command. + // + + Path[] predefinedAppImage = new Path[1]; + + new PackageTest().addRunOnceInitializer(() -> { + var cmd = JPackageCommand.helloAppImage() + .setArgumentValue("--name", "foo") + .setFakeRuntime(); + + for (var i = 1; i != cfgs.length; ++i) { + var al = new AdditionalLauncher("launcher-" + i); + cfgs[i].applyToAdditionalLauncher(al); + al.withoutVerifyActions(Action.EXECUTE_LAUNCHER).applyTo(cmd); + } + + cmd.execute(); + + predefinedAppImage[0] = cmd.outputBundle(); + }).addInitializer(cmd -> { + cmd.removeArgumentWithValue("--input"); + cmd.setArgumentValue("--name", "AddLShortcutDir2Test"); + cmd.addArguments("--app-image", predefinedAppImage[0]); + cfgs[0].applyToMainLauncher(cmd); + }).run(RunnablePackageTest.Action.CREATE_AND_UNPACK); + } + + public static Collection testShortcutStartupDirectoryLinux() { + return testShortcutStartupDirectory(LauncherShortcut.LINUX_SHORTCUT); + } + + public static Collection testShortcutStartupDirectoryWindows() { + return testShortcutStartupDirectory(LauncherShortcut.WIN_DESKTOP_SHORTCUT, LauncherShortcut.WIN_START_MENU_SHORTCUT); + } + + @Test(ifNotOS = OperatingSystem.MACOS) + @Parameter(value = "DEFAULT") + public void testInvokeShortcuts(StartupDirectory startupDirectory) { + + var testApp = TKit.TEST_SRC_ROOT.resolve("apps/PrintEnv.java"); + + var name = "AddLShortcutRunTest"; + + var test = new PackageTest().addInitializer(cmd -> { + cmd.setArgumentValue("--name", name); + }).addInitializer(cmd -> { + cmd.addArguments("--arguments", "--print-workdir"); + }).addInitializer(JPackageCommand::ignoreFakeRuntime).addHelloAppInitializer(testApp + "*Hello"); + + var shortcutStartupDirectoryVerifier = new ShortcutStartupDirectoryVerifier(name, "a"); + + shortcutStartupDirectoryVerifier.applyTo(test, startupDirectory); + + test.addInstallVerifier(cmd -> { + if (!cmd.isPackageUnpacked("Not invoking launcher shortcuts")) { + Collection invokeShortcutSpecs; + if (TKit.isLinux()) { + invokeShortcutSpecs = LinuxHelper.getInvokeShortcutSpecs(cmd); + } else if (TKit.isWindows()) { + invokeShortcutSpecs = WinShortcutVerifier.getInvokeShortcutSpecs(cmd); + } else { + throw new UnsupportedOperationException(); + } + shortcutStartupDirectoryVerifier.verify(invokeShortcutSpecs); + } + }); + + test.run(); + } + + + private record ShortcutStartupDirectoryVerifier(String packageName, String launcherName) { + ShortcutStartupDirectoryVerifier { + Objects.requireNonNull(packageName); + Objects.requireNonNull(launcherName); + } + + void applyTo(PackageTest test, StartupDirectory startupDirectory) { + var al = new AdditionalLauncher(launcherName); + al.setShortcut(shortcut(), Objects.requireNonNull(startupDirectory)); + al.addJavaOptions(String.format("-Djpackage.test.appOutput=${%s}/%s", + outputDirVarName(), expectedOutputFilename())); + al.withoutVerifyActions(Action.EXECUTE_LAUNCHER).applyTo(test); + } + + void verify(Collection invokeShortcutSpecs) throws IOException { + + TKit.trace(String.format("Verify shortcut [%s]", launcherName)); + + var expectedOutputFile = Path.of(System.getenv(outputDirVarName())).resolve(expectedOutputFilename()); + + TKit.deleteIfExists(expectedOutputFile); + + var invokeShortcutSpec = invokeShortcutSpecs.stream().filter(v -> { + return launcherName.equals(v.launcherName()); + }).findAny().orElseThrow(); + + invokeShortcutSpec.execute(); + + // On Linux, "gtk-launch" is used to launch a .desktop file. It is async and there is no + // way to make it wait for exit of a process it triggers. + TKit.waitForFileCreated(expectedOutputFile, Duration.ofSeconds(10), Duration.ofSeconds(3)); + + TKit.assertFileExists(expectedOutputFile); + var actualStr = Files.readAllLines(expectedOutputFile).getFirst(); + + var outputPrefix = "$CD="; + + TKit.assertTrue(actualStr.startsWith(outputPrefix), "Check output starts with '" + outputPrefix+ "' string"); + + invokeShortcutSpec.expectedWorkDirectory().ifPresent(expectedWorkDirectory -> { + TKit.assertEquals( + expectedWorkDirectory, + Path.of(actualStr.substring(outputPrefix.length())), + String.format("Check work directory of %s of launcher [%s]", + invokeShortcutSpec.shortcut().propertyName(), + invokeShortcutSpec.launcherName())); + }); + } + + private String expectedOutputFilename() { + return String.format("%s-%s.out", packageName, launcherName); + } + + private String outputDirVarName() { + if (TKit.isLinux()) { + return "HOME"; + } else if (TKit.isWindows()) { + return "LOCALAPPDATA"; + } else { + throw new UnsupportedOperationException(); + } + } + + private LauncherShortcut shortcut() { + if (TKit.isLinux()) { + return LauncherShortcut.LINUX_SHORTCUT; + } else if (TKit.isWindows()) { + return LauncherShortcut.WIN_DESKTOP_SHORTCUT; + } else { + throw new UnsupportedOperationException(); + } + } + } + + + private static Collection testShortcutStartupDirectory(LauncherShortcut... shortcuts) { + List> items = new ArrayList<>(); + + for (var shortcut : shortcuts) { + List mainLauncherVariants = new ArrayList<>(); + for (var valueSetter : StartupDirectoryValueSetter.MAIN_LAUNCHER_VALUES) { + mainLauncherVariants.add(new LauncherShortcutStartupDirectoryConfig(shortcut, valueSetter)); + } + mainLauncherVariants.stream().map(List::of).forEach(items::add); + mainLauncherVariants.add(new LauncherShortcutStartupDirectoryConfig(shortcut)); + + List addLauncherVariants = new ArrayList<>(); + addLauncherVariants.add(new LauncherShortcutStartupDirectoryConfig(shortcut)); + for (var valueSetter : StartupDirectoryValueSetter.ADD_LAUNCHER_VALUES) { + addLauncherVariants.add(new LauncherShortcutStartupDirectoryConfig(shortcut, valueSetter)); + } + + for (var mainLauncherVariant : mainLauncherVariants) { + for (var addLauncherVariant : addLauncherVariants) { + if (mainLauncherVariant.valueSetter().isPresent() || addLauncherVariant.valueSetter().isPresent()) { + items.add(List.of(mainLauncherVariant, addLauncherVariant)); + } + } + } + } + + return items.stream().map(List::toArray).toList(); + } + + + private enum StartupDirectoryValueSetter { + DEFAULT(""), + TRUE("true"), + FALSE("false"), + ; + + StartupDirectoryValueSetter(String value) { + this.value = Objects.requireNonNull(value); + } + + void applyToMainLauncher(LauncherShortcut shortcut, JPackageCommand cmd) { + switch (this) { + case TRUE, FALSE -> { + throw new UnsupportedOperationException(); + } + case DEFAULT -> { + cmd.addArgument(shortcut.optionName()); + } + default -> { + cmd.addArguments(shortcut.optionName(), value); + } + } + } + + void applyToAdditionalLauncher(LauncherShortcut shortcut, AdditionalLauncher addLauncher) { + addLauncher.setProperty(shortcut.propertyName(), value); + } + + private final String value; + + static final List MAIN_LAUNCHER_VALUES = List.of( + StartupDirectoryValueSetter.DEFAULT + ); + + static final List ADD_LAUNCHER_VALUES = List.of( + StartupDirectoryValueSetter.TRUE, + StartupDirectoryValueSetter.FALSE + ); + } + + + record LauncherShortcutStartupDirectoryConfig(LauncherShortcut shortcut, Optional valueSetter) { + + LauncherShortcutStartupDirectoryConfig { + Objects.requireNonNull(shortcut); + Objects.requireNonNull(valueSetter); + } + + LauncherShortcutStartupDirectoryConfig(LauncherShortcut shortcut, StartupDirectoryValueSetter valueSetter) { + this(shortcut, Optional.of(valueSetter)); + } + + LauncherShortcutStartupDirectoryConfig(LauncherShortcut shortcut) { + this(shortcut, Optional.empty()); + } + + void applyToMainLauncher(JPackageCommand target) { + valueSetter.ifPresent(valueSetter -> { + valueSetter.applyToMainLauncher(shortcut, target); + }); + } + + void applyToAdditionalLauncher(AdditionalLauncher target) { + valueSetter.ifPresent(valueSetter -> { + valueSetter.applyToAdditionalLauncher(shortcut, target); + }); + } + + @Override + public String toString() { + return shortcut + "=" + valueSetter.map(Object::toString).orElse(""); + } + } + + private static final Path GOLDEN_ICON = TKit.TEST_SRC_ROOT.resolve(Path.of( "resources", "icon" + TKit.ICON_SUFFIX)); } diff --git a/test/jdk/tools/jpackage/share/AddLauncherTest.java b/test/jdk/tools/jpackage/share/AddLauncherTest.java index 5c21be71258..8d5f0de28f2 100644 --- a/test/jdk/tools/jpackage/share/AddLauncherTest.java +++ b/test/jdk/tools/jpackage/share/AddLauncherTest.java @@ -89,17 +89,17 @@ public class AddLauncherTest { new AdditionalLauncher("Baz2") .setDefaultArguments() - .addRawProperties(Map.entry("description", "Baz2 Description")) + .setProperty("description", "Baz2 Description") .applyTo(packageTest); new AdditionalLauncher("foo") .setDefaultArguments("yep!") - .addRawProperties(Map.entry("description", "foo Description")) + .setProperty("description", "foo Description") .applyTo(packageTest); new AdditionalLauncher("Bar") .setDefaultArguments("one", "two", "three") - .addRawProperties(Map.entry("description", "Bar Description")) + .setProperty("description", "Bar Description") .setIcon(GOLDEN_ICON) .applyTo(packageTest); @@ -194,8 +194,8 @@ public class AddLauncherTest { .toString(); new AdditionalLauncher("ModularAppLauncher") - .addRawProperties(Map.entry("module", expectedMod)) - .addRawProperties(Map.entry("main-jar", "")) + .setProperty("module", expectedMod) + .setProperty("main-jar", "") .applyTo(cmd); new AdditionalLauncher("NonModularAppLauncher") @@ -204,8 +204,8 @@ public class AddLauncherTest { .setPersistenceHandler((path, properties) -> TKit.createTextFile(path, properties.stream().map(entry -> String.join(" ", entry.getKey(), entry.getValue())))) - .addRawProperties(Map.entry("main-class", nonModularAppDesc.className())) - .addRawProperties(Map.entry("main-jar", nonModularAppDesc.jarFileName())) + .setProperty("main-class", nonModularAppDesc.className()) + .setProperty("main-jar", nonModularAppDesc.jarFileName()) .applyTo(cmd); cmd.executeAndAssertHelloAppImageCreated(); diff --git a/test/jdk/tools/jpackage/share/PerUserCfgTest.java b/test/jdk/tools/jpackage/share/PerUserCfgTest.java index 080df1f959d..d2f368cd824 100644 --- a/test/jdk/tools/jpackage/share/PerUserCfgTest.java +++ b/test/jdk/tools/jpackage/share/PerUserCfgTest.java @@ -27,13 +27,14 @@ import java.nio.file.Path; import java.util.List; import java.util.Objects; import java.util.Optional; -import jdk.jpackage.test.AdditionalLauncher; -import jdk.jpackage.test.PackageTest; -import jdk.jpackage.test.Annotations.Test; import jdk.jpackage.internal.util.function.ThrowingConsumer; +import jdk.jpackage.test.AdditionalLauncher; +import jdk.jpackage.test.Annotations.Test; import jdk.jpackage.test.HelloApp; import jdk.jpackage.test.JPackageCommand; +import jdk.jpackage.test.LauncherVerifier.Action; import jdk.jpackage.test.LinuxHelper; +import jdk.jpackage.test.PackageTest; import jdk.jpackage.test.PackageType; import jdk.jpackage.test.TKit; @@ -65,7 +66,7 @@ public class PerUserCfgTest { cfgCmd.execute(); - new PackageTest().configureHelloApp().addInstallVerifier(cmd -> { + new PackageTest().addHelloAppInitializer(null).addInstallVerifier(cmd -> { if (cmd.isPackageUnpacked("Not running per-user configuration tests")) { return; } @@ -144,10 +145,7 @@ public class PerUserCfgTest { } private static void addLauncher(JPackageCommand cmd, String name) { - new AdditionalLauncher(name) { - @Override - protected void verify(JPackageCommand cmd) {} - }.setDefaultArguments(name).applyTo(cmd); + new AdditionalLauncher(name).setDefaultArguments(name).withoutVerifyActions(Action.EXECUTE_LAUNCHER).applyTo(cmd); } private static Path getUserHomeDir() { From 078d0d4968e26bb7a15417f1c4e891869c69dc6c Mon Sep 17 00:00:00 2001 From: David Holmes Date: Thu, 7 Aug 2025 04:37:21 +0000 Subject: [PATCH 043/580] 8364235: Fix for JDK-8361447 breaks the alignment requirements for GuardedMemory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johan Sjölen Reviewed-by: dcubed, jsjolen, aboldtch --- src/hotspot/share/memory/guardedMemory.hpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/hotspot/share/memory/guardedMemory.hpp b/src/hotspot/share/memory/guardedMemory.hpp index a3bbf48b0cd..2b6d34e8e0a 100644 --- a/src/hotspot/share/memory/guardedMemory.hpp +++ b/src/hotspot/share/memory/guardedMemory.hpp @@ -42,9 +42,10 @@ * |Offset | Content | Description | * |------------------------------------------------------------ * |base_addr | 0xABABABABABABABAB | Head guard | - * |+16 | | User data size | - * |+sizeof(uintptr_t) | | Tag word | - * |+sizeof(uintptr_t) | | Tag word | + * |+GUARD_SIZE | | User data size | + * |+sizeof(size_t) | | Tag word | + * |+sizeof(void*) | | Tag word | + * |+sizeof(void*) | | Padding | * |+sizeof(void*) | 0xF1 ( | User data | * |+user_size | 0xABABABABABABABAB | Tail guard | * ------------------------------------------------------------- @@ -52,6 +53,8 @@ * Where: * - guard padding uses "badResourceValue" (0xAB) * - tag word and tag2 word are general purpose + * - padding is inserted as-needed by the compiler to ensure + * the user data is aligned on a 16-byte boundary * - user data * -- initially padded with "uninitBlockPad" (0xF1), * -- to "freeBlockPad" (0xBA), when freed @@ -132,12 +135,15 @@ protected: /** * Header guard and size + * + * NB: the size and placement of the GuardHeader must be such that the + * user-ptr is maximally aligned i.e. 16-byte alignment for x86 ABI for + * stack alignment and use of vector (xmm) instructions. We use alignas + * to achieve this. */ - class GuardHeader : Guard { + class alignas(16) GuardHeader : Guard { friend class GuardedMemory; protected: - // Take care in modifying fields here, will effect alignment - // e.g. x86 ABI 16 byte stack alignment union { uintptr_t __unused_full_word1; size_t _user_size; From 487cc3c5be769d15d61cb950137d52ba0eb982b5 Mon Sep 17 00:00:00 2001 From: Johannes Bechberger Date: Thu, 7 Aug 2025 07:52:48 +0000 Subject: [PATCH 044/580] 8359690: New test TestCPUTimeSampleThrottling still fails intermittently Reviewed-by: mbaesken --- .../TestCPUTimeSampleThrottling.java | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleThrottling.java b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleThrottling.java index b0b9d6d2be7..ef2c73514a3 100644 --- a/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleThrottling.java +++ b/test/jdk/jdk/jfr/event/profiling/TestCPUTimeSampleThrottling.java @@ -23,6 +23,7 @@ package jdk.jfr.event.profiling; import java.lang.management.ManagementFactory; +import java.lang.management.ThreadMXBean; import java.time.Duration; import java.time.Instant; import java.util.List; @@ -71,7 +72,7 @@ public class TestCPUTimeSampleThrottling { } /** - * Counting the events that are emitted for a given throttle in a given time. + * Counting the events that are emitted for a given throttle in a given (CPU) time. *

* The result is wall-clock independent; it only records the CPU-time and the number of * emitted events. The result, therefore, does not depend on the load of the machine. @@ -83,15 +84,9 @@ public class TestCPUTimeSampleThrottling { recording.enable(EventNames.CPUTimeSample) .with("throttle", throttle); - var bean = ManagementFactory.getThreadMXBean(); - recording.start(); - long startThreadCpuTime = bean.getCurrentThreadCpuTime(); - - wasteCPU(timeMs); - - long spendCPUTime = bean.getCurrentThreadCpuTime() - startThreadCpuTime; + long spendCPUTime = wasteCPU(timeMs); recording.stop(); @@ -99,19 +94,20 @@ public class TestCPUTimeSampleThrottling { .filter(e -> e.getThread().getJavaName() .equals(Thread.currentThread().getName())) .count(); - return new EventCount(eventCount, spendCPUTime / 1_000_000_000f); } } - private static void wasteCPU(int durationMs) { - long start = System.currentTimeMillis(); + private static long wasteCPU(int durationMs) { + ThreadMXBean bean = ManagementFactory.getThreadMXBean(); + long start = bean.getCurrentThreadCpuTime(); double i = 0; - while (System.currentTimeMillis() - start < durationMs) { + while (bean.getCurrentThreadCpuTime() - start < durationMs * 1_000_000) { for (int j = 0; j < 100000; j++) { i = Math.sqrt(i * Math.pow(Math.sqrt(Math.random()), Math.random())); } } + return bean.getCurrentThreadCpuTime() - start; } } From c56fb0b6eff7d3f36bc65f300b784e0dd73c563e Mon Sep 17 00:00:00 2001 From: Thomas Schatzl Date: Thu, 7 Aug 2025 08:40:42 +0000 Subject: [PATCH 045/580] 8364503: gc/g1/TestCodeCacheUnloadDuringConcCycle.java fails because of race printing to stdout Reviewed-by: ayang, dholmes --- .../jtreg/gc/g1/TestCodeCacheUnloadDuringConcCycle.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/hotspot/jtreg/gc/g1/TestCodeCacheUnloadDuringConcCycle.java b/test/hotspot/jtreg/gc/g1/TestCodeCacheUnloadDuringConcCycle.java index 94f65a4328f..a4fadc185d2 100644 --- a/test/hotspot/jtreg/gc/g1/TestCodeCacheUnloadDuringConcCycle.java +++ b/test/hotspot/jtreg/gc/g1/TestCodeCacheUnloadDuringConcCycle.java @@ -147,6 +147,7 @@ class TestCodeCacheUnloadDuringConcCycleRunner { } public static void main(String[] args) throws Exception { + System.out.println("Running to breakpoint: " + args[0]); try { WB.concurrentGCAcquireControl(); WB.concurrentGCRunTo(args[0]); @@ -157,9 +158,12 @@ class TestCodeCacheUnloadDuringConcCycleRunner { WB.concurrentGCRunToIdle(); } finally { + // Make sure that the marker we use to find the expected log message is printed + // before we release whitebox control, i.e. before the expected garbage collection + // can start. + System.out.println(TestCodeCacheUnloadDuringConcCycle.AFTER_FIRST_CYCLE_MARKER); WB.concurrentGCReleaseControl(); } - System.out.println(TestCodeCacheUnloadDuringConcCycle.AFTER_FIRST_CYCLE_MARKER); Thread.sleep(1000); triggerCodeCacheGC(); } From 8d73fe91bccd1da53424b9f8a52d9efafabeb243 Mon Sep 17 00:00:00 2001 From: Jeremy Wood Date: Thu, 7 Aug 2025 10:21:54 +0000 Subject: [PATCH 046/580] 8358813: JPasswordField identifies spaces in password via delete shortcuts Reviewed-by: aivanov, dnguyen --- .../com/apple/laf/AquaKeyBindings.java | 3 + .../PasswordFieldInputMapWordTest.java | 118 ++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 test/jdk/javax/swing/JPasswordField/PasswordFieldInputMapWordTest.java diff --git a/src/java.desktop/macosx/classes/com/apple/laf/AquaKeyBindings.java b/src/java.desktop/macosx/classes/com/apple/laf/AquaKeyBindings.java index c6224c63543..8d160230844 100644 --- a/src/java.desktop/macosx/classes/com/apple/laf/AquaKeyBindings.java +++ b/src/java.desktop/macosx/classes/com/apple/laf/AquaKeyBindings.java @@ -157,6 +157,9 @@ public final class AquaKeyBindings { "shift alt KP_LEFT", null, "shift alt RIGHT", null, "shift alt KP_RIGHT", null, + "alt BACK_SPACE", null, + "ctrl W", null, + "alt DELETE", null, })); } diff --git a/test/jdk/javax/swing/JPasswordField/PasswordFieldInputMapWordTest.java b/test/jdk/javax/swing/JPasswordField/PasswordFieldInputMapWordTest.java new file mode 100644 index 00000000000..c49e9f7083c --- /dev/null +++ b/test/jdk/javax/swing/JPasswordField/PasswordFieldInputMapWordTest.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. + */ + +/* + * @test + * @key headful + * @bug 8358813 + * @summary Password fields' InputMap should not include any word-related action. + * + * @run main PasswordFieldInputMapWordTest + */ + +import java.util.Collection; +import java.util.Set; + +import javax.swing.InputMap; +import javax.swing.JComponent; +import javax.swing.JPasswordField; +import javax.swing.KeyStroke; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.UnsupportedLookAndFeelException; +import javax.swing.text.DefaultEditorKit; + +public class PasswordFieldInputMapWordTest { + public static void main(String[] args) throws Exception { + for (UIManager.LookAndFeelInfo laf : + UIManager.getInstalledLookAndFeels()) { + System.out.println("Testing LAF: " + laf.getClassName()); + SwingUtilities.invokeAndWait(() -> { + if (setLookAndFeel(laf)) { + runTest(); + } + }); + } + } + + private static boolean setLookAndFeel(UIManager.LookAndFeelInfo laf) { + try { + UIManager.setLookAndFeel(laf.getClassName()); + return true; + } catch (UnsupportedLookAndFeelException e) { + System.err.println("Skipping unsupported look and feel:"); + e.printStackTrace(); + return false; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + static int[] inputMapConditions = new int[] { + JComponent.WHEN_IN_FOCUSED_WINDOW, + JComponent.WHEN_FOCUSED, + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT + }; + + /** + * These are all the actions with "word" in their field name. + */ + static Collection wordActions = Set.of( + DefaultEditorKit.deleteNextWordAction, + DefaultEditorKit.deletePrevWordAction, + DefaultEditorKit.beginWordAction, + DefaultEditorKit.endWordAction, + DefaultEditorKit.selectionBeginWordAction, + DefaultEditorKit.selectionEndWordAction, + DefaultEditorKit.previousWordAction, + DefaultEditorKit.nextWordAction, + DefaultEditorKit.selectionPreviousWordAction, + DefaultEditorKit.selectionNextWordAction + ); + + private static void runTest() { + JPasswordField field = new JPasswordField(); + + boolean testPassed = true; + for (int condition : inputMapConditions) { + InputMap inputMap = field.getInputMap(condition); + if (inputMap.allKeys() == null) { + continue; + } + for (KeyStroke keyStroke : inputMap.allKeys()) { + Object actionBinding = inputMap.get(keyStroke); + if (wordActions.contains(actionBinding)) { + if (testPassed) { + System.err.println("The following inputs/actions should not be available in a JPasswordField:"); + } + System.err.println(inputMap.get(keyStroke) + " (try typing " + keyStroke + ")"); + testPassed = false; + } + } + } + + if (!testPassed) { + throw new RuntimeException("One or more input/action binding was observed for a JPasswordField."); + } + } +} From bc3d86564042208cee5119abe11905e747a5ef4c Mon Sep 17 00:00:00 2001 From: Ashutosh Mehra Date: Thu, 7 Aug 2025 13:26:33 +0000 Subject: [PATCH 047/580] 8364128: Improve gathering of cpu feature names using stringStream MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johan Sjölen Reviewed-by: kvn, jsjolen --- .../cpu/aarch64/vm_version_aarch64.cpp | 58 ++++++++++++------- .../cpu/aarch64/vm_version_aarch64.hpp | 25 +++++++- src/hotspot/cpu/x86/vm_version_x86.cpp | 42 +++++++------- src/hotspot/cpu/x86/vm_version_x86.hpp | 4 +- .../bsd_aarch64/vm_version_bsd_aarch64.cpp | 18 +++--- .../vm_version_linux_aarch64.cpp | 42 ++++++++------ .../share/runtime/abstract_vm_version.cpp | 13 ----- .../share/runtime/abstract_vm_version.hpp | 3 - src/hotspot/share/utilities/ostream.hpp | 14 +++++ 9 files changed, 130 insertions(+), 89 deletions(-) diff --git a/src/hotspot/cpu/aarch64/vm_version_aarch64.cpp b/src/hotspot/cpu/aarch64/vm_version_aarch64.cpp index 9321dd0542e..24c77174711 100644 --- a/src/hotspot/cpu/aarch64/vm_version_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/vm_version_aarch64.cpp @@ -32,6 +32,7 @@ #include "runtime/vm_version.hpp" #include "utilities/formatBuffer.hpp" #include "utilities/macros.hpp" +#include "utilities/ostream.hpp" int VM_Version::_cpu; int VM_Version::_model; @@ -50,6 +51,8 @@ uintptr_t VM_Version::_pac_mask; SpinWait VM_Version::_spin_wait; +const char* VM_Version::_features_names[MAX_CPU_FEATURES] = { nullptr }; + static SpinWait get_spin_wait_desc() { SpinWait spin_wait(OnSpinWaitInst, OnSpinWaitInstCount); if (spin_wait.inst() == SpinWait::SB && !VM_Version::supports_sb()) { @@ -60,6 +63,11 @@ static SpinWait get_spin_wait_desc() { } void VM_Version::initialize() { +#define SET_CPU_FEATURE_NAME(id, name, bit) \ + _features_names[bit] = XSTR(name); + CPU_FEATURE_FLAGS(SET_CPU_FEATURE_NAME) +#undef SET_CPU_FEATURE_NAME + _supports_atomic_getset4 = true; _supports_atomic_getadd4 = true; _supports_atomic_getset8 = true; @@ -194,7 +202,7 @@ void VM_Version::initialize() { // Cortex A53 if (_cpu == CPU_ARM && model_is(0xd03)) { - _features |= CPU_A53MAC; + set_feature(CPU_A53MAC); if (FLAG_IS_DEFAULT(UseSIMDForArrayEquals)) { FLAG_SET_DEFAULT(UseSIMDForArrayEquals, false); } @@ -234,7 +242,7 @@ void VM_Version::initialize() { } } - if (_features & (CPU_FP | CPU_ASIMD)) { + if (supports_feature(CPU_FP) || supports_feature(CPU_ASIMD)) { if (FLAG_IS_DEFAULT(UseSignumIntrinsic)) { FLAG_SET_DEFAULT(UseSignumIntrinsic, true); } @@ -397,7 +405,7 @@ void VM_Version::initialize() { FLAG_SET_DEFAULT(UseGHASHIntrinsics, false); } - if (_features & CPU_ASIMD) { + if (supports_feature(CPU_ASIMD)) { if (FLAG_IS_DEFAULT(UseChaCha20Intrinsics)) { UseChaCha20Intrinsics = true; } @@ -408,7 +416,7 @@ void VM_Version::initialize() { FLAG_SET_DEFAULT(UseChaCha20Intrinsics, false); } - if (_features & CPU_ASIMD) { + if (supports_feature(CPU_ASIMD)) { if (FLAG_IS_DEFAULT(UseKyberIntrinsics)) { UseKyberIntrinsics = true; } @@ -419,7 +427,7 @@ void VM_Version::initialize() { FLAG_SET_DEFAULT(UseKyberIntrinsics, false); } - if (_features & CPU_ASIMD) { + if (supports_feature(CPU_ASIMD)) { if (FLAG_IS_DEFAULT(UseDilithiumIntrinsics)) { UseDilithiumIntrinsics = true; } @@ -620,32 +628,38 @@ void VM_Version::initialize() { // Sync SVE related CPU features with flags if (UseSVE < 2) { - _features &= ~CPU_SVE2; - _features &= ~CPU_SVEBITPERM; + clear_feature(CPU_SVE2); + clear_feature(CPU_SVEBITPERM); } if (UseSVE < 1) { - _features &= ~CPU_SVE; + clear_feature(CPU_SVE); } // Construct the "features" string - char buf[512]; - int buf_used_len = os::snprintf_checked(buf, sizeof(buf), "0x%02x:0x%x:0x%03x:%d", _cpu, _variant, _model, _revision); + stringStream ss(512); + ss.print("0x%02x:0x%x:0x%03x:%d", _cpu, _variant, _model, _revision); if (_model2) { - os::snprintf_checked(buf + buf_used_len, sizeof(buf) - buf_used_len, "(0x%03x)", _model2); + ss.print("(0x%03x)", _model2); } - size_t features_offset = strnlen(buf, sizeof(buf)); -#define ADD_FEATURE_IF_SUPPORTED(id, name, bit) \ - do { \ - if (VM_Version::supports_##name()) strcat(buf, ", " #name); \ - } while(0); - CPU_FEATURE_FLAGS(ADD_FEATURE_IF_SUPPORTED) -#undef ADD_FEATURE_IF_SUPPORTED + ss.print(", "); + int features_offset = (int)ss.size(); + insert_features_names(_features, ss); - _cpu_info_string = os::strdup(buf); + _cpu_info_string = ss.as_string(true); + _features_string = _cpu_info_string + features_offset; +} - _features_string = extract_features_string(_cpu_info_string, - strnlen(_cpu_info_string, sizeof(buf)), - features_offset); +void VM_Version::insert_features_names(uint64_t features, stringStream& ss) { + int i = 0; + ss.join([&]() { + while (i < MAX_CPU_FEATURES) { + if (supports_feature((VM_Version::Feature_Flag)i)) { + return _features_names[i++]; + } + i += 1; + } + return (const char*)nullptr; + }, ", "); } #if defined(LINUX) diff --git a/src/hotspot/cpu/aarch64/vm_version_aarch64.hpp b/src/hotspot/cpu/aarch64/vm_version_aarch64.hpp index 99450d3dde1..5a8642a285a 100644 --- a/src/hotspot/cpu/aarch64/vm_version_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/vm_version_aarch64.hpp @@ -30,6 +30,10 @@ #include "runtime/abstract_vm_version.hpp" #include "utilities/sizes.hpp" +class stringStream; + +#define BIT_MASK(flag) (1ULL<<(flag)) + class VM_Version : public Abstract_VM_Version { friend class VMStructs; friend class JVMCIVMStructs; @@ -66,6 +70,8 @@ public: static void initialize(); static void check_virtualizations(); + static void insert_features_names(uint64_t features, stringStream& ss); + static void print_platform_virtualization_info(outputStream*); // Asserts @@ -139,17 +145,32 @@ enum Ampere_CPU_Model { decl(A53MAC, a53mac, 31) enum Feature_Flag { -#define DECLARE_CPU_FEATURE_FLAG(id, name, bit) CPU_##id = (1 << bit), +#define DECLARE_CPU_FEATURE_FLAG(id, name, bit) CPU_##id = bit, CPU_FEATURE_FLAGS(DECLARE_CPU_FEATURE_FLAG) #undef DECLARE_CPU_FEATURE_FLAG + MAX_CPU_FEATURES }; + STATIC_ASSERT(sizeof(_features) * BitsPerByte >= MAX_CPU_FEATURES); + + static const char* _features_names[MAX_CPU_FEATURES]; + // Feature identification #define CPU_FEATURE_DETECTION(id, name, bit) \ - static bool supports_##name() { return (_features & CPU_##id) != 0; }; + static bool supports_##name() { return supports_feature(CPU_##id); } CPU_FEATURE_FLAGS(CPU_FEATURE_DETECTION) #undef CPU_FEATURE_DETECTION + static void set_feature(Feature_Flag flag) { + _features |= BIT_MASK(flag); + } + static void clear_feature(Feature_Flag flag) { + _features &= (~BIT_MASK(flag)); + } + static bool supports_feature(Feature_Flag flag) { + return (_features & BIT_MASK(flag)) != 0; + } + static int cpu_family() { return _cpu; } static int cpu_model() { return _model; } static int cpu_model2() { return _model2; } diff --git a/src/hotspot/cpu/x86/vm_version_x86.cpp b/src/hotspot/cpu/x86/vm_version_x86.cpp index 001cb6b0255..b81c200c440 100644 --- a/src/hotspot/cpu/x86/vm_version_x86.cpp +++ b/src/hotspot/cpu/x86/vm_version_x86.cpp @@ -38,6 +38,7 @@ #include "runtime/stubCodeGenerator.hpp" #include "runtime/vm_version.hpp" #include "utilities/checkedCast.hpp" +#include "utilities/ostream.hpp" #include "utilities/powerOfTwo.hpp" #include "utilities/virtualizationSupport.hpp" @@ -1097,21 +1098,16 @@ void VM_Version::get_processor_features() { } } - char buf[2048]; - size_t cpu_info_size = jio_snprintf( - buf, sizeof(buf), - "(%u cores per cpu, %u threads per core) family %d model %d stepping %d microcode 0x%x", - cores_per_cpu(), threads_per_core(), - cpu_family(), _model, _stepping, os::cpu_microcode_revision()); - assert(cpu_info_size > 0, "not enough temporary space allocated"); + stringStream ss(2048); + ss.print("(%u cores per cpu, %u threads per core) family %d model %d stepping %d microcode 0x%x", + cores_per_cpu(), threads_per_core(), + cpu_family(), _model, _stepping, os::cpu_microcode_revision()); + ss.print(", "); + int features_offset = (int)ss.size(); + insert_features_names(_features, ss); - insert_features_names(_features, buf + cpu_info_size, sizeof(buf) - cpu_info_size); - - _cpu_info_string = os::strdup(buf); - - _features_string = extract_features_string(_cpu_info_string, - strnlen(_cpu_info_string, sizeof(buf)), - cpu_info_size); + _cpu_info_string = ss.as_string(true); + _features_string = _cpu_info_string + features_offset; // Use AES instructions if available. if (supports_aes()) { @@ -3266,13 +3262,15 @@ bool VM_Version::is_intrinsic_supported(vmIntrinsicID id) { return true; } -void VM_Version::insert_features_names(VM_Version::VM_Features features, char* buf, size_t buflen) { - for (int i = 0; i < MAX_CPU_FEATURES; i++) { - if (features.supports_feature((VM_Version::Feature_Flag)i)) { - int res = jio_snprintf(buf, buflen, ", %s", _features_names[i]); - assert(res > 0, "not enough temporary space allocated"); - buf += res; - buflen -= res; +void VM_Version::insert_features_names(VM_Version::VM_Features features, stringStream& ss) { + int i = 0; + ss.join([&]() { + while (i < MAX_CPU_FEATURES) { + if (_features.supports_feature((VM_Version::Feature_Flag)i)) { + return _features_names[i++]; + } + i += 1; } - } + return (const char*)nullptr; + }, ", "); } diff --git a/src/hotspot/cpu/x86/vm_version_x86.hpp b/src/hotspot/cpu/x86/vm_version_x86.hpp index 3c8971e474b..6ee9f95fdf5 100644 --- a/src/hotspot/cpu/x86/vm_version_x86.hpp +++ b/src/hotspot/cpu/x86/vm_version_x86.hpp @@ -30,6 +30,8 @@ #include "utilities/macros.hpp" #include "utilities/sizes.hpp" +class stringStream; + class VM_Version : public Abstract_VM_Version { friend class VMStructs; friend class JVMCIVMStructs; @@ -922,7 +924,7 @@ public: static bool is_intel_tsc_synched_at_init(); - static void insert_features_names(VM_Version::VM_Features features, char* buf, size_t buflen); + static void insert_features_names(VM_Version::VM_Features features, stringStream& ss); // This checks if the JVM is potentially affected by an erratum on Intel CPUs (SKX102) // that causes unpredictable behaviour when jcc crosses 64 byte boundaries. Its microcode diff --git a/src/hotspot/os_cpu/bsd_aarch64/vm_version_bsd_aarch64.cpp b/src/hotspot/os_cpu/bsd_aarch64/vm_version_bsd_aarch64.cpp index b55c1cd27c8..ffa7e1d5d2b 100644 --- a/src/hotspot/os_cpu/bsd_aarch64/vm_version_bsd_aarch64.cpp +++ b/src/hotspot/os_cpu/bsd_aarch64/vm_version_bsd_aarch64.cpp @@ -67,7 +67,8 @@ void VM_Version::get_os_cpu_info() { // 2) ID_AA64PFR0_EL1 describes AdvSIMD always equals to FP field. // See the Arm ARM, section "ID_AA64PFR0_EL1, AArch64 Processor Feature // Register 0". - _features = CPU_FP | CPU_ASIMD; + set_feature(CPU_FP); + set_feature(CPU_ASIMD); // All Apple-darwin Arm processors have AES, PMULL, SHA1 and SHA2. // See https://github.com/apple-oss-distributions/xnu/blob/main/osfmk/arm/commpage/commpage.c#L412 @@ -75,25 +76,28 @@ void VM_Version::get_os_cpu_info() { // these four CPU features, e.g., "hw.optional.arm.FEAT_AES", but the // corresponding string names are not available before xnu-8019 version. // Hence, assertions are omitted considering backward compatibility. - _features |= CPU_AES | CPU_PMULL | CPU_SHA1 | CPU_SHA2; + set_feature(CPU_AES); + set_feature(CPU_PMULL); + set_feature(CPU_SHA1); + set_feature(CPU_SHA2); if (cpu_has("hw.optional.armv8_crc32")) { - _features |= CPU_CRC32; + set_feature(CPU_CRC32); } if (cpu_has("hw.optional.arm.FEAT_LSE") || cpu_has("hw.optional.armv8_1_atomics")) { - _features |= CPU_LSE; + set_feature(CPU_LSE); } if (cpu_has("hw.optional.arm.FEAT_SHA512") || cpu_has("hw.optional.armv8_2_sha512")) { - _features |= CPU_SHA512; + set_feature(CPU_SHA512); } if (cpu_has("hw.optional.arm.FEAT_SHA3") || cpu_has("hw.optional.armv8_2_sha3")) { - _features |= CPU_SHA3; + set_feature(CPU_SHA3); } if (cpu_has("hw.optional.arm.FEAT_SB")) { - _features |= CPU_SB; + set_feature(CPU_SB); } int cache_line_size; diff --git a/src/hotspot/os_cpu/linux_aarch64/vm_version_linux_aarch64.cpp b/src/hotspot/os_cpu/linux_aarch64/vm_version_linux_aarch64.cpp index a8edeefc885..1fe06dc640d 100644 --- a/src/hotspot/os_cpu/linux_aarch64/vm_version_linux_aarch64.cpp +++ b/src/hotspot/os_cpu/linux_aarch64/vm_version_linux_aarch64.cpp @@ -117,22 +117,22 @@ void VM_Version::get_os_cpu_info() { uint64_t auxv = getauxval(AT_HWCAP); uint64_t auxv2 = getauxval(AT_HWCAP2); - static_assert(CPU_FP == HWCAP_FP, "Flag CPU_FP must follow Linux HWCAP"); - static_assert(CPU_ASIMD == HWCAP_ASIMD, "Flag CPU_ASIMD must follow Linux HWCAP"); - static_assert(CPU_EVTSTRM == HWCAP_EVTSTRM, "Flag CPU_EVTSTRM must follow Linux HWCAP"); - static_assert(CPU_AES == HWCAP_AES, "Flag CPU_AES must follow Linux HWCAP"); - static_assert(CPU_PMULL == HWCAP_PMULL, "Flag CPU_PMULL must follow Linux HWCAP"); - static_assert(CPU_SHA1 == HWCAP_SHA1, "Flag CPU_SHA1 must follow Linux HWCAP"); - static_assert(CPU_SHA2 == HWCAP_SHA2, "Flag CPU_SHA2 must follow Linux HWCAP"); - static_assert(CPU_CRC32 == HWCAP_CRC32, "Flag CPU_CRC32 must follow Linux HWCAP"); - static_assert(CPU_LSE == HWCAP_ATOMICS, "Flag CPU_LSE must follow Linux HWCAP"); - static_assert(CPU_DCPOP == HWCAP_DCPOP, "Flag CPU_DCPOP must follow Linux HWCAP"); - static_assert(CPU_SHA3 == HWCAP_SHA3, "Flag CPU_SHA3 must follow Linux HWCAP"); - static_assert(CPU_SHA512 == HWCAP_SHA512, "Flag CPU_SHA512 must follow Linux HWCAP"); - static_assert(CPU_SVE == HWCAP_SVE, "Flag CPU_SVE must follow Linux HWCAP"); - static_assert(CPU_PACA == HWCAP_PACA, "Flag CPU_PACA must follow Linux HWCAP"); - static_assert(CPU_FPHP == HWCAP_FPHP, "Flag CPU_FPHP must follow Linux HWCAP"); - static_assert(CPU_ASIMDHP == HWCAP_ASIMDHP, "Flag CPU_ASIMDHP must follow Linux HWCAP"); + static_assert(BIT_MASK(CPU_FP) == HWCAP_FP, "Flag CPU_FP must follow Linux HWCAP"); + static_assert(BIT_MASK(CPU_ASIMD) == HWCAP_ASIMD, "Flag CPU_ASIMD must follow Linux HWCAP"); + static_assert(BIT_MASK(CPU_EVTSTRM) == HWCAP_EVTSTRM, "Flag CPU_EVTSTRM must follow Linux HWCAP"); + static_assert(BIT_MASK(CPU_AES) == HWCAP_AES, "Flag CPU_AES must follow Linux HWCAP"); + static_assert(BIT_MASK(CPU_PMULL) == HWCAP_PMULL, "Flag CPU_PMULL must follow Linux HWCAP"); + static_assert(BIT_MASK(CPU_SHA1) == HWCAP_SHA1, "Flag CPU_SHA1 must follow Linux HWCAP"); + static_assert(BIT_MASK(CPU_SHA2) == HWCAP_SHA2, "Flag CPU_SHA2 must follow Linux HWCAP"); + static_assert(BIT_MASK(CPU_CRC32) == HWCAP_CRC32, "Flag CPU_CRC32 must follow Linux HWCAP"); + static_assert(BIT_MASK(CPU_LSE) == HWCAP_ATOMICS, "Flag CPU_LSE must follow Linux HWCAP"); + static_assert(BIT_MASK(CPU_DCPOP) == HWCAP_DCPOP, "Flag CPU_DCPOP must follow Linux HWCAP"); + static_assert(BIT_MASK(CPU_SHA3) == HWCAP_SHA3, "Flag CPU_SHA3 must follow Linux HWCAP"); + static_assert(BIT_MASK(CPU_SHA512) == HWCAP_SHA512, "Flag CPU_SHA512 must follow Linux HWCAP"); + static_assert(BIT_MASK(CPU_SVE) == HWCAP_SVE, "Flag CPU_SVE must follow Linux HWCAP"); + static_assert(BIT_MASK(CPU_PACA) == HWCAP_PACA, "Flag CPU_PACA must follow Linux HWCAP"); + static_assert(BIT_MASK(CPU_FPHP) == HWCAP_FPHP, "Flag CPU_FPHP must follow Linux HWCAP"); + static_assert(BIT_MASK(CPU_ASIMDHP) == HWCAP_ASIMDHP, "Flag CPU_ASIMDHP must follow Linux HWCAP"); _features = auxv & ( HWCAP_FP | HWCAP_ASIMD | @@ -152,8 +152,12 @@ void VM_Version::get_os_cpu_info() { HWCAP_FPHP | HWCAP_ASIMDHP); - if (auxv2 & HWCAP2_SVE2) _features |= CPU_SVE2; - if (auxv2 & HWCAP2_SVEBITPERM) _features |= CPU_SVEBITPERM; + if (auxv2 & HWCAP2_SVE2) { + set_feature(CPU_SVE2); + } + if (auxv2 & HWCAP2_SVEBITPERM) { + set_feature(CPU_SVEBITPERM); + } uint64_t ctr_el0; uint64_t dczid_el0; @@ -187,7 +191,7 @@ void VM_Version::get_os_cpu_info() { _revision = v; } else if (strncmp(buf, "flags", sizeof("flags") - 1) == 0) { if (strstr(p+1, "dcpop")) { - guarantee(_features & CPU_DCPOP, "dcpop availability should be consistent"); + guarantee(supports_feature(CPU_DCPOP), "dcpop availability should be consistent"); } } } diff --git a/src/hotspot/share/runtime/abstract_vm_version.cpp b/src/hotspot/share/runtime/abstract_vm_version.cpp index 5b9d36115f8..97d4f7f228d 100644 --- a/src/hotspot/share/runtime/abstract_vm_version.cpp +++ b/src/hotspot/share/runtime/abstract_vm_version.cpp @@ -325,19 +325,6 @@ unsigned int Abstract_VM_Version::jvm_version() { (Abstract_VM_Version::vm_build_number() & 0xFF); } -const char* Abstract_VM_Version::extract_features_string(const char* cpu_info_string, - size_t cpu_info_string_len, - size_t features_offset) { - assert(features_offset <= cpu_info_string_len, ""); - if (features_offset < cpu_info_string_len) { - assert(cpu_info_string[features_offset + 0] == ',', ""); - assert(cpu_info_string[features_offset + 1] == ' ', ""); - return cpu_info_string + features_offset + 2; // skip initial ", " - } else { - return ""; // empty - } -} - bool Abstract_VM_Version::print_matching_lines_from_file(const char* filename, outputStream* st, const char* keywords_to_match[]) { char line[500]; FILE* fp = os::fopen(filename, "r"); diff --git a/src/hotspot/share/runtime/abstract_vm_version.hpp b/src/hotspot/share/runtime/abstract_vm_version.hpp index 4972f02e3d8..b9c52b27182 100644 --- a/src/hotspot/share/runtime/abstract_vm_version.hpp +++ b/src/hotspot/share/runtime/abstract_vm_version.hpp @@ -132,9 +132,6 @@ class Abstract_VM_Version: AllStatic { static const char* features_string() { return _features_string; } static const char* cpu_info_string() { return _cpu_info_string; } - static const char* extract_features_string(const char* cpu_info_string, - size_t cpu_info_string_len, - size_t features_offset); static VirtualizationType get_detected_virtualization() { return _detected_virtualization; diff --git a/src/hotspot/share/utilities/ostream.hpp b/src/hotspot/share/utilities/ostream.hpp index a148557fd32..e971ac4d125 100644 --- a/src/hotspot/share/utilities/ostream.hpp +++ b/src/hotspot/share/utilities/ostream.hpp @@ -165,6 +165,20 @@ class outputStream : public CHeapObjBase { void dec_cr() { dec(); cr(); } void inc_cr() { inc(); cr(); } + + // Append strings returned by gen, separating each with separator. + // Stops when gen returns null. + template + void join(Generator gen, const char* separator) { + bool first = true; + const char* str = gen(); + while (str != nullptr) { + const char* sep = first ? "" : separator; + print("%s%s", sep, str); + first = false; + str = gen(); + } + } }; // standard output From 83953c458eb65b2af184340dd460325f2b56e5b9 Mon Sep 17 00:00:00 2001 From: Guanqiang Han Date: Thu, 7 Aug 2025 14:11:46 +0000 Subject: [PATCH 048/580] 8364822: Comment cleanup, stale references to closeDescriptors and UNIXProcess.c Reviewed-by: kevinw, rriggs --- src/hotspot/os/aix/os_aix.cpp | 3 +-- src/hotspot/os/bsd/os_bsd.cpp | 3 +-- src/hotspot/os/linux/os_linux.cpp | 5 ++--- src/java.base/unix/native/libjava/childproc.c | 2 +- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/hotspot/os/aix/os_aix.cpp b/src/hotspot/os/aix/os_aix.cpp index 17186fb9f3d..25a930dc1d9 100644 --- a/src/hotspot/os/aix/os_aix.cpp +++ b/src/hotspot/os/aix/os_aix.cpp @@ -2343,8 +2343,7 @@ int os::open(const char *path, int oflag, int mode) { // specifically destined for a subprocess should have the // close-on-exec flag set. If we don't set it, then careless 3rd // party native code might fork and exec without closing all - // appropriate file descriptors (e.g. as we do in closeDescriptors in - // UNIXProcess.c), and this in turn might: + // appropriate file descriptors, and this in turn might: // // - cause end-of-file to fail to be detected on some file // descriptors, resulting in mysterious hangs, or diff --git a/src/hotspot/os/bsd/os_bsd.cpp b/src/hotspot/os/bsd/os_bsd.cpp index eb9b4c3f862..b7b88e8e606 100644 --- a/src/hotspot/os/bsd/os_bsd.cpp +++ b/src/hotspot/os/bsd/os_bsd.cpp @@ -2251,8 +2251,7 @@ int os::open(const char *path, int oflag, int mode) { // specifically destined for a subprocess should have the // close-on-exec flag set. If we don't set it, then careless 3rd // party native code might fork and exec without closing all - // appropriate file descriptors (e.g. as we do in closeDescriptors in - // UNIXProcess.c), and this in turn might: + // appropriate file descriptors, and this in turn might: // // - cause end-of-file to fail to be detected on some file // descriptors, resulting in mysterious hangs, or diff --git a/src/hotspot/os/linux/os_linux.cpp b/src/hotspot/os/linux/os_linux.cpp index b77a1f36954..4d6225cf21e 100644 --- a/src/hotspot/os/linux/os_linux.cpp +++ b/src/hotspot/os/linux/os_linux.cpp @@ -4872,9 +4872,8 @@ int os::open(const char *path, int oflag, int mode) { // All file descriptors that are opened in the Java process and not // specifically destined for a subprocess should have the close-on-exec // flag set. If we don't set it, then careless 3rd party native code - // might fork and exec without closing all appropriate file descriptors - // (e.g. as we do in closeDescriptors in UNIXProcess.c), and this in - // turn might: + // might fork and exec without closing all appropriate file descriptors, + // and this in turn might: // // - cause end-of-file to fail to be detected on some file // descriptors, resulting in mysterious hangs, or diff --git a/src/java.base/unix/native/libjava/childproc.c b/src/java.base/unix/native/libjava/childproc.c index 0dc0788879e..93d2200a465 100644 --- a/src/java.base/unix/native/libjava/childproc.c +++ b/src/java.base/unix/native/libjava/childproc.c @@ -372,7 +372,7 @@ childProcess(void *arg) jtregSimulateCrash(0, 6); #endif /* Close the parent sides of the pipes. - Closing pipe fds here is redundant, since closeDescriptors() + Closing pipe fds here is redundant, since markDescriptorsCloseOnExec() would do it anyways, but a little paranoia is a good thing. */ if ((closeSafely(p->in[1]) == -1) || (closeSafely(p->out[0]) == -1) || From e606278fc8929fe563dd50a1c3f332747e210276 Mon Sep 17 00:00:00 2001 From: Francesco Andreuzzi Date: Thu, 7 Aug 2025 15:43:36 +0000 Subject: [PATCH 049/580] 8358598: PhaseIterGVN::PhaseIterGVN(PhaseGVN* gvn) doesn't use its parameter Reviewed-by: galder, mhaessig, shade --- src/hotspot/share/opto/compile.cpp | 7 +++---- src/hotspot/share/opto/phaseX.cpp | 6 +++--- src/hotspot/share/opto/phaseX.hpp | 14 +++++--------- src/hotspot/share/opto/vector.cpp | 2 +- 4 files changed, 12 insertions(+), 17 deletions(-) diff --git a/src/hotspot/share/opto/compile.cpp b/src/hotspot/share/opto/compile.cpp index 2848ab5cc57..5a4a6c2667d 100644 --- a/src/hotspot/share/opto/compile.cpp +++ b/src/hotspot/share/opto/compile.cpp @@ -2149,7 +2149,7 @@ void Compile::inline_incrementally_cleanup(PhaseIterGVN& igvn) { } { TracePhase tp(_t_incrInline_igvn); - igvn.reset_from_gvn(initial_gvn()); + igvn.reset(); igvn.optimize(); if (failing()) return; } @@ -2310,8 +2310,7 @@ void Compile::Optimize() { { // Iterative Global Value Numbering, including ideal transforms - // Initialize IterGVN with types and values from parse-time GVN - PhaseIterGVN igvn(initial_gvn()); + PhaseIterGVN igvn; #ifdef ASSERT _modified_nodes = new (comp_arena()) Unique_Node_List(comp_arena()); #endif @@ -2380,7 +2379,7 @@ void Compile::Optimize() { ResourceMark rm; PhaseRenumberLive prl(initial_gvn(), *igvn_worklist()); } - igvn.reset_from_gvn(initial_gvn()); + igvn.reset(); igvn.optimize(); if (failing()) return; } diff --git a/src/hotspot/share/opto/phaseX.cpp b/src/hotspot/share/opto/phaseX.cpp index 3d67c80f1bb..1df2cdb179e 100644 --- a/src/hotspot/share/opto/phaseX.cpp +++ b/src/hotspot/share/opto/phaseX.cpp @@ -807,9 +807,9 @@ PhaseIterGVN::PhaseIterGVN(PhaseIterGVN* igvn) : _delay_transform(igvn->_delay_t } //------------------------------PhaseIterGVN----------------------------------- -// Initialize with previous PhaseGVN info from Parser -PhaseIterGVN::PhaseIterGVN(PhaseGVN* gvn) : _delay_transform(false), - _worklist(*C->igvn_worklist()) +// Initialize from scratch +PhaseIterGVN::PhaseIterGVN() : _delay_transform(false), + _worklist(*C->igvn_worklist()) { _iterGVN = true; uint max; diff --git a/src/hotspot/share/opto/phaseX.hpp b/src/hotspot/share/opto/phaseX.hpp index aeba5e8662d..300c8fc2757 100644 --- a/src/hotspot/share/opto/phaseX.hpp +++ b/src/hotspot/share/opto/phaseX.hpp @@ -459,16 +459,12 @@ protected: public: PhaseIterGVN(PhaseIterGVN* igvn); // Used by CCP constructor - PhaseIterGVN(PhaseGVN* gvn); // Used after Parser + PhaseIterGVN(); - // Reset IGVN from GVN: call deconstructor, and placement new. - // Achieves the same as the following (but without move constructors): - // igvn = PhaseIterGVN(gvn); - void reset_from_gvn(PhaseGVN* gvn) { - if (this != gvn) { - this->~PhaseIterGVN(); - ::new (static_cast(this)) PhaseIterGVN(gvn); - } + // Reset IGVN: call deconstructor, and placement new. + void reset() { + this->~PhaseIterGVN(); + ::new (static_cast(this)) PhaseIterGVN(); } // Reset IGVN with another: call deconstructor, and placement new. diff --git a/src/hotspot/share/opto/vector.cpp b/src/hotspot/share/opto/vector.cpp index 4ffbd2e96b4..cf01b2442e6 100644 --- a/src/hotspot/share/opto/vector.cpp +++ b/src/hotspot/share/opto/vector.cpp @@ -71,7 +71,7 @@ void PhaseVector::do_cleanup() { } { Compile::TracePhase tp(_t_vector_igvn); - _igvn.reset_from_gvn(C->initial_gvn()); + _igvn.reset(); _igvn.optimize(); if (C->failing()) return; } From e29346dbd6328dcadc347a70d8c06ce141efef02 Mon Sep 17 00:00:00 2001 From: Prasanta Sadhukhan Date: Thu, 7 Aug 2025 16:03:12 +0000 Subject: [PATCH 050/580] 8348760: RadioButton is not shown if JRadioButtonMenuItem is rendered with ImageIcon in WindowsLookAndFeel Reviewed-by: prr, kizune, abhiscxk --- .../com/sun/java/swing/SwingUtilities3.java | 136 ++++++++++++++- .../swing/plaf/basic/BasicMenuItemUI.java | 144 ++++++---------- .../windows/WindowsCheckBoxMenuItemUI.java | 21 ++- .../plaf/windows/WindowsIconFactory.java | 16 +- .../swing/plaf/windows/WindowsMenuItemUI.java | 142 +++++++++++++++- .../swing/plaf/windows/WindowsMenuUI.java | 21 ++- .../windows/WindowsRadioButtonMenuItemUI.java | 20 ++- .../TestRadioAndCheckMenuItemWithIcon.java | 155 ++++++++++++++++++ 8 files changed, 549 insertions(+), 106 deletions(-) create mode 100644 test/jdk/javax/swing/JMenuItem/TestRadioAndCheckMenuItemWithIcon.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 277b00ba4cf..a17f1f480ed 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 @@ -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 @@ -25,10 +25,13 @@ package com.sun.java.swing; +import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Graphics; import java.awt.Graphics2D; +import java.awt.Insets; +import java.awt.Rectangle; import java.awt.Stroke; import java.awt.Window; import java.awt.geom.AffineTransform; @@ -36,11 +39,16 @@ import java.util.Collections; import java.util.Map; import java.util.WeakHashMap; +import javax.swing.ButtonModel; +import javax.swing.Icon; import javax.swing.JComponent; +import javax.swing.JMenu; import javax.swing.RepaintManager; import sun.awt.AppContext; import sun.awt.SunToolkit; +import sun.swing.MenuItemLayoutHelper; +import sun.swing.SwingUtilities2; import static sun.java2d.pipe.Region.clipRound; @@ -61,6 +69,10 @@ public class SwingUtilities3 { private static final Object DELEGATE_REPAINT_MANAGER_KEY = new StringBuilder("DelegateRepaintManagerKey"); + private static Color disabledForeground; + private static Color acceleratorSelectionForeground; + private static Color acceleratorForeground; + /** * Registers delegate RepaintManager for {@code JComponent}. */ @@ -137,6 +149,128 @@ public class SwingUtilities3 { return delegate; } + public static void applyInsets(Rectangle rect, Insets insets) { + if (insets != null) { + rect.x += insets.left; + rect.y += insets.top; + rect.width -= (insets.right + rect.x); + rect.height -= (insets.bottom + rect.y); + } + } + + public static void paintCheckIcon(Graphics g, MenuItemLayoutHelper lh, + MenuItemLayoutHelper.LayoutResult lr, + Color holdc, Color foreground) { + if (lh.getCheckIcon() != null) { + ButtonModel model = lh.getMenuItem().getModel(); + if (model.isArmed() || (lh.getMenuItem() instanceof JMenu + && model.isSelected())) { + g.setColor(foreground); + } else { + g.setColor(holdc); + } + if (lh.useCheckAndArrow()) { + lh.getCheckIcon().paintIcon(lh.getMenuItem(), g, + lr.getCheckRect().x, lr.getCheckRect().y); + } + g.setColor(holdc); + } + } + + public static void paintIcon(Graphics g, MenuItemLayoutHelper lh, + MenuItemLayoutHelper.LayoutResult lr, Color holdc) { + if (lh.getIcon() != null) { + Icon icon; + ButtonModel model = lh.getMenuItem().getModel(); + if (!model.isEnabled()) { + icon = lh.getMenuItem().getDisabledIcon(); + } else if (model.isPressed() && model.isArmed()) { + icon = lh.getMenuItem().getPressedIcon(); + if (icon == null) { + // Use default icon + icon = lh.getMenuItem().getIcon(); + } + } else { + icon = lh.getMenuItem().getIcon(); + } + + if (icon != null) { + icon.paintIcon(lh.getMenuItem(), g, lr.getIconRect().x, + lr.getIconRect().y); + g.setColor(holdc); + } + } + } + + + public static void paintAccText(Graphics g, MenuItemLayoutHelper lh, + MenuItemLayoutHelper.LayoutResult lr) { + if (!lh.getAccText().isEmpty()) { + ButtonModel model = lh.getMenuItem().getModel(); + g.setFont(lh.getAccFontMetrics().getFont()); + if (!model.isEnabled()) { + + // paint the accText disabled + if (disabledForeground != null) { + g.setColor(disabledForeground); + SwingUtilities2.drawString(lh.getMenuItem(), g, + lh.getAccText(), lr.getAccRect().x, + lr.getAccRect().y + lh.getAccFontMetrics().getAscent()); + } else { + g.setColor(lh.getMenuItem().getBackground().brighter()); + SwingUtilities2.drawString(lh.getMenuItem(), g, + lh.getAccText(), lr.getAccRect().x, + lr.getAccRect().y + lh.getAccFontMetrics().getAscent()); + g.setColor(lh.getMenuItem().getBackground().darker()); + SwingUtilities2.drawString(lh.getMenuItem(), g, + lh.getAccText(), lr.getAccRect().x - 1, + lr.getAccRect().y + lh.getFontMetrics().getAscent() - 1); + } + } else { + + // paint the accText normally + if (model.isArmed() + || (lh.getMenuItem() instanceof JMenu + && model.isSelected())) { + g.setColor(acceleratorSelectionForeground); + } else { + g.setColor(acceleratorForeground); + } + SwingUtilities2.drawString(lh.getMenuItem(), g, lh.getAccText(), + lr.getAccRect().x, lr.getAccRect().y + + lh.getAccFontMetrics().getAscent()); + } + } + } + + public static void setDisabledForeground(Color disabledFg) { + disabledForeground = disabledFg; + } + + public static void setAcceleratorSelectionForeground(Color acceleratorSelectionFg) { + acceleratorForeground = acceleratorSelectionFg; + } + + public static void setAcceleratorForeground(Color acceleratorFg) { + acceleratorForeground = acceleratorFg; + } + + public static void paintArrowIcon(Graphics g, MenuItemLayoutHelper lh, + MenuItemLayoutHelper.LayoutResult lr, + Color foreground) { + if (lh.getArrowIcon() != null) { + ButtonModel model = lh.getMenuItem().getModel(); + if (model.isArmed() || (lh.getMenuItem() instanceof JMenu + && model.isSelected())) { + g.setColor(foreground); + } + if (lh.useCheckAndArrow()) { + lh.getArrowIcon().paintIcon(lh.getMenuItem(), g, + lr.getArrowRect().x, lr.getArrowRect().y); + } + } + } + /** * A task which paints an unscaled border after {@code Graphics} * transforms are removed. It's used with the 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 dcd569910e9..524f0337a8f 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2022, 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 @@ -25,17 +25,52 @@ package javax.swing.plaf.basic; -import java.awt.*; -import java.awt.event.*; +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Insets; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.InputEvent; +import java.awt.event.MouseEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; -import javax.swing.*; -import javax.swing.event.*; -import javax.swing.plaf.*; +import javax.swing.ButtonModel; +import javax.swing.Icon; +import javax.swing.InputMap; +import javax.swing.JCheckBoxMenuItem; +import javax.swing.JComponent; +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import javax.swing.JRadioButtonMenuItem; +import javax.swing.KeyStroke; +import javax.swing.LookAndFeel; +import javax.swing.MenuElement; +import javax.swing.MenuSelectionManager; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.event.MenuDragMouseEvent; +import javax.swing.event.MenuDragMouseListener; +import javax.swing.event.MenuKeyListener; + +import javax.swing.event.MouseInputListener; +import javax.swing.plaf.ComponentInputMapUIResource; +import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.MenuItemUI; +import javax.swing.plaf.UIResource; import javax.swing.text.View; -import sun.swing.*; +import com.sun.java.swing.SwingUtilities3; +import sun.swing.MenuItemCheckIconFactory; +import sun.swing.MenuItemLayoutHelper; +import sun.swing.SwingUtilities2; +import sun.swing.UIAction; + /** * BasicMenuItem implementation @@ -670,84 +705,22 @@ public class BasicMenuItemUI extends MenuItemUI private void paintIcon(Graphics g, MenuItemLayoutHelper lh, MenuItemLayoutHelper.LayoutResult lr, Color holdc) { - if (lh.getIcon() != null) { - Icon icon; - ButtonModel model = lh.getMenuItem().getModel(); - if (!model.isEnabled()) { - icon = lh.getMenuItem().getDisabledIcon(); - } else if (model.isPressed() && model.isArmed()) { - icon = lh.getMenuItem().getPressedIcon(); - if (icon == null) { - // Use default icon - icon = lh.getMenuItem().getIcon(); - } - } else { - icon = lh.getMenuItem().getIcon(); - } - - if (icon != null) { - icon.paintIcon(lh.getMenuItem(), g, lr.getIconRect().x, - lr.getIconRect().y); - g.setColor(holdc); - } - } + SwingUtilities3.paintIcon(g, lh, lr, holdc); } private void paintCheckIcon(Graphics g, MenuItemLayoutHelper lh, MenuItemLayoutHelper.LayoutResult lr, Color holdc, Color foreground) { - if (lh.getCheckIcon() != null) { - ButtonModel model = lh.getMenuItem().getModel(); - if (model.isArmed() || (lh.getMenuItem() instanceof JMenu - && model.isSelected())) { - g.setColor(foreground); - } else { - g.setColor(holdc); - } - if (lh.useCheckAndArrow()) { - lh.getCheckIcon().paintIcon(lh.getMenuItem(), g, - lr.getCheckRect().x, lr.getCheckRect().y); - } - g.setColor(holdc); - } + SwingUtilities3.paintCheckIcon(g, lh, lr, holdc, foreground); } private void paintAccText(Graphics g, MenuItemLayoutHelper lh, MenuItemLayoutHelper.LayoutResult lr) { - if (!lh.getAccText().isEmpty()) { - ButtonModel model = lh.getMenuItem().getModel(); - g.setFont(lh.getAccFontMetrics().getFont()); - if (!model.isEnabled()) { - // *** paint the accText disabled - if (disabledForeground != null) { - g.setColor(disabledForeground); - SwingUtilities2.drawString(lh.getMenuItem(), g, - lh.getAccText(), lr.getAccRect().x, - lr.getAccRect().y + lh.getAccFontMetrics().getAscent()); - } else { - g.setColor(lh.getMenuItem().getBackground().brighter()); - SwingUtilities2.drawString(lh.getMenuItem(), g, - lh.getAccText(), lr.getAccRect().x, - lr.getAccRect().y + lh.getAccFontMetrics().getAscent()); - g.setColor(lh.getMenuItem().getBackground().darker()); - SwingUtilities2.drawString(lh.getMenuItem(), g, - lh.getAccText(), lr.getAccRect().x - 1, - lr.getAccRect().y + lh.getFontMetrics().getAscent() - 1); - } - } else { - // *** paint the accText normally - if (model.isArmed() - || (lh.getMenuItem() instanceof JMenu - && model.isSelected())) { - g.setColor(acceleratorSelectionForeground); - } else { - g.setColor(acceleratorForeground); - } - SwingUtilities2.drawString(lh.getMenuItem(), g, lh.getAccText(), - lr.getAccRect().x, lr.getAccRect().y + - lh.getAccFontMetrics().getAscent()); - } - } + SwingUtilities3.setDisabledForeground(disabledForeground); + SwingUtilities3.setAcceleratorSelectionForeground( + acceleratorSelectionForeground); + SwingUtilities3.setAcceleratorForeground(acceleratorForeground); + SwingUtilities3.paintAccText(g, lh, lr); } private void paintText(Graphics g, MenuItemLayoutHelper lh, @@ -766,26 +739,11 @@ public class BasicMenuItemUI extends MenuItemUI private void paintArrowIcon(Graphics g, MenuItemLayoutHelper lh, MenuItemLayoutHelper.LayoutResult lr, Color foreground) { - if (lh.getArrowIcon() != null) { - ButtonModel model = lh.getMenuItem().getModel(); - if (model.isArmed() || (lh.getMenuItem() instanceof JMenu - && model.isSelected())) { - g.setColor(foreground); - } - if (lh.useCheckAndArrow()) { - lh.getArrowIcon().paintIcon(lh.getMenuItem(), g, - lr.getArrowRect().x, lr.getArrowRect().y); - } - } + SwingUtilities3.paintArrowIcon(g, lh, lr, foreground); } private void applyInsets(Rectangle rect, Insets insets) { - if(insets != null) { - rect.x += insets.left; - rect.y += insets.top; - rect.width -= (insets.right + rect.x); - rect.height -= (insets.bottom + rect.y); - } + SwingUtilities3.applyInsets(rect, insets); } /** 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 6b85a88e50c..f59a59a5125 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsCheckBoxMenuItemUI.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsCheckBoxMenuItemUI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2020, 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 @@ -30,6 +30,7 @@ import java.awt.Graphics; import java.awt.Rectangle; import javax.swing.ButtonModel; +import javax.swing.Icon; import javax.swing.JComponent; import javax.swing.JMenuItem; import javax.swing.plaf.ComponentUI; @@ -73,6 +74,24 @@ public final class WindowsCheckBoxMenuItemUI extends BasicCheckBoxMenuItemUI { } super.paintBackground(g, menuItem, bgColor); } + + /** + * Paint MenuItem. + */ + 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, defaultTextIconGap, + menuItem, getPropertyPrefix()); + return; + } + super.paintMenuItem(g, c, checkIcon, arrowIcon, background, + foreground, defaultTextIconGap); + } + /** * Method which renders the text of the current menu item. * 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 cf2fd119423..efa710391d5 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, 2023, 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 @@ -880,6 +880,7 @@ public final class WindowsIconFactory implements Serializable } assert menuItem == null || c == menuItem; Icon icon = getIcon(); + if (type == JCheckBoxMenuItem.class || type == JRadioButtonMenuItem.class) { AbstractButton b = (AbstractButton) c; @@ -903,19 +904,18 @@ public final class WindowsIconFactory implements Serializable } XPStyle xp = XPStyle.getXP(); if (xp != null) { - Skin skin; - skin = xp.getSkin(c, backgroundPart); - skin.paintSkin(g, x, y, - getIconWidth(), getIconHeight(), backgroundState); - if (icon == null) { - skin = xp.getSkin(c, part); + Skin skin = xp.getSkin(c, part); + 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); } } } } if (icon != null) { - icon.paintIcon(c, g, x + OFFSET, y + OFFSET); + icon.paintIcon(c, g, x + VistaMenuItemCheckIconFactory.getIconWidth(), + y + OFFSET); } } private static WindowsMenuItemUIAccessor getAccessor( 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 2ee1b2d119f..a8bafc54c33 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, 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 @@ -26,13 +26,20 @@ package com.sun.java.swing.plaf.windows; import java.awt.Color; +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; import javax.swing.JMenuItem; @@ -41,6 +48,7 @@ import javax.swing.plaf.ComponentUI; import javax.swing.plaf.UIResource; import javax.swing.plaf.basic.BasicMenuItemUI; +import com.sun.java.swing.SwingUtilities3; import com.sun.java.swing.plaf.windows.TMSchema.Part; import com.sun.java.swing.plaf.windows.TMSchema.State; import com.sun.java.swing.plaf.windows.XPStyle.Skin; @@ -59,6 +67,9 @@ public final class WindowsMenuItemUI extends BasicMenuItemUI { * The instance of {@code PropertyChangeListener}. */ private PropertyChangeListener changeListener; + private static Color disabledForeground; + private static Color acceleratorSelectionForeground; + private static Color acceleratorForeground; final WindowsMenuItemUIAccessor accessor = new WindowsMenuItemUIAccessor() { @@ -123,6 +134,27 @@ public final class WindowsMenuItemUI extends BasicMenuItemUI { 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} */ @@ -135,6 +167,114 @@ public final class WindowsMenuItemUI extends BasicMenuItemUI { changeListener = null; } + private static void applyInsets(Rectangle rect, Insets insets) { + SwingUtilities3.applyInsets(rect, insets); + } + + private static void paintCheckIcon(Graphics g, MenuItemLayoutHelper lh, + MenuItemLayoutHelper.LayoutResult lr, + Color holdc, Color foreground) { + SwingUtilities3.paintCheckIcon(g, lh, lr, holdc, foreground); + } + + private static void paintIcon(Graphics g, MenuItemLayoutHelper lh, + MenuItemLayoutHelper.LayoutResult lr, Color holdc) { + SwingUtilities3.paintIcon(g, lh, lr, holdc); + } + + private static void paintAccText(Graphics g, MenuItemLayoutHelper lh, + MenuItemLayoutHelper.LayoutResult lr) { + SwingUtilities3.setDisabledForeground(disabledForeground); + SwingUtilities3.setAcceleratorSelectionForeground( + acceleratorSelectionForeground); + SwingUtilities3.setAcceleratorForeground(acceleratorForeground); + SwingUtilities3.paintAccText(g, lh, lr); + } + + private static void paintArrowIcon(Graphics g, MenuItemLayoutHelper lh, + MenuItemLayoutHelper.LayoutResult lr, + Color foreground) { + SwingUtilities3.paintArrowIcon(g, lh, lr, foreground); + } + + 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, + defaultTextIconGap, menuItem, + getPropertyPrefix()); + return; + } + super.paintMenuItem(g, c, checkIcon, arrowIcon, background, + foreground, defaultTextIconGap); + } + + static void paintMenuItem(WindowsMenuItemUIAccessor accessor, Graphics g, + JComponent c, Icon checkIcon, Icon arrowIcon, + Color background, Color foreground, + int defaultTextIconGap, JMenuItem menuItem, String prefix) { + // Save original graphics font and color + Font holdf = g.getFont(); + Color holdc = g.getColor(); + + JMenuItem mi = (JMenuItem) c; + g.setFont(mi.getFont()); + + Rectangle viewRect = new Rectangle(0, 0, mi.getWidth(), mi.getHeight()); + applyInsets(viewRect, mi.getInsets()); + + String acceleratorDelimiter = + UIManager.getString("MenuItem.acceleratorDelimiter"); + if (acceleratorDelimiter == null) { acceleratorDelimiter = "+"; } + Font acceleratorFont = UIManager.getFont("MenuItem.acceleratorFont"); + if (acceleratorFont == null) { + acceleratorFont = UIManager.getFont("MenuItem.font"); + } + + MenuItemLayoutHelper lh = new MenuItemLayoutHelper(mi, checkIcon, + arrowIcon, viewRect, defaultTextIconGap, acceleratorDelimiter, + mi.getComponentOrientation().isLeftToRight(), mi.getFont(), + acceleratorFont, MenuItemLayoutHelper.useCheckAndArrow(menuItem), + prefix); + MenuItemLayoutHelper.LayoutResult lr = lh.layoutMenuItem(); + + paintBackground(accessor, g, mi, background); + paintCheckIcon(g, lh, lr, holdc, foreground); + paintIcon(g, lh, lr, holdc); + + if (lh.getCheckIcon() != null && lh.useCheckAndArrow()) { + Rectangle rect = lr.getTextRect(); + + rect.x += lh.getAfterCheckIconGap(); + + lr.setTextRect(rect); + } + if (!lh.getText().isEmpty()) { + if (lh.getHtmlView() != null) { + // Text is HTML + lh.getHtmlView().paint(g, lr.getTextRect()); + } else { + // Text isn't HTML + paintText(accessor, g, lh.getMenuItem(), + lr.getTextRect(), lh.getText()); + } + } + if (lh.getCheckIcon() != null && lh.useCheckAndArrow()) { + Rectangle rect = lr.getAccRect(); + rect.x += lh.getAfterCheckIconGap(); + lr.setAccRect(rect); + } + paintAccText(g, lh, lr); + paintArrowIcon(g, lh, lr, foreground); + + // Restore original graphics font and color + g.setColor(holdc); + g.setFont(holdf); + } + /** * Method which renders the text of the current menu item. * 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 5562ce60388..81c01c11036 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsMenuUI.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsMenuUI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2020, 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 @@ -130,6 +130,25 @@ public final class WindowsMenuUI extends BasicMenuUI { hotTrackingOn = (obj instanceof Boolean) ? (Boolean)obj : true; } + /** + * Paint MenuItem. + */ + 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, + defaultTextIconGap, menuItem, + getPropertyPrefix()); + return; + } + super.paintMenuItem(g, c, checkIcon, arrowIcon, background, + foreground, defaultTextIconGap); + } + + /** * Draws the background of the menu. * @since 1.4 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 f6f3d4f06d1..385ab6b3634 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsRadioButtonMenuItemUI.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsRadioButtonMenuItemUI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2020, 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 @@ -30,6 +30,7 @@ import java.awt.Graphics; import java.awt.Rectangle; import javax.swing.ButtonModel; +import javax.swing.Icon; import javax.swing.JComponent; import javax.swing.JMenuItem; import javax.swing.plaf.ComponentUI; @@ -74,6 +75,23 @@ public final class WindowsRadioButtonMenuItemUI extends BasicRadioButtonMenuItem super.paintBackground(g, menuItem, bgColor); } + /** + * Paint MenuItem. + */ + 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, defaultTextIconGap, + menuItem, getPropertyPrefix()); + return; + } + super.paintMenuItem(g, c, checkIcon, arrowIcon, background, + foreground, defaultTextIconGap); + } + /** * Method which renders the text of the current menu item. * diff --git a/test/jdk/javax/swing/JMenuItem/TestRadioAndCheckMenuItemWithIcon.java b/test/jdk/javax/swing/JMenuItem/TestRadioAndCheckMenuItemWithIcon.java new file mode 100644 index 00000000000..15b1c7fe217 --- /dev/null +++ b/test/jdk/javax/swing/JMenuItem/TestRadioAndCheckMenuItemWithIcon.java @@ -0,0 +1,155 @@ +/* + * 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 8348760 + * @summary Verify if RadioButtonMenuItem bullet and + * JCheckboxMenuItem checkmark is shown if + * JRadioButtonMenuItem and JCheckboxMenuItem + * is rendered with ImageIcon in WindowsLookAndFeel + * @requires (os.family == "windows") + * @library /java/awt/regtesthelpers + * @build PassFailJFrame + * @run main/manual TestRadioAndCheckMenuItemWithIcon + */ + +import java.awt.Color; +import java.awt.Graphics; +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; +import java.awt.image.BufferedImage; + +import javax.swing.AbstractButton; +import javax.swing.ButtonGroup; +import javax.swing.ImageIcon; +import javax.swing.JCheckBoxMenuItem; +import javax.swing.JFrame; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.JRadioButtonMenuItem; +import javax.swing.KeyStroke; +import javax.swing.UIManager; + +public class TestRadioAndCheckMenuItemWithIcon { + + private static final String INSTRUCTIONS = """ + A top level Menu will be shown. + + Clicking on the Menu will show a + JRadioButtonMenuItem group with 3 radiobutton menuitems + and a JCheckBoxMenuItem group with 3 checkbox menuitems. + + First radiobutton menuitem is selected with imageicon of a red square. + Second radiobutton menuitem is unselected with imageicon. + Third radiobutton menuItem is unselected without imageicon. + + First checkbox menuitem is selected with imageicon. + Second checkbox menuitem is unselected with imageicon. + Third checkbox menuItem is unselected without imageicon. + + Verify that for first JRadioButtonMenuItem with imageicon, + a bullet is shown alongside the imageicon and + for first JCheckBoxMenuItem with imageicon + a checkmark is shown alongside the imageicon. + + If bullet and checkmark is shown, test passes else fails."""; + + public static void main(String[] args) throws Exception { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + PassFailJFrame.builder() + .title("JRadioButtonMenuItem Instructions") + .instructions(INSTRUCTIONS) + .columns(60) + .testUI(TestRadioAndCheckMenuItemWithIcon::doTest) + .build() + .awaitAndCheck(); + } + + public static JFrame doTest() { + BufferedImage img = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB); + Graphics g = img.getGraphics(); + g.setColor(Color.red); + g.fillRect(0, 0, img.getWidth(), img.getHeight()); + g.dispose(); + + BufferedImage img2 = new BufferedImage(64, 64, BufferedImage.TYPE_INT_ARGB); + Graphics g2 = img2.getGraphics(); + g2.setColor(Color.red); + g2.fillRect(0, 0, img2.getWidth(), img2.getHeight()); + g2.dispose(); + + JFrame frame = new JFrame("RadioButtonWithImageIcon"); + ImageIcon imageIcon1 = new ImageIcon(img); + ImageIcon imageIcon2 = new ImageIcon(img2); + AbstractButton button1; + JRadioButtonMenuItem m1 = new JRadioButtonMenuItem("JRadioButtonMenuItem 1", + imageIcon1); + m1.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F4, ActionEvent.ALT_MASK|ActionEvent.CTRL_MASK|ActionEvent.SHIFT_MASK)); + button1 = m1; + button1.setSelected(true); + AbstractButton button2 = new JRadioButtonMenuItem("JRadioButtonMenuItem 2", imageIcon2); + AbstractButton button3 = new JRadioButtonMenuItem("JRadioButtonMenuItem 3"); + + ButtonGroup buttonGroup = new ButtonGroup(); + buttonGroup.add(button1); + buttonGroup.add(button2); + buttonGroup.add(button3); + + AbstractButton check1 = new JCheckBoxMenuItem("JCheckBoxMenuItem 1", + imageIcon1); + check1.setSelected(true); + AbstractButton check2 = new JCheckBoxMenuItem("JCheckBoxMenuItem 2", imageIcon1); + JCheckBoxMenuItem c3; + AbstractButton check3 = c3 = new JCheckBoxMenuItem("JCheckBoxMenuItem 3"); + c3.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F5, ActionEvent.ALT_MASK|ActionEvent.CTRL_MASK|ActionEvent.SHIFT_MASK)); + + JMenu topLevel = new JMenu("Menu"); + + topLevel.add(button1); + topLevel.add(button2); + topLevel.add(button3); + + topLevel.addSeparator(); + + topLevel.add(check1); + topLevel.add(check2); + topLevel.add(check3); + + AbstractButton menuitem1 = new JMenuItem("MenuItem1"); + AbstractButton menuitem2 = new JMenuItem("MenuItem2", imageIcon1); + topLevel.addSeparator(); + topLevel.add(menuitem1); + topLevel.add(menuitem2); + + JMenuBar menuBar = new JMenuBar(); + menuBar.add(topLevel); + + frame.setJMenuBar(menuBar); + frame.setSize(300, 300); + return frame; + + } +} From 90ea42f716770fd567e4e3b3bf7466fa93964f07 Mon Sep 17 00:00:00 2001 From: Andrew Dinn Date: Thu, 7 Aug 2025 16:23:32 +0000 Subject: [PATCH 051/580] 8364558: Failure to generate compiler stubs from compiler thread should not crash VM when compilation disabled due to full CodeCache Reviewed-by: kvn, shade --- src/hotspot/share/opto/c2compiler.cpp | 6 ++++++ src/hotspot/share/runtime/stubRoutines.cpp | 11 +++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/hotspot/share/opto/c2compiler.cpp b/src/hotspot/share/opto/c2compiler.cpp index bed0f38d6a6..acc28964627 100644 --- a/src/hotspot/share/opto/c2compiler.cpp +++ b/src/hotspot/share/opto/c2compiler.cpp @@ -90,6 +90,12 @@ bool C2Compiler::init_c2_runtime() { compiler_stubs_init(true /* in_compiler_thread */); // generate compiler's intrinsics stubs + // If there was an error generating the blob then UseCompiler will + // have been unset and we need to skip the remaining initialization + if (!UseCompiler) { + return false; + } + Compile::pd_compiler2_init(); CompilerThread* thread = CompilerThread::current(); diff --git a/src/hotspot/share/runtime/stubRoutines.cpp b/src/hotspot/share/runtime/stubRoutines.cpp index b287b89df84..86975f7d0a6 100644 --- a/src/hotspot/share/runtime/stubRoutines.cpp +++ b/src/hotspot/share/runtime/stubRoutines.cpp @@ -181,6 +181,17 @@ static BufferBlob* initialize_stubs(BlobId blob_id, int size = code_size + CodeEntryAlignment * max_aligned_stubs; BufferBlob* stubs_code = BufferBlob::create(buffer_name, size); if (stubs_code == nullptr) { + // The compiler blob may be created late by a C2 compiler thread + // rather than during normal initialization by the initial thread. + // In that case we can tolerate an allocation failure because the + // compiler will have been shut down and we have no need of the + // blob. + if (Thread::current()->is_Compiler_thread()) { + assert(blob_id == BlobId::stubgen_compiler_id, "sanity"); + assert(DelayCompilerStubsGeneration, "sanity"); + log_warning(stubs)("%s\t not generated:\t no space left in CodeCache", buffer_name); + return nullptr; + } vm_exit_out_of_memory(code_size, OOM_MALLOC_ERROR, "CodeCache: no room for %s", buffer_name); } CodeBuffer buffer(stubs_code); From 02e187119d0ca94d46e631a174c55db4945f3295 Mon Sep 17 00:00:00 2001 From: Brian Burkhalter Date: Thu, 7 Aug 2025 18:24:22 +0000 Subject: [PATCH 052/580] 8364277: (fs) BasicFileAttributes.isDirectory and isOther return true for NTFS directory junctions when links not followed Reviewed-by: alanb --- .../classes/sun/nio/fs/WindowsConstants.java | 5 +- .../sun/nio/fs/WindowsFileAttributes.java | 12 +- .../sun/nio/fs/WindowsFileSystemProvider.java | 3 +- .../BasicFileAttributeView/Basic.java | 39 ++++- test/lib/jdk/test/lib/util/FileUtils.java | 36 +++- test/lib/jdk/test/lib/util/libFileUtils.c | 158 +++++++++++++++++- 6 files changed, 228 insertions(+), 25 deletions(-) diff --git a/src/java.base/windows/classes/sun/nio/fs/WindowsConstants.java b/src/java.base/windows/classes/sun/nio/fs/WindowsConstants.java index 46315533515..b1de66ac4f2 100644 --- a/src/java.base/windows/classes/sun/nio/fs/WindowsConstants.java +++ b/src/java.base/windows/classes/sun/nio/fs/WindowsConstants.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2025, Oracle and/or its affiliates. All rights reserved. * 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,8 +72,9 @@ class WindowsConstants { public static final int BACKUP_SPARSE_BLOCK = 0x00000009; // reparse point/symbolic link related constants - public static final int IO_REPARSE_TAG_SYMLINK = 0xA000000C; public static final int IO_REPARSE_TAG_AF_UNIX = 0x80000023; + public static final int IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003; + public static final int IO_REPARSE_TAG_SYMLINK = 0xA000000C; public static final int MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16 * 1024; public static final int SYMBOLIC_LINK_FLAG_DIRECTORY = 0x1; public static final int SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE = 0x2; diff --git a/src/java.base/windows/classes/sun/nio/fs/WindowsFileAttributes.java b/src/java.base/windows/classes/sun/nio/fs/WindowsFileAttributes.java index 6e544b1c926..3c94e8bc4a2 100644 --- a/src/java.base/windows/classes/sun/nio/fs/WindowsFileAttributes.java +++ b/src/java.base/windows/classes/sun/nio/fs/WindowsFileAttributes.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -412,6 +412,10 @@ class WindowsFileAttributes return isSymbolicLink() && ((fileAttrs & FILE_ATTRIBUTE_DIRECTORY) != 0); } + boolean isDirectoryJunction() { + return reparseTag == IO_REPARSE_TAG_MOUNT_POINT; + } + @Override public boolean isSymbolicLink() { return reparseTag == IO_REPARSE_TAG_SYMLINK; @@ -423,10 +427,8 @@ class WindowsFileAttributes @Override public boolean isDirectory() { - // ignore FILE_ATTRIBUTE_DIRECTORY attribute if file is a sym link - if (isSymbolicLink()) - return false; - return ((fileAttrs & FILE_ATTRIBUTE_DIRECTORY) != 0); + return ((fileAttrs & FILE_ATTRIBUTE_DIRECTORY) != 0 && + (fileAttrs & FILE_ATTRIBUTE_REPARSE_POINT) == 0); } @Override diff --git a/src/java.base/windows/classes/sun/nio/fs/WindowsFileSystemProvider.java b/src/java.base/windows/classes/sun/nio/fs/WindowsFileSystemProvider.java index 7c280d87f62..3a1bb416fe7 100644 --- a/src/java.base/windows/classes/sun/nio/fs/WindowsFileSystemProvider.java +++ b/src/java.base/windows/classes/sun/nio/fs/WindowsFileSystemProvider.java @@ -243,7 +243,8 @@ class WindowsFileSystemProvider try { // need to know if file is a directory or junction attrs = WindowsFileAttributes.get(file, false); - if (attrs.isDirectory() || attrs.isDirectoryLink()) { + if (attrs.isDirectory() || attrs.isDirectoryLink() || + attrs.isDirectoryJunction()) { RemoveDirectory(file.getPathForWin32Calls()); } else { DeleteFile(file.getPathForWin32Calls()); diff --git a/test/jdk/java/nio/file/attribute/BasicFileAttributeView/Basic.java b/test/jdk/java/nio/file/attribute/BasicFileAttributeView/Basic.java index 1605910a2dd..c988d89a2c7 100644 --- a/test/jdk/java/nio/file/attribute/BasicFileAttributeView/Basic.java +++ b/test/jdk/java/nio/file/attribute/BasicFileAttributeView/Basic.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,9 +22,12 @@ */ /* @test - * @bug 4313887 6838333 + * @bug 4313887 6838333 8364277 * @summary Unit test for java.nio.file.attribute.BasicFileAttributeView - * @library ../.. + * @library ../.. /test/lib + * @build jdk.test.lib.Platform + * jdk.test.lib.util.FileUtils + * @run main/othervm --enable-native-access=ALL-UNNAMED Basic */ import java.nio.file.*; @@ -33,6 +36,9 @@ import java.util.*; import java.util.concurrent.TimeUnit; import java.io.*; +import jdk.test.lib.Platform; +import jdk.test.lib.util.FileUtils; + public class Basic { static void check(boolean okay, String msg) { @@ -97,6 +103,17 @@ public class Basic { check(!attrs.isOther(), "is not other"); } + static void checkAttributesOfJunction(Path junction) + throws IOException + { + BasicFileAttributes attrs = + Files.readAttributes(junction, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS); + check(!attrs.isSymbolicLink(), "is a link"); + check(!attrs.isDirectory(), "is a directory"); + check(!attrs.isRegularFile(), "is not a regular file"); + check(attrs.isOther(), "is other"); + } + static void attributeReadWriteTests(Path dir) throws IOException { @@ -114,12 +131,18 @@ public class Basic { Path link = dir.resolve("link"); try { Files.createSymbolicLink(link, file); - } catch (UnsupportedOperationException x) { - return; - } catch (IOException x) { - return; + checkAttributesOfLink(link); + } catch (IOException | UnsupportedOperationException x) { + if (!Platform.isWindows()) + return; + } + + // NTFS junctions are Windows-only + if (Platform.isWindows()) { + Path junction = dir.resolve("junction"); + FileUtils.createWinDirectoryJunction(junction, dir); + checkAttributesOfJunction(junction); } - checkAttributesOfLink(link); } public static void main(String[] args) throws IOException { diff --git a/test/lib/jdk/test/lib/util/FileUtils.java b/test/lib/jdk/test/lib/util/FileUtils.java index 8b99d1e9d54..3e537835c0b 100644 --- a/test/lib/jdk/test/lib/util/FileUtils.java +++ b/test/lib/jdk/test/lib/util/FileUtils.java @@ -24,6 +24,7 @@ package jdk.test.lib.util; import java.io.BufferedReader; +import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; @@ -33,6 +34,7 @@ import java.lang.management.ManagementFactory; import java.nio.file.DirectoryNotEmptyException; import java.nio.file.FileVisitResult; import java.nio.file.Files; +import java.nio.file.LinkOption; import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; @@ -64,6 +66,14 @@ public final class FileUtils { private static final int MAX_RETRY_DELETE_TIMES = IS_WINDOWS ? 15 : 0; private static volatile boolean nativeLibLoaded; + @SuppressWarnings("restricted") + private static void loadNativeLib() { + if (!nativeLibLoaded) { + System.loadLibrary("FileUtils"); + nativeLibLoaded = true; + } + } + /** * Deletes a file, retrying if necessary. * @@ -392,14 +402,10 @@ public final class FileUtils { } // Return the current process handle count - @SuppressWarnings("restricted") public static long getProcessHandleCount() { if (IS_WINDOWS) { - if (!nativeLibLoaded) { - System.loadLibrary("FileUtils"); - nativeLibLoaded = true; - } - return getWinProcessHandleCount(); + loadNativeLib(); + return getWinProcessHandleCount0(); } else { return ((UnixOperatingSystemMXBean)ManagementFactory.getOperatingSystemMXBean()).getOpenFileDescriptorCount(); } @@ -443,7 +449,23 @@ public final class FileUtils { Files.write(path, lines); } - private static native long getWinProcessHandleCount(); + // Create a directory junction with the specified target + public static boolean createWinDirectoryJunction(Path junction, Path target) + throws IOException + { + assert IS_WINDOWS; + + // Convert "target" to its real path + target = target.toRealPath(); + + // Create a directory junction + loadNativeLib(); + return createWinDirectoryJunction0(junction.toString(), target.toString()); + } + + private static native long getWinProcessHandleCount0(); + private static native boolean createWinDirectoryJunction0(String junction, + String target) throws IOException; // Possible command locations and arguments static String[][] lsCommands = new String[][] { diff --git a/test/lib/jdk/test/lib/util/libFileUtils.c b/test/lib/jdk/test/lib/util/libFileUtils.c index 1af90afff49..d0da04f9fb1 100644 --- a/test/lib/jdk/test/lib/util/libFileUtils.c +++ b/test/lib/jdk/test/lib/util/libFileUtils.c @@ -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 @@ -27,9 +27,49 @@ #ifdef _WIN32 #include "jni.h" +#include "jni_util.h" +#include #include +#include +#include +#include +#include +#include -JNIEXPORT jlong JNICALL Java_jdk_test_lib_util_FileUtils_getWinProcessHandleCount(JNIEnv *env) +// Based on Microsoft documentation +#define MAX_REPARSE_BUFFER_SIZE 16384 + +// Unavailable in standard header files: +// copied from Microsoft documentation +typedef struct _REPARSE_DATA_BUFFER { + ULONG ReparseTag; + USHORT ReparseDataLength; + USHORT Reserved; + union { + struct { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + ULONG Flags; + WCHAR PathBuffer[1]; + } SymbolicLinkReparseBuffer; + struct { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + WCHAR PathBuffer[1]; + } MountPointReparseBuffer; + struct { + UCHAR DataBuffer[1]; + } GenericReparseBuffer; + } DUMMYUNIONNAME; +} REPARSE_DATA_BUFFER, * PREPARSE_DATA_BUFFER; + +JNIEXPORT jlong JNICALL +Java_jdk_test_lib_util_FileUtils_getWinProcessHandleCount0 + (JNIEnv* env) { DWORD handleCount; HANDLE handle = GetCurrentProcess(); @@ -40,4 +80,118 @@ JNIEXPORT jlong JNICALL Java_jdk_test_lib_util_FileUtils_getWinProcessHandleCoun } } +void throwIOExceptionWithLastError(JNIEnv* env) { +#define BUFSIZE 256 + DWORD errval; + WCHAR buf[BUFSIZE]; + + if ((errval = GetLastError()) != 0) { + jsize n = FormatMessageW( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, errval, 0, buf, BUFSIZE, NULL); + + jclass ioExceptionClass = (*env)->FindClass(env, "java/io/IOException"); + (*env)->ThrowNew(env, ioExceptionClass, (const char*) buf); + } +} + +JNIEXPORT jboolean JNICALL +Java_jdk_test_lib_util_FileUtils_createWinDirectoryJunction0 + (JNIEnv* env, jclass unused, jstring sjunction, jstring starget) +{ + BOOL error = FALSE; + + const jshort bpc = sizeof(wchar_t); // bytes per character + HANDLE hJunction = INVALID_HANDLE_VALUE; + + const jchar* junction = (*env)->GetStringChars(env, sjunction, NULL); + const jchar* target = (*env)->GetStringChars(env, starget, NULL); + if (junction == NULL || target == NULL) { + jclass npeClass = (*env)->FindClass(env, "java/lang/NullPointerException"); + (*env)->ThrowNew(env, npeClass, NULL); + error = TRUE; + } + + USHORT wlen = (USHORT)0; + USHORT blen = (USHORT)0; + void* lpInBuffer = NULL; + if (!error) { + wlen = (USHORT)wcslen(target); + blen = (USHORT)(wlen * sizeof(wchar_t)); + lpInBuffer = calloc(MAX_REPARSE_BUFFER_SIZE, sizeof(char)); + if (lpInBuffer == NULL) { + jclass oomeClass = (*env)->FindClass(env, "java/lang/OutOfMemoryError"); + (*env)->ThrowNew(env, oomeClass, NULL); + error = TRUE; + } + } + + if (!error) { + if (CreateDirectoryW(junction, NULL) == 0) { + throwIOExceptionWithLastError(env); + error = TRUE; + } + } + + if (!error) { + hJunction = CreateFileW(junction, GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + OPEN_EXISTING, + FILE_FLAG_OPEN_REPARSE_POINT + | FILE_FLAG_BACKUP_SEMANTICS, NULL); + if (hJunction == INVALID_HANDLE_VALUE) { + throwIOExceptionWithLastError(env); + error = TRUE; + } + } + + if (!error) { + PREPARSE_DATA_BUFFER reparseBuffer = (PREPARSE_DATA_BUFFER)lpInBuffer; + reparseBuffer->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; + reparseBuffer->Reserved = 0; + WCHAR* prefix = L"\\??\\"; + USHORT prefixLength = (USHORT)(bpc * wcslen(prefix)); + reparseBuffer->MountPointReparseBuffer.SubstituteNameOffset = 0; + reparseBuffer->MountPointReparseBuffer.SubstituteNameLength = + prefixLength + blen; + reparseBuffer->MountPointReparseBuffer.PrintNameOffset = + prefixLength + blen + sizeof(WCHAR); + reparseBuffer->MountPointReparseBuffer.PrintNameLength = blen; + memcpy(&reparseBuffer->MountPointReparseBuffer.PathBuffer, + prefix, prefixLength); + memcpy(&reparseBuffer->MountPointReparseBuffer.PathBuffer[prefixLength/bpc], + target, blen); + memcpy(&reparseBuffer->MountPointReparseBuffer.PathBuffer[prefixLength/bpc + blen/bpc + 1], + target, blen); + reparseBuffer->ReparseDataLength = + (USHORT)(sizeof(reparseBuffer->MountPointReparseBuffer) + + prefixLength + bpc*blen + bpc); + DWORD nInBufferSize = FIELD_OFFSET(REPARSE_DATA_BUFFER, + MountPointReparseBuffer) + reparseBuffer->ReparseDataLength; + BOOL result = DeviceIoControl(hJunction, FSCTL_SET_REPARSE_POINT, + lpInBuffer, nInBufferSize, + NULL, 0, NULL, NULL); + if (result == 0) { + throwIOExceptionWithLastError(env); + error = TRUE; + } + } + + if (junction != NULL) { + (*env)->ReleaseStringChars(env, sjunction, junction); + if (target != NULL) { + (*env)->ReleaseStringChars(env, starget, target); + if (lpInBuffer != NULL) { + free(lpInBuffer); + if (hJunction != INVALID_HANDLE_VALUE) { + // Ignore any error in CloseHandle + CloseHandle(hJunction); + } + } + } + } + + return error ? JNI_FALSE : JNI_TRUE; +} + #endif /* _WIN32 */ From 78117eff563e59a738c59efa7ef595b13f62b621 Mon Sep 17 00:00:00 2001 From: Phil Race Date: Thu, 7 Aug 2025 18:58:28 +0000 Subject: [PATCH 053/580] 8364230: javax/swing/text/StringContent can be migrated away from using finalize Reviewed-by: psadhukhan, abhiscxk, kizune --- .../javax/swing/text/StringContent.java | 30 +++----- .../StringContentPositionTest.java | 69 +++++++++++++++++++ 2 files changed, 78 insertions(+), 21 deletions(-) create mode 100644 test/jdk/javax/swing/text/AbstractDocument/StringContentPositionTest.java diff --git a/src/java.desktop/share/classes/javax/swing/text/StringContent.java b/src/java.desktop/share/classes/javax/swing/text/StringContent.java index 562b336c770..1ce375fa3ab 100644 --- a/src/java.desktop/share/classes/javax/swing/text/StringContent.java +++ b/src/java.desktop/share/classes/javax/swing/text/StringContent.java @@ -27,6 +27,7 @@ package javax.swing.text; import java.util.Vector; import java.io.Serializable; import javax.swing.undo.*; +import java.lang.ref.WeakReference; /** * An implementation of the AbstractDocument.Content interface that is @@ -227,7 +228,7 @@ public final class StringContent implements AbstractDocument.Content, Serializab int n = marks.size(); for (int i = 0; i < n; i++) { PosRec mark = marks.elementAt(i); - if (mark.unused) { + if (mark.get() == null) { // this record is no longer used, get rid of it marks.removeElementAt(i); i -= 1; @@ -242,7 +243,7 @@ public final class StringContent implements AbstractDocument.Content, Serializab int n = marks.size(); for (int i = 0; i < n; i++) { PosRec mark = marks.elementAt(i); - if (mark.unused) { + if (mark.get() == null) { // this record is no longer used, get rid of it marks.removeElementAt(i); i -= 1; @@ -278,7 +279,7 @@ public final class StringContent implements AbstractDocument.Content, Serializab Vector placeIn = (v == null) ? new Vector() : v; for (int i = 0; i < n; i++) { PosRec mark = marks.elementAt(i); - if (mark.unused) { + if (mark.get() == null) { // this record is no longer used, get rid of it marks.removeElementAt(i); i -= 1; @@ -303,7 +304,7 @@ public final class StringContent implements AbstractDocument.Content, Serializab for(int counter = positions.size() - 1; counter >= 0; counter--) { UndoPosRef ref = (UndoPosRef) positions.elementAt(counter); // Check if the Position is still valid. - if(ref.rec.unused) { + if(ref.rec.get() == null) { positions.removeElementAt(counter); } else @@ -323,26 +324,20 @@ public final class StringContent implements AbstractDocument.Content, Serializab * it.... the update table holds only a reference * to this grungy thing. */ - static final class PosRec { + static final class PosRec extends WeakReference { - PosRec(int offset) { + PosRec(int offset, StickyPosition position) { + super(position); this.offset = offset; } int offset; - boolean unused; } - /** - * This really wants to be a weak reference but - * in 1.1 we don't have a 100% pure solution for - * this... so this class tries to hack a solution - * to causing the marks to be collected. - */ final class StickyPosition implements Position { StickyPosition(int offset) { - rec = new PosRec(offset); + rec = new PosRec(offset, this); marks.addElement(rec); } @@ -350,13 +345,6 @@ public final class StringContent implements AbstractDocument.Content, Serializab return rec.offset; } - @SuppressWarnings("removal") - protected void finalize() throws Throwable { - // schedule the record to be removed later - // on another thread. - rec.unused = true; - } - public String toString() { return Integer.toString(getOffset()); } diff --git a/test/jdk/javax/swing/text/AbstractDocument/StringContentPositionTest.java b/test/jdk/javax/swing/text/AbstractDocument/StringContentPositionTest.java new file mode 100644 index 00000000000..c535c4819e6 --- /dev/null +++ b/test/jdk/javax/swing/text/AbstractDocument/StringContentPositionTest.java @@ -0,0 +1,69 @@ +/* + * 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 javax.swing.text.BadLocationException; +import javax.swing.text.Position; +import javax.swing.text.StringContent; + +/* + * @test + * @summary test that StringContent Position APIs behave as expected. + */ + +public class StringContentPositionTest { + + static final int SIZE = 20; + static final String TEXT = "hello"; + static final int LEN = TEXT.length(); + static final StringContent SC = new StringContent(); + + public static void main(String[] args) throws BadLocationException { + + for (int i = 0; i < 1000; i++) { + test(); + System.gc(); + } + } + + static void test() throws BadLocationException { + + Position[] positions = new Position[SIZE]; + + for (int i = 0; i < SIZE; i++) { + SC.insertString(0, TEXT); + positions[i] = SC.createPosition(LEN); + } + for (int i = 0; i < SIZE; i++) { + int expected = ((SIZE - i) * LEN); + if (positions[i].getOffset() != expected) { + throw new RuntimeException("insert: Bad offset i=" + i + " off=" + positions[i].getOffset()); + } + } + SC.remove(0, SIZE * LEN); + for (int i = 0; i < SIZE; i++) { + if (positions[i].getOffset() != 0) { + throw new RuntimeException("remove: Bad offset i=" + i + " off=" + positions[i].getOffset()); + } + } + } +} From 5116d9e5fe6b63f12e9ae0eb5283433256872dc1 Mon Sep 17 00:00:00 2001 From: Brett Okken Date: Thu, 7 Aug 2025 19:27:28 +0000 Subject: [PATCH 054/580] 8364213: (bf) Improve java/nio/Buffer/CharBufferAsCharSequenceTest test comments 8364345: Test java/nio/Buffer/CharBufferAsCharSequenceTest.java failed Reviewed-by: bpb, rriggs --- .../Buffer/CharBufferAsCharSequenceTest.java | 178 +++++++++--------- 1 file changed, 86 insertions(+), 92 deletions(-) diff --git a/test/jdk/java/nio/Buffer/CharBufferAsCharSequenceTest.java b/test/jdk/java/nio/Buffer/CharBufferAsCharSequenceTest.java index f127cb96c84..ae5a6122712 100644 --- a/test/jdk/java/nio/Buffer/CharBufferAsCharSequenceTest.java +++ b/test/jdk/java/nio/Buffer/CharBufferAsCharSequenceTest.java @@ -41,11 +41,11 @@ import static org.junit.jupiter.api.Assertions.assertThrows; /* * @test - * @bug 8343110 8361299 + * @bug 8343110 8361299 8364345 * @key randomness * @library /test/lib * @build jdk.test.lib.RandomFactory - * @summary tests the CharBuffer implementations behaving as CharSequence in various states (position, limit, offset) + * @summary Tests CharBuffer implementations of CharSequence * @run junit CharBufferAsCharSequenceTest */ public class CharBufferAsCharSequenceTest { @@ -61,15 +61,28 @@ public class CharBufferAsCharSequenceTest { return chars; } + /** + * Randomly adjusts the position and limit such that the position will be in the + * first 1/4th and the limit in the last half. + */ private static CharBuffer randomizeRange(CharBuffer cb) { int mid = cb.capacity() >>> 1; - int start = RAND.nextInt(mid - 3); // from 0 to mid - int end = RAND.nextInt(mid + 3, cb.capacity()); // from mid to capacity + int start = RAND.nextInt(mid >> 1); // from 0 to 1/4 + int end = RAND.nextInt(mid + 1, cb.capacity()); // from mid to capacity cb.position(start); cb.limit(end); return cb; } + /** + * Generates random content to use for populating cb then calling through + * to {@code addCases(String, char[], CharBuffer, List)} + * + * @param type String description of the type of CharBuffer under test. + * @param cb CharBuffer instance to populate as base of creating cases. + * @param cases The {@code List} to populate with the cases for use from + * {@link #charBufferArguments()}. + */ private static void populateAndAddCases(String type, CharBuffer cb, List cases) { assert cb.position() == 0 && cb.limit() == cb.capacity(); char[] buf = randomChars(); @@ -78,26 +91,82 @@ public class CharBufferAsCharSequenceTest { addCases(type, buf, cb, cases); } + /** + * Adds 4 cases to cases. + *

    + *
  • Full use of cb
  • . + *
  • A duplicate of cb with a randomized position and limit. See + * {@code randomizeRange(CharBuffer)} + *
  • + *
  • A {@link CharBuffer#slice() sliced} copy of randomized range.
  • + *
  • A {@link CharBuffer#slice() sliced} copy of randomized range with a + * randomized position and limit.
  • + *
+ */ private static void addCases(String type, char[] buf, CharBuffer cb, List cases) { assert cb.position() == 0 && cb.limit() == cb.capacity(); cases.add(Arguments.of(cb, buf, 0, buf.length, type + " full")); CharBuffer rndRange = randomizeRange(cb.duplicate()); - cases.add(Arguments.of(rndRange, buf, rndRange.position(), rndRange.limit(), type + " at " + rndRange.position() + " through " + rndRange.limit())); - cases.add(Arguments.of(rndRange.slice(), buf, rndRange.position(), rndRange.limit(), type + " sliced at " + rndRange.position() + " through " + rndRange.limit())); + cases.add(Arguments.of(rndRange, buf, rndRange.position(), rndRange.limit(), + type + " at " + rndRange.position() + " through " + rndRange.limit())); + cases.add(Arguments.of(rndRange.slice(), buf, rndRange.position(), rndRange.limit(), + type + " sliced at " + rndRange.position() + " through " + rndRange.limit())); CharBuffer rndSlicedRange = randomizeRange(rndRange.slice()); - cases.add(Arguments.of(rndSlicedRange, buf, rndRange.position() + rndSlicedRange.position(), rndRange.position() + rndSlicedRange.limit(), type + " sliced at " + rndRange.position() + " with position " + rndSlicedRange.position() + " and limit " + rndSlicedRange.limit())); + cases.add(Arguments.of(rndSlicedRange, + buf, + rndRange.position() + rndSlicedRange.position(), + rndRange.position() + rndSlicedRange.limit(), + type + " sliced at " + rndRange.position() + " with position " + + rndSlicedRange.position() + " and limit " + rndSlicedRange.limit())); } + /** + * Returns a {@code List} of {@link Arguments}, with each entry representing a + * test case scenario. + *
    + *
  • CharBuffer - the instance to be tested
  • + *
  • char[] - the data expected to be backing the current state of the CharBuffer
  • + *
  • int start - index (inclusive) into char[] where the CharBuffer should be positioned
  • + *
  • int stop - index (exclusive) into char[] where the CharBuffer should be limited
  • + *
  • String - description of the test scenario
  • + *
+ * + * Generates the following sets of arguments/test cases. + *
    + *
  • See {@code populateAndAddCases(String, CharBuffer, List)} for the + * following types: + *
      + *
    • HeapCharBuffer + *
    • HeapByteBuffer Big Endian + *
    • HeapByteBuffer Little Endian + *
    • DirectByteBuffer Big Endian + *
    • DirectByteBuffer Little Endian + *
    + *
  • + *
  • Randomly generated content into {@link CharBuffer#wrap(CharSequence) + * StringCharBuffer} - see {@code addCases(String, char[], CharBuffer, List)}. + *
      + *
    • StringCharBuffer wrapping a {@code CharBuffer} created from + * {@link CharBuffer#wrap(char[])}
    • + *
    • StringCharBuffer wrapping a {@code String}
    • + *
    + *
  • + *
+ */ static List charBufferArguments() { List args = new ArrayList<>(); populateAndAddCases("HeapCharBuffer", CharBuffer.allocate(SIZE), args); - populateAndAddCases("BEHeapByteBuffer", ByteBuffer.allocate(SIZE*2).order(ByteOrder.BIG_ENDIAN).asCharBuffer(), args); - populateAndAddCases("LEHeapByteBuffer", ByteBuffer.allocate(SIZE*2).order(ByteOrder.LITTLE_ENDIAN).asCharBuffer(), args); - populateAndAddCases("BEDirectByteBuffer", ByteBuffer.allocateDirect(SIZE*2).order(ByteOrder.BIG_ENDIAN).asCharBuffer(), args); - populateAndAddCases("LEDirectByteBuffer", ByteBuffer.allocateDirect(SIZE*2).order(ByteOrder.LITTLE_ENDIAN).asCharBuffer(), args); + populateAndAddCases("HeapByteBuffer BE", + ByteBuffer.allocate(SIZE * 2).order(ByteOrder.BIG_ENDIAN).asCharBuffer(), args); + populateAndAddCases("HeapByteBuffer LE", + ByteBuffer.allocate(SIZE * 2).order(ByteOrder.LITTLE_ENDIAN).asCharBuffer(), args); + populateAndAddCases("DirectByteBuffer BE", + ByteBuffer.allocateDirect(SIZE * 2).order(ByteOrder.BIG_ENDIAN).asCharBuffer(), args); + populateAndAddCases("DirectByteBuffer LE", + ByteBuffer.allocateDirect(SIZE * 2).order(ByteOrder.LITTLE_ENDIAN).asCharBuffer(), args); char[] randomChars = randomChars(); CharBuffer cb = CharBuffer.wrap(randomChars); @@ -105,81 +174,6 @@ public class CharBufferAsCharSequenceTest { addCases("StringCharBuffer over String", randomChars, CharBuffer.wrap(new String(randomChars)), args); - // nothing magic about 1273, it is just larger than 1k and an odd number - eliminating any alignment assumptions - char[] buf = new char[1273]; - for (int i = 0; i < buf.length; ++i) { - buf[i] = (char) i; - } - String stringBuf = new String(buf); - - // nothing magic about 7, it is simply an odd number to advance - making sure no expectations of alignment - // comparing to 29 results in 5 loops (0, 7, 14, 21, 28), giving decent coverage of offset and limits - for (int i = 0; i < 29; i += 7) { - CharBuffer buffer = CharBuffer.wrap(buf, i, buf.length - i); - args.add(Arguments.of(buffer, buf, i, buf.length, "HeapCharBuffer index " + i + " to end")); - args.add(Arguments.of(buffer.slice(), buf, i, buf.length, "HeapCharBuffer slice " + i + " to end")); - - args.add(Arguments.of(CharBuffer.wrap(new String(buf, i, buf.length - i)), buf, i, buf.length, - "StringCharBuffer index " + i + " to end")); - buffer = CharBuffer.wrap(stringBuf); - buffer.position(i); - args.add(Arguments.of(buffer.slice(), buf, i, buf.length, "StringCharBuffer slice " + i + " to end")); - - CharBuffer lehbbAsCB = ByteBuffer.allocate(buf.length * 2) - .order(ByteOrder.LITTLE_ENDIAN) - .asCharBuffer() - .put(buf) - .position(i); - args.add(Arguments.of(lehbbAsCB, buf, i, buf.length, "LE HeapByteBuffer as CharBuffer index " + i + " to end")); - - CharBuffer behbdAsCB = ByteBuffer.allocateDirect(buf.length * 2) - .order(ByteOrder.BIG_ENDIAN) - .asCharBuffer() - .put(buf) - .position(i); - args.add(Arguments.of(behbdAsCB, buf, i, buf.length, - "BE DirectByteBuffer as CharBuffer index " + i + " to end")); - - if (i > 0) { - buffer = CharBuffer.wrap(buf, 1, buf.length - 1).slice(); - buffer.position(i - 1); - args.add(Arguments.of(buffer, buf, i, buf.length, - "HeapCharBuffer slice/offset 1 index " + (i - 1) + " to end")); - - int end = buf.length - i; - - buffer = CharBuffer.wrap(buf, i, buf.length - (2 * i)); - args.add(Arguments.of(buffer, buf, i, end, "HeapCharBuffer index " + i + " to " + end)); - args.add(Arguments.of(buffer.slice(), buf, i, end, "HeapCharBuffer slice " + i + " to " + end)); - - args.add(Arguments.of(CharBuffer.wrap(new String(buf, i, buf.length - (2 * i))), buf, i, end, - "StringCharBuffer index " + i + " to " + end)); - buffer = CharBuffer.wrap(stringBuf); - buffer.position(i); - buffer.limit(end); - args.add(Arguments.of(buffer.slice(), buf, i, end, "StringCharBuffer slice " + i + " to " + end)); - - CharBuffer behbbAsCB = ByteBuffer.allocate(buf.length * 2) - .order(ByteOrder.BIG_ENDIAN) - .asCharBuffer() - .put(buf) - .position(1) - .slice() - .position(i - 1) - .limit(end - 1); - args.add(Arguments.of(behbbAsCB, buf, i, buf.length - i, "BE HeapByteBuffer as CharBuffer index " + i + " to " + end)); - - CharBuffer ledbbAsCB = ByteBuffer.allocateDirect(buf.length * 2) - .order(ByteOrder.LITTLE_ENDIAN) - .asCharBuffer() - .put(buf) - .position(1) - .slice() - .position(i - 1) - .limit(end - 1); - args.add(Arguments.of(ledbbAsCB, buf, i, buf.length - i, "LE DirectByteBuffer as CharBuffer index " + i + " to " + end)); - } - } return args; } @@ -224,42 +218,42 @@ public class CharBufferAsCharSequenceTest { @ParameterizedTest(name="{4}") @MethodSource("charBufferArguments") - void testGetCharsNegativeSourceBeg(CharSequence actual, char[] expected, int start, int stop, String description) { + void testGetCharsNegativeSrcBegin(CharSequence actual, char[] expected, int start, int stop, String description) { char[] val = new char[16]; assertThrows(IndexOutOfBoundsException.class, () -> actual.getChars(-1, 4, val, 1)); } @ParameterizedTest(name="{4}") @MethodSource("charBufferArguments") - void testGetCharsNegativeSourceEnd(CharSequence actual, char[] expected, int start, int stop, String description) { + void testGetCharsNegativeSrcEnd(CharSequence actual, char[] expected, int start, int stop, String description) { char[] val = new char[16]; assertThrows(IndexOutOfBoundsException.class, () -> actual.getChars(0, -4, val, 1)); } @ParameterizedTest(name="{4}") @MethodSource("charBufferArguments") - void testGetCharsSourceEndBeforeBeg(CharSequence actual, char[] expected, int start, int stop, String description) { + void testGetCharsSrcEndBeforeBegin(CharSequence actual, char[] expected, int start, int stop, String description) { char[] val = new char[16]; assertThrows(IndexOutOfBoundsException.class, () -> actual.getChars(3, 2, val, 1)); } @ParameterizedTest(name="{4}") @MethodSource("charBufferArguments") - void testGetCharsNegativeDestBeg(CharSequence actual, char[] expected, int start, int stop, String description) { + void testGetCharsNegativeDstBegin(CharSequence actual, char[] expected, int start, int stop, String description) { char[] val = new char[16]; assertThrows(IndexOutOfBoundsException.class, () -> actual.getChars(1, 3, val, -1)); } @ParameterizedTest(name="{4}") @MethodSource("charBufferArguments") - void testGetCharsDestBegOOB(CharSequence actual, char[] expected, int start, int stop, String description) { + void testGetCharsDstBeginOOB(CharSequence actual, char[] expected, int start, int stop, String description) { char[] val = new char[16]; assertThrows(IndexOutOfBoundsException.class, () -> actual.getChars(1, 4, val, val.length + 1)); } @ParameterizedTest(name="{4}") @MethodSource("charBufferArguments") - void testGetCharsDestLengthOOB(CharSequence actual, char[] expected, int start, int stop, String description) { + void testGetCharsDstLengthOOB(CharSequence actual, char[] expected, int start, int stop, String description) { char[] val = new char[16]; assertThrows(IndexOutOfBoundsException.class, () -> actual.getChars(1, 4, val, val.length - 2)); } From c0e6ffabc216279068ab887939028ca27f5143f2 Mon Sep 17 00:00:00 2001 From: Liam Miller-Cushon Date: Thu, 7 Aug 2025 19:43:45 +0000 Subject: [PATCH 055/580] 8364954: (bf) CleaningThread should be InnocuousThread Reviewed-by: rriggs, alanb --- .../share/classes/java/nio/BufferCleaner.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/java.base/share/classes/java/nio/BufferCleaner.java b/src/java.base/share/classes/java/nio/BufferCleaner.java index ddacecbcf63..f8ee76c4689 100644 --- a/src/java.base/share/classes/java/nio/BufferCleaner.java +++ b/src/java.base/share/classes/java/nio/BufferCleaner.java @@ -29,6 +29,7 @@ import java.lang.ref.PhantomReference; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.util.Objects; +import jdk.internal.misc.InnocuousThread; import sun.nio.Cleaner; /** @@ -200,8 +201,9 @@ class BufferCleaner { } } - private static final class CleaningThread extends Thread { - public CleaningThread() {} + + private static final class CleaningRunnable implements Runnable { + public CleaningRunnable() {} @Override public void run() { @@ -234,14 +236,14 @@ class BufferCleaner { private static final CleanerList cleanerList = new CleanerList(); private static final ReferenceQueue queue = new ReferenceQueue(); - private static CleaningThread cleaningThread = null; + private static Thread cleaningThread; private static void startCleaningThreadIfNeeded() { synchronized (cleanerList) { if (cleaningThread != null) { return; } - cleaningThread = new CleaningThread(); + cleaningThread = InnocuousThread.newThread(new CleaningRunnable()); } cleaningThread.setDaemon(true); cleaningThread.start(); From 244e6293c3b332105658900639a9f3db7b21a9fe Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Thu, 7 Aug 2025 19:55:41 +0000 Subject: [PATCH 056/580] 8364984: Many jpackage tests are failing on Linux after JDK-8334238 Reviewed-by: almatvee --- .../tools/jpackage/helpers/jdk/jpackage/test/LinuxHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LinuxHelper.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LinuxHelper.java index b99d2bdeceb..e8f6273b18b 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LinuxHelper.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LinuxHelper.java @@ -455,7 +455,7 @@ public final class LinuxHelper { var unpackedDir = cmd.appLayout().desktopIntegrationDirectory(); var packageDir = cmd.pathToPackageFile(unpackedDir); return getPackageFiles(cmd).filter(path -> { - return path.getParent().equals(packageDir) && path.getFileName().toString().endsWith(".desktop"); + return packageDir.equals(path.getParent()) && path.getFileName().toString().endsWith(".desktop"); }).map(Path::getFileName).map(unpackedDir::resolve).toList(); } From b8acbc3ed8675ad4cc4b9dea69ee1e87c2a2ca45 Mon Sep 17 00:00:00 2001 From: Ayush Rigal Date: Thu, 7 Aug 2025 21:11:26 +0000 Subject: [PATCH 057/580] 8364315: Remove unused xml files from test/jaxp/javax/xml/jaxp/functional/javax/xml/transform/xmlfiles Reviewed-by: jpai, joehw --- .../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 e7ea712cdd9..00000000000 --- 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 d4a9d98475f..00000000000 --- 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 789983f79f3..00000000000 --- 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 f4b4241dd39..00000000000 --- 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 c71be802b530034169d17325478dba6e2f1c3238 Mon Sep 17 00:00:00 2001 From: Harshitha Onkar Date: Thu, 7 Aug 2025 21:19:47 +0000 Subject: [PATCH 058/580] 8361748: Enforce limits on the size of an XBM image Reviewed-by: prr, jdv --- .../sun/awt/image/XbmImageDecoder.java | 205 ++++++++++-------- .../awt/image/XBMDecoder/XBMDecoderTest.java | 77 +++++++ .../jdk/java/awt/image/XBMDecoder/invalid.xbm | 2 + .../java/awt/image/XBMDecoder/invalid_hex.xbm | 3 + .../java/awt/image/XBMDecoder/invalid_ht.xbm | 3 + test/jdk/java/awt/image/XBMDecoder/valid.xbm | 6 + .../java/awt/image/XBMDecoder/valid_hex.xbm | 4 + 7 files changed, 212 insertions(+), 88 deletions(-) create mode 100644 test/jdk/java/awt/image/XBMDecoder/XBMDecoderTest.java create mode 100644 test/jdk/java/awt/image/XBMDecoder/invalid.xbm create mode 100644 test/jdk/java/awt/image/XBMDecoder/invalid_hex.xbm create mode 100644 test/jdk/java/awt/image/XBMDecoder/invalid_ht.xbm create mode 100644 test/jdk/java/awt/image/XBMDecoder/valid.xbm create mode 100644 test/jdk/java/awt/image/XBMDecoder/valid_hex.xbm diff --git a/src/java.desktop/share/classes/sun/awt/image/XbmImageDecoder.java b/src/java.desktop/share/classes/sun/awt/image/XbmImageDecoder.java index 03b36b3b819..cac9f8baab2 100644 --- a/src/java.desktop/share/classes/sun/awt/image/XbmImageDecoder.java +++ b/src/java.desktop/share/classes/sun/awt/image/XbmImageDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1995, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1995, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,13 +23,22 @@ * questions. */ -/*- +/* * Reads xbitmap format images into a DIBitmap structure. */ package sun.awt.image; -import java.io.*; -import java.awt.image.*; +import java.awt.image.ImageConsumer; +import java.awt.image.IndexColorModel; +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static java.lang.Math.multiplyExact; /** * Parse files of the form: @@ -50,6 +59,8 @@ public class XbmImageDecoder extends ImageDecoder { ImageConsumer.COMPLETESCANLINES | ImageConsumer.SINGLEPASS | ImageConsumer.SINGLEFRAME); + private static final int MAX_XBM_SIZE = 16384; + private static final int HEADER_SCAN_LIMIT = 100; public XbmImageDecoder(InputStreamImageSource src, InputStream is) { super(src, is); @@ -72,107 +83,125 @@ public class XbmImageDecoder extends ImageDecoder { * produce an image from the stream. */ public void produceImage() throws IOException, ImageFormatException { - char[] nm = new char[80]; - int c; - int i = 0; - int state = 0; int H = 0; int W = 0; int x = 0; int y = 0; - boolean start = true; + int n = 0; + int state = 0; byte[] raster = null; IndexColorModel model = null; - while (!aborted && (c = input.read()) != -1) { - if ('a' <= c && c <= 'z' || - 'A' <= c && c <= 'Z' || - '0' <= c && c <= '9' || c == '#' || c == '_') { - if (i < 78) - nm[i++] = (char) c; - } else if (i > 0) { - int nc = i; - i = 0; - if (start) { - if (nc != 7 || - nm[0] != '#' || - nm[1] != 'd' || - nm[2] != 'e' || - nm[3] != 'f' || - nm[4] != 'i' || - nm[5] != 'n' || - nm[6] != 'e') - { - error("Not an XBM file"); + + String matchRegex = "(0[xX])?[0-9a-fA-F]+[\\s+]?[,|};]"; + String replaceRegex = "(0[xX])|,|[\\s+]|[};]"; + + String line; + int lineNum = 0; + + try (BufferedReader br = new BufferedReader(new InputStreamReader(input))) { + // loop to process XBM header - width, height and create raster + while (!aborted && (line = br.readLine()) != null + && lineNum <= HEADER_SCAN_LIMIT) { + lineNum++; + // process #define stmts + if (line.trim().startsWith("#define")) { + String[] token = line.split("\\s+"); + if (token.length != 3) { + error("Error while parsing define statement"); + } + try { + if (!token[2].isBlank() && state == 0) { + W = Integer.parseInt(token[2]); + state = 1; // after width is set + } else if (!token[2].isBlank() && state == 1) { + H = Integer.parseInt(token[2]); + state = 2; // after height is set + } + } catch (NumberFormatException nfe) { + // parseInt() can throw NFE + error("Error while parsing width or height."); } - start = false; } - if (nm[nc - 1] == 'h') - state = 1; /* expecting width */ - else if (nm[nc - 1] == 't' && nc > 1 && nm[nc - 2] == 'h') - state = 2; /* expecting height */ - else if (nc > 2 && state < 0 && nm[0] == '0' && nm[1] == 'x') { - int n = 0; - for (int p = 2; p < nc; p++) { - c = nm[p]; - if ('0' <= c && c <= '9') - c = c - '0'; - else if ('A' <= c && c <= 'Z') - c = c - 'A' + 10; - else if ('a' <= c && c <= 'z') - c = c - 'a' + 10; - else - c = 0; - n = n * 16 + c; + + if (state == 2) { + if (W <= 0 || H <= 0) { + error("Invalid values for width or height."); } - for (int mask = 1; mask <= 0x80; mask <<= 1) { - if (x < W) { - if ((n & mask) != 0) - raster[x] = 1; - else - raster[x] = 0; - } - x++; + if (multiplyExact(W, H) > MAX_XBM_SIZE) { + error("Large XBM file size." + + " Maximum allowed size: " + MAX_XBM_SIZE); } - if (x >= W) { - if (setPixels(0, y, W, 1, model, raster, 0, W) <= 0) { - return; + model = new IndexColorModel(8, 2, XbmColormap, + 0, false, 0); + setDimensions(W, H); + setColorModel(model); + setHints(XbmHints); + headerComplete(); + raster = new byte[W]; + state = 3; + break; + } + } + + if (state != 3) { + error("Width or Height of XBM file not defined"); + } + + // loop to process image data + while (!aborted && (line = br.readLine()) != null) { + lineNum++; + + if (line.contains("[]")) { + Matcher matcher = Pattern.compile(matchRegex).matcher(line); + while (matcher.find()) { + if (y >= H) { + error("Scan size of XBM file exceeds" + + " the defined width x height"); } - x = 0; - if (y++ >= H) { - break; + + int startIndex = matcher.start(); + int endIndex = matcher.end(); + String hexByte = line.substring(startIndex, endIndex); + + if (!(hexByte.startsWith("0x") + || hexByte.startsWith("0X"))) { + error("Invalid hexadecimal number at Ln#:" + lineNum + + " Col#:" + (startIndex + 1)); } - } - } else { - int n = 0; - for (int p = 0; p < nc; p++) - if ('0' <= (c = nm[p]) && c <= '9') - n = n * 10 + c - '0'; - else { - n = -1; - break; + hexByte = hexByte.replaceAll(replaceRegex, ""); + if (hexByte.length() != 2) { + error("Invalid hexadecimal number at Ln#:" + lineNum + + " Col#:" + (startIndex + 1)); } - if (n > 0 && state > 0) { - if (state == 1) - W = n; - else - H = n; - if (W == 0 || H == 0) - state = 0; - else { - model = new IndexColorModel(8, 2, XbmColormap, - 0, false, 0); - setDimensions(W, H); - setColorModel(model); - setHints(XbmHints); - headerComplete(); - raster = new byte[W]; - state = -1; + + try { + n = Integer.parseInt(hexByte, 16); + } catch (NumberFormatException nfe) { + error("Error parsing hexadecimal at Ln#:" + lineNum + + " Col#:" + (startIndex + 1)); + } + for (int mask = 1; mask <= 0x80; mask <<= 1) { + if (x < W) { + if ((n & mask) != 0) + raster[x] = 1; + else + raster[x] = 0; + } + x++; + } + + if (x >= W) { + int result = setPixels(0, y, W, 1, model, raster, 0, W); + if (result <= 0) { + error("Unexpected error occurred during setPixel()"); + } + x = 0; + y++; } } } } + imageComplete(ImageConsumer.STATICIMAGEDONE, true); } - input.close(); - imageComplete(ImageConsumer.STATICIMAGEDONE, true); } } diff --git a/test/jdk/java/awt/image/XBMDecoder/XBMDecoderTest.java b/test/jdk/java/awt/image/XBMDecoder/XBMDecoderTest.java new file mode 100644 index 00000000000..19bc6d95c39 --- /dev/null +++ b/test/jdk/java/awt/image/XBMDecoder/XBMDecoderTest.java @@ -0,0 +1,77 @@ +/* + * 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 8361748 + * @summary Tests XBM image size limits and if XBMImageDecoder.produceImage() + * throws appropriate error when parsing invalid XBM image data. + * @run main XBMDecoderTest + */ + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.PrintStream; +import javax.swing.ImageIcon; + +public class XBMDecoderTest { + + public static void main(String[] args) throws Exception { + String dir = System.getProperty("test.src"); + PrintStream originalErr = System.err; + boolean validCase; + + File currentDir = new File(dir); + File[] files = currentDir.listFiles((File d, String s) + -> s.endsWith(".xbm")); + + for (File file : files) { + String fileName = file.getName(); + validCase = fileName.startsWith("valid"); + + System.out.println("--- Testing " + fileName + " ---"); + try (FileInputStream fis = new FileInputStream(file); + ByteArrayOutputStream errContent = new ByteArrayOutputStream()) { + System.setErr(new PrintStream(errContent)); + + ImageIcon icon = new ImageIcon(fis.readAllBytes()); + boolean isErrEmpty = errContent.toString().isEmpty(); + if (!isErrEmpty) { + System.out.println("Expected ImageFormatException occurred."); + System.out.print(errContent); + } + + if (validCase && !isErrEmpty) { + throw new RuntimeException("Test failed: Error stream not empty"); + } else if (!validCase && isErrEmpty) { + throw new RuntimeException("Test failed: ImageFormatException" + + " expected but not thrown"); + } + System.out.println("PASSED\n"); + } finally { + System.setErr(originalErr); + } + } + } +} diff --git a/test/jdk/java/awt/image/XBMDecoder/invalid.xbm b/test/jdk/java/awt/image/XBMDecoder/invalid.xbm new file mode 100644 index 00000000000..8a8cfc27632 --- /dev/null +++ b/test/jdk/java/awt/image/XBMDecoder/invalid.xbm @@ -0,0 +1,2 @@ +#define k_ht 3 +h` k[] = { 01x0, 42222222222236319330:: diff --git a/test/jdk/java/awt/image/XBMDecoder/invalid_hex.xbm b/test/jdk/java/awt/image/XBMDecoder/invalid_hex.xbm new file mode 100644 index 00000000000..c6f819582d0 --- /dev/null +++ b/test/jdk/java/awt/image/XBMDecoder/invalid_hex.xbm @@ -0,0 +1,3 @@ +#define k_wt 16 +#define k_ht 1 +k[] = { 0x10, 1234567890}; diff --git a/test/jdk/java/awt/image/XBMDecoder/invalid_ht.xbm b/test/jdk/java/awt/image/XBMDecoder/invalid_ht.xbm new file mode 100644 index 00000000000..5244651a4cb --- /dev/null +++ b/test/jdk/java/awt/image/XBMDecoder/invalid_ht.xbm @@ -0,0 +1,3 @@ +#define k_wt 16 +#define k_ht 0 +k[] = { 0x10, 0x12}; diff --git a/test/jdk/java/awt/image/XBMDecoder/valid.xbm b/test/jdk/java/awt/image/XBMDecoder/valid.xbm new file mode 100644 index 00000000000..23b57b2c811 --- /dev/null +++ b/test/jdk/java/awt/image/XBMDecoder/valid.xbm @@ -0,0 +1,6 @@ +#define test_width 16 +#define test_height 3 +#define ht_x 1 +#define ht_y 2 +static unsigned char test_bits[] = { +0x13, 0x11, 0x15, 0x00, 0xAB, 0xcd }; diff --git a/test/jdk/java/awt/image/XBMDecoder/valid_hex.xbm b/test/jdk/java/awt/image/XBMDecoder/valid_hex.xbm new file mode 100644 index 00000000000..e365d802447 --- /dev/null +++ b/test/jdk/java/awt/image/XBMDecoder/valid_hex.xbm @@ -0,0 +1,4 @@ +#define test_width 16 +#define test_height 2 +static unsigned char test_bits[] = { 0x13, 0x11, + 0xAB, 0xff }; From 4c9eaddaef83c6ba30e27ae3e0d16caeeec206cb Mon Sep 17 00:00:00 2001 From: John Jiang Date: Fri, 8 Aug 2025 02:27:30 +0000 Subject: [PATCH 059/580] 8364597: Replace THL A29 Limited with Tencent Reviewed-by: jiefu --- src/hotspot/cpu/x86/stubGenerator_x86_64_exp.cpp | 2 +- src/hotspot/cpu/x86/stubGenerator_x86_64_log.cpp | 2 +- src/hotspot/cpu/x86/stubGenerator_x86_64_pow.cpp | 2 +- src/hotspot/share/gc/shenandoah/c2/shenandoahSupport.cpp | 2 +- src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp | 2 +- .../gc/shenandoah/shenandoahGenerationalControlThread.cpp | 2 +- .../arraycopy/TestIllegalArrayCopyBeforeInfiniteLoop.java | 2 +- .../jtreg/compiler/arraycopy/TestNegArrayLengthAsIndex1.java | 2 +- .../jtreg/compiler/arraycopy/TestNegArrayLengthAsIndex2.java | 2 +- .../compiler/arraycopy/TestNegativeArrayCopyAfterLoop.java | 2 +- test/hotspot/jtreg/compiler/c1/TestRangeCheckEliminated.java | 2 +- .../jtreg/compiler/c2/TestDuplicateSimpleLoopBackedge.java | 2 +- test/hotspot/jtreg/compiler/c2/cr6865031/Test.java | 2 +- .../compiler/c2/irTests/TestAutoVectorization2DArray.java | 2 +- .../compiler/compilercontrol/TestConflictInlineCommands.java | 2 +- test/hotspot/jtreg/compiler/debug/TraceIterativeGVN.java | 2 +- .../jtreg/compiler/intrinsics/math/TestPow0Dot5Opt.java | 2 +- test/hotspot/jtreg/compiler/intrinsics/math/TestPow2Opt.java | 2 +- .../sha/cli/TestUseSHA3IntrinsicsOptionOnSupportedCPU.java | 2 +- .../sha/cli/TestUseSHA3IntrinsicsOptionOnUnsupportedCPU.java | 2 +- .../compiler/jvmci/errors/TestInvalidTieredStopAtLevel.java | 2 +- .../jtreg/compiler/loopopts/TestLoopEndNodeEliminate.java | 2 +- test/hotspot/jtreg/compiler/loopopts/TestLoopPredicateDep.java | 2 +- .../jtreg/compiler/loopopts/TestSkeletonPredicateNegation.java | 2 +- .../jtreg/compiler/oracle/TestInvalidCompileCommand.java | 2 +- test/hotspot/jtreg/compiler/print/TestTraceOptoParse.java | 2 +- .../jtreg/compiler/regalloc/TestGCMRecalcPressureNodes.java | 3 +-- .../jtreg/compiler/unsafe/TestMisalignedUnsafeAccess.java | 2 +- .../hotspot/jtreg/compiler/vectorapi/TestIntrinsicBailOut.java | 2 +- .../hotspot/jtreg/compiler/vectorapi/TestVectorErgonomics.java | 2 +- .../jtreg/compiler/vectorapi/VectorReinterpretTest.java | 2 +- .../jtreg/containers/docker/TestMemoryWithCgroupV1.java | 2 +- test/hotspot/jtreg/gc/arguments/TestG1CompressedOops.java | 2 +- .../hotspot/jtreg/runtime/cds/appcds/FillerObjectLoadTest.java | 2 +- test/jdk/java/lang/Thread/virtual/ParkWithFixedThreadPool.java | 2 +- test/jdk/javax/net/ssl/DTLS/DTLSNamedGroups.java | 3 +-- test/jdk/javax/net/ssl/DTLS/DTLSSignatureSchemes.java | 3 +-- .../javax/net/ssl/SSLException/CheckSSLHandshakeException.java | 2 +- test/jdk/javax/net/ssl/SSLException/CheckSSLKeyException.java | 2 +- .../net/ssl/SSLException/CheckSSLPeerUnverifiedException.java | 2 +- .../javax/net/ssl/SSLException/CheckSSLProtocolException.java | 2 +- test/jdk/javax/net/ssl/SSLParameters/NamedGroups.java | 2 +- test/jdk/javax/net/ssl/SSLParameters/NamedGroupsSpec.java | 2 +- test/jdk/javax/net/ssl/SSLParameters/SignatureSchemes.java | 2 +- test/jdk/javax/net/ssl/ServerName/EndingDotHostname.java | 3 +-- test/jdk/javax/net/ssl/templates/SSLExampleCert.java | 2 +- .../auth/callback/PasswordCallback/CheckCleanerBound.java | 3 +-- .../auth/callback/PasswordCallback/PasswordCleanup.java | 3 +-- .../jdk/jdk/internal/platform/docker/GetFreeSwapSpaceSize.java | 2 +- .../jdk/internal/platform/docker/TestGetFreeSwapSpaceSize.java | 2 +- test/jdk/sun/security/ec/ECDHKeyAgreementParamValidation.java | 2 +- test/jdk/sun/security/jgss/GssContextCleanup.java | 3 +-- test/jdk/sun/security/jgss/GssNameCleanup.java | 3 +-- .../security/ssl/SignatureScheme/SigAlgosExtTestWithTLS12.java | 2 +- .../security/ssl/SignatureScheme/SigAlgosExtTestWithTLS13.java | 2 +- test/micro/org/openjdk/bench/java/security/Signatures.java | 3 +-- .../openjdk/bench/vm/compiler/AutoVectorization2DArray.java | 2 +- test/micro/org/openjdk/bench/vm/compiler/LoopUnroll.java | 2 +- 58 files changed, 58 insertions(+), 67 deletions(-) diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64_exp.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_exp.cpp index 299a37b88fd..5130fd2c9d2 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64_exp.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_exp.cpp @@ -1,6 +1,6 @@ /* * Copyright (c) 2016, 2025, Intel Corporation. All rights reserved. -* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. +* Copyright (C) 2021, Tencent. All rights reserved. * Intel Math Library (LIBM) Source Code * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64_log.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_log.cpp index 8665aec5903..6b5b4d704e3 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64_log.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_log.cpp @@ -1,6 +1,6 @@ /* * Copyright (c) 2016, 2025, Intel Corporation. All rights reserved. -* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. +* Copyright (C) 2021, Tencent. All rights reserved. * Intel Math Library (LIBM) Source Code * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64_pow.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_pow.cpp index 22ac8059458..3c3df7e6ac4 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64_pow.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_pow.cpp @@ -1,6 +1,6 @@ /* * Copyright (c) 2016, 2025, Intel Corporation. All rights reserved. -* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. +* Copyright (C) 2021, Tencent. All rights reserved. * Intel Math Library (LIBM) Source Code * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. diff --git a/src/hotspot/share/gc/shenandoah/c2/shenandoahSupport.cpp b/src/hotspot/share/gc/shenandoah/c2/shenandoahSupport.cpp index 807d4197b12..8210718126b 100644 --- a/src/hotspot/share/gc/shenandoah/c2/shenandoahSupport.cpp +++ b/src/hotspot/share/gc/shenandoah/c2/shenandoahSupport.cpp @@ -1,6 +1,6 @@ /* * Copyright (c) 2015, 2021, Red Hat, Inc. All rights reserved. - * Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2022, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp index 0c80b800ac5..6290101bc49 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp @@ -1,6 +1,6 @@ /* * Copyright (c) 2013, 2021, Red Hat, Inc. All rights reserved. - * Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2022, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp index f5de41cb9ca..1cf8b78ef1a 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp @@ -1,6 +1,6 @@ /* * Copyright (c) 2013, 2021, Red Hat, Inc. All rights reserved. - * Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2022, Tencent. All rights reserved. * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * diff --git a/test/hotspot/jtreg/compiler/arraycopy/TestIllegalArrayCopyBeforeInfiniteLoop.java b/test/hotspot/jtreg/compiler/arraycopy/TestIllegalArrayCopyBeforeInfiniteLoop.java index 0b3572d6afa..af166571909 100644 --- a/test/hotspot/jtreg/compiler/arraycopy/TestIllegalArrayCopyBeforeInfiniteLoop.java +++ b/test/hotspot/jtreg/compiler/arraycopy/TestIllegalArrayCopyBeforeInfiniteLoop.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2021, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/hotspot/jtreg/compiler/arraycopy/TestNegArrayLengthAsIndex1.java b/test/hotspot/jtreg/compiler/arraycopy/TestNegArrayLengthAsIndex1.java index 2f35a11e948..b7da2119bd3 100644 --- a/test/hotspot/jtreg/compiler/arraycopy/TestNegArrayLengthAsIndex1.java +++ b/test/hotspot/jtreg/compiler/arraycopy/TestNegArrayLengthAsIndex1.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2021, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/hotspot/jtreg/compiler/arraycopy/TestNegArrayLengthAsIndex2.java b/test/hotspot/jtreg/compiler/arraycopy/TestNegArrayLengthAsIndex2.java index da8f0936cae..b101254d189 100644 --- a/test/hotspot/jtreg/compiler/arraycopy/TestNegArrayLengthAsIndex2.java +++ b/test/hotspot/jtreg/compiler/arraycopy/TestNegArrayLengthAsIndex2.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2021, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/hotspot/jtreg/compiler/arraycopy/TestNegativeArrayCopyAfterLoop.java b/test/hotspot/jtreg/compiler/arraycopy/TestNegativeArrayCopyAfterLoop.java index e28cfb871f7..901ff78347d 100644 --- a/test/hotspot/jtreg/compiler/arraycopy/TestNegativeArrayCopyAfterLoop.java +++ b/test/hotspot/jtreg/compiler/arraycopy/TestNegativeArrayCopyAfterLoop.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2021, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/hotspot/jtreg/compiler/c1/TestRangeCheckEliminated.java b/test/hotspot/jtreg/compiler/c1/TestRangeCheckEliminated.java index 9da8a5390da..0f5b8860195 100644 --- a/test/hotspot/jtreg/compiler/c1/TestRangeCheckEliminated.java +++ b/test/hotspot/jtreg/compiler/c1/TestRangeCheckEliminated.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2021, Tencent. All rights reserved. * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * diff --git a/test/hotspot/jtreg/compiler/c2/TestDuplicateSimpleLoopBackedge.java b/test/hotspot/jtreg/compiler/c2/TestDuplicateSimpleLoopBackedge.java index 0b102b4c9fd..414a351e53f 100644 --- a/test/hotspot/jtreg/compiler/c2/TestDuplicateSimpleLoopBackedge.java +++ b/test/hotspot/jtreg/compiler/c2/TestDuplicateSimpleLoopBackedge.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (c) 2022, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/hotspot/jtreg/compiler/c2/cr6865031/Test.java b/test/hotspot/jtreg/compiler/c2/cr6865031/Test.java index af23628577b..beb41943183 100644 --- a/test/hotspot/jtreg/compiler/c2/cr6865031/Test.java +++ b/test/hotspot/jtreg/compiler/c2/cr6865031/Test.java @@ -1,6 +1,6 @@ /* * Copyright 2009 Goldman Sachs International. All Rights Reserved. - * Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2022, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/hotspot/jtreg/compiler/c2/irTests/TestAutoVectorization2DArray.java b/test/hotspot/jtreg/compiler/c2/irTests/TestAutoVectorization2DArray.java index ac622e3bcc3..5b1d6f51bb3 100644 --- a/test/hotspot/jtreg/compiler/c2/irTests/TestAutoVectorization2DArray.java +++ b/test/hotspot/jtreg/compiler/c2/irTests/TestAutoVectorization2DArray.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2022, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/hotspot/jtreg/compiler/compilercontrol/TestConflictInlineCommands.java b/test/hotspot/jtreg/compiler/compilercontrol/TestConflictInlineCommands.java index 9c12ea6b8a7..c7e20157e7e 100644 --- a/test/hotspot/jtreg/compiler/compilercontrol/TestConflictInlineCommands.java +++ b/test/hotspot/jtreg/compiler/compilercontrol/TestConflictInlineCommands.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2021, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/hotspot/jtreg/compiler/debug/TraceIterativeGVN.java b/test/hotspot/jtreg/compiler/debug/TraceIterativeGVN.java index 59d3de0d58e..8e6169f07dc 100644 --- a/test/hotspot/jtreg/compiler/debug/TraceIterativeGVN.java +++ b/test/hotspot/jtreg/compiler/debug/TraceIterativeGVN.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. - * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2021, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/hotspot/jtreg/compiler/intrinsics/math/TestPow0Dot5Opt.java b/test/hotspot/jtreg/compiler/intrinsics/math/TestPow0Dot5Opt.java index a0560870d2a..9854eb8c386 100644 --- a/test/hotspot/jtreg/compiler/intrinsics/math/TestPow0Dot5Opt.java +++ b/test/hotspot/jtreg/compiler/intrinsics/math/TestPow0Dot5Opt.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2021, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/hotspot/jtreg/compiler/intrinsics/math/TestPow2Opt.java b/test/hotspot/jtreg/compiler/intrinsics/math/TestPow2Opt.java index 2bf48407a36..db05baf9683 100644 --- a/test/hotspot/jtreg/compiler/intrinsics/math/TestPow2Opt.java +++ b/test/hotspot/jtreg/compiler/intrinsics/math/TestPow2Opt.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2021, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/hotspot/jtreg/compiler/intrinsics/sha/cli/TestUseSHA3IntrinsicsOptionOnSupportedCPU.java b/test/hotspot/jtreg/compiler/intrinsics/sha/cli/TestUseSHA3IntrinsicsOptionOnSupportedCPU.java index 3706f3abfd8..d3c0a4a8da7 100644 --- a/test/hotspot/jtreg/compiler/intrinsics/sha/cli/TestUseSHA3IntrinsicsOptionOnSupportedCPU.java +++ b/test/hotspot/jtreg/compiler/intrinsics/sha/cli/TestUseSHA3IntrinsicsOptionOnSupportedCPU.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2020, Huawei Technologies Co., Ltd. All rights reserved. - * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2021, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/hotspot/jtreg/compiler/intrinsics/sha/cli/TestUseSHA3IntrinsicsOptionOnUnsupportedCPU.java b/test/hotspot/jtreg/compiler/intrinsics/sha/cli/TestUseSHA3IntrinsicsOptionOnUnsupportedCPU.java index 633ceb1d9ab..c0045ae2922 100644 --- a/test/hotspot/jtreg/compiler/intrinsics/sha/cli/TestUseSHA3IntrinsicsOptionOnUnsupportedCPU.java +++ b/test/hotspot/jtreg/compiler/intrinsics/sha/cli/TestUseSHA3IntrinsicsOptionOnUnsupportedCPU.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2020, Huawei Technologies Co., Ltd. All rights reserved. - * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2021, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/hotspot/jtreg/compiler/jvmci/errors/TestInvalidTieredStopAtLevel.java b/test/hotspot/jtreg/compiler/jvmci/errors/TestInvalidTieredStopAtLevel.java index 3527dd891d6..0a00987f3d8 100644 --- a/test/hotspot/jtreg/compiler/jvmci/errors/TestInvalidTieredStopAtLevel.java +++ b/test/hotspot/jtreg/compiler/jvmci/errors/TestInvalidTieredStopAtLevel.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2020, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/hotspot/jtreg/compiler/loopopts/TestLoopEndNodeEliminate.java b/test/hotspot/jtreg/compiler/loopopts/TestLoopEndNodeEliminate.java index a4683db4f70..e3b5bdff12f 100644 --- a/test/hotspot/jtreg/compiler/loopopts/TestLoopEndNodeEliminate.java +++ b/test/hotspot/jtreg/compiler/loopopts/TestLoopEndNodeEliminate.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2021, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/hotspot/jtreg/compiler/loopopts/TestLoopPredicateDep.java b/test/hotspot/jtreg/compiler/loopopts/TestLoopPredicateDep.java index ca651a20750..bb945342c0d 100644 --- a/test/hotspot/jtreg/compiler/loopopts/TestLoopPredicateDep.java +++ b/test/hotspot/jtreg/compiler/loopopts/TestLoopPredicateDep.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2021, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/hotspot/jtreg/compiler/loopopts/TestSkeletonPredicateNegation.java b/test/hotspot/jtreg/compiler/loopopts/TestSkeletonPredicateNegation.java index 38a82139553..eae00ca3522 100644 --- a/test/hotspot/jtreg/compiler/loopopts/TestSkeletonPredicateNegation.java +++ b/test/hotspot/jtreg/compiler/loopopts/TestSkeletonPredicateNegation.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2021, Tencent. All rights reserved. * Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * diff --git a/test/hotspot/jtreg/compiler/oracle/TestInvalidCompileCommand.java b/test/hotspot/jtreg/compiler/oracle/TestInvalidCompileCommand.java index 791b1d042e3..a64e5f2b8bb 100644 --- a/test/hotspot/jtreg/compiler/oracle/TestInvalidCompileCommand.java +++ b/test/hotspot/jtreg/compiler/oracle/TestInvalidCompileCommand.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. - * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2021, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/hotspot/jtreg/compiler/print/TestTraceOptoParse.java b/test/hotspot/jtreg/compiler/print/TestTraceOptoParse.java index 65aa6bcb2d0..52a7aba1a7e 100644 --- a/test/hotspot/jtreg/compiler/print/TestTraceOptoParse.java +++ b/test/hotspot/jtreg/compiler/print/TestTraceOptoParse.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (c) 2022, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/hotspot/jtreg/compiler/regalloc/TestGCMRecalcPressureNodes.java b/test/hotspot/jtreg/compiler/regalloc/TestGCMRecalcPressureNodes.java index fe2fd7e4444..c99bdca2cb1 100644 --- a/test/hotspot/jtreg/compiler/regalloc/TestGCMRecalcPressureNodes.java +++ b/test/hotspot/jtreg/compiler/regalloc/TestGCMRecalcPressureNodes.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2021, Tencent. All rights reserved. * 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,4 +50,3 @@ public class TestGCMRecalcPressureNodes { } } } - diff --git a/test/hotspot/jtreg/compiler/unsafe/TestMisalignedUnsafeAccess.java b/test/hotspot/jtreg/compiler/unsafe/TestMisalignedUnsafeAccess.java index 84752a0ccf0..f4b1e3f0a53 100644 --- a/test/hotspot/jtreg/compiler/unsafe/TestMisalignedUnsafeAccess.java +++ b/test/hotspot/jtreg/compiler/unsafe/TestMisalignedUnsafeAccess.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2020, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/hotspot/jtreg/compiler/vectorapi/TestIntrinsicBailOut.java b/test/hotspot/jtreg/compiler/vectorapi/TestIntrinsicBailOut.java index 72e1c3aa30f..b6ddadad49b 100644 --- a/test/hotspot/jtreg/compiler/vectorapi/TestIntrinsicBailOut.java +++ b/test/hotspot/jtreg/compiler/vectorapi/TestIntrinsicBailOut.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021, 2022, THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2021, 2022, Tencent. All rights reserved. * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * diff --git a/test/hotspot/jtreg/compiler/vectorapi/TestVectorErgonomics.java b/test/hotspot/jtreg/compiler/vectorapi/TestVectorErgonomics.java index 4f9f8ca3bd2..0fd586f8d6e 100644 --- a/test/hotspot/jtreg/compiler/vectorapi/TestVectorErgonomics.java +++ b/test/hotspot/jtreg/compiler/vectorapi/TestVectorErgonomics.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2021, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/hotspot/jtreg/compiler/vectorapi/VectorReinterpretTest.java b/test/hotspot/jtreg/compiler/vectorapi/VectorReinterpretTest.java index b453311f857..2f9b97b56d7 100644 --- a/test/hotspot/jtreg/compiler/vectorapi/VectorReinterpretTest.java +++ b/test/hotspot/jtreg/compiler/vectorapi/VectorReinterpretTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2021, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/hotspot/jtreg/containers/docker/TestMemoryWithCgroupV1.java b/test/hotspot/jtreg/containers/docker/TestMemoryWithCgroupV1.java index f80a83842c9..3340f9de03c 100644 --- a/test/hotspot/jtreg/containers/docker/TestMemoryWithCgroupV1.java +++ b/test/hotspot/jtreg/containers/docker/TestMemoryWithCgroupV1.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2022, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/hotspot/jtreg/gc/arguments/TestG1CompressedOops.java b/test/hotspot/jtreg/gc/arguments/TestG1CompressedOops.java index 5a705ffe8ed..3aec113990c 100644 --- a/test/hotspot/jtreg/gc/arguments/TestG1CompressedOops.java +++ b/test/hotspot/jtreg/gc/arguments/TestG1CompressedOops.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. - * Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2025, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/hotspot/jtreg/runtime/cds/appcds/FillerObjectLoadTest.java b/test/hotspot/jtreg/runtime/cds/appcds/FillerObjectLoadTest.java index 79d377ca876..8538155375c 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/FillerObjectLoadTest.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/FillerObjectLoadTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2022, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/jdk/java/lang/Thread/virtual/ParkWithFixedThreadPool.java b/test/jdk/java/lang/Thread/virtual/ParkWithFixedThreadPool.java index 70b73884d92..2b23d866af4 100644 --- a/test/jdk/java/lang/Thread/virtual/ParkWithFixedThreadPool.java +++ b/test/jdk/java/lang/Thread/virtual/ParkWithFixedThreadPool.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2021, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/jdk/javax/net/ssl/DTLS/DTLSNamedGroups.java b/test/jdk/javax/net/ssl/DTLS/DTLSNamedGroups.java index e9165fa391c..1fc4f962675 100644 --- a/test/jdk/javax/net/ssl/DTLS/DTLSNamedGroups.java +++ b/test/jdk/javax/net/ssl/DTLS/DTLSNamedGroups.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2022, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -140,4 +140,3 @@ public class DTLSNamedGroups extends DTLSOverDatagram { } } } - diff --git a/test/jdk/javax/net/ssl/DTLS/DTLSSignatureSchemes.java b/test/jdk/javax/net/ssl/DTLS/DTLSSignatureSchemes.java index 5dd897b1bd7..9c1c64f7fa0 100644 --- a/test/jdk/javax/net/ssl/DTLS/DTLSSignatureSchemes.java +++ b/test/jdk/javax/net/ssl/DTLS/DTLSSignatureSchemes.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2022, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -134,4 +134,3 @@ public class DTLSSignatureSchemes extends DTLSOverDatagram { } } } - diff --git a/test/jdk/javax/net/ssl/SSLException/CheckSSLHandshakeException.java b/test/jdk/javax/net/ssl/SSLException/CheckSSLHandshakeException.java index 4c8aba3de44..4ba3e906597 100644 --- a/test/jdk/javax/net/ssl/SSLException/CheckSSLHandshakeException.java +++ b/test/jdk/javax/net/ssl/SSLException/CheckSSLHandshakeException.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2022, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/jdk/javax/net/ssl/SSLException/CheckSSLKeyException.java b/test/jdk/javax/net/ssl/SSLException/CheckSSLKeyException.java index dcd62fcf8e7..2d271236de1 100644 --- a/test/jdk/javax/net/ssl/SSLException/CheckSSLKeyException.java +++ b/test/jdk/javax/net/ssl/SSLException/CheckSSLKeyException.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2022, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/jdk/javax/net/ssl/SSLException/CheckSSLPeerUnverifiedException.java b/test/jdk/javax/net/ssl/SSLException/CheckSSLPeerUnverifiedException.java index 04184e99306..1d37271ed53 100644 --- a/test/jdk/javax/net/ssl/SSLException/CheckSSLPeerUnverifiedException.java +++ b/test/jdk/javax/net/ssl/SSLException/CheckSSLPeerUnverifiedException.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2022, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/jdk/javax/net/ssl/SSLException/CheckSSLProtocolException.java b/test/jdk/javax/net/ssl/SSLException/CheckSSLProtocolException.java index 3f62fac8f77..c1bd53e21e9 100644 --- a/test/jdk/javax/net/ssl/SSLException/CheckSSLProtocolException.java +++ b/test/jdk/javax/net/ssl/SSLException/CheckSSLProtocolException.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2022, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/jdk/javax/net/ssl/SSLParameters/NamedGroups.java b/test/jdk/javax/net/ssl/SSLParameters/NamedGroups.java index fc5001e89b8..25f73606b96 100644 --- a/test/jdk/javax/net/ssl/SSLParameters/NamedGroups.java +++ b/test/jdk/javax/net/ssl/SSLParameters/NamedGroups.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2022, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/jdk/javax/net/ssl/SSLParameters/NamedGroupsSpec.java b/test/jdk/javax/net/ssl/SSLParameters/NamedGroupsSpec.java index 9146e7b5f7b..0d910ccfb67 100644 --- a/test/jdk/javax/net/ssl/SSLParameters/NamedGroupsSpec.java +++ b/test/jdk/javax/net/ssl/SSLParameters/NamedGroupsSpec.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2022, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/jdk/javax/net/ssl/SSLParameters/SignatureSchemes.java b/test/jdk/javax/net/ssl/SSLParameters/SignatureSchemes.java index 7dadeff5703..7f41cb1af79 100644 --- a/test/jdk/javax/net/ssl/SSLParameters/SignatureSchemes.java +++ b/test/jdk/javax/net/ssl/SSLParameters/SignatureSchemes.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2022, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/jdk/javax/net/ssl/ServerName/EndingDotHostname.java b/test/jdk/javax/net/ssl/ServerName/EndingDotHostname.java index bac110aa033..5438f429455 100644 --- a/test/jdk/javax/net/ssl/ServerName/EndingDotHostname.java +++ b/test/jdk/javax/net/ssl/ServerName/EndingDotHostname.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2022, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -248,4 +248,3 @@ public class EndingDotHostname { sslIS.read(); } } - diff --git a/test/jdk/javax/net/ssl/templates/SSLExampleCert.java b/test/jdk/javax/net/ssl/templates/SSLExampleCert.java index 0b82aed3b7b..46f69a62e41 100644 --- a/test/jdk/javax/net/ssl/templates/SSLExampleCert.java +++ b/test/jdk/javax/net/ssl/templates/SSLExampleCert.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2022, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/jdk/javax/security/auth/callback/PasswordCallback/CheckCleanerBound.java b/test/jdk/javax/security/auth/callback/PasswordCallback/CheckCleanerBound.java index 8f68773398c..6d500fea656 100644 --- a/test/jdk/javax/security/auth/callback/PasswordCallback/CheckCleanerBound.java +++ b/test/jdk/javax/security/auth/callback/PasswordCallback/CheckCleanerBound.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2022, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -55,4 +55,3 @@ public final class CheckCleanerBound { } } } - diff --git a/test/jdk/javax/security/auth/callback/PasswordCallback/PasswordCleanup.java b/test/jdk/javax/security/auth/callback/PasswordCallback/PasswordCleanup.java index ea8b1d1c145..64db7de2b18 100644 --- a/test/jdk/javax/security/auth/callback/PasswordCallback/PasswordCleanup.java +++ b/test/jdk/javax/security/auth/callback/PasswordCallback/PasswordCleanup.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2022, Tencent. All rights reserved. * 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,4 +49,3 @@ public final class PasswordCleanup { } } } - diff --git a/test/jdk/jdk/internal/platform/docker/GetFreeSwapSpaceSize.java b/test/jdk/jdk/internal/platform/docker/GetFreeSwapSpaceSize.java index 92b8cf282b7..b7721899ea3 100644 --- a/test/jdk/jdk/internal/platform/docker/GetFreeSwapSpaceSize.java +++ b/test/jdk/jdk/internal/platform/docker/GetFreeSwapSpaceSize.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020, 2022 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2020, 2022, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/jdk/jdk/internal/platform/docker/TestGetFreeSwapSpaceSize.java b/test/jdk/jdk/internal/platform/docker/TestGetFreeSwapSpaceSize.java index 8e3d0cacd57..b9d031f0309 100644 --- a/test/jdk/jdk/internal/platform/docker/TestGetFreeSwapSpaceSize.java +++ b/test/jdk/jdk/internal/platform/docker/TestGetFreeSwapSpaceSize.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020, 2022 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2020, 2022, Tencent. All rights reserved. * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * diff --git a/test/jdk/sun/security/ec/ECDHKeyAgreementParamValidation.java b/test/jdk/sun/security/ec/ECDHKeyAgreementParamValidation.java index 0864d650162..3a77030f259 100644 --- a/test/jdk/sun/security/ec/ECDHKeyAgreementParamValidation.java +++ b/test/jdk/sun/security/ec/ECDHKeyAgreementParamValidation.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024, THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2024, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/jdk/sun/security/jgss/GssContextCleanup.java b/test/jdk/sun/security/jgss/GssContextCleanup.java index 00d84e8a70e..1e992df97d5 100644 --- a/test/jdk/sun/security/jgss/GssContextCleanup.java +++ b/test/jdk/sun/security/jgss/GssContextCleanup.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2022, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -59,4 +59,3 @@ public final class GssContextCleanup { } } } - diff --git a/test/jdk/sun/security/jgss/GssNameCleanup.java b/test/jdk/sun/security/jgss/GssNameCleanup.java index be68c46087d..ef19479b6a0 100644 --- a/test/jdk/sun/security/jgss/GssNameCleanup.java +++ b/test/jdk/sun/security/jgss/GssNameCleanup.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2022, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -66,4 +66,3 @@ public final class GssNameCleanup { } } } - diff --git a/test/jdk/sun/security/ssl/SignatureScheme/SigAlgosExtTestWithTLS12.java b/test/jdk/sun/security/ssl/SignatureScheme/SigAlgosExtTestWithTLS12.java index 598c0fe62af..43bc40fabf2 100644 --- a/test/jdk/sun/security/ssl/SignatureScheme/SigAlgosExtTestWithTLS12.java +++ b/test/jdk/sun/security/ssl/SignatureScheme/SigAlgosExtTestWithTLS12.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. - * Copyright (C) 2021, 2024 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2021, 2024, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/jdk/sun/security/ssl/SignatureScheme/SigAlgosExtTestWithTLS13.java b/test/jdk/sun/security/ssl/SignatureScheme/SigAlgosExtTestWithTLS13.java index 2ad48f59e83..034af3ac7c5 100644 --- a/test/jdk/sun/security/ssl/SignatureScheme/SigAlgosExtTestWithTLS13.java +++ b/test/jdk/sun/security/ssl/SignatureScheme/SigAlgosExtTestWithTLS13.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. - * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2021, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/micro/org/openjdk/bench/java/security/Signatures.java b/test/micro/org/openjdk/bench/java/security/Signatures.java index 1bd72334343..1216e253663 100644 --- a/test/micro/org/openjdk/bench/java/security/Signatures.java +++ b/test/micro/org/openjdk/bench/java/security/Signatures.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2022, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -194,4 +194,3 @@ public class Signatures { } } } - diff --git a/test/micro/org/openjdk/bench/vm/compiler/AutoVectorization2DArray.java b/test/micro/org/openjdk/bench/vm/compiler/AutoVectorization2DArray.java index a2a4654b40a..e41b1e7a4d0 100644 --- a/test/micro/org/openjdk/bench/vm/compiler/AutoVectorization2DArray.java +++ b/test/micro/org/openjdk/bench/vm/compiler/AutoVectorization2DArray.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2022, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/micro/org/openjdk/bench/vm/compiler/LoopUnroll.java b/test/micro/org/openjdk/bench/vm/compiler/LoopUnroll.java index cbb216bbaa6..2e7e1da4ba9 100644 --- a/test/micro/org/openjdk/bench/vm/compiler/LoopUnroll.java +++ b/test/micro/org/openjdk/bench/vm/compiler/LoopUnroll.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved. + * Copyright (C) 2021, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it From d0624f8b62fe0c70e5b6a47e05235ca65a2e1a13 Mon Sep 17 00:00:00 2001 From: Andrey Turbanov Date: Fri, 8 Aug 2025 05:03:55 +0000 Subject: [PATCH 060/580] 8364808: Make BasicDesktopPaneUI.Actions.MOVE_RESIZE_INCREMENT static Reviewed-by: tr, azvegint, kizune, aivanov --- .../classes/javax/swing/plaf/basic/BasicDesktopPaneUI.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicDesktopPaneUI.java b/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicDesktopPaneUI.java index 8d55a12c98f..e9d95a3f970 100644 --- a/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicDesktopPaneUI.java +++ b/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicDesktopPaneUI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2021, 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 @@ -30,7 +30,7 @@ import javax.swing.plaf.*; import java.beans.*; -import java.awt.event.*; +import java.awt.event.ActionEvent; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; @@ -374,7 +374,7 @@ public class BasicDesktopPaneUI extends DesktopPaneUI { private static String PREVIOUS_FRAME = "selectPreviousFrame"; private static String NAVIGATE_NEXT = "navigateNext"; private static String NAVIGATE_PREVIOUS = "navigatePrevious"; - private final int MOVE_RESIZE_INCREMENT = 10; + private static final int MOVE_RESIZE_INCREMENT = 10; private static boolean moving = false; private static boolean resizing = false; private static JInternalFrame sourceFrame = null; From 198782c957c728ed959d1fd31e2c2ff6cd1a9bb5 Mon Sep 17 00:00:00 2001 From: Thomas Schatzl Date: Fri, 8 Aug 2025 07:54:23 +0000 Subject: [PATCH 061/580] 8364877: G1: Inline G1CollectedHeap::set_region_short_lived_locked Reviewed-by: ayang, sangheki --- src/hotspot/share/gc/g1/g1CollectedHeap.cpp | 14 ++++++-------- src/hotspot/share/gc/g1/g1CollectedHeap.hpp | 1 - src/hotspot/share/gc/g1/g1EdenRegions.hpp | 2 +- src/hotspot/share/gc/g1/g1Policy.hpp | 1 - 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp index 8d65011b560..27a2cbfb000 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp @@ -2803,12 +2803,6 @@ bool G1CollectedHeap::is_old_gc_alloc_region(G1HeapRegion* hr) { return _allocator->is_retained_old_region(hr); } -void G1CollectedHeap::set_region_short_lived_locked(G1HeapRegion* hr) { - _eden.add(hr); - _policy->set_region_eden(hr); - young_regions_cset_group()->add(hr); -} - #ifdef ASSERT class NoYoungRegionsClosure: public G1HeapRegionClosure { @@ -2957,9 +2951,13 @@ G1HeapRegion* G1CollectedHeap::new_mutator_alloc_region(size_t word_size, false /* do_expand */, node_index); if (new_alloc_region != nullptr) { - set_region_short_lived_locked(new_alloc_region); - G1HeapRegionPrinter::alloc(new_alloc_region); + new_alloc_region->set_eden(); + _eden.add(new_alloc_region); + _policy->set_region_eden(new_alloc_region); _policy->remset_tracker()->update_at_allocate(new_alloc_region); + // Install the group cardset. + young_regions_cset_group()->add(new_alloc_region); + G1HeapRegionPrinter::alloc(new_alloc_region); return new_alloc_region; } } diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp index 90e0ea8608a..48d7300bf41 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp @@ -1214,7 +1214,6 @@ public: return named_heap(CollectedHeap::G1); } - void set_region_short_lived_locked(G1HeapRegion* hr); // add appropriate methods for any other surv rate groups G1SurvivorRegions* survivor() { return &_survivor; } diff --git a/src/hotspot/share/gc/g1/g1EdenRegions.hpp b/src/hotspot/share/gc/g1/g1EdenRegions.hpp index a6e005ff2dc..c7ed9008ee7 100644 --- a/src/hotspot/share/gc/g1/g1EdenRegions.hpp +++ b/src/hotspot/share/gc/g1/g1EdenRegions.hpp @@ -42,7 +42,7 @@ public: G1EdenRegions() : _length(0), _used_bytes(0), _regions_on_node() { } uint add(G1HeapRegion* hr) { - assert(!hr->is_eden(), "should not already be set"); + assert(hr->is_eden(), "must be"); _length++; return _regions_on_node.add(hr); } diff --git a/src/hotspot/share/gc/g1/g1Policy.hpp b/src/hotspot/share/gc/g1/g1Policy.hpp index 804950f1ef3..3571945d587 100644 --- a/src/hotspot/share/gc/g1/g1Policy.hpp +++ b/src/hotspot/share/gc/g1/g1Policy.hpp @@ -121,7 +121,6 @@ public: G1OldGenAllocationTracker* old_gen_alloc_tracker() { return &_old_gen_alloc_tracker; } void set_region_eden(G1HeapRegion* hr) { - hr->set_eden(); hr->install_surv_rate_group(_eden_surv_rate_group); } From bcca5cee2d788c745bea55388b2844b395519ed0 Mon Sep 17 00:00:00 2001 From: Thomas Schatzl Date: Fri, 8 Aug 2025 07:56:29 +0000 Subject: [PATCH 062/580] 8364642: G1: Remove parameter in G1CollectedHeap::abandon_collection_set() Reviewed-by: ayang --- src/hotspot/share/gc/g1/g1CollectedHeap.cpp | 8 ++++---- src/hotspot/share/gc/g1/g1CollectedHeap.hpp | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp index 27a2cbfb000..186abfd81ad 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp @@ -772,7 +772,7 @@ void G1CollectedHeap::prepare_heap_for_full_collection() { // set between the last GC or pause and now. We need to clear the // incremental collection set and then start rebuilding it afresh // after this full GC. - abandon_collection_set(collection_set()); + abandon_collection_set(); _hrm.remove_all_free_regions(); } @@ -2791,12 +2791,12 @@ public: } }; -void G1CollectedHeap::abandon_collection_set(G1CollectionSet* collection_set) { +void G1CollectedHeap::abandon_collection_set() { G1AbandonCollectionSetClosure cl; collection_set_iterate_all(&cl); - collection_set->clear(); - collection_set->stop_incremental_building(); + collection_set()->clear(); + collection_set()->stop_incremental_building(); } bool G1CollectedHeap::is_old_gc_alloc_region(G1HeapRegion* hr) { diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp index 48d7300bf41..49bbcf888be 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp @@ -804,7 +804,7 @@ public: // Abandon the current collection set without recording policy // statistics or updating free lists. - void abandon_collection_set(G1CollectionSet* collection_set); + void abandon_collection_set(); // The concurrent marker (and the thread it runs in.) G1ConcurrentMark* _cm; From 47017e38642a58fd6425ec68c1fed96f19f39404 Mon Sep 17 00:00:00 2001 From: Thomas Schatzl Date: Fri, 8 Aug 2025 07:57:06 +0000 Subject: [PATCH 063/580] 8364760: G1: Remove obsolete code in G1MergeCardSetClosure Reviewed-by: ayang, sangheki --- src/hotspot/share/gc/g1/g1RemSet.cpp | 41 ++-------------------------- 1 file changed, 3 insertions(+), 38 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1RemSet.cpp b/src/hotspot/share/gc/g1/g1RemSet.cpp index 4114f0e7ba5..44b3234d26b 100644 --- a/src/hotspot/share/gc/g1/g1RemSet.cpp +++ b/src/hotspot/share/gc/g1/g1RemSet.cpp @@ -986,13 +986,12 @@ class G1MergeHeapRootsTask : public WorkerTask { // Visitor for remembered sets. Several methods of it are called by a region's // card set iterator to drop card set remembered set entries onto the card. - // table. This is in addition to being the HG1eapRegionClosure to iterate over - // all region's remembered sets. + // table. // // We add a small prefetching cache in front of the actual work as dropping // onto the card table is basically random memory access. This improves // performance of this operation significantly. - class G1MergeCardSetClosure : public G1HeapRegionClosure { + class G1MergeCardSetClosure { friend class G1MergeCardSetCache; G1RemSetScanState* _scan_state; @@ -1039,7 +1038,6 @@ class G1MergeHeapRootsTask : public WorkerTask { } public: - G1MergeCardSetClosure(G1RemSetScanState* scan_state) : _scan_state(scan_state), _ct(G1CollectedHeap::heap()->card_table()), @@ -1071,38 +1069,6 @@ class G1MergeHeapRootsTask : public WorkerTask { _scan_state->set_chunk_range_dirty(_region_base_idx + start_card_idx, length); } - // Helper to merge the cards in the card set for the given region onto the card - // table. - // - // Called directly for humongous starts regions because we should not add - // humongous eager reclaim candidates to the "all" list of regions to - // clear the card table by default as we do not know yet whether this region - // will be reclaimed (and reused). - // If the humongous region contains dirty cards, g1 will scan them - // because dumping the remembered set entries onto the card table will add - // the humongous region to the "dirty" region list to scan. Then scanning - // either clears the card during scan (if there is only an initial evacuation - // pass) or the "dirty" list will be merged with the "all" list later otherwise. - // (And there is no problem either way if the region does not contain dirty - // cards). - void merge_card_set_for_region(G1HeapRegion* r) { - assert(r->in_collection_set() || r->is_starts_humongous(), "must be"); - - G1HeapRegionRemSet* rem_set = r->rem_set(); - if (!rem_set->is_empty()) { - rem_set->iterate_for_merge(*this); - } - } - - virtual bool do_heap_region(G1HeapRegion* r) { - assert(r->in_collection_set(), "must be"); - - _scan_state->add_all_dirty_region(r->hrm_index()); - merge_card_set_for_region(r); - - return false; - } - G1MergeCardSetStats stats() { _merge_card_set_cache.flush(); // Compensation for the dummy cards that were initially pushed into the @@ -1189,8 +1155,7 @@ class G1MergeHeapRootsTask : public WorkerTask { "Found a not-small remembered set here. This is inconsistent with previous assumptions."); if (!r->rem_set()->is_empty()) { - _cl.merge_card_set_for_region(r); - + r->rem_set()->iterate_for_merge(_cl); // We should only clear the card based remembered set here as we will not // implicitly rebuild anything else during eager reclaim. Note that at the moment // (and probably never) we do not enter this path if there are other kind of From a26a6f31524aba61ed83bf3ffdc7713e3e5f5911 Mon Sep 17 00:00:00 2001 From: Thomas Schatzl Date: Fri, 8 Aug 2025 08:06:56 +0000 Subject: [PATCH 064/580] 8364649: G1: Move collection set related full gc reset code into abandon_collection_set() method Reviewed-by: ayang, sangheki --- src/hotspot/share/gc/g1/g1CollectedHeap.cpp | 4 ++++ src/hotspot/share/gc/g1/g1FullCollector.cpp | 2 -- src/hotspot/share/gc/g1/g1Policy.cpp | 1 - 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp index 186abfd81ad..b9b12f628eb 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp @@ -2797,6 +2797,10 @@ void G1CollectedHeap::abandon_collection_set() { collection_set()->clear(); collection_set()->stop_incremental_building(); + + collection_set()->abandon_all_candidates(); + + young_regions_cset_group()->clear(); } bool G1CollectedHeap::is_old_gc_alloc_region(G1HeapRegion* hr) { diff --git a/src/hotspot/share/gc/g1/g1FullCollector.cpp b/src/hotspot/share/gc/g1/g1FullCollector.cpp index 4992df8e214..bae8115bb9c 100644 --- a/src/hotspot/share/gc/g1/g1FullCollector.cpp +++ b/src/hotspot/share/gc/g1/g1FullCollector.cpp @@ -246,8 +246,6 @@ void G1FullCollector::complete_collection(size_t allocation_word_size) { _heap->resize_all_tlabs(); - _heap->young_regions_cset_group()->clear(); - _heap->policy()->record_full_collection_end(); _heap->gc_epilogue(true); diff --git a/src/hotspot/share/gc/g1/g1Policy.cpp b/src/hotspot/share/gc/g1/g1Policy.cpp index 49fab954799..3bbc64e0fe7 100644 --- a/src/hotspot/share/gc/g1/g1Policy.cpp +++ b/src/hotspot/share/gc/g1/g1Policy.cpp @@ -574,7 +574,6 @@ void G1Policy::record_full_collection_start() { // Release the future to-space so that it is available for compaction into. collector_state()->set_in_young_only_phase(false); collector_state()->set_in_full_gc(true); - _collection_set->abandon_all_candidates(); _pending_cards_at_gc_start = 0; } From 1b3e23110b2262e470a8c520b977273fd6a9e8d1 Mon Sep 17 00:00:00 2001 From: Afshin Zafari Date: Fri, 8 Aug 2025 09:06:43 +0000 Subject: [PATCH 065/580] 8360048: NMT crash in gtest/NMTGtests.java: fatal error: NMT corruption: Block at 0x0000017748307120: header canary broken Reviewed-by: jsjolen, gziemski --- src/hotspot/share/nmt/memBaseline.cpp | 2 +- src/hotspot/share/nmt/memReporter.cpp | 2 +- .../share/nmt/virtualMemoryTracker.cpp | 25 ++- .../share/nmt/virtualMemoryTracker.hpp | 12 +- .../runtime/test_virtualMemoryTracker.cpp | 166 +++++++++--------- 5 files changed, 104 insertions(+), 103 deletions(-) diff --git a/src/hotspot/share/nmt/memBaseline.cpp b/src/hotspot/share/nmt/memBaseline.cpp index 8661f91174a..35dff0d8646 100644 --- a/src/hotspot/share/nmt/memBaseline.cpp +++ b/src/hotspot/share/nmt/memBaseline.cpp @@ -215,7 +215,7 @@ bool MemBaseline::aggregate_virtual_memory_allocation_sites() { site = node->data(); } site->reserve_memory(rgn->size()); - site->commit_memory(rgn->committed_size()); + site->commit_memory(VirtualMemoryTracker::Instance::committed_size(rgn)); } _virtual_memory_sites.move(&allocation_sites); diff --git a/src/hotspot/share/nmt/memReporter.cpp b/src/hotspot/share/nmt/memReporter.cpp index c1c847a4f2e..a7c564eff53 100644 --- a/src/hotspot/share/nmt/memReporter.cpp +++ b/src/hotspot/share/nmt/memReporter.cpp @@ -422,7 +422,7 @@ void MemDetailReporter::report_virtual_memory_region(const ReservedMemoryRegion* outputStream* out = output(); const char* scale = current_scale(); const NativeCallStack* stack = reserved_rgn->call_stack(); - bool all_committed = reserved_rgn->size() == reserved_rgn->committed_size(); + bool all_committed = reserved_rgn->size() == VirtualMemoryTracker::Instance::committed_size(reserved_rgn); const char* region_type = (all_committed ? "reserved and committed" : "reserved"); out->cr(); print_virtual_memory_region(region_type, reserved_rgn->base(), reserved_rgn->size()); diff --git a/src/hotspot/share/nmt/virtualMemoryTracker.cpp b/src/hotspot/share/nmt/virtualMemoryTracker.cpp index 51d8696a9ce..8a97253860c 100644 --- a/src/hotspot/share/nmt/virtualMemoryTracker.cpp +++ b/src/hotspot/share/nmt/virtualMemoryTracker.cpp @@ -219,20 +219,29 @@ bool VirtualMemoryTracker::walk_virtual_memory(VirtualMemoryWalker* walker) { return true; } -size_t ReservedMemoryRegion::committed_size() const { - size_t committed = 0; +size_t VirtualMemoryTracker::committed_size(const ReservedMemoryRegion* rmr) { size_t result = 0; - VirtualMemoryTracker::Instance::tree()->visit_committed_regions(*this, [&](CommittedMemoryRegion& crgn) { + tree()->visit_committed_regions(*rmr, [&](CommittedMemoryRegion& crgn) { result += crgn.size(); return true; }); return result; } -address ReservedMemoryRegion::thread_stack_uncommitted_bottom() const { - address bottom = base(); - address top = base() + size(); - VirtualMemoryTracker::Instance::tree()->visit_committed_regions(*this, [&](CommittedMemoryRegion& crgn) { +size_t VirtualMemoryTracker::Instance::committed_size(const ReservedMemoryRegion* rmr) { + assert(_tracker != nullptr, "Sanity check"); + return _tracker->committed_size(rmr); +} + +address VirtualMemoryTracker::Instance::thread_stack_uncommitted_bottom(const ReservedMemoryRegion* rmr) { + assert(_tracker != nullptr, "Sanity check"); + return _tracker->thread_stack_uncommitted_bottom(rmr); +} + +address VirtualMemoryTracker::thread_stack_uncommitted_bottom(const ReservedMemoryRegion* rmr) { + address bottom = rmr->base(); + address top = rmr->end(); + tree()->visit_committed_regions(*rmr, [&](CommittedMemoryRegion& crgn) { address committed_top = crgn.base() + crgn.size(); if (committed_top < top) { // committed stack guard pages, skip them @@ -291,7 +300,7 @@ public: assert_lock_strong(NmtVirtualMemory_lock); } if (rgn->mem_tag() == mtThreadStack) { - address stack_bottom = rgn->thread_stack_uncommitted_bottom(); + address stack_bottom = VirtualMemoryTracker::Instance::thread_stack_uncommitted_bottom(rgn); address committed_start; size_t committed_size; size_t stack_size = rgn->base() + rgn->size() - stack_bottom; diff --git a/src/hotspot/share/nmt/virtualMemoryTracker.hpp b/src/hotspot/share/nmt/virtualMemoryTracker.hpp index 1c9628cd828..3c6c1efd6a2 100644 --- a/src/hotspot/share/nmt/virtualMemoryTracker.hpp +++ b/src/hotspot/share/nmt/virtualMemoryTracker.hpp @@ -330,12 +330,6 @@ class ReservedMemoryRegion : public VirtualMemoryRegion { inline MemTag mem_tag() const { return _mem_tag; } - // uncommitted thread stack bottom, above guard pages if there is any. - address thread_stack_uncommitted_bottom() const; - - size_t committed_size() const; - - ReservedMemoryRegion& operator= (const ReservedMemoryRegion& other) { set_base(other.base()); set_size(other.size()); @@ -382,6 +376,9 @@ class VirtualMemoryTracker { // Snapshot current thread stacks void snapshot_thread_stacks(); void apply_summary_diff(VMATree::SummaryDiff diff); + size_t committed_size(const ReservedMemoryRegion* rmr); + address thread_stack_uncommitted_bottom(const ReservedMemoryRegion* rmr); + RegionsTree* tree() { return &_tree; } class Instance : public AllStatic { @@ -404,6 +401,9 @@ class VirtualMemoryTracker { static bool print_containing_region(const void* p, outputStream* st); static void snapshot_thread_stacks(); static void apply_summary_diff(VMATree::SummaryDiff diff); + static size_t committed_size(const ReservedMemoryRegion* rmr); + // uncommitted thread stack bottom, above guard pages if there is any. + static address thread_stack_uncommitted_bottom(const ReservedMemoryRegion* rmr); static RegionsTree* tree() { return _tracker->tree(); } }; diff --git a/test/hotspot/gtest/runtime/test_virtualMemoryTracker.cpp b/test/hotspot/gtest/runtime/test_virtualMemoryTracker.cpp index 9c0599fb513..b7ef0663c8a 100644 --- a/test/hotspot/gtest/runtime/test_virtualMemoryTracker.cpp +++ b/test/hotspot/gtest/runtime/test_virtualMemoryTracker.cpp @@ -50,14 +50,14 @@ namespace { }; } -#define check(rmr, regions) check_inner((rmr), (regions), ARRAY_SIZE(regions), __FILE__, __LINE__) +#define check(vmt, rmr, regions) check_inner((vmt), (rmr), (regions), ARRAY_SIZE(regions), __FILE__, __LINE__) -#define check_empty(rmr) \ +#define check_empty(vmt, rmr) \ do { \ - check_inner((rmr), nullptr, 0, __FILE__, __LINE__); \ + check_inner((vmt), (rmr), nullptr, 0, __FILE__, __LINE__); \ } while (false) -static void diagnostic_print(const ReservedMemoryRegion& rmr) { +static void diagnostic_print(VirtualMemoryTracker& vmt, const ReservedMemoryRegion& rmr) { LOG("In reserved region " PTR_FORMAT ", size %X:", p2i(rmr.base()), rmr.size()); VirtualMemoryTracker::Instance::tree()->visit_committed_regions(rmr, [&](CommittedMemoryRegion& region) { LOG(" committed region: " PTR_FORMAT ", size %X", p2i(region.base()), region.size()); @@ -65,16 +65,16 @@ static void diagnostic_print(const ReservedMemoryRegion& rmr) { }); } -static void check_inner(const ReservedMemoryRegion& rmr, R* regions, size_t regions_size, const char* file, int line) { +static void check_inner(VirtualMemoryTracker& vmt, const ReservedMemoryRegion& rmr, R* regions, size_t regions_size, const char* file, int line) { size_t i = 0; size_t size = 0; // Helpful log - diagnostic_print(rmr); + diagnostic_print(vmt, rmr); #define WHERE " from " << file << ":" << line - VirtualMemoryTracker::Instance::tree()->visit_committed_regions(rmr, [&](CommittedMemoryRegion& region) { + vmt.tree()->visit_committed_regions(rmr, [&](CommittedMemoryRegion& region) { EXPECT_LT(i, regions_size) << WHERE; EXPECT_EQ(region.base(), regions[i]._addr) << WHERE; EXPECT_EQ(region.size(), regions[i]._size) << WHERE; @@ -84,16 +84,18 @@ static void check_inner(const ReservedMemoryRegion& rmr, R* regions, size_t regi }); EXPECT_EQ(i, regions_size) << WHERE; - EXPECT_EQ(size, rmr.committed_size()) << WHERE; + EXPECT_EQ(size, vmt.committed_size(&rmr)) << WHERE; } class VirtualMemoryTrackerTest { public: static void test_add_committed_region_adjacent() { + VirtualMemoryTracker vmt(true); + RegionsTree* rtree = vmt.tree(); size_t size = 0x01000000; - ReservedSpace rs = MemoryReserver::reserve(size, mtTest); + const address addr = (address)0x0000A000; - address addr = (address)rs.base(); + vmt.add_reserved_region(addr, size, CALLER_PC, mtTest); address frame1 = (address)0x1234; address frame2 = (address)0x1235; @@ -102,9 +104,6 @@ public: NativeCallStack stack2(&frame2, 1); // Fetch the added RMR for the space - RegionsTree* rtree = VirtualMemoryTracker::Instance::tree(); - MemTracker::NmtVirtualMemoryLocker nvml; - ReservedMemoryRegion rmr = rtree->find_reserved_region(addr); ASSERT_EQ(rmr.size(), size); @@ -118,24 +117,24 @@ public: { // Commit one region rtree->commit_region(addr + cs, cs, stack); R r[] = { {addr + cs, cs} }; - check(rmr, r); + check(vmt, rmr, r); } { // Commit adjacent - lower address rtree->commit_region(addr, cs, stack); R r[] = { {addr, 2 * cs} }; - check(rmr, r); + check(vmt, rmr, r); } { // Commit adjacent - higher address rtree->commit_region(addr + 2 * cs, cs, stack); R r[] = { {addr, 3 * cs} }; - check(rmr, r); + check(vmt,rmr, r); } // Cleanup rtree->uncommit_region(addr, 3 * cs); - ASSERT_EQ(rmr.committed_size(), 0u); + ASSERT_EQ(vmt.committed_size(&rmr), 0u); // Commit adjacent regions with different stacks @@ -143,14 +142,14 @@ public: { // Commit one region rtree->commit_region(addr + cs, cs, stack); R r[] = { {addr + cs, cs} }; - check(rmr, r); + check(vmt, rmr, r); } { // Commit adjacent - lower address rtree->commit_region(addr, cs, stack2); R r[] = { {addr, cs}, {addr + cs, cs} }; - check(rmr, r); + check(vmt, rmr, r); } { // Commit adjacent - higher address @@ -158,25 +157,21 @@ public: R r[] = { {addr, cs}, {addr + cs, cs}, {addr + 2 * cs, cs} }; - check(rmr, r); + check(vmt, rmr, r); } // Cleanup rtree->uncommit_region(addr, 3 * cs); - ASSERT_EQ(rmr.committed_size(), 0u); - - rtree->tree().remove_all(); + ASSERT_EQ(vmt.committed_size(&rmr), 0u); } static void test_add_committed_region_adjacent_overlapping() { + VirtualMemoryTracker vmt(true); + RegionsTree* rtree = vmt.tree(); size_t size = 0x01000000; - ReservedSpace rs = MemoryReserver::reserve(size, mtTest); - - RegionsTree* rtree = VirtualMemoryTracker::Instance::tree(); - MemTracker::NmtVirtualMemoryLocker nvml; - - address addr = (address)rs.base(); + const address addr = (address)0x0000A000; + vmt.add_reserved_region(addr, size, CALLER_PC, mtTest); address frame1 = (address)0x1234; address frame2 = (address)0x1235; @@ -199,28 +194,28 @@ public: rtree->commit_region(addr + 3 * cs, 2 * cs, stack); R r[] = { {addr, 2 * cs}, {addr + 3 * cs, 2 * cs} }; - check(rmr, r); + check(vmt, rmr, r); } { // Commit adjacent and overlapping rtree->commit_region(addr + 2 * cs, 2 * cs, stack); R r[] = { {addr, 5 * cs} }; - check(rmr, r); + check(vmt, rmr, r); } // revert to two non-adjacent regions rtree->uncommit_region(addr + 2 * cs, cs); - ASSERT_EQ(rmr.committed_size(), 4 * cs); + ASSERT_EQ(vmt.committed_size(&rmr), 4 * cs); { // Commit overlapping and adjacent rtree->commit_region(addr + cs, 2 * cs, stack); R r[] = { {addr, 5 * cs} }; - check(rmr, r); + check(vmt, rmr, r); } // Cleanup rtree->uncommit_region(addr, 5 * cs); - ASSERT_EQ(rmr.committed_size(), 0u); + ASSERT_EQ(vmt.committed_size(&rmr), 0u); // Commit adjacent and overlapping regions with different stacks @@ -230,7 +225,7 @@ public: rtree->commit_region(addr + 3 * cs, 2 * cs, stack); R r[] = { {addr, 2 * cs}, {addr + 3 * cs, 2 * cs} }; - check(rmr, r); + check(vmt, rmr, r); } { // Commit adjacent and overlapping @@ -238,33 +233,32 @@ public: R r[] = { {addr, 2 * cs}, {addr + 2 * cs, 2 * cs}, {addr + 4 * cs, cs} }; - check(rmr, r); + check(vmt, rmr, r); } // revert to two non-adjacent regions rtree->commit_region(addr, 5 * cs, stack); rtree->uncommit_region(addr + 2 * cs, cs); - ASSERT_EQ(rmr.committed_size(), 4 * cs); + ASSERT_EQ(vmt.committed_size(&rmr), 4 * cs); { // Commit overlapping and adjacent rtree->commit_region(addr + cs, 2 * cs, stack2); R r[] = { {addr, cs}, {addr + cs, 2 * cs}, {addr + 3 * cs, 2 * cs} }; - check(rmr, r); + check(vmt, rmr, r); } rtree->tree().remove_all(); } static void test_add_committed_region_overlapping() { + VirtualMemoryTracker vmt(true); + RegionsTree* rtree = vmt.tree(); size_t size = 0x01000000; - ReservedSpace rs = MemoryReserver::reserve(size, mtTest); + const address addr = (address)0x0000A000; - RegionsTree* rtree = VirtualMemoryTracker::Instance::tree(); - MemTracker::NmtVirtualMemoryLocker nvml; - - address addr = (address)rs.base(); + vmt.add_reserved_region(addr, size, CALLER_PC, mtTest); address frame1 = (address)0x1234; address frame2 = (address)0x1235; @@ -287,54 +281,54 @@ public: { // Commit one region rtree->commit_region(addr, cs, stack); R r[] = { {addr, cs} }; - check(rmr, r); + check(vmt, rmr, r); } { // Commit the same region rtree->commit_region(addr, cs, stack); R r[] = { {addr, cs} }; - check(rmr, r); + check(vmt, rmr, r); } { // Commit a succeeding region rtree->commit_region(addr + cs, cs, stack); R r[] = { {addr, 2 * cs} }; - check(rmr, r); + check(vmt, rmr, r); } { // Commit over two regions rtree->commit_region(addr, 2 * cs, stack); R r[] = { {addr, 2 * cs} }; - check(rmr, r); + check(vmt, rmr, r); } {// Commit first part of a region rtree->commit_region(addr, cs, stack); R r[] = { {addr, 2 * cs} }; - check(rmr, r); + check(vmt, rmr, r); } { // Commit second part of a region rtree->commit_region(addr + cs, cs, stack); R r[] = { {addr, 2 * cs} }; - check(rmr, r); + check(vmt, rmr, r); } { // Commit a third part rtree->commit_region(addr + 2 * cs, cs, stack); R r[] = { {addr, 3 * cs} }; - check(rmr, r); + check(vmt, rmr, r); } { // Commit in the middle of a region rtree->commit_region(addr + 1 * cs, cs, stack); R r[] = { {addr, 3 * cs} }; - check(rmr, r); + check(vmt, rmr, r); } // Cleanup rtree->uncommit_region(addr, 3 * cs); - ASSERT_EQ(rmr.committed_size(), 0u); + ASSERT_EQ(vmt.committed_size(&rmr), 0u); // With preceding region @@ -345,71 +339,71 @@ public: { R r[] = { {addr, cs}, {addr + 2 * cs, 3 * cs} }; - check(rmr, r); + check(vmt, rmr, r); } rtree->commit_region(addr + 3 * cs, cs, stack); { R r[] = { {addr, cs}, {addr + 2 * cs, 3 * cs} }; - check(rmr, r); + check(vmt, rmr, r); } rtree->commit_region(addr + 4 * cs, cs, stack); { R r[] = { {addr, cs}, {addr + 2 * cs, 3 * cs} }; - check(rmr, r); + check(vmt, rmr, r); } // Cleanup rtree->uncommit_region(addr, 5 * cs); - ASSERT_EQ(rmr.committed_size(), 0u); + ASSERT_EQ(vmt.committed_size(&rmr), 0u); // With different stacks { // Commit one region rtree->commit_region(addr, cs, stack); R r[] = { {addr, cs} }; - check(rmr, r); + check(vmt, rmr, r); } { // Commit the same region rtree->commit_region(addr, cs, stack2); R r[] = { {addr, cs} }; - check(rmr, r); + check(vmt, rmr, r); } { // Commit a succeeding region rtree->commit_region(addr + cs, cs, stack); R r[] = { {addr, cs}, {addr + cs, cs} }; - check(rmr, r); + check(vmt, rmr, r); } { // Commit over two regions rtree->commit_region(addr, 2 * cs, stack); R r[] = { {addr, 2 * cs} }; - check(rmr, r); + check(vmt, rmr, r); } {// Commit first part of a region rtree->commit_region(addr, cs, stack2); R r[] = { {addr, cs}, {addr + cs, cs} }; - check(rmr, r); + check(vmt, rmr, r); } { // Commit second part of a region rtree->commit_region(addr + cs, cs, stack2); R r[] = { {addr, 2 * cs} }; - check(rmr, r); + check(vmt, rmr, r); } { // Commit a third part rtree->commit_region(addr + 2 * cs, cs, stack2); R r[] = { {addr, 3 * cs} }; - check(rmr, r); + check(vmt, rmr, r); } { // Commit in the middle of a region @@ -417,7 +411,7 @@ public: R r[] = { {addr, cs}, {addr + cs, cs}, {addr + 2 * cs, cs} }; - check(rmr, r); + check(vmt, rmr, r); } rtree->tree().remove_all(); @@ -435,14 +429,12 @@ public: } static void test_remove_uncommitted_region() { + VirtualMemoryTracker vmt(true); + RegionsTree* rtree = vmt.tree(); size_t size = 0x01000000; - ReservedSpace rs = MemoryReserver::reserve(size, mtTest); - - RegionsTree* rtree = VirtualMemoryTracker::Instance::tree(); - MemTracker::NmtVirtualMemoryLocker nvml; - - address addr = (address)rs.base(); + const address addr = (address)0x0000A000; + vmt.add_reserved_region(addr, size, CALLER_PC, mtTest); address frame1 = (address)0x1234; address frame2 = (address)0x1235; @@ -461,11 +453,11 @@ public: { // Commit regions rtree->commit_region(addr, 3 * cs, stack); R r[] = { {addr, 3 * cs} }; - check(rmr, r); + check(vmt, rmr, r); // Remove only existing rtree->uncommit_region(addr, 3 * cs); - check_empty(rmr); + check_empty(vmt, rmr); } { @@ -477,7 +469,7 @@ public: rtree->uncommit_region(addr, cs); R r[] = { {addr + 2 * cs, cs}, {addr + 4 * cs, cs} }; - check(rmr, r); + check(vmt, rmr, r); } // add back @@ -487,7 +479,7 @@ public: rtree->uncommit_region(addr + 2 * cs, cs); R r[] = { {addr + 0 * cs, cs}, {addr + 4 * cs, cs} }; - check(rmr, r); + check(vmt, rmr, r); } // add back @@ -497,17 +489,17 @@ public: rtree->uncommit_region(addr + 4 * cs, cs); R r[] = { {addr + 0 * cs, cs}, {addr + 2 * cs, cs} }; - check(rmr, r); + check(vmt, rmr, r); } rtree->uncommit_region(addr, 5 * cs); - check_empty(rmr); + check_empty(vmt, rmr); } { // Remove larger region rtree->commit_region(addr + 1 * cs, cs, stack); rtree->uncommit_region(addr, 3 * cs); - check_empty(rmr); + check_empty(vmt, rmr); } { // Remove smaller region - in the middle @@ -515,50 +507,50 @@ public: rtree->uncommit_region(addr + 1 * cs, cs); R r[] = { { addr + 0 * cs, cs}, { addr + 2 * cs, cs} }; - check(rmr, r); + check(vmt, rmr, r); rtree->uncommit_region(addr, 3 * cs); - check_empty(rmr); + check_empty(vmt, rmr); } { // Remove smaller region - at the beginning rtree->commit_region(addr, 3 * cs, stack); rtree->uncommit_region(addr + 0 * cs, cs); R r[] = { { addr + 1 * cs, 2 * cs} }; - check(rmr, r); + check(vmt, rmr, r); rtree->uncommit_region(addr, 3 * cs); - check_empty(rmr); + check_empty(vmt, rmr); } { // Remove smaller region - at the end rtree->commit_region(addr, 3 * cs, stack); rtree->uncommit_region(addr + 2 * cs, cs); R r[] = { { addr, 2 * cs} }; - check(rmr, r); + check(vmt, rmr, r); rtree->uncommit_region(addr, 3 * cs); - check_empty(rmr); + check_empty(vmt, rmr); } { // Remove smaller, overlapping region - at the beginning rtree->commit_region(addr + 1 * cs, 4 * cs, stack); rtree->uncommit_region(addr, 2 * cs); R r[] = { { addr + 2 * cs, 3 * cs} }; - check(rmr, r); + check(vmt, rmr, r); rtree->uncommit_region(addr + 1 * cs, 4 * cs); - check_empty(rmr); + check_empty(vmt, rmr); } { // Remove smaller, overlapping region - at the end rtree->commit_region(addr, 3 * cs, stack); rtree->uncommit_region(addr + 2 * cs, 2 * cs); R r[] = { { addr, 2 * cs} }; - check(rmr, r); + check(vmt, rmr, r); rtree->uncommit_region(addr, 3 * cs); - check_empty(rmr); + check_empty(vmt, rmr); } rtree->tree().remove_all(); From 241808e13fb032b0ec192e0b7ff94891a653ac94 Mon Sep 17 00:00:00 2001 From: Andrew Dinn Date: Fri, 8 Aug 2025 09:12:08 +0000 Subject: [PATCH 066/580] 8364269: Simplify code cache API by storing adapter entry offsets in blob Reviewed-by: kvn, shade, asmehra --- src/hotspot/share/code/aotCodeCache.cpp | 43 ++++----------------- src/hotspot/share/code/aotCodeCache.hpp | 18 +++------ src/hotspot/share/code/codeBlob.cpp | 31 +++++++++++---- src/hotspot/share/code/codeBlob.hpp | 16 +++++--- src/hotspot/share/runtime/sharedRuntime.cpp | 24 ++++++------ 5 files changed, 60 insertions(+), 72 deletions(-) diff --git a/src/hotspot/share/code/aotCodeCache.cpp b/src/hotspot/share/code/aotCodeCache.cpp index 38974da2eeb..df2e9648b84 100644 --- a/src/hotspot/share/code/aotCodeCache.cpp +++ b/src/hotspot/share/code/aotCodeCache.cpp @@ -799,7 +799,7 @@ bool AOTCodeCache::finish_write() { //------------------Store/Load AOT code ---------------------- -bool AOTCodeCache::store_code_blob(CodeBlob& blob, AOTCodeEntry::Kind entry_kind, uint id, const char* name, int entry_offset_count, int* entry_offsets) { +bool AOTCodeCache::store_code_blob(CodeBlob& blob, AOTCodeEntry::Kind entry_kind, uint id, const char* name) { AOTCodeCache* cache = open_for_dump(); if (cache == nullptr) { return false; @@ -883,18 +883,6 @@ bool AOTCodeCache::store_code_blob(CodeBlob& blob, AOTCodeEntry::Kind entry_kind return false; } - // Write entries offsets - n = cache->write_bytes(&entry_offset_count, sizeof(int)); - if (n != sizeof(int)) { - return false; - } - for (int i = 0; i < entry_offset_count; i++) { - uint32_t off = (uint32_t)entry_offsets[i]; - n = cache->write_bytes(&off, sizeof(uint32_t)); - if (n != sizeof(uint32_t)) { - return false; - } - } uint entry_size = cache->_write_position - entry_position; AOTCodeEntry* entry = new(cache) AOTCodeEntry(entry_kind, encode_id(entry_kind, id), entry_position, entry_size, name_offset, name_size, @@ -903,13 +891,13 @@ bool AOTCodeCache::store_code_blob(CodeBlob& blob, AOTCodeEntry::Kind entry_kind return true; } -bool AOTCodeCache::store_code_blob(CodeBlob& blob, AOTCodeEntry::Kind entry_kind, BlobId id, int entry_offset_count, int* entry_offsets) { +bool AOTCodeCache::store_code_blob(CodeBlob& blob, AOTCodeEntry::Kind entry_kind, BlobId id) { assert(AOTCodeEntry::is_blob(entry_kind), "wrong entry kind for blob id %s", StubInfo::name(id)); - return store_code_blob(blob, entry_kind, (uint)id, StubInfo::name(id), entry_offset_count, entry_offsets); + return store_code_blob(blob, entry_kind, (uint)id, StubInfo::name(id)); } -CodeBlob* AOTCodeCache::load_code_blob(AOTCodeEntry::Kind entry_kind, uint id, const char* name, int entry_offset_count, int* entry_offsets) { +CodeBlob* AOTCodeCache::load_code_blob(AOTCodeEntry::Kind entry_kind, uint id, const char* name) { AOTCodeCache* cache = open_for_use(); if (cache == nullptr) { return nullptr; @@ -929,20 +917,20 @@ CodeBlob* AOTCodeCache::load_code_blob(AOTCodeEntry::Kind entry_kind, uint id, c return nullptr; } AOTCodeReader reader(cache, entry); - CodeBlob* blob = reader.compile_code_blob(name, entry_offset_count, entry_offsets); + CodeBlob* blob = reader.compile_code_blob(name); log_debug(aot, codecache, stubs)("%sRead blob '%s' (id=%u, kind=%s) from AOT Code Cache", (blob == nullptr? "Failed to " : ""), name, id, aot_code_entry_kind_name[entry_kind]); return blob; } -CodeBlob* AOTCodeCache::load_code_blob(AOTCodeEntry::Kind entry_kind, BlobId id, int entry_offset_count, int* entry_offsets) { +CodeBlob* AOTCodeCache::load_code_blob(AOTCodeEntry::Kind entry_kind, BlobId id) { assert(AOTCodeEntry::is_blob(entry_kind), "wrong entry kind for blob id %s", StubInfo::name(id)); - return load_code_blob(entry_kind, (uint)id, StubInfo::name(id), entry_offset_count, entry_offsets); + return load_code_blob(entry_kind, (uint)id, StubInfo::name(id)); } -CodeBlob* AOTCodeReader::compile_code_blob(const char* name, int entry_offset_count, int* entry_offsets) { +CodeBlob* AOTCodeReader::compile_code_blob(const char* name) { uint entry_position = _entry->offset(); // Read name @@ -989,21 +977,6 @@ CodeBlob* AOTCodeReader::compile_code_blob(const char* name, int entry_offset_co fix_relocations(code_blob); - // Read entries offsets - offset = read_position(); - int stored_count = *(int*)addr(offset); - assert(stored_count == entry_offset_count, "entry offset count mismatch, count in AOT code cache=%d, expected=%d", stored_count, entry_offset_count); - offset += sizeof(int); - set_read_position(offset); - for (int i = 0; i < stored_count; i++) { - uint32_t off = *(uint32_t*)addr(offset); - offset += sizeof(uint32_t); - const char* entry_name = (_entry->kind() == AOTCodeEntry::Adapter) ? AdapterHandlerEntry::entry_name(i) : ""; - log_trace(aot, codecache, stubs)("Reading adapter '%s:%s' (0x%x) offset: 0x%x from AOT Code Cache", - stored_name, entry_name, _entry->id(), off); - entry_offsets[i] = off; - } - #ifdef ASSERT LogStreamHandle(Trace, aot, codecache, stubs) log; if (log.is_enabled()) { diff --git a/src/hotspot/share/code/aotCodeCache.hpp b/src/hotspot/share/code/aotCodeCache.hpp index 69fd7549b43..778ad34e448 100644 --- a/src/hotspot/share/code/aotCodeCache.hpp +++ b/src/hotspot/share/code/aotCodeCache.hpp @@ -332,26 +332,18 @@ public: // save and restore API for non-enumerable code blobs static bool store_code_blob(CodeBlob& blob, AOTCodeEntry::Kind entry_kind, - uint id, const char* name, - int entry_offset_count = 0, - int* entry_offsets = nullptr) NOT_CDS_RETURN_(false); + uint id, const char* name) NOT_CDS_RETURN_(false); static CodeBlob* load_code_blob(AOTCodeEntry::Kind kind, - uint id, const char* name, - int entry_offset_count = 0, - int* entry_offsets = nullptr) NOT_CDS_RETURN_(nullptr); + uint id, const char* name) NOT_CDS_RETURN_(nullptr); // save and restore API for enumerable code blobs static bool store_code_blob(CodeBlob& blob, AOTCodeEntry::Kind entry_kind, - BlobId id, - int entry_offset_count = 0, - int* entry_offsets = nullptr) NOT_CDS_RETURN_(false); + BlobId id) NOT_CDS_RETURN_(false); static CodeBlob* load_code_blob(AOTCodeEntry::Kind kind, - BlobId id, - int entry_offset_count = 0, - int* entry_offsets = nullptr) NOT_CDS_RETURN_(nullptr); + BlobId id) NOT_CDS_RETURN_(nullptr); static uint store_entries_cnt() { if (is_on_for_dump()) { @@ -414,7 +406,7 @@ private: public: AOTCodeReader(AOTCodeCache* cache, AOTCodeEntry* entry); - CodeBlob* compile_code_blob(const char* name, int entry_offset_count, int* entry_offsets); + CodeBlob* compile_code_blob(const char* name); ImmutableOopMapSet* read_oop_map_set(); diff --git a/src/hotspot/share/code/codeBlob.cpp b/src/hotspot/share/code/codeBlob.cpp index cf21f1f89a4..5b87e8c5670 100644 --- a/src/hotspot/share/code/codeBlob.cpp +++ b/src/hotspot/share/code/codeBlob.cpp @@ -389,8 +389,8 @@ void RuntimeBlob::trace_new_stub(RuntimeBlob* stub, const char* name1, const cha //---------------------------------------------------------------------------------------------------- // Implementation of BufferBlob -BufferBlob::BufferBlob(const char* name, CodeBlobKind kind, int size) -: RuntimeBlob(name, kind, size, sizeof(BufferBlob)) +BufferBlob::BufferBlob(const char* name, CodeBlobKind kind, int size, uint16_t header_size) +: RuntimeBlob(name, kind, size, header_size) {} BufferBlob* BufferBlob::create(const char* name, uint buffer_size) { @@ -413,8 +413,8 @@ BufferBlob* BufferBlob::create(const char* name, uint buffer_size) { } -BufferBlob::BufferBlob(const char* name, CodeBlobKind kind, CodeBuffer* cb, int size) - : RuntimeBlob(name, kind, cb, size, sizeof(BufferBlob), CodeOffsets::frame_never_safe, 0, nullptr) +BufferBlob::BufferBlob(const char* name, CodeBlobKind kind, CodeBuffer* cb, int size, uint16_t header_size) + : RuntimeBlob(name, kind, cb, size, header_size, CodeOffsets::frame_never_safe, 0, nullptr) {} // Used by gtest @@ -446,12 +446,20 @@ void BufferBlob::free(BufferBlob *blob) { //---------------------------------------------------------------------------------------------------- // Implementation of AdapterBlob -AdapterBlob::AdapterBlob(int size, CodeBuffer* cb) : - BufferBlob("I2C/C2I adapters", CodeBlobKind::Adapter, cb, size) { +AdapterBlob::AdapterBlob(int size, CodeBuffer* cb, int entry_offset[AdapterBlob::ENTRY_COUNT]) : + BufferBlob("I2C/C2I adapters", CodeBlobKind::Adapter, cb, size, sizeof(AdapterBlob)) { + assert(entry_offset[0] == 0, "sanity check"); + for (int i = 1; i < AdapterBlob::ENTRY_COUNT; i++) { + assert(entry_offset[i] > 0 && entry_offset[i] < cb->insts()->size(), + "invalid entry offset 0x%x", entry_offset[i]); + } + _c2i_offset = entry_offset[1]; + _c2i_unverified_offset = entry_offset[2]; + _c2i_no_clinit_check_offset = entry_offset[3]; CodeCache::commit(this); } -AdapterBlob* AdapterBlob::create(CodeBuffer* cb) { +AdapterBlob* AdapterBlob::create(CodeBuffer* cb, int entry_offset[AdapterBlob::ENTRY_COUNT]) { ThreadInVMfromUnknown __tiv; // get to VM state in case we block on CodeCache_lock CodeCache::gc_on_allocation(); @@ -460,7 +468,7 @@ AdapterBlob* AdapterBlob::create(CodeBuffer* cb) { unsigned int size = CodeBlob::allocation_size(cb, sizeof(AdapterBlob)); { MutexLocker mu(CodeCache_lock, Mutex::_no_safepoint_check_flag); - blob = new (size) AdapterBlob(size, cb); + blob = new (size) AdapterBlob(size, cb, entry_offset); } // Track memory usage statistic after releasing CodeCache_lock MemoryService::track_code_cache_memory_usage(); @@ -468,6 +476,13 @@ AdapterBlob* AdapterBlob::create(CodeBuffer* cb) { return blob; } +void AdapterBlob::get_offsets(int entry_offset[ENTRY_COUNT]) { + entry_offset[0] = 0; + entry_offset[1] = _c2i_offset; + entry_offset[2] = _c2i_unverified_offset; + entry_offset[3] = _c2i_no_clinit_check_offset; +} + //---------------------------------------------------------------------------------------------------- // Implementation of VtableBlob diff --git a/src/hotspot/share/code/codeBlob.hpp b/src/hotspot/share/code/codeBlob.hpp index e5e47e2c5bb..407974f0428 100644 --- a/src/hotspot/share/code/codeBlob.hpp +++ b/src/hotspot/share/code/codeBlob.hpp @@ -372,8 +372,8 @@ class BufferBlob: public RuntimeBlob { private: // Creation support - BufferBlob(const char* name, CodeBlobKind kind, int size); - BufferBlob(const char* name, CodeBlobKind kind, CodeBuffer* cb, int size); + BufferBlob(const char* name, CodeBlobKind kind, int size, uint16_t header_size = sizeof(BufferBlob)); + BufferBlob(const char* name, CodeBlobKind kind, CodeBuffer* cb, int size, uint16_t header_size = sizeof(BufferBlob)); void* operator new(size_t s, unsigned size) throw(); @@ -404,12 +404,18 @@ class BufferBlob: public RuntimeBlob { // AdapterBlob: used to hold C2I/I2C adapters class AdapterBlob: public BufferBlob { +public: + static const int ENTRY_COUNT = 4; private: - AdapterBlob(int size, CodeBuffer* cb); - + AdapterBlob(int size, CodeBuffer* cb, int entry_offset[ENTRY_COUNT]); + // _i2c_offset is always 0 so no need to store it + int _c2i_offset; + int _c2i_unverified_offset; + int _c2i_no_clinit_check_offset; public: // Creation - static AdapterBlob* create(CodeBuffer* cb); + static AdapterBlob* create(CodeBuffer* cb, int entry_offset[ENTRY_COUNT]); + void get_offsets(int entry_offset[ENTRY_COUNT]); }; //--------------------------------------------------------------------------------------------------- diff --git a/src/hotspot/share/runtime/sharedRuntime.cpp b/src/hotspot/share/runtime/sharedRuntime.cpp index 554b94b56c6..149ebef4294 100644 --- a/src/hotspot/share/runtime/sharedRuntime.cpp +++ b/src/hotspot/share/runtime/sharedRuntime.cpp @@ -2769,12 +2769,13 @@ AdapterBlob* AdapterHandlerLibrary::lookup_aot_cache(AdapterHandlerEntry* handle ResourceMark rm; const char* name = AdapterHandlerLibrary::name(handler->fingerprint()); const uint32_t id = AdapterHandlerLibrary::id(handler->fingerprint()); - int offsets[AdapterHandlerEntry::ENTRIES_COUNT]; + int offsets[AdapterBlob::ENTRY_COUNT]; AdapterBlob* adapter_blob = nullptr; - CodeBlob* blob = AOTCodeCache::load_code_blob(AOTCodeEntry::Adapter, id, name, AdapterHandlerEntry::ENTRIES_COUNT, offsets); + CodeBlob* blob = AOTCodeCache::load_code_blob(AOTCodeEntry::Adapter, id, name); if (blob != nullptr) { adapter_blob = blob->as_adapter_blob(); + adapter_blob->get_offsets(offsets); address i2c_entry = adapter_blob->content_begin(); assert(offsets[0] == 0, "sanity check"); handler->set_entry_points(i2c_entry, i2c_entry + offsets[1], i2c_entry + offsets[2], i2c_entry + offsets[3]); @@ -2837,7 +2838,15 @@ bool AdapterHandlerLibrary::generate_adapter_code(AdapterBlob*& adapter_blob, } #endif - adapter_blob = AdapterBlob::create(&buffer); + int entry_offset[AdapterBlob::ENTRY_COUNT]; + assert(AdapterBlob::ENTRY_COUNT == 4, "sanity"); + address i2c_entry = handler->get_i2c_entry(); + entry_offset[0] = 0; // i2c_entry offset + entry_offset[1] = handler->get_c2i_entry() - i2c_entry; + entry_offset[2] = handler->get_c2i_unverified_entry() - i2c_entry; + entry_offset[3] = handler->get_c2i_no_clinit_check_entry() - i2c_entry; + + adapter_blob = AdapterBlob::create(&buffer, entry_offset); if (adapter_blob == nullptr) { // CodeCache is full, disable compilation // Ought to log this but compile log is only per compile thread @@ -2848,14 +2857,7 @@ bool AdapterHandlerLibrary::generate_adapter_code(AdapterBlob*& adapter_blob, // try to save generated code const char* name = AdapterHandlerLibrary::name(handler->fingerprint()); const uint32_t id = AdapterHandlerLibrary::id(handler->fingerprint()); - int entry_offset[AdapterHandlerEntry::ENTRIES_COUNT]; - assert(AdapterHandlerEntry::ENTRIES_COUNT == 4, "sanity"); - address i2c_entry = handler->get_i2c_entry(); - entry_offset[0] = 0; // i2c_entry offset - entry_offset[1] = handler->get_c2i_entry() - i2c_entry; - entry_offset[2] = handler->get_c2i_unverified_entry() - i2c_entry; - entry_offset[3] = handler->get_c2i_no_clinit_check_entry() - i2c_entry; - bool success = AOTCodeCache::store_code_blob(*adapter_blob, AOTCodeEntry::Adapter, id, name, AdapterHandlerEntry::ENTRIES_COUNT, entry_offset); + bool success = AOTCodeCache::store_code_blob(*adapter_blob, AOTCodeEntry::Adapter, id, name); assert(success || !AOTCodeCache::is_dumping_adapter(), "caching of adapter must be disabled"); } handler->relocate(adapter_blob->content_begin()); From cd50d78d447f9f39065bc844fb3041cba2db32db Mon Sep 17 00:00:00 2001 From: Chen Liang Date: Fri, 8 Aug 2025 17:17:21 +0000 Subject: [PATCH 067/580] 8361300: Document exceptions for Unsafe offset methods Reviewed-by: jrose, vyazici --- src/hotspot/share/prims/unsafe.cpp | 20 ++-- .../atomic/AtomicIntegerFieldUpdater.java | 3 + .../atomic/AtomicLongFieldUpdater.java | 3 + .../atomic/AtomicReferenceFieldUpdater.java | 3 + .../classes/jdk/internal/misc/Unsafe.java | 42 +++++-- .../tck/AtomicIntegerFieldUpdaterTest.java | 11 ++ .../tck/AtomicLongFieldUpdaterTest.java | 11 ++ .../tck/AtomicReferenceFieldUpdaterTest.java | 12 ++ .../AddressComputationContractTest.java | 104 ++++++++++++++++++ 9 files changed, 192 insertions(+), 17 deletions(-) create mode 100644 test/jdk/jdk/internal/misc/Unsafe/AddressComputationContractTest.java diff --git a/src/hotspot/share/prims/unsafe.cpp b/src/hotspot/share/prims/unsafe.cpp index a6300e81468..80dfaf90a28 100644 --- a/src/hotspot/share/prims/unsafe.cpp +++ b/src/hotspot/share/prims/unsafe.cpp @@ -480,7 +480,9 @@ UNSAFE_LEAF (void, Unsafe_WriteBackPostSync0(JNIEnv *env, jobject unsafe)) { ////// Random queries -static jlong find_field_offset(jclass clazz, jstring name, TRAPS) { +// Finds the object field offset of a field with the matching name, or an error code +// Error code -1 is not found, -2 is static field +static jlong find_known_instance_field_offset(jclass clazz, jstring name, TRAPS) { assert(clazz != nullptr, "clazz must not be null"); assert(name != nullptr, "name must not be null"); @@ -489,16 +491,20 @@ static jlong find_field_offset(jclass clazz, jstring name, TRAPS) { InstanceKlass* k = InstanceKlass::cast(java_lang_Class::as_Klass(JNIHandles::resolve_non_null(clazz))); - jint offset = -1; + jint offset = -1; // Not found for (JavaFieldStream fs(k); !fs.done(); fs.next()) { Symbol *name = fs.name(); if (name->equals(utf_name)) { - offset = fs.offset(); + if (!fs.access_flags().is_static()) { + offset = fs.offset(); + } else { + offset = -2; // A static field + } break; } } if (offset < 0) { - THROW_0(vmSymbols::java_lang_InternalError()); + return offset; // Error code } return field_offset_from_byte_offset(offset); } @@ -527,8 +533,8 @@ UNSAFE_ENTRY(jlong, Unsafe_ObjectFieldOffset0(JNIEnv *env, jobject unsafe, jobje return find_field_offset(field, 0, THREAD); } UNSAFE_END -UNSAFE_ENTRY(jlong, Unsafe_ObjectFieldOffset1(JNIEnv *env, jobject unsafe, jclass c, jstring name)) { - return find_field_offset(c, name, THREAD); +UNSAFE_ENTRY(jlong, Unsafe_KnownObjectFieldOffset0(JNIEnv *env, jobject unsafe, jclass c, jstring name)) { + return find_known_instance_field_offset(c, name, THREAD); } UNSAFE_END UNSAFE_ENTRY(jlong, Unsafe_StaticFieldOffset0(JNIEnv *env, jobject unsafe, jobject field)) { @@ -882,7 +888,7 @@ static JNINativeMethod jdk_internal_misc_Unsafe_methods[] = { {CC "freeMemory0", CC "(" ADR ")V", FN_PTR(Unsafe_FreeMemory0)}, {CC "objectFieldOffset0", CC "(" FLD ")J", FN_PTR(Unsafe_ObjectFieldOffset0)}, - {CC "objectFieldOffset1", CC "(" CLS LANG "String;)J", FN_PTR(Unsafe_ObjectFieldOffset1)}, + {CC "knownObjectFieldOffset0", CC "(" CLS LANG "String;)J", FN_PTR(Unsafe_KnownObjectFieldOffset0)}, {CC "staticFieldOffset0", CC "(" FLD ")J", FN_PTR(Unsafe_StaticFieldOffset0)}, {CC "staticFieldBase0", CC "(" FLD ")" OBJ, FN_PTR(Unsafe_StaticFieldBase0)}, {CC "ensureClassInitialized0", CC "(" CLS ")V", FN_PTR(Unsafe_EnsureClassInitialized0)}, diff --git a/src/java.base/share/classes/java/util/concurrent/atomic/AtomicIntegerFieldUpdater.java b/src/java.base/share/classes/java/util/concurrent/atomic/AtomicIntegerFieldUpdater.java index f947eb4f7db..2250009e8f5 100644 --- a/src/java.base/share/classes/java/util/concurrent/atomic/AtomicIntegerFieldUpdater.java +++ b/src/java.base/share/classes/java/util/concurrent/atomic/AtomicIntegerFieldUpdater.java @@ -403,6 +403,9 @@ public abstract class AtomicIntegerFieldUpdater { if (!Modifier.isVolatile(modifiers)) throw new IllegalArgumentException("Must be volatile type"); + if (Modifier.isStatic(modifiers)) + throw new IllegalArgumentException("Must not be a static field"); + // Access to protected field members is restricted to receivers only // of the accessing class, or one of its subclasses, and the // accessing class must in turn be a subclass (or package sibling) diff --git a/src/java.base/share/classes/java/util/concurrent/atomic/AtomicLongFieldUpdater.java b/src/java.base/share/classes/java/util/concurrent/atomic/AtomicLongFieldUpdater.java index b31a8edf53a..5f0a666cb04 100644 --- a/src/java.base/share/classes/java/util/concurrent/atomic/AtomicLongFieldUpdater.java +++ b/src/java.base/share/classes/java/util/concurrent/atomic/AtomicLongFieldUpdater.java @@ -398,6 +398,9 @@ public abstract class AtomicLongFieldUpdater { if (!Modifier.isVolatile(modifiers)) throw new IllegalArgumentException("Must be volatile type"); + if (Modifier.isStatic(modifiers)) + throw new IllegalArgumentException("Must not be a static field"); + // Access to protected field members is restricted to receivers only // of the accessing class, or one of its subclasses, and the // accessing class must in turn be a subclass (or package sibling) diff --git a/src/java.base/share/classes/java/util/concurrent/atomic/AtomicReferenceFieldUpdater.java b/src/java.base/share/classes/java/util/concurrent/atomic/AtomicReferenceFieldUpdater.java index e3ca4830d5a..4a758f77a47 100644 --- a/src/java.base/share/classes/java/util/concurrent/atomic/AtomicReferenceFieldUpdater.java +++ b/src/java.base/share/classes/java/util/concurrent/atomic/AtomicReferenceFieldUpdater.java @@ -363,6 +363,9 @@ public abstract class AtomicReferenceFieldUpdater { if (!Modifier.isVolatile(modifiers)) throw new IllegalArgumentException("Must be volatile type"); + if (Modifier.isStatic(modifiers)) + throw new IllegalArgumentException("Must not be a static field"); + // Access to protected field members is restricted to receivers only // of the accessing class, or one of its subclasses, and the // accessing class must in turn be a subclass (or package sibling) diff --git a/src/java.base/share/classes/jdk/internal/misc/Unsafe.java b/src/java.base/share/classes/jdk/internal/misc/Unsafe.java index ff854a8ecb3..016566ae659 100644 --- a/src/java.base/share/classes/jdk/internal/misc/Unsafe.java +++ b/src/java.base/share/classes/jdk/internal/misc/Unsafe.java @@ -1069,6 +1069,9 @@ public final class Unsafe { * the field locations in a form usable by {@link #getInt(Object,long)}. * Therefore, code which will be ported to such JVMs on 64-bit platforms * must preserve all bits of static field offsets. + * + * @throws NullPointerException if the field is {@code null} + * @throws IllegalArgumentException if the field is static * @see #getInt(Object, long) */ public long objectFieldOffset(Field f) { @@ -1080,13 +1083,17 @@ public final class Unsafe { } /** - * Reports the location of the field with a given name in the storage - * allocation of its class. + * (For compile-time known instance fields in JDK code only) Reports the + * location of the field with a given name in the storage allocation of its + * class. + *

+ * This API is used to avoid creating reflective Objects in Java code at + * startup. This should not be used to find fields in non-trusted code. + * Use the {@link #objectFieldOffset(Field) Field}-accepting version for + * arbitrary fields instead. * * @throws NullPointerException if any parameter is {@code null}. - * @throws InternalError if there is no field named {@code name} declared - * in class {@code c}, i.e., if {@code c.getDeclaredField(name)} - * would throw {@code java.lang.NoSuchFieldException}. + * @throws InternalError if the presumably known field couldn't be found * * @see #objectFieldOffset(Field) */ @@ -1095,7 +1102,16 @@ public final class Unsafe { throw new NullPointerException(); } - return objectFieldOffset1(c, name); + long result = knownObjectFieldOffset0(c, name); + if (result < 0) { + String type = switch ((int) result) { + case -2 -> "a static field"; + case -1 -> "not found"; + default -> "unknown"; + }; + throw new InternalError("Field %s.%s %s".formatted(c.getTypeName(), name, type)); + } + return result; } /** @@ -1113,6 +1129,9 @@ public final class Unsafe { * a few bits to encode an offset within a non-array object, * However, for consistency with other methods in this class, * this method reports its result as a long value. + * + * @throws NullPointerException if the field is {@code null} + * @throws IllegalArgumentException if the field is not static * @see #getInt(Object, long) */ public long staticFieldOffset(Field f) { @@ -1132,6 +1151,9 @@ public final class Unsafe { * which is a "cookie", not guaranteed to be a real Object, and it should * not be used in any way except as argument to the get and put routines in * this class. + * + * @throws NullPointerException if the field is {@code null} + * @throws IllegalArgumentException if the field is not static */ public Object staticFieldBase(Field f) { if (f == null) { @@ -3848,10 +3870,10 @@ public final class Unsafe { @IntrinsicCandidate private native void copyMemory0(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes); private native void copySwapMemory0(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes, long elemSize); - private native long objectFieldOffset0(Field f); - private native long objectFieldOffset1(Class c, String name); - private native long staticFieldOffset0(Field f); - private native Object staticFieldBase0(Field f); + private native long objectFieldOffset0(Field f); // throws IAE + private native long knownObjectFieldOffset0(Class c, String name); // error code: -1 not found, -2 static + private native long staticFieldOffset0(Field f); // throws IAE + private native Object staticFieldBase0(Field f); // throws IAE private native boolean shouldBeInitialized0(Class c); private native void ensureClassInitialized0(Class c); private native int arrayBaseOffset0(Class arrayClass); // public version returns long to promote correct arithmetic diff --git a/test/jdk/java/util/concurrent/tck/AtomicIntegerFieldUpdaterTest.java b/test/jdk/java/util/concurrent/tck/AtomicIntegerFieldUpdaterTest.java index edbf0f3ae86..91900e37b6a 100644 --- a/test/jdk/java/util/concurrent/tck/AtomicIntegerFieldUpdaterTest.java +++ b/test/jdk/java/util/concurrent/tck/AtomicIntegerFieldUpdaterTest.java @@ -44,6 +44,7 @@ public class AtomicIntegerFieldUpdaterTest extends JSR166TestCase { private volatile int privateField; int w; float z; + static volatile int q; public static void main(String[] args) { main(suite(), args); } @@ -88,6 +89,16 @@ public class AtomicIntegerFieldUpdaterTest extends JSR166TestCase { } catch (IllegalArgumentException success) {} } + /** + * construction with static field throws IllegalArgumentException + */ + public void testConstructor4() { + try { + updaterFor("q"); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + /** * construction using private field from subclass throws RuntimeException */ diff --git a/test/jdk/java/util/concurrent/tck/AtomicLongFieldUpdaterTest.java b/test/jdk/java/util/concurrent/tck/AtomicLongFieldUpdaterTest.java index 17689061426..5edcf871f42 100644 --- a/test/jdk/java/util/concurrent/tck/AtomicLongFieldUpdaterTest.java +++ b/test/jdk/java/util/concurrent/tck/AtomicLongFieldUpdaterTest.java @@ -44,6 +44,7 @@ public class AtomicLongFieldUpdaterTest extends JSR166TestCase { private volatile long privateField; long w; float z; + static volatile long q; public static void main(String[] args) { main(suite(), args); } @@ -88,6 +89,16 @@ public class AtomicLongFieldUpdaterTest extends JSR166TestCase { } catch (IllegalArgumentException success) {} } + /** + * construction with static field throws IllegalArgumentException + */ + public void testConstructor4() { + try { + updaterFor("q"); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + /** * construction using private field from subclass throws RuntimeException */ diff --git a/test/jdk/java/util/concurrent/tck/AtomicReferenceFieldUpdaterTest.java b/test/jdk/java/util/concurrent/tck/AtomicReferenceFieldUpdaterTest.java index 6df68c4c634..313f40f7918 100644 --- a/test/jdk/java/util/concurrent/tck/AtomicReferenceFieldUpdaterTest.java +++ b/test/jdk/java/util/concurrent/tck/AtomicReferenceFieldUpdaterTest.java @@ -45,6 +45,7 @@ public class AtomicReferenceFieldUpdaterTest extends JSR166TestCase { Object z; Item w; volatile int i; + static volatile Item q; public static void main(String[] args) { main(suite(), args); @@ -100,6 +101,17 @@ public class AtomicReferenceFieldUpdaterTest extends JSR166TestCase { } catch (ClassCastException success) {} } + /** + * construction with static field throws IllegalArgumentException + */ + public void testConstructor5() { + try { + updaterFor("q"); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** * construction using private field from subclass throws RuntimeException */ diff --git a/test/jdk/jdk/internal/misc/Unsafe/AddressComputationContractTest.java b/test/jdk/jdk/internal/misc/Unsafe/AddressComputationContractTest.java new file mode 100644 index 00000000000..ebdfa843272 --- /dev/null +++ b/test/jdk/jdk/internal/misc/Unsafe/AddressComputationContractTest.java @@ -0,0 +1,104 @@ +/* + * 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.lang.reflect.Field; + +import org.junit.jupiter.api.Test; + +import static jdk.internal.misc.Unsafe.getUnsafe; +import static org.junit.jupiter.api.Assertions.*; + +/* + * @test + * @bug 8361300 + * @summary Verify Unsafe memory address computation method contracts, + * exposed via sun.misc.Unsafe + * @modules java.base/jdk.internal.misc + * @run junit AddressComputationContractTest + */ +public class AddressComputationContractTest { + + int instanceField; + static int staticField; + + private static final Field INSTANCE_FIELD; + private static final Field STATIC_FIELD; + + static { + try { + INSTANCE_FIELD = AddressComputationContractTest.class.getDeclaredField("instanceField"); + STATIC_FIELD = AddressComputationContractTest.class.getDeclaredField("staticField"); + } catch (ReflectiveOperationException ex) { + throw new ExceptionInInitializerError(ex); + } + } + + @Test + void objectFieldOffset() { + assertDoesNotThrow(() -> getUnsafe().objectFieldOffset(INSTANCE_FIELD)); + assertThrows(NullPointerException.class, () -> getUnsafe().objectFieldOffset(null)); + assertThrows(IllegalArgumentException.class, () -> getUnsafe().objectFieldOffset(STATIC_FIELD)); + } + + @Test + void knownObjectFieldOffset() { + assertDoesNotThrow(() -> getUnsafe().objectFieldOffset(AddressComputationContractTest.class, "instanceField")); + assertThrows(NullPointerException.class, () -> getUnsafe().objectFieldOffset(null, "instanceField")); + assertThrows(NullPointerException.class, () -> getUnsafe().objectFieldOffset(AddressComputationContractTest.class, null)); + // Two conventional failure cases, not necessarily complete + var dneMsg = assertThrows(InternalError.class, () -> getUnsafe().objectFieldOffset(AddressComputationContractTest.class, "doesNotExist")).getMessage(); + assertTrue(dneMsg.contains("AddressComputationContractTest.doesNotExist") && dneMsg.contains("not found"), dneMsg); + var staticMsg = assertThrows(InternalError.class, () -> getUnsafe().objectFieldOffset(AddressComputationContractTest.class, "staticField")).getMessage(); + assertTrue(staticMsg.contains("AddressComputationContractTest.staticField") && staticMsg.contains("static field"), staticMsg); + } + + @Test + void staticFieldOffset() { + assertDoesNotThrow(() -> getUnsafe().staticFieldOffset(STATIC_FIELD)); + assertThrows(NullPointerException.class, () -> getUnsafe().staticFieldOffset(null)); + assertThrows(IllegalArgumentException.class, () -> getUnsafe().staticFieldOffset(INSTANCE_FIELD)); + } + + @Test + void staticFieldBase() { + assertDoesNotThrow(() -> getUnsafe().staticFieldBase(STATIC_FIELD)); + assertThrows(NullPointerException.class, () -> getUnsafe().staticFieldBase(null)); + assertThrows(IllegalArgumentException.class, () -> getUnsafe().staticFieldBase(INSTANCE_FIELD)); + } + + @Test + void arrayBaseOffset() { + assertDoesNotThrow(() -> getUnsafe().arrayBaseOffset(int[].class)); + assertThrows(NullPointerException.class, () -> getUnsafe().arrayBaseOffset(null)); + // Caused by VM trying to throw java.lang.InvalidClassException (there's one in java.io instead) + assertThrows(NoClassDefFoundError.class, () -> getUnsafe().arrayBaseOffset(AddressComputationContractTest.class)); + } + + @Test + void arrayIndexScale() { + assertDoesNotThrow(() -> getUnsafe().arrayIndexScale(int[].class)); + assertThrows(NullPointerException.class, () -> getUnsafe().arrayIndexScale(null)); + // Caused by VM trying to throw java.lang.InvalidClassException (there's one in java.io instead) + assertThrows(NoClassDefFoundError.class, () -> getUnsafe().arrayIndexScale(AddressComputationContractTest.class)); + } +} From c1c0155604cbb6c42a220d391a88b029776bdb95 Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Fri, 8 Aug 2025 21:41:44 +0000 Subject: [PATCH 068/580] 8364129: Rename libwixhelper Reviewed-by: erikj, almatvee --- make/modules/jdk.jpackage/Lib.gmk | 12 ++++++------ .../jdk/jpackage/internal/WixUiFragmentBuilder.java | 2 +- .../classes/jdk/jpackage/internal/resources/main.wxs | 2 +- .../native/{libwixhelper => libmsica}/Version.cpp | 0 .../native/{libwixhelper => libmsica}/Version.h | 0 .../libwixhelper.cpp => libmsica/libmsica.cpp} | 0 .../tools/jpackage/windows/WinLongVersionTest.java | 6 +++--- 7 files changed, 11 insertions(+), 11 deletions(-) rename src/jdk.jpackage/windows/native/{libwixhelper => libmsica}/Version.cpp (100%) rename src/jdk.jpackage/windows/native/{libwixhelper => libmsica}/Version.h (100%) rename src/jdk.jpackage/windows/native/{libwixhelper/libwixhelper.cpp => libmsica/libmsica.cpp} (100%) diff --git a/make/modules/jdk.jpackage/Lib.gmk b/make/modules/jdk.jpackage/Lib.gmk index e9c55548b0d..d2dd9d92a03 100644 --- a/make/modules/jdk.jpackage/Lib.gmk +++ b/make/modules/jdk.jpackage/Lib.gmk @@ -121,15 +121,15 @@ ifeq ($(call isTargetOs, windows), true) TARGETS += $(BUILD_LIBJPACKAGE) ############################################################################## - ## Build libwixhelper + ## Build libmsica ############################################################################## - # Build Wix custom action helper + # Build MSI custom action library # Output library in resources dir, and symbols in the object dir - $(eval $(call SetupJdkLibrary, BUILD_LIBWIXHELPER, \ - NAME := wixhelper, \ + $(eval $(call SetupJdkLibrary, BUILD_LIBMSICA, \ + NAME := msica, \ OUTPUT_DIR := $(JPACKAGE_OUTPUT_DIR), \ - SYMBOLS_DIR := $(SUPPORT_OUTPUTDIR)/native/$(MODULE)/libwixhelper, \ + SYMBOLS_DIR := $(SUPPORT_OUTPUTDIR)/native/$(MODULE)/libmsica, \ ONLY_EXPORTED := true, \ OPTIMIZATION := LOW, \ EXTRA_SRC := common, \ @@ -139,7 +139,7 @@ ifeq ($(call isTargetOs, windows), true) LIBS_windows := msi.lib ole32.lib shell32.lib shlwapi.lib user32.lib, \ )) - TARGETS += $(BUILD_LIBWIXHELPER) + TARGETS += $(BUILD_LIBMSICA) ############################################################################## ## Build msiwrapper diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixUiFragmentBuilder.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixUiFragmentBuilder.java index a00eb16a4a9..4a2a0756dbd 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixUiFragmentBuilder.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixUiFragmentBuilder.java @@ -120,7 +120,7 @@ final class WixUiFragmentBuilder extends WixFragmentBuilder { super.addFilesToConfigRoot(); if (withCustomActionsDll) { - String fname = "wixhelper.dll"; // CA dll + String fname = "msica.dll"; // CA dll try (InputStream is = ResourceLocator.class.getResourceAsStream(fname)) { Files.copy(is, getConfigRoot().resolve(fname)); } diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/main.wxs b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/main.wxs index 2a3ea3743e2..f1e8f594164 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/main.wxs +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/main.wxs @@ -65,7 +65,7 @@ - + diff --git a/src/jdk.jpackage/windows/native/libwixhelper/Version.cpp b/src/jdk.jpackage/windows/native/libmsica/Version.cpp similarity index 100% rename from src/jdk.jpackage/windows/native/libwixhelper/Version.cpp rename to src/jdk.jpackage/windows/native/libmsica/Version.cpp diff --git a/src/jdk.jpackage/windows/native/libwixhelper/Version.h b/src/jdk.jpackage/windows/native/libmsica/Version.h similarity index 100% rename from src/jdk.jpackage/windows/native/libwixhelper/Version.h rename to src/jdk.jpackage/windows/native/libmsica/Version.h diff --git a/src/jdk.jpackage/windows/native/libwixhelper/libwixhelper.cpp b/src/jdk.jpackage/windows/native/libmsica/libmsica.cpp similarity index 100% rename from src/jdk.jpackage/windows/native/libwixhelper/libwixhelper.cpp rename to src/jdk.jpackage/windows/native/libmsica/libmsica.cpp diff --git a/test/jdk/tools/jpackage/windows/WinLongVersionTest.java b/test/jdk/tools/jpackage/windows/WinLongVersionTest.java index d70cda7b264..586b69c1913 100644 --- a/test/jdk/tools/jpackage/windows/WinLongVersionTest.java +++ b/test/jdk/tools/jpackage/windows/WinLongVersionTest.java @@ -126,12 +126,12 @@ public class WinLongVersionTest { Action ended 12:08:38: FindRelatedProducts. Return value 1. ... Action start 12:08:38: JpFindRelatedProducts. - Java [12:08:38.180 libwixhelper.cpp:120 (FindRelatedProductsEx)] TRACE: Entering FindRelatedProductsEx - Java [12:08:38.185 libwixhelper.cpp:85 (`anonymous-namespace'::findInstalledPackages)] TRACE: Found {D88EEA02-56CC-34AD-8216-C2CC244FA898} product + Java [12:08:38.180 libmsica.cpp:120 (FindRelatedProductsEx)] TRACE: Entering FindRelatedProductsEx + Java [12:08:38.185 libmsica.cpp:85 (`anonymous-namespace'::findInstalledPackages)] TRACE: Found {D88EEA02-56CC-34AD-8216-C2CC244FA898} product Java [12:08:38.187 MsiCA.cpp:61 (msi::CAImpl::removeProperty)] TRACE: Removing MSI property 'JP_UPGRADABLE_FOUND' Java [12:08:38.187 MsiCA.cpp:61 (msi::CAImpl::removeProperty)] TRACE: Removing MSI property 'MIGRATE' Java [12:08:38.189 MsiCA.cpp:61 (msi::CAImpl::removeProperty)] TRACE: Removing MSI property 'JP_DOWNGRADABLE_FOUND' - Java [12:08:38.190 libwixhelper.cpp:0 (FindRelatedProductsEx)] TRACE: Exiting FindRelatedProductsEx (entered at libwixhelper.cpp:120) + Java [12:08:38.190 libmsica.cpp:0 (FindRelatedProductsEx)] TRACE: Exiting FindRelatedProductsEx (entered at libmsica.cpp:120) Action ended 12:08:38: JpFindRelatedProducts. Return value 1. */ PackageTest test2 = init.get().addInstallVerifier(cmd -> { From 8ad1fcc48a4ba49ffde6dfbb851dbb3f56077dec Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Fri, 8 Aug 2025 22:11:52 +0000 Subject: [PATCH 069/580] 8364564: Shortcut configuration is not recorded in .jpackage.xml file Reviewed-by: almatvee --- .../jpackage/internal/DesktopIntegration.java | 25 ++++--- .../jpackage/internal/LinuxFromParams.java | 9 +-- .../internal/model/LinuxLauncher.java | 11 ++- .../internal/model/LinuxLauncherMixin.java | 20 ++--- .../internal/AddLauncherArguments.java | 8 +- .../jdk/jpackage/internal/FromParams.java | 36 ++++++++- .../internal/StandardBundlerParam.java | 18 ----- .../internal/model/LauncherShortcut.java | 74 +++++++++++++++++++ .../LauncherShortcutStartupDirectory.java | 55 ++++++++++++++ .../jdk/jpackage/internal/WinFromParams.java | 20 +---- .../internal/WixAppImageFragmentBuilder.java | 64 ++++++++++------ .../jpackage/internal/model/WinLauncher.java | 15 +++- .../internal/model/WinLauncherMixin.java | 43 ++++++----- .../jdk/jpackage/test/LauncherShortcut.java | 2 +- .../jdk/jpackage/test/LauncherVerifier.java | 46 +++++++++++- 15 files changed, 327 insertions(+), 119 deletions(-) create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/LauncherShortcut.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/LauncherShortcutStartupDirectory.java diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/DesktopIntegration.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/DesktopIntegration.java index 8de462abac7..476ca3201ce 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/DesktopIntegration.java +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/DesktopIntegration.java @@ -24,10 +24,10 @@ */ package jdk.jpackage.internal; -import jdk.jpackage.internal.model.LinuxPackage; -import jdk.jpackage.internal.model.LinuxLauncher; -import jdk.jpackage.internal.model.Package; -import jdk.jpackage.internal.model.Launcher; +import static jdk.jpackage.internal.ApplicationImageUtils.createLauncherIconResource; +import static jdk.jpackage.internal.model.LauncherShortcut.toRequest; +import static jdk.jpackage.internal.util.function.ThrowingFunction.toFunction; + import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; @@ -45,12 +45,13 @@ import java.util.stream.Stream; import javax.imageio.ImageIO; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; -import static jdk.jpackage.internal.ApplicationImageUtils.createLauncherIconResource; import jdk.jpackage.internal.model.FileAssociation; +import jdk.jpackage.internal.model.LinuxLauncher; +import jdk.jpackage.internal.model.LinuxPackage; +import jdk.jpackage.internal.model.Package; import jdk.jpackage.internal.util.CompositeProxy; import jdk.jpackage.internal.util.PathUtils; import jdk.jpackage.internal.util.XmlUtils; -import static jdk.jpackage.internal.util.function.ThrowingFunction.toFunction; /** * Helper to create files for desktop integration. @@ -77,7 +78,7 @@ final class DesktopIntegration extends ShellCustomAction { // Need desktop and icon files if one of conditions is met: // - there are file associations configured // - user explicitly requested to create a shortcut - boolean withDesktopFile = !associations.isEmpty() || launcher.shortcut().orElse(false); + boolean withDesktopFile = !associations.isEmpty() || toRequest(launcher.shortcut()).orElse(false); var curIconResource = createLauncherIconResource(pkg.app(), launcher, env::createResource); @@ -132,7 +133,7 @@ final class DesktopIntegration extends ShellCustomAction { nestedIntegrations = pkg.app().additionalLaunchers().stream().map(v -> { return (LinuxLauncher)v; }).filter(l -> { - return l.shortcut().orElse(true); + return toRequest(l.shortcut()).orElse(true); }).map(toFunction(l -> { return new DesktopIntegration(env, pkg, l); })).toList(); @@ -225,6 +226,9 @@ final class DesktopIntegration extends ShellCustomAction { } private Map createDataForDesktopFile() { + + var installedLayout = pkg.asInstalledPackageApplicationLayout().orElseThrow(); + Map data = new HashMap<>(); data.put("APPLICATION_NAME", launcher.name()); data.put("APPLICATION_DESCRIPTION", launcher.description()); @@ -232,8 +236,7 @@ final class DesktopIntegration extends ShellCustomAction { f -> f.installPath().toString()).orElse(null)); data.put("DEPLOY_BUNDLE_CATEGORY", pkg.menuGroupName()); data.put("APPLICATION_LAUNCHER", Enquoter.forPropertyValues().applyTo( - pkg.asInstalledPackageApplicationLayout().orElseThrow().launchersDirectory().resolve( - launcher.executableNameWithSuffix()).toString())); + installedLayout.launchersDirectory().resolve(launcher.executableNameWithSuffix()).toString())); return data; } @@ -481,7 +484,7 @@ final class DesktopIntegration extends ShellCustomAction { private final BuildEnv env; private final LinuxPackage pkg; - private final Launcher launcher; + private final LinuxLauncher launcher; private final List associations; diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxFromParams.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxFromParams.java index 7dff3cd73ae..6967dea111e 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxFromParams.java +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxFromParams.java @@ -29,16 +29,14 @@ import static jdk.jpackage.internal.FromParams.createApplicationBuilder; import static jdk.jpackage.internal.FromParams.createApplicationBundlerParam; import static jdk.jpackage.internal.FromParams.createPackageBuilder; import static jdk.jpackage.internal.FromParams.createPackageBundlerParam; +import static jdk.jpackage.internal.FromParams.findLauncherShortcut; import static jdk.jpackage.internal.LinuxPackagingPipeline.APPLICATION_LAYOUT; -import static jdk.jpackage.internal.StandardBundlerParam.SHORTCUT_HINT; import static jdk.jpackage.internal.model.StandardPackageType.LINUX_DEB; import static jdk.jpackage.internal.model.StandardPackageType.LINUX_RPM; import static jdk.jpackage.internal.util.function.ThrowingFunction.toFunction; import java.io.IOException; import java.util.Map; -import java.util.Optional; -import java.util.stream.Stream; import jdk.jpackage.internal.model.ConfigException; import jdk.jpackage.internal.model.LinuxApplication; import jdk.jpackage.internal.model.LinuxLauncher; @@ -51,11 +49,10 @@ final class LinuxFromParams { private static LinuxApplication createLinuxApplication( Map params) throws ConfigException, IOException { final var launcherFromParams = new LauncherFromParams(); + final var app = createApplicationBuilder(params, toFunction(launcherParams -> { final var launcher = launcherFromParams.create(launcherParams); - final var shortcut = Stream.of(SHORTCUT_HINT, LINUX_SHORTCUT_HINT).map(param -> { - return param.findIn(launcherParams); - }).filter(Optional::isPresent).map(Optional::get).findFirst(); + final var shortcut = findLauncherShortcut(LINUX_SHORTCUT_HINT, params, launcherParams); return LinuxLauncher.create(launcher, new LinuxLauncherMixin.Stub(shortcut)); }), APPLICATION_LAYOUT).create(); return LinuxApplication.create(app); diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/model/LinuxLauncher.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/model/LinuxLauncher.java index 8970f2198c2..c84b5e3bbf5 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/model/LinuxLauncher.java +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/model/LinuxLauncher.java @@ -24,6 +24,7 @@ */ package jdk.jpackage.internal.model; +import java.util.HashMap; import java.util.Map; import jdk.jpackage.internal.util.CompositeProxy; @@ -36,9 +37,11 @@ public interface LinuxLauncher extends Launcher, LinuxLauncherMixin { @Override default Map extraAppImageFileData() { - return shortcut().map(v -> { - return Map.of("shortcut", Boolean.toString(v)); - }).orElseGet(Map::of); + Map map = new HashMap<>(); + shortcut().ifPresent(shortcut -> { + shortcut.store(SHORTCUT_ID, map::put); + }); + return map; } /** @@ -52,4 +55,6 @@ public interface LinuxLauncher extends Launcher, LinuxLauncherMixin { public static LinuxLauncher create(Launcher launcher, LinuxLauncherMixin mixin) { return CompositeProxy.create(LinuxLauncher.class, launcher, mixin); } + + public static final String SHORTCUT_ID = "linux-shortcut"; } diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/model/LinuxLauncherMixin.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/model/LinuxLauncherMixin.java index d5e15101c7e..e8ff61ca239 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/model/LinuxLauncherMixin.java +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/model/LinuxLauncherMixin.java @@ -32,24 +32,20 @@ import java.util.Optional; public interface LinuxLauncherMixin { /** - * Gets the start menu shortcut setting of this application launcher. + * Gets the start menu shortcut of this application launcher. *

- * Returns true if this application launcher was requested to have - * the start menu shortcut. - *

- * Returns false if this application launcher was requested not to - * have the start menu shortcut. - *

- * Returns an empty {@link Optional} instance if there was no request about the - * start menu shortcut for this application launcher. + * Returns a non-empty {@link Optional} instance if a request about the start + * menu shortcut for this application launcher was made and an empty + * {@link Optional} instance if there was no request about the start menu + * shortcut for this application launcher. * - * @return the start menu shortcut setting of this application launcher + * @return the start menu shortcut of this application launcher */ - Optional shortcut(); + Optional shortcut(); /** * Default implementation of {@link LinuxLauncherMixin} interface. */ - record Stub(Optional shortcut) implements LinuxLauncherMixin { + record Stub(Optional shortcut) implements LinuxLauncherMixin { } } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AddLauncherArguments.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AddLauncherArguments.java index d9946075c4f..93d037c6a45 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AddLauncherArguments.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AddLauncherArguments.java @@ -36,8 +36,6 @@ import jdk.internal.util.OperatingSystem; import jdk.jpackage.internal.Arguments.CLIOptions; import static jdk.jpackage.internal.StandardBundlerParam.LAUNCHER_DATA; import static jdk.jpackage.internal.StandardBundlerParam.APP_NAME; -import static jdk.jpackage.internal.StandardBundlerParam.MENU_HINT; -import static jdk.jpackage.internal.StandardBundlerParam.SHORTCUT_HINT; /* * AddLauncherArguments @@ -135,16 +133,16 @@ class AddLauncherArguments { Arguments.putUnlessNull(bundleParams, CLIOptions.WIN_CONSOLE_HINT.getId(), getOptionValue(CLIOptions.WIN_CONSOLE_HINT)); - Arguments.putUnlessNull(bundleParams, SHORTCUT_HINT.getID(), + Arguments.putUnlessNull(bundleParams, CLIOptions.WIN_SHORTCUT_HINT.getId(), getOptionValue(CLIOptions.WIN_SHORTCUT_HINT)); - Arguments.putUnlessNull(bundleParams, MENU_HINT.getID(), + Arguments.putUnlessNull(bundleParams, CLIOptions.WIN_MENU_HINT.getId(), getOptionValue(CLIOptions.WIN_MENU_HINT)); } if (OperatingSystem.isLinux()) { Arguments.putUnlessNull(bundleParams, CLIOptions.LINUX_CATEGORY.getId(), getOptionValue(CLIOptions.LINUX_CATEGORY)); - Arguments.putUnlessNull(bundleParams, SHORTCUT_HINT.getID(), + Arguments.putUnlessNull(bundleParams, CLIOptions.LINUX_SHORTCUT_HINT.getId(), getOptionValue(CLIOptions.LINUX_SHORTCUT_HINT)); } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromParams.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromParams.java index 5e940aba18b..34818fafc94 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromParams.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromParams.java @@ -24,6 +24,9 @@ */ package jdk.jpackage.internal; +import static jdk.jpackage.internal.Arguments.CLIOptions.LINUX_SHORTCUT_HINT; +import static jdk.jpackage.internal.Arguments.CLIOptions.WIN_MENU_HINT; +import static jdk.jpackage.internal.Arguments.CLIOptions.WIN_SHORTCUT_HINT; import static jdk.jpackage.internal.StandardBundlerParam.ABOUT_URL; import static jdk.jpackage.internal.StandardBundlerParam.ADD_LAUNCHERS; import static jdk.jpackage.internal.StandardBundlerParam.ADD_MODULES; @@ -63,6 +66,8 @@ import jdk.jpackage.internal.model.ApplicationLayout; import jdk.jpackage.internal.model.ConfigException; import jdk.jpackage.internal.model.ExternalApplication.LauncherInfo; import jdk.jpackage.internal.model.Launcher; +import jdk.jpackage.internal.model.LauncherShortcut; +import jdk.jpackage.internal.model.LauncherShortcutStartupDirectory; import jdk.jpackage.internal.model.PackageType; import jdk.jpackage.internal.model.RuntimeLayout; import jdk.jpackage.internal.util.function.ThrowingFunction; @@ -165,6 +170,32 @@ final class FromParams { jdk.jpackage.internal.model.Package.class.getName())); } + static Optional findLauncherShortcut( + BundlerParamInfo shortcutParam, + Map mainParams, + Map launcherParams) { + + Optional launcherValue; + if (launcherParams == mainParams) { + // The main launcher + launcherValue = Optional.empty(); + } else { + launcherValue = shortcutParam.findIn(launcherParams); + } + + return launcherValue.map(withShortcut -> { + if (withShortcut) { + return Optional.of(LauncherShortcutStartupDirectory.DEFAULT); + } else { + return Optional.empty(); + } + }).or(() -> { + return shortcutParam.findIn(mainParams).map(_ -> { + return Optional.of(LauncherShortcutStartupDirectory.DEFAULT); + }); + }).map(LauncherShortcut::new); + } + private static ApplicationLaunchers createLaunchers( Map params, Function, Launcher> launcherMapper) { @@ -195,8 +226,9 @@ final class FromParams { // mainParams), APP_NAME.fetchFrom(launcherParams))); launcherParams.put(DESCRIPTION.getID(), DESCRIPTION.fetchFrom(mainParams)); } - return AddLauncherArguments.merge(mainParams, launcherParams, ICON.getID(), ADD_LAUNCHERS - .getID(), FILE_ASSOCIATIONS.getID()); + return AddLauncherArguments.merge(mainParams, launcherParams, ICON.getID(), + ADD_LAUNCHERS.getID(), FILE_ASSOCIATIONS.getID(), WIN_MENU_HINT.getId(), + WIN_SHORTCUT_HINT.getId(), LINUX_SHORTCUT_HINT.getId()); } static final BundlerParamInfo APPLICATION = createApplicationBundlerParam(null); diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/StandardBundlerParam.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/StandardBundlerParam.java index 076d6bfc895..6b89bb3ee65 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/StandardBundlerParam.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/StandardBundlerParam.java @@ -307,24 +307,6 @@ final class StandardBundlerParam { true : Boolean.valueOf(s) ); - static final BundlerParamInfo SHORTCUT_HINT = - new BundlerParamInfo<>( - "shortcut-hint", // not directly related to a CLI option - Boolean.class, - params -> true, // defaults to true - (s, p) -> (s == null || "null".equalsIgnoreCase(s)) ? - true : Boolean.valueOf(s) - ); - - static final BundlerParamInfo MENU_HINT = - new BundlerParamInfo<>( - "menu-hint", // not directly related to a CLI option - Boolean.class, - params -> true, // defaults to true - (s, p) -> (s == null || "null".equalsIgnoreCase(s)) ? - true : Boolean.valueOf(s) - ); - static final BundlerParamInfo RESOURCE_DIR = new BundlerParamInfo<>( Arguments.CLIOptions.RESOURCE_DIR.getId(), diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/LauncherShortcut.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/LauncherShortcut.java new file mode 100644 index 00000000000..4188bccb073 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/LauncherShortcut.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.jpackage.internal.model; + +import java.util.Objects; +import java.util.Optional; +import java.util.function.BiConsumer; + +/** + * A shortcut to launch an application launcher. + */ +public record LauncherShortcut(Optional startupDirectory) { + + public LauncherShortcut { + Objects.requireNonNull(startupDirectory); + } + + public LauncherShortcut(LauncherShortcutStartupDirectory startupDirectory) { + this(Optional.of(startupDirectory)); + } + + public LauncherShortcut() { + this(Optional.empty()); + } + + void store(String propertyName, BiConsumer sink) { + Objects.requireNonNull(propertyName); + Objects.requireNonNull(sink); + if (startupDirectory.isEmpty()) { + sink.accept(propertyName, Boolean.FALSE.toString()); + } else { + startupDirectory.ifPresent(v -> { + sink.accept(propertyName, v.asStringValue()); + }); + } + } + + /** + * Converts the given shortcut into a shortcut request. + *

+ * Returns true if shortcut was explicitly requested. + *

+ * Returns false if no shortcut was explicitly requested. + *

+ * Returns an empty {@link Optional} instance if there was no shortcut request. + * + * @return shortcut request + */ + public static Optional toRequest(Optional shortcut) { + return shortcut.map(v -> v.startupDirectory().isPresent()); + } +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/LauncherShortcutStartupDirectory.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/LauncherShortcutStartupDirectory.java new file mode 100644 index 00000000000..c604b00c3e2 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/LauncherShortcutStartupDirectory.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. 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.jpackage.internal.model; + +import java.util.Objects; + +/** + * The directory in which to run an application launcher when it is started from + * a shortcut. + */ +public enum LauncherShortcutStartupDirectory { + + /** + * Platform-specific default value. + *

+ * On Windows, it indicates that the startup directory should be the package's + * installation directory. + *

+ * On Linux, it indicates that a shortcut doesn't have the startup directory + * configured explicitly. + */ + DEFAULT("true"); + + LauncherShortcutStartupDirectory(String stringValue) { + this.stringValue = Objects.requireNonNull(stringValue); + } + + public String asStringValue() { + return stringValue; + } + + private final String stringValue; +} diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinFromParams.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinFromParams.java index 95f16d09575..15d8d2f83b0 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinFromParams.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinFromParams.java @@ -24,24 +24,19 @@ */ package jdk.jpackage.internal; -import static java.util.stream.Collectors.toSet; import static jdk.jpackage.internal.BundlerParamInfo.createBooleanBundlerParam; import static jdk.jpackage.internal.BundlerParamInfo.createStringBundlerParam; import static jdk.jpackage.internal.FromParams.createApplicationBuilder; import static jdk.jpackage.internal.FromParams.createApplicationBundlerParam; import static jdk.jpackage.internal.FromParams.createPackageBuilder; import static jdk.jpackage.internal.FromParams.createPackageBundlerParam; -import static jdk.jpackage.internal.StandardBundlerParam.MENU_HINT; +import static jdk.jpackage.internal.FromParams.findLauncherShortcut; import static jdk.jpackage.internal.StandardBundlerParam.RESOURCE_DIR; -import static jdk.jpackage.internal.StandardBundlerParam.SHORTCUT_HINT; import static jdk.jpackage.internal.WinPackagingPipeline.APPLICATION_LAYOUT; import static jdk.jpackage.internal.model.StandardPackageType.WIN_MSI; -import static jdk.jpackage.internal.model.WinLauncherMixin.WinShortcut.WIN_SHORTCUT_DESKTOP; -import static jdk.jpackage.internal.model.WinLauncherMixin.WinShortcut.WIN_SHORTCUT_START_MENU; import static jdk.jpackage.internal.util.function.ThrowingFunction.toFunction; import java.io.IOException; -import java.util.List; import java.util.Map; import java.util.UUID; import jdk.jpackage.internal.model.ConfigException; @@ -63,18 +58,11 @@ final class WinFromParams { final boolean isConsole = CONSOLE_HINT.findIn(launcherParams).orElse(false); - final var shortcuts = Map.of(WIN_SHORTCUT_DESKTOP, List.of(SHORTCUT_HINT, - WIN_SHORTCUT_HINT), WIN_SHORTCUT_START_MENU, List.of(MENU_HINT, - WIN_MENU_HINT)).entrySet().stream().filter(e -> { + final var startMenuShortcut = findLauncherShortcut(WIN_MENU_HINT, params, launcherParams); - final var shortcutParams = e.getValue(); + final var desktopShortcut = findLauncherShortcut(WIN_SHORTCUT_HINT, params, launcherParams); - return shortcutParams.get(0).findIn(launcherParams).orElseGet(() -> { - return shortcutParams.get(1).findIn(launcherParams).orElse(false); - }); - }).map(Map.Entry::getKey).collect(toSet()); - - return WinLauncher.create(launcher, new WinLauncherMixin.Stub(isConsole, shortcuts)); + return WinLauncher.create(launcher, new WinLauncherMixin.Stub(isConsole, startMenuShortcut, desktopShortcut)); }), APPLICATION_LAYOUT).create(); diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixAppImageFragmentBuilder.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixAppImageFragmentBuilder.java index 7e400c5be29..ea4d9eee19a 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixAppImageFragmentBuilder.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixAppImageFragmentBuilder.java @@ -25,12 +25,9 @@ package jdk.jpackage.internal; -import jdk.jpackage.internal.model.WinLauncher; -import jdk.jpackage.internal.model.WinMsiPackage; -import jdk.jpackage.internal.model.Launcher; -import jdk.jpackage.internal.model.DottedVersion; -import jdk.jpackage.internal.model.ApplicationLayout; -import jdk.jpackage.internal.util.PathGroup; +import static java.util.stream.Collectors.toMap; +import static jdk.jpackage.internal.util.CollectionUtils.toCollection; + import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Path; @@ -50,7 +47,6 @@ import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Collectors; -import static java.util.stream.Collectors.toMap; import java.util.stream.Stream; import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamException; @@ -60,15 +56,19 @@ import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; -import static jdk.jpackage.internal.util.CollectionUtils.toCollection; -import jdk.jpackage.internal.model.WinLauncherMixin.WinShortcut; import jdk.jpackage.internal.WixToolset.WixToolsetType; import jdk.jpackage.internal.model.AppImageLayout; +import jdk.jpackage.internal.model.ApplicationLayout; +import jdk.jpackage.internal.model.DottedVersion; import jdk.jpackage.internal.model.FileAssociation; +import jdk.jpackage.internal.model.Launcher; +import jdk.jpackage.internal.model.LauncherShortcut; +import jdk.jpackage.internal.model.WinLauncher; +import jdk.jpackage.internal.model.WinMsiPackage; +import jdk.jpackage.internal.util.PathGroup; import jdk.jpackage.internal.util.PathUtils; -import jdk.jpackage.internal.util.XmlUtils; import jdk.jpackage.internal.util.XmlConsumer; -import static jdk.jpackage.internal.util.function.ThrowingConsumer.toConsumer; +import jdk.jpackage.internal.util.XmlUtils; import org.w3c.dom.NodeList; /** @@ -352,7 +352,7 @@ final class WixAppImageFragmentBuilder extends WixFragmentBuilder { private final Config cfg; private final Id id; - }; + } private static void addComponentGroup(XMLStreamWriter xml, String id, List componentIds) throws XMLStreamException, IOException { @@ -469,7 +469,18 @@ final class WixAppImageFragmentBuilder extends WixFragmentBuilder { launcher.executableNameWithSuffix()); if (folder.isRequestedFor(launcher)) { - String componentId = addShortcutComponent(xml, launcherPath, folder); + var workDirectory = folder.shortcut(launcher).startupDirectory().map(v -> { + switch (v) { + case DEFAULT -> { + return INSTALLDIR; + } + default -> { + throw new AssertionError(); + } + } + }).orElseThrow(); + + String componentId = addShortcutComponent(xml, launcherPath, folder, workDirectory); if (componentId != null) { Path folderPath = folder.getPath(this); @@ -499,23 +510,26 @@ final class WixAppImageFragmentBuilder extends WixFragmentBuilder { } private String addShortcutComponent(XMLStreamWriter xml, Path launcherPath, - ShortcutsFolder folder) throws XMLStreamException, IOException { + ShortcutsFolder folder, Path shortcutWorkDir) throws XMLStreamException, IOException { Objects.requireNonNull(folder); if (!INSTALLDIR.equals(launcherPath.getName(0))) { throw throwInvalidPathException(launcherPath); } + if (!INSTALLDIR.equals(shortcutWorkDir.getName(0))) { + throw throwInvalidPathException(shortcutWorkDir); + } + String launcherBasename = PathUtils.replaceSuffix( IOUtils.getFileName(launcherPath), "").toString(); Path shortcutPath = folder.getPath(this).resolve(launcherBasename); return addComponent(xml, shortcutPath, Component.Shortcut, unused -> { xml.writeAttribute("Name", launcherBasename); - xml.writeAttribute("WorkingDirectory", INSTALLDIR.toString()); + xml.writeAttribute("WorkingDirectory", Id.Folder.of(shortcutWorkDir)); xml.writeAttribute("Advertise", "no"); - xml.writeAttribute("Target", String.format("[#%s]", - Component.File.idOf(launcherPath))); + xml.writeAttribute("Target", String.format("[#%s]", Id.File.of(launcherPath))); }); } @@ -906,15 +920,15 @@ final class WixAppImageFragmentBuilder extends WixFragmentBuilder { } enum ShortcutsFolder { - ProgramMenu(PROGRAM_MENU_PATH, WinShortcut.WIN_SHORTCUT_START_MENU, + ProgramMenu(PROGRAM_MENU_PATH, WinLauncher::startMenuShortcut, "JP_INSTALL_STARTMENU_SHORTCUT", "JpStartMenuShortcutPrompt"), - Desktop(DESKTOP_PATH, WinShortcut.WIN_SHORTCUT_DESKTOP, + Desktop(DESKTOP_PATH, WinLauncher::desktopShortcut, "JP_INSTALL_DESKTOP_SHORTCUT", "JpDesktopShortcutPrompt"); - private ShortcutsFolder(Path root, WinShortcut shortcutId, + private ShortcutsFolder(Path root, Function> shortcut, String property, String wixVariableName) { this.root = root; - this.shortcutId = shortcutId; + this.shortcut = shortcut; this.wixVariableName = wixVariableName; this.property = property; } @@ -927,7 +941,11 @@ final class WixAppImageFragmentBuilder extends WixFragmentBuilder { } boolean isRequestedFor(WinLauncher launcher) { - return launcher.shortcuts().contains(shortcutId); + return LauncherShortcut.toRequest(shortcut.apply(launcher)).orElse(false); + } + + LauncherShortcut shortcut(WinLauncher launcher) { + return shortcut.apply(launcher).orElseThrow(); } String getWixVariableName() { @@ -947,7 +965,7 @@ final class WixAppImageFragmentBuilder extends WixFragmentBuilder { private final Path root; private final String property; private final String wixVariableName; - private final WinShortcut shortcutId; + private final Function> shortcut; } private boolean systemWide; diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/model/WinLauncher.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/model/WinLauncher.java index b10ca99d483..3052029a33c 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/model/WinLauncher.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/model/WinLauncher.java @@ -24,9 +24,8 @@ */ package jdk.jpackage.internal.model; -import static java.util.stream.Collectors.toMap; - import java.io.InputStream; +import java.util.HashMap; import java.util.Map; import java.util.Optional; import jdk.jpackage.internal.resources.ResourceLocator; @@ -47,10 +46,20 @@ public interface WinLauncher extends Launcher, WinLauncherMixin { @Override default Map extraAppImageFileData() { - return shortcuts().stream().collect(toMap(WinShortcut::name, v -> Boolean.toString(true))); + Map map = new HashMap<>(); + desktopShortcut().ifPresent(shortcut -> { + shortcut.store(SHORTCUT_DESKTOP_ID, map::put); + }); + startMenuShortcut().ifPresent(shortcut -> { + shortcut.store(SHORTCUT_START_MENU_ID, map::put); + }); + return map; } public static WinLauncher create(Launcher launcher, WinLauncherMixin mixin) { return CompositeProxy.create(WinLauncher.class, launcher, mixin); } + + public static final String SHORTCUT_START_MENU_ID = "win-menu"; + public static final String SHORTCUT_DESKTOP_ID = "win-shortcut"; } diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/model/WinLauncherMixin.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/model/WinLauncherMixin.java index 65b6a1bab46..1762678434b 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/model/WinLauncherMixin.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/model/WinLauncherMixin.java @@ -24,30 +24,37 @@ */ package jdk.jpackage.internal.model; -import java.util.Set; +import java.util.Optional; public interface WinLauncherMixin { boolean isConsole(); - enum WinShortcut { - WIN_SHORTCUT_DESKTOP("shortcut"), - WIN_SHORTCUT_START_MENU("menu"), - ; + /** + * Gets the start menu shortcut of this application launcher. + *

+ * Returns a non-empty {@link Optional} instance if a request about the start + * menu shortcut for this application launcher was made and an empty + * {@link Optional} instance if there was no request about the start menu + * shortcut for this application launcher. + * + * @return the start menu shortcut of this application launcher + */ + Optional startMenuShortcut(); - WinShortcut(String name) { - this.name = name; - } + /** + * Gets the desktop shortcut of this application launcher. + *

+ * Returns a non-empty {@link Optional} instance if a request about the desktop + * shortcut for this application launcher was made and an empty {@link Optional} + * instance if there was no request about the desktop shortcut for this + * application launcher. + * + * @return the start menu shortcut of this application launcher + */ + Optional desktopShortcut(); - public String getName() { - return name; - } - - private final String name; - } - - Set shortcuts(); - - record Stub(boolean isConsole, Set shortcuts) implements WinLauncherMixin { + record Stub(boolean isConsole, Optional startMenuShortcut, + Optional desktopShortcut) implements WinLauncherMixin { } } diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LauncherShortcut.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LauncherShortcut.java index 5e86f975870..15bb3ea0333 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LauncherShortcut.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LauncherShortcut.java @@ -92,7 +92,7 @@ public enum LauncherShortcut { } public String appImageFilePropertyName() { - return propertyName.substring(propertyName.indexOf('-') + 1); + return propertyName; } public String optionName() { diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LauncherVerifier.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LauncherVerifier.java index ceda32eb8ed..b3ed030c69d 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LauncherVerifier.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LauncherVerifier.java @@ -77,6 +77,11 @@ public final class LauncherVerifier { VERIFY_UNINSTALLED((verifier, cmd) -> { verifier.verifyInstalled(cmd, false); }), + VERIFY_APP_IMAGE_FILE((verifier, cmd) -> { + if (cmd.isImagePackageType()) { + verifier.verifyInAppImageFile(cmd); + } + }), EXECUTE_LAUNCHER(LauncherVerifier::executeLauncher), ; @@ -91,7 +96,7 @@ public final class LauncherVerifier { private final BiConsumer action; static final List VERIFY_APP_IMAGE = List.of( - VERIFY_ICON, VERIFY_DESCRIPTION, VERIFY_INSTALLED + VERIFY_ICON, VERIFY_DESCRIPTION, VERIFY_INSTALLED, VERIFY_APP_IMAGE_FILE ); static final List VERIFY_DEFAULTS = Stream.concat( @@ -279,6 +284,45 @@ public final class LauncherVerifier { } } + private void verifyInAppImageFile(JPackageCommand cmd) { + cmd.verifyIsOfType(PackageType.IMAGE); + if (!isMainLauncher()) { + Stream shortcuts; + if (TKit.isWindows()) { + shortcuts = Stream.of(LauncherShortcut.WIN_DESKTOP_SHORTCUT, LauncherShortcut.WIN_START_MENU_SHORTCUT); + } else if (TKit.isLinux()) { + shortcuts = Stream.of(LauncherShortcut.LINUX_SHORTCUT); + } else { + shortcuts = Stream.of(); + } + + var aif = AppImageFile.load(cmd.outputBundle()); + var aifFileName = AppImageFile.getPathInAppImage(Path.of("")).getFileName(); + + var aifProps = Objects.requireNonNull(aif.addLaunchers().get(name)); + + shortcuts.forEach(shortcut -> { + var recordedShortcut = aifProps.get(shortcut.appImageFilePropertyName()); + properties.flatMap(props -> { + return props.findProperty(shortcut.propertyName()); + }).ifPresentOrElse(expectedShortcut -> { + TKit.assertNotNull(recordedShortcut, String.format( + "Check shortcut [%s] of launcher [%s] is recorded in %s file", + shortcut, name, aifFileName)); + TKit.assertEquals( + StartupDirectory.parse(expectedShortcut), + StartupDirectory.parse(recordedShortcut), + String.format("Check the value of shortcut [%s] of launcher [%s] recorded in %s file", + shortcut, name, aifFileName)); + }, () -> { + TKit.assertNull(recordedShortcut, String.format( + "Check shortcut [%s] of launcher [%s] is NOT recorded in %s file", + shortcut, name, aifFileName)); + }); + }); + } + } + private void executeLauncher(JPackageCommand cmd) throws IOException { Path launcherPath = cmd.appLauncherPath(name); From f83454cd61538b653656ccf81759b3cc7286ed67 Mon Sep 17 00:00:00 2001 From: Jaikiran Pai Date: Sat, 9 Aug 2025 02:00:58 +0000 Subject: [PATCH 070/580] 8364786: Test java/net/vthread/HttpALot.java intermittently fails - 24999 handled, expected 25000 Reviewed-by: dfuchs, alanb, vyazici --- test/jdk/java/net/vthread/HttpALot.java | 45 +++++++++++++++++-------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/test/jdk/java/net/vthread/HttpALot.java b/test/jdk/java/net/vthread/HttpALot.java index c016824a92a..6418e103a35 100644 --- a/test/jdk/java/net/vthread/HttpALot.java +++ b/test/jdk/java/net/vthread/HttpALot.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 @@ -21,7 +21,7 @@ * questions. */ -/** +/* * @test * @bug 8284161 * @summary Stress test the HTTP protocol handler and HTTP server @@ -44,6 +44,7 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Proxy; import java.net.URL; +import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; @@ -52,6 +53,8 @@ import jdk.test.lib.net.URIBuilder; public class HttpALot { + private static final String HELLO = "Hello"; + public static void main(String[] args) throws Exception { int requests = 25_000; if (args.length > 0) { @@ -65,12 +68,20 @@ public class HttpALot { InetAddress lb = InetAddress.getLoopbackAddress(); HttpServer server = HttpServer.create(new InetSocketAddress(lb, 0), 1024); ThreadFactory factory = Thread.ofVirtual().factory(); - server.setExecutor(Executors.newThreadPerTaskExecutor(factory)); + final ExecutorService serverExecutor = Executors.newThreadPerTaskExecutor(factory); + server.setExecutor(serverExecutor); server.createContext("/hello", e -> { - byte[] response = "Hello".getBytes("UTF-8"); - e.sendResponseHeaders(200, response.length); - try (OutputStream out = e.getResponseBody()) { - out.write(response); + try { + byte[] response = HELLO.getBytes("UTF-8"); + e.sendResponseHeaders(200, response.length); + try (OutputStream out = e.getResponseBody()) { + out.write(response); + } + } catch (Throwable t) { + System.err.println("failed to handle request " + e.getRequestURI() + + " due to: " + t); + t.printStackTrace(); + throw t; // let it propagate } requestsHandled.incrementAndGet(); }); @@ -85,15 +96,21 @@ public class HttpALot { // go server.start(); - try { - factory = Thread.ofVirtual().name("fetcher-", 0).factory(); - try (var executor = Executors.newThreadPerTaskExecutor(factory)) { - for (int i = 1; i <= requests; i++) { - executor.submit(() -> fetch(url)).get(); + try (serverExecutor) { + try { + factory = Thread.ofVirtual().name("fetcher-", 0).factory(); + try (var executor = Executors.newThreadPerTaskExecutor(factory)) { + for (int i = 1; i <= requests; i++) { + final String actual = executor.submit(() -> fetch(url)).get(); + if (!HELLO.equals(actual)) { + throw new RuntimeException("unexpected response: \"" + actual + + "\" for request " + i); + } + } } + } finally { + server.stop(1); } - } finally { - server.stop(1); } if (requestsHandled.get() < requests) { From e13b4c8de944ab14a1d12f6251e83f4fdd9e0198 Mon Sep 17 00:00:00 2001 From: Chen Liang Date: Sat, 9 Aug 2025 23:44:21 +0000 Subject: [PATCH 071/580] 8358535: Changes in ClassValue (JDK-8351996) caused a 1-9% regression in Renaissance-PageRank Reviewed-by: jrose, shade --- .../share/classes/java/lang/ClassValue.java | 3 + test/jdk/java/lang/invoke/ClassValueTest.java | 73 +++++++++++++++++-- 2 files changed, 70 insertions(+), 6 deletions(-) diff --git a/src/java.base/share/classes/java/lang/ClassValue.java b/src/java.base/share/classes/java/lang/ClassValue.java index dfbdf3c5bf8..2a133324fb5 100644 --- a/src/java.base/share/classes/java/lang/ClassValue.java +++ b/src/java.base/share/classes/java/lang/ClassValue.java @@ -476,6 +476,9 @@ public abstract class ClassValue { if (updated != entry) { put(classValue.identity, updated); } + // Add to the cache, to enable the fast path, next time. + checkCacheLoad(); + addToCache(classValue, updated); } return item; } diff --git a/test/jdk/java/lang/invoke/ClassValueTest.java b/test/jdk/java/lang/invoke/ClassValueTest.java index 856653b3f92..34f7af1a8a0 100644 --- a/test/jdk/java/lang/invoke/ClassValueTest.java +++ b/test/jdk/java/lang/invoke/ClassValueTest.java @@ -23,23 +23,23 @@ /* * @test - * @bug 8351045 8351996 - * @enablePreview - * @comment Remove preview if ScopedValue is finalized + * @bug 8351045 8351996 8358535 * @summary tests for class-specific values + * @modules java.base/java.lang:+open * @library /test/lib * @run junit ClassValueTest */ import java.lang.classfile.ClassFile; import java.lang.constant.ClassDesc; +import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; import java.lang.ref.WeakReference; import java.time.Duration; import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Arrays; -import java.util.Iterator; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ThreadLocalRandom; @@ -49,9 +49,7 @@ import java.util.concurrent.atomic.AtomicReference; import jdk.test.lib.util.ForceGC; import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.Timeout; import static org.junit.jupiter.api.Assertions.*; @@ -479,4 +477,67 @@ final class ClassValueTest { awaitThreads(t); assertEquals(42, clv.get(int.class), "slow computation reinstalled value"); } + + // ClassValue cache invalidated and not reinstated when another + // unrelated entry is removed + @Test + public void testCacheRefresh() throws Throwable { + // Setup + var lookup = MethodHandles.privateLookupIn(ClassValue.class, MethodHandles.lookup()); + var classValueEntryClass = Class.forName("java.lang.ClassValue$Entry"); + MethodHandle getCacheCarefully = lookup.findStatic(ClassValue.class, "getCacheCarefully", + MethodType.methodType(classValueEntryClass.arrayType(), Class.class)); + var classValueMapClass = Class.forName("java.lang.ClassValue$ClassValueMap"); + MethodHandle probeHomeLocation = lookup.findStatic(classValueMapClass, "probeHomeLocation", + MethodType.methodType(classValueEntryClass, classValueEntryClass.arrayType(), ClassValue.class)); + MethodHandle match = lookup.findVirtual(ClassValue.class, "match", + MethodType.methodType(boolean.class, classValueEntryClass)); + + // Work + ClassValue clv = new ClassValue<>() { + @Override + protected String computeValue(Class type) { + return ""; + } + }; + // A class that shouldn't have arbitrary values stuffing the cache + var cleanClass = clv.getClass(); + clv.get(cleanClass); // create cache on clean class + assertTrue(checkDirectCacheMatch( + getCacheCarefully, + probeHomeLocation, + match, + clv, + cleanClass + )); + clv.get(int.class); + clv.remove(int.class); // invalidate cache on clean class + assertFalse(checkDirectCacheMatch( + getCacheCarefully, + probeHomeLocation, + match, + clv, + cleanClass + )); + clv.get(cleanClass); + assertTrue(checkDirectCacheMatch( + getCacheCarefully, + probeHomeLocation, + match, + clv, + cleanClass + )); + } + + private static boolean checkDirectCacheMatch( + MethodHandle getCacheCarefully, + MethodHandle probeHomeLocation, + MethodHandle match, + ClassValue clv, + Class cl + ) throws Throwable { + Object cache = getCacheCarefully.invoke(cl); + Object entry = probeHomeLocation.invoke(cache, clv); + return (boolean) match.invoke(clv, entry); + } } From 022e29a77533aacabd56820d00ecffa9646a8362 Mon Sep 17 00:00:00 2001 From: Jaikiran Pai Date: Sun, 10 Aug 2025 04:22:10 +0000 Subject: [PATCH 072/580] 8365086: CookieStore.getURIs() and get(URI) should return an immutable List Reviewed-by: liach, vyazici, dfuchs --- .../classes/java/net/InMemoryCookieStore.java | 18 +-- test/jdk/java/net/CookieStoreTest.java | 113 ++++++++++++++++++ 2 files changed, 123 insertions(+), 8 deletions(-) create mode 100644 test/jdk/java/net/CookieStoreTest.java diff --git a/src/java.base/share/classes/java/net/InMemoryCookieStore.java b/src/java.base/share/classes/java/net/InMemoryCookieStore.java index 0ce3dc5b68c..1c72549e37d 100644 --- a/src/java.base/share/classes/java/net/InMemoryCookieStore.java +++ b/src/java.base/share/classes/java/net/InMemoryCookieStore.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 @@ -25,10 +25,6 @@ package java.net; -import java.net.URI; -import java.net.CookieStore; -import java.net.HttpCookie; -import java.net.URISyntaxException; import java.util.List; import java.util.Map; import java.util.ArrayList; @@ -72,6 +68,7 @@ class InMemoryCookieStore implements CookieStore { /** * Add one cookie into cookie store. */ + @Override public void add(URI uri, HttpCookie cookie) { // pre-condition : argument can't be null if (cookie == null) { @@ -109,6 +106,7 @@ class InMemoryCookieStore implements CookieStore { * 3) not expired. * See RFC 2965 sec. 3.3.4 for more detail. */ + @Override public List get(URI uri) { // argument can't be null if (uri == null) { @@ -127,12 +125,13 @@ class InMemoryCookieStore implements CookieStore { lock.unlock(); } - return cookies; + return Collections.unmodifiableList(cookies); } /** * Get all cookies in cookie store, except those have expired */ + @Override public List getCookies() { List rt; @@ -156,6 +155,7 @@ class InMemoryCookieStore implements CookieStore { * Get all URIs, which are associated with at least one cookie * of this cookie store. */ + @Override public List getURIs() { List uris = new ArrayList<>(); @@ -165,7 +165,7 @@ class InMemoryCookieStore implements CookieStore { while (it.hasNext()) { URI uri = it.next(); List cookies = uriIndex.get(uri); - if (cookies == null || cookies.size() == 0) { + if (cookies == null || cookies.isEmpty()) { // no cookies list or an empty list associated with // this uri entry, delete it it.remove(); @@ -176,13 +176,14 @@ class InMemoryCookieStore implements CookieStore { lock.unlock(); } - return uris; + return Collections.unmodifiableList(uris); } /** * Remove a cookie from store */ + @Override public boolean remove(URI uri, HttpCookie ck) { // argument can't be null if (ck == null) { @@ -204,6 +205,7 @@ class InMemoryCookieStore implements CookieStore { /** * Remove all cookies in this cookie store. */ + @Override public boolean removeAll() { lock.lock(); try { diff --git a/test/jdk/java/net/CookieStoreTest.java b/test/jdk/java/net/CookieStoreTest.java new file mode 100644 index 00000000000..50a5ab3fac1 --- /dev/null +++ b/test/jdk/java/net/CookieStoreTest.java @@ -0,0 +1,113 @@ +/* + * 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.net.CookieManager; +import java.net.CookieStore; +import java.net.HttpCookie; +import java.net.URI; +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/* + * @test + * @bug 8365086 + * @summary verify that the implementation of java.net.CookieStore works + * as expected + * @run junit CookieStoreTest + */ +class CookieStoreTest { + + // neither the scheme, host nor the port matters in this test + private static final URI COOKIE_TEST_URI = URI.create("https://127.0.0.1:12345"); + + static List cookieStores() { + final List params = new ArrayList<>(); + // empty CookieStore + params.add(Arguments.of(new CookieManager().getCookieStore(), true)); + + final CookieStore cookieStore = new CookieManager().getCookieStore(); + cookieStore.add(COOKIE_TEST_URI, new HttpCookie("foo", "bar")); + // non-empty CookieStore + params.add(Arguments.of(cookieStore, false)); + + return params; + } + + /* + * Verify that the List returned by CookieStore.getURIs() is immutable. + */ + @ParameterizedTest + @MethodSource("cookieStores") + void testImmutableGetURIs(final CookieStore cookieStore, final boolean expectEmpty) { + final List uris = cookieStore.getURIs(); + assertNotNull(uris, "CookieStore.getURIs() returned null"); + assertEquals(expectEmpty, uris.isEmpty(), "CookieStore.getURIs() returned: " + uris); + assertImmutableList(uris, COOKIE_TEST_URI); + } + + /* + * Verify that the List returned by CookieStore.getCookies() is immutable. + */ + @ParameterizedTest + @MethodSource("cookieStores") + void testImmutableGetCookies(final CookieStore cookieStore, final boolean expectEmpty) { + final List cookies = cookieStore.getCookies(); + assertNotNull(cookies, "CookieStore.getCookies() returned null"); + assertEquals(expectEmpty, cookies.isEmpty(), "CookieStore.getCookies() returned: " + cookies); + assertImmutableList(cookies, new HttpCookie("hello", "world")); + } + + /* + * Verify that the List returned by CookieStore.get(URI) is immutable. + */ + @ParameterizedTest + @MethodSource("cookieStores") + void testImmutableGetCookiesForURI(final CookieStore cookieStore, final boolean expectEmpty) { + final List cookies = cookieStore.get(COOKIE_TEST_URI); + assertNotNull(cookies, "CookieStore.get(URI) returned null"); + assertEquals(expectEmpty, cookies.isEmpty(), "CookieStore.get(URI) returned: " + cookies); + assertImmutableList(cookies, new HttpCookie("hello", "world")); + } + + /* + * Verifies that the attempt to modify the contents of the list will fail + * due to the list being immutable. + */ + private static void assertImmutableList(final List list, T elementToAddOrRemove) { + // the list is expected to be immutable, so each of these operations must fail + assertThrows(UnsupportedOperationException.class, () -> list.add(elementToAddOrRemove)); + assertThrows(UnsupportedOperationException.class, () -> list.remove(elementToAddOrRemove)); + assertThrows(UnsupportedOperationException.class, list::clear); + // even try the replace operation when the list isn't empty + if (!list.isEmpty()) { + assertThrows(UnsupportedOperationException.class, () -> list.set(0, elementToAddOrRemove)); + } + } +} From 15e8609a2c3d246e89cfb349cbd21777bc471bae Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Mon, 11 Aug 2025 07:08:03 +0000 Subject: [PATCH 073/580] 8364996: java/awt/font/FontNames/LocaleFamilyNames.java times out on Windows Reviewed-by: clanger, prr, asteiner --- .../java/awt/font/FontNames/LocaleFamilyNames.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/test/jdk/java/awt/font/FontNames/LocaleFamilyNames.java b/test/jdk/java/awt/font/FontNames/LocaleFamilyNames.java index 5356464334e..0fc27ea3de0 100644 --- a/test/jdk/java/awt/font/FontNames/LocaleFamilyNames.java +++ b/test/jdk/java/awt/font/FontNames/LocaleFamilyNames.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2011, 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 @@ -26,29 +26,32 @@ * @bug 4935798 6521210 6901159 * @summary Tests that all family names that are reported in all locales * correspond to some font returned from getAllFonts(). - * @run main LocaleFamilyNames + * @run main/othervm/timeout=360 LocaleFamilyNames */ import java.awt.*; import java.util.*; public class LocaleFamilyNames { public static void main(String[] args) throws Exception { + System.out.println("Start time: " + java.time.LocalDateTime.now()); GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); - Font[] all_fonts = ge.getAllFonts(); - Locale[] all_locales = Locale.getAvailableLocales(); + System.out.println("Number of fonts: " + all_fonts.length); + System.out.println("Number of locales: " + all_locales.length); + HashSet all_families = new HashSet(); for (int i=0; i Date: Mon, 11 Aug 2025 07:10:38 +0000 Subject: [PATCH 074/580] 8364365: HKSCS encoder does not properly set the replacement character Reviewed-by: sherman --- .../share/classes/sun/nio/cs/HKSCS.java | 13 +- .../sun/nio/cs/TestEncoderReplaceLatin1.java | 229 ++++++++++++++++++ .../sun/nio/cs/TestEncoderReplaceUTF16.java | 205 ++++++++++++++++ 3 files changed, 436 insertions(+), 11 deletions(-) create mode 100644 test/jdk/sun/nio/cs/TestEncoderReplaceLatin1.java create mode 100644 test/jdk/sun/nio/cs/TestEncoderReplaceUTF16.java diff --git a/src/java.base/share/classes/sun/nio/cs/HKSCS.java b/src/java.base/share/classes/sun/nio/cs/HKSCS.java index 04bbd386a31..cfe9f879c04 100644 --- a/src/java.base/share/classes/sun/nio/cs/HKSCS.java +++ b/src/java.base/share/classes/sun/nio/cs/HKSCS.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2025, Oracle and/or its affiliates. All rights reserved. * 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,9 @@ package sun.nio.cs; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; -import java.nio.charset.CharsetDecoder; -import java.nio.charset.CharsetEncoder; import java.nio.charset.CoderResult; import java.util.Arrays; -import sun.nio.cs.DoubleByte; -import sun.nio.cs.Surrogate; + import static sun.nio.cs.CharsetMapping.*; public class HKSCS { @@ -355,12 +352,6 @@ public class HKSCS { return encodeBufferLoop(src, dst); } - @SuppressWarnings("this-escape") - private byte[] repl = replacement(); - protected void implReplaceWith(byte[] newReplacement) { - repl = newReplacement; - } - public int encode(char[] src, int sp, int len, byte[] dst) { int dp = 0; int sl = sp + len; diff --git a/test/jdk/sun/nio/cs/TestEncoderReplaceLatin1.java b/test/jdk/sun/nio/cs/TestEncoderReplaceLatin1.java new file mode 100644 index 00000000000..401f3650734 --- /dev/null +++ b/test/jdk/sun/nio/cs/TestEncoderReplaceLatin1.java @@ -0,0 +1,229 @@ +/* + * 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 org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import sun.nio.cs.ArrayEncoder; + +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.CoderResult; +import java.nio.charset.CodingErrorAction; +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/* + * @test + * @bug 8364365 + * @summary Verifies `CodingErrorAction.REPLACE` behaviour of all available + * character set encoders while encoding a Latin-1 character + * @modules java.base/jdk.internal.access + * java.base/sun.nio.cs + * @run junit TestEncoderReplaceLatin1 + */ + +class TestEncoderReplaceLatin1 { + + static Collection charsets() { + return Charset.availableCharsets().values(); + } + + @ParameterizedTest + @MethodSource("charsets") + void testEncoderReplace(Charset charset) { + + // Create an encoder + CharsetEncoder encoder = createEncoder(charset); + if (encoder == null) { + return; + } + + // Find an unmappable character to test the `REPLACE` action. + char[] unmappable = findUnmappable(encoder); + if (unmappable == null) { + return; + } + + // Configure the `REPLACE` action + byte[] replacement = findCustomReplacement(encoder, new byte[]{(byte) unmappable[0]}); + if (replacement == null) { + return; + } + encoder.onUnmappableCharacter(CodingErrorAction.REPLACE).replaceWith(replacement); + + // Verify the replacement + System.err.println("Verifying replacement... " + Map.of( + "unmappable", TestEncoderReplaceLatin1.prettyPrintChars(unmappable), + "replacement", TestEncoderReplaceLatin1.prettyPrintBytes(replacement))); + testCharsetEncoderReplace(encoder, unmappable, replacement); + testArrayEncoderLatin1Replace(encoder, unmappable[0], replacement); + + } + + private static CharsetEncoder createEncoder(Charset charset) { + try { + return charset.newEncoder(); + } catch (UnsupportedOperationException _) { + System.err.println("Could not create the character encoder!"); + } + return null; + } + + private static char[] findUnmappable(CharsetEncoder encoder) { + char[] unmappable1 = {0}; + for (char c = 0; c < 0xFF; c++) { + unmappable1[0] = c; + boolean unmappable = !encoder.canEncode(c); + if (unmappable) { + return unmappable1; + } + } + System.err.println("Could not find an unmappable character!"); + return null; + } + + /** + * Finds a {@linkplain CharsetEncoder#replacement() replacement} which is + * different from the given unmappable and the default one. + */ + static byte[] findCustomReplacement(CharsetEncoder encoder, byte[] unmappable) { + + // Obtain the default replacement + byte[] replacementD = encoder.replacement(); + + // Try to find a single-byte replacement + byte[] replacement1 = {0}; + for (int i = 0; i < 0xFF; i++) { + // Skip if the replacement is equal to the unmappable. + // They need to be distinct to be able to determine whether the replacement has occurred. + if (unmappable[0] == i) { + continue; + } + replacement1[0] = (byte) i; + // Skip the default value, since we're verifying if a custom one works + if (replacement1[0] == replacementD[0]) { + continue; + } + if (encoder.isLegalReplacement(replacement1)) { + return replacement1; + } + } + + // Try to find a double-byte replacement + byte[] replacement2 = {0, 0}; + for (int i = 0; i < 0xFF; i++) { + // Skip if the replacement is equal to the unmappable. + // They need to be distinct to be able to determine whether the replacement has occurred. + if (unmappable[0] == i) { + continue; + } + replacement2[0] = (byte) i; + for (int j = 0; j < 0xFF; j++) { + // Skip if the replacement is equal to the unmappable. + // They need to be distinct to be able to determine whether the replacement has occurred. + if (unmappable.length > 1 && unmappable[1] == j) { + continue; + } + replacement2[1] = (byte) j; + // Skip the default value, since we're verifying if a custom one works + if (replacementD.length > 1 && replacement2[1] == replacementD[1]) { + continue; + } + if (encoder.isLegalReplacement(replacement2)) { + return replacement2; + } + } + } + + System.err.println("Could not find a replacement!"); + return null; + + } + + /** + * Verifies {@linkplain CoderResult#isUnmappable() unmappable} character + * {@linkplain CodingErrorAction#REPLACE replacement} using {@link + * CharsetEncoder#encode(CharBuffer, ByteBuffer, boolean) + * CharsetEncoder::encode}. + */ + static void testCharsetEncoderReplace(CharsetEncoder encoder, char[] unmappable, byte[] replacement) { + CharBuffer charBuffer = CharBuffer.wrap(unmappable); + ByteBuffer byteBuffer = ByteBuffer.allocate(replacement.length); + CoderResult coderResult = encoder.encode(charBuffer, byteBuffer, true); + assertArrayEquals(replacement, byteBuffer.array(), () -> { + Object context = Map.of( + "coderResult", coderResult, + "byteBuffer.position()", byteBuffer.position(), + "byteBuffer.array()", prettyPrintBytes(byteBuffer.array()), + "unmappable", prettyPrintChars(unmappable), + "replacement", prettyPrintBytes(replacement)); + return "Unexpected `CharsetEncoder::encode` output! " + context; + }); + } + + /** + * Verifies {@linkplain CoderResult#isUnmappable() unmappable} character + * {@linkplain CodingErrorAction#REPLACE replacement} using {@link + * ArrayEncoder#encodeFromLatin1(byte[], int, int, byte[]) + * ArrayEncoder::encodeFromLatin1}. + */ + private static void testArrayEncoderLatin1Replace(CharsetEncoder encoder, char unmappable, byte[] replacement) { + if (!(encoder instanceof ArrayEncoder arrayEncoder)) { + System.err.println("Encoder is not of type `ArrayEncoder`, skipping the `ArrayEncoder::encodeFromLatin1` test."); + return; + } + byte[] sa = {(byte) unmappable}; + byte[] da = new byte[replacement.length]; + int dp = arrayEncoder.encodeFromLatin1(sa, 0, 1, da); + assertTrue(dp == replacement.length && Arrays.equals(da, replacement), () -> { + Object context = Map.of( + "dp", dp, + "da", prettyPrintBytes(da), + "sa", prettyPrintBytes(sa), + "unmappable", prettyPrintChars(new char[]{unmappable}), + "replacement", prettyPrintBytes(replacement)); + return "Unexpected `ArrayEncoder::encodeFromLatin1` output! " + context; + }); + } + + static String prettyPrintChars(char[] cs) { + return IntStream.range(0, cs.length) + .mapToObj(i -> String.format("U+%04X", (int) cs[i])) + .collect(Collectors.joining(", ", "[", "]")); + } + + static String prettyPrintBytes(byte[] bs) { + return IntStream.range(0, bs.length) + .mapToObj(i -> String.format("0x%02X", bs[i] & 0xFF)) + .collect(Collectors.joining(", ", "[", "]")); + } + +} diff --git a/test/jdk/sun/nio/cs/TestEncoderReplaceUTF16.java b/test/jdk/sun/nio/cs/TestEncoderReplaceUTF16.java new file mode 100644 index 00000000000..c61e85d6698 --- /dev/null +++ b/test/jdk/sun/nio/cs/TestEncoderReplaceUTF16.java @@ -0,0 +1,205 @@ +/* + * 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 jdk.internal.access.JavaLangAccess; +import jdk.internal.access.SharedSecrets; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import sun.nio.cs.ArrayEncoder; + +import java.nio.charset.Charset; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.CoderResult; +import java.nio.charset.CodingErrorAction; +import java.util.Arrays; +import java.util.Map; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +/* + * @test + * @bug 8364365 + * @summary Verifies `CodingErrorAction.REPLACE` behaviour of all available + * character set encoders while encoding a UTF-16 character + * @modules java.base/jdk.internal.access + * java.base/sun.nio.cs + * @build TestEncoderReplaceLatin1 + * @run junit/timeout=10 TestEncoderReplaceUTF16 + * @run junit/timeout=10/othervm -XX:-CompactStrings TestEncoderReplaceUTF16 + */ + +class TestEncoderReplaceUTF16 { + + private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess(); + + /** + * Character sets known to be absent of non-Latin-1 {@linkplain CoderResult#isUnmappable() unmappable} characters. + */ + private static final Set CHARSETS_WITHOUT_UNMAPPABLE = Set.of( + "CESU-8", + "EUC-JP", + "GB18030", + "ISO-2022-JP", + "ISO-2022-JP-2", + "ISO-2022-KR", + "ISO-8859-1", + "US-ASCII", + "UTF-16", + "UTF-16BE", + "UTF-16LE", + "UTF-32", + "UTF-32BE", + "UTF-32LE", + "UTF-8", + "x-euc-jp-linux", + "x-EUC-TW", + "x-eucJP-Open", + "x-IBM29626C", + "x-IBM33722", + "x-IBM964", + "x-ISCII91", + "x-ISO-2022-CN-CNS", + "x-ISO-2022-CN-GB", + "x-MS932_0213", + "x-SJIS_0213", + "x-UTF-16LE-BOM", + "X-UTF-32BE-BOM", + "X-UTF-32LE-BOM", + "x-windows-50220", + "x-windows-50221", + "x-windows-iso2022jp"); + + @ParameterizedTest + @MethodSource("TestEncoderReplaceLatin1#charsets") + void testEncoderReplace(Charset charset) { + + // Create an encoder + CharsetEncoder encoder = createEncoder(charset); + if (encoder == null) { + return; + } + + // Find an unmappable character to test the `REPLACE` action. + char[] unmappable = findUnmappableNonLatin1(encoder); + if (unmappable == null) { + return; + } + + // Configure the `REPLACE` action + byte[] unmappableUTF16Bytes = utf16Bytes(unmappable); + byte[] replacement = TestEncoderReplaceLatin1.findCustomReplacement(encoder, unmappableUTF16Bytes); + if (replacement == null) { + return; + } + encoder.onUnmappableCharacter(CodingErrorAction.REPLACE).replaceWith(replacement); + + // Verify the replacement + System.err.println("Verifying replacement... " + Map.of( + "unmappable", TestEncoderReplaceLatin1.prettyPrintChars(unmappable), + "unmappableUTF16Bytes", TestEncoderReplaceLatin1.prettyPrintBytes(unmappableUTF16Bytes), + "replacement", TestEncoderReplaceLatin1.prettyPrintBytes(replacement))); + TestEncoderReplaceLatin1.testCharsetEncoderReplace(encoder, unmappable, replacement); + testArrayEncoderUTF16Replace(encoder, unmappableUTF16Bytes, replacement); + + } + + private static CharsetEncoder createEncoder(Charset charset) { + try { + return charset.newEncoder(); + } catch (UnsupportedOperationException _) { + System.err.println("Could not create the character encoder!"); + } + return null; + } + + /** + * Finds an {@linkplain CoderResult#isUnmappable() unmappable} non-Latin-1 {@code char[]} for the given encoder. + */ + private static char[] findUnmappableNonLatin1(CharsetEncoder encoder) { + + // Fast-path for characters sets known to be absent of unmappable non-Latin-1 characters + if (CHARSETS_WITHOUT_UNMAPPABLE.contains(encoder.charset().name())) { + System.err.println("Character set is known to be absent of unmappable non-Latin-1 characters!"); + return null; + } + + // Try to find a single-`char` unmappable + for (int i = 0xFF; i <= 0xFFFF; i++) { + char c = (char) i; + // Skip the surrogate, as a single dangling surrogate `char` should + // trigger a "malformed" error, instead of "unmappable" + if (Character.isSurrogate(c)) { + continue; + } + boolean unmappable = !encoder.canEncode(c); + if (unmappable) { + return new char[]{c}; + } + } + + // Try to find a double-`char` (i.e., surrogate pair) unmappable + int[] nonBmpRange = {0x10000, 0x10FFFF}; + for (int i = nonBmpRange[0]; i < nonBmpRange[1]; i++) { + char[] cs = Character.toChars(i); + if (!encoder.canEncode(new String(cs))) + return cs; + } + + System.err.println("Could not find an unmappable character!"); + return null; + } + + private static byte[] utf16Bytes(char[] cs) { + int sl = cs.length; + byte[] sa = new byte[sl << 1]; + for (int i = 0; i < sl; i++) { + JLA.uncheckedPutCharUTF16(sa, i, cs[i]); + } + return sa; + } + + /** + * Verifies {@linkplain CoderResult#isUnmappable() unmappable} character + * {@linkplain CodingErrorAction#REPLACE replacement} using {@link + * ArrayEncoder#encodeFromUTF16(byte[], int, int, byte[]) + * ArrayEncoder::encodeFromUTF16}. + */ + private static void testArrayEncoderUTF16Replace(CharsetEncoder encoder, byte[] unmappableUTF16Bytes, byte[] replacement) { + if (!(encoder instanceof ArrayEncoder arrayEncoder)) { + System.err.println("Encoder is not of type `ArrayEncoder`, skipping the `ArrayEncoder::encodeFromUTF16` test."); + return; + } + byte[] da = new byte[replacement.length]; + int dp = arrayEncoder.encodeFromUTF16(unmappableUTF16Bytes, 0, unmappableUTF16Bytes.length >>> 1, da); + assertTrue(dp == replacement.length && Arrays.equals(da, replacement), () -> { + Object context = Map.of( + "dp", dp, + "da", TestEncoderReplaceLatin1.prettyPrintBytes(da), + "unmappableUTF16Bytes", TestEncoderReplaceLatin1.prettyPrintBytes(unmappableUTF16Bytes), + "replacement", TestEncoderReplaceLatin1.prettyPrintBytes(replacement)); + return "Unexpected `ArrayEncoder::encodeFromUTF16` output! " + context; + }); + } + +} From f28126ebc2f792f0d64bbe6e841d9fafb433b7ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joel=20Sikstr=C3=B6m?= Date: Mon, 11 Aug 2025 08:18:28 +0000 Subject: [PATCH 075/580] 8365050: Too verbose warning in os::commit_memory_limit() on Windows Reviewed-by: dholmes, mbaesken --- src/hotspot/os/windows/os_windows.cpp | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/hotspot/os/windows/os_windows.cpp b/src/hotspot/os/windows/os_windows.cpp index f5067757125..ffa22bd0365 100644 --- a/src/hotspot/os/windows/os_windows.cpp +++ b/src/hotspot/os/windows/os_windows.cpp @@ -3297,13 +3297,28 @@ static char* map_or_reserve_memory_aligned(size_t size, size_t alignment, int fi } size_t os::commit_memory_limit() { - JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = {}; - BOOL res = QueryInformationJobObject(nullptr, JobObjectExtendedLimitInformation, &jeli, sizeof(jeli), nullptr); - + BOOL is_in_job_object = false; + BOOL res = IsProcessInJob(GetCurrentProcess(), nullptr, &is_in_job_object); if (!res) { char buf[512]; size_t buf_len = os::lasterror(buf, sizeof(buf)); - warning("Attempt to query job object information failed: %s", buf_len != 0 ? buf : ""); + warning("Attempt to determine whether the process is running in a job failed for commit limit: %s", buf_len != 0 ? buf : ""); + + // Conservatively assume no limit when there was an error calling IsProcessInJob. + return SIZE_MAX; + } + + if (!is_in_job_object) { + // Not limited by a Job Object + return SIZE_MAX; + } + + JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = {}; + res = QueryInformationJobObject(nullptr, JobObjectExtendedLimitInformation, &jeli, sizeof(jeli), nullptr); + if (!res) { + char buf[512]; + size_t buf_len = os::lasterror(buf, sizeof(buf)); + warning("Attempt to query job object information failed for commit limit: %s", buf_len != 0 ? buf : ""); // Conservatively assume no limit when there was an error calling QueryInformationJobObject. return SIZE_MAX; From 10762d408bba9ce0945100847a8674e7eb7fa75e Mon Sep 17 00:00:00 2001 From: Dmitry Cherepanov Date: Mon, 11 Aug 2025 08:19:02 +0000 Subject: [PATCH 076/580] 8365044: Missing copyright header in Contextual.java Reviewed-by: egahlin --- .../share/classes/jdk/jfr/Contextual.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/jdk.jfr/share/classes/jdk/jfr/Contextual.java b/src/jdk.jfr/share/classes/jdk/jfr/Contextual.java index 92deeb8d562..e1cf0544e6c 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/Contextual.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/Contextual.java @@ -1,3 +1,28 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + package jdk.jfr; import java.lang.annotation.ElementType; From 0c39228ec1c8c6eadafb54567c94ad5f19f27f7a Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Mon, 11 Aug 2025 09:42:12 +0000 Subject: [PATCH 077/580] 8364767: G1: Remove use of CollectedHeap::_soft_ref_policy Reviewed-by: tschatzl, sangheki --- src/hotspot/share/gc/g1/g1CollectedHeap.cpp | 8 +------- src/hotspot/share/gc/g1/g1FullGCScope.cpp | 6 +----- src/hotspot/share/gc/g1/g1FullGCScope.hpp | 4 ++-- src/hotspot/share/gc/g1/g1VMOperations.cpp | 4 +++- src/hotspot/share/gc/shared/softRefPolicy.hpp | 17 ----------------- src/hotspot/share/prims/whitebox.cpp | 6 ------ 6 files changed, 7 insertions(+), 38 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp index b9b12f628eb..e5266a527f0 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp @@ -848,12 +848,9 @@ void G1CollectedHeap::do_full_collection(bool clear_all_soft_refs, size_t allocation_word_size) { assert_at_safepoint_on_vm_thread(); - const bool do_clear_all_soft_refs = clear_all_soft_refs || - soft_ref_policy()->should_clear_all_soft_refs(); - G1FullGCMark gc_mark; GCTraceTime(Info, gc) tm("Pause Full", nullptr, gc_cause(), true); - G1FullCollector collector(this, do_clear_all_soft_refs, do_maximal_compaction, gc_mark.tracer()); + G1FullCollector collector(this, clear_all_soft_refs, do_maximal_compaction, gc_mark.tracer()); collector.prepare_collection(); collector.collect(); @@ -986,9 +983,6 @@ HeapWord* G1CollectedHeap::satisfy_failed_allocation(size_t word_size) { return result; } - assert(!soft_ref_policy()->should_clear_all_soft_refs(), - "Flag should have been handled and cleared prior to this point"); - // What else? We might try synchronous finalization later. If the total // space available is large enough for the allocation, then a more // complete compaction phase than we've tried so far might be diff --git a/src/hotspot/share/gc/g1/g1FullGCScope.cpp b/src/hotspot/share/gc/g1/g1FullGCScope.cpp index 5879442b82b..8b92d51a8a3 100644 --- a/src/hotspot/share/gc/g1/g1FullGCScope.cpp +++ b/src/hotspot/share/gc/g1/g1FullGCScope.cpp @@ -40,6 +40,7 @@ G1FullGCScope::G1FullGCScope(G1MonitoringSupport* monitoring_support, bool do_maximal_compaction, G1FullGCTracer* tracer) : _rm(), + _should_clear_soft_refs(clear_soft), _do_maximal_compaction(do_maximal_compaction), _g1h(G1CollectedHeap::heap()), _svc_marker(SvcGCMarker::FULL), @@ -47,17 +48,12 @@ G1FullGCScope::G1FullGCScope(G1MonitoringSupport* monitoring_support, _tracer(tracer), _active(), _tracer_mark(&_timer, _tracer), - _soft_refs(clear_soft, _g1h->soft_ref_policy()), _monitoring_scope(monitoring_support), _heap_printer(_g1h), _region_compaction_threshold(do_maximal_compaction ? G1HeapRegion::GrainWords : (1 - MarkSweepDeadRatio / 100.0) * G1HeapRegion::GrainWords) { } -bool G1FullGCScope::should_clear_soft_refs() { - return _soft_refs.should_clear(); -} - STWGCTimer* G1FullGCScope::timer() { return &_timer; } diff --git a/src/hotspot/share/gc/g1/g1FullGCScope.hpp b/src/hotspot/share/gc/g1/g1FullGCScope.hpp index f7362d20d4f..ab5a48f3574 100644 --- a/src/hotspot/share/gc/g1/g1FullGCScope.hpp +++ b/src/hotspot/share/gc/g1/g1FullGCScope.hpp @@ -47,6 +47,7 @@ public: // Class used to group scoped objects used in the Full GC together. class G1FullGCScope : public StackObj { ResourceMark _rm; + bool _should_clear_soft_refs; bool _do_maximal_compaction; G1CollectedHeap* _g1h; SvcGCMarker _svc_marker; @@ -54,7 +55,6 @@ class G1FullGCScope : public StackObj { G1FullGCTracer* _tracer; IsSTWGCActiveMark _active; G1FullGCJFRTracerMark _tracer_mark; - ClearedAllSoftRefs _soft_refs; G1FullGCMonitoringScope _monitoring_scope; G1HeapPrinterMark _heap_printer; size_t _region_compaction_threshold; @@ -65,7 +65,7 @@ public: bool do_maximal_compaction, G1FullGCTracer* tracer); - bool should_clear_soft_refs(); + bool should_clear_soft_refs() const { return _should_clear_soft_refs; } bool do_maximal_compaction() { return _do_maximal_compaction; } STWGCTimer* timer(); diff --git a/src/hotspot/share/gc/g1/g1VMOperations.cpp b/src/hotspot/share/gc/g1/g1VMOperations.cpp index 6ddeba3d2e2..6757172b625 100644 --- a/src/hotspot/share/gc/g1/g1VMOperations.cpp +++ b/src/hotspot/share/gc/g1/g1VMOperations.cpp @@ -50,7 +50,9 @@ bool VM_G1CollectFull::skip_operation() const { void VM_G1CollectFull::doit() { G1CollectedHeap* g1h = G1CollectedHeap::heap(); GCCauseSetter x(g1h, _gc_cause); - g1h->do_full_collection(false /* clear_all_soft_refs */, + bool clear_all_soft_refs = _gc_cause == GCCause::_metadata_GC_clear_soft_refs || + _gc_cause == GCCause::_wb_full_gc; + g1h->do_full_collection(clear_all_soft_refs /* clear_all_soft_refs */, false /* do_maximal_compaction */, size_t(0) /* allocation_word_size */); } diff --git a/src/hotspot/share/gc/shared/softRefPolicy.hpp b/src/hotspot/share/gc/shared/softRefPolicy.hpp index b725b843d2d..fe2706288f7 100644 --- a/src/hotspot/share/gc/shared/softRefPolicy.hpp +++ b/src/hotspot/share/gc/shared/softRefPolicy.hpp @@ -58,21 +58,4 @@ class SoftRefPolicy { } }; -class ClearedAllSoftRefs : public StackObj { - bool _clear_all_soft_refs; - SoftRefPolicy* _soft_ref_policy; - public: - ClearedAllSoftRefs(bool clear_all_soft_refs, SoftRefPolicy* soft_ref_policy) : - _clear_all_soft_refs(clear_all_soft_refs), - _soft_ref_policy(soft_ref_policy) {} - - ~ClearedAllSoftRefs() { - if (_clear_all_soft_refs) { - _soft_ref_policy->cleared_all_soft_refs(); - } - } - - bool should_clear() { return _clear_all_soft_refs; } -}; - #endif // SHARE_GC_SHARED_SOFTREFPOLICY_HPP diff --git a/src/hotspot/share/prims/whitebox.cpp b/src/hotspot/share/prims/whitebox.cpp index be5d9d3d8a1..68fab39b23a 100644 --- a/src/hotspot/share/prims/whitebox.cpp +++ b/src/hotspot/share/prims/whitebox.cpp @@ -1502,12 +1502,6 @@ WB_END WB_ENTRY(void, WB_FullGC(JNIEnv* env, jobject o)) Universe::heap()->soft_ref_policy()->set_should_clear_all_soft_refs(true); Universe::heap()->collect(GCCause::_wb_full_gc); -#if INCLUDE_G1GC - if (UseG1GC) { - // Needs to be cleared explicitly for G1 GC. - Universe::heap()->soft_ref_policy()->set_should_clear_all_soft_refs(false); - } -#endif // INCLUDE_G1GC WB_END WB_ENTRY(void, WB_YoungGC(JNIEnv* env, jobject o)) From 1fc0b01601af454a0e871afce8ae0c9da1358f13 Mon Sep 17 00:00:00 2001 From: Magnus Ihse Bursie Date: Mon, 11 Aug 2025 09:44:49 +0000 Subject: [PATCH 078/580] 8361142: Improve custom hooks for makefiles Reviewed-by: erikj --- make/CompileJavaModules.gmk | 2 +- make/CreateJmods.gmk | 2 +- make/Images.gmk | 1 + make/Main.gmk | 2 +- make/MainSupport.gmk | 2 +- make/ModuleWrapper.gmk | 17 +++++++++++------ make/common/JavaCompilation.gmk | 6 +++++- make/common/Modules.gmk | 20 +++++++++++++++++--- 8 files changed, 38 insertions(+), 14 deletions(-) diff --git a/make/CompileJavaModules.gmk b/make/CompileJavaModules.gmk index 1e26fb2b529..c6e8fab3038 100644 --- a/make/CompileJavaModules.gmk +++ b/make/CompileJavaModules.gmk @@ -85,7 +85,7 @@ CreateHkTargets = \ ################################################################################ # Include module specific build settings -THIS_SNIPPET := modules/$(MODULE)/Java.gmk +THIS_SNIPPET := $(call GetModuleSnippetName, Java) ifneq ($(wildcard $(THIS_SNIPPET)), ) include MakeSnippetStart.gmk diff --git a/make/CreateJmods.gmk b/make/CreateJmods.gmk index 40bceda69a9..a26042fb0d5 100644 --- a/make/CreateJmods.gmk +++ b/make/CreateJmods.gmk @@ -184,7 +184,7 @@ endif ################################################################################ # Include module specific build settings -THIS_SNIPPET := modules/$(MODULE)/Jmod.gmk +THIS_SNIPPET := $(call GetModuleSnippetName, Jmod) ifneq ($(wildcard $(THIS_SNIPPET)), ) include MakeSnippetStart.gmk diff --git a/make/Images.gmk b/make/Images.gmk index 22e3e43cb1f..34d81081d29 100644 --- a/make/Images.gmk +++ b/make/Images.gmk @@ -270,6 +270,7 @@ endif # Since debug symbols are not included in the jmod files, they need to be copied # in manually after generating the images. +# These variables are read by SetupCopyDebuginfo ALL_JDK_MODULES := $(JDK_MODULES) ALL_JRE_MODULES := $(sort $(JRE_MODULES), $(foreach m, $(JRE_MODULES), \ $(call FindTransitiveDepsForModule, $m))) diff --git a/make/Main.gmk b/make/Main.gmk index d9433e722f0..e9f0b5bb9eb 100644 --- a/make/Main.gmk +++ b/make/Main.gmk @@ -1407,7 +1407,7 @@ CLEAN_SUPPORT_DIRS += demos CLEAN_SUPPORT_DIR_TARGETS := $(addprefix clean-, $(CLEAN_SUPPORT_DIRS)) CLEAN_TESTS += hotspot-jtreg-native jdk-jtreg-native lib CLEAN_TEST_TARGETS += $(addprefix clean-test-, $(CLEAN_TESTS)) -CLEAN_PHASES := gensrc java native include +CLEAN_PHASES += gensrc java native include CLEAN_PHASE_TARGETS := $(addprefix clean-, $(CLEAN_PHASES)) CLEAN_MODULE_TARGETS := $(addprefix clean-, $(ALL_MODULES)) # Construct targets of the form clean-$module-$phase diff --git a/make/MainSupport.gmk b/make/MainSupport.gmk index d8dc894c1e9..ee5bb324f8f 100644 --- a/make/MainSupport.gmk +++ b/make/MainSupport.gmk @@ -149,7 +149,7 @@ endef ################################################################################ -PHASE_MAKEDIRS := $(TOPDIR)/make +PHASE_MAKEDIRS += $(TOPDIR)/make # Helper macro for DeclareRecipesForPhase # Declare a recipe for calling the module and phase specific makefile. diff --git a/make/ModuleWrapper.gmk b/make/ModuleWrapper.gmk index b3ddf940e00..2db77b9ea32 100644 --- a/make/ModuleWrapper.gmk +++ b/make/ModuleWrapper.gmk @@ -34,18 +34,23 @@ include MakeFileStart.gmk ################################################################################ include CopyFiles.gmk +include Modules.gmk MODULE_SRC := $(TOPDIR)/src/$(MODULE) -# Define the snippet for MakeSnippetStart/End -THIS_SNIPPET := modules/$(MODULE)/$(MAKEFILE_PREFIX).gmk +################################################################################ +# Include module specific build settings -include MakeSnippetStart.gmk +THIS_SNIPPET := $(call GetModuleSnippetName, $(MAKEFILE_PREFIX)) -# Include the file being wrapped. -include $(THIS_SNIPPET) +ifneq ($(wildcard $(THIS_SNIPPET)), ) + include MakeSnippetStart.gmk -include MakeSnippetEnd.gmk + # Include the file being wrapped. + include $(THIS_SNIPPET) + + include MakeSnippetEnd.gmk +endif ifeq ($(MAKEFILE_PREFIX), Lib) # We need to keep track of what libraries are generated/needed by this diff --git a/make/common/JavaCompilation.gmk b/make/common/JavaCompilation.gmk index c5a74413de1..99672d59884 100644 --- a/make/common/JavaCompilation.gmk +++ b/make/common/JavaCompilation.gmk @@ -178,6 +178,10 @@ define SetupJavaCompilationBody $1_SAFE_NAME := $$(strip $$(subst /,_, $1)) + ifeq ($$($1_LOG_ACTION), ) + $1_LOG_ACTION := Compiling + endif + ifeq ($$($1_SMALL_JAVA), ) # If unspecified, default to true $1_SMALL_JAVA := true @@ -472,7 +476,7 @@ define SetupJavaCompilationBody # list of files. $$($1_FILELIST): $$($1_SRCS) $$($1_VARDEPS_FILE) $$(call MakeDir, $$(@D)) - $$(call LogWarn, Compiling up to $$(words $$($1_SRCS)) files for $1) + $$(call LogWarn, $$($1_LOG_ACTION) up to $$(words $$($1_SRCS)) files for $1) $$(eval $$(call ListPathsSafely, $1_SRCS, $$($1_FILELIST))) # Create a $$($1_MODFILELIST) file with significant modified dependencies diff --git a/make/common/Modules.gmk b/make/common/Modules.gmk index 725424d7618..2880504676a 100644 --- a/make/common/Modules.gmk +++ b/make/common/Modules.gmk @@ -33,7 +33,7 @@ include $(TOPDIR)/make/conf/module-loader-map.conf # Append platform-specific and upgradeable modules PLATFORM_MODULES += $(PLATFORM_MODULES_$(OPENJDK_TARGET_OS)) \ - $(UPGRADEABLE_PLATFORM_MODULES) + $(UPGRADEABLE_PLATFORM_MODULES) $(CUSTOM_UPGRADEABLE_PLATFORM_MODULES) ################################################################################ # Setup module sets for docs @@ -216,7 +216,7 @@ endif # Find dependencies ("requires") for a given module. # Param 1: Module to find dependencies for. FindDepsForModule = \ - $(DEPS_$(strip $1)) + $(filter-out $(IMPORT_MODULES), $(DEPS_$(strip $1))) # Find dependencies ("requires") transitively in 3 levels for a given module. # Param 1: Module to find dependencies for. @@ -254,7 +254,8 @@ FindTransitiveIndirectDepsForModules = \ # Upgradeable modules are those that are either defined as upgradeable or that # require an upradeable module. FindAllUpgradeableModules = \ - $(sort $(filter-out $(MODULES_FILTER), $(UPGRADEABLE_PLATFORM_MODULES))) + $(sort $(filter-out $(MODULES_FILTER), \ + $(UPGRADEABLE_PLATFORM_MODULES) $(CUSTOM_UPGRADEABLE_PLATFORM_MODULES))) ################################################################################ @@ -316,6 +317,19 @@ define ReadImportMetaData $$(eval $$(call ReadSingleImportMetaData, $$m))) endef +################################################################################ +# Get a full snippet path for the current module and a given base name. +# +# Param 1 - The base name of the snippet file to include +GetModuleSnippetName = \ + $(if $(CUSTOM_MODULE_MAKE_ROOT), \ + $(if $(wildcard $(CUSTOM_MODULE_MAKE_ROOT)/$(MODULE)/$(strip $1).gmk), \ + $(CUSTOM_MODULE_MAKE_ROOT)/$(MODULE)/$(strip $1).gmk, \ + $(wildcard modules/$(MODULE)/$(strip $1).gmk) \ + ), \ + $(wildcard modules/$(MODULE)/$(strip $1).gmk) \ + ) + ################################################################################ endif # include guard From 8b5bb013557478c9ceb49f94f22600d5901f4eee Mon Sep 17 00:00:00 2001 From: Jan Lahoda Date: Mon, 11 Aug 2025 10:28:59 +0000 Subject: [PATCH 079/580] 8364987: javac fails with an exception when looking for diamond creation Reviewed-by: vromero --- .../com/sun/tools/javac/comp/Analyzer.java | 2 +- .../tools/javac/analyzer/Diamond.java | 41 ++++++++++++++++++- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Analyzer.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Analyzer.java index 1aea9f77a20..f45e8500000 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Analyzer.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Analyzer.java @@ -240,7 +240,7 @@ public class Analyzer { @Override List rewrite(JCNewClass oldTree) { - if (oldTree.clazz.hasTag(TYPEAPPLY)) { + if (oldTree.clazz.hasTag(TYPEAPPLY) && !oldTree.type.isErroneous()) { JCNewClass nc = copier.copy(oldTree); ((JCTypeApply)nc.clazz).arguments = List.nil(); return List.of(nc); diff --git a/test/langtools/tools/javac/analyzer/Diamond.java b/test/langtools/tools/javac/analyzer/Diamond.java index cee8d096cbd..5f77745beaf 100644 --- a/test/langtools/tools/javac/analyzer/Diamond.java +++ b/test/langtools/tools/javac/analyzer/Diamond.java @@ -23,7 +23,7 @@ /** * @test - * @bug 8349132 + * @bug 8349132 8364987 * @summary Check behavior of the diamond analyzer * @library /tools/lib * @modules jdk.compiler/com.sun.tools.javac.api @@ -139,4 +139,43 @@ public class Diamond extends TestRunner { } } + @Test //JDK-8364987: + public void testNoCrashErroneousTypes(Path base) throws Exception { + Path current = base.resolve("."); + Path src = current.resolve("src"); + Path classes = current.resolve("classes"); + tb.writeJavaFiles(src, + """ + public class Test { + void t() { + L l = new L(); + } + static class L { } + } + """); + + Files.createDirectories(classes); + + var out = new JavacTask(tb) + .options("-XDfind=diamond", + "-XDshould-stop.at=FLOW", + "-XDrawDiagnostics") + .outdir(classes) + .files(tb.findJavaFiles(src)) + .run(Task.Expect.FAIL) + .writeAll() + .getOutputLines(Task.OutputKind.DIRECT); + + var expectedOut = List.of( + "Test.java:3:23: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: Test.L, Test.L)", + "1 error" + ); + + if (!Objects.equals(expectedOut, out)) { + throw new AssertionError("Incorrect Output, expected: " + expectedOut + + ", actual: " + out); + + } + } + } From fd766b27b9f862075a415780901c242a7d48c26f Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Mon, 11 Aug 2025 10:49:47 +0000 Subject: [PATCH 080/580] 8364541: Parallel: Support allocation in old generation when heap is almost full Reviewed-by: phh, tschatzl --- .../share/gc/parallel/parallelScavengeHeap.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp b/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp index 2359ab9e158..c6a9a312e5c 100644 --- a/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp +++ b/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp @@ -310,11 +310,13 @@ HeapWord* ParallelScavengeHeap::mem_allocate_work(size_t size, return result; } - // If certain conditions hold, try allocating from the old gen. - if (!is_tlab && !should_alloc_in_eden(size)) { - result = old_gen()->cas_allocate_noexpand(size); - if (result != nullptr) { - return result; + // Try allocating from the old gen for non-TLAB in certain scenarios. + if (!is_tlab) { + if (!should_alloc_in_eden(size) || _is_heap_almost_full) { + result = old_gen()->cas_allocate_noexpand(size); + if (result != nullptr) { + return result; + } } } } From a60e523f88e7022abe80725b82a8b16a87a377e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Maillard?= Date: Mon, 11 Aug 2025 11:15:34 +0000 Subject: [PATCH 081/580] 8349191: Test compiler/ciReplay/TestIncrementalInlining.java failed Reviewed-by: mhaessig, dfenacci, chagedorn --- src/hotspot/share/opto/printinlining.cpp | 5 ++++- test/hotspot/jtreg/ProblemList.txt | 3 --- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/hotspot/share/opto/printinlining.cpp b/src/hotspot/share/opto/printinlining.cpp index be51c08fcfb..fe039623764 100644 --- a/src/hotspot/share/opto/printinlining.cpp +++ b/src/hotspot/share/opto/printinlining.cpp @@ -46,7 +46,10 @@ void InlinePrinter::print_on(outputStream* tty) const { if (!is_enabled()) { return; } - _root.dump(tty, -1); + // using stringStream prevents interleaving with multiple compile threads + stringStream ss; + _root.dump(&ss, -1); + tty->print_raw(ss.freeze()); } InlinePrinter::IPInlineSite* InlinePrinter::locate(JVMState* state, ciMethod* callee) { diff --git a/test/hotspot/jtreg/ProblemList.txt b/test/hotspot/jtreg/ProblemList.txt index 88b8f2d6941..7026b6f79d6 100644 --- a/test/hotspot/jtreg/ProblemList.txt +++ b/test/hotspot/jtreg/ProblemList.txt @@ -75,9 +75,6 @@ compiler/codecache/CodeCacheFullCountTest.java 8332954 generic-all compiler/interpreter/Test6833129.java 8335266 generic-i586 -compiler/ciReplay/TestInliningProtectionDomain.java 8349191 generic-all -compiler/ciReplay/TestIncrementalInlining.java 8349191 generic-all - compiler/c2/TestVerifyConstraintCasts.java 8355574 generic-all compiler/c2/aarch64/TestStaticCallStub.java 8359963 linux-aarch64,macosx-aarch64 From 43cfd80c1c0493f2f50ffd75461ca75a002e0127 Mon Sep 17 00:00:00 2001 From: Darragh Clarke Date: Mon, 11 Aug 2025 11:57:08 +0000 Subject: [PATCH 082/580] 8352502: Response message is null if expect 100 assertion fails with non 100 Reviewed-by: dfuchs --- .../classes/java/net/HttpURLConnection.java | 4 + .../www/protocol/http/HttpURLConnection.java | 5 + ...tionExpectContinueResponseMessageTest.java | 196 ++++++++++++++++++ 3 files changed, 205 insertions(+) create mode 100644 test/jdk/java/net/HttpURLConnection/HttpUrlConnectionExpectContinueResponseMessageTest.java diff --git a/src/java.base/share/classes/java/net/HttpURLConnection.java b/src/java.base/share/classes/java/net/HttpURLConnection.java index d9e9b738617..c0ae12dfdba 100644 --- a/src/java.base/share/classes/java/net/HttpURLConnection.java +++ b/src/java.base/share/classes/java/net/HttpURLConnection.java @@ -556,6 +556,10 @@ public abstract class HttpURLConnection extends URLConnection { * @return the HTTP response message, or {@code null} */ public String getResponseMessage() throws IOException { + // If the responseMessage is already set then return it + if (responseMessage != null) { + return responseMessage; + } getResponseCode(); return responseMessage; } 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 857c2f6ad6d..aee9670ce26 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 @@ -1080,6 +1080,11 @@ public class HttpURLConnection extends java.net.HttpURLConnection { if (logger.isLoggable(PlatformLogger.Level.FINE)) { logger.fine("response code received " + responseCode); } + if (sa.length > 2) + responseMessage = String.join(" ", Arrays.copyOfRange(sa, 2, sa.length)); + if (logger.isLoggable(PlatformLogger.Level.FINE)) { + logger.fine("response message received " + responseMessage); + } } catch (NumberFormatException numberFormatException) { } } diff --git a/test/jdk/java/net/HttpURLConnection/HttpUrlConnectionExpectContinueResponseMessageTest.java b/test/jdk/java/net/HttpURLConnection/HttpUrlConnectionExpectContinueResponseMessageTest.java new file mode 100644 index 00000000000..06aa9319161 --- /dev/null +++ b/test/jdk/java/net/HttpURLConnection/HttpUrlConnectionExpectContinueResponseMessageTest.java @@ -0,0 +1,196 @@ +/* + * 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 that Response Message gets set even if a non 100 response + * gets return when Expect Continue is set + * @bug 8352502 + * @library /test/lib + * @run junit/othervm -Djdk.internal.httpclient.debug=true + * -Djdk.httpclient.HttpClient.log=all + * HttpUrlConnectionExpectContinueResponseMessageTest + */ + +import jdk.test.lib.net.URIBuilder; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.BufferedReader; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.Socket; +import java.net.ServerSocket; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.URL; +import java.util.StringTokenizer; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class HttpUrlConnectionExpectContinueResponseMessageTest { + class Control { + volatile ServerSocket serverSocket = null; + volatile boolean stop = false; + volatile String response = null; + volatile Socket acceptingSocket = null; + volatile String testPath = null; + } + + private Thread serverThread = null; + private volatile Control control; + static final Logger logger; + + static { + logger = Logger.getLogger("sun.net.www.protocol.http.HttpURLConnection"); + logger.setLevel(Level.ALL); + Logger.getLogger("").getHandlers()[0].setLevel(Level.ALL); + } + + public Object[][] args() { + return new Object[][]{ + // Expected Status Code, Status Line, Expected responseMessage + { 404, "HTTP/1.1 404 Not Found", "Not Found" }, + { 405, "HTTP/1.1 405 Method Not Allowed", "Method Not Allowed" }, + { 401, "HTTP/1.1 401 Unauthorized", "Unauthorized"} + }; + } + + @BeforeAll + public void startServerSocket() throws Exception { + Control control = this.control = new Control(); + + control.serverSocket = new ServerSocket(); + control.serverSocket.setReuseAddress(true); + control.serverSocket.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); + Runnable runnable = () -> { + while (!control.stop) { + try { + Socket socket = control.serverSocket.accept(); + String path = getPath(socket); + + OutputStream outputStream; + if (path.equals(control.testPath)) { + control.acceptingSocket = socket; + outputStream = control.acceptingSocket.getOutputStream(); + + // send a wrong response and then shutdown + outputStream.write(control.response.getBytes()); + outputStream.flush(); + control.acceptingSocket.shutdownOutput(); + } else { + // stray request showed up, return 500 and close socket + outputStream = socket.getOutputStream(); + outputStream.write("HTTP/1.1 500 Internal Server Error\r\n".getBytes()); + outputStream.write("Connection: close\r\n".getBytes()); + outputStream.write("Content-Length: 0\r\n".getBytes()); + outputStream.write("\r\n".getBytes()); + outputStream.flush(); + socket.close(); + } + } catch (Exception e) { + // Any exceptions will be ignored + } + } + }; + serverThread = new Thread(runnable); + serverThread.start(); + } + + private static String getPath(Socket socket) throws IOException { + InputStream inputStream = socket.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + + StringBuilder reqBuilder = new StringBuilder(); + String line = null; + while (!(line = reader.readLine()).isEmpty()) { + reqBuilder.append(line + "\r\n"); + } + String req = reqBuilder.toString(); + StringTokenizer tokenizer = new StringTokenizer(req); + String method = tokenizer.nextToken(); + String path = tokenizer.nextToken(); + return path; + } + + @AfterAll + public void stopServerSocket() throws Exception { + Control control = this.control; + control.stop = true; + control.serverSocket.close(); + serverThread.join(); + } + + @ParameterizedTest + @MethodSource("args") + public void test(int expectedCode, String statusLine, String expectedMessage) throws Exception { + String body = "Testing: " + expectedCode; + Control control = this.control; + control.response = statusLine + "\r\n" + + "Content-Length: 0\r\n" + + "\r\n"; + control.testPath = "/ContinueResponseMessageTest/" + expectedCode; + + URL url = URIBuilder.newBuilder() + .scheme("http") + .loopback() + .port(control.serverSocket.getLocalPort()) + .path(control.testPath) + .toURL(); + + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setDoOutput(true); + connection.setRequestMethod("POST"); + connection.setRequestProperty("Connection", "Close"); + connection.setRequestProperty("Expect", "100-Continue"); + + try { + connection.setFixedLengthStreamingMode(body.getBytes().length); + OutputStream outputStream = connection.getOutputStream(); + outputStream.write(body.getBytes()); + outputStream.close(); + } catch (Exception ex) { + // server returning 4xx responses can result in exceptions + // but we can just swallow them + } + + int responseCode = connection.getResponseCode(); + String responseMessage = connection.getResponseMessage(); + assertTrue(responseCode == expectedCode, + String.format("Expected %s response, instead received %s", expectedCode, responseCode)); + assertTrue(expectedMessage.equals(responseMessage), + String.format("Expected Response Message %s, instead received %s", + expectedMessage, responseMessage)); + control.acceptingSocket.close(); + } +} From 0ad919c1e54895b000b58f6a1b54d79f76970845 Mon Sep 17 00:00:00 2001 From: Casper Norrbin Date: Mon, 11 Aug 2025 12:22:52 +0000 Subject: [PATCH 083/580] 8352067: Remove the NMT treap and replace its uses with the utilities red-black tree Reviewed-by: jsjolen, ayang --- src/hotspot/share/nmt/memoryFileTracker.cpp | 8 +- src/hotspot/share/nmt/nmtTreap.hpp | 447 ------------------ src/hotspot/share/nmt/regionsTree.cpp | 4 +- src/hotspot/share/nmt/regionsTree.hpp | 2 +- src/hotspot/share/nmt/regionsTree.inline.hpp | 4 +- src/hotspot/share/nmt/vmatree.cpp | 40 +- src/hotspot/share/nmt/vmatree.hpp | 20 +- src/hotspot/share/opto/printinlining.cpp | 18 +- src/hotspot/share/opto/printinlining.hpp | 4 +- src/hotspot/share/utilities/rbTree.hpp | 15 + src/hotspot/share/utilities/rbTree.inline.hpp | 43 +- test/hotspot/gtest/nmt/test_nmt_treap.cpp | 364 -------------- test/hotspot/gtest/nmt/test_vmatree.cpp | 60 +-- test/hotspot/gtest/utilities/test_rbtree.cpp | 63 ++- 14 files changed, 200 insertions(+), 892 deletions(-) delete mode 100644 src/hotspot/share/nmt/nmtTreap.hpp delete mode 100644 test/hotspot/gtest/nmt/test_nmt_treap.cpp diff --git a/src/hotspot/share/nmt/memoryFileTracker.cpp b/src/hotspot/share/nmt/memoryFileTracker.cpp index 677cd174650..55f1cb64626 100644 --- a/src/hotspot/share/nmt/memoryFileTracker.cpp +++ b/src/hotspot/share/nmt/memoryFileTracker.cpp @@ -64,12 +64,12 @@ void MemoryFileTracker::print_report_on(const MemoryFile* file, outputStream* st stream->print_cr("Memory map of %s", file->_descriptive_name); stream->cr(); - VMATree::TreapNode* prev = nullptr; + const VMATree::TNode* prev = nullptr; #ifdef ASSERT - VMATree::TreapNode* broken_start = nullptr; - VMATree::TreapNode* broken_end = nullptr; + const VMATree::TNode* broken_start = nullptr; + const VMATree::TNode* broken_end = nullptr; #endif - file->_tree.visit_in_order([&](VMATree::TreapNode* current) { + file->_tree.visit_in_order([&](const VMATree::TNode* current) { if (prev == nullptr) { // Must be first node. prev = current; diff --git a/src/hotspot/share/nmt/nmtTreap.hpp b/src/hotspot/share/nmt/nmtTreap.hpp deleted file mode 100644 index 13e759cbd3a..00000000000 --- a/src/hotspot/share/nmt/nmtTreap.hpp +++ /dev/null @@ -1,447 +0,0 @@ -/* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * 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_NMT_NMTTREAP_HPP -#define SHARE_NMT_NMTTREAP_HPP - -#include "runtime/os.hpp" -#include "utilities/debug.hpp" -#include "utilities/globalDefinitions.hpp" -#include "utilities/growableArray.hpp" -#include "utilities/macros.hpp" -#include "utilities/powerOfTwo.hpp" -#include - -// A Treap is a self-balanced binary tree where each node is equipped with a -// priority. It adds the invariant that the priority of a parent P is strictly larger -// larger than the priority of its children. When priorities are randomly -// assigned the tree is balanced. -// All operations are defined through merge and split, which are each other's inverse. -// merge(left_treap, right_treap) => treap where left_treap <= right_treap -// split(treap, key) => (left_treap, right_treap) where left_treap <= right_treap -// Recursion is used in these, but the depth of the call stack is the depth of -// the tree which is O(log n) so we are safe from stack overflow. -// TreapNode has LEQ nodes on the left, GT nodes on the right. -// -// COMPARATOR must have a static function `cmp(a,b)` which returns: -// - an int < 0 when a < b -// - an int == 0 when a == b -// - an int > 0 when a > b -// ALLOCATOR must check for oom and exit, as Treap currently does not handle the allocation -// failing. - -template -class Treap { - friend class NMTVMATreeTest; - friend class NMTTreapTest; - friend class VMTWithVMATreeTest; -public: - class TreapNode { - friend Treap; - uint64_t _priority; - const K _key; - V _value; - - TreapNode* _left; - TreapNode* _right; - - public: - TreapNode(const K& k, uint64_t p) : _priority(p), _key(k), _left(nullptr), _right(nullptr) {} - - TreapNode(const K& k, const V& v, uint64_t p) - : _priority(p), - _key(k), - _value(v), - _left(nullptr), - _right(nullptr) { - } - - const K& key() const { return _key; } - V& val() { return _value; } - - TreapNode* left() const { return _left; } - TreapNode* right() const { return _right; } - }; - -private: - ALLOCATOR _allocator; - TreapNode* _root; - - // A random number - static constexpr const uint64_t _initial_seed = 0xC8DD2114AE0543A3; - uint64_t _prng_seed; - int _node_count; - - uint64_t prng_next() { - uint64_t first_half = os::next_random(_prng_seed); - uint64_t second_half = os::next_random(_prng_seed >> 32); - _prng_seed = first_half | (second_half << 32); - return _prng_seed; - } - - struct node_pair { - TreapNode* left; - TreapNode* right; - }; - - enum SplitMode { - LT, // < - LEQ // <= - }; - - // Split tree at head into two trees, SplitMode decides where EQ values go. - // We have SplitMode because it makes remove() trivial to implement. - static node_pair split(TreapNode* head, const K& key, SplitMode mode = LEQ DEBUG_ONLY(COMMA int recur_count = 0)) { - assert(recur_count < 200, "Call-stack depth should never exceed 200"); - - if (head == nullptr) { - return {nullptr, nullptr}; - } - if ((COMPARATOR::cmp(head->_key, key) <= 0 && mode == LEQ) || (COMPARATOR::cmp(head->_key, key) < 0 && mode == LT)) { - node_pair p = split(head->_right, key, mode DEBUG_ONLY(COMMA recur_count + 1)); - head->_right = p.left; - return node_pair{head, p.right}; - } else { - node_pair p = split(head->_left, key, mode DEBUG_ONLY(COMMA recur_count + 1)); - head->_left = p.right; - return node_pair{p.left, head}; - } - } - - // Invariant: left is a treap whose keys are LEQ to the keys in right. - static TreapNode* merge(TreapNode* left, TreapNode* right DEBUG_ONLY(COMMA int recur_count = 0)) { - assert(recur_count < 200, "Call-stack depth should never exceed 200"); - - if (left == nullptr) return right; - if (right == nullptr) return left; - - if (left->_priority > right->_priority) { - // We need - // LEFT - // | - // RIGHT - // for the invariant re: priorities to hold. - left->_right = merge(left->_right, right DEBUG_ONLY(COMMA recur_count + 1)); - return left; - } else { - // We need - // RIGHT - // | - // LEFT - // for the invariant re: priorities to hold. - right->_left = merge(left, right->_left DEBUG_ONLY(COMMA recur_count + 1)); - return right; - } - } - - static TreapNode* find(TreapNode* node, const K& k DEBUG_ONLY(COMMA int recur_count = 0)) { - if (node == nullptr) { - return nullptr; - } - - int key_cmp_k = COMPARATOR::cmp(node->key(), k); - - if (key_cmp_k == 0) { // key EQ k - return node; - } - - if (key_cmp_k < 0) { // key LT k - return find(node->right(), k DEBUG_ONLY(COMMA recur_count + 1)); - } else { // key GT k - return find(node->left(), k DEBUG_ONLY(COMMA recur_count + 1)); - } - } - -#ifdef ASSERT - void verify_self() { - // A balanced binary search tree should have a depth on the order of log(N). - // We take the ceiling of log_2(N + 1) * 3 as our maximum bound. - // For comparison, a RB-tree has a proven max depth of log_2(N + 1) * 2. - const int expected_maximum_depth = ceil(log2i(this->_node_count+1) * 3); - // Find the maximum depth through DFS and ensure that the priority invariant holds. - int maximum_depth_found = 0; - - struct DFS { - int depth; - uint64_t parent_prio; - TreapNode* n; - }; - GrowableArrayCHeap to_visit; - constexpr const uint64_t positive_infinity = 0xFFFFFFFFFFFFFFFF; - - to_visit.push({0, positive_infinity, this->_root}); - while (!to_visit.is_empty()) { - DFS head = to_visit.pop(); - if (head.n == nullptr) continue; - maximum_depth_found = MAX2(maximum_depth_found, head.depth); - - assert(head.parent_prio >= head.n->_priority, "broken priority invariant"); - - to_visit.push({head.depth + 1, head.n->_priority, head.n->left()}); - to_visit.push({head.depth + 1, head.n->_priority, head.n->right()}); - } - assert(maximum_depth_found - expected_maximum_depth <= 3, - "depth unexpectedly large for treap of node count %d, was: %d, expected between %d and %d", - _node_count, maximum_depth_found, expected_maximum_depth - 3, expected_maximum_depth); - - // Visit everything in order, see that the key ordering is monotonically increasing. - TreapNode* last_seen = nullptr; - bool failed = false; - int seen_count = 0; - this->visit_in_order([&](TreapNode* node) { - seen_count++; - if (last_seen == nullptr) { - last_seen = node; - return true; - } - if (COMPARATOR::cmp(last_seen->key(), node->key()) > 0) { - failed = false; - } - last_seen = node; - return true; - }); - assert(seen_count == _node_count, "the number of visited nodes do not match with the number of stored nodes"); - assert(!failed, "keys was not monotonically strongly increasing when visiting in order"); - } -#endif // ASSERT - -public: - NONCOPYABLE(Treap); - - Treap() - : _allocator(), - _root(nullptr), - _prng_seed(_initial_seed), - _node_count(0) { - static_assert(std::is_trivially_destructible::value, "must be"); - } - - ~Treap() { - this->remove_all(); - } - - int size() { - return _node_count; - } - - void upsert(const K& k, const V& v) { - TreapNode* found = find(_root, k); - if (found != nullptr) { - // Already exists, update value. - found->_value = v; - return; - } - _node_count++; - // Doesn't exist, make node - void* node_place = _allocator.allocate(sizeof(TreapNode)); - uint64_t prio = prng_next(); - TreapNode* node = new (node_place) TreapNode(k, v, prio); - - // (LEQ_k, GT_k) - node_pair split_up = split(this->_root, k); - // merge(merge(LEQ_k, EQ_k), GT_k) - this->_root = merge(merge(split_up.left, node), split_up.right); - } - - void remove(const K& k) { - // (LEQ_k, GT_k) - node_pair first_split = split(this->_root, k, LEQ); - // (LT_k, GEQ_k) == (LT_k, EQ_k) since it's from LEQ_k and keys are unique. - node_pair second_split = split(first_split.left, k, LT); - - if (second_split.right != nullptr) { - // The key k existed, we delete it. - _node_count--; - second_split.right->_value.~V(); - _allocator.free(second_split.right); - } - // Merge together everything - _root = merge(second_split.left, first_split.right); - } - - // Delete all nodes. - void remove_all() { - _node_count = 0; - GrowableArrayCHeap to_delete; - to_delete.push(_root); - - while (!to_delete.is_empty()) { - TreapNode* head = to_delete.pop(); - if (head == nullptr) continue; - to_delete.push(head->_left); - to_delete.push(head->_right); - head->_value.~V(); - _allocator.free(head); - } - _root = nullptr; - } - - TreapNode* closest_leq(const K& key) { - TreapNode* candidate = nullptr; - TreapNode* pos = _root; - while (pos != nullptr) { - int cmp_r = COMPARATOR::cmp(pos->key(), key); - if (cmp_r == 0) { // Exact match - candidate = pos; - break; // Can't become better than that. - } - if (cmp_r < 0) { - // Found a match, try to find a better one. - candidate = pos; - pos = pos->_right; - } else if (cmp_r > 0) { - pos = pos->_left; - } - } - return candidate; - } - - struct FindResult { - FindResult(TreapNode* node, bool new_node) : node(node), new_node(new_node) {} - TreapNode* const node; - bool const new_node; - }; - - // Finds the node for the given k in the tree or inserts a new node with the default constructed value. - FindResult find(const K& k) { - if (TreapNode* found = find(_root, k)) { - return FindResult(found, false); - } - _node_count++; - // Doesn't exist, make node - void* node_place = _allocator.allocate(sizeof(TreapNode)); - uint64_t prio = prng_next(); - TreapNode* node = new (node_place) TreapNode(k, prio); - - // (LEQ_k, GT_k) - node_pair split_up = split(this->_root, k); - // merge(merge(LEQ_k, EQ_k), GT_k) - this->_root = merge(merge(split_up.left, node), split_up.right); - return FindResult(node, true); - } - - TreapNode* closest_gt(const K& key) { - TreapNode* candidate = nullptr; - TreapNode* pos = _root; - while (pos != nullptr) { - int cmp_r = COMPARATOR::cmp(pos->key(), key); - if (cmp_r > 0) { - // Found a match, try to find a better one. - candidate = pos; - pos = pos->_left; - } else if (cmp_r <= 0) { - pos = pos->_right; - } - } - return candidate; - } - - struct Range { - TreapNode* start; - TreapNode* end; - Range(TreapNode* start, TreapNode* end) - : start(start), end(end) {} - }; - - // Return the range [start, end) - // where start->key() <= addr < end->key(). - // Failure to find the range leads to start and/or end being null. - Range find_enclosing_range(K addr) { - TreapNode* start = closest_leq(addr); - TreapNode* end = closest_gt(addr); - return Range(start, end); - } - - // Visit all TreapNodes in ascending key order. - template - void visit_in_order(F f) const { - GrowableArrayCHeap to_visit; - TreapNode* head = _root; - while (!to_visit.is_empty() || head != nullptr) { - while (head != nullptr) { - to_visit.push(head); - head = head->left(); - } - head = to_visit.pop(); - if (!f(head)) { - return; - } - head = head->right(); - } - } - - // Visit all TreapNodes in ascending order whose keys are in range [from, to). - template - void visit_range_in_order(const K& from, const K& to, F f) { - assert(COMPARATOR::cmp(from, to) <= 0, "from must be less or equal to to"); - GrowableArrayCHeap to_visit; - TreapNode* head = _root; - while (!to_visit.is_empty() || head != nullptr) { - while (head != nullptr) { - int cmp_from = COMPARATOR::cmp(head->key(), from); - to_visit.push(head); - if (cmp_from >= 0) { - head = head->left(); - } else { - // We've reached a node which is strictly less than from - // We don't need to visit any further to the left. - break; - } - } - head = to_visit.pop(); - const int cmp_from = COMPARATOR::cmp(head->key(), from); - const int cmp_to = COMPARATOR::cmp(head->key(), to); - if (cmp_from >= 0 && cmp_to < 0) { - if (!f(head)) { - return; - } - } - if (cmp_to < 0) { - head = head->right(); - } else { - head = nullptr; - } - } - } -}; - -class TreapCHeapAllocator { -public: - void* allocate(size_t sz) { - void* allocation = os::malloc(sz, mtNMT); - if (allocation == nullptr) { - vm_exit_out_of_memory(sz, OOM_MALLOC_ERROR, "treap failed allocation"); - } - return allocation; - } - - void free(void* ptr) { - os::free(ptr); - } -}; - -template -using TreapCHeap = Treap; - -#endif //SHARE_NMT_NMTTREAP_HPP diff --git a/src/hotspot/share/nmt/regionsTree.cpp b/src/hotspot/share/nmt/regionsTree.cpp index 09686f52067..370c69a2485 100644 --- a/src/hotspot/share/nmt/regionsTree.cpp +++ b/src/hotspot/share/nmt/regionsTree.cpp @@ -48,8 +48,8 @@ void RegionsTree::NodeHelper::print_on(outputStream* st) { } void RegionsTree::print_on(outputStream* st) { - visit_in_order([&](Node* node) { - NodeHelper curr(node); + visit_in_order([&](const Node* node) { + NodeHelper curr(const_cast(node)); curr.print_on(st); return true; }); diff --git a/src/hotspot/share/nmt/regionsTree.hpp b/src/hotspot/share/nmt/regionsTree.hpp index 7fdb4e6fd39..bf2ab711b2d 100644 --- a/src/hotspot/share/nmt/regionsTree.hpp +++ b/src/hotspot/share/nmt/regionsTree.hpp @@ -45,7 +45,7 @@ class RegionsTree : public VMATree { SummaryDiff commit_region(address addr, size_t size, const NativeCallStack& stack); SummaryDiff uncommit_region(address addr, size_t size); - using Node = VMATree::TreapNode; + using Node = VMATree::TNode; class NodeHelper { Node* _node; diff --git a/src/hotspot/share/nmt/regionsTree.inline.hpp b/src/hotspot/share/nmt/regionsTree.inline.hpp index f1b7319f5ca..665f4a93c88 100644 --- a/src/hotspot/share/nmt/regionsTree.inline.hpp +++ b/src/hotspot/share/nmt/regionsTree.inline.hpp @@ -52,8 +52,8 @@ void RegionsTree::visit_reserved_regions(F func) { NodeHelper begin_node, prev; size_t rgn_size = 0; - visit_in_order([&](Node* node) { - NodeHelper curr(node); + visit_in_order([&](const Node* node) { + NodeHelper curr(const_cast(node)); if (prev.is_valid()) { rgn_size += curr.distance_from(prev); } else { diff --git a/src/hotspot/share/nmt/vmatree.cpp b/src/hotspot/share/nmt/vmatree.cpp index a60c30c812d..4f6f8e12185 100644 --- a/src/hotspot/share/nmt/vmatree.cpp +++ b/src/hotspot/share/nmt/vmatree.cpp @@ -204,7 +204,7 @@ void VMATree::compute_summary_diff(const SingleDiff::delta region_size, // update the region state between n1 and n2. Since n1 and n2 are pointers, any update of them will be visible from tree. // If n1 is noop, it can be removed because its left region (n1->val().in) is already decided and its right state (n1->val().out) is decided here. // The state of right of n2 (n2->val().out) cannot be decided here yet. -void VMATree::update_region(TreapNode* n1, TreapNode* n2, const RequestInfo& req, SummaryDiff& diff) { +void VMATree::update_region(TNode* n1, TNode* n2, const RequestInfo& req, SummaryDiff& diff) { assert(n1 != nullptr,"sanity"); assert(n2 != nullptr,"sanity"); //.........n1......n2...... @@ -261,8 +261,8 @@ VMATree::SummaryDiff VMATree::register_mapping(position _A, position _B, StateTy }; stA.out.set_commit_stack(NativeCallStackStorage::invalid); stB.in.set_commit_stack(NativeCallStackStorage::invalid); - VMATreap::Range rA = _tree.find_enclosing_range(_A); - VMATreap::Range rB = _tree.find_enclosing_range(_B); + VMARBTree::Range rA = _tree.find_enclosing_range(_A); + VMARBTree::Range rB = _tree.find_enclosing_range(_B); // nodes: .....X.......Y...Z......W........U // request: A------------------B @@ -320,24 +320,24 @@ VMATree::SummaryDiff VMATree::register_mapping(position _A, position _B, StateTy // Meaning that whenever any of one item in this sequence is changed, the rest of the consequent items to // be checked/changed. - TreapNode* X = rA.start; - TreapNode* Y = rA.end; - TreapNode* W = rB.start; - TreapNode* U = rB.end; - TreapNode nA{_A, stA, 0}; // the node that represents A - TreapNode nB{_B, stB, 0}; // the node that represents B - TreapNode* A = &nA; - TreapNode* B = &nB; - auto upsert_if= [&](TreapNode* node) { + TNode* X = rA.start; + TNode* Y = rA.end; + TNode* W = rB.start; + TNode* U = rB.end; + TNode nA{_A, stA}; // the node that represents A + TNode nB{_B, stB}; // the node that represents B + TNode* A = &nA; + TNode* B = &nB; + auto upsert_if= [&](TNode* node) { if (!node->val().is_noop()) { _tree.upsert(node->key(), node->val()); } }; // update region between n1 and n2 - auto update = [&](TreapNode* n1, TreapNode* n2) { + auto update = [&](TNode* n1, TNode* n2) { update_region(n1, n2, req, diff); }; - auto remove_if = [&](TreapNode* node) -> bool{ + auto remove_if = [&](TNode* node) -> bool{ if (node->val().is_noop()) { _tree.remove(node->key()); return true; @@ -347,8 +347,8 @@ VMATree::SummaryDiff VMATree::register_mapping(position _A, position _B, StateTy GrowableArrayCHeap to_be_removed; // update regions in range A to B auto update_loop = [&]() { - TreapNode* prev = nullptr; - _tree.visit_range_in_order(_A + 1, _B + 1, [&](TreapNode* curr) { + TNode* prev = nullptr; + _tree.visit_range_in_order(_A + 1, _B + 1, [&](TNode* curr) { if (prev != nullptr) { update_region(prev, curr, req, diff); // during visit, structure of the tree should not be changed @@ -362,7 +362,7 @@ VMATree::SummaryDiff VMATree::register_mapping(position _A, position _B, StateTy }); }; // update region of [A,T) - auto update_A = [&](TreapNode* T) { + auto update_A = [&](TNode* T) { A->val().out = A->val().in; update(A, T); }; @@ -650,7 +650,7 @@ VMATree::SummaryDiff VMATree::register_mapping(position _A, position _B, StateTy #ifdef ASSERT void VMATree::print_on(outputStream* out) { - visit_in_order([&](TreapNode* current) { + visit_in_order([&](const TNode* current) { out->print("%zu (%s) - %s [%d, %d]-> ", current->key(), NMTUtil::tag_to_name(out_state(current).mem_tag()), statetype_to_string(out_state(current).type()), current->val().out.reserved_stack(), current->val().out.committed_stack()); return true; @@ -660,11 +660,11 @@ void VMATree::print_on(outputStream* out) { #endif VMATree::SummaryDiff VMATree::set_tag(const position start, const size size, const MemTag tag) { - auto pos = [](TreapNode* n) { return n->key(); }; + auto pos = [](TNode* n) { return n->key(); }; position from = start; position end = from+size; size_t remsize = size; - VMATreap::Range range(nullptr, nullptr); + VMARBTree::Range range(nullptr, nullptr); // Find the next range to adjust and set range, remsize and from // appropriately. If it returns false, there is no valid next range. diff --git a/src/hotspot/share/nmt/vmatree.hpp b/src/hotspot/share/nmt/vmatree.hpp index 7f52b406309..d2acabdae07 100644 --- a/src/hotspot/share/nmt/vmatree.hpp +++ b/src/hotspot/share/nmt/vmatree.hpp @@ -29,9 +29,9 @@ #include "nmt/memTag.hpp" #include "nmt/memTag.hpp" #include "nmt/nmtNativeCallStackStorage.hpp" -#include "nmt/nmtTreap.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/ostream.hpp" +#include "utilities/rbTree.inline.hpp" #include // A VMATree stores a sequence of points on the natural number line. @@ -193,17 +193,21 @@ private: }; public: - using VMATreap = TreapCHeap; - using TreapNode = VMATreap::TreapNode; + using VMARBTree = RBTreeCHeap; + using TNode = RBNode; private: - VMATreap _tree; + VMARBTree _tree; - static IntervalState& in_state(TreapNode* node) { + static IntervalState& in_state(TNode* node) { return node->val().in; } - static IntervalState& out_state(TreapNode* node) { + static IntervalState& out_state(TNode* node) { + return node->val().out; + } + + static const IntervalState& out_state(const TNode* node) { return node->val().out; } @@ -281,7 +285,7 @@ public: SIndex get_new_reserve_callstack(const SIndex existinting_stack, const StateType ex, const RequestInfo& req) const; SIndex get_new_commit_callstack(const SIndex existinting_stack, const StateType ex, const RequestInfo& req) const; void compute_summary_diff(const SingleDiff::delta region_size, const MemTag t1, const StateType& ex, const RequestInfo& req, const MemTag new_tag, SummaryDiff& diff) const; - void update_region(TreapNode* n1, TreapNode* n2, const RequestInfo& req, SummaryDiff& diff); + void update_region(TNode* n1, TNode* n2, const RequestInfo& req, SummaryDiff& diff); int state_to_index(const StateType st) const { return st == StateType::Released ? 0 : @@ -325,6 +329,6 @@ public: void visit_range_in_order(const position& from, const position& to, F f) { _tree.visit_range_in_order(from, to, f); } - VMATreap& tree() { return _tree; } + VMARBTree& tree() { return _tree; } }; #endif diff --git a/src/hotspot/share/opto/printinlining.cpp b/src/hotspot/share/opto/printinlining.cpp index fe039623764..06d14a7f3af 100644 --- a/src/hotspot/share/opto/printinlining.cpp +++ b/src/hotspot/share/opto/printinlining.cpp @@ -71,21 +71,25 @@ InlinePrinter::IPInlineSite* InlinePrinter::locate(JVMState* state, ciMethod* ca } InlinePrinter::IPInlineSite& InlinePrinter::IPInlineSite::at_bci(int bci, ciMethod* callee) { - auto find_result = _children.find(bci); - IPInlineSite& child = find_result.node->val(); + RBTreeCHeap::Cursor cursor = _children.cursor(bci); - if (find_result.new_node) { - assert(callee != nullptr, "an inline call is missing in the chain up to the root"); - child.set_source(callee, bci); - } else { // We already saw a call at this site before + if (cursor.found()) { // We already saw a call at this site before + IPInlineSite& child = cursor.node()->val(); if (callee != nullptr && callee != child._method) { outputStream* stream = child.add(InliningResult::SUCCESS); stream->print("callee changed to "); CompileTask::print_inline_inner_method_info(stream, callee); } + return child; } - return child; + assert(callee != nullptr, "an inline call is missing in the chain up to the root"); + + RBNode* node = _children.allocate_node(bci); + _children.insert_at_cursor(node, cursor); + node->val().set_source(callee, bci); + + return node->val(); } outputStream* InlinePrinter::IPInlineSite::add(InliningResult result) { diff --git a/src/hotspot/share/opto/printinlining.hpp b/src/hotspot/share/opto/printinlining.hpp index 57f4b51858e..3bf09bc921f 100644 --- a/src/hotspot/share/opto/printinlining.hpp +++ b/src/hotspot/share/opto/printinlining.hpp @@ -26,9 +26,9 @@ #define PRINTINLINING_HPP #include "memory/allocation.hpp" -#include "nmt/nmtTreap.hpp" #include "utilities/growableArray.hpp" #include "utilities/ostream.hpp" +#include "utilities/rbTree.inline.hpp" class JVMState; class ciMethod; @@ -77,7 +77,7 @@ private: ciMethod* _method; int _bci; GrowableArrayCHeap _attempts; - TreapCHeap _children; + RBTreeCHeap _children; public: IPInlineSite(ciMethod* method, int bci) : _method(method), _bci(bci) {} diff --git a/src/hotspot/share/utilities/rbTree.hpp b/src/hotspot/share/utilities/rbTree.hpp index 761148d1c89..405fa3e9ae9 100644 --- a/src/hotspot/share/utilities/rbTree.hpp +++ b/src/hotspot/share/utilities/rbTree.hpp @@ -167,6 +167,7 @@ public: template class AbstractRBTree { friend class RBTreeTest; + friend class NMTVMATreeTest; typedef AbstractRBTree TreeType; public: @@ -404,13 +405,21 @@ public: } // Visit all RBNodes in ascending order, calling f on each node. + // If f returns `true` the iteration continues, otherwise it is stopped at the current node. template void visit_in_order(F f) const; + template + void visit_in_order(F f); + // Visit all RBNodes in ascending order whose keys are in range [from, to], calling f on each node. + // If f returns `true` the iteration continues, otherwise it is stopped at the current node. template void visit_range_in_order(const K& from, const K& to, F f) const; + template + void visit_range_in_order(const K &from, const K &to, F f); + // Verifies that the tree is correct and holds rb-properties // If not using a key comparator (when using IntrusiveRBTree for example), // A second `cmp` must exist in COMPARATOR (see top of file). @@ -457,6 +466,12 @@ public: free_node(old_node); } + RBNode* allocate_node(const K& key) { + void* node_place = _allocator.allocate(sizeof(RBNode)); + assert(node_place != nullptr, "rb-tree allocator must exit on failure"); + return new (node_place) RBNode(key); + } + RBNode* allocate_node(const K& key, const V& val) { void* node_place = _allocator.allocate(sizeof(RBNode)); assert(node_place != nullptr, "rb-tree allocator must exit on failure"); diff --git a/src/hotspot/share/utilities/rbTree.inline.hpp b/src/hotspot/share/utilities/rbTree.inline.hpp index 1c3fdfb0dd7..365786f7fc1 100644 --- a/src/hotspot/share/utilities/rbTree.inline.hpp +++ b/src/hotspot/share/utilities/rbTree.inline.hpp @@ -85,7 +85,7 @@ inline IntrusiveRBNode* IntrusiveRBNode::rotate_right() { inline const IntrusiveRBNode* IntrusiveRBNode::prev() const { const IntrusiveRBNode* node = this; - if (_left != nullptr) { // right subtree exists + if (_left != nullptr) { // left subtree exists node = _left; while (node->_right != nullptr) { node = node->_right; @@ -599,7 +599,21 @@ template inline void AbstractRBTree::visit_in_order(F f) const { const NodeType* node = leftmost(); while (node != nullptr) { - f(node); + if (!f(node)) { + return; + } + node = node->next(); + } +} + +template +template +inline void AbstractRBTree::visit_in_order(F f) { + NodeType* node = leftmost(); + while (node != nullptr) { + if (!f(node)) { + return; + } node = node->next(); } } @@ -618,7 +632,30 @@ inline void AbstractRBTree::visit_range_in_order(const const NodeType* end = next(cursor_end).node(); while (start != end) { - f(start); + if (!f(start)) { + return; + } + start = start->next(); + } +} + +template +template +inline void AbstractRBTree::visit_range_in_order(const K& from, const K& to, F f) { + assert_key_leq(from, to); + if (_root == nullptr) { + return; + } + + Cursor cursor_start = cursor(from); + Cursor cursor_end = cursor(to); + NodeType* start = cursor_start.found() ? cursor_start.node() : next(cursor_start).node(); + NodeType* end = next(cursor_end).node(); + + while (start != end) { + if (!f(start)) { + return; + } start = start->next(); } } diff --git a/test/hotspot/gtest/nmt/test_nmt_treap.cpp b/test/hotspot/gtest/nmt/test_nmt_treap.cpp deleted file mode 100644 index bc8c24b592f..00000000000 --- a/test/hotspot/gtest/nmt/test_nmt_treap.cpp +++ /dev/null @@ -1,364 +0,0 @@ -/* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - * - */ - -#include "memory/resourceArea.hpp" -#include "nmt/nmtTreap.hpp" -#include "nmt/virtualMemoryTracker.hpp" -#include "runtime/os.hpp" -#include "unittest.hpp" -class NMTTreapTest : public testing::Test { -public: - struct Cmp { - static int cmp(int a, int b) { - return a - b; - } - }; - - struct CmpInverse { - static int cmp(int a, int b) { - return b - a; - } - }; - - struct FCmp { - static int cmp(float a, float b) { - if (a < b) return -1; - if (a == b) return 0; - return 1; - } - }; - -#ifdef ASSERT - template - void verify_it(Treap& t) { - t.verify_self(); - } -#endif // ASSERT - -public: - void inserting_duplicates_results_in_one_value() { - constexpr const int up_to = 10; - GrowableArrayCHeap nums_seen(up_to, up_to, 0); - TreapCHeap treap; - - for (int i = 0; i < up_to; i++) { - treap.upsert(i, i); - treap.upsert(i, i); - treap.upsert(i, i); - treap.upsert(i, i); - treap.upsert(i, i); - } - - treap.visit_in_order([&](TreapCHeap::TreapNode* node) { - nums_seen.at(node->key())++; - return true; - }); - for (int i = 0; i < up_to; i++) { - EXPECT_EQ(1, nums_seen.at(i)); - } - } - - void treap_ought_not_leak() { - struct LeakCheckedAllocator { - int allocations; - - LeakCheckedAllocator() - : allocations(0) { - } - - void* allocate(size_t sz) { - void* allocation = os::malloc(sz, mtTest); - if (allocation == nullptr) { - vm_exit_out_of_memory(sz, OOM_MALLOC_ERROR, "treap failed allocation"); - } - ++allocations; - return allocation; - } - - void free(void* ptr) { - --allocations; - os::free(ptr); - } - }; - - constexpr const int up_to = 10; - { - Treap treap; - for (int i = 0; i < up_to; i++) { - treap.upsert(i, i); - } - EXPECT_EQ(up_to, treap._allocator.allocations); - for (int i = 0; i < up_to; i++) { - treap.remove(i); - } - EXPECT_EQ(0, treap._allocator.allocations); - EXPECT_EQ(nullptr, treap._root); - } - - { - Treap treap; - for (int i = 0; i < up_to; i++) { - treap.upsert(i, i); - } - treap.remove_all(); - EXPECT_EQ(0, treap._allocator.allocations); - EXPECT_EQ(nullptr, treap._root); - } - } - - void test_find() { - struct Empty {}; - TreapCHeap treap; - using Node = TreapCHeap::TreapNode; - - Node* n = nullptr; - auto test = [&](float f) { - EXPECT_EQ(nullptr, treap.find(treap._root, f)); - treap.upsert(f, Empty{}); - Node* n = treap.find(treap._root, f); - EXPECT_NE(nullptr, n); - EXPECT_EQ(f, n->key()); - }; - - test(1.0f); - test(5.0f); - test(0.0f); - } -}; - -TEST_VM_F(NMTTreapTest, InsertingDuplicatesResultsInOneValue) { - this->inserting_duplicates_results_in_one_value(); -} - -TEST_VM_F(NMTTreapTest, TreapOughtNotLeak) { - this->treap_ought_not_leak(); -} - -TEST_VM_F(NMTTreapTest, TestVisitors) { - { // Tests with 'default' ordering (ascending) - TreapCHeap treap; - using Node = TreapCHeap::TreapNode; - - treap.visit_range_in_order(0, 100, [&](Node* x) { - EXPECT_TRUE(false) << "Empty treap has no nodes to visit"; - return true; - }); - - // Single-element set - treap.upsert(1, 0); - int count = 0; - treap.visit_range_in_order(0, 100, [&](Node* x) { - count++; - return true; - }); - EXPECT_EQ(1, count); - - count = 0; - treap.visit_in_order([&](Node* x) { - count++; - return true; - }); - EXPECT_EQ(1, count); - - // Add an element outside of the range that should not be visited on the right side and - // one on the left side. - treap.upsert(101, 0); - treap.upsert(-1, 0); - count = 0; - treap.visit_range_in_order(0, 100, [&](Node* x) { - count++; - return true; - }); - EXPECT_EQ(1, count); - - count = 0; - treap.visit_in_order([&](Node* x) { - count++; - return true; - }); - EXPECT_EQ(3, count); - - // Visiting empty range [0, 0) == {} - treap.upsert(0, 0); // This node should not be visited. - treap.visit_range_in_order(0, 0, [&](Node* x) { - EXPECT_TRUE(false) << "Empty visiting range should not visit any node"; - return true; - }); - - treap.remove_all(); - for (int i = 0; i < 11; i++) { - treap.upsert(i, 0); - } - - ResourceMark rm; - GrowableArray seen; - treap.visit_range_in_order(0, 10, [&](Node* x) { - seen.push(x->key()); - return true; - }); - EXPECT_EQ(10, seen.length()); - for (int i = 0; i < 10; i++) { - EXPECT_EQ(i, seen.at(i)); - } - - seen.clear(); - treap.visit_in_order([&](Node* x) { - seen.push(x->key()); - return true; - }); - EXPECT_EQ(11, seen.length()); - for (int i = 0; i < 10; i++) { - EXPECT_EQ(i, seen.at(i)); - } - - seen.clear(); - treap.visit_range_in_order(10, 12, [&](Node* x) { - seen.push(x->key()); - return true; - }); - EXPECT_EQ(1, seen.length()); - EXPECT_EQ(10, seen.at(0)); - } - { // Test with descending ordering - TreapCHeap treap; - using Node = TreapCHeap::TreapNode; - - for (int i = 0; i < 10; i++) { - treap.upsert(i, 0); - } - ResourceMark rm; - GrowableArray seen; - treap.visit_range_in_order(9, -1, [&](Node* x) { - seen.push(x->key()); - return true; - }); - EXPECT_EQ(10, seen.length()); - for (int i = 0; i < 10; i++) { - EXPECT_EQ(10-i-1, seen.at(i)); - } - seen.clear(); - - treap.visit_in_order([&](Node* x) { - seen.push(x->key()); - return true; - }); - EXPECT_EQ(10, seen.length()); - for (int i = 0; i < 10; i++) { - EXPECT_EQ(10 - i - 1, seen.at(i)); - } - } -} - -TEST_VM_F(NMTTreapTest, TestFind) { - test_find(); -} - -TEST_VM_F(NMTTreapTest, TestClosestLeq) { - using Node = TreapCHeap::TreapNode; - { - TreapCHeap treap; - Node* n = treap.closest_leq(0); - EXPECT_EQ(nullptr, n); - - treap.upsert(0, 0); - n = treap.closest_leq(0); - EXPECT_EQ(0, n->key()); - - treap.upsert(-1, -1); - n = treap.closest_leq(0); - EXPECT_EQ(0, n->key()); - - treap.upsert(6, 0); - n = treap.closest_leq(6); - EXPECT_EQ(6, n->key()); - - n = treap.closest_leq(-2); - EXPECT_EQ(nullptr, n); - } -} - -#ifdef ASSERT - -TEST_VM_F(NMTTreapTest, VerifyItThroughStressTest) { - { // Repeatedly verify a treap of moderate size - TreapCHeap treap; - constexpr const int ten_thousand = 10000; - for (int i = 0; i < ten_thousand; i++) { - int r = os::random(); - if (r % 2 == 0) { - treap.upsert(i, i); - } else { - treap.remove(i); - } - if (i % 100 == 0) { - verify_it(treap); - } - } - for (int i = 0; i < ten_thousand; i++) { - int r = os::random(); - if (r % 2 == 0) { - treap.upsert(i, i); - } else { - treap.remove(i); - } - if (i % 100 == 0) { - verify_it(treap); - } - } - } - { // Make a very large treap and verify at the end - struct Nothing {}; - TreapCHeap treap; - constexpr const int one_hundred_thousand = 100000; - for (int i = 0; i < one_hundred_thousand; i++) { - treap.upsert(i, Nothing()); - } - verify_it(treap); - } -} -struct NTD { - static bool has_run_destructor; - ~NTD() { - has_run_destructor = true; - } -}; - -bool NTD::has_run_destructor = false; - -TEST_VM_F(NMTTreapTest, ValueDestructorsAreRun) { - TreapCHeap treap; - NTD ntd; - treap.upsert(0, ntd); - treap.remove(0); - EXPECT_TRUE(NTD::has_run_destructor); - NTD::has_run_destructor = false; - { - TreapCHeap treap; - NTD ntd; - treap.upsert(0, ntd); - } - EXPECT_TRUE(NTD::has_run_destructor); -} - -#endif // ASSERT diff --git a/test/hotspot/gtest/nmt/test_vmatree.cpp b/test/hotspot/gtest/nmt/test_vmatree.cpp index b43bfc58c69..cc4493a0e0f 100644 --- a/test/hotspot/gtest/nmt/test_vmatree.cpp +++ b/test/hotspot/gtest/nmt/test_vmatree.cpp @@ -31,7 +31,7 @@ #include "unittest.hpp" using Tree = VMATree; -using TNode = Tree::TreapNode; +using TNode = Tree::TNode; using NCS = NativeCallStackStorage; class NMTVMATreeTest : public testing::Test { @@ -54,16 +54,16 @@ public: // Utilities - VMATree::TreapNode* treap_root(VMATree& tree) { - return tree._tree._root; + VMATree::TNode* rbtree_root(VMATree& tree) { + return static_cast(tree._tree._root); } - VMATree::VMATreap& treap(VMATree& tree) { + VMATree::VMARBTree& rbtree(VMATree& tree) { return tree._tree; } - VMATree::TreapNode* find(VMATree::VMATreap& treap, const VMATree::position key) { - return treap.find(treap._root, key); + VMATree::TNode* find(VMATree::VMARBTree& rbtree, const VMATree::position key) { + return rbtree.find_node(key); } NativeCallStack make_stack(size_t a) { @@ -71,17 +71,17 @@ public: return stack; } - VMATree::StateType in_type_of(VMATree::TreapNode* x) { + VMATree::StateType in_type_of(VMATree::TNode* x) { return x->val().in.type(); } - VMATree::StateType out_type_of(VMATree::TreapNode* x) { + VMATree::StateType out_type_of(VMATree::TNode* x) { return x->val().out.type(); } int count_nodes(Tree& tree) { int count = 0; - treap(tree).visit_in_order([&](TNode* x) { + rbtree(tree).visit_in_order([&](TNode* x) { ++count; return true; }); @@ -123,14 +123,14 @@ public: for (int i = 0; i < 10; i++) { tree.release_mapping(i * 100, 100); } - EXPECT_EQ(nullptr, treap_root(tree)); + EXPECT_EQ(nullptr, rbtree_root(tree)); // Other way around tree.reserve_mapping(0, 100 * 10, rd); for (int i = 9; i >= 0; i--) { tree.release_mapping(i * 100, 100); } - EXPECT_EQ(nullptr, treap_root(tree)); + EXPECT_EQ(nullptr, rbtree_root(tree)); } // Committing in a whole reserved range results in 2 nodes @@ -140,7 +140,7 @@ public: for (int i = 0; i < 10; i++) { tree.commit_mapping(i * 100, 100, rd); } - treap(tree).visit_in_order([&](TNode* x) { + rbtree(tree).visit_in_order([&](TNode* x) { VMATree::StateType in = in_type_of(x); VMATree::StateType out = out_type_of(x); EXPECT_TRUE((in == VMATree::StateType::Released && out == VMATree::StateType::Committed) || @@ -166,7 +166,7 @@ public: }; int i = 0; - treap(tree).visit_in_order([&](TNode* x) { + rbtree(tree).visit_in_order([&](TNode* x) { if (i < 16) { found[i] = x->key(); } @@ -199,7 +199,7 @@ public: }; void call_update_region(const UpdateCallInfo upd) { - VMATree::TreapNode n1{upd.req.A, {}, 0}, n2{upd.req.B, {}, 0}; + VMATree::TNode n1{upd.req.A, {}}, n2{upd.req.B, {}}; n1.val().out= upd.ex_st; n2.val().in = n1.val().out; Tree tree; @@ -264,7 +264,7 @@ public: template void check_tree(Tree& tree, const ExpectedTree& et, int line_no) { - using Node = VMATree::TreapNode; + using Node = VMATree::TNode; auto left_released = [&](Node n) -> bool { return n.val().in.type() == VMATree::StateType::Released and n.val().in.mem_tag() == mtNone; @@ -274,7 +274,7 @@ public: n.val().out.mem_tag() == mtNone; }; for (int i = 0; i < N; i++) { - VMATree::VMATreap::Range r = tree.tree().find_enclosing_range(et.nodes[i]); + VMATree::VMARBTree::Range r = tree.tree().find_enclosing_range(et.nodes[i]); ASSERT_TRUE(r.start != nullptr); Node node = *r.start; ASSERT_EQ(node.key(), (VMATree::position)et.nodes[i]) << "at line " << line_no; @@ -354,7 +354,7 @@ TEST_VM_F(NMTVMATreeTest, DuplicateReserve) { tree.reserve_mapping(100, 100, rd); tree.reserve_mapping(100, 100, rd); EXPECT_EQ(2, count_nodes(tree)); - VMATree::VMATreap::Range r = tree.tree().find_enclosing_range(110); + VMATree::VMARBTree::Range r = tree.tree().find_enclosing_range(110); EXPECT_EQ(100, (int)(r.end->key() - r.start->key())); } @@ -369,7 +369,7 @@ TEST_VM_F(NMTVMATreeTest, UseTagInplace) { // post-cond: 0---20**30--40**70----100 tree.commit_mapping(20, 50, rd_None_cs1, true); tree.uncommit_mapping(30, 10, rd_None_cs1); - tree.visit_in_order([&](TNode* node) { + tree.visit_in_order([&](const TNode* node) { if (node->key() != 100) { EXPECT_EQ(mtTest, node->val().out.mem_tag()) << "failed at: " << node->key(); if (node->key() != 20 && node->key() != 40) { @@ -410,9 +410,9 @@ TEST_VM_F(NMTVMATreeTest, LowLevel) { VMATree::RegionData rd_NMT_cs1{si[1], mtNMT}; tree.commit_mapping(50, 50, rd_NMT_cs1); tree.reserve_mapping(0, 100, rd_Test_cs0); - treap(tree).visit_in_order([&](TNode* x) { + rbtree(tree).visit_in_order([&](const TNode* x) { EXPECT_TRUE(x->key() == 0 || x->key() == 100); - if (x->key() == 0) { + if (x->key() == 0UL) { EXPECT_EQ(x->val().out.reserved_regiondata().mem_tag, mtTest); } return true; @@ -438,7 +438,7 @@ TEST_VM_F(NMTVMATreeTest, LowLevel) { tree.reserve_mapping(0, 500000, rd_NMT_cs0); tree.release_mapping(0, 500000); - EXPECT_EQ(nullptr, treap_root(tree)); + EXPECT_EQ(nullptr, rbtree_root(tree)); } { // A committed region inside of/replacing a reserved region @@ -448,7 +448,7 @@ TEST_VM_F(NMTVMATreeTest, LowLevel) { Tree tree; tree.reserve_mapping(0, 100, rd_NMT_cs0); tree.commit_mapping(0, 100, rd_Test_cs1); - treap(tree).visit_range_in_order(0, 99999, [&](TNode* x) { + rbtree(tree).visit_range_in_order(0, 99999, [&](TNode* x) { if (x->key() == 0) { EXPECT_EQ(mtTest, x->val().out.reserved_regiondata().mem_tag); } @@ -463,9 +463,9 @@ TEST_VM_F(NMTVMATreeTest, LowLevel) { Tree tree; VMATree::RegionData rd_NMT_cs0{si[0], mtNMT}; tree.reserve_mapping(0, 0, rd_NMT_cs0); - EXPECT_EQ(nullptr, treap_root(tree)); + EXPECT_EQ(nullptr, rbtree_root(tree)); tree.commit_mapping(0, 0, rd_NMT_cs0); - EXPECT_EQ(nullptr, treap_root(tree)); + EXPECT_EQ(nullptr, rbtree_root(tree)); } } @@ -483,14 +483,14 @@ TEST_VM_F(NMTVMATreeTest, SetTag) { auto expect_equivalent_form = [&](auto& expected, VMATree& tree, int line_no) { // With auto& our arrays do not deteriorate to pointers but are kept as testrange[N] // so this actually works! - int len = sizeof(expected) / sizeof(testrange); + size_t len = sizeof(expected) / sizeof(testrange); VMATree::position previous_to = 0; - for (int i = 0; i < len; i++) { + for (size_t i = 0; i < len; i++) { testrange expect = expected[i]; assert(previous_to == 0 || previous_to <= expect.from, "the expected list must be sorted"); previous_to = expect.to; - VMATree::VMATreap::Range found = tree.tree().find_enclosing_range(expect.from); + VMATree::VMARBTree::Range found = tree.tree().find_enclosing_range(expect.from); ASSERT_NE(nullptr, found.start); ASSERT_NE(nullptr, found.end); // Same region @@ -993,10 +993,10 @@ TEST_VM_F(NMTVMATreeTest, TestConsistencyWithSimpleTracker) { ASSERT_LE(end, SimpleVMATracker::num_pages); SimpleVMATracker::Info endi = tr->pages[end]; - VMATree::VMATreap& treap = this->treap(tree); - VMATree::TreapNode* startn = find(treap, start * page_size); + VMATree::VMARBTree& rbtree = this->rbtree(tree); + VMATree::TNode* startn = find(rbtree, start * page_size); ASSERT_NE(nullptr, startn); - VMATree::TreapNode* endn = find(treap, (end * page_size) + page_size); + VMATree::TNode* endn = find(rbtree, (end * page_size) + page_size); ASSERT_NE(nullptr, endn); const NativeCallStack& start_stack = ncss.get(startn->val().out.reserved_stack()); diff --git a/test/hotspot/gtest/utilities/test_rbtree.cpp b/test/hotspot/gtest/utilities/test_rbtree.cpp index c47e6cb2d58..e1be83bfd58 100644 --- a/test/hotspot/gtest/utilities/test_rbtree.cpp +++ b/test/hotspot/gtest/utilities/test_rbtree.cpp @@ -129,6 +129,7 @@ public: rbtree_const.visit_in_order([&](const RBTreeIntNode* node) { nums_seen.at(node->key())++; + return true; }); for (int i = 0; i < up_to; i++) { EXPECT_EQ(1, nums_seen.at(i)); @@ -210,6 +211,7 @@ public: rbtree_const.visit_range_in_order(0, 100, [&](const Node* x) { EXPECT_TRUE(false) << "Empty rbtree has no nodes to visit"; + return true; }); // Single-element set @@ -217,14 +219,21 @@ public: int count = 0; rbtree_const.visit_range_in_order(0, 100, [&](const Node* x) { count++; + return true; }); EXPECT_EQ(1, count); count = 0; rbtree_const.visit_in_order([&](const Node* x) { count++; + return true; }); EXPECT_EQ(1, count); + rbtree.visit_in_order([&](const Node* x) { + count++; + return true; + }); + EXPECT_EQ(2, count); // Add an element outside of the range that should not be visited on the right side and // one on the left side. @@ -233,21 +242,62 @@ public: count = 0; rbtree_const.visit_range_in_order(0, 100, [&](const Node* x) { count++; + return true; }); EXPECT_EQ(1, count); + rbtree.visit_range_in_order(0, 100, [&](const Node* x) { + count++; + return true; + }); + EXPECT_EQ(2, count); count = 0; rbtree_const.visit_in_order([&](const Node* x) { count++; + return true; }); EXPECT_EQ(3, count); + rbtree.visit_in_order([&](const Node* x) { + count++; + return true; + }); + EXPECT_EQ(6, count); count = 0; rbtree.upsert(0, 0); rbtree_const.visit_range_in_order(0, 0, [&](const Node* x) { count++; + return true; }); EXPECT_EQ(1, count); + rbtree.visit_range_in_order(0, 0, [&](const Node* x) { + count++; + return true; + }); + EXPECT_EQ(2, count); + + // Test exiting visit early + rbtree.remove_all(); + for (int i = 0; i < 11; i++) { + rbtree.upsert(i, 0); + } + + count = 0; + rbtree_const.visit_in_order([&](const Node* x) { + if (x->key() >= 6) return false; + count++; + return true; + }); + EXPECT_EQ(6, count); + + count = 0; + rbtree_const.visit_range_in_order(6, 10, [&](const Node* x) { + if (x->key() >= 6) return false; + count++; + return true; + }); + + EXPECT_EQ(0, count); rbtree.remove_all(); for (int i = 0; i < 11; i++) { @@ -258,6 +308,7 @@ public: GrowableArray seen; rbtree_const.visit_range_in_order(0, 9, [&](const Node* x) { seen.push(x->key()); + return true; }); EXPECT_EQ(10, seen.length()); for (int i = 0; i < 10; i++) { @@ -267,6 +318,7 @@ public: seen.clear(); rbtree_const.visit_in_order([&](const Node* x) { seen.push(x->key()); + return true; }); EXPECT_EQ(11, seen.length()); for (int i = 0; i < 10; i++) { @@ -276,6 +328,7 @@ public: seen.clear(); rbtree_const.visit_range_in_order(10, 12, [&](const Node* x) { seen.push(x->key()); + return true; }); EXPECT_EQ(1, seen.length()); EXPECT_EQ(10, seen.at(0)); @@ -292,6 +345,7 @@ public: GrowableArray seen; rbtree_const.visit_range_in_order(9, -1, [&](const Node* x) { seen.push(x->key()); + return true; }); EXPECT_EQ(10, seen.length()); for (int i = 0; i < 10; i++) { @@ -301,6 +355,7 @@ public: rbtree_const.visit_in_order([&](const Node* x) { seen.push(x->key()); + return true; }); EXPECT_EQ(10, seen.length()); for (int i = 0; i < 10; i++) { @@ -320,9 +375,12 @@ public: {4, 4}, {6, 6}, {6, 7}, {7, 7}}; for (const int (&test_case)[2] : test_cases) { - rbtree.visit_range_in_order(test_case[0], test_case[1], [&](const Node* x) { - FAIL() << "Range should not visit nodes"; + bool visited = false; + rbtree.visit_range_in_order(test_case[0], test_case[1], [&](const Node* x) -> bool { + visited = true; + return true; }); + EXPECT_FALSE(visited); } } @@ -515,6 +573,7 @@ public: // After deleting, values should have remained consistant rbtree.visit_in_order([&](const Node* node) { EXPECT_EQ(node, node->val()); + return true; }); } From 23985c29b44b489472dcd3aad2cb98d9ce003a7b Mon Sep 17 00:00:00 2001 From: Magnus Ihse Bursie Date: Mon, 11 Aug 2025 14:12:55 +0000 Subject: [PATCH 084/580] 8357979: Compile jdk.internal.vm.ci targeting the Boot JDK version Reviewed-by: erikj, dnsimon --- make/CompileJavaModules.gmk | 1 + make/autoconf/boot-jdk.m4 | 6 ++---- make/autoconf/spec.gmk.template | 5 ++--- make/common/JavaCompilation.gmk | 11 ++++++++--- make/modules/jdk.internal.vm.ci/Java.gmk | 2 ++ 5 files changed, 15 insertions(+), 10 deletions(-) diff --git a/make/CompileJavaModules.gmk b/make/CompileJavaModules.gmk index c6e8fab3038..54d063a7a71 100644 --- a/make/CompileJavaModules.gmk +++ b/make/CompileJavaModules.gmk @@ -115,6 +115,7 @@ $(eval $(call SetupJavaCompilation, $(MODULE), \ EXCLUDE_FILES := $(EXCLUDE_FILES), \ EXCLUDE_PATTERNS := -files, \ KEEP_ALL_TRANSLATIONS := $(KEEP_ALL_TRANSLATIONS), \ + TARGET_RELEASE := $(TARGET_RELEASE), \ JAVAC_FLAGS := \ $(DOCLINT) \ $(JAVAC_FLAGS) \ diff --git a/make/autoconf/boot-jdk.m4 b/make/autoconf/boot-jdk.m4 index feb16c7d179..4ba1f9f2089 100644 --- a/make/autoconf/boot-jdk.m4 +++ b/make/autoconf/boot-jdk.m4 @@ -395,11 +395,9 @@ AC_DEFUN_ONCE([BOOTJDK_SETUP_BOOT_JDK], # When compiling code to be executed by the Boot JDK, force compatibility with the # oldest supported bootjdk. - OLDEST_BOOT_JDK=`$ECHO $DEFAULT_ACCEPTABLE_BOOT_VERSIONS \ + OLDEST_BOOT_JDK_VERSION=`$ECHO $DEFAULT_ACCEPTABLE_BOOT_VERSIONS \ | $TR " " "\n" | $SORT -n | $HEAD -n1` - # -Xlint:-options is added to avoid "warning: [options] system modules path not set in conjunction with -source" - BOOT_JDK_SOURCETARGET="-source $OLDEST_BOOT_JDK -target $OLDEST_BOOT_JDK -Xlint:-options" - AC_SUBST(BOOT_JDK_SOURCETARGET) + AC_SUBST(OLDEST_BOOT_JDK_VERSION) # Check if the boot jdk is 32 or 64 bit if $JAVA -version 2>&1 | $GREP -q "64-Bit"; then diff --git a/make/autoconf/spec.gmk.template b/make/autoconf/spec.gmk.template index e720916d88a..c96b730c6fa 100644 --- a/make/autoconf/spec.gmk.template +++ b/make/autoconf/spec.gmk.template @@ -393,9 +393,8 @@ EXTERNAL_BUILDJDK := @EXTERNAL_BUILDJDK@ # Whether the boot jdk jar supports --date=TIMESTAMP BOOT_JDK_JAR_SUPPORTS_DATE := @BOOT_JDK_JAR_SUPPORTS_DATE@ -# When compiling Java source to be run by the boot jdk -# use these extra flags, eg -source 6 -target 6 -BOOT_JDK_SOURCETARGET := @BOOT_JDK_SOURCETARGET@ +# The oldest supported boot jdk version +OLDEST_BOOT_JDK_VERSION := @OLDEST_BOOT_JDK_VERSION@ # Information about the build system NUM_CORES := @NUM_CORES@ diff --git a/make/common/JavaCompilation.gmk b/make/common/JavaCompilation.gmk index 99672d59884..33f5d10535a 100644 --- a/make/common/JavaCompilation.gmk +++ b/make/common/JavaCompilation.gmk @@ -38,10 +38,15 @@ include JarArchive.gmk ### # Create classes that can run on the bootjdk -TARGET_RELEASE_BOOTJDK := $(BOOT_JDK_SOURCETARGET) +# -Xlint:-options is added to avoid the warning +# "system modules path not set in conjunction with -source" +TARGET_RELEASE_BOOTJDK := -source $(OLDEST_BOOT_JDK_VERSION) \ + -target $(OLDEST_BOOT_JDK_VERSION) -Xlint:-options -# Create classes that can be used in (or be a part of) the new jdk we're building -TARGET_RELEASE_NEWJDK := -source $(JDK_SOURCE_TARGET_VERSION) -target $(JDK_SOURCE_TARGET_VERSION) +# Create classes that can be used in (or be a part of) the new jdk we're +# building +TARGET_RELEASE_NEWJDK := -source $(JDK_SOURCE_TARGET_VERSION) \ + -target $(JDK_SOURCE_TARGET_VERSION) # Create classes that can be used in JDK 8, for legacy support TARGET_RELEASE_JDK8 := --release 8 diff --git a/make/modules/jdk.internal.vm.ci/Java.gmk b/make/modules/jdk.internal.vm.ci/Java.gmk index cb569bb8817..75a52a3128d 100644 --- a/make/modules/jdk.internal.vm.ci/Java.gmk +++ b/make/modules/jdk.internal.vm.ci/Java.gmk @@ -33,4 +33,6 @@ DISABLED_WARNINGS_java += dangling-doc-comments this-escape JAVAC_FLAGS += -parameters -XDstringConcat=inline +TARGET_RELEASE := $(TARGET_RELEASE_BOOTJDK) + ################################################################################ From bdb1646a1e39bae0535efe3f593e7fc0545e4114 Mon Sep 17 00:00:00 2001 From: Thomas Stuefe Date: Mon, 11 Aug 2025 15:37:31 +0000 Subject: [PATCH 085/580] 8364611: (process) Child process SIGPIPE signal disposition should be default Reviewed-by: erikj, rriggs --- make/test/JtregNativeJdk.gmk | 3 +- src/java.base/unix/native/libjava/childproc.c | 5 ++ .../TestChildSignalDisposition.java | 70 ++++++++++++++++ .../exePrintSignalDisposition.c | 79 +++++++++++++++++++ .../libChangeSignalDisposition.c | 40 ++++++++++ 5 files changed, 196 insertions(+), 1 deletion(-) create mode 100644 test/jdk/java/lang/ProcessBuilder/childSignalDisposition/TestChildSignalDisposition.java create mode 100644 test/jdk/java/lang/ProcessBuilder/childSignalDisposition/exePrintSignalDisposition.c create mode 100644 test/jdk/java/lang/ProcessBuilder/childSignalDisposition/libChangeSignalDisposition.c diff --git a/make/test/JtregNativeJdk.gmk b/make/test/JtregNativeJdk.gmk index f5970527648..7578d4a50d0 100644 --- a/make/test/JtregNativeJdk.gmk +++ b/make/test/JtregNativeJdk.gmk @@ -62,7 +62,8 @@ BUILD_JDK_JTREG_LIBRARIES_JDK_LIBS_libGetXSpace := java.base:libjava ifeq ($(call isTargetOs, windows), true) BUILD_JDK_JTREG_EXCLUDE += libDirectIO.c libInheritedChannel.c \ libExplicitAttach.c libImplicitAttach.c \ - exelauncher.c libFDLeaker.c exeFDLeakTester.c + exelauncher.c libFDLeaker.c exeFDLeakTester.c \ + libChangeSignalDisposition.c exePrintSignalDisposition.c BUILD_JDK_JTREG_EXECUTABLES_LIBS_exeNullCallerTest := $(LIBCXX) BUILD_JDK_JTREG_EXECUTABLES_LIBS_exerevokeall := advapi32.lib diff --git a/src/java.base/unix/native/libjava/childproc.c b/src/java.base/unix/native/libjava/childproc.c index 93d2200a465..c4b5a2d7b29 100644 --- a/src/java.base/unix/native/libjava/childproc.c +++ b/src/java.base/unix/native/libjava/childproc.c @@ -426,6 +426,11 @@ childProcess(void *arg) sigprocmask(SIG_SETMASK, &unblock_signals, NULL); } + // Children should be started with default signal disposition for SIGPIPE + if (signal(SIGPIPE, SIG_DFL) == SIG_ERR) { + goto WhyCantJohnnyExec; + } + JDK_execvpe(p->mode, p->argv[0], p->argv, p->envv); WhyCantJohnnyExec: diff --git a/test/jdk/java/lang/ProcessBuilder/childSignalDisposition/TestChildSignalDisposition.java b/test/jdk/java/lang/ProcessBuilder/childSignalDisposition/TestChildSignalDisposition.java new file mode 100644 index 00000000000..50fe054ee34 --- /dev/null +++ b/test/jdk/java/lang/ProcessBuilder/childSignalDisposition/TestChildSignalDisposition.java @@ -0,0 +1,70 @@ +/* + * 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 id=posix_spawn + * @bug 8364611 + * @summary Check that childs start with SIG_DFL as SIGPIPE disposition + * @requires os.family != "windows" + * @library /test/lib + * @run main/othervm/native -Djdk.lang.Process.launchMechanism=posix_spawn -agentlib:ChangeSignalDisposition TestChildSignalDisposition + */ + +/** + * @test id=fork + * @bug 8364611 + * @summary Check that childs start with SIG_DFL as SIGPIPE disposition + * @requires os.family != "windows" + * @library /test/lib + * @run main/othervm/native -Djdk.lang.Process.launchMechanism=fork -agentlib:ChangeSignalDisposition TestChildSignalDisposition + */ + +/** + * @test id=vfork + * @bug 8364611 + * @summary Check that childs start with SIG_DFL as SIGPIPE disposition + * @requires os.family == "linux" + * @library /test/lib + * @run main/othervm/native -Djdk.lang.Process.launchMechanism=vfork -agentlib:ChangeSignalDisposition TestChildSignalDisposition + */ + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; +public class TestChildSignalDisposition { + // This test has two native parts: + // - a library injected into the JVM with -agentlib changes signal disposition of the VM process for SIGPIPE to + // SIG_IGN + // - a small native executable that prints out, in its main function, all signal handler dispositions, to be executed + // as a child process. + // + // What should happen: In child process, SIGPIPE should be set to default. + public static void main(String[] args) throws Exception { + ProcessBuilder pb = ProcessTools.createNativeTestProcessBuilder("PrintSignalDisposition"); + OutputAnalyzer output = ProcessTools.executeProcess(pb); + output.shouldHaveExitValue(0); + output.shouldNotMatch("SIGPIPE: +ignore"); + output.shouldNotMatch("SIGPIPE: +block"); + output.shouldMatch("SIGPIPE: +default"); + output.reportDiagnosticSummary(); + } +} diff --git a/test/jdk/java/lang/ProcessBuilder/childSignalDisposition/exePrintSignalDisposition.c b/test/jdk/java/lang/ProcessBuilder/childSignalDisposition/exePrintSignalDisposition.c new file mode 100644 index 00000000000..5a96c2556fc --- /dev/null +++ b/test/jdk/java/lang/ProcessBuilder/childSignalDisposition/exePrintSignalDisposition.c @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include +#include "jvmti.h" +#include +#include +#include + +static const struct { int sig; const char* name; } signals[] = { + { SIGABRT, "SIGABRT" }, { SIGALRM, "SIGALRM" }, { SIGBUS, "SIGBUS" }, { SIGCHLD, "SIGCHLD" }, { SIGCONT, "SIGCONT" }, + { SIGFPE, "SIGFPE" }, { SIGHUP, "SIGHUP" }, { SIGILL, "SIGILL" }, { SIGINT, "SIGINT" }, { SIGKILL, "SIGKILL" }, + { SIGPIPE, "SIGPIPE" }, { SIGQUIT, "SIGQUIT" }, { SIGSEGV, "SIGSEGV" }, { SIGSTOP, "SIGSTOP" }, { SIGTERM, "SIGTERM" }, + { SIGTSTP, "SIGTSTP" }, { SIGTTIN, "SIGTTIN" }, { SIGTTOU, "SIGTTOU" }, { SIGUSR1, "SIGUSR1" }, { SIGUSR2, "SIGUSR2" }, +#ifdef SIGPOLL + { SIGPOLL, "SIGPOLL" }, +#endif + { SIGPROF, "SIGPROF" }, { SIGSYS, "SIGSYS" }, { SIGTRAP, "SIGTRAP" }, { SIGURG, "SIGURG" }, { SIGVTALRM, "SIGVTALRM" }, + { SIGXCPU, "SIGXCPU" }, { SIGXFSZ, "SIGXFSZ" }, { -1, NULL } +}; + +int main(int argc, char** argv) { + + printf("PID: %d\n", getpid()); + + sigset_t current_mask; + sigemptyset(¤t_mask); + if (sigprocmask(SIG_BLOCK /* ignored */, NULL, ¤t_mask) != 0) { + printf("sigprocmask %d\n", errno); + return -1; + } + + for (int n = 0; signals[n].sig != -1; n++) { + printf("%s: ", signals[n].name); + if (sigismember(¤t_mask, signals[n].sig)) { + printf("blocked "); + } + struct sigaction act; + if (sigaction(signals[n].sig, NULL, &act) != 0) { + printf("sigaction %d\n", errno); + printf("\n"); + continue; + } + const void* const handler = (act.sa_flags & SA_SIGINFO ? + (void*)act.sa_sigaction : (void*)act.sa_handler); + if (handler == (void*)SIG_DFL) { + printf("default "); + } else if (handler == (void*)SIG_IGN) { + printf("ignore "); + } else if (handler == (void*)SIG_HOLD) { + printf("hold "); + } else { + printf("%p ", handler); + } + printf("%X %X\n", act.sa_flags, act.sa_mask); + } + + return 0; +} diff --git a/test/jdk/java/lang/ProcessBuilder/childSignalDisposition/libChangeSignalDisposition.c b/test/jdk/java/lang/ProcessBuilder/childSignalDisposition/libChangeSignalDisposition.c new file mode 100644 index 00000000000..83365bb79c6 --- /dev/null +++ b/test/jdk/java/lang/ProcessBuilder/childSignalDisposition/libChangeSignalDisposition.c @@ -0,0 +1,40 @@ +/* + * 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. + */ + +#include +#include "jvmti.h" +#include +#include + +JNIEXPORT jint JNICALL +Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) { + + if (signal(SIGPIPE, SIG_IGN) != SIG_ERR) { + printf("changed signal disposition for SIGPIPE to SIG_IGN\n"); + } else { + printf("FAILED to change signal disposition for SIGPIPE to SIG_IGN (%d)\n", errno); + return JNI_ERR; + } + + return JNI_OK; +} From e9e331b2a957180dac2e9ce19a58d0a57d2f5dae Mon Sep 17 00:00:00 2001 From: Francesco Andreuzzi Date: Mon, 11 Aug 2025 17:10:10 +0000 Subject: [PATCH 086/580] 8365238: 'jfr' feature requires 'services' with 'custom' build variant Reviewed-by: erikj, shade, ihse --- make/autoconf/jvm-features.m4 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/make/autoconf/jvm-features.m4 b/make/autoconf/jvm-features.m4 index 668de019469..234d7b74268 100644 --- a/make/autoconf/jvm-features.m4 +++ b/make/autoconf/jvm-features.m4 @@ -513,6 +513,10 @@ AC_DEFUN([JVM_FEATURES_VERIFY], [ variant=$1 + if JVM_FEATURES_IS_ACTIVE(jfr) && ! JVM_FEATURES_IS_ACTIVE(services); then + AC_MSG_ERROR([Specified JVM feature 'jfr' requires feature 'services' for variant '$variant']) + fi + if JVM_FEATURES_IS_ACTIVE(jvmci) && ! (JVM_FEATURES_IS_ACTIVE(compiler1) || \ JVM_FEATURES_IS_ACTIVE(compiler2)); then AC_MSG_ERROR([Specified JVM feature 'jvmci' requires feature 'compiler2' or 'compiler1' for variant '$variant']) From 958383d69c8742fdb78c28ad856559367c3513d7 Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Mon, 11 Aug 2025 18:49:37 +0000 Subject: [PATCH 087/580] 8364501: Compiler shutdown crashes on access to deleted CompileTask Reviewed-by: kvn, mhaessig --- src/hotspot/share/compiler/compileBroker.cpp | 22 +++++++++++++------- src/hotspot/share/compiler/compileTask.cpp | 1 + src/hotspot/share/compiler/compileTask.hpp | 3 ++- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/hotspot/share/compiler/compileBroker.cpp b/src/hotspot/share/compiler/compileBroker.cpp index 59f41f1c0dc..36663ab1088 100644 --- a/src/hotspot/share/compiler/compileBroker.cpp +++ b/src/hotspot/share/compiler/compileBroker.cpp @@ -371,6 +371,7 @@ void CompileQueue::delete_all() { // Iterate over all tasks in the compile queue while (current != nullptr) { + CompileTask* next = current->next(); if (!current->is_blocking()) { // Non-blocking task. No one is waiting for it, delete it now. delete current; @@ -379,7 +380,7 @@ void CompileQueue::delete_all() { // to delete the task. We cannot delete it here, because we do not // coordinate with waiters. We will notify the waiters later. } - current = current->next(); + current = next; } _first = nullptr; _last = nullptr; @@ -504,6 +505,8 @@ void CompileQueue::remove(CompileTask* task) { assert(task == _last, "Sanity"); _last = task->prev(); } + task->set_next(nullptr); + task->set_prev(nullptr); --_size; ++_total_removed; } @@ -1728,17 +1731,22 @@ void CompileBroker::wait_for_completion(CompileTask* task) { // It is harmless to check this status without the lock, because // completion is a stable property. - if (!task->is_complete() && is_compilation_disabled_forever()) { - // Task is not complete, and we are exiting for compilation shutdown. - // The task can still be executed by some compiler thread, therefore - // we cannot delete it. This will leave task allocated, which leaks it. - // At this (degraded) point, it is less risky to abandon the task, - // rather than attempting a more complicated deletion protocol. + if (!task->is_complete()) { + // Task is not complete, likely because we are exiting for compilation + // shutdown. The task can still be reached through the queue, or executed + // by some compiler thread. There is no coordination with either MCQ lock + // holders or compilers, therefore we cannot delete the task. + // + // This will leave task allocated, which leaks it. At this (degraded) point, + // it is less risky to abandon the task, rather than attempting a more + // complicated deletion protocol. free_task = false; } if (free_task) { assert(task->is_complete(), "Compilation should have completed"); + assert(task->next() == nullptr && task->prev() == nullptr, + "Completed task should not be in the queue"); // By convention, the waiter is responsible for deleting a // blocking CompileTask. Since there is only one waiter ever diff --git a/src/hotspot/share/compiler/compileTask.cpp b/src/hotspot/share/compiler/compileTask.cpp index c5f1c789039..e3b640372a3 100644 --- a/src/hotspot/share/compiler/compileTask.cpp +++ b/src/hotspot/share/compiler/compileTask.cpp @@ -74,6 +74,7 @@ CompileTask::CompileTask(int compile_id, _arena_bytes = 0; _next = nullptr; + _prev = nullptr; Atomic::add(&_active_tasks, 1, memory_order_relaxed); } diff --git a/src/hotspot/share/compiler/compileTask.hpp b/src/hotspot/share/compiler/compileTask.hpp index 148bdd28009..2cc5e9afe3c 100644 --- a/src/hotspot/share/compiler/compileTask.hpp +++ b/src/hotspot/share/compiler/compileTask.hpp @@ -101,7 +101,8 @@ class CompileTask : public CHeapObj { #endif int _comp_level; int _num_inlined_bytecodes; - CompileTask* _next, *_prev; + CompileTask* _next; + CompileTask* _prev; // Fields used for logging why the compilation was initiated: jlong _time_queued; // time when task was enqueued jlong _time_started; // time when compilation started From 8cd79752c6426780c6772eafe296aa5b713b2b64 Mon Sep 17 00:00:00 2001 From: Brian Burkhalter Date: Mon, 11 Aug 2025 18:50:39 +0000 Subject: [PATCH 088/580] 8364761: (aio) AsynchronousChannelGroup.execute doesn't check null command Reviewed-by: alanb, vyazici --- .../nio/ch/AsynchronousChannelGroupImpl.java | 4 +- .../AsynchronousChannelGroup/AsExecutor.java | 80 ++++++++++++------- 2 files changed, 56 insertions(+), 28 deletions(-) diff --git a/src/java.base/share/classes/sun/nio/ch/AsynchronousChannelGroupImpl.java b/src/java.base/share/classes/sun/nio/ch/AsynchronousChannelGroupImpl.java index d69869975a5..3472b18322d 100644 --- a/src/java.base/share/classes/sun/nio/ch/AsynchronousChannelGroupImpl.java +++ b/src/java.base/share/classes/sun/nio/ch/AsynchronousChannelGroupImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -30,6 +30,7 @@ import java.nio.channels.AsynchronousChannelGroup; import java.nio.channels.spi.AsynchronousChannelProvider; import java.io.IOException; import java.io.FileDescriptor; +import java.util.Objects; import java.util.Queue; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; @@ -299,6 +300,7 @@ abstract class AsynchronousChannelGroupImpl */ @Override public final void execute(Runnable task) { + Objects.requireNonNull(task, "task"); executeOnPooledThread(task); } } diff --git a/test/jdk/java/nio/channels/AsynchronousChannelGroup/AsExecutor.java b/test/jdk/java/nio/channels/AsynchronousChannelGroup/AsExecutor.java index 02a8cbf3deb..a1564e4ff72 100644 --- a/test/jdk/java/nio/channels/AsynchronousChannelGroup/AsExecutor.java +++ b/test/jdk/java/nio/channels/AsynchronousChannelGroup/AsExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,49 +23,75 @@ /* * @test - * @bug 4607272 + * @bug 4607272 8364761 * @summary tests tasks can be submitted to a channel group's thread pool. - * @run main AsExecutor + * @run junit AsExecutor */ +import java.io.IOException; import java.nio.channels.AsynchronousChannelGroup; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; +import java.util.stream.Stream; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.assertThrows; public class AsExecutor { + private static ThreadFactory factory; - public static void main(String[] args) throws Exception { - // create channel groups - ThreadFactory factory = Executors.defaultThreadFactory(); - AsynchronousChannelGroup group1 = AsynchronousChannelGroup - .withFixedThreadPool(5, factory); - AsynchronousChannelGroup group2 = AsynchronousChannelGroup - .withCachedThreadPool(Executors.newCachedThreadPool(factory), 0); - AsynchronousChannelGroup group3 = AsynchronousChannelGroup - .withThreadPool(Executors.newFixedThreadPool(10, factory)); + @BeforeAll + public static void createThreadFactory() { + factory = Executors.defaultThreadFactory(); + } + private static Stream channelGroups() throws IOException { + List list = new ArrayList(); + list.add(Arguments.of(AsynchronousChannelGroup + .withFixedThreadPool(5, factory))); + list.add(Arguments.of(AsynchronousChannelGroup + .withCachedThreadPool(Executors.newCachedThreadPool(factory), 0))); + list.add(Arguments.of(AsynchronousChannelGroup + .withThreadPool(Executors.newFixedThreadPool(10, factory)))); + return list.stream(); + } + + @ParameterizedTest + @MethodSource("channelGroups") + public void simpleTask(AsynchronousChannelGroup group) + throws InterruptedException + { try { - // execute simple tasks - testSimpleTask(group1); - testSimpleTask(group2); - testSimpleTask(group3); + Executor executor = (Executor)group; + final CountDownLatch latch = new CountDownLatch(1); + executor.execute(new Runnable() { + public void run() { + latch.countDown(); + } + }); + latch.await(); } finally { - group1.shutdown(); - group2.shutdown(); - group3.shutdown(); + group.shutdown(); } } - static void testSimpleTask(AsynchronousChannelGroup group) throws Exception { + @ParameterizedTest + @MethodSource("channelGroups") + public void nullTask(AsynchronousChannelGroup group) { Executor executor = (Executor)group; - final CountDownLatch latch = new CountDownLatch(1); - executor.execute(new Runnable() { - public void run() { - latch.countDown(); - } - }); - latch.await(); + try { + assertThrows(NullPointerException.class, + () -> executor.execute(null)); + } finally { + group.shutdown(); + } } } From 9593730a23f465d26ba7b310d5b0c5d3b4ee4326 Mon Sep 17 00:00:00 2001 From: Joe Darcy Date: Mon, 11 Aug 2025 23:45:24 +0000 Subject: [PATCH 089/580] 8362376: Use @Stable annotation in Java FDLIBM implementation Reviewed-by: liach, rgiulietti --- .../share/classes/java/lang/FdLibm.java | 51 +++++++++++-------- test/jdk/java/lang/StrictMath/ExpTests.java | 8 ++- test/jdk/java/lang/StrictMath/PowTests.java | 19 ++++++- 3 files changed, 53 insertions(+), 25 deletions(-) diff --git a/src/java.base/share/classes/java/lang/FdLibm.java b/src/java.base/share/classes/java/lang/FdLibm.java index 70d728a16db..73e1da46af4 100644 --- a/src/java.base/share/classes/java/lang/FdLibm.java +++ b/src/java.base/share/classes/java/lang/FdLibm.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,6 +25,8 @@ package java.lang; +import jdk.internal.vm.annotation.Stable; + /** * Port of the "Freely Distributable Math Library", version 5.3, from * C to Java. @@ -451,8 +453,10 @@ final class FdLibm { */ private static final double pio4 = 0x1.921fb54442d18p-1, // 7.85398163397448278999e-01 - pio4lo= 0x1.1a62633145c07p-55, // 3.06161699786838301793e-17 - T[] = { + pio4lo= 0x1.1a62633145c07p-55; // 3.06161699786838301793e-17 + @Stable + private static final double[] + T = { 0x1.5555555555563p-2, // 3.33333333333334091986e-01 0x1.111111110fe7ap-3, // 1.33333333333201242699e-01 0x1.ba1ba1bb341fep-5, // 5.39682539762260521377e-02 @@ -546,6 +550,7 @@ final class FdLibm { /* * Table of constants for 2/pi, 396 Hex digits (476 decimal) of 2/pi */ + @Stable private static final int[] two_over_pi = { 0xA2F983, 0x6E4E44, 0x1529FC, 0x2757D1, 0xF534DD, 0xC0DB62, 0x95993C, 0x439041, 0xFE5163, 0xABDEBB, 0xC561B7, 0x246E3A, @@ -560,6 +565,7 @@ final class FdLibm { 0x4D7327, 0x310606, 0x1556CA, 0x73A8C9, 0x60E27B, 0xC08C6B, }; + @Stable private static final int[] npio2_hw = { 0x3FF921FB, 0x400921FB, 0x4012D97C, 0x401921FB, 0x401F6A7A, 0x4022D97C, 0x4025FDBB, 0x402921FB, 0x402C463A, 0x402F6A7A, 0x4031475C, 0x4032D97C, @@ -807,8 +813,10 @@ final class FdLibm { * to produce the hexadecimal values shown. */ + @Stable private static final int init_jk[] = {2, 3, 4, 6}; // initial value for jk + @Stable private static final double PIo2[] = { 0x1.921fb4p0, // 1.57079625129699707031e+00 0x1.4442dp-24, // 7.54978941586159635335e-08 @@ -1232,6 +1240,7 @@ final class FdLibm { static final class Atan { private Atan() {throw new UnsupportedOperationException();} + @Stable private static final double atanhi[] = { 0x1.dac670561bb4fp-2, // atan(0.5)hi 4.63647609000806093515e-01 0x1.921fb54442d18p-1, // atan(1.0)hi 7.85398163397448278999e-01 @@ -1239,6 +1248,7 @@ final class FdLibm { 0x1.921fb54442d18p0, // atan(inf)hi 1.57079632679489655800e+00 }; + @Stable private static final double atanlo[] = { 0x1.a2b7f222f65e2p-56, // atan(0.5)lo 2.26987774529616870924e-17 0x1.1a62633145c07p-55, // atan(1.0)lo 3.06161699786838301793e-17 @@ -1246,6 +1256,7 @@ final class FdLibm { 0x1.1a62633145c07p-54, // atan(inf)lo 6.12323399573676603587e-17 }; + @Stable private static final double aT[] = { 0x1.555555555550dp-2, // 3.33333333333329318027e-01 -0x1.999999998ebc4p-3, // -1.99999999998764832476e-01 @@ -2245,12 +2256,8 @@ final class FdLibm { // Compute ss = s_h + s_l = (x-1)/(x+1) or (x-1.5)/(x+1.5) - final double BP[] = {1.0, - 1.5}; - final double DP_H[] = {0.0, - 0x1.2b80_34p-1}; // 5.84962487220764160156e-01 - final double DP_L[] = {0.0, - 0x1.cfde_b43c_fd006p-27};// 1.35003920212974897128e-08 + final double DP_H = 0x1.2b80_34p-1; // 5.84962487220764160156e-01 + final double DP_L = 0x1.cfde_b43c_fd006p-27; // 1.35003920212974897128e-08 // Poly coefs for (3/2)*(log(x)-2s-2/3*s**3 final double L1 = 0x1.3333_3333_33303p-1; // 5.99999999999994648725e-01 @@ -2259,15 +2266,17 @@ final class FdLibm { final double L4 = 0x1.1746_0a91_d4101p-2; // 2.72728123808534006489e-01 final double L5 = 0x1.d864_a93c_9db65p-3; // 2.30660745775561754067e-01 final double L6 = 0x1.a7e2_84a4_54eefp-3; // 2.06975017800338417784e-01 - u = x_abs - BP[k]; // BP[0]=1.0, BP[1]=1.5 - v = 1.0 / (x_abs + BP[k]); + + double BP_k = 1.0 + 0.5*k; // BP[0]=1.0, BP[1]=1.5 + u = x_abs - BP_k; + v = 1.0 / (x_abs + BP_k); ss = u * v; s_h = ss; s_h = __LO(s_h, 0); // t_h=x_abs + BP[k] High t_h = 0.0; t_h = __HI(t_h, ((ix >> 1) | 0x20000000) + 0x00080000 + (k << 18) ); - t_l = x_abs - (t_h - BP[k]); + t_l = x_abs - (t_h - BP_k); s_l = v * ((u - s_h * t_h) - s_h * t_l); // Compute log(x_abs) s2 = ss * ss; @@ -2285,12 +2294,12 @@ final class FdLibm { p_h = __LO(p_h, 0); p_l = v - (p_h - u); z_h = CP_H * p_h; // CP_H + CP_L = 2/(3*log2) - z_l = CP_L * p_h + p_l * CP + DP_L[k]; + z_l = CP_L * p_h + p_l * CP + DP_L*k; // log2(x_abs) = (ss + ..)*2/(3*log2) = n + DP_H + z_h + z_l t = (double)n; - t1 = (((z_h + z_l) + DP_H[k]) + t); + t1 = (((z_h + z_l) + DP_H*k) + t); t1 = __LO(t1, 0); - t2 = z_l - (((t1 - t) - DP_H[k]) - z_h); + t2 = z_l - (((t1 - t) - DP_H*k) - z_h); } // Split up y into (y1 + y2) and compute (y1 + y2) * (t1 + t2) @@ -2430,13 +2439,13 @@ final class FdLibm { static final class Exp { private Exp() {throw new UnsupportedOperationException();} - private static final double[] half = {0.5, -0.5,}; private static final double huge = 1.0e+300; private static final double twom1000= 0x1.0p-1000; // 9.33263618503218878990e-302 = 2^-1000 private static final double o_threshold= 0x1.62e42fefa39efp9; // 7.09782712893383973096e+02 private static final double u_threshold= -0x1.74910d52d3051p9; // -7.45133219101941108420e+02; - private static final double[] ln2HI ={ 0x1.62e42feep-1, // 6.93147180369123816490e-01 - -0x1.62e42feep-1}; // -6.93147180369123816490e-01 + private static final double ln2HI = 0x1.62e42feep-1; // 6.93147180369123816490e-01 + + @Stable private static final double[] ln2LO ={ 0x1.a39ef35793c76p-33, // 1.90821492927058770002e-10 -0x1.a39ef35793c76p-33}; // -1.90821492927058770002e-10 private static final double invln2 = 0x1.71547652b82fep0; // 1.44269504088896338700e+00 @@ -2478,13 +2487,13 @@ final class FdLibm { /* argument reduction */ if (hx > 0x3fd62e42) { /* if |x| > 0.5 ln2 */ if(hx < 0x3FF0A2B2) { /* and |x| < 1.5 ln2 */ - hi = x - ln2HI[xsb]; + hi = x - ln2HI*(1 - 2*xsb); /* +/- ln2HI */ lo=ln2LO[xsb]; k = 1 - xsb - xsb; } else { - k = (int)(invln2 * x + half[xsb]); + k = (int)(invln2 * x + 0.5 * (1 - 2*xsb) /* +/- 0.5 */ ); t = k; - hi = x - t*ln2HI[0]; /* t*ln2HI is exact here */ + hi = x - t*ln2HI; /* t*ln2HI is exact here */ lo = t*ln2LO[0]; } x = hi - lo; diff --git a/test/jdk/java/lang/StrictMath/ExpTests.java b/test/jdk/java/lang/StrictMath/ExpTests.java index 9f7d2d1ffd0..c36314a48fa 100644 --- a/test/jdk/java/lang/StrictMath/ExpTests.java +++ b/test/jdk/java/lang/StrictMath/ExpTests.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 @@ -23,7 +23,7 @@ /* * @test - * @bug 8139688 + * @bug 8139688 8362376 * @key randomness * @library /test/lib * @build jdk.test.lib.RandomFactory @@ -87,6 +87,10 @@ public class ExpTests { {-0x1.49f33ad2c1c58p+9, 0x1.f3ccc815431b6p-953}, {+0x1.fce66609f7428p+5, 0x1.b59724cb0bc4cp91}, {-0x1.49f33ad2c1c58p+9, 0x1.f3ccc815431b6p-953}, + + // Test values near 0.5*ln*(2) and 1.5*ln(2) to check refactoring + {0.34657359027997275, 1.4142135623730951}, + {1.0397207708399183, 2.828427124746191}, }; for(double[] testCase: testCases) diff --git a/test/jdk/java/lang/StrictMath/PowTests.java b/test/jdk/java/lang/StrictMath/PowTests.java index 2936dfdf247..4b68c5bdcd6 100644 --- a/test/jdk/java/lang/StrictMath/PowTests.java +++ b/test/jdk/java/lang/StrictMath/PowTests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2022, 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 @@ -23,7 +23,7 @@ /* * @test - * @bug 8136874 + * @bug 8136874 8362376 * @summary Tests for StrictMath.pow */ @@ -283,6 +283,21 @@ public class PowTests { 0x1.ffffffffffd9fp1023, // 1.7976931348621944E308 }, + // Check refactoring, abs(y) < 2^31, x < sqrt(3/2), x < sqrt(3) + {1.2, // x < sqrt(3/2) + 5.0, + 2.4883199999999994 + }, + + {1.4142135623730951, // sqrt(3/2) < x < sqrt(3) + 5.0, + 5.656854249492382 + }, + + {2.23606797749979, // x > sqrt(3) + 5.0, + 55.901699437494756 + }, }; for (double[] testCase: testCases) From 6927fc3904eb239bd43ab7c581d479c00a6a4af2 Mon Sep 17 00:00:00 2001 From: Dingli Zhang Date: Tue, 12 Aug 2025 01:25:35 +0000 Subject: [PATCH 090/580] 8365200: RISC-V: compiler/loopopts/superword/TestGeneralizedReductions.java fails with Zvbb and vlen=128 Reviewed-by: fyang, fjiang --- .../loopopts/superword/TestGeneralizedReductions.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/hotspot/jtreg/compiler/loopopts/superword/TestGeneralizedReductions.java b/test/hotspot/jtreg/compiler/loopopts/superword/TestGeneralizedReductions.java index bda0979a70b..6c2f0d825b9 100644 --- a/test/hotspot/jtreg/compiler/loopopts/superword/TestGeneralizedReductions.java +++ b/test/hotspot/jtreg/compiler/loopopts/superword/TestGeneralizedReductions.java @@ -160,13 +160,13 @@ public class TestGeneralizedReductions { @Test @IR(applyIfCPUFeatureOr = {"avx2", "true"}, - applyIfAnd = {"SuperWordReductions", "true","UsePopCountInstruction", "true"}, + applyIfAnd = {"SuperWordReductions", "true", "UsePopCountInstruction", "true"}, applyIfPlatform = {"64-bit", "true"}, counts = {IRNode.ADD_REDUCTION_VI, ">= 1", IRNode.POPCOUNT_VL, ">= 1"}) @IR(applyIfPlatform = {"riscv64", "true"}, applyIfCPUFeatureOr = {"zvbb", "true"}, - applyIfAnd = {"SuperWordReductions", "true","UsePopCountInstruction", "true"}, + applyIfAnd = {"SuperWordReductions", "true", "UsePopCountInstruction", "true", "MaxVectorSize", ">=32"}, counts = {IRNode.ADD_REDUCTION_VI, ">= 1", IRNode.POPCOUNT_VL, ">= 1"}) private static long testMapReductionOnGlobalAccumulator(long[] array) { From 72d3a2a9773b2a3fe0351e0acb7b10c0751d23d8 Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Tue, 12 Aug 2025 03:15:49 +0000 Subject: [PATCH 091/580] 8308349: missing working directory option for launcher when invoked from shortcuts Reviewed-by: almatvee --- .../jpackage/internal/DesktopIntegration.java | 18 ++ .../jpackage/internal/LinuxFromParams.java | 8 +- .../internal/resources/template.desktop | 1 + .../jdk/jpackage/internal/Arguments.java | 41 ++- .../jdk/jpackage/internal/FromParams.java | 30 ++- .../LauncherShortcutStartupDirectory.java | 9 +- .../jpackage/internal/model/ParseUtils.java | 59 +++++ .../resources/MainResources.properties | 2 + .../jdk/jpackage/internal/WinFromParams.java | 4 +- .../internal/WixAppImageFragmentBuilder.java | 3 + .../jdk/jpackage/test/AdditionalLauncher.java | 4 + .../jdk/jpackage/test/LauncherShortcut.java | 12 +- .../jdk/jpackage/test/LinuxHelper.java | 3 + .../jpackage/test/WinShortcutVerifier.java | 12 +- .../jpackage/share/AddLShortcutTest.java | 242 ++++++++++++------ test/jdk/tools/jpackage/share/ErrorTest.java | 12 + 16 files changed, 352 insertions(+), 108 deletions(-) create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/ParseUtils.java diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/DesktopIntegration.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/DesktopIntegration.java index 476ca3201ce..dad9917c48f 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/DesktopIntegration.java +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/DesktopIntegration.java @@ -46,6 +46,7 @@ import javax.imageio.ImageIO; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; import jdk.jpackage.internal.model.FileAssociation; +import jdk.jpackage.internal.model.LauncherShortcut; import jdk.jpackage.internal.model.LinuxLauncher; import jdk.jpackage.internal.model.LinuxPackage; import jdk.jpackage.internal.model.Package; @@ -237,6 +238,23 @@ final class DesktopIntegration extends ShellCustomAction { data.put("DEPLOY_BUNDLE_CATEGORY", pkg.menuGroupName()); data.put("APPLICATION_LAUNCHER", Enquoter.forPropertyValues().applyTo( installedLayout.launchersDirectory().resolve(launcher.executableNameWithSuffix()).toString())); + data.put("STARTUP_DIRECTORY", launcher.shortcut() + .flatMap(LauncherShortcut::startupDirectory) + .map(startupDirectory -> { + switch (startupDirectory) { + case DEFAULT -> { + return (Path)null; + } + case APP_DIR -> { + return installedLayout.appDirectory(); + } + default -> { + throw new AssertionError(); + } + } + }).map(str -> { + return "Path=" + str; + }).orElse(null)); return data; } diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxFromParams.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxFromParams.java index 6967dea111e..ced77b1aa68 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxFromParams.java +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxFromParams.java @@ -109,12 +109,8 @@ final class LinuxFromParams { static final BundlerParamInfo DEB_PACKAGE = createPackageBundlerParam( LinuxFromParams::createLinuxDebPackage); - private static final BundlerParamInfo LINUX_SHORTCUT_HINT = new BundlerParamInfo<>( - Arguments.CLIOptions.LINUX_SHORTCUT_HINT.getId(), - Boolean.class, - params -> false, - (s, p) -> (s == null || "null".equalsIgnoreCase(s)) ? false : Boolean.valueOf(s) - ); + private static final BundlerParamInfo LINUX_SHORTCUT_HINT = createStringBundlerParam( + Arguments.CLIOptions.LINUX_SHORTCUT_HINT.getId()); private static final BundlerParamInfo LINUX_CATEGORY = createStringBundlerParam( Arguments.CLIOptions.LINUX_CATEGORY.getId()); diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.desktop b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.desktop index bd645b77669..de7df568454 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.desktop +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.desktop @@ -2,6 +2,7 @@ Name=APPLICATION_NAME Comment=APPLICATION_DESCRIPTION Exec=APPLICATION_LAUNCHER +STARTUP_DIRECTORY Icon=APPLICATION_ICON Terminal=false Type=Application diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Arguments.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Arguments.java index 4700231a162..f9a5429a8bf 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Arguments.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Arguments.java @@ -37,6 +37,7 @@ import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Properties; import java.util.ResourceBundle; @@ -348,16 +349,13 @@ public class Arguments { WIN_UPDATE_URL ("win-update-url", OptionCategories.PLATFORM_WIN), - WIN_MENU_HINT ("win-menu", OptionCategories.PLATFORM_WIN, () -> { - setOptionValue("win-menu", true); - }), + WIN_MENU_HINT ("win-menu", OptionCategories.PLATFORM_WIN, + createArgumentWithOptionalValueAction("win-menu")), WIN_MENU_GROUP ("win-menu-group", OptionCategories.PLATFORM_WIN), - WIN_SHORTCUT_HINT ("win-shortcut", - OptionCategories.PLATFORM_WIN, () -> { - setOptionValue("win-shortcut", true); - }), + WIN_SHORTCUT_HINT ("win-shortcut", OptionCategories.PLATFORM_WIN, + createArgumentWithOptionalValueAction("win-shortcut")), WIN_SHORTCUT_PROMPT ("win-shortcut-prompt", OptionCategories.PLATFORM_WIN, () -> { @@ -396,10 +394,8 @@ public class Arguments { LINUX_PACKAGE_DEPENDENCIES ("linux-package-deps", OptionCategories.PLATFORM_LINUX), - LINUX_SHORTCUT_HINT ("linux-shortcut", - OptionCategories.PLATFORM_LINUX, () -> { - setOptionValue("linux-shortcut", true); - }), + LINUX_SHORTCUT_HINT ("linux-shortcut", OptionCategories.PLATFORM_LINUX, + createArgumentWithOptionalValueAction("linux-shortcut")), LINUX_MENU_GROUP ("linux-menu-group", OptionCategories.PLATFORM_LINUX); @@ -478,9 +474,32 @@ public class Arguments { context().pos++; } + private static void prevArg() { + Objects.checkIndex(context().pos, context().argList.size()); + context().pos--; + } + private static boolean hasNextArg() { return context().pos < context().argList.size(); } + + private static Runnable createArgumentWithOptionalValueAction(String option) { + Objects.requireNonNull(option); + return () -> { + nextArg(); + if (hasNextArg()) { + var value = getArg(); + if (value.startsWith("-")) { + prevArg(); + setOptionValue(option, true); + } else { + setOptionValue(option, value); + } + } else { + setOptionValue(option, true); + } + }; + } } enum OptionCategories { diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromParams.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromParams.java index 34818fafc94..d4ea595969a 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromParams.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromParams.java @@ -52,6 +52,7 @@ import static jdk.jpackage.internal.StandardBundlerParam.VENDOR; import static jdk.jpackage.internal.StandardBundlerParam.VERSION; import static jdk.jpackage.internal.StandardBundlerParam.hasPredefinedAppImage; import static jdk.jpackage.internal.StandardBundlerParam.isRuntimeInstaller; +import static jdk.jpackage.internal.util.function.ThrowingFunction.toFunction; import java.io.IOException; import java.nio.file.Path; @@ -69,6 +70,7 @@ import jdk.jpackage.internal.model.Launcher; import jdk.jpackage.internal.model.LauncherShortcut; import jdk.jpackage.internal.model.LauncherShortcutStartupDirectory; import jdk.jpackage.internal.model.PackageType; +import jdk.jpackage.internal.model.ParseUtils; import jdk.jpackage.internal.model.RuntimeLayout; import jdk.jpackage.internal.util.function.ThrowingFunction; @@ -171,11 +173,11 @@ final class FromParams { } static Optional findLauncherShortcut( - BundlerParamInfo shortcutParam, + BundlerParamInfo shortcutParam, Map mainParams, Map launcherParams) { - Optional launcherValue; + Optional launcherValue; if (launcherParams == mainParams) { // The main launcher launcherValue = Optional.empty(); @@ -183,17 +185,19 @@ final class FromParams { launcherValue = shortcutParam.findIn(launcherParams); } - return launcherValue.map(withShortcut -> { - if (withShortcut) { - return Optional.of(LauncherShortcutStartupDirectory.DEFAULT); - } else { - return Optional.empty(); - } - }).or(() -> { - return shortcutParam.findIn(mainParams).map(_ -> { - return Optional.of(LauncherShortcutStartupDirectory.DEFAULT); - }); - }).map(LauncherShortcut::new); + return launcherValue.map(ParseUtils::parseLauncherShortcutForAddLauncher).or(() -> { + return Optional.ofNullable(mainParams.get(shortcutParam.getID())).map(toFunction(value -> { + if (value instanceof Boolean) { + return new LauncherShortcut(LauncherShortcutStartupDirectory.DEFAULT); + } else { + try { + return ParseUtils.parseLauncherShortcutForMainLauncher((String)value); + } catch (IllegalArgumentException ex) { + throw I18N.buildConfigException("error.invalid-option-value", value, "--" + shortcutParam.getID()).create(); + } + } + })); + }); } private static ApplicationLaunchers createLaunchers( diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/LauncherShortcutStartupDirectory.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/LauncherShortcutStartupDirectory.java index c604b00c3e2..c6ceb1af40d 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/LauncherShortcutStartupDirectory.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/LauncherShortcutStartupDirectory.java @@ -41,7 +41,14 @@ public enum LauncherShortcutStartupDirectory { * On Linux, it indicates that a shortcut doesn't have the startup directory * configured explicitly. */ - DEFAULT("true"); + DEFAULT("true"), + + /** + * The 'app' directory in the installed application app image. This is the + * directory that is referenced with {@link ApplicationLayout#appDirectory()} + * method. + */ + APP_DIR("app-dir"); LauncherShortcutStartupDirectory(String stringValue) { this.stringValue = Objects.requireNonNull(stringValue); diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/ParseUtils.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/ParseUtils.java new file mode 100644 index 00000000000..411e487d9e0 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/ParseUtils.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.jpackage.internal.model; + +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Stream; + +/** + * Collection of functions to create instances of types defined in this package from strings. + */ +public final class ParseUtils { + + private ParseUtils() { + } + + public static LauncherShortcut parseLauncherShortcutForMainLauncher(String str) { + return parse(str, LauncherShortcutStartupDirectory.APP_DIR).map(LauncherShortcut::new).orElseThrow(IllegalArgumentException::new); + } + + public static LauncherShortcut parseLauncherShortcutForAddLauncher(String str) { + return parse(str, LauncherShortcutStartupDirectory.values()).map(LauncherShortcut::new).orElseGet(() -> { + if (Boolean.valueOf(str)) { + return new LauncherShortcut(LauncherShortcutStartupDirectory.DEFAULT); + } else { + return new LauncherShortcut(); + } + }); + } + + private static Optional parse(String str, LauncherShortcutStartupDirectory... recognizedValues) { + Objects.requireNonNull(str); + return Stream.of(recognizedValues).filter(v -> { + return str.equals(v.asStringValue()); + }).findFirst(); + } +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/MainResources.properties b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/MainResources.properties index ae225e15ea2..684a97bc1bd 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/MainResources.properties +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/MainResources.properties @@ -82,6 +82,8 @@ error.invalid-app-image=Error: app-image dir "{0}" generated by another jpackage error.invalid-install-dir=Invalid installation directory "{0}" +error.invalid-option-value=Invalid value "{0}" of option {1} + MSG_BundlerFailed=Error: Bundler "{1}" ({0}) failed to produce a package MSG_BundlerConfigException=Bundler {0} skipped because of a configuration problem: {1} \n\ Advice to fix: {2} diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinFromParams.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinFromParams.java index 15d8d2f83b0..e2259535058 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinFromParams.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinFromParams.java @@ -105,10 +105,10 @@ final class WinFromParams { static final BundlerParamInfo MSI_PACKAGE = createPackageBundlerParam( WinFromParams::createWinMsiPackage); - private static final BundlerParamInfo WIN_MENU_HINT = createBooleanBundlerParam( + private static final BundlerParamInfo WIN_MENU_HINT = createStringBundlerParam( Arguments.CLIOptions.WIN_MENU_HINT.getId()); - private static final BundlerParamInfo WIN_SHORTCUT_HINT = createBooleanBundlerParam( + private static final BundlerParamInfo WIN_SHORTCUT_HINT = createStringBundlerParam( Arguments.CLIOptions.WIN_SHORTCUT_HINT.getId()); public static final BundlerParamInfo CONSOLE_HINT = createBooleanBundlerParam( diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixAppImageFragmentBuilder.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixAppImageFragmentBuilder.java index ea4d9eee19a..63be18a5ee8 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixAppImageFragmentBuilder.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixAppImageFragmentBuilder.java @@ -474,6 +474,9 @@ final class WixAppImageFragmentBuilder extends WixFragmentBuilder { case DEFAULT -> { return INSTALLDIR; } + case APP_DIR -> { + return installedAppImage.appDirectory(); + } default -> { throw new AssertionError(); } diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/AdditionalLauncher.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/AdditionalLauncher.java index 07c8e06856f..50222d89ceb 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/AdditionalLauncher.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/AdditionalLauncher.java @@ -54,6 +54,10 @@ public final class AdditionalLauncher { setPersistenceHandler(null); } + public String name() { + return name; + } + public AdditionalLauncher withVerifyActions(Action... actions) { verifyActions.addAll(List.of(actions)); return this; diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LauncherShortcut.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LauncherShortcut.java index 15bb3ea0333..1ee3b8d47b0 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LauncherShortcut.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LauncherShortcut.java @@ -44,6 +44,7 @@ public enum LauncherShortcut { public enum StartupDirectory { DEFAULT("true"), + APP_DIR("app-dir"), ; StartupDirectory(String stringValue) { @@ -79,7 +80,7 @@ public enum LauncherShortcut { private final String stringValue; - private final static Map VALUE_MAP = + private static final Map VALUE_MAP = Stream.of(values()).collect(toMap(StartupDirectory::asStringValue, x -> x)); } @@ -147,7 +148,14 @@ public enum LauncherShortcut { private Optional findMainLauncherShortcut(JPackageCommand cmd) { if (cmd.hasArgument(optionName())) { - return Optional.of(StartupDirectory.DEFAULT); + var value = Optional.ofNullable(cmd.getArgumentValue(optionName())).filter(optionValue -> { + return !optionValue.startsWith("-"); + }); + if (value.isPresent()) { + return value.flatMap(StartupDirectory::parse); + } else { + return Optional.of(StartupDirectory.DEFAULT); + } } else { return Optional.empty(); } diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LinuxHelper.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LinuxHelper.java index e8f6273b18b..caec0e315c4 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LinuxHelper.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LinuxHelper.java @@ -514,6 +514,9 @@ public final class LinuxHelper { case DEFAULT -> { return (Path)null; } + case APP_DIR -> { + return cmd.pathToPackageFile(appLayout.appDirectory()); + } default -> { throw new AssertionError(); } diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WinShortcutVerifier.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WinShortcutVerifier.java index cca904e017e..27e94366e69 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WinShortcutVerifier.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WinShortcutVerifier.java @@ -213,7 +213,17 @@ public final class WinShortcutVerifier { final var installDir = Path.of(installRoot.getMsiPropertyName()).resolve(getInstallationSubDirectory(cmd)); final Function workDir = startupDirectory -> { - return installDir; + switch (startupDirectory) { + case DEFAULT -> { + return installDir; + } + case APP_DIR -> { + return ApplicationLayout.windowsAppImage().resolveAt(installDir).appDirectory(); + } + default -> { + throw new IllegalArgumentException(); + } + } }; if (winMenu.isPresent()) { diff --git a/test/jdk/tools/jpackage/share/AddLShortcutTest.java b/test/jdk/tools/jpackage/share/AddLShortcutTest.java index 7d7d8b50c1d..9c50c6ffc98 100644 --- a/test/jdk/tools/jpackage/share/AddLShortcutTest.java +++ b/test/jdk/tools/jpackage/share/AddLShortcutTest.java @@ -28,16 +28,21 @@ import java.nio.file.Path; import java.time.Duration; import java.util.ArrayList; import java.util.Collection; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.stream.Collectors; import jdk.internal.util.OperatingSystem; import jdk.jpackage.test.AdditionalLauncher; import jdk.jpackage.test.Annotations.Parameter; import jdk.jpackage.test.Annotations.ParameterSupplier; import jdk.jpackage.test.Annotations.Test; import jdk.jpackage.test.FileAssociations; +import jdk.jpackage.test.HelloApp; import jdk.jpackage.test.JPackageCommand; +import jdk.jpackage.test.JavaAppDesc; import jdk.jpackage.test.LauncherShortcut; import jdk.jpackage.test.LauncherShortcut.InvokeShortcutSpec; import jdk.jpackage.test.LauncherShortcut.StartupDirectory; @@ -63,6 +68,7 @@ import jdk.jpackage.test.WinShortcutVerifier; * @key jpackagePlatformPackage * @library /test/jdk/tools/jpackage/helpers * @build jdk.jpackage.test.* + * @requires (os.family != "mac") * @requires (jpackage.test.SQETest != null) * @compile -Xlint:all -Werror AddLShortcutTest.java * @run main/othervm/timeout=540 -Xmx512m @@ -76,6 +82,7 @@ import jdk.jpackage.test.WinShortcutVerifier; * @key jpackagePlatformPackage * @library /test/jdk/tools/jpackage/helpers * @build jdk.jpackage.test.* + * @requires (os.family != "mac") * @requires (jpackage.test.SQETest == null) * @compile -Xlint:all -Werror AddLShortcutTest.java * @run main/othervm/timeout=1080 -Xmx512m @@ -85,7 +92,7 @@ import jdk.jpackage.test.WinShortcutVerifier; public class AddLShortcutTest { - @Test + @Test(ifNotOS = OperatingSystem.MACOS) public void test() { // Configure several additional launchers with each combination of // possible shortcut hints in add-launcher property file. @@ -93,6 +100,8 @@ public class AddLShortcutTest { // will have shortcuts while other launchers with some properties set // to "false" will have none. + final var packageName = MethodHandles.lookup().lookupClass().getSimpleName(); + PackageTest packageTest = new PackageTest().configureHelloApp(); packageTest.addInitializer(cmd -> { cmd.addArguments("--arguments", "Duke", "--arguments", "is", @@ -102,11 +111,14 @@ public class AddLShortcutTest { } else if (TKit.isLinux()) { cmd.addArguments("--linux-shortcut"); } + + cmd.setArgumentValue("--name", packageName); + + var addLauncherApp = TKit.TEST_SRC_ROOT.resolve("apps/PrintEnv.java"); + HelloApp.createBundle(JavaAppDesc.parse(addLauncherApp + "*another.jar:Welcome"), cmd.inputDir()); }); - new FileAssociations( - MethodHandles.lookup().lookupClass().getSimpleName()).applyTo( - packageTest); + new FileAssociations(packageName).applyTo(packageTest); new AdditionalLauncher("Foo") .setDefaultArguments("yep!") @@ -131,11 +143,16 @@ public class AddLShortcutTest { .setShortcuts(true, false) .applyTo(packageTest); - new AdditionalLauncher("Launcher5") - .setDefaultArguments() + var launcher5 = new AdditionalLauncher("Launcher5") + .setDefaultArguments("--print-workdir") .setIcon(GOLDEN_ICON) - .setShortcuts(false, true) - .applyTo(packageTest); + .setShortcut(LauncherShortcut.LINUX_SHORTCUT, StartupDirectory.APP_DIR) + .setShortcut(LauncherShortcut.WIN_DESKTOP_SHORTCUT, StartupDirectory.APP_DIR) + .setShortcut(LauncherShortcut.WIN_START_MENU_SHORTCUT, null) + .setProperty("main-jar", "another.jar") + .setProperty("main-class", "Welcome"); + + new ShortcutStartupDirectoryVerifier(packageName).add(launcher5).applyTo(packageTest); packageTest.run(); } @@ -190,10 +207,50 @@ public class AddLShortcutTest { predefinedAppImage[0] = cmd.outputBundle(); }).addInitializer(cmd -> { + cfgs[0].applyToMainLauncher(cmd); cmd.removeArgumentWithValue("--input"); cmd.setArgumentValue("--name", "AddLShortcutDir2Test"); cmd.addArguments("--app-image", predefinedAppImage[0]); - cfgs[0].applyToMainLauncher(cmd); + }).run(RunnablePackageTest.Action.CREATE_AND_UNPACK); + } + + @Test(ifNotOS = OperatingSystem.MACOS) + @Parameter(value = "DEFAULT") + @Parameter(value = "APP_DIR") + public void testLastArg(StartupDirectory startupDirectory) { + final List shortcutArgs = new ArrayList<>(); + if (TKit.isLinux()) { + shortcutArgs.add("--linux-shortcut"); + } else if (TKit.isWindows()) { + shortcutArgs.add("--win-shortcut"); + } else { + TKit.assertUnexpected("Unsupported platform"); + } + + if (startupDirectory == StartupDirectory.APP_DIR) { + shortcutArgs.add(startupDirectory.asStringValue()); + } + + Path[] predefinedAppImage = new Path[1]; + + new PackageTest().addRunOnceInitializer(() -> { + var cmd = JPackageCommand.helloAppImage() + .setArgumentValue("--name", "foo") + .setFakeRuntime(); + + cmd.execute(); + + predefinedAppImage[0] = cmd.outputBundle(); + }).addInitializer(cmd -> { + cmd.removeArgumentWithValue("--input"); + cmd.setArgumentValue("--name", "AddLShortcutDir3Test"); + cmd.addArguments("--app-image", predefinedAppImage[0]); + cmd.ignoreDefaultVerbose(true); + }).addInitializer(cmd -> { + cmd.addArguments(shortcutArgs); + }).addBundleVerifier(cmd -> { + TKit.assertEquals(shortcutArgs.getLast(), cmd.getAllArguments().getLast(), + "Check the last argument of jpackage command line"); }).run(RunnablePackageTest.Action.CREATE_AND_UNPACK); } @@ -207,6 +264,7 @@ public class AddLShortcutTest { @Test(ifNotOS = OperatingSystem.MACOS) @Parameter(value = "DEFAULT") + @Parameter(value = "APP_DIR") public void testInvokeShortcuts(StartupDirectory startupDirectory) { var testApp = TKit.TEST_SRC_ROOT.resolve("apps/PrintEnv.java"); @@ -219,82 +277,118 @@ public class AddLShortcutTest { cmd.addArguments("--arguments", "--print-workdir"); }).addInitializer(JPackageCommand::ignoreFakeRuntime).addHelloAppInitializer(testApp + "*Hello"); - var shortcutStartupDirectoryVerifier = new ShortcutStartupDirectoryVerifier(name, "a"); - - shortcutStartupDirectoryVerifier.applyTo(test, startupDirectory); - - test.addInstallVerifier(cmd -> { - if (!cmd.isPackageUnpacked("Not invoking launcher shortcuts")) { - Collection invokeShortcutSpecs; - if (TKit.isLinux()) { - invokeShortcutSpecs = LinuxHelper.getInvokeShortcutSpecs(cmd); - } else if (TKit.isWindows()) { - invokeShortcutSpecs = WinShortcutVerifier.getInvokeShortcutSpecs(cmd); - } else { - throw new UnsupportedOperationException(); - } - shortcutStartupDirectoryVerifier.verify(invokeShortcutSpecs); - } - }); + new ShortcutStartupDirectoryVerifier(name).add("a", startupDirectory).applyTo(test); test.run(); } - private record ShortcutStartupDirectoryVerifier(String packageName, String launcherName) { - ShortcutStartupDirectoryVerifier { - Objects.requireNonNull(packageName); - Objects.requireNonNull(launcherName); + private static final class ShortcutStartupDirectoryVerifier { + + ShortcutStartupDirectoryVerifier(String packageName) { + this.packageName = Objects.requireNonNull(packageName); } - void applyTo(PackageTest test, StartupDirectory startupDirectory) { - var al = new AdditionalLauncher(launcherName); - al.setShortcut(shortcut(), Objects.requireNonNull(startupDirectory)); - al.addJavaOptions(String.format("-Djpackage.test.appOutput=${%s}/%s", - outputDirVarName(), expectedOutputFilename())); - al.withoutVerifyActions(Action.EXECUTE_LAUNCHER).applyTo(test); - } + void applyTo(PackageTest test) { + verifiers.values().forEach(verifier -> { + verifier.applyTo(test); + }); + test.addInstallVerifier(cmd -> { + if (!cmd.isPackageUnpacked("Not invoking launcher shortcuts")) { + Collection invokeShortcutSpecs; + if (TKit.isLinux()) { + invokeShortcutSpecs = LinuxHelper.getInvokeShortcutSpecs(cmd); + } else if (TKit.isWindows()) { + invokeShortcutSpecs = WinShortcutVerifier.getInvokeShortcutSpecs(cmd); + } else { + throw new UnsupportedOperationException(); + } - void verify(Collection invokeShortcutSpecs) throws IOException { + var invokeShortcutSpecsMap = invokeShortcutSpecs.stream().collect(Collectors.groupingBy(InvokeShortcutSpec::launcherName)); - TKit.trace(String.format("Verify shortcut [%s]", launcherName)); - - var expectedOutputFile = Path.of(System.getenv(outputDirVarName())).resolve(expectedOutputFilename()); - - TKit.deleteIfExists(expectedOutputFile); - - var invokeShortcutSpec = invokeShortcutSpecs.stream().filter(v -> { - return launcherName.equals(v.launcherName()); - }).findAny().orElseThrow(); - - invokeShortcutSpec.execute(); - - // On Linux, "gtk-launch" is used to launch a .desktop file. It is async and there is no - // way to make it wait for exit of a process it triggers. - TKit.waitForFileCreated(expectedOutputFile, Duration.ofSeconds(10), Duration.ofSeconds(3)); - - TKit.assertFileExists(expectedOutputFile); - var actualStr = Files.readAllLines(expectedOutputFile).getFirst(); - - var outputPrefix = "$CD="; - - TKit.assertTrue(actualStr.startsWith(outputPrefix), "Check output starts with '" + outputPrefix+ "' string"); - - invokeShortcutSpec.expectedWorkDirectory().ifPresent(expectedWorkDirectory -> { - TKit.assertEquals( - expectedWorkDirectory, - Path.of(actualStr.substring(outputPrefix.length())), - String.format("Check work directory of %s of launcher [%s]", - invokeShortcutSpec.shortcut().propertyName(), - invokeShortcutSpec.launcherName())); + for (var e : verifiers.entrySet()) { + e.getValue().verify(invokeShortcutSpecsMap.get(e.getKey())); + } + } }); } - private String expectedOutputFilename() { - return String.format("%s-%s.out", packageName, launcherName); + ShortcutStartupDirectoryVerifier add(String launcherName, StartupDirectory startupDirectory) { + return add(new AdditionalLauncher(launcherName) + .setShortcut(shortcut(), Objects.requireNonNull(Objects.requireNonNull(startupDirectory)))); } - private String outputDirVarName() { + ShortcutStartupDirectoryVerifier add(AdditionalLauncher addLauncher) { + var launcherVerifier = new LauncherVerifier(addLauncher); + verifiers.put(launcherVerifier.launcherName(), launcherVerifier); + return this; + } + + + private final class LauncherVerifier { + + private LauncherVerifier(AdditionalLauncher addLauncher) { + this.addLauncher = Objects.requireNonNull(addLauncher); + } + + private String launcherName() { + return addLauncher.name(); + } + + private void applyTo(PackageTest test) { + addLauncher.addJavaOptions(String.format("-Djpackage.test.appOutput=${%s}/%s", + outputDirVarName(), expectedOutputFilename())); + addLauncher.withoutVerifyActions(Action.EXECUTE_LAUNCHER).applyTo(test); + } + + private void verify(Collection invokeShortcutSpecs) throws IOException { + Objects.requireNonNull(invokeShortcutSpecs); + if (invokeShortcutSpecs.isEmpty()) { + throw new IllegalArgumentException(); + } + + TKit.trace(String.format("Verify shortcut [%s]", launcherName())); + + var expectedOutputFile = Path.of(System.getenv(outputDirVarName())).resolve(expectedOutputFilename()); + + TKit.deleteIfExists(expectedOutputFile); + + var invokeShortcutSpec = invokeShortcutSpecs.stream().filter(v -> { + return launcherName().equals(v.launcherName()); + }).findAny().orElseThrow(); + + invokeShortcutSpec.execute(); + + // On Linux, "gtk-launch" is used to launch a .desktop file. It is async and there is no + // way to make it wait for exit of a process it triggers. + TKit.waitForFileCreated(expectedOutputFile, Duration.ofSeconds(10), Duration.ofSeconds(3)); + + TKit.assertFileExists(expectedOutputFile); + var actualStr = Files.readAllLines(expectedOutputFile).getFirst(); + + var outputPrefix = "$CD="; + + TKit.assertTrue(actualStr.startsWith(outputPrefix), "Check output starts with '" + outputPrefix+ "' string"); + + invokeShortcutSpec.expectedWorkDirectory().ifPresent(expectedWorkDirectory -> { + TKit.assertEquals( + expectedWorkDirectory, + Path.of(actualStr.substring(outputPrefix.length())), + String.format("Check work directory of %s of launcher [%s]", + invokeShortcutSpec.shortcut().propertyName(), + invokeShortcutSpec.launcherName())); + }); + } + + private String expectedOutputFilename() { + return String.format("%s-%s.out", packageName, launcherName()); + } + + private final AdditionalLauncher addLauncher; + } + + + private static String outputDirVarName() { if (TKit.isLinux()) { return "HOME"; } else if (TKit.isWindows()) { @@ -304,7 +398,7 @@ public class AddLShortcutTest { } } - private LauncherShortcut shortcut() { + private static LauncherShortcut shortcut() { if (TKit.isLinux()) { return LauncherShortcut.LINUX_SHORTCUT; } else if (TKit.isWindows()) { @@ -313,6 +407,10 @@ public class AddLShortcutTest { throw new UnsupportedOperationException(); } } + + private final String packageName; + // Keep the order + private final Map verifiers = new LinkedHashMap<>(); } diff --git a/test/jdk/tools/jpackage/share/ErrorTest.java b/test/jdk/tools/jpackage/share/ErrorTest.java index c352decc0f3..2d3d61de864 100644 --- a/test/jdk/tools/jpackage/share/ErrorTest.java +++ b/test/jdk/tools/jpackage/share/ErrorTest.java @@ -576,6 +576,9 @@ public final class ErrorTest { ); }).flatMap(x -> x).map(TestSpec.Builder::create).toList()); + invalidShortcut(testCases::add, "--win-menu"); + invalidShortcut(testCases::add, "--win-shortcut"); + return toTestArgs(testCases.stream()); } @@ -642,6 +645,8 @@ public final class ErrorTest { .error("error.rpm-invalid-value-for-package-name.advice") ).map(TestSpec.Builder::create).toList()); + invalidShortcut(testCases::add, "--linux-shortcut"); + return toTestArgs(testCases.stream()); } @@ -697,6 +702,13 @@ public final class ErrorTest { duplicateAddArgs(builder, accumulator, "--mac-sign"); } + private static void invalidShortcut(Consumer accumulator, String shortcutOption) { + Objects.requireNonNull(shortcutOption); + Stream.of("true", "false", "").map(value -> { + return testSpec().nativeType().addArgs(shortcutOption, value).error("error.invalid-option-value", value, shortcutOption).create(); + }).forEach(accumulator); + } + private record UnsupportedPlatformOption(String name, Optional value) { UnsupportedPlatformOption { Objects.requireNonNull(name); From d78fa5a9f6254e2e93e75c693efba75e09736749 Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Tue, 12 Aug 2025 07:16:57 +0000 Subject: [PATCH 092/580] 8365240: [asan] exclude some tests when using asan enabled binaries Reviewed-by: lmesnik, sspitsyn --- .../vmTestbase/nsk/jvmti/Allocate/alloc001/alloc001.java | 4 +++- test/jdk/tools/launcher/TooSmallStackSize.java | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/Allocate/alloc001/alloc001.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/Allocate/alloc001/alloc001.java index eafbbf8b05f..e9f4ee7c4f1 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/Allocate/alloc001/alloc001.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/Allocate/alloc001/alloc001.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2023, 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 @@ -43,6 +43,8 @@ * * @comment Not run on AIX as it does not support ulimit -v * @requires os.family != "aix" + * @comment Do not run with asan enabled because asan has issues with ulimit + * @requires !vm.asan * @run main/native nsk.jvmti.Allocate.alloc001.alloc001 */ diff --git a/test/jdk/tools/launcher/TooSmallStackSize.java b/test/jdk/tools/launcher/TooSmallStackSize.java index 8485f115d6e..8133dbf4550 100644 --- a/test/jdk/tools/launcher/TooSmallStackSize.java +++ b/test/jdk/tools/launcher/TooSmallStackSize.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,6 +26,8 @@ * @bug 6762191 8222334 * @summary Setting stack size to 16K causes segmentation fault * @compile TooSmallStackSize.java + * @comment VM fails to launch with minimum allowed stack size of 136k when asan is enabled + * @requires !vm.asan * @run main TooSmallStackSize */ From db12f1934a659843d9cc77f4f21e67ebf9fa94e6 Mon Sep 17 00:00:00 2001 From: Afshin Zafari Date: Tue, 12 Aug 2025 08:03:18 +0000 Subject: [PATCH 093/580] 8364280: NMTCommittedVirtualMemoryTracker.test_committed_virtualmemory_region_vm fails with assertion "negative distance" Reviewed-by: gziemski, jsjolen --- .../runtime/test_committed_virtualmemory.cpp | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/test/hotspot/gtest/runtime/test_committed_virtualmemory.cpp b/test/hotspot/gtest/runtime/test_committed_virtualmemory.cpp index cd47e3a4e17..caf685f587b 100644 --- a/test/hotspot/gtest/runtime/test_committed_virtualmemory.cpp +++ b/test/hotspot/gtest/runtime/test_committed_virtualmemory.cpp @@ -43,10 +43,16 @@ public: // snapshot current stack usage VirtualMemoryTracker::Instance::snapshot_thread_stacks(); - ReservedMemoryRegion rmr_found = VirtualMemoryTracker::Instance::tree()->find_reserved_region(stack_end); + ReservedMemoryRegion rmr_found; + { + MemTracker::NmtVirtualMemoryLocker vml; + rmr_found = VirtualMemoryTracker::Instance::tree()->find_reserved_region(stack_end); + } + ASSERT_TRUE(rmr_found.is_valid()); ASSERT_EQ(rmr_found.base(), stack_end); + int i = 0; address i_addr = (address)&i; bool found_i_addr = false; @@ -54,18 +60,20 @@ public: // stack grows downward address stack_top = stack_end + stack_size; bool found_stack_top = false; - VirtualMemoryTracker::Instance::tree()->visit_committed_regions(rmr_found, [&](const CommittedMemoryRegion& cmr) { - if (cmr.base() + cmr.size() == stack_top) { - EXPECT_TRUE(cmr.size() <= stack_size); - found_stack_top = true; - } - if(i_addr < stack_top && i_addr >= cmr.base()) { - found_i_addr = true; - } - i++; - return true; - }); - + { + MemTracker::NmtVirtualMemoryLocker vml; + VirtualMemoryTracker::Instance::tree()->visit_committed_regions(rmr_found, [&](const CommittedMemoryRegion& cmr) { + if (cmr.base() + cmr.size() == stack_top) { + EXPECT_TRUE(cmr.size() <= stack_size); + found_stack_top = true; + } + if (i_addr < stack_top && i_addr >= cmr.base()) { + found_i_addr = true; + } + i++; + return true; + }); + } // stack and guard pages may be contiguous as one region ASSERT_TRUE(i >= 1); From 5a442197d21e1dfb89cdbf5f0ad5596869ab333a Mon Sep 17 00:00:00 2001 From: Johny Jose Date: Tue, 12 Aug 2025 08:26:42 +0000 Subject: [PATCH 094/580] 7191877: TEST_BUG: java/rmi/transport/checkLeaseInfoLeak/CheckLeaseLeak.java failing intermittently Reviewed-by: smarks, coffeys --- test/jdk/ProblemList.txt | 2 - .../checkLeaseInfoLeak/CheckLeaseLeak.java | 98 ++++++++----------- .../checkLeaseInfoLeak/LeaseLeakClient.java | 3 +- 3 files changed, 44 insertions(+), 59 deletions(-) diff --git a/test/jdk/ProblemList.txt b/test/jdk/ProblemList.txt index 84555a6edfb..02eef5888e0 100644 --- a/test/jdk/ProblemList.txt +++ b/test/jdk/ProblemList.txt @@ -615,8 +615,6 @@ java/rmi/server/Unreferenced/finiteGCLatency/FiniteGCLatency.java 7140992 generi java/rmi/transport/rapidExportUnexport/RapidExportUnexport.java 7146541 linux-all -java/rmi/transport/checkLeaseInfoLeak/CheckLeaseLeak.java 7191877 generic-all - java/rmi/registry/readTest/CodebaseTest.java 8173324 windows-all java/rmi/registry/multipleRegistries/MultipleRegistries.java 8268182 macosx-all diff --git a/test/jdk/java/rmi/transport/checkLeaseInfoLeak/CheckLeaseLeak.java b/test/jdk/java/rmi/transport/checkLeaseInfoLeak/CheckLeaseLeak.java index 2370dcd35c4..4de6598a0f4 100644 --- a/test/jdk/java/rmi/transport/checkLeaseInfoLeak/CheckLeaseLeak.java +++ b/test/jdk/java/rmi/transport/checkLeaseInfoLeak/CheckLeaseLeak.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2017, 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 @@ -54,12 +54,11 @@ import java.rmi.*; import java.rmi.server.*; -import sun.rmi.transport.*; -import sun.rmi.*; import java.util.Map; import java.io.*; import java.lang.reflect.*; import java.rmi.registry.*; +import sun.rmi.transport.*; public class CheckLeaseLeak extends UnicastRemoteObject implements LeaseLeak { public CheckLeaseLeak() throws RemoteException { } @@ -102,8 +101,6 @@ public class CheckLeaseLeak extends UnicastRemoteObject implements LeaseLeak { System.err.println("Created client: " + i); JavaVM jvm = new JavaVM("LeaseLeakClient", - " -Djava.security.policy=" + - TestParams.defaultPolicy + " -Drmi.registry.port=" + registryPort, ""); @@ -128,8 +125,8 @@ public class CheckLeaseLeak extends UnicastRemoteObject implements LeaseLeak { } } - /* numLeft should be 2 - if 11 there is a problem. */ - if (numLeft > 2) { + /* numLeft should be 4 - if 11 there is a problem. */ + if (numLeft > 4) { TestLibrary.bomb("Too many objects in DGCImpl.leaseTable: "+ numLeft); } else { @@ -156,57 +153,51 @@ public class CheckLeaseLeak extends UnicastRemoteObject implements LeaseLeak { Field f; try { - f = (Field) java.security.AccessController.doPrivileged - (new java.security.PrivilegedExceptionAction() { - public Object run() throws Exception { + ObjID dgcID = new ObjID(DGC_ID); + /* + * Construct an ObjectEndpoint containing DGC's + * ObjID. + */ + Class oeClass = + Class.forName("sun.rmi.transport.ObjectEndpoint"); + Class[] constrParams = + new Class[]{ ObjID.class, Transport.class }; + Constructor oeConstructor = + oeClass.getDeclaredConstructor(constrParams); + oeConstructor.setAccessible(true); + Object oe = + oeConstructor.newInstance( + new Object[]{ dgcID, null }); - ObjID dgcID = new ObjID(DGC_ID); + /* + * Get Target that contains DGCImpl in ObjectTable + */ + Class objTableClass = + Class.forName("sun.rmi.transport.ObjectTable"); + Class getTargetParams[] = new Class[] { oeClass }; + Method objTableGetTarget = + objTableClass.getDeclaredMethod("getTarget", + getTargetParams); + objTableGetTarget.setAccessible(true); + Target dgcTarget = (Target) + objTableGetTarget.invoke(null, new Object[]{ oe }); - /* - * Construct an ObjectEndpoint containing DGC's - * ObjID. - */ - Class oeClass = - Class.forName("sun.rmi.transport.ObjectEndpoint"); - Class[] constrParams = - new Class[]{ ObjID.class, Transport.class }; - Constructor oeConstructor = - oeClass.getDeclaredConstructor(constrParams); - oeConstructor.setAccessible(true); - Object oe = - oeConstructor.newInstance( - new Object[]{ dgcID, null }); - - /* - * Get Target that contains DGCImpl in ObjectTable - */ - Class objTableClass = - Class.forName("sun.rmi.transport.ObjectTable"); - Class getTargetParams[] = new Class[] { oeClass }; - Method objTableGetTarget = - objTableClass.getDeclaredMethod("getTarget", - getTargetParams); - objTableGetTarget.setAccessible(true); - Target dgcTarget = (Target) - objTableGetTarget.invoke(null, new Object[]{ oe }); - - /* get the DGCImpl from its Target */ - Method targetGetImpl = - dgcTarget.getClass().getDeclaredMethod + /* get the DGCImpl from its Target */ + Method targetGetImpl = + dgcTarget.getClass().getDeclaredMethod ("getImpl", null); - targetGetImpl.setAccessible(true); - dgcImpl[0] = - (Remote) targetGetImpl.invoke(dgcTarget, null); + targetGetImpl.setAccessible(true); + dgcImpl[0] = + (Remote) targetGetImpl.invoke(dgcTarget, null); - /* Get the lease table from the DGCImpl. */ - Field reflectedLeaseTable = - dgcImpl[0].getClass().getDeclaredField + /* Get the lease table from the DGCImpl. */ + Field reflectedLeaseTable = + dgcImpl[0].getClass().getDeclaredField ("leaseTable"); - reflectedLeaseTable.setAccessible(true); + reflectedLeaseTable.setAccessible(true); + + f = reflectedLeaseTable; - return reflectedLeaseTable; - } - }); /** * This is the leaseTable that will fill up with LeaseInfo @@ -217,9 +208,6 @@ public class CheckLeaseLeak extends UnicastRemoteObject implements LeaseLeak { numLeaseInfosLeft = leaseTable.size(); } catch(Exception e) { - if (e instanceof java.security.PrivilegedActionException) - e = ((java.security.PrivilegedActionException) e). - getException(); TestLibrary.bomb(e); } diff --git a/test/jdk/java/rmi/transport/checkLeaseInfoLeak/LeaseLeakClient.java b/test/jdk/java/rmi/transport/checkLeaseInfoLeak/LeaseLeakClient.java index d9cf9fa9e96..dd24b819146 100644 --- a/test/jdk/java/rmi/transport/checkLeaseInfoLeak/LeaseLeakClient.java +++ b/test/jdk/java/rmi/transport/checkLeaseInfoLeak/LeaseLeakClient.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2012, 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 @@ -27,7 +27,6 @@ import java.rmi.registry.*; public class LeaseLeakClient { public static void main(String args[]) { - TestLibrary.suggestSecurityManager("java.rmi.RMISecurityManager"); try { LeaseLeak leaseLeak = null; From b81f4faed7180e51aa966a9bf2f84ba755c6736d Mon Sep 17 00:00:00 2001 From: David Beaumont Date: Tue, 12 Aug 2025 08:34:26 +0000 Subject: [PATCH 095/580] 8360037: Refactor ImageReader in preparation for Valhalla support Reviewed-by: alanb, rriggs, jpai --- .../jdk/internal/jimage/ImageReader.java | 1019 ++++++++--------- .../jdk/internal/jrtfs/ExplodedImage.java | 6 +- .../jdk/internal/jrtfs/JrtFileAttributes.java | 8 +- .../jdk/internal/jrtfs/JrtFileSystem.java | 26 +- .../jdk/internal/jrtfs/SystemImage.java | 1 - .../internal/module/SystemModuleFinders.java | 115 +- .../jdk/internal/jimage/ImageReaderTest.java | 279 +++++ .../jdk/internal/jimage/JImageReadTest.java | 38 +- .../ImageReaderDuplicateChildNodesTest.java | 10 +- .../internal/jrtfs/ImageReaderBenchmark.java | 4 +- 10 files changed, 880 insertions(+), 626 deletions(-) create mode 100644 test/jdk/jdk/internal/jimage/ImageReaderTest.java diff --git a/src/java.base/share/classes/jdk/internal/jimage/ImageReader.java b/src/java.base/share/classes/jdk/internal/jimage/ImageReader.java index f12c39f3e81..79e718c76e5 100644 --- a/src/java.base/share/classes/jdk/internal/jimage/ImageReader.java +++ b/src/java.base/share/classes/jdk/internal/jimage/ImageReader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. * 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,16 +25,14 @@ package jdk.internal.jimage; import java.io.IOException; -import java.io.InputStream; -import java.io.UncheckedIOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.IntBuffer; import java.nio.file.Files; -import java.nio.file.attribute.BasicFileAttributes; -import java.nio.file.attribute.FileTime; import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -42,9 +40,36 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; -import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Stream; /** + * A view over the entries of a jimage file with a unified namespace suitable + * for file system use. The jimage entries (resources, module and package + * information) are mapped into a unified hierarchy of named nodes, which serve + * as the underlying structure for {@code JrtFileSystem} and other utilities. + * + *

Entries in jimage are expressed as one of three {@link Node} types; + * resource nodes, directory nodes and link nodes. + * + *

When remapping jimage entries, jimage location names (e.g. {@code + * "/java.base/java/lang/Integer.class"}) are prefixed with {@code "/modules"} + * to form the names of resource nodes. This aligns with the naming of module + * entries in jimage (e.g. "/modules/java.base/java/lang"), which appear as + * directory nodes in {@code ImageReader}. + * + *

Package entries (e.g. {@code "/packages/java.lang"} appear as directory + * nodes containing link nodes, which resolve back to the root directory of the + * module in which that package exists (e.g. {@code "/modules/java.base"}). + * Unlike other nodes, the jimage file does not contain explicit entries for + * link nodes, and their existence is derived only from the contents of the + * parent directory. + * + *

While similar to {@code BasicImageReader}, this class is not a conceptual + * subtype of it, and deliberately hides types such as {@code ImageLocation} to + * give a focused API based only on nodes. + * * @implNote This class needs to maintain JDK 8 source compatibility. * * It is used internally in the JDK to implement jimage/jrtfs access, @@ -60,6 +85,10 @@ public final class ImageReader implements AutoCloseable { this.reader = reader; } + /** + * Opens an image reader for a jimage file at the specified path, using the + * given byte order. + */ public static ImageReader open(Path imagePath, ByteOrder byteOrder) throws IOException { Objects.requireNonNull(imagePath); Objects.requireNonNull(byteOrder); @@ -67,6 +96,10 @@ public final class ImageReader implements AutoCloseable { return SharedImageReader.open(imagePath, byteOrder); } + /** + * Opens an image reader for a jimage file at the specified path, using the + * platform native byte order. + */ public static ImageReader open(Path imagePath) throws IOException { return open(imagePath, ByteOrder.nativeOrder()); } @@ -92,146 +125,113 @@ public final class ImageReader implements AutoCloseable { } } - // directory management interface - public Directory getRootDirectory() throws IOException { - ensureOpen(); - return reader.getRootDirectory(); - } - - + /** + * Finds the node with the given name. + * + * @param name a node name of the form {@code "/modules//...} or + * {@code "/packages//...}. + * @return a node representing a resource, directory or symbolic link. + */ public Node findNode(String name) throws IOException { ensureOpen(); return reader.findNode(name); } + /** + * Returns a copy of the content of a resource node. The buffer returned by + * this method is not cached by the node, and each call returns a new array + * instance. + * + * @throws IOException if the content cannot be returned (including if the + * given node is not a resource node). + */ public byte[] getResource(Node node) throws IOException { ensureOpen(); return reader.getResource(node); } - public byte[] getResource(Resource rs) throws IOException { - ensureOpen(); - return reader.getResource(rs); - } - - public ImageHeader getHeader() { - requireOpen(); - return reader.getHeader(); - } - + /** + * Releases a (possibly cached) {@link ByteBuffer} obtained via + * {@link #getResourceBuffer(Node)}. + * + *

Note that no testing is performed to check whether the buffer about + * to be released actually came from a call to {@code getResourceBuffer()}. + */ public static void releaseByteBuffer(ByteBuffer buffer) { BasicImageReader.releaseByteBuffer(buffer); } - public String getName() { + /** + * Returns the content of a resource node in a possibly cached byte buffer. + * Callers of this method must call {@link #releaseByteBuffer(ByteBuffer)} + * when they are finished with it. + */ + public ByteBuffer getResourceBuffer(Node node) { requireOpen(); - return reader.getName(); - } - - public ByteOrder getByteOrder() { - requireOpen(); - return reader.getByteOrder(); - } - - public Path getImagePath() { - requireOpen(); - return reader.getImagePath(); - } - - public ImageStringsReader getStrings() { - requireOpen(); - return reader.getStrings(); - } - - public ImageLocation findLocation(String mn, String rn) { - requireOpen(); - return reader.findLocation(mn, rn); - } - - public boolean verifyLocation(String mn, String rn) { - requireOpen(); - return reader.verifyLocation(mn, rn); - } - - public ImageLocation findLocation(String name) { - requireOpen(); - return reader.findLocation(name); - } - - public String[] getEntryNames() { - requireOpen(); - return reader.getEntryNames(); - } - - public String[] getModuleNames() { - requireOpen(); - int off = "/modules/".length(); - return reader.findNode("/modules") - .getChildren() - .stream() - .map(Node::getNameString) - .map(s -> s.substring(off, s.length())) - .toArray(String[]::new); - } - - public long[] getAttributes(int offset) { - requireOpen(); - return reader.getAttributes(offset); - } - - public String getString(int offset) { - requireOpen(); - return reader.getString(offset); - } - - public byte[] getResource(String name) { - requireOpen(); - return reader.getResource(name); - } - - public byte[] getResource(ImageLocation loc) { - requireOpen(); - return reader.getResource(loc); - } - - public ByteBuffer getResourceBuffer(ImageLocation loc) { - requireOpen(); - return reader.getResourceBuffer(loc); - } - - public InputStream getResourceStream(ImageLocation loc) { - requireOpen(); - return reader.getResourceStream(loc); + if (!node.isResource()) { + throw new IllegalArgumentException("Not a resource node: " + node); + } + return reader.getResourceBuffer(node.getLocation()); } private static final class SharedImageReader extends BasicImageReader { - static final int SIZE_OF_OFFSET = Integer.BYTES; - - static final Map OPEN_FILES = new HashMap<>(); + private static final Map OPEN_FILES = new HashMap<>(); + private static final String MODULES_ROOT = "/modules"; + private static final String PACKAGES_ROOT = "/packages"; + // There are >30,000 nodes in a complete jimage tree, and even relatively + // common tasks (e.g. starting up javac) load somewhere in the region of + // 1000 classes. Thus, an initial capacity of 2000 is a reasonable guess. + private static final int INITIAL_NODE_CACHE_CAPACITY = 2000; // List of openers for this shared image. - final Set openers; + private final Set openers = new HashSet<>(); - // attributes of the .jimage file. jimage file does not contain + // Attributes of the jimage file. The jimage file does not contain // attributes for the individual resources (yet). We use attributes // of the jimage file itself (creation, modification, access times). - // Iniitalized lazily, see {@link #imageFileAttributes()}. - BasicFileAttributes imageFileAttributes; + private final BasicFileAttributes imageFileAttributes; - // directory management implementation - final HashMap nodes; - volatile Directory rootDir; - - Directory packagesDir; - Directory modulesDir; + // Cache of all user visible nodes, guarded by synchronizing 'this' instance. + private final Map nodes; + // Used to classify ImageLocation instances without string comparison. + private final int modulesStringOffset; + private final int packagesStringOffset; private SharedImageReader(Path imagePath, ByteOrder byteOrder) throws IOException { super(imagePath, byteOrder); - this.openers = new HashSet<>(); - this.nodes = new HashMap<>(); + this.imageFileAttributes = Files.readAttributes(imagePath, BasicFileAttributes.class); + this.nodes = new HashMap<>(INITIAL_NODE_CACHE_CAPACITY); + // Pick stable jimage names from which to extract string offsets (we cannot + // use "/modules" or "/packages", since those have a module offset of zero). + this.modulesStringOffset = getModuleOffset("/modules/java.base"); + this.packagesStringOffset = getModuleOffset("/packages/java.lang"); + + // Node creation is very lazy, so we can just make the top-level directories + // now without the risk of triggering the building of lots of other nodes. + Directory packages = newDirectory(PACKAGES_ROOT); + nodes.put(packages.getName(), packages); + Directory modules = newDirectory(MODULES_ROOT); + nodes.put(modules.getName(), modules); + + Directory root = newDirectory("/"); + root.setChildren(Arrays.asList(packages, modules)); + nodes.put(root.getName(), root); } - public static ImageReader open(Path imagePath, ByteOrder byteOrder) throws IOException { + /** + * Returns the offset of the string denoting the leading "module" segment in + * the given path (e.g. {@code /}). We can't just pass in the + * {@code /} string here because that has a module offset of zero. + */ + private int getModuleOffset(String path) { + ImageLocation location = findLocation(path); + assert location != null : "Cannot find expected jimage location: " + path; + int offset = location.getModuleOffset(); + assert offset != 0 : "Invalid module offset for jimage location: " + path; + return offset; + } + + private static ImageReader open(Path imagePath, ByteOrder byteOrder) throws IOException { Objects.requireNonNull(imagePath); Objects.requireNonNull(byteOrder); @@ -264,7 +264,6 @@ public final class ImageReader implements AutoCloseable { if (openers.isEmpty()) { close(); nodes.clear(); - rootDir = null; if (!OPEN_FILES.remove(this.getImagePath(), this)) { throw new IOException("image file not found in open list"); @@ -273,448 +272,412 @@ public final class ImageReader implements AutoCloseable { } } - void addOpener(ImageReader reader) { - synchronized (OPEN_FILES) { - openers.add(reader); + /** + * Returns a node with the given name, or null if no resource or directory of + * that name exists. + * + *

This is the only public API by which anything outside this class can access + * {@code Node} instances either directly, or by resolving symbolic links. + * + *

Note also that there is no reentrant calling back to this method from within + * the node handling code. + * + * @param name an absolute, {@code /}-separated path string, prefixed with either + * "/modules" or "/packages". + */ + synchronized Node findNode(String name) { + Node node = nodes.get(name); + if (node == null) { + // We cannot get the root paths ("/modules" or "/packages") here + // because those nodes are already in the nodes cache. + if (name.startsWith(MODULES_ROOT + "/")) { + node = buildModulesNode(name); + } else if (name.startsWith(PACKAGES_ROOT + "/")) { + node = buildPackagesNode(name); + } + if (node != null) { + nodes.put(node.getName(), node); + } + } else if (!node.isCompleted()) { + // Only directories can be incomplete. + assert node instanceof Directory : "Invalid incomplete node: " + node; + completeDirectory((Directory) node); } - } - - boolean removeOpener(ImageReader reader) { - synchronized (OPEN_FILES) { - return openers.remove(reader); - } - } - - // directory management interface - Directory getRootDirectory() { - return buildRootDirectory(); + assert node == null || node.isCompleted() : "Incomplete node: " + node; + return node; } /** - * Lazily build a node from a name. + * Builds a node in the "/modules/..." namespace. + * + *

Called by {@link #findNode(String)} if a {@code /modules/...} node + * is not present in the cache. */ - synchronized Node buildNode(String name) { - Node n; - boolean isPackages = name.startsWith("/packages"); - boolean isModules = !isPackages && name.startsWith("/modules"); - - if (!(isModules || isPackages)) { - return null; - } - + private Node buildModulesNode(String name) { + assert name.startsWith(MODULES_ROOT + "/") : "Invalid module node name: " + name; + // Returns null for non-directory resources, since the jimage name does not + // start with "/modules" (e.g. "/java.base/java/lang/Object.class"). ImageLocation loc = findLocation(name); - - if (loc != null) { // A sub tree node - if (isPackages) { - n = handlePackages(name, loc); - } else { // modules sub tree - n = handleModulesSubTree(name, loc); - } - } else { // Asking for a resource? /modules/java.base/java/lang/Object.class - if (isModules) { - n = handleResource(name); - } else { - // Possibly ask for /packages/java.lang/java.base - // although /packages/java.base not created - n = handleModuleLink(name); - } + if (loc != null) { + assert name.equals(loc.getFullName()) : "Mismatched location for directory: " + name; + assert isModulesSubdirectory(loc) : "Invalid modules directory: " + name; + return completeModuleDirectory(newDirectory(name), loc); } - return n; - } - - synchronized Directory buildRootDirectory() { - Directory root = rootDir; // volatile read - if (root != null) { - return root; - } - - root = newDirectory(null, "/"); - root.setIsRootDir(); - - // /packages dir - packagesDir = newDirectory(root, "/packages"); - packagesDir.setIsPackagesDir(); - - // /modules dir - modulesDir = newDirectory(root, "/modules"); - modulesDir.setIsModulesDir(); - - root.setCompleted(true); - return rootDir = root; + // Now try the non-prefixed resource name, but be careful to avoid false + // positives for names like "/modules/modules/xxx" which could return a + // location of a directory entry. + loc = findLocation(name.substring(MODULES_ROOT.length())); + return loc != null && isResource(loc) ? newResource(name, loc) : null; } /** - * To visit sub tree resources. + * Builds a node in the "/packages/..." namespace. + * + *

Called by {@link #findNode(String)} if a {@code /packages/...} node + * is not present in the cache. */ - interface LocationVisitor { - void visit(ImageLocation loc); - } - - void visitLocation(ImageLocation loc, LocationVisitor visitor) { - byte[] offsets = getResource(loc); - ByteBuffer buffer = ByteBuffer.wrap(offsets); - buffer.order(getByteOrder()); - IntBuffer intBuffer = buffer.asIntBuffer(); - for (int i = 0; i < offsets.length / SIZE_OF_OFFSET; i++) { - int offset = intBuffer.get(i); - ImageLocation pkgLoc = getLocation(offset); - visitor.visit(pkgLoc); - } - } - - void visitPackageLocation(ImageLocation loc) { - // Retrieve package name - String pkgName = getBaseExt(loc); - // Content is array of offsets in Strings table - byte[] stringsOffsets = getResource(loc); - ByteBuffer buffer = ByteBuffer.wrap(stringsOffsets); - buffer.order(getByteOrder()); - IntBuffer intBuffer = buffer.asIntBuffer(); - // For each module, create a link node. - for (int i = 0; i < stringsOffsets.length / SIZE_OF_OFFSET; i++) { - // skip empty state, useless. - intBuffer.get(i); - i++; - int offset = intBuffer.get(i); - String moduleName = getString(offset); - Node targetNode = findNode("/modules/" + moduleName); - if (targetNode != null) { - String pkgDirName = packagesDir.getName() + "/" + pkgName; - Directory pkgDir = (Directory) nodes.get(pkgDirName); - newLinkNode(pkgDir, pkgDir.getName() + "/" + moduleName, targetNode); - } - } - } - - Node handlePackages(String name, ImageLocation loc) { - long size = loc.getUncompressedSize(); - Node n = null; - // Only possibilities are /packages, /packages/package/module - if (name.equals("/packages")) { - visitLocation(loc, (childloc) -> { - findNode(childloc.getFullName()); - }); - packagesDir.setCompleted(true); - n = packagesDir; + private Node buildPackagesNode(String name) { + // There are only locations for the root "/packages" or "/packages/xxx" + // directories, but not the symbolic links below them (the links can be + // entirely derived from the name information in the parent directory). + // However, unlike resources this means that we do not have a constant + // time lookup for link nodes when creating them. + int packageStart = PACKAGES_ROOT.length() + 1; + int packageEnd = name.indexOf('/', packageStart); + if (packageEnd == -1) { + ImageLocation loc = findLocation(name); + return loc != null ? completePackageDirectory(newDirectory(name), loc) : null; } else { - if (size != 0) { // children are offsets to module in StringsTable - String pkgName = getBaseExt(loc); - Directory pkgDir = newDirectory(packagesDir, packagesDir.getName() + "/" + pkgName); - visitPackageLocation(loc); - pkgDir.setCompleted(true); - n = pkgDir; - } else { // Link to module - String pkgName = loc.getParent(); - String modName = getBaseExt(loc); - Node targetNode = findNode("/modules/" + modName); - if (targetNode != null) { - String pkgDirName = packagesDir.getName() + "/" + pkgName; - Directory pkgDir = (Directory) nodes.get(pkgDirName); - Node linkNode = newLinkNode(pkgDir, pkgDir.getName() + "/" + modName, targetNode); - n = linkNode; + // We cannot assume that the parent directory exists for a link node, since + // the given name is untrusted and could reference a non-existent link. + // However, if the parent directory is present, we can conclude that the + // given name was not a valid link (or else it would already be cached). + String dirName = name.substring(0, packageEnd); + if (!nodes.containsKey(dirName)) { + ImageLocation loc = findLocation(dirName); + // If the parent location doesn't exist, the link node cannot exist. + if (loc != null) { + nodes.put(dirName, completePackageDirectory(newDirectory(dirName), loc)); + // When the parent is created its child nodes are created and cached, + // but this can still return null if given name wasn't a valid link. + return nodes.get(name); } } } - return n; + return null; } - // Asking for /packages/package/module although - // /packages// not yet created, need to create it - // prior to return the link to module node. - Node handleModuleLink(String name) { - // eg: unresolved /packages/package/module - // Build /packages/package node - Node ret = null; - String radical = "/packages/"; - String path = name; - if (path.startsWith(radical)) { - int start = radical.length(); - int pkgEnd = path.indexOf('/', start); - if (pkgEnd != -1) { - String pkg = path.substring(start, pkgEnd); - String pkgPath = radical + pkg; - Node n = findNode(pkgPath); - // If not found means that this is a symbolic link such as: - // /packages/java.util/java.base/java/util/Vector.class - // and will be done by a retry of the filesystem - for (Node child : n.getChildren()) { - if (child.name.equals(name)) { - ret = child; - break; - } - } - } + /** Completes a directory by ensuring its child list is populated correctly. */ + private void completeDirectory(Directory dir) { + String name = dir.getName(); + // Since the node exists, we can assert that its name starts with + // either "/modules" or "/packages", making differentiation easy. + // It also means that the name is valid, so it must yield a location. + assert name.startsWith(MODULES_ROOT) || name.startsWith(PACKAGES_ROOT); + ImageLocation loc = findLocation(name); + assert loc != null && name.equals(loc.getFullName()) : "Invalid location for name: " + name; + // We cannot use 'isXxxSubdirectory()' methods here since we could + // be given a top-level directory (for which that test doesn't work). + // The string MUST start "/modules" or "/packages" here. + if (name.charAt(1) == 'm') { + completeModuleDirectory(dir, loc); + } else { + completePackageDirectory(dir, loc); } - return ret; + assert dir.isCompleted() : "Directory must be complete by now: " + dir; } - Node handleModulesSubTree(String name, ImageLocation loc) { - Node n; - assert (name.equals(loc.getFullName())); - Directory dir = makeDirectories(name); - visitLocation(loc, (childloc) -> { - String path = childloc.getFullName(); - if (path.startsWith("/modules")) { // a package - makeDirectories(path); - } else { // a resource - makeDirectories(childloc.buildName(true, true, false)); - // if we have already created a resource for this name previously, then don't - // recreate it - if (!nodes.containsKey(childloc.getFullName(true))) { - newResource(dir, childloc); - } + /** + * Completes a modules directory by setting the list of child nodes. + * + *

The given directory can be the top level {@code /modules} directory, + * so it is NOT safe to use {@code isModulesSubdirectory(loc)} here. + */ + private Directory completeModuleDirectory(Directory dir, ImageLocation loc) { + assert dir.getName().equals(loc.getFullName()) : "Mismatched location for directory: " + dir; + List children = createChildNodes(loc, childLoc -> { + if (isModulesSubdirectory(childLoc)) { + return nodes.computeIfAbsent(childLoc.getFullName(), this::newDirectory); + } else { + // Add "/modules" prefix to image location paths to get node names. + String resourceName = childLoc.getFullName(true); + return nodes.computeIfAbsent(resourceName, n -> newResource(n, childLoc)); } }); - dir.setCompleted(true); - n = dir; - return n; - } - - Node handleResource(String name) { - Node n = null; - if (!name.startsWith("/modules/")) { - return null; - } - // Make sure that the thing that follows "/modules/" is a module name. - int moduleEndIndex = name.indexOf('/', "/modules/".length()); - if (moduleEndIndex == -1) { - return null; - } - ImageLocation moduleLoc = findLocation(name.substring(0, moduleEndIndex)); - if (moduleLoc == null || moduleLoc.getModuleOffset() == 0) { - return null; - } - - String locationPath = name.substring("/modules".length()); - ImageLocation resourceLoc = findLocation(locationPath); - if (resourceLoc != null) { - Directory dir = makeDirectories(resourceLoc.buildName(true, true, false)); - Resource res = newResource(dir, resourceLoc); - n = res; - } - return n; - } - - String getBaseExt(ImageLocation loc) { - String base = loc.getBase(); - String ext = loc.getExtension(); - if (ext != null && !ext.isEmpty()) { - base = base + "." + ext; - } - return base; - } - - synchronized Node findNode(String name) { - buildRootDirectory(); - Node n = nodes.get(name); - if (n == null || !n.isCompleted()) { - n = buildNode(name); - } - return n; - } - - /** - * Returns the file attributes of the image file. - */ - BasicFileAttributes imageFileAttributes() { - BasicFileAttributes attrs = imageFileAttributes; - if (attrs == null) { - try { - Path file = getImagePath(); - attrs = Files.readAttributes(file, BasicFileAttributes.class); - } catch (IOException ioe) { - throw new UncheckedIOException(ioe); - } - imageFileAttributes = attrs; - } - return attrs; - } - - Directory newDirectory(Directory parent, String name) { - Directory dir = Directory.create(parent, name, imageFileAttributes()); - nodes.put(dir.getName(), dir); + dir.setChildren(children); return dir; } - Resource newResource(Directory parent, ImageLocation loc) { - Resource res = Resource.create(parent, loc, imageFileAttributes()); - nodes.put(res.getName(), res); - return res; - } - - LinkNode newLinkNode(Directory dir, String name, Node link) { - LinkNode linkNode = LinkNode.create(dir, name, link); - nodes.put(linkNode.getName(), linkNode); - return linkNode; - } - - Directory makeDirectories(String parent) { - Directory last = rootDir; - for (int offset = parent.indexOf('/', 1); - offset != -1; - offset = parent.indexOf('/', offset + 1)) { - String dir = parent.substring(0, offset); - last = makeDirectory(dir, last); + /** + * Completes a package directory by setting the list of child nodes. + * + *

The given directory can be the top level {@code /packages} directory, + * so it is NOT safe to use {@code isPackagesSubdirectory(loc)} here. + */ + private Directory completePackageDirectory(Directory dir, ImageLocation loc) { + assert dir.getName().equals(loc.getFullName()) : "Mismatched location for directory: " + dir; + // The only directories in the "/packages" namespace are "/packages" or + // "/packages/". However, unlike "/modules" directories, the + // location offsets mean different things. + List children; + if (dir.getName().equals(PACKAGES_ROOT)) { + // Top-level directory just contains a list of subdirectories. + children = createChildNodes(loc, c -> nodes.computeIfAbsent(c.getFullName(), this::newDirectory)); + } else { + // A package directory's content is array of offset PAIRS in the + // Strings table, but we only need the 2nd value of each pair. + IntBuffer intBuffer = getOffsetBuffer(loc); + int offsetCount = intBuffer.capacity(); + assert (offsetCount & 0x1) == 0 : "Offset count must be even: " + offsetCount; + children = new ArrayList<>(offsetCount / 2); + // Iterate the 2nd offset in each pair (odd indices). + for (int i = 1; i < offsetCount; i += 2) { + String moduleName = getString(intBuffer.get(i)); + children.add(nodes.computeIfAbsent( + dir.getName() + "/" + moduleName, + n -> newLinkNode(n, MODULES_ROOT + "/" + moduleName))); + } } - return makeDirectory(parent, last); - + // This only happens once and "completes" the directory. + dir.setChildren(children); + return dir; } - Directory makeDirectory(String dir, Directory last) { - Directory nextDir = (Directory) nodes.get(dir); - if (nextDir == null) { - nextDir = newDirectory(last, dir); + /** + * Creates the list of child nodes for a {@code Directory} based on a given + * + *

Note: This cannot be used for package subdirectories as they have + * child offsets stored differently to other directories. + */ + private List createChildNodes(ImageLocation loc, Function newChildFn) { + IntBuffer offsets = getOffsetBuffer(loc); + int childCount = offsets.capacity(); + List children = new ArrayList<>(childCount); + for (int i = 0; i < childCount; i++) { + children.add(newChildFn.apply(getLocation(offsets.get(i)))); } - return nextDir; + return children; } - byte[] getResource(Node node) throws IOException { + /** Helper to extract the integer offset buffer from a directory location. */ + private IntBuffer getOffsetBuffer(ImageLocation dir) { + assert !isResource(dir) : "Not a directory: " + dir.getFullName(); + byte[] offsets = getResource(dir); + ByteBuffer buffer = ByteBuffer.wrap(offsets); + buffer.order(getByteOrder()); + return buffer.asIntBuffer(); + } + + /** + * Efficiently determines if an image location is a resource. + * + *

A resource must have a valid module associated with it, so its + * module offset must be non-zero, and not equal to the offsets for + * "/modules/..." or "/packages/..." entries. + */ + private boolean isResource(ImageLocation loc) { + int moduleOffset = loc.getModuleOffset(); + return moduleOffset != 0 + && moduleOffset != modulesStringOffset + && moduleOffset != packagesStringOffset; + } + + /** + * Determines if an image location is a directory in the {@code /modules} + * namespace (if so, the location name is the node name). + * + *

In jimage, every {@code ImageLocation} under {@code /modules/} is a + * directory and has the same value for {@code getModule()}, and {@code + * getModuleOffset()}. + */ + private boolean isModulesSubdirectory(ImageLocation loc) { + return loc.getModuleOffset() == modulesStringOffset; + } + + /** + * Creates an "incomplete" directory node with no child nodes set. + * Directories need to be "completed" before they are returned by + * {@link #findNode(String)}. + */ + private Directory newDirectory(String name) { + return new Directory(name, imageFileAttributes); + } + + /** + * Creates a new resource from an image location. This is the only case + * where the image location name does not match the requested node name. + * In image files, resource locations are NOT prefixed by {@code /modules}. + */ + private Resource newResource(String name, ImageLocation loc) { + assert name.equals(loc.getFullName(true)) : "Mismatched location for resource: " + name; + return new Resource(name, loc, imageFileAttributes); + } + + /** + * Creates a new link node pointing at the given target name. + * + *

Note that target node is resolved each time {@code resolve()} is called, + * so if a link node is retained after its reader is closed, it will fail. + */ + private LinkNode newLinkNode(String name, String targetName) { + return new LinkNode(name, () -> findNode(targetName), imageFileAttributes); + } + + /** Returns the content of a resource node. */ + private byte[] getResource(Node node) throws IOException { + // We could have been given a non-resource node here. if (node.isResource()) { return super.getResource(node.getLocation()); } throw new IOException("Not a resource: " + node); } - - byte[] getResource(Resource rs) throws IOException { - return super.getResource(rs.getLocation()); - } } - // jimage file does not store directory structure. We build nodes - // using the "path" strings found in the jimage file. - // Node can be a directory or a resource + /** + * A directory, resource or symbolic link. + * + *

Node Equality

+ * + * Nodes are identified solely by their name, and it is not valid to attempt + * to compare nodes from different reader instances. Different readers may + * produce nodes with the same names, but different contents. + * + *

Furthermore, since a {@link ImageReader} provides "perfect" caching of + * nodes, equality of nodes from the same reader is equivalent to instance + * identity. + */ public abstract static class Node { - private static final int ROOT_DIR = 0b0000_0000_0000_0001; - private static final int PACKAGES_DIR = 0b0000_0000_0000_0010; - private static final int MODULES_DIR = 0b0000_0000_0000_0100; - - private int flags; private final String name; private final BasicFileAttributes fileAttrs; - private boolean completed; + /** + * Creates an abstract {@code Node}, which is either a resource, directory + * or symbolic link. + * + *

This constructor is only non-private so it can be used by the + * {@code ExplodedImage} class, and must not be used otherwise. + */ protected Node(String name, BasicFileAttributes fileAttrs) { this.name = Objects.requireNonNull(name); this.fileAttrs = Objects.requireNonNull(fileAttrs); } + // A node is completed when all its direct children have been built. + // As such, non-directory nodes are always complete. + boolean isCompleted() { + return true; + } + + // Only resources can return a location. + ImageLocation getLocation() { + throw new IllegalStateException("not a resource: " + getName()); + } + /** - * A node is completed when all its direct children have been built. + * Returns the name of this node (e.g. {@code + * "/modules/java.base/java/lang/Object.class"} or {@code + * "/packages/java.lang"}). * - * @return + *

Note that for resource nodes this is NOT the underlying jimage + * resource name (it is prefixed with {@code "/modules"}). */ - public boolean isCompleted() { - return completed; - } - - public void setCompleted(boolean completed) { - this.completed = completed; - } - - public final void setIsRootDir() { - flags |= ROOT_DIR; - } - - public final boolean isRootDir() { - return (flags & ROOT_DIR) != 0; - } - - public final void setIsPackagesDir() { - flags |= PACKAGES_DIR; - } - - public final boolean isPackagesDir() { - return (flags & PACKAGES_DIR) != 0; - } - - public final void setIsModulesDir() { - flags |= MODULES_DIR; - } - - public final boolean isModulesDir() { - return (flags & MODULES_DIR) != 0; - } - public final String getName() { return name; } + /** + * Returns file attributes for this node. The value returned may be the + * same for all nodes, and should not be relied upon for accuracy. + */ public final BasicFileAttributes getFileAttributes() { return fileAttrs; } - // resolve this Node (if this is a soft link, get underlying Node) + /** + * Resolves a symbolic link to its target node. If this code is not a + * symbolic link, then it resolves to itself. + */ public final Node resolveLink() { return resolveLink(false); } + /** + * Resolves a symbolic link to its target node. If this code is not a + * symbolic link, then it resolves to itself. + */ public Node resolveLink(boolean recursive) { return this; } - // is this a soft link Node? + /** Returns whether this node is a symbolic link. */ public boolean isLink() { return false; } + /** + * Returns whether this node is a directory. Directory nodes can have + * {@link #getChildNames()} invoked to get the fully qualified names + * of any child nodes. + */ public boolean isDirectory() { return false; } - public List getChildren() { - throw new IllegalArgumentException("not a directory: " + getNameString()); - } - + /** + * Returns whether this node is a resource. Resource nodes can have + * their contents obtained via {@link ImageReader#getResource(Node)} + * or {@link ImageReader#getResourceBuffer(Node)}. + */ public boolean isResource() { return false; } - public ImageLocation getLocation() { - throw new IllegalArgumentException("not a resource: " + getNameString()); + /** + * Returns the fully qualified names of any child nodes for a directory. + * + *

By default, this method throws {@link IllegalStateException} and + * is overridden for directories. + */ + public Stream getChildNames() { + throw new IllegalStateException("not a directory: " + getName()); } + /** + * Returns the uncompressed size of this node's content. If this node is + * not a resource, this method returns zero. + */ public long size() { return 0L; } + /** + * Returns the compressed size of this node's content. If this node is + * not a resource, this method returns zero. + */ public long compressedSize() { return 0L; } + /** + * Returns the extension string of a resource node. If this node is not + * a resource, this method returns null. + */ public String extension() { return null; } - public long contentOffset() { - return 0L; - } - - public final FileTime creationTime() { - return fileAttrs.creationTime(); - } - - public final FileTime lastAccessTime() { - return fileAttrs.lastAccessTime(); - } - - public final FileTime lastModifiedTime() { - return fileAttrs.lastModifiedTime(); - } - - public final String getNameString() { - return name; - } - @Override public final String toString() { - return getNameString(); + return getName(); } + /** See Node Equality. */ @Override public final int hashCode() { return name.hashCode(); } + /** See Node Equality. */ @Override public final boolean equals(Object other) { if (this == other) { @@ -729,21 +692,40 @@ public final class ImageReader implements AutoCloseable { } } - // directory node - directory has full path name without '/' at end. - static final class Directory extends Node { - private final List children; + /** + * Directory node (referenced from a full path, without a trailing '/'). + * + *

Directory nodes have two distinct states: + *

    + *
  • Incomplete: The child list has not been set. + *
  • Complete: The child list has been set. + *
+ * + *

When a directory node is returned by {@link ImageReader#findNode(String)} + * it is always complete, but this DOES NOT mean that its child nodes are + * complete yet. + * + *

To avoid users being able to access incomplete child nodes, the + * {@code Node} API offers only a way to obtain child node names, forcing + * callers to invoke {@code findNode()} if they need to access the child + * node itself. + * + *

This approach allows directories to be implemented lazily with respect + * to child nodes, while retaining efficiency when child nodes are accessed + * (since any incomplete nodes will be created and placed in the node cache + * when the parent was first returned to the user). + */ + private static final class Directory extends Node { + // Monotonic reference, will be set to the unmodifiable child list exactly once. + private List children = null; private Directory(String name, BasicFileAttributes fileAttrs) { super(name, fileAttrs); - children = new ArrayList<>(); } - static Directory create(Directory parent, String name, BasicFileAttributes fileAttrs) { - Directory d = new Directory(name, fileAttrs); - if (parent != null) { - parent.addChild(d); - } - return d; + @Override + boolean isCompleted() { + return children != null; } @Override @@ -752,46 +734,41 @@ public final class ImageReader implements AutoCloseable { } @Override - public List getChildren() { - return Collections.unmodifiableList(children); - } - - void addChild(Node node) { - assert !children.contains(node) : "Child " + node + " already added"; - children.add(node); - } - - public void walk(Consumer consumer) { - consumer.accept(this); - for (Node child : children) { - if (child.isDirectory()) { - ((Directory)child).walk(consumer); - } else { - consumer.accept(child); - } + public Stream getChildNames() { + if (children != null) { + return children.stream().map(Node::getName); } + throw new IllegalStateException("Cannot get child nodes of an incomplete directory: " + getName()); + } + + private void setChildren(List children) { + assert this.children == null : this + ": Cannot set child nodes twice!"; + this.children = Collections.unmodifiableList(children); } } - - // "resource" is .class or any other resource (compressed/uncompressed) in a jimage. - // full path of the resource is the "name" of the resource. - static class Resource extends Node { + /** + * Resource node (e.g. a ".class" entry, or any other data resource). + * + *

Resources are leaf nodes referencing an underlying image location. They + * are lightweight, and do not cache their contents. + * + *

Unlike directories (where the node name matches the jimage path for the + * corresponding {@code ImageLocation}), resource node names are NOT the same + * as the corresponding jimage path. The difference is that node names for + * resources are prefixed with "/modules", which is missing from the + * equivalent jimage path. + */ + private static class Resource extends Node { private final ImageLocation loc; - private Resource(ImageLocation loc, BasicFileAttributes fileAttrs) { - super(loc.getFullName(true), fileAttrs); + private Resource(String name, ImageLocation loc, BasicFileAttributes fileAttrs) { + super(name, fileAttrs); this.loc = loc; } - static Resource create(Directory parent, ImageLocation loc, BasicFileAttributes fileAttrs) { - Resource rs = new Resource(loc, fileAttrs); - parent.addChild(rs); - return rs; - } - @Override - public boolean isCompleted() { - return true; + ImageLocation getLocation() { + return loc; } @Override @@ -799,11 +776,6 @@ public final class ImageReader implements AutoCloseable { return true; } - @Override - public ImageLocation getLocation() { - return loc; - } - @Override public long size() { return loc.getUncompressedSize(); @@ -818,36 +790,29 @@ public final class ImageReader implements AutoCloseable { public String extension() { return loc.getExtension(); } - - @Override - public long contentOffset() { - return loc.getContentOffset(); - } } - // represents a soft link to another Node - static class LinkNode extends Node { - private final Node link; + /** + * Link node (a symbolic link to a top-level modules directory). + * + *

Link nodes resolve their target by invoking a given supplier, and do + * not cache the result. Since nodes are cached by the {@code ImageReader}, + * this means that only the first call to {@link #resolveLink(boolean)} + * could do any significant work. + */ + private static class LinkNode extends Node { + private final Supplier link; - private LinkNode(String name, Node link) { - super(name, link.getFileAttributes()); + private LinkNode(String name, Supplier link, BasicFileAttributes fileAttrs) { + super(name, fileAttrs); this.link = link; } - static LinkNode create(Directory parent, String name, Node link) { - LinkNode ln = new LinkNode(name, link); - parent.addChild(ln); - return ln; - } - - @Override - public boolean isCompleted() { - return true; - } - @Override public Node resolveLink(boolean recursive) { - return (recursive && link instanceof LinkNode) ? ((LinkNode)link).resolveLink(true) : link; + // No need to use or propagate the recursive flag, since the target + // cannot possibly be a link node (links only point to directories). + return link.get(); } @Override diff --git a/src/java.base/share/classes/jdk/internal/jrtfs/ExplodedImage.java b/src/java.base/share/classes/jdk/internal/jrtfs/ExplodedImage.java index e2c17f8ca25..87a00da4393 100644 --- a/src/java.base/share/classes/jdk/internal/jrtfs/ExplodedImage.java +++ b/src/java.base/share/classes/jdk/internal/jrtfs/ExplodedImage.java @@ -119,9 +119,9 @@ class ExplodedImage extends SystemImage { } @Override - public List getChildren() { + public Stream getChildNames() { if (!isDirectory()) - throw new IllegalArgumentException("not a directory: " + getNameString()); + throw new IllegalArgumentException("not a directory: " + getName()); if (children == null) { List list = new ArrayList<>(); try (DirectoryStream stream = Files.newDirectoryStream(path)) { @@ -138,7 +138,7 @@ class ExplodedImage extends SystemImage { } children = list; } - return children; + return children.stream().map(Node::getName); } @Override diff --git a/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileAttributes.java b/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileAttributes.java index f0804b58c1c..e1eb1115260 100644 --- a/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileAttributes.java +++ b/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileAttributes.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -49,7 +49,7 @@ final class JrtFileAttributes implements BasicFileAttributes { //-------- basic attributes -------- @Override public FileTime creationTime() { - return node.creationTime(); + return node.getFileAttributes().creationTime(); } @Override @@ -69,12 +69,12 @@ final class JrtFileAttributes implements BasicFileAttributes { @Override public FileTime lastAccessTime() { - return node.lastAccessTime(); + return node.getFileAttributes().lastAccessTime(); } @Override public FileTime lastModifiedTime() { - return node.lastModifiedTime(); + return node.getFileAttributes().lastModifiedTime(); } @Override diff --git a/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileSystem.java b/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileSystem.java index 9a8d9d22dfa..6530bd1f90a 100644 --- a/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileSystem.java +++ b/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileSystem.java @@ -54,7 +54,6 @@ import java.nio.file.attribute.FileAttribute; import java.nio.file.attribute.FileTime; import java.nio.file.attribute.UserPrincipalLookupService; import java.nio.file.spi.FileSystemProvider; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; @@ -64,7 +63,6 @@ import java.util.Objects; import java.util.Set; import java.util.regex.Pattern; import jdk.internal.jimage.ImageReader.Node; -import static java.util.stream.Collectors.toList; /** * jrt file system implementation built on System jimage files. @@ -225,19 +223,19 @@ class JrtFileSystem extends FileSystem { throw new NotDirectoryException(path.getName()); } if (filter == null) { - return node.getChildren() - .stream() - .map(child -> (Path)(path.resolve(new JrtPath(this, child.getNameString()).getFileName()))) - .iterator(); + return node.getChildNames() + .map(child -> (Path) (path.resolve(new JrtPath(this, child).getFileName()))) + .iterator(); } - return node.getChildren() - .stream() - .map(child -> (Path)(path.resolve(new JrtPath(this, child.getNameString()).getFileName()))) - .filter(p -> { try { return filter.accept(p); - } catch (IOException x) {} - return false; - }) - .iterator(); + return node.getChildNames() + .map(child -> (Path) (path.resolve(new JrtPath(this, child).getFileName()))) + .filter(p -> { + try { + return filter.accept(p); + } catch (IOException x) {} + return false; + }) + .iterator(); } // returns the content of the file resource specified by the path diff --git a/src/java.base/share/classes/jdk/internal/jrtfs/SystemImage.java b/src/java.base/share/classes/jdk/internal/jrtfs/SystemImage.java index 88cdf724e7d..6813c7e627f 100644 --- a/src/java.base/share/classes/jdk/internal/jrtfs/SystemImage.java +++ b/src/java.base/share/classes/jdk/internal/jrtfs/SystemImage.java @@ -58,7 +58,6 @@ abstract class SystemImage { if (modulesImageExists) { // open a .jimage and build directory structure final ImageReader image = ImageReader.open(moduleImageFile); - image.getRootDirectory(); return new SystemImage() { @Override Node findNode(String path) throws IOException { diff --git a/src/java.base/share/classes/jdk/internal/module/SystemModuleFinders.java b/src/java.base/share/classes/jdk/internal/module/SystemModuleFinders.java index c520e6e636a..09ad3dc456d 100644 --- a/src/java.base/share/classes/jdk/internal/module/SystemModuleFinders.java +++ b/src/java.base/share/classes/jdk/internal/module/SystemModuleFinders.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 @@ -34,7 +34,6 @@ import java.lang.module.ModuleReader; import java.lang.module.ModuleReference; import java.lang.reflect.Constructor; import java.net.URI; -import java.net.URLConnection; import java.nio.ByteBuffer; import java.nio.file.Files; import java.nio.file.Path; @@ -54,7 +53,6 @@ import java.util.function.Supplier; import java.util.stream.Stream; import java.util.stream.StreamSupport; -import jdk.internal.jimage.ImageLocation; import jdk.internal.jimage.ImageReader; import jdk.internal.jimage.ImageReaderFactory; import jdk.internal.access.JavaNetUriAccess; @@ -210,7 +208,7 @@ public final class SystemModuleFinders { } /** - * Parses the module-info.class of all module in the runtime image and + * Parses the {@code module-info.class} of all modules in the runtime image and * returns a ModuleFinder to find the modules. * * @apiNote The returned ModuleFinder is thread safe. @@ -219,20 +217,16 @@ public final class SystemModuleFinders { // parse the module-info.class in every module Map nameToAttributes = new HashMap<>(); Map nameToHash = new HashMap<>(); - ImageReader reader = SystemImage.reader(); - for (String mn : reader.getModuleNames()) { - ImageLocation loc = reader.findLocation(mn, "module-info.class"); - ModuleInfo.Attributes attrs - = ModuleInfo.read(reader.getResourceBuffer(loc), null); - nameToAttributes.put(mn, attrs); + allModuleAttributes().forEach(attrs -> { + nameToAttributes.put(attrs.descriptor().name(), attrs); ModuleHashes hashes = attrs.recordedHashes(); if (hashes != null) { for (String name : hashes.names()) { nameToHash.computeIfAbsent(name, k -> hashes.hashFor(name)); } } - } + }); // create a ModuleReference for each module Set mrefs = new HashSet<>(); @@ -253,6 +247,40 @@ public final class SystemModuleFinders { return new SystemModuleFinder(mrefs, nameToModule); } + /** + * Parses the {@code module-info.class} of all modules in the runtime image and + * returns a stream of {@link ModuleInfo.Attributes Attributes} for them. The + * returned attributes are in no specific order. + */ + private static Stream allModuleAttributes() { + // System-wide image reader. + ImageReader reader = SystemImage.reader(); + try { + return reader.findNode("/modules") + .getChildNames() + .map(mn -> readModuleAttributes(reader, mn)); + } catch (IOException e) { + throw new Error("Error reading root /modules entry", e); + } + } + + /** + * Returns the module's "module-info", returning a holder for its class file + * attributes. Every module is required to have a valid {@code module-info.class}. + */ + private static ModuleInfo.Attributes readModuleAttributes(ImageReader reader, String moduleName) { + Exception err = null; + try { + ImageReader.Node node = reader.findNode(moduleName + "/module-info.class"); + if (node != null && node.isResource()) { + return ModuleInfo.read(reader.getResourceBuffer(node), null); + } + } catch (IOException | UncheckedIOException e) { + err = e; + } + throw new Error("Missing or invalid module-info.class for module: " + moduleName, err); + } + /** * A ModuleFinder that finds module in an array or set of modules. */ @@ -382,34 +410,18 @@ public final class SystemModuleFinders { this.module = module; } - /** - * Returns the ImageLocation for the given resource, {@code null} - * if not found. - */ - private ImageLocation findImageLocation(String name) throws IOException { - Objects.requireNonNull(name); - if (closed) - throw new IOException("ModuleReader is closed"); - ImageReader imageReader = SystemImage.reader(); - if (imageReader != null) { - return imageReader.findLocation(module, name); - } else { - // not an images build - return null; - } - } - /** * Returns {@code true} if the given resource exists, {@code false} * if not found. */ - private boolean containsImageLocation(String name) throws IOException { - Objects.requireNonNull(name); + private boolean containsResource(String resourcePath) throws IOException { + Objects.requireNonNull(resourcePath); if (closed) throw new IOException("ModuleReader is closed"); ImageReader imageReader = SystemImage.reader(); if (imageReader != null) { - return imageReader.verifyLocation(module, name); + ImageReader.Node node = imageReader.findNode("/modules" + resourcePath); + return node != null && node.isResource(); } else { // not an images build return false; @@ -418,8 +430,9 @@ public final class SystemModuleFinders { @Override public Optional find(String name) throws IOException { - if (containsImageLocation(name)) { - URI u = JNUA.create("jrt", "/" + module + "/" + name); + String resourcePath = "/" + module + "/" + name; + if (containsResource(resourcePath)) { + URI u = JNUA.create("jrt", resourcePath); return Optional.of(u); } else { return Optional.empty(); @@ -442,14 +455,25 @@ public final class SystemModuleFinders { } } + /** + * Returns the node for the given resource if found. If the name references + * a non-resource node, then {@code null} is returned. + */ + private ImageReader.Node findResource(ImageReader reader, String name) throws IOException { + Objects.requireNonNull(name); + if (closed) { + throw new IOException("ModuleReader is closed"); + } + String nodeName = "/modules/" + module + "/" + name; + ImageReader.Node node = reader.findNode(nodeName); + return (node != null && node.isResource()) ? node : null; + } + @Override public Optional read(String name) throws IOException { - ImageLocation location = findImageLocation(name); - if (location != null) { - return Optional.of(SystemImage.reader().getResourceBuffer(location)); - } else { - return Optional.empty(); - } + ImageReader reader = SystemImage.reader(); + return Optional.ofNullable(findResource(reader, name)) + .map(reader::getResourceBuffer); } @Override @@ -481,7 +505,7 @@ public final class SystemModuleFinders { private static class ModuleContentSpliterator implements Spliterator { final String moduleRoot; final Deque stack; - Iterator iterator; + Iterator iterator; ModuleContentSpliterator(String module) throws IOException { moduleRoot = "/modules/" + module; @@ -502,13 +526,10 @@ public final class SystemModuleFinders { private String next() throws IOException { for (;;) { while (iterator.hasNext()) { - ImageReader.Node node = iterator.next(); - String name = node.getName(); + String name = iterator.next(); + ImageReader.Node node = SystemImage.reader().findNode(name); if (node.isDirectory()) { - // build node - ImageReader.Node dir = SystemImage.reader().findNode(name); - assert dir.isDirectory(); - stack.push(dir); + stack.push(node); } else { // strip /modules/$MODULE/ prefix return name.substring(moduleRoot.length() + 1); @@ -520,7 +541,7 @@ public final class SystemModuleFinders { } else { ImageReader.Node dir = stack.poll(); assert dir.isDirectory(); - iterator = dir.getChildren().iterator(); + iterator = dir.getChildNames().iterator(); } } } diff --git a/test/jdk/jdk/internal/jimage/ImageReaderTest.java b/test/jdk/jdk/internal/jimage/ImageReaderTest.java new file mode 100644 index 00000000000..6bdf0cf479a --- /dev/null +++ b/test/jdk/jdk/internal/jimage/ImageReaderTest.java @@ -0,0 +1,279 @@ +/* + * 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 jdk.internal.jimage.ImageReader; +import jdk.internal.jimage.ImageReader.Node; +import jdk.test.lib.compiler.InMemoryJavaCompiler; +import jdk.test.lib.util.JarBuilder; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.opentest4j.TestSkippedException; +import tests.Helper; +import tests.JImageGenerator; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; + +/* + * @test + * @summary Tests for ImageReader. + * @modules java.base/jdk.internal.jimage + * jdk.jlink/jdk.tools.jimage + * @library /test/jdk/tools/lib + * /test/lib + * @build tests.* + * @run junit/othervm ImageReaderTest + */ + +/// Using PER_CLASS lifecycle means the (expensive) image file is only build once. +/// There is no mutable test instance state to worry about. +@TestInstance(PER_CLASS) +public class ImageReaderTest { + + private static final Map> IMAGE_ENTRIES = Map.of( + "modfoo", Arrays.asList( + "com.foo.Alpha", + "com.foo.Beta", + "com.foo.bar.Gamma"), + "modbar", Arrays.asList( + "com.bar.One", + "com.bar.Two")); + private final Path image = buildJImage(IMAGE_ENTRIES); + + @ParameterizedTest + @ValueSource(strings = { + "/", + "/modules", + "/modules/modfoo", + "/modules/modbar", + "/modules/modfoo/com", + "/modules/modfoo/com/foo", + "/modules/modfoo/com/foo/bar"}) + public void testModuleDirectories_expected(String name) throws IOException { + try (ImageReader reader = ImageReader.open(image)) { + assertDir(reader, name); + } + } + + @ParameterizedTest + @ValueSource(strings = { + "", + "//", + "/modules/", + "/modules/unknown", + "/modules/modbar/", + "/modules/modfoo//com", + "/modules/modfoo/com/"}) + public void testModuleNodes_absent(String name) throws IOException { + try (ImageReader reader = ImageReader.open(image)) { + assertAbsent(reader, name); + } + } + + @Test + public void testModuleResources() throws IOException { + try (ImageReader reader = ImageReader.open(image)) { + assertNode(reader, "/modules/modfoo/com/foo/Alpha.class"); + assertNode(reader, "/modules/modbar/com/bar/One.class"); + + ImageClassLoader loader = new ImageClassLoader(reader, IMAGE_ENTRIES.keySet()); + assertEquals("Class: com.foo.Alpha", loader.loadAndGetToString("modfoo", "com.foo.Alpha")); + assertEquals("Class: com.foo.Beta", loader.loadAndGetToString("modfoo", "com.foo.Beta")); + assertEquals("Class: com.foo.bar.Gamma", loader.loadAndGetToString("modfoo", "com.foo.bar.Gamma")); + assertEquals("Class: com.bar.One", loader.loadAndGetToString("modbar", "com.bar.One")); + } + } + + @Test + public void testPackageDirectories() throws IOException { + try (ImageReader reader = ImageReader.open(image)) { + Node root = assertDir(reader, "/packages"); + Set pkgNames = root.getChildNames().collect(Collectors.toSet()); + assertTrue(pkgNames.contains("/packages/com")); + assertTrue(pkgNames.contains("/packages/com.foo")); + assertTrue(pkgNames.contains("/packages/com.bar")); + + // Even though no classes exist directly in the "com" package, it still + // creates a directory with links back to all the modules which contain it. + Set comLinks = assertDir(reader, "/packages/com").getChildNames().collect(Collectors.toSet()); + assertTrue(comLinks.contains("/packages/com/modfoo")); + assertTrue(comLinks.contains("/packages/com/modbar")); + } + } + + @Test + public void testPackageLinks() throws IOException { + try (ImageReader reader = ImageReader.open(image)) { + Node moduleFoo = assertDir(reader, "/modules/modfoo"); + Node moduleBar = assertDir(reader, "/modules/modbar"); + assertSame(assertLink(reader, "/packages/com.foo/modfoo").resolveLink(), moduleFoo); + assertSame(assertLink(reader, "/packages/com.bar/modbar").resolveLink(), moduleBar); + } + } + + private static ImageReader.Node assertNode(ImageReader reader, String name) throws IOException { + ImageReader.Node node = reader.findNode(name); + assertNotNull(node, "Could not find node: " + name); + return node; + } + + private static ImageReader.Node assertDir(ImageReader reader, String name) throws IOException { + ImageReader.Node dir = assertNode(reader, name); + assertTrue(dir.isDirectory(), "Node was not a directory: " + name); + return dir; + } + + private static ImageReader.Node assertLink(ImageReader reader, String name) throws IOException { + ImageReader.Node link = assertNode(reader, name); + assertTrue(link.isLink(), "Node was not a symbolic link: " + name); + return link; + } + + private static void assertAbsent(ImageReader reader, String name) throws IOException { + assertNull(reader.findNode(name), "Should not be able to find node: " + name); + } + + /// Builds a jimage file with the specified class entries. The classes in the built + /// image can be loaded and executed to return their names via `toString()` to confirm + /// the correct bytes were returned. + public static Path buildJImage(Map> entries) { + Helper helper = getHelper(); + Path outDir = helper.createNewImageDir("test"); + JImageGenerator.JLinkTask jlink = JImageGenerator.getJLinkTask() + .modulePath(helper.defaultModulePath()) + .output(outDir); + + Path jarDir = helper.getJarDir(); + entries.forEach((module, classes) -> { + JarBuilder jar = new JarBuilder(jarDir.resolve(module + ".jar").toString()); + String moduleInfo = "module " + module + " {}"; + jar.addEntry("module-info.class", InMemoryJavaCompiler.compile("module-info", moduleInfo)); + + classes.forEach(fqn -> { + int lastDot = fqn.lastIndexOf('.'); + String pkg = fqn.substring(0, lastDot); + String cls = fqn.substring(lastDot + 1); + + String path = fqn.replace('.', '/') + ".class"; + String source = String.format( + """ + package %s; + public class %s { + public String toString() { + return "Class: %s"; + } + } + """, pkg, cls, fqn); + jar.addEntry(path, InMemoryJavaCompiler.compile(fqn, source)); + }); + try { + jar.build(); + } catch (IOException e) { + throw new RuntimeException(e); + } + jlink.addMods(module); + }); + return jlink.call().assertSuccess().resolve("lib", "modules"); + } + + /// Returns the helper for building JAR and jimage files. + private static Helper getHelper() { + try { + Helper helper = Helper.newHelper(); + if (helper == null) { + throw new TestSkippedException("Cannot create test helper (exploded image?)"); + } + return helper; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /// Loads and performs actions on classes stored in a given `ImageReader`. + private static class ImageClassLoader extends ClassLoader { + private final ImageReader reader; + private final Set testModules; + + private ImageClassLoader(ImageReader reader, Set testModules) { + this.reader = reader; + this.testModules = testModules; + } + + @FunctionalInterface + public interface ClassAction { + R call(Class cls) throws T; + } + + String loadAndGetToString(String module, String fqn) { + return loadAndCall(module, fqn, c -> c.getDeclaredConstructor().newInstance().toString()); + } + + R loadAndCall(String module, String fqn, ClassAction action) { + Class cls = findClass(module, fqn); + assertNotNull(cls, "Could not load class: " + module + "/" + fqn); + try { + return action.call(cls); + } catch (Exception e) { + fail("Class loading failed", e); + return null; + } + } + + @Override + protected Class findClass(String module, String fqn) { + assumeTrue(testModules.contains(module), "Can only load classes in modules: " + testModules); + String name = "/modules/" + module + "/" + fqn.replace('.', '/') + ".class"; + Class cls = findLoadedClass(fqn); + if (cls == null) { + try { + ImageReader.Node node = reader.findNode(name); + if (node != null && node.isResource()) { + byte[] classBytes = reader.getResource(node); + cls = defineClass(fqn, classBytes, 0, classBytes.length); + resolveClass(cls); + return cls; + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + return null; + } + } +} diff --git a/test/jdk/jdk/internal/jimage/JImageReadTest.java b/test/jdk/jdk/internal/jimage/JImageReadTest.java index ea700d03a4f..35fb2adb687 100644 --- a/test/jdk/jdk/internal/jimage/JImageReadTest.java +++ b/test/jdk/jdk/internal/jimage/JImageReadTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2019, 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 @@ -49,6 +49,9 @@ import org.testng.annotations.Test; import org.testng.Assert; import org.testng.TestNG; +import static java.nio.ByteOrder.BIG_ENDIAN; +import static java.nio.ByteOrder.LITTLE_ENDIAN; + @Test public class JImageReadTest { @@ -333,32 +336,21 @@ public class JImageReadTest { */ @Test static void test5_imageReaderEndianness() throws IOException { - ImageReader nativeReader = ImageReader.open(imageFile); - Assert.assertEquals(nativeReader.getByteOrder(), ByteOrder.nativeOrder()); - - try { - ImageReader leReader = ImageReader.open(imageFile, ByteOrder.LITTLE_ENDIAN); - Assert.assertEquals(leReader.getByteOrder(), ByteOrder.LITTLE_ENDIAN); - leReader.close(); - } catch (IOException io) { - // IOException expected if LITTLE_ENDIAN not the nativeOrder() - Assert.assertNotEquals(ByteOrder.nativeOrder(), ByteOrder.LITTLE_ENDIAN); + // Will be opened with native byte order. + try (ImageReader nativeReader = ImageReader.open(imageFile)) { + // Just ensure something works as expected. + Assert.assertNotNull(nativeReader.findNode("/")); + } catch (IOException expected) { + Assert.fail("Reader should be openable with native byte order."); } - try { - ImageReader beReader = ImageReader.open(imageFile, ByteOrder.BIG_ENDIAN); - Assert.assertEquals(beReader.getByteOrder(), ByteOrder.BIG_ENDIAN); - beReader.close(); - } catch (IOException io) { - // IOException expected if LITTLE_ENDIAN not the nativeOrder() - Assert.assertNotEquals(ByteOrder.nativeOrder(), ByteOrder.BIG_ENDIAN); - } - - nativeReader.close(); + // Reader should not be openable with the wrong byte order. + ByteOrder otherOrder = ByteOrder.nativeOrder() == BIG_ENDIAN ? LITTLE_ENDIAN : BIG_ENDIAN; + Assert.assertThrows(IOException.class, () -> ImageReader.open(imageFile, otherOrder)); } - // main method to run standalone from jtreg - @Test(enabled=false) + // main method to run standalone from jtreg + @Test(enabled = false) @Parameters({"x"}) @SuppressWarnings("raw_types") public static void main(@Optional String[] args) { diff --git a/test/jdk/tools/jimage/ImageReaderDuplicateChildNodesTest.java b/test/jdk/tools/jimage/ImageReaderDuplicateChildNodesTest.java index bec32bee0f8..8656a4a3d00 100644 --- a/test/jdk/tools/jimage/ImageReaderDuplicateChildNodesTest.java +++ b/test/jdk/tools/jimage/ImageReaderDuplicateChildNodesTest.java @@ -68,17 +68,17 @@ public class ImageReaderDuplicateChildNodesTest { + " in " + imagePath); } // now verify that the parent node which is a directory, doesn't have duplicate children - final List children = parent.getChildren(); - if (children == null || children.isEmpty()) { + final List childNames = parent.getChildNames().toList(); + if (childNames.isEmpty()) { throw new RuntimeException("ImageReader did not return any child resources under " + integersParentResource + " in " + imagePath); } final Set uniqueChildren = new HashSet<>(); - for (final ImageReader.Node child : children) { - final boolean unique = uniqueChildren.add(child); + for (final String childName : childNames) { + final boolean unique = uniqueChildren.add(reader.findNode(childName)); if (!unique) { throw new RuntimeException("ImageReader returned duplicate child resource " - + child + " under " + parent + " from image " + imagePath); + + childName + " under " + parent + " from image " + imagePath); } } } diff --git a/test/micro/org/openjdk/bench/jdk/internal/jrtfs/ImageReaderBenchmark.java b/test/micro/org/openjdk/bench/jdk/internal/jrtfs/ImageReaderBenchmark.java index c58a4dc33b2..4f97e12171f 100644 --- a/test/micro/org/openjdk/bench/jdk/internal/jrtfs/ImageReaderBenchmark.java +++ b/test/micro/org/openjdk/bench/jdk/internal/jrtfs/ImageReaderBenchmark.java @@ -195,9 +195,9 @@ public class ImageReaderBenchmark { static long countAllNodes(ImageReader reader, Node node) { long count = 1; if (node.isDirectory()) { - count += node.getChildren().stream().mapToLong(n -> { + count += node.getChildNames().mapToLong(n -> { try { - return countAllNodes(reader, reader.findNode(n.getName())); + return countAllNodes(reader, reader.findNode(n)); } catch (IOException e) { throw new RuntimeException(e); } From f155f7d6e50c702f65858774cfd02ef60aa9cad5 Mon Sep 17 00:00:00 2001 From: Fredrik Bredberg Date: Tue, 12 Aug 2025 08:45:02 +0000 Subject: [PATCH 096/580] 8364141: Remove LockingMode related code from x86 Reviewed-by: aboldtch, dholmes, coleenp --- src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp | 16 +- src/hotspot/cpu/x86/c1_LIRGenerator_x86.cpp | 2 +- src/hotspot/cpu/x86/c1_MacroAssembler_x86.cpp | 79 +---- src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp | 272 +++--------------- src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp | 7 +- src/hotspot/cpu/x86/interp_masm_x86.cpp | 169 ++--------- src/hotspot/cpu/x86/sharedRuntime_x86.cpp | 15 +- src/hotspot/cpu/x86/sharedRuntime_x86_64.cpp | 79 +---- .../x86/templateInterpreterGenerator_x86.cpp | 25 +- src/hotspot/cpu/x86/x86_64.ad | 27 -- 10 files changed, 91 insertions(+), 600 deletions(-) diff --git a/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp b/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp index 0176ff967ce..a30bbe08c55 100644 --- a/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp @@ -413,11 +413,7 @@ int LIR_Assembler::emit_unwind_handler() { if (method()->is_synchronized()) { monitor_address(0, FrameMap::rax_opr); stub = new MonitorExitStub(FrameMap::rax_opr, true, 0); - if (LockingMode == LM_MONITOR) { - __ jmp(*stub->entry()); - } else { - __ unlock_object(rdi, rsi, rax, *stub->entry()); - } + __ unlock_object(rdi, rsi, rax, *stub->entry()); __ bind(*stub->continuation()); } @@ -2733,15 +2729,9 @@ void LIR_Assembler::emit_lock(LIR_OpLock* op) { Register obj = op->obj_opr()->as_register(); // may not be an oop Register hdr = op->hdr_opr()->as_register(); Register lock = op->lock_opr()->as_register(); - if (LockingMode == LM_MONITOR) { - if (op->info() != nullptr) { - add_debug_info_for_null_check_here(op->info()); - __ null_check(obj); - } - __ jmp(*op->stub()->entry()); - } else if (op->code() == lir_lock) { + if (op->code() == lir_lock) { assert(BasicLock::displaced_header_offset_in_bytes() == 0, "lock_reg must point to the displaced header"); - Register tmp = LockingMode == LM_LIGHTWEIGHT ? op->scratch_opr()->as_register() : noreg; + Register tmp = op->scratch_opr()->as_register(); // add debug info for NullPointerException only if one is possible int null_check_offset = __ lock_object(hdr, obj, lock, tmp, *op->stub()->entry()); if (op->info() != nullptr) { diff --git a/src/hotspot/cpu/x86/c1_LIRGenerator_x86.cpp b/src/hotspot/cpu/x86/c1_LIRGenerator_x86.cpp index 3bd86d9b7e5..1fdfcc4f9cd 100644 --- a/src/hotspot/cpu/x86/c1_LIRGenerator_x86.cpp +++ b/src/hotspot/cpu/x86/c1_LIRGenerator_x86.cpp @@ -289,7 +289,7 @@ void LIRGenerator::do_MonitorEnter(MonitorEnter* x) { // this CodeEmitInfo must not have the xhandlers because here the // object is already locked (xhandlers expect object to be unlocked) CodeEmitInfo* info = state_for(x, x->state(), true); - LIR_Opr tmp = LockingMode == LM_LIGHTWEIGHT ? new_register(T_ADDRESS) : LIR_OprFact::illegalOpr; + LIR_Opr tmp = new_register(T_ADDRESS); monitor_enter(obj.result(), lock, syncTempOpr(), tmp, x->monitor_no(), info_for_exception, info); } diff --git a/src/hotspot/cpu/x86/c1_MacroAssembler_x86.cpp b/src/hotspot/cpu/x86/c1_MacroAssembler_x86.cpp index c9d103768b8..36efeafa940 100644 --- a/src/hotspot/cpu/x86/c1_MacroAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/c1_MacroAssembler_x86.cpp @@ -42,8 +42,6 @@ #include "utilities/globalDefinitions.hpp" int C1_MacroAssembler::lock_object(Register hdr, Register obj, Register disp_hdr, Register tmp, Label& slow_case) { - const int aligned_mask = BytesPerWord -1; - const int hdr_offset = oopDesc::mark_offset_in_bytes(); assert(hdr == rax, "hdr must be rax, for the cmpxchg instruction"); assert_different_registers(hdr, obj, disp_hdr, tmp); int null_check_offset = -1; @@ -55,93 +53,20 @@ int C1_MacroAssembler::lock_object(Register hdr, Register obj, Register disp_hdr null_check_offset = offset(); - if (LockingMode == LM_LIGHTWEIGHT) { - lightweight_lock(disp_hdr, obj, hdr, tmp, slow_case); - } else if (LockingMode == LM_LEGACY) { - Label done; - - if (DiagnoseSyncOnValueBasedClasses != 0) { - load_klass(hdr, obj, rscratch1); - testb(Address(hdr, Klass::misc_flags_offset()), KlassFlags::_misc_is_value_based_class); - jcc(Assembler::notZero, slow_case); - } - - // Load object header - movptr(hdr, Address(obj, hdr_offset)); - // and mark it as unlocked - orptr(hdr, markWord::unlocked_value); - // save unlocked object header into the displaced header location on the stack - movptr(Address(disp_hdr, 0), hdr); - // test if object header is still the same (i.e. unlocked), and if so, store the - // displaced header address in the object header - if it is not the same, get the - // object header instead - MacroAssembler::lock(); // must be immediately before cmpxchg! - cmpxchgptr(disp_hdr, Address(obj, hdr_offset)); - // if the object header was the same, we're done - jcc(Assembler::equal, done); - // if the object header was not the same, it is now in the hdr register - // => test if it is a stack pointer into the same stack (recursive locking), i.e.: - // - // 1) (hdr & aligned_mask) == 0 - // 2) rsp <= hdr - // 3) hdr <= rsp + page_size - // - // these 3 tests can be done by evaluating the following expression: - // - // (hdr - rsp) & (aligned_mask - page_size) - // - // assuming both the stack pointer and page_size have their least - // significant 2 bits cleared and page_size is a power of 2 - subptr(hdr, rsp); - andptr(hdr, aligned_mask - (int)os::vm_page_size()); - // for recursive locking, the result is zero => save it in the displaced header - // location (null in the displaced hdr location indicates recursive locking) - movptr(Address(disp_hdr, 0), hdr); - // otherwise we don't care about the result and handle locking via runtime call - jcc(Assembler::notZero, slow_case); - // done - bind(done); - inc_held_monitor_count(); - } + lightweight_lock(disp_hdr, obj, hdr, tmp, slow_case); return null_check_offset; } void C1_MacroAssembler::unlock_object(Register hdr, Register obj, Register disp_hdr, Label& slow_case) { - const int aligned_mask = BytesPerWord -1; - const int hdr_offset = oopDesc::mark_offset_in_bytes(); assert(disp_hdr == rax, "disp_hdr must be rax, for the cmpxchg instruction"); assert(hdr != obj && hdr != disp_hdr && obj != disp_hdr, "registers must be different"); - Label done; - - if (LockingMode != LM_LIGHTWEIGHT) { - // load displaced header - movptr(hdr, Address(disp_hdr, 0)); - // if the loaded hdr is null we had recursive locking - testptr(hdr, hdr); - // if we had recursive locking, we are done - jcc(Assembler::zero, done); - } // load object movptr(obj, Address(disp_hdr, BasicObjectLock::obj_offset())); verify_oop(obj); - if (LockingMode == LM_LIGHTWEIGHT) { - lightweight_unlock(obj, disp_hdr, hdr, slow_case); - } else if (LockingMode == LM_LEGACY) { - // test if object header is pointing to the displaced header, and if so, restore - // the displaced header in the object - if the object header is not pointing to - // the displaced header, get the object header instead - MacroAssembler::lock(); // must be immediately before cmpxchg! - cmpxchgptr(hdr, Address(obj, hdr_offset)); - // if the object header was not pointing to the displaced header, - // we do unlocking via runtime call - jcc(Assembler::notEqual, slow_case); - // done - bind(done); - dec_held_monitor_count(); - } + lightweight_unlock(obj, disp_hdr, hdr, slow_case); } diff --git a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp index b5b65171e32..8c3f33e0aca 100644 --- a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp @@ -219,244 +219,11 @@ inline Assembler::AvxVectorLen C2_MacroAssembler::vector_length_encoding(int vle // obj: object to lock -// box: on-stack box address (displaced header location) - KILLED -// rax,: tmp -- KILLED -// scr: tmp -- KILLED -void C2_MacroAssembler::fast_lock(Register objReg, Register boxReg, Register tmpReg, - Register scrReg, Register cx1Reg, Register cx2Reg, Register thread, - Metadata* method_data) { - assert(LockingMode != LM_LIGHTWEIGHT, "lightweight locking should use fast_lock_lightweight"); - // Ensure the register assignments are disjoint - assert(tmpReg == rax, ""); - assert(cx1Reg == noreg, ""); - assert(cx2Reg == noreg, ""); - assert_different_registers(objReg, boxReg, tmpReg, scrReg); - - // Possible cases that we'll encounter in fast_lock - // ------------------------------------------------ - // * Inflated - // -- unlocked - // -- Locked - // = by self - // = by other - // * neutral - // * stack-locked - // -- by self - // = sp-proximity test hits - // = sp-proximity test generates false-negative - // -- by other - // - - Label IsInflated, DONE_LABEL, NO_COUNT, COUNT; - - if (DiagnoseSyncOnValueBasedClasses != 0) { - load_klass(tmpReg, objReg, scrReg); - testb(Address(tmpReg, Klass::misc_flags_offset()), KlassFlags::_misc_is_value_based_class); - jcc(Assembler::notZero, DONE_LABEL); - } - - movptr(tmpReg, Address(objReg, oopDesc::mark_offset_in_bytes())); // [FETCH] - testptr(tmpReg, markWord::monitor_value); // inflated vs stack-locked|neutral - jcc(Assembler::notZero, IsInflated); - - if (LockingMode == LM_MONITOR) { - // Clear ZF so that we take the slow path at the DONE label. objReg is known to be not 0. - testptr(objReg, objReg); - } else { - assert(LockingMode == LM_LEGACY, "must be"); - // Attempt stack-locking ... - orptr (tmpReg, markWord::unlocked_value); - movptr(Address(boxReg, 0), tmpReg); // Anticipate successful CAS - lock(); - cmpxchgptr(boxReg, Address(objReg, oopDesc::mark_offset_in_bytes())); // Updates tmpReg - jcc(Assembler::equal, COUNT); // Success - - // Recursive locking. - // The object is stack-locked: markword contains stack pointer to BasicLock. - // Locked by current thread if difference with current SP is less than one page. - subptr(tmpReg, rsp); - // Next instruction set ZFlag == 1 (Success) if difference is less then one page. - andptr(tmpReg, (int32_t) (7 - (int)os::vm_page_size()) ); - movptr(Address(boxReg, 0), tmpReg); - } - jmp(DONE_LABEL); - - bind(IsInflated); - // The object is inflated. tmpReg contains pointer to ObjectMonitor* + markWord::monitor_value - - // Unconditionally set box->_displaced_header = markWord::unused_mark(). - // Without cast to int32_t this style of movptr will destroy r10 which is typically obj. - movptr(Address(boxReg, 0), checked_cast(markWord::unused_mark().value())); - - // It's inflated and we use scrReg for ObjectMonitor* in this section. - movptr(boxReg, Address(r15_thread, JavaThread::monitor_owner_id_offset())); - movq(scrReg, tmpReg); - xorq(tmpReg, tmpReg); - lock(); - cmpxchgptr(boxReg, Address(scrReg, OM_OFFSET_NO_MONITOR_VALUE_TAG(owner))); - - // Propagate ICC.ZF from CAS above into DONE_LABEL. - jccb(Assembler::equal, COUNT); // CAS above succeeded; propagate ZF = 1 (success) - - cmpptr(boxReg, rax); // Check if we are already the owner (recursive lock) - jccb(Assembler::notEqual, NO_COUNT); // If not recursive, ZF = 0 at this point (fail) - incq(Address(scrReg, OM_OFFSET_NO_MONITOR_VALUE_TAG(recursions))); - xorq(rax, rax); // Set ZF = 1 (success) for recursive lock, denoting locking success - bind(DONE_LABEL); - - // ZFlag == 1 count in fast path - // ZFlag == 0 count in slow path - jccb(Assembler::notZero, NO_COUNT); // jump if ZFlag == 0 - - bind(COUNT); - if (LockingMode == LM_LEGACY) { - // Count monitors in fast path - increment(Address(thread, JavaThread::held_monitor_count_offset())); - } - xorl(tmpReg, tmpReg); // Set ZF == 1 - - bind(NO_COUNT); - - // At NO_COUNT the icc ZFlag is set as follows ... - // fast_unlock uses the same protocol. - // ZFlag == 1 -> Success - // ZFlag == 0 -> Failure - force control through the slow path -} - -// obj: object to unlock -// box: box address (displaced header location), killed. Must be EAX. -// tmp: killed, cannot be obj nor box. -// -// Some commentary on balanced locking: -// -// fast_lock and fast_unlock are emitted only for provably balanced lock sites. -// Methods that don't have provably balanced locking are forced to run in the -// interpreter - such methods won't be compiled to use fast_lock and fast_unlock. -// The interpreter provides two properties: -// I1: At return-time the interpreter automatically and quietly unlocks any -// objects acquired the current activation (frame). Recall that the -// interpreter maintains an on-stack list of locks currently held by -// a frame. -// I2: If a method attempts to unlock an object that is not held by the -// the frame the interpreter throws IMSX. -// -// Lets say A(), which has provably balanced locking, acquires O and then calls B(). -// B() doesn't have provably balanced locking so it runs in the interpreter. -// Control returns to A() and A() unlocks O. By I1 and I2, above, we know that O -// is still locked by A(). -// -// The only other source of unbalanced locking would be JNI. The "Java Native Interface: -// Programmer's Guide and Specification" claims that an object locked by jni_monitorenter -// should not be unlocked by "normal" java-level locking and vice-versa. The specification -// doesn't specify what will occur if a program engages in such mixed-mode locking, however. -// Arguably given that the spec legislates the JNI case as undefined our implementation -// could reasonably *avoid* checking owner in fast_unlock(). -// In the interest of performance we elide m->Owner==Self check in unlock. -// A perfectly viable alternative is to elide the owner check except when -// Xcheck:jni is enabled. - -void C2_MacroAssembler::fast_unlock(Register objReg, Register boxReg, Register tmpReg) { - assert(LockingMode != LM_LIGHTWEIGHT, "lightweight locking should use fast_unlock_lightweight"); - assert(boxReg == rax, ""); - assert_different_registers(objReg, boxReg, tmpReg); - - Label DONE_LABEL, Stacked, COUNT, NO_COUNT; - - if (LockingMode == LM_LEGACY) { - cmpptr(Address(boxReg, 0), NULL_WORD); // Examine the displaced header - jcc (Assembler::zero, COUNT); // 0 indicates recursive stack-lock - } - movptr(tmpReg, Address(objReg, oopDesc::mark_offset_in_bytes())); // Examine the object's markword - if (LockingMode != LM_MONITOR) { - testptr(tmpReg, markWord::monitor_value); // Inflated? - jcc(Assembler::zero, Stacked); - } - - // It's inflated. - - // Despite our balanced locking property we still check that m->_owner == Self - // as java routines or native JNI code called by this thread might - // have released the lock. - // - // If there's no contention try a 1-0 exit. That is, exit without - // a costly MEMBAR or CAS. See synchronizer.cpp for details on how - // we detect and recover from the race that the 1-0 exit admits. - // - // Conceptually fast_unlock() must execute a STST|LDST "release" barrier - // before it STs null into _owner, releasing the lock. Updates - // to data protected by the critical section must be visible before - // we drop the lock (and thus before any other thread could acquire - // the lock and observe the fields protected by the lock). - // IA32's memory-model is SPO, so STs are ordered with respect to - // each other and there's no need for an explicit barrier (fence). - // See also http://gee.cs.oswego.edu/dl/jmm/cookbook.html. - Label LSuccess, LNotRecursive; - - cmpptr(Address(tmpReg, OM_OFFSET_NO_MONITOR_VALUE_TAG(recursions)), 0); - jccb(Assembler::equal, LNotRecursive); - - // Recursive inflated unlock - decrement(Address(tmpReg, OM_OFFSET_NO_MONITOR_VALUE_TAG(recursions))); - jmpb(LSuccess); - - bind(LNotRecursive); - - // Set owner to null. - // Release to satisfy the JMM - movptr(Address(tmpReg, OM_OFFSET_NO_MONITOR_VALUE_TAG(owner)), NULL_WORD); - // We need a full fence after clearing owner to avoid stranding. - // StoreLoad achieves this. - membar(StoreLoad); - - // Check if the entry_list is empty. - cmpptr(Address(tmpReg, OM_OFFSET_NO_MONITOR_VALUE_TAG(entry_list)), NULL_WORD); - jccb(Assembler::zero, LSuccess); // If so we are done. - - // Check if there is a successor. - cmpptr(Address(tmpReg, OM_OFFSET_NO_MONITOR_VALUE_TAG(succ)), NULL_WORD); - jccb(Assembler::notZero, LSuccess); // If so we are done. - - // Save the monitor pointer in the current thread, so we can try to - // reacquire the lock in SharedRuntime::monitor_exit_helper(). - andptr(tmpReg, ~(int32_t)markWord::monitor_value); - movptr(Address(r15_thread, JavaThread::unlocked_inflated_monitor_offset()), tmpReg); - - orl (boxReg, 1); // set ICC.ZF=0 to indicate failure - jmpb (DONE_LABEL); - - bind (LSuccess); - testl (boxReg, 0); // set ICC.ZF=1 to indicate success - jmpb (DONE_LABEL); - - if (LockingMode == LM_LEGACY) { - bind (Stacked); - movptr(tmpReg, Address (boxReg, 0)); // re-fetch - lock(); - cmpxchgptr(tmpReg, Address(objReg, oopDesc::mark_offset_in_bytes())); // Uses RAX which is box - // Intentional fall-thru into DONE_LABEL - } - - bind(DONE_LABEL); - - // ZFlag == 1 count in fast path - // ZFlag == 0 count in slow path - jccb(Assembler::notZero, NO_COUNT); - - bind(COUNT); - - if (LockingMode == LM_LEGACY) { - // Count monitors in fast path - decrementq(Address(r15_thread, JavaThread::held_monitor_count_offset())); - } - - xorl(tmpReg, tmpReg); // Set ZF == 1 - - bind(NO_COUNT); -} - +// box: on-stack box address -- KILLED +// rax: tmp -- KILLED +// t : tmp -- KILLED void C2_MacroAssembler::fast_lock_lightweight(Register obj, Register box, Register rax_reg, Register t, Register thread) { - assert(LockingMode == LM_LIGHTWEIGHT, "must be"); assert(rax_reg == rax, "Used for CAS"); assert_different_registers(obj, box, rax_reg, t, thread); @@ -616,8 +383,39 @@ void C2_MacroAssembler::fast_lock_lightweight(Register obj, Register box, Regist // C2 uses the value of ZF to determine the continuation. } +// obj: object to lock +// rax: tmp -- KILLED +// t : tmp - cannot be obj nor rax -- KILLED +// +// Some commentary on balanced locking: +// +// fast_lock and fast_unlock are emitted only for provably balanced lock sites. +// Methods that don't have provably balanced locking are forced to run in the +// interpreter - such methods won't be compiled to use fast_lock and fast_unlock. +// The interpreter provides two properties: +// I1: At return-time the interpreter automatically and quietly unlocks any +// objects acquired in the current activation (frame). Recall that the +// interpreter maintains an on-stack list of locks currently held by +// a frame. +// I2: If a method attempts to unlock an object that is not held by the +// frame the interpreter throws IMSX. +// +// Lets say A(), which has provably balanced locking, acquires O and then calls B(). +// B() doesn't have provably balanced locking so it runs in the interpreter. +// Control returns to A() and A() unlocks O. By I1 and I2, above, we know that O +// is still locked by A(). +// +// The only other source of unbalanced locking would be JNI. The "Java Native Interface +// Specification" states that an object locked by JNI's MonitorEnter should not be +// unlocked by "normal" java-level locking and vice-versa. The specification doesn't +// specify what will occur if a program engages in such mixed-mode locking, however. +// Arguably given that the spec legislates the JNI case as undefined our implementation +// could reasonably *avoid* checking owner in fast_unlock(). +// In the interest of performance we elide m->Owner==Self check in unlock. +// A perfectly viable alternative is to elide the owner check except when +// Xcheck:jni is enabled. + void C2_MacroAssembler::fast_unlock_lightweight(Register obj, Register reg_rax, Register t, Register thread) { - assert(LockingMode == LM_LIGHTWEIGHT, "must be"); assert(reg_rax == rax, "Used for CAS"); assert_different_registers(obj, reg_rax, t); diff --git a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp index e1652213688..950fcb75290 100644 --- a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp +++ b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp @@ -34,12 +34,7 @@ public: Assembler::AvxVectorLen vector_length_encoding(int vlen_in_bytes); // Code used by cmpFastLock and cmpFastUnlock mach instructions in .ad file. - // See full description in macroAssembler_x86.cpp. - void fast_lock(Register obj, Register box, Register tmp, - Register scr, Register cx1, Register cx2, Register thread, - Metadata* method_data); - void fast_unlock(Register obj, Register box, Register tmp); - + // See full description in c2_MacroAssembler_x86.cpp. void fast_lock_lightweight(Register obj, Register box, Register rax_reg, Register t, Register thread); void fast_unlock_lightweight(Register obj, Register reg_rax, Register t, Register thread); diff --git a/src/hotspot/cpu/x86/interp_masm_x86.cpp b/src/hotspot/cpu/x86/interp_masm_x86.cpp index 92233ee0d07..a6b4efbe4f2 100644 --- a/src/hotspot/cpu/x86/interp_masm_x86.cpp +++ b/src/hotspot/cpu/x86/interp_masm_x86.cpp @@ -1024,100 +1024,25 @@ void InterpreterMacroAssembler::get_method_counters(Register method, void InterpreterMacroAssembler::lock_object(Register lock_reg) { assert(lock_reg == c_rarg1, "The argument is only for looks. It must be c_rarg1"); - if (LockingMode == LM_MONITOR) { - call_VM_preemptable(noreg, - CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter), - lock_reg); - } else { - Label count_locking, done, slow_case; + Label done, slow_case; - const Register swap_reg = rax; // Must use rax for cmpxchg instruction - const Register tmp_reg = rbx; - const Register obj_reg = c_rarg3; // Will contain the oop - const Register rklass_decode_tmp = rscratch1; + const Register swap_reg = rax; // Must use rax for cmpxchg instruction + const Register tmp_reg = rbx; + const Register obj_reg = c_rarg3; // Will contain the oop - const int obj_offset = in_bytes(BasicObjectLock::obj_offset()); - const int lock_offset = in_bytes(BasicObjectLock::lock_offset()); - const int mark_offset = lock_offset + - BasicLock::displaced_header_offset_in_bytes(); + // Load object pointer into obj_reg + movptr(obj_reg, Address(lock_reg, BasicObjectLock::obj_offset())); - // Load object pointer into obj_reg - movptr(obj_reg, Address(lock_reg, obj_offset)); + lightweight_lock(lock_reg, obj_reg, swap_reg, tmp_reg, slow_case); + jmp(done); - if (LockingMode == LM_LIGHTWEIGHT) { - lightweight_lock(lock_reg, obj_reg, swap_reg, tmp_reg, slow_case); - } else if (LockingMode == LM_LEGACY) { - if (DiagnoseSyncOnValueBasedClasses != 0) { - load_klass(tmp_reg, obj_reg, rklass_decode_tmp); - testb(Address(tmp_reg, Klass::misc_flags_offset()), KlassFlags::_misc_is_value_based_class); - jcc(Assembler::notZero, slow_case); - } + bind(slow_case); - // Load immediate 1 into swap_reg %rax - movl(swap_reg, 1); - - // Load (object->mark() | 1) into swap_reg %rax - orptr(swap_reg, Address(obj_reg, oopDesc::mark_offset_in_bytes())); - - // Save (object->mark() | 1) into BasicLock's displaced header - movptr(Address(lock_reg, mark_offset), swap_reg); - - assert(lock_offset == 0, - "displaced header must be first word in BasicObjectLock"); - - lock(); - cmpxchgptr(lock_reg, Address(obj_reg, oopDesc::mark_offset_in_bytes())); - jcc(Assembler::zero, count_locking); - - const int zero_bits = 7; - - // Fast check for recursive lock. - // - // Can apply the optimization only if this is a stack lock - // allocated in this thread. For efficiency, we can focus on - // recently allocated stack locks (instead of reading the stack - // base and checking whether 'mark' points inside the current - // thread stack): - // 1) (mark & zero_bits) == 0, and - // 2) rsp <= mark < mark + os::pagesize() - // - // Warning: rsp + os::pagesize can overflow the stack base. We must - // neither apply the optimization for an inflated lock allocated - // just above the thread stack (this is why condition 1 matters) - // nor apply the optimization if the stack lock is inside the stack - // of another thread. The latter is avoided even in case of overflow - // because we have guard pages at the end of all stacks. Hence, if - // we go over the stack base and hit the stack of another thread, - // this should not be in a writeable area that could contain a - // stack lock allocated by that thread. As a consequence, a stack - // lock less than page size away from rsp is guaranteed to be - // owned by the current thread. - // - // These 3 tests can be done by evaluating the following - // expression: ((mark - rsp) & (zero_bits - os::vm_page_size())), - // assuming both stack pointer and pagesize have their - // least significant bits clear. - // NOTE: the mark is in swap_reg %rax as the result of cmpxchg - subptr(swap_reg, rsp); - andptr(swap_reg, zero_bits - (int)os::vm_page_size()); - - // Save the test result, for recursive case, the result is zero - movptr(Address(lock_reg, mark_offset), swap_reg); - jcc(Assembler::notZero, slow_case); - - bind(count_locking); - inc_held_monitor_count(); - } - jmp(done); - - bind(slow_case); - - // Call the runtime routine for slow case - call_VM_preemptable(noreg, - CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter), - lock_reg); - bind(done); - } + // Call the runtime routine for slow case + call_VM_preemptable(noreg, + CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter), + lock_reg); + bind(done); } @@ -1136,63 +1061,31 @@ void InterpreterMacroAssembler::lock_object(Register lock_reg) { void InterpreterMacroAssembler::unlock_object(Register lock_reg) { assert(lock_reg == c_rarg1, "The argument is only for looks. It must be c_rarg1"); - if (LockingMode == LM_MONITOR) { - call_VM_leaf(CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorexit), lock_reg); - } else { - Label count_locking, done, slow_case; + Label done, slow_case; - const Register swap_reg = rax; // Must use rax for cmpxchg instruction - const Register header_reg = c_rarg2; // Will contain the old oopMark - const Register obj_reg = c_rarg3; // Will contain the oop + const Register swap_reg = rax; // Must use rax for cmpxchg instruction + const Register header_reg = c_rarg2; // Will contain the old oopMark + const Register obj_reg = c_rarg3; // Will contain the oop - save_bcp(); // Save in case of exception + save_bcp(); // Save in case of exception - if (LockingMode != LM_LIGHTWEIGHT) { - // Convert from BasicObjectLock structure to object and BasicLock - // structure Store the BasicLock address into %rax - lea(swap_reg, Address(lock_reg, BasicObjectLock::lock_offset())); - } + // Load oop into obj_reg(%c_rarg3) + movptr(obj_reg, Address(lock_reg, BasicObjectLock::obj_offset())); - // Load oop into obj_reg(%c_rarg3) - movptr(obj_reg, Address(lock_reg, BasicObjectLock::obj_offset())); + // Free entry + movptr(Address(lock_reg, BasicObjectLock::obj_offset()), NULL_WORD); - // Free entry - movptr(Address(lock_reg, BasicObjectLock::obj_offset()), NULL_WORD); + lightweight_unlock(obj_reg, swap_reg, header_reg, slow_case); + jmp(done); - if (LockingMode == LM_LIGHTWEIGHT) { - lightweight_unlock(obj_reg, swap_reg, header_reg, slow_case); - } else if (LockingMode == LM_LEGACY) { - // Load the old header from BasicLock structure - movptr(header_reg, Address(swap_reg, - BasicLock::displaced_header_offset_in_bytes())); + bind(slow_case); + // Call the runtime routine for slow case. + movptr(Address(lock_reg, BasicObjectLock::obj_offset()), obj_reg); // restore obj + call_VM_leaf(CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorexit), lock_reg); - // Test for recursion - testptr(header_reg, header_reg); + bind(done); - // zero for recursive case - jcc(Assembler::zero, count_locking); - - // Atomic swap back the old header - lock(); - cmpxchgptr(header_reg, Address(obj_reg, oopDesc::mark_offset_in_bytes())); - - // zero for simple unlock of a stack-lock case - jcc(Assembler::notZero, slow_case); - - bind(count_locking); - dec_held_monitor_count(); - } - jmp(done); - - bind(slow_case); - // Call the runtime routine for slow case. - movptr(Address(lock_reg, BasicObjectLock::obj_offset()), obj_reg); // restore obj - call_VM_leaf(CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorexit), lock_reg); - - bind(done); - - restore_bcp(); - } + restore_bcp(); } void InterpreterMacroAssembler::test_method_data_pointer(Register mdp, diff --git a/src/hotspot/cpu/x86/sharedRuntime_x86.cpp b/src/hotspot/cpu/x86/sharedRuntime_x86.cpp index b8a4b829159..17fdfa61d36 100644 --- a/src/hotspot/cpu/x86/sharedRuntime_x86.cpp +++ b/src/hotspot/cpu/x86/sharedRuntime_x86.cpp @@ -59,17 +59,10 @@ void SharedRuntime::inline_check_hashcode_from_object_header(MacroAssembler* mas __ movptr(result, Address(obj_reg, oopDesc::mark_offset_in_bytes())); - - if (LockingMode == LM_LIGHTWEIGHT) { - if (!UseObjectMonitorTable) { - // check if monitor - __ testptr(result, markWord::monitor_value); - __ jcc(Assembler::notZero, slowCase); - } - } else { - // check if locked - __ testptr(result, markWord::unlocked_value); - __ jcc(Assembler::zero, slowCase); + if (!UseObjectMonitorTable) { + // check if monitor + __ testptr(result, markWord::monitor_value); + __ jcc(Assembler::notZero, slowCase); } // get hash diff --git a/src/hotspot/cpu/x86/sharedRuntime_x86_64.cpp b/src/hotspot/cpu/x86/sharedRuntime_x86_64.cpp index 7c7ca61f1ae..d60f535fefc 100644 --- a/src/hotspot/cpu/x86/sharedRuntime_x86_64.cpp +++ b/src/hotspot/cpu/x86/sharedRuntime_x86_64.cpp @@ -2133,7 +2133,7 @@ nmethod* SharedRuntime::generate_native_wrapper(MacroAssembler* masm, // We use the same pc/oopMap repeatedly when we call out Label native_return; - if (LockingMode != LM_LEGACY && method->is_object_wait0()) { + if (method->is_object_wait0()) { // For convenience we use the pc we want to resume to in case of preemption on Object.wait. __ set_last_Java_frame(rsp, noreg, native_return, rscratch1); } else { @@ -2174,16 +2174,11 @@ nmethod* SharedRuntime::generate_native_wrapper(MacroAssembler* masm, const Register swap_reg = rax; // Must use rax for cmpxchg instruction const Register obj_reg = rbx; // Will contain the oop const Register lock_reg = r13; // Address of compiler lock object (BasicLock) - const Register old_hdr = r13; // value of old header at unlock time Label slow_path_lock; Label lock_done; if (method->is_synchronized()) { - Label count_mon; - - const int mark_word_offset = BasicLock::displaced_header_offset_in_bytes(); - // Get the handle (the 2nd argument) __ mov(oop_handle_reg, c_rarg1); @@ -2194,47 +2189,7 @@ nmethod* SharedRuntime::generate_native_wrapper(MacroAssembler* masm, // Load the oop from the handle __ movptr(obj_reg, Address(oop_handle_reg, 0)); - if (LockingMode == LM_MONITOR) { - __ jmp(slow_path_lock); - } else if (LockingMode == LM_LEGACY) { - // Load immediate 1 into swap_reg %rax - __ movl(swap_reg, 1); - - // Load (object->mark() | 1) into swap_reg %rax - __ orptr(swap_reg, Address(obj_reg, oopDesc::mark_offset_in_bytes())); - - // Save (object->mark() | 1) into BasicLock's displaced header - __ movptr(Address(lock_reg, mark_word_offset), swap_reg); - - // src -> dest iff dest == rax else rax <- dest - __ lock(); - __ cmpxchgptr(lock_reg, Address(obj_reg, oopDesc::mark_offset_in_bytes())); - __ jcc(Assembler::equal, count_mon); - - // Hmm should this move to the slow path code area??? - - // Test if the oopMark is an obvious stack pointer, i.e., - // 1) (mark & 3) == 0, and - // 2) rsp <= mark < mark + os::pagesize() - // These 3 tests can be done by evaluating the following - // expression: ((mark - rsp) & (3 - os::vm_page_size())), - // assuming both stack pointer and pagesize have their - // least significant 2 bits clear. - // NOTE: the oopMark is in swap_reg %rax as the result of cmpxchg - - __ subptr(swap_reg, rsp); - __ andptr(swap_reg, 3 - (int)os::vm_page_size()); - - // Save the test result, for recursive case, the result is zero - __ movptr(Address(lock_reg, mark_word_offset), swap_reg); - __ jcc(Assembler::notEqual, slow_path_lock); - - __ bind(count_mon); - __ inc_held_monitor_count(); - } else { - assert(LockingMode == LM_LIGHTWEIGHT, "must be"); - __ lightweight_lock(lock_reg, obj_reg, swap_reg, rscratch1, slow_path_lock); - } + __ lightweight_lock(lock_reg, obj_reg, swap_reg, rscratch1, slow_path_lock); // Slow path will re-enter here __ bind(lock_done); @@ -2322,7 +2277,7 @@ nmethod* SharedRuntime::generate_native_wrapper(MacroAssembler* masm, // change thread state __ movl(Address(r15_thread, JavaThread::thread_state_offset()), _thread_in_Java); - if (LockingMode != LM_LEGACY && method->is_object_wait0()) { + if (method->is_object_wait0()) { // Check preemption for Object.wait() __ movptr(rscratch1, Address(r15_thread, JavaThread::preempt_alternate_return_offset())); __ cmpptr(rscratch1, NULL_WORD); @@ -2354,38 +2309,12 @@ nmethod* SharedRuntime::generate_native_wrapper(MacroAssembler* masm, // Get locked oop from the handle we passed to jni __ movptr(obj_reg, Address(oop_handle_reg, 0)); - if (LockingMode == LM_LEGACY) { - Label not_recur; - // Simple recursive lock? - __ cmpptr(Address(rsp, lock_slot_offset * VMRegImpl::stack_slot_size), NULL_WORD); - __ jcc(Assembler::notEqual, not_recur); - __ dec_held_monitor_count(); - __ jmpb(fast_done); - __ bind(not_recur); - } - // Must save rax if it is live now because cmpxchg must use it if (ret_type != T_FLOAT && ret_type != T_DOUBLE && ret_type != T_VOID) { save_native_result(masm, ret_type, stack_slots); } - if (LockingMode == LM_MONITOR) { - __ jmp(slow_path_unlock); - } else if (LockingMode == LM_LEGACY) { - // get address of the stack lock - __ lea(rax, Address(rsp, lock_slot_offset * VMRegImpl::stack_slot_size)); - // get old displaced header - __ movptr(old_hdr, Address(rax, 0)); - - // Atomic swap old header if oop still contains the stack lock - __ lock(); - __ cmpxchgptr(old_hdr, Address(obj_reg, oopDesc::mark_offset_in_bytes())); - __ jcc(Assembler::notEqual, slow_path_unlock); - __ dec_held_monitor_count(); - } else { - assert(LockingMode == LM_LIGHTWEIGHT, "must be"); - __ lightweight_unlock(obj_reg, swap_reg, lock_reg, slow_path_unlock); - } + __ lightweight_unlock(obj_reg, swap_reg, lock_reg, slow_path_unlock); // slow path re-enters here __ bind(unlock_done); diff --git a/src/hotspot/cpu/x86/templateInterpreterGenerator_x86.cpp b/src/hotspot/cpu/x86/templateInterpreterGenerator_x86.cpp index bd061d45fbd..47ef0aef2bb 100644 --- a/src/hotspot/cpu/x86/templateInterpreterGenerator_x86.cpp +++ b/src/hotspot/cpu/x86/templateInterpreterGenerator_x86.cpp @@ -1017,21 +1017,16 @@ address TemplateInterpreterGenerator::generate_native_entry(bool synchronized) { // change thread state __ movl(Address(thread, JavaThread::thread_state_offset()), _thread_in_Java); - if (LockingMode != LM_LEGACY) { - // Check preemption for Object.wait() - Label not_preempted; - __ movptr(rscratch1, Address(r15_thread, JavaThread::preempt_alternate_return_offset())); - __ cmpptr(rscratch1, NULL_WORD); - __ jccb(Assembler::equal, not_preempted); - __ movptr(Address(r15_thread, JavaThread::preempt_alternate_return_offset()), NULL_WORD); - __ jmp(rscratch1); - __ bind(native_return); - __ restore_after_resume(true /* is_native */); - __ bind(not_preempted); - } else { - // any pc will do so just use this one for LM_LEGACY to keep code together. - __ bind(native_return); - } + // Check preemption for Object.wait() + Label not_preempted; + __ movptr(rscratch1, Address(r15_thread, JavaThread::preempt_alternate_return_offset())); + __ cmpptr(rscratch1, NULL_WORD); + __ jccb(Assembler::equal, not_preempted); + __ movptr(Address(r15_thread, JavaThread::preempt_alternate_return_offset()), NULL_WORD); + __ jmp(rscratch1); + __ bind(native_return); + __ restore_after_resume(true /* is_native */); + __ bind(not_preempted); // reset_last_Java_frame __ reset_last_Java_frame(true); diff --git a/src/hotspot/cpu/x86/x86_64.ad b/src/hotspot/cpu/x86/x86_64.ad index 5b5292fbde2..932dc9e1ca7 100644 --- a/src/hotspot/cpu/x86/x86_64.ad +++ b/src/hotspot/cpu/x86/x86_64.ad @@ -14073,33 +14073,7 @@ instruct jmpConUCF2_short(cmpOpUCF2 cop, rFlagsRegUCF cmp, label labl) %{ // ============================================================================ // inlined locking and unlocking -instruct cmpFastLock(rFlagsReg cr, rRegP object, rbx_RegP box, rax_RegI tmp, rRegP scr) %{ - predicate(LockingMode != LM_LIGHTWEIGHT); - match(Set cr (FastLock object box)); - effect(TEMP tmp, TEMP scr, USE_KILL box); - ins_cost(300); - format %{ "fastlock $object,$box\t! kills $box,$tmp,$scr" %} - ins_encode %{ - __ fast_lock($object$$Register, $box$$Register, $tmp$$Register, - $scr$$Register, noreg, noreg, r15_thread, nullptr); - %} - ins_pipe(pipe_slow); -%} - -instruct cmpFastUnlock(rFlagsReg cr, rRegP object, rax_RegP box, rRegP tmp) %{ - predicate(LockingMode != LM_LIGHTWEIGHT); - match(Set cr (FastUnlock object box)); - effect(TEMP tmp, USE_KILL box); - ins_cost(300); - format %{ "fastunlock $object,$box\t! kills $box,$tmp" %} - ins_encode %{ - __ fast_unlock($object$$Register, $box$$Register, $tmp$$Register); - %} - ins_pipe(pipe_slow); -%} - instruct cmpFastLockLightweight(rFlagsReg cr, rRegP object, rbx_RegP box, rax_RegI rax_reg, rRegP tmp) %{ - predicate(LockingMode == LM_LIGHTWEIGHT); match(Set cr (FastLock object box)); effect(TEMP rax_reg, TEMP tmp, USE_KILL box); ins_cost(300); @@ -14111,7 +14085,6 @@ instruct cmpFastLockLightweight(rFlagsReg cr, rRegP object, rbx_RegP box, rax_Re %} instruct cmpFastUnlockLightweight(rFlagsReg cr, rRegP object, rax_RegP rax_reg, rRegP tmp) %{ - predicate(LockingMode == LM_LIGHTWEIGHT); match(Set cr (FastUnlock object rax_reg)); effect(TEMP tmp, USE_KILL rax_reg); ins_cost(300); From 3c0eed8e476e4da540e4ea44ee966f278e04a067 Mon Sep 17 00:00:00 2001 From: Fredrik Bredberg Date: Tue, 12 Aug 2025 08:45:36 +0000 Subject: [PATCH 097/580] 8364406: Remove LockingMode related code from aarch64 Reviewed-by: aph, dholmes --- src/hotspot/cpu/aarch64/aarch64.ad | 34 --- .../cpu/aarch64/c1_LIRAssembler_aarch64.cpp | 14 +- .../cpu/aarch64/c1_MacroAssembler_aarch64.cpp | 81 +------ .../cpu/aarch64/c2_MacroAssembler_aarch64.cpp | 208 ------------------ .../cpu/aarch64/c2_MacroAssembler_aarch64.hpp | 3 - .../cpu/aarch64/interp_masm_aarch64.cpp | 171 +++----------- .../cpu/aarch64/macroAssembler_aarch64.cpp | 2 - .../cpu/aarch64/sharedRuntime_aarch64.cpp | 75 +------ .../templateInterpreterGenerator_aarch64.cpp | 27 +-- 9 files changed, 52 insertions(+), 563 deletions(-) diff --git a/src/hotspot/cpu/aarch64/aarch64.ad b/src/hotspot/cpu/aarch64/aarch64.ad index 517da8066de..9697ac31350 100644 --- a/src/hotspot/cpu/aarch64/aarch64.ad +++ b/src/hotspot/cpu/aarch64/aarch64.ad @@ -16281,41 +16281,8 @@ instruct branchLoopEnd(cmpOp cmp, rFlagsReg cr, label lbl) // ============================================================================ // inlined locking and unlocking -instruct cmpFastLock(rFlagsReg cr, iRegP object, iRegP box, iRegPNoSp tmp, iRegPNoSp tmp2, iRegPNoSp tmp3) -%{ - predicate(LockingMode != LM_LIGHTWEIGHT); - match(Set cr (FastLock object box)); - effect(TEMP tmp, TEMP tmp2, TEMP tmp3); - - ins_cost(5 * INSN_COST); - format %{ "fastlock $object,$box\t! kills $tmp,$tmp2,$tmp3" %} - - ins_encode %{ - __ fast_lock($object$$Register, $box$$Register, $tmp$$Register, $tmp2$$Register, $tmp3$$Register); - %} - - ins_pipe(pipe_serial); -%} - -instruct cmpFastUnlock(rFlagsReg cr, iRegP object, iRegP box, iRegPNoSp tmp, iRegPNoSp tmp2) -%{ - predicate(LockingMode != LM_LIGHTWEIGHT); - match(Set cr (FastUnlock object box)); - effect(TEMP tmp, TEMP tmp2); - - ins_cost(5 * INSN_COST); - format %{ "fastunlock $object,$box\t! kills $tmp, $tmp2" %} - - ins_encode %{ - __ fast_unlock($object$$Register, $box$$Register, $tmp$$Register, $tmp2$$Register); - %} - - ins_pipe(pipe_serial); -%} - instruct cmpFastLockLightweight(rFlagsReg cr, iRegP object, iRegP box, iRegPNoSp tmp, iRegPNoSp tmp2, iRegPNoSp tmp3) %{ - predicate(LockingMode == LM_LIGHTWEIGHT); match(Set cr (FastLock object box)); effect(TEMP tmp, TEMP tmp2, TEMP tmp3); @@ -16331,7 +16298,6 @@ instruct cmpFastLockLightweight(rFlagsReg cr, iRegP object, iRegP box, iRegPNoSp instruct cmpFastUnlockLightweight(rFlagsReg cr, iRegP object, iRegP box, iRegPNoSp tmp, iRegPNoSp tmp2, iRegPNoSp tmp3) %{ - predicate(LockingMode == LM_LIGHTWEIGHT); match(Set cr (FastUnlock object box)); effect(TEMP tmp, TEMP tmp2, TEMP tmp3); diff --git a/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp index 31f1e97002a..bdba957d1df 100644 --- a/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp @@ -410,11 +410,7 @@ int LIR_Assembler::emit_unwind_handler() { if (method()->is_synchronized()) { monitor_address(0, FrameMap::r0_opr); stub = new MonitorExitStub(FrameMap::r0_opr, true, 0); - if (LockingMode == LM_MONITOR) { - __ b(*stub->entry()); - } else { - __ unlock_object(r5, r4, r0, r6, *stub->entry()); - } + __ unlock_object(r5, r4, r0, r6, *stub->entry()); __ bind(*stub->continuation()); } @@ -2484,13 +2480,7 @@ void LIR_Assembler::emit_lock(LIR_OpLock* op) { Register hdr = op->hdr_opr()->as_register(); Register lock = op->lock_opr()->as_register(); Register temp = op->scratch_opr()->as_register(); - if (LockingMode == LM_MONITOR) { - if (op->info() != nullptr) { - add_debug_info_for_null_check_here(op->info()); - __ null_check(obj, -1); - } - __ b(*op->stub()->entry()); - } else if (op->code() == lir_lock) { + if (op->code() == lir_lock) { assert(BasicLock::displaced_header_offset_in_bytes() == 0, "lock_reg must point to the displaced header"); // add debug info for NullPointerException only if one is possible int null_check_offset = __ lock_object(hdr, obj, lock, temp, *op->stub()->entry()); diff --git a/src/hotspot/cpu/aarch64/c1_MacroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/c1_MacroAssembler_aarch64.cpp index b0f9d6d6c41..8a79274b2ff 100644 --- a/src/hotspot/cpu/aarch64/c1_MacroAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/c1_MacroAssembler_aarch64.cpp @@ -60,8 +60,6 @@ void C1_MacroAssembler::float_cmp(bool is_float, int unordered_result, } int C1_MacroAssembler::lock_object(Register hdr, Register obj, Register disp_hdr, Register temp, Label& slow_case) { - const int aligned_mask = BytesPerWord -1; - const int hdr_offset = oopDesc::mark_offset_in_bytes(); assert_different_registers(hdr, obj, disp_hdr, temp, rscratch2); int null_check_offset = -1; @@ -72,95 +70,20 @@ int C1_MacroAssembler::lock_object(Register hdr, Register obj, Register disp_hdr null_check_offset = offset(); - if (LockingMode == LM_LIGHTWEIGHT) { - lightweight_lock(disp_hdr, obj, hdr, temp, rscratch2, slow_case); - } else if (LockingMode == LM_LEGACY) { + lightweight_lock(disp_hdr, obj, hdr, temp, rscratch2, slow_case); - if (DiagnoseSyncOnValueBasedClasses != 0) { - load_klass(hdr, obj); - ldrb(hdr, Address(hdr, Klass::misc_flags_offset())); - tst(hdr, KlassFlags::_misc_is_value_based_class); - br(Assembler::NE, slow_case); - } - - Label done; - // Load object header - ldr(hdr, Address(obj, hdr_offset)); - // and mark it as unlocked - orr(hdr, hdr, markWord::unlocked_value); - // save unlocked object header into the displaced header location on the stack - str(hdr, Address(disp_hdr, 0)); - // test if object header is still the same (i.e. unlocked), and if so, store the - // displaced header address in the object header - if it is not the same, get the - // object header instead - lea(rscratch2, Address(obj, hdr_offset)); - cmpxchgptr(hdr, disp_hdr, rscratch2, rscratch1, done, /*fallthough*/nullptr); - // if the object header was the same, we're done - // if the object header was not the same, it is now in the hdr register - // => test if it is a stack pointer into the same stack (recursive locking), i.e.: - // - // 1) (hdr & aligned_mask) == 0 - // 2) sp <= hdr - // 3) hdr <= sp + page_size - // - // these 3 tests can be done by evaluating the following expression: - // - // (hdr - sp) & (aligned_mask - page_size) - // - // assuming both the stack pointer and page_size have their least - // significant 2 bits cleared and page_size is a power of 2 - mov(rscratch1, sp); - sub(hdr, hdr, rscratch1); - ands(hdr, hdr, aligned_mask - (int)os::vm_page_size()); - // for recursive locking, the result is zero => save it in the displaced header - // location (null in the displaced hdr location indicates recursive locking) - str(hdr, Address(disp_hdr, 0)); - // otherwise we don't care about the result and handle locking via runtime call - cbnz(hdr, slow_case); - // done - bind(done); - inc_held_monitor_count(rscratch1); - } return null_check_offset; } void C1_MacroAssembler::unlock_object(Register hdr, Register obj, Register disp_hdr, Register temp, Label& slow_case) { - const int aligned_mask = BytesPerWord -1; - const int hdr_offset = oopDesc::mark_offset_in_bytes(); assert_different_registers(hdr, obj, disp_hdr, temp, rscratch2); - Label done; - - if (LockingMode != LM_LIGHTWEIGHT) { - // load displaced header - ldr(hdr, Address(disp_hdr, 0)); - // if the loaded hdr is null we had recursive locking - // if we had recursive locking, we are done - cbz(hdr, done); - } // load object ldr(obj, Address(disp_hdr, BasicObjectLock::obj_offset())); verify_oop(obj); - if (LockingMode == LM_LIGHTWEIGHT) { - lightweight_unlock(obj, hdr, temp, rscratch2, slow_case); - } else if (LockingMode == LM_LEGACY) { - // test if object header is pointing to the displaced header, and if so, restore - // the displaced header in the object - if the object header is not pointing to - // the displaced header, get the object header instead - // if the object header was not pointing to the displaced header, - // we do unlocking via runtime call - if (hdr_offset) { - lea(rscratch1, Address(obj, hdr_offset)); - cmpxchgptr(disp_hdr, hdr, rscratch1, rscratch2, done, &slow_case); - } else { - cmpxchgptr(disp_hdr, hdr, obj, rscratch2, done, &slow_case); - } - // done - bind(done); - dec_held_monitor_count(rscratch1); - } + lightweight_unlock(obj, hdr, temp, rscratch2, slow_case); } diff --git a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp index e87cb478c8f..b1562c54f4e 100644 --- a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp @@ -147,215 +147,8 @@ address C2_MacroAssembler::arrays_hashcode(Register ary, Register cnt, Register return pc(); } -void C2_MacroAssembler::fast_lock(Register objectReg, Register boxReg, Register tmpReg, - Register tmp2Reg, Register tmp3Reg) { - Register oop = objectReg; - Register box = boxReg; - Register disp_hdr = tmpReg; - Register tmp = tmp2Reg; - Label cont; - Label object_has_monitor; - Label count, no_count; - - assert(LockingMode != LM_LIGHTWEIGHT, "lightweight locking should use fast_lock_lightweight"); - assert_different_registers(oop, box, tmp, disp_hdr, rscratch2); - - // Load markWord from object into displaced_header. - ldr(disp_hdr, Address(oop, oopDesc::mark_offset_in_bytes())); - - if (DiagnoseSyncOnValueBasedClasses != 0) { - load_klass(tmp, oop); - ldrb(tmp, Address(tmp, Klass::misc_flags_offset())); - tst(tmp, KlassFlags::_misc_is_value_based_class); - br(Assembler::NE, cont); - } - - // Check for existing monitor - tbnz(disp_hdr, exact_log2(markWord::monitor_value), object_has_monitor); - - if (LockingMode == LM_MONITOR) { - tst(oop, oop); // Set NE to indicate 'failure' -> take slow-path. We know that oop != 0. - b(cont); - } else { - assert(LockingMode == LM_LEGACY, "must be"); - // Set tmp to be (markWord of object | UNLOCK_VALUE). - orr(tmp, disp_hdr, markWord::unlocked_value); - - // Initialize the box. (Must happen before we update the object mark!) - str(tmp, Address(box, BasicLock::displaced_header_offset_in_bytes())); - - // Compare object markWord with an unlocked value (tmp) and if - // equal exchange the stack address of our box with object markWord. - // On failure disp_hdr contains the possibly locked markWord. - cmpxchg(oop, tmp, box, Assembler::xword, /*acquire*/ true, - /*release*/ true, /*weak*/ false, disp_hdr); - br(Assembler::EQ, cont); - - assert(oopDesc::mark_offset_in_bytes() == 0, "offset of _mark is not 0"); - - // If the compare-and-exchange succeeded, then we found an unlocked - // object, will have now locked it will continue at label cont - - // Check if the owner is self by comparing the value in the - // markWord of object (disp_hdr) with the stack pointer. - mov(rscratch1, sp); - sub(disp_hdr, disp_hdr, rscratch1); - mov(tmp, (address) (~(os::vm_page_size()-1) | markWord::lock_mask_in_place)); - // If condition is true we are cont and hence we can store 0 as the - // displaced header in the box, which indicates that it is a recursive lock. - ands(tmp/*==0?*/, disp_hdr, tmp); // Sets flags for result - str(tmp/*==0, perhaps*/, Address(box, BasicLock::displaced_header_offset_in_bytes())); - b(cont); - } - - // Handle existing monitor. - bind(object_has_monitor); - - // Try to CAS owner (no owner => current thread's _monitor_owner_id). - ldr(rscratch2, Address(rthread, JavaThread::monitor_owner_id_offset())); - add(tmp, disp_hdr, (in_bytes(ObjectMonitor::owner_offset())-markWord::monitor_value)); - cmpxchg(tmp, zr, rscratch2, Assembler::xword, /*acquire*/ true, - /*release*/ true, /*weak*/ false, tmp3Reg); // Sets flags for result - - // Store a non-null value into the box to avoid looking like a re-entrant - // lock. The fast-path monitor unlock code checks for - // markWord::monitor_value so use markWord::unused_mark which has the - // relevant bit set, and also matches ObjectSynchronizer::enter. - mov(tmp, (address)markWord::unused_mark().value()); - str(tmp, Address(box, BasicLock::displaced_header_offset_in_bytes())); - - br(Assembler::EQ, cont); // CAS success means locking succeeded - - cmp(tmp3Reg, rscratch2); - br(Assembler::NE, cont); // Check for recursive locking - - // Recursive lock case - increment(Address(disp_hdr, in_bytes(ObjectMonitor::recursions_offset()) - markWord::monitor_value), 1); - // flag == EQ still from the cmp above, checking if this is a reentrant lock - - bind(cont); - // flag == EQ indicates success - // flag == NE indicates failure - br(Assembler::NE, no_count); - - bind(count); - if (LockingMode == LM_LEGACY) { - inc_held_monitor_count(rscratch1); - } - - bind(no_count); -} - -void C2_MacroAssembler::fast_unlock(Register objectReg, Register boxReg, Register tmpReg, - Register tmp2Reg) { - Register oop = objectReg; - Register box = boxReg; - Register disp_hdr = tmpReg; - Register owner_addr = tmpReg; - Register tmp = tmp2Reg; - Label cont; - Label object_has_monitor; - Label count, no_count; - Label unlocked; - - assert(LockingMode != LM_LIGHTWEIGHT, "lightweight locking should use fast_unlock_lightweight"); - assert_different_registers(oop, box, tmp, disp_hdr); - - if (LockingMode == LM_LEGACY) { - // Find the lock address and load the displaced header from the stack. - ldr(disp_hdr, Address(box, BasicLock::displaced_header_offset_in_bytes())); - - // If the displaced header is 0, we have a recursive unlock. - cmp(disp_hdr, zr); - br(Assembler::EQ, cont); - } - - // Handle existing monitor. - ldr(tmp, Address(oop, oopDesc::mark_offset_in_bytes())); - tbnz(tmp, exact_log2(markWord::monitor_value), object_has_monitor); - - if (LockingMode == LM_MONITOR) { - tst(oop, oop); // Set NE to indicate 'failure' -> take slow-path. We know that oop != 0. - b(cont); - } else { - assert(LockingMode == LM_LEGACY, "must be"); - // Check if it is still a light weight lock, this is is true if we - // see the stack address of the basicLock in the markWord of the - // object. - - cmpxchg(oop, box, disp_hdr, Assembler::xword, /*acquire*/ false, - /*release*/ true, /*weak*/ false, tmp); - b(cont); - } - - assert(oopDesc::mark_offset_in_bytes() == 0, "offset of _mark is not 0"); - - // Handle existing monitor. - bind(object_has_monitor); - STATIC_ASSERT(markWord::monitor_value <= INT_MAX); - add(tmp, tmp, -(int)markWord::monitor_value); // monitor - - ldr(disp_hdr, Address(tmp, ObjectMonitor::recursions_offset())); - - Label notRecursive; - cbz(disp_hdr, notRecursive); - - // Recursive lock - sub(disp_hdr, disp_hdr, 1u); - str(disp_hdr, Address(tmp, ObjectMonitor::recursions_offset())); - cmp(disp_hdr, disp_hdr); // Sets flags for result - b(cont); - - bind(notRecursive); - - // Compute owner address. - lea(owner_addr, Address(tmp, ObjectMonitor::owner_offset())); - - // Set owner to null. - // Release to satisfy the JMM - stlr(zr, owner_addr); - // We need a full fence after clearing owner to avoid stranding. - // StoreLoad achieves this. - membar(StoreLoad); - - // Check if the entry_list is empty. - ldr(rscratch1, Address(tmp, ObjectMonitor::entry_list_offset())); - cmp(rscratch1, zr); - br(Assembler::EQ, cont); // If so we are done. - - // Check if there is a successor. - ldr(rscratch1, Address(tmp, ObjectMonitor::succ_offset())); - cmp(rscratch1, zr); - br(Assembler::NE, unlocked); // If so we are done. - - // Save the monitor pointer in the current thread, so we can try to - // reacquire the lock in SharedRuntime::monitor_exit_helper(). - str(tmp, Address(rthread, JavaThread::unlocked_inflated_monitor_offset())); - - cmp(zr, rthread); // Set Flag to NE => slow path - b(cont); - - bind(unlocked); - cmp(zr, zr); // Set Flag to EQ => fast path - - // Intentional fall-through - - bind(cont); - // flag == EQ indicates success - // flag == NE indicates failure - br(Assembler::NE, no_count); - - bind(count); - if (LockingMode == LM_LEGACY) { - dec_held_monitor_count(rscratch1); - } - - bind(no_count); -} - void C2_MacroAssembler::fast_lock_lightweight(Register obj, Register box, Register t1, Register t2, Register t3) { - assert(LockingMode == LM_LIGHTWEIGHT, "must be"); assert_different_registers(obj, box, t1, t2, t3, rscratch2); // Handle inflated monitor. @@ -512,7 +305,6 @@ void C2_MacroAssembler::fast_lock_lightweight(Register obj, Register box, Regist void C2_MacroAssembler::fast_unlock_lightweight(Register obj, Register box, Register t1, Register t2, Register t3) { - assert(LockingMode == LM_LIGHTWEIGHT, "must be"); assert_different_registers(obj, box, t1, t2, t3); // Handle inflated monitor. diff --git a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.hpp b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.hpp index 233f600cb14..0403a27910f 100644 --- a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.hpp @@ -51,9 +51,6 @@ FloatRegister vmul3, FloatRegister vpow, FloatRegister vpowm, BasicType eltype); - // Code used by cmpFastLock and cmpFastUnlock mach instructions in .ad file. - void fast_lock(Register object, Register box, Register tmp, Register tmp2, Register tmp3); - void fast_unlock(Register object, Register box, Register tmp, Register tmp2); // Code used by cmpFastLockLightweight and cmpFastUnlockLightweight mach instructions in .ad file. void fast_lock_lightweight(Register object, Register box, Register t1, Register t2, Register t3); void fast_unlock_lightweight(Register object, Register box, Register t1, Register t2, Register t3); diff --git a/src/hotspot/cpu/aarch64/interp_masm_aarch64.cpp b/src/hotspot/cpu/aarch64/interp_masm_aarch64.cpp index e14829b7c89..607912e6e49 100644 --- a/src/hotspot/cpu/aarch64/interp_masm_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/interp_masm_aarch64.cpp @@ -691,104 +691,27 @@ void InterpreterMacroAssembler::leave_jfr_critical_section() { void InterpreterMacroAssembler::lock_object(Register lock_reg) { assert(lock_reg == c_rarg1, "The argument is only for looks. It must be c_rarg1"); - if (LockingMode == LM_MONITOR) { - call_VM_preemptable(noreg, - CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter), - lock_reg); - } else { - Label count, done; - const Register swap_reg = r0; - const Register tmp = c_rarg2; - const Register obj_reg = c_rarg3; // Will contain the oop - const Register tmp2 = c_rarg4; - const Register tmp3 = c_rarg5; + const Register tmp = c_rarg2; + const Register obj_reg = c_rarg3; // Will contain the oop + const Register tmp2 = c_rarg4; + const Register tmp3 = c_rarg5; - const int obj_offset = in_bytes(BasicObjectLock::obj_offset()); - const int lock_offset = in_bytes(BasicObjectLock::lock_offset()); - const int mark_offset = lock_offset + - BasicLock::displaced_header_offset_in_bytes(); + // Load object pointer into obj_reg %c_rarg3 + ldr(obj_reg, Address(lock_reg, BasicObjectLock::obj_offset())); - Label slow_case; + Label slow_case, done; + lightweight_lock(lock_reg, obj_reg, tmp, tmp2, tmp3, slow_case); + b(done); - // Load object pointer into obj_reg %c_rarg3 - ldr(obj_reg, Address(lock_reg, obj_offset)); + bind(slow_case); - if (LockingMode == LM_LIGHTWEIGHT) { - lightweight_lock(lock_reg, obj_reg, tmp, tmp2, tmp3, slow_case); - b(done); - } else if (LockingMode == LM_LEGACY) { + // Call the runtime routine for slow case + call_VM_preemptable(noreg, + CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter), + lock_reg); - if (DiagnoseSyncOnValueBasedClasses != 0) { - load_klass(tmp, obj_reg); - ldrb(tmp, Address(tmp, Klass::misc_flags_offset())); - tst(tmp, KlassFlags::_misc_is_value_based_class); - br(Assembler::NE, slow_case); - } - - // Load (object->mark() | 1) into swap_reg - ldr(rscratch1, Address(obj_reg, oopDesc::mark_offset_in_bytes())); - orr(swap_reg, rscratch1, 1); - - // Save (object->mark() | 1) into BasicLock's displaced header - str(swap_reg, Address(lock_reg, mark_offset)); - - assert(lock_offset == 0, - "displached header must be first word in BasicObjectLock"); - - Label fail; - cmpxchg_obj_header(swap_reg, lock_reg, obj_reg, rscratch1, count, /*fallthrough*/nullptr); - - // Fast check for recursive lock. - // - // Can apply the optimization only if this is a stack lock - // allocated in this thread. For efficiency, we can focus on - // recently allocated stack locks (instead of reading the stack - // base and checking whether 'mark' points inside the current - // thread stack): - // 1) (mark & 7) == 0, and - // 2) sp <= mark < mark + os::pagesize() - // - // Warning: sp + os::pagesize can overflow the stack base. We must - // neither apply the optimization for an inflated lock allocated - // just above the thread stack (this is why condition 1 matters) - // nor apply the optimization if the stack lock is inside the stack - // of another thread. The latter is avoided even in case of overflow - // because we have guard pages at the end of all stacks. Hence, if - // we go over the stack base and hit the stack of another thread, - // this should not be in a writeable area that could contain a - // stack lock allocated by that thread. As a consequence, a stack - // lock less than page size away from sp is guaranteed to be - // owned by the current thread. - // - // These 3 tests can be done by evaluating the following - // expression: ((mark - sp) & (7 - os::vm_page_size())), - // assuming both stack pointer and pagesize have their - // least significant 3 bits clear. - // NOTE: the mark is in swap_reg %r0 as the result of cmpxchg - // NOTE2: aarch64 does not like to subtract sp from rn so take a - // copy - mov(rscratch1, sp); - sub(swap_reg, swap_reg, rscratch1); - ands(swap_reg, swap_reg, (uint64_t)(7 - (int)os::vm_page_size())); - - // Save the test result, for recursive case, the result is zero - str(swap_reg, Address(lock_reg, mark_offset)); - br(Assembler::NE, slow_case); - - bind(count); - inc_held_monitor_count(rscratch1); - b(done); - } - bind(slow_case); - - // Call the runtime routine for slow case - call_VM_preemptable(noreg, - CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter), - lock_reg); - - bind(done); - } + bind(done); } @@ -807,57 +730,29 @@ void InterpreterMacroAssembler::unlock_object(Register lock_reg) { assert(lock_reg == c_rarg1, "The argument is only for looks. It must be rarg1"); - if (LockingMode == LM_MONITOR) { - call_VM_leaf(CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorexit), lock_reg); - } else { - Label count, done; + const Register swap_reg = r0; + const Register header_reg = c_rarg2; // Will contain the old oopMark + const Register obj_reg = c_rarg3; // Will contain the oop + const Register tmp_reg = c_rarg4; // Temporary used by lightweight_unlock - const Register swap_reg = r0; - const Register header_reg = c_rarg2; // Will contain the old oopMark - const Register obj_reg = c_rarg3; // Will contain the oop - const Register tmp_reg = c_rarg4; // Temporary used by lightweight_unlock + save_bcp(); // Save in case of exception - save_bcp(); // Save in case of exception + // Load oop into obj_reg(%c_rarg3) + ldr(obj_reg, Address(lock_reg, BasicObjectLock::obj_offset())); - if (LockingMode != LM_LIGHTWEIGHT) { - // Convert from BasicObjectLock structure to object and BasicLock - // structure Store the BasicLock address into %r0 - lea(swap_reg, Address(lock_reg, BasicObjectLock::lock_offset())); - } + // Free entry + str(zr, Address(lock_reg, BasicObjectLock::obj_offset())); - // Load oop into obj_reg(%c_rarg3) - ldr(obj_reg, Address(lock_reg, BasicObjectLock::obj_offset())); + Label slow_case, done; + lightweight_unlock(obj_reg, header_reg, swap_reg, tmp_reg, slow_case); + b(done); - // Free entry - str(zr, Address(lock_reg, BasicObjectLock::obj_offset())); - - Label slow_case; - if (LockingMode == LM_LIGHTWEIGHT) { - lightweight_unlock(obj_reg, header_reg, swap_reg, tmp_reg, slow_case); - b(done); - } else if (LockingMode == LM_LEGACY) { - // Load the old header from BasicLock structure - ldr(header_reg, Address(swap_reg, - BasicLock::displaced_header_offset_in_bytes())); - - // Test for recursion - cbz(header_reg, count); - - // Atomic swap back the old header - cmpxchg_obj_header(swap_reg, header_reg, obj_reg, rscratch1, count, &slow_case); - - bind(count); - dec_held_monitor_count(rscratch1); - b(done); - } - - bind(slow_case); - // Call the runtime routine for slow case. - str(obj_reg, Address(lock_reg, BasicObjectLock::obj_offset())); // restore obj - call_VM_leaf(CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorexit), lock_reg); - bind(done); - restore_bcp(); - } + bind(slow_case); + // Call the runtime routine for slow case. + str(obj_reg, Address(lock_reg, BasicObjectLock::obj_offset())); // restore obj + call_VM_leaf(CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorexit), lock_reg); + bind(done); + restore_bcp(); } void InterpreterMacroAssembler::test_method_data_pointer(Register mdp, diff --git a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp index 6ae6861e38b..e51b3c530de 100644 --- a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp @@ -7097,7 +7097,6 @@ void MacroAssembler::double_move(VMRegPair src, VMRegPair dst, Register tmp) { // - t1, t2, t3: temporary registers, will be destroyed // - slow: branched to if locking fails, absolute offset may larger than 32KB (imm14 encoding). void MacroAssembler::lightweight_lock(Register basic_lock, Register obj, Register t1, Register t2, Register t3, Label& slow) { - assert(LockingMode == LM_LIGHTWEIGHT, "only used with new lightweight locking"); assert_different_registers(basic_lock, obj, t1, t2, t3, rscratch1); Label push; @@ -7157,7 +7156,6 @@ void MacroAssembler::lightweight_lock(Register basic_lock, Register obj, Registe // - t1, t2, t3: temporary registers // - slow: branched to if unlocking fails, absolute offset may larger than 32KB (imm14 encoding). void MacroAssembler::lightweight_unlock(Register obj, Register t1, Register t2, Register t3, Label& slow) { - assert(LockingMode == LM_LIGHTWEIGHT, "only used with new lightweight locking"); // cmpxchg clobbers rscratch1. assert_different_registers(obj, t1, t2, t3, rscratch1); diff --git a/src/hotspot/cpu/aarch64/sharedRuntime_aarch64.cpp b/src/hotspot/cpu/aarch64/sharedRuntime_aarch64.cpp index 00f97ed04e4..748c3e8fb11 100644 --- a/src/hotspot/cpu/aarch64/sharedRuntime_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/sharedRuntime_aarch64.cpp @@ -1721,7 +1721,7 @@ nmethod* SharedRuntime::generate_native_wrapper(MacroAssembler* masm, // We use the same pc/oopMap repeatedly when we call out. Label native_return; - if (LockingMode != LM_LEGACY && method->is_object_wait0()) { + if (method->is_object_wait0()) { // For convenience we use the pc we want to resume to in case of preemption on Object.wait. __ set_last_Java_frame(sp, noreg, native_return, rscratch1); } else { @@ -1776,44 +1776,7 @@ nmethod* SharedRuntime::generate_native_wrapper(MacroAssembler* masm, // Load the oop from the handle __ ldr(obj_reg, Address(oop_handle_reg, 0)); - if (LockingMode == LM_MONITOR) { - __ b(slow_path_lock); - } else if (LockingMode == LM_LEGACY) { - // Load (object->mark() | 1) into swap_reg %r0 - __ ldr(rscratch1, Address(obj_reg, oopDesc::mark_offset_in_bytes())); - __ orr(swap_reg, rscratch1, 1); - - // Save (object->mark() | 1) into BasicLock's displaced header - __ str(swap_reg, Address(lock_reg, mark_word_offset)); - - // src -> dest iff dest == r0 else r0 <- dest - __ cmpxchg_obj_header(r0, lock_reg, obj_reg, rscratch1, count, /*fallthrough*/nullptr); - - // Hmm should this move to the slow path code area??? - - // Test if the oopMark is an obvious stack pointer, i.e., - // 1) (mark & 3) == 0, and - // 2) sp <= mark < mark + os::pagesize() - // These 3 tests can be done by evaluating the following - // expression: ((mark - sp) & (3 - os::vm_page_size())), - // assuming both stack pointer and pagesize have their - // least significant 2 bits clear. - // NOTE: the oopMark is in swap_reg %r0 as the result of cmpxchg - - __ sub(swap_reg, sp, swap_reg); - __ neg(swap_reg, swap_reg); - __ ands(swap_reg, swap_reg, 3 - (int)os::vm_page_size()); - - // Save the test result, for recursive case, the result is zero - __ str(swap_reg, Address(lock_reg, mark_word_offset)); - __ br(Assembler::NE, slow_path_lock); - - __ bind(count); - __ inc_held_monitor_count(rscratch1); - } else { - assert(LockingMode == LM_LIGHTWEIGHT, "must be"); - __ lightweight_lock(lock_reg, obj_reg, swap_reg, tmp, lock_tmp, slow_path_lock); - } + __ lightweight_lock(lock_reg, obj_reg, swap_reg, tmp, lock_tmp, slow_path_lock); // Slow path will re-enter here __ bind(lock_done); @@ -1888,7 +1851,7 @@ nmethod* SharedRuntime::generate_native_wrapper(MacroAssembler* masm, __ lea(rscratch2, Address(rthread, JavaThread::thread_state_offset())); __ stlrw(rscratch1, rscratch2); - if (LockingMode != LM_LEGACY && method->is_object_wait0()) { + if (method->is_object_wait0()) { // Check preemption for Object.wait() __ ldr(rscratch1, Address(rthread, JavaThread::preempt_alternate_return_offset())); __ cbz(rscratch1, native_return); @@ -1917,48 +1880,18 @@ nmethod* SharedRuntime::generate_native_wrapper(MacroAssembler* masm, // Get locked oop from the handle we passed to jni __ ldr(obj_reg, Address(oop_handle_reg, 0)); - Label done, not_recursive; - - if (LockingMode == LM_LEGACY) { - // Simple recursive lock? - __ ldr(rscratch1, Address(sp, lock_slot_offset * VMRegImpl::stack_slot_size)); - __ cbnz(rscratch1, not_recursive); - __ dec_held_monitor_count(rscratch1); - __ b(done); - } - - __ bind(not_recursive); - // Must save r0 if if it is live now because cmpxchg must use it if (ret_type != T_FLOAT && ret_type != T_DOUBLE && ret_type != T_VOID) { save_native_result(masm, ret_type, stack_slots); } - if (LockingMode == LM_MONITOR) { - __ b(slow_path_unlock); - } else if (LockingMode == LM_LEGACY) { - // get address of the stack lock - __ lea(r0, Address(sp, lock_slot_offset * VMRegImpl::stack_slot_size)); - // get old displaced header - __ ldr(old_hdr, Address(r0, 0)); - - // Atomic swap old header if oop still contains the stack lock - Label count; - __ cmpxchg_obj_header(r0, old_hdr, obj_reg, rscratch1, count, &slow_path_unlock); - __ bind(count); - __ dec_held_monitor_count(rscratch1); - } else { - assert(LockingMode == LM_LIGHTWEIGHT, ""); - __ lightweight_unlock(obj_reg, old_hdr, swap_reg, lock_tmp, slow_path_unlock); - } + __ lightweight_unlock(obj_reg, old_hdr, swap_reg, lock_tmp, slow_path_unlock); // slow path re-enters here __ bind(unlock_done); if (ret_type != T_FLOAT && ret_type != T_DOUBLE && ret_type != T_VOID) { restore_native_result(masm, ret_type, stack_slots); } - - __ bind(done); } Label dtrace_method_exit, dtrace_method_exit_done; diff --git a/src/hotspot/cpu/aarch64/templateInterpreterGenerator_aarch64.cpp b/src/hotspot/cpu/aarch64/templateInterpreterGenerator_aarch64.cpp index 6593406902f..c1eabed8ade 100644 --- a/src/hotspot/cpu/aarch64/templateInterpreterGenerator_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/templateInterpreterGenerator_aarch64.cpp @@ -1478,22 +1478,17 @@ address TemplateInterpreterGenerator::generate_native_entry(bool synchronized) { __ lea(rscratch2, Address(rthread, JavaThread::thread_state_offset())); __ stlrw(rscratch1, rscratch2); - if (LockingMode != LM_LEGACY) { - // Check preemption for Object.wait() - Label not_preempted; - __ ldr(rscratch1, Address(rthread, JavaThread::preempt_alternate_return_offset())); - __ cbz(rscratch1, not_preempted); - __ str(zr, Address(rthread, JavaThread::preempt_alternate_return_offset())); - __ br(rscratch1); - __ bind(native_return); - __ restore_after_resume(true /* is_native */); - // reload result_handler - __ ldr(result_handler, Address(rfp, frame::interpreter_frame_result_handler_offset*wordSize)); - __ bind(not_preempted); - } else { - // any pc will do so just use this one for LM_LEGACY to keep code together. - __ bind(native_return); - } + // Check preemption for Object.wait() + Label not_preempted; + __ ldr(rscratch1, Address(rthread, JavaThread::preempt_alternate_return_offset())); + __ cbz(rscratch1, not_preempted); + __ str(zr, Address(rthread, JavaThread::preempt_alternate_return_offset())); + __ br(rscratch1); + __ bind(native_return); + __ restore_after_resume(true /* is_native */); + // reload result_handler + __ ldr(result_handler, Address(rfp, frame::interpreter_frame_result_handler_offset*wordSize)); + __ bind(not_preempted); // reset_last_Java_frame __ reset_last_Java_frame(true); From 16e461ef31628ab47cd8484cdfc970c0008b97ba Mon Sep 17 00:00:00 2001 From: Thomas Schatzl Date: Tue, 12 Aug 2025 08:52:37 +0000 Subject: [PATCH 098/580] 8365122: G1: Minor clean up of G1SurvivorRegions Reviewed-by: sangheki, kbarrett --- src/hotspot/share/gc/g1/g1Policy.cpp | 16 +++++----------- src/hotspot/share/gc/g1/g1SurvivorRegions.cpp | 15 ++++++--------- src/hotspot/share/gc/g1/g1SurvivorRegions.hpp | 11 ++++++----- 3 files changed, 17 insertions(+), 25 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1Policy.cpp b/src/hotspot/share/gc/g1/g1Policy.cpp index 3bbc64e0fe7..bb54d344ca0 100644 --- a/src/hotspot/share/gc/g1/g1Policy.cpp +++ b/src/hotspot/share/gc/g1/g1Policy.cpp @@ -508,12 +508,9 @@ uint G1Policy::calculate_desired_eden_length_before_mixed(double base_time_ms, } double G1Policy::predict_survivor_regions_evac_time() const { - const GrowableArray* survivor_regions = _g1h->survivor()->regions(); double survivor_regions_evac_time = predict_young_region_other_time_ms(_g1h->survivor()->length()); - for (GrowableArrayIterator it = survivor_regions->begin(); - it != survivor_regions->end(); - ++it) { - survivor_regions_evac_time += predict_region_copy_time_ms(*it, _g1h->collector_state()->in_young_only_phase()); + for (G1HeapRegion* r : _g1h->survivor()->regions()) { + survivor_regions_evac_time += predict_region_copy_time_ms(r, _g1h->collector_state()->in_young_only_phase()); } return survivor_regions_evac_time; @@ -1461,16 +1458,13 @@ uint G1Policy::calc_max_old_cset_length() const { void G1Policy::transfer_survivors_to_cset(const G1SurvivorRegions* survivors) { start_adding_survivor_regions(); - for (GrowableArrayIterator it = survivors->regions()->begin(); - it != survivors->regions()->end(); - ++it) { - G1HeapRegion* curr = *it; - set_region_survivor(curr); + for (G1HeapRegion* r : survivors->regions()) { + set_region_survivor(r); // The region is a non-empty survivor so let's add it to // the incremental collection set for the next evacuation // pause. - _collection_set->add_survivor_regions(curr); + _collection_set->add_survivor_regions(r); } stop_adding_survivor_regions(); diff --git a/src/hotspot/share/gc/g1/g1SurvivorRegions.cpp b/src/hotspot/share/gc/g1/g1SurvivorRegions.cpp index edd4f35d5ed..4aa752e5823 100644 --- a/src/hotspot/share/gc/g1/g1SurvivorRegions.cpp +++ b/src/hotspot/share/gc/g1/g1SurvivorRegions.cpp @@ -28,18 +28,18 @@ #include "utilities/growableArray.hpp" G1SurvivorRegions::G1SurvivorRegions() : - _regions(new (mtGC) GrowableArray(8, mtGC)), + _regions(8, mtGC), _used_bytes(0), _regions_on_node() {} uint G1SurvivorRegions::add(G1HeapRegion* hr) { assert(hr->is_survivor(), "should be flagged as survivor region"); - _regions->append(hr); + _regions.append(hr); return _regions_on_node.add(hr); } uint G1SurvivorRegions::length() const { - return (uint)_regions->length(); + return (uint)_regions.length(); } uint G1SurvivorRegions::regions_on_node(uint node_index) const { @@ -47,17 +47,14 @@ uint G1SurvivorRegions::regions_on_node(uint node_index) const { } void G1SurvivorRegions::convert_to_eden() { - for (GrowableArrayIterator it = _regions->begin(); - it != _regions->end(); - ++it) { - G1HeapRegion* hr = *it; - hr->set_eden_pre_gc(); + for (G1HeapRegion* r : _regions) { + r->set_eden_pre_gc(); } clear(); } void G1SurvivorRegions::clear() { - _regions->clear(); + _regions.clear(); _used_bytes = 0; _regions_on_node.clear(); } diff --git a/src/hotspot/share/gc/g1/g1SurvivorRegions.hpp b/src/hotspot/share/gc/g1/g1SurvivorRegions.hpp index cc6bbc44fa8..0532ee12162 100644 --- a/src/hotspot/share/gc/g1/g1SurvivorRegions.hpp +++ b/src/hotspot/share/gc/g1/g1SurvivorRegions.hpp @@ -27,16 +27,17 @@ #include "gc/g1/g1RegionsOnNodes.hpp" #include "runtime/globals.hpp" +#include "utilities/growableArray.hpp" template class GrowableArray; class G1HeapRegion; +// Set of current survivor regions. class G1SurvivorRegions { -private: - GrowableArray* _regions; - volatile size_t _used_bytes; - G1RegionsOnNodes _regions_on_node; + GrowableArray _regions; + volatile size_t _used_bytes; + G1RegionsOnNodes _regions_on_node; public: G1SurvivorRegions(); @@ -50,7 +51,7 @@ public: uint length() const; uint regions_on_node(uint node_index) const; - const GrowableArray* regions() const { + const GrowableArray& regions() const { return _regions; } From 95b7a8b3e300bf40a2a5bdf8ca8310485db363e6 Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Tue, 12 Aug 2025 11:29:43 +0000 Subject: [PATCH 099/580] 8365237: Remove unused SoftRefPolicy::_all_soft_refs_clear Reviewed-by: tschatzl, kbarrett --- .../share/gc/shared/gcVMOperations.cpp | 5 ----- .../share/gc/shared/gcVMOperations.hpp | 9 -------- src/hotspot/share/gc/shared/softRefPolicy.hpp | 21 +------------------ 3 files changed, 1 insertion(+), 34 deletions(-) diff --git a/src/hotspot/share/gc/shared/gcVMOperations.cpp b/src/hotspot/share/gc/shared/gcVMOperations.cpp index 19a81504722..1299f64995f 100644 --- a/src/hotspot/share/gc/shared/gcVMOperations.cpp +++ b/src/hotspot/share/gc/shared/gcVMOperations.cpp @@ -62,11 +62,6 @@ void VM_Verify::doit() { Universe::verify(); } -VM_GC_Operation::~VM_GC_Operation() { - CollectedHeap* ch = Universe::heap(); - ch->soft_ref_policy()->set_all_soft_refs_clear(false); -} - const char* VM_GC_Operation::cause() const { return GCCause::to_string(_gc_cause); } diff --git a/src/hotspot/share/gc/shared/gcVMOperations.hpp b/src/hotspot/share/gc/shared/gcVMOperations.hpp index 6752e0bc32d..0121a468b56 100644 --- a/src/hotspot/share/gc/shared/gcVMOperations.hpp +++ b/src/hotspot/share/gc/shared/gcVMOperations.hpp @@ -126,16 +126,7 @@ class VM_GC_Operation: public VM_Heap_Sync_Operation { _gc_cause = _cause; _full_gc_count_before = full_gc_count_before; - // In ParallelScavengeHeap::mem_allocate() collections can be - // executed within a loop and _all_soft_refs_clear can be set - // true after they have been cleared by a collection and another - // collection started so that _all_soft_refs_clear can be true - // when this collection is started. Don't assert that - // _all_soft_refs_clear have to be false here even though - // mutators have run. Soft refs will be cleared again in this - // collection. } - ~VM_GC_Operation(); virtual const char* cause() const; diff --git a/src/hotspot/share/gc/shared/softRefPolicy.hpp b/src/hotspot/share/gc/shared/softRefPolicy.hpp index fe2706288f7..1e8ec356c40 100644 --- a/src/hotspot/share/gc/shared/softRefPolicy.hpp +++ b/src/hotspot/share/gc/shared/softRefPolicy.hpp @@ -25,37 +25,18 @@ #ifndef SHARE_GC_SHARED_SOFTREFPOLICY_HPP #define SHARE_GC_SHARED_SOFTREFPOLICY_HPP -#include "memory/allocation.hpp" - class SoftRefPolicy { private: // Set to true when policy wants soft refs cleared. // Reset to false by gc after it clears all soft refs. bool _should_clear_all_soft_refs; - // Set to true by the GC if the just-completed gc cleared all - // softrefs. This is set to true whenever a gc clears all softrefs, and - // set to false each time gc returns to the mutator. For example, in the - // ParallelScavengeHeap case the latter would be done toward the end of - // mem_allocate() where it returns op.result() - bool _all_soft_refs_clear; - public: SoftRefPolicy() : - _should_clear_all_soft_refs(false), - _all_soft_refs_clear(false) {} + _should_clear_all_soft_refs(false) {} bool should_clear_all_soft_refs() { return _should_clear_all_soft_refs; } void set_should_clear_all_soft_refs(bool v) { _should_clear_all_soft_refs = v; } - - bool all_soft_refs_clear() { return _all_soft_refs_clear; } - void set_all_soft_refs_clear(bool v) { _all_soft_refs_clear = v; } - - // Called by the GC after Soft Refs have been cleared to indicate - // that the request in _should_clear_all_soft_refs has been fulfilled. - void cleared_all_soft_refs() { - _all_soft_refs_clear = true; - } }; #endif // SHARE_GC_SHARED_SOFTREFPOLICY_HPP From 19a76a45e9c8616414830c865801660bc6761e92 Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Tue, 12 Aug 2025 11:58:37 +0000 Subject: [PATCH 100/580] 8365316: Remove unnecessary default arg value in gcVMOperations Reviewed-by: tschatzl --- src/hotspot/share/gc/shared/gcVMOperations.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/hotspot/share/gc/shared/gcVMOperations.hpp b/src/hotspot/share/gc/shared/gcVMOperations.hpp index 0121a468b56..5048bc3c1ed 100644 --- a/src/hotspot/share/gc/shared/gcVMOperations.hpp +++ b/src/hotspot/share/gc/shared/gcVMOperations.hpp @@ -117,8 +117,8 @@ class VM_GC_Operation: public VM_Heap_Sync_Operation { public: VM_GC_Operation(uint gc_count_before, GCCause::Cause _cause, - uint full_gc_count_before = 0, - bool full = false) : VM_Heap_Sync_Operation() { + uint full_gc_count_before, + bool full) : VM_Heap_Sync_Operation() { _full = full; _prologue_succeeded = false; _gc_count_before = gc_count_before; @@ -147,8 +147,8 @@ class VM_GC_Service_Operation : public VM_GC_Operation { public: VM_GC_Service_Operation(uint gc_count_before, GCCause::Cause _cause, - uint full_gc_count_before = 0, - bool full = false) : + uint full_gc_count_before, + bool full) : VM_GC_Operation(gc_count_before, _cause, full_gc_count_before, full) {} }; From 391ea151184c5621f263742605416c3ccd2c3d73 Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Tue, 12 Aug 2025 13:16:54 +0000 Subject: [PATCH 101/580] 8365307: AIX make fails after JDK-8364611 Reviewed-by: clanger, asteiner --- .../childSignalDisposition/exePrintSignalDisposition.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/jdk/java/lang/ProcessBuilder/childSignalDisposition/exePrintSignalDisposition.c b/test/jdk/java/lang/ProcessBuilder/childSignalDisposition/exePrintSignalDisposition.c index 5a96c2556fc..8235cdc7410 100644 --- a/test/jdk/java/lang/ProcessBuilder/childSignalDisposition/exePrintSignalDisposition.c +++ b/test/jdk/java/lang/ProcessBuilder/childSignalDisposition/exePrintSignalDisposition.c @@ -72,7 +72,11 @@ int main(int argc, char** argv) { } else { printf("%p ", handler); } +#ifdef _AIX + printf("%X\n", act.sa_flags); +#else printf("%X %X\n", act.sa_flags, act.sa_mask); +#endif } return 0; From a382996bb496d50b4eb5a6be9f61e5c2f8aaae2d Mon Sep 17 00:00:00 2001 From: Erik Gahlin Date: Tue, 12 Aug 2025 13:42:53 +0000 Subject: [PATCH 102/580] 8364993: JFR: Disable jdk.ModuleExport in default.jfc Reviewed-by: mgronlun --- src/jdk.jfr/share/conf/jfr/default.jfc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jdk.jfr/share/conf/jfr/default.jfc b/src/jdk.jfr/share/conf/jfr/default.jfc index 20051864895..463639d7f1e 100644 --- a/src/jdk.jfr/share/conf/jfr/default.jfc +++ b/src/jdk.jfr/share/conf/jfr/default.jfc @@ -762,7 +762,7 @@ - true + false endChunk From ad0fd13f2007c93d8a109626a627823f30e4c8d7 Mon Sep 17 00:00:00 2001 From: Ioi Lam Date: Tue, 12 Aug 2025 16:20:00 +0000 Subject: [PATCH 103/580] 8364454: ProblemList runtime/cds/DeterministicDump.java on macos for JDK-8363986 Reviewed-by: ccheung --- test/hotspot/jtreg/ProblemList.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/test/hotspot/jtreg/ProblemList.txt b/test/hotspot/jtreg/ProblemList.txt index 7026b6f79d6..37986c67dd8 100644 --- a/test/hotspot/jtreg/ProblemList.txt +++ b/test/hotspot/jtreg/ProblemList.txt @@ -97,6 +97,7 @@ gc/shenandoah/TestEvilSyncBug.java#generational 8345501 generic-all # :hotspot_runtime +runtime/cds/DeterministicDump.java 8363986 macosx-x64,macosx-aarch64 runtime/jni/terminatedThread/TestTerminatedThread.java 8317789 aix-ppc64 runtime/Monitor/SyncOnValueBasedClassTest.java 8340995 linux-s390x runtime/os/TestTracePageSizes.java#no-options 8267460 linux-aarch64 From 4c03e5938df0a9cb10c2379af81163795dd3a086 Mon Sep 17 00:00:00 2001 From: Coleen Phillimore Date: Tue, 12 Aug 2025 16:30:09 +0000 Subject: [PATCH 104/580] 8364750: Remove unused declaration in jvm.h Reviewed-by: shade --- src/hotspot/share/include/jvm.h | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/hotspot/share/include/jvm.h b/src/hotspot/share/include/jvm.h index f97374553ca..4d39537e5a1 100644 --- a/src/hotspot/share/include/jvm.h +++ b/src/hotspot/share/include/jvm.h @@ -599,16 +599,6 @@ JVM_GetClassDeclaredFields(JNIEnv *env, jclass ofClass, jboolean publicOnly); JNIEXPORT jobjectArray JNICALL JVM_GetClassDeclaredConstructors(JNIEnv *env, jclass ofClass, jboolean publicOnly); - -/* Differs from JVM_GetClassModifiers in treatment of inner classes. - This returns the access flags for the class as specified in the - class file rather than searching the InnerClasses attribute (if - present) to find the source-level access flags. Only the values of - the low 13 bits (i.e., a mask of 0x1FFF) are guaranteed to be - valid. */ -JNIEXPORT jint JNICALL -JVM_GetClassAccessFlags(JNIEnv *env, jclass cls); - /* Nestmates - since JDK 11 */ JNIEXPORT jboolean JNICALL From d023982600f8bb19053f579710953aa29e0f30c5 Mon Sep 17 00:00:00 2001 From: Brian Burkhalter Date: Tue, 12 Aug 2025 17:39:14 +0000 Subject: [PATCH 105/580] 8361209: (bf) Use CharSequence::getChars for StringCharBuffer bulk get methods Reviewed-by: rriggs, alanb --- .../classes/java/nio/StringCharBuffer.java | 10 ++- .../classes/java/nio/X-Buffer.java.template | 29 ++++++-- .../nio/StringCharBufferBulkTransfer.java | 72 +++++++++++++++++++ 3 files changed, 102 insertions(+), 9 deletions(-) create mode 100644 test/micro/org/openjdk/bench/java/nio/StringCharBufferBulkTransfer.java diff --git a/src/java.base/share/classes/java/nio/StringCharBuffer.java b/src/java.base/share/classes/java/nio/StringCharBuffer.java index a62e8023363..39cd6910f5d 100644 --- a/src/java.base/share/classes/java/nio/StringCharBuffer.java +++ b/src/java.base/share/classes/java/nio/StringCharBuffer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -97,7 +97,13 @@ final class StringCharBuffer // package-private return str.charAt(index + offset); } - // ## Override bulk get methods for better performance + @Override + CharBuffer getArray(int index, char[] dst, int dstOffset, int length) { + // Note: the variable "offset" is defined and set in the superclass + int srcOffset = offset + index; + str.getChars(srcOffset, srcOffset + length, dst, dstOffset); + return this; + } public final CharBuffer put(char c) { throw new ReadOnlyBufferException(); diff --git a/src/java.base/share/classes/java/nio/X-Buffer.java.template b/src/java.base/share/classes/java/nio/X-Buffer.java.template index c43004cc0ac..b2b96288d57 100644 --- a/src/java.base/share/classes/java/nio/X-Buffer.java.template +++ b/src/java.base/share/classes/java/nio/X-Buffer.java.template @@ -950,12 +950,15 @@ public abstract sealed class $Type$Buffer return get(index, dst, 0, dst.length); } - private $Type$Buffer getArray(int index, $type$[] dst, int offset, int length) { - if ( + // + // This method does not perform bounds checking which it expects + // will have been done by the caller + // + $Type$Buffer getArray(int index, $type$[] dst, int offset, int length) { #if[char] - isAddressable() && + assert isAddressable(); #end[char] - ((long)length << $LG_BYTES_PER_VALUE$) > Bits.JNI_COPY_TO_ARRAY_THRESHOLD) { + if (((long)length << $LG_BYTES_PER_VALUE$) > Bits.JNI_COPY_TO_ARRAY_THRESHOLD) { long bufAddr = address + ((long)index << $LG_BYTES_PER_VALUE$); long dstOffset = ARRAY_BASE_OFFSET + ((long)offset << $LG_BYTES_PER_VALUE$); @@ -1116,6 +1119,10 @@ public abstract sealed class $Type$Buffer return this; } + // + // This method does not perform bounds checking which it expects + // will have been done by the caller + // void putBuffer(int pos, $Type$Buffer src, int srcPos, int n) { #if[rw] Object srcBase = src.base(); @@ -1150,9 +1157,13 @@ public abstract sealed class $Type$Buffer #if[char] } else { // src.isAddressable() == false assert StringCharBuffer.class.isInstance(src); - int posMax = pos + n; - for (int i = pos, j = srcPos; i < posMax; i++, j++) - put(i, src.get(j)); + if (this.hb != null && src instanceof StringCharBuffer scb) { + scb.getArray(srcPos, this.hb, pos, n); + } else { + int posMax = pos + n; + for (int i = pos, j = srcPos; i < posMax; i++, j++) + put(i, src.get(j)); + } } #end[char] #else[rw] @@ -1342,6 +1353,10 @@ public abstract sealed class $Type$Buffer return put(index, src, 0, src.length); } + // + // This method does not perform bounds checking which it expects + // will have been done by the caller + // $Type$Buffer putArray(int index, $type$[] src, int offset, int length) { #if[rw] if ( diff --git a/test/micro/org/openjdk/bench/java/nio/StringCharBufferBulkTransfer.java b/test/micro/org/openjdk/bench/java/nio/StringCharBufferBulkTransfer.java new file mode 100644 index 00000000000..246116e4d43 --- /dev/null +++ b/test/micro/org/openjdk/bench/java/nio/StringCharBufferBulkTransfer.java @@ -0,0 +1,72 @@ +/* + * 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.java.nio; + +import java.nio.CharBuffer; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +/** + * Benchmark for bulk get methods of a {@code CharBuffer} created from a + * {@code CharSequence}. + */ + +@Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) +@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS) +@State(Scope.Benchmark) +@Fork(1) +public class StringCharBufferBulkTransfer { + private static final int LENGTH = 16384; + + char[] buf = new char[LENGTH]; + CharBuffer cb = CharBuffer.wrap(new String(buf)); + char[] dst = new char[LENGTH]; + CharBuffer cbw = CharBuffer.allocate(LENGTH); + + @Benchmark + public void absoluteBulkGet() { + cb.get(0, dst, 0, dst.length); + } + + @Benchmark + public void relativeBulkGet() { + cb.get(dst, 0, dst.length); + cb.position(0); + } + + @Benchmark + public void getChars() { + cb.getChars(0, LENGTH, dst, 0); + } + + @Benchmark + public void absoluteBulkPut() { + cbw.put(0, cb, 0, dst.length); + } +} From 87d734012e3130501bfd37b23cee7f5e0a3a476f Mon Sep 17 00:00:00 2001 From: Erik Gahlin Date: Tue, 12 Aug 2025 17:44:34 +0000 Subject: [PATCH 106/580] 8364756: JFR: Improve slow tests Reviewed-by: mgronlun --- .../api/consumer/streaming/TestFilledChunks.java | 2 ++ .../consumer/streaming/TestStartMultiChunk.java | 4 ++-- .../event/runtime/StressJavaMonitorEvents.java | 2 +- test/jdk/jdk/jfr/jmx/streaming/TestMaxSize.java | 6 ++++-- .../jdk/jfr/jmx/streaming/TestRemoteDump.java | 16 ++++++++++------ test/jdk/jdk/jfr/jvm/TestWaste.java | 2 +- .../TestMultipleStartupRecordings.java | 8 -------- 7 files changed, 20 insertions(+), 20 deletions(-) diff --git a/test/jdk/jdk/jfr/api/consumer/streaming/TestFilledChunks.java b/test/jdk/jdk/jfr/api/consumer/streaming/TestFilledChunks.java index 423e3969af8..f435fe9747f 100644 --- a/test/jdk/jdk/jfr/api/consumer/streaming/TestFilledChunks.java +++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestFilledChunks.java @@ -26,6 +26,7 @@ package jdk.jfr.api.consumer.streaming; import java.util.Random; import jdk.jfr.Event; +import jdk.jfr.StackTrace; import jdk.jfr.consumer.RecordingStream; /** @@ -38,6 +39,7 @@ import jdk.jfr.consumer.RecordingStream; */ public class TestFilledChunks { + @StackTrace(false) static class FillEvent extends Event { String message; int value; diff --git a/test/jdk/jdk/jfr/api/consumer/streaming/TestStartMultiChunk.java b/test/jdk/jdk/jfr/api/consumer/streaming/TestStartMultiChunk.java index fbc27b051b0..71113631521 100644 --- a/test/jdk/jdk/jfr/api/consumer/streaming/TestStartMultiChunk.java +++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestStartMultiChunk.java @@ -44,7 +44,7 @@ import jdk.jfr.consumer.RecordingStream; */ public class TestStartMultiChunk { - @Period("10 s") + @Period("2 s") @Name("Zebra") static class ZebraEvent extends Event { } @@ -65,7 +65,7 @@ public class TestStartMultiChunk { CountDownLatch dogLatch = new CountDownLatch(1); CountDownLatch catLatch = new CountDownLatch(1); CountDownLatch mouseLatch = new CountDownLatch(1); - CountDownLatch zebraLatch = new CountDownLatch(3); + CountDownLatch zebraLatch = new CountDownLatch(2); FlightRecorder.addPeriodicEvent(ZebraEvent.class, () -> { ZebraEvent ze = new ZebraEvent(); diff --git a/test/jdk/jdk/jfr/event/runtime/StressJavaMonitorEvents.java b/test/jdk/jdk/jfr/event/runtime/StressJavaMonitorEvents.java index ed3b5454163..fa9bb0c18a2 100644 --- a/test/jdk/jdk/jfr/event/runtime/StressJavaMonitorEvents.java +++ b/test/jdk/jdk/jfr/event/runtime/StressJavaMonitorEvents.java @@ -52,7 +52,7 @@ import jdk.test.lib.thread.XRun; */ public class StressJavaMonitorEvents { - static final int RUN_TIME_MS = 10000; + static final int RUN_TIME_MS = 5000; static final int GC_EVERY_MS = 500; static final int THREADS = 4; static final int NUM_LOCKS = 1024; diff --git a/test/jdk/jdk/jfr/jmx/streaming/TestMaxSize.java b/test/jdk/jdk/jfr/jmx/streaming/TestMaxSize.java index b79fbc2bcf0..5f490594370 100644 --- a/test/jdk/jdk/jfr/jmx/streaming/TestMaxSize.java +++ b/test/jdk/jdk/jfr/jmx/streaming/TestMaxSize.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 @@ -33,6 +33,7 @@ import java.util.concurrent.atomic.AtomicInteger; import javax.management.MBeanServerConnection; import jdk.jfr.Event; +import jdk.jfr.StackTrace; import jdk.management.jfr.RemoteRecordingStream; /** @@ -45,6 +46,7 @@ import jdk.management.jfr.RemoteRecordingStream; */ public class TestMaxSize { + @StackTrace(false) static class Monkey extends Event { } @@ -92,7 +94,7 @@ public class TestMaxSize { m.commit(); } System.out.println("Emitted " + count + " events"); - Thread.sleep(1000); + Thread.sleep(100); } private static int fileCount(Path dir) throws IOException { diff --git a/test/jdk/jdk/jfr/jmx/streaming/TestRemoteDump.java b/test/jdk/jdk/jfr/jmx/streaming/TestRemoteDump.java index 9390857c09e..6731f3c44cf 100644 --- a/test/jdk/jdk/jfr/jmx/streaming/TestRemoteDump.java +++ b/test/jdk/jdk/jfr/jmx/streaming/TestRemoteDump.java @@ -97,7 +97,7 @@ public class TestRemoteDump { } } - private static List recordWithPolicy(String filename, Consumer policy) throws Exception { + private static List recordWithPolicy(String filename, boolean awaitEvents, Consumer policy) throws Exception { CountDownLatch latch1 = new CountDownLatch(1); CountDownLatch latch2 = new CountDownLatch(2); CountDownLatch latch3 = new CountDownLatch(3); @@ -111,14 +111,18 @@ public class TestRemoteDump { rs.startAsync(); DumpEvent e1 = new DumpEvent(); e1.commit(); - latch1.await(); + if (awaitEvents) { + latch1.await(); + } // Force chunk rotation try (Recording r = new Recording()) { r.start(); DumpEvent e2 = new DumpEvent(); e2.commit(); } - latch2.await(); + if (awaitEvents) { + latch2.await(); + } DumpEvent e3 = new DumpEvent(); e3.commit(); latch3.await(); @@ -129,7 +133,7 @@ public class TestRemoteDump { } private static void testSetMaxSize() throws Exception { - var events = recordWithPolicy("max-size.jfr", rs -> { + var events = recordWithPolicy("max-size.jfr", false, rs -> { // keeps all events for the dump rs.setMaxSize(100_000_000); }); @@ -140,7 +144,7 @@ public class TestRemoteDump { } private static void testSetMaxAge() throws Exception { - var events = recordWithPolicy("max-age.jfr", rs -> { + var events = recordWithPolicy("max-age.jfr", false, rs -> { // keeps all events for the dump rs.setMaxAge(Duration.ofDays(1)); }); @@ -151,7 +155,7 @@ public class TestRemoteDump { } private static void testSetNoPolicy() throws Exception { - var events = recordWithPolicy("no-policy.jfr", rs -> { + var events = recordWithPolicy("no-policy.jfr", true, rs -> { // use default policy, remove after consumption }); // Since latch3 have been triggered at least two events/chunks diff --git a/test/jdk/jdk/jfr/jvm/TestWaste.java b/test/jdk/jdk/jfr/jvm/TestWaste.java index c755ca4c3d0..4de14fc2461 100644 --- a/test/jdk/jdk/jfr/jvm/TestWaste.java +++ b/test/jdk/jdk/jfr/jvm/TestWaste.java @@ -61,7 +61,7 @@ public class TestWaste { try (Recording r = new Recording(c)) { // Old objects that are cleared out should not create waste r.enable("jdk.OldObjectSample") - .with("cutoff", "infinity") + .with("cutoff", "2 s") .withStackTrace(); // No stack trace waste from allocation sample r.enable("jdk.ObjectAllocationSample") diff --git a/test/jdk/jdk/jfr/startupargs/TestMultipleStartupRecordings.java b/test/jdk/jdk/jfr/startupargs/TestMultipleStartupRecordings.java index c70f9810568..bfa02fd8a74 100644 --- a/test/jdk/jdk/jfr/startupargs/TestMultipleStartupRecordings.java +++ b/test/jdk/jdk/jfr/startupargs/TestMultipleStartupRecordings.java @@ -74,13 +74,6 @@ public class TestMultipleStartupRecordings { test(pb, "Started recording 1", "Started recording 2", "Started recording 3"); } - private static void testDefault() throws Exception { - System.out.println("testDefault"); - launchUnary(null); - launchBinary(null, null); - launchTernary(null, null, null); - } - private static void testColonDelimited() throws Exception { launchBinary(":name=myrecording1,filename=myrecording1.jfr", ":filename=myrecording2.jfr,name=myrecording2"); } @@ -99,7 +92,6 @@ public class TestMultipleStartupRecordings { } public static void main(String[] args) throws Exception { - testDefault(); testColonDelimited(); testMixed(); testWithFlightRecorderOptions(); From 636c61a3868d9c01b672b3b45cda1e476acdc045 Mon Sep 17 00:00:00 2001 From: Dingli Zhang Date: Wed, 13 Aug 2025 01:24:39 +0000 Subject: [PATCH 107/580] 8365302: RISC-V: compiler/loopopts/superword/TestAlignVector.java fails when vlen=128 Reviewed-by: fyang, fjiang --- .../loopopts/superword/TestAlignVector.java | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/test/hotspot/jtreg/compiler/loopopts/superword/TestAlignVector.java b/test/hotspot/jtreg/compiler/loopopts/superword/TestAlignVector.java index 97a3c4ed037..322c36c39e1 100644 --- a/test/hotspot/jtreg/compiler/loopopts/superword/TestAlignVector.java +++ b/test/hotspot/jtreg/compiler/loopopts/superword/TestAlignVector.java @@ -1063,8 +1063,16 @@ public class TestAlignVector { IRNode.ADD_VL, IRNode.VECTOR_SIZE + "min(max_int, max_long)", "> 0", IRNode.STORE_VECTOR, "> 0"}, applyIfPlatform = {"64-bit", "true"}, - applyIfCPUFeatureOr = {"avx2", "true", "rvv", "true"}) + applyIfCPUFeature = {"avx2", "true"}) // require avx to ensure vectors are larger than what unrolling produces + @IR(counts = {IRNode.LOAD_VECTOR_I, IRNode.VECTOR_SIZE + "min(max_int, max_long)", "> 0", + IRNode.LOAD_VECTOR_L, IRNode.VECTOR_SIZE + "min(max_int, max_long)", "> 0", + IRNode.ADD_VI, IRNode.VECTOR_SIZE + "min(max_int, max_long)", "> 0", + IRNode.ADD_VL, IRNode.VECTOR_SIZE + "min(max_int, max_long)", "> 0", + IRNode.STORE_VECTOR, "> 0"}, + applyIfPlatform = {"riscv64", "true"}, + applyIfCPUFeature = {"rvv", "true"}, + applyIf = {"MaxVectorSize", ">=32"}) static Object[] test13aIL(int[] a, long[] b) { for (int i = 0; i < RANGE; i++) { a[i]++; @@ -1175,8 +1183,16 @@ public class TestAlignVector { IRNode.ADD_VL, IRNode.VECTOR_SIZE + "min(max_int, max_long)", "> 0", IRNode.STORE_VECTOR, "> 0"}, applyIfPlatform = {"64-bit", "true"}, - applyIfCPUFeatureOr = {"avx2", "true", "rvv", "true"}) + applyIfCPUFeature = {"avx2", "true"}) // require avx to ensure vectors are larger than what unrolling produces + @IR(counts = {IRNode.LOAD_VECTOR_I, IRNode.VECTOR_SIZE + "min(max_int, max_long)", "> 0", + IRNode.LOAD_VECTOR_L, IRNode.VECTOR_SIZE + "min(max_int, max_long)", "> 0", + IRNode.ADD_VI, IRNode.VECTOR_SIZE + "min(max_int, max_long)", "> 0", + IRNode.ADD_VL, IRNode.VECTOR_SIZE + "min(max_int, max_long)", "> 0", + IRNode.STORE_VECTOR, "> 0"}, + applyIfPlatform = {"riscv64", "true"}, + applyIfCPUFeature = {"rvv", "true"}, + applyIf = {"MaxVectorSize", ">=32"}) static Object[] test13bIL(int[] a, long[] b) { for (int i = 1; i < RANGE; i++) { a[i]++; From 25480f0011297ad209eca1b1b56bcf983ea4ee5d Mon Sep 17 00:00:00 2001 From: Ramkumar Sunderbabu Date: Wed, 13 Aug 2025 01:45:49 +0000 Subject: [PATCH 108/580] 8365184: sun/tools/jhsdb/HeapDumpTestWithActiveProcess.java Re-enable SerialGC flag on debuggee process Reviewed-by: lmesnik, cjplummer, sspitsyn --- test/jdk/sun/tools/jhsdb/JShellHeapDumpTest.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/test/jdk/sun/tools/jhsdb/JShellHeapDumpTest.java b/test/jdk/sun/tools/jhsdb/JShellHeapDumpTest.java index 9e9a6c79c7b..6fbc96362a9 100644 --- a/test/jdk/sun/tools/jhsdb/JShellHeapDumpTest.java +++ b/test/jdk/sun/tools/jhsdb/JShellHeapDumpTest.java @@ -162,12 +162,7 @@ public class JShellHeapDumpTest { long startTime = System.currentTimeMillis(); try { JDKToolLauncher launcher = JDKToolLauncher.createUsingTestJDK("jshell"); - if (doSleep) { - launcher.addVMArgs(Utils.getTestJavaOpts()); - } else { - // Don't allow use of SerialGC. See JDK-8313655. - launcher.addVMArgs(Utils.getFilteredTestJavaOpts("-XX:\\+UseSerialGC")); - } + launcher.addVMArgs(Utils.getTestJavaOpts()); ProcessBuilder pb = new ProcessBuilder(launcher.getCommand()); // Needed so we can properly parse the "Welcome to JShell" output. pb.command().add("-J-Duser.language=en"); From 72e22b4de59a18f83c75be9a51fd99726f77f6f6 Mon Sep 17 00:00:00 2001 From: Jan Lahoda Date: Wed, 13 Aug 2025 08:07:45 +0000 Subject: [PATCH 109/580] 8362885: A more formal way to mark javac's Flags that belong to a specific Symbol type only Reviewed-by: ihse, liach, vromero, mcimadamore, erikj --- make/ToolsLangtools.gmk | 2 +- .../tools/flagsgenerator/FlagsGenerator.java | 161 +++++++++++ .../propertiesparser/parser/MessageType.java | 4 +- make/modules/jdk.compiler/Gensrc.gmk | 33 ++- .../com/sun/tools/javac/code/Flags.java | 269 ++++++++++-------- .../com/sun/tools/javac/comp/Modules.java | 4 +- .../javac/diags/ArgTypeCompilerFactory.java | 4 +- .../tools/javac/flags/FlagsTest.java | 85 +++++- 8 files changed, 422 insertions(+), 140 deletions(-) create mode 100644 make/langtools/tools/flagsgenerator/FlagsGenerator.java diff --git a/make/ToolsLangtools.gmk b/make/ToolsLangtools.gmk index 4146652bf8b..1a764d6019b 100644 --- a/make/ToolsLangtools.gmk +++ b/make/ToolsLangtools.gmk @@ -36,7 +36,7 @@ $(eval $(call SetupJavaCompilation, BUILD_TOOLS_LANGTOOLS, \ COMPILER := bootjdk, \ TARGET_RELEASE := $(TARGET_RELEASE_BOOTJDK), \ SRC := $(TOPDIR)/make/langtools/tools, \ - INCLUDES := compileproperties propertiesparser, \ + INCLUDES := compileproperties flagsgenerator propertiesparser, \ COPY := .properties, \ BIN := $(BUILDTOOLS_OUTPUTDIR)/langtools_tools_classes, \ )) diff --git a/make/langtools/tools/flagsgenerator/FlagsGenerator.java b/make/langtools/tools/flagsgenerator/FlagsGenerator.java new file mode 100644 index 00000000000..1c192d214de --- /dev/null +++ b/make/langtools/tools/flagsgenerator/FlagsGenerator.java @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package flagsgenerator; + +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.util.JavacTask; +import com.sun.source.util.TreePath; +import com.sun.source.util.Trees; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.TreeMap; +import java.util.stream.Collectors; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.util.ElementFilter; +import javax.tools.ToolProvider; + +public class FlagsGenerator { + public static void main(String... args) throws IOException { + var compiler = ToolProvider.getSystemJavaCompiler(); + + try (var fm = compiler.getStandardFileManager(null, null, null)) { + JavacTask task = (JavacTask) compiler.getTask(null, null, d -> {}, null, null, fm.getJavaFileObjects(args[0])); + Trees trees = Trees.instance(task); + CompilationUnitTree cut = task.parse().iterator().next(); + + task.analyze(); + + TypeElement clazz = (TypeElement) trees.getElement(new TreePath(new TreePath(cut), cut.getTypeDecls().get(0))); + Map> flag2Names = new TreeMap<>(); + Map>> target2FlagBit2Fields = new EnumMap<>(FlagTarget.class); + Map customToString = new HashMap<>(); + Set noToString = new HashSet<>(); + + for (VariableElement field : ElementFilter.fieldsIn(clazz.getEnclosedElements())) { + String flagName = field.getSimpleName().toString(); + for (AnnotationMirror am : field.getAnnotationMirrors()) { + switch (am.getAnnotationType().toString()) { + case "com.sun.tools.javac.code.Flags.Use" -> { + long flagValue = ((Number) field.getConstantValue()).longValue(); + int flagBit = 63 - Long.numberOfLeadingZeros(flagValue); + + flag2Names.computeIfAbsent(flagBit, _ -> new ArrayList<>()) + .add(flagName); + + List originalTargets = (List) valueOfValueAttribute(am); + originalTargets.stream() + .map(value -> FlagTarget.valueOf(value.toString())) + .forEach(target -> target2FlagBit2Fields.computeIfAbsent(target, _ -> new HashMap<>()) + .computeIfAbsent(flagBit, _ -> new ArrayList<>()) + .add(flagName)); + } + case "com.sun.tools.javac.code.Flags.CustomToStringValue" -> { + customToString.put(flagName, (String) valueOfValueAttribute(am)); + } + case "com.sun.tools.javac.code.Flags.NoToStringValue" -> { + noToString.add(flagName); + } + } + } + } + + //verify there are no flag overlaps: + for (Entry>> targetAndFlag : target2FlagBit2Fields.entrySet()) { + for (Entry> flagAndFields : targetAndFlag.getValue().entrySet()) { + if (flagAndFields.getValue().size() > 1) { + throw new AssertionError("duplicate flag for target: " + targetAndFlag.getKey() + + ", flag: " + flagAndFields.getKey() + + ", flags fields: " + flagAndFields.getValue()); + } + } + } + + try (PrintWriter out = new PrintWriter(Files.newBufferedWriter(Paths.get(args[1])))) { + out.println(""" + package com.sun.tools.javac.code; + + public enum FlagsEnum { + """); + for (Entry> e : flag2Names.entrySet()) { + String constantName = e.getValue().stream().collect(Collectors.joining("_OR_")); + String toString = e.getValue() + .stream() + .filter(n -> !noToString.contains(n)) + .map(n -> customToString.getOrDefault(n, n.toLowerCase(Locale.US))) + .collect(Collectors.joining(" or ")); + out.println(" " + constantName + "(1L<<" + e.getKey() + ", \"" + toString + "\"),"); + } + out.println(""" + ; + + private final long value; + private final String toString; + private FlagsEnum(long value, String toString) { + this.value = value; + this.toString = toString; + } + public long value() { + return value; + } + public String toString() { + return toString; + } + } + """); + } + } + } + + private static Object valueOfValueAttribute(AnnotationMirror am) { + return am.getElementValues() + .values() + .iterator() + .next() + .getValue(); + } + + private enum FlagTarget { + BLOCK, + CLASS, + METHOD, + MODULE, + PACKAGE, + TYPE_VAR, + VARIABLE; + } +} diff --git a/make/langtools/tools/propertiesparser/parser/MessageType.java b/make/langtools/tools/propertiesparser/parser/MessageType.java index ea518dc536b..a4ea0ddc3c0 100644 --- a/make/langtools/tools/propertiesparser/parser/MessageType.java +++ b/make/langtools/tools/propertiesparser/parser/MessageType.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -76,7 +76,7 @@ public interface MessageType { ANNOTATION("annotation", "Compound", "com.sun.tools.javac.code.Attribute"), BOOLEAN("boolean", "boolean", null), COLLECTION("collection", "Collection", "java.util"), - FLAG("flag", "Flag", "com.sun.tools.javac.code.Flags"), + FLAG("flag", "FlagsEnum", "com.sun.tools.javac.code"), FRAGMENT("fragment", "Fragment", null), DIAGNOSTIC("diagnostic", "JCDiagnostic", "com.sun.tools.javac.util"), MODIFIER("modifier", "Modifier", "javax.lang.model.element"), diff --git a/make/modules/jdk.compiler/Gensrc.gmk b/make/modules/jdk.compiler/Gensrc.gmk index c6c5879745c..501ea37ae8b 100644 --- a/make/modules/jdk.compiler/Gensrc.gmk +++ b/make/modules/jdk.compiler/Gensrc.gmk @@ -41,17 +41,17 @@ $(eval $(call SetupCompileProperties, COMPILE_PROPERTIES, \ TARGETS += $(COMPILE_PROPERTIES) -################################################################################ -# -# Compile properties files into enum-like classes using the propertiesparser tool -# - # To avoid reevaluating the compilation setup for the tools each time this file # is included, the following trick is used to be able to declare a dependency on # the built tools. BUILD_TOOLS_LANGTOOLS := $(call SetupJavaCompilationCompileTarget, \ BUILD_TOOLS_LANGTOOLS, $(BUILDTOOLS_OUTPUTDIR)/langtools_tools_classes) +################################################################################ +# +# Compile properties files into enum-like classes using the propertiesparser tool +# + TOOL_PARSEPROPERTIES_CMD := $(JAVA_SMALL) -cp $(BUILDTOOLS_OUTPUTDIR)/langtools_tools_classes \ propertiesparser.PropertiesParser @@ -76,3 +76,26 @@ $(eval $(call SetupExecute, PARSEPROPERTIES, \ TARGETS += $(PARSEPROPERTIES) ################################################################################ +# +# Generate FlagsEnum from Flags constants +# + +TOOL_FLAGSGENERATOR_CMD := $(JAVA_SMALL) -cp $(BUILDTOOLS_OUTPUTDIR)/langtools_tools_classes \ + flagsgenerator.FlagsGenerator + +FLAGS_SRC := \ + $(MODULE_SRC)/share/classes/com/sun/tools/javac/code/Flags.java + +FLAGS_OUT := \ + $(SUPPORT_OUTPUTDIR)/gensrc/$(MODULE)/com/sun/tools/javac/code/FlagsEnum.java + +$(eval $(call SetupExecute, FLAGSGENERATOR, \ + WARN := Generating FlagsEnum, \ + DEPS := $(FLAGS_SRC) $(BUILD_TOOLS_LANGTOOLS), \ + OUTPUT_FILE := $(FLAGS_OUT), \ + COMMAND := $(TOOL_FLAGSGENERATOR_CMD) $(FLAGS_SRC) $(FLAGS_OUT), \ +)) + +TARGETS += $(FLAGSGENERATOR) + +################################################################################ diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Flags.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Flags.java index 26f0d338a2e..5b59e47027e 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Flags.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Flags.java @@ -25,6 +25,8 @@ package com.sun.tools.javac.code; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Collections; import java.util.EnumSet; import java.util.Map; @@ -35,7 +37,6 @@ import java.util.stream.Collectors; import javax.lang.model.element.Modifier; import com.sun.tools.javac.util.Assert; -import com.sun.tools.javac.util.StringUtils; /** Access flags and other modifiers for Java classes and members. * @@ -51,7 +52,7 @@ public class Flags { public static String toString(long flags) { StringBuilder buf = new StringBuilder(); String sep = ""; - for (Flag flag : asFlagSet(flags)) { + for (FlagsEnum flag : asFlagSet(flags)) { buf.append(sep); buf.append(flag); sep = " "; @@ -59,12 +60,12 @@ public class Flags { return buf.toString(); } - public static EnumSet asFlagSet(long flags) { - EnumSet flagSet = EnumSet.noneOf(Flag.class); - for (Flag flag : Flag.values()) { - if ((flags & flag.value) != 0) { + public static EnumSet asFlagSet(long flags) { + EnumSet flagSet = EnumSet.noneOf(FlagsEnum.class); + for (FlagsEnum flag : FlagsEnum.values()) { + if ((flags & flag.value()) != 0) { flagSet.add(flag); - flags &= ~flag.value; + flags &= ~flag.value(); } } Assert.check(flags == 0); @@ -73,42 +74,67 @@ public class Flags { /* Standard Java flags. */ + @Use({FlagTarget.CLASS, FlagTarget.METHOD, FlagTarget.VARIABLE}) public static final int PUBLIC = 1; + @Use({FlagTarget.CLASS, FlagTarget.METHOD, FlagTarget.VARIABLE}) public static final int PRIVATE = 1<<1; + @Use({FlagTarget.CLASS, FlagTarget.METHOD, FlagTarget.VARIABLE}) public static final int PROTECTED = 1<<2; + @Use({FlagTarget.BLOCK, FlagTarget.CLASS, FlagTarget.METHOD, FlagTarget.VARIABLE}) public static final int STATIC = 1<<3; + @Use({FlagTarget.CLASS, FlagTarget.METHOD, FlagTarget.VARIABLE}) public static final int FINAL = 1<<4; + @Use({FlagTarget.METHOD}) public static final int SYNCHRONIZED = 1<<5; + @Use({FlagTarget.VARIABLE}) public static final int VOLATILE = 1<<6; + @Use({FlagTarget.VARIABLE}) public static final int TRANSIENT = 1<<7; + @Use({FlagTarget.METHOD}) public static final int NATIVE = 1<<8; + @Use({FlagTarget.CLASS}) public static final int INTERFACE = 1<<9; + @Use({FlagTarget.CLASS, FlagTarget.METHOD}) public static final int ABSTRACT = 1<<10; + @Use({FlagTarget.CLASS, FlagTarget.METHOD}) public static final int STRICTFP = 1<<11; /* Flag that marks a symbol synthetic, added in classfile v49.0. */ + @Use({FlagTarget.CLASS, FlagTarget.METHOD, FlagTarget.VARIABLE}) public static final int SYNTHETIC = 1<<12; /** Flag that marks attribute interfaces, added in classfile v49.0. */ + @Use({FlagTarget.CLASS}) public static final int ANNOTATION = 1<<13; /** An enumeration type or an enumeration constant, added in * classfile v49.0. */ + @Use({FlagTarget.CLASS, FlagTarget.VARIABLE}) public static final int ENUM = 1<<14; /** Added in SE8, represents constructs implicitly declared in source. */ + @Use({FlagTarget.MODULE, FlagTarget.VARIABLE}) public static final int MANDATED = 1<<15; + @NotFlag public static final int StandardFlags = 0x0fff; // Because the following access flags are overloaded with other // bit positions, we translate them when reading and writing class // files into unique bits positions: ACC_SYNTHETIC <-> SYNTHETIC, // for example. - public static final int ACC_SUPER = 0x0020; - public static final int ACC_BRIDGE = 0x0040; - public static final int ACC_VARARGS = 0x0080; - public static final int ACC_MODULE = 0x8000; + @Use({FlagTarget.CLASS}) + @NoToStringValue + public static final int ACC_SUPER = 1<<5; + @Use({FlagTarget.METHOD}) + @NoToStringValue + public static final int ACC_BRIDGE = 1<<6; + @Use({FlagTarget.METHOD}) + @NoToStringValue + public static final int ACC_VARARGS = 1<<7; + @Use({FlagTarget.CLASS}) + @NoToStringValue + public static final int ACC_MODULE = 1<<15; /* *************************************** * Internal compiler flags (no bits in the lower 16). @@ -116,25 +142,30 @@ public class Flags { /** Flag is set if symbol is deprecated. See also DEPRECATED_REMOVAL. */ + @Use({FlagTarget.CLASS, FlagTarget.METHOD, FlagTarget.MODULE, FlagTarget.PACKAGE, FlagTarget.TYPE_VAR, FlagTarget.VARIABLE}) public static final int DEPRECATED = 1<<17; /** Flag is set for a variable symbol if the variable's definition * has an initializer part. */ + @Use({FlagTarget.VARIABLE}) public static final int HASINIT = 1<<18; /** Class is an implicitly declared top level class. */ + @Use({FlagTarget.CLASS}) public static final int IMPLICIT_CLASS = 1<<19; /** Flag is set for compiler-generated anonymous method symbols * that `own' an initializer block. */ + @Use({FlagTarget.METHOD}) public static final int BLOCK = 1<<20; /** Flag is set for ClassSymbols that are being compiled from source. */ - public static final int FROM_SOURCE = 1<<21; //ClassSymbols + @Use({FlagTarget.CLASS}) + public static final int FROM_SOURCE = 1<<21; /** Flag is set for nested classes that do not access instance members * or `this' of an outer class and therefore don't need to be passed @@ -143,25 +174,30 @@ public class Flags { * todo: use this value for optimizing away this$n parameters in * other cases. */ + @Use({FlagTarget.CLASS, FlagTarget.VARIABLE}) public static final int NOOUTERTHIS = 1<<22; /** Flag is set for package symbols if a package has a member or * directory and therefore exists. */ + @Use({FlagTarget.CLASS, FlagTarget.PACKAGE}) public static final int EXISTS = 1<<23; /** Flag is set for compiler-generated compound classes * representing multiple variable bounds */ + @Use({FlagTarget.CLASS}) public static final int COMPOUND = 1<<24; /** Flag is set for class symbols if a class file was found for this class. */ + @Use({FlagTarget.CLASS}) public static final int CLASS_SEEN = 1<<25; /** Flag is set for class symbols if a source file was found for this * class. */ + @Use({FlagTarget.CLASS}) public static final int SOURCE_SEEN = 1<<26; /* State flags (are reset during compilation). @@ -172,242 +208,291 @@ public class Flags { * relations. Similarly for constructor call cycle detection in * Attr. */ + @Use({FlagTarget.CLASS, FlagTarget.METHOD}) public static final int LOCKED = 1<<27; /** Flag for class symbols is set and later re-set to indicate that a class * has been entered but has not yet been attributed. */ + @Use({FlagTarget.CLASS}) public static final int UNATTRIBUTED = 1<<28; /** Flag for synthesized default constructors of anonymous classes. */ - public static final int ANONCONSTR = 1<<29; //non-class members + @Use({FlagTarget.METHOD}) + public static final int ANONCONSTR = 1<<29; /** * Flag to indicate the superclasses of this ClassSymbol has been attributed. */ - public static final int SUPER_OWNER_ATTRIBUTED = 1<<29; //ClassSymbols + @Use({FlagTarget.CLASS}) + public static final int SUPER_OWNER_ATTRIBUTED = 1<<29; /** Flag for class symbols to indicate it has been checked and found * acyclic. */ + @Use({FlagTarget.CLASS, FlagTarget.METHOD, FlagTarget.TYPE_VAR}) public static final int ACYCLIC = 1<<30; /** Flag that marks bridge methods. */ + @Use({FlagTarget.METHOD}) public static final long BRIDGE = 1L<<31; /** Flag that marks formal parameters. */ + @Use({FlagTarget.VARIABLE}) public static final long PARAMETER = 1L<<33; /** Flag that marks varargs methods. */ + @Use({FlagTarget.METHOD, FlagTarget.VARIABLE}) public static final long VARARGS = 1L<<34; /** Flag for annotation type symbols to indicate it has been * checked and found acyclic. */ + @Use({FlagTarget.CLASS}) public static final long ACYCLIC_ANN = 1L<<35; /** Flag that marks a generated default constructor. */ + @Use({FlagTarget.METHOD}) public static final long GENERATEDCONSTR = 1L<<36; /** Flag that marks a hypothetical method that need not really be * generated in the binary, but is present in the symbol table to * simplify checking for erasure clashes - also used for 292 poly sig methods. */ + @Use({FlagTarget.METHOD}) public static final long HYPOTHETICAL = 1L<<37; /** * Flag that marks an internal proprietary class. */ + @Use({FlagTarget.CLASS}) public static final long PROPRIETARY = 1L<<38; /** * Flag that marks a multi-catch parameter. */ + @Use({FlagTarget.VARIABLE}) public static final long UNION = 1L<<39; /** * Flags an erroneous TypeSymbol as viable for recovery. * TypeSymbols only. */ + @Use({FlagTarget.CLASS, FlagTarget.TYPE_VAR}) public static final long RECOVERABLE = 1L<<40; /** * Flag that marks an 'effectively final' local variable. */ + @Use({FlagTarget.VARIABLE}) public static final long EFFECTIVELY_FINAL = 1L<<41; /** * Flag that marks non-override equivalent methods with the same signature, * or a conflicting match binding (BindingSymbol). */ + @Use({FlagTarget.METHOD, FlagTarget.VARIABLE}) public static final long CLASH = 1L<<42; /** * Flag that marks either a default method or an interface containing default methods. */ + @Use({FlagTarget.CLASS, FlagTarget.METHOD}) public static final long DEFAULT = 1L<<43; // part of ExtendedStandardFlags, cannot be reused /** * Flag that marks class as auxiliary, ie a non-public class following * the public class in a source file, that could block implicit compilation. */ + @Use({FlagTarget.CLASS}) public static final long AUXILIARY = 1L<<44; /** * Flag that marks that a symbol is not available in the current profile */ + @Use({FlagTarget.CLASS}) public static final long NOT_IN_PROFILE = 1L<<45; /** * Flag that indicates that an override error has been detected by Check. */ + @Use({FlagTarget.METHOD}) public static final long BAD_OVERRIDE = 1L<<45; /** * Flag that indicates a signature polymorphic method (292). */ + @Use({FlagTarget.METHOD}) public static final long SIGNATURE_POLYMORPHIC = 1L<<46; /** * Flag that indicates that an inference variable is used in a 'throws' clause. */ + @Use({FlagTarget.TYPE_VAR}) public static final long THROWS = 1L<<47; /** * Flag to indicate sealed class/interface declaration. */ + @Use({FlagTarget.CLASS}) public static final long SEALED = 1L<<48; // part of ExtendedStandardFlags, cannot be reused /** * Flag that marks a synthetic method body for a lambda expression */ - public static final long LAMBDA_METHOD = 1L<<49; //MethodSymbols only + @Use({FlagTarget.METHOD}) + public static final long LAMBDA_METHOD = 1L<<49; /** * Flag that marks a synthetic local capture field in a local/anon class */ - public static final long LOCAL_CAPTURE_FIELD = 1L<<49; //VarSymbols only + @Use({FlagTarget.VARIABLE}) + public static final long LOCAL_CAPTURE_FIELD = 1L<<49; /** * Flag to control recursion in TransTypes */ + @Use({FlagTarget.CLASS}) public static final long TYPE_TRANSLATED = 1L<<50; /** * Flag to indicate class symbol is for module-info */ + @Use({FlagTarget.CLASS}) public static final long MODULE = 1L<<51; /** * Flag to indicate the given ModuleSymbol is an automatic module. */ - public static final long AUTOMATIC_MODULE = 1L<<52; //ModuleSymbols only + @Use({FlagTarget.MODULE}) + public static final long AUTOMATIC_MODULE = 1L<<52; /** * Flag to indicate the given PackageSymbol contains any non-.java and non-.class resources. */ - public static final long HAS_RESOURCE = 1L<<52; //PackageSymbols only + @Use({FlagTarget.PACKAGE}) + public static final long HAS_RESOURCE = 1L<<52; /** * Flag to indicate the given ParamSymbol has a user-friendly name filled. */ - public static final long NAME_FILLED = 1L<<52; //ParamSymbols only + @Use({FlagTarget.VARIABLE}) //ParamSymbols only + public static final long NAME_FILLED = 1L<<52; /** * Flag to indicate the given ModuleSymbol is a system module. */ - public static final long SYSTEM_MODULE = 1L<<53; //ModuleSymbols only + @Use({FlagTarget.MODULE}) + public static final long SYSTEM_MODULE = 1L<<53; /** * Flag to indicate the given ClassSymbol is a value based. */ - public static final long VALUE_BASED = 1L<<53; //ClassSymbols only + @Use({FlagTarget.CLASS}) + public static final long VALUE_BASED = 1L<<53; /** * Flag to indicate the given symbol has a @Deprecated annotation. */ + @Use({FlagTarget.CLASS, FlagTarget.METHOD, FlagTarget.MODULE, FlagTarget.PACKAGE, FlagTarget.TYPE_VAR, FlagTarget.VARIABLE}) public static final long DEPRECATED_ANNOTATION = 1L<<54; /** * Flag to indicate the given symbol has been deprecated and marked for removal. */ + @Use({FlagTarget.CLASS, FlagTarget.METHOD, FlagTarget.MODULE, FlagTarget.PACKAGE, FlagTarget.TYPE_VAR, FlagTarget.VARIABLE}) public static final long DEPRECATED_REMOVAL = 1L<<55; /** * Flag to indicate the API element in question is for a preview API. */ + @Use({FlagTarget.CLASS, FlagTarget.METHOD, FlagTarget.MODULE, FlagTarget.PACKAGE, FlagTarget.TYPE_VAR, FlagTarget.VARIABLE}) public static final long PREVIEW_API = 1L<<56; //any Symbol kind /** * Flag for synthesized default constructors of anonymous classes that have an enclosing expression. */ + @Use({FlagTarget.METHOD}) public static final long ANONCONSTR_BASED = 1L<<57; /** * Flag that marks finalize block as body-only, should not be copied into catch clauses. * Used to implement try-with-resources. */ - public static final long BODY_ONLY_FINALIZE = 1L<<17; //blocks only + @Use({FlagTarget.BLOCK}) + public static final long BODY_ONLY_FINALIZE = 1L<<17; /** * Flag to indicate the API element in question is for a preview API. */ + @Use({FlagTarget.CLASS, FlagTarget.METHOD, FlagTarget.MODULE, FlagTarget.PACKAGE, FlagTarget.TYPE_VAR, FlagTarget.VARIABLE}) public static final long PREVIEW_REFLECTIVE = 1L<<58; //any Symbol kind /** * Flag to indicate the given variable is a match binding variable. */ + @Use({FlagTarget.VARIABLE}) public static final long MATCH_BINDING = 1L<<59; /** * A flag to indicate a match binding variable whose scope extends after the current statement. */ + @Use({FlagTarget.VARIABLE}) public static final long MATCH_BINDING_TO_OUTER = 1L<<60; /** * Flag to indicate that a class is a record. The flag is also used to mark fields that are * part of the state vector of a record and to mark the canonical constructor */ - public static final long RECORD = 1L<<61; // ClassSymbols, MethodSymbols and VarSymbols + @Use({FlagTarget.CLASS, FlagTarget.VARIABLE, FlagTarget.METHOD}) + public static final long RECORD = 1L<<61; /** * Flag to mark a record constructor as a compact one */ - public static final long COMPACT_RECORD_CONSTRUCTOR = 1L<<51; // MethodSymbols only + @Use({FlagTarget.METHOD}) + public static final long COMPACT_RECORD_CONSTRUCTOR = 1L<<51; /** * Flag to mark a record field that was not initialized in the compact constructor */ - public static final long UNINITIALIZED_FIELD= 1L<<51; // VarSymbols only + @Use({FlagTarget.VARIABLE}) + public static final long UNINITIALIZED_FIELD= 1L<<51; /** Flag is set for compiler-generated record members, it could be applied to * accessors and fields */ - public static final int GENERATED_MEMBER = 1<<24; // MethodSymbols and VarSymbols + @Use({FlagTarget.METHOD, FlagTarget.VARIABLE}) + public static final int GENERATED_MEMBER = 1<<24; /** * Flag to indicate restricted method declaration. */ - public static final long RESTRICTED = 1L<<62; // MethodSymbols + @Use({FlagTarget.METHOD}) + public static final long RESTRICTED = 1L<<62; /** * Flag to indicate parameters that require identity. */ - public static final long REQUIRES_IDENTITY = 1L<<62; // VarSymbols (parameters) + @Use({FlagTarget.VARIABLE}) //ParamSymbols only + public static final long REQUIRES_IDENTITY = 1L<<62; /** * Flag to indicate type annotations have been queued for field initializers. */ - public static final long FIELD_INIT_TYPE_ANNOTATIONS_QUEUED = 1L<<53; // VarSymbols + @Use({FlagTarget.VARIABLE}) + public static final long FIELD_INIT_TYPE_ANNOTATIONS_QUEUED = 1L<<53; /** * Flag to indicate that the class/interface was declared with the non-sealed modifier. */ + @Use({FlagTarget.CLASS}) + @CustomToStringValue("non-sealed") public static final long NON_SEALED = 1L<<63; // part of ExtendedStandardFlags, cannot be reused /** @@ -422,6 +507,7 @@ public class Flags { /** Modifier masks. */ + @NotFlag public static final int AccessFlags = PUBLIC | PROTECTED | PRIVATE, LocalClassFlags = FINAL | ABSTRACT | STRICTFP | ENUM | SYNTHETIC, @@ -438,6 +524,7 @@ public class Flags { SYNCHRONIZED | FINAL | STRICTFP, RecordMethodFlags = AccessFlags | ABSTRACT | STATIC | SYNCHRONIZED | FINAL | STRICTFP; + @NotFlag public static final long //NOTE: flags in ExtendedStandardFlags cannot be overlayed across Symbol kinds: ExtendedStandardFlags = (long)StandardFlags | DEFAULT | SEALED | NON_SEALED, @@ -491,91 +578,45 @@ public class Flags { return symbol.getConstValue() != null; } - - public enum Flag { - PUBLIC(Flags.PUBLIC), - PRIVATE(Flags.PRIVATE), - PROTECTED(Flags.PROTECTED), - STATIC(Flags.STATIC), - FINAL(Flags.FINAL), - SYNCHRONIZED(Flags.SYNCHRONIZED), - VOLATILE(Flags.VOLATILE), - TRANSIENT(Flags.TRANSIENT), - NATIVE(Flags.NATIVE), - INTERFACE(Flags.INTERFACE), - ABSTRACT(Flags.ABSTRACT), - DEFAULT(Flags.DEFAULT), - STRICTFP(Flags.STRICTFP), - BRIDGE(Flags.BRIDGE), - SYNTHETIC(Flags.SYNTHETIC), - ANNOTATION(Flags.ANNOTATION), - DEPRECATED(Flags.DEPRECATED), - HASINIT(Flags.HASINIT), - IMPLICIT_CLASS(Flags.IMPLICIT_CLASS), - BLOCK(Flags.BLOCK), - FROM_SOURCE(Flags.FROM_SOURCE), - ENUM(Flags.ENUM), - MANDATED(Flags.MANDATED), - NOOUTERTHIS(Flags.NOOUTERTHIS), - EXISTS(Flags.EXISTS), - COMPOUND(Flags.COMPOUND), - CLASS_SEEN(Flags.CLASS_SEEN), - SOURCE_SEEN(Flags.SOURCE_SEEN), - LOCKED(Flags.LOCKED), - UNATTRIBUTED(Flags.UNATTRIBUTED), - ANONCONSTR(Flags.ANONCONSTR), - ACYCLIC(Flags.ACYCLIC), - PARAMETER(Flags.PARAMETER), - VARARGS(Flags.VARARGS), - ACYCLIC_ANN(Flags.ACYCLIC_ANN), - GENERATEDCONSTR(Flags.GENERATEDCONSTR), - HYPOTHETICAL(Flags.HYPOTHETICAL), - PROPRIETARY(Flags.PROPRIETARY), - UNION(Flags.UNION), - EFFECTIVELY_FINAL(Flags.EFFECTIVELY_FINAL), - CLASH(Flags.CLASH), - AUXILIARY(Flags.AUXILIARY), - NOT_IN_PROFILE(Flags.NOT_IN_PROFILE), - BAD_OVERRIDE(Flags.BAD_OVERRIDE), - SIGNATURE_POLYMORPHIC(Flags.SIGNATURE_POLYMORPHIC), - THROWS(Flags.THROWS), - LAMBDA_METHOD(Flags.LAMBDA_METHOD), - TYPE_TRANSLATED(Flags.TYPE_TRANSLATED), - MODULE(Flags.MODULE), - AUTOMATIC_MODULE(Flags.AUTOMATIC_MODULE), - SYSTEM_MODULE(Flags.SYSTEM_MODULE), - DEPRECATED_ANNOTATION(Flags.DEPRECATED_ANNOTATION), - DEPRECATED_REMOVAL(Flags.DEPRECATED_REMOVAL), - HAS_RESOURCE(Flags.HAS_RESOURCE), - SEALED(Flags.SEALED), - ANONCONSTR_BASED(Flags.ANONCONSTR_BASED), - NAME_FILLED(Flags.NAME_FILLED), - PREVIEW_API(Flags.PREVIEW_API), - PREVIEW_REFLECTIVE(Flags.PREVIEW_REFLECTIVE), - MATCH_BINDING(Flags.MATCH_BINDING), - MATCH_BINDING_TO_OUTER(Flags.MATCH_BINDING_TO_OUTER), - RECORD(Flags.RECORD), - RECOVERABLE(Flags.RECOVERABLE), - RESTRICTED(Flags.RESTRICTED), - NON_SEALED(Flags.NON_SEALED) { - @Override - public String toString() { - return "non-sealed"; - } - }; - - Flag(long flag) { - this.value = flag; - this.lowercaseName = StringUtils.toLowerCase(name()); - } - - @Override - public String toString() { - return lowercaseName; - } - - final long value; - final String lowercaseName; + public enum FlagTarget { + /** This flag can appear the JCBlock. + */ + BLOCK, + /** This flag can appear on ClassSymbols. + */ + CLASS, + /** This flag can appear on ModuleSymbols. + */ + MODULE, + /** This flag can appear on PackageSymbols. + */ + PACKAGE, + /** This flag can appear on TypeVarSymbols. + */ + TYPE_VAR, + /** This flag can appear on MethodSymbols. + */ + METHOD, + /** This flag can appear on VarSymbols, includes + * including ParamSymbol, and BindingSymbol. + */ + VARIABLE; } + @Retention(RetentionPolicy.RUNTIME) + public @interface Use { + public FlagTarget[] value(); + } + + @Retention(RetentionPolicy.RUNTIME) + public @interface NotFlag {} + + @Retention(RetentionPolicy.RUNTIME) + public @interface CustomToStringValue { + public String value(); + } + + @Retention(RetentionPolicy.RUNTIME) + public @interface NoToStringValue { + } } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Modules.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Modules.java index 47066b24de9..4d0af014d83 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Modules.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Modules.java @@ -62,7 +62,7 @@ import com.sun.tools.javac.code.Directive.RequiresDirective; import com.sun.tools.javac.code.Directive.RequiresFlag; import com.sun.tools.javac.code.Directive.UsesDirective; import com.sun.tools.javac.code.Flags; -import com.sun.tools.javac.code.Flags.Flag; +import com.sun.tools.javac.code.FlagsEnum; import com.sun.tools.javac.code.Kinds; import com.sun.tools.javac.code.Lint; import com.sun.tools.javac.code.Lint.LintCategory; @@ -825,7 +825,7 @@ public class Modules extends JCTree.Visitor { } if (tree.isStaticPhase) { if (msym == syms.java_base && source.compareTo(Source.JDK10) >= 0) { - log.error(tree.pos(), Errors.ModNotAllowedHere(EnumSet.of(Flag.STATIC))); + log.error(tree.pos(), Errors.ModNotAllowedHere(EnumSet.of(FlagsEnum.STATIC))); } else { flags.add(RequiresFlag.STATIC_PHASE); } diff --git a/test/langtools/tools/javac/diags/ArgTypeCompilerFactory.java b/test/langtools/tools/javac/diags/ArgTypeCompilerFactory.java index e40109f99f3..823a0f01a7f 100644 --- a/test/langtools/tools/javac/diags/ArgTypeCompilerFactory.java +++ b/test/langtools/tools/javac/diags/ArgTypeCompilerFactory.java @@ -29,7 +29,7 @@ import javax.tools.*; import com.sun.tools.javac.api.*; import com.sun.tools.javac.api.DiagnosticFormatter.Configuration.DiagnosticPart; import com.sun.tools.javac.api.Formattable.LocalizedString; -import com.sun.tools.javac.code.Flags.Flag; +import com.sun.tools.javac.code.FlagsEnum; import com.sun.tools.javac.code.Kinds.KindName; import com.sun.tools.javac.code.*; import com.sun.tools.javac.file.*; @@ -303,7 +303,7 @@ class ArgTypeCompilerFactory implements Example.Compiler.Factory { return "number"; if (o instanceof String) return "string"; - if (o instanceof Flag) + if (o instanceof FlagsEnum) return "modifier"; if (o instanceof KindName) return "symbol kind"; diff --git a/test/langtools/tools/javac/flags/FlagsTest.java b/test/langtools/tools/javac/flags/FlagsTest.java index 1dcb0606a65..a7a9051af33 100644 --- a/test/langtools/tools/javac/flags/FlagsTest.java +++ b/test/langtools/tools/javac/flags/FlagsTest.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2018, Google LLC. All rights reserved. + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,28 +24,84 @@ /** * @test - * @bug 8211138 + * @bug 8211138 8362885 * @summary Missing Flag enum constants * @library /tools/javac/lib * @modules jdk.compiler/com.sun.tools.javac.code - * @run main FlagsTest + * @compile FlagsTest.java + * @run main/manual FlagsTest */ import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.code.Flags.FlagTarget; +import com.sun.tools.javac.code.Flags.NotFlag; +import com.sun.tools.javac.code.Flags.Use; import java.lang.reflect.Field; -import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; public class FlagsTest { - public static void main(String[] args) throws IllegalAccessException { - for (Field f : Flags.class.getFields()) { - if (!Modifier.isStatic(f.getModifiers())) { - continue; - } - long flag = ((Number) f.get(null)).longValue(); - try { - Flags.asFlagSet(flag); - } catch (AssertionError e) { - throw new AssertionError("missing Flags enum constant for: " + f.getName(), e); - } + + private static final int U2_SIZE = 16; + + public static void main(String[] args) throws Throwable { + findFreeFlags(); + } + + private static void findFreeFlags() throws Throwable { + Map>> target2Flag2Fields = computeTarget2Flag2Fields(); + + for (FlagTarget target : FlagTarget.values()) { + long freeFlags = ~collectFlags(target2Flag2Fields, target); + + printFreeFlags(target.name(), freeFlags); } } + + private static Map>> computeTarget2Flag2Fields() throws Throwable { + Map>> target2Flag2Fields = new HashMap<>(); + for (Field f : Flags.class.getFields()) { + if (f.isAnnotationPresent(NotFlag.class)) { + continue; + } + + Use use = f.getAnnotation(Use.class); + + if (use == null) { + throw new AssertionError("No @Use and no @NotFlag for: " + f.getName()); + } + + long flagValue = ((Number) f.get(null)).longValue(); + + for (FlagTarget target : use.value()) { + target2Flag2Fields.computeIfAbsent(target, _ -> new HashMap<>()) + .computeIfAbsent(flagValue, _ -> new ArrayList<>()) + .add(f); + } + } + return target2Flag2Fields; + } + + private static void printFreeFlags(String comment, long freeFlags) { + System.err.print("free flags for " + comment + ": "); + for (int bit = U2_SIZE; bit < Long.SIZE; bit++) { //lowest 16 bits are used in classfiles, never suggest adding anything there + if ((freeFlags & (1L << bit)) != 0) { + System.err.print("1L<<" + bit + " "); + } + } + System.err.println(); + } + + private static long collectFlags(Map>> target2Flag2Fields, FlagTarget... forTargets) { + long flags = 0; + + for (FlagTarget target : forTargets) { + for (long used : target2Flag2Fields.get(target).keySet()) { + flags |= used; + } + } + + return flags; + } } From e77cdd93ead5601fea4bb1bf1847835e1097b851 Mon Sep 17 00:00:00 2001 From: Fredrik Bredberg Date: Wed, 13 Aug 2025 08:47:08 +0000 Subject: [PATCH 110/580] 8364570: Remove LockingMode related code from riscv64 Reviewed-by: fyang, fjiang --- .../cpu/riscv/c1_LIRAssembler_riscv.cpp | 14 +- .../cpu/riscv/c1_MacroAssembler_riscv.cpp | 84 +------ .../cpu/riscv/c2_MacroAssembler_riscv.cpp | 230 ------------------ .../cpu/riscv/c2_MacroAssembler_riscv.hpp | 7 +- src/hotspot/cpu/riscv/interp_masm_riscv.cpp | 152 +++--------- .../cpu/riscv/macroAssembler_riscv.cpp | 2 - src/hotspot/cpu/riscv/riscv.ad | 40 +-- src/hotspot/cpu/riscv/sharedRuntime_riscv.cpp | 75 +----- .../templateInterpreterGenerator_riscv.cpp | 27 +- 9 files changed, 55 insertions(+), 576 deletions(-) diff --git a/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp b/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp index 81b829bde9a..2a65270af92 100644 --- a/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp @@ -339,11 +339,7 @@ int LIR_Assembler::emit_unwind_handler() { if (method()->is_synchronized()) { monitor_address(0, FrameMap::r10_opr); stub = new MonitorExitStub(FrameMap::r10_opr, true, 0); - if (LockingMode == LM_MONITOR) { - __ j(*stub->entry()); - } else { - __ unlock_object(x15, x14, x10, x16, *stub->entry()); - } + __ unlock_object(x15, x14, x10, x16, *stub->entry()); __ bind(*stub->continuation()); } @@ -1497,13 +1493,7 @@ void LIR_Assembler::emit_lock(LIR_OpLock* op) { Register hdr = op->hdr_opr()->as_register(); Register lock = op->lock_opr()->as_register(); Register temp = op->scratch_opr()->as_register(); - if (LockingMode == LM_MONITOR) { - if (op->info() != nullptr) { - add_debug_info_for_null_check_here(op->info()); - __ null_check(obj, -1); - } - __ j(*op->stub()->entry()); - } else if (op->code() == lir_lock) { + if (op->code() == lir_lock) { assert(BasicLock::displaced_header_offset_in_bytes() == 0, "lock_reg must point to the displaced header"); // add debug info for NullPointerException only if one is possible int null_check_offset = __ lock_object(hdr, obj, lock, temp, *op->stub()->entry()); diff --git a/src/hotspot/cpu/riscv/c1_MacroAssembler_riscv.cpp b/src/hotspot/cpu/riscv/c1_MacroAssembler_riscv.cpp index 3ce764b1861..8198192f506 100644 --- a/src/hotspot/cpu/riscv/c1_MacroAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/c1_MacroAssembler_riscv.cpp @@ -49,8 +49,6 @@ void C1_MacroAssembler::float_cmp(bool is_float, int unordered_result, } int C1_MacroAssembler::lock_object(Register hdr, Register obj, Register disp_hdr, Register temp, Label& slow_case) { - const int aligned_mask = BytesPerWord - 1; - const int hdr_offset = oopDesc::mark_offset_in_bytes(); assert_different_registers(hdr, obj, disp_hdr, temp, t0, t1); int null_check_offset = -1; @@ -61,97 +59,19 @@ int C1_MacroAssembler::lock_object(Register hdr, Register obj, Register disp_hdr null_check_offset = offset(); - if (LockingMode == LM_LIGHTWEIGHT) { - lightweight_lock(disp_hdr, obj, hdr, temp, t1, slow_case); - } else if (LockingMode == LM_LEGACY) { - - if (DiagnoseSyncOnValueBasedClasses != 0) { - load_klass(hdr, obj); - lbu(hdr, Address(hdr, Klass::misc_flags_offset())); - test_bit(temp, hdr, exact_log2(KlassFlags::_misc_is_value_based_class)); - bnez(temp, slow_case, /* is_far */ true); - } - - Label done; - // Load object header - ld(hdr, Address(obj, hdr_offset)); - // and mark it as unlocked - ori(hdr, hdr, markWord::unlocked_value); - // save unlocked object header into the displaced header location on the stack - sd(hdr, Address(disp_hdr, 0)); - // test if object header is still the same (i.e. unlocked), and if so, store the - // displaced header address in the object header - if it is not the same, get the - // object header instead - la(temp, Address(obj, hdr_offset)); - // if the object header was the same, we're done - cmpxchgptr(hdr, disp_hdr, temp, t1, done, /*fallthough*/nullptr); - // if the object header was not the same, it is now in the hdr register - // => test if it is a stack pointer into the same stack (recursive locking), i.e.: - // - // 1) (hdr & aligned_mask) == 0 - // 2) sp <= hdr - // 3) hdr <= sp + page_size - // - // these 3 tests can be done by evaluating the following expression: - // - // (hdr -sp) & (aligned_mask - page_size) - // - // assuming both the stack pointer and page_size have their least - // significant 2 bits cleared and page_size is a power of 2 - sub(hdr, hdr, sp); - mv(temp, aligned_mask - (int)os::vm_page_size()); - andr(hdr, hdr, temp); - // for recursive locking, the result is zero => save it in the displaced header - // location (null in the displaced hdr location indicates recursive locking) - sd(hdr, Address(disp_hdr, 0)); - // otherwise we don't care about the result and handle locking via runtime call - bnez(hdr, slow_case, /* is_far */ true); - - // done - bind(done); - inc_held_monitor_count(t0); - } + lightweight_lock(disp_hdr, obj, hdr, temp, t1, slow_case); return null_check_offset; } void C1_MacroAssembler::unlock_object(Register hdr, Register obj, Register disp_hdr, Register temp, Label& slow_case) { - const int aligned_mask = BytesPerWord - 1; - const int hdr_offset = oopDesc::mark_offset_in_bytes(); assert_different_registers(hdr, obj, disp_hdr, temp, t0, t1); - Label done; - - if (LockingMode != LM_LIGHTWEIGHT) { - // load displaced header - ld(hdr, Address(disp_hdr, 0)); - // if the loaded hdr is null we had recursive locking - // if we had recursive locking, we are done - beqz(hdr, done); - } // load object ld(obj, Address(disp_hdr, BasicObjectLock::obj_offset())); verify_oop(obj); - if (LockingMode == LM_LIGHTWEIGHT) { - lightweight_unlock(obj, hdr, temp, t1, slow_case); - } else if (LockingMode == LM_LEGACY) { - // test if object header is pointing to the displaced header, and if so, restore - // the displaced header in the object - if the object header is not pointing to - // the displaced header, get the object header instead - // if the object header was not pointing to the displaced header, - // we do unlocking via runtime call - if (hdr_offset) { - la(temp, Address(obj, hdr_offset)); - cmpxchgptr(disp_hdr, hdr, temp, t1, done, &slow_case); - } else { - cmpxchgptr(disp_hdr, hdr, obj, t1, done, &slow_case); - } - - // done - bind(done); - dec_held_monitor_count(t0); - } + lightweight_unlock(obj, hdr, temp, t1, slow_case); } // Defines obj, preserves var_size_in_bytes diff --git a/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp b/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp index bf71d2c68f1..aac2c98fc86 100644 --- a/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp @@ -43,240 +43,11 @@ #define BIND(label) bind(label); BLOCK_COMMENT(#label ":") -void C2_MacroAssembler::fast_lock(Register objectReg, Register boxReg, - Register tmp1Reg, Register tmp2Reg, Register tmp3Reg, Register tmp4Reg) { - // Use cr register to indicate the fast_lock result: zero for success; non-zero for failure. - Register flag = t1; - Register oop = objectReg; - Register box = boxReg; - Register disp_hdr = tmp1Reg; - Register tmp = tmp2Reg; - Label object_has_monitor; - // Finish fast lock successfully. MUST branch to with flag == 0 - Label locked; - // Finish fast lock unsuccessfully. slow_path MUST branch to with flag != 0 - Label slow_path; - - assert(LockingMode != LM_LIGHTWEIGHT, "lightweight locking should use fast_lock_lightweight"); - assert_different_registers(oop, box, tmp, disp_hdr, flag, tmp3Reg, t0); - - mv(flag, 1); - - // Load markWord from object into displaced_header. - ld(disp_hdr, Address(oop, oopDesc::mark_offset_in_bytes())); - - if (DiagnoseSyncOnValueBasedClasses != 0) { - load_klass(tmp, oop); - lbu(tmp, Address(tmp, Klass::misc_flags_offset())); - test_bit(tmp, tmp, exact_log2(KlassFlags::_misc_is_value_based_class)); - bnez(tmp, slow_path); - } - - // Check for existing monitor - test_bit(tmp, disp_hdr, exact_log2(markWord::monitor_value)); - bnez(tmp, object_has_monitor); - - if (LockingMode == LM_MONITOR) { - j(slow_path); - } else { - assert(LockingMode == LM_LEGACY, "must be"); - // Set tmp to be (markWord of object | UNLOCK_VALUE). - ori(tmp, disp_hdr, markWord::unlocked_value); - - // Initialize the box. (Must happen before we update the object mark!) - sd(tmp, Address(box, BasicLock::displaced_header_offset_in_bytes())); - - // Compare object markWord with an unlocked value (tmp) and if - // equal exchange the stack address of our box with object markWord. - // On failure disp_hdr contains the possibly locked markWord. - cmpxchg(/*memory address*/oop, /*expected value*/tmp, /*new value*/box, Assembler::int64, - Assembler::aq, Assembler::rl, /*result*/disp_hdr); - beq(disp_hdr, tmp, locked); - - assert(oopDesc::mark_offset_in_bytes() == 0, "offset of _mark is not 0"); - - // If the compare-and-exchange succeeded, then we found an unlocked - // object, will have now locked it will continue at label locked - // We did not see an unlocked object so try the fast recursive case. - - // Check if the owner is self by comparing the value in the - // markWord of object (disp_hdr) with the stack pointer. - sub(disp_hdr, disp_hdr, sp); - mv(tmp, (intptr_t) (~(os::vm_page_size()-1) | (uintptr_t)markWord::lock_mask_in_place)); - // If (mark & lock_mask) == 0 and mark - sp < page_size, we are stack-locking and goto label - // locked, hence we can store 0 as the displaced header in the box, which indicates that it - // is a recursive lock. - andr(tmp/*==0?*/, disp_hdr, tmp); - sd(tmp/*==0, perhaps*/, Address(box, BasicLock::displaced_header_offset_in_bytes())); - beqz(tmp, locked); - j(slow_path); - } - - // Handle existing monitor. - bind(object_has_monitor); - - // Try to CAS owner (no owner => current thread's _monitor_owner_id). - add(tmp, disp_hdr, (in_bytes(ObjectMonitor::owner_offset()) - markWord::monitor_value)); - Register tid = tmp4Reg; - ld(tid, Address(xthread, JavaThread::monitor_owner_id_offset())); - cmpxchg(/*memory address*/tmp, /*expected value*/zr, /*new value*/tid, Assembler::int64, - Assembler::aq, Assembler::rl, /*result*/tmp3Reg); // cas succeeds if tmp3Reg == zr(expected) - - // Store a non-null value into the box to avoid looking like a re-entrant - // lock. The fast-path monitor unlock code checks for - // markWord::monitor_value so use markWord::unused_mark which has the - // relevant bit set, and also matches ObjectSynchronizer::slow_enter. - mv(tmp, (address)markWord::unused_mark().value()); - sd(tmp, Address(box, BasicLock::displaced_header_offset_in_bytes())); - - beqz(tmp3Reg, locked); // CAS success means locking succeeded - - bne(tmp3Reg, tid, slow_path); // Check for recursive locking - - // Recursive lock case - increment(Address(disp_hdr, in_bytes(ObjectMonitor::recursions_offset()) - markWord::monitor_value), 1, tmp2Reg, tmp3Reg); - - bind(locked); - mv(flag, zr); - if (LockingMode == LM_LEGACY) { - inc_held_monitor_count(t0); - } - -#ifdef ASSERT - // Check that locked label is reached with flag == 0. - Label flag_correct; - beqz(flag, flag_correct); - stop("Fast Lock Flag != 0"); -#endif - - bind(slow_path); -#ifdef ASSERT - // Check that slow_path label is reached with flag != 0. - bnez(flag, flag_correct); - stop("Fast Lock Flag == 0"); - bind(flag_correct); -#endif - // C2 uses the value of flag (0 vs !0) to determine the continuation. -} - -void C2_MacroAssembler::fast_unlock(Register objectReg, Register boxReg, - Register tmp1Reg, Register tmp2Reg) { - // Use cr register to indicate the fast_unlock result: zero for success; non-zero for failure. - Register flag = t1; - Register oop = objectReg; - Register box = boxReg; - Register disp_hdr = tmp1Reg; - Register owner_addr = tmp1Reg; - Register tmp = tmp2Reg; - Label object_has_monitor; - // Finish fast lock successfully. MUST branch to with flag == 0 - Label unlocked; - // Finish fast lock unsuccessfully. slow_path MUST branch to with flag != 0 - Label slow_path; - - assert(LockingMode != LM_LIGHTWEIGHT, "lightweight locking should use fast_unlock_lightweight"); - assert_different_registers(oop, box, tmp, disp_hdr, flag, t0); - - mv(flag, 1); - - if (LockingMode == LM_LEGACY) { - // Find the lock address and load the displaced header from the stack. - ld(disp_hdr, Address(box, BasicLock::displaced_header_offset_in_bytes())); - - // If the displaced header is 0, we have a recursive unlock. - beqz(disp_hdr, unlocked); - } - - // Handle existing monitor. - ld(tmp, Address(oop, oopDesc::mark_offset_in_bytes())); - test_bit(t0, tmp, exact_log2(markWord::monitor_value)); - bnez(t0, object_has_monitor); - - if (LockingMode == LM_MONITOR) { - j(slow_path); - } else { - assert(LockingMode == LM_LEGACY, "must be"); - // Check if it is still a light weight lock, this is true if we - // see the stack address of the basicLock in the markWord of the - // object. - - cmpxchg(/*memory address*/oop, /*expected value*/box, /*new value*/disp_hdr, Assembler::int64, - Assembler::relaxed, Assembler::rl, /*result*/tmp); - beq(box, tmp, unlocked); // box == tmp if cas succeeds - j(slow_path); - } - - assert(oopDesc::mark_offset_in_bytes() == 0, "offset of _mark is not 0"); - - // Handle existing monitor. - bind(object_has_monitor); - subi(tmp, tmp, (int)markWord::monitor_value); // monitor - ld(disp_hdr, Address(tmp, ObjectMonitor::recursions_offset())); - - Label notRecursive; - beqz(disp_hdr, notRecursive); // Will be 0 if not recursive. - - // Recursive lock - subi(disp_hdr, disp_hdr, 1); - sd(disp_hdr, Address(tmp, ObjectMonitor::recursions_offset())); - j(unlocked); - - bind(notRecursive); - // Compute owner address. - la(owner_addr, Address(tmp, ObjectMonitor::owner_offset())); - - // Set owner to null. - // Release to satisfy the JMM - membar(MacroAssembler::LoadStore | MacroAssembler::StoreStore); - sd(zr, Address(owner_addr)); - // We need a full fence after clearing owner to avoid stranding. - // StoreLoad achieves this. - membar(StoreLoad); - - // Check if the entry_list is empty. - ld(t0, Address(tmp, ObjectMonitor::entry_list_offset())); - beqz(t0, unlocked); // If so we are done. - - // Check if there is a successor. - ld(t0, Address(tmp, ObjectMonitor::succ_offset())); - bnez(t0, unlocked); // If so we are done. - - // Save the monitor pointer in the current thread, so we can try to - // reacquire the lock in SharedRuntime::monitor_exit_helper(). - sd(tmp, Address(xthread, JavaThread::unlocked_inflated_monitor_offset())); - - mv(flag, 1); - j(slow_path); - - bind(unlocked); - mv(flag, zr); - if (LockingMode == LM_LEGACY) { - dec_held_monitor_count(t0); - } - -#ifdef ASSERT - // Check that unlocked label is reached with flag == 0. - Label flag_correct; - beqz(flag, flag_correct); - stop("Fast Lock Flag != 0"); -#endif - - bind(slow_path); -#ifdef ASSERT - // Check that slow_path label is reached with flag != 0. - bnez(flag, flag_correct); - stop("Fast Lock Flag == 0"); - bind(flag_correct); -#endif - // C2 uses the value of flag (0 vs !0) to determine the continuation. -} - void C2_MacroAssembler::fast_lock_lightweight(Register obj, Register box, Register tmp1, Register tmp2, Register tmp3, Register tmp4) { // Flag register, zero for success; non-zero for failure. Register flag = t1; - assert(LockingMode == LM_LIGHTWEIGHT, "must be"); assert_different_registers(obj, box, tmp1, tmp2, tmp3, tmp4, flag, t0); mv(flag, 1); @@ -439,7 +210,6 @@ void C2_MacroAssembler::fast_unlock_lightweight(Register obj, Register box, // Flag register, zero for success; non-zero for failure. Register flag = t1; - assert(LockingMode == LM_LIGHTWEIGHT, "must be"); assert_different_registers(obj, box, tmp1, tmp2, tmp3, flag, t0); mv(flag, 1); diff --git a/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.hpp b/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.hpp index 73fceea3805..309ef8d9d5e 100644 --- a/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.hpp +++ b/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2022, Huawei Technologies Co., Ltd. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -49,11 +49,6 @@ const int STUB_THRESHOLD, Label *STUB, Label *DONE); public: - // Code used by cmpFastLock and cmpFastUnlock mach instructions in .ad file. - void fast_lock(Register object, Register box, - Register tmp1, Register tmp2, Register tmp3, Register tmp4); - void fast_unlock(Register object, Register box, Register tmp1, Register tmp2); - // Code used by cmpFastLockLightweight and cmpFastUnlockLightweight mach instructions in .ad file. void fast_lock_lightweight(Register object, Register box, Register tmp1, Register tmp2, Register tmp3, Register tmp4); diff --git a/src/hotspot/cpu/riscv/interp_masm_riscv.cpp b/src/hotspot/cpu/riscv/interp_masm_riscv.cpp index 92f7169f471..7c4b8444407 100644 --- a/src/hotspot/cpu/riscv/interp_masm_riscv.cpp +++ b/src/hotspot/cpu/riscv/interp_masm_riscv.cpp @@ -733,84 +733,26 @@ void InterpreterMacroAssembler::leave_jfr_critical_section() { void InterpreterMacroAssembler::lock_object(Register lock_reg) { assert(lock_reg == c_rarg1, "The argument is only for looks. It must be c_rarg1"); - if (LockingMode == LM_MONITOR) { - call_VM_preemptable(noreg, - CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter), - lock_reg); - } else { - Label count, done; - const Register swap_reg = x10; - const Register tmp = c_rarg2; - const Register obj_reg = c_rarg3; // Will contain the oop - const Register tmp2 = c_rarg4; - const Register tmp3 = c_rarg5; + const Register tmp = c_rarg2; + const Register obj_reg = c_rarg3; // Will contain the oop + const Register tmp2 = c_rarg4; + const Register tmp3 = c_rarg5; - const int obj_offset = in_bytes(BasicObjectLock::obj_offset()); - const int lock_offset = in_bytes(BasicObjectLock::lock_offset()); - const int mark_offset = lock_offset + - BasicLock::displaced_header_offset_in_bytes(); + // Load object pointer into obj_reg (c_rarg3) + ld(obj_reg, Address(lock_reg, BasicObjectLock::obj_offset())); - Label slow_case; + Label done, slow_case; + lightweight_lock(lock_reg, obj_reg, tmp, tmp2, tmp3, slow_case); + j(done); - // Load object pointer into obj_reg c_rarg3 - ld(obj_reg, Address(lock_reg, obj_offset)); + bind(slow_case); + // Call the runtime routine for slow case + call_VM_preemptable(noreg, + CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter), + lock_reg); - if (LockingMode == LM_LIGHTWEIGHT) { - lightweight_lock(lock_reg, obj_reg, tmp, tmp2, tmp3, slow_case); - j(done); - } else if (LockingMode == LM_LEGACY) { - - if (DiagnoseSyncOnValueBasedClasses != 0) { - load_klass(tmp, obj_reg); - lbu(tmp, Address(tmp, Klass::misc_flags_offset())); - test_bit(tmp, tmp, exact_log2(KlassFlags::_misc_is_value_based_class)); - bnez(tmp, slow_case); - } - - // Load (object->mark() | 1) into swap_reg - ld(t0, Address(obj_reg, oopDesc::mark_offset_in_bytes())); - ori(swap_reg, t0, 1); - - // Save (object->mark() | 1) into BasicLock's displaced header - sd(swap_reg, Address(lock_reg, mark_offset)); - - assert(lock_offset == 0, - "displached header must be first word in BasicObjectLock"); - - cmpxchg_obj_header(swap_reg, lock_reg, obj_reg, tmp, count, /*fallthrough*/nullptr); - - // Test if the oopMark is an obvious stack pointer, i.e., - // 1) (mark & 7) == 0, and - // 2) sp <= mark < mark + os::pagesize() - // - // These 3 tests can be done by evaluating the following - // expression: ((mark - sp) & (7 - os::vm_page_size())), - // assuming both stack pointer and pagesize have their - // least significant 3 bits clear. - // NOTE: the oopMark is in swap_reg x10 as the result of cmpxchg - sub(swap_reg, swap_reg, sp); - mv(t0, (int64_t)(7 - (int)os::vm_page_size())); - andr(swap_reg, swap_reg, t0); - - // Save the test result, for recursive case, the result is zero - sd(swap_reg, Address(lock_reg, mark_offset)); - bnez(swap_reg, slow_case); - - bind(count); - inc_held_monitor_count(t0); - j(done); - } - - bind(slow_case); - - // Call the runtime routine for slow case - call_VM_preemptable(noreg, - CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter), - lock_reg); - - bind(done); - } + bind(done); } @@ -829,58 +771,30 @@ void InterpreterMacroAssembler::unlock_object(Register lock_reg) { assert(lock_reg == c_rarg1, "The argument is only for looks. It must be rarg1"); - if (LockingMode == LM_MONITOR) { - call_VM_leaf(CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorexit), lock_reg); - } else { - Label count, done; + const Register swap_reg = x10; + const Register header_reg = c_rarg2; // Will contain the old oopMark + const Register obj_reg = c_rarg3; // Will contain the oop + const Register tmp_reg = c_rarg4; // Temporary used by lightweight_unlock - const Register swap_reg = x10; - const Register header_reg = c_rarg2; // Will contain the old oopMark - const Register obj_reg = c_rarg3; // Will contain the oop - const Register tmp_reg = c_rarg4; // Temporary used by lightweight_unlock + save_bcp(); // Save in case of exception - save_bcp(); // Save in case of exception + // Load oop into obj_reg (c_rarg3) + ld(obj_reg, Address(lock_reg, BasicObjectLock::obj_offset())); - if (LockingMode != LM_LIGHTWEIGHT) { - // Convert from BasicObjectLock structure to object and BasicLock - // structure Store the BasicLock address into x10 - la(swap_reg, Address(lock_reg, BasicObjectLock::lock_offset())); - } + // Free entry + sd(zr, Address(lock_reg, BasicObjectLock::obj_offset())); - // Load oop into obj_reg(c_rarg3) - ld(obj_reg, Address(lock_reg, BasicObjectLock::obj_offset())); + Label done, slow_case; + lightweight_unlock(obj_reg, header_reg, swap_reg, tmp_reg, slow_case); + j(done); - // Free entry - sd(zr, Address(lock_reg, BasicObjectLock::obj_offset())); + bind(slow_case); + // Call the runtime routine for slow case. + sd(obj_reg, Address(lock_reg, BasicObjectLock::obj_offset())); // restore obj + call_VM_leaf(CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorexit), lock_reg); - Label slow_case; - if (LockingMode == LM_LIGHTWEIGHT) { - lightweight_unlock(obj_reg, header_reg, swap_reg, tmp_reg, slow_case); - j(done); - } else if (LockingMode == LM_LEGACY) { - // Load the old header from BasicLock structure - ld(header_reg, Address(swap_reg, - BasicLock::displaced_header_offset_in_bytes())); - - // Test for recursion - beqz(header_reg, count); - - // Atomic swap back the old header - cmpxchg_obj_header(swap_reg, header_reg, obj_reg, tmp_reg, count, &slow_case); - - bind(count); - dec_held_monitor_count(t0); - j(done); - } - - bind(slow_case); - // Call the runtime routine for slow case. - sd(obj_reg, Address(lock_reg, BasicObjectLock::obj_offset())); // restore obj - call_VM_leaf(CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorexit), lock_reg); - - bind(done); - restore_bcp(); - } + bind(done); + restore_bcp(); } diff --git a/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp b/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp index a4c44b0b1d1..453538e4a5a 100644 --- a/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp @@ -6421,7 +6421,6 @@ void MacroAssembler::test_bit(Register Rd, Register Rs, uint32_t bit_pos) { // - tmp1, tmp2, tmp3: temporary registers, will be destroyed // - slow: branched to if locking fails void MacroAssembler::lightweight_lock(Register basic_lock, Register obj, Register tmp1, Register tmp2, Register tmp3, Label& slow) { - assert(LockingMode == LM_LIGHTWEIGHT, "only used with new lightweight locking"); assert_different_registers(basic_lock, obj, tmp1, tmp2, tmp3, t0); Label push; @@ -6481,7 +6480,6 @@ void MacroAssembler::lightweight_lock(Register basic_lock, Register obj, Registe // - tmp1, tmp2, tmp3: temporary registers // - slow: branched to if unlocking fails void MacroAssembler::lightweight_unlock(Register obj, Register tmp1, Register tmp2, Register tmp3, Label& slow) { - assert(LockingMode == LM_LIGHTWEIGHT, "only used with new lightweight locking"); assert_different_registers(obj, tmp1, tmp2, tmp3, t0); #ifdef ASSERT diff --git a/src/hotspot/cpu/riscv/riscv.ad b/src/hotspot/cpu/riscv/riscv.ad index 8e7b772414a..7d204007898 100644 --- a/src/hotspot/cpu/riscv/riscv.ad +++ b/src/hotspot/cpu/riscv/riscv.ad @@ -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. // Copyright (c) 2014, 2020, Red Hat Inc. All rights reserved. // Copyright (c) 2020, 2024, Huawei Technologies Co., Ltd. All rights reserved. // DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. @@ -11021,45 +11021,9 @@ instruct tlsLoadP(javaThread_RegP dst) // inlined locking and unlocking // using t1 as the 'flag' register to bridge the BoolNode producers and consumers -instruct cmpFastLock(rFlagsReg cr, iRegP object, iRegP box, - iRegPNoSp tmp1, iRegPNoSp tmp2, iRegPNoSp tmp3, iRegPNoSp tmp4) -%{ - predicate(LockingMode != LM_LIGHTWEIGHT); - match(Set cr (FastLock object box)); - effect(TEMP tmp1, TEMP tmp2, TEMP tmp3, TEMP tmp4); - - ins_cost(10 * DEFAULT_COST); - format %{ "fastlock $object,$box\t! kills $tmp1,$tmp2,$tmp3,$tmp4 #@cmpFastLock" %} - - ins_encode %{ - __ fast_lock($object$$Register, $box$$Register, - $tmp1$$Register, $tmp2$$Register, $tmp3$$Register, $tmp4$$Register); - %} - - ins_pipe(pipe_serial); -%} - -// using t1 as the 'flag' register to bridge the BoolNode producers and consumers -instruct cmpFastUnlock(rFlagsReg cr, iRegP object, iRegP box, iRegPNoSp tmp1, iRegPNoSp tmp2) -%{ - predicate(LockingMode != LM_LIGHTWEIGHT); - match(Set cr (FastUnlock object box)); - effect(TEMP tmp1, TEMP tmp2); - - ins_cost(10 * DEFAULT_COST); - format %{ "fastunlock $object,$box\t! kills $tmp1, $tmp2, #@cmpFastUnlock" %} - - ins_encode %{ - __ fast_unlock($object$$Register, $box$$Register, $tmp1$$Register, $tmp2$$Register); - %} - - ins_pipe(pipe_serial); -%} - instruct cmpFastLockLightweight(rFlagsReg cr, iRegP object, iRegP box, iRegPNoSp tmp1, iRegPNoSp tmp2, iRegPNoSp tmp3, iRegPNoSp tmp4) %{ - predicate(LockingMode == LM_LIGHTWEIGHT); match(Set cr (FastLock object box)); effect(TEMP tmp1, TEMP tmp2, TEMP tmp3, TEMP tmp4); @@ -11074,10 +11038,10 @@ instruct cmpFastLockLightweight(rFlagsReg cr, iRegP object, iRegP box, ins_pipe(pipe_serial); %} +// using t1 as the 'flag' register to bridge the BoolNode producers and consumers instruct cmpFastUnlockLightweight(rFlagsReg cr, iRegP object, iRegP box, iRegPNoSp tmp1, iRegPNoSp tmp2, iRegPNoSp tmp3) %{ - predicate(LockingMode == LM_LIGHTWEIGHT); match(Set cr (FastUnlock object box)); effect(TEMP tmp1, TEMP tmp2, TEMP tmp3); diff --git a/src/hotspot/cpu/riscv/sharedRuntime_riscv.cpp b/src/hotspot/cpu/riscv/sharedRuntime_riscv.cpp index c4e5a871a88..2cc1fb8eeff 100644 --- a/src/hotspot/cpu/riscv/sharedRuntime_riscv.cpp +++ b/src/hotspot/cpu/riscv/sharedRuntime_riscv.cpp @@ -1637,7 +1637,7 @@ nmethod* SharedRuntime::generate_native_wrapper(MacroAssembler* masm, // We use the same pc/oopMap repeatedly when we call out. Label native_return; - if (LockingMode != LM_LEGACY && method->is_object_wait0()) { + if (method->is_object_wait0()) { // For convenience we use the pc we want to resume to in case of preemption on Object.wait. __ set_last_Java_frame(sp, noreg, native_return, t0); } else { @@ -1679,8 +1679,6 @@ nmethod* SharedRuntime::generate_native_wrapper(MacroAssembler* masm, Label lock_done; if (method->is_synchronized()) { - Label count; - const int mark_word_offset = BasicLock::displaced_header_offset_in_bytes(); // Get the handle (the 2nd argument) @@ -1693,42 +1691,7 @@ nmethod* SharedRuntime::generate_native_wrapper(MacroAssembler* masm, // Load the oop from the handle __ ld(obj_reg, Address(oop_handle_reg, 0)); - if (LockingMode == LM_MONITOR) { - __ j(slow_path_lock); - } else if (LockingMode == LM_LEGACY) { - // Load (object->mark() | 1) into swap_reg % x10 - __ ld(t0, Address(obj_reg, oopDesc::mark_offset_in_bytes())); - __ ori(swap_reg, t0, 1); - - // Save (object->mark() | 1) into BasicLock's displaced header - __ sd(swap_reg, Address(lock_reg, mark_word_offset)); - - // src -> dest if dest == x10 else x10 <- dest - __ cmpxchg_obj_header(x10, lock_reg, obj_reg, lock_tmp, count, /*fallthrough*/nullptr); - - // Test if the oopMark is an obvious stack pointer, i.e., - // 1) (mark & 3) == 0, and - // 2) sp <= mark < mark + os::pagesize() - // These 3 tests can be done by evaluating the following - // expression: ((mark - sp) & (3 - os::vm_page_size())), - // assuming both stack pointer and pagesize have their - // least significant 2 bits clear. - // NOTE: the oopMark is in swap_reg % 10 as the result of cmpxchg - - __ sub(swap_reg, swap_reg, sp); - __ mv(t0, 3 - (int)os::vm_page_size()); - __ andr(swap_reg, swap_reg, t0); - - // Save the test result, for recursive case, the result is zero - __ sd(swap_reg, Address(lock_reg, mark_word_offset)); - __ bnez(swap_reg, slow_path_lock); - - __ bind(count); - __ inc_held_monitor_count(t0); - } else { - assert(LockingMode == LM_LIGHTWEIGHT, "must be"); - __ lightweight_lock(lock_reg, obj_reg, swap_reg, tmp, lock_tmp, slow_path_lock); - } + __ lightweight_lock(lock_reg, obj_reg, swap_reg, tmp, lock_tmp, slow_path_lock); // Slow path will re-enter here __ bind(lock_done); @@ -1789,7 +1752,7 @@ nmethod* SharedRuntime::generate_native_wrapper(MacroAssembler* masm, __ membar(MacroAssembler::LoadStore | MacroAssembler::StoreStore); __ sw(t0, Address(t1)); - if (LockingMode != LM_LEGACY && method->is_object_wait0()) { + if (method->is_object_wait0()) { // Check preemption for Object.wait() __ ld(t1, Address(xthread, JavaThread::preempt_alternate_return_offset())); __ beqz(t1, native_return); @@ -1818,48 +1781,18 @@ nmethod* SharedRuntime::generate_native_wrapper(MacroAssembler* masm, // Get locked oop from the handle we passed to jni __ ld(obj_reg, Address(oop_handle_reg, 0)); - Label done, not_recursive; - - if (LockingMode == LM_LEGACY) { - // Simple recursive lock? - __ ld(t0, Address(sp, lock_slot_offset * VMRegImpl::stack_slot_size)); - __ bnez(t0, not_recursive); - __ dec_held_monitor_count(t0); - __ j(done); - } - - __ bind(not_recursive); - // Must save x10 if if it is live now because cmpxchg must use it if (ret_type != T_FLOAT && ret_type != T_DOUBLE && ret_type != T_VOID) { save_native_result(masm, ret_type, stack_slots); } - if (LockingMode == LM_MONITOR) { - __ j(slow_path_unlock); - } else if (LockingMode == LM_LEGACY) { - // get address of the stack lock - __ la(x10, Address(sp, lock_slot_offset * VMRegImpl::stack_slot_size)); - // get old displaced header - __ ld(old_hdr, Address(x10, 0)); - - // Atomic swap old header if oop still contains the stack lock - Label count; - __ cmpxchg_obj_header(x10, old_hdr, obj_reg, lock_tmp, count, &slow_path_unlock); - __ bind(count); - __ dec_held_monitor_count(t0); - } else { - assert(LockingMode == LM_LIGHTWEIGHT, ""); - __ lightweight_unlock(obj_reg, old_hdr, swap_reg, lock_tmp, slow_path_unlock); - } + __ lightweight_unlock(obj_reg, old_hdr, swap_reg, lock_tmp, slow_path_unlock); // slow path re-enters here __ bind(unlock_done); if (ret_type != T_FLOAT && ret_type != T_DOUBLE && ret_type != T_VOID) { restore_native_result(masm, ret_type, stack_slots); } - - __ bind(done); } Label dtrace_method_exit, dtrace_method_exit_done; diff --git a/src/hotspot/cpu/riscv/templateInterpreterGenerator_riscv.cpp b/src/hotspot/cpu/riscv/templateInterpreterGenerator_riscv.cpp index 21164107053..61f4aa3e722 100644 --- a/src/hotspot/cpu/riscv/templateInterpreterGenerator_riscv.cpp +++ b/src/hotspot/cpu/riscv/templateInterpreterGenerator_riscv.cpp @@ -1253,22 +1253,17 @@ address TemplateInterpreterGenerator::generate_native_entry(bool synchronized) { __ mv(t0, _thread_in_Java); __ sw(t0, Address(xthread, JavaThread::thread_state_offset())); - if (LockingMode != LM_LEGACY) { - // Check preemption for Object.wait() - Label not_preempted; - __ ld(t1, Address(xthread, JavaThread::preempt_alternate_return_offset())); - __ beqz(t1, not_preempted); - __ sd(zr, Address(xthread, JavaThread::preempt_alternate_return_offset())); - __ jr(t1); - __ bind(native_return); - __ restore_after_resume(true /* is_native */); - // reload result_handler - __ ld(result_handler, Address(fp, frame::interpreter_frame_result_handler_offset * wordSize)); - __ bind(not_preempted); - } else { - // any pc will do so just use this one for LM_LEGACY to keep code together. - __ bind(native_return); - } + // Check preemption for Object.wait() + Label not_preempted; + __ ld(t1, Address(xthread, JavaThread::preempt_alternate_return_offset())); + __ beqz(t1, not_preempted); + __ sd(zr, Address(xthread, JavaThread::preempt_alternate_return_offset())); + __ jr(t1); + __ bind(native_return); + __ restore_after_resume(true /* is_native */); + // reload result_handler + __ ld(result_handler, Address(fp, frame::interpreter_frame_result_handler_offset * wordSize)); + __ bind(not_preempted); // reset_last_Java_frame __ reset_last_Java_frame(true); From f3b34d32d6ea409f8c8f0382e8f01e746366f842 Mon Sep 17 00:00:00 2001 From: Guanqiang Han Date: Wed, 13 Aug 2025 10:52:54 +0000 Subject: [PATCH 111/580] 8359235: C1 compilation fails with "assert(is_single_stack() && !is_virtual()) failed: type check" Reviewed-by: thartmann, dlong --- .../cpu/aarch64/c1_LIRAssembler_aarch64.cpp | 2 +- .../cpu/aarch64/c1_LIRGenerator_aarch64.cpp | 4 +- .../cpu/riscv/c1_LIRAssembler_riscv.cpp | 2 +- .../cpu/riscv/c1_LIRGenerator_riscv.cpp | 2 +- src/hotspot/cpu/x86/c1_LIRGenerator_x86.cpp | 6 +- .../intrinsics/TestStack2RegSlotMismatch.java | 76 +++++++++++++++++++ 6 files changed, 84 insertions(+), 8 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/intrinsics/TestStack2RegSlotMismatch.java diff --git a/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp index bdba957d1df..d7e2ff2e88d 100644 --- a/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp @@ -2813,7 +2813,7 @@ void LIR_Assembler::leal(LIR_Opr addr, LIR_Opr dest, LIR_PatchCode patch_code, C return; } - __ lea(dest->as_register_lo(), as_Address(addr->as_address_ptr())); + __ lea(dest->as_pointer_register(), as_Address(addr->as_address_ptr())); } diff --git a/src/hotspot/cpu/aarch64/c1_LIRGenerator_aarch64.cpp b/src/hotspot/cpu/aarch64/c1_LIRGenerator_aarch64.cpp index f0d09edd0a9..ad26d494b2d 100644 --- a/src/hotspot/cpu/aarch64/c1_LIRGenerator_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/c1_LIRGenerator_aarch64.cpp @@ -981,7 +981,7 @@ void LIRGenerator::do_update_CRC32(Intrinsic* x) { CallingConvention* cc = frame_map()->c_calling_convention(&signature); const LIR_Opr result_reg = result_register_for(x->type()); - LIR_Opr addr = new_pointer_register(); + LIR_Opr addr = new_register(T_ADDRESS); __ leal(LIR_OprFact::address(a), addr); crc.load_item_force(cc->at(0)); @@ -1058,7 +1058,7 @@ void LIRGenerator::do_update_CRC32C(Intrinsic* x) { CallingConvention* cc = frame_map()->c_calling_convention(&signature); const LIR_Opr result_reg = result_register_for(x->type()); - LIR_Opr addr = new_pointer_register(); + LIR_Opr addr = new_register(T_ADDRESS); __ leal(LIR_OprFact::address(a), addr); crc.load_item_force(cc->at(0)); diff --git a/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp b/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp index 2a65270af92..f60be85a141 100644 --- a/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp @@ -1821,7 +1821,7 @@ void LIR_Assembler::leal(LIR_Opr addr, LIR_Opr dest, LIR_PatchCode patch_code, C } LIR_Address* adr = addr->as_address_ptr(); - Register dst = dest->as_register_lo(); + Register dst = dest->as_pointer_register(); assert_different_registers(dst, t0); if (adr->base()->is_valid() && dst == adr->base()->as_pointer_register() && (!adr->index()->is_cpu_register())) { diff --git a/src/hotspot/cpu/riscv/c1_LIRGenerator_riscv.cpp b/src/hotspot/cpu/riscv/c1_LIRGenerator_riscv.cpp index e450c04c47d..88565d9136f 100644 --- a/src/hotspot/cpu/riscv/c1_LIRGenerator_riscv.cpp +++ b/src/hotspot/cpu/riscv/c1_LIRGenerator_riscv.cpp @@ -837,7 +837,7 @@ void LIRGenerator::do_update_CRC32(Intrinsic* x) { CallingConvention* cc = frame_map()->c_calling_convention(&signature); const LIR_Opr result_reg = result_register_for(x->type()); - LIR_Opr addr = new_pointer_register(); + LIR_Opr addr = new_register(T_ADDRESS); __ leal(LIR_OprFact::address(a), addr); crc.load_item_force(cc->at(0)); diff --git a/src/hotspot/cpu/x86/c1_LIRGenerator_x86.cpp b/src/hotspot/cpu/x86/c1_LIRGenerator_x86.cpp index 1fdfcc4f9cd..5459e8df229 100644 --- a/src/hotspot/cpu/x86/c1_LIRGenerator_x86.cpp +++ b/src/hotspot/cpu/x86/c1_LIRGenerator_x86.cpp @@ -961,7 +961,7 @@ void LIRGenerator::do_update_CRC32(Intrinsic* x) { CallingConvention* cc = frame_map()->c_calling_convention(&signature); const LIR_Opr result_reg = result_register_for(x->type()); - LIR_Opr addr = new_pointer_register(); + LIR_Opr addr = new_register(T_ADDRESS); __ leal(LIR_OprFact::address(a), addr); crc.load_item_force(cc->at(0)); @@ -1100,10 +1100,10 @@ void LIRGenerator::do_vectorizedMismatch(Intrinsic* x) { CallingConvention* cc = frame_map()->c_calling_convention(&signature); const LIR_Opr result_reg = result_register_for(x->type()); - LIR_Opr ptr_addr_a = new_pointer_register(); + LIR_Opr ptr_addr_a = new_register(T_ADDRESS); __ leal(LIR_OprFact::address(addr_a), ptr_addr_a); - LIR_Opr ptr_addr_b = new_pointer_register(); + LIR_Opr ptr_addr_b = new_register(T_ADDRESS); __ leal(LIR_OprFact::address(addr_b), ptr_addr_b); __ move(ptr_addr_a, cc->at(0)); diff --git a/test/hotspot/jtreg/compiler/intrinsics/TestStack2RegSlotMismatch.java b/test/hotspot/jtreg/compiler/intrinsics/TestStack2RegSlotMismatch.java new file mode 100644 index 00000000000..d2942f061db --- /dev/null +++ b/test/hotspot/jtreg/compiler/intrinsics/TestStack2RegSlotMismatch.java @@ -0,0 +1,76 @@ +/* + * 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 8359235 + * @summary Test C1 stack2reg after fixing incorrect use of T_LONG in intrinsic + * @requires vm.debug == true & vm.compiler1.enabled + * @run main/othervm -XX:TieredStopAtLevel=1 + * -XX:C1MaxInlineSize=200 + * -XX:CompileThreshold=10 + * -XX:CompileCommand=compileonly,java.lang.invoke.LambdaFormEditor::putInCache + * compiler.intrinsics.TestStack2RegSlotMismatch + */ +package compiler.intrinsics; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodType; +import java.util.ArrayList; +import java.util.List; + +public class TestStack2RegSlotMismatch { + public static int target(int x, int y) { + return x + y; + } + + public static void main(String[] args) throws Throwable { + MethodHandle mh = MethodHandles.lookup().findStatic( + TestStack2RegSlotMismatch.class, + "target", + MethodType.methodType(int.class, int.class, int.class) + ); + List argsList = new ArrayList<>(); + int j = 0; + + for (int i = 0; i < 50; i++) { + mh = MethodHandles.dropArguments(mh, 0, int.class); + argsList.add(0); + argsList.add(1); + argsList.add(2); + Object result = mh.invokeWithArguments(argsList); + j += (int) result; + argsList.remove(argsList.size() - 1); + argsList.remove(argsList.size() - 1); + if (i % 5 == 0) { + Thread.sleep(1000); + } + } + + if (j == 150) { + System.out.println("passed"); + } else { + throw new Exception("TestStack2RegSlotMismatch Error"); + } + } +} \ No newline at end of file From 001aaa1e49f2692061cad44d68c9e81a27ea3b98 Mon Sep 17 00:00:00 2001 From: Boris Ulasevich Date: Wed, 13 Aug 2025 12:45:48 +0000 Subject: [PATCH 112/580] 8365166: ARM32: missing os::fetch_bcp_from_context implementation Reviewed-by: shade --- src/hotspot/os_cpu/linux_arm/os_linux_arm.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/hotspot/os_cpu/linux_arm/os_linux_arm.cpp b/src/hotspot/os_cpu/linux_arm/os_linux_arm.cpp index 28b47877264..f4196d89fe3 100644 --- a/src/hotspot/os_cpu/linux_arm/os_linux_arm.cpp +++ b/src/hotspot/os_cpu/linux_arm/os_linux_arm.cpp @@ -209,8 +209,16 @@ frame os::fetch_compiled_frame_from_context(const void* ucVoid) { } intptr_t* os::fetch_bcp_from_context(const void* ucVoid) { - Unimplemented(); - return nullptr; + assert(ucVoid != nullptr, "invariant"); + const ucontext_t* uc = (const ucontext_t*)ucVoid; + assert(os::Posix::ucontext_is_interpreter(uc), "invariant"); +#if (FP_REG_NUM == 11) + assert(Rbcp == R7, "expected FP=R11, Rbcp=R7"); + return (intptr_t*)uc->uc_mcontext.arm_r7; +#else + assert(Rbcp == R11, "expected FP=R7, Rbcp=R11"); + return (intptr_t*)uc->uc_mcontext.arm_fp; // r11 +#endif } frame os::get_sender_for_C_frame(frame* fr) { From 899e13f40a70c98d1d393ba6c3973abcb36e1f00 Mon Sep 17 00:00:00 2001 From: Nikita Gubarkov Date: Wed, 13 Aug 2025 17:36:07 +0000 Subject: [PATCH 113/580] 8364434: Inconsistent BufferedContext state after GC Reviewed-by: jdv, azvegint, avu --- .../sun/java2d/pipe/BufferedContext.java | 60 ++++++++------ .../java/awt/ColorClass/WeakColorTest.java | 83 +++++++++++++++++++ 2 files changed, 118 insertions(+), 25 deletions(-) create mode 100644 test/jdk/java/awt/ColorClass/WeakColorTest.java diff --git a/src/java.desktop/share/classes/sun/java2d/pipe/BufferedContext.java b/src/java.desktop/share/classes/sun/java2d/pipe/BufferedContext.java index e57b240bce5..83e63fc75d8 100644 --- a/src/java.desktop/share/classes/sun/java2d/pipe/BufferedContext.java +++ b/src/java.desktop/share/classes/sun/java2d/pipe/BufferedContext.java @@ -100,11 +100,11 @@ public abstract class BufferedContext { */ protected static BufferedContext currentContext; - private Reference validSrcDataRef = new WeakReference<>(null); - private Reference validDstDataRef = new WeakReference<>(null); - private Reference validClipRef = new WeakReference<>(null); - private Reference validCompRef = new WeakReference<>(null); - private Reference validPaintRef = new WeakReference<>(null); + private Reference validSrcDataRef = null; + private Reference validDstDataRef = null; + private Reference validClipRef = null; + private Reference validCompRef = null; + private Reference validPaintRef = null; // renamed from isValidatedPaintAColor as part of a work around for 6764257 private boolean isValidatedPaintJustAColor; private int validatedRGB; @@ -213,20 +213,18 @@ public abstract class BufferedContext { updatePaint = true; isValidatedPaintJustAColor = true; } - } else if (validPaintRef.get() != paint) { + } else if (stateChanged(validPaintRef, paint)) { updatePaint = true; // this should be set when we are switching from paint to color // in which case this condition will be true isValidatedPaintJustAColor = false; } - final AccelSurface validatedSrcData = validSrcDataRef.get(); - final AccelSurface validatedDstData = validDstDataRef.get(); - if ((currentContext != this) || - (srcData != validatedSrcData) || - (dstData != validatedDstData)) + final boolean srcChanged = stateChanged(validSrcDataRef, srcData); + final boolean dstChanged = stateChanged(validDstDataRef, dstData); + if ((currentContext != this) || srcChanged || dstChanged) { - if (dstData != validatedDstData) { + if (dstChanged) { // the clip is dependent on the destination surface, so we // need to update it if we have a new destination surface updateClip = true; @@ -243,13 +241,13 @@ public abstract class BufferedContext { setSurfaces(srcData, dstData); currentContext = this; - validSrcDataRef = new WeakReference<>(srcData); - validDstDataRef = new WeakReference<>(dstData); + validSrcDataRef = wrapState(srcData); + validDstDataRef = wrapState(dstData); } // validate clip - final Region validatedClip = validClipRef.get(); - if ((clip != validatedClip) || updateClip) { + final Region validatedClip = validClipRef == null ? null : validClipRef.get(); + if (stateChanged(validClipRef, clip) || updateClip) { if (clip != null) { if (updateClip || validatedClip == null || @@ -264,13 +262,13 @@ public abstract class BufferedContext { } else { resetClip(); } - validClipRef = new WeakReference<>(clip); + validClipRef = wrapState(clip); } // validate composite (note that a change in the context flags // may require us to update the composite state, even if the // composite has not changed) - if ((comp != validCompRef.get()) || (flags != validatedFlags)) { + if (stateChanged(validCompRef, comp) || (flags != validatedFlags)) { if (comp != null) { setComposite(comp, flags); } else { @@ -279,7 +277,7 @@ public abstract class BufferedContext { // the paint state is dependent on the composite state, so make // sure we update the color below updatePaint = true; - validCompRef = new WeakReference<>(comp); + validCompRef = wrapState(comp); validatedFlags = flags; } @@ -313,7 +311,7 @@ public abstract class BufferedContext { } else { BufferedPaints.resetPaint(rq); } - validPaintRef = new WeakReference<>(paint); + validPaintRef = wrapState(paint); } // mark dstData dirty @@ -321,6 +319,18 @@ public abstract class BufferedContext { dstData.markDirty(); } + private static boolean stateChanged(Reference ref, T obj) { + // null ref means "true" null object + if (ref == null) return obj != null; + T old = ref.get(); + // null ref value means the object was GC'ed, return true in that case + return old == null || old != obj; + } + + private static Reference wrapState(T obj) { + return obj == null ? null : new WeakReference<>(obj); + } + private void setSurfaces(AccelSurface srcData, AccelSurface dstData) { @@ -434,11 +444,11 @@ public abstract class BufferedContext { resetComposite(); resetClip(); BufferedPaints.resetPaint(rq); - validSrcDataRef.clear(); - validDstDataRef.clear(); - validCompRef.clear(); - validClipRef.clear(); - validPaintRef.clear(); + validSrcDataRef = null; + validDstDataRef = null; + validCompRef = null; + validClipRef = null; + validPaintRef = null; isValidatedPaintJustAColor = false; xformInUse = false; } diff --git a/test/jdk/java/awt/ColorClass/WeakColorTest.java b/test/jdk/java/awt/ColorClass/WeakColorTest.java new file mode 100644 index 00000000000..d4adbc3f111 --- /dev/null +++ b/test/jdk/java/awt/ColorClass/WeakColorTest.java @@ -0,0 +1,83 @@ +/* + * 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.Color; +import java.awt.Graphics2D; +import java.awt.GraphicsEnvironment; +import java.awt.image.BufferedImage; +import java.awt.image.VolatileImage; +import java.lang.ref.WeakReference; + +import jtreg.SkippedException; + +/* + * @test + * @key headful + * @bug 8364434 + * @summary Check that garbage-collecting Color before accelerated painting is complete does not cause artifacts. + * @requires (os.family != "linux") + * @library /test/lib + * @run main/othervm -Xms16m -Xmx16m WeakColorTest + */ + +public class WeakColorTest { + public static void main(String[] args) throws Exception { + BufferedImage bi = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB); // This image is full-black. + VolatileImage image = GraphicsEnvironment.getLocalGraphicsEnvironment() + .getDefaultScreenDevice().getDefaultConfiguration().createCompatibleVolatileImage(100, 100); + Graphics2D g = image.createGraphics(); + + // Create a new Color - we want it to be collected later. + g.setColor(new Color(255, 0, 0)); + WeakReference color = new WeakReference<>(g.getColor()); + + g.fillRect(0, 0, 100, 100); + + // Change color to prevent Graphics from keeping our Color alive. + g.setColor(Color.BLACK); + + // Force Color to be GC'ed. + final int MAX_ITERATIONS = 1000, ARRAY_SIZE = 1000000; + WeakReference array = null; + for (int i = 0;; i++) { + System.gc(); + if (color.get() == null) { + System.out.println("Color collected at: " + i); + break; + } else if (i >= MAX_ITERATIONS) { + throw new SkippedException("Color was not collected after " + MAX_ITERATIONS + " iterations"); + } + Object[] a = new Object[ARRAY_SIZE]; + a[0] = array; + array = new WeakReference<>(a); + } + + // Do a blit. If it succeeds, the resulting image will be full-black. + g.drawImage(bi, 0, 0, null); + g.dispose(); + + // We expect black. If it's red, then the blit must have failed. + int actualColor = image.getSnapshot().getRGB(50, 50); + if ((actualColor & 0xFFFFFF) != 0) throw new Error("Wrong color: 0x" + Integer.toHexString(actualColor)); + } +} From 38a261415dc29aae01c9b878d94cb302c60a3983 Mon Sep 17 00:00:00 2001 From: Srinivas Vamsi Parasa Date: Wed, 13 Aug 2025 17:53:05 +0000 Subject: [PATCH 114/580] 8365265: x86 short forward jump exceeds 8-bit offset in methodHandles_x86.cpp when using Intel APX Reviewed-by: shade, jbhateja, aph --- src/hotspot/cpu/x86/methodHandles_x86.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hotspot/cpu/x86/methodHandles_x86.cpp b/src/hotspot/cpu/x86/methodHandles_x86.cpp index 0363cb04341..b921a157a52 100644 --- a/src/hotspot/cpu/x86/methodHandles_x86.cpp +++ b/src/hotspot/cpu/x86/methodHandles_x86.cpp @@ -137,7 +137,7 @@ void MethodHandles::verify_method(MacroAssembler* _masm, Register method, Regist case vmIntrinsicID::_invokeBasic: // Require compiled LambdaForm class to be fully initialized. __ cmpb(Address(method_holder, InstanceKlass::init_state_offset()), InstanceKlass::fully_initialized); - __ jccb(Assembler::equal, L_ok); + __ jcc(Assembler::equal, L_ok); break; case vmIntrinsicID::_linkToStatic: @@ -154,7 +154,7 @@ void MethodHandles::verify_method(MacroAssembler* _masm, Register method, Regist // init_state check failed, but it may be an abstract interface method __ load_unsigned_short(temp, Address(method, Method::access_flags_offset())); __ testl(temp, JVM_ACC_ABSTRACT); - __ jccb(Assembler::notZero, L_ok); + __ jcc(Assembler::notZero, L_ok); break; default: From ecbdd3405a1d46f555deb82098e1865b44601a1f Mon Sep 17 00:00:00 2001 From: Alex Menkov Date: Wed, 13 Aug 2025 18:24:56 +0000 Subject: [PATCH 115/580] 8361103: java_lang_Thread::async_get_stack_trace does not properly protect JavaThread Reviewed-by: sspitsyn, dholmes --- src/hotspot/share/classfile/javaClasses.cpp | 53 ++++++++++----------- src/hotspot/share/classfile/javaClasses.hpp | 2 +- src/hotspot/share/prims/jvm.cpp | 2 +- 3 files changed, 27 insertions(+), 30 deletions(-) diff --git a/src/hotspot/share/classfile/javaClasses.cpp b/src/hotspot/share/classfile/javaClasses.cpp index 28bebee7d9a..5c41ea789b5 100644 --- a/src/hotspot/share/classfile/javaClasses.cpp +++ b/src/hotspot/share/classfile/javaClasses.cpp @@ -1899,33 +1899,29 @@ oop java_lang_Thread::park_blocker(oop java_thread) { return java_thread->obj_field_access(_park_blocker_offset); } -oop java_lang_Thread::async_get_stack_trace(oop java_thread, TRAPS) { - ThreadsListHandle tlh(JavaThread::current()); - JavaThread* thread; - bool is_virtual = java_lang_VirtualThread::is_instance(java_thread); - if (is_virtual) { - oop carrier_thread = java_lang_VirtualThread::carrier_thread(java_thread); - if (carrier_thread == nullptr) { - return nullptr; - } - thread = java_lang_Thread::thread(carrier_thread); - } else { - thread = java_lang_Thread::thread(java_thread); - } - if (thread == nullptr) { +// Obtain stack trace for platform or mounted virtual thread. +// If jthread is a virtual thread and it has been unmounted (or remounted to different carrier) the method returns null. +// The caller (java.lang.VirtualThread) handles returned nulls via retry. +oop java_lang_Thread::async_get_stack_trace(jobject jthread, TRAPS) { + ThreadsListHandle tlh(THREAD); + JavaThread* java_thread = nullptr; + oop thread_oop; + + bool has_java_thread = tlh.cv_internal_thread_to_JavaThread(jthread, &java_thread, &thread_oop); + if (!has_java_thread) { return nullptr; } class GetStackTraceHandshakeClosure : public HandshakeClosure { public: - const Handle _java_thread; + const Handle _thread_h; int _depth; bool _retry_handshake; GrowableArray* _methods; GrowableArray* _bcis; - GetStackTraceHandshakeClosure(Handle java_thread) : - HandshakeClosure("GetStackTraceHandshakeClosure"), _java_thread(java_thread), _depth(0), _retry_handshake(false), + GetStackTraceHandshakeClosure(Handle thread_h) : + HandshakeClosure("GetStackTraceHandshakeClosure"), _thread_h(thread_h), _depth(0), _retry_handshake(false), _methods(nullptr), _bcis(nullptr) { } ~GetStackTraceHandshakeClosure() { @@ -1947,21 +1943,22 @@ oop java_lang_Thread::async_get_stack_trace(oop java_thread, TRAPS) { return; } - JavaThread* thread = JavaThread::cast(th); + JavaThread* java_thread = JavaThread::cast(th); - if (!thread->has_last_Java_frame()) { + if (!java_thread->has_last_Java_frame()) { return; } bool carrier = false; - if (java_lang_VirtualThread::is_instance(_java_thread())) { - // if (thread->vthread() != _java_thread()) // We might be inside a System.executeOnCarrierThread - const ContinuationEntry* ce = thread->vthread_continuation(); - if (ce == nullptr || ce->cont_oop(thread) != java_lang_VirtualThread::continuation(_java_thread())) { - return; // not mounted + if (java_lang_VirtualThread::is_instance(_thread_h())) { + // Ensure _thread_h is still mounted to java_thread. + const ContinuationEntry* ce = java_thread->vthread_continuation(); + if (ce == nullptr || ce->cont_oop(java_thread) != java_lang_VirtualThread::continuation(_thread_h())) { + // Target thread has been unmounted. + return; } } else { - carrier = (thread->vthread_continuation() != nullptr); + carrier = (java_thread->vthread_continuation() != nullptr); } const int max_depth = MaxJavaStackTraceDepth; @@ -1973,7 +1970,7 @@ oop java_lang_Thread::async_get_stack_trace(oop java_thread, TRAPS) { _bcis = new (mtInternal) GrowableArray(init_length, mtInternal); int total_count = 0; - for (vframeStream vfst(thread, false, false, carrier); // we don't process frames as we don't care about oops + for (vframeStream vfst(java_thread, false, false, carrier); // we don't process frames as we don't care about oops !vfst.at_end() && (max_depth == 0 || max_depth != total_count); vfst.next()) { @@ -1994,9 +1991,9 @@ oop java_lang_Thread::async_get_stack_trace(oop java_thread, TRAPS) { // Handshake with target ResourceMark rm(THREAD); HandleMark hm(THREAD); - GetStackTraceHandshakeClosure gsthc(Handle(THREAD, java_thread)); + GetStackTraceHandshakeClosure gsthc(Handle(THREAD, thread_oop)); do { - Handshake::execute(&gsthc, &tlh, thread); + Handshake::execute(&gsthc, &tlh, java_thread); } while (gsthc.read_reset_retry()); // Stop if no stack trace is found. diff --git a/src/hotspot/share/classfile/javaClasses.hpp b/src/hotspot/share/classfile/javaClasses.hpp index faa325d55dd..70fa519f0f0 100644 --- a/src/hotspot/share/classfile/javaClasses.hpp +++ b/src/hotspot/share/classfile/javaClasses.hpp @@ -463,7 +463,7 @@ class java_lang_Thread : AllStatic { static const char* thread_status_name(oop java_thread_oop); // Fill in current stack trace, can cause GC - static oop async_get_stack_trace(oop java_thread, TRAPS); + static oop async_get_stack_trace(jobject jthread, TRAPS); JFR_ONLY(static u2 jfr_epoch(oop java_thread);) JFR_ONLY(static void set_jfr_epoch(oop java_thread, u2 epoch);) diff --git a/src/hotspot/share/prims/jvm.cpp b/src/hotspot/share/prims/jvm.cpp index 005776b00c2..098bd729c0b 100644 --- a/src/hotspot/share/prims/jvm.cpp +++ b/src/hotspot/share/prims/jvm.cpp @@ -2945,7 +2945,7 @@ JVM_ENTRY(jboolean, JVM_HoldsLock(JNIEnv* env, jclass threadClass, jobject obj)) JVM_END JVM_ENTRY(jobject, JVM_GetStackTrace(JNIEnv *env, jobject jthread)) - oop trace = java_lang_Thread::async_get_stack_trace(JNIHandles::resolve(jthread), THREAD); + oop trace = java_lang_Thread::async_get_stack_trace(jthread, THREAD); return JNIHandles::make_local(THREAD, trace); JVM_END From 4680dc983169d48fcf83eb50dc60e32e79d5d976 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20Sj=C3=B6len?= Date: Wed, 13 Aug 2025 18:41:57 +0000 Subject: [PATCH 116/580] 8365264: Rename ResourceHashtable to HashTable Reviewed-by: iklam, ayang --- src/hotspot/share/asm/codeBuffer.hpp | 4 +- src/hotspot/share/cds/aotArtifactFinder.cpp | 4 +- src/hotspot/share/cds/aotClassLinker.hpp | 4 +- .../share/cds/aotConstantPoolResolver.hpp | 4 +- .../share/cds/aotReferenceObjSupport.cpp | 4 +- src/hotspot/share/cds/archiveBuilder.hpp | 8 +- src/hotspot/share/cds/archiveHeapLoader.cpp | 6 +- src/hotspot/share/cds/archiveHeapWriter.cpp | 2 +- src/hotspot/share/cds/archiveHeapWriter.hpp | 4 +- src/hotspot/share/cds/cdsHeapVerifier.hpp | 6 +- src/hotspot/share/cds/classListParser.hpp | 4 +- src/hotspot/share/cds/classListWriter.cpp | 2 +- src/hotspot/share/cds/dumpTimeClassInfo.hpp | 2 +- src/hotspot/share/cds/heapShared.cpp | 2 +- src/hotspot/share/cds/heapShared.hpp | 12 +-- .../share/cds/lambdaProxyClassDictionary.hpp | 4 +- src/hotspot/share/cds/metaspaceShared.cpp | 4 +- src/hotspot/share/cds/regeneratedClasses.cpp | 4 +- .../share/classfile/bytecodeAssembler.hpp | 5 +- .../share/classfile/classFileParser.cpp | 8 +- .../share/classfile/classLoaderStats.hpp | 6 +- .../share/classfile/loaderConstraints.cpp | 4 +- src/hotspot/share/classfile/moduleEntry.cpp | 4 +- src/hotspot/share/classfile/moduleEntry.hpp | 4 +- src/hotspot/share/classfile/packageEntry.cpp | 4 +- src/hotspot/share/classfile/packageEntry.hpp | 4 +- src/hotspot/share/classfile/placeholders.cpp | 4 +- .../share/classfile/resolutionErrors.cpp | 4 +- src/hotspot/share/classfile/stringTable.cpp | 4 +- .../share/classfile/systemDictionary.cpp | 4 +- .../classfile/systemDictionaryShared.cpp | 5 +- src/hotspot/share/classfile/verifier.hpp | 4 +- src/hotspot/share/code/codeCache.cpp | 2 +- src/hotspot/share/code/nmethod.cpp | 2 +- src/hotspot/share/compiler/disassembler.cpp | 5 +- src/hotspot/share/gc/z/zVerify.cpp | 4 +- .../methodtracer/jfrClassFilterClosure.cpp | 2 +- .../methodtracer/jfrClassFilterClosure.hpp | 4 +- .../support/methodtracer/jfrMethodTracer.cpp | 2 +- src/hotspot/share/jfr/utilities/jfrSet.hpp | 4 +- .../share/jvmci/jvmciCompilerToVMInit.cpp | 6 +- src/hotspot/share/logging/logAsyncWriter.hpp | 6 +- src/hotspot/share/memory/metaspaceClosure.hpp | 4 +- .../share/nmt/nativeCallStackPrinter.hpp | 4 +- src/hotspot/share/oops/constantPool.hpp | 4 +- src/hotspot/share/oops/instanceKlass.cpp | 2 +- src/hotspot/share/oops/trainingData.hpp | 6 +- src/hotspot/share/opto/compile.cpp | 8 +- src/hotspot/share/opto/loopnode.hpp | 2 +- src/hotspot/share/opto/mempointer.cpp | 2 +- src/hotspot/share/opto/node.hpp | 2 +- .../share/opto/superwordVTransformBuilder.hpp | 2 +- src/hotspot/share/prims/foreignGlobals.cpp | 4 +- src/hotspot/share/prims/jvmtiTagMapTable.hpp | 8 +- src/hotspot/share/runtime/arguments.hpp | 2 +- src/hotspot/share/runtime/sharedRuntime.cpp | 4 +- src/hotspot/share/runtime/synchronizer.hpp | 2 +- src/hotspot/share/runtime/threadSMR.cpp | 8 +- src/hotspot/share/runtime/vmOperations.cpp | 6 +- src/hotspot/share/services/heapDumper.cpp | 4 +- .../share/utilities/globalDefinitions.hpp | 2 +- .../{resourceHash.hpp => hashTable.hpp} | 48 +++++------ .../share/utilities/nativeCallStack.hpp | 2 +- src/hotspot/share/utilities/objectBitSet.hpp | 4 +- .../share/utilities/objectBitSet.inline.hpp | 4 +- ...esourceHash.hpp => resizableHashTable.hpp} | 28 +++---- .../gtest/runtime/test_os_reserve_between.cpp | 4 +- ...st_resourceHash.cpp => test_hashtable.cpp} | 82 +++++++++---------- 68 files changed, 214 insertions(+), 215 deletions(-) rename src/hotspot/share/utilities/{resourceHash.hpp => hashTable.hpp} (89%) rename src/hotspot/share/utilities/{resizeableResourceHash.hpp => resizableHashTable.hpp} (86%) rename test/hotspot/gtest/utilities/{test_resourceHash.cpp => test_hashtable.cpp} (82%) diff --git a/src/hotspot/share/asm/codeBuffer.hpp b/src/hotspot/share/asm/codeBuffer.hpp index ec9f347e334..57b4aaacdce 100644 --- a/src/hotspot/share/asm/codeBuffer.hpp +++ b/src/hotspot/share/asm/codeBuffer.hpp @@ -33,7 +33,7 @@ #include "utilities/debug.hpp" #include "utilities/growableArray.hpp" #include "utilities/linkedlist.hpp" -#include "utilities/resizeableResourceHash.hpp" +#include "utilities/resizableHashTable.hpp" #include "utilities/macros.hpp" template @@ -541,7 +541,7 @@ class CodeBuffer: public StackObj DEBUG_ONLY(COMMA private Scrubber) { }; typedef LinkedListImpl Offsets; - typedef ResizeableResourceHashtable SharedTrampolineRequests; + typedef ResizeableHashTable SharedTrampolineRequests; private: enum { diff --git a/src/hotspot/share/cds/aotArtifactFinder.cpp b/src/hotspot/share/cds/aotArtifactFinder.cpp index adcc4bfb50a..a42d8882099 100644 --- a/src/hotspot/share/cds/aotArtifactFinder.cpp +++ b/src/hotspot/share/cds/aotArtifactFinder.cpp @@ -37,7 +37,7 @@ #include "oops/instanceKlass.hpp" #include "oops/objArrayKlass.hpp" #include "oops/trainingData.hpp" -#include "utilities/resourceHash.hpp" +#include "utilities/hashTable.hpp" // All the classes that should be included in the AOT cache (in at least the "allocated" state) static GrowableArrayCHeap* _all_cached_classes = nullptr; @@ -47,7 +47,7 @@ static GrowableArrayCHeap* _all_cached_classes = nullptr; static GrowableArrayCHeap* _pending_aot_inited_classes = nullptr; static const int TABLE_SIZE = 15889; // prime number -using ClassesTable = ResourceHashtable; +using ClassesTable = HashTable; static ClassesTable* _seen_classes; // all classes that have been seen by AOTArtifactFinder static ClassesTable* _aot_inited_classes; // all classes that need to be AOT-initialized. diff --git a/src/hotspot/share/cds/aotClassLinker.hpp b/src/hotspot/share/cds/aotClassLinker.hpp index f15684a1ad2..7bbc9ce8138 100644 --- a/src/hotspot/share/cds/aotClassLinker.hpp +++ b/src/hotspot/share/cds/aotClassLinker.hpp @@ -31,8 +31,8 @@ #include "oops/oopsHierarchy.hpp" #include "utilities/exceptions.hpp" #include "utilities/growableArray.hpp" +#include "utilities/hashTable.hpp" #include "utilities/macros.hpp" -#include "utilities/resourceHash.hpp" class AOTLinkedClassTable; class InstanceKlass; @@ -69,7 +69,7 @@ enum class AOTLinkedClassCategory : int; // class AOTClassLinker : AllStatic { static const int TABLE_SIZE = 15889; // prime number - using ClassesTable = ResourceHashtable; + using ClassesTable = HashTable; // Classes loaded inside vmClasses::resolve_all() static ClassesTable* _vm_classes; diff --git a/src/hotspot/share/cds/aotConstantPoolResolver.hpp b/src/hotspot/share/cds/aotConstantPoolResolver.hpp index bab9e263a22..e49d9d1ad0b 100644 --- a/src/hotspot/share/cds/aotConstantPoolResolver.hpp +++ b/src/hotspot/share/cds/aotConstantPoolResolver.hpp @@ -31,8 +31,8 @@ #include "oops/oopsHierarchy.hpp" #include "runtime/handles.hpp" #include "utilities/exceptions.hpp" +#include "utilities/hashTable.hpp" #include "utilities/macros.hpp" -#include "utilities/resourceHash.hpp" class ConstantPool; class constantPoolHandle; @@ -53,7 +53,7 @@ template class GrowableArray; // if all of its supertypes are loaded from the CDS archive. class AOTConstantPoolResolver : AllStatic { static const int TABLE_SIZE = 15889; // prime number - using ClassesTable = ResourceHashtable ; + using ClassesTable = HashTable ; static ClassesTable* _processed_classes; #ifdef ASSERT diff --git a/src/hotspot/share/cds/aotReferenceObjSupport.cpp b/src/hotspot/share/cds/aotReferenceObjSupport.cpp index a62e9c7735a..b43c766b8db 100644 --- a/src/hotspot/share/cds/aotReferenceObjSupport.cpp +++ b/src/hotspot/share/cds/aotReferenceObjSupport.cpp @@ -35,7 +35,7 @@ #include "oops/oopHandle.inline.hpp" #include "runtime/fieldDescriptor.inline.hpp" #include "runtime/javaCalls.hpp" -#include "utilities/resourceHash.hpp" +#include "utilities/hashTable.hpp" // Handling of java.lang.ref.Reference objects in the AOT cache // ============================================================ @@ -92,7 +92,7 @@ #if INCLUDE_CDS_JAVA_HEAP -class KeepAliveObjectsTable : public ResourceHashtable _src_obj_table; - ResizeableResourceHashtable _buffered_to_src_table; + ResizeableHashTable _src_obj_table; + ResizeableHashTable _buffered_to_src_table; GrowableArray* _klasses; GrowableArray* _symbols; unsigned int _entropy_seed; diff --git a/src/hotspot/share/cds/archiveHeapLoader.cpp b/src/hotspot/share/cds/archiveHeapLoader.cpp index 181fa6b2bb4..673c4baa6f6 100644 --- a/src/hotspot/share/cds/archiveHeapLoader.cpp +++ b/src/hotspot/share/cds/archiveHeapLoader.cpp @@ -348,10 +348,10 @@ bool ArchiveHeapLoader::load_heap_region(FileMapInfo* mapinfo) { } class VerifyLoadedHeapEmbeddedPointers: public BasicOopIterateClosure { - ResourceHashtable* _table; + HashTable* _table; public: - VerifyLoadedHeapEmbeddedPointers(ResourceHashtable* table) : _table(table) {} + VerifyLoadedHeapEmbeddedPointers(HashTable* table) : _table(table) {} virtual void do_oop(narrowOop* p) { // This should be called before the loaded region is modified, so all the embedded pointers @@ -411,7 +411,7 @@ void ArchiveHeapLoader::verify_loaded_heap() { log_info(aot, heap)("Verify all oops and pointers in loaded heap"); ResourceMark rm; - ResourceHashtable table; + HashTable table; VerifyLoadedHeapEmbeddedPointers verifier(&table); HeapWord* bottom = (HeapWord*)_loaded_heap_bottom; HeapWord* top = (HeapWord*)_loaded_heap_top; diff --git a/src/hotspot/share/cds/archiveHeapWriter.cpp b/src/hotspot/share/cds/archiveHeapWriter.cpp index db93027e348..b651da8418b 100644 --- a/src/hotspot/share/cds/archiveHeapWriter.cpp +++ b/src/hotspot/share/cds/archiveHeapWriter.cpp @@ -70,7 +70,7 @@ ArchiveHeapWriter::BufferOffsetToSourceObjectTable* ArchiveHeapWriter::_buffer_offset_to_source_obj_table = nullptr; -typedef ResourceHashtable< +typedef HashTable< size_t, // offset of a filler from ArchiveHeapWriter::buffer_bottom() size_t, // size of this filler (in bytes) 127, // prime number diff --git a/src/hotspot/share/cds/archiveHeapWriter.hpp b/src/hotspot/share/cds/archiveHeapWriter.hpp index 1e1319cccc3..f8f55a745ee 100644 --- a/src/hotspot/share/cds/archiveHeapWriter.hpp +++ b/src/hotspot/share/cds/archiveHeapWriter.hpp @@ -32,8 +32,8 @@ #include "utilities/bitMap.hpp" #include "utilities/exceptions.hpp" #include "utilities/growableArray.hpp" +#include "utilities/hashTable.hpp" #include "utilities/macros.hpp" -#include "utilities/resourceHash.hpp" class MemRegion; @@ -152,7 +152,7 @@ private: }; static GrowableArrayCHeap* _source_objs_order; - typedef ResizeableResourceHashtable BufferOffsetToSourceObjectTable; static BufferOffsetToSourceObjectTable* _buffer_offset_to_source_obj_table; diff --git a/src/hotspot/share/cds/cdsHeapVerifier.hpp b/src/hotspot/share/cds/cdsHeapVerifier.hpp index c53f913e42b..811751e8ca2 100644 --- a/src/hotspot/share/cds/cdsHeapVerifier.hpp +++ b/src/hotspot/share/cds/cdsHeapVerifier.hpp @@ -28,7 +28,7 @@ #include "cds/heapShared.hpp" #include "memory/iterator.hpp" #include "utilities/growableArray.hpp" -#include "utilities/resourceHash.hpp" +#include "utilities/hashTable.hpp" class InstanceKlass; class Symbol; @@ -47,7 +47,7 @@ class CDSHeapVerifier : public KlassClosure { Symbol* _name; }; - ResourceHashtable ID2KlassTable; enum { diff --git a/src/hotspot/share/cds/classListWriter.cpp b/src/hotspot/share/cds/classListWriter.cpp index c1ac1b47b11..727cc03c216 100644 --- a/src/hotspot/share/cds/classListWriter.cpp +++ b/src/hotspot/share/cds/classListWriter.cpp @@ -66,7 +66,7 @@ void ClassListWriter::write(const InstanceKlass* k, const ClassFileStream* cfs) write_to_stream(k, w.stream(), cfs); } -class ClassListWriter::IDTable : public ResourceHashtable< +class ClassListWriter::IDTable : public HashTable< const InstanceKlass*, int, 15889, // prime number AnyObj::C_HEAP> {}; diff --git a/src/hotspot/share/cds/dumpTimeClassInfo.hpp b/src/hotspot/share/cds/dumpTimeClassInfo.hpp index 0bb60dfbb29..0feed8682da 100644 --- a/src/hotspot/share/cds/dumpTimeClassInfo.hpp +++ b/src/hotspot/share/cds/dumpTimeClassInfo.hpp @@ -240,7 +240,7 @@ inline unsigned DumpTimeSharedClassTable_hash(T* const& k) { } } -using DumpTimeSharedClassTableBaseType = ResourceHashtable< +using DumpTimeSharedClassTableBaseType = HashTable< InstanceKlass*, DumpTimeClassInfo, 15889, // prime number diff --git a/src/hotspot/share/cds/heapShared.cpp b/src/hotspot/share/cds/heapShared.cpp index 51572bcace7..ea9368c9277 100644 --- a/src/hotspot/share/cds/heapShared.cpp +++ b/src/hotspot/share/cds/heapShared.cpp @@ -367,7 +367,7 @@ bool HeapShared::archive_object(oop obj, oop referrer, KlassSubGraphInfo* subgra } } -class MetaspaceObjToOopHandleTable: public ResourceHashtable { diff --git a/src/hotspot/share/cds/heapShared.hpp b/src/hotspot/share/cds/heapShared.hpp index f4e86aa5895..c0e89809274 100644 --- a/src/hotspot/share/cds/heapShared.hpp +++ b/src/hotspot/share/cds/heapShared.hpp @@ -37,7 +37,7 @@ #include "oops/oopHandle.hpp" #include "oops/oopsHierarchy.hpp" #include "utilities/growableArray.hpp" -#include "utilities/resourceHash.hpp" +#include "utilities/hashTable.hpp" #if INCLUDE_CDS_JAVA_HEAP class DumpedInternedStrings; @@ -202,14 +202,14 @@ public: private: static const int INITIAL_TABLE_SIZE = 15889; // prime number static const int MAX_TABLE_SIZE = 1000000; - typedef ResizeableResourceHashtable ArchivedObjectCache; static ArchivedObjectCache* _archived_object_cache; class DumpTimeKlassSubGraphInfoTable - : public ResourceHashtable SeenObjectsTable; @@ -468,14 +468,14 @@ private: #if INCLUDE_CDS_JAVA_HEAP class DumpedInternedStrings : - public ResizeableResourceHashtable { public: DumpedInternedStrings(unsigned size, unsigned max_size) : - ResizeableResourceHashtable(size, max_size) {} diff --git a/src/hotspot/share/cds/lambdaProxyClassDictionary.hpp b/src/hotspot/share/cds/lambdaProxyClassDictionary.hpp index 814dda80827..ff7acb15292 100644 --- a/src/hotspot/share/cds/lambdaProxyClassDictionary.hpp +++ b/src/hotspot/share/cds/lambdaProxyClassDictionary.hpp @@ -30,7 +30,7 @@ #include "classfile/javaClasses.hpp" #include "memory/metaspaceClosure.hpp" #include "utilities/growableArray.hpp" -#include "utilities/resourceHash.hpp" +#include "utilities/hashTable.hpp" // This file contains *legacy* optimization for lambdas before JEP 483. May be removed in the future. // @@ -249,7 +249,7 @@ public: }; class DumpTimeLambdaProxyClassDictionary - : public ResourceHashtable @@ -178,7 +178,7 @@ class DumpClassListCLDClosure : public CLDClosure { static const int MAX_TABLE_SIZE = 61333; fileStream *_stream; - ResizeableResourceHashtable _dumped_classes; void dump(InstanceKlass* ik) { diff --git a/src/hotspot/share/cds/regeneratedClasses.cpp b/src/hotspot/share/cds/regeneratedClasses.cpp index b36f360b82a..ae14866cea5 100644 --- a/src/hotspot/share/cds/regeneratedClasses.cpp +++ b/src/hotspot/share/cds/regeneratedClasses.cpp @@ -32,9 +32,9 @@ #include "runtime/mutexLocker.hpp" #include "runtime/thread.hpp" #include "utilities/growableArray.hpp" -#include "utilities/resourceHash.hpp" +#include "utilities/hashTable.hpp" -using RegeneratedObjTable = ResourceHashtable; +using RegeneratedObjTable = HashTable; static RegeneratedObjTable* _regenerated_objs = nullptr; // InstanceKlass* and Method* orig_obj -> regen_obj static RegeneratedObjTable* _original_objs = nullptr; // InstanceKlass* and Method* regen_obj -> orig_obj static GrowableArrayCHeap* _regenerated_mirrors = nullptr; diff --git a/src/hotspot/share/classfile/bytecodeAssembler.hpp b/src/hotspot/share/classfile/bytecodeAssembler.hpp index fb51924a9c6..c0383a9678c 100644 --- a/src/hotspot/share/classfile/bytecodeAssembler.hpp +++ b/src/hotspot/share/classfile/bytecodeAssembler.hpp @@ -30,8 +30,7 @@ #include "oops/symbol.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/growableArray.hpp" -#include "utilities/resourceHash.hpp" - +#include "utilities/hashTable.hpp" /** * Bytecode Assembler @@ -125,7 +124,7 @@ class BytecodeCPEntry { class BytecodeConstantPool : public ResourceObj { private: - typedef ResourceHashtable IndexHash; diff --git a/src/hotspot/share/classfile/classFileParser.cpp b/src/hotspot/share/classfile/classFileParser.cpp index 9aca20ee29f..c678fa5e058 100644 --- a/src/hotspot/share/classfile/classFileParser.cpp +++ b/src/hotspot/share/classfile/classFileParser.cpp @@ -81,9 +81,9 @@ #include "utilities/formatBuffer.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/growableArray.hpp" +#include "utilities/hashTable.hpp" #include "utilities/macros.hpp" #include "utilities/ostream.hpp" -#include "utilities/resourceHash.hpp" #include "utilities/utf8.hpp" #if INCLUDE_CDS #include "classfile/systemDictionaryShared.hpp" @@ -782,7 +782,7 @@ class NameSigHash: public ResourceObj { } }; -using NameSigHashtable = ResourceHashtable; @@ -849,7 +849,7 @@ void ClassFileParser::parse_interfaces(const ClassFileStream* const stream, // Check if there's any duplicates in interfaces ResourceMark rm(THREAD); // Set containing interface names - ResourceHashtable* interface_names = new ResourceHashtable(); + HashTable* interface_names = new HashTable(); for (index = 0; index < itfs_len; index++) { const InstanceKlass* const k = _local_interfaces->at(index); Symbol* interface_name = k->name(); @@ -2022,7 +2022,7 @@ void ClassFileParser::copy_localvariable_table(const ConstMethod* cm, ResourceMark rm(THREAD); - typedef ResourceHashtable LVT_HashTable; diff --git a/src/hotspot/share/classfile/classLoaderStats.hpp b/src/hotspot/share/classfile/classLoaderStats.hpp index 06d375b3e9b..23da6bff63c 100644 --- a/src/hotspot/share/classfile/classLoaderStats.hpp +++ b/src/hotspot/share/classfile/classLoaderStats.hpp @@ -25,15 +25,13 @@ #ifndef SHARE_CLASSFILE_CLASSLOADERSTATS_HPP #define SHARE_CLASSFILE_CLASSLOADERSTATS_HPP - #include "classfile/classLoaderData.hpp" #include "oops/klass.hpp" #include "oops/oop.hpp" #include "oops/oopsHierarchy.hpp" #include "runtime/vmOperation.hpp" #include "services/diagnosticCommand.hpp" -#include "utilities/resourceHash.hpp" - +#include "utilities/hashTable.hpp" class ClassLoaderStatsDCmd : public DCmd { public: @@ -105,7 +103,7 @@ protected: return hash; } - typedef ResourceHashtable StatsTable; diff --git a/src/hotspot/share/classfile/loaderConstraints.cpp b/src/hotspot/share/classfile/loaderConstraints.cpp index 21161f44326..13c1ff7319d 100644 --- a/src/hotspot/share/classfile/loaderConstraints.cpp +++ b/src/hotspot/share/classfile/loaderConstraints.cpp @@ -31,7 +31,7 @@ #include "oops/klass.inline.hpp" #include "oops/symbolHandle.hpp" #include "runtime/mutexLocker.hpp" -#include "utilities/resourceHash.hpp" +#include "utilities/hashTable.hpp" // Overview // @@ -147,7 +147,7 @@ class ConstraintSet { // copied into hashtable as }; -using InternalLoaderConstraintTable = ResourceHashtable; +using InternalLoaderConstraintTable = HashTable; static InternalLoaderConstraintTable* _loader_constraint_table; void LoaderConstraint::extend_loader_constraint(Symbol* class_name, diff --git a/src/hotspot/share/classfile/moduleEntry.cpp b/src/hotspot/share/classfile/moduleEntry.cpp index 7f9bf09aa51..ce887081cdb 100644 --- a/src/hotspot/share/classfile/moduleEntry.cpp +++ b/src/hotspot/share/classfile/moduleEntry.cpp @@ -45,9 +45,9 @@ #include "runtime/safepoint.hpp" #include "utilities/events.hpp" #include "utilities/growableArray.hpp" +#include "utilities/hashTable.hpp" #include "utilities/ostream.hpp" #include "utilities/quickSort.hpp" -#include "utilities/resourceHash.hpp" ModuleEntry* ModuleEntryTable::_javabase_module = nullptr; @@ -403,7 +403,7 @@ void ModuleEntry::set_loader_data(ClassLoaderData* cld) { } #if INCLUDE_CDS_JAVA_HEAP -typedef ResourceHashtable< +typedef HashTable< const ModuleEntry*, ModuleEntry*, 557, // prime number diff --git a/src/hotspot/share/classfile/moduleEntry.hpp b/src/hotspot/share/classfile/moduleEntry.hpp index 1ae504577e3..1074c88f010 100644 --- a/src/hotspot/share/classfile/moduleEntry.hpp +++ b/src/hotspot/share/classfile/moduleEntry.hpp @@ -31,9 +31,9 @@ #include "oops/symbolHandle.hpp" #include "runtime/mutexLocker.hpp" #include "utilities/growableArray.hpp" +#include "utilities/hashTable.hpp" #include "utilities/macros.hpp" #include "utilities/ostream.hpp" -#include "utilities/resourceHash.hpp" #if INCLUDE_JFR #include "jfr/support/jfrTraceIdExtension.hpp" #endif @@ -233,7 +233,7 @@ class ModuleClosure: public StackObj { class ModuleEntryTable : public CHeapObj { private: static ModuleEntry* _javabase_module; - ResourceHashtable _table; public: diff --git a/src/hotspot/share/classfile/packageEntry.cpp b/src/hotspot/share/classfile/packageEntry.cpp index 9a21578630f..ea2e6cd1def 100644 --- a/src/hotspot/share/classfile/packageEntry.cpp +++ b/src/hotspot/share/classfile/packageEntry.cpp @@ -38,9 +38,9 @@ #include "runtime/java.hpp" #include "utilities/events.hpp" #include "utilities/growableArray.hpp" +#include "utilities/hashTable.hpp" #include "utilities/ostream.hpp" #include "utilities/quickSort.hpp" -#include "utilities/resourceHash.hpp" PackageEntry::PackageEntry(Symbol* name, ModuleEntry* module) : _name(name), @@ -212,7 +212,7 @@ PackageEntryTable::~PackageEntryTable() { } #if INCLUDE_CDS_JAVA_HEAP -typedef ResourceHashtable< +typedef HashTable< const PackageEntry*, PackageEntry*, 557, // prime number diff --git a/src/hotspot/share/classfile/packageEntry.hpp b/src/hotspot/share/classfile/packageEntry.hpp index aa572dc15b2..84620785ebb 100644 --- a/src/hotspot/share/classfile/packageEntry.hpp +++ b/src/hotspot/share/classfile/packageEntry.hpp @@ -30,9 +30,9 @@ #include "oops/symbolHandle.hpp" #include "runtime/atomic.hpp" #include "utilities/growableArray.hpp" +#include "utilities/hashTable.hpp" #include "utilities/macros.hpp" #include "utilities/ostream.hpp" -#include "utilities/resourceHash.hpp" #if INCLUDE_JFR #include "jfr/support/jfrTraceIdExtension.hpp" #endif @@ -233,7 +233,7 @@ public: // The PackageEntryTable is a Hashtable containing a list of all packages defined // by a particular class loader. Each package is represented as a PackageEntry node. class PackageEntryTable : public CHeapObj { - ResourceHashtable _table; public: PackageEntryTable(); diff --git a/src/hotspot/share/classfile/placeholders.cpp b/src/hotspot/share/classfile/placeholders.cpp index b2f1ed40106..f4c381eafdf 100644 --- a/src/hotspot/share/classfile/placeholders.cpp +++ b/src/hotspot/share/classfile/placeholders.cpp @@ -31,7 +31,7 @@ #include "oops/symbolHandle.hpp" #include "runtime/javaThread.hpp" #include "runtime/mutexLocker.hpp" -#include "utilities/resourceHash.hpp" +#include "utilities/hashTable.hpp" class PlaceholderKey { SymbolHandle _name; @@ -49,7 +49,7 @@ class PlaceholderKey { }; const int _placeholder_table_size = 503; // Does this really have to be prime? -using InternalPlaceholderTable = ResourceHashtable; static InternalPlaceholderTable* _placeholders; diff --git a/src/hotspot/share/classfile/resolutionErrors.cpp b/src/hotspot/share/classfile/resolutionErrors.cpp index 03af71bc26f..25aa370d257 100644 --- a/src/hotspot/share/classfile/resolutionErrors.cpp +++ b/src/hotspot/share/classfile/resolutionErrors.cpp @@ -30,7 +30,7 @@ #include "oops/symbol.hpp" #include "runtime/handles.inline.hpp" #include "runtime/mutexLocker.hpp" -#include "utilities/resourceHash.hpp" +#include "utilities/hashTable.hpp" class ResolutionErrorKey { ConstantPool* _cpool; @@ -53,7 +53,7 @@ class ResolutionErrorKey { } }; -using InternalResolutionErrorTable = ResourceHashtable; diff --git a/src/hotspot/share/classfile/stringTable.cpp b/src/hotspot/share/classfile/stringTable.cpp index 957ecd8ebe8..cf5d98650ce 100644 --- a/src/hotspot/share/classfile/stringTable.cpp +++ b/src/hotspot/share/classfile/stringTable.cpp @@ -59,7 +59,7 @@ #include "utilities/concurrentHashTable.inline.hpp" #include "utilities/concurrentHashTableTasks.inline.hpp" #include "utilities/macros.hpp" -#include "utilities/resizeableResourceHash.hpp" +#include "utilities/resizableHashTable.hpp" #include "utilities/utf8.hpp" #if INCLUDE_G1GC #include "gc/g1/g1CollectedHeap.hpp" @@ -810,7 +810,7 @@ class StringTable::VerifyCompStrings : StackObj { return java_lang_String::equals(a, b); } - ResizeableResourceHashtable _table; public: size_t _errors; diff --git a/src/hotspot/share/classfile/systemDictionary.cpp b/src/hotspot/share/classfile/systemDictionary.cpp index f482a5bb303..783ae9d35ba 100644 --- a/src/hotspot/share/classfile/systemDictionary.cpp +++ b/src/hotspot/share/classfile/systemDictionary.cpp @@ -111,10 +111,10 @@ class InvokeMethodKey : public StackObj { }; -using InvokeMethodIntrinsicTable = ResourceHashtable; static InvokeMethodIntrinsicTable* _invoke_method_intrinsic_table; -using InvokeMethodTypeTable = ResourceHashtable; +using InvokeMethodTypeTable = HashTable; static InvokeMethodTypeTable* _invoke_method_type_table; OopHandle SystemDictionary::_java_system_loader; diff --git a/src/hotspot/share/classfile/systemDictionaryShared.cpp b/src/hotspot/share/classfile/systemDictionaryShared.cpp index 7dd3a7d1bb2..1845c28d819 100644 --- a/src/hotspot/share/classfile/systemDictionaryShared.cpp +++ b/src/hotspot/share/classfile/systemDictionaryShared.cpp @@ -22,6 +22,7 @@ * */ + #include "cds/aotClassFilter.hpp" #include "cds/aotClassLocation.hpp" #include "cds/aotLogging.hpp" @@ -74,7 +75,7 @@ #include "runtime/java.hpp" #include "runtime/javaCalls.hpp" #include "runtime/mutexLocker.hpp" -#include "utilities/resourceHash.hpp" +#include "utilities/hashTable.hpp" #include "utilities/stringUtils.hpp" SystemDictionaryShared::ArchiveInfo SystemDictionaryShared::_static_archive; @@ -445,7 +446,7 @@ InstanceKlass* SystemDictionaryShared::find_or_load_shared_class( return k; } -class UnregisteredClassesTable : public ResourceHashtable< +class UnregisteredClassesTable : public HashTable< Symbol*, InstanceKlass*, 15889, // prime number AnyObj::C_HEAP> {}; diff --git a/src/hotspot/share/classfile/verifier.hpp b/src/hotspot/share/classfile/verifier.hpp index 7857d472705..87ea8bd2970 100644 --- a/src/hotspot/share/classfile/verifier.hpp +++ b/src/hotspot/share/classfile/verifier.hpp @@ -31,7 +31,7 @@ #include "runtime/handles.hpp" #include "utilities/exceptions.hpp" #include "utilities/growableArray.hpp" -#include "utilities/resourceHash.hpp" +#include "utilities/hashTable.hpp" // The verifier class class Verifier : AllStatic { @@ -270,7 +270,7 @@ class sig_as_verification_types : public ResourceObj { // This hashtable is indexed by the Utf8 constant pool indexes pointed to // by constant pool (Interface)Method_refs' NameAndType signature entries. -typedef ResourceHashtable +typedef HashTable method_signatures_table_type; // A new instance of this class is created for each class being verified diff --git a/src/hotspot/share/code/codeCache.cpp b/src/hotspot/share/code/codeCache.cpp index f7c86ce58fa..8f2932f6abc 100644 --- a/src/hotspot/share/code/codeCache.cpp +++ b/src/hotspot/share/code/codeCache.cpp @@ -1185,7 +1185,7 @@ static void check_live_nmethods_dependencies(DepChange& changes) { // Turn off dependency tracing while actually testing dependencies. FlagSetting fs(Dependencies::_verify_in_progress, true); - typedef ResourceHashtable DepTable; diff --git a/src/hotspot/share/code/nmethod.cpp b/src/hotspot/share/code/nmethod.cpp index d8cbf6b9e66..b8b3a70bf58 100644 --- a/src/hotspot/share/code/nmethod.cpp +++ b/src/hotspot/share/code/nmethod.cpp @@ -79,7 +79,7 @@ #include "utilities/dtrace.hpp" #include "utilities/events.hpp" #include "utilities/globalDefinitions.hpp" -#include "utilities/resourceHash.hpp" +#include "utilities/hashTable.hpp" #include "utilities/xmlstream.hpp" #if INCLUDE_JVMCI #include "jvmci/jvmciRuntime.hpp" diff --git a/src/hotspot/share/compiler/disassembler.cpp b/src/hotspot/share/compiler/disassembler.cpp index f8313db66aa..c79c15e0f32 100644 --- a/src/hotspot/share/compiler/disassembler.cpp +++ b/src/hotspot/share/compiler/disassembler.cpp @@ -22,6 +22,7 @@ * */ + #include "asm/assembler.inline.hpp" #include "asm/macroAssembler.hpp" #include "ci/ciUtilities.hpp" @@ -40,7 +41,7 @@ #include "runtime/os.hpp" #include "runtime/stubCodeGenerator.hpp" #include "runtime/stubRoutines.hpp" -#include "utilities/resourceHash.hpp" +#include "utilities/hashTable.hpp" void* Disassembler::_library = nullptr; bool Disassembler::_tried_to_load_library = false; @@ -189,7 +190,7 @@ class decode_env { } }; - typedef ResourceHashtable< + typedef HashTable< address, SourceFileInfo, 15889, // prime number AnyObj::C_HEAP> SourceFileInfoTable; diff --git a/src/hotspot/share/gc/z/zVerify.cpp b/src/hotspot/share/gc/z/zVerify.cpp index 68290c2c009..53cbcd0a421 100644 --- a/src/hotspot/share/gc/z/zVerify.cpp +++ b/src/hotspot/share/gc/z/zVerify.cpp @@ -51,8 +51,8 @@ #include "runtime/thread.hpp" #include "utilities/debug.hpp" #include "utilities/globalDefinitions.hpp" +#include "utilities/hashTable.hpp" #include "utilities/preserveException.hpp" -#include "utilities/resourceHash.hpp" #include "utilities/vmError.hpp" #ifdef ASSERT @@ -516,7 +516,7 @@ void ZVerify::after_weak_processing() { // Remembered set verification // -typedef ResourceHashtable ZStoreBarrierBufferTable; +typedef HashTable ZStoreBarrierBufferTable; static ZStoreBarrierBufferTable* z_verify_store_barrier_buffer_table = nullptr; diff --git a/src/hotspot/share/jfr/support/methodtracer/jfrClassFilterClosure.cpp b/src/hotspot/share/jfr/support/methodtracer/jfrClassFilterClosure.cpp index 37f2d438bc6..dc2908e8a81 100644 --- a/src/hotspot/share/jfr/support/methodtracer/jfrClassFilterClosure.cpp +++ b/src/hotspot/share/jfr/support/methodtracer/jfrClassFilterClosure.cpp @@ -33,7 +33,7 @@ #include "oops/instanceKlass.inline.hpp" #include "runtime/mutexLocker.hpp" #include "utilities/growableArray.hpp" -#include "utilities/resizeableResourceHash.hpp" +#include "utilities/resizableHashTable.hpp" constexpr static unsigned int TABLE_SIZE = 1009; constexpr static unsigned int MAX_TABLE_SIZE = 0x3fffffff; diff --git a/src/hotspot/share/jfr/support/methodtracer/jfrClassFilterClosure.hpp b/src/hotspot/share/jfr/support/methodtracer/jfrClassFilterClosure.hpp index a23b27eb12a..6cf1b321f1e 100644 --- a/src/hotspot/share/jfr/support/methodtracer/jfrClassFilterClosure.hpp +++ b/src/hotspot/share/jfr/support/methodtracer/jfrClassFilterClosure.hpp @@ -40,7 +40,7 @@ template class GrowableArray; template class ResizeableResourceHashtable; + bool (*EQUALS)(K const&, K const&)> class ResizeableHashTable; // Knuth multiplicative hashing. inline uint32_t knuth_hash(const traceid& id) { @@ -48,7 +48,7 @@ inline uint32_t knuth_hash(const traceid& id) { return v * UINT32_C(2654435761); } -typedef ResizeableResourceHashtable* JfrMethodTracer::_instrumented_classes = nullptr; diff --git a/src/hotspot/share/jfr/utilities/jfrSet.hpp b/src/hotspot/share/jfr/utilities/jfrSet.hpp index b4dfc4f6240..40a5d73f370 100644 --- a/src/hotspot/share/jfr/utilities/jfrSet.hpp +++ b/src/hotspot/share/jfr/utilities/jfrSet.hpp @@ -27,7 +27,7 @@ #include "jfr/utilities/jfrAllocation.hpp" #include "jfr/utilities/jfrTypes.hpp" -#include "utilities/resizeableResourceHash.hpp" +#include "utilities/resizableHashTable.hpp" template class ConfigTraceID : public AllStatic { @@ -60,7 +60,7 @@ template class JfrSet : public CONFIG::STORAGE { public: typedef typename CONFIG::TYPE TYPE; - typedef ResizeableResourceHashtable HashMap; + typedef ResizeableHashTable HashMap; constexpr static bool is_cheap() { return CONFIG::alloc_type() == AnyObj::C_HEAP; diff --git a/src/hotspot/share/jvmci/jvmciCompilerToVMInit.cpp b/src/hotspot/share/jvmci/jvmciCompilerToVMInit.cpp index 753d673f54c..801a9bf8eb4 100644 --- a/src/hotspot/share/jvmci/jvmciCompilerToVMInit.cpp +++ b/src/hotspot/share/jvmci/jvmciCompilerToVMInit.cpp @@ -51,7 +51,7 @@ #include "runtime/flags/jvmFlag.hpp" #include "runtime/sharedRuntime.hpp" #include "runtime/stubRoutines.hpp" -#include "utilities/resourceHash.hpp" +#include "utilities/hashTable.hpp" #if INCLUDE_SHENANDOAHGC #include "gc/shenandoah/shenandoahHeap.hpp" #include "gc/shenandoah/shenandoahHeapRegion.hpp" @@ -437,8 +437,8 @@ JVMCIObjectArray CompilerToVM::initialize_intrinsics(JVMCI_TRAPS) { jobjectArray readConfiguration0(JNIEnv *env, JVMCI_TRAPS) { JavaThread* THREAD = JavaThread::current(); // For exception macros. - ResourceHashtable longs; - ResourceHashtable longs; + HashTable strings; diff --git a/src/hotspot/share/logging/logAsyncWriter.hpp b/src/hotspot/share/logging/logAsyncWriter.hpp index 5ffd9dc7b33..a93b1ca58f6 100644 --- a/src/hotspot/share/logging/logAsyncWriter.hpp +++ b/src/hotspot/share/logging/logAsyncWriter.hpp @@ -29,10 +29,10 @@ #include "logging/logMessageBuffer.hpp" #include "memory/allocation.hpp" #include "runtime/mutex.hpp" -#include "runtime/os.inline.hpp" #include "runtime/nonJavaThread.hpp" +#include "runtime/os.inline.hpp" #include "runtime/semaphore.hpp" -#include "utilities/resourceHash.hpp" +#include "utilities/hashTable.hpp" class LogFileStreamOutput; @@ -66,7 +66,7 @@ class AsyncLogWriter : public NonJavaThread { // account for dropped messages template - using AsyncLogMap = ResourceHashtable; diff --git a/src/hotspot/share/memory/metaspaceClosure.hpp b/src/hotspot/share/memory/metaspaceClosure.hpp index 8b419034093..c35363eb71f 100644 --- a/src/hotspot/share/memory/metaspaceClosure.hpp +++ b/src/hotspot/share/memory/metaspaceClosure.hpp @@ -33,7 +33,7 @@ #include "utilities/globalDefinitions.hpp" #include "utilities/growableArray.hpp" #include "utilities/macros.hpp" -#include "utilities/resizeableResourceHash.hpp" +#include "utilities/resizableHashTable.hpp" #include // The metadata hierarchy is separate from the oop hierarchy @@ -374,7 +374,7 @@ public: UniqueMetaspaceClosure() : _has_been_visited(INITIAL_TABLE_SIZE, MAX_TABLE_SIZE) {} private: - ResizeableResourceHashtable _has_been_visited; }; diff --git a/src/hotspot/share/nmt/nativeCallStackPrinter.hpp b/src/hotspot/share/nmt/nativeCallStackPrinter.hpp index 78fd541fc98..12bd1176485 100644 --- a/src/hotspot/share/nmt/nativeCallStackPrinter.hpp +++ b/src/hotspot/share/nmt/nativeCallStackPrinter.hpp @@ -29,7 +29,7 @@ #include "memory/arena.hpp" #include "nmt/memTag.hpp" #include "utilities/globalDefinitions.hpp" -#include "utilities/resourceHash.hpp" +#include "utilities/hashTable.hpp" class outputStream; class NativeCallStack; @@ -41,7 +41,7 @@ class NativeCallStackPrinter { // Cache-related data are mutable to be able to use NativeCallStackPrinter as // inline member in classes with const printing methods. mutable Arena _text_storage; - mutable ResourceHashtable _cache; + mutable HashTable _cache; outputStream* const _out; public: NativeCallStackPrinter(outputStream* out); diff --git a/src/hotspot/share/oops/constantPool.hpp b/src/hotspot/share/oops/constantPool.hpp index be4a7a474d4..8999acf4d3a 100644 --- a/src/hotspot/share/oops/constantPool.hpp +++ b/src/hotspot/share/oops/constantPool.hpp @@ -37,8 +37,8 @@ #include "utilities/align.hpp" #include "utilities/bytes.hpp" #include "utilities/constantTag.hpp" +#include "utilities/hashTable.hpp" #include "utilities/macros.hpp" -#include "utilities/resourceHash.hpp" // A ConstantPool is an array containing class constants as described in the // class file. @@ -872,7 +872,7 @@ private: private: class SymbolHash: public CHeapObj { - ResourceHashtable _table; + HashTable _table; public: void add_if_absent(const Symbol* sym, u2 value) { diff --git a/src/hotspot/share/oops/instanceKlass.cpp b/src/hotspot/share/oops/instanceKlass.cpp index 9266e8ca091..dcec45ff4e2 100644 --- a/src/hotspot/share/oops/instanceKlass.cpp +++ b/src/hotspot/share/oops/instanceKlass.cpp @@ -1116,7 +1116,7 @@ void InstanceKlass::initialize_super_interfaces(TRAPS) { } } -using InitializationErrorTable = ResourceHashtable; +using InitializationErrorTable = HashTable; static InitializationErrorTable* _initialization_error_table; void InstanceKlass::add_initialization_error(JavaThread* current, Handle exception) { diff --git a/src/hotspot/share/oops/trainingData.hpp b/src/hotspot/share/oops/trainingData.hpp index b4fd5fd61ab..baf19773a7b 100644 --- a/src/hotspot/share/oops/trainingData.hpp +++ b/src/hotspot/share/oops/trainingData.hpp @@ -37,7 +37,7 @@ #include "oops/objArrayKlass.hpp" #include "runtime/handles.hpp" #include "runtime/mutexLocker.hpp" -#include "utilities/resizeableResourceHash.hpp" +#include "utilities/resizableHashTable.hpp" class ciEnv; class ciBaseObject; @@ -173,7 +173,7 @@ public: // A set of TD objects that we collect during the training run. class TrainingDataSet { friend TrainingData; - ResizeableResourceHashtable @@ -229,7 +229,7 @@ public: // A widget to ensure that we visit TD object only once (TD objects can have pointer to // other TD object that are sometimes circular). class Visitor { - ResizeableResourceHashtable _visited; + ResizeableHashTable _visited; public: Visitor(unsigned size) : _visited(size, 0x3fffffff) { } bool is_visited(TrainingData* td) { diff --git a/src/hotspot/share/opto/compile.cpp b/src/hotspot/share/opto/compile.cpp index 5a4a6c2667d..cef7aa61219 100644 --- a/src/hotspot/share/opto/compile.cpp +++ b/src/hotspot/share/opto/compile.cpp @@ -87,8 +87,8 @@ #include "runtime/timer.hpp" #include "utilities/align.hpp" #include "utilities/copy.hpp" +#include "utilities/hashTable.hpp" #include "utilities/macros.hpp" -#include "utilities/resourceHash.hpp" // -------------------- Compile::mach_constant_base_node ----------------------- // Constant table base node singleton. @@ -2786,7 +2786,7 @@ uint Compile::eval_macro_logic_op(uint func, uint in1 , uint in2, uint in3) { return res; } -static uint eval_operand(Node* n, ResourceHashtable& eval_map) { +static uint eval_operand(Node* n, HashTable& eval_map) { assert(n != nullptr, ""); assert(eval_map.contains(n), "absent"); return *(eval_map.get(n)); @@ -2794,7 +2794,7 @@ static uint eval_operand(Node* n, ResourceHashtable& eval_map) { static void eval_operands(Node* n, uint& func1, uint& func2, uint& func3, - ResourceHashtable& eval_map) { + HashTable& eval_map) { assert(is_vector_bitwise_op(n), ""); if (is_vector_unary_bitwise_op(n)) { @@ -2818,7 +2818,7 @@ uint Compile::compute_truth_table(Unique_Node_List& partition, Unique_Node_List& assert(inputs.size() <= 3, "sanity"); ResourceMark rm; uint res = 0; - ResourceHashtable eval_map; + HashTable eval_map; // Populate precomputed functions for inputs. // Each input corresponds to one column of 3 input truth-table. diff --git a/src/hotspot/share/opto/loopnode.hpp b/src/hotspot/share/opto/loopnode.hpp index 27e397790d4..730f0750159 100644 --- a/src/hotspot/share/opto/loopnode.hpp +++ b/src/hotspot/share/opto/loopnode.hpp @@ -1982,7 +1982,7 @@ class DataNodeGraph : public StackObj { DataNodeGraph(const Unique_Node_List& data_nodes, PhaseIdealLoop* phase) : _phase(phase), _data_nodes(data_nodes), - // Use 107 as best guess which is the first resize value in ResizeableResourceHashtable::large_table_sizes. + // Use 107 as best guess which is the first resize value in ResizeableHashTable::large_table_sizes. _orig_to_new(107, MaxNodeLimit) { #ifdef ASSERT diff --git a/src/hotspot/share/opto/mempointer.cpp b/src/hotspot/share/opto/mempointer.cpp index e400e40e4a2..0dadc14a686 100644 --- a/src/hotspot/share/opto/mempointer.cpp +++ b/src/hotspot/share/opto/mempointer.cpp @@ -25,7 +25,7 @@ #include "classfile/vmSymbols.hpp" #include "opto/addnode.hpp" #include "opto/mempointer.hpp" -#include "utilities/resourceHash.hpp" +#include "utilities/hashTable.hpp" MemPointerParserCallback MemPointerParserCallback::_empty; diff --git a/src/hotspot/share/opto/node.hpp b/src/hotspot/share/opto/node.hpp index dc0ac474c4b..c2b3c4fb0ad 100644 --- a/src/hotspot/share/opto/node.hpp +++ b/src/hotspot/share/opto/node.hpp @@ -222,7 +222,7 @@ typedef Node** DUIterator_Fast; typedef Node** DUIterator_Last; #endif -typedef ResizeableResourceHashtable OrigToNewHashtable; +typedef ResizeableHashTable OrigToNewHashtable; // Node Sentinel #define NodeSentinel (Node*)-1 diff --git a/src/hotspot/share/opto/superwordVTransformBuilder.hpp b/src/hotspot/share/opto/superwordVTransformBuilder.hpp index ecec380fbfe..f2198113d31 100644 --- a/src/hotspot/share/opto/superwordVTransformBuilder.hpp +++ b/src/hotspot/share/opto/superwordVTransformBuilder.hpp @@ -35,7 +35,7 @@ private: const PackSet& _packset; VTransform& _vtransform; - ResourceHashtable _idx_to_vtnode; + HashTable _idx_to_vtnode; public: SuperWordVTransformBuilder(const PackSet& packset, diff --git a/src/hotspot/share/prims/foreignGlobals.cpp b/src/hotspot/share/prims/foreignGlobals.cpp index d3d0c2cefae..f8aa73bed05 100644 --- a/src/hotspot/share/prims/foreignGlobals.cpp +++ b/src/hotspot/share/prims/foreignGlobals.cpp @@ -26,7 +26,7 @@ #include "memory/resourceArea.hpp" #include "prims/foreignGlobals.inline.hpp" #include "runtime/jniHandles.inline.hpp" -#include "utilities/resourceHash.hpp" +#include "utilities/hashTable.hpp" StubLocations::StubLocations() { for (uint32_t i = 0; i < LOCATION_LIMIT; i++) { @@ -201,7 +201,7 @@ class ArgumentShuffle::ComputeMoveOrder: public StackObj { return a.type() == b.type() && a.index_or_offset() == b.index_or_offset(); } - using KillerTable = ResourceHashtable< + using KillerTable = HashTable< VMStorage, MoveOperation*, 32, // doesn't need to be big. don't have that many argument registers (in known ABIs) AnyObj::RESOURCE_AREA, diff --git a/src/hotspot/share/prims/jvmtiTagMapTable.hpp b/src/hotspot/share/prims/jvmtiTagMapTable.hpp index f86b6386a9a..f717552da2b 100644 --- a/src/hotspot/share/prims/jvmtiTagMapTable.hpp +++ b/src/hotspot/share/prims/jvmtiTagMapTable.hpp @@ -28,7 +28,7 @@ #include "gc/shared/collectedHeap.hpp" #include "memory/allocation.hpp" #include "oops/weakHandle.hpp" -#include "utilities/resizeableResourceHash.hpp" +#include "utilities/resizableHashTable.hpp" class JvmtiEnv; class JvmtiTagMapKeyClosure; @@ -65,14 +65,14 @@ class JvmtiTagMapKey : public CHeapObj { }; typedef -ResizeableResourceHashtable ResizableResourceHT; + JvmtiTagMapKey::equals> ResizableHT; class JvmtiTagMapTable : public CHeapObj { private: - ResizableResourceHT _table; + ResizableHT _table; public: JvmtiTagMapTable(); diff --git a/src/hotspot/share/runtime/arguments.hpp b/src/hotspot/share/runtime/arguments.hpp index 5eb9601e4c5..3d83959d76a 100644 --- a/src/hotspot/share/runtime/arguments.hpp +++ b/src/hotspot/share/runtime/arguments.hpp @@ -27,8 +27,8 @@ #include "logging/logLevel.hpp" #include "logging/logTag.hpp" -#include "memory/allStatic.hpp" #include "memory/allocation.hpp" +#include "memory/allStatic.hpp" #include "runtime/globals.hpp" #include "runtime/java.hpp" #include "runtime/os.hpp" diff --git a/src/hotspot/share/runtime/sharedRuntime.cpp b/src/hotspot/share/runtime/sharedRuntime.cpp index 149ebef4294..beed5df5254 100644 --- a/src/hotspot/share/runtime/sharedRuntime.cpp +++ b/src/hotspot/share/runtime/sharedRuntime.cpp @@ -80,7 +80,7 @@ #include "utilities/dtrace.hpp" #include "utilities/events.hpp" #include "utilities/globalDefinitions.hpp" -#include "utilities/resourceHash.hpp" +#include "utilities/hashTable.hpp" #include "utilities/macros.hpp" #include "utilities/xmlstream.hpp" #ifdef COMPILER1 @@ -2450,7 +2450,7 @@ class ArchivedAdapterTable : public OffsetCompactHashtable< #endif // INCLUDE_CDS // A hashtable mapping from AdapterFingerPrints to AdapterHandlerEntries -using AdapterHandlerTable = ResourceHashtable; diff --git a/src/hotspot/share/runtime/synchronizer.hpp b/src/hotspot/share/runtime/synchronizer.hpp index 089a432a8b5..e35c3651f5b 100644 --- a/src/hotspot/share/runtime/synchronizer.hpp +++ b/src/hotspot/share/runtime/synchronizer.hpp @@ -30,7 +30,7 @@ #include "runtime/basicLock.hpp" #include "runtime/handles.hpp" #include "runtime/javaThread.hpp" -#include "utilities/resourceHash.hpp" +#include "utilities/hashTable.hpp" template class GrowableArray; class LogStream; diff --git a/src/hotspot/share/runtime/threadSMR.cpp b/src/hotspot/share/runtime/threadSMR.cpp index c8af260f66e..6247b351573 100644 --- a/src/hotspot/share/runtime/threadSMR.cpp +++ b/src/hotspot/share/runtime/threadSMR.cpp @@ -39,9 +39,9 @@ #include "utilities/copy.hpp" #include "utilities/debug.hpp" #include "utilities/globalDefinitions.hpp" +#include "utilities/hashTable.hpp" #include "utilities/ostream.hpp" #include "utilities/powerOfTwo.hpp" -#include "utilities/resourceHash.hpp" #include "utilities/vmError.hpp" // The '_cnt', '_max' and '_times" fields are enabled via @@ -191,15 +191,15 @@ class ThreadScanHashtable : public CHeapObj { return (unsigned int)(((uint32_t)(uintptr_t)s1) * 2654435761u); } - // ResourceHashtable SIZE is specified at compile time so we + // HashTable SIZE is specified at compile time so we // use 1031 which is the first prime after 1024. - typedef ResourceHashtable PtrTable; PtrTable * _ptrs; public: - // ResourceHashtable is passed to various functions and populated in + // HashTable is passed to various functions and populated in // different places so we allocate it using C_HEAP to make it immune // from any ResourceMarks that happen to be in the code paths. ThreadScanHashtable() : _ptrs(new (mtThread) PtrTable()) {} diff --git a/src/hotspot/share/runtime/vmOperations.cpp b/src/hotspot/share/runtime/vmOperations.cpp index da833150d74..2cfc84376af 100644 --- a/src/hotspot/share/runtime/vmOperations.cpp +++ b/src/hotspot/share/runtime/vmOperations.cpp @@ -291,9 +291,9 @@ class ObjectMonitorsDump : public MonitorClosure, public ObjectMonitorsView { AnyObj::C_HEAP, mtThread, AllocFailStrategy::RETURN_NULL> {}; - // ResourceHashtable SIZE is specified at compile time so we + // HashTable SIZE is specified at compile time so we // use 1031 which is the first prime after 1024. - typedef ResourceHashtable PtrTable; PtrTable* _ptrs; size_t _key_count; @@ -326,7 +326,7 @@ class ObjectMonitorsDump : public MonitorClosure, public ObjectMonitorsView { } public: - // ResourceHashtable is passed to various functions and populated in + // HashTable is passed to various functions and populated in // different places so we allocate it using C_HEAP to make it immune // from any ResourceMarks that happen to be in the code paths. ObjectMonitorsDump() : _ptrs(new (mtThread) PtrTable), _key_count(0), _om_count(0) {} diff --git a/src/hotspot/share/services/heapDumper.cpp b/src/hotspot/share/services/heapDumper.cpp index 2e299083ac3..051096ae24a 100644 --- a/src/hotspot/share/services/heapDumper.cpp +++ b/src/hotspot/share/services/heapDumper.cpp @@ -831,7 +831,7 @@ public: class DumperClassCacheTable { private: - // ResourceHashtable SIZE is specified at compile time so we + // HashTable SIZE is specified at compile time so we // use 1031 which is the first prime after 1024. static constexpr size_t TABLE_SIZE = 1031; @@ -841,7 +841,7 @@ private: // sized table from overloading. static constexpr int CACHE_TOP = 256; - typedef ResourceHashtable PtrTable; PtrTable* _ptrs; diff --git a/src/hotspot/share/utilities/globalDefinitions.hpp b/src/hotspot/share/utilities/globalDefinitions.hpp index 181786cd238..2a179f5814f 100644 --- a/src/hotspot/share/utilities/globalDefinitions.hpp +++ b/src/hotspot/share/utilities/globalDefinitions.hpp @@ -1332,7 +1332,7 @@ typedef const char* ccstr; typedef const char* ccstrlist; // represents string arguments which accumulate //---------------------------------------------------------------------------------------------------- -// Default hash/equals functions used by ResourceHashtable +// Default hash/equals functions used by HashTable template unsigned primitive_hash(const K& k) { unsigned hash = (unsigned)((uintptr_t)k); diff --git a/src/hotspot/share/utilities/resourceHash.hpp b/src/hotspot/share/utilities/hashTable.hpp similarity index 89% rename from src/hotspot/share/utilities/resourceHash.hpp rename to src/hotspot/share/utilities/hashTable.hpp index a99239b21a0..cd85328657a 100644 --- a/src/hotspot/share/utilities/resourceHash.hpp +++ b/src/hotspot/share/utilities/hashTable.hpp @@ -22,8 +22,8 @@ * */ -#ifndef SHARE_UTILITIES_RESOURCEHASH_HPP -#define SHARE_UTILITIES_RESOURCEHASH_HPP +#ifndef SHARE_UTILITIES_HASHTABLE_HPP +#define SHARE_UTILITIES_HASHTABLE_HPP #include "memory/allocation.hpp" #include "utilities/globalDefinitions.hpp" @@ -33,20 +33,20 @@ #include template -class ResourceHashtableNode : public AnyObj { +class HashTableNode : public AnyObj { public: unsigned _hash; K _key; V _value; - ResourceHashtableNode* _next; + HashTableNode* _next; - ResourceHashtableNode(unsigned hash, K const& key, V const& value, - ResourceHashtableNode* next = nullptr) : + HashTableNode(unsigned hash, K const& key, V const& value, + HashTableNode* next = nullptr) : _hash(hash), _key(key), _value(value), _next(next) {} // Create a node with a default-constructed value. - ResourceHashtableNode(unsigned hash, K const& key, - ResourceHashtableNode* next = nullptr) : + HashTableNode(unsigned hash, K const& key, + HashTableNode* next = nullptr) : _hash(hash), _key(key), _value(), _next(next) {} }; @@ -58,12 +58,12 @@ template< unsigned (*HASH) (K const&), bool (*EQUALS)(K const&, K const&) > -class ResourceHashtableBase : public STORAGE { +class HashTableBase : public STORAGE { static_assert(ALLOC_TYPE == AnyObj::C_HEAP || std::is_trivially_destructible::value, "Destructor for K is only called with C_HEAP"); static_assert(ALLOC_TYPE == AnyObj::C_HEAP || std::is_trivially_destructible::value, "Destructor for V is only called with C_HEAP"); - using Node = ResourceHashtableNode; + using Node = HashTableNode; private: int _number_of_entries; @@ -94,17 +94,17 @@ class ResourceHashtableBase : public STORAGE { Node const** lookup_node(unsigned hash, K const& key) const { return const_cast( - const_cast(this)->lookup_node(hash, key)); + const_cast(this)->lookup_node(hash, key)); } protected: Node** table() const { return STORAGE::table(); } - ResourceHashtableBase() : STORAGE(), _number_of_entries(0) {} - ResourceHashtableBase(unsigned size) : STORAGE(size), _number_of_entries(0) {} - NONCOPYABLE(ResourceHashtableBase); + HashTableBase() : STORAGE(), _number_of_entries(0) {} + HashTableBase(unsigned size) : STORAGE(size), _number_of_entries(0) {} + NONCOPYABLE(HashTableBase); - ~ResourceHashtableBase() { + ~HashTableBase() { if (ALLOC_TYPE == AnyObj::C_HEAP) { Node* const* bucket = table(); const unsigned sz = table_size(); @@ -343,13 +343,13 @@ class ResourceHashtableBase : public STORAGE { }; template -class FixedResourceHashtableStorage : public AnyObj { - using Node = ResourceHashtableNode; +class FixedHashTableStorage : public AnyObj { + using Node = HashTableNode; Node* _table[TABLE_SIZE]; protected: - FixedResourceHashtableStorage() { memset(_table, 0, sizeof(_table)); } - ~FixedResourceHashtableStorage() = default; + FixedHashTableStorage() { memset(_table, 0, sizeof(_table)); } + ~FixedHashTableStorage() = default; constexpr unsigned table_size() const { return TABLE_SIZE; @@ -368,13 +368,13 @@ template< unsigned (*HASH) (K const&) = primitive_hash, bool (*EQUALS)(K const&, K const&) = primitive_equals > -class ResourceHashtable : public ResourceHashtableBase< - FixedResourceHashtableStorage, +class HashTable : public HashTableBase< + FixedHashTableStorage, K, V, ALLOC_TYPE, MEM_TAG, HASH, EQUALS> { - NONCOPYABLE(ResourceHashtable); + NONCOPYABLE(HashTable); public: - ResourceHashtable() : ResourceHashtableBase, + HashTable() : HashTableBase, K, V, ALLOC_TYPE, MEM_TAG, HASH, EQUALS>() {} }; -#endif // SHARE_UTILITIES_RESOURCEHASH_HPP +#endif // SHARE_UTILITIES_HASHTABLE_HPP diff --git a/src/hotspot/share/utilities/nativeCallStack.hpp b/src/hotspot/share/utilities/nativeCallStack.hpp index 5038fe31aef..32635a72d51 100644 --- a/src/hotspot/share/utilities/nativeCallStack.hpp +++ b/src/hotspot/share/utilities/nativeCallStack.hpp @@ -27,8 +27,8 @@ #include "memory/allocation.hpp" #include "nmt/nmtCommon.hpp" +#include "utilities/hashTable.hpp" #include "utilities/ostream.hpp" -#include "utilities/resourceHash.hpp" /* * This class represents a native call path (does not include Java frame) diff --git a/src/hotspot/share/utilities/objectBitSet.hpp b/src/hotspot/share/utilities/objectBitSet.hpp index 124188cd321..73f1c89dd40 100644 --- a/src/hotspot/share/utilities/objectBitSet.hpp +++ b/src/hotspot/share/utilities/objectBitSet.hpp @@ -29,7 +29,7 @@ #include "oops/oop.hpp" #include "oops/oopsHierarchy.hpp" #include "utilities/bitMap.hpp" -#include "utilities/resizeableResourceHash.hpp" +#include "utilities/resizableHashTable.hpp" class MemRegion; @@ -52,7 +52,7 @@ class ObjectBitSet : public CHeapObj { return hash ^ (hash >> 3); } - typedef ResizeableResourceHashtable BitMapFragmentTable; CHeapBitMap* get_fragment_bits(uintptr_t addr); diff --git a/src/hotspot/share/utilities/objectBitSet.inline.hpp b/src/hotspot/share/utilities/objectBitSet.inline.hpp index 482a97bc2d1..e1b4d728b10 100644 --- a/src/hotspot/share/utilities/objectBitSet.inline.hpp +++ b/src/hotspot/share/utilities/objectBitSet.inline.hpp @@ -52,8 +52,8 @@ ObjectBitSet::~ObjectBitSet() { delete current; current = next; } - // destructors for ResourceHashtable base deletes nodes, and - // ResizeableResourceHashtableStorage deletes the table. + // destructors for HashTable base deletes nodes, and + // ResizeableHashTableStorage deletes the table. } template diff --git a/src/hotspot/share/utilities/resizeableResourceHash.hpp b/src/hotspot/share/utilities/resizableHashTable.hpp similarity index 86% rename from src/hotspot/share/utilities/resizeableResourceHash.hpp rename to src/hotspot/share/utilities/resizableHashTable.hpp index b0c992bf1ef..b445ed8a55e 100644 --- a/src/hotspot/share/utilities/resizeableResourceHash.hpp +++ b/src/hotspot/share/utilities/resizableHashTable.hpp @@ -22,28 +22,28 @@ * */ -#ifndef SHARE_UTILITIES_RESIZEABLERESOURCEHASH_HPP -#define SHARE_UTILITIES_RESIZEABLERESOURCEHASH_HPP +#ifndef SHARE_UTILITIES_RESIZEABLEHASHTABLE_HPP +#define SHARE_UTILITIES_RESIZEABLEHASHTABLE_HPP -#include "utilities/resourceHash.hpp" +#include "utilities/hashTable.hpp" template< typename K, typename V, AnyObj::allocation_type ALLOC_TYPE, MemTag MEM_TAG> -class ResizeableResourceHashtableStorage : public AnyObj { - using Node = ResourceHashtableNode; +class ResizeableHashTableStorage : public AnyObj { + using Node = HashTableNode; protected: unsigned _table_size; Node** _table; - ResizeableResourceHashtableStorage(unsigned table_size) { + ResizeableHashTableStorage(unsigned table_size) { _table_size = table_size; _table = alloc_table(table_size); } - ~ResizeableResourceHashtableStorage() { + ~ResizeableHashTableStorage() { if (ALLOC_TYPE == C_HEAP) { FREE_C_HEAP_ARRAY(Node*, _table); } @@ -76,15 +76,15 @@ template< unsigned (*HASH) (K const&) = primitive_hash, bool (*EQUALS)(K const&, K const&) = primitive_equals > -class ResizeableResourceHashtable : public ResourceHashtableBase< - ResizeableResourceHashtableStorage, +class ResizeableHashTable : public HashTableBase< + ResizeableHashTableStorage, K, V, ALLOC_TYPE, MEM_TAG, HASH, EQUALS> { unsigned _max_size; - using BASE = ResourceHashtableBase, + using BASE = HashTableBase, K, V, ALLOC_TYPE, MEM_TAG, HASH, EQUALS>; - using Node = ResourceHashtableNode; - NONCOPYABLE(ResizeableResourceHashtable); + using Node = HashTableNode; + NONCOPYABLE(ResizeableHashTable); // Calculate next "good" hashtable size based on requested count int calculate_resize(bool use_large_table_sizes) const { @@ -111,7 +111,7 @@ class ResizeableResourceHashtable : public ResourceHashtableBase< } public: - ResizeableResourceHashtable(unsigned size, unsigned max_size) + ResizeableHashTable(unsigned size, unsigned max_size) : BASE(size), _max_size(max_size) { assert(size <= 0x3fffffff && max_size <= 0x3fffffff, "avoid overflow in resize"); } @@ -180,4 +180,4 @@ public: #endif // ASSERT }; -#endif // SHARE_UTILITIES_RESIZEABLERESOURCEHASH_HPP +#endif // SHARE_UTILITIES_RESIZABLEHASHTABLE_HPP diff --git a/test/hotspot/gtest/runtime/test_os_reserve_between.cpp b/test/hotspot/gtest/runtime/test_os_reserve_between.cpp index ee91ff49ded..8e68be22749 100644 --- a/test/hotspot/gtest/runtime/test_os_reserve_between.cpp +++ b/test/hotspot/gtest/runtime/test_os_reserve_between.cpp @@ -27,8 +27,8 @@ #include "runtime/os.hpp" #include "utilities/align.hpp" #include "utilities/globalDefinitions.hpp" +#include "utilities/hashTable.hpp" #include "utilities/macros.hpp" -#include "utilities/resourceHash.hpp" // #define LOG_PLEASE #include "testutils.hpp" @@ -206,7 +206,7 @@ static void test_attempt_reserve_memory_between_random_distribution(unsigned num // Allocate n times within that hole (with subsequent deletions) and remember unique addresses returned. constexpr unsigned num_tries_per_attach_point = 100; ResourceMark rm; - ResourceHashtable ht; + HashTable ht; const unsigned num_tries = expect_failure ? 3 : (num_possible_attach_points * num_tries_per_attach_point); unsigned num_uniq = 0; // Number of uniq addresses returned diff --git a/test/hotspot/gtest/utilities/test_resourceHash.cpp b/test/hotspot/gtest/utilities/test_hashtable.cpp similarity index 82% rename from test/hotspot/gtest/utilities/test_resourceHash.cpp rename to test/hotspot/gtest/utilities/test_hashtable.cpp index 7f89f6c6b91..4fa4bcfa8fc 100644 --- a/test/hotspot/gtest/utilities/test_resourceHash.cpp +++ b/test/hotspot/gtest/utilities/test_hashtable.cpp @@ -29,9 +29,9 @@ #include "unittest.hpp" #include "utilities/debug.hpp" #include "utilities/globalDefinitions.hpp" -#include "utilities/resourceHash.hpp" +#include "utilities/hashTable.hpp" -class CommonResourceHashtableTest : public ::testing::Test { +class CommonHashTableTest : public ::testing::Test { protected: typedef void* K; typedef uintx V; @@ -79,7 +79,7 @@ class CommonResourceHashtableTest : public ::testing::Test { }; -class SmallResourceHashtableTest : public CommonResourceHashtableTest { +class SmallHashTableTest : public CommonHashTableTest { protected: template< @@ -93,7 +93,7 @@ class SmallResourceHashtableTest : public CommonResourceHashtableTest { static void test(V step) { EqualityTestIter et; - ResourceHashtable rh; + HashTable rh; ASSERT_FALSE(rh.contains(as_K(step))); @@ -157,61 +157,61 @@ class SmallResourceHashtableTest : public CommonResourceHashtableTest { }; }; -TEST_VM_F(SmallResourceHashtableTest, default) { +TEST_VM_F(SmallHashTableTest, default) { ResourceMark rm; Runner<>::test(0x1); } -TEST_VM_F(SmallResourceHashtableTest, default_shifted) { +TEST_VM_F(SmallHashTableTest, default_shifted) { ResourceMark rm; Runner<>::test(0x10); } -TEST_VM_F(SmallResourceHashtableTest, bad_hash) { +TEST_VM_F(SmallHashTableTest, bad_hash) { ResourceMark rm; Runner::test(0x1); } -TEST_VM_F(SmallResourceHashtableTest, bad_hash_shifted) { +TEST_VM_F(SmallHashTableTest, bad_hash_shifted) { ResourceMark rm; Runner::test(0x10); } -TEST_VM_F(SmallResourceHashtableTest, identity_hash) { +TEST_VM_F(SmallHashTableTest, identity_hash) { ResourceMark rm; Runner::test(0x1); } -TEST_VM_F(SmallResourceHashtableTest, identity_hash_shifted) { +TEST_VM_F(SmallHashTableTest, identity_hash_shifted) { ResourceMark rm; Runner::test(0x10); } -TEST_VM_F(SmallResourceHashtableTest, primitive_hash_no_rm) { +TEST_VM_F(SmallHashTableTest, primitive_hash_no_rm) { Runner, primitive_equals, 512, AnyObj::C_HEAP>::test(0x1); } -TEST_VM_F(SmallResourceHashtableTest, primitive_hash_no_rm_shifted) { +TEST_VM_F(SmallHashTableTest, primitive_hash_no_rm_shifted) { Runner, primitive_equals, 512, AnyObj::C_HEAP>::test(0x10); } -TEST_VM_F(SmallResourceHashtableTest, bad_hash_no_rm) { +TEST_VM_F(SmallHashTableTest, bad_hash_no_rm) { Runner, 512, AnyObj::C_HEAP>::test(0x1); } -TEST_VM_F(SmallResourceHashtableTest, bad_hash_no_rm_shifted) { +TEST_VM_F(SmallHashTableTest, bad_hash_no_rm_shifted) { Runner, 512, AnyObj::C_HEAP>::test(0x10); } -TEST_VM_F(SmallResourceHashtableTest, identity_hash_no_rm) { +TEST_VM_F(SmallHashTableTest, identity_hash_no_rm) { Runner, 1, AnyObj::C_HEAP>::test(0x1); } -TEST_VM_F(SmallResourceHashtableTest, identity_hash_no_rm_shifted) { +TEST_VM_F(SmallHashTableTest, identity_hash_no_rm_shifted) { Runner, 1, AnyObj::C_HEAP>::test(0x10); } -class GenericResourceHashtableTest : public CommonResourceHashtableTest { +class GenericHashTableTest : public CommonHashTableTest { protected: template< @@ -225,7 +225,7 @@ class GenericResourceHashtableTest : public CommonResourceHashtableTest { static void test(unsigned num_elements = SIZE) { EqualityTestIter et; - ResourceHashtable rh; + HashTable rh; for (uintptr_t i = 0; i < num_elements; ++i) { ASSERT_TRUE(rh.put(as_K(i), i)); @@ -263,39 +263,39 @@ class GenericResourceHashtableTest : public CommonResourceHashtableTest { }; }; -TEST_VM_F(GenericResourceHashtableTest, default) { +TEST_VM_F(GenericHashTableTest, default) { ResourceMark rm; Runner<>::test(); } -TEST_VM_F(GenericResourceHashtableTest, bad_hash) { +TEST_VM_F(GenericHashTableTest, bad_hash) { ResourceMark rm; Runner::test(); } -TEST_VM_F(GenericResourceHashtableTest, identity_hash) { +TEST_VM_F(GenericHashTableTest, identity_hash) { ResourceMark rm; Runner::test(); } -TEST_VM_F(GenericResourceHashtableTest, primitive_hash_no_rm) { +TEST_VM_F(GenericHashTableTest, primitive_hash_no_rm) { Runner, primitive_equals, 512, AnyObj::C_HEAP>::test(); } -TEST_VM_F(GenericResourceHashtableTest, bad_hash_no_rm) { +TEST_VM_F(GenericHashTableTest, bad_hash_no_rm) { Runner, 512, AnyObj::C_HEAP>::test(); } -TEST_VM_F(GenericResourceHashtableTest, identity_hash_no_rm) { +TEST_VM_F(GenericHashTableTest, identity_hash_no_rm) { Runner, 1, AnyObj::C_HEAP>::test(512); } -// Simple ResourceHashtable whose key is a SymbolHandle and value is an int +// Simple HashTable whose key is a SymbolHandle and value is an int // This test is to show that the SymbolHandle will correctly handle the refcounting // in the table. -class SimpleResourceHashtableDeleteTest : public ::testing::Test { +class SimpleHashTableDeleteTest : public ::testing::Test { public: - ResourceHashtable _simple_test_table; + HashTable _simple_test_table; class SimpleDeleter : public StackObj { public: @@ -305,7 +305,7 @@ class SimpleResourceHashtableDeleteTest : public ::testing::Test { }; }; -TEST_VM_F(SimpleResourceHashtableDeleteTest, simple_remove) { +TEST_VM_F(SimpleHashTableDeleteTest, simple_remove) { TempNewSymbol t = SymbolTable::new_symbol("abcdefg_simple"); Symbol* s = t; int s_orig_count = s->refcount(); @@ -317,7 +317,7 @@ TEST_VM_F(SimpleResourceHashtableDeleteTest, simple_remove) { ASSERT_EQ(s->refcount(), s_orig_count) << "refcount should be same as start"; } -TEST_VM_F(SimpleResourceHashtableDeleteTest, simple_delete) { +TEST_VM_F(SimpleHashTableDeleteTest, simple_delete) { TempNewSymbol t = SymbolTable::new_symbol("abcdefg_simple"); Symbol* s = t; int s_orig_count = s->refcount(); @@ -330,10 +330,10 @@ TEST_VM_F(SimpleResourceHashtableDeleteTest, simple_delete) { ASSERT_EQ(s->refcount(), s_orig_count) << "refcount should be same as start"; } -// More complicated ResourceHashtable with SymbolHandle in the key. Since the *same* Symbol is part +// More complicated HashTable with SymbolHandle in the key. Since the *same* Symbol is part // of the value, it's not necessary to manipulate the refcount of the key, but you must in the value. // Luckily SymbolHandle does this. -class ResourceHashtableDeleteTest : public ::testing::Test { +class HashTableDeleteTest : public ::testing::Test { public: class TestValue : public CHeapObj { SymbolHandle _s; @@ -348,8 +348,8 @@ class ResourceHashtableDeleteTest : public ::testing::Test { // Symbol* s() const { return _s; } // needed for conversion from TempNewSymbol to SymbolHandle member }; - // ResourceHashtable whose value is a *copy* of TestValue. - ResourceHashtable _test_table; + // HashTable whose value is a *copy* of TestValue. + HashTable _test_table; class Deleter : public StackObj { public: @@ -362,8 +362,8 @@ class ResourceHashtableDeleteTest : public ::testing::Test { } }; - // ResourceHashtable whose value is a pointer to TestValue. - ResourceHashtable _ptr_test_table; + // HashTable whose value is a pointer to TestValue. + HashTable _ptr_test_table; class PtrDeleter : public StackObj { public: @@ -378,7 +378,7 @@ class ResourceHashtableDeleteTest : public ::testing::Test { }; -TEST_VM_F(ResourceHashtableDeleteTest, value_remove) { +TEST_VM_F(HashTableDeleteTest, value_remove) { TempNewSymbol s = SymbolTable::new_symbol("abcdefg"); int s_orig_count = s->refcount(); { @@ -396,7 +396,7 @@ TEST_VM_F(ResourceHashtableDeleteTest, value_remove) { ASSERT_EQ(s->refcount(), s_orig_count) << "refcount should be as we started"; } -TEST_VM_F(ResourceHashtableDeleteTest, value_delete) { +TEST_VM_F(HashTableDeleteTest, value_delete) { TempNewSymbol d = SymbolTable::new_symbol("defghijklmnop"); int d_orig_count = d->refcount(); { @@ -412,7 +412,7 @@ TEST_VM_F(ResourceHashtableDeleteTest, value_delete) { ASSERT_EQ(d->refcount(), d_orig_count) << "refcount should be as we started"; } -TEST_VM_F(ResourceHashtableDeleteTest, check_delete_ptr) { +TEST_VM_F(HashTableDeleteTest, check_delete_ptr) { TempNewSymbol s = SymbolTable::new_symbol("abcdefg_ptr"); int s_orig_count = s->refcount(); { @@ -432,7 +432,7 @@ TEST_VM_F(ResourceHashtableDeleteTest, check_delete_ptr) { ASSERT_EQ(s->refcount(), s_orig_count) << "refcount should be as we started"; } -class ResourceHashtablePrintTest : public ::testing::Test { +class HashTablePrintTest : public ::testing::Test { public: class TestValue { int _i; @@ -441,7 +441,7 @@ class ResourceHashtablePrintTest : public ::testing::Test { public: TestValue(int i) : _i(i), _j(i+1), _k(i+2) {} }; - ResourceHashtable _test_table; + HashTable _test_table; class TableDeleter { public: @@ -452,7 +452,7 @@ class ResourceHashtablePrintTest : public ::testing::Test { }; }; -TEST_VM_F(ResourceHashtablePrintTest, print_test) { +TEST_VM_F(HashTablePrintTest, print_test) { for (int i = 0; i < 300; i++) { TestValue* tv = new TestValue(i); _test_table.put(i, tv); // all the entries can be the same. From 9660320041d0ba0f22ebe074a64472557b85a24c Mon Sep 17 00:00:00 2001 From: Justin Lu Date: Wed, 13 Aug 2025 20:43:46 +0000 Subject: [PATCH 117/580] 8364781: Re-examine DigitList digits resizing during parsing Reviewed-by: liach, naoto --- src/java.base/share/classes/java/text/DigitList.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/text/DigitList.java b/src/java.base/share/classes/java/text/DigitList.java index 3eeabb7e77e..26238345047 100644 --- a/src/java.base/share/classes/java/text/DigitList.java +++ b/src/java.base/share/classes/java/text/DigitList.java @@ -42,6 +42,7 @@ import java.math.BigDecimal; import java.math.BigInteger; import java.math.RoundingMode; import jdk.internal.math.FloatingDecimal; +import jdk.internal.util.ArraysSupport; /** * Digit List. Private to DecimalFormat. @@ -153,7 +154,7 @@ final class DigitList implements Cloneable { */ public void append(char digit) { if (count == digits.length) { - char[] data = new char[count + 100]; + char[] data = new char[ArraysSupport.newLength(count, 1, count)]; System.arraycopy(digits, 0, data, 0, count); digits = data; } From 9c266ae83c047025d778da41e413701ac3b50b03 Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Wed, 13 Aug 2025 20:49:16 +0000 Subject: [PATCH 118/580] 8365229: ARM32: c2i_no_clinit_check_entry assert failed after JDK-8364269 Reviewed-by: kvn, adinn, bulasevich, phh --- src/hotspot/cpu/zero/sharedRuntime_zero.cpp | 6 +++--- src/hotspot/share/code/codeBlob.cpp | 6 ++++-- src/hotspot/share/runtime/sharedRuntime.cpp | 16 ++++++++++++---- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/hotspot/cpu/zero/sharedRuntime_zero.cpp b/src/hotspot/cpu/zero/sharedRuntime_zero.cpp index 2c5fb717c40..df162fbfa74 100644 --- a/src/hotspot/cpu/zero/sharedRuntime_zero.cpp +++ b/src/hotspot/cpu/zero/sharedRuntime_zero.cpp @@ -56,11 +56,11 @@ void SharedRuntime::generate_i2c2i_adapters(MacroAssembler *masm, const BasicType *sig_bt, const VMRegPair *regs, AdapterHandlerEntry* handler) { + // VM expects i2c entry to be always filled. The rest can be unset. handler->set_entry_points(CAST_FROM_FN_PTR(address,zero_null_code_stub), - CAST_FROM_FN_PTR(address,zero_null_code_stub), - CAST_FROM_FN_PTR(address,zero_null_code_stub), + nullptr, + nullptr, nullptr); - return; } nmethod *SharedRuntime::generate_native_wrapper(MacroAssembler *masm, diff --git a/src/hotspot/share/code/codeBlob.cpp b/src/hotspot/share/code/codeBlob.cpp index 5b87e8c5670..9ec5478a071 100644 --- a/src/hotspot/share/code/codeBlob.cpp +++ b/src/hotspot/share/code/codeBlob.cpp @@ -450,8 +450,10 @@ AdapterBlob::AdapterBlob(int size, CodeBuffer* cb, int entry_offset[AdapterBlob: BufferBlob("I2C/C2I adapters", CodeBlobKind::Adapter, cb, size, sizeof(AdapterBlob)) { assert(entry_offset[0] == 0, "sanity check"); for (int i = 1; i < AdapterBlob::ENTRY_COUNT; i++) { - assert(entry_offset[i] > 0 && entry_offset[i] < cb->insts()->size(), - "invalid entry offset 0x%x", entry_offset[i]); + // The entry is within the adapter blob or unset. + assert((entry_offset[i] > 0 && entry_offset[i] < cb->insts()->size()) || + (entry_offset[i] == -1), + "invalid entry offset[%d] = 0x%x", i, entry_offset[i]); } _c2i_offset = entry_offset[1]; _c2i_unverified_offset = entry_offset[2]; diff --git a/src/hotspot/share/runtime/sharedRuntime.cpp b/src/hotspot/share/runtime/sharedRuntime.cpp index beed5df5254..5bfddb8a04a 100644 --- a/src/hotspot/share/runtime/sharedRuntime.cpp +++ b/src/hotspot/share/runtime/sharedRuntime.cpp @@ -2778,7 +2778,12 @@ AdapterBlob* AdapterHandlerLibrary::lookup_aot_cache(AdapterHandlerEntry* handle adapter_blob->get_offsets(offsets); address i2c_entry = adapter_blob->content_begin(); assert(offsets[0] == 0, "sanity check"); - handler->set_entry_points(i2c_entry, i2c_entry + offsets[1], i2c_entry + offsets[2], i2c_entry + offsets[3]); + handler->set_entry_points( + i2c_entry, + (offsets[1] != -1) ? (i2c_entry + offsets[1]) : nullptr, + (offsets[2] != -1) ? (i2c_entry + offsets[2]) : nullptr, + (offsets[3] != -1) ? (i2c_entry + offsets[3]) : nullptr + ); } return adapter_blob; } @@ -2842,9 +2847,12 @@ bool AdapterHandlerLibrary::generate_adapter_code(AdapterBlob*& adapter_blob, assert(AdapterBlob::ENTRY_COUNT == 4, "sanity"); address i2c_entry = handler->get_i2c_entry(); entry_offset[0] = 0; // i2c_entry offset - entry_offset[1] = handler->get_c2i_entry() - i2c_entry; - entry_offset[2] = handler->get_c2i_unverified_entry() - i2c_entry; - entry_offset[3] = handler->get_c2i_no_clinit_check_entry() - i2c_entry; + entry_offset[1] = (handler->get_c2i_entry() != nullptr) ? + (handler->get_c2i_entry() - i2c_entry) : -1; + entry_offset[2] = (handler->get_c2i_unverified_entry() != nullptr) ? + (handler->get_c2i_unverified_entry() - i2c_entry) : -1; + entry_offset[3] = (handler->get_c2i_no_clinit_check_entry() != nullptr) ? + (handler->get_c2i_no_clinit_check_entry() - i2c_entry) : -1; adapter_blob = AdapterBlob::create(&buffer, entry_offset); if (adapter_blob == nullptr) { From 9dcc502cc83773561707f2afe9aee1f9e2386b9e Mon Sep 17 00:00:00 2001 From: Prasanta Sadhukhan Date: Thu, 14 Aug 2025 04:55:02 +0000 Subject: [PATCH 119/580] 8365375: Method SU3.setAcceleratorSelectionForeground assigns to acceleratorForeground Reviewed-by: aivanov, prr, kizune --- .../share/classes/com/sun/java/swing/SwingUtilities3.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 a17f1f480ed..5e1e149eb9e 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 @@ -248,7 +248,7 @@ public class SwingUtilities3 { } public static void setAcceleratorSelectionForeground(Color acceleratorSelectionFg) { - acceleratorForeground = acceleratorSelectionFg; + acceleratorSelectionForeground = acceleratorSelectionFg; } public static void setAcceleratorForeground(Color acceleratorFg) { From c22e01d77648036db4ed640521e82c49f8791ca1 Mon Sep 17 00:00:00 2001 From: Jan Lahoda Date: Thu, 14 Aug 2025 07:02:08 +0000 Subject: [PATCH 120/580] 8341342: Elements.getAllModuleElements() does not work properly before JavacTask.analyze() Reviewed-by: vromero, liach --- .../sun/tools/javac/model/JavacElements.java | 1 + test/jdk/tools/sincechecker/SinceChecker.java | 1 - .../elements/TestElementsProgrammatic.java | 74 +++++++++++++++++++ 3 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 test/langtools/tools/javac/processing/model/util/elements/TestElementsProgrammatic.java diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/model/JavacElements.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/model/JavacElements.java index 7cf419f3ca8..73219ca38ae 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/model/JavacElements.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/model/JavacElements.java @@ -128,6 +128,7 @@ public class JavacElements implements Elements { @Override @DefinedBy(Api.LANGUAGE_MODEL) public Set getAllModuleElements() { + ensureEntered("getAllModuleElements"); if (allowModules) return Collections.unmodifiableSet(modules.allModules()); else diff --git a/test/jdk/tools/sincechecker/SinceChecker.java b/test/jdk/tools/sincechecker/SinceChecker.java index a7ec90d53f8..f9a8e3d49dd 100644 --- a/test/jdk/tools/sincechecker/SinceChecker.java +++ b/test/jdk/tools/sincechecker/SinceChecker.java @@ -297,7 +297,6 @@ public class SinceChecker { Collections.singletonList(SimpleJavaFileObject.forSource(URI.create("myfo:/Test.java"), ""))); ct.analyze(); Elements elements = ct.getElements(); - elements.getModuleElement("java.base"); try (EffectiveSourceSinceHelper javadocHelper = EffectiveSourceSinceHelper.create(ct, List.of(root), this)) { processModuleCheck(elements.getModuleElement(moduleName), ct, moduleDirectory, javadocHelper); } catch (Exception e) { diff --git a/test/langtools/tools/javac/processing/model/util/elements/TestElementsProgrammatic.java b/test/langtools/tools/javac/processing/model/util/elements/TestElementsProgrammatic.java new file mode 100644 index 00000000000..dda3faac232 --- /dev/null +++ b/test/langtools/tools/javac/processing/model/util/elements/TestElementsProgrammatic.java @@ -0,0 +1,74 @@ +/* + * 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 8341342 + * @summary Test Elements methods programmatically + * @modules jdk.compiler + * @run junit TestElementsProgrammatic + */ + +import com.sun.source.util.JavacTask; +import java.net.URI; +import java.util.List; +import java.util.function.Consumer; +import javax.tools.JavaCompiler; +import javax.tools.JavaFileObject; +import javax.tools.SimpleJavaFileObject; +import javax.tools.ToolProvider; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Programmatically test workings of various methods of Elements. + */ +public class TestElementsProgrammatic { + + private final JavaCompiler systemCompiler = ToolProvider.getSystemJavaCompiler(); + + @ParameterizedTest + @MethodSource + public void automaticallyEnter(Consumer verify) { + //make sure the get{All,}{Module,Package,Type}Element{s,} methods will automatically enter: + JavaFileObject input = + SimpleJavaFileObject.forSource(URI.create("mem://Test.java"), ""); + List inputs = List.of(input); + JavacTask task = (JavacTask) systemCompiler.getTask(null, null, null, null, null, inputs); + verify.accept(task); + } + + private static List> automaticallyEnter() { + return List.of( + task -> assertFalse(task.getElements().getAllModuleElements().isEmpty()), + task -> assertNotNull(task.getElements().getModuleElement("java.base")), + task -> assertNotNull(task.getElements().getPackageElement("java.lang")), + task -> assertFalse(task.getElements().getAllPackageElements("java.lang").isEmpty()), + task -> assertNotNull(task.getElements().getTypeElement("java.lang.Object")), + task -> assertFalse(task.getElements().getAllTypeElements("java.lang.Object").isEmpty()) + ); + } +} From a6be2286421e069a292c749eecd6bdc38a8deaf2 Mon Sep 17 00:00:00 2001 From: Jan Lahoda Date: Thu, 14 Aug 2025 07:04:40 +0000 Subject: [PATCH 121/580] 8365314: javac fails with an exception for erroneous source Reviewed-by: vromero --- .../com/sun/tools/javac/code/Lint.java | 17 ++++++------ .../javac/recovery/AnnotationRecovery.java | 26 ++++++++++++++++++- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Lint.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Lint.java index 5a4d09b67ba..6d83f95fce4 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Lint.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Lint.java @@ -528,14 +528,15 @@ public class Lint { // Given a @SuppressWarnings annotation, extract the recognized suppressions private EnumSet suppressionsFrom(Attribute.Compound suppressWarnings) { EnumSet result = LintCategory.newEmptySet(); - Attribute.Array values = (Attribute.Array)suppressWarnings.member(names.value); - for (Attribute value : values.values) { - Optional.of(value) - .filter(val -> val instanceof Attribute.Constant) - .map(val -> (String) ((Attribute.Constant) val).value) - .flatMap(LintCategory::get) - .filter(lc -> lc.annotationSuppression) - .ifPresent(result::add); + if (suppressWarnings.member(names.value) instanceof Attribute.Array values) { + for (Attribute value : values.values) { + Optional.of(value) + .filter(val -> val instanceof Attribute.Constant) + .map(val -> (String) ((Attribute.Constant) val).value) + .flatMap(LintCategory::get) + .filter(lc -> lc.annotationSuppression) + .ifPresent(result::add); + } } return result; } diff --git a/test/langtools/tools/javac/recovery/AnnotationRecovery.java b/test/langtools/tools/javac/recovery/AnnotationRecovery.java index e5e52b82159..ea94f0f582d 100644 --- a/test/langtools/tools/javac/recovery/AnnotationRecovery.java +++ b/test/langtools/tools/javac/recovery/AnnotationRecovery.java @@ -23,7 +23,7 @@ /* * @test - * @bug 8270139 8361445 + * @bug 8270139 8361445 8365314 * @summary Verify error recovery w.r.t. annotations * @library /tools/lib * @modules jdk.compiler/com.sun.tools.javac.api @@ -224,4 +224,28 @@ public class AnnotationRecovery extends TestRunner { } } + @Test //JDK-8365314 + public void testSuppressWarningsMissingAttribute() throws Exception { + String code = """ + @SuppressWarnings + public class Test { + } + """; + Path curPath = Path.of("."); + List actual = new JavacTask(tb) + .options("-XDrawDiagnostics", "-XDdev") + .sources(code) + .outdir(curPath) + .run(Expect.FAIL) + .getOutputLines(OutputKind.DIRECT); + + List expected = List.of( + "Test.java:1:1: compiler.err.annotation.missing.default.value: java.lang.SuppressWarnings, value", + "1 error" + ); + + if (!Objects.equals(actual, expected)) { + error("Expected: " + expected + ", but got: " + actual); + } + } } From 3e3298509f136583b18e5ab8bf75a8b012016f16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joel=20Sikstr=C3=B6m?= Date: Thu, 14 Aug 2025 07:37:10 +0000 Subject: [PATCH 122/580] 8365317: ZGC: Setting ZYoungGCThreads lower than ZOldGCThreads may result in a crash Reviewed-by: tschatzl, eosterlund --- src/hotspot/share/gc/z/zDirector.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hotspot/share/gc/z/zDirector.cpp b/src/hotspot/share/gc/z/zDirector.cpp index e46b1c9e167..380a1e48ab4 100644 --- a/src/hotspot/share/gc/z/zDirector.cpp +++ b/src/hotspot/share/gc/z/zDirector.cpp @@ -725,8 +725,8 @@ static ZWorkerCounts select_worker_threads(const ZDirectorStats& stats, uint you // Adjust down the old workers so the next minor during major will be less sad old_workers = old_workers_clamped; // Since collecting the old generation depends on the initial young collection - // finishing, we don't want it to have fewer workers than the old generation. - young_workers = MAX2(old_workers, young_workers); + // finishing, we ideally don't want it to have fewer workers than the old generation. + young_workers = clamp(MAX2(old_workers, young_workers), 1u, ZYoungGCThreads); } else if (type == ZWorkerSelectionType::minor_during_old) { // Adjust young and old workers for minor during old to fit within ConcGCThreads young_workers = young_workers_clamped; From e320162815d529bc65cd058b34ec39d60d032ce7 Mon Sep 17 00:00:00 2001 From: Yudi Zheng Date: Thu, 14 Aug 2025 07:39:49 +0000 Subject: [PATCH 123/580] 8365218: [JVMCI] AArch64 CPU features are not computed correctly after 8364128 Reviewed-by: dnsimon --- .../hotspot/HotSpotJVMCIBackendFactory.java | 65 ++----------------- .../AArch64HotSpotJVMCIBackendFactory.java | 2 +- .../AMD64HotSpotJVMCIBackendFactory.java | 16 +++-- .../RISCV64HotSpotJVMCIBackendFactory.java | 2 +- 4 files changed, 20 insertions(+), 65 deletions(-) diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotJVMCIBackendFactory.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotJVMCIBackendFactory.java index 319b9b9bc44..e0fc314de5b 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotJVMCIBackendFactory.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotJVMCIBackendFactory.java @@ -27,10 +27,10 @@ import java.util.EnumSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.function.LongFunction; import jdk.vm.ci.common.JVMCIError; import jdk.vm.ci.runtime.JVMCIBackend; -import static jdk.vm.ci.hotspot.UnsafeAccess.UNSAFE; public interface HotSpotJVMCIBackendFactory { @@ -48,7 +48,8 @@ public interface HotSpotJVMCIBackendFactory { * @param enumType the class of {@code CPUFeatureType} * @param constants VM constants. Each entry whose key starts with {@code "VM_Version::CPU_"} * specifies a CPU feature and its value is a mask for a bit in {@code features} - * @param features bits specifying CPU features + * @param bitMaskSupplier supplier to get the bit mask for the corresponding VM constant + * @param featuresSupplier supplier to get the bits specifying CPU features * @param renaming maps from VM feature names to enum constant names where the two differ * @throws IllegalArgumentException if any VM CPU feature constant cannot be converted to an * enum value @@ -57,18 +58,19 @@ public interface HotSpotJVMCIBackendFactory { static > EnumSet convertFeatures( Class enumType, Map constants, - long features, + LongFunction bitMaskSupplier, + LongFunction featuresSupplier, Map renaming) { EnumSet outFeatures = EnumSet.noneOf(enumType); List missing = new ArrayList<>(); for (Entry e : constants.entrySet()) { - long bitMask = e.getValue(); + long bitMask = bitMaskSupplier.apply(e.getValue()); String key = e.getKey(); if (key.startsWith("VM_Version::CPU_")) { String name = key.substring("VM_Version::CPU_".length()); try { CPUFeatureType feature = Enum.valueOf(enumType, renaming.getOrDefault(name, name)); - if ((features & bitMask) != 0) { + if ((featuresSupplier.apply(e.getValue()) & bitMask) != 0) { outFeatures.add(feature); } } catch (IllegalArgumentException iae) { @@ -82,57 +84,4 @@ public interface HotSpotJVMCIBackendFactory { return outFeatures; } - /** - * Converts CPU features bit map into enum constants. - * - * @param CPU feature enum type - * @param enumType the class of {@code CPUFeatureType} - * @param constants VM constants. Each entry whose key starts with {@code "VM_Version::CPU_"} - * specifies a CPU feature and its value is a mask for a bit in {@code features} - * @param featuresBitMapAddress pointer to {@code VM_Features::_features_bitmap} field of {@code VM_Version::_features} - * @param featuresBitMapSize size of feature bit map in bytes - * @param renaming maps from VM feature names to enum constant names where the two differ - * @throws IllegalArgumentException if any VM CPU feature constant cannot be converted to an - * enum value - * @return the set of converted values - */ - static > EnumSet convertFeatures( - Class enumType, - Map constants, - long featuresBitMapAddress, - long featuresBitMapSize, - Map renaming) { - EnumSet outFeatures = EnumSet.noneOf(enumType); - List missing = new ArrayList<>(); - - for (Entry e : constants.entrySet()) { - String key = e.getKey(); - long bitIndex = e.getValue(); - if (key.startsWith("VM_Version::CPU_")) { - String name = key.substring("VM_Version::CPU_".length()); - try { - final long featuresElementShiftCount = 6; // log (# of bits per long) - final long featuresElementMask = (1L << featuresElementShiftCount) - 1; - - CPUFeatureType feature = Enum.valueOf(enumType, renaming.getOrDefault(name, name)); - - long featureIndex = bitIndex >>> featuresElementShiftCount; - long featureBitMask = 1L << (bitIndex & featuresElementMask); - assert featureIndex < featuresBitMapSize; - - long featuresElement = UNSAFE.getLong(featuresBitMapAddress + featureIndex * Long.BYTES); - - if ((featuresElement & featureBitMask) != 0) { - outFeatures.add(feature); - } - } catch (IllegalArgumentException iae) { - missing.add(name); - } - } - } - if (!missing.isEmpty()) { - throw new JVMCIError("Missing CPU feature constants: %s", missing); - } - return outFeatures; - } } diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/aarch64/AArch64HotSpotJVMCIBackendFactory.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/aarch64/AArch64HotSpotJVMCIBackendFactory.java index 19c161d6bde..1ee016b73a8 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/aarch64/AArch64HotSpotJVMCIBackendFactory.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/aarch64/AArch64HotSpotJVMCIBackendFactory.java @@ -49,7 +49,7 @@ public class AArch64HotSpotJVMCIBackendFactory implements HotSpotJVMCIBackendFac private static EnumSet computeFeatures(AArch64HotSpotVMConfig config) { // Configure the feature set using the HotSpot flag settings. Map constants = config.getStore().getConstants(); - return HotSpotJVMCIBackendFactory.convertFeatures(CPUFeature.class, constants, config.vmVersionFeatures, emptyMap()); + return HotSpotJVMCIBackendFactory.convertFeatures(CPUFeature.class, constants, idx -> 1L << idx, _ -> config.vmVersionFeatures, emptyMap()); } private static TargetDescription createTarget(AArch64HotSpotVMConfig config) { diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/amd64/AMD64HotSpotJVMCIBackendFactory.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/amd64/AMD64HotSpotJVMCIBackendFactory.java index 040f39e3b8a..cae6d18e71e 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/amd64/AMD64HotSpotJVMCIBackendFactory.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/amd64/AMD64HotSpotJVMCIBackendFactory.java @@ -26,6 +26,8 @@ import static jdk.vm.ci.common.InitTimer.timer; import java.util.EnumSet; import java.util.Map; + +import jdk.internal.misc.Unsafe; import jdk.internal.util.OperatingSystem; import jdk.vm.ci.amd64.AMD64; import jdk.vm.ci.amd64.AMD64.CPUFeature; @@ -50,11 +52,15 @@ public class AMD64HotSpotJVMCIBackendFactory implements HotSpotJVMCIBackendFacto Map constants = config.getStore().getConstants(); Map renaming = Map.of("3DNOW_PREFETCH", "AMD_3DNOW_PREFETCH"); long featuresBitMapAddress = config.vmVersionFeatures + config.vmFeaturesFeaturesOffset; - EnumSet features = HotSpotJVMCIBackendFactory.convertFeatures(CPUFeature.class, - constants, - featuresBitMapAddress, - config.vmFeaturesFeaturesSize, - renaming); + EnumSet features = HotSpotJVMCIBackendFactory.convertFeatures(CPUFeature.class, constants, idx -> { + final long featuresElementShiftCount = 6; // log (# of bits per long) + final long featuresElementMask = (1L << featuresElementShiftCount) - 1; + return 1L << (idx & featuresElementMask); + }, idx -> { + final long featuresElementShiftCount = 6; // log (# of bits per long) + long featureIndex = idx >>> featuresElementShiftCount; + return Unsafe.getUnsafe().getLong(featuresBitMapAddress + featureIndex * Long.BYTES); + }, renaming); assert features.contains(AMD64.CPUFeature.SSE) : "minimum config for x64"; assert features.contains(AMD64.CPUFeature.SSE2) : "minimum config for x64"; return features; diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/riscv64/RISCV64HotSpotJVMCIBackendFactory.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/riscv64/RISCV64HotSpotJVMCIBackendFactory.java index c0e31124500..f50fcf1dbbc 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/riscv64/RISCV64HotSpotJVMCIBackendFactory.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/riscv64/RISCV64HotSpotJVMCIBackendFactory.java @@ -49,7 +49,7 @@ public class RISCV64HotSpotJVMCIBackendFactory implements HotSpotJVMCIBackendFac private static EnumSet computeFeatures(RISCV64HotSpotVMConfig config) { // Configure the feature set using the HotSpot flag settings. Map constants = config.getStore().getConstants(); - return HotSpotJVMCIBackendFactory.convertFeatures(CPUFeature.class, constants, config.vmVersionFeatures, emptyMap()); + return HotSpotJVMCIBackendFactory.convertFeatures(CPUFeature.class, constants, mask -> mask, _ -> config.vmVersionFeatures, emptyMap()); } private static TargetDescription createTarget(RISCV64HotSpotVMConfig config) { From 7698c373a684235812c9dc11edd751059f9e8e81 Mon Sep 17 00:00:00 2001 From: Erik Gahlin Date: Thu, 14 Aug 2025 10:43:21 +0000 Subject: [PATCH 124/580] 8364556: JFR: Disable SymbolTableStatistics and StringTableStatistics in default.jfc Reviewed-by: mgronlun --- src/jdk.jfr/share/conf/jfr/default.jfc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/jdk.jfr/share/conf/jfr/default.jfc b/src/jdk.jfr/share/conf/jfr/default.jfc index 463639d7f1e..eb3b8626722 100644 --- a/src/jdk.jfr/share/conf/jfr/default.jfc +++ b/src/jdk.jfr/share/conf/jfr/default.jfc @@ -33,12 +33,12 @@ - true + false 10 s - true + false 10 s From 98f54d90ea56f63c2fc5137af98b57dbc90fe150 Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Thu, 14 Aug 2025 11:11:47 +0000 Subject: [PATCH 125/580] 8365487: [asan] some oops (mode) related tests fail Reviewed-by: kbarrett, syan --- .../jtreg/runtime/CompressedOops/UseCompressedOops.java | 4 +++- .../TestGCHeapConfigurationEventWith32BitOops.java | 2 ++ .../TestGCHeapConfigurationEventWithZeroBasedOops.java | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/test/hotspot/jtreg/runtime/CompressedOops/UseCompressedOops.java b/test/hotspot/jtreg/runtime/CompressedOops/UseCompressedOops.java index 3d118bf73b1..e5e2a23c565 100644 --- a/test/hotspot/jtreg/runtime/CompressedOops/UseCompressedOops.java +++ b/test/hotspot/jtreg/runtime/CompressedOops/UseCompressedOops.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -30,6 +30,8 @@ * @modules java.base/jdk.internal.misc * java.management * @build jdk.test.whitebox.WhiteBox + * @comment Asan changes memory layout and we get a different coops mode + * @requires !vm.asan * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox * @run main/othervm/timeout=480 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. UseCompressedOops */ diff --git a/test/jdk/jdk/jfr/event/gc/configuration/TestGCHeapConfigurationEventWith32BitOops.java b/test/jdk/jdk/jfr/event/gc/configuration/TestGCHeapConfigurationEventWith32BitOops.java index 9ef90afb063..c98f055aa72 100644 --- a/test/jdk/jdk/jfr/event/gc/configuration/TestGCHeapConfigurationEventWith32BitOops.java +++ b/test/jdk/jdk/jfr/event/gc/configuration/TestGCHeapConfigurationEventWith32BitOops.java @@ -35,6 +35,8 @@ import jdk.test.whitebox.WhiteBox; * @requires vm.gc == "Parallel" | vm.gc == null * @requires os.family == "linux" | os.family == "windows" * @requires sun.arch.data.model == "64" + * @comment Asan changes memory layout and we get a different coops mode + * @requires !vm.asan * @library /test/lib /test/jdk * @build jdk.test.whitebox.WhiteBox * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox diff --git a/test/jdk/jdk/jfr/event/gc/configuration/TestGCHeapConfigurationEventWithZeroBasedOops.java b/test/jdk/jdk/jfr/event/gc/configuration/TestGCHeapConfigurationEventWithZeroBasedOops.java index 62d858eef98..f69d192ca5f 100644 --- a/test/jdk/jdk/jfr/event/gc/configuration/TestGCHeapConfigurationEventWithZeroBasedOops.java +++ b/test/jdk/jdk/jfr/event/gc/configuration/TestGCHeapConfigurationEventWithZeroBasedOops.java @@ -33,6 +33,8 @@ import jdk.test.lib.jfr.EventVerifier; * @requires vm.gc == "Parallel" | vm.gc == null * @requires os.family == "linux" | os.family == "windows" * @requires sun.arch.data.model == "64" + * @comment Asan changes memory layout and we get a different coops mode + * @requires !vm.asan * @library /test/lib /test/jdk * @run main/othervm -XX:+UnlockExperimentalVMOptions -XX:-UseFastUnorderedTimeStamps -XX:+UseParallelGC -XX:+UseCompressedOops -Xmx4g jdk.jfr.event.gc.configuration.TestGCHeapConfigurationEventWithZeroBasedOops */ From 41520998aa8808452ee384b213b2a77c7bad668d Mon Sep 17 00:00:00 2001 From: Roman Marchenko Date: Thu, 14 Aug 2025 12:31:20 +0000 Subject: [PATCH 126/580] 8365098: make/RunTests.gmk generates a wrong path to test artifacts on Alpine Reviewed-by: erikj, ihse --- make/RunTests.gmk | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/make/RunTests.gmk b/make/RunTests.gmk index 60ae1bd4763..574fd869092 100644 --- a/make/RunTests.gmk +++ b/make/RunTests.gmk @@ -1243,7 +1243,7 @@ UseSpecialTestHandler = \ # Now process each test to run and setup a proper make rule $(foreach test, $(TESTS_TO_RUN), \ $(eval TEST_ID := $(shell $(ECHO) $(strip $(test)) | \ - $(TR) -cs '[a-z][A-Z][0-9]\n' '[_*1000]')) \ + $(TR) -cs '[a-z][A-Z][0-9]\n' '_')) \ $(eval ALL_TEST_IDS += $(TEST_ID)) \ $(if $(call UseCustomTestHandler, $(test)), \ $(eval $(call SetupRunCustomTest, $(TEST_ID), \ @@ -1323,9 +1323,9 @@ run-test-report: post-run-test TEST TOTAL PASS FAIL ERROR SKIP " " $(foreach test, $(TESTS_TO_RUN), \ $(eval TEST_ID := $(shell $(ECHO) $(strip $(test)) | \ - $(TR) -cs '[a-z][A-Z][0-9]\n' '[_*1000]')) \ + $(TR) -cs '[a-z][A-Z][0-9]\n' '_')) \ $(ECHO) >> $(TEST_LAST_IDS) $(TEST_ID) $(NEWLINE) \ - $(eval NAME_PATTERN := $(shell $(ECHO) $(test) | $(TR) -c '\n' '[_*1000]')) \ + $(eval NAME_PATTERN := $(shell $(ECHO) $(test) | $(TR) -c '\n' '_')) \ $(if $(filter __________________________________________________%, $(NAME_PATTERN)), \ $(eval TEST_NAME := ) \ $(PRINTF) >> $(TEST_SUMMARY) "%2s %-49s\n" " " "$(test)" $(NEWLINE) \ From dd113c8df06cc7e1465fb3dfef2e9b2a5a99f1fb Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Thu, 14 Aug 2025 14:50:56 +0000 Subject: [PATCH 127/580] 8364628: Serial: Refactor SerialHeap::mem_allocate_work Reviewed-by: phh, kbarrett --- src/hotspot/share/gc/serial/serialHeap.cpp | 51 +++++++------------ src/hotspot/share/gc/serial/serialHeap.hpp | 4 -- .../share/gc/serial/tenuredGeneration.hpp | 2 + .../gc/serial/tenuredGeneration.inline.hpp | 8 +++ 4 files changed, 28 insertions(+), 37 deletions(-) diff --git a/src/hotspot/share/gc/serial/serialHeap.cpp b/src/hotspot/share/gc/serial/serialHeap.cpp index 48d6a1b74ea..6b9328b8697 100644 --- a/src/hotspot/share/gc/serial/serialHeap.cpp +++ b/src/hotspot/share/gc/serial/serialHeap.cpp @@ -281,15 +281,6 @@ size_t SerialHeap::max_capacity() const { return _young_gen->max_capacity() + _old_gen->max_capacity(); } -// Return true if any of the following is true: -// . the allocation won't fit into the current young gen heap -// . heap memory is tight -bool SerialHeap::should_try_older_generation_allocation(size_t word_size) const { - size_t young_capacity = _young_gen->capacity_before_gc(); - return (word_size > heap_word_size(young_capacity)) - || _is_heap_almost_full; -} - HeapWord* SerialHeap::expand_heap_and_allocate(size_t size, bool is_tlab) { HeapWord* result = nullptr; if (_old_gen->should_allocate(size, is_tlab)) { @@ -308,32 +299,26 @@ HeapWord* SerialHeap::expand_heap_and_allocate(size_t size, bool is_tlab) { HeapWord* SerialHeap::mem_allocate_work(size_t size, bool is_tlab) { HeapWord* result = nullptr; - // Loop until the allocation is satisfied, or unsatisfied after GC. - for (uint try_count = 1; /* return or throw */; try_count += 1) { - // First allocation attempt is lock-free. - DefNewGeneration *young = _young_gen; - if (young->should_allocate(size, is_tlab)) { - result = young->par_allocate(size); + for (uint try_count = 1; /* break */; try_count++) { + if (_young_gen->should_allocate(size, is_tlab)) { + result = _young_gen->par_allocate(size); if (result != nullptr) { - assert(is_in_reserved(result), "result not in heap"); - return result; + break; + } + } + // Try old-gen allocation for non-TLAB. + if (!is_tlab) { + // If it's too large for young-gen or heap is too full. + if (size > heap_word_size(_young_gen->capacity_before_gc()) || _is_heap_almost_full) { + result = _old_gen->par_allocate(size); + if (result != nullptr) { + break; + } } } uint gc_count_before; // Read inside the Heap_lock locked region. { MutexLocker ml(Heap_lock); - log_trace(gc, alloc)("SerialHeap::mem_allocate_work: attempting locked slow path allocation"); - // Note that only large objects get a shot at being - // allocated in later generations. - bool first_only = !should_try_older_generation_allocation(size); - - result = attempt_allocation(size, is_tlab, first_only); - if (result != nullptr) { - assert(is_in_reserved(result), "result not in heap"); - return result; - } - - // Read the gc count while the heap lock is held. gc_count_before = total_collections(); } @@ -341,10 +326,7 @@ HeapWord* SerialHeap::mem_allocate_work(size_t size, bool is_tlab) { VMThread::execute(&op); if (op.gc_succeeded()) { result = op.result(); - - assert(result == nullptr || is_in_reserved(result), - "result not in heap"); - return result; + break; } // Give a warning if we seem to be looping forever. @@ -354,6 +336,9 @@ HeapWord* SerialHeap::mem_allocate_work(size_t size, bool is_tlab) { " size=%zu %s", try_count, size, is_tlab ? "(TLAB)" : ""); } } + + assert(result == nullptr || is_in_reserved(result), "postcondition"); + return result; } HeapWord* SerialHeap::attempt_allocation(size_t size, diff --git a/src/hotspot/share/gc/serial/serialHeap.hpp b/src/hotspot/share/gc/serial/serialHeap.hpp index e86eac58515..b89edb33307 100644 --- a/src/hotspot/share/gc/serial/serialHeap.hpp +++ b/src/hotspot/share/gc/serial/serialHeap.hpp @@ -229,10 +229,6 @@ public: void save_marks(); private: - // Return true if an allocation should be attempted in the older generation - // if it fails in the younger generation. Return false, otherwise. - bool should_try_older_generation_allocation(size_t word_size) const; - // Try to allocate space by expanding the heap. HeapWord* expand_heap_and_allocate(size_t size, bool is_tlab); diff --git a/src/hotspot/share/gc/serial/tenuredGeneration.hpp b/src/hotspot/share/gc/serial/tenuredGeneration.hpp index 892fc5bb86c..1225005aff4 100644 --- a/src/hotspot/share/gc/serial/tenuredGeneration.hpp +++ b/src/hotspot/share/gc/serial/tenuredGeneration.hpp @@ -126,6 +126,8 @@ public: // Allocate and returns a block of the requested size, or returns "null". // Assumes the caller has done any necessary locking. inline HeapWord* allocate(size_t word_size); + // Multi-threaded version. + inline HeapWord* par_allocate(size_t word_size); // Expand the old-gen then invoke allocate above. HeapWord* expand_and_allocate(size_t size); diff --git a/src/hotspot/share/gc/serial/tenuredGeneration.inline.hpp b/src/hotspot/share/gc/serial/tenuredGeneration.inline.hpp index d88b2566299..1c82566bdf4 100644 --- a/src/hotspot/share/gc/serial/tenuredGeneration.inline.hpp +++ b/src/hotspot/share/gc/serial/tenuredGeneration.inline.hpp @@ -57,4 +57,12 @@ HeapWord* TenuredGeneration::allocate(size_t word_size) { return res; } +HeapWord* TenuredGeneration::par_allocate(size_t word_size) { + HeapWord* res = _the_space->par_allocate(word_size); + if (res != nullptr) { + _bts->update_for_block(res, res + word_size); + } + return res; +} + #endif // SHARE_GC_SERIAL_TENUREDGENERATION_INLINE_HPP From b0f98df75aee1e94a8c4b3eb8d0b1f4e715011ae Mon Sep 17 00:00:00 2001 From: Phil Race Date: Thu, 14 Aug 2025 15:20:47 +0000 Subject: [PATCH 128/580] 8365416: java.desktop no longer needs preview feature access Reviewed-by: alanb, jpai --- src/java.base/share/classes/module-info.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/java.base/share/classes/module-info.java b/src/java.base/share/classes/module-info.java index 43d148a3428..92ed53d2176 100644 --- a/src/java.base/share/classes/module-info.java +++ b/src/java.base/share/classes/module-info.java @@ -154,7 +154,6 @@ module java.base { // module declaration be annotated with jdk.internal.javac.ParticipatesInPreview exports jdk.internal.javac to java.compiler, - java.desktop, // for ScopedValue jdk.compiler, jdk.incubator.vector, // participates in preview features jdk.jartool, // participates in preview features From 26ccb3cef17a7a2a4b09af1e1e29b96d54a418aa Mon Sep 17 00:00:00 2001 From: Igor Veresov Date: Thu, 14 Aug 2025 16:59:05 +0000 Subject: [PATCH 129/580] 8362530: VM crash with -XX:+PrintTieredEvents when collecting AOT profiling Reviewed-by: chagedorn, kvn --- .../share/compiler/compilationPolicy.cpp | 117 ++++++++++-------- .../share/compiler/compilationPolicy.hpp | 5 +- .../runtime/cds/appcds/aotFlags/AOTFlags.java | 10 ++ 3 files changed, 75 insertions(+), 57 deletions(-) diff --git a/src/hotspot/share/compiler/compilationPolicy.cpp b/src/hotspot/share/compiler/compilationPolicy.cpp index fd1357398f5..9c49d941bbc 100644 --- a/src/hotspot/share/compiler/compilationPolicy.cpp +++ b/src/hotspot/share/compiler/compilationPolicy.cpp @@ -404,7 +404,7 @@ double CompilationPolicy::threshold_scale(CompLevel level, int feedback_k) { return 1; } -void CompilationPolicy::print_counters(const char* prefix, Method* m) { +void CompilationPolicy::print_counters_on(outputStream* st, const char* prefix, Method* m) { int invocation_count = m->invocation_count(); int backedge_count = m->backedge_count(); MethodData* mdh = m->method_data(); @@ -416,133 +416,140 @@ void CompilationPolicy::print_counters(const char* prefix, Method* m) { mdo_invocations_start = mdh->invocation_count_start(); mdo_backedges_start = mdh->backedge_count_start(); } - tty->print(" %stotal=%d,%d %smdo=%d(%d),%d(%d)", prefix, - invocation_count, backedge_count, prefix, - mdo_invocations, mdo_invocations_start, - mdo_backedges, mdo_backedges_start); - tty->print(" %smax levels=%d,%d", prefix, - m->highest_comp_level(), m->highest_osr_comp_level()); + st->print(" %stotal=%d,%d %smdo=%d(%d),%d(%d)", prefix, + invocation_count, backedge_count, prefix, + mdo_invocations, mdo_invocations_start, + mdo_backedges, mdo_backedges_start); + st->print(" %smax levels=%d,%d", prefix, m->highest_comp_level(), m->highest_osr_comp_level()); } -void CompilationPolicy::print_training_data(const char* prefix, Method* method) { +void CompilationPolicy::print_training_data_on(outputStream* st, const char* prefix, Method* method) { methodHandle m(Thread::current(), method); - tty->print(" %smtd: ", prefix); + st->print(" %smtd: ", prefix); MethodTrainingData* mtd = MethodTrainingData::find(m); if (mtd == nullptr) { - tty->print("null"); + st->print("null"); } else { MethodData* md = mtd->final_profile(); - tty->print("mdo="); + st->print("mdo="); if (md == nullptr) { - tty->print("null"); + st->print("null"); } else { int mdo_invocations = md->invocation_count(); int mdo_backedges = md->backedge_count(); int mdo_invocations_start = md->invocation_count_start(); int mdo_backedges_start = md->backedge_count_start(); - tty->print("%d(%d), %d(%d)", mdo_invocations, mdo_invocations_start, mdo_backedges, mdo_backedges_start); + st->print("%d(%d), %d(%d)", mdo_invocations, mdo_invocations_start, mdo_backedges, mdo_backedges_start); } CompileTrainingData* ctd = mtd->last_toplevel_compile(CompLevel_full_optimization); - tty->print(", deps="); + st->print(", deps="); if (ctd == nullptr) { - tty->print("null"); + st->print("null"); } else { - tty->print("%d", ctd->init_deps_left()); + st->print("%d", ctd->init_deps_left()); } } } // Print an event. -void CompilationPolicy::print_event(EventType type, Method* m, Method* im, int bci, CompLevel level) { +void CompilationPolicy::print_event_on(outputStream *st, EventType type, Method* m, Method* im, int bci, CompLevel level) { bool inlinee_event = m != im; - ttyLocker tty_lock; - tty->print("%lf: [", os::elapsedTime()); + st->print("%lf: [", os::elapsedTime()); switch(type) { case CALL: - tty->print("call"); + st->print("call"); break; case LOOP: - tty->print("loop"); + st->print("loop"); break; case COMPILE: - tty->print("compile"); + st->print("compile"); break; case FORCE_COMPILE: - tty->print("force-compile"); + st->print("force-compile"); break; case REMOVE_FROM_QUEUE: - tty->print("remove-from-queue"); + st->print("remove-from-queue"); break; case UPDATE_IN_QUEUE: - tty->print("update-in-queue"); + st->print("update-in-queue"); break; case REPROFILE: - tty->print("reprofile"); + st->print("reprofile"); break; case MAKE_NOT_ENTRANT: - tty->print("make-not-entrant"); + st->print("make-not-entrant"); break; default: - tty->print("unknown"); + st->print("unknown"); } - tty->print(" level=%d ", level); + st->print(" level=%d ", level); ResourceMark rm; char *method_name = m->name_and_sig_as_C_string(); - tty->print("[%s", method_name); + st->print("[%s", method_name); if (inlinee_event) { char *inlinee_name = im->name_and_sig_as_C_string(); - tty->print(" [%s]] ", inlinee_name); + st->print(" [%s]] ", inlinee_name); } - else tty->print("] "); - tty->print("@%d queues=%d,%d", bci, CompileBroker::queue_size(CompLevel_full_profile), - CompileBroker::queue_size(CompLevel_full_optimization)); + else st->print("] "); + st->print("@%d queues=%d,%d", bci, CompileBroker::queue_size(CompLevel_full_profile), + CompileBroker::queue_size(CompLevel_full_optimization)); - tty->print(" rate="); - if (m->prev_time() == 0) tty->print("n/a"); - else tty->print("%f", m->rate()); + st->print(" rate="); + if (m->prev_time() == 0) st->print("n/a"); + else st->print("%f", m->rate()); - tty->print(" k=%.2lf,%.2lf", threshold_scale(CompLevel_full_profile, Tier3LoadFeedback), - threshold_scale(CompLevel_full_optimization, Tier4LoadFeedback)); + st->print(" k=%.2lf,%.2lf", threshold_scale(CompLevel_full_profile, Tier3LoadFeedback), + threshold_scale(CompLevel_full_optimization, Tier4LoadFeedback)); if (type != COMPILE) { - print_counters("", m); + print_counters_on(st, "", m); if (inlinee_event) { - print_counters("inlinee ", im); + print_counters_on(st, "inlinee ", im); } - tty->print(" compilable="); + st->print(" compilable="); bool need_comma = false; if (!m->is_not_compilable(CompLevel_full_profile)) { - tty->print("c1"); + st->print("c1"); need_comma = true; } if (!m->is_not_osr_compilable(CompLevel_full_profile)) { - if (need_comma) tty->print(","); - tty->print("c1-osr"); + if (need_comma) st->print(","); + st->print("c1-osr"); need_comma = true; } if (!m->is_not_compilable(CompLevel_full_optimization)) { - if (need_comma) tty->print(","); - tty->print("c2"); + if (need_comma) st->print(","); + st->print("c2"); need_comma = true; } if (!m->is_not_osr_compilable(CompLevel_full_optimization)) { - if (need_comma) tty->print(","); - tty->print("c2-osr"); + if (need_comma) st->print(","); + st->print("c2-osr"); } - tty->print(" status="); + st->print(" status="); if (m->queued_for_compilation()) { - tty->print("in-queue"); - } else tty->print("idle"); - print_training_data("", m); + st->print("in-queue"); + } else st->print("idle"); + + print_training_data_on(st, "", m); if (inlinee_event) { - print_training_data("inlinee ", im); + print_training_data_on(st, "inlinee ", im); } } - tty->print_cr("]"); + st->print_cr("]"); + +} + +void CompilationPolicy::print_event(EventType type, Method* m, Method* im, int bci, CompLevel level) { + stringStream s; + print_event_on(&s, type, m, im, bci, level); + ResourceMark rm; + tty->print("%s", s.as_string()); } void CompilationPolicy::initialize() { diff --git a/src/hotspot/share/compiler/compilationPolicy.hpp b/src/hotspot/share/compiler/compilationPolicy.hpp index 75374a2f830..f4a7c4c249b 100644 --- a/src/hotspot/share/compiler/compilationPolicy.hpp +++ b/src/hotspot/share/compiler/compilationPolicy.hpp @@ -287,8 +287,8 @@ class CompilationPolicy : AllStatic { // loop_event checks if a method should be OSR compiled at a different // level. static CompLevel loop_event(const methodHandle& method, CompLevel cur_level, JavaThread* THREAD); - static void print_counters(const char* prefix, Method* m); - static void print_training_data(const char* prefix, Method* method); + static void print_counters_on(outputStream* st, const char* prefix, Method* m); + static void print_training_data_on(outputStream* st, const char* prefix, Method* method); // Has a method been long around? // We don't remove old methods from the compile queue even if they have // very low activity (see select_task()). @@ -318,6 +318,7 @@ class CompilationPolicy : AllStatic { static void set_c2_count(int x) { _c2_count = x; } enum EventType { CALL, LOOP, COMPILE, FORCE_COMPILE, FORCE_RECOMPILE, REMOVE_FROM_QUEUE, UPDATE_IN_QUEUE, REPROFILE, MAKE_NOT_ENTRANT }; + static void print_event_on(outputStream *st, EventType type, Method* m, Method* im, int bci, CompLevel level); static void print_event(EventType type, Method* m, Method* im, int bci, CompLevel level); // Check if the method can be compiled, change level if necessary static void compile(const methodHandle& mh, int bci, CompLevel level, TRAPS); diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotFlags/AOTFlags.java b/test/hotspot/jtreg/runtime/cds/appcds/aotFlags/AOTFlags.java index 0d03313bec2..6db81351e6b 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotFlags/AOTFlags.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotFlags/AOTFlags.java @@ -257,6 +257,16 @@ public class AOTFlags { out.shouldContain("AOTCache creation is complete: hello.aot"); out.shouldMatch("Picked up JAVA_TOOL_OPTIONS:.* -Dmy.prop=My' 'string' '-Xshare:off' 'here"); out.shouldHaveExitValue(0); + + // Training run with -XX:+PrintTieredEvents (see JDK-8362530). + printTestCase("Training run with -XX:+PrintTieredEvents"); + pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-XX:AOTMode=record", + "-XX:+PrintTieredEvents", + "-XX:AOTConfiguration=" + aotConfigFile, + "-cp", appJar, helloClass); + out = CDSTestUtils.executeAndLog(pb, "train-with-tiered-events"); + out.shouldHaveExitValue(0); } static void negativeTests() throws Exception { From ba231052319676ece5105253b58efa4e906feab4 Mon Sep 17 00:00:00 2001 From: David Beaumont Date: Thu, 14 Aug 2025 17:02:05 +0000 Subject: [PATCH 130/580] 8365048: idea.sh script does not correctly detect/handle git worktrees Reviewed-by: shade, vyazici, erikj, mcimadamore, ihse --- bin/idea.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bin/idea.sh b/bin/idea.sh index eb37964f396..a184884b61a 100644 --- a/bin/idea.sh +++ b/bin/idea.sh @@ -125,7 +125,8 @@ if [ -d "$TOPLEVEL_DIR/.hg" ] ; then VCS_TYPE="hg4idea" fi -if [ -d "$TOPLEVEL_DIR/.git" ] ; then +# Git worktrees use a '.git' file rather than directory, so test both. +if [ -d "$TOPLEVEL_DIR/.git" -o -f "$TOPLEVEL_DIR/.git" ] ; then VCS_TYPE="Git" fi From dccca0fb7a892d31179b70fa861b8b3cdde54e84 Mon Sep 17 00:00:00 2001 From: William Kemper Date: Thu, 14 Aug 2025 19:58:54 +0000 Subject: [PATCH 131/580] 8365572: Shenandoah: Remove unused thread local _paced_time field Reviewed-by: shade --- .../gc/shenandoah/shenandoahThreadLocalData.cpp | 1 - .../gc/shenandoah/shenandoahThreadLocalData.hpp | 14 -------------- 2 files changed, 15 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.cpp b/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.cpp index dd500462d0f..ace5ab5e69a 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.cpp @@ -37,7 +37,6 @@ ShenandoahThreadLocalData::ShenandoahThreadLocalData() : _card_table(nullptr), _gclab(nullptr), _gclab_size(0), - _paced_time(0), _plab(nullptr), _plab_desired_size(0), _plab_actual_size(0), diff --git a/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.hpp b/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.hpp index 098e20a72ec..37d935d1f78 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.hpp @@ -58,8 +58,6 @@ private: PLAB* _gclab; size_t _gclab_size; - double _paced_time; - // Thread-local allocation buffer only used in generational mode. // Used both by mutator threads and by GC worker threads // for evacuations within the old generation and @@ -237,18 +235,6 @@ public: return data(thread)->_plab_actual_size; } - static void add_paced_time(Thread* thread, double v) { - data(thread)->_paced_time += v; - } - - static double paced_time(Thread* thread) { - return data(thread)->_paced_time; - } - - static void reset_paced_time(Thread* thread) { - data(thread)->_paced_time = 0; - } - // Evacuation OOM handling static bool is_oom_during_evac(Thread* thread) { return data(thread)->_oom_during_evac; From c5cbcac828e1c7aa845cf16e68f6306ae49e050c Mon Sep 17 00:00:00 2001 From: Chen Liang Date: Thu, 14 Aug 2025 20:27:08 +0000 Subject: [PATCH 132/580] 8361730: The CodeBuilder.trying(BlockCodeBuilder,CatchBuilder) method generates corrupted bytecode in certain cases Reviewed-by: asotona --- .../share/classes/java/lang/classfile/CodeBuilder.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/java.base/share/classes/java/lang/classfile/CodeBuilder.java b/src/java.base/share/classes/java/lang/classfile/CodeBuilder.java index e640700c2e9..fe82d5d0bf0 100644 --- a/src/java.base/share/classes/java/lang/classfile/CodeBuilder.java +++ b/src/java.base/share/classes/java/lang/classfile/CodeBuilder.java @@ -175,6 +175,11 @@ public sealed interface CodeBuilder * A builder for blocks of code. Its {@link #startLabel()} and {@link * #endLabel()} do not enclose the entire method body, but from the start to * the end of the block. + *

+ * The location where a block of code merges back to its parent block, as + * represented by the {@link #breakLabel()}, is expected to be reachable, + * either from this block or the parent block. The built code may be + * malformed if there is no executable code at that location. * * @since 24 */ From 8c363b3e3e5c1273a5e9b3393ed09a31b0647a21 Mon Sep 17 00:00:00 2001 From: Chen Liang Date: Thu, 14 Aug 2025 21:41:14 +0000 Subject: [PATCH 133/580] 8364319: Move java.lang.constant.AsTypeMethodHandleDesc to jdk.internal Reviewed-by: redestad --- .../java/lang/constant/ConstantDescs.java | 4 ---- .../java/lang/constant/MethodHandleDesc.java | 3 ++- .../constant/AsTypeMethodHandleDesc.java | 19 ++++++++++++++----- 3 files changed, 16 insertions(+), 10 deletions(-) rename src/java.base/share/classes/{java/lang => jdk/internal}/constant/AsTypeMethodHandleDesc.java (75%) diff --git a/src/java.base/share/classes/java/lang/constant/ConstantDescs.java b/src/java.base/share/classes/java/lang/constant/ConstantDescs.java index c976342033f..0ef45ec34bb 100644 --- a/src/java.base/share/classes/java/lang/constant/ConstantDescs.java +++ b/src/java.base/share/classes/java/lang/constant/ConstantDescs.java @@ -44,7 +44,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import static java.lang.constant.DirectMethodHandleDesc.*; import static java.lang.constant.DirectMethodHandleDesc.Kind.STATIC; /** @@ -338,9 +337,6 @@ public final class ConstantDescs { */ public static final MethodTypeDesc MTD_void = MethodTypeDesc.of(CD_void); - static final DirectMethodHandleDesc MHD_METHODHANDLE_ASTYPE - = MethodHandleDesc.ofMethod(Kind.VIRTUAL, CD_MethodHandle, "asType", - MethodTypeDesc.of(CD_MethodHandle, CD_MethodType)); /** * Returns a {@link MethodHandleDesc} corresponding to a bootstrap method for * an {@code invokedynamic} callsite, which is a static method whose leading diff --git a/src/java.base/share/classes/java/lang/constant/MethodHandleDesc.java b/src/java.base/share/classes/java/lang/constant/MethodHandleDesc.java index 6a3541bf5e9..18786481fa6 100644 --- a/src/java.base/share/classes/java/lang/constant/MethodHandleDesc.java +++ b/src/java.base/share/classes/java/lang/constant/MethodHandleDesc.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 @@ -28,6 +28,7 @@ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; +import jdk.internal.constant.AsTypeMethodHandleDesc; import jdk.internal.constant.DirectMethodHandleDescImpl; import static java.lang.constant.ConstantDescs.CD_void; diff --git a/src/java.base/share/classes/java/lang/constant/AsTypeMethodHandleDesc.java b/src/java.base/share/classes/jdk/internal/constant/AsTypeMethodHandleDesc.java similarity index 75% rename from src/java.base/share/classes/java/lang/constant/AsTypeMethodHandleDesc.java rename to src/java.base/share/classes/jdk/internal/constant/AsTypeMethodHandleDesc.java index 32818a9bc00..8b250c0fff8 100644 --- a/src/java.base/share/classes/java/lang/constant/AsTypeMethodHandleDesc.java +++ b/src/java.base/share/classes/jdk/internal/constant/AsTypeMethodHandleDesc.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 @@ -22,8 +22,13 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package java.lang.constant; +package jdk.internal.constant; +import java.lang.constant.ConstantDescs; +import java.lang.constant.DirectMethodHandleDesc; +import java.lang.constant.DynamicConstantDesc; +import java.lang.constant.MethodHandleDesc; +import java.lang.constant.MethodTypeDesc; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; @@ -37,15 +42,19 @@ import static java.util.Objects.requireNonNull; * {@link MethodHandle} constant that performs a {@link MethodHandle#asType(MethodType)} * adaptation on another {@link MethodHandle}. */ -final class AsTypeMethodHandleDesc extends DynamicConstantDesc +public final class AsTypeMethodHandleDesc extends DynamicConstantDesc implements MethodHandleDesc { + private static final DirectMethodHandleDesc MHD_METHODHANDLE_ASTYPE + = MethodHandleDesc.ofMethod(DirectMethodHandleDesc.Kind.VIRTUAL, CD_MethodHandle, "asType", + MethodTypeDesc.of(CD_MethodHandle, ConstantDescs.CD_MethodType)); + private final MethodHandleDesc underlying; private final MethodTypeDesc type; - AsTypeMethodHandleDesc(MethodHandleDesc underlying, MethodTypeDesc type) { + public AsTypeMethodHandleDesc(MethodHandleDesc underlying, MethodTypeDesc type) { super(BSM_INVOKE, ConstantDescs.DEFAULT_NAME, CD_MethodHandle, - ConstantDescs.MHD_METHODHANDLE_ASTYPE, underlying, type); + MHD_METHODHANDLE_ASTYPE, underlying, type); this.underlying = requireNonNull(underlying); this.type = requireNonNull(type); } From a65f20022080e627da4782b9b643912a9dd69335 Mon Sep 17 00:00:00 2001 From: Vladimir Kozlov Date: Thu, 14 Aug 2025 23:59:34 +0000 Subject: [PATCH 134/580] 8365512: Replace -Xcomp with -Xmixed for AOT assembly phase Reviewed-by: shade --- src/hotspot/share/cds/cdsConfig.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/hotspot/share/cds/cdsConfig.cpp b/src/hotspot/share/cds/cdsConfig.cpp index 85c40df2606..51899490a12 100644 --- a/src/hotspot/share/cds/cdsConfig.cpp +++ b/src/hotspot/share/cds/cdsConfig.cpp @@ -643,8 +643,17 @@ bool CDSConfig::check_vm_args_consistency(bool patch_mod_javabase, bool mode_fla } if (is_dumping_static_archive()) { - if (is_dumping_preimage_static_archive() || is_dumping_final_static_archive()) { - // Don't tweak execution mode + if (is_dumping_preimage_static_archive()) { + // Don't tweak execution mode during AOT training run + } else if (is_dumping_final_static_archive()) { + if (Arguments::mode() == Arguments::_comp) { + // AOT assembly phase submits the non-blocking compilation requests + // for methods collected during training run, then waits for all compilations + // to complete. With -Xcomp, we block for each compilation request, which is + // counter-productive. Switching back to mixed mode improves testing time + // with AOT and -Xcomp. + Arguments::set_mode_flags(Arguments::_mixed); + } } else if (!mode_flag_cmd_line) { // By default, -Xshare:dump runs in interpreter-only mode, which is required for deterministic archive. // From 44b19c01acdfff07a4f017466be3f03fae6013c6 Mon Sep 17 00:00:00 2001 From: David Beaumont Date: Fri, 15 Aug 2025 02:53:42 +0000 Subject: [PATCH 135/580] 8365532: java/lang/module/ModuleReader/ModuleReaderTest.testImage fails Reviewed-by: alanb --- .../share/classes/jdk/internal/module/SystemModuleFinders.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/java.base/share/classes/jdk/internal/module/SystemModuleFinders.java b/src/java.base/share/classes/jdk/internal/module/SystemModuleFinders.java index 09ad3dc456d..39f433d4041 100644 --- a/src/java.base/share/classes/jdk/internal/module/SystemModuleFinders.java +++ b/src/java.base/share/classes/jdk/internal/module/SystemModuleFinders.java @@ -430,6 +430,7 @@ public final class SystemModuleFinders { @Override public Optional find(String name) throws IOException { + Objects.requireNonNull(name); String resourcePath = "/" + module + "/" + name; if (containsResource(resourcePath)) { URI u = JNUA.create("jrt", resourcePath); From 6fb6f3d39b321e2a1c1fa2cef2c19222a6dcf7b9 Mon Sep 17 00:00:00 2001 From: Chen Liang Date: Fri, 15 Aug 2025 04:25:37 +0000 Subject: [PATCH 136/580] 8361638: java.lang.classfile.CodeBuilder.CatchBuilder should not throw IllegalArgumentException for representable exception handlers Reviewed-by: asotona --- .../java/lang/classfile/CodeBuilder.java | 28 +++++++++++---- .../classfile/instruction/ExceptionCatch.java | 6 ++-- .../classfile/impl/CatchBuilderImpl.java | 35 ++++++++++--------- .../jdk/classfile/BuilderTryCatchTest.java | 34 ++++++++++++++---- 4 files changed, 71 insertions(+), 32 deletions(-) diff --git a/src/java.base/share/classes/java/lang/classfile/CodeBuilder.java b/src/java.base/share/classes/java/lang/classfile/CodeBuilder.java index fe82d5d0bf0..c1070bbcc77 100644 --- a/src/java.base/share/classes/java/lang/classfile/CodeBuilder.java +++ b/src/java.base/share/classes/java/lang/classfile/CodeBuilder.java @@ -330,6 +330,11 @@ public sealed interface CodeBuilder /** * A builder to add catch blocks. + *

+ * The order of catch blocks is significant. When an exception is thrown + * by the try block, the first catch block whose exception type is {@linkplain + * Class#isAssignableFrom(Class) the same class as or a superclass of} the + * class of exception thrown is branched to (JVMS {@jvms 2.10}). * * @see #trying * @see ExceptionCatch @@ -348,13 +353,16 @@ public sealed interface CodeBuilder * If the type of exception is {@code null} then the catch block catches * all exceptions. * + * @apiNote + * If the type of exception to catch is already handled by previous + * catch blocks, this block will never be executed. + * * @param exceptionType the type of exception to catch, may be {@code null} * @param catchHandler handler that receives a {@link BlockCodeBuilder} to * generate the body of the catch block * @return this builder - * @throws IllegalArgumentException if an existing catch block catches - * an exception of the given type or {@code exceptionType} - * represents a primitive type + * @throws IllegalArgumentException if {@code exceptionType} represents + * a primitive type * @see #catchingMulti * @see #catchingAll */ @@ -372,12 +380,16 @@ public sealed interface CodeBuilder * If list of exception types is empty then the catch block catches all * exceptions. * + * @apiNote + * If every type of exception to catch is already handled by previous + * catch blocks, this block will never be executed. + * * @param exceptionTypes the types of exception to catch * @param catchHandler handler that receives a {@link BlockCodeBuilder} * to generate the body of the catch block * @return this builder - * @throws IllegalArgumentException if an existing catch block catches - * one or more exceptions of the given types + * @throws IllegalArgumentException if any exception type represents a + * primitive type * @see #catching * @see #catchingAll */ @@ -392,10 +404,12 @@ public sealed interface CodeBuilder * The caught exception will be on top of the operand stack when the * catch block is entered. * + * @apiNote + * Since this block intercepts all exceptions, all subsequent catch + * blocks will never be executed. + * * @param catchAllHandler handler that receives a {@link BlockCodeBuilder} * to generate the body of the catch block - * @throws IllegalArgumentException if an existing catch block catches - * all exceptions * @see #catching * @see #catchingMulti */ diff --git a/src/java.base/share/classes/java/lang/classfile/instruction/ExceptionCatch.java b/src/java.base/share/classes/java/lang/classfile/instruction/ExceptionCatch.java index e924ca718e7..b0e5af2941e 100644 --- a/src/java.base/share/classes/java/lang/classfile/instruction/ExceptionCatch.java +++ b/src/java.base/share/classes/java/lang/classfile/instruction/ExceptionCatch.java @@ -39,8 +39,10 @@ import jdk.internal.classfile.impl.AbstractPseudoInstruction; * A pseudo-instruction modeling an entry in the {@code exception_table} array * of a {@link CodeAttribute Code} attribute. Catch (JVMS {@jvms 3.12}) and * finally (JVMS {@jvms 3.14}) blocks in Java source code compile to exception - * table entries. Delivered as a {@link CodeElement} when traversing the - * contents of a {@link CodeModel}. + * table entries. The order of exception table entries is significant: when an + * exception is thrown in a method, execution branches to the first matching + * exception handler if such a handler exists (JVMS {@jvms 2.10}). Delivered as + * a {@link CodeElement} when traversing the contents of a {@link CodeModel}. *

* An exception table entry is composite: * {@snippet lang=text : diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/CatchBuilderImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/CatchBuilderImpl.java index d520753326e..89d43135551 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/CatchBuilderImpl.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/CatchBuilderImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,8 +27,9 @@ package jdk.internal.classfile.impl; import java.lang.classfile.CodeBuilder; import java.lang.classfile.Label; import java.lang.classfile.Opcode; +import java.lang.classfile.constantpool.ClassEntry; import java.lang.constant.ClassDesc; -import java.lang.constant.ConstantDesc; +import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Objects; @@ -39,14 +40,12 @@ public final class CatchBuilderImpl implements CodeBuilder.CatchBuilder { final CodeBuilder b; final BlockCodeBuilderImpl tryBlock; final Label tryCatchEnd; - final Set catchTypes; BlockCodeBuilderImpl catchBlock; public CatchBuilderImpl(CodeBuilder b, BlockCodeBuilderImpl tryBlock, Label tryCatchEnd) { this.b = b; this.tryBlock = tryBlock; this.tryCatchEnd = tryCatchEnd; - this.catchTypes = new HashSet<>(); } @Override @@ -59,18 +58,24 @@ public final class CatchBuilderImpl implements CodeBuilder.CatchBuilder { Objects.requireNonNull(exceptionTypes); Objects.requireNonNull(catchHandler); + // nullable list of CP entries - null means catching all (0) + List entries = new ArrayList<>(Math.max(1, exceptionTypes.size())); + if (exceptionTypes.isEmpty()) { + entries.add(null); + } else { + for (var exceptionType : exceptionTypes) { + var entry = b.constantPool().classEntry(exceptionType); // throws IAE + entries.add(entry); + } + } + // End validation + if (catchBlock == null) { if (tryBlock.reachable()) { b.branch(Opcode.GOTO, tryCatchEnd); } } - for (var exceptionType : exceptionTypes) { - if (!catchTypes.add(exceptionType)) { - throw new IllegalArgumentException("Existing catch block catches exception of type: " + exceptionType); - } - } - // Finish prior catch block if (catchBlock != null) { catchBlock.end(); @@ -82,13 +87,9 @@ public final class CatchBuilderImpl implements CodeBuilder.CatchBuilder { catchBlock = new BlockCodeBuilderImpl(b, tryCatchEnd); Label tryStart = tryBlock.startLabel(); Label tryEnd = tryBlock.endLabel(); - if (exceptionTypes.isEmpty()) { - catchBlock.exceptionCatchAll(tryStart, tryEnd, catchBlock.startLabel()); - } - else { - for (var exceptionType : exceptionTypes) { - catchBlock.exceptionCatch(tryStart, tryEnd, catchBlock.startLabel(), exceptionType); - } + for (var entry : entries) { + // This accepts null for catching all + catchBlock.exceptionCatch(tryStart, tryEnd, catchBlock.startLabel(), entry); } catchBlock.start(); catchHandler.accept(catchBlock); diff --git a/test/jdk/jdk/classfile/BuilderTryCatchTest.java b/test/jdk/jdk/classfile/BuilderTryCatchTest.java index 3de12559163..0a966e2a655 100644 --- a/test/jdk/jdk/classfile/BuilderTryCatchTest.java +++ b/test/jdk/jdk/classfile/BuilderTryCatchTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,6 +23,7 @@ /* * @test + * @bug 8361638 * @summary Testing ClassFile builder blocks. * @run junit BuilderTryCatchTest */ @@ -37,6 +38,7 @@ import java.lang.classfile.instruction.ExceptionCatch; import static java.lang.classfile.ClassFile.ACC_PUBLIC; import static java.lang.classfile.ClassFile.ACC_STATIC; +import static java.lang.constant.ConstantDescs.*; import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test; @@ -47,20 +49,40 @@ import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Collections; import java.util.List; import java.util.function.Consumer; -import static java.lang.constant.ConstantDescs.CD_Double; -import static java.lang.constant.ConstantDescs.CD_Integer; -import static java.lang.constant.ConstantDescs.CD_Object; -import static java.lang.constant.ConstantDescs.CD_String; - class BuilderTryCatchTest { static final ClassDesc CD_IOOBE = IndexOutOfBoundsException.class.describeConstable().get(); static final ClassDesc CD_NPE = NullPointerException.class.describeConstable().get(); static final MethodTypeDesc MTD_String = MethodType.methodType(String.class).describeConstable().get(); + @Test + void testExceptionalContracts() throws Throwable { + generateTryCatchMethod(catchBuilder -> { + Consumer handler = tb -> tb.pop().aconst_null().areturn(); + assertThrows(NullPointerException.class, () -> catchBuilder.catching(CD_NPE, null)); + assertThrows(NullPointerException.class, () -> catchBuilder.catchingMulti(null, handler)); + assertThrows(NullPointerException.class, () -> catchBuilder.catchingMulti(List.of(), null)); + assertThrows(NullPointerException.class, () -> catchBuilder.catchingMulti(Collections.singletonList(null), null)); + assertThrows(NullPointerException.class, () -> catchBuilder.catchingAll(null)); + catchBuilder.catchingMulti(List.of(CD_IOOBE, CD_NPE), tb -> { + tb.invokevirtual(CD_Object, "toString", MTD_String); + tb.astore(1); + }); + catchBuilder.catchingAll(tb -> tb.pop().loadConstant("all").areturn()); + assertThrows(IllegalArgumentException.class, () -> catchBuilder.catching(CD_int, handler)); + assertDoesNotThrow(() -> catchBuilder.catching(CD_NPE, handler)); + assertDoesNotThrow(() -> catchBuilder.catching(null, handler)); + assertDoesNotThrow(() -> catchBuilder.catchingMulti(List.of(), handler)); + assertDoesNotThrow(() -> catchBuilder.catchingMulti(List.of(CD_Exception, CD_IOOBE), handler)); + assertThrows(IllegalArgumentException.class, () -> catchBuilder.catchingMulti(List.of(CD_long, CD_Throwable), handler)); + assertDoesNotThrow(() -> catchBuilder.catchingAll(handler)); + }); + } + @Test void testTryCatchCatchAll() throws Throwable { byte[] bytes = generateTryCatchMethod(catchBuilder -> { From e3aeebec1798b9adbb02e11f285951d4275c52e8 Mon Sep 17 00:00:00 2001 From: Doug Simon Date: Fri, 15 Aug 2025 07:35:52 +0000 Subject: [PATCH 137/580] 8365468: EagerJVMCI should only apply to the CompilerBroker JVMCI runtime Reviewed-by: never --- .../share/classes/jdk/vm/ci/hotspot/HotSpotJVMCIRuntime.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotJVMCIRuntime.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotJVMCIRuntime.java index 492f20f1b59..c35f216e624 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotJVMCIRuntime.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotJVMCIRuntime.java @@ -186,7 +186,10 @@ public final class HotSpotJVMCIRuntime implements JVMCIRuntime { // Can only do eager initialization of the JVMCI compiler // once the singleton instance is available. if (result.config.getFlag("EagerJVMCI", Boolean.class)) { - result.getCompiler(); + // EagerJVMCI only applies to JVMCI when used by the CompileBroker. + if (result.getCompilerToVM().isCompilerThread()) { + result.getCompiler(); + } } } // Ensures JVMCIRuntime::_HotSpotJVMCIRuntime_instance is From fa2eb616482250dff6a3b667798aec37114005a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20H=C3=A4ssig?= Date: Fri, 15 Aug 2025 08:55:11 +0000 Subject: [PATCH 138/580] 8365491: VSCode IDE: add basic configuration for the Oracle Java extension Reviewed-by: ihse, jlahoda --- make/ide/vscode/hotspot/template-workspace.jsonc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/make/ide/vscode/hotspot/template-workspace.jsonc b/make/ide/vscode/hotspot/template-workspace.jsonc index 30533c7ce84..c582c48047d 100644 --- a/make/ide/vscode/hotspot/template-workspace.jsonc +++ b/make/ide/vscode/hotspot/template-workspace.jsonc @@ -12,12 +12,17 @@ ], "extensions": { "recommendations": [ + "oracle.oracle-java", // {{INDEXER_EXTENSIONS}} ] }, "settings": { // {{INDEXER_SETTINGS}} + // Java extension + "jdk.project.jdkhome": "{{OUTPUTDIR}}/jdk", + "jdk.java.onSave.organizeImports": false, // prevents unnecessary changes + // Additional conventions "files.associations": { "*.gmk": "makefile" From 5856dc34c82de9f840be1dc28a9917224971491f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Gr=C3=B6nlund?= Date: Fri, 15 Aug 2025 09:32:51 +0000 Subject: [PATCH 139/580] 8365199: Use a set instead of a list as the intermediary Klass* storage to reduce typeset processing Reviewed-by: egahlin --- .../checkpoint/objectSampleCheckpoint.cpp | 78 +++---- .../leakprofiler/sampling/objectSample.hpp | 13 +- .../recorder/checkpoint/jfrMetadataEvent.cpp | 5 +- .../recorder/checkpoint/jfrMetadataEvent.hpp | 4 +- .../recorder/checkpoint/types/jfrTypeSet.cpp | 13 +- .../checkpoint/types/jfrTypeSetUtils.cpp | 53 +++-- .../checkpoint/types/jfrTypeSetUtils.hpp | 74 +++---- .../recorder/service/jfrRecorderService.cpp | 7 +- .../share/jfr/support/jfrKlassUnloading.cpp | 15 +- src/hotspot/share/jfr/utilities/jfrSet.hpp | 197 +++++++++++++----- test/jdk/jdk/jfr/event/runtime/TestFlush.java | 2 - 11 files changed, 281 insertions(+), 180 deletions(-) diff --git a/src/hotspot/share/jfr/leakprofiler/checkpoint/objectSampleCheckpoint.cpp b/src/hotspot/share/jfr/leakprofiler/checkpoint/objectSampleCheckpoint.cpp index 3d87e86e9bd..300786daf05 100644 --- a/src/hotspot/share/jfr/leakprofiler/checkpoint/objectSampleCheckpoint.cpp +++ b/src/hotspot/share/jfr/leakprofiler/checkpoint/objectSampleCheckpoint.cpp @@ -48,14 +48,6 @@ #include "runtime/mutexLocker.hpp" #include "runtime/safepoint.hpp" -const unsigned int initial_size = 431; - -static JfrCHeapTraceIdSet* c_heap_allocate_set(int size = initial_size) { - return new JfrCHeapTraceIdSet(size); -} - -static JfrCHeapTraceIdSet* unloaded_thread_id_set = nullptr; - class ThreadIdExclusiveAccess : public StackObj { private: static Semaphore _mutex_semaphore; @@ -66,19 +58,13 @@ class ThreadIdExclusiveAccess : public StackObj { Semaphore ThreadIdExclusiveAccess::_mutex_semaphore(1); -static bool has_thread_exited(traceid tid) { - assert(tid != 0, "invariant"); - if (unloaded_thread_id_set == nullptr) { - return false; - } - ThreadIdExclusiveAccess lock; - return unloaded_thread_id_set->contains(tid); -} +static const unsigned initial_set_size = 512; +static JfrCHeapTraceIdSet* unloaded_thread_id_set = nullptr; static void add_to_unloaded_thread_set(traceid tid) { ThreadIdExclusiveAccess lock; if (unloaded_thread_id_set == nullptr) { - unloaded_thread_id_set = c_heap_allocate_set(); + unloaded_thread_id_set = new (mtTracing) JfrCHeapTraceIdSet(initial_set_size); } unloaded_thread_id_set->add(tid); } @@ -193,12 +179,6 @@ inline void BlobCache::on_unlink(BlobEntry* entry) const { assert(entry != nullptr, "invariant"); } -static JfrResourceAreaTraceIdSet* id_set = nullptr; - -static void prepare_for_resolution() { - id_set = new JfrResourceAreaTraceIdSet(initial_size); -} - static bool stack_trace_precondition(const ObjectSample* sample) { assert(sample != nullptr, "invariant"); return sample->has_stack_trace_id() && !sample->is_dead(); @@ -213,6 +193,8 @@ static void add_to_leakp_set(const ObjectSample* sample) { JfrTraceId::load_leakp(object->klass()); } +static JfrResourceAreaTraceIdSet* resolution_set = nullptr; + class StackTraceBlobInstaller { private: BlobCache _cache; @@ -220,8 +202,9 @@ class StackTraceBlobInstaller { const JfrStackTrace* resolve(const ObjectSample* sample) const; public: StackTraceBlobInstaller() : _cache(JfrOptionSet::old_object_queue_size()) { - prepare_for_resolution(); + resolution_set = new JfrResourceAreaTraceIdSet(initial_set_size); } + void sample_do(ObjectSample* sample) { if (stack_trace_precondition(sample)) { add_to_leakp_set(sample); @@ -314,8 +297,8 @@ static bool is_klass_unloaded(traceid klass_id) { static bool is_processed(traceid method_id) { assert(method_id != 0, "invariant"); - assert(id_set != nullptr, "invariant"); - return !id_set->add(method_id); + assert(resolution_set != nullptr, "invariant"); + return !resolution_set->add(method_id); } void ObjectSampleCheckpoint::add_to_leakp_set(const InstanceKlass* ik, traceid method_id) { @@ -356,7 +339,7 @@ static void write_type_set_blob(const ObjectSample* sample, JfrCheckpointWriter& static void write_thread_blob(const ObjectSample* sample, JfrCheckpointWriter& writer) { assert(sample->has_thread(), "invariant"); - if (sample->is_virtual_thread() || has_thread_exited(sample->thread_id())) { + if (sample->is_virtual_thread() || sample->thread_exited()) { write_blob(sample->thread(), writer); } } @@ -372,13 +355,13 @@ static inline bool should_write(const JfrStackTrace* stacktrace) { class LeakProfilerStackTraceWriter { private: JfrCheckpointWriter& _writer; - int _count; + unsigned _count; public: LeakProfilerStackTraceWriter(JfrCheckpointWriter& writer) : _writer(writer), _count(0) { assert(_stacktrace_id_set != nullptr, "invariant"); } - int count() const { return _count; } + unsigned count() const { return _count; } void operator()(const JfrStackTrace* stacktrace) { if (should_write(stacktrace)) { @@ -394,12 +377,10 @@ void ObjectSampleCheckpoint::write_stacktraces(Thread* thread) { JfrCheckpointWriter writer(thread); writer.write_type(TYPE_STACKTRACE); - const int64_t count_offset = writer.reserve(sizeof(u4)); // Don't know how many yet - + writer.write_count(_stacktrace_id_set->size()); LeakProfilerStackTraceWriter lpstw(writer); JfrStackTraceRepository::iterate_leakprofiler(lpstw); assert(lpstw.count() == _stacktrace_id_set->size(), "invariant"); - writer.write_count(lpstw.count(), count_offset); } static void write_stacktrace_blob(const ObjectSample* sample, JfrCheckpointWriter& writer) { @@ -422,6 +403,16 @@ static void write_blobs(const ObjectSample* sample, JfrCheckpointWriter& writer) write_type_set_blob(sample, writer); } +static void check_if_thread_exited(const ObjectSample* sample) { + assert(sample != nullptr, "invariant"); + if (sample->thread_exited() || unloaded_thread_id_set == nullptr) { + return; + } + if (unloaded_thread_id_set->contains(sample->thread_id())) { + sample->set_thread_exited(); + } +} + class BlobWriter { private: const ObjectSampler* _sampler; @@ -431,23 +422,36 @@ class BlobWriter { BlobWriter(const ObjectSampler* sampler, JfrCheckpointWriter& writer, jlong last_sweep) : _sampler(sampler), _writer(writer), _last_sweep(last_sweep) {} void sample_do(ObjectSample* sample) { + check_if_thread_exited(sample); if (sample->is_alive_and_older_than(_last_sweep)) { write_blobs(sample, _writer); } } }; +static void delete_unloaded_thread_id_set() { + if (unloaded_thread_id_set != nullptr) { + delete unloaded_thread_id_set; + unloaded_thread_id_set = nullptr; + } +} + static void write_sample_blobs(const ObjectSampler* sampler, bool emit_all, Thread* thread) { // sample set is predicated on time of last sweep const jlong last_sweep = emit_all ? max_jlong : ObjectSampler::last_sweep(); JfrCheckpointWriter writer(thread, false); BlobWriter cbw(sampler, writer, last_sweep); + ThreadIdExclusiveAccess lock; iterate_samples(cbw, true); + delete_unloaded_thread_id_set(); } -static inline unsigned int set_size() { - const unsigned int queue_size = static_cast(JfrOptionSet::old_object_queue_size()); - return queue_size > initial_size ? queue_size : initial_size; +static inline unsigned stacktrace_id_set_size() { + unsigned queue_size = static_cast(JfrOptionSet::old_object_queue_size()); + if (!is_power_of_2(queue_size)) { + queue_size = next_power_of_2(queue_size); + } + return queue_size > initial_set_size ? queue_size : initial_set_size; } void ObjectSampleCheckpoint::write(const ObjectSampler* sampler, EdgeStore* edge_store, bool emit_all, Thread* thread) { @@ -456,7 +460,9 @@ void ObjectSampleCheckpoint::write(const ObjectSampler* sampler, EdgeStore* edge assert(thread != nullptr, "invariant"); { ResourceMark rm(thread); - _stacktrace_id_set = new JfrResourceAreaTraceIdSet(set_size()); + const unsigned stacktrace_set_size = stacktrace_id_set_size(); + assert(is_power_of_2(stacktrace_set_size), "invariant"); + _stacktrace_id_set = new JfrResourceAreaTraceIdSet(stacktrace_set_size); write_sample_blobs(sampler, emit_all, thread); if (_stacktrace_id_set->is_nonempty()) { write_stacktraces(thread); diff --git a/src/hotspot/share/jfr/leakprofiler/sampling/objectSample.hpp b/src/hotspot/share/jfr/leakprofiler/sampling/objectSample.hpp index 214de827d03..66ed9145c81 100644 --- a/src/hotspot/share/jfr/leakprofiler/sampling/objectSample.hpp +++ b/src/hotspot/share/jfr/leakprofiler/sampling/objectSample.hpp @@ -59,6 +59,7 @@ class ObjectSample : public JfrCHeapObj { size_t _heap_used_at_last_gc; int _index; bool _virtual_thread; + mutable bool _thread_exited; void release_references() { _stacktrace.~JfrBlobHandle(); @@ -82,7 +83,8 @@ class ObjectSample : public JfrCHeapObj { _allocated(0), _heap_used_at_last_gc(0), _index(0), - _virtual_thread(false) {} + _virtual_thread(false), + _thread_exited(false) {} ObjectSample* next() const { return _next; @@ -225,6 +227,15 @@ class ObjectSample : public JfrCHeapObj { _virtual_thread = true; } + bool thread_exited() const { + return _thread_exited; + } + + void set_thread_exited() const { + assert(!_thread_exited, "invariant"); + _thread_exited = true; + } + const JfrBlobHandle& type_set() const { return _type_set; } diff --git a/src/hotspot/share/jfr/recorder/checkpoint/jfrMetadataEvent.cpp b/src/hotspot/share/jfr/recorder/checkpoint/jfrMetadataEvent.cpp index bf27fa59031..20e9c1e6798 100644 --- a/src/hotspot/share/jfr/recorder/checkpoint/jfrMetadataEvent.cpp +++ b/src/hotspot/share/jfr/recorder/checkpoint/jfrMetadataEvent.cpp @@ -64,11 +64,11 @@ static void write_metadata_blob(JfrChunkWriter& chunkwriter, JavaThread* thread) chunkwriter.write_unbuffered(data_address, length); } -void JfrMetadataEvent::write(JfrChunkWriter& chunkwriter) { +size_t JfrMetadataEvent::write(JfrChunkWriter& chunkwriter) { assert(chunkwriter.is_valid(), "invariant"); check_internal_types(); if (last_metadata_id == metadata_id && chunkwriter.has_metadata()) { - return; + return 0; } JavaThread* const jt = JavaThread::current(); DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_native(jt)); @@ -87,6 +87,7 @@ void JfrMetadataEvent::write(JfrChunkWriter& chunkwriter) { chunkwriter.write_padded_at_offset((u4)size_written, metadata_offset); chunkwriter.set_last_metadata_offset(metadata_offset); last_metadata_id = metadata_id; + return 1; } void JfrMetadataEvent::update(jbyteArray metadata) { diff --git a/src/hotspot/share/jfr/recorder/checkpoint/jfrMetadataEvent.hpp b/src/hotspot/share/jfr/recorder/checkpoint/jfrMetadataEvent.hpp index abadbfb0b13..1b5bd45c946 100644 --- a/src/hotspot/share/jfr/recorder/checkpoint/jfrMetadataEvent.hpp +++ b/src/hotspot/share/jfr/recorder/checkpoint/jfrMetadataEvent.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -36,7 +36,7 @@ class JfrChunkWriter; // class JfrMetadataEvent : AllStatic { public: - static void write(JfrChunkWriter& writer); + static size_t write(JfrChunkWriter& writer); static void update(jbyteArray metadata); }; diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSet.cpp b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSet.cpp index 69f002138ec..375ab4d04e9 100644 --- a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSet.cpp +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSet.cpp @@ -1048,19 +1048,15 @@ class MethodIteratorHost { private: MethodCallback _method_cb; KlassCallback _klass_cb; - KlassUsedPredicate _klass_used_predicate; - MethodUsedPredicate _method_used_predicate; MethodFlagPredicate _method_flag_predicate; public: MethodIteratorHost(JfrCheckpointWriter* writer) : _method_cb(writer, unloading(), false), _klass_cb(writer, unloading(), false), - _klass_used_predicate(current_epoch()), - _method_used_predicate(current_epoch()), _method_flag_predicate(current_epoch()) {} bool operator()(KlassPtr klass) { - if (_method_used_predicate(klass)) { + if (klass->is_instance_klass()) { const InstanceKlass* ik = InstanceKlass::cast(klass); while (ik != nullptr) { const int len = ik->methods()->length(); @@ -1075,7 +1071,7 @@ class MethodIteratorHost { ik = ik->previous_versions(); } } - return _klass_used_predicate(klass) ? _klass_cb(klass) : true; + return _klass_cb(klass); } int count() const { return _method_cb.count(); } @@ -1280,10 +1276,11 @@ static void setup(JfrCheckpointWriter* writer, JfrCheckpointWriter* leakp_writer _class_unload = class_unload; _flushpoint = flushpoint; if (_artifacts == nullptr) { - _artifacts = new JfrArtifactSet(class_unload); + _artifacts = new JfrArtifactSet(class_unload, previous_epoch()); } else { - _artifacts->initialize(class_unload); + _artifacts->initialize(class_unload, previous_epoch()); } + assert(current_epoch() || _leakp_writer != nullptr, "invariant"); assert(_artifacts != nullptr, "invariant"); assert(!_artifacts->has_klass_entries(), "invariant"); } diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSetUtils.cpp b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSetUtils.cpp index d213ecd7d75..c60556927ad 100644 --- a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSetUtils.cpp +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSetUtils.cpp @@ -29,18 +29,23 @@ #include "oops/oop.inline.hpp" #include "oops/symbol.hpp" -JfrArtifactSet::JfrArtifactSet(bool class_unload) : _symbol_table(nullptr), - _klass_list(nullptr), - _total_count(0), - _class_unload(class_unload) { - initialize(class_unload); - assert(_klass_list != nullptr, "invariant"); +JfrArtifactSet::JfrArtifactSet(bool class_unload, bool previous_epoch) : _symbol_table(nullptr), + _klass_set(nullptr), + _klass_loader_set(nullptr), + _klass_loader_leakp_set(nullptr), + _total_count(0), + _class_unload(class_unload) { + initialize(class_unload, previous_epoch); + assert(!previous_epoch || _klass_loader_leakp_set != nullptr, "invariant"); + assert(_klass_loader_set != nullptr, "invariant"); + assert(_klass_set != nullptr, "invariant"); } -static const size_t initial_klass_list_size = 4096; -const int initial_klass_loader_set_size = 64; +static unsigned initial_klass_set_size = 4096; +static unsigned initial_klass_loader_set_size = 64; +static unsigned initial_klass_loader_leakp_set_size = 64; -void JfrArtifactSet::initialize(bool class_unload) { +void JfrArtifactSet::initialize(bool class_unload, bool previous_epoch) { _class_unload = class_unload; if (_symbol_table == nullptr) { _symbol_table = JfrSymbolTable::create(); @@ -50,9 +55,11 @@ void JfrArtifactSet::initialize(bool class_unload) { _symbol_table->set_class_unload(class_unload); _total_count = 0; // Resource allocations. Keep in this allocation order. - _klass_loader_leakp_set = new GrowableArray(initial_klass_loader_set_size); - _klass_loader_set = new GrowableArray(initial_klass_loader_set_size); - _klass_list = new GrowableArray(initial_klass_list_size); + if (previous_epoch) { + _klass_loader_leakp_set = new JfrKlassSet(initial_klass_loader_leakp_set_size); + } + _klass_loader_set = new JfrKlassSet(initial_klass_loader_set_size); + _klass_set = new JfrKlassSet(initial_klass_set_size); } void JfrArtifactSet::clear() { @@ -93,17 +100,12 @@ traceid JfrArtifactSet::mark(uintptr_t hash, const char* const str, bool leakp) } bool JfrArtifactSet::has_klass_entries() const { - return _klass_list->is_nonempty(); + return _klass_set->is_nonempty(); } - -int JfrArtifactSet::entries() const { - return _klass_list->length(); -} - -static inline bool not_in_set(GrowableArray* set, const Klass* k) { +static inline bool not_in_set(JfrArtifactSet::JfrKlassSet* set, const Klass* k) { assert(set != nullptr, "invariant"); assert(k != nullptr, "invariant"); - return !JfrMutablePredicate::test(set, k); + return set->add(k); } bool JfrArtifactSet::should_do_cld_klass(const Klass* k, bool leakp) { @@ -116,16 +118,21 @@ bool JfrArtifactSet::should_do_cld_klass(const Klass* k, bool leakp) { void JfrArtifactSet::register_klass(const Klass* k) { assert(k != nullptr, "invariant"); assert(IS_SERIALIZED(k), "invariant"); - assert(_klass_list != nullptr, "invariant"); - _klass_list->append(k); + assert(_klass_set != nullptr, "invariant"); + _klass_set->add(k); } size_t JfrArtifactSet::total_count() const { + assert(_klass_set != nullptr, "invariant"); + initial_klass_set_size = MAX2(initial_klass_set_size, _klass_set->table_size()); + assert(_klass_loader_set != nullptr, "invariant"); + initial_klass_loader_set_size = MAX2(initial_klass_loader_set_size, _klass_loader_set->table_size()); return _total_count; } void JfrArtifactSet::increment_checkpoint_id() { assert(_symbol_table != nullptr, "invariant"); _symbol_table->increment_checkpoint_id(); + assert(_klass_loader_leakp_set != nullptr, "invariant"); + initial_klass_loader_leakp_set_size = MAX2(initial_klass_loader_leakp_set_size, _klass_loader_leakp_set->table_size()); } - diff --git a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSetUtils.hpp b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSetUtils.hpp index 657aee9dc53..74200aef1f1 100644 --- a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSetUtils.hpp +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSetUtils.hpp @@ -28,12 +28,10 @@ #include "jfr/recorder/checkpoint/types/traceid/jfrTraceId.inline.hpp" #include "jfr/support/jfrSymbolTable.hpp" #include "jfr/utilities/jfrAllocation.hpp" +#include "jfr/utilities/jfrSet.hpp" #include "oops/klass.hpp" #include "oops/method.hpp" -template -class GrowableArray; - // Composite callback/functor building block template class CompositeFunctor { @@ -135,27 +133,6 @@ class SymbolPredicate { } }; -class KlassUsedPredicate { - bool _current_epoch; - public: - KlassUsedPredicate(bool current_epoch) : _current_epoch(current_epoch) {} - bool operator()(const Klass* klass) { - return _current_epoch ? USED_THIS_EPOCH(klass) : USED_PREVIOUS_EPOCH(klass); - } -}; - -class MethodUsedPredicate { - bool _current_epoch; -public: - MethodUsedPredicate(bool current_epoch) : _current_epoch(current_epoch) {} - bool operator()(const Klass* klass) { - if (!klass->is_instance_klass()) { - return false; - } - return _current_epoch ? USED_THIS_EPOCH(klass) : USED_PREVIOUS_EPOCH(klass); - } -}; - template class MethodFlagPredicate { bool _current_epoch; @@ -203,20 +180,46 @@ class LeakPredicate { * in the respective VM subsystems. */ class JfrArtifactSet : public JfrCHeapObj { + public: + class JfrArtifactSetConfig : public AllStatic { + public: + typedef const Klass* KEY_TYPE; + + constexpr static AnyObj::allocation_type alloc_type() { + return AnyObj::RESOURCE_AREA; + } + + constexpr static MemTag memory_tag() { + return mtInternal; + } + + // Knuth multiplicative hashing. + static uint32_t hash(const KEY_TYPE& k) { + const uint32_t v = static_cast(JfrTraceId::load_raw(k)); + return v * UINT32_C(2654435761); + } + + static bool cmp(const KEY_TYPE& lhs, const KEY_TYPE& rhs) { + return lhs == rhs; + } + }; + + typedef JfrSet JfrKlassSet; + private: JfrSymbolTable* _symbol_table; - GrowableArray* _klass_list; - GrowableArray* _klass_loader_set; - GrowableArray* _klass_loader_leakp_set; + JfrKlassSet* _klass_set; + JfrKlassSet* _klass_loader_set; + JfrKlassSet* _klass_loader_leakp_set; size_t _total_count; bool _class_unload; public: - JfrArtifactSet(bool class_unload); + JfrArtifactSet(bool class_unload, bool previous_epoch); ~JfrArtifactSet(); // caller needs ResourceMark - void initialize(bool class_unload); + void initialize(bool class_unload, bool previous_epoch); void clear(); traceid mark(uintptr_t hash, const Symbol* sym, bool leakp); @@ -231,7 +234,6 @@ class JfrArtifactSet : public JfrCHeapObj { const JfrSymbolTable::StringEntry* map_string(uintptr_t hash) const; bool has_klass_entries() const; - int entries() const; size_t total_count() const; void register_klass(const Klass* k); bool should_do_cld_klass(const Klass* k, bool leakp); @@ -254,19 +256,17 @@ class JfrArtifactSet : public JfrCHeapObj { template void iterate_klasses(Functor& functor) const { - if (iterate(functor, _klass_list)) { + if (iterate(functor, _klass_set)) { iterate(functor, _klass_loader_set); } } private: template - bool iterate(Functor& functor, GrowableArray* list) const { - assert(list != nullptr, "invariant"); - for (int i = 0; i < list->length(); ++i) { - if (!functor(list->at(i))) { - return false; - } + bool iterate(Functor& functor, JfrKlassSet* set) const { + assert(set != nullptr, "invariant"); + if (set->is_nonempty()) { + set->iterate(functor); } return true; } diff --git a/src/hotspot/share/jfr/recorder/service/jfrRecorderService.cpp b/src/hotspot/share/jfr/recorder/service/jfrRecorderService.cpp index f0170bac460..7784148aee5 100644 --- a/src/hotspot/share/jfr/recorder/service/jfrRecorderService.cpp +++ b/src/hotspot/share/jfr/recorder/service/jfrRecorderService.cpp @@ -367,13 +367,14 @@ static u4 flush_typeset(JfrCheckpointManager& checkpoint_manager, JfrChunkWriter class MetadataEvent : public StackObj { private: JfrChunkWriter& _cw; + size_t _elements; public: - MetadataEvent(JfrChunkWriter& cw) : _cw(cw) {} + MetadataEvent(JfrChunkWriter& cw) : _cw(cw), _elements(0) {} bool process() { - JfrMetadataEvent::write(_cw); + _elements = JfrMetadataEvent::write(_cw); return true; } - size_t elements() const { return 1; } + size_t elements() const { return _elements; } }; typedef WriteContent WriteMetadata; diff --git a/src/hotspot/share/jfr/support/jfrKlassUnloading.cpp b/src/hotspot/share/jfr/support/jfrKlassUnloading.cpp index ce5de54ed16..d136eeab53a 100644 --- a/src/hotspot/share/jfr/support/jfrKlassUnloading.cpp +++ b/src/hotspot/share/jfr/support/jfrKlassUnloading.cpp @@ -30,10 +30,10 @@ #include "runtime/mutexLocker.hpp" #include "utilities/macros.hpp" -static const int initial_size = 1009; +static const int initial_size = 1024; static JfrCHeapTraceIdSet* c_heap_allocate_set(int size = initial_size) { - return new JfrCHeapTraceIdSet(size); + return new (mtTracing) JfrCHeapTraceIdSet(size); } // Track the set of unloaded klasses during a chunk / epoch. @@ -68,18 +68,9 @@ static JfrCHeapTraceIdSet* get_unload_set_previous_epoch() { return get_unload_set(JfrTraceIdEpoch::previous()); } -static bool is_nonempty_set(u1 epoch) { - if (epoch == 0) { - return _unload_set_epoch_0 != nullptr && _unload_set_epoch_0->is_nonempty(); - } - return _unload_set_epoch_1 != nullptr && _unload_set_epoch_1->is_nonempty(); -} - void JfrKlassUnloading::clear() { assert_locked_or_safepoint(ClassLoaderDataGraph_lock); - if (is_nonempty_set(JfrTraceIdEpoch::previous())) { - get_unload_set_previous_epoch()->clear(); - } + get_unload_set_previous_epoch()->clear(); } static void add_to_unloaded_klass_set(traceid klass_id) { diff --git a/src/hotspot/share/jfr/utilities/jfrSet.hpp b/src/hotspot/share/jfr/utilities/jfrSet.hpp index 40a5d73f370..b6458d3f9a4 100644 --- a/src/hotspot/share/jfr/utilities/jfrSet.hpp +++ b/src/hotspot/share/jfr/utilities/jfrSet.hpp @@ -25,15 +25,13 @@ #ifndef SHARE_JFR_UTILITIES_JFRSET_HPP #define SHARE_JFR_UTILITIES_JFRSET_HPP -#include "jfr/utilities/jfrAllocation.hpp" +#include "memory/allocation.hpp" #include "jfr/utilities/jfrTypes.hpp" -#include "utilities/resizableHashTable.hpp" -template -class ConfigTraceID : public AllStatic { +template +class JfrSetConfig : public AllStatic { public: - typedef AllocPolicy STORAGE; - typedef traceid TYPE; + typedef K KEY_TYPE; constexpr static AnyObj::allocation_type alloc_type() { return AllocType; @@ -44,80 +42,171 @@ class ConfigTraceID : public AllStatic { } // Knuth multiplicative hashing. - static uint32_t hash(const TYPE& id) { - const uint32_t v = static_cast(id); - return v * UINT32_C(2654435761); + static uint32_t hash(const KEY_TYPE& key) { + const uint32_t k = static_cast(key); + return k * UINT32_C(2654435761); } - static bool cmp(const TYPE& lhs, const TYPE& rhs) { + static bool cmp(const KEY_TYPE& lhs, const KEY_TYPE& rhs) { return lhs == rhs; } }; -constexpr static unsigned int MAX_TABLE_SIZE = 0x3fffffff; - template -class JfrSet : public CONFIG::STORAGE { - public: - typedef typename CONFIG::TYPE TYPE; - typedef ResizeableHashTable HashMap; +class JfrSetStorage : public AnyObj { + typedef typename CONFIG::KEY_TYPE K; + protected: + K* _table; + unsigned _table_size; + unsigned _elements; - constexpr static bool is_cheap() { - return CONFIG::alloc_type() == AnyObj::C_HEAP; + static K* alloc_table(unsigned table_size) { + K* table; + if (CONFIG::alloc_type() == C_HEAP) { + table = NEW_C_HEAP_ARRAY(K, table_size, CONFIG::memory_tag()); + } else { + table = NEW_RESOURCE_ARRAY(K, table_size); + } + memset(table, 0, table_size * sizeof(K)); + return table; } - JfrSet(unsigned int initial_size, unsigned int max_size = MAX_TABLE_SIZE) : - _map(is_cheap() ? new (CONFIG::memory_tag()) HashMap(initial_size, max_size) : new HashMap(initial_size, max_size)) {} + JfrSetStorage(unsigned table_size) : + _table(alloc_table(table_size)), + _table_size(table_size), + _elements(0) {} - ~JfrSet() { - if (is_cheap()) { - delete _map; + ~JfrSetStorage() { + if (CONFIG::alloc_type() == C_HEAP) { + FREE_C_HEAP_ARRAY(K, _table); } } - bool add(const TYPE& k) { - bool inserted; - _map->put_if_absent(k, &inserted); - return inserted; + public: + template + void iterate(Functor& functor) { + assert(is_nonempty(), "invariant"); + for (unsigned i = 0; i < _table_size; ++i) { + K k = _table[i]; + if (k != 0) { + functor(k); + } + } } - bool remove(const TYPE& k) { - return _map->remove(k); + unsigned table_size() const { + return _table_size; } - bool contains(const TYPE& k) const { - return _map->contains(k); - } - - bool is_empty() const { - return _map->number_of_entries() == 0; + unsigned size() const { + return _elements; } bool is_nonempty() const { - return !is_empty(); - } - - int size() const { - return _map->number_of_entries(); + return _elements > 0; } void clear() { - if (is_nonempty()) { - _map->unlink(this); - } - assert(is_empty(), "invariant"); + memset(_table, 0, _table_size * sizeof(K)); } - - // Callback for node deletion, used by clear(). - bool do_entry(const TYPE& k, const TYPE& v) { - return true; - } - - private: - HashMap* _map; }; -typedef JfrSet > JfrCHeapTraceIdSet; -typedef JfrSet > JfrResourceAreaTraceIdSet; +template +class JfrSet : public JfrSetStorage { + typedef typename CONFIG::KEY_TYPE K; + static_assert(sizeof(K) > 1, "invalid size of CONFIG::KEY_TYPE"); + private: + static const constexpr unsigned max_initial_size = 1 << 30; + unsigned _table_mask; + unsigned _resize_threshold; // 0.5 load factor + + uint32_t slot_idx(const uint32_t hash) const { + return hash & _table_mask; + } + + void resize() { + assert(this->_elements == _resize_threshold, "invariant"); + K* const old_table = this->_table; + assert(old_table != nullptr, "invariant"); + const unsigned old_table_size = this->table_size(); + guarantee(old_table_size <= max_initial_size, "overflow"); + this->_table_size = old_table_size << 1; + this->_table = JfrSetStorage::alloc_table(this->_table_size); + _table_mask = this->_table_size - 1; + _resize_threshold = old_table_size; + for (unsigned i = 0; i < old_table_size; ++i) { + const K k = old_table[i]; + if (k != 0) { + uint32_t idx = slot_idx(CONFIG::hash(k)); + do { + K v = this->_table[idx]; + if (v == 0) { + this->_table[idx] = k; + break; + } + idx = slot_idx(idx + 1); + } while (true); + } + } + if (CONFIG::alloc_type() == AnyObj::C_HEAP) { + FREE_C_HEAP_ARRAY(K, old_table); + } + assert(_table_mask + 1 == this->_table_size, "invariant"); + assert(_resize_threshold << 1 == this->_table_size, "invariant"); + } + + K* find_slot(K const& k) const { + uint32_t idx = slot_idx(CONFIG::hash(k)); + assert(idx < this->table_size(), "invariant"); + K* result = nullptr; + while (true) { + K v = this->_table[idx]; + if (v == 0) { + result = &this->_table[idx]; + break; + } + if (CONFIG::cmp(v, k)) { + result = reinterpret_cast(p2i(&this->_table[idx]) | 1); + break; + } + idx = slot_idx(idx + 1); + } + assert(result != nullptr, "invariant"); + return result; + } + + public: + JfrSet(unsigned size) : + JfrSetStorage(size), + _table_mask(size - 1), + _resize_threshold(size >> 1) { + assert(size >= 2, "invariant"); + assert(size % 2 == 0, "invariant"); + assert(size <= max_initial_size, "avoid overflow in resize"); + } + + bool contains(K const& k) const { + K* const slot = find_slot(k); + return p2i(slot) & 1; + } + + bool add(K const& k) { + K* const slot = find_slot(k); + if (p2i(slot) & 1) { + // Already exists. + return false; + } + assert(*slot == 0, "invariant"); + *slot = k; + if (++this->_elements == _resize_threshold) { + resize(); + } + assert(this->_elements < _resize_threshold, "invariant"); + return true; + } +}; + +typedef JfrSet > JfrCHeapTraceIdSet; +typedef JfrSet > JfrResourceAreaTraceIdSet; #endif // SHARE_JFR_UTILITIES_JFRSET_HPP diff --git a/test/jdk/jdk/jfr/event/runtime/TestFlush.java b/test/jdk/jdk/jfr/event/runtime/TestFlush.java index 5c450eab05f..fa570406f2a 100644 --- a/test/jdk/jdk/jfr/event/runtime/TestFlush.java +++ b/test/jdk/jdk/jfr/event/runtime/TestFlush.java @@ -143,8 +143,6 @@ public class TestFlush { printFlushEvent(re); Asserts.assertTrue(re.getEventType().getName().contains("Flush"), "invalid Event type"); Asserts.assertGT((long) re.getValue("flushId"), 0L, "Invalid flush ID"); - Asserts.assertGT((long) re.getValue("elements"), 0L, "No elements"); - Asserts.assertGT((long) re.getValue("size"), 0L, "Empty size"); } private static void acknowledgeFlushEvent() { From b6d5f49b8dc2cb7c8e93d7885c2432a28d04e57e Mon Sep 17 00:00:00 2001 From: Guanqiang Han Date: Fri, 15 Aug 2025 09:41:17 +0000 Subject: [PATCH 140/580] 8365296: Build failure with Clang due to -Wformat warning after JDK-8364611 Reviewed-by: ayang, mbaesken --- .../childSignalDisposition/exePrintSignalDisposition.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/test/jdk/java/lang/ProcessBuilder/childSignalDisposition/exePrintSignalDisposition.c b/test/jdk/java/lang/ProcessBuilder/childSignalDisposition/exePrintSignalDisposition.c index 8235cdc7410..8f18a32a0f9 100644 --- a/test/jdk/java/lang/ProcessBuilder/childSignalDisposition/exePrintSignalDisposition.c +++ b/test/jdk/java/lang/ProcessBuilder/childSignalDisposition/exePrintSignalDisposition.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -72,11 +72,7 @@ int main(int argc, char** argv) { } else { printf("%p ", handler); } -#ifdef _AIX printf("%X\n", act.sa_flags); -#else - printf("%X %X\n", act.sa_flags, act.sa_mask); -#endif } return 0; From 059b49b9551ad52f211613a3da2ac0a79deb5ed4 Mon Sep 17 00:00:00 2001 From: Volkan Yazici Date: Fri, 15 Aug 2025 10:37:26 +0000 Subject: [PATCH 141/580] 8365244: Some test control variables are undocumented in doc/testing.md Reviewed-by: erikj --- doc/testing.html | 19 +++++++++++++------ doc/testing.md | 18 +++++++++++++++++- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/doc/testing.html b/doc/testing.html index b9d1f4ed22f..f75e0da309e 100644 --- a/doc/testing.html +++ b/doc/testing.html @@ -72,11 +72,9 @@ id="toc-notes-for-specific-tests">Notes for Specific Tests

  • Non-US locale
  • PKCS11 Tests
  • -
  • ### Testing Ahead-of-time -Optimizations -
      +id="toc-testing-ahead-of-time-optimizations">Testing Ahead-of-time +Optimizations
    • Testing with alternative security providers
    • @@ -435,6 +433,9 @@ the diff between the specified revision and the repository tip.

      The report is stored in build/$BUILD/test-results/jcov-output/diff_coverage_report file.

      +

      AOT_JDK

      +

      See Testing +Ahead-of-time optimizations.

      JTReg keywords

      JOBS

      The test concurrency (-concurrency).

      @@ -556,6 +557,12 @@ each fork. Same as specifying -wi <num>.

      same values as -rff, i.e., text, csv, scsv, json, or latex.

      +

      TEST_JDK

      +

      The path to the JDK that will be used to run the benchmarks.

      +

      Defaults to build/<CONF-NAME>/jdk.

      +

      BENCHMARKS_JAR

      +

      The path to the JAR containing the benchmarks.

      +

      Defaults to test/micro/benchmarks.jar.

      VM_OPTIONS

      Additional VM arguments to provide to forked off VMs. Same as -jvmArgs <args>

      @@ -601,8 +608,8 @@ element of the appropriate @Artifact class. (See JTREG="JAVA_OPTIONS=-Djdk.test.lib.artifacts.nsslib-linux_aarch64=/path/to/NSS-libs"

      For more notes about the PKCS11 tests, please refer to test/jdk/sun/security/pkcs11/README.

      -

      ### Testing Ahead-of-time -Optimizations

      +

      Testing Ahead-of-time +Optimizations

      One way to improve test coverage of ahead-of-time (AOT) optimizations in the JDK is to run existing jtreg test cases in a special "AOT_JDK" mode. Example:

      diff --git a/doc/testing.md b/doc/testing.md index bb56c05c295..525b85a8438 100644 --- a/doc/testing.md +++ b/doc/testing.md @@ -367,6 +367,10 @@ between the specified revision and the repository tip. The report is stored in `build/$BUILD/test-results/jcov-output/diff_coverage_report` file. +#### AOT_JDK + +See [Testing Ahead-of-time optimizations](#testing-ahead-of-time-optimizations). + ### JTReg keywords #### JOBS @@ -545,6 +549,18 @@ Amount of time to spend in each warmup iteration. Same as specifying `-w Specify to have the test run save a log of the values. Accepts the same values as `-rff`, i.e., `text`, `csv`, `scsv`, `json`, or `latex`. +#### TEST_JDK + +The path to the JDK that will be used to run the benchmarks. + +Defaults to `build//jdk`. + +#### BENCHMARKS_JAR + +The path to the JAR containing the benchmarks. + +Defaults to `test/micro/benchmarks.jar`. + #### VM_OPTIONS Additional VM arguments to provide to forked off VMs. Same as `-jvmArgs ` @@ -612,7 +628,7 @@ For more notes about the PKCS11 tests, please refer to test/jdk/sun/security/pkcs11/README. ### Testing Ahead-of-time Optimizations -------------------------------------------------------------------------------- + One way to improve test coverage of ahead-of-time (AOT) optimizations in the JDK is to run existing jtreg test cases in a special "AOT_JDK" mode. Example: From dbae90c950200cb417aebeab65e5fce7a7e5f94f Mon Sep 17 00:00:00 2001 From: Francesco Andreuzzi Date: Fri, 15 Aug 2025 10:45:00 +0000 Subject: [PATCH 142/580] 8364723: Sort share/interpreter includes Reviewed-by: shade, ayang --- src/hotspot/share/interpreter/abstractInterpreter.cpp | 5 ++--- src/hotspot/share/interpreter/bytecodeStream.cpp | 2 +- src/hotspot/share/interpreter/bytecodeTracer.cpp | 4 ++-- src/hotspot/share/interpreter/bytecodeUtils.cpp | 2 +- src/hotspot/share/interpreter/interpreter.cpp | 5 ++--- src/hotspot/share/interpreter/interpreterRuntime.cpp | 2 +- src/hotspot/share/interpreter/templateInterpreter.cpp | 2 +- .../share/interpreter/templateInterpreterGenerator.cpp | 2 +- src/hotspot/share/interpreter/zero/bytecodeInterpreter.cpp | 2 +- .../share/interpreter/zero/bytecodeInterpreter.inline.hpp | 3 +-- .../share/interpreter/zero/zeroInterpreterGenerator.hpp | 1 - test/hotspot/jtreg/sources/TestIncludesAreSorted.java | 1 + 12 files changed, 14 insertions(+), 17 deletions(-) diff --git a/src/hotspot/share/interpreter/abstractInterpreter.cpp b/src/hotspot/share/interpreter/abstractInterpreter.cpp index 0d6ccf3a710..05590add8ff 100644 --- a/src/hotspot/share/interpreter/abstractInterpreter.cpp +++ b/src/hotspot/share/interpreter/abstractInterpreter.cpp @@ -22,23 +22,22 @@ * */ -#include "asm/macroAssembler.hpp" #include "asm/macroAssembler.inline.hpp" #include "cds/metaspaceShared.hpp" #include "compiler/disassembler.hpp" #include "interpreter/bytecodeHistogram.hpp" #include "interpreter/bytecodeStream.hpp" +#include "interpreter/interp_masm.hpp" #include "interpreter/interpreter.hpp" #include "interpreter/interpreterRuntime.hpp" -#include "interpreter/interp_masm.hpp" #include "interpreter/templateTable.hpp" #include "memory/allocation.inline.hpp" #include "memory/resourceArea.hpp" #include "oops/arrayOop.hpp" #include "oops/constantPool.inline.hpp" #include "oops/cpCache.inline.hpp" -#include "oops/methodData.hpp" #include "oops/method.inline.hpp" +#include "oops/methodData.hpp" #include "oops/oop.inline.hpp" #include "prims/jvmtiExport.hpp" #include "prims/methodHandles.hpp" diff --git a/src/hotspot/share/interpreter/bytecodeStream.cpp b/src/hotspot/share/interpreter/bytecodeStream.cpp index 7454c749828..5a9e32a46b0 100644 --- a/src/hotspot/share/interpreter/bytecodeStream.cpp +++ b/src/hotspot/share/interpreter/bytecodeStream.cpp @@ -22,8 +22,8 @@ * */ -#include "interpreter/bytecodeStream.hpp" #include "interpreter/bytecodes.hpp" +#include "interpreter/bytecodeStream.hpp" #include "runtime/handles.inline.hpp" Bytecodes::Code RawBytecodeStream::raw_next_special(Bytecodes::Code code) { diff --git a/src/hotspot/share/interpreter/bytecodeTracer.cpp b/src/hotspot/share/interpreter/bytecodeTracer.cpp index 1f912b2b00f..34170108dc4 100644 --- a/src/hotspot/share/interpreter/bytecodeTracer.cpp +++ b/src/hotspot/share/interpreter/bytecodeTracer.cpp @@ -25,14 +25,14 @@ #include "classfile/classPrinter.hpp" #include "classfile/javaClasses.inline.hpp" #include "interpreter/bytecodeHistogram.hpp" +#include "interpreter/bytecodes.hpp" #include "interpreter/bytecodeStream.hpp" #include "interpreter/bytecodeTracer.hpp" -#include "interpreter/bytecodes.hpp" #include "interpreter/interpreter.hpp" #include "memory/resourceArea.hpp" #include "oops/constantPool.inline.hpp" -#include "oops/methodData.hpp" #include "oops/method.hpp" +#include "oops/methodData.hpp" #include "runtime/atomic.hpp" #include "runtime/handles.inline.hpp" #include "runtime/mutexLocker.hpp" diff --git a/src/hotspot/share/interpreter/bytecodeUtils.cpp b/src/hotspot/share/interpreter/bytecodeUtils.cpp index eb4557d6ba0..a5503cc4b88 100644 --- a/src/hotspot/share/interpreter/bytecodeUtils.cpp +++ b/src/hotspot/share/interpreter/bytecodeUtils.cpp @@ -28,8 +28,8 @@ #include "gc/shared/gcLocker.hpp" #include "interpreter/bytecodeUtils.hpp" #include "memory/resourceArea.hpp" -#include "runtime/signature.hpp" #include "runtime/safepointVerifiers.hpp" +#include "runtime/signature.hpp" #include "utilities/events.hpp" #include "utilities/ostream.hpp" diff --git a/src/hotspot/share/interpreter/interpreter.cpp b/src/hotspot/share/interpreter/interpreter.cpp index 6272deea349..2cc163186e8 100644 --- a/src/hotspot/share/interpreter/interpreter.cpp +++ b/src/hotspot/share/interpreter/interpreter.cpp @@ -22,18 +22,17 @@ * */ -#include "asm/macroAssembler.hpp" #include "asm/macroAssembler.inline.hpp" #include "compiler/disassembler.hpp" +#include "interpreter/interp_masm.hpp" #include "interpreter/interpreter.hpp" #include "interpreter/interpreterRuntime.hpp" -#include "interpreter/interp_masm.hpp" #include "interpreter/templateTable.hpp" #include "memory/allocation.inline.hpp" #include "memory/resourceArea.hpp" #include "oops/arrayOop.hpp" -#include "oops/methodData.hpp" #include "oops/method.hpp" +#include "oops/methodData.hpp" #include "oops/oop.inline.hpp" #include "prims/forte.hpp" #include "prims/jvmtiExport.hpp" diff --git a/src/hotspot/share/interpreter/interpreterRuntime.cpp b/src/hotspot/share/interpreter/interpreterRuntime.cpp index 8e7e5772ba6..50115f842e2 100644 --- a/src/hotspot/share/interpreter/interpreterRuntime.cpp +++ b/src/hotspot/share/interpreter/interpreterRuntime.cpp @@ -46,8 +46,8 @@ #include "oops/cpCache.inline.hpp" #include "oops/instanceKlass.inline.hpp" #include "oops/klass.inline.hpp" -#include "oops/methodData.hpp" #include "oops/method.inline.hpp" +#include "oops/methodData.hpp" #include "oops/objArrayKlass.hpp" #include "oops/objArrayOop.inline.hpp" #include "oops/oop.inline.hpp" diff --git a/src/hotspot/share/interpreter/templateInterpreter.cpp b/src/hotspot/share/interpreter/templateInterpreter.cpp index 0dd82addbfe..44804221dc7 100644 --- a/src/hotspot/share/interpreter/templateInterpreter.cpp +++ b/src/hotspot/share/interpreter/templateInterpreter.cpp @@ -22,9 +22,9 @@ * */ +#include "interpreter/interp_masm.hpp" #include "interpreter/interpreter.hpp" #include "interpreter/interpreterRuntime.hpp" -#include "interpreter/interp_masm.hpp" #include "interpreter/templateInterpreter.hpp" #include "interpreter/templateInterpreterGenerator.hpp" #include "interpreter/templateTable.hpp" diff --git a/src/hotspot/share/interpreter/templateInterpreterGenerator.cpp b/src/hotspot/share/interpreter/templateInterpreterGenerator.cpp index 9a316f3ba46..96f53c517a3 100644 --- a/src/hotspot/share/interpreter/templateInterpreterGenerator.cpp +++ b/src/hotspot/share/interpreter/templateInterpreterGenerator.cpp @@ -23,9 +23,9 @@ */ #include "compiler/disassembler.hpp" +#include "interpreter/interp_masm.hpp" #include "interpreter/interpreter.hpp" #include "interpreter/interpreterRuntime.hpp" -#include "interpreter/interp_masm.hpp" #include "interpreter/templateInterpreter.hpp" #include "interpreter/templateInterpreterGenerator.hpp" #include "interpreter/templateTable.hpp" diff --git a/src/hotspot/share/interpreter/zero/bytecodeInterpreter.cpp b/src/hotspot/share/interpreter/zero/bytecodeInterpreter.cpp index 07ec57a6ffe..bfed16f2769 100644 --- a/src/hotspot/share/interpreter/zero/bytecodeInterpreter.cpp +++ b/src/hotspot/share/interpreter/zero/bytecodeInterpreter.cpp @@ -29,9 +29,9 @@ #include "gc/shared/threadLocalAllocBuffer.inline.hpp" #include "gc/shared/tlab_globals.hpp" #include "interpreter/bytecodeHistogram.hpp" -#include "interpreter/zero/bytecodeInterpreter.inline.hpp" #include "interpreter/interpreter.hpp" #include "interpreter/interpreterRuntime.hpp" +#include "interpreter/zero/bytecodeInterpreter.inline.hpp" #include "jvm_io.h" #include "logging/log.hpp" #include "memory/resourceArea.hpp" diff --git a/src/hotspot/share/interpreter/zero/bytecodeInterpreter.inline.hpp b/src/hotspot/share/interpreter/zero/bytecodeInterpreter.inline.hpp index 2a396aec9ad..effee05c2f1 100644 --- a/src/hotspot/share/interpreter/zero/bytecodeInterpreter.inline.hpp +++ b/src/hotspot/share/interpreter/zero/bytecodeInterpreter.inline.hpp @@ -27,6 +27,7 @@ #include "interpreter/zero/bytecodeInterpreter.hpp" +#include "bytecodeInterpreter_zero.inline.hpp" #include "runtime/stubRoutines.hpp" #include "utilities/macros.hpp" @@ -42,6 +43,4 @@ #define VERIFY_OOP(o) #endif -# include "bytecodeInterpreter_zero.inline.hpp" - #endif // SHARE_INTERPRETER_BYTECODEINTERPRETER_INLINE_HPP diff --git a/src/hotspot/share/interpreter/zero/zeroInterpreterGenerator.hpp b/src/hotspot/share/interpreter/zero/zeroInterpreterGenerator.hpp index 8c745e51949..f8db4a07e9a 100644 --- a/src/hotspot/share/interpreter/zero/zeroInterpreterGenerator.hpp +++ b/src/hotspot/share/interpreter/zero/zeroInterpreterGenerator.hpp @@ -29,7 +29,6 @@ // of the Zero interpreter generator. # include "entry_zero.hpp" -// # include "interpreter/interp_masm.hpp" class ZeroInterpreterGenerator: public AbstractInterpreterGenerator { diff --git a/test/hotspot/jtreg/sources/TestIncludesAreSorted.java b/test/hotspot/jtreg/sources/TestIncludesAreSorted.java index 072784cda9d..7460a160ee6 100644 --- a/test/hotspot/jtreg/sources/TestIncludesAreSorted.java +++ b/test/hotspot/jtreg/sources/TestIncludesAreSorted.java @@ -50,6 +50,7 @@ public class TestIncludesAreSorted { "share/classfile", "share/code", "share/compiler", + "share/interpreter", "share/jvmci", "share/libadt", "share/metaprogramming", From 08db4b99622e488558dd7987c34f1c515fa30426 Mon Sep 17 00:00:00 2001 From: William Kemper Date: Fri, 15 Aug 2025 17:56:47 +0000 Subject: [PATCH 143/580] 8365571: GenShen: PLAB promotions may remain disabled for evacuation threads Reviewed-by: kdnilsen, ysr, shade --- .../gc/shenandoah/shenandoahConcurrentGC.cpp | 23 ++++--------------- .../share/gc/shenandoah/shenandoahHeap.cpp | 5 ++++ 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp index 81154aff9f0..d4c7ad5df50 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp @@ -823,7 +823,6 @@ bool ShenandoahConcurrentGC::has_in_place_promotions(ShenandoahHeap* heap) { return heap->mode()->is_generational() && heap->old_generation()->has_in_place_promotions(); } -template class ShenandoahConcurrentEvacThreadClosure : public ThreadClosure { private: OopClosure* const _oops; @@ -833,13 +832,9 @@ public: void do_thread(Thread* thread) override { JavaThread* const jt = JavaThread::cast(thread); StackWatermarkSet::finish_processing(jt, _oops, StackWatermarkKind::gc); - if (GENERATIONAL) { - ShenandoahThreadLocalData::enable_plab_promotions(thread); - } } }; -template class ShenandoahConcurrentEvacUpdateThreadTask : public WorkerTask { private: ShenandoahJavaThreadsIterator _java_threads; @@ -851,30 +846,20 @@ public: } void work(uint worker_id) override { - if (GENERATIONAL) { - Thread* worker_thread = Thread::current(); - ShenandoahThreadLocalData::enable_plab_promotions(worker_thread); - } - // ShenandoahEvacOOMScope has to be setup by ShenandoahContextEvacuateUpdateRootsClosure. // Otherwise, may deadlock with watermark lock ShenandoahContextEvacuateUpdateRootsClosure oops_cl; - ShenandoahConcurrentEvacThreadClosure thr_cl(&oops_cl); + ShenandoahConcurrentEvacThreadClosure thr_cl(&oops_cl); _java_threads.threads_do(&thr_cl, worker_id); } }; void ShenandoahConcurrentGC::op_thread_roots() { - ShenandoahHeap* const heap = ShenandoahHeap::heap(); + const ShenandoahHeap* const heap = ShenandoahHeap::heap(); assert(heap->is_evacuation_in_progress(), "Checked by caller"); ShenandoahGCWorkerPhase worker_phase(ShenandoahPhaseTimings::conc_thread_roots); - if (heap->mode()->is_generational()) { - ShenandoahConcurrentEvacUpdateThreadTask task(heap->workers()->active_workers()); - heap->workers()->run_task(&task); - } else { - ShenandoahConcurrentEvacUpdateThreadTask task(heap->workers()->active_workers()); - heap->workers()->run_task(&task); - } + ShenandoahConcurrentEvacUpdateThreadTask task(heap->workers()->active_workers()); + heap->workers()->run_task(&task); } void ShenandoahConcurrentGC::op_weak_refs() { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp index 2baf7e25cba..b90468501f6 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp @@ -1216,6 +1216,11 @@ public: // 1. We need to make the plab memory parsable by remembered-set scanning. // 2. We need to establish a trustworthy UpdateWaterMark value within each old-gen heap region ShenandoahGenerationalHeap::heap()->retire_plab(plab, thread); + + // Re-enable promotions for the next evacuation phase. + ShenandoahThreadLocalData::enable_plab_promotions(thread); + + // Reset the fill size for next evacuation phase. if (_resize && ShenandoahThreadLocalData::plab_size(thread) > 0) { ShenandoahThreadLocalData::set_plab_size(thread, 0); } From 39a365296882b0df49398cd7ac36e801a9aa1c35 Mon Sep 17 00:00:00 2001 From: Dean Long Date: Fri, 15 Aug 2025 18:52:45 +0000 Subject: [PATCH 144/580] 8278874: tighten VerifyStack constraints Co-authored-by: Tom Rodriguez Reviewed-by: mhaessig, never --- src/hotspot/share/classfile/javaClasses.cpp | 5 +- src/hotspot/share/runtime/deoptimization.cpp | 180 ++++++++++--------- src/hotspot/share/runtime/vframeArray.cpp | 117 +++++++----- src/hotspot/share/runtime/vframeArray.hpp | 9 +- 4 files changed, 178 insertions(+), 133 deletions(-) diff --git a/src/hotspot/share/classfile/javaClasses.cpp b/src/hotspot/share/classfile/javaClasses.cpp index 5c41ea789b5..20534b93290 100644 --- a/src/hotspot/share/classfile/javaClasses.cpp +++ b/src/hotspot/share/classfile/javaClasses.cpp @@ -1886,8 +1886,9 @@ JavaThreadStatus java_lang_Thread::get_thread_status(oop java_thread) { // Make sure the caller is operating on behalf of the VM or is // running VM code (state == _thread_in_vm). assert(Threads_lock->owned_by_self() || Thread::current()->is_VM_thread() || - JavaThread::current()->thread_state() == _thread_in_vm, - "Java Thread is not running in vm"); + JavaThread::current()->thread_state() == _thread_in_vm || + JavaThread::current() == java_lang_Thread::thread(java_thread), + "unsafe call to java_lang_Thread::get_thread_status()?"); GET_FIELDHOLDER_FIELD(java_thread, get_thread_status, JavaThreadStatus::NEW /* not initialized */); } diff --git a/src/hotspot/share/runtime/deoptimization.cpp b/src/hotspot/share/runtime/deoptimization.cpp index 5f9a80cf100..243903ed233 100644 --- a/src/hotspot/share/runtime/deoptimization.cpp +++ b/src/hotspot/share/runtime/deoptimization.cpp @@ -561,7 +561,8 @@ Deoptimization::UnrollBlock* Deoptimization::fetch_unroll_info_helper(JavaThread #endif // !PRODUCT GrowableArray* expressions = trap_scope->expressions(); - guarantee(expressions != nullptr && expressions->length() > 0, "must have exception to throw"); + guarantee(expressions != nullptr && expressions->length() == 1, "should have only exception on stack"); + guarantee(exec_mode != Unpack_exception, "rethrow_exception set with Unpack_exception"); ScopeValue* topOfStack = expressions->top(); exceptionObject = StackValue::create_stack_value(&deoptee, &map, topOfStack)->get_obj(); guarantee(exceptionObject() != nullptr, "exception oop can not be null"); @@ -737,6 +738,7 @@ Deoptimization::UnrollBlock* Deoptimization::fetch_unroll_info_helper(JavaThread if (exceptionObject() != nullptr) { current->set_exception_oop(exceptionObject()); exec_mode = Unpack_exception; + assert(array->element(0)->rethrow_exception(), "must be"); } #endif @@ -844,6 +846,7 @@ void Deoptimization::unwind_callee_save_values(frame* f, vframeArray* vframe_arr } #ifndef PRODUCT +#ifdef ASSERT // Return true if the execution after the provided bytecode continues at the // next bytecode in the code. This is not the case for gotos, returns, and // throws. @@ -868,6 +871,7 @@ static bool falls_through(Bytecodes::Code bc) { } } #endif +#endif // Return BasicType of value being returned JRT_LEAF(BasicType, Deoptimization::unpack_frames(JavaThread* thread, int exec_mode)) @@ -932,116 +936,114 @@ JRT_LEAF(BasicType, Deoptimization::unpack_frames(JavaThread* thread, int exec_m RegisterMap::ProcessFrames::include, RegisterMap::WalkContinuation::skip); rm.set_include_argument_oops(false); - bool is_top_frame = true; int callee_size_of_parameters = 0; - int callee_max_locals = 0; - for (int i = 0; i < cur_array->frames(); i++) { - vframeArrayElement* el = cur_array->element(i); + for (int frame_idx = 0; frame_idx < cur_array->frames(); frame_idx++) { + bool is_top_frame = (frame_idx == 0); + vframeArrayElement* el = cur_array->element(frame_idx); frame* iframe = el->iframe(); guarantee(iframe->is_interpreted_frame(), "Wrong frame type"); + methodHandle mh(thread, iframe->interpreter_frame_method()); + bool reexecute = el->should_reexecute(); + + int cur_invoke_parameter_size = 0; + int top_frame_expression_stack_adjustment = 0; + int max_bci = mh->code_size(); + BytecodeStream str(mh, iframe->interpreter_frame_bci()); + assert(str.bci() < max_bci, "bci in interpreter frame out of bounds"); + Bytecodes::Code cur_code = str.next(); + + if (!reexecute && !Bytecodes::is_invoke(cur_code)) { + // We can only compute OopMaps for the before state, so we need to roll forward + // to the next bytecode. + assert(is_top_frame, "must be"); + assert(falls_through(cur_code), "must be"); + assert(cur_code != Bytecodes::_illegal, "illegal bytecode"); + assert(str.bci() < max_bci, "bci in interpreter frame out of bounds"); + + // Need to subtract off the size of the result type of + // the bytecode because this is not described in the + // debug info but returned to the interpreter in the TOS + // caching register + BasicType bytecode_result_type = Bytecodes::result_type(cur_code); + if (bytecode_result_type != T_ILLEGAL) { + top_frame_expression_stack_adjustment = type2size[bytecode_result_type]; + } + assert(top_frame_expression_stack_adjustment >= 0, "stack adjustment must be positive"); + + cur_code = str.next(); + // Reflect the fact that we have rolled forward and now need + // top_frame_expression_stack_adjustment + reexecute = true; + } + + assert(cur_code != Bytecodes::_illegal, "illegal bytecode"); + assert(str.bci() < max_bci, "bci in interpreter frame out of bounds"); // Get the oop map for this bci InterpreterOopMap mask; - int cur_invoke_parameter_size = 0; - bool try_next_mask = false; - int next_mask_expression_stack_size = -1; - int top_frame_expression_stack_adjustment = 0; - methodHandle mh(thread, iframe->interpreter_frame_method()); - OopMapCache::compute_one_oop_map(mh, iframe->interpreter_frame_bci(), &mask); - BytecodeStream str(mh, iframe->interpreter_frame_bci()); - int max_bci = mh->code_size(); - // Get to the next bytecode if possible - assert(str.bci() < max_bci, "bci in interpreter frame out of bounds"); + OopMapCache::compute_one_oop_map(mh, str.bci(), &mask); // Check to see if we can grab the number of outgoing arguments // at an uncommon trap for an invoke (where the compiler // generates debug info before the invoke has executed) - Bytecodes::Code cur_code = str.next(); - Bytecodes::Code next_code = Bytecodes::_shouldnotreachhere; if (Bytecodes::is_invoke(cur_code)) { - Bytecode_invoke invoke(mh, iframe->interpreter_frame_bci()); + Bytecode_invoke invoke(mh, str.bci()); cur_invoke_parameter_size = invoke.size_of_parameters(); - if (i != 0 && invoke.has_member_arg()) { + if (!is_top_frame && invoke.has_member_arg()) { callee_size_of_parameters++; } } - if (str.bci() < max_bci) { - next_code = str.next(); - if (next_code >= 0) { - // The interpreter oop map generator reports results before - // the current bytecode has executed except in the case of - // calls. It seems to be hard to tell whether the compiler - // has emitted debug information matching the "state before" - // a given bytecode or the state after, so we try both - if (!Bytecodes::is_invoke(cur_code) && falls_through(cur_code)) { - // Get expression stack size for the next bytecode - InterpreterOopMap next_mask; - OopMapCache::compute_one_oop_map(mh, str.bci(), &next_mask); - next_mask_expression_stack_size = next_mask.expression_stack_size(); - if (Bytecodes::is_invoke(next_code)) { - Bytecode_invoke invoke(mh, str.bci()); - next_mask_expression_stack_size += invoke.size_of_parameters(); - } - // Need to subtract off the size of the result type of - // the bytecode because this is not described in the - // debug info but returned to the interpreter in the TOS - // caching register - BasicType bytecode_result_type = Bytecodes::result_type(cur_code); - if (bytecode_result_type != T_ILLEGAL) { - top_frame_expression_stack_adjustment = type2size[bytecode_result_type]; - } - assert(top_frame_expression_stack_adjustment >= 0, "stack adjustment must be positive"); - try_next_mask = true; - } - } - } // Verify stack depth and oops in frame - // This assertion may be dependent on the platform we're running on and may need modification (tested on x86 and sparc) - if (!( - /* SPARC */ - (iframe->interpreter_frame_expression_stack_size() == mask.expression_stack_size() + callee_size_of_parameters) || - /* x86 */ - (iframe->interpreter_frame_expression_stack_size() == mask.expression_stack_size() + callee_max_locals) || - (try_next_mask && - (iframe->interpreter_frame_expression_stack_size() == (next_mask_expression_stack_size - - top_frame_expression_stack_adjustment))) || - (is_top_frame && (exec_mode == Unpack_exception) && iframe->interpreter_frame_expression_stack_size() == 0) || - (is_top_frame && (exec_mode == Unpack_uncommon_trap || exec_mode == Unpack_reexecute || el->should_reexecute()) && - (iframe->interpreter_frame_expression_stack_size() == mask.expression_stack_size() + cur_invoke_parameter_size)) - )) { - { - // Print out some information that will help us debug the problem - tty->print_cr("Wrong number of expression stack elements during deoptimization"); - tty->print_cr(" Error occurred while verifying frame %d (0..%d, 0 is topmost)", i, cur_array->frames() - 1); - tty->print_cr(" Current code %s", Bytecodes::name(cur_code)); - if (try_next_mask) { - tty->print_cr(" Next code %s", Bytecodes::name(next_code)); - } - tty->print_cr(" Fabricated interpreter frame had %d expression stack elements", - iframe->interpreter_frame_expression_stack_size()); - tty->print_cr(" Interpreter oop map had %d expression stack elements", mask.expression_stack_size()); - tty->print_cr(" try_next_mask = %d", try_next_mask); - tty->print_cr(" next_mask_expression_stack_size = %d", next_mask_expression_stack_size); - tty->print_cr(" callee_size_of_parameters = %d", callee_size_of_parameters); - tty->print_cr(" callee_max_locals = %d", callee_max_locals); - tty->print_cr(" top_frame_expression_stack_adjustment = %d", top_frame_expression_stack_adjustment); - tty->print_cr(" exec_mode = %d", exec_mode); - tty->print_cr(" cur_invoke_parameter_size = %d", cur_invoke_parameter_size); - tty->print_cr(" Thread = " INTPTR_FORMAT ", thread ID = %d", p2i(thread), thread->osthread()->thread_id()); - tty->print_cr(" Interpreted frames:"); - for (int k = 0; k < cur_array->frames(); k++) { - vframeArrayElement* el = cur_array->element(k); - tty->print_cr(" %s (bci %d)", el->method()->name_and_sig_as_C_string(), el->bci()); - } - cur_array->print_on_2(tty); + auto match = [&]() { + int iframe_expr_ssize = iframe->interpreter_frame_expression_stack_size(); +#if INCLUDE_JVMCI + if (is_top_frame && el->rethrow_exception()) { + return iframe_expr_ssize == 1; } +#endif + // This should only be needed for C1 + if (is_top_frame && exec_mode == Unpack_exception && iframe_expr_ssize == 0) { + return true; + } + if (reexecute) { + int expr_ssize_before = iframe_expr_ssize + top_frame_expression_stack_adjustment; + int oopmap_expr_invoke_ssize = mask.expression_stack_size() + cur_invoke_parameter_size; + return expr_ssize_before == oopmap_expr_invoke_ssize; + } else { + int oopmap_expr_callee_ssize = mask.expression_stack_size() + callee_size_of_parameters; + return iframe_expr_ssize == oopmap_expr_callee_ssize; + } + }; + if (!match()) { + // Print out some information that will help us debug the problem + tty->print_cr("Wrong number of expression stack elements during deoptimization"); + tty->print_cr(" Error occurred while verifying frame %d (0..%d, 0 is topmost)", frame_idx, cur_array->frames() - 1); + tty->print_cr(" Current code %s", Bytecodes::name(cur_code)); + tty->print_cr(" Fabricated interpreter frame had %d expression stack elements", + iframe->interpreter_frame_expression_stack_size()); + tty->print_cr(" Interpreter oop map had %d expression stack elements", mask.expression_stack_size()); + tty->print_cr(" callee_size_of_parameters = %d", callee_size_of_parameters); + tty->print_cr(" top_frame_expression_stack_adjustment = %d", top_frame_expression_stack_adjustment); + tty->print_cr(" exec_mode = %d", exec_mode); + tty->print_cr(" original should_reexecute = %s", el->should_reexecute() ? "true" : "false"); + tty->print_cr(" reexecute = %s%s", reexecute ? "true" : "false", + (reexecute != el->should_reexecute()) ? " (changed)" : ""); +#if INCLUDE_JVMCI + tty->print_cr(" rethrow_exception = %s", el->rethrow_exception() ? "true" : "false"); +#endif + tty->print_cr(" cur_invoke_parameter_size = %d", cur_invoke_parameter_size); + tty->print_cr(" Thread = " INTPTR_FORMAT ", thread ID = %d", p2i(thread), thread->osthread()->thread_id()); + tty->print_cr(" Interpreted frames:"); + for (int k = 0; k < cur_array->frames(); k++) { + vframeArrayElement* el = cur_array->element(k); + tty->print_cr(" %s (bci %d)", el->method()->name_and_sig_as_C_string(), el->bci()); + } + cur_array->print_on_2(tty); guarantee(false, "wrong number of expression stack elements during deopt"); } VerifyOopClosure verify; iframe->oops_interpreted_do(&verify, &rm, false); callee_size_of_parameters = mh->size_of_parameters(); - callee_max_locals = mh->max_locals(); - is_top_frame = false; } } #endif // !PRODUCT diff --git a/src/hotspot/share/runtime/vframeArray.cpp b/src/hotspot/share/runtime/vframeArray.cpp index e17961fc424..224bce4513a 100644 --- a/src/hotspot/share/runtime/vframeArray.cpp +++ b/src/hotspot/share/runtime/vframeArray.cpp @@ -23,6 +23,7 @@ */ #include "classfile/vmSymbols.hpp" +#include "code/scopeDesc.hpp" #include "code/vmreg.inline.hpp" #include "interpreter/bytecode.hpp" #include "interpreter/bytecode.inline.hpp" @@ -61,7 +62,10 @@ void vframeArrayElement::fill_in(compiledVFrame* vf, bool realloc_failures) { _method = vf->method(); _bci = vf->raw_bci(); - _reexecute = vf->should_reexecute(); + _reexecute = vf->should_reexecute(); // initial value, updated in unpack_on_stack +#if INCLUDE_JVMCI + _rethrow = vf->scope()->rethrow_exception(); +#endif #ifdef ASSERT _removed_monitors = false; #endif @@ -171,7 +175,34 @@ void vframeArrayElement::fill_in(compiledVFrame* vf, bool realloc_failures) { } } -int unpack_counter = 0; +static int unpack_counter = 0; + +bool vframeArrayElement::should_reexecute(bool is_top_frame, int exec_mode) const { + if (is_top_frame) { + switch (exec_mode) { + case Deoptimization::Unpack_uncommon_trap: + case Deoptimization::Unpack_reexecute: + return true; + case Deoptimization::Unpack_exception: + assert(raw_bci() >= 0, "bad bci %d for Unpack_exception", raw_bci()); + default: + break; + } + } + if (raw_bci() == SynchronizationEntryBCI) { + return true; + } + bool reexec = should_reexecute(); + assert(is_top_frame || reexec == false, "unexepected should_reexecute()"); +#ifdef ASSERT + if (!reexec) { + address bcp = method()->bcp_from(bci()); + Bytecodes::Code code = Bytecodes::code_at(method(), bcp); + assert(!Interpreter::bytecode_should_reexecute(code), "should_reexecute mismatch"); + } +#endif + return reexec; +} void vframeArrayElement::unpack_on_stack(int caller_actual_parameters, int callee_parameters, @@ -189,20 +220,37 @@ void vframeArrayElement::unpack_on_stack(int caller_actual_parameters, // C++ interpreter doesn't need a pc since it will figure out what to do when it // begins execution address pc; - bool use_next_mdp = false; // true if we should use the mdp associated with the next bci - // rather than the one associated with bcp - if (raw_bci() == SynchronizationEntryBCI) { + bool reexecute = should_reexecute(is_top_frame, exec_mode); + if (is_top_frame && exec_mode == Deoptimization::Unpack_exception) { + assert(raw_bci() >= 0, "bad bci %d for Unpack_exception", raw_bci()); + bcp = method()->bcp_from(bci()); + // exception is pending + pc = Interpreter::rethrow_exception_entry(); + // [phh] We're going to end up in some handler or other, so it doesn't + // matter what mdp we point to. See exception_handler_for_exception() + // in interpreterRuntime.cpp. + } else if (raw_bci() == SynchronizationEntryBCI) { // We are deoptimizing while hanging in prologue code for synchronized method bcp = method()->bcp_from(0); // first byte code pc = Interpreter::deopt_entry(vtos, 0); // step = 0 since we don't skip current bytecode - } else if (should_reexecute()) { //reexecute this bytecode + assert(reexecute, "must be"); + } else if (reexecute) { //reexecute this bytecode assert(is_top_frame, "reexecute allowed only for the top frame"); bcp = method()->bcp_from(bci()); - pc = Interpreter::deopt_reexecute_entry(method(), bcp); + switch (exec_mode) { + case Deoptimization::Unpack_uncommon_trap: + case Deoptimization::Unpack_reexecute: + // Do not special-case _athrow or _return_register_finalizer + pc = Interpreter::deopt_entry(vtos, 0); + break; + default: + // Yes, special-case _athrow and _return_register_finalizer + pc = Interpreter::deopt_reexecute_entry(method(), bcp); + } } else { bcp = method()->bcp_from(bci()); + assert(!reexecute, "must be"); pc = Interpreter::deopt_continue_after_entry(method(), bcp, callee_parameters, is_top_frame); - use_next_mdp = true; } assert(Bytecodes::is_defined(*bcp), "must be a valid bytecode"); @@ -239,44 +287,32 @@ void vframeArrayElement::unpack_on_stack(int caller_actual_parameters, } else { // Reexecute invoke in top frame pc = Interpreter::deopt_entry(vtos, 0); - use_next_mdp = false; +#ifdef ASSERT + Bytecodes::Code code = Bytecodes::code_at(method(), bcp); + assert(Bytecodes::is_invoke(code), "must be"); + assert(!reexecute, "must be"); +#endif + // It would be nice if the VerifyStack logic in unpack_frames() was refactored so + // we could check the stack before and after changing the reexecute mode, but + // it should pass either way because an invoke uses the same stack state for both modes, + // which is: args popped but result not yet pushed. + reexecute = true; popframe_preserved_args_size_in_bytes = in_bytes(thread->popframe_preserved_args_size()); // Note: the PopFrame-related extension of the expression stack size is done in // Deoptimization::fetch_unroll_info_helper popframe_preserved_args_size_in_words = in_words(thread->popframe_preserved_args_size_in_words()); } - } else if (!realloc_failure_exception && JvmtiExport::can_force_early_return() && state != nullptr && - state->is_earlyret_pending()) { - // Force early return from top frame after deoptimization - pc = Interpreter::remove_activation_early_entry(state->earlyret_tos()); - } else { - if (realloc_failure_exception && JvmtiExport::can_force_early_return() && state != nullptr && state->is_earlyret_pending()) { + } else if (JvmtiExport::can_force_early_return() && state != nullptr && state->is_earlyret_pending()) { + if (!realloc_failure_exception) { + // Force early return from top frame after deoptimization + pc = Interpreter::remove_activation_early_entry(state->earlyret_tos()); + } else { state->clr_earlyret_pending(); state->set_earlyret_oop(nullptr); state->clr_earlyret_value(); } - // Possibly override the previous pc computation of the top (youngest) frame - switch (exec_mode) { - case Deoptimization::Unpack_deopt: - // use what we've got - break; - case Deoptimization::Unpack_exception: - // exception is pending - pc = SharedRuntime::raw_exception_handler_for_return_address(thread, pc); - // [phh] We're going to end up in some handler or other, so it doesn't - // matter what mdp we point to. See exception_handler_for_exception() - // in interpreterRuntime.cpp. - break; - case Deoptimization::Unpack_uncommon_trap: - case Deoptimization::Unpack_reexecute: - // redo last byte code - pc = Interpreter::deopt_entry(vtos, 0); - use_next_mdp = false; - break; - default: - ShouldNotReachHere(); - } } + _reexecute = reexecute; } // Setup the interpreter frame @@ -317,17 +353,16 @@ void vframeArrayElement::unpack_on_stack(int caller_actual_parameters, assert(src->obj() != nullptr || ObjectSynchronizer::current_thread_holds_lock(thread, Handle(thread, src->obj())), "should be held, after move_to"); } - if (ProfileInterpreter) { - iframe()->interpreter_frame_set_mdp(nullptr); // clear out the mdp. - } iframe()->interpreter_frame_set_bcp(bcp); if (ProfileInterpreter) { MethodData* mdo = method()->method_data(); - if (mdo != nullptr) { + if (mdo != nullptr && exec_mode != Deoptimization::Unpack_exception) { int bci = iframe()->interpreter_frame_bci(); - if (use_next_mdp) ++bci; + if (!reexecute) ++bci; address mdp = mdo->bci_to_dp(bci); iframe()->interpreter_frame_set_mdp(mdp); + } else { + iframe()->interpreter_frame_set_mdp(nullptr); // clear out the mdp. } } diff --git a/src/hotspot/share/runtime/vframeArray.hpp b/src/hotspot/share/runtime/vframeArray.hpp index b270046252d..d7390bedff4 100644 --- a/src/hotspot/share/runtime/vframeArray.hpp +++ b/src/hotspot/share/runtime/vframeArray.hpp @@ -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 @@ -56,6 +56,9 @@ class vframeArrayElement { frame _frame; // the interpreter frame we will unpack into int _bci; // raw bci for this vframe bool _reexecute; // whether we should reexecute this bytecode +#if INCLUDE_JVMCI + bool _rethrow; // from ScopeDesc::rethrow_exception() +#endif Method* _method; // the method for this vframe MonitorChunk* _monitors; // active monitors for this vframe StackValueCollection* _locals; @@ -71,7 +74,11 @@ class vframeArrayElement { int bci(void) const; int raw_bci(void) const { return _bci; } + bool should_reexecute(bool is_top_frame, int exec_mode) const; bool should_reexecute(void) const { return _reexecute; } +#if INCLUDE_JVMCI + bool rethrow_exception(void) const { return _rethrow; } +#endif Method* method(void) const { return _method; } From 6e760b9b746eba3d40ec246f3e194ce9f8c5ae29 Mon Sep 17 00:00:00 2001 From: William Kemper Date: Fri, 15 Aug 2025 20:00:01 +0000 Subject: [PATCH 145/580] 8365622: Shenandoah: Fix Shenandoah simple bit map test Reviewed-by: ysr --- .../hotspot/gtest/gc/shenandoah/test_shenandoahSimpleBitMap.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/hotspot/gtest/gc/shenandoah/test_shenandoahSimpleBitMap.cpp b/test/hotspot/gtest/gc/shenandoah/test_shenandoahSimpleBitMap.cpp index 45d6cf47fe0..0d81320befc 100644 --- a/test/hotspot/gtest/gc/shenandoah/test_shenandoahSimpleBitMap.cpp +++ b/test/hotspot/gtest/gc/shenandoah/test_shenandoahSimpleBitMap.cpp @@ -443,7 +443,7 @@ public: }; -TEST(BasicShenandoahSimpleBitMapTest, minimum_test) { +TEST_F(ShenandoahSimpleBitMapTest, minimum_test) { bool result = ShenandoahSimpleBitMapTest::run_test(); ASSERT_EQ(result, true); From b69a3849b21b4bb1e21ad276633de45da6200168 Mon Sep 17 00:00:00 2001 From: Phil Race Date: Fri, 15 Aug 2025 20:02:43 +0000 Subject: [PATCH 146/580] 8365198: Remove unnecessary mention of finalize in ImageIO reader/writer docs Reviewed-by: bchristi, azvegint --- src/java.desktop/share/classes/javax/imageio/ImageReader.java | 3 +-- src/java.desktop/share/classes/javax/imageio/ImageWriter.java | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/java.desktop/share/classes/javax/imageio/ImageReader.java b/src/java.desktop/share/classes/javax/imageio/ImageReader.java index 3488f60b9b7..281e7298354 100644 --- a/src/java.desktop/share/classes/javax/imageio/ImageReader.java +++ b/src/java.desktop/share/classes/javax/imageio/ImageReader.java @@ -2501,8 +2501,7 @@ public abstract class ImageReader { /** * Allows any resources held by this object to be released. The - * result of calling any other method (other than - * {@code finalize}) subsequent to a call to this method + * result of calling any other method subsequent to a call to this method * is undefined. * *

      It is important for applications to call this method when they diff --git a/src/java.desktop/share/classes/javax/imageio/ImageWriter.java b/src/java.desktop/share/classes/javax/imageio/ImageWriter.java index a688d9aea91..fd8cc3a753d 100644 --- a/src/java.desktop/share/classes/javax/imageio/ImageWriter.java +++ b/src/java.desktop/share/classes/javax/imageio/ImageWriter.java @@ -2000,8 +2000,7 @@ public abstract class ImageWriter implements ImageTranscoder { /** * Allows any resources held by this object to be released. The - * result of calling any other method (other than - * {@code finalize}) subsequent to a call to this method + * result of calling any other method subsequent to a call to this method * is undefined. * *

      It is important for applications to call this method when they From b023fea06216d5196592ff5239dc592aa8e34a02 Mon Sep 17 00:00:00 2001 From: Andrew Dinn Date: Fri, 15 Aug 2025 22:12:57 +0000 Subject: [PATCH 147/580] 8365558: Fix stub entry init and blob creation on Zero Reviewed-by: asmehra, kvn --- src/hotspot/cpu/zero/sharedRuntime_zero.cpp | 6 ++-- .../cpu/zero/stubDeclarations_zero.hpp | 4 +-- src/hotspot/share/runtime/sharedRuntime.cpp | 33 +++++++++++++++---- src/hotspot/share/runtime/stubRoutines.cpp | 19 ++++++----- 4 files changed, 42 insertions(+), 20 deletions(-) diff --git a/src/hotspot/cpu/zero/sharedRuntime_zero.cpp b/src/hotspot/cpu/zero/sharedRuntime_zero.cpp index df162fbfa74..b2e406c205b 100644 --- a/src/hotspot/cpu/zero/sharedRuntime_zero.cpp +++ b/src/hotspot/cpu/zero/sharedRuntime_zero.cpp @@ -56,10 +56,10 @@ void SharedRuntime::generate_i2c2i_adapters(MacroAssembler *masm, const BasicType *sig_bt, const VMRegPair *regs, AdapterHandlerEntry* handler) { - // VM expects i2c entry to be always filled. The rest can be unset. + // foil any attempt to call the i2c, c2i or unverified c2i entries handler->set_entry_points(CAST_FROM_FN_PTR(address,zero_null_code_stub), - nullptr, - nullptr, + CAST_FROM_FN_PTR(address,zero_null_code_stub), + CAST_FROM_FN_PTR(address,zero_null_code_stub), nullptr); } diff --git a/src/hotspot/cpu/zero/stubDeclarations_zero.hpp b/src/hotspot/cpu/zero/stubDeclarations_zero.hpp index 3126cf71460..2357bbb5169 100644 --- a/src/hotspot/cpu/zero/stubDeclarations_zero.hpp +++ b/src/hotspot/cpu/zero/stubDeclarations_zero.hpp @@ -37,7 +37,7 @@ do_arch_blob, \ do_arch_entry, \ do_arch_entry_init) \ - do_arch_blob(initial, 32) \ + do_arch_blob(initial, 0) \ #define STUBGEN_CONTINUATION_BLOBS_ARCH_DO(do_stub, \ @@ -58,7 +58,7 @@ do_arch_blob, \ do_arch_entry, \ do_arch_entry_init) \ - do_arch_blob(final, 32) \ + do_arch_blob(final, 0) \ #endif // CPU_ZERO_STUBDECLARATIONS_HPP diff --git a/src/hotspot/share/runtime/sharedRuntime.cpp b/src/hotspot/share/runtime/sharedRuntime.cpp index 5bfddb8a04a..13612496fbd 100644 --- a/src/hotspot/share/runtime/sharedRuntime.cpp +++ b/src/hotspot/share/runtime/sharedRuntime.cpp @@ -2602,19 +2602,24 @@ void AdapterHandlerLibrary::initialize() { BasicType obj_obj_args[] = { T_OBJECT, T_OBJECT }; _obj_obj_arg_handler = create_adapter(obj_obj_arg_blob, 2, obj_obj_args); - assert(no_arg_blob != nullptr && - obj_arg_blob != nullptr && - int_arg_blob != nullptr && - obj_int_arg_blob != nullptr && - obj_obj_arg_blob != nullptr, "Initial adapters must be properly created"); + // we should always get an entry back but we don't have any + // associated blob on Zero + assert(_no_arg_handler != nullptr && + _obj_arg_handler != nullptr && + _int_arg_handler != nullptr && + _obj_int_arg_handler != nullptr && + _obj_obj_arg_handler != nullptr, "Initial adapter handlers must be properly created"); } // Outside of the lock +#ifndef ZERO + // no blobs to register when we are on Zero post_adapter_creation(no_arg_blob, _no_arg_handler); post_adapter_creation(obj_arg_blob, _obj_arg_handler); post_adapter_creation(int_arg_blob, _int_arg_handler); post_adapter_creation(obj_int_arg_blob, _obj_int_arg_handler); post_adapter_creation(obj_obj_arg_blob, _obj_obj_arg_handler); +#endif // ZERO } AdapterHandlerEntry* AdapterHandlerLibrary::new_entry(AdapterFingerPrint* fingerprint) { @@ -2709,12 +2714,15 @@ const char* AdapterHandlerEntry::_entry_names[] = { #ifdef ASSERT void AdapterHandlerLibrary::verify_adapter_sharing(int total_args_passed, BasicType* sig_bt, AdapterHandlerEntry* cached_entry) { + // we can only check for the same code if there is any +#ifndef ZERO AdapterBlob* comparison_blob = nullptr; AdapterHandlerEntry* comparison_entry = create_adapter(comparison_blob, total_args_passed, sig_bt, true); assert(comparison_blob == nullptr, "no blob should be created when creating an adapter for comparison"); assert(comparison_entry->compare_code(cached_entry), "code must match"); // Release the one just created AdapterHandlerEntry::deallocate(comparison_entry); +# endif // ZERO } #endif /* ASSERT*/ @@ -2792,8 +2800,13 @@ AdapterBlob* AdapterHandlerLibrary::lookup_aot_cache(AdapterHandlerEntry* handle void AdapterHandlerLibrary::print_adapter_handler_info(outputStream* st, AdapterHandlerEntry* handler, AdapterBlob* adapter_blob) { ttyLocker ttyl; ResourceMark rm; - int insts_size = adapter_blob->code_size(); + int insts_size; + // on Zero the blob may be null handler->print_adapter_on(tty); + if (adapter_blob == nullptr) { + return; + } + insts_size = adapter_blob->code_size(); st->print_cr("i2c argument handler for: %s %s (%d bytes generated)", handler->fingerprint()->as_basic_args_string(), handler->fingerprint()->as_string(), insts_size); @@ -2834,6 +2847,11 @@ bool AdapterHandlerLibrary::generate_adapter_code(AdapterBlob*& adapter_blob, sig_bt, regs, handler); +#ifdef ZERO + // On zero there is no code to save and no need to create a blob and + // or relocate the handler. + adapter_blob = nullptr; +#else #ifdef ASSERT if (VerifyAdapterSharing) { handler->save_code(buf->code_begin(), buffer.insts_size()); @@ -2869,12 +2887,15 @@ bool AdapterHandlerLibrary::generate_adapter_code(AdapterBlob*& adapter_blob, assert(success || !AOTCodeCache::is_dumping_adapter(), "caching of adapter must be disabled"); } handler->relocate(adapter_blob->content_begin()); +#endif // ZERO + #ifndef PRODUCT // debugging support if (PrintAdapterHandlers || PrintStubCode) { print_adapter_handler_info(tty, handler, adapter_blob); } #endif + return true; } diff --git a/src/hotspot/share/runtime/stubRoutines.cpp b/src/hotspot/share/runtime/stubRoutines.cpp index 86975f7d0a6..82608737be8 100644 --- a/src/hotspot/share/runtime/stubRoutines.cpp +++ b/src/hotspot/share/runtime/stubRoutines.cpp @@ -168,14 +168,6 @@ static BufferBlob* initialize_stubs(BlobId blob_id, const char* assert_msg) { assert(StubInfo::is_stubgen(blob_id), "not a stubgen blob %s", StubInfo::name(blob_id)); ResourceMark rm; - if (code_size == 0) { - LogTarget(Info, stubs) lt; - if (lt.is_enabled()) { - LogStream ls(lt); - ls.print_cr("%s\t not generated", buffer_name); - } - return nullptr; - } TraceTime timer(timer_msg, TRACETIME_LOG(Info, startuptime)); // Add extra space for large CodeEntryAlignment int size = code_size + CodeEntryAlignment * max_aligned_stubs; @@ -196,9 +188,18 @@ static BufferBlob* initialize_stubs(BlobId blob_id, } CodeBuffer buffer(stubs_code); StubGenerator_generate(&buffer, blob_id); + if (code_size == 0) { + assert(buffer.insts_size() == 0, "should not write into buffer when bob size declared as 0"); + LogTarget(Info, stubs) lt; + if (lt.is_enabled()) { + LogStream ls(lt); + ls.print_cr("%s\t not generated", buffer_name); + } + return nullptr; + } // When new stubs added we need to make sure there is some space left // to catch situation when we should increase size again. - assert(code_size == 0 || buffer.insts_remaining() > 200, + assert(buffer.insts_remaining() > 200, "increase %s, code_size: %d, used: %d, free: %d", assert_msg, code_size, buffer.total_content_size(), buffer.insts_remaining()); From a70521c62e0841895d71cce2c872bd12f1183e33 Mon Sep 17 00:00:00 2001 From: Leonid Mesnik Date: Fri, 15 Aug 2025 22:45:01 +0000 Subject: [PATCH 148/580] 8364973: Add JVMTI stress testing mode Reviewed-by: erikj, ihse, sspitsyn --- doc/starting-next-release.html | 5 +- doc/testing.html | 6 + doc/testing.md | 7 + make/RunTests.gmk | 14 +- make/RunTestsPrebuiltSpec.gmk | 16 + .../jtreg/ProblemList-jvmti-stress-agent.txt | 101 ++ .../lib/ir_framework/TestFramework.java | 13 + test/jdk/ProblemList-jvmti-stress-agent.txt | 47 + test/jtreg-ext/requires/VMProps.java | 7 +- .../test/lib/jvmti/libJvmtiStressAgent.cpp | 987 ++++++++++++++++++ 10 files changed, 1196 insertions(+), 7 deletions(-) create mode 100644 test/hotspot/jtreg/ProblemList-jvmti-stress-agent.txt create mode 100644 test/jdk/ProblemList-jvmti-stress-agent.txt create mode 100644 test/lib/jdk/test/lib/jvmti/libJvmtiStressAgent.cpp diff --git a/doc/starting-next-release.html b/doc/starting-next-release.html index 421229f9fbc..6cffdf38b0f 100644 --- a/doc/starting-next-release.html +++ b/doc/starting-next-release.html @@ -11,11 +11,8 @@ div.columns{display: flex; gap: min(4vw, 1.5em);} div.column{flex: auto; overflow-x: auto;} div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;} - /* The extra [class] is a hack that increases specificity enough to - override a similar rule in reveal.js */ - ul.task-list[class]{list-style: none;} + ul.task-list{list-style: none;} ul.task-list li input[type="checkbox"] { - font-size: inherit; width: 0.8em; margin: 0 0.8em 0.2em -1.6em; vertical-align: middle; diff --git a/doc/testing.html b/doc/testing.html index f75e0da309e..fa774aa312f 100644 --- a/doc/testing.html +++ b/doc/testing.html @@ -458,6 +458,12 @@ class, named Virtual, is currently part of the JDK build in the test/jtreg_test_thread_factory/ directory. This class gets compiled during the test image build. The implementation of the Virtual class creates a new virtual thread for executing each test class.

      +

      JVMTI_STRESS_AGENT

      +

      Executes JTReg tests with JVM TI stress agent. The stress agent is +the part of test library and located in +test/lib/jdk/test/lib/jvmti/libJvmtiStressAgent.cpp. The +value of this argument is set as JVM TI agent options. This mode uses +ProblemList-jvmti-stress-agent.txt as an additional exclude list.

      TEST_MODE

      The test mode (agentvm or othervm).

      Defaults to agentvm.

      diff --git a/doc/testing.md b/doc/testing.md index 525b85a8438..4cfc36c85f9 100644 --- a/doc/testing.md +++ b/doc/testing.md @@ -401,6 +401,13 @@ the `test/jtreg_test_thread_factory/` directory. This class gets compiled during the test image build. The implementation of the Virtual class creates a new virtual thread for executing each test class. +#### JVMTI_STRESS_AGENT + +Executes JTReg tests with JVM TI stress agent. The stress agent is the part of +test library and located in `test/lib/jdk/test/lib/jvmti/libJvmtiStressAgent.cpp`. +The value of this argument is set as JVM TI agent options. +This mode uses ProblemList-jvmti-stress-agent.txt as an additional exclude list. + #### TEST_MODE The test mode (`agentvm` or `othervm`). diff --git a/make/RunTests.gmk b/make/RunTests.gmk index 574fd869092..46f9a2e4047 100644 --- a/make/RunTests.gmk +++ b/make/RunTests.gmk @@ -204,8 +204,9 @@ $(eval $(call SetTestOpt,AOT_JDK,JTREG)) $(eval $(call ParseKeywordVariable, JTREG, \ SINGLE_KEYWORDS := JOBS TIMEOUT_FACTOR FAILURE_HANDLER_TIMEOUT \ - TEST_MODE ASSERT VERBOSE RETAIN TEST_THREAD_FACTORY MAX_MEM RUN_PROBLEM_LISTS \ - RETRY_COUNT REPEAT_COUNT MAX_OUTPUT REPORT AOT_JDK $(CUSTOM_JTREG_SINGLE_KEYWORDS), \ + TEST_MODE ASSERT VERBOSE RETAIN TEST_THREAD_FACTORY JVMTI_STRESS_AGENT \ + MAX_MEM RUN_PROBLEM_LISTS RETRY_COUNT REPEAT_COUNT MAX_OUTPUT REPORT \ + AOT_JDK $(CUSTOM_JTREG_SINGLE_KEYWORDS), \ STRING_KEYWORDS := OPTIONS JAVA_OPTIONS VM_OPTIONS KEYWORDS \ EXTRA_PROBLEM_LISTS LAUNCHER_OPTIONS \ $(CUSTOM_JTREG_STRING_KEYWORDS), \ @@ -876,6 +877,15 @@ define SetupRunJtregTestBody )) endif + ifneq ($$(JTREG_JVMTI_STRESS_AGENT), ) + AGENT := $$(LIBRARY_PREFIX)JvmtiStressAgent$$(SHARED_LIBRARY_SUFFIX)=$$(JTREG_JVMTI_STRESS_AGENT) + $1_JTREG_BASIC_OPTIONS += -javaoption:'-agentpath:$(TEST_IMAGE_DIR)/hotspot/jtreg/native/$$(AGENT)' + $1_JTREG_BASIC_OPTIONS += $$(addprefix $$(JTREG_PROBLEM_LIST_PREFIX), $$(wildcard \ + $$(addprefix $$($1_TEST_ROOT)/, ProblemList-jvmti-stress-agent.txt) \ + )) + endif + + ifneq ($$(JTREG_LAUNCHER_OPTIONS), ) $1_JTREG_LAUNCHER_OPTIONS += $$(JTREG_LAUNCHER_OPTIONS) endif diff --git a/make/RunTestsPrebuiltSpec.gmk b/make/RunTestsPrebuiltSpec.gmk index 03c877a4277..e9fd901c9e1 100644 --- a/make/RunTestsPrebuiltSpec.gmk +++ b/make/RunTestsPrebuiltSpec.gmk @@ -176,3 +176,19 @@ ULIMIT := ulimit ifeq ($(OPENJDK_BUILD_OS), windows) PATHTOOL := cygpath endif + +# These settings are needed to run testing with jvmti agent +ifeq ($(OPENJDK_BUILD_OS), linux) + LIBRARY_PREFIX := lib + SHARED_LIBRARY_SUFFIX := .so +endif + +ifeq ($(OPENJDK_BUILD_OS), windows) + LIBRARY_PREFIX := + SHARED_LIBRARY_SUFFIX := .dll +endif + +ifeq ($(OPENJDK_BUILD_OS), macosx) + LIBRARY_PREFIX := lib + SHARED_LIBRARY_SUFFIX := .dylib +endif diff --git a/test/hotspot/jtreg/ProblemList-jvmti-stress-agent.txt b/test/hotspot/jtreg/ProblemList-jvmti-stress-agent.txt new file mode 100644 index 00000000000..636d02cb8b0 --- /dev/null +++ b/test/hotspot/jtreg/ProblemList-jvmti-stress-agent.txt @@ -0,0 +1,101 @@ +# +# 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. +# + +############################################################################# +# +# List of quarantined tests failing with jvmti stress agent in any mode. +# +############################################################################# + + +compiler/macronodes/TestTopInMacroElimination.java 8362832 generic-all + +gc/stringdedup/TestStringDeduplicationAgeThreshold.java 8362562 generic-all +gc/stringdedup/TestStringDeduplicationInterned.java 8362562 generic-all +gc/stringdedup/TestStringDeduplicationPrintOptions.java 8362562 generic-all + + +serviceability/jvmti/events/SingleStep/singlestep02/singlestep02.java 8362350 generic-all +vmTestbase/nsk/jvmti/scenarios/events/EM02/em02t007/TestDescription.java 8362350 generic-all + +# Incompatbile tests + +# IR +compiler/loopopts/superword/TestDependencyOffsets.java#avx1-v016-A 0000000 generic-all +compiler/loopopts/superword/TestDependencyOffsets.java#avx1-v016-U 0000000 generic-all +compiler/loopopts/superword/TestDependencyOffsets.java#avx1-v032-A 0000000 generic-all +compiler/loopopts/superword/TestDependencyOffsets.java#avx1-v032-U 0000000 generic-all +compiler/loopopts/superword/TestDependencyOffsets.java#avx2-v016-A 0000000 generic-all +compiler/loopopts/superword/TestDependencyOffsets.java#avx2-v016-U 0000000 generic-all +compiler/loopopts/superword/TestDependencyOffsets.java#avx2-v032-A 0000000 generic-all +compiler/loopopts/superword/TestDependencyOffsets.java#avx2-v032-U 0000000 generic-all +compiler/loopopts/superword/TestDependencyOffsets.java#sse4-v004-A 0000000 generic-all +compiler/loopopts/superword/TestDependencyOffsets.java#sse4-v004-U 0000000 generic-all +compiler/loopopts/superword/TestDependencyOffsets.java#sse4-v008-A 0000000 generic-all +compiler/loopopts/superword/TestDependencyOffsets.java#sse4-v008-U 0000000 generic-all +compiler/loopopts/superword/TestDependencyOffsets.java#sse4-v016-A 0000000 generic-all +compiler/loopopts/superword/TestDependencyOffsets.java#sse4-v016-U 0000000 generic-all +compiler/loopopts/superword/TestDependencyOffsets.java#vanilla-A 0000000 generic-all +compiler/loopopts/superword/TestDependencyOffsets.java#vanilla-U 0000000 generic-all +compiler/loopopts/superword/TestDependencyOffsets.java#vec-v004-A 0000000 generic-all +compiler/loopopts/superword/TestDependencyOffsets.java#vec-v004-U 0000000 generic-all +compiler/loopopts/superword/TestDependencyOffsets.java#vec-v008-A 0000000 generic-all +compiler/loopopts/superword/TestDependencyOffsets.java#vec-v008-U 0000000 generic-all +compiler/loopopts/superword/TestDependencyOffsets.java#vec-v016-A 0000000 generic-all +compiler/loopopts/superword/TestDependencyOffsets.java#vec-v016-U 0000000 generic-all +compiler/loopopts/superword/TestDependencyOffsets.java#vec-v032-A 0000000 generic-all +compiler/loopopts/superword/TestDependencyOffsets.java#vec-v032-U 0000000 generic-all +compiler/loopopts/superword/TestDependencyOffsets.java#vec-v064-A 0000000 generic-all +compiler/loopopts/superword/TestDependencyOffsets.java#vec-v064-U 0000000 generic-all + +# Requires solo jvmti capabilities + +compiler/jvmci/events/JvmciShutdownEventTest.java 0000000 generic-all + +# jdwp +runtime/6294277/SourceDebugExtension.java 0000000 generic-all + +# heap stats +serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorArrayAllSampledTest.java 0000000 generic-all +serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorEventOnOffTest.java 0000000 generic-all +serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorGCParallelTest.java 0000000 generic-all +serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorGCSerialTest.java 0000000 generic-all +serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorGCTest.java 0000000 generic-all +serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorIllegalArgumentTest.java 0000000 generic-all +serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorInitialAllocationTest.java 0000000 generic-all +serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorInterpreterArrayTest.java 0000000 generic-all +serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorInterpreterObjectTest.java 0000000 generic-all +serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorMultiArrayTest.java 0000000 generic-all +serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorNoCapabilityTest.java 0000000 generic-all +serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorRecursiveTest.java 0000000 generic-all +serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorStatArrayCorrectnessTest.java 0000000 generic-all +serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorStatIntervalTest.java 0000000 generic-all +serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorStatObjectCorrectnessTest.java 0000000 generic-all +serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorStatSimpleTest.java 0000000 generic-all +serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorTest.java 0000000 generic-all +serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorThreadDisabledTest.java 0000000 generic-all +serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorThreadOnOffTest.java 0000000 generic-all +serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorThreadTest.java 0000000 generic-all +serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorTwoAgentsTest.java 0000000 generic-all +serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorVMEventsTest.java#id0 0000000 generic-all +serviceability/jvmti/HeapMonitor/MyPackage/HeapMonitorVMEventsTest.java#id1 0000000 generic-all diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/TestFramework.java b/test/hotspot/jtreg/compiler/lib/ir_framework/TestFramework.java index 6782bd68fed..8e509c66c9a 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/TestFramework.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/TestFramework.java @@ -36,6 +36,7 @@ import jdk.test.lib.Platform; import jdk.test.lib.Utils; import jdk.test.lib.helpers.ClassFileInstaller; import jdk.test.whitebox.WhiteBox; +import jtreg.SkippedException; import java.io.PrintWriter; import java.io.StringWriter; @@ -349,6 +350,7 @@ public class TestFramework { if (shouldInstallWhiteBox()) { installWhiteBox(); } + checkCompatibleFlags(); checkIRRuleCompilePhasesFormat(); disableIRVerificationIfNotFeasible(); @@ -775,12 +777,23 @@ public class TestFramework { return Arrays.stream(testClass.getDeclaredMethods()).anyMatch(m -> m.getAnnotationsByType(IR.class).length > 0); } + private void checkCompatibleFlags() { + for (String flag : Utils.getTestJavaOpts()) { + if (flag.contains("-agentpath")) { + throw new SkippedException("Can't run test with agent."); + } + } + } + private List anyNonWhitelistedJTregVMAndJavaOptsFlags() { List flags = Arrays.stream(Utils.getTestJavaOpts()) .map(s -> s.replaceFirst("-XX:[+|-]?|-(?=[^D|^e])", "")) .collect(Collectors.toList()); List nonWhiteListedFlags = new ArrayList(); for (String flag : flags) { + if (flag.contains("agentpath")) { + throw new SkippedException("Can't run test with -javaagent"); + } // Property flags (prefix -D), -ea and -esa are whitelisted. if (!flag.startsWith("-D") && !flag.startsWith("-e") && JTREG_WHITELIST_FLAGS.stream().noneMatch(flag::contains)) { // Found VM flag that is not whitelisted diff --git a/test/jdk/ProblemList-jvmti-stress-agent.txt b/test/jdk/ProblemList-jvmti-stress-agent.txt new file mode 100644 index 00000000000..4a6e80a9402 --- /dev/null +++ b/test/jdk/ProblemList-jvmti-stress-agent.txt @@ -0,0 +1,47 @@ +# +# 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. +# + +############################################################################# +# +# List of quarantined tests failing with jvmti stress agent in any mode. +# +############################################################################# + + +sun/security/ssl/SSLEngineImpl/SSLEngineKeyLimit.java 8362658 generic-all +sun/security/ssl/SSLSessionImpl/MultiNSTClient.java 8362658 generic-all +sun/security/ssl/SSLSessionImpl/MultiNSTNoSessionCreation.java 8362658 generic-all +sun/security/ssl/SSLSessionImpl/MultiNSTParallel.java 8362658 generic-all +sun/security/ssl/SSLSessionImpl/MultiNSTSequence.java 8362658 generic-all +sun/security/ssl/SSLSessionImpl/ResumptionUpdateBoundValues.java 8362658 generic-all +sun/security/ssl/SSLSocketImpl/SSLSocketKeyLimit.java 8362658 generic-all + + +# List of tests incompatible with jvmti stress agent or requiring more investigation + +com/sun/jdi/EATests.java#id0 0000000 generic-all +com/sun/jdi/ThreadMemoryLeakTest.java 0000000 generic-all + +# weak referenced are not cleared +java/lang/WeakPairMap/Driver.java 0000000 generic-all +java/lang/ref/ReachabilityFenceTest.java 0000000 generic-all diff --git a/test/jtreg-ext/requires/VMProps.java b/test/jtreg-ext/requires/VMProps.java index 5ac0bea3937..74ea525b415 100644 --- a/test/jtreg-ext/requires/VMProps.java +++ b/test/jtreg-ext/requires/VMProps.java @@ -422,7 +422,12 @@ public class VMProps implements Callable> { * @return true if CDS is supported by the VM to be tested. */ protected String vmCDS() { - return "" + WB.isCDSIncluded(); + boolean noJvmtiAdded = allFlags() + .filter(s -> s.startsWith("-agentpath")) + .findAny() + .isEmpty(); + + return "" + (noJvmtiAdded && WB.isCDSIncluded()); } /** diff --git a/test/lib/jdk/test/lib/jvmti/libJvmtiStressAgent.cpp b/test/lib/jdk/test/lib/jvmti/libJvmtiStressAgent.cpp new file mode 100644 index 00000000000..c760edc66d6 --- /dev/null +++ b/test/lib/jdk/test/lib/jvmti/libJvmtiStressAgent.cpp @@ -0,0 +1,987 @@ +/* + * 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. + */ + +#include "jvmti.h" +#include "jvmti_common.hpp" + +/* +* + * The jtreg tests might be executed with this agent to ensure that corresponding + * JDK functionality is not broken. + * + * IMPORTANT + * The tests that are incompatible with agent should be placed + * into ProblemList-jvmti-stress-agent.txt with 000000 bug. + * + * Test supports 2 modes: + * - standard, where the agent doesn't require debugging capabilities + * - debug, where the agent additionally test debug-related functionality + * The debug mode is incompatible with debugger tests and debug jvmti tests. + * The standard mode should be compatible with all tests except problemlisted. + * + * The JVMTI agent starts jvmti agent tread that enable/disable different + * events and call different jvmti functions concurrently with test execution. + * + * The main requirement is to don't change test behaviour. + * + */ + +#define JVMTI_AGENT_NAME "JvmtiStressAgent" + +/* Global settings and some statistics counters */ +typedef struct { + + /* Verbose logging support */ + jboolean is_verbose; + + /* If debugging functionality could be used. Set from agent args.*/ + jboolean is_debugger_enabled; + + /* Monitor and flags to synchronize agent completion.*/ + jrawMonitorID finished_lock; + volatile jboolean request_agent_thread_stop; + volatile jboolean is_agent_finished; + + /* Some settings configured in gdata_init(). */ + + /* If agent enabled or not. */ + jboolean is_tracing_enabled; + + /* If events testing is enabled. */ + jboolean are_events_enabled; + + /* If interponly and frequent events testing is enabled. */ + jboolean are_frequent_events_enabled; + + /* Should we iterate heap */ + jboolean is_heap_iterate_enabled; + + /* Is Heap sampling enabled */ + jboolean is_heap_sampling_enabled; + + jint heap_sampling_interval; + jint events_interval; + jint frequent_events_interval; + + /* Excluded events */ + jint* events_excluded; + jsize events_excluded_size; + + /* Event statistics */ + + /* The counters are racy intentionally to avoid synchronization. */ + jlong cbBreakpoint; + jlong cbClassFileLoadHook; + jlong cbClassLoad; + jlong cbClassPrepare; + jlong cbCompiledMethodLoad; + jlong cbCompiledMethodUnload; + jlong cbDataDumpRequest; + jlong cbDynamicCodeGenerated; + jlong cbException; + jlong cbExceptionCatch; + jlong cbFieldAccess; + jlong cbFieldModification; + jlong cbFramePop; + jlong cbGarbageCollectionFinish; + jlong cbGarbageCollectionStart; + jlong cbMethodEntry; + jlong cbMethodExit; + jlong cbMonitorContendedEnter; + jlong cbMonitorContendedEntered; + jlong cbMonitorWait; + jlong cbMonitorWaited; + jlong cbNativeMethodBind; + jlong cbObjectFree; + jlong cbResourceExhausted; + jlong cbSampledObjectAlloc; + jlong cbSingleStep; + jlong cbThreadEnd; + jlong cbThreadStart; + jlong cbVirtualThreadEnd; + jlong cbVirtualThreadStart; + jlong cbVMDeath; + jlong cbVMInit; + jlong cbVMObjectAlloc; + + /* Inspector statistics are intentionally racy. */ + jlong inspectedMethods; + jlong inspectedVariables; + + /* File for debug output, agent shouldn't write into stdout. */ + FILE* log_file; +} GlobalData; + +GlobalData *gdata; + +static GlobalData* +gdata_init(jboolean is_debugger_enabled, jboolean is_verbose) { + static GlobalData data; + (void) memset(&data, 0, sizeof (GlobalData)); + + data.is_debugger_enabled = is_debugger_enabled; + data.is_verbose = is_verbose; + + data.request_agent_thread_stop = JNI_FALSE; + data.is_agent_finished = JNI_FALSE; + + /* Set jvmti stress properties */ + data.heap_sampling_interval = 1000; + data.frequent_events_interval = 10; + + data.is_tracing_enabled = JNI_TRUE; + data.are_events_enabled = JNI_TRUE; + data.are_frequent_events_enabled = JNI_TRUE; + // disabled so far + data.is_heap_iterate_enabled = JNI_FALSE; + data.is_heap_sampling_enabled = JNI_FALSE; + + + if (data.is_debugger_enabled) { + data.events_excluded_size = 0; + data.events_excluded = nullptr; + } else { + data.events_excluded_size = 4; + data.events_excluded = new jint[4] { + JVMTI_EVENT_BREAKPOINT, + JVMTI_EVENT_FIELD_ACCESS, + JVMTI_EVENT_FIELD_MODIFICATION, + JVMTI_EVENT_SAMPLED_OBJECT_ALLOC, + }; + } + if (data.is_verbose) { + data.log_file = fopen("JvmtiStressAgent.out", "w"); + } + + return &data; +} + +void +gdata_close() { + free(gdata->events_excluded); + if (gdata->is_verbose) { + fclose(gdata->log_file); + } +} + +// Internal buffer length for all messages +#define MESSAGE_LIMIT 16384 + +void +debug(const char* format, ...) { + if (!gdata->is_verbose) { + return; + } + char dest[MESSAGE_LIMIT]; + va_list argptr; + va_start(argptr, format); + vsnprintf(dest, MESSAGE_LIMIT, format, argptr); + va_end(argptr); + // Enable if needed, tests might fail with unexpected output + //printf("%s\n", dest); + fprintf(gdata->log_file, "%s\n", dest); + fflush(gdata->log_file); +} + +/* Some helper functions to start/stop jvmti stress agent thread. */ +void +check_jni_exception(JNIEnv *jni, const char *message) { + jobject exception = jni->ExceptionOccurred(); + if (exception != nullptr) { + jni->ExceptionDescribe(); + fatal(jni, message); + } +} + +jclass +find_class(JNIEnv *jni, const char *name) { + char message[MESSAGE_LIMIT]; + jclass clazz = jni->FindClass(name); + snprintf(message, MESSAGE_LIMIT, "Failed to find class %s.", name); + check_jni_exception(jni, message); + return clazz; +} + +jmethodID +get_method_id(JNIEnv *jni, jclass clazz, const char *name, const char *sig) { + char message[MESSAGE_LIMIT]; + jmethodID method = jni->GetMethodID(clazz, name, sig); + snprintf(message, MESSAGE_LIMIT, "Failed to find method %s.", name); + check_jni_exception(jni, message); + return method; +} + +void +create_agent_thread(jvmtiEnv *jvmti, JNIEnv *jni, const char *name, jvmtiStartFunction func) { + + check_jni_exception(jni, "JNIException before creating Agent Thread."); + jclass clazz = find_class(jni, "java/lang/Thread"); + jmethodID thread_ctor = get_method_id(jni, clazz, "", + "(Ljava/lang/String;)V"); + + jstring name_utf = jni->NewStringUTF(name); + check_jni_exception(jni, "Error creating utf name of thread."); + + jthread thread = jni->NewObject(clazz, thread_ctor, name_utf); + check_jni_exception(jni, "Error during instantiation of Thread object."); + jvmtiError err = jvmti->RunAgentThread( + thread, func, nullptr, JVMTI_THREAD_NORM_PRIORITY); + check_jvmti_status(jni, err, "RunAgentThread"); +} + +/* + * The method blocks execution until agent thread finishes. + * Should be executed during VMDeath to don't run JVMTI functionality + * during dead phase. + */ +void +request_agent_thread_stop_and_wait(jvmtiEnv *jvmti, JNIEnv *jni) { + RawMonitorLocker rml(jvmti, jni, gdata->finished_lock); + gdata->request_agent_thread_stop = JNI_TRUE; + while (!gdata->is_agent_finished) { + rml.wait(1000); + } + debug("Native agent stopped"); +} + +/* + * The method is called by agent thread to ensure that thread correctly exits. + */ +static jboolean +should_stop(jvmtiEnv *jvmti, JNIEnv *jni) { + jboolean should_stop = JNI_FALSE; + RawMonitorLocker rml(jvmti, jni, gdata->finished_lock); + should_stop = gdata->request_agent_thread_stop; + if (should_stop == JNI_TRUE) { + gdata->is_agent_finished = JNI_TRUE; + rml.notify_all(); + } + return should_stop; +} + +/* + * Agent stress functions. The agent is stopped in VMDeath only and should be + * always ready to get JVMTI_ERROR_THREAD_NOT_ALIVE error. + */ + +/* Read stack, frames, method, variables, etc. */ +static void +walk_stack(jvmtiEnv *jvmti, JNIEnv *jni, jthread thread) { + jvmtiError err = JVMTI_ERROR_NONE; + debug("In walk_stack: %p", thread); + + jvmtiFrameInfo frames[5]; + jint count = 0; + err = jvmti->GetStackTrace(thread, 0, 5, frames, &count); + if (err == JVMTI_ERROR_THREAD_NOT_ALIVE || err == JVMTI_ERROR_WRONG_PHASE) { + return; + } + check_jvmti_error(err, "GetStackTrace"); + + debug("Stack depth: %d", count); + + for (int frame_index = 0; frame_index < count; frame_index++) { + char *method_name = nullptr; + jint method_modifiers = 0; + err = jvmti->GetMethodName(frames[frame_index].method, &method_name, nullptr, nullptr); + if (err == JVMTI_ERROR_WRONG_PHASE) { + return; + } + check_jvmti_status(jni, err, "GetMethodName"); + + err = jvmti->GetMethodModifiers(frames[frame_index].method, &method_modifiers); + if (err == JVMTI_ERROR_WRONG_PHASE) { + return; + } + check_jvmti_status(jni, err, "GetMethodModifiers"); + + debug("Inspecting method: %s, %d", method_name, method_modifiers); + deallocate(jvmti, jni, method_name); + + jvmtiLocalVariableEntry* table = nullptr; + jint entry_count = 0; + err = jvmti->GetLocalVariableTable(frames[frame_index].method, &entry_count, &table); + if (err == JVMTI_ERROR_NATIVE_METHOD || err == JVMTI_ERROR_ABSENT_INFORMATION + || err == JVMTI_ERROR_WRONG_PHASE) { + continue; + } + check_jvmti_status(jni, err, "GetLocalVariableTable"); + + gdata->inspectedMethods += 1; + gdata->inspectedVariables += entry_count; + + debug("Variables: "); + for (int cnt = 0; cnt < entry_count; cnt++) { + debug(" %s %d", table[cnt].name, table[cnt].slot); + deallocate(jvmti, jni, table[cnt].name); + deallocate(jvmti, jni, table[cnt].signature); + deallocate(jvmti, jni, table[cnt].generic_signature); + } + deallocate(jvmti, jni, table); + } + debug("---- End of stack inspection %d -----", count); +} + +/* Iterate with walk_stack through all thread. */ +static void JNICALL +walk_all_threads_stacks(jvmtiEnv *jvmti, JNIEnv *jni) { + jint threads_count = 0; + jthread *threads = nullptr; + jvmtiError err = JVMTI_ERROR_NONE; + debug("Inspect: Starting cycle..."); + err = jvmti->GetAllThreads(&threads_count, &threads); + if (err == JVMTI_ERROR_WRONG_PHASE) { + return; + } + check_jvmti_status(jni, err, "GetAllThreads"); + for (int t = 0; t < (int)threads_count; t++) { + jvmtiThreadInfo info; + debug("Inspecting thread num %d at addr [%p]",t, threads[t]); + err = jvmti->GetThreadInfo(threads[t], &info); + if (err == JVMTI_ERROR_WRONG_PHASE) { + return; + } + check_jvmti_status(jni, err, "GetThreadInfo"); + // Skip agent thread itself and JFR threads to avoid potential deadlocks + if (strstr(info.name, JVMTI_AGENT_NAME) == nullptr + && strstr(info.name, "JFR") == nullptr) { + // The non-intrusive actions are allowed to ensure that results of target + // thread are not affected. + jthread thread = threads[t]; + walk_stack(jvmti, jni, thread); + + // Suspend/resume are solo capabilities and are treated like debugging + if (gdata->is_debugger_enabled) { + debug("Inspect: Trying to suspend thread %s", info.name); + err = jvmti->SuspendThread(thread); + if (err == JVMTI_ERROR_WRONG_PHASE) { + return; + } + if (err == JVMTI_ERROR_THREAD_NOT_ALIVE) { + debug("Inspect: thread %s is not alive. Skipping.", info.name); + continue; + } + check_jvmti_status(jni, err, "SuspendThread"); + debug("Inspect: Suspended thread %s", info.name); + + walk_stack(jvmti, jni, thread); + + debug("Inspect: Trying to resume thread %s", info.name); + err = jvmti->ResumeThread(thread); + if (err == JVMTI_ERROR_WRONG_PHASE) { + return; + } + check_jvmti_status(jni, err, "ResumeThread"); + debug("Inspect: Resumed thread %s", info.name); + } + + } + deallocate(jvmti, jni, info.name); + jni->DeleteLocalRef(info.thread_group); + jni->DeleteLocalRef(info.context_class_loader); + jni->DeleteLocalRef(threads[t]); + } + deallocate(jvmti, jni, threads); +} + +/* Heap inspection helpers. */ +static jint JNICALL +heap_iteration_callback(jlong class_tag, jlong size, jlong* tag_ptr, jint length, void* user_data) { + int* count = (int*) user_data; + *count += 1; + return JVMTI_VISIT_OBJECTS; +} + +static jint +get_heap_info(jvmtiEnv *jvmti, JNIEnv *jni, jclass klass) { + jvmtiError err = JVMTI_ERROR_NONE; + int count = 0; + jvmtiHeapCallbacks callbacks; + (void) memset(&callbacks, 0, sizeof (callbacks)); + callbacks.heap_iteration_callback = &heap_iteration_callback; + err = jvmti->IterateThroughHeap(0, klass, &callbacks, &count); + if (err == JVMTI_ERROR_WRONG_PHASE) { + return count; + } + check_jvmti_status(jni, err, "IterateThroughHeap"); + return count; +} + + +/* + * Events testing helper functions. + */ + + +int +is_event_frequent(int event) { + // Should include all interpreter-only events and all frequent events. + return event == JVMTI_EVENT_SINGLE_STEP + || event == JVMTI_EVENT_METHOD_ENTRY + || event == JVMTI_EVENT_METHOD_EXIT + || event == JVMTI_EVENT_FRAME_POP + || event == JVMTI_EVENT_FIELD_ACCESS + || event == JVMTI_EVENT_FIELD_MODIFICATION + || event == JVMTI_EVENT_EXCEPTION_CATCH + || event == JVMTI_EVENT_EXCEPTION + ; +} + +int +is_event_excluded(int event) { + for (int i = 0; i < gdata->events_excluded_size; i++) { + if (event == gdata->events_excluded[i]) { + return JNI_TRUE; + } + } + return JNI_FALSE; +} + +static void +enable_events(jvmtiEnv *jvmti, jboolean update_frequent_events) { + debug("Enabling events\n"); + for(int event = JVMTI_MIN_EVENT_TYPE_VAL; event < JVMTI_MAX_EVENT_TYPE_VAL; event++) { + if (is_event_excluded(event)) { + debug("Event %d excluded.", event); + continue; + } + if (is_event_frequent(event) != update_frequent_events ) { + debug("Event %d is not enabled as frequent/slow.", event); + continue; + } + jvmtiError err = jvmti->SetEventNotificationMode(JVMTI_ENABLE, + static_cast(event), nullptr); + if (err == JVMTI_ERROR_WRONG_PHASE) { + return; + } + check_jvmti_error(err, "SetEventNotificationMode"); + } + debug("Enabling events done\n"); +} + +static void +enable_frequent_events(jvmtiEnv *jvmti) { + enable_events(jvmti, JNI_TRUE); +} + +static void +enable_common_events(jvmtiEnv *jvmti) { + enable_events(jvmti,JNI_FALSE); +} + + +static void +disable_all_events(jvmtiEnv *jvmti) { + jvmtiError err = JVMTI_ERROR_NONE; + for (int event = JVMTI_MIN_EVENT_TYPE_VAL; event < JVMTI_MAX_EVENT_TYPE_VAL; event++) { + // VM_DEATH is used to stop agent + if (event == JVMTI_EVENT_VM_DEATH) { + continue; + } + err = jvmti->SetEventNotificationMode(JVMTI_DISABLE, static_cast(event), nullptr); + if (err == JVMTI_ERROR_WRONG_PHASE) { + return; + } + check_jvmti_error(err, "SetEventNotificationMode"); + } +} + +/* + * The JVMTI agent main loop. + */ + +static void JNICALL +stress_agent(jvmtiEnv *jvmti, JNIEnv *jni, void *p) { + jvmtiError err = JVMTI_ERROR_NONE; + debug("Debugger: Thread started."); + while (!should_stop(jvmti, jni)) { + + if (gdata->are_events_enabled) { + enable_common_events(jvmti); + } + + // Iterate through heap and get some statistics + if (gdata->is_heap_iterate_enabled) { + jclass kls = find_class(jni, "java/lang/String"); + jlong obj_count = get_heap_info(jvmti, jni, kls); + debug("Debugger: Heap info: %d", obj_count); + } + + + // requires can_generate_sampled_object_alloc_events + // which is solo capability + if (gdata->is_heap_sampling_enabled) { + err = jvmti->SetHeapSamplingInterval(gdata->heap_sampling_interval); + if (err == JVMTI_ERROR_WRONG_PHASE) { + return; + } + check_jvmti_status(jni, err, "SetHeapSamplingInterval"); + } + + if (gdata->is_tracing_enabled) { + walk_all_threads_stacks(jvmti, jni); + } + + sleep_ms(gdata->events_interval); + + err = jvmti->SetHeapSamplingInterval(0); + if (err == JVMTI_ERROR_WRONG_PHASE) { + return; + } + if (gdata->is_heap_sampling_enabled) { + check_jvmti_status(jni, err, "SetHeapSamplingInterval"); + } + + if (gdata->are_frequent_events_enabled) { + enable_frequent_events(jvmti); + sleep_ms(gdata->frequent_events_interval); + } + disable_all_events(jvmti); + sleep_ms(gdata->events_interval); + } + debug("Debugger: Thread finished."); +} + + +/* + * Events section. + * Most of the events just increase counter and print debug info. + * The VMInit/VMDeath are also start and stop jvmti stress agent. + */ + +static void +register_event(jlong *event) { + (*event)++; +} + +static void JNICALL +cbVMInit(jvmtiEnv *jvmti, JNIEnv *jni, jthread thread) { + register_event(&gdata->cbVMInit); + debug("Event cbVMInit\n"); + create_agent_thread(jvmti, jni, JVMTI_AGENT_NAME, &stress_agent); +} + +static void JNICALL +cbVMDeath(jvmtiEnv *jvmti, JNIEnv *jni) { + register_event(&gdata->cbVMDeath); + debug("Event cbVMDeath\n"); + request_agent_thread_stop_and_wait(jvmti, jni); + destroy_raw_monitor(jvmti, jni, gdata->finished_lock); +} + +static void JNICALL +cbThreadStart(jvmtiEnv *jvmti, JNIEnv *jni, jthread thread) { + register_event(&gdata->cbThreadStart); + debug("Event cbThreadStart\n"); +} + +static void JNICALL +cbThreadEnd(jvmtiEnv *jvmti, JNIEnv *jni, jthread thread) { + register_event(&gdata->cbThreadEnd); + debug("Event cbThreadEnd\n"); +} + +static void JNICALL +cbVirtualThreadStart(jvmtiEnv *jvmti, JNIEnv *jni, jthread thread) { + register_event(&gdata->cbThreadStart); + debug("Event cbThreadStart\n"); +} + +static void JNICALL +cbVirtualThreadEnd(jvmtiEnv *jvmti, JNIEnv *jni, jthread thread) { + register_event(&gdata->cbThreadEnd); + debug("Event cbThreadEnd\n"); +} + +static void JNICALL +cbClassFileLoadHook(jvmtiEnv *jvmti, JNIEnv* jni, + jclass class_being_redefined, jobject loader, + const char* name, jobject protection_domain, + jint class_data_len, const unsigned char *class_data, + jint *new_class_data_len, unsigned char **new_class_data) { + /* TODO uncomment for more stress + unsigned char* new_class_data_copy = (unsigned char*) malloc(class_data_len); + memcpy(new_class_data_copy, class_data, class_data_len); + *new_class_data_len = class_data_len; + *new_class_data = new_class_data_copy; + */ + register_event(&gdata->cbClassFileLoadHook); + debug("Event cbClassFileLoadHook\n"); +} + +static void JNICALL +cbClassLoad(jvmtiEnv *jvmti, JNIEnv *jni, jthread thread, jclass klass) { + register_event(&gdata->cbClassLoad); + debug("Event cbClassLoad\n"); +} + +static void JNICALL +cbClassPrepare(jvmtiEnv *jvmti, JNIEnv *jni, jthread thread, jclass klass) { + register_event(&gdata->cbClassPrepare); + debug("Event cbClassPrepare\n"); +} + +static void JNICALL +cbDataDumpRequest(jvmtiEnv *jvmti) { + register_event(&gdata->cbDataDumpRequest); + debug("Event cbDataDumpRequest\n"); +} + +static void JNICALL +cbException(jvmtiEnv *jvmti, + JNIEnv *jni, + jthread thread, + jmethodID method, + jlocation location, + jobject exception, + jmethodID catch_method, + jlocation catch_location) { + register_event(&gdata->cbException); + debug("Event cbException\n"); +} + +static void JNICALL +cbExceptionCatch(jvmtiEnv *jvmti, JNIEnv *jni, + jthread thread, jmethodID method, jlocation location, + jobject exception) { + register_event(&gdata->cbExceptionCatch); + debug("Event cbExceptionCatch\n"); +} + +static void JNICALL +cbMonitorWait(jvmtiEnv *jvmti, JNIEnv *jni, + jthread thread, jobject object, jlong timeout) { + register_event(&gdata->cbMonitorWait); + debug("Event cbMonitorWait\n"); +} + +static void JNICALL +cbMonitorWaited(jvmtiEnv *jvmti, JNIEnv *jni, + jthread thread, jobject object, jboolean timed_out) { + register_event(&gdata->cbMonitorWaited); + debug("Event cbMonitorWaited\n"); +} + +static void JNICALL +cbMonitorContendedEnter(jvmtiEnv *jvmti, JNIEnv *jni, + jthread thread, jobject object) { + register_event(&gdata->cbMonitorContendedEnter); + debug("Event cbMonitorContendedEnter\n"); +} + +static void JNICALL +cbMonitorContendedEntered(jvmtiEnv *jvmti, JNIEnv* jni, + jthread thread, jobject object) { + register_event(&gdata->cbMonitorContendedEntered); + debug("Event cbMonitorContendedEntered\n"); +} + +static void JNICALL +cbGarbageCollectionStart(jvmtiEnv *jvmti) { + register_event(&gdata->cbGarbageCollectionStart); + debug("Event cbGarbageCollectionStart\n"); +} + +static void JNICALL +cbGarbageCollectionFinish(jvmtiEnv *jvmti) { + register_event(&gdata->cbGarbageCollectionFinish); + debug("Event cbGarbageCollectionFinish\n"); +} + +static void JNICALL +cbObjectFree(jvmtiEnv *jvmti, jlong tag) { + register_event(&gdata->cbObjectFree); + debug("Event cbObjectFree\n"); +} + +static void JNICALL +cbBreakpoint(jvmtiEnv *jvmti, + JNIEnv *jni, + jthread thread, + jmethodID method, + jlocation location) { + register_event(&gdata->cbBreakpoint); + debug("Event cbBreakpoint\n"); +} + +static void JNICALL +cbSingleStep(jvmtiEnv *jvmti, + JNIEnv *jni, + jthread thread, + jmethodID method, + jlocation location) { + register_event(&gdata->cbSingleStep); + debug("Event cbSingleStep\n"); +} + +static void JNICALL +cbFieldAccess(jvmtiEnv *jvmti, + JNIEnv *jni, + jthread thread, + jmethodID method, + jlocation location, + jclass field_klass, + jobject object, + jfieldID field) { + register_event(&gdata->cbFieldAccess); + debug("Event cbFieldAccess\n"); +} + +static void JNICALL +cbFieldModification(jvmtiEnv *jvmti, + JNIEnv *jni, + jthread thread, + jmethodID method, + jlocation location, + jclass field_klass, + jobject object, + jfieldID field, + char signature_type, + jvalue new_value) { + register_event(&gdata->cbFieldModification); + debug("Event cbFieldModification\n"); +} + +static void JNICALL +cbFramePop(jvmtiEnv *jvmti, + JNIEnv *jni, + jthread thread, + jmethodID method, + jboolean was_popped_by_exception) { + register_event(&gdata->cbFramePop); + debug("Event cbFramePop\n"); +} + +static void JNICALL +cbMethodEntry(jvmtiEnv *jvmti, + JNIEnv *jni, + jthread thread, + jmethodID method) { + register_event(&gdata->cbMethodEntry); + debug("Event cbMethodEntry\n"); +} + +static void JNICALL +cbMethodExit(jvmtiEnv *jvmti, + JNIEnv *jni, + jthread thread, + jmethodID method, + jboolean was_popped_by_exception, + jvalue return_value) { + register_event(&gdata->cbMethodExit); + debug("Event cbMethodExit\n"); +} + +static void JNICALL +cbNativeMethodBind(jvmtiEnv *jvmti, + JNIEnv *jni, + jthread thread, + jmethodID method, + void* address, + void** new_address_ptr) { + register_event(&gdata->cbNativeMethodBind); + debug("Event cbNativeMethodBind\n"); +} + +static void JNICALL +cbCompiledMethodLoad(jvmtiEnv *jvmti, + jmethodID method, + jint code_size, + const void* code_addr, + jint map_length, + const jvmtiAddrLocationMap* map, + const void* compile_info) { + register_event(&gdata->cbCompiledMethodLoad); + debug("Event cbCompiledMethodLoad\n"); +} + +static void JNICALL +cbCompiledMethodUnload(jvmtiEnv *jvmti, + jmethodID method, + const void* code_addr) { + register_event(&gdata->cbCompiledMethodUnload); + debug("Event cbCompiledMethodUnload\n"); +} + +static void JNICALL +cbDynamicCodeGenerated(jvmtiEnv *jvmti, + const char* name, + const void* address, + jint length) { + register_event(&gdata->cbDynamicCodeGenerated); + debug("Event cbDynamicCodeGenerated\n"); +} + +static void JNICALL +cbResourceExhausted(jvmtiEnv *jvmti, + JNIEnv *jni, + jint flags, + const void* reserved, + const char* description) { + register_event(&gdata->cbResourceExhausted); + debug("Event cbResourceExhausted\n"); +} + +static void JNICALL +cbVMObjectAlloc(jvmtiEnv *jvmti, + JNIEnv *jni, + jthread thread, + jobject object, + jclass object_klass, + jlong size) { + register_event(&gdata->cbVMObjectAlloc); + debug("Event cbVMObjectAlloc\n"); +} + +static void JNICALL +cbSampledObjectAlloc(jvmtiEnv *jvmti, + JNIEnv *jni, + jthread thread, + jobject object, + jclass object_klass, + jlong size) { + register_event(&gdata->cbSampledObjectAlloc); + debug("Event cbSampledObjectAlloc\n"); +} + + + +static void +set_callbacks(jvmtiEnv *jvmti, jboolean on) { + jvmtiError err = JVMTI_ERROR_NONE; + jvmtiEventCallbacks callbacks; + + (void) memset(&callbacks, 0, sizeof (callbacks)); + if (on == JNI_FALSE) { + err = jvmti->SetEventCallbacks(&callbacks, (int) sizeof (jvmtiEventCallbacks)); + check_jvmti_error(err, "SetEventCallbacks"); + return; + } + callbacks.Breakpoint = &cbBreakpoint; + callbacks.ClassFileLoadHook = &cbClassFileLoadHook; + callbacks.ClassLoad = &cbClassLoad; + callbacks.ClassPrepare = &cbClassPrepare; + callbacks.CompiledMethodLoad = &cbCompiledMethodLoad; + callbacks.CompiledMethodUnload = &cbCompiledMethodUnload; + callbacks.DataDumpRequest = &cbDataDumpRequest; + callbacks.DynamicCodeGenerated = &cbDynamicCodeGenerated; + callbacks.Exception = &cbException; + callbacks.ExceptionCatch = &cbExceptionCatch; + callbacks.FieldAccess = &cbFieldAccess; + callbacks.FieldModification = &cbFieldModification; + callbacks.FramePop = &cbFramePop; + callbacks.GarbageCollectionFinish = &cbGarbageCollectionFinish; + callbacks.GarbageCollectionStart = &cbGarbageCollectionStart; + callbacks.MethodEntry = &cbMethodEntry; + callbacks.MethodExit = &cbMethodExit; + callbacks.MonitorContendedEnter = &cbMonitorContendedEnter; + callbacks.MonitorContendedEntered = &cbMonitorContendedEntered; + callbacks.MonitorWait = &cbMonitorWait; + callbacks.MonitorWaited = &cbMonitorWaited; + callbacks.NativeMethodBind = &cbNativeMethodBind; + callbacks.ObjectFree = &cbObjectFree; + callbacks.ResourceExhausted = &cbResourceExhausted; + callbacks.SampledObjectAlloc = &cbSampledObjectAlloc; + callbacks.SingleStep = &cbSingleStep; + callbacks.ThreadEnd = &cbThreadEnd; + callbacks.ThreadStart = &cbThreadStart; + callbacks.VirtualThreadEnd = &cbVirtualThreadEnd; + callbacks.VirtualThreadStart = &cbVirtualThreadStart; + callbacks.VMDeath = &cbVMDeath; + callbacks.VMInit = &cbVMInit; + callbacks.VMObjectAlloc = &cbVMObjectAlloc; + err = jvmti->SetEventCallbacks(&callbacks, (int) sizeof (jvmtiEventCallbacks)); + check_jvmti_error(err, "SetEventCallbacks"); +} + +static +void get_capabilities(jvmtiEnv *jvmti) { + jvmtiError err = JVMTI_ERROR_NONE; + jvmtiCapabilities capabilities; + (void) memset(&capabilities, 0, sizeof (capabilities)); + err = jvmti->GetPotentialCapabilities(&capabilities); + + if (!gdata->is_debugger_enabled) { + //init_always_solo_capabilities + capabilities.can_suspend = false; + + // onload_solo + capabilities.can_generate_breakpoint_events = false; + capabilities.can_generate_field_access_events = false; + capabilities.can_generate_field_modification_events = false; + } + + capabilities.can_generate_early_vmstart = false; + + check_jvmti_error(err, "GetPotentialCapabilities"); + err = jvmti->AddCapabilities(&capabilities); + check_jvmti_error(err, "AddCapabilities"); +} + +JNIEXPORT jint JNICALL +Agent_OnLoad(JavaVM *vm, char *options, void *reserved) { + jvmtiEnv *jvmti = nullptr; + jint res = vm->GetEnv((void **) &jvmti, JVMTI_VERSION_21); + if (res != JNI_OK) { + return JNI_ERR; + } + + jboolean is_debugger_enabled = JNI_TRUE; + jboolean is_verbose = JNI_FALSE; + + if (options != nullptr) { + char *opts = strdup(options); + char *token = strtok(opts, ","); + + while (token != nullptr) { + if (strncmp(token, "debugger=", 9) == 0) { + if (strcmp(token + 9, "true") == 0) { + is_debugger_enabled = JNI_TRUE; + } else { + is_debugger_enabled = JNI_FALSE; + } + } + if (strncmp(token, "verbose", 7) == 0) { + is_verbose = JNI_TRUE; + } + token = strtok(nullptr, ","); + } + free(opts); + } + gdata = gdata_init(is_debugger_enabled, is_verbose); + get_capabilities(jvmti); + gdata->finished_lock = create_raw_monitor(jvmti, "Finished lock"); + set_callbacks(jvmti, JNI_TRUE); + jvmtiError err = jvmti->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_VM_INIT, nullptr); + check_jvmti_error(err, "SetEventNotificationMode"); + return JNI_OK; +} + +JNIEXPORT void JNICALL +Agent_OnUnload(JavaVM *vm) { + if (!gdata->request_agent_thread_stop) { + printf("Agent_OnUnload happened before requested stop.\n"); + } + gdata_close(); +} From 57210af9bceb582be112564465ab66cebd43a4c0 Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Sat, 16 Aug 2025 04:41:25 +0000 Subject: [PATCH 149/580] 8365555: Cleanup redundancies in jpackage implementation Reviewed-by: almatvee --- .../jpackage/internal/DesktopIntegration.java | 2 +- .../internal/LinuxApplicationLayout.java | 25 +- .../internal/LinuxApplicationLayoutMixin.java | 2 +- .../jpackage/internal/LinuxDebBundler.java | 3 +- .../internal/LinuxPackageBuilder.java | 31 +- .../jpackage/internal/model/LinuxPackage.java | 3 - .../internal/model/LinuxPackageMixin.java | 7 +- .../jdk/jpackage/internal/AppImageSigner.java | 38 +- .../jdk/jpackage/internal/MacAppBundler.java | 4 +- .../internal/MacApplicationBuilder.java | 21 +- .../internal/MacApplicationLayout.java | 29 +- .../internal/MacApplicationLayoutMixin.java | 7 +- .../internal/MacBuildEnvFromParams.java} | 32 +- .../jdk/jpackage/internal/MacBundle.java | 17 +- .../jdk/jpackage/internal/MacDmgBundler.java | 2 +- .../internal/MacDmgPackageBuilder.java | 1 - .../jdk/jpackage/internal/MacFromParams.java | 16 +- .../jpackage/internal/MacPackageBuilder.java | 13 +- .../internal/MacPackagingPipeline.java | 148 ++- .../jdk/jpackage/internal/MacPkgBundler.java | 2 +- .../jpackage/internal/model/MacPackage.java | 27 +- .../jpackage/internal/ApplicationBuilder.java | 27 +- .../internal/ApplicationImageUtils.java | 2 +- .../jdk/jpackage/internal/BuildEnv.java | 82 +- .../jpackage/internal/BuildEnvBuilder.java | 29 +- .../jpackage/internal/BuildEnvFromParams.java | 24 +- .../jdk/jpackage/internal/FromParams.java | 7 +- .../internal/JLinkRuntimeBuilder.java | 2 +- .../jdk/jpackage/internal/PackageBuilder.java | 38 +- .../jpackage/internal/PackagingPipeline.java | 211 ++--- .../internal/model/AppImageLayout.java | 99 +- .../internal/model/ApplicationLayout.java | 40 +- .../jdk/jpackage/internal/model/Package.java | 73 +- .../internal/model/RuntimeBuilder.java | 2 +- .../internal/model/RuntimeLayout.java | 26 +- .../internal/resources/ResourceLocator.java | 4 +- .../jdk/jpackage/internal/util/PathGroup.java | 17 + .../jdk/jpackage/internal/util/PathUtils.java | 11 +- .../internal/WinPackagingPipeline.java | 2 - .../internal/model/WinExePackage.java | 1 + .../jpackage/helpers-test/TEST.properties | 2 +- .../jdk/jpackage/test/JavaAppDescTest.java | 15 +- .../jdk/jpackage/test/JPackageCommand.java | 13 +- test/jdk/tools/jpackage/junit/TEST.properties | 2 + .../internal/LinuxApplicationLayoutTest.java | 57 ++ .../jdk/tools/jpackage/junit/linux/junit.java | 32 + .../internal/MacApplicationLayoutTest.java | 57 ++ .../tools/jpackage/junit/macosx/junit.java | 32 + .../jdk/jpackage/internal/BuildEnvTest.java | 140 +++ .../internal/PackagingPipelineTest.java | 872 ++++++++++++++++++ .../internal/model/AppImageLayoutTest.java | 99 +- .../internal/model/ApplicationLayoutTest.java | 63 +- .../jpackage/internal/util/PathGroupTest.java | 8 + .../jdk/jpackage/test/JUnitAdapter.java | 14 +- .../tools/jpackage/junit/windows/junit.java | 3 +- 55 files changed, 2114 insertions(+), 422 deletions(-) rename src/jdk.jpackage/{share/classes/jdk/jpackage/internal/AppImageDesc.java => macosx/classes/jdk/jpackage/internal/MacBuildEnvFromParams.java} (59%) create mode 100644 test/jdk/tools/jpackage/junit/linux/jdk.jpackage/jdk/jpackage/internal/LinuxApplicationLayoutTest.java create mode 100644 test/jdk/tools/jpackage/junit/linux/junit.java create mode 100644 test/jdk/tools/jpackage/junit/macosx/jdk.jpackage/jdk/jpackage/internal/MacApplicationLayoutTest.java create mode 100644 test/jdk/tools/jpackage/junit/macosx/junit.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/BuildEnvTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/PackagingPipelineTest.java rename test/jdk/tools/jpackage/{helpers-test => junit/tools}/jdk/jpackage/test/JUnitAdapter.java (90%) diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/DesktopIntegration.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/DesktopIntegration.java index dad9917c48f..2d0c78c2474 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/DesktopIntegration.java +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/DesktopIntegration.java @@ -355,7 +355,7 @@ final class DesktopIntegration extends ShellCustomAction { * - installPath(): path where it should be installed by package manager; */ private InstallableFile createDesktopFile(String fileName) { - var srcPath = pkg.asPackageApplicationLayout().orElseThrow().resolveAt(env.appImageDir()).desktopIntegrationDirectory().resolve(fileName); + var srcPath = env.asApplicationLayout().orElseThrow().desktopIntegrationDirectory().resolve(fileName); var installPath = pkg.asInstalledPackageApplicationLayout().orElseThrow().desktopIntegrationDirectory().resolve(fileName); return new InstallableFile(srcPath, installPath); } diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxApplicationLayout.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxApplicationLayout.java index ea87363a9bb..677ee199a84 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxApplicationLayout.java +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxApplicationLayout.java @@ -24,9 +24,10 @@ */ package jdk.jpackage.internal; -import static jdk.jpackage.internal.util.PathUtils.resolveNullablePath; +import static jdk.jpackage.internal.util.PathUtils.mapNullablePath; import java.nio.file.Path; +import java.util.function.UnaryOperator; import jdk.jpackage.internal.model.ApplicationLayout; import jdk.jpackage.internal.util.CompositeProxy; @@ -40,7 +41,25 @@ interface LinuxApplicationLayout extends ApplicationLayout, LinuxApplicationLayo @Override default LinuxApplicationLayout resolveAt(Path root) { - return create(ApplicationLayout.super.resolveAt(root), - resolveNullablePath(root, libAppLauncher())); + return (LinuxApplicationLayout)ApplicationLayout.super.resolveAt(root); + } + + @Override + default LinuxApplicationLayout unresolve() { + return (LinuxApplicationLayout)ApplicationLayout.super.unresolve(); + } + + @Override + default LinuxApplicationLayout resetRootDirectory() { + if (isResolved()) { + return create(ApplicationLayout.super.resetRootDirectory(), libAppLauncher()); + } else { + return this; + } + } + + @Override + default LinuxApplicationLayout map(UnaryOperator mapper) { + return create(ApplicationLayout.super.map(mapper), mapNullablePath(mapper, libAppLauncher())); } } diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxApplicationLayoutMixin.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxApplicationLayoutMixin.java index a88dbc9e16e..21606153669 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxApplicationLayoutMixin.java +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxApplicationLayoutMixin.java @@ -26,7 +26,7 @@ package jdk.jpackage.internal; import java.nio.file.Path; -// Must be publc to allow access from AppImageLayout.toPathGroup() +// Must be public to allow access from AppImageLayout.toPathGroup() public interface LinuxApplicationLayoutMixin { /** diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebBundler.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebBundler.java index 1915fd00c0e..d5b369ba23a 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebBundler.java +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebBundler.java @@ -351,8 +351,7 @@ public class LinuxDebBundler extends LinuxPackageBundler { data.put("APPLICATION_LICENSE_TEXT", licenseText); data.put("APPLICATION_ARCH", pkg.arch()); data.put("APPLICATION_INSTALLED_SIZE", Long.toString( - AppImageLayout.toPathGroup(pkg.packageLayout().resolveAt( - env.appImageDir())).sizeInBytes() >> 10)); + AppImageLayout.toPathGroup(env.appImageLayout()).sizeInBytes() >> 10)); data.put("APPLICATION_HOMEPAGE", pkg.aboutURL().map( value -> "Homepage: " + value).orElse("")); data.put("APPLICATION_VERSION_WITH_RELEASE", ((LinuxDebPackage) pkg).versionWithRelease()); diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageBuilder.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageBuilder.java index 4ed84909958..cc00d7816f5 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageBuilder.java +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageBuilder.java @@ -33,7 +33,9 @@ import java.util.regex.Pattern; import jdk.jpackage.internal.model.AppImageLayout; import jdk.jpackage.internal.model.ApplicationLayout; import jdk.jpackage.internal.model.ConfigException; +import jdk.jpackage.internal.model.LinuxApplication; import jdk.jpackage.internal.model.LinuxPackage; +import jdk.jpackage.internal.model.RuntimeLayout; import jdk.jpackage.internal.model.LinuxPackageMixin; import jdk.jpackage.internal.model.Package; import jdk.jpackage.internal.model.StandardPackageType; @@ -52,24 +54,35 @@ final class LinuxPackageBuilder { pkgBuilder.name(pkgBuilder.create().packageName().toLowerCase().replaceAll("[ _]", "-")); } - final var pkg = pkgBuilder.create(); + final var tmpPkg = pkgBuilder.create(); - final var stdPkgType = pkg.asStandardPackageType(); + final var stdPkgType = tmpPkg.asStandardPackageType(); if (stdPkgType.isPresent()) { - validatePackageName(pkg.packageName(), stdPkgType.orElseThrow()); + validatePackageName(tmpPkg.packageName(), stdPkgType.orElseThrow()); } - var reply = create(pkg, pkg.packageLayout()); - if (reply.isInstallDirInUsrTree()) { - reply = create(pkg, usrTreePackageLayout(pkg.relativeInstallDir(), pkg.packageName())); + final AppImageLayout relativeInstalledLayout; + if (create(tmpPkg).isInstallDirInUsrTree()) { + final var usrTreeLayout = usrTreePackageLayout(tmpPkg.relativeInstallDir(), tmpPkg.packageName()); + if (tmpPkg.isRuntimeInstaller()) { + relativeInstalledLayout = RuntimeLayout.create(usrTreeLayout.runtimeDirectory()); + } else { + relativeInstalledLayout = usrTreeLayout; + } + } else { + relativeInstalledLayout = tmpPkg.appImageLayout().resolveAt(tmpPkg.relativeInstallDir()).resetRootDirectory(); } - return reply; + final var app = ApplicationBuilder.overrideAppImageLayout(pkgBuilder.app(), relativeInstalledLayout); + + return create(pkgBuilder + .app(LinuxApplication.create(app)) + .installedPackageLayout(relativeInstalledLayout.resolveAt(Path.of("/")).resetRootDirectory()) + .create()); } - private LinuxPackage create(Package pkg, AppImageLayout pkgLayout) throws ConfigException { + private LinuxPackage create(Package pkg) throws ConfigException { return LinuxPackage.create(pkg, new LinuxPackageMixin.Stub( - pkgLayout, Optional.ofNullable(menuGroupName).orElseGet(DEFAULTS::menuGroupName), Optional.ofNullable(category), Optional.ofNullable(additionalDependencies), diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/model/LinuxPackage.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/model/LinuxPackage.java index aef1c5eb700..28d47fc46a8 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/model/LinuxPackage.java +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/model/LinuxPackage.java @@ -36,9 +36,6 @@ public interface LinuxPackage extends Package, LinuxPackageMixin { LinuxApplication app(); - @Override - AppImageLayout packageLayout(); - @Override default String packageFileName() { String packageFileNameTemlate = asStandardPackageType().map(stdType -> { diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/model/LinuxPackageMixin.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/model/LinuxPackageMixin.java index f890aa6ff39..5bcf57194f6 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/model/LinuxPackageMixin.java +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/model/LinuxPackageMixin.java @@ -31,11 +31,6 @@ import java.util.Optional; */ public interface LinuxPackageMixin { - /** - * Overrides {@link Package#packageLayout()}. - */ - AppImageLayout packageLayout(); - /** * Gets the name of the start menu group where to create shortcuts for * application launchers of this package. @@ -88,7 +83,7 @@ public interface LinuxPackageMixin { /** * Default implementation of {@link LinuxPackageMixin} interface. */ - record Stub(AppImageLayout packageLayout, String menuGroupName, + record Stub(String menuGroupName, Optional category, Optional additionalDependencies, Optional release, String arch) implements LinuxPackageMixin { } diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/AppImageSigner.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/AppImageSigner.java index d7b2431c1d3..7563dfa0ed3 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/AppImageSigner.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/AppImageSigner.java @@ -25,6 +25,8 @@ package jdk.jpackage.internal; import static java.util.stream.Collectors.joining; +import static jdk.jpackage.internal.MacPackagingPipeline.APPLICATION_LAYOUT; +import static jdk.jpackage.internal.model.MacPackage.RUNTIME_BUNDLE_LAYOUT; import static jdk.jpackage.internal.util.function.ThrowingConsumer.toConsumer; import java.io.IOException; @@ -44,13 +46,14 @@ import jdk.jpackage.internal.model.Application; import jdk.jpackage.internal.model.ApplicationLayout; import jdk.jpackage.internal.model.Launcher; import jdk.jpackage.internal.model.MacApplication; +import jdk.jpackage.internal.model.RuntimeLayout; import jdk.jpackage.internal.util.PathUtils; import jdk.jpackage.internal.util.function.ExceptionBox; final class AppImageSigner { - static Consumer createSigner(MacApplication app, CodesignConfig signingCfg) { + static Consumer createSigner(MacApplication app, CodesignConfig signingCfg) { return toConsumer(appImage -> { try { new AppImageSigner(Codesigners.create(signingCfg)).sign(app, appImage); @@ -67,12 +70,12 @@ final class AppImageSigner { private static final class SignFilter implements Predicate { - SignFilter(Application app, Path appImage) { + SignFilter(Application app, MacBundle appImage) { Objects.requireNonNull(appImage); // Don't explicitly sign main launcher. It will be implicitly signed when the bundle is signed. otherExcludePaths = app.asApplicationLayout().map(appLayout -> { - return appLayout.resolveAt(appImage); + return appLayout.resolveAt(appImage.root()); }).map(ApplicationLayout::launchersDirectory).flatMap(launchersDir -> { return app.mainLauncher().map(Launcher::executableNameWithSuffix).map(launchersDir::resolve); }).map(Set::of).orElseGet(Set::of); @@ -98,11 +101,16 @@ final class AppImageSigner { private final Set otherExcludePaths; } - private void sign(MacApplication app, Path appImage) throws CodesignException, IOException { + private void sign(MacApplication app, MacBundle appImage) throws CodesignException, IOException { + if (!appImage.isValid()) { + throw new IllegalArgumentException(); + } + + app = normalizeAppImageLayout(app); final var fileFilter = new SignFilter(app, appImage); - try (var content = Files.walk(appImage)) { + try (var content = Files.walk(appImage.root())) { content.filter(fileFilter).forEach(toConsumer(path -> { final var origPerms = ensureCanWrite(path); try { @@ -118,10 +126,10 @@ final class AppImageSigner { // Sign runtime root directory if present app.asApplicationLayout().map(appLayout -> { - return appLayout.resolveAt(appImage); + return appLayout.resolveAt(appImage.root()); }).map(MacApplicationLayout.class::cast).map(MacApplicationLayout::runtimeRootDirectory).ifPresent(codesigners); - final var frameworkPath = appImage.resolve("Contents/Frameworks"); + final var frameworkPath = appImage.contentsDir().resolve("Frameworks"); if (Files.isDirectory(frameworkPath)) { try (var content = Files.list(frameworkPath)) { content.forEach(toConsumer(path -> { @@ -131,7 +139,7 @@ final class AppImageSigner { } // Sign the app image itself - codesigners.accept(appImage); + codesigners.accept(appImage.root()); } private static Set ensureCanWrite(Path path) { @@ -235,5 +243,19 @@ final class AppImageSigner { } } + private static MacApplication normalizeAppImageLayout(MacApplication app) { + switch (app.imageLayout()) { + case MacApplicationLayout macLayout -> { + return MacApplicationBuilder.overrideAppImageLayout(app, APPLICATION_LAYOUT); + } + case RuntimeLayout macLayout -> { + return MacApplicationBuilder.overrideAppImageLayout(app, RUNTIME_BUNDLE_LAYOUT); + } + default -> { + throw new IllegalArgumentException(); + } + } + } + private final Codesigners codesigners; } diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacAppBundler.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacAppBundler.java index 28d91156059..cce35ece117 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacAppBundler.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacAppBundler.java @@ -42,11 +42,11 @@ public class MacAppBundler extends AppImageBundler { final BuildEnv env; if (StandardBundlerParam.hasPredefinedAppImage(params)) { - env = BuildEnvFromParams.BUILD_ENV.fetchFrom(params); + env = MacBuildEnvFromParams.BUILD_ENV.fetchFrom(params); final var pkg = MacPackagingPipeline.createSignAppImagePackage(app, env); MacPackagingPipeline.build(Optional.of(pkg)).create().execute(env, pkg, output); } else { - env = BuildEnv.withAppImageDir(BuildEnvFromParams.BUILD_ENV.fetchFrom(params), output); + env = BuildEnv.withAppImageDir(MacBuildEnvFromParams.BUILD_ENV.fetchFrom(params), output); MacPackagingPipeline.build(Optional.empty()) .excludeDirFromCopying(output.getParent()) .excludeDirFromCopying(OUTPUT_DIR.fetchFrom(params)).create().execute(env, app); diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacApplicationBuilder.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacApplicationBuilder.java index ca05be519ff..fc1dd97d9ab 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacApplicationBuilder.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacApplicationBuilder.java @@ -34,6 +34,7 @@ import jdk.jpackage.internal.model.ConfigException; import jdk.jpackage.internal.model.Launcher; import jdk.jpackage.internal.model.MacApplication; import jdk.jpackage.internal.model.MacApplicationMixin; +import jdk.jpackage.internal.model.AppImageLayout; import jdk.jpackage.internal.model.AppImageSigningConfig; final class MacApplicationBuilder { @@ -95,12 +96,28 @@ final class MacApplicationBuilder { validateAppVersion(app); - final var mixin = new MacApplicationMixin.Stub(validatedIcon(), validatedBundleName(), - validatedBundleIdentifier(), validatedCategory(), appStore, createSigningConfig()); + final var mixin = new MacApplicationMixin.Stub( + validatedIcon(), + validatedBundleName(), + validatedBundleIdentifier(), + validatedCategory(), + appStore, + createSigningConfig()); return MacApplication.create(app, mixin); } + static MacApplication overrideAppImageLayout(MacApplication app, AppImageLayout appImageLayout) { + final var mixin = new MacApplicationMixin.Stub( + app.icon(), + app.bundleName(), + app.bundleIdentifier(), + app.category(), + app.appStore(), + app.signingConfig()); + return MacApplication.create(ApplicationBuilder.overrideAppImageLayout(app, appImageLayout), mixin); + } + static boolean isValidBundleIdentifier(String id) { for (int i = 0; i < id.length(); i++) { char a = id.charAt(i); diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacApplicationLayout.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacApplicationLayout.java index 971548ea4f5..7c90134e288 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacApplicationLayout.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacApplicationLayout.java @@ -24,9 +24,10 @@ */ package jdk.jpackage.internal; -import static jdk.jpackage.internal.util.PathUtils.resolveNullablePath; +import static jdk.jpackage.internal.util.PathUtils.mapNullablePath; import java.nio.file.Path; +import java.util.function.UnaryOperator; import jdk.jpackage.internal.model.ApplicationLayout; import jdk.jpackage.internal.util.CompositeProxy; @@ -35,12 +36,32 @@ interface MacApplicationLayout extends ApplicationLayout, MacApplicationLayoutMi static MacApplicationLayout create(ApplicationLayout layout, Path runtimeRootDir) { return CompositeProxy.build() .invokeTunnel(CompositeProxyTunnel.INSTANCE) - .create(MacApplicationLayout.class, layout, new MacApplicationLayoutMixin.Stub(runtimeRootDir)); + .create(MacApplicationLayout.class, layout, + new MacApplicationLayoutMixin.Stub(runtimeRootDir)); } @Override default MacApplicationLayout resolveAt(Path root) { - return create(ApplicationLayout.super.resolveAt(root), - resolveNullablePath(root, runtimeRootDirectory())); + return (MacApplicationLayout)ApplicationLayout.super.resolveAt(root); } + + @Override + default MacApplicationLayout unresolve() { + return (MacApplicationLayout)ApplicationLayout.super.unresolve(); + } + + @Override + default MacApplicationLayout resetRootDirectory() { + if (isResolved()) { + return create(ApplicationLayout.super.resetRootDirectory(), runtimeRootDirectory()); + } else { + return this; + } + } + + @Override + default MacApplicationLayout map(UnaryOperator mapper) { + return create(ApplicationLayout.super.map(mapper), mapNullablePath(mapper, runtimeRootDirectory())); + } + } diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacApplicationLayoutMixin.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacApplicationLayoutMixin.java index b6342b173dc..0acc853f58b 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacApplicationLayoutMixin.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacApplicationLayoutMixin.java @@ -26,12 +26,15 @@ package jdk.jpackage.internal; import java.nio.file.Path; -// Must be publc to allow access from AppImageLayout.toPathGroup() +// Must be public to allow access from AppImageLayout.toPathGroup() public interface MacApplicationLayoutMixin { /** - * Path to the root Java runtime directory in the application image. + * Returns path to the root Java runtime directory in the application image. + *

      * The root Java runtime directory should have "Contents/Home" subdirectory. + * + * @return the path to the root Java runtime directory in the application image */ Path runtimeRootDirectory(); diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageDesc.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBuildEnvFromParams.java similarity index 59% rename from src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageDesc.java rename to src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBuildEnvFromParams.java index fafcb7777a6..31759c8c529 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageDesc.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBuildEnvFromParams.java @@ -22,35 +22,13 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ - package jdk.jpackage.internal; -import java.nio.file.Path; -import java.util.Objects; -import java.util.Optional; -import jdk.jpackage.internal.model.AppImageLayout; -import jdk.jpackage.internal.model.ApplicationLayout; +import jdk.jpackage.internal.model.MacPackage; -record AppImageDesc(AppImageLayout appImageLayout, Path path) { +final class MacBuildEnvFromParams { - AppImageDesc { - Objects.requireNonNull(appImageLayout); - Objects.requireNonNull(path); - } - - AppImageLayout resolvedAppImagelayout() { - return appImageLayout.resolveAt(path); - } - - Optional asResolvedApplicationLayout() { - return asApplicationLayout().map(v -> v.resolveAt(path)); - } - - Optional asApplicationLayout() { - if (appImageLayout instanceof ApplicationLayout layout) { - return Optional.of(layout); - } else { - return Optional.empty(); - } - } + static final BundlerParamInfo BUILD_ENV = BundlerParamInfo.createBundlerParam(BuildEnv.class, params -> { + return BuildEnvFromParams.create(params, MacPackagingPipeline.APPLICATION_LAYOUT::resolveAt, MacPackage::guessRuntimeLayout); + }); } diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBundle.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBundle.java index af07a1145dc..f485653cf80 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBundle.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBundle.java @@ -28,6 +28,7 @@ package jdk.jpackage.internal; import java.nio.file.Files; import java.nio.file.Path; import java.util.Objects; +import java.util.Optional; import jdk.jpackage.internal.model.AppImageLayout; /** @@ -73,7 +74,19 @@ record MacBundle(Path root) { return new MacBundle(dir).isValid(); } - static MacBundle fromAppImageLayout(AppImageLayout layout) { - return new MacBundle(layout.rootDirectory()); + static Optional fromAppImageLayout(AppImageLayout layout) { + final var root = layout.rootDirectory(); + final var bundleSubdir = root.relativize(layout.runtimeDirectory()); + final var contentsDirname = Path.of("Contents"); + var bundleRoot = root; + for (int i = 0; i != bundleSubdir.getNameCount(); i++) { + var nameComponent = bundleSubdir.getName(i); + if (contentsDirname.equals(nameComponent)) { + return Optional.of(new MacBundle(bundleRoot)); + } else { + bundleRoot = bundleRoot.resolve(nameComponent); + } + } + return Optional.empty(); } } diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgBundler.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgBundler.java index 56aacf6e44a..d2c72765d7a 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgBundler.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgBundler.java @@ -71,7 +71,7 @@ public class MacDmgBundler extends MacBaseInstallerBundler { Path outputParentDir) throws PackagerException { final var pkg = MacFromParams.DMG_PACKAGE.fetchFrom(params); - var env = BuildEnvFromParams.BUILD_ENV.fetchFrom(params); + var env = MacBuildEnvFromParams.BUILD_ENV.fetchFrom(params); final var packager = MacDmgPackager.build().outputDir(outputParentDir).pkg(pkg).env(env); diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgPackageBuilder.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgPackageBuilder.java index d453bb59184..c2b9c25f327 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgPackageBuilder.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgPackageBuilder.java @@ -53,7 +53,6 @@ final class MacDmgPackageBuilder { } MacDmgPackage create() throws ConfigException { - final var superPkgBuilder = pkgBuilder.pkgBuilder(); final var pkg = pkgBuilder.create(); return MacDmgPackage.create(pkg, new MacDmgPackageMixin.Stub( diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFromParams.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFromParams.java index 754d09a7156..4be2f9859c8 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFromParams.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFromParams.java @@ -39,13 +39,12 @@ import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE_FI import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_RUNTIME_IMAGE; import static jdk.jpackage.internal.StandardBundlerParam.SIGN_BUNDLE; import static jdk.jpackage.internal.StandardBundlerParam.hasPredefinedAppImage; -import static jdk.jpackage.internal.model.MacPackage.RUNTIME_PACKAGE_LAYOUT; +import static jdk.jpackage.internal.model.MacPackage.RUNTIME_BUNDLE_LAYOUT; import static jdk.jpackage.internal.model.StandardPackageType.MAC_DMG; import static jdk.jpackage.internal.model.StandardPackageType.MAC_PKG; import static jdk.jpackage.internal.util.function.ThrowingFunction.toFunction; import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; import java.util.List; @@ -64,6 +63,7 @@ import jdk.jpackage.internal.model.MacApplication; import jdk.jpackage.internal.model.MacDmgPackage; import jdk.jpackage.internal.model.MacFileAssociation; import jdk.jpackage.internal.model.MacLauncher; +import jdk.jpackage.internal.model.MacPackage; import jdk.jpackage.internal.model.MacPkgPackage; import jdk.jpackage.internal.model.PackageType; import jdk.jpackage.internal.model.RuntimeLayout; @@ -75,20 +75,16 @@ final class MacFromParams { private static MacApplication createMacApplication( Map params) throws ConfigException, IOException { - final var predefinedRuntimeLayout = PREDEFINED_RUNTIME_IMAGE.findIn(params).map(predefinedRuntimeImage -> { - if (Files.isDirectory(RUNTIME_PACKAGE_LAYOUT.resolveAt(predefinedRuntimeImage).runtimeDirectory())) { - return RUNTIME_PACKAGE_LAYOUT; - } else { - return RuntimeLayout.DEFAULT; - } - }); + final var predefinedRuntimeLayout = PREDEFINED_RUNTIME_IMAGE.findIn(params) + .map(MacPackage::guessRuntimeLayout) + .map(RuntimeLayout::unresolve); final var launcherFromParams = new LauncherFromParams(Optional.of(MacFromParams::createMacFa)); final var superAppBuilder = createApplicationBuilder(params, toFunction(launcherParams -> { var launcher = launcherFromParams.create(launcherParams); return MacLauncher.create(launcher); - }), APPLICATION_LAYOUT, predefinedRuntimeLayout); + }), APPLICATION_LAYOUT, RUNTIME_BUNDLE_LAYOUT, predefinedRuntimeLayout); if (hasPredefinedAppImage(params)) { // Set the main launcher start up info. diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPackageBuilder.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPackageBuilder.java index a16194a6260..cf5c6a934f7 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPackageBuilder.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPackageBuilder.java @@ -24,8 +24,11 @@ */ package jdk.jpackage.internal; +import static jdk.jpackage.internal.MacPackagingPipeline.LayoutUtils.packagerLayout; + import java.util.Objects; import jdk.jpackage.internal.model.ConfigException; +import jdk.jpackage.internal.model.MacApplication; import jdk.jpackage.internal.model.MacPackage; import jdk.jpackage.internal.model.MacPackageMixin; @@ -45,7 +48,15 @@ final class MacPackageBuilder { } MacPackage create() throws ConfigException { - final var pkg = pkgBuilder.create(); + + final var app = (MacApplication)pkgBuilder.app(); + + var pkg = pkgBuilder.create(); + + pkgBuilder.app(MacApplicationBuilder.overrideAppImageLayout(app, packagerLayout(pkg))) + .installedPackageLayout(pkg.installedPackageLayout()); + + pkg = pkgBuilder.create(); return MacPackage.create(pkg, new MacPackageMixin.Stub(pkg.predefinedAppImage().map(v -> predefinedAppImageSigned))); } diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPackagingPipeline.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPackagingPipeline.java index 556efdd0fd3..a6ed164d9f7 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPackagingPipeline.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPackagingPipeline.java @@ -39,6 +39,7 @@ import static jdk.jpackage.internal.util.function.ThrowingSupplier.toSupplier; import java.io.IOException; import java.io.StringWriter; +import java.io.UncheckedIOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.LinkOption; @@ -49,10 +50,12 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.function.UnaryOperator; import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; import jdk.jpackage.internal.PackagingPipeline.AppImageBuildEnv; +import jdk.jpackage.internal.PackagingPipeline.AppImageTaskAction; import jdk.jpackage.internal.PackagingPipeline.ApplicationImageTaskAction; import jdk.jpackage.internal.PackagingPipeline.BuildApplicationTaskID; import jdk.jpackage.internal.PackagingPipeline.CopyAppImageTaskID; @@ -98,21 +101,10 @@ final class MacPackagingPipeline { COPY_SIGN } - static AppImageLayout packagingLayout(Package pkg) { - return pkg.appImageLayout().resolveAt(pkg.relativeInstallDir().getFileName()); - } - static PackagingPipeline.Builder build(Optional pkg) { final var builder = PackagingPipeline.buildStandard() - .appContextMapper(appContext -> { - return new TaskContextProxy(appContext, true, false); - }) - .pkgContextMapper(appContext -> { - final var isRuntimeInstaller = pkg.map(Package::isRuntimeInstaller).orElse(false); - final var withPredefinedAppImage = pkg.flatMap(Package::predefinedAppImage).isPresent(); - return new TaskContextProxy(appContext, false, isRuntimeInstaller || withPredefinedAppImage); - }) - .appImageLayoutForPackaging(MacPackagingPipeline::packagingLayout) + .contextMapper(pkg.map(MacPackagingPipeline::mapPackageTaskContext) + .orElseGet(MacPackagingPipeline::mapAppTaskContext)) .task(PackageTaskID.RUN_POST_IMAGE_USER_SCRIPT) .packageAction(MacPackagingPipeline::runPostAppImageUserScript).add() .task(CopyAppImageTaskID.COPY) @@ -156,13 +148,13 @@ final class MacPackagingPipeline { .addDependent(BuildApplicationTaskID.CONTENT).add(); builder.task(MacBuildApplicationTaskID.SIGN) - .appImageAction(MacPackagingPipeline::sign) + .appImageAction(LayoutUtils.withBundleLayout(MacPackagingPipeline::sign)) .addDependencies(builder.taskGraphSnapshot().getAllTailsOf(PrimaryTaskID.BUILD_APPLICATION_IMAGE)) .addDependent(PrimaryTaskID.BUILD_APPLICATION_IMAGE) .add(); builder.task(MacCopyAppImageTaskID.COPY_SIGN) - .appImageAction(MacPackagingPipeline::sign) + .appImageAction(LayoutUtils.withBundleLayout(MacPackagingPipeline::sign)) .addDependencies(builder.taskGraphSnapshot().getAllTailsOf(PrimaryTaskID.COPY_APP_IMAGE)) .addDependent(PrimaryTaskID.COPY_APP_IMAGE) .add(); @@ -182,7 +174,6 @@ final class MacPackagingPipeline { disabledTasks.add(PackageTaskID.RUN_POST_IMAGE_USER_SCRIPT); builder.task(MacCopyAppImageTaskID.REPLACE_APP_IMAGE_FILE) .applicationAction(createWriteAppImageFileAction()).add(); - builder.appImageLayoutForPackaging(Package::appImageLayout); } else if (p.isRuntimeInstaller()) { builder.task(MacCopyAppImageTaskID.COPY_RUNTIME_JLILIB) @@ -236,23 +227,83 @@ final class MacPackagingPipeline { }).get(); } - private static void copyAppImage(MacPackage pkg, AppImageDesc srcAppImage, - AppImageDesc dstAppImage) throws IOException { + static final class LayoutUtils { + /** + * Returns unresolved app image layout for the specified package for use with + * the signing function defined in {@link MacPackagingPipeline} class and + * {@link MacPkgPackager} and {@link MacDmgPackager} packagers. + *

      + * Paths of the result app image layout will start with the bundle name. E.g.: + * for a package with relative installation directory set to + * {@code "Applications/Acme/MyApp.app"} and the "launchers" directory of an + * application layout set to {@code "Contents/MacOS"}, the result application + * layout object will be such that the value of its "launchers" directory will + * be {@code "MyApp.app/Contents/MacOS"}. The root directory of the result app + * image layout will be an empty path ({@link Path.of("")}), i.e. the app image + * layout will be unresolved. + * + * @param pkg the package + * @return the unresolved app image layout for the specified package suitable + * for the use with macosx packaging pipeline and packagers + */ + static AppImageLayout packagerLayout(Package pkg) { + return pkg.appImageLayout().resolveAt(pkg.relativeInstallDir().getFileName()).resetRootDirectory(); + } + + static AppImageBuildEnv fromPackagerLayout(AppImageBuildEnv cfg) { + + var bundleDirectoryName = cfg.envLayout().runtimeDirectory().getName(0); + var bundleLayout = cfg.envLayout().map(bundleDirectoryName::relativize).resetRootDirectory(); + var bundleRoot = cfg.env().appImageDir().resolve(bundleDirectoryName); + var app = MacApplicationBuilder.overrideAppImageLayout(cfg.app(), bundleLayout); + var env = BuildEnv.withAppImageLayout(cfg.env(), bundleLayout.resolveAt(bundleRoot)); + + return new AppImageBuildEnv<>(env, app); + } + + static AppImageTaskAction withBundleLayout(AppImageTaskAction action) { + return new AppImageTaskAction<>() { + @Override + public void execute(AppImageBuildEnv env) throws IOException, PackagerException { + if (!env.envLayout().runtimeDirectory().getName(0).equals(Path.of("Contents"))) { + env = LayoutUtils.fromPackagerLayout(env); + } + action.execute(env); + } + }; + } + } + + private static void copyAppImage(MacPackage pkg, AppImageLayout srcAppImage, + AppImageLayout dstAppImage) throws IOException { boolean predefinedAppImageSigned = pkg.predefinedAppImageSigned().orElse(false); - var inputRootDirectory = srcAppImage.resolvedAppImagelayout().rootDirectory(); + final Optional srcMacBundle; + if (pkg.isRuntimeInstaller()) { + srcMacBundle = MacBundle.fromAppImageLayout(srcAppImage); + } else { + srcMacBundle = Optional.empty(); + } - if (pkg.isRuntimeInstaller() && MacBundle.isDirectoryMacBundle(inputRootDirectory)) { + srcMacBundle.ifPresentOrElse(inputBundle -> { // Building runtime package from the input runtime bundle. // Copy the input bundle verbatim. - FileUtils.copyRecursive( - inputRootDirectory, - dstAppImage.resolvedAppImagelayout().rootDirectory(), - LinkOption.NOFOLLOW_LINKS); - } else { - PackagingPipeline.copyAppImage(srcAppImage, dstAppImage, !predefinedAppImageSigned); - } + try { + FileUtils.copyRecursive( + inputBundle.root(), + MacBundle.fromAppImageLayout(dstAppImage).orElseThrow().root(), + LinkOption.NOFOLLOW_LINKS); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + }, () -> { + try { + PackagingPipeline.copyAppImage(srcAppImage, dstAppImage, !predefinedAppImageSigned); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + }); } private static void copyJliLib( @@ -272,10 +323,23 @@ final class MacPackagingPipeline { } } - private static void runPostAppImageUserScript(PackageBuildEnv env) throws IOException { - PackagingPipeline.runPostAppImageUserScript(new PackageBuildEnv<>( - BuildEnv.withAppImageDir(env.env(), env.env().appImageDir().resolve(env.envLayout().rootDirectory())), - env.pkg(), env.pkg().appImageLayout(), env.outputDir())); + private static void runPostAppImageUserScript(PackageBuildEnv cfg) throws IOException { + var appCfg = LayoutUtils.fromPackagerLayout( + new AppImageBuildEnv<>(cfg.env(), (MacApplication)cfg.pkg().app())); + + var pkg = cfg.pkg(); pkg = new Package.Stub( + appCfg.app(), + pkg.type(), + pkg.packageName(), + pkg.description(), + pkg.version(), + pkg.aboutURL(), + pkg.licenseFile(), + pkg.predefinedAppImage(), + pkg.installedPackageLayout(), + pkg.relativeInstallDir()); + + PackagingPipeline.runPostAppImageUserScript(new PackageBuildEnv<>(appCfg.env(), pkg, cfg.outputDir())); } private static void writePackageFile(PackageBuildEnv env) throws IOException { @@ -331,7 +395,7 @@ final class MacPackagingPipeline { final var app = env.app(); - final var infoPlistFile = MacBundle.fromAppImageLayout(env.resolvedLayout()).infoPlistFile(); + final var infoPlistFile = MacBundle.fromAppImageLayout(env.resolvedLayout()).orElseThrow().infoPlistFile(); Log.verbose(I18N.format("message.preparing-info-plist", PathUtils.normalizedAbsolutePathString(infoPlistFile))); @@ -384,8 +448,7 @@ final class MacPackagingPipeline { } final Runnable signAction = () -> { - final var appImageDir = env.resolvedLayout().rootDirectory(); - AppImageSigner.createSigner(app, codesignConfigBuilder.create()).accept(appImageDir); + AppImageSigner.createSigner(app, codesignConfigBuilder.create()).accept(MacBundle.fromAppImageLayout(env.resolvedLayout()).orElseThrow()); }; app.signingConfig().flatMap(AppImageSigningConfig::keychain).map(Keychain::new).ifPresentOrElse(keychain -> { @@ -467,7 +530,7 @@ final class MacPackagingPipeline { private static MacBundle runtimeBundle(AppImageBuildEnv env) { if (env.app().isRuntime()) { - return new MacBundle(env.resolvedLayout().rootDirectory()); + return MacBundle.fromAppImageLayout(env.resolvedLayout()).orElseThrow(); } else { return new MacBundle(((MacApplicationLayout)env.resolvedLayout()).runtimeRootDirectory()); } @@ -498,6 +561,21 @@ final class MacPackagingPipeline { } } + private static UnaryOperator mapAppTaskContext() { + return ctx -> { + return new TaskContextProxy(ctx, true, false); + }; + } + + private static UnaryOperator mapPackageTaskContext(Package pkg) { + return ctx -> { + final var isRuntimeInstaller = pkg.isRuntimeInstaller(); + final var withPredefinedAppImage = pkg.predefinedAppImage().isPresent(); + return new TaskContextProxy(ctx, false, isRuntimeInstaller || withPredefinedAppImage); + }; + } + + private record TaskContextProxy(TaskContext delegate, boolean forApp, boolean copyAppImage) implements TaskContext { @Override diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgBundler.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgBundler.java index f785cc49f67..bc19e7c4a1a 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgBundler.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgBundler.java @@ -73,7 +73,7 @@ public class MacPkgBundler extends MacBaseInstallerBundler { Path outputParentDir) throws PackagerException { final var pkg = MacFromParams.PKG_PACKAGE.fetchFrom(params); - var env = BuildEnvFromParams.BUILD_ENV.fetchFrom(params); + var env = MacBuildEnvFromParams.BUILD_ENV.fetchFrom(params); final var packager = MacPkgPackager.build().outputDir(outputParentDir).pkg(pkg).env(env); diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/model/MacPackage.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/model/MacPackage.java index 083fc225581..4d38cf4ed70 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/model/MacPackage.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/model/MacPackage.java @@ -24,21 +24,14 @@ */ package jdk.jpackage.internal.model; +import java.nio.file.Files; import java.nio.file.Path; import jdk.jpackage.internal.util.CompositeProxy; public interface MacPackage extends Package, MacPackageMixin { - MacApplication app(); - @Override - default AppImageLayout appImageLayout() { - if (isRuntimeInstaller()) { - return RUNTIME_PACKAGE_LAYOUT; - } else { - return Package.super.appImageLayout(); - } - } + MacApplication app(); default Path installDir() { return Path.of("/").resolve(relativeInstallDir()); @@ -48,5 +41,19 @@ public interface MacPackage extends Package, MacPackageMixin { return CompositeProxy.create(MacPackage.class, pkg, mixin); } - public static final RuntimeLayout RUNTIME_PACKAGE_LAYOUT = RuntimeLayout.create(Path.of("Contents/Home")); + /** + * Guesses layout of a runtime image at the given path. + * + * @param path the path to a runtime image + * @return the runtime image layout resolved at the given path + */ + public static RuntimeLayout guessRuntimeLayout(Path path) { + if (Files.isDirectory(RUNTIME_BUNDLE_LAYOUT.resolveAt(path).runtimeDirectory())) { + return RUNTIME_BUNDLE_LAYOUT.resolveAt(path); + } else { + return RuntimeLayout.DEFAULT.resolveAt(path); + } + } + + public static final RuntimeLayout RUNTIME_BUNDLE_LAYOUT = RuntimeLayout.create(Path.of("Contents/Home")); } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationBuilder.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationBuilder.java index 09bf04372ce..141a1b5155f 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationBuilder.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationBuilder.java @@ -153,9 +153,30 @@ final class ApplicationBuilder { } static Launcher overrideLauncherStartupInfo(Launcher launcher, LauncherStartupInfo startupInfo) { - return new Launcher.Stub(launcher.name(), Optional.of(startupInfo), - launcher.fileAssociations(), launcher.isService(), launcher.description(), - launcher.icon(), launcher.defaultIconResourceName(), launcher.extraAppImageFileData()); + return new Launcher.Stub( + launcher.name(), + Optional.of(startupInfo), + launcher.fileAssociations(), + launcher.isService(), + launcher.description(), + launcher.icon(), + launcher.defaultIconResourceName(), + launcher.extraAppImageFileData()); + } + + static Application overrideAppImageLayout(Application app, AppImageLayout appImageLayout) { + return new Application.Stub( + app.name(), + app.description(), + app.version(), + app.vendor(), + app.copyright(), + app.srcDir(), + app.contentDirs(), + Objects.requireNonNull(appImageLayout), + app.runtimeBuilder(), + app.launchers(), + app.extraAppImageFileData()); } record MainLauncherStartupInfo(String qualifiedClassName) implements LauncherStartupInfo { diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationImageUtils.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationImageUtils.java index e2686c17128..7d3250ff7b9 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationImageUtils.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationImageUtils.java @@ -84,7 +84,7 @@ final class ApplicationImageUtils { static ApplicationImageTaskAction createWriteRuntimeAction() { return env -> { - env.app().runtimeBuilder().orElseThrow().createRuntime(env.resolvedLayout()); + env.app().runtimeBuilder().orElseThrow().create(env.resolvedLayout()); }; } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BuildEnv.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BuildEnv.java index 1d2a43bf5d6..527f747b229 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BuildEnv.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BuildEnv.java @@ -27,57 +27,111 @@ package jdk.jpackage.internal; import java.nio.file.Path; import java.util.Objects; import java.util.Optional; +import jdk.jpackage.internal.model.AppImageLayout; +import jdk.jpackage.internal.model.ApplicationLayout; +/** + * Build environment. + */ interface BuildEnv { + /** + * Returns root directory for intermediate build files. + * + * @return the root directory for intermediate build files + */ Path buildRoot(); + /** + * Returns true if the build should be verbose output. + * + * @return true if the build should be verbose output + */ boolean verbose(); + /** + * Returns the path of the resource directory or an empty {@link Optional} + * instance if none is configured with the build. + * + * @return the path of the resource directory or an empty {@link Optional} + * instance if non is configured with the build + */ Optional resourceDir(); /** - * Returns path to application image directory. + * Returns the path of the app image directory of this build. * - * The return value is supposed to be used as a parameter for - * ApplicationLayout#resolveAt function. + * @return the path of the app image directory of this build */ default Path appImageDir() { - return buildRoot().resolve("image"); + return appImageLayout().rootDirectory(); } + /** + * Returns resolved app image layout of the app image directory. The return + * layout is resolved at {@link #appImageDir()} path. + * + * @return the resolved app image layout of the app image directory + */ + AppImageLayout appImageLayout(); + + default Optional asApplicationLayout() { + if (appImageLayout() instanceof ApplicationLayout layout) { + return Optional.of(layout); + } else { + return Optional.empty(); + } + } + + /** + * Returns a path to a directory for intermediate configuration files. + * @return the path to the directory for intermediate configuration files + */ default Path configDir() { return buildRoot().resolve("config"); } + /** + * Creates an {@link OverridableResource} instance for the given resource name. + * + * @param defaultName the resource name + * @return the {@link OverridableResource} instance wrapping a resource with the + * given name + */ OverridableResource createResource(String defaultName); static BuildEnv withAppImageDir(BuildEnv env, Path appImageDir) { return ((Internal.DefaultBuildEnv)env).copyWithAppImageDir(appImageDir); } - static BuildEnv create(Path buildRoot, Optional resourceDir, boolean verbose, Class resourceLocator) { - return new Internal.DefaultBuildEnv(buildRoot, resourceDir, verbose, resourceLocator, Optional.empty()); + static BuildEnv withAppImageLayout(BuildEnv env, AppImageLayout appImageLayout) { + return ((Internal.DefaultBuildEnv)env).copyWithAppImageLayout(appImageLayout); + } + + static BuildEnv create(Path buildRoot, Optional resourceDir, boolean verbose, + Class resourceLocator, AppImageLayout appImageLayout) { + return new Internal.DefaultBuildEnv(buildRoot, resourceDir, verbose, + resourceLocator, appImageLayout); } static final class Internal { - private static record DefaultBuildEnv(Path buildRoot, Optional resourceDir, - boolean verbose, Class resourceLocator, Optional optAppImageDir) implements BuildEnv { + private record DefaultBuildEnv(Path buildRoot, Optional resourceDir, + boolean verbose, Class resourceLocator, + AppImageLayout appImageLayout) implements BuildEnv { DefaultBuildEnv { Objects.requireNonNull(buildRoot); Objects.requireNonNull(resourceDir); Objects.requireNonNull(resourceLocator); - Objects.requireNonNull(optAppImageDir); + Objects.requireNonNull(appImageLayout); } - DefaultBuildEnv copyWithAppImageDir(Path appImageDir) { - return new DefaultBuildEnv(buildRoot, resourceDir, verbose, resourceLocator, Optional.of(appImageDir)); + DefaultBuildEnv copyWithAppImageDir(Path v) { + return copyWithAppImageLayout(appImageLayout.unresolve().resolveAt(v)); } - @Override - public Path appImageDir() { - return optAppImageDir.orElseGet(BuildEnv.super::appImageDir); + DefaultBuildEnv copyWithAppImageLayout(AppImageLayout v) { + return new DefaultBuildEnv(buildRoot, resourceDir, verbose, resourceLocator, v); } @Override diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BuildEnvBuilder.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BuildEnvBuilder.java index 9974e407ca7..ba70913eca9 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BuildEnvBuilder.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BuildEnvBuilder.java @@ -29,8 +29,10 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.Objects; import java.util.Optional; +import jdk.jpackage.internal.model.AppImageLayout; import jdk.jpackage.internal.model.Application; import jdk.jpackage.internal.model.ConfigException; +import jdk.jpackage.internal.model.Package; import jdk.jpackage.internal.resources.ResourceLocator; final class BuildEnvBuilder { @@ -40,8 +42,6 @@ final class BuildEnvBuilder { } BuildEnv create() throws ConfigException { - Objects.requireNonNull(appImageDir); - var exceptionBuilder = I18N.buildConfigException("ERR_BuildRootInvalid", root); if (Files.isDirectory(root)) { try (var rootDirContents = Files.list(root)) { @@ -57,8 +57,8 @@ final class BuildEnvBuilder { throw exceptionBuilder.create(); } - return BuildEnv.withAppImageDir(BuildEnv.create(root, Optional.ofNullable(resourceDir), - verbose, ResourceLocator.class), appImageDir); + return BuildEnv.create(root, Optional.ofNullable(resourceDir), verbose, + ResourceLocator.class, resolvedAppImageLayout()); } BuildEnvBuilder verbose(boolean v) { @@ -76,21 +76,34 @@ final class BuildEnvBuilder { return this; } - BuildEnvBuilder appImageDirFor(Application app) { - appImageDir = defaultAppImageDir(root).resolve(app.appImageDirName()); + BuildEnvBuilder appImageLayout(AppImageLayout v) { + appImageLayout = v; return this; } - BuildEnvBuilder appImageDirForPackage() { - appImageDir = defaultAppImageDir(root); + BuildEnvBuilder appImageDirFor(Application app) { + appImageDir = defaultAppImageDir(root).resolve(app.appImageDirName()); + appImageLayout = app.imageLayout(); return this; } + BuildEnvBuilder appImageDirFor(Package pkg) { + appImageDir = defaultAppImageDir(root); + appImageLayout = pkg.appImageLayout(); + return this; + } + + private AppImageLayout resolvedAppImageLayout() { + Objects.requireNonNull(appImageLayout); + return Optional.ofNullable(appImageDir).map(appImageLayout.unresolve()::resolveAt).orElse(appImageLayout); + } + private static Path defaultAppImageDir(Path root) { return root.resolve("image"); } private Path appImageDir; + private AppImageLayout appImageLayout; private Path resourceDir; private boolean verbose; diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BuildEnvFromParams.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BuildEnvFromParams.java index 24ae249297e..6fb1a342fbf 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BuildEnvFromParams.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BuildEnvFromParams.java @@ -24,18 +24,25 @@ */ package jdk.jpackage.internal; -import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_RUNTIME_IMAGE; +import static jdk.jpackage.internal.ApplicationLayoutUtils.PLATFORM_APPLICATION_LAYOUT; import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE; +import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_RUNTIME_IMAGE; import static jdk.jpackage.internal.StandardBundlerParam.RESOURCE_DIR; import static jdk.jpackage.internal.StandardBundlerParam.TEMP_ROOT; import static jdk.jpackage.internal.StandardBundlerParam.VERBOSE; +import java.nio.file.Path; import java.util.Map; +import java.util.function.Function; +import jdk.jpackage.internal.model.ApplicationLayout; import jdk.jpackage.internal.model.ConfigException; +import jdk.jpackage.internal.model.RuntimeLayout; final class BuildEnvFromParams { - static BuildEnv create(Map params) throws ConfigException { + static BuildEnv create(Map params, + Function predefinedAppImageLayoutProvider, + Function predefinedRuntimeImageLayoutProvider) throws ConfigException { final var builder = new BuildEnvBuilder(TEMP_ROOT.fetchFrom(params)); @@ -47,11 +54,13 @@ final class BuildEnvFromParams { final var pkg = FromParams.getCurrentPackage(params); if (app.isRuntime()) { - PREDEFINED_RUNTIME_IMAGE.copyInto(params, builder::appImageDir); + var layout = predefinedRuntimeImageLayoutProvider.apply(PREDEFINED_RUNTIME_IMAGE.findIn(params).orElseThrow()); + builder.appImageLayout(layout); } else if (StandardBundlerParam.hasPredefinedAppImage(params)) { - PREDEFINED_APP_IMAGE.copyInto(params, builder::appImageDir); + var layout = predefinedAppImageLayoutProvider.apply(PREDEFINED_APP_IMAGE.findIn(params).orElseThrow()); + builder.appImageLayout(layout); } else if (pkg.isPresent()) { - builder.appImageDirForPackage(); + builder.appImageDirFor(pkg.orElseThrow()); } else { builder.appImageDirFor(app); } @@ -59,6 +68,7 @@ final class BuildEnvFromParams { return builder.create(); } - static final BundlerParamInfo BUILD_ENV = BundlerParamInfo.createBundlerParam( - BuildEnv.class, BuildEnvFromParams::create); + static final BundlerParamInfo BUILD_ENV = BundlerParamInfo.createBundlerParam(BuildEnv.class, params -> { + return create(params, PLATFORM_APPLICATION_LAYOUT::resolveAt, RuntimeLayout.DEFAULT::resolveAt); + }); } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromParams.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromParams.java index d4ea595969a..ec9718c5d24 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromParams.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromParams.java @@ -79,12 +79,13 @@ final class FromParams { static ApplicationBuilder createApplicationBuilder(Map params, Function, Launcher> launcherMapper, ApplicationLayout appLayout) throws ConfigException, IOException { - return createApplicationBuilder(params, launcherMapper, appLayout, Optional.of(RuntimeLayout.DEFAULT)); + return createApplicationBuilder(params, launcherMapper, appLayout, RuntimeLayout.DEFAULT, Optional.of(RuntimeLayout.DEFAULT)); } static ApplicationBuilder createApplicationBuilder(Map params, Function, Launcher> launcherMapper, - ApplicationLayout appLayout, Optional predefinedRuntimeLayout) throws ConfigException, IOException { + ApplicationLayout appLayout, RuntimeLayout runtimeLayout, + Optional predefinedRuntimeLayout) throws ConfigException, IOException { final var appBuilder = new ApplicationBuilder(); @@ -104,7 +105,7 @@ final class FromParams { layout -> predefinedRuntimeImage.map(layout::resolveAt)).map(RuntimeLayout::runtimeDirectory); if (isRuntimeInstaller) { - appBuilder.appImageLayout(predefinedRuntimeLayout.orElseThrow()); + appBuilder.appImageLayout(runtimeLayout); } else { appBuilder.appImageLayout(appLayout); diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/JLinkRuntimeBuilder.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/JLinkRuntimeBuilder.java index c09c1ae4b1f..bd82b34d897 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/JLinkRuntimeBuilder.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/JLinkRuntimeBuilder.java @@ -61,7 +61,7 @@ final class JLinkRuntimeBuilder implements RuntimeBuilder { } @Override - public void createRuntime(AppImageLayout appImageLayout) throws PackagerException { + public void create(AppImageLayout appImageLayout) throws PackagerException { var args = new ArrayList(); args.add("--output"); args.add(appImageLayout.runtimeDirectory().toString()); diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/PackageBuilder.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/PackageBuilder.java index 221e0dcac07..f93cd0eab14 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/PackageBuilder.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/PackageBuilder.java @@ -29,6 +29,7 @@ import static jdk.jpackage.internal.I18N.buildConfigException; import java.nio.file.Path; import java.util.Objects; import java.util.Optional; +import jdk.jpackage.internal.model.AppImageLayout; import jdk.jpackage.internal.model.Application; import jdk.jpackage.internal.model.ConfigException; import jdk.jpackage.internal.model.Package; @@ -86,9 +87,19 @@ final class PackageBuilder { Optional.ofNullable(aboutURL), Optional.ofNullable(licenseFile), Optional.ofNullable(predefinedAppImage), + validatedInstalledPackageLayout(relativeInstallDir), relativeInstallDir); } + PackageBuilder app(Application v) { + app = v; + return this; + } + + Application app() { + return app; + } + PackageBuilder name(String v) { name = v; return this; @@ -169,10 +180,34 @@ final class PackageBuilder { } } + PackageBuilder installedPackageLayout(AppImageLayout v) { + installedPackageLayout = v; + return this; + } + + Optional installedPackageLayout() { + return Optional.ofNullable(installedPackageLayout); + } + private String validatedName() { return name().orElseGet(app::name); } + private AppImageLayout validatedInstalledPackageLayout(Path relativeInstallDir) { + return installedPackageLayout().orElseGet(() -> { + var theInstallDir = relativeInstallDir; + if (type instanceof StandardPackageType stdType) { + switch (stdType) { + case LINUX_DEB, LINUX_RPM, MAC_DMG, MAC_PKG -> { + theInstallDir = Path.of("/").resolve(theInstallDir); + } + default -> {} + } + } + return app.imageLayout().resolveAt(theInstallDir).resetRootDirectory(); + }); + } + private static Path mapInstallDir(Path installDir, PackageType pkgType) throws ConfigException { var ex = buildConfigException("error.invalid-install-dir", installDir).create(); @@ -235,6 +270,7 @@ final class PackageBuilder { } } + private Application app; private String name; private Path fileName; private String description; @@ -243,7 +279,7 @@ final class PackageBuilder { private Path licenseFile; private Path predefinedAppImage; private Path installDir; + private AppImageLayout installedPackageLayout; private final PackageType type; - private final Application app; } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/PackagingPipeline.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/PackagingPipeline.java index 276bed4f31c..64a37878bd8 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/PackagingPipeline.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/PackagingPipeline.java @@ -26,6 +26,7 @@ package jdk.jpackage.internal; import static java.util.stream.Collectors.toMap; +import static jdk.jpackage.internal.model.AppImageLayout.toPathGroup; import java.io.IOException; import java.nio.file.LinkOption; @@ -38,9 +39,9 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.concurrent.Callable; -import java.util.function.Function; import java.util.function.Predicate; import java.util.function.UnaryOperator; +import java.util.stream.Stream; import jdk.jpackage.internal.model.AppImageLayout; import jdk.jpackage.internal.model.Application; import jdk.jpackage.internal.model.ApplicationLayout; @@ -62,7 +63,7 @@ final class PackagingPipeline { * @param app the application */ void execute(BuildEnv env, Application app) throws PackagerException { - execute(appContextMapper.apply(createTaskContext(env, app))); + execute(contextMapper.apply(createTaskContext(env, app))); } /** @@ -81,8 +82,7 @@ final class PackagingPipeline { * @param outputDir the output directory for the package file */ void execute(BuildEnv env, Package pkg, Path outputDir) throws PackagerException { - execute((StartupParameters)createPackagingTaskContext(env, pkg, outputDir, - taskConfig, appImageLayoutForPackaging.apply(pkg))); + execute((StartupParameters)createPackagingTaskContext(env, pkg, outputDir, taskConfig)); } /** @@ -92,7 +92,7 @@ final class PackagingPipeline { * @param startupParameters the pipeline startup parameters */ void execute(StartupParameters startupParameters) throws PackagerException { - execute(pkgContextMapper.apply(createTaskContext((PackagingTaskContext)startupParameters))); + execute(contextMapper.apply(createTaskContext((PackagingTaskContext)startupParameters))); } /** @@ -135,21 +135,27 @@ final class PackagingPipeline { void execute(TaskAction taskAction) throws IOException, PackagerException; } - record AppImageBuildEnv(BuildEnv env, T app, U envLayout) { + record AppImageBuildEnv(BuildEnv env, T app) { + @SuppressWarnings("unchecked") + U envLayout() { + return (U)app.imageLayout(); + } + @SuppressWarnings("unchecked") U resolvedLayout() { - return (U)envLayout.resolveAt(env.appImageDir()); + return (U)env.appImageLayout(); } } - record PackageBuildEnv(BuildEnv env, T pkg, U envLayout, Path outputDir) { + record PackageBuildEnv(BuildEnv env, T pkg, Path outputDir) { @SuppressWarnings("unchecked") - U resolvedLayout() { - return (U)envLayout.resolveAt(env.appImageDir()); + U envLayout() { + return (U)pkg.appImageLayout(); } - AppImageBuildEnv appImageBuildEnv() { - return new AppImageBuildEnv<>(env, pkg.app(), envLayout); + @SuppressWarnings("unchecked") + U resolvedLayout() { + return (U)env.appImageLayout(); } } @@ -165,7 +171,7 @@ final class PackagingPipeline { @FunctionalInterface interface CopyAppImageTaskAction extends TaskAction { - void execute(T pkg, AppImageDesc srcAppImage, AppImageDesc dstAppImage) throws IOException, PackagerException; + void execute(T pkg, AppImageLayout srcAppImage, AppImageLayout dstAppImage) throws IOException, PackagerException; } @FunctionalInterface @@ -195,6 +201,11 @@ final class PackagingPipeline { super(id); } + private TaskBuilder(TaskID id, TaskConfig config) { + this(id); + config.action().ifPresent(this::setAction); + } + private TaskBuilder setAction(TaskAction v) { action = v; return this; @@ -225,6 +236,10 @@ final class PackagingPipeline { return setAction(action); } + boolean hasAction() { + return action != null; + } + @Override public TaskBuilder addDependent(TaskID v) { super.addDependent(v); @@ -283,6 +298,12 @@ final class PackagingPipeline { return new TaskBuilder(id); } + Stream configuredTasks() { + return taskConfig.entrySet().stream().map(e -> { + return new TaskBuilder(e.getKey(), e.getValue()); + }); + } + Builder excludeDirFromCopying(Path path) { Objects.requireNonNull(path); excludeCopyDirs.add(path); @@ -290,23 +311,7 @@ final class PackagingPipeline { } Builder contextMapper(UnaryOperator v) { - appContextMapper(v); - pkgContextMapper(v); - return this; - } - - Builder appContextMapper(UnaryOperator v) { - appContextMapper = v; - return this; - } - - Builder pkgContextMapper(UnaryOperator v) { - pkgContextMapper = v; - return this; - } - - Builder appImageLayoutForPackaging(Function v) { - appImageLayoutForPackaging = v; + contextMapper = v; return this; } @@ -318,27 +323,18 @@ final class PackagingPipeline { } StartupParameters createStartupParameters(BuildEnv env, Package pkg, Path outputDir) { - return createPackagingTaskContext(env, pkg, outputDir, taskConfig, - validatedAppImageLayoutForPackaging().apply(pkg)); - } - - private Function validatedAppImageLayoutForPackaging() { - return Optional.ofNullable(appImageLayoutForPackaging).orElse(Package::packageLayout); + return createPackagingTaskContext(env, pkg, outputDir, taskConfig); } PackagingPipeline create() { return new PackagingPipeline(taskGraphSnapshot(), taskConfig, - Optional.ofNullable(appContextMapper).orElse(UnaryOperator.identity()), - Optional.ofNullable(pkgContextMapper).orElse(UnaryOperator.identity()), - validatedAppImageLayoutForPackaging()); + Optional.ofNullable(contextMapper).orElse(UnaryOperator.identity())); } private final FixedDAG.Builder taskGraphBuilder = FixedDAG.build(); private final List excludeCopyDirs = new ArrayList<>(); private final Map taskConfig = new HashMap<>(); - private UnaryOperator appContextMapper; - private UnaryOperator pkgContextMapper; - private Function appImageLayoutForPackaging; + private UnaryOperator contextMapper; private FixedDAG taskGraphSnapshot; } @@ -404,21 +400,20 @@ final class PackagingPipeline { return builder; } - static void copyAppImage(Package pkg, AppImageDesc srcAppImage, AppImageDesc dstAppImage) throws IOException { + static void copyAppImage(Package pkg, AppImageLayout srcAppImage, AppImageLayout dstAppImage) throws IOException { copyAppImage(srcAppImage, dstAppImage, true); } - static void copyAppImage(AppImageDesc srcAppImage, AppImageDesc dstAppImage, + static void copyAppImage(AppImageLayout srcAppImage, AppImageLayout dstAppImage, boolean removeAppImageFile) throws IOException { - final var srcLayout = srcAppImage.resolvedAppImagelayout(); - final var srcLayoutPathGroup = AppImageLayout.toPathGroup(srcLayout); + final var srcLayoutPathGroup = toPathGroup(srcAppImage); - if (removeAppImageFile && srcLayout instanceof ApplicationLayout appLayout) { + if (removeAppImageFile && srcAppImage instanceof ApplicationLayout appLayout) { // Copy app layout omitting application image info file. srcLayoutPathGroup.ghostPath(AppImageFile.getPathInAppImage(appLayout)); } - srcLayoutPathGroup.copy(AppImageLayout.toPathGroup(dstAppImage.resolvedAppImagelayout()), LinkOption.NOFOLLOW_LINKS); + srcLayoutPathGroup.copy(toPathGroup(dstAppImage), LinkOption.NOFOLLOW_LINKS); } static void runPostAppImageUserScript(PackageBuildEnv env) throws IOException { @@ -432,57 +427,66 @@ final class PackagingPipeline { } private PackagingPipeline(FixedDAG taskGraph, Map taskConfig, - UnaryOperator appContextMapper, UnaryOperator pkgContextMapper, - Function appImageLayoutForPackaging) { + UnaryOperator contextMapper) { this.taskGraph = Objects.requireNonNull(taskGraph); this.taskConfig = Objects.requireNonNull(taskConfig); - this.appContextMapper = Objects.requireNonNull(appContextMapper); - this.pkgContextMapper = Objects.requireNonNull(pkgContextMapper); - this.appImageLayoutForPackaging = Objects.requireNonNull(appImageLayoutForPackaging); + this.contextMapper = Objects.requireNonNull(contextMapper); } private TaskContext createTaskContext(BuildEnv env, Application app) { - return new DefaultTaskContext(taskGraph, env, app, app.asApplicationLayout(), Optional.empty()); + return new DefaultTaskContext(taskGraph, env, app, Optional.empty()); } private TaskContext createTaskContext(PackagingTaskContext packagingContext) { - final var pkgEnv = BuildEnv.withAppImageDir(packagingContext.env.env(), packagingContext.srcAppImage.path()); - return new DefaultTaskContext(taskGraph, pkgEnv, packagingContext.env.pkg.app(), - packagingContext.srcAppImage.asApplicationLayout(), Optional.of(packagingContext)); + return new DefaultTaskContext(taskGraph, packagingContext.env(), + packagingContext.pkg().app(), Optional.of(packagingContext)); } private static PackagingTaskContext createPackagingTaskContext(BuildEnv env, Package pkg, - Path outputDir, Map taskConfig, AppImageLayout appImageLayoutForPackaging) { + Path outputDir, Map taskConfig) { Objects.requireNonNull(env); Objects.requireNonNull(outputDir); Objects.requireNonNull(taskConfig); - Objects.requireNonNull(appImageLayoutForPackaging); + if (pkg.appImageLayout().isResolved()) { + throw new IllegalArgumentException(); + } - final AppImageDesc srcAppImageDesc; - final AppImageDesc dstAppImageDesc; + final AppImageLayout srcLayout; + final AppImageLayout dstLayout; if (pkg.app().runtimeBuilder().isPresent()) { // Runtime builder is present, will build application image. - // appImageDir() should point to a directory where the application image will be created. - srcAppImageDesc = new AppImageDesc(appImageLayoutForPackaging, env.appImageDir()); - dstAppImageDesc = srcAppImageDesc; + srcLayout = pkg.appImageLayout().resolveAt(env.appImageDir()); + dstLayout = srcLayout; } else { - srcAppImageDesc = new AppImageDesc(pkg.app().imageLayout(), - pkg.predefinedAppImage().orElseThrow(UnsupportedOperationException::new)); + srcLayout = pkg.predefinedAppImage().map(predefinedAppImage -> { + // Will create a package from the predefined app image. + if (predefinedAppImage.equals(env.appImageDir())) { + return env.appImageLayout(); + } else { + return pkg.appImageLayout().resolveAt(predefinedAppImage); + } + }).orElseGet(() -> { + // No predefined app image and no runtime builder. + // This should be runtime packaging. + if (pkg.isRuntimeInstaller()) { + return env.appImageLayout(); + } else { + // Can't create app image without runtime builder. + throw new UnsupportedOperationException(); + } + }); if (taskConfig.get(CopyAppImageTaskID.COPY).action().isEmpty()) { - // "copy app image" task action is undefined indicating - // the package will use provided app image as-is. - dstAppImageDesc = srcAppImageDesc; + // "copy app image" task action is empty indicating + // the package will use provided app image in place. + dstLayout = srcLayout; } else { - dstAppImageDesc = new AppImageDesc(appImageLayoutForPackaging, env.buildRoot().resolve("image")); + dstLayout = pkg.appImageLayout().resolveAt(env.buildRoot().resolve("image")); } } - final var pkgEnv = new PackageBuildEnv<>( - BuildEnv.withAppImageDir(env, dstAppImageDesc.path()), pkg, dstAppImageDesc.appImageLayout(), outputDir); - - return new PackagingTaskContext(pkgEnv, srcAppImageDesc); + return new PackagingTaskContext(BuildEnv.withAppImageLayout(env, dstLayout), pkg, outputDir, srcLayout); } private void execute(TaskContext context) throws PackagerException { @@ -500,28 +504,30 @@ final class PackagingPipeline { try { builder.create().call(); + } catch (ExceptionBox ex) { + throw new PackagerException(ex.getCause()); + } catch (RuntimeException ex) { + throw ex; + } catch (PackagerException ex) { + throw ex; } catch (Exception ex) { - if (ex instanceof PackagerException pex) { - throw pex; - } else if (ex instanceof ExceptionBox bex) { - throw new PackagerException(bex.getCause()); - } else { - throw new PackagerException(ex); - } + throw new PackagerException(ex); } } - private record PackagingTaskContext(PackageBuildEnv env, - AppImageDesc srcAppImage) implements TaskContext, StartupParameters { + private record PackagingTaskContext(BuildEnv env, Package pkg, Path outputDir, + AppImageLayout srcAppImage) implements TaskContext, StartupParameters { PackagingTaskContext { Objects.requireNonNull(env); + Objects.requireNonNull(pkg); + Objects.requireNonNull(outputDir); Objects.requireNonNull(srcAppImage); } @Override public BuildEnv packagingEnv() { - return env.env; + return env; } @Override @@ -538,28 +544,31 @@ final class PackagingPipeline { @Override public void execute(TaskAction taskAction) throws IOException, PackagerException { if (taskAction instanceof PackageTaskAction) { - ((PackageTaskAction)taskAction).execute(env); + ((PackageTaskAction)taskAction).execute(pkgBuildEnv()); } else if (taskAction instanceof CopyAppImageTaskAction) { - ((CopyAppImageTaskAction)taskAction).execute(env.pkg(), - srcAppImage, new AppImageDesc(env.envLayout(), env.env().appImageDir())); + ((CopyAppImageTaskAction)taskAction).execute(pkg(), + srcAppImage, env.appImageLayout()); } else { throw new IllegalArgumentException(); } } AppImageBuildEnv appImageBuildEnv() { - return env.appImageBuildEnv(); + return new AppImageBuildEnv<>(env, pkg.app()); + } + + PackageBuildEnv pkgBuildEnv() { + return new PackageBuildEnv<>(env, pkg, outputDir); } } private record DefaultTaskContext(FixedDAG taskGraph, BuildEnv env, Application app, - Optional appLayout, Optional pkg) implements TaskContext { + Optional pkg) implements TaskContext { DefaultTaskContext { Objects.requireNonNull(taskGraph); Objects.requireNonNull(env); Objects.requireNonNull(app); - Objects.requireNonNull(appLayout); Objects.requireNonNull(pkg); } @@ -571,11 +580,11 @@ final class PackagingPipeline { if (pkg.isPresent() && !pkg.orElseThrow().test(taskID)) { return false; - } else if (pkg.isEmpty() && isPackageTask) { - // Building application image, skip packaging tasks. + } else if (pkg.isEmpty() && (isPackageTask || isCopyAppImageTask)) { + // Building application image, skip packaging and copying app image tasks. return false; - } else if (app.runtimeBuilder().isEmpty() && isBuildApplicationImageTask && !isCopyAppImageTask) { - // Runtime builder is not present, skip building application image tasks. + } else if (pkg.isPresent() && app.runtimeBuilder().isEmpty() && isBuildApplicationImageTask && !isCopyAppImageTask) { + // Building a package, runtime builder is not present, skip building application image tasks. return false; } else if (app.runtimeBuilder().isPresent() && isCopyAppImageTask && !isBuildApplicationImageTask) { // Runtime builder is present, skip copying app image tasks. @@ -596,7 +605,7 @@ final class PackagingPipeline { } else if (taskAction instanceof NoArgTaskAction noArgAction) { noArgAction.execute(); } else { - pkg.orElseThrow().execute(taskAction); + pkg.orElseThrow(UnsupportedOperationException::new).execute(taskAction); } } @@ -612,7 +621,7 @@ final class PackagingPipeline { @SuppressWarnings("unchecked") private AppImageBuildEnv appBuildEnv() { - return new AppImageBuildEnv<>(env, app, (T)appLayout.orElseThrow()); + return new AppImageBuildEnv<>(env, app); } } @@ -622,11 +631,7 @@ final class PackagingPipeline { Objects.requireNonNull(config); return () -> { if (config.action.isPresent() && context.test(id)) { - try { - context.execute(config.action.orElseThrow()); - } catch (ExceptionBox ex) { - throw ExceptionBox.rethrowUnchecked(ex); - } + context.execute(config.action.orElseThrow()); } return null; }; @@ -634,7 +639,5 @@ final class PackagingPipeline { private final FixedDAG taskGraph; private final Map taskConfig; - private final Function appImageLayoutForPackaging; - private final UnaryOperator appContextMapper; - private final UnaryOperator pkgContextMapper; + private final UnaryOperator contextMapper; } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/AppImageLayout.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/AppImageLayout.java index 1cd0e0f6c2e..a64946c06d0 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/AppImageLayout.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/AppImageLayout.java @@ -25,15 +25,18 @@ package jdk.jpackage.internal.model; +import static jdk.jpackage.internal.util.PathUtils.mapNullablePath; +import static jdk.jpackage.internal.util.function.ThrowingFunction.toFunction; + import java.lang.reflect.Modifier; import java.nio.file.Path; import java.util.HashMap; import java.util.Map; +import java.util.Objects; import java.util.Optional; +import java.util.function.UnaryOperator; import java.util.stream.Stream; import jdk.jpackage.internal.util.PathGroup; -import static jdk.jpackage.internal.util.function.ThrowingFunction.toFunction; -import static jdk.jpackage.internal.util.PathUtils.resolveNullablePath; /** @@ -41,9 +44,14 @@ import static jdk.jpackage.internal.util.PathUtils.resolveNullablePath; * * App image layout is a collection of files and directories with specific roles * (executables, configuration files, etc.) sharing the same root directory. - * - * The layout is "unresolved" if the root directory is an empty string and - * "resolved" otherwise. + *

      + * The layout is "unresolved" if the root directory is an empty path + * ({@code Path.of("")}) and "resolved" otherwise. + *

      + * The return value of the {@link #runtimeDirectory()} method call is always a + * path starting with the path returned by the {@link #rootDirectory()} method + * call. Public methods without parameters and with the return type {@link Path} + * in the derived interfaces must comply to this constrain. */ public interface AppImageLayout { @@ -56,29 +64,94 @@ public interface AppImageLayout { Path runtimeDirectory(); /** - * Root directory of this app image. + * Root directory of this app image layout. * It should normally be equal to Path.of("") for unresolved layout. * - * @return the root directory of this app image + * @return the root directory of this app image layout */ Path rootDirectory(); /** - * Creates a copy of this app image resolved at the given root directory. + * Returns a copy of this app image layout with the root directory set to an empty + * path ({@code Path.of("")}) or this instance if its root directory is already + * an empty path. + * + * @return an app image layout with the root directory set to an empty path + */ + AppImageLayout resetRootDirectory(); + + /** + * Returns true if the root directory of this app image layout is + * not an empty path, i.e, if it is not equal to Path.of(""). + * + * @return true if the root directory of this app image layout is + * not an empty path + */ + default boolean isResolved() { + return !rootDirectory().equals(Path.of("")); + } + + /** + * Creates a copy of this app image layout resolved at the given root directory. * * @param root path to a directory at which to resolve the layout - * @return a copy of this app image resolved at the given root directory + * @return a copy of this app image layout resolved at the given root directory */ - AppImageLayout resolveAt(Path root); + default AppImageLayout resolveAt(Path root) { + return map(root::resolve); + } + + /** + * Returns a copy of this app image layout resolved such that its root directory + * is set to an empty path ({@code Path.of("")}) or this instance if its root + * directory is already an empty path. + * + * @return an app image layout resolved at {@code Path.of("")} path + */ + default AppImageLayout unresolve() { + if (isResolved()) { + final var root = rootDirectory(); + return map(root::relativize); + } else { + return this; + } + } + + /** + * Returns a copy of this app image layout with the specified mapper applied to + * every path. + * + * @param mapper the mapper to use with every path in this app image layout. + * @return the copy of this app image layout with the specified mapper applied + * to every path + */ + AppImageLayout map(UnaryOperator mapper); /** * Default implementation of {@link AppImageLayout} interface. - */ + */ record Stub(Path rootDirectory, Path runtimeDirectory) implements AppImageLayout { + public Stub { + Objects.requireNonNull(rootDirectory); + } + + public Stub(Path runtimeDirectory) { + this(Path.of(""), runtimeDirectory); + } + @Override - public AppImageLayout resolveAt(Path base) { - return new Stub(resolveNullablePath(base, rootDirectory), resolveNullablePath(base, runtimeDirectory)); + public AppImageLayout resetRootDirectory() { + if (isResolved()) { + return new Stub(runtimeDirectory); + } else { + return this; + } + } + + @Override + public AppImageLayout map(UnaryOperator mapper) { + return new Stub(mapNullablePath(mapper, rootDirectory), mapNullablePath(mapper, runtimeDirectory)); } } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/ApplicationLayout.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/ApplicationLayout.java index b8b2eda4437..013d2fc78cd 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/ApplicationLayout.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/ApplicationLayout.java @@ -24,10 +24,11 @@ */ package jdk.jpackage.internal.model; -import static jdk.jpackage.internal.util.PathUtils.resolveNullablePath; +import static jdk.jpackage.internal.util.PathUtils.mapNullablePath; import java.nio.file.Path; import java.util.Objects; +import java.util.function.UnaryOperator; import jdk.jpackage.internal.util.CompositeProxy; /** @@ -42,7 +43,26 @@ public interface ApplicationLayout extends AppImageLayout, ApplicationLayoutMixi @Override default ApplicationLayout resolveAt(Path root) { - return buildFrom(this).resolveAt(root).create(); + return (ApplicationLayout)AppImageLayout.super.resolveAt(root); + } + + @Override + default ApplicationLayout unresolve() { + return (ApplicationLayout)AppImageLayout.super.unresolve(); + } + + @Override + default ApplicationLayout resetRootDirectory() { + if (isResolved()) { + return buildFrom(this).rootDirectory("").create(); + } else { + return this; + } + } + + @Override + default ApplicationLayout map(UnaryOperator mapper) { + return buildFrom(this).mutate(mapper).create(); } /** @@ -113,14 +133,14 @@ public interface ApplicationLayout extends AppImageLayout, ApplicationLayoutMixi return this; } - public Builder resolveAt(Path base) { - rootDirectory(resolveNullablePath(base, rootDirectory)); - launchersDirectory(resolveNullablePath(base, launchersDirectory)); - appDirectory(resolveNullablePath(base, appDirectory)); - runtimeDirectory(resolveNullablePath(base, runtimeDirectory)); - appModsDirectory(resolveNullablePath(base, appModsDirectory)); - desktopIntegrationDirectory(resolveNullablePath(base, desktopIntegrationDirectory)); - contentDirectory(resolveNullablePath(base, contentDirectory)); + public Builder mutate(UnaryOperator mapper) { + rootDirectory(mapNullablePath(mapper, rootDirectory)); + launchersDirectory(mapNullablePath(mapper, launchersDirectory)); + appDirectory(mapNullablePath(mapper, appDirectory)); + runtimeDirectory(mapNullablePath(mapper, runtimeDirectory)); + appModsDirectory(mapNullablePath(mapper, appModsDirectory)); + desktopIntegrationDirectory(mapNullablePath(mapper, desktopIntegrationDirectory)); + contentDirectory(mapNullablePath(mapper, contentDirectory)); return this; } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/Package.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/Package.java index 6d2fdaf0bbb..33c707b776f 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/Package.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/Package.java @@ -30,19 +30,11 @@ import java.util.Optional; /** * Native application package. * - * The interface specifies the source app image layout with two transformations: - * package app image layout and installed app image layout. + * The interface specifies the source app image and the installed app image layouts. *

      * Use {@link #appImageLayout()} or {@link #asApplicationLayout()} to get the * unresolved source app image layout. *

      - * Package app image layout is the source app image layout resolved at the - * relative installation directory of the package. Additionally, to resolve the - * source layout, some packages may transform the source layout. - *

      - * Use {@link #packageLayout()} or {@link #asPackageApplicationLayout()} to get - * the package app image layout. - *

      * Installed app image layout is the layout of the installed app image. *

      * Use {@link #installedPackageLayout()} or @@ -55,25 +47,21 @@ import java.util.Optional; *

  • * * - * * * * * * * - * * * * * - * * * * * * - * * * *
    Source app image layoutPackage app image layoutInstalled app image layout
    Windowsbin/foo.exe app/foo.jarDuke/bin/foo.exe Duke/app/foo.jarDuke/bin/foo.exe Duke/app/foo.jar
    Linuxbin/foo lib/app/foo.jaropt/duke/bin/foo opt/duke/lib/app/foo.jar/opt/duke/bin/foo /opt/duke/lib/app/foo.jar
    OSXContents/MacOS/foo Contents/app/foo.jarApplications/Duke.app/Contents/MacOS/foo Applications/Duke.app/Contents/app/foo.jar/Applications/Duke.app/Contents/MacOS/foo /Applications/Duke.app/Contents/app/foo.jar
    @@ -169,7 +157,6 @@ public interface Package extends BundleSpec { * * @return the unresolved app image layout of the application of this package * - * @see #packageLayout * @see #installedPackageLayout */ default AppImageLayout appImageLayout() { @@ -193,68 +180,14 @@ public interface Package extends BundleSpec { return app().asApplicationLayout(); } - /** - * Gets the layout of the installed app image of the application resolved at the - * relative installation directory of this package. - * - * @return the layout of the installed app image of the application resolved at - * the relative installation directory of this package - * - * @see #relativeInstallDir - * @see #appImageLayout - * @see #installedPackageLayout - */ - default AppImageLayout packageLayout() { - return appImageLayout().resolveAt(relativeInstallDir()); - } - - /** - * Returns the layout of the installed app image of the application resolved at - * the relative installation directory of this package as - * {@link ApplicationLayout} type or an empty {@link Optional} instance if the - * layout object is of incompatible type. - *

    - * Returns an empty {@link Optional} instance if {@link #isRuntimeInstaller()} - * returns true. - * - * @return the layout of the installed app image of the application resolved at - * the relative installation directory of this package as - * {@link ApplicationLayout} type - * - * @see #packageLayout - */ - default Optional asPackageApplicationLayout() { - if (packageLayout() instanceof ApplicationLayout layout) { - return Optional.of(layout); - } else { - return Optional.empty(); - } - } - /** * Gets the layout of the installed app image of this package. * * @return the layout of the installed app image of this package * * @see #appImageLayout - * @see #packageLayout */ - default AppImageLayout installedPackageLayout() { - return asStandardPackageType().map(stdType -> { - switch (stdType) { - case LINUX_DEB, LINUX_RPM, MAC_DMG, MAC_PKG -> { - return packageLayout().resolveAt(Path.of("/")); - } - case WIN_EXE, WIN_MSI -> { - return packageLayout(); - } - default -> { - // Should never get here - throw new IllegalStateException(); - } - } - }).orElseThrow(UnsupportedOperationException::new); - } + AppImageLayout installedPackageLayout(); /** * Returns the layout of the installed app image of this package as @@ -334,6 +267,6 @@ public interface Package extends BundleSpec { */ record Stub(Application app, PackageType type, String packageName, String description, String version, Optional aboutURL, Optional licenseFile, Optional predefinedAppImage, - Path relativeInstallDir) implements Package { + AppImageLayout installedPackageLayout, Path relativeInstallDir) implements Package { } } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/RuntimeBuilder.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/RuntimeBuilder.java index bce3b417b7c..c989bcc8915 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/RuntimeBuilder.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/RuntimeBuilder.java @@ -43,7 +43,7 @@ public interface RuntimeBuilder { * @param appImageLayout the app image where to create Java runtime. * @throws PackagerException if packaging error occurs */ - void createRuntime(AppImageLayout appImageLayout) throws PackagerException; + void create(AppImageLayout appImageLayout) throws PackagerException; /** * Gets the default set of paths where to find Java modules. diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/RuntimeLayout.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/RuntimeLayout.java index 733fc7a74c3..d995774af8f 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/RuntimeLayout.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/RuntimeLayout.java @@ -24,9 +24,10 @@ */ package jdk.jpackage.internal.model; -import static jdk.jpackage.internal.util.PathUtils.resolveNullablePath; +import static jdk.jpackage.internal.util.PathUtils.mapNullablePath; import java.nio.file.Path; +import java.util.function.UnaryOperator; import jdk.jpackage.internal.util.CompositeProxy; /** @@ -39,8 +40,27 @@ public interface RuntimeLayout extends AppImageLayout { @Override default RuntimeLayout resolveAt(Path root) { - return create(new AppImageLayout.Stub(resolveNullablePath(root, rootDirectory()), - resolveNullablePath(root, runtimeDirectory()))); + return (RuntimeLayout)AppImageLayout.super.resolveAt(root); + } + + @Override + default RuntimeLayout resetRootDirectory() { + if (isResolved()) { + return create(runtimeDirectory()); + } else { + return this; + } + } + + @Override + default RuntimeLayout unresolve() { + return (RuntimeLayout)AppImageLayout.super.unresolve(); + } + + @Override + default RuntimeLayout map(UnaryOperator mapper) { + return create(new RuntimeLayout.Stub(mapNullablePath(mapper, rootDirectory()), + mapNullablePath(mapper, runtimeDirectory()))); } /** diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/ResourceLocator.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/ResourceLocator.java index dfb5da7f829..93d317b0128 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/ResourceLocator.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/ResourceLocator.java @@ -32,7 +32,7 @@ package jdk.jpackage.internal.resources; * to call getResourceAsStream() to get those resources. */ -public class ResourceLocator { - public ResourceLocator() { +public final class ResourceLocator { + private ResourceLocator() { } } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/PathGroup.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/PathGroup.java index 08a91175c1e..55144826efb 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/PathGroup.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/PathGroup.java @@ -470,5 +470,22 @@ public final class PathGroup { } } + @Override + public int hashCode() { + return Objects.hash(entries); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + PathGroup other = (PathGroup) obj; + return Objects.equals(entries, other.entries); + } + private final Map entries; } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/PathUtils.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/PathUtils.java index 31019832b73..a8944a67ae0 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/PathUtils.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/PathUtils.java @@ -25,7 +25,9 @@ package jdk.jpackage.internal.util; import java.nio.file.Path; +import java.util.Objects; import java.util.Optional; +import java.util.function.UnaryOperator; public final class PathUtils { @@ -47,8 +49,13 @@ public final class PathUtils { return parent != null ? parent.resolve(filename) : Path.of(filename); } - public static Path resolveNullablePath(Path base, Path path) { - return Optional.ofNullable(path).map(base::resolve).orElse(null); + public static Path mapNullablePath(UnaryOperator mapper, Path path) { + Objects.requireNonNull(mapper); + if (path != null) { + return mapper.apply(path); + } else { + return null; + } } public static Path normalizedAbsolutePath(Path path) { diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinPackagingPipeline.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinPackagingPipeline.java index be529bdc19e..2fa7dd895c3 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinPackagingPipeline.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinPackagingPipeline.java @@ -34,7 +34,6 @@ import jdk.jpackage.internal.PackagingPipeline.CopyAppImageTaskID; import jdk.jpackage.internal.PackagingPipeline.PrimaryTaskID; import jdk.jpackage.internal.PackagingPipeline.TaskID; import jdk.jpackage.internal.model.ApplicationLayout; -import jdk.jpackage.internal.model.Package; import jdk.jpackage.internal.model.PackagerException; import jdk.jpackage.internal.model.WinApplication; import jdk.jpackage.internal.model.WinLauncher; @@ -47,7 +46,6 @@ final class WinPackagingPipeline { static PackagingPipeline.Builder build() { return PackagingPipeline.buildStandard() - .appImageLayoutForPackaging(Package::appImageLayout) .task(CopyAppImageTaskID.COPY).noaction().add() .task(WinAppImageTaskID.REBRAND_LAUNCHERS) .addDependency(BuildApplicationTaskID.LAUNCHERS) diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/model/WinExePackage.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/model/WinExePackage.java index 6244090b71f..f7715c5ac3d 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/model/WinExePackage.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/model/WinExePackage.java @@ -46,6 +46,7 @@ public interface WinExePackage extends Package, WinExePackageMixin { pkg.aboutURL(), pkg.licenseFile(), pkg.predefinedAppImage(), + pkg.installedPackageLayout(), pkg.relativeInstallDir()); } } diff --git a/test/jdk/tools/jpackage/helpers-test/TEST.properties b/test/jdk/tools/jpackage/helpers-test/TEST.properties index 1830ea05443..ff5e0eef9de 100644 --- a/test/jdk/tools/jpackage/helpers-test/TEST.properties +++ b/test/jdk/tools/jpackage/helpers-test/TEST.properties @@ -1,6 +1,6 @@ JUnit.dirs = . -lib.dirs = /test/jdk/tools/jpackage/helpers /test/jdk/tools/jpackage/helpers-test +lib.dirs = /test/jdk/tools/jpackage/helpers /test/jdk/tools/jpackage/junit/tools modules=jdk.jpackage/jdk.jpackage.internal:+open \ jdk.jpackage/jdk.jpackage.internal.util:+open \ diff --git a/test/jdk/tools/jpackage/helpers-test/jdk/jpackage/test/JavaAppDescTest.java b/test/jdk/tools/jpackage/helpers-test/jdk/jpackage/test/JavaAppDescTest.java index 50f1992f796..08d0ba76d1c 100644 --- a/test/jdk/tools/jpackage/helpers-test/jdk/jpackage/test/JavaAppDescTest.java +++ b/test/jdk/tools/jpackage/helpers-test/jdk/jpackage/test/JavaAppDescTest.java @@ -42,11 +42,10 @@ public class JavaAppDescTest extends JUnitAdapter { @Parameter({"Foo", "Foo.class"}) @Parameter({"com.bar.A", "com/bar/A.class"}) @Parameter({"module/com.bar.A", "com/bar/A.class"}) - public static void testClassFilePath(String... args) { - var appDesc = args[0]; - var expectedClassFilePath = Path.of(args[1]); - TKit.assertEquals(expectedClassFilePath.toString(), JavaAppDesc.parse( - appDesc).classFilePath().toString(), null); + public static void testClassFilePath(String appDesc, String expectedClassFile) { + var expectedClassFilePath = Path.of(expectedClassFile); + TKit.assertEquals(expectedClassFilePath.toString(), + JavaAppDesc.parse(appDesc).classFilePath().toString(), null); } public static List input() { @@ -55,6 +54,12 @@ public class JavaAppDescTest extends JUnitAdapter { createTestCase("foo.jar*", "foo.jar*hello.jar:Hello"), createTestCase("Bye", "hello.jar:Bye"), createTestCase("bye.jar:", "bye.jar:Hello"), + createTestCase("bye.jar:!", appDesc -> { + return appDesc + .setBundleFileName("bye.jar") + .setClassName("Hello") + .setWithMainClass(true); + }), createTestCase("duke.jar:com.other/com.other.foo.bar.Buz!@3.7", appDesc -> { return appDesc .setBundleFileName("duke.jar") diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java index 3a89ba28d26..3a423fd71ca 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java @@ -1174,10 +1174,15 @@ public class JPackageCommand extends CommandArguments { "Check for unexpected value of property in app image file"); } - TKit.assertStringListEquals( - addLauncherNames().stream().sorted().toList(), - aif.addLaunchers().keySet().stream().sorted().toList(), - "Check additional launcher names"); + // Don't compare the add launchers configured on the command line with the + // add launchers listed in the `.jpackage.xml` file if the latter comes from + // a predefined app image. + if (!hasArgument("--app-image")) { + TKit.assertStringListEquals( + addLauncherNames().stream().sorted().toList(), + aif.addLaunchers().keySet().stream().sorted().toList(), + "Check additional launcher names"); + } } } diff --git a/test/jdk/tools/jpackage/junit/TEST.properties b/test/jdk/tools/jpackage/junit/TEST.properties index 34d66c3b8d7..c540eb76869 100644 --- a/test/jdk/tools/jpackage/junit/TEST.properties +++ b/test/jdk/tools/jpackage/junit/TEST.properties @@ -1,5 +1,7 @@ JUnit.dirs = share +lib.dirs = /test/jdk/tools/jpackage/helpers /test/jdk/tools/jpackage/junit/tools + modules=jdk.jpackage/jdk.jpackage.internal:+open \ jdk.jpackage/jdk.jpackage.internal.model:+open \ jdk.jpackage/jdk.jpackage.internal.pipeline:+open \ diff --git a/test/jdk/tools/jpackage/junit/linux/jdk.jpackage/jdk/jpackage/internal/LinuxApplicationLayoutTest.java b/test/jdk/tools/jpackage/junit/linux/jdk.jpackage/jdk/jpackage/internal/LinuxApplicationLayoutTest.java new file mode 100644 index 00000000000..0bb0fe5b73e --- /dev/null +++ b/test/jdk/tools/jpackage/junit/linux/jdk.jpackage/jdk/jpackage/internal/LinuxApplicationLayoutTest.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.jpackage.internal; + +import java.nio.file.Path; +import jdk.jpackage.internal.model.AppImageLayoutTest; +import jdk.jpackage.internal.model.ApplicationLayoutTest; +import org.junit.jupiter.api.Test; + +public class LinuxApplicationLayoutTest { + + @Test + public void testResolveAt() { + AppImageLayoutTest.testResolveAt(createLayout()); + } + + @Test + public void testResolveAtRepeat() { + AppImageLayoutTest.testResolveAtRepeat(createLayout()); + } + + @Test + public void testUnresolve() { + AppImageLayoutTest.testUnresolve(createLayout()); + } + + @Test + public void testEmptyRootDirectory() { + AppImageLayoutTest.testEmptyRootDirectory(createLayout()); + } + + public static LinuxApplicationLayout createLayout() { + return LinuxApplicationLayout.create(ApplicationLayoutTest.createLayout(), + Path.of("libapplauncher.so")); + } +} diff --git a/test/jdk/tools/jpackage/junit/linux/junit.java b/test/jdk/tools/jpackage/junit/linux/junit.java new file mode 100644 index 00000000000..214812e951e --- /dev/null +++ b/test/jdk/tools/jpackage/junit/linux/junit.java @@ -0,0 +1,32 @@ +/* + * 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 LinuxApplicationLayout + * @requires (os.family == "linux") + * @compile/module=jdk.jpackage -Xlint:all -Werror + * jdk/jpackage/internal/LinuxApplicationLayoutTest.java + * ../../share/jdk.jpackage/jdk/jpackage/internal/model/AppImageLayoutTest.java + * ../../share/jdk.jpackage/jdk/jpackage/internal/model/ApplicationLayoutTest.java + * @run junit jdk.jpackage/jdk.jpackage.internal.LinuxApplicationLayoutTest + */ diff --git a/test/jdk/tools/jpackage/junit/macosx/jdk.jpackage/jdk/jpackage/internal/MacApplicationLayoutTest.java b/test/jdk/tools/jpackage/junit/macosx/jdk.jpackage/jdk/jpackage/internal/MacApplicationLayoutTest.java new file mode 100644 index 00000000000..7ee3b41b7f2 --- /dev/null +++ b/test/jdk/tools/jpackage/junit/macosx/jdk.jpackage/jdk/jpackage/internal/MacApplicationLayoutTest.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.jpackage.internal; + +import java.nio.file.Path; +import jdk.jpackage.internal.model.AppImageLayoutTest; +import jdk.jpackage.internal.model.ApplicationLayoutTest; +import org.junit.jupiter.api.Test; + +public class MacApplicationLayoutTest { + + @Test + public void testResolveAt() { + AppImageLayoutTest.testResolveAt(createLayout()); + } + + @Test + public void testResolveAtRepeat() { + AppImageLayoutTest.testResolveAtRepeat(createLayout()); + } + + @Test + public void testUnresolve() { + AppImageLayoutTest.testUnresolve(createLayout()); + } + + @Test + public void testEmptyRootDirectory() { + AppImageLayoutTest.testEmptyRootDirectory(createLayout()); + } + + public static MacApplicationLayout createLayout() { + return MacApplicationLayout.create(ApplicationLayoutTest.createLayout(), + Path.of("Contents/runtime")); + } +} diff --git a/test/jdk/tools/jpackage/junit/macosx/junit.java b/test/jdk/tools/jpackage/junit/macosx/junit.java new file mode 100644 index 00000000000..1549aaa6cd7 --- /dev/null +++ b/test/jdk/tools/jpackage/junit/macosx/junit.java @@ -0,0 +1,32 @@ +/* + * 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 MacApplicationLayoutTest + * @requires (os.family == "mac") + * @compile/module=jdk.jpackage -Xlint:all -Werror + * jdk/jpackage/internal/MacApplicationLayoutTest.java + * ../../share/jdk.jpackage/jdk/jpackage/internal/model/AppImageLayoutTest.java + * ../../share/jdk.jpackage/jdk/jpackage/internal/model/ApplicationLayoutTest.java + * @run junit jdk.jpackage/jdk.jpackage.internal.MacApplicationLayoutTest + */ diff --git a/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/BuildEnvTest.java b/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/BuildEnvTest.java new file mode 100644 index 00000000000..c71ef307f0e --- /dev/null +++ b/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/BuildEnvTest.java @@ -0,0 +1,140 @@ +/* + * 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 + * 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.jpackage.internal; + +import static jdk.jpackage.internal.model.AppImageLayout.toPathGroup; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.file.Path; +import java.util.Optional; +import jdk.jpackage.internal.model.AppImageLayout; +import jdk.jpackage.internal.model.ApplicationLayout; +import jdk.jpackage.internal.model.RuntimeLayout; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + + +public class BuildEnvTest { + + @ParameterizedTest + @ValueSource(strings = {"", "image"}) + public void testUnresolvedAppImageLayout(Path appImageDir) { + final var rootDir = Path.of(""); + + final var env = BuildEnv.create(rootDir, Optional.empty(), true, + BuildEnvTest.class, RuntimeLayout.DEFAULT.resolveAt(appImageDir).resetRootDirectory()); + + assertEquals(env.appImageDir(), env.appImageLayout().rootDirectory()); + + assertEquals(Path.of(""), env.appImageDir()); + assertEquals(toPathGroup(RuntimeLayout.DEFAULT.resolveAt(appImageDir)), toPathGroup(env.appImageLayout())); + assertEquals(rootDir, env.buildRoot()); + assertEquals(rootDir.resolve("config"), env.configDir()); + assertEquals(Optional.empty(), env.resourceDir()); + assertTrue(env.verbose()); + } + + @Test + public void testResolvedAppImageLayout() { + final var rootDir = Path.of("/oof"); + final var appImageDir = Path.of("/foo/bar"); + + final var layout = RuntimeLayout.DEFAULT.resolveAt(appImageDir); + final var env = BuildEnv.create(rootDir, Optional.empty(), true, BuildEnvTest.class, layout); + + assertSame(layout, env.appImageLayout()); + assertEquals(env.appImageDir(), env.appImageLayout().rootDirectory()); + + assertEquals(Path.of("/foo/bar"), env.appImageDir()); + assertEquals(toPathGroup(RuntimeLayout.DEFAULT.resolveAt(appImageDir)), toPathGroup(env.appImageLayout())); + assertEquals(rootDir, env.buildRoot()); + assertEquals(rootDir.resolve("config"), env.configDir()); + assertEquals(Optional.empty(), env.resourceDir()); + assertTrue(env.verbose()); + } + + @ParameterizedTest + @ValueSource(strings = {"", "/foo/bar"}) + public void test_withAppImageDir(Path appImageDir) { + final var rootDir = Path.of("/oof"); + + final var layout = RuntimeLayout.DEFAULT; + final var env = BuildEnv.withAppImageDir(BuildEnv.create(rootDir, + Optional.empty(), false, BuildEnvTest.class, layout), appImageDir); + + assertNotSame(layout, env.appImageLayout()); + assertEquals(env.appImageDir(), env.appImageLayout().rootDirectory()); + + assertEquals(appImageDir, env.appImageDir()); + assertEquals(toPathGroup(RuntimeLayout.DEFAULT.resolveAt(appImageDir)), toPathGroup(env.appImageLayout())); + assertEquals(rootDir, env.buildRoot()); + assertEquals(rootDir.resolve("config"), env.configDir()); + assertEquals(Optional.empty(), env.resourceDir()); + assertFalse(env.verbose()); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + public void test_withAppImageLayout(boolean resolved) { + final var rootDir = Path.of("/oof"); + + final var appImageDir = Path.of("/foo/bar"); + + final AppImageLayout layout; + if (resolved) { + layout = RuntimeLayout.DEFAULT.resolveAt(appImageDir); + } else { + layout = RuntimeLayout.DEFAULT.resolveAt(appImageDir).resetRootDirectory(); + } + + final var env = BuildEnv.withAppImageLayout(BuildEnv.create(rootDir, + Optional.empty(), false, BuildEnvTest.class, RuntimeLayout.DEFAULT), layout); + + assertSame(layout, env.appImageLayout()); + assertEquals(env.appImageDir(), env.appImageLayout().rootDirectory()); + + assertEquals(toPathGroup(RuntimeLayout.DEFAULT.resolveAt(appImageDir)), toPathGroup(env.appImageLayout())); + assertEquals(rootDir, env.buildRoot()); + assertEquals(rootDir.resolve("config"), env.configDir()); + assertEquals(Optional.empty(), env.resourceDir()); + assertFalse(env.verbose()); + } + + @Test + public void test_asApplicationLayout() { + final var rootDir = Path.of("r"); + + assertTrue(BuildEnv.create(rootDir, Optional.empty(), false, + BuildEnvTest.class, RuntimeLayout.DEFAULT).asApplicationLayout().isEmpty()); + + var layout = ApplicationLayout.build().setAll("foo").create(); + assertSame(layout, BuildEnv.create(rootDir, Optional.empty(), false, + BuildEnvTest.class, layout).asApplicationLayout().orElseThrow()); + } +} diff --git a/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/PackagingPipelineTest.java b/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/PackagingPipelineTest.java new file mode 100644 index 00000000000..24c1d62c34f --- /dev/null +++ b/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/PackagingPipelineTest.java @@ -0,0 +1,872 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.jpackage.internal; + +import static jdk.jpackage.internal.util.function.ExceptionBox.rethrowUnchecked; +import static jdk.jpackage.internal.util.function.ThrowingConsumer.toConsumer; +import static jdk.jpackage.internal.util.function.ThrowingSupplier.toSupplier; +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.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrowsExactly; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import jdk.jpackage.internal.PackagingPipeline.BuildApplicationTaskID; +import jdk.jpackage.internal.PackagingPipeline.CopyAppImageTaskID; +import jdk.jpackage.internal.PackagingPipeline.NoArgTaskAction; +import jdk.jpackage.internal.PackagingPipeline.PackageTaskID; +import jdk.jpackage.internal.PackagingPipeline.PrimaryTaskID; +import jdk.jpackage.internal.PackagingPipeline.TaskAction; +import jdk.jpackage.internal.PackagingPipeline.TaskContext; +import jdk.jpackage.internal.PackagingPipeline.TaskID; +import jdk.jpackage.internal.model.AppImageLayout; +import jdk.jpackage.internal.model.Application; +import jdk.jpackage.internal.model.ApplicationLayout; +import jdk.jpackage.internal.model.ConfigException; +import jdk.jpackage.internal.model.Package; +import jdk.jpackage.internal.model.PackageType; +import jdk.jpackage.internal.model.PackagerException; +import jdk.jpackage.internal.model.RuntimeBuilder; +import jdk.jpackage.internal.model.RuntimeLayout; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.ValueSource; + + +public class PackagingPipelineTest { + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void testBuildApplication(boolean withRuntimeBuilder, @TempDir Path workDir) throws ConfigException, PackagerException, IOException { + + final var app = createApp(TEST_LAYOUT_1, withRuntimeBuilder ? Optional.of(TestRuntimeBuilder.INSTANCE) : Optional.empty()); + final var env = buildEnv(workDir.resolve("build")).appImageDirFor(app).create(); + + // Build application image in `env.appImageDir()` directory. + final var builder = buildPipeline(); + if (app.runtimeBuilder().isEmpty()) { + builder.task(BuildApplicationTaskID.RUNTIME).noaction().add(); + } + + builder.create().execute(env, app); + + assertEquals(app.appImageDirName(), env.appImageDir().getFileName()); + + var executedTaskActions = dryRun(builder, toConsumer(_ -> { + builder.create().execute(env, app); + })); + + List expectedActions = new ArrayList<>(); + if (app.runtimeBuilder().isPresent()) { + expectedActions.add(BuildApplicationTaskID.RUNTIME); + } + expectedActions.addAll(List.of(BuildApplicationTaskID.LAUNCHERS, BuildApplicationTaskID.CONTENT)); + + assertEquals(expectedActions, executedTaskActions); + + final ExpectedAppImage expectedAppImage; + if (withRuntimeBuilder) { + expectedAppImage = ExpectedAppImage.build().dir("") + .file("launchers/my-launcher", TestLauncher.CONTENT) + .file("runtime/my-runtime", TestRuntimeBuilder.CONTENT); + } else { + expectedAppImage = ExpectedAppImage.build().dir("") + .file("launchers/my-launcher", TestLauncher.CONTENT); + } + + assertEquals(expectedAppImage, ExpectedAppImage.load(env.appImageDir())); + } + + @Test + void testCopyApplication(@TempDir Path workDir) throws ConfigException, PackagerException, IOException { + + final var srcApp = createApp(TEST_LAYOUT_1, TestRuntimeBuilder.INSTANCE); + + final var srcEnv = buildEnv(workDir.resolve("build")).appImageDirFor(srcApp).create(); + + // Build application image in `srcEnv.appImageDir()` directory. + buildPipeline().create().execute(srcEnv, srcApp); + + final var dstApp = createApp(TEST_LAYOUT_2, TestRuntimeBuilder.INSTANCE); + + final var dstEnv = buildEnv(workDir.resolve("build-2")) + .appImageLayout(dstApp.imageLayout().resolveAt(workDir.resolve("a/b/c"))) + .create(); + + // Copy application image from `srcEnv.appImageDir()` into `dstEnv.appImageDir()` + // with layout transformation. + // This test exercises flexibility of the packaging pipeline. + final var builder = buildPipeline() + .task(PrimaryTaskID.BUILD_APPLICATION_IMAGE).applicationAction(cfg -> { + assertSame(dstApp, cfg.app()); + assertEquals(dstEnv.appImageDir(), cfg.env().appImageLayout().rootDirectory()); + assertFalse(Files.exists(dstEnv.appImageDir())); + PackagingPipeline.copyAppImage(srcEnv.appImageLayout(), cfg.env().appImageLayout(), false); + }).add(); + + // Disable the default "build application image" actions of the tasks which + // are the dependencies of `PrimaryTaskID.BUILD_APPLICATION_IMAGE` task as + // their output will be overwritten in the custom action of this task. + builder.taskGraphSnapshot().getAllTailsOf(PrimaryTaskID.BUILD_APPLICATION_IMAGE).forEach(taskId -> { + builder.task(taskId).noaction().add(); + }); + + builder.create().execute(dstEnv, dstApp); + + AppImageLayout.toPathGroup(dstEnv.appImageLayout()).paths().forEach(path -> { + assertTrue(Files.exists(path)); + }); + + assertEquals(Path.of("c"), dstEnv.appImageDir().getFileName()); + + var executedTaskActions = dryRun(builder, toConsumer(_ -> { + builder.create().execute(dstEnv, dstApp); + })); + + assertEquals(List.of(PrimaryTaskID.BUILD_APPLICATION_IMAGE), executedTaskActions); + + final ExpectedAppImage expectedAppImage = ExpectedAppImage.build().dir("") + .file("q/launchers/my-launcher", TestLauncher.CONTENT) + .file("qqq/runtime/my-runtime", TestRuntimeBuilder.CONTENT); + + assertEquals(expectedAppImage, ExpectedAppImage.load(dstEnv.appImageDir())); + } + + @Test + void testCreatePackage(@TempDir Path workDir) throws ConfigException, PackagerException, IOException { + + final var outputDir = workDir.resolve("bundles"); + final var pkg = buildPackage(createApp(TEST_LAYOUT_1_WITH_INSTALL_DIR, TestRuntimeBuilder.INSTANCE)).create(); + final var env = buildEnv(workDir.resolve("build")).appImageDirFor(pkg).create(); + + final var builder = buildPipeline(); + + // Will create an app image in `env.appImageDir()` directory with `pkg.appImageLayout()` layout. + // Will convert the created app image into a package. + builder.create().execute(env, pkg, outputDir); + + final var expected = createTestPackageFileContents(env.appImageLayout()); + final var actual = Files.readString(outputDir.resolve(pkg.packageFileNameWithSuffix())); + + assertEquals(expected, actual); + System.out.println(String.format("testCreatePackage:\n---\n%s\n---", actual)); + + var executedTaskActions = dryRun(builder, toConsumer(_ -> { + builder.create().execute(env, pkg, outputDir); + })); + + assertEquals(List.of( + BuildApplicationTaskID.RUNTIME, + BuildApplicationTaskID.LAUNCHERS, + BuildApplicationTaskID.CONTENT, + PackageTaskID.RUN_POST_IMAGE_USER_SCRIPT, + PrimaryTaskID.PACKAGE + ), executedTaskActions); + + final var expectedAppImage = ExpectedAppImage.build().dir("") + .file(TEST_INSTALL_DIR.resolve("launchers/my-launcher"), TestLauncher.CONTENT) + .file(TEST_INSTALL_DIR.resolve("runtime/my-runtime"), TestRuntimeBuilder.CONTENT); + + assertEquals(expectedAppImage, ExpectedAppImage.load(env.appImageDir())); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void testCreateRuntimeInstaller(boolean transformLayout, @TempDir Path workDir) throws ConfigException, PackagerException, IOException { + + final AppImageLayout srcLayout; + if (transformLayout) { + // Create an application layout such that the runtime directory doesn't + // have a common parent with other directories otherwise the runtime directory + // will be skipped when copying the app image layout as path groups because + // the destination app image layout is of type RuntimeLayout and have only + // the runtime directory. + srcLayout = ApplicationLayout.build() + .launchersDirectory("launchers") + .appDirectory("lib") + .runtimeDirectory("runtime") + .appModsDirectory("lib") + .contentDirectory("lib") + .desktopIntegrationDirectory("lib") + .create(); + } else { + srcLayout = RuntimeLayout.DEFAULT; + } + + // Create a runtime image in `env.appImageDir()` directory. + final var env = buildEnv(workDir.resolve("build")) + .appImageLayout(srcLayout) + .appImageDir(workDir.resolve("rt")) + .create(); + TestRuntimeBuilder.INSTANCE.create(env.appImageLayout()); + + final var pipeline = buildPackage(createApp( + RuntimeLayout.DEFAULT.resolveAt(TEST_INSTALL_DIR).resetRootDirectory())).create(); + + final var expectedAppImage = ExpectedAppImage.build().dir("") + .file(TEST_INSTALL_DIR.resolve("my-runtime"), TestRuntimeBuilder.CONTENT); + + createAndVerifyPackage(buildPipeline(), pipeline, env, workDir.resolve("bundles"), + String.format("testCreateRuntimeInstaller(%s)", transformLayout), expectedAppImage, + CopyAppImageTaskID.COPY, + PackageTaskID.RUN_POST_IMAGE_USER_SCRIPT, + PrimaryTaskID.PACKAGE); + } + + private enum ExternalAppImageMode { + + // Copy predefined app image from `BuildEnv.appImageDir()`. + // Layout of the predefined app image is `BuildEnv.appImageLayout()` and + // its unresolved variant equals to `Package.appImageLayout()`. + COPY_FROM_BUILD_ENV, + + // Copy predefined app image from some directory. + // Layout of the predefined app image is `Package.appImageLayout()`. + COPY, + + // Copy predefined app image from `BuildEnv.appImageDir()`. + // Layout of the predefined app image is `BuildEnv.appImageLayout()` and + // its unresolved variant is NOT equal to `Package.appImageLayout()`. + TRANSFORM_FROM_BUILD_ENV, + ; + + static final Set FROM_BUILD_ENV = Set.of( + COPY_FROM_BUILD_ENV, TRANSFORM_FROM_BUILD_ENV); + } + + @ParameterizedTest + @EnumSource(ExternalAppImageMode.class) + void testCreatePackageFromExternalAppImage(ExternalAppImageMode mode, @TempDir Path workDir) throws ConfigException, PackagerException, IOException { + + final ApplicationLayout appLayout; + final ExpectedAppImage expectedAppImage; + if (ExternalAppImageMode.TRANSFORM_FROM_BUILD_ENV == mode) { + appLayout = TEST_LAYOUT_2_WITH_INSTALL_DIR; + expectedAppImage = ExpectedAppImage.build().dir("") + .file(TEST_INSTALL_DIR.resolve("q/launchers/my-launcher"), TestLauncher.CONTENT) + .file(TEST_INSTALL_DIR.resolve("qqq/runtime/my-runtime"), TestRuntimeBuilder.CONTENT); + } else { + appLayout = TEST_LAYOUT_1_WITH_INSTALL_DIR; + expectedAppImage = ExpectedAppImage.build().dir("") + .file(TEST_INSTALL_DIR.resolve("launchers/my-launcher"), TestLauncher.CONTENT) + .file(TEST_INSTALL_DIR.resolve("runtime/my-runtime"), TestRuntimeBuilder.CONTENT); + } + + final BuildEnv env; + final Path predefinedAppImage; + if (ExternalAppImageMode.FROM_BUILD_ENV.contains(mode)) { + // External app image is stored in the build env app image directory. + env = setupBuildEnvForExternalAppImage(workDir); + predefinedAppImage = env.appImageDir(); + } else { + // External app image is stored outside of the build env app image directory + // and should have the same layout as the app's app image layout. + env = buildEnv(workDir.resolve("build")) + .appImageDir(workDir) + // Always need some app image layout. + .appImageLayout(new AppImageLayout.Stub(Path.of(""))) + .create(); + final var externalAppImageLayout = appLayout.resolveAt(workDir.resolve("app-image")); + TestRuntimeBuilder.INSTANCE.create(externalAppImageLayout); + TestLauncher.INSTANCE.create(externalAppImageLayout); + predefinedAppImage = externalAppImageLayout.rootDirectory(); + } + + final var pkg = buildPackage(createApp(appLayout)) + .predefinedAppImage(predefinedAppImage) + .create(); + + createAndVerifyPackage(buildPipeline(), pkg, env, workDir.resolve("bundles"), + String.format("testCreatePackageFromExternalAppImage(%s)", mode), expectedAppImage, + CopyAppImageTaskID.COPY, + PackageTaskID.RUN_POST_IMAGE_USER_SCRIPT, + PrimaryTaskID.PACKAGE); + } + + @ParameterizedTest + @EnumSource(names={"COPY", "COPY_FROM_BUILD_ENV"}) + void testCreatePackageFromExternalAppImageNoCopyAction(ExternalAppImageMode mode, @TempDir Path workDir) throws ConfigException, PackagerException, IOException { + + final ApplicationLayout appLayout = TEST_LAYOUT_1_WITH_INSTALL_DIR; + + final BuildEnv env; + final ApplicationLayout predefinedAppImageLayout; + if (ExternalAppImageMode.FROM_BUILD_ENV.contains(mode)) { + // External app image is stored in the build env app image directory. + env = setupBuildEnvForExternalAppImage(workDir); + predefinedAppImageLayout = env.asApplicationLayout().orElseThrow(); + } else { + // External app image is stored outside of the build env app image directory + // and should have the same layout as the app's app image layout. + env = buildEnv(workDir.resolve("build")) + .appImageDir(workDir) + // Always need some app image layout. + .appImageLayout(new AppImageLayout.Stub(Path.of(""))) + .create(); + predefinedAppImageLayout = appLayout.resolveAt(workDir.resolve("app-image")); + TestRuntimeBuilder.INSTANCE.create(predefinedAppImageLayout); + TestLauncher.INSTANCE.create(predefinedAppImageLayout); + } + + final var pkg = buildPackage(createApp(appLayout)) + .predefinedAppImage(predefinedAppImageLayout.rootDirectory()) + .create(); + + final var outputDir = workDir.resolve("bundles"); + + final var builder = buildPipeline().configuredTasks().filter(task -> { + return CopyAppImageTaskID.COPY.equals(task.task()); + }).findFirst().orElseThrow().noaction().add(); + + final var startupParameters = builder.createStartupParameters(env, pkg, outputDir); + + builder.create().execute(startupParameters); + + final var expected = createTestPackageFileContents(predefinedAppImageLayout); + final var actual = Files.readString(outputDir.resolve(pkg.packageFileNameWithSuffix())); + assertEquals(expected, actual); + System.out.println(String.format("%s:\n---\n%s\n---", + String.format("testCreatePackageFromExternalAppImage(%s)", mode), actual)); + + final ExpectedAppImage expectedAppImage = ExpectedAppImage.build().dir("") + .file(predefinedAppImageLayout.unresolve().launchersDirectory().resolve("my-launcher"), TestLauncher.CONTENT) + .file(predefinedAppImageLayout.unresolve().runtimeDirectory().resolve("my-runtime"), TestRuntimeBuilder.CONTENT); + assertEquals(expectedAppImage, ExpectedAppImage.load(pkg.predefinedAppImage().orElseThrow())); + + var actualExecutedTaskActions = dryRun(builder, toConsumer(_ -> { + builder.create().execute(startupParameters); + })); + assertEquals(List.of( + PackageTaskID.RUN_POST_IMAGE_USER_SCRIPT, + PrimaryTaskID.PACKAGE), actualExecutedTaskActions); + } + + @Test + void testCreatePackageFromExternalAppImageWithoutExternalAppImageError(@TempDir Path workDir) throws ConfigException, PackagerException, IOException { + + final var env = setupBuildEnvForExternalAppImage(workDir); + final var pkg = buildPackage(createApp(TEST_LAYOUT_1_WITH_INSTALL_DIR)).create(); + final var pipeline = buildPipeline().create(); + + assertThrowsExactly(UnsupportedOperationException.class, () -> pipeline.execute(env, pkg, workDir)); + } + + @Test + void testExceptionRethrow_RuntimeException() throws ConfigException, PackagerException, IOException { + + final var expectedException = new RuntimeException("foo"); + final var ex = testExceptionRethrow(expectedException, expectedException.getClass(), () -> { + throw expectedException; + }); + assertSame(expectedException, ex); + } + + @Test + void testExceptionRethrow_PackagerException() throws ConfigException, PackagerException, IOException { + + final var expectedException = new PackagerException("param.vendor.default"); + final var ex = testExceptionRethrow(expectedException, expectedException.getClass(), () -> { + throw expectedException; + }); + assertSame(expectedException, ex); + } + + @Test + void testExceptionRethrow_Exception() throws ConfigException, PackagerException, IOException { + + final var expectedException = new Exception("foo"); + final var ex = testExceptionRethrow(expectedException, PackagerException.class, () -> { + rethrowUnchecked(expectedException); + }); + assertSame(expectedException, ex.getCause()); + } + + @Test + void testAppImageAction() throws PackagerException, IOException { + + final var app = createApp(TEST_LAYOUT_1); + final var env = dummyBuildEnv(); + + final var executed = new boolean[1]; + + PackagingPipeline.build() + // The pipleline must have at least two tasks, add a dummy. + .task(new TaskID() {}).addDependent(PrimaryTaskID.BUILD_APPLICATION_IMAGE).add() + .task(PrimaryTaskID.BUILD_APPLICATION_IMAGE).appImageAction(ctx -> { + assertSame(app, ctx.app()); + assertSame(env, ctx.env()); + executed[0] = true; + }).add().create().execute(env, app); + + assertTrue(executed[0]); + } + + @Test + void testAppImageActionWithPackage() throws PackagerException, IOException { + + final var pkg = buildPackage(createApp(TEST_LAYOUT_1, TestRuntimeBuilder.INSTANCE)).create(); + final var env = dummyBuildEnv(); + + final var executed = new boolean[1]; + + final var builder = PackagingPipeline.build() + // The pipleline must have at least two tasks, add a dummy. + .task(new TaskID() {}).addDependent(PrimaryTaskID.PACKAGE).add(); + + final var startupParameters = builder.createStartupParameters(env, pkg, Path.of("")); + + builder.task(PrimaryTaskID.PACKAGE).appImageAction(ctx -> { + assertSame(pkg.app(), ctx.app()); + assertSame(startupParameters.packagingEnv(), ctx.env()); + executed[0] = true; + }).add().create().execute(startupParameters); + + assertTrue(executed[0]); + } + + @Test + void testPackageActionWithApplication() throws PackagerException, IOException { + + final var app = createApp(TEST_LAYOUT_1); + final var env = dummyBuildEnv(); + + final var pipeline = PackagingPipeline.build() + // The pipleline must have at least two tasks, add a dummy. + .task(new TaskID() {}).addDependent(PrimaryTaskID.BUILD_APPLICATION_IMAGE).add() + .task(PrimaryTaskID.BUILD_APPLICATION_IMAGE).packageAction(ctx -> { + throw new AssertionError(); + }).add().create(); + + // If the pipeline is building an application, it can not execute actions that take a package as an argument. + assertThrowsExactly(UnsupportedOperationException.class, () -> pipeline.execute(env, app)); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void testContextMapper(boolean allowAll) throws PackagerException, IOException { + + var builder = PackagingPipeline.buildStandard().contextMapper(ctx -> { + return new TaskContext() { + @Override + public boolean test(TaskID task) { + return allowAll; + } + + @Override + public void execute(TaskAction taskAction) throws IOException, PackagerException { + if (!allowAll) { + throw new AssertionError(); + } + ctx.execute(taskAction); + } + }; + }); + + var actualExecutedTaskActions = dryRun(builder, toConsumer(_ -> { + builder.create().execute(dummyBuildEnv(), createApp(TEST_LAYOUT_1)); + })); + + List expectedExecutedTaskActions; + + if (allowAll) { + expectedExecutedTaskActions = List.of( + BuildApplicationTaskID.RUNTIME, + BuildApplicationTaskID.LAUNCHERS, + BuildApplicationTaskID.CONTENT, + BuildApplicationTaskID.APP_IMAGE_FILE, + CopyAppImageTaskID.COPY, + PackageTaskID.RUN_POST_IMAGE_USER_SCRIPT); + } else { + expectedExecutedTaskActions = List.of(); + } + + assertEquals(expectedExecutedTaskActions, actualExecutedTaskActions); + } + + public static List dryRun(PackagingPipeline.Builder builder, + Consumer callback) { + + List executedTaskActions = new ArrayList<>(); + builder.configuredTasks().filter(PackagingPipeline.Builder.TaskBuilder::hasAction).forEach(taskBuilder -> { + var taskId = taskBuilder.task(); + taskBuilder.action(() -> { + executedTaskActions.add(taskId); + }).add(); + }); + + callback.accept(builder); + + return executedTaskActions; + } + + private static Exception testExceptionRethrow(Exception expectedException, + Class expectedCatchExceptionType, + NoArgTaskAction throwAction) throws PackagerException, IOException { + + final var app = createApp(TEST_LAYOUT_1); + final var env = dummyBuildEnv(); + + var pipeline = PackagingPipeline.build() + // The pipleline must have at least two tasks, add a dummy. + .task(new TaskID() {}).addDependent(PrimaryTaskID.BUILD_APPLICATION_IMAGE).add() + .task(PrimaryTaskID.BUILD_APPLICATION_IMAGE).action(throwAction).add().create(); + + return assertThrowsExactly(expectedCatchExceptionType, () -> pipeline.execute(env, app)); + } + + private static BuildEnv setupBuildEnvForExternalAppImage(Path workDir) throws ConfigException { + // Create an app image in `env.appImageDir()` directory. + final var env = buildEnv(workDir.resolve("build")) + .appImageLayout(TEST_LAYOUT_1.resolveAt(Path.of("a/b/c")).resetRootDirectory()) + .appImageDir(workDir.resolve("app-image")) + .create(); + TestRuntimeBuilder.INSTANCE.create(env.appImageLayout()); + TestLauncher.INSTANCE.create((ApplicationLayout)env.appImageLayout()); + + return env; + } + + private static void createAndVerifyPackage(PackagingPipeline.Builder builder, Package pkg, + BuildEnv env, Path outputDir, String logMsgHeader, ExpectedAppImage expectedAppImage, + TaskID... expectedExecutedTaskActions) throws PackagerException, IOException { + Objects.requireNonNull(logMsgHeader); + + final var startupParameters = builder.createStartupParameters(env, pkg, outputDir); + + assertNotSameAppImageDirs(env, startupParameters.packagingEnv()); + + // Will create an app image in `startupParameters.packagingEnv().appImageDir()` directory + // with `pkg.appImageLayout()` layout using an app image (runtime image) from `env.appImageDir()` as input. + // Will convert the created app image into a package. + // Will not overwrite the contents of `env.appImageDir()` directory. + builder.create().execute(startupParameters); + + final var packagingAppImageDir = startupParameters.packagingEnv().appImageDir(); + + final var expected = createTestPackageFileContents(pkg.appImageLayout().resolveAt(packagingAppImageDir)); + + final var actual = Files.readString(outputDir.resolve(pkg.packageFileNameWithSuffix())); + + assertEquals(expected, actual); + System.out.println(String.format("%s:\n---\n%s\n---", logMsgHeader, actual)); + + assertEquals(expectedAppImage, ExpectedAppImage.load(packagingAppImageDir)); + + var actualExecutedTaskActions = dryRun(builder, toConsumer(_ -> { + builder.create().execute(startupParameters); + })); + + assertEquals(List.of(expectedExecutedTaskActions), actualExecutedTaskActions); + } + + private static Application createApp(AppImageLayout appImageLayout) { + return createApp(appImageLayout, Optional.empty()); + } + + private static Application createApp(AppImageLayout appImageLayout, RuntimeBuilder runtimeBuilder) { + return createApp(appImageLayout, Optional.of(runtimeBuilder)); + } + + private static Application createApp(AppImageLayout appImageLayout, Optional runtimeBuilder) { + Objects.requireNonNull(appImageLayout); + Objects.requireNonNull(runtimeBuilder); + if (appImageLayout.isResolved()) { + throw new IllegalArgumentException(); + } + + return new Application.Stub( + "foo", + "My app", + "1.0", + "Acme", + "copyright", + Optional.empty(), + List.of(), + appImageLayout, + runtimeBuilder, + List.of(), + Map.of()); + } + + + private static final class PackageBuilder { + PackageBuilder(Application app) { + this.app = Objects.requireNonNull(app); + } + + Package create() { + return new Package.Stub( + app, + new PackageType() {}, + "the-package", + "My package", + "1.0", + Optional.empty(), + Optional.empty(), + Optional.ofNullable(predefinedAppImage), + null, + TEST_INSTALL_DIR); + } + + PackageBuilder predefinedAppImage(Path v) { + predefinedAppImage = v; + return this; + } + + private Path predefinedAppImage; + private final Application app; + } + + + private static PackageBuilder buildPackage(Application app) { + return new PackageBuilder(app); + } + + private static BuildEnvBuilder buildEnv(Path rootDir) { + return new BuildEnvBuilder(rootDir); + } + + private static BuildEnv dummyBuildEnv() { + return BuildEnv.create(Path.of("foo"), Optional.empty(), false, PackagingPipeline.class, RuntimeLayout.DEFAULT); + } + + private static PackagingPipeline.Builder buildPipeline() { + return PackagingPipeline.buildStandard() + // Disable building the app image file (.jpackage.xml) as we don't have launchers in the test app. + .task(BuildApplicationTaskID.APP_IMAGE_FILE).noaction().add() + .task(BuildApplicationTaskID.LAUNCHERS).applicationAction(cfg -> { + TestLauncher.INSTANCE.create(cfg.resolvedLayout()); + }).add() + .task(PrimaryTaskID.PACKAGE).packageAction(cfg -> { + var str = createTestPackageFileContents(cfg.resolvedLayout()); + var packageFile = cfg.outputDir().resolve(cfg.pkg().packageFileNameWithSuffix()); + Files.createDirectories(packageFile.getParent()); + Files.writeString(packageFile, str); + }).add(); + } + + private static String createTestPackageFileContents(AppImageLayout pkgLayout) throws IOException { + return ExpectedAppImage.load(pkgLayout.rootDirectory()).toString(); + } + + private static void assertNotSameAppImageDirs(BuildEnv a, BuildEnv b) { + assertNotEquals(a.appImageDir(), b.appImageDir()); + assertEquals(a.buildRoot(), b.buildRoot()); + assertEquals(a.configDir(), b.configDir()); + assertEquals(a.resourceDir(), b.resourceDir()); + } + + + private static final class TestRuntimeBuilder implements RuntimeBuilder { + @Override + public void create(AppImageLayout appImageLayout) { + assertTrue(appImageLayout.isResolved()); + try { + Files.createDirectories(appImageLayout.runtimeDirectory()); + Files.writeString(runtimeFile(appImageLayout), CONTENT); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + + private static Path runtimeFile(AppImageLayout appImageLayout) { + return appImageLayout.runtimeDirectory().resolve("my-runtime"); + } + + static final String CONTENT = "this is the runtime"; + + static final TestRuntimeBuilder INSTANCE = new TestRuntimeBuilder(); + } + + + private static final class TestLauncher { + public void create(ApplicationLayout appLayout) { + assertTrue(appLayout.isResolved()); + try { + Files.createDirectories(appLayout.launchersDirectory()); + Files.writeString(launcherFile(appLayout), CONTENT); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + + private static Path launcherFile(ApplicationLayout appLayout) { + return appLayout.launchersDirectory().resolve("my-launcher"); + } + + static final String CONTENT = "this is the launcher"; + + static final TestLauncher INSTANCE = new TestLauncher(); + } + + + private static final class ExpectedAppImage { + + static ExpectedAppImage build() { + return new ExpectedAppImage(new HashSet<>()); + } + + static ExpectedAppImage load(Path appImageRoot) throws IOException { + try (var walk = Files.walk(appImageRoot)) { + return new ExpectedAppImage(walk.sorted().map(path -> { + var relativePath = appImageRoot.relativize(path); + if (Files.isDirectory(path)) { + return new Directory(relativePath); + } else { + return new File(relativePath, toSupplier(() -> Files.readString(path)).get()); + } + }).collect(Collectors.toSet())); + } + } + + ExpectedAppImage file(Path path, String content) { + return add(new File(path, content)); + } + + ExpectedAppImage file(String path, String content) { + return file(Path.of(path), content); + } + + ExpectedAppImage dir(Path path) { + return add(new Directory(path)); + } + + ExpectedAppImage dir(String path) { + return dir(Path.of(path)); + } + + @Override + public String toString() { + return items.stream().map(AppImageItem::toString).sorted().collect(Collectors.joining("\n")); + } + + @Override + public int hashCode() { + return Objects.hash(items); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if ((obj == null) || (getClass() != obj.getClass())) { + return false; + } + ExpectedAppImage other = (ExpectedAppImage) obj; + return Objects.equals(items, other.items); + } + + private ExpectedAppImage(Set items) { + this.items = Objects.requireNonNull(items); + } + + private ExpectedAppImage add(AppImageItem v) { + var path = v.path(); + if (path.isAbsolute()) { + throw new IllegalArgumentException(); + } + + items.add(v); + while (path.getNameCount() > 1) { + items.add(new Directory(path = path.getParent())); + } + return this; + } + + private interface AppImageItem { + Path path(); + } + + private record File(Path path, String content) implements AppImageItem { + + File { + Objects.requireNonNull(path); + Objects.requireNonNull(content); + } + + @Override + public String toString() { + return String.format("%s[%s]", path, content); + } + } + + private record Directory(Path path) implements AppImageItem { + + Directory { + Objects.requireNonNull(path); + } + + @Override + public String toString() { + return path.toString(); + } + } + + private final Set items; + } + + + private static final ApplicationLayout TEST_LAYOUT_1 = ApplicationLayout.build() + .launchersDirectory("launchers") + .appDirectory("") + .runtimeDirectory("runtime") + .appModsDirectory("") + .contentDirectory("") + .desktopIntegrationDirectory("") + .create(); + + private static final ApplicationLayout TEST_LAYOUT_2 = ApplicationLayout.build() + .launchersDirectory("q/launchers") + .appDirectory("") + .runtimeDirectory("qqq/runtime") + .appModsDirectory("") + .contentDirectory("") + .desktopIntegrationDirectory("") + .create(); + + private static final Path TEST_INSTALL_DIR = Path.of("Acme/My app"); + + private static final ApplicationLayout TEST_LAYOUT_1_WITH_INSTALL_DIR = + TEST_LAYOUT_1.resolveAt(TEST_INSTALL_DIR).resetRootDirectory(); + + private static final ApplicationLayout TEST_LAYOUT_2_WITH_INSTALL_DIR = + TEST_LAYOUT_2.resolveAt(TEST_INSTALL_DIR).resetRootDirectory(); +} diff --git a/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/model/AppImageLayoutTest.java b/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/model/AppImageLayoutTest.java index 1c863b653b1..5948d74079e 100644 --- a/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/model/AppImageLayoutTest.java +++ b/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/model/AppImageLayoutTest.java @@ -23,12 +23,14 @@ package jdk.jpackage.internal.model; +import static jdk.jpackage.internal.model.AppImageLayout.toPathGroup; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertSame; import java.nio.file.Path; import java.util.List; import java.util.Set; +import jdk.jpackage.internal.util.PathGroup; import org.junit.jupiter.api.Test; @@ -48,7 +50,7 @@ public class AppImageLayoutTest { public void testPathGroup() { final var layout = new AppImageLayout.Stub(Path.of("root"), Path.of("runtime")); - final var pathGroup = AppImageLayout.toPathGroup(layout); + final var pathGroup = toPathGroup(layout); assertEquals(Set.of("runtimeDirectory"), pathGroup.keys()); assertEquals(List.of(layout.runtimeDirectory()), pathGroup.paths()); @@ -56,15 +58,88 @@ public class AppImageLayoutTest { @Test public void testResolveAt() { - final var dir = Path.of("foo/bar"); - - final var layout = new AppImageLayout.Stub(Path.of(""), Path.of("runtime")); - - final var resolvedLayout = layout.resolveAt(dir); - - assertNotSame(layout, resolvedLayout); - - assertEquals(dir.resolve(layout.rootDirectory()), resolvedLayout.rootDirectory()); - assertEquals(dir.resolve(layout.runtimeDirectory()), resolvedLayout.runtimeDirectory()); + testResolveAt(new AppImageLayout.Stub(Path.of("foo"))); } + + @Test + public void testResolveAtRepeat() { + testResolveAtRepeat(new AppImageLayout.Stub(Path.of("foo"))); + } + + @Test + public void testUnresolve() { + testUnresolve(new AppImageLayout.Stub(Path.of("runtime"))); + } + + @Test + public void testEmptyRootDirectory() { + testEmptyRootDirectory(new AppImageLayout.Stub(Path.of("rt"))); + } + + public static void testResolveAt(AppImageLayout testee) { + + var dir = Path.of("foo/bar"); + + assertLayout(testee.resolveAt(dir), true, testee, dir); + } + + public static void testResolveAtRepeat(AppImageLayout testee) { + + var resolvedLayout = testee.resolveAt(Path.of("b/c")).resolveAt(Path.of("a")); + + assertLayout(resolvedLayout, true, testee, Path.of("a/b/c")); + } + + public static void testUnresolve(AppImageLayout testee) { + if (testee.isResolved()) { + throw new IllegalArgumentException(); + } + + var resolvedLayout = testee.resolveAt(Path.of("foo/bar")); + var layout = resolvedLayout.unresolve(); + + assertLayout(layout, false, testee, Path.of("")); + + resolvedLayout = testee.resolveAt(Path.of("").toAbsolutePath()); + layout = resolvedLayout.unresolve(); + + assertLayout(layout, false, testee, Path.of("")); + + assertSame(testee, testee.unresolve()); + } + + public static void testEmptyRootDirectory(AppImageLayout testee) { + if (testee.isResolved()) { + throw new IllegalArgumentException(); + } + + assertEmptyRootDirectory(testee); + + final var resolved = testee.resolveAt(Path.of("t")); + + assertEmptyRootDirectory(resolved); + } + + private static void assertEmptyRootDirectory(AppImageLayout testee) { + if (testee.isResolved()) { + var newLayout = testee.resetRootDirectory(); + assertLayout(newLayout, false, Path.of(""), toPathGroup(testee)); + } else { + assertSame(testee, testee.resetRootDirectory()); + } + } + + private static void assertLayout(AppImageLayout actual, boolean expectedResolved, + AppImageLayout base, Path baseResolveAt) { + assertLayout(actual, expectedResolved, baseResolveAt.resolve(base.rootDirectory()), + toPathGroup(base).resolveAt(baseResolveAt)); + } + + private static void assertLayout(AppImageLayout actual, boolean expectedResolved, + Path expectedRootDir, PathGroup expectedPaths) { + assertEquals(expectedResolved, actual.isResolved()); + assertEquals(expectedRootDir, actual.rootDirectory()); + assertEquals(expectedPaths, toPathGroup(actual)); + } + } diff --git a/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/model/ApplicationLayoutTest.java b/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/model/ApplicationLayoutTest.java index 089a363e0e4..063b11ec589 100644 --- a/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/model/ApplicationLayoutTest.java +++ b/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/model/ApplicationLayoutTest.java @@ -23,20 +23,51 @@ package jdk.jpackage.internal.model; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.stream.Stream; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; public class ApplicationLayoutTest { - public void test(boolean move, Path tempDir) throws IOException { + @Test + public void testMove(@TempDir Path tempDir) throws IOException { + test(true, tempDir); + } + + @Test + public void testCopy(@TempDir Path tempDir) throws IOException { + test(false, tempDir); + } + + @Test + public void testResolveAt() { + AppImageLayoutTest.testResolveAt(createLayout()); + } + + @Test + public void testResolveAtRepeat() { + AppImageLayoutTest.testResolveAtRepeat(createLayout()); + } + + @Test + public void testUnresolve() { + AppImageLayoutTest.testUnresolve(createLayout()); + } + + @Test + public void testEmptyRootDirectory() { + AppImageLayoutTest.testEmptyRootDirectory(createLayout()); + } + + private static void test(boolean move, Path tempDir) throws IOException { final var srcAppImageRoot = tempDir.resolve("src"); Files.createDirectories(srcAppImageRoot); @@ -55,14 +86,7 @@ public class ApplicationLayoutTest { Files.createDirectories(path); } - final var layout = ApplicationLayout.build() - .launchersDirectory("bin") - .appDirectory("lib/app") - .runtimeDirectory("runtime") - .appModsDirectory("mods") - .contentDirectory("content") - .desktopIntegrationDirectory("lib/apps") - .create(); + final var layout = createLayout(); final var dstAppImageRoot = tempDir.resolve("dst"); Files.createDirectories(dstAppImageRoot); @@ -100,13 +124,14 @@ public class ApplicationLayoutTest { } } - @Test - public void testMove(@TempDir Path tempDir) throws IOException { - test(true, tempDir); - } - - @Test - public void testCopy(@TempDir Path tempDir) throws IOException { - test(false, tempDir); + public static ApplicationLayout createLayout() { + return ApplicationLayout.build() + .launchersDirectory("bin") + .appDirectory("lib/app") + .runtimeDirectory("runtime") + .appModsDirectory("mods") + .contentDirectory("content") + .desktopIntegrationDirectory("lib/apps") + .create(); } } diff --git a/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/util/PathGroupTest.java b/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/util/PathGroupTest.java index 579cf5f083c..c5b5e271960 100644 --- a/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/util/PathGroupTest.java +++ b/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/util/PathGroupTest.java @@ -65,6 +65,14 @@ public class PathGroupTest { assertThrowsExactly(NullPointerException.class, () -> new PathGroup(Map.of()).getPath(null)); } + @Test + public void equals() { + assertEquals(new PathGroup(Map.of()), new PathGroup(Map.of())); + assertEquals(new PathGroup(Map.of("foo", Path.of("bar"))), new PathGroup(Map.of("foo", Path.of("bar")))); + assertNotEquals(new PathGroup(Map.of("foo", Path.of("bar"))), new PathGroup(Map.of("foo", Path.of("rab")))); + assertNotEquals(new PathGroup(Map.of("foo", Path.of("bar"))), new PathGroup(Map.of("Foo", Path.of("bar")))); + } + @Test public void testEmptyPathGroup() { PathGroup pg = new PathGroup(Map.of()); diff --git a/test/jdk/tools/jpackage/helpers-test/jdk/jpackage/test/JUnitAdapter.java b/test/jdk/tools/jpackage/junit/tools/jdk/jpackage/test/JUnitAdapter.java similarity index 90% rename from test/jdk/tools/jpackage/helpers-test/jdk/jpackage/test/JUnitAdapter.java rename to test/jdk/tools/jpackage/junit/tools/jdk/jpackage/test/JUnitAdapter.java index b7c50285797..5a605b1642d 100644 --- a/test/jdk/tools/jpackage/helpers-test/jdk/jpackage/test/JUnitAdapter.java +++ b/test/jdk/tools/jpackage/junit/tools/jdk/jpackage/test/JUnitAdapter.java @@ -40,10 +40,12 @@ import org.junit.jupiter.api.TestFactory; public class JUnitAdapter { - static { - if (System.getProperty("test.src") == null) { - // Was called by somebody else but not by jtreg - System.setProperty("test.src", Path.of("@@openJdkDir@@/test/jdk/tools/jpackage").toString()); + public static class TestSrcInitializer { + static { + if (System.getProperty("test.src") == null) { + // Was called by somebody else but not by jtreg + System.setProperty("test.src", Path.of("@@openJdkDir@@/test/jdk/tools/jpackage").toString()); + } } } @@ -84,5 +86,9 @@ public class JUnitAdapter { } } + static { + new TestSrcInitializer(); + } + private static final int LOG_MSG_TIMESTAMP_LENGTH = "[HH:mm:ss.SSS] ".length(); } diff --git a/test/jdk/tools/jpackage/junit/windows/junit.java b/test/jdk/tools/jpackage/junit/windows/junit.java index 38b5cd5ca0c..d27a282f0dc 100644 --- a/test/jdk/tools/jpackage/junit/windows/junit.java +++ b/test/jdk/tools/jpackage/junit/windows/junit.java @@ -24,6 +24,7 @@ /* @test * @summary Test function reading OS version from PE file * @requires (os.family == "windows") - * @compile/module=jdk.jpackage jdk/jpackage/internal/ExecutableOSVersionTest.java + * @compile/module=jdk.jpackage -Xlint:all -Werror + * jdk/jpackage/internal/ExecutableOSVersionTest.java * @run junit jdk.jpackage/jdk.jpackage.internal.ExecutableOSVersionTest */ From bd65d483df4742bb7ce79b613f10f70a45117f84 Mon Sep 17 00:00:00 2001 From: Kim Barrett Date: Sun, 17 Aug 2025 12:56:42 +0000 Subject: [PATCH 150/580] 8365245: Move size reducing operations to GrowableArrayWithAllocator Reviewed-by: jsjolen, stefank --- src/hotspot/share/utilities/growableArray.hpp | 131 +++++++++--------- test/hotspot/gtest/gc/z/test_zArray.cpp | 4 +- 2 files changed, 70 insertions(+), 65 deletions(-) diff --git a/src/hotspot/share/utilities/growableArray.hpp b/src/hotspot/share/utilities/growableArray.hpp index 759b9451490..53403ca5cf1 100644 --- a/src/hotspot/share/utilities/growableArray.hpp +++ b/src/hotspot/share/utilities/growableArray.hpp @@ -93,12 +93,6 @@ public: bool is_empty() const { return _len == 0; } bool is_nonempty() const { return _len != 0; } bool is_full() const { return _len == _capacity; } - - void clear() { _len = 0; } - void trunc_to(int length) { - assert(length <= _len,"cannot increase length"); - _len = length; - } }; template class GrowableArrayIterator; @@ -189,11 +183,6 @@ public: return GrowableArrayIterator(this, length()); } - E pop() { - assert(_len > 0, "empty list"); - return _data[--_len]; - } - void at_put(int i, const E& elem) { assert(0 <= i && i < _len, "illegal index %d for length %d", i, _len); _data[i] = elem; @@ -247,59 +236,6 @@ public: return -1; } - // Order preserving remove operations. - - void remove(const E& elem) { - // Assuming that element does exist. - bool removed = remove_if_existing(elem); - if (removed) return; - ShouldNotReachHere(); - } - - bool remove_if_existing(const E& elem) { - // Returns TRUE if elem is removed. - for (int i = 0; i < _len; i++) { - if (_data[i] == elem) { - remove_at(i); - return true; - } - } - return false; - } - - void remove_at(int index) { - assert(0 <= index && index < _len, "illegal index %d for length %d", index, _len); - for (int j = index + 1; j < _len; j++) { - _data[j-1] = _data[j]; - } - _len--; - } - - // Remove all elements up to the index (exclusive). The order is preserved. - void remove_till(int idx) { - remove_range(0, idx); - } - - // Remove all elements in the range [start - end). The order is preserved. - void remove_range(int start, int end) { - assert(0 <= start, "illegal start index %d", start); - assert(start < end && end <= _len, "erase called with invalid range (%d, %d) for length %d", start, end, _len); - - for (int i = start, j = end; j < length(); i++, j++) { - at_put(i, at(j)); - } - trunc_to(length() - (end - start)); - } - - // The order is changed. - void delete_at(int index) { - assert(0 <= index && index < _len, "illegal index %d for length %d", index, _len); - if (index < --_len) { - // Replace removed element with last one. - _data[index] = _data[_len]; - } - } - void sort(int f(E*, E*)) { if (_data == nullptr) return; qsort(_data, length(), sizeof(E), (_sort_Fn)f); @@ -427,6 +363,11 @@ public: void push(const E& elem) { append(elem); } + E pop() { + assert(this->_len > 0, "empty list"); + return this->_data[--this->_len]; + } + E& at_grow(int i, const E& fill = E()) { assert(0 <= i, "negative index %d", i); if (i >= this->_len) { @@ -514,9 +455,71 @@ public: // Ensure capacity is at least new_capacity. void reserve(int new_capacity); + void trunc_to(int length) { + assert(length <= this->_len,"cannot increase length"); + this->_len = length; + } + + // Order preserving remove operations. + + void remove_at(int index) { + assert(0 <= index && index < this->_len, + "illegal index %d for length %d", index, this->_len); + for (int j = index + 1; j < this->_len; j++) { + this->_data[j-1] = this->_data[j]; + } + this->_len--; + } + + void remove(const E& elem) { + // Assuming that element does exist. + bool removed = this->remove_if_existing(elem); + if (removed) return; + ShouldNotReachHere(); + } + + bool remove_if_existing(const E& elem) { + // Returns TRUE if elem is removed. + for (int i = 0; i < this->_len; i++) { + if (this->_data[i] == elem) { + this->remove_at(i); + return true; + } + } + return false; + } + + // Remove all elements up to the index (exclusive). The order is preserved. + void remove_till(int idx) { + remove_range(0, idx); + } + + // Remove all elements in the range [start - end). The order is preserved. + void remove_range(int start, int end) { + assert(0 <= start, "illegal start index %d", start); + assert(start < end && end <= this->_len, + "erase called with invalid range (%d, %d) for length %d", + start, end, this->_len); + + for (int i = start, j = end; j < this->length(); i++, j++) { + this->at_put(i, this->at(j)); + } + this->_len -= (end - start); + } + + // Replaces the designated element with the last element and shrinks by 1. + void delete_at(int index) { + assert(0 <= index && index < this->_len, "illegal index %d for length %d", index, this->_len); + if (index < --this->_len) { + // Replace removed element with last one. + this->_data[index] = this->_data[this->_len]; + } + } + // Reduce capacity to length. void shrink_to_fit(); + void clear() { this->_len = 0; } void clear_and_deallocate(); }; diff --git a/test/hotspot/gtest/gc/z/test_zArray.cpp b/test/hotspot/gtest/gc/z/test_zArray.cpp index acc7688cb10..79d4b29b1f5 100644 --- a/test/hotspot/gtest/gc/z/test_zArray.cpp +++ b/test/hotspot/gtest/gc/z/test_zArray.cpp @@ -127,9 +127,11 @@ TEST_F(ZArrayTest, slice) { const auto check_reversed = [](ZArraySlice original, ZArraySlice reversed) { ASSERT_EQ(original.length(), reversed.length()); + int ri = reversed.length(); for (int e : original) { - ASSERT_EQ(e, reversed.pop()); + ASSERT_EQ(e, reversed.at(--ri)); } + ASSERT_EQ(ri, 0); }; ZArraySlice a_reversed = reverse(a, reverse); From f364fcab792ed5a14e5c2779fa85ecc9d6915ae3 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 18 Aug 2025 05:32:03 +0000 Subject: [PATCH 151/580] 8359119: Change Charset to use StableValue Reviewed-by: alanb, rriggs --- .../classes/java/nio/charset/Charset.java | 116 +++++++++--------- 1 file changed, 56 insertions(+), 60 deletions(-) diff --git a/src/java.base/share/classes/java/nio/charset/Charset.java b/src/java.base/share/classes/java/nio/charset/Charset.java index b36e11cdc36..1eb3c9c2094 100644 --- a/src/java.base/share/classes/java/nio/charset/Charset.java +++ b/src/java.base/share/classes/java/nio/charset/Charset.java @@ -37,13 +37,16 @@ import java.nio.charset.spi.CharsetProvider; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.NoSuchElementException; +import java.util.Objects; import java.util.ServiceLoader; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; +import java.util.function.Supplier; /** @@ -381,17 +384,7 @@ public abstract class Charset }; } - private static class ThreadTrackHolder { - static final ThreadTracker TRACKER = new ThreadTracker(); - } - - private static Object tryBeginLookup() { - return ThreadTrackHolder.TRACKER.tryBegin(); - } - - private static void endLookup(Object key) { - ThreadTrackHolder.TRACKER.end(key); - } + private static final ScopedValue IN_LOOKUP = ScopedValue.newInstance(); private static Charset lookupViaProviders(final String charsetName) { @@ -406,48 +399,54 @@ public abstract class Charset if (!VM.isBooted()) return null; - Object key = tryBeginLookup(); - if (key == null) { + if (IN_LOOKUP.isBound()) { // Avoid recursive provider lookups return null; } try { - for (Iterator i = providers(); i.hasNext();) { - CharsetProvider cp = i.next(); - Charset cs = cp.charsetForName(charsetName); - if (cs != null) - return cs; - } - return null; - } finally { - endLookup(key); + return ScopedValue.where(IN_LOOKUP, true).call( + new ScopedValue.CallableOp() { + @Override + public Charset call() { + for (Iterator i = providers(); i.hasNext(); ) { + CharsetProvider cp = i.next(); + Charset cs = cp.charsetForName(charsetName); + if (cs != null) + return cs; + } + return null; + } + } + ); + } catch (Exception t) { + // Should not happen + throw new RuntimeException(t); } + } /* The extended set of charsets */ - private static class ExtendedProviderHolder { - static final CharsetProvider[] extendedProviders = extendedProviders(); - // returns ExtendedProvider, if installed - private static CharsetProvider[] extendedProviders() { - CharsetProvider[] cps = new CharsetProvider[1]; - int n = 0; - ServiceLoader sl = + private static final Supplier> EXTENDED_PROVIDERS = StableValue.supplier( + new Supplier<>() { public List get() { return extendedProviders0(); }}); + + private static List extendedProviders0() { + CharsetProvider[] cps = new CharsetProvider[1]; + int n = 0; + final ServiceLoader sl = ServiceLoader.loadInstalled(CharsetProvider.class); - for (CharsetProvider cp : sl) { - if (n + 1 > cps.length) { - cps = Arrays.copyOf(cps, cps.length << 1); - } - cps[n++] = cp; + for (CharsetProvider cp : sl) { + if (n + 1 > cps.length) { + cps = Arrays.copyOf(cps, cps.length << 1); } - return n == cps.length ? cps : Arrays.copyOf(cps, n); + cps[n++] = cp; } + return List.of(n == cps.length ? cps : Arrays.copyOf(cps, n)); } private static Charset lookupExtendedCharset(String charsetName) { if (!VM.isBooted()) // see lookupViaProviders() return null; - CharsetProvider[] ecps = ExtendedProviderHolder.extendedProviders; - for (CharsetProvider cp : ecps) { + for (CharsetProvider cp : EXTENDED_PROVIDERS.get()) { Charset cs = cp.charsetForName(charsetName); if (cs != null) return cs; @@ -608,8 +607,7 @@ public abstract class Charset new TreeMap<>( String.CASE_INSENSITIVE_ORDER); put(standardProvider.charsets(), m); - CharsetProvider[] ecps = ExtendedProviderHolder.extendedProviders; - for (CharsetProvider ecp :ecps) { + for (CharsetProvider ecp : EXTENDED_PROVIDERS.get()) { put(ecp.charsets(), m); } for (Iterator i = providers(); i.hasNext();) { @@ -619,7 +617,16 @@ public abstract class Charset return Collections.unmodifiableSortedMap(m); } - private @Stable static Charset defaultCharset; + private static final Supplier defaultCharset = StableValue.supplier( + new Supplier<>() { public Charset get() { return defaultCharset0(); }}); + + private static Charset defaultCharset0() { + // do not look for providers other than the standard one + final Charset cs = standardProvider.charsetForName(StaticProperty.fileEncoding()); + return (cs == null) + ? sun.nio.cs.UTF_8.INSTANCE + : cs; + } /** * Returns the default charset of this Java virtual machine. @@ -640,25 +647,19 @@ public abstract class Charset * @since 1.5 */ public static Charset defaultCharset() { - if (defaultCharset == null) { - synchronized (Charset.class) { - // do not look for providers other than the standard one - Charset cs = standardProvider.charsetForName(StaticProperty.fileEncoding()); - if (cs != null) - defaultCharset = cs; - else - defaultCharset = sun.nio.cs.UTF_8.INSTANCE; - } - } - return defaultCharset; + return defaultCharset.get(); } /* -- Instance fields and methods -- */ - private final String name; // tickles a bug in oldjavac - private final String[] aliases; // tickles a bug in oldjavac - private Set aliasSet; + @Stable + private final String name; + @Stable + private final String[] aliases; + @Stable + private final Supplier> aliasSet = StableValue.supplier( + new Supplier<>() { public Set get() { return Set.of(aliases); }}); /** * Initializes a new charset with the given canonical name and alias @@ -710,12 +711,7 @@ public abstract class Charset * @return An immutable set of this charset's aliases */ public final Set aliases() { - Set set = this.aliasSet; - if (set == null) { - set = Set.of(aliases); - this.aliasSet = set; - } - return set; + return aliasSet.get(); } /** From e7ca8c7d55fa959cb43d49d63128420b05b7cc92 Mon Sep 17 00:00:00 2001 From: David Beaumont Date: Mon, 18 Aug 2025 07:08:19 +0000 Subject: [PATCH 152/580] 8365436: ImageReaderTest fails when jmods directory not present Reviewed-by: sgehwolf, alanb --- test/jdk/jdk/internal/jimage/ImageReaderTest.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/test/jdk/jdk/internal/jimage/ImageReaderTest.java b/test/jdk/jdk/internal/jimage/ImageReaderTest.java index 6bdf0cf479a..8db9a3768d4 100644 --- a/test/jdk/jdk/internal/jimage/ImageReaderTest.java +++ b/test/jdk/jdk/internal/jimage/ImageReaderTest.java @@ -25,11 +25,12 @@ import jdk.internal.jimage.ImageReader; import jdk.internal.jimage.ImageReader.Node; import jdk.test.lib.compiler.InMemoryJavaCompiler; import jdk.test.lib.util.JarBuilder; +import jdk.tools.jlink.internal.LinkableRuntimeImage; +import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import org.opentest4j.TestSkippedException; import tests.Helper; import tests.JImageGenerator; @@ -54,6 +55,7 @@ import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; * @test * @summary Tests for ImageReader. * @modules java.base/jdk.internal.jimage + * jdk.jlink/jdk.tools.jlink.internal * jdk.jlink/jdk.tools.jimage * @library /test/jdk/tools/lib * /test/lib @@ -214,15 +216,15 @@ public class ImageReaderTest { /// Returns the helper for building JAR and jimage files. private static Helper getHelper() { + Helper helper; try { - Helper helper = Helper.newHelper(); - if (helper == null) { - throw new TestSkippedException("Cannot create test helper (exploded image?)"); - } - return helper; + boolean isLinkableRuntime = LinkableRuntimeImage.isLinkableRuntime(); + helper = Helper.newHelper(isLinkableRuntime); } catch (IOException e) { throw new RuntimeException(e); } + Assumptions.assumeTrue(helper != null, "Cannot create test helper, skipping test!"); + return helper; } /// Loads and performs actions on classes stored in a given `ImageReader`. From 166ea12d73c7a40a1a26dc586e3db9d9430c068f Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Mon, 18 Aug 2025 07:14:09 +0000 Subject: [PATCH 153/580] 8365543: UnixNativeDispatcher.init should lookup open64at and stat64at on AIX Co-authored-by: Joachim Kern Reviewed-by: jkern, stuefe, goetz, alanb --- .../unix/native/libnio/fs/UnixNativeDispatcher.c | 15 +++++++-------- .../java/nio/file/DirectoryStream/SecureDS.java | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/java.base/unix/native/libnio/fs/UnixNativeDispatcher.c b/src/java.base/unix/native/libnio/fs/UnixNativeDispatcher.c index fbf996886ae..5f81241b7dd 100644 --- a/src/java.base/unix/native/libnio/fs/UnixNativeDispatcher.c +++ b/src/java.base/unix/native/libnio/fs/UnixNativeDispatcher.c @@ -343,22 +343,21 @@ Java_sun_nio_fs_UnixNativeDispatcher_init(JNIEnv* env, jclass this) /* system calls that might not be available at run time */ -#if defined(_ALLBSD_SOURCE) - my_openat_func = (openat_func*) openat; - my_fstatat_func = (fstatat_func*) fstatat; -#else - // Make sure we link to the 64-bit version of the functions - my_openat_func = (openat_func*) dlsym(RTLD_DEFAULT, "openat64"); - my_fstatat_func = (fstatat_func*) dlsym(RTLD_DEFAULT, "fstatat64"); -#endif my_unlinkat_func = (unlinkat_func*) dlsym(RTLD_DEFAULT, "unlinkat"); my_renameat_func = (renameat_func*) dlsym(RTLD_DEFAULT, "renameat"); #if defined(_AIX) // Make sure we link to the 64-bit version of the function + my_openat_func = (openat_func*) dlsym(RTLD_DEFAULT, "open64at"); + my_fstatat_func = (fstatat_func*) dlsym(RTLD_DEFAULT, "stat64at"); my_fdopendir_func = (fdopendir_func*) dlsym(RTLD_DEFAULT, "fdopendir64"); #elif defined(_ALLBSD_SOURCE) + my_openat_func = (openat_func*) openat; + my_fstatat_func = (fstatat_func*) fstatat; my_fdopendir_func = (fdopendir_func*) fdopendir; #else + // Make sure we link to the 64-bit version of the functions + my_openat_func = (openat_func*) dlsym(RTLD_DEFAULT, "openat64"); + my_fstatat_func = (fstatat_func*) dlsym(RTLD_DEFAULT, "fstatat64"); my_fdopendir_func = (fdopendir_func*) dlsym(RTLD_DEFAULT, "fdopendir"); #endif diff --git a/test/jdk/java/nio/file/DirectoryStream/SecureDS.java b/test/jdk/java/nio/file/DirectoryStream/SecureDS.java index 6199596b30c..d5d4b1904ea 100644 --- a/test/jdk/java/nio/file/DirectoryStream/SecureDS.java +++ b/test/jdk/java/nio/file/DirectoryStream/SecureDS.java @@ -24,7 +24,7 @@ /* @test * @bug 4313887 6838333 8343020 8357425 * @summary Unit test for java.nio.file.SecureDirectoryStream - * @requires (os.family == "linux" | os.family == "mac") + * @requires (os.family == "linux" | os.family == "mac" | os.family == "aix") * @library .. /test/lib * @build jdk.test.lib.Platform * @run main SecureDS From 190e113031bc6ece781fdf0d9f3c853ce324f170 Mon Sep 17 00:00:00 2001 From: Volkan Yazici Date: Mon, 18 Aug 2025 08:11:19 +0000 Subject: [PATCH 154/580] 8364263: HttpClient: Improve encapsulation of ProxyServer Reviewed-by: dfuchs, jpai --- test/jdk/java/net/httpclient/ProxyServer.java | 28 +++++++------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/test/jdk/java/net/httpclient/ProxyServer.java b/test/jdk/java/net/httpclient/ProxyServer.java index 747a20772d1..9ec84fe428f 100644 --- a/test/jdk/java/net/httpclient/ProxyServer.java +++ b/test/jdk/java/net/httpclient/ProxyServer.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 @@ -40,7 +40,7 @@ import static java.util.stream.Collectors.toList; * Two threads are created per client connection. So, it's not * intended for large numbers of parallel connections. */ -public class ProxyServer extends Thread implements Closeable { +public final class ProxyServer implements Closeable { // could use the test library here - Platform.isWindows(), // but it would force all tests that use ProxyServer to @@ -97,9 +97,7 @@ public class ProxyServer extends Thread implements Closeable { this(port, debug, null); } - public ProxyServer(Integer port, - Boolean debug, - Credentials credentials) + private ProxyServer(Integer port, Boolean debug, Credentials credentials) throws IOException { this.debug = debug; @@ -108,15 +106,8 @@ public class ProxyServer extends Thread implements Closeable { listener.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), port)); this.port = ((InetSocketAddress)listener.getLocalAddress()).getPort(); this.credentials = credentials; - setName("ProxyListener"); - setDaemon(true); - connections = new CopyOnWriteArrayList(); - start(); - } - - public ProxyServer(String s) { - credentials = null; connections = new CopyOnWriteArrayList(); + Thread.ofPlatform().name("ProxyListener").daemon().start(this::run); } /** @@ -148,7 +139,7 @@ public class ProxyServer extends Thread implements Closeable { volatile boolean done; - public void run() { + private void run() { int id = 0; try { while (!done) { @@ -656,10 +647,11 @@ public class ProxyServer extends Thread implements Closeable { int port = Integer.parseInt(args[0]); boolean debug = args.length > 1 && args[1].equals("-debug"); System.out.println("Debugging : " + debug); - ProxyServer ps = new ProxyServer(port, debug); - System.out.println("Proxy server listening on port " + ps.getPort()); - while (true) { - Thread.sleep(5000); + try (ProxyServer ps = new ProxyServer(port, debug)) { + System.out.println("Proxy server listening on port " + ps.getPort()); + while (true) { + Thread.sleep(5000); + } } } } From ca753ebad6681a76d18800d23898b7d6af83f567 Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Mon, 18 Aug 2025 08:12:20 +0000 Subject: [PATCH 155/580] 8365165: Zap C-heap memory at delete/free Reviewed-by: kvn, kbarrett --- src/hotspot/share/nmt/mallocTracker.cpp | 6 ++++++ src/hotspot/share/runtime/globals.hpp | 3 +++ src/hotspot/share/runtime/os.cpp | 6 +++--- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/hotspot/share/nmt/mallocTracker.cpp b/src/hotspot/share/nmt/mallocTracker.cpp index 6a2da5f79cd..d919f3ce873 100644 --- a/src/hotspot/share/nmt/mallocTracker.cpp +++ b/src/hotspot/share/nmt/mallocTracker.cpp @@ -207,6 +207,12 @@ void* MallocTracker::record_free_block(void* memblock) { deaccount(header->free_info()); + if (ZapCHeap) { + // To do this zapping, we need to know the block size. + // This is why we have to do it here, and not in os::free. + memset(memblock, freeBlockPad, header->size()); + } + header->mark_block_as_dead(); return (void*)header; diff --git a/src/hotspot/share/runtime/globals.hpp b/src/hotspot/share/runtime/globals.hpp index 24dda9ac6d8..ee3c37b471b 100644 --- a/src/hotspot/share/runtime/globals.hpp +++ b/src/hotspot/share/runtime/globals.hpp @@ -483,6 +483,9 @@ const int ObjectAlignmentInBytes = 8; develop(bool, ZapFillerObjects, trueInDebug, \ "Zap filler objects") \ \ + develop(bool, ZapCHeap, trueInDebug, \ + "Zap allocated/freed C heap space") \ + \ develop(bool, ZapTLAB, trueInDebug, \ "Zap allocated TLABs") \ develop(bool, TestingAsyncLoggingDeathTest, false, \ diff --git a/src/hotspot/share/runtime/os.cpp b/src/hotspot/share/runtime/os.cpp index db2256d70cb..04a363a7aab 100644 --- a/src/hotspot/share/runtime/os.cpp +++ b/src/hotspot/share/runtime/os.cpp @@ -666,8 +666,8 @@ void* os::malloc(size_t size, MemTag mem_tag, const NativeCallStack& stack) { if (CDSConfig::is_dumping_static_archive()) { // Need to deterministically fill all the alignment gaps in C++ structures. ::memset(inner_ptr, 0, size); - } else { - DEBUG_ONLY(::memset(inner_ptr, uninitBlockPad, size);) + } else if (ZapCHeap) { + ::memset(inner_ptr, uninitBlockPad, size); } DEBUG_ONLY(break_if_ptr_caught(inner_ptr);) return inner_ptr; @@ -740,7 +740,7 @@ void* os::realloc(void *memblock, size_t size, MemTag mem_tag, const NativeCallS #ifdef ASSERT assert(old_size == free_info.size, "Sanity"); - if (old_size < size) { + if (ZapCHeap && old_size < size) { // We also zap the newly extended region. ::memset((char*)new_inner_ptr + old_size, uninitBlockPad, size - old_size); } From 2b756ab1e8cfacc5cf5d9c6dfdf1d1c9a6ecf4b1 Mon Sep 17 00:00:00 2001 From: Saranya Natarajan Date: Mon, 18 Aug 2025 08:16:32 +0000 Subject: [PATCH 156/580] 8358781: C2 fails with assert "bad profile data type" when TypeProfileCasts is disabled Reviewed-by: mhaessig, kvn, dfenacci --- src/hotspot/share/opto/graphKit.cpp | 18 ++++--- .../compiler/arguments/TestProfileCasts.java | 49 +++++++++++++++++++ 2 files changed, 59 insertions(+), 8 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/arguments/TestProfileCasts.java diff --git a/src/hotspot/share/opto/graphKit.cpp b/src/hotspot/share/opto/graphKit.cpp index 902968ef4d4..d65239ab0f8 100644 --- a/src/hotspot/share/opto/graphKit.cpp +++ b/src/hotspot/share/opto/graphKit.cpp @@ -2309,16 +2309,18 @@ Node* GraphKit::record_profiled_receiver_for_speculation(Node* n) { if (!data->as_BitData()->null_seen()) { ptr_kind = ProfileNeverNull; } else { - assert(data->is_ReceiverTypeData(), "bad profile data type"); - ciReceiverTypeData* call = (ciReceiverTypeData*)data->as_ReceiverTypeData(); - uint i = 0; - for (; i < call->row_limit(); i++) { - ciKlass* receiver = call->receiver(i); - if (receiver != nullptr) { - break; + if (TypeProfileCasts) { + assert(data->is_ReceiverTypeData(), "bad profile data type"); + ciReceiverTypeData* call = (ciReceiverTypeData*)data->as_ReceiverTypeData(); + uint i = 0; + for (; i < call->row_limit(); i++) { + ciKlass* receiver = call->receiver(i); + if (receiver != nullptr) { + break; + } } + ptr_kind = (i == call->row_limit()) ? ProfileAlwaysNull : ProfileMaybeNull; } - ptr_kind = (i == call->row_limit()) ? ProfileAlwaysNull : ProfileMaybeNull; } } } diff --git a/test/hotspot/jtreg/compiler/arguments/TestProfileCasts.java b/test/hotspot/jtreg/compiler/arguments/TestProfileCasts.java new file mode 100644 index 00000000000..1a6bcf37919 --- /dev/null +++ b/test/hotspot/jtreg/compiler/arguments/TestProfileCasts.java @@ -0,0 +1,49 @@ +/* + * 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 8358781 + * @summary Regression test for -XX:-TypeProfileCasts crash + * @requires vm.debug + * @run main/othervm -XX:-TypeProfileCasts -XX:CompileThresholdScaling=0.01 + * compiler.arguments.TestProfileCasts + */ +package compiler.arguments; + +public class TestProfileCasts { + static class Foo { + } + + private static void test(Object o) { + if (o instanceof Foo) { + } + } + + public static void main(String[] args) { + for (int i = 0; i < 100; i++) { + test(new Foo()); + test(null); + } + } +} \ No newline at end of file From 6e91ccd1c3926094a9b6d8f9177d895aba3424a1 Mon Sep 17 00:00:00 2001 From: Pasam Soujanya Date: Mon, 18 Aug 2025 09:37:58 +0000 Subject: [PATCH 157/580] =?UTF-8?q?8365305:=20The=20ARIA=20role=20?= =?UTF-8?q?=E2=80=98contentinfo=E2=80=99=20is=20not=20valid=20for=20the=20?= =?UTF-8?q?element=20