From ea2d289141ad894521611295bf6d6999cf3b421f Mon Sep 17 00:00:00 2001 From: Weibing Xiao Date: Wed, 30 Jul 2025 15:57:54 -0400 Subject: [PATCH 001/807] 8362268 : NPE thrown from SASL GSSAPI impl on Java 11+ when TLS is used with QOP auth-int against Active Directory --- .../com/sun/security/sasl/util/AbstractSaslImpl.java | 5 ++++- .../classes/com/sun/security/sasl/gsskerb/GssKrb5Base.java | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/java.security.sasl/share/classes/com/sun/security/sasl/util/AbstractSaslImpl.java b/src/java.security.sasl/share/classes/com/sun/security/sasl/util/AbstractSaslImpl.java index 6a21d1e9be2..1ae0c5bd6c7 100644 --- a/src/java.security.sasl/share/classes/com/sun/security/sasl/util/AbstractSaslImpl.java +++ b/src/java.security.sasl/share/classes/com/sun/security/sasl/util/AbstractSaslImpl.java @@ -56,7 +56,10 @@ public abstract class AbstractSaslImpl { // These are relevant only when privacy or integray have been negotiated protected int sendMaxBufSize = 0; // specified by peer but can override - protected int recvMaxBufSize = 65536; // optionally specified by self + + // optionally specified by self + protected int recvMaxBufSize = System.getProperty("javax.security.sasl.maxbuffer") == null ? + 65536 : Integer.valueOf(System.getProperty("javax.security.sasl.maxbuffer")); protected int rawSendSize; // derived from sendMaxBufSize protected String myClassName; diff --git a/src/jdk.security.jgss/share/classes/com/sun/security/sasl/gsskerb/GssKrb5Base.java b/src/jdk.security.jgss/share/classes/com/sun/security/sasl/gsskerb/GssKrb5Base.java index afdb7279c17..1cdb5d8a9a3 100644 --- a/src/jdk.security.jgss/share/classes/com/sun/security/sasl/gsskerb/GssKrb5Base.java +++ b/src/jdk.security.jgss/share/classes/com/sun/security/sasl/gsskerb/GssKrb5Base.java @@ -134,6 +134,12 @@ abstract class GssKrb5Base extends AbstractSaslImpl { throw new IllegalStateException("No security layer negotiated"); } + // Connection::cleanup is called. Context might be set to null already + // before this method is completed. + if (secCtx == null) { + throw new SaslException("GSSContext is null"); + } + // Generate GSS token try { MessageProp msgProp = new MessageProp(JGSS_QOP, privacy); From c44693c04b691aff977e8b0bdc07919c723643b1 Mon Sep 17 00:00:00 2001 From: Weibing Xiao Date: Wed, 30 Jul 2025 16:01:57 -0400 Subject: [PATCH 002/807] Revert "8362268 : NPE thrown from SASL GSSAPI impl on Java 11+ when TLS is used with QOP auth-int against Active Directory" This reverts commit ea2d289141ad894521611295bf6d6999cf3b421f. --- .../com/sun/security/sasl/util/AbstractSaslImpl.java | 5 +---- .../classes/com/sun/security/sasl/gsskerb/GssKrb5Base.java | 6 ------ 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/java.security.sasl/share/classes/com/sun/security/sasl/util/AbstractSaslImpl.java b/src/java.security.sasl/share/classes/com/sun/security/sasl/util/AbstractSaslImpl.java index 1ae0c5bd6c7..6a21d1e9be2 100644 --- a/src/java.security.sasl/share/classes/com/sun/security/sasl/util/AbstractSaslImpl.java +++ b/src/java.security.sasl/share/classes/com/sun/security/sasl/util/AbstractSaslImpl.java @@ -56,10 +56,7 @@ public abstract class AbstractSaslImpl { // These are relevant only when privacy or integray have been negotiated protected int sendMaxBufSize = 0; // specified by peer but can override - - // optionally specified by self - protected int recvMaxBufSize = System.getProperty("javax.security.sasl.maxbuffer") == null ? - 65536 : Integer.valueOf(System.getProperty("javax.security.sasl.maxbuffer")); + protected int recvMaxBufSize = 65536; // optionally specified by self protected int rawSendSize; // derived from sendMaxBufSize protected String myClassName; diff --git a/src/jdk.security.jgss/share/classes/com/sun/security/sasl/gsskerb/GssKrb5Base.java b/src/jdk.security.jgss/share/classes/com/sun/security/sasl/gsskerb/GssKrb5Base.java index 1cdb5d8a9a3..afdb7279c17 100644 --- a/src/jdk.security.jgss/share/classes/com/sun/security/sasl/gsskerb/GssKrb5Base.java +++ b/src/jdk.security.jgss/share/classes/com/sun/security/sasl/gsskerb/GssKrb5Base.java @@ -134,12 +134,6 @@ abstract class GssKrb5Base extends AbstractSaslImpl { throw new IllegalStateException("No security layer negotiated"); } - // Connection::cleanup is called. Context might be set to null already - // before this method is completed. - if (secCtx == null) { - throw new SaslException("GSSContext is null"); - } - // Generate GSS token try { MessageProp msgProp = new MessageProp(JGSS_QOP, privacy); From 7e484e2a63e40740282b3da5d7b10e9f500bf6ab Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Thu, 7 Aug 2025 02:02:36 +0000 Subject: [PATCH 003/807] 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 004/807] 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 005/807] 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 006/807] 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 007/807] 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 008/807] 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 009/807] 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 010/807] 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 011/807] 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 012/807] 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 013/807] 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 014/807] 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 015/807] 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 016/807] 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 017/807] 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 018/807] 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 019/807] 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 020/807] 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 021/807] 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 022/807] 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 023/807] 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 024/807] 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 025/807] 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 026/807] 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 027/807] 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 028/807] 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 029/807] 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 030/807] 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 031/807] 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 032/807] 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 033/807] 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 034/807] 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 035/807] 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 036/807] 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 037/807] 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 038/807] 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 039/807] 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 040/807] 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 041/807] 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 042/807] 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 043/807] 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 044/807] 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 045/807] 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 046/807] 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 047/807] 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 048/807] 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 049/807] 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 050/807] 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 051/807] 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 052/807] 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 053/807] 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 054/807] 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 055/807] 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 056/807] 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 057/807] 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 058/807] 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 059/807] 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 060/807] 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 061/807] 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 062/807] 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 063/807] 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 064/807] 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 065/807] 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 066/807] 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 067/807] 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 068/807] 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 069/807] 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 070/807] 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 071/807] 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 072/807] 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 073/807] 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 074/807] 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 075/807] 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 076/807] 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 077/807] 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 078/807] 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 079/807] 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 080/807] 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 081/807] 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 082/807] 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 083/807] 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 084/807] 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 085/807] 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 086/807] 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 087/807] 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 088/807] 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 089/807] 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 090/807] 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 091/807] 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 092/807] 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 093/807] 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 094/807] 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 095/807] 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 096/807] 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 097/807] 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 098/807] 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 099/807] 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 100/807] 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 101/807] 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 102/807] 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 103/807] 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 104/807] 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 105/807] 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 106/807] 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 107/807] 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 108/807] 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 109/807] 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 110/807] 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 111/807] 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 112/807] 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 113/807] 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 114/807] 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 115/807] 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 116/807] 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 117/807] 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 118/807] =?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